Skip to content

Commit b4ae220

Browse files
committed
updates after c+
1 parent 076c9ed commit b4ae220

10 files changed

Lines changed: 105 additions & 78 deletions

src/ROUTES.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,11 +1857,11 @@ const ROUTES = {
18571857
},
18581858
WORKSPACE_DUPLICATE: {
18591859
route: 'workspace/:policyID/duplicate',
1860-
getRoute: (policyID: string) => `workspace/${policyID}/duplicate` as const,
1860+
getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/duplicate`, backTo),
18611861
},
18621862
WORKSPACE_DUPLICATE_SELECT_FEATURES: {
18631863
route: 'workspace/:policyID/duplicate/select-features',
1864-
getRoute: (policyID: string) => `workspace/${policyID}/duplicate/select-features` as const,
1864+
getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/duplicate/select-features`, backTo),
18651865
},
18661866
WORKSPACE_RECEIPT_PARTNERS: {
18671867
route: 'workspaces/:policyID/receipt-partners',

src/components/WorkspaceConfirmationForm.tsx

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput';
55
import useLocalize from '@hooks/useLocalize';
66
import useOnyx from '@hooks/useOnyx';
77
import useThemeStyles from '@hooks/useThemeStyles';
8+
import useWorkspaceConfirmationAvatar from '@hooks/useWorkspaceConfirmationAvatar';
89
import {generateDefaultWorkspaceName, generatePolicyID} from '@libs/actions/Policy/Policy';
910
import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types';
1011
import {addErrorMessage} from '@libs/ErrorUtils';
12+
import getFirstAlphaNumericCharacter from '@libs/getFirstAlphaNumericCharacter';
1113
import Navigation from '@libs/Navigation/Navigation';
1214
import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils';
1315
import {isRequiredFulfilled} from '@libs/ValidationUtils';
1416
import CONST from '@src/CONST';
1517
import ONYXKEYS from '@src/ONYXKEYS';
1618
import INPUT_IDS from '@src/types/form/WorkspaceConfirmationForm';
1719
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
18-
import Avatar from './Avatar';
1920
import AvatarWithImagePicker from './AvatarWithImagePicker';
2021
import CurrencyPicker from './CurrencyPicker';
2122
import FormProvider from './Form/FormProvider';
@@ -27,13 +28,6 @@ import ScrollView from './ScrollView';
2728
import Text from './Text';
2829
import TextInput from './TextInput';
2930

30-
function getFirstAlphaNumericCharacter(str = '') {
31-
return str
32-
.normalize('NFD')
33-
.replace(/[^0-9a-z]/gi, '')
34-
.toUpperCase()[0];
35-
}
36-
3731
type WorkspaceConfirmationSubmitFunctionParams = {
3832
name: string;
3933
currency: string;
@@ -96,7 +90,6 @@ function WorkspaceConfirmationForm({onSubmit, policyOwnerEmail = '', onBackButto
9690
const currencyParam = route.params && 'currency' in route.params && !!route.params.currency ? (route.params.currency as string) : undefined;
9791
const currencyValue = !!currencyList && currencyParam && currencyParam in currencyList ? currencyParam : userCurrency;
9892
const [currency] = useState(currencyValue);
99-
10093
useEffect(() => {
10194
Navigation.setParams({currency});
10295
}, [currency]);
@@ -110,22 +103,12 @@ function WorkspaceConfirmationForm({onSubmit, policyOwnerEmail = '', onBackButto
110103

111104
const stashedLocalAvatarImage = workspaceAvatar?.avatarUri ?? undefined;
112105

113-
const DefaultAvatar = useCallback(
114-
() => (
115-
<Avatar
116-
containerStyles={styles.avatarXLarge}
117-
imageStyles={[styles.avatarXLarge, styles.alignSelfCenter]}
118-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing cannot be used if left side can be empty string
119-
source={workspaceAvatar?.avatarUri || getDefaultWorkspaceAvatar(workspaceNameFirstCharacter)}
120-
fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
121-
size={CONST.AVATAR_SIZE.X_LARGE}
122-
name={workspaceNameFirstCharacter}
123-
avatarID={policyID}
124-
type={CONST.ICON_TYPE_WORKSPACE}
125-
/>
126-
),
127-
[workspaceAvatar?.avatarUri, workspaceNameFirstCharacter, styles.alignSelfCenter, styles.avatarXLarge, policyID],
128-
);
106+
const DefaultAvatar = useWorkspaceConfirmationAvatar({
107+
policyID,
108+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing cannot be used if left side can be empty string
109+
source: stashedLocalAvatarImage || getDefaultWorkspaceAvatar(workspaceNameFirstCharacter),
110+
name: workspaceNameFirstCharacter,
111+
});
129112

130113
return (
131114
<>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React, {useCallback} from 'react';
2+
import Avatar from '@components/Avatar';
3+
import * as Expensicons from '@components/Icon/Expensicons';
4+
import CONST from '@src/CONST';
5+
import useThemeStyles from './useThemeStyles';
6+
7+
function useWorkspaceConfirmationAvatar({policyID, source, name}: {policyID: string; source: string; name: string}) {
8+
const styles = useThemeStyles();
9+
10+
return useCallback(
11+
() => (
12+
<Avatar
13+
containerStyles={styles.avatarXLarge}
14+
imageStyles={[styles.avatarXLarge, styles.alignSelfCenter]}
15+
source={source}
16+
fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
17+
size={CONST.AVATAR_SIZE.X_LARGE}
18+
name={name}
19+
avatarID={policyID}
20+
type={CONST.ICON_TYPE_WORKSPACE}
21+
/>
22+
),
23+
[name, policyID, source, styles.alignSelfCenter, styles.avatarXLarge],
24+
);
25+
}
26+
27+
export default useWorkspaceConfirmationAvatar;

src/libs/PolicyUtils.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,12 +1264,6 @@ function getValidConnectedIntegration(policy: Policy | undefined, accountingInte
12641264
);
12651265
}
12661266

1267-
function getAllValidConnectedIntegration(policy: Policy | undefined, accountingIntegrations?: ConnectionName[]) {
1268-
return (accountingIntegrations ?? Object.values(CONST.POLICY.CONNECTIONS.NAME)).filter(
1269-
(integration) => !!policy?.connections?.[integration] && !isAuthenticationError(policy, integration),
1270-
);
1271-
}
1272-
12731267
function hasIntegrationAutoSync(policy: Policy | undefined, connectedIntegration?: ConnectionName) {
12741268
return (connectedIntegration && policy?.connections?.[connectedIntegration]?.config?.autoSync?.enabled) ?? false;
12751269
}
@@ -1666,7 +1660,6 @@ export {
16661660
getCountOfRequiredTagLists,
16671661
getActiveEmployeeWorkspaces,
16681662
isUserInvitedToWorkspace,
1669-
getAllValidConnectedIntegration,
16701663
getPolicyRole,
16711664
hasIndependentTags,
16721665
getLengthOfTag,

src/libs/actions/Policy/Policy.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2497,7 +2497,8 @@ function buildDuplicatePolicyData(policy: Policy, options: DuplicatePolicyDataOp
24972497
expenseCreatedReportActionID,
24982498
pendingChatMembers,
24992499
} = ReportUtils.buildOptimisticWorkspaceChats(targetPolicyID, policyName);
2500-
const policyMemberAccountIDs = Object.values(getMemberAccountIDsForWorkspace(policy?.employeeList, false, false));
2500+
const isMemberOptionSelected = parts?.people;
2501+
const policyMemberAccountIDs = isMemberOptionSelected ? Object.values(getMemberAccountIDsForWorkspace(policy?.employeeList, false, false)) : [];
25012502
const {customUnitID, customUnitRateID} = buildOptimisticDistanceRateCustomUnits(policy?.outputCurrency);
25022503

25032504
const optimisticAnnounceChat = ReportUtils.buildOptimisticAnnounceChat(targetPolicyID, [...policyMemberAccountIDs]);
@@ -2512,6 +2513,7 @@ function buildDuplicatePolicyData(policy: Policy, options: DuplicatePolicyDataOp
25122513
key: `${ONYXKEYS.COLLECTION.POLICY}${targetPolicyID}`,
25132514
value: {
25142515
...policy,
2516+
employeeList: isMemberOptionSelected ? policy.employeeList : {[policy.owner]: policy?.employeeList?.[policy.owner]},
25152517
id: targetPolicyID,
25162518
name: policyName,
25172519
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function getFirstAlphaNumericCharacter(str = '') {
2+
return str
3+
.normalize('NFD')
4+
.replace(/[^0-9a-z]/gi, '')
5+
.toUpperCase()[0];
6+
}
7+
8+
export default getFirstAlphaNumericCharacter;

src/pages/workspace/WorkspacesListPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ function WorkspacesListPage() {
236236
threeDotsMenuItems.push({
237237
icon: Expensicons.Copy,
238238
text: translate('workspace.common.duplicateWorkspace'),
239-
onSelected: () => (item.policyID ? Navigation.navigate(ROUTES.WORKSPACE_DUPLICATE.getRoute(item.policyID)) : undefined),
239+
onSelected: () => (item.policyID ? Navigation.navigate(ROUTES.WORKSPACE_DUPLICATE.getRoute(item.policyID, ROUTES.WORKSPACES_LIST.route)) : undefined),
240240
});
241241
}
242242

src/pages/workspace/duplicate/WorkspaceDuplicateForm.tsx

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React, {useCallback, useState} from 'react';
22
import {View} from 'react-native';
3-
import Avatar from '@components/Avatar';
43
import AvatarWithImagePicker from '@components/AvatarWithImagePicker';
54
import FormProvider from '@components/Form/FormProvider';
65
import InputWrapper from '@components/Form/InputWrapper';
@@ -14,9 +13,11 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput';
1413
import useLocalize from '@hooks/useLocalize';
1514
import usePolicy from '@hooks/usePolicy';
1615
import useThemeStyles from '@hooks/useThemeStyles';
16+
import useWorkspaceConfirmationAvatar from '@hooks/useWorkspaceConfirmationAvatar';
1717
import {generatePolicyID, setDuplicateWorkspaceData} from '@libs/actions/Policy/Policy';
1818
import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types';
1919
import {addErrorMessage} from '@libs/ErrorUtils';
20+
import getFirstAlphaNumericCharacter from '@libs/getFirstAlphaNumericCharacter';
2021
import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils';
2122
import {isRequiredFulfilled} from '@libs/ValidationUtils';
2223
import Navigation from '@navigation/Navigation';
@@ -25,13 +26,6 @@ import ONYXKEYS from '@src/ONYXKEYS';
2526
import ROUTES from '@src/ROUTES';
2627
import INPUT_IDS from '@src/types/form/WorkspaceDuplicateForm';
2728

28-
function getFirstAlphaNumericCharacter(str = '') {
29-
return str
30-
.normalize('NFD')
31-
.replace(/[^0-9a-z]/gi, '')
32-
.toUpperCase()[0];
33-
}
34-
3529
type WorkspaceDuplicateFormProps = {
3630
policyID?: string;
3731
};
@@ -68,7 +62,7 @@ function WorkspaceDuplicateForm({policyID}: WorkspaceDuplicateFormProps) {
6862
}
6963
const newPolicyID = generatePolicyID();
7064
setDuplicateWorkspaceData({policyID: newPolicyID, name, file: avatarFile});
71-
Navigation.navigate(ROUTES.WORKSPACE_DUPLICATE_SELECT_FEATURES.getRoute(policyID));
65+
Navigation.navigate(ROUTES.WORKSPACE_DUPLICATE_SELECT_FEATURES.getRoute(policyID, ROUTES.WORKSPACES_LIST.route));
7266
},
7367
[policyID],
7468
);
@@ -84,22 +78,12 @@ function WorkspaceDuplicateForm({policyID}: WorkspaceDuplicateFormProps) {
8478

8579
const stashedLocalAvatarImage = workspaceAvatar?.avatarUri ?? undefined;
8680

87-
const DefaultAvatar = useCallback(
88-
() => (
89-
<Avatar
90-
containerStyles={styles.avatarXLarge}
91-
imageStyles={[styles.avatarXLarge, styles.alignSelfCenter]}
92-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing cannot be used if left side can be empty string
93-
source={workspaceAvatar?.avatarUri || getDefaultWorkspaceAvatar(workspaceNameFirstCharacter)}
94-
fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
95-
size={CONST.AVATAR_SIZE.X_LARGE}
96-
name={workspaceNameFirstCharacter}
97-
avatarID={policyID}
98-
type={CONST.ICON_TYPE_WORKSPACE}
99-
/>
100-
),
101-
[workspaceAvatar?.avatarUri, workspaceNameFirstCharacter, styles.alignSelfCenter, styles.avatarXLarge, policyID],
102-
);
81+
const DefaultAvatar = useWorkspaceConfirmationAvatar({
82+
policyID,
83+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing cannot be used if left side can be empty string
84+
source: stashedLocalAvatarImage || getDefaultWorkspaceAvatar(workspaceNameFirstCharacter),
85+
name: workspaceNameFirstCharacter,
86+
});
10387

10488
return (
10589
<>

src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import useLocalize from '@hooks/useLocalize';
1010
import useOnyx from '@hooks/useOnyx';
1111
import usePolicy from '@hooks/usePolicy';
1212
import useThemeStyles from '@hooks/useThemeStyles';
13-
import {getAllValidConnectedIntegration, getDistanceRateCustomUnit, getMemberAccountIDsForWorkspace, getPerDiemCustomUnit} from '@libs/PolicyUtils';
13+
import {getDistanceRateCustomUnit, getMemberAccountIDsForWorkspace, getPerDiemCustomUnit} from '@libs/PolicyUtils';
1414
import {getReportFieldsByPolicyID} from '@libs/ReportUtils';
1515
import Navigation from '@navigation/Navigation';
1616
import {openPolicyCategoriesPage} from '@userActions/Policy/Category';
@@ -22,9 +22,10 @@ import {openPolicyReportFieldsPage} from '@userActions/Policy/ReportField';
2222
import {openPolicyTagsPage} from '@userActions/Policy/Tag';
2323
import CONST from '@src/CONST';
2424
import ONYXKEYS from '@src/ONYXKEYS';
25+
import ROUTES from '@src/ROUTES';
2526
import type {Rate} from '@src/types/onyx/Policy';
2627
import {isEmptyObject} from '@src/types/utils/EmptyObject';
27-
import {getWorkflowRules, getWorkspaceRules} from './utils';
28+
import {getAllValidConnectedIntegration, getWorkflowRules, getWorkspaceRules} from './utils';
2829

2930
type WorkspaceDuplicateFormProps = {
3031
policyID?: string;
@@ -64,7 +65,7 @@ function WorkspaceDuplicateSelectFeaturesForm({policyID}: WorkspaceDuplicateForm
6465
const [street1, street2] = (policy?.address?.addressStreet ?? '').split('\n');
6566
const formattedAddress =
6667
!isEmptyObject(policy) && !isEmptyObject(policy.address)
67-
? `${street1?.trim()}, ${street2 ? `${street2.trim()}, ` : ''}${policy.address.city}, ${policy.address.state} ${policy.address.zipCode ?? ''}`
68+
? `, ${street1?.trim()}, ${street2 ? `${street2.trim()}, ` : ''}${policy.address.city}, ${policy.address.state} ${policy.address.zipCode ?? ''}`
6869
: '';
6970

7071
const items = useMemo(() => {
@@ -150,7 +151,7 @@ function WorkspaceDuplicateSelectFeaturesForm({policyID}: WorkspaceDuplicateForm
150151
? {
151152
translation: translate('workspace.common.perDiem'),
152153
value: 'perDiem',
153-
alternateText: allRates ? `${allRates}${translate('workspace.common.perDiem').toLowerCase()}` : undefined,
154+
alternateText: allRates ? `${allRates} ${translate('workspace.common.perDiem').toLowerCase()}` : undefined,
154155
}
155156
: undefined,
156157
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
@@ -181,12 +182,15 @@ function WorkspaceDuplicateSelectFeaturesForm({policyID}: WorkspaceDuplicateForm
181182
]);
182183

183184
const listData: ListItem[] = useMemo(() => {
184-
return items.map((option) => ({
185-
text: option.translation,
186-
keyForList: option.value,
187-
isSelected: selectedItems.includes(option.value),
188-
alternateText: option.alternateText,
189-
}));
185+
return items.map((option) => {
186+
const alternateText = option?.alternateText ? option.alternateText.trim().replace(/,$/, '') : undefined;
187+
return {
188+
text: option.translation,
189+
keyForList: option.value,
190+
isSelected: selectedItems.includes(option.value),
191+
alternateText,
192+
};
193+
});
190194
}, [items, selectedItems]);
191195

192196
const fetchWorkspaceRelatedData = useCallback(() => {
@@ -203,14 +207,13 @@ function WorkspaceDuplicateSelectFeaturesForm({policyID}: WorkspaceDuplicateForm
203207
openPolicyWorkflowsPage(policyID);
204208
}, [policyID, allIds]);
205209

206-
const confirmDuplicateAndHideModal = useCallback(() => {
207-
setIsDuplicateModalOpen(false);
210+
const confirmDuplicate = useCallback(() => {
208211
if (!policy || !duplicateWorkspace?.name || !duplicateWorkspace?.policyID) {
209212
return;
210213
}
211214
duplicateWorkspaceAction(policy, {
212215
policyName: duplicateWorkspace.name,
213-
policyID,
216+
policyID: policy.id,
214217
targetPolicyID: duplicateWorkspace.policyID,
215218
welcomeNote: `${translate('workspace.duplicateWorkspace.welcomeNote')} ${duplicateWorkspace.name}`,
216219
parts: {
@@ -229,7 +232,23 @@ function WorkspaceDuplicateSelectFeaturesForm({policyID}: WorkspaceDuplicateForm
229232
file: duplicateWorkspace?.file,
230233
});
231234
Navigation.closeRHPFlow();
232-
}, [duplicateWorkspace?.file, duplicateWorkspace?.name, duplicateWorkspace?.policyID, policy, policyID, selectedItems, translate]);
235+
}, [duplicateWorkspace?.file, duplicateWorkspace?.name, duplicateWorkspace?.policyID, policy, selectedItems, translate]);
236+
237+
const confirmDuplicateAndHideModal = useCallback(() => {
238+
setIsDuplicateModalOpen(false);
239+
if (!policy || !duplicateWorkspace?.name || !duplicateWorkspace?.policyID) {
240+
return;
241+
}
242+
confirmDuplicate();
243+
}, [confirmDuplicate, duplicateWorkspace?.name, duplicateWorkspace?.policyID, policy]);
244+
245+
const onConfirmSelectList = useCallback(() => {
246+
if (!totalMembers || totalMembers < 2 || !selectedItems.includes('members')) {
247+
confirmDuplicate();
248+
return;
249+
}
250+
setIsDuplicateModalOpen(true);
251+
}, [confirmDuplicate, selectedItems, totalMembers]);
233252

234253
const updateSelectedItems = useCallback(
235254
(listItem: ListItem) => {
@@ -280,7 +299,10 @@ function WorkspaceDuplicateSelectFeaturesForm({policyID}: WorkspaceDuplicateForm
280299

281300
return (
282301
<>
283-
<HeaderWithBackButton title={translate('workspace.common.duplicateWorkspace')} />
302+
<HeaderWithBackButton
303+
onBackButtonPress={policyID ? () => Navigation.goBack(ROUTES.WORKSPACE_DUPLICATE.getRoute(policyID, ROUTES.WORKSPACES_LIST.route)) : undefined}
304+
title={translate('workspace.common.duplicateWorkspace')}
305+
/>
284306
<>
285307
<View style={[styles.ph5, styles.pv3]}>
286308
<Text style={[styles.textHeadline]}>{translate('workspace.duplicateWorkspace.selectFeatures')}</Text>
@@ -296,7 +318,7 @@ function WorkspaceDuplicateSelectFeaturesForm({policyID}: WorkspaceDuplicateForm
296318
addBottomSafeAreaPadding
297319
showConfirmButton
298320
confirmButtonText={translate('common.next')}
299-
onConfirm={() => setIsDuplicateModalOpen(true)}
321+
onConfirm={onConfirmSelectList}
300322
/>
301323
</View>
302324
</>

src/pages/workspace/duplicate/utils.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import type {LocaleContextProps} from '@components/LocaleContextProvider';
22
import {getCorrectedAutoReportingFrequency, getWorkflowApprovalsUnavailable, hasVBBA} from '@libs/PolicyUtils';
33
import {getAutoReportingFrequencyDisplayNames} from '@pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage';
44
import type {AutoReportingFrequencyKey} from '@pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage';
5+
import {isAuthenticationError} from '@userActions/connections';
56
import CONST from '@src/CONST';
67
import type {Policy} from '@src/types/onyx';
8+
import type {ConnectionName} from '@src/types/onyx/Policy';
79

810
function getWorkspaceRules(policy: Policy | undefined, translate: LocaleContextProps['translate']) {
911
const workflowApprovalsUnavailable = getWorkflowApprovalsUnavailable(policy);
@@ -68,4 +70,10 @@ function getWorkflowRules(policy: Policy | undefined, translate: LocaleContextPr
6870
return total.length > 0 ? total : null;
6971
}
7072

71-
export {getWorkspaceRules, getWorkflowRules};
73+
function getAllValidConnectedIntegration(policy: Policy | undefined, accountingIntegrations?: ConnectionName[]) {
74+
return (accountingIntegrations ?? Object.values(CONST.POLICY.CONNECTIONS.NAME)).filter(
75+
(integration) => !!policy?.connections?.[integration] && !isAuthenticationError(policy, integration),
76+
);
77+
}
78+
79+
export {getWorkspaceRules, getWorkflowRules, getAllValidConnectedIntegration};

0 commit comments

Comments
 (0)