Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add spinning loader until full list of policies is successfully download on the client #54238

Merged
merged 10 commits into from
Dec 30, 2024
2 changes: 1 addition & 1 deletion src/libs/API/parameters/LeavePolicyParams.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type LeavePolicyParams = {
policyID: string;
policyID?: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not change the type.

email: string;
};

Expand Down
2 changes: 1 addition & 1 deletion src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1093,7 +1093,7 @@ function getCurrentTaxID(policy: OnyxEntry<Policy>, taxID: string): string | und
return Object.keys(policy?.taxRates?.taxes ?? {}).find((taxIDKey) => policy?.taxRates?.taxes?.[taxIDKey].previousTaxCode === taxID || taxIDKey === taxID);
}

function getWorkspaceAccountID(policyID: string) {
function getWorkspaceAccountID(policyID: string | undefined) {
const policy = getPolicy(policyID);

if (!policy) {
Expand Down
2 changes: 1 addition & 1 deletion src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7381,7 +7381,7 @@ function getWorkspaceChats(policyID: string, accountIDs: number[], reports: Onyx
*
* @param policyID - the workspace ID to get all associated reports
*/
function getAllWorkspaceReports(policyID: string): Array<OnyxEntry<Report>> {
function getAllWorkspaceReports(policyID: string | undefined): Array<OnyxEntry<Report>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this.

return Object.values(allReports ?? {}).filter((report) => (report?.policyID ?? '-1') === policyID);
}

Expand Down
2 changes: 1 addition & 1 deletion src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ function clearWorkspaceReimbursementErrors(policyID: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {errorFields: {reimbursementChoice: null}});
}

function leaveWorkspace(policyID: string) {
function leaveWorkspace(policyID: string | undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's early return here when policyid is undefined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure!

const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
const workspaceChats = ReportUtils.getAllWorkspaceReports(policyID);

Expand Down
44 changes: 26 additions & 18 deletions src/pages/WorkspaceSwitcherPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {useIsFocused} from '@react-navigation/native';
import React, {useCallback, useMemo} from 'react';
import {useOnyx} from 'react-native-onyx';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import ScreenWrapper from '@components/ScreenWrapper';
Expand All @@ -11,6 +12,7 @@ import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import {sortWorkspacesBySelected} from '@libs/PolicyUtils';
Expand All @@ -34,6 +36,7 @@ const WorkspaceCardCreateAWorkspaceInstance = <WorkspaceCardCreateAWorkspace />;

function WorkspaceSwitcherPage() {
const {isOffline} = useNetwork();
const styles = useThemeStyles();
const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState('');
const {translate} = useLocalize();
const {activeWorkspaceID, setActiveWorkspaceID} = useActiveWorkspace();
Expand All @@ -43,9 +46,10 @@ function WorkspaceSwitcherPage() {
const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email});

const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const brickRoadsForPolicies = useMemo(() => getWorkspacesBrickRoads(reports, policies, reportActions), [reports, policies, reportActions]);
const unreadStatusesForPolicies = useMemo(() => getWorkspacesUnreadStatuses(reports), [reports]);
const shouldShowLoadingIndicator = isLoadingApp && !isOffline;

const getIndicatorTypeForPolicy = useCallback(
(policyId?: string) => {
Expand Down Expand Up @@ -157,23 +161,27 @@ function WorkspaceSwitcherPage() {
title={translate('workspace.switcher.headerTitle')}
onBackButtonPress={Navigation.goBack}
/>
<SelectionList<WorkspaceListItem>
ListItem={UserListItem}
sections={sections}
onSelectRow={(option) => selectPolicy(option.policyID)}
textInputLabel={usersWorkspaces.length >= CONST.STANDARD_LIST_ITEM_LIMIT ? translate('common.search') : undefined}
textInputValue={searchTerm}
onChangeText={setSearchTerm}
headerMessage={headerMessage}
listEmptyContent={WorkspaceCardCreateAWorkspaceInstance}
shouldShowListEmptyContent={shouldShowCreateWorkspace}
initiallyFocusedOptionKey={activeWorkspaceID ?? CONST.WORKSPACE_SWITCHER.NAME}
showLoadingPlaceholder={fetchStatus.status === 'loading' || !didScreenTransitionEnd}
showConfirmButton={!!activeWorkspaceID}
shouldUseDefaultTheme
confirmButtonText={translate('workspace.common.clearFilter')}
onConfirm={() => selectPolicy(undefined)}
/>
{shouldShowLoadingIndicator ? (
<FullScreenLoadingIndicator style={[styles.flex1, styles.pRelative]} />
) : (
<SelectionList<WorkspaceListItem>
ListItem={UserListItem}
sections={sections}
onSelectRow={(option) => selectPolicy(option.policyID)}
textInputLabel={usersWorkspaces.length >= CONST.STANDARD_LIST_ITEM_LIMIT ? translate('common.search') : undefined}
textInputValue={searchTerm}
onChangeText={setSearchTerm}
headerMessage={headerMessage}
listEmptyContent={WorkspaceCardCreateAWorkspaceInstance}
shouldShowListEmptyContent={shouldShowCreateWorkspace}
initiallyFocusedOptionKey={activeWorkspaceID ?? CONST.WORKSPACE_SWITCHER.NAME}
showLoadingPlaceholder={fetchStatus.status === 'loading' || !didScreenTransitionEnd}
showConfirmButton={!!activeWorkspaceID}
shouldUseDefaultTheme
confirmButtonText={translate('workspace.common.clearFilter')}
onConfirm={() => selectPolicy(undefined)}
/>
)}
</>
)}
</ScreenWrapper>
Expand Down
45 changes: 26 additions & 19 deletions src/pages/workspace/WorkspacesListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Button from '@components/Button';
import ConfirmModal from '@components/ConfirmModal';
import FeatureList from '@components/FeatureList';
import type {FeatureListItem} from '@components/FeatureList';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
Expand Down Expand Up @@ -113,6 +114,8 @@ function WorkspacesListPage() {
const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT);
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [session] = useOnyx(ONYXKEYS.SESSION);
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const shouldShowLoadingIndicator = isLoadingApp && !isOffline;

const {activeWorkspaceID, setActiveWorkspaceID} = useActiveWorkspace();

Expand All @@ -122,7 +125,7 @@ function WorkspacesListPage() {
const isLessThanMediumScreen = isMediumScreenWidth || shouldUseNarrowLayout;

// We need this to update translation for deleting a workspace when it has third party card feeds or expensify card assigned.
const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyIDToDelete ?? '-1');
const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyIDToDelete);
const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`);
const policyToDelete = PolicyUtils.getPolicy(policyIDToDelete);
Expand Down Expand Up @@ -180,7 +183,7 @@ function WorkspacesListPage() {
setIsSupportalActionRestrictedModalOpen(true);
return;
}
setPolicyIDToDelete(item.policyID ?? '-1');
setPolicyIDToDelete(item.policyID);
setPolicyNameToDelete(item.title);
setIsDeleteModalOpen(true);
},
Expand All @@ -192,7 +195,7 @@ function WorkspacesListPage() {
threeDotsMenuItems.push({
icon: Expensicons.Exit,
text: translate('common.leave'),
onSelected: Session.checkIfActionIsAllowed(() => Policy.leaveWorkspace(item.policyID ?? '-1')),
onSelected: Session.checkIfActionIsAllowed(() => Policy.leaveWorkspace(item.policyID)),
});
}

Expand Down Expand Up @@ -419,22 +422,26 @@ function WorkspacesListPage() {
icon={Illustrations.BigRocket}
shouldUseHeadlineHeader
/>
<ScrollView contentContainerStyle={styles.pt3}>
<View style={[styles.flex1, isLessThanMediumScreen ? styles.workspaceSectionMobile : styles.workspaceSection]}>
<FeatureList
menuItems={workspaceFeatures}
title={translate('workspace.emptyWorkspace.title')}
subtitle={translate('workspace.emptyWorkspace.subtitle')}
ctaText={translate('workspace.new.newWorkspace')}
ctaAccessibilityLabel={translate('workspace.new.newWorkspace')}
onCtaPress={() => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt())}
illustration={LottieAnimations.WorkspacePlanet}
// We use this style to vertically center the illustration, as the original illustration is not centered
illustrationStyle={styles.emptyWorkspaceIllustrationStyle}
titleStyles={styles.textHeadlineH1}
/>
</View>
</ScrollView>
{shouldShowLoadingIndicator ? (
<FullScreenLoadingIndicator style={[styles.flex1, styles.pRelative]} />
) : (
<ScrollView contentContainerStyle={styles.pt3}>
<View style={[styles.flex1, isLessThanMediumScreen ? styles.workspaceSectionMobile : styles.workspaceSection]}>
<FeatureList
menuItems={workspaceFeatures}
title={translate('workspace.emptyWorkspace.title')}
subtitle={translate('workspace.emptyWorkspace.subtitle')}
ctaText={translate('workspace.new.newWorkspace')}
ctaAccessibilityLabel={translate('workspace.new.newWorkspace')}
onCtaPress={() => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt())}
illustration={LottieAnimations.WorkspacePlanet}
// We use this style to vertically center the illustration, as the original illustration is not centered
illustrationStyle={styles.emptyWorkspaceIllustrationStyle}
titleStyles={styles.textHeadlineH1}
/>
</View>
</ScrollView>
)}
</ScreenWrapper>
);
}
Expand Down
Loading