From 3f5ac7daf1b74690517130c44dccf9a07bfc5d38 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 19 Mar 2026 20:15:26 +0800 Subject: [PATCH 1/8] create a new generateDefaultWorkspaceName that doesn't use deprecatedAllPolicies --- src/hooks/useLastWorkspaceNumber.ts | 15 +++++ .../AppNavigator/AuthScreensInitHandler.tsx | 6 +- src/libs/actions/App.ts | 3 + src/libs/actions/Policy/Policy.ts | 47 +++++++++++++++ .../workspace/WorkspaceConfirmationPage.tsx | 1 + src/selectors/Policy.ts | 30 ++++++++++ tests/actions/PolicyTest.ts | 14 ++--- tests/unit/PolicySelectorTest.ts | 59 +++++++++++++++++++ tests/unit/useLastWorkspaceNumberTest.ts | 55 +++++++++++++++++ 9 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 src/hooks/useLastWorkspaceNumber.ts create mode 100644 tests/unit/PolicySelectorTest.ts create mode 100644 tests/unit/useLastWorkspaceNumberTest.ts diff --git a/src/hooks/useLastWorkspaceNumber.ts b/src/hooks/useLastWorkspaceNumber.ts new file mode 100644 index 000000000000..9a17691bf58e --- /dev/null +++ b/src/hooks/useLastWorkspaceNumber.ts @@ -0,0 +1,15 @@ +import type {OnyxCollection} from 'react-native-onyx'; +import useOnyx from '@hooks/useOnyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {lastWorkspaceNumberSelector} from '@src/selectors/Policy'; +import {emailSelector} from '@src/selectors/Session'; +import type {Policy} from '@src/types/onyx'; + +function useLastWorkspaceNumber() { + const [sessionEmail] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector}); + const lastWorkspaceNumberSelectorWithEmail = (policies: OnyxCollection) => lastWorkspaceNumberSelector(policies, sessionEmail ?? ''); + const [lastWorkspaceNumber] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: lastWorkspaceNumberSelectorWithEmail}); + return lastWorkspaceNumber; +} + +export default useLastWorkspaceNumber; diff --git a/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx b/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx index 0ab9fe8ec596..ef7a25575013 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx @@ -1,6 +1,7 @@ import {hasSeenTourSelector} from '@selectors/Onboarding'; import {useEffect, useRef} from 'react'; import {useInitialURLActions, useInitialURLState} from '@components/InitialURLContextProvider'; +import useLastWorkspaceNumber from '@hooks/useLastWorkspaceNumber'; import useOnyx from '@hooks/useOnyx'; import {init, isClientTheLeader} from '@libs/ActiveClientManager'; import Log from '@libs/Log'; @@ -55,13 +56,14 @@ function AuthScreensInitHandler() { const [initialLastUpdateIDAppliedToClient] = useOnyx(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); - const [lastUpdateIDAppliedToClient] = useOnyx(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); const [conciergeReportID, conciergeReportIDMetadata] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); const lastUpdateIDAppliedToClientRef = useRef(lastUpdateIDAppliedToClient); const isLoadingAppRef = useRef(isLoadingApp); + const lastWorkspaceNumber = useLastWorkspaceNumber(); + lastUpdateIDAppliedToClientRef.current = lastUpdateIDAppliedToClient; isLoadingAppRef.current = isLoadingApp; @@ -133,7 +135,7 @@ function AuthScreensInitHandler() { App.reconnectApp(initialLastUpdateIDAppliedToClient); } - App.setUpPoliciesAndNavigate(session, introSelected, activePolicyID, isSelfTourViewed); + App.setUpPoliciesAndNavigate(session, introSelected, activePolicyID, isSelfTourViewed, lastWorkspaceNumber); Download.clearDownloads(); diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index de84e0220b6e..e38fcc90f336 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -569,6 +569,7 @@ type CreateWorkspaceWithPolicyDraftParams = { type?: PolicyType; // TODO: Remove optional (?) once allBetas Onyx.connect is removed (https://github.com/Expensify/App/issues/66417) betas?: OnyxEntry; + lastWorkspaceNumber: number | undefined; }; /** @@ -746,6 +747,7 @@ function setUpPoliciesAndNavigate( introSelected: OnyxEntry, activePolicyID: string | undefined, isSelfTourViewed: boolean | undefined, + lastWorkspaceNumber: number | undefined, ) { const currentUrl = getCurrentUrl(); if (!session || !currentUrl?.includes('exitTo')) { @@ -777,6 +779,7 @@ function setUpPoliciesAndNavigate( currentUserAccountIDParam: currentSessionData.accountID ?? CONST.DEFAULT_NUMBER_ID, currentUserEmailParam: currentSessionData.email ?? '', isSelfTourViewed, + lastWorkspaceNumber, }); return; } diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index b462506f03a3..db1d8dcf491d 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -2142,6 +2142,53 @@ function clearDuplicateWorkspace() { Onyx.set(ONYXKEYS.DUPLICATE_WORKSPACE, {}); } +function getDisplayNameForWorkspace(email: string) { + const emailParts = email.split('@'); + const domain = emailParts.at(1) ?? ''; + const isSMSDomain = `@${domain}` === CONST.SMS.DOMAIN; + if (isSMSDomain) { + // eslint-disable-next-line @typescript-eslint/no-deprecated + return translateLocal('workspace.new.myGroupWorkspace', {}); + } + + if (!PUBLIC_DOMAINS_SET.has(domain.toLowerCase())) { + return Str.UCFirst(domain.split('.').at(0) ?? ''); + } + + const userDetails = PersonalDetailsUtils.getPersonalDetailByEmail(email); + const displayName = userDetails?.displayName?.trim(); + if (displayName) { + return Str.UCFirst(displayName); + } + + const username = emailParts.at(0) ?? ''; + return Str.UCFirst(username); +} + +/** + * Generate a policy name based on an email and policy list. + * @param [email] the email to base the workspace name on. If not passed, will use the logged-in user's email instead + * @param [lastWorkspaceNumber] the last workspace number + */ +function newGenerateDefaultWorkspaceName(email = '', lastWorkspaceNumber: number | undefined): string { + const emailParts = email ? email.split('@') : deprecatedSessionEmail.split('@'); + if (!emailParts || emailParts.length !== 2) { + return ''; + } + const domain = emailParts.at(1) ?? ''; + const isSMSDomain = `@${domain}` === CONST.SMS.DOMAIN; + + if (isSMSDomain) { + // eslint-disable-next-line @typescript-eslint/no-deprecated + return translateLocal('workspace.new.myGroupWorkspace', {workspaceNumber: lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined}); + } + + const displayNameForWorkspace = getDisplayNameForWorkspace(email || deprecatedSessionEmail); + + // eslint-disable-next-line @typescript-eslint/no-deprecated + return translateLocal('workspace.new.workspaceName', displayNameForWorkspace, lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined); +} + /** * Generate a policy name based on an email and policy list. * @param [email] the email to base the workspace name on. If not passed, will use the logged-in user's email instead diff --git a/src/pages/workspace/WorkspaceConfirmationPage.tsx b/src/pages/workspace/WorkspaceConfirmationPage.tsx index 5870f8439172..b7f62ae04b6d 100644 --- a/src/pages/workspace/WorkspaceConfirmationPage.tsx +++ b/src/pages/workspace/WorkspaceConfirmationPage.tsx @@ -39,6 +39,7 @@ function WorkspaceConfirmationPage() { makeMeAdmin: params.makeMeAdmin, backTo: '', policyID, + lastWorkspaceNumber: undefined, currency: params.currency, file: params.avatarFile as File, routeToNavigateAfterCreate: routeToNavigate, diff --git a/src/selectors/Policy.ts b/src/selectors/Policy.ts index e5a606c68860..1c409d399d27 100644 --- a/src/selectors/Policy.ts +++ b/src/selectors/Policy.ts @@ -1,4 +1,7 @@ +import escapeRegExp from 'lodash/escapeRegExp'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import {getDisplayNameForWorkspace} from '@libs/actions/Policy/Policy'; +import {translate} from '@libs/Localize'; import {areAllGroupPoliciesExpenseChatDisabled, getActiveAdminWorkspaces, getOwnedPaidPolicies, isPaidGroupPolicy, shouldShowPolicy} from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import type {Policy, PolicyReportField} from '@src/types/onyx'; @@ -132,6 +135,32 @@ const hasPoliciesConnectedToSageIntacctSelector = (policies: OnyxCollection) => !!adminPoliciesConnectedToNetSuiteSelector(policies).length; +function lastWorkspaceNumberSelector(policies: OnyxCollection = {}, email: string): number | undefined { + const emailParts = email.split('@'); + if (emailParts.length !== 2) { + return undefined; + } + + const displayNameForWorkspace = getDisplayNameForWorkspace(email); + // find default named workspaces and increment the last number + const escapedName = escapeRegExp(displayNameForWorkspace); + const workspaceTranslations = Object.values(CONST.LOCALES) + .map((lang) => translate(lang, 'workspace.common.workspace')) + .join('|'); + + const domain = emailParts.at(1) ?? ''; + const isSMSDomain = `@${domain}` === CONST.SMS.DOMAIN; + const workspaceRegex = isSMSDomain ? new RegExp(`^${escapedName}\\s*(\\d+)?$`, 'i') : new RegExp(`^(?=.*${escapedName})(?:.*(?:${workspaceTranslations})\\s*(\\d+)?)`, 'i'); + + const workspaceNumbers = Object.values(policies) + .map((policy) => workspaceRegex.exec(policy?.name ?? '')) + .filter(Boolean) // Remove null matches + .map((match) => Number(match?.[1] ?? '0')); + const lastWorkspaceNumber = workspaceNumbers.length > 0 ? Math.max(...workspaceNumbers) : undefined; + + return lastWorkspaceNumber; +} + export { activePolicySelector, createAllPolicyReportFieldsSelector, @@ -147,4 +176,5 @@ export { adminPoliciesConnectedToNetSuiteSelector, hasPoliciesConnectedToSageIntacctSelector, hasPoliciesConnectedToNetSuiteSelector, + lastWorkspaceNumberSelector, }; diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 42b508d18425..b1dbe0e03886 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -1728,7 +1728,7 @@ describe('actions/Policy', () => { await waitForBatchedUpdates(); const policyID = Policy.generatePolicyID(); - const expectedName = Policy.generateDefaultWorkspaceName(ESH_EMAIL); + const expectedName = Policy.newGenerateDefaultWorkspaceName(ESH_EMAIL, undefined); Policy.createDraftInitialWorkspace({choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, ESH_EMAIL, '', policyID, false); await waitForBatchedUpdates(); @@ -2943,7 +2943,7 @@ describe('actions/Policy', () => { accountID: TEST_ACCOUNT_ID, }); - const workspaceName = Policy.generateDefaultWorkspaceName(TEST_NON_PUBLIC_DOMAIN_EMAIL); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_NON_PUBLIC_DOMAIN_EMAIL, undefined); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', displayNameForWorkspace)); }); @@ -2956,7 +2956,7 @@ describe('actions/Policy', () => { accountID: TEST_ACCOUNT_ID, }); - const workspaceName = Policy.generateDefaultWorkspaceName(TEST_EMAIL); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, undefined); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', displayNameForWorkspace)); }); @@ -2971,7 +2971,7 @@ describe('actions/Policy', () => { accountID: TEST_ACCOUNT_ID, }); - const workspaceName = Policy.generateDefaultWorkspaceName(TEST_EMAIL_2); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL_2, undefined); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', displayNameForWorkspace)); }); @@ -2989,7 +2989,7 @@ describe('actions/Policy', () => { await Onyx.set(ONYXKEYS.COLLECTION.POLICY, existingPolicies); - const workspaceName = Policy.generateDefaultWorkspaceName(TEST_EMAIL); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, undefined); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', TEST_DISPLAY_NAME, 2)); }); @@ -3000,7 +3000,7 @@ describe('actions/Policy', () => { accountID: TEST_ACCOUNT_ID, }); - const workspaceName = Policy.generateDefaultWorkspaceName(TEST_SMS_DOMAIN_EMAIL); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_SMS_DOMAIN_EMAIL, undefined); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.myGroupWorkspace', {})); }); @@ -3022,7 +3022,7 @@ describe('actions/Policy', () => { await Onyx.set(ONYXKEYS.COLLECTION.POLICY, existingPolicies); - const workspaceName = Policy.generateDefaultWorkspaceName(TEST_EMAIL); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, undefined); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', TEST_DISPLAY_NAME, 2)); }); }); diff --git a/tests/unit/PolicySelectorTest.ts b/tests/unit/PolicySelectorTest.ts new file mode 100644 index 000000000000..9c8a713532fc --- /dev/null +++ b/tests/unit/PolicySelectorTest.ts @@ -0,0 +1,59 @@ +import CONST from '@src/CONST'; +import {lastWorkspaceNumberSelector} from '@src/selectors/Policy'; +import type {Policy} from '@src/types/onyx'; + +describe('lastWorkspaceNumberSelector', () => { + const email = 'jdoe@expensify.com'; + const displayName = 'Expensify'; + const workspaceName = `${displayName} Workspace`; + + it('should return undefined when there are no policies', () => { + expect(lastWorkspaceNumberSelector({}, email)).toBeUndefined(); + }); + + it('should return undefined when email is invalid', () => { + expect(lastWorkspaceNumberSelector({}, 'invalid-email')).toBeUndefined(); + }); + + it('should return 0 when there is a matching workspace without a number', () => { + const policies = { + policy_1: {name: workspaceName} as Policy, + }; + expect(lastWorkspaceNumberSelector(policies, email)).toBe(0); + }); + + it('should return the number when there is a matching workspace with a number', () => { + const policies = { + policy_1: {name: `${workspaceName} 2`} as Policy, + }; + expect(lastWorkspaceNumberSelector(policies, email)).toBe(2); + }); + + it('should return the maximum number when there are multiple matching workspaces', () => { + const policies = { + policy_1: {name: workspaceName} as Policy, + policy_2: {name: `${workspaceName} 2`} as Policy, + policy_3: {name: `${workspaceName} 5`} as Policy, + policy_4: {name: 'Other Workspace'} as Policy, + }; + expect(lastWorkspaceNumberSelector(policies, email)).toBe(5); + }); + + it('should handle SMS domain correctly', () => { + const smsEmail = `+15555555555${CONST.SMS.DOMAIN}`; + const smsDisplayName = 'My Group Workspace'; + const policies = { + policy_1: {name: smsDisplayName} as Policy, + policy_2: {name: `${smsDisplayName} 3`} as Policy, + }; + expect(lastWorkspaceNumberSelector(policies, smsEmail)).toBe(3); + }); + + it('should ignore case when matching workspace names', () => { + const policies = { + policy_1: {name: workspaceName.toLowerCase()} as Policy, + policy_2: {name: `${workspaceName.toUpperCase()} 4`} as Policy, + }; + expect(lastWorkspaceNumberSelector(policies, email)).toBe(4); + }); +}); diff --git a/tests/unit/useLastWorkspaceNumberTest.ts b/tests/unit/useLastWorkspaceNumberTest.ts new file mode 100644 index 000000000000..8d1578dc2c84 --- /dev/null +++ b/tests/unit/useLastWorkspaceNumberTest.ts @@ -0,0 +1,55 @@ +import {renderHook} from '@testing-library/react-native'; +import Onyx from 'react-native-onyx'; +import useLastWorkspaceNumber from '@hooks/useLastWorkspaceNumber'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Policy} from '@src/types/onyx'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + +describe('useLastWorkspaceNumber', () => { + const email = 'jdoe@expensify.com'; + const displayName = 'Expensify'; + const workspaceName = `${displayName} Workspace`; + + beforeAll(() => { + Onyx.init({keys: ONYXKEYS}); + }); + + beforeEach(() => { + Onyx.clear(); + return waitForBatchedUpdates(); + }); + + it('should return the correct last workspace number from Onyx', async () => { + await Onyx.merge(ONYXKEYS.SESSION, {email}); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}1`, {name: `${workspaceName} 3`} as Policy); + await waitForBatchedUpdates(); + + const {result} = renderHook(() => useLastWorkspaceNumber()); + + expect(result.current).toBe(3); + }); + + it('should update when Onyx data changes', async () => { + await Onyx.merge(ONYXKEYS.SESSION, {email}); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}1`, {name: `${workspaceName} 3`} as Policy); + await waitForBatchedUpdates(); + + const {result} = renderHook(() => useLastWorkspaceNumber()); + expect(result.current).toBe(3); + + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}2`, {name: `${workspaceName} 5`} as Policy); + await waitForBatchedUpdates(); + + expect(result.current).toBe(5); + }); + + it('should return undefined if no matching workspaces exist', async () => { + await Onyx.merge(ONYXKEYS.SESSION, {email}); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}1`, {name: 'Other Workspace'} as Policy); + await waitForBatchedUpdates(); + + const {result} = renderHook(() => useLastWorkspaceNumber()); + + expect(result.current).toBeUndefined(); + }); +}); From 80a6e2079ae57531fb897337aaaea306d35b179b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 20 Mar 2026 11:00:10 +0800 Subject: [PATCH 2/8] use the new generateDefaultWorkspaceName --- src/components/WorkspaceConfirmationForm.tsx | 7 +++++-- src/libs/actions/Policy/Policy.ts | 4 +++- .../BaseOnboardingWorkspaceConfirmation.tsx | 7 +++++-- .../BaseOnboardingWorkspaceOptional.tsx | 7 +++++-- src/selectors/Policy.ts | 4 ++-- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/components/WorkspaceConfirmationForm.tsx b/src/components/WorkspaceConfirmationForm.tsx index 10d5194797b1..604c4cb4aaa8 100644 --- a/src/components/WorkspaceConfirmationForm.tsx +++ b/src/components/WorkspaceConfirmationForm.tsx @@ -10,7 +10,7 @@ import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import useWorkspaceConfirmationAvatar from '@hooks/useWorkspaceConfirmationAvatar'; import {clearDraftValues} from '@libs/actions/FormActions'; -import {generateDefaultWorkspaceName, generatePolicyID} from '@libs/actions/Policy/Policy'; +import {newGenerateDefaultWorkspaceName, generatePolicyID} from '@libs/actions/Policy/Policy'; import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types'; import {addErrorMessage} from '@libs/ErrorUtils'; import getFirstAlphaNumericCharacter from '@libs/getFirstAlphaNumericCharacter'; @@ -35,6 +35,7 @@ import ScrollView from './ScrollView'; import Switch from './Switch'; import Text from './Text'; import TextInput from './TextInput'; +import { lastWorkspaceNumberSelector } from '@src/selectors/Policy'; type PolicyType = typeof CONST.POLICY.TYPE.TEAM | typeof CONST.POLICY.TYPE.CORPORATE; @@ -114,7 +115,9 @@ function WorkspaceConfirmationForm({onSubmit, policyOwnerEmail = '', onBackButto const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [draftValues] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_CONFIRMATION_FORM_DRAFT); - const defaultWorkspaceName = generateDefaultWorkspaceName(policyOwnerEmail || session?.email); + const email = policyOwnerEmail || session?.email || ''; + const lastWorkspaceNumber = lastWorkspaceNumberSelector(policies, email); + const defaultWorkspaceName = newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber); const [workspaceNameFirstCharacter, setWorkspaceNameFirstCharacter] = useState(defaultWorkspaceName ?? ''); const userCurrency = draftValues?.currency ?? currentUserPersonalDetails?.localCurrencyCode ?? CONST.CURRENCY.USD; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 412c33d5abc8..eb41ddb19dc6 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -2163,7 +2163,7 @@ function getDisplayNameForWorkspace(email: string) { * @param [email] the email to base the workspace name on. If not passed, will use the logged-in user's email instead * @param [lastWorkspaceNumber] the last workspace number */ -function newGenerateDefaultWorkspaceName(email = '', lastWorkspaceNumber: number | undefined): string { +function newGenerateDefaultWorkspaceName(email: string, lastWorkspaceNumber: number | undefined): string { const emailParts = email ? email.split('@') : deprecatedSessionEmail.split('@'); if (!emailParts || emailParts.length !== 2) { return ''; @@ -7128,6 +7128,8 @@ export { updateLastAccessedWorkspace, clearDeleteWorkspaceError, setWorkspaceDefaultSpendCategory, + getDisplayNameForWorkspace, + newGenerateDefaultWorkspaceName, generateDefaultWorkspaceName, updateGeneralSettings, deleteWorkspaceAvatar, diff --git a/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx b/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx index 6c7f875602b6..afbae8fc86d1 100644 --- a/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx +++ b/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx @@ -23,7 +23,7 @@ import {isPaidGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils'; import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan'; import {isRequiredFulfilled} from '@libs/ValidationUtils'; import {clearWorkspaceDetailsDraft} from '@userActions/Onboarding'; -import {createWorkspace, generateDefaultWorkspaceName, generatePolicyID} from '@userActions/Policy/Policy'; +import {createWorkspace, generatePolicyID, newGenerateDefaultWorkspaceName} from '@userActions/Policy/Policy'; import {setOnboardingAdminsChatReportID, setOnboardingErrorMessage, setOnboardingPolicyID} from '@userActions/Welcome'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -33,6 +33,7 @@ import INPUT_IDS from '@src/types/form/WorkspaceConfirmationForm'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import OnboardingCurrencyPicker from './OnboardingCurrencyPicker'; import type {BaseOnboardingWorkspaceConfirmationProps} from './types'; +import { lastWorkspaceNumberSelector } from '@src/selectors/Policy'; function BaseOnboardingWorkspaceConfirmation({shouldUseNativeStyles}: BaseOnboardingWorkspaceConfirmationProps) { const styles = useThemeStyles(); @@ -57,7 +58,9 @@ function BaseOnboardingWorkspaceConfirmation({shouldUseNativeStyles}: BaseOnboar const paidGroupPolicy = Object.values(allPolicies ?? {}).find((policy) => isPaidGroupPolicy(policy) && isPolicyAdmin(policy, session?.email)); - const defaultWorkspaceName = draftValues?.name ?? generateDefaultWorkspaceName(session?.email); + const email = session?.email ?? ''; + const lastWorkspaceNumber = lastWorkspaceNumberSelector(allPolicies, email); + const defaultWorkspaceName = draftValues?.name ?? newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber); const defaultCurrency = draftValues?.currency ?? currentUserPersonalDetails?.localCurrencyCode ?? CONST.CURRENCY.USD; useEffect(() => { diff --git a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx index a9bc092db922..51f2d336b18a 100644 --- a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx +++ b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx @@ -26,7 +26,7 @@ import {navigateAfterOnboardingWithMicrotaskQueue} from '@libs/navigateAfterOnbo import Navigation from '@libs/Navigation/Navigation'; import {isPaidGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils'; import {getSubscriptionPrice} from '@libs/SubscriptionUtils'; -import {createWorkspace, generateDefaultWorkspaceName, generatePolicyID} from '@userActions/Policy/Policy'; +import {createWorkspace, generatePolicyID, newGenerateDefaultWorkspaceName} from '@userActions/Policy/Policy'; import {completeOnboarding as completeOnboardingReport} from '@userActions/Report'; import {setOnboardingAdminsChatReportID, setOnboardingErrorMessage, setOnboardingPolicyID} from '@userActions/Welcome'; import CONST from '@src/CONST'; @@ -37,6 +37,7 @@ import SCREENS from '@src/SCREENS'; import type {OnboardingPurpose} from '@src/types/onyx'; import type IconAsset from '@src/types/utils/IconAsset'; import type {BaseOnboardingWorkspaceOptionalProps} from './types'; +import { lastWorkspaceNumberSelector } from '@src/selectors/Policy'; type Item = { icon: IconAsset; @@ -158,11 +159,13 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding const paidGroupPolicy = Object.values(allPolicies ?? {}).find((policy) => isPaidGroupPolicy(policy) && isPolicyAdmin(policy, session?.email)); const shouldCreateWorkspace = !onboardingPolicyID && !paidGroupPolicy; + const email = session?.email ?? ''; + const lastWorkspaceNumber = lastWorkspaceNumberSelector(allPolicies, email); const {adminsChatReportID, policyID} = shouldCreateWorkspace ? createWorkspace({ policyOwnerEmail: undefined, makeMeAdmin: true, - policyName: generateDefaultWorkspaceName(session?.email), + policyName: newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber), policyID: generatePolicyID(), engagementChoice: CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE, currency: currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD, diff --git a/src/selectors/Policy.ts b/src/selectors/Policy.ts index 5ef00a59b49d..8fd11ff22753 100644 --- a/src/selectors/Policy.ts +++ b/src/selectors/Policy.ts @@ -137,7 +137,7 @@ const hasPoliciesConnectedToSageIntacctSelector = (policies: OnyxCollection) => !!adminPoliciesConnectedToNetSuiteSelector(policies).length; -function lastWorkspaceNumberSelector(policies: OnyxCollection = {}, email: string): number | undefined { +function lastWorkspaceNumberSelector(policies: OnyxCollection, email: string): number | undefined { const emailParts = email.split('@'); if (emailParts.length !== 2) { return undefined; @@ -154,7 +154,7 @@ function lastWorkspaceNumberSelector(policies: OnyxCollection = {}, emai const isSMSDomain = `@${domain}` === CONST.SMS.DOMAIN; const workspaceRegex = isSMSDomain ? new RegExp(`^${escapedName}\\s*(\\d+)?$`, 'i') : new RegExp(`^(?=.*${escapedName})(?:.*(?:${workspaceTranslations})\\s*(\\d+)?)`, 'i'); - const workspaceNumbers = Object.values(policies) + const workspaceNumbers = Object.values(policies ?? {}) .map((policy) => workspaceRegex.exec(policy?.name ?? '')) .filter(Boolean) // Remove null matches .map((match) => Number(match?.[1] ?? '0')); From 8d2a52490f04da939e220a1ac10d3a4921f23b47 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 20 Mar 2026 11:00:15 +0800 Subject: [PATCH 3/8] int --- tests/unit/PolicySelectorTest.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/unit/PolicySelectorTest.ts b/tests/unit/PolicySelectorTest.ts index 9c8a713532fc..f3c73d8058fb 100644 --- a/tests/unit/PolicySelectorTest.ts +++ b/tests/unit/PolicySelectorTest.ts @@ -1,4 +1,5 @@ import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import {lastWorkspaceNumberSelector} from '@src/selectors/Policy'; import type {Policy} from '@src/types/onyx'; @@ -17,24 +18,24 @@ describe('lastWorkspaceNumberSelector', () => { it('should return 0 when there is a matching workspace without a number', () => { const policies = { - policy_1: {name: workspaceName} as Policy, + [`${ONYXKEYS.COLLECTION.POLICY}1`]: {name: workspaceName} as Policy, }; expect(lastWorkspaceNumberSelector(policies, email)).toBe(0); }); it('should return the number when there is a matching workspace with a number', () => { const policies = { - policy_1: {name: `${workspaceName} 2`} as Policy, + [`${ONYXKEYS.COLLECTION.POLICY}1`]: {name: `${workspaceName} 2`} as Policy, }; expect(lastWorkspaceNumberSelector(policies, email)).toBe(2); }); it('should return the maximum number when there are multiple matching workspaces', () => { const policies = { - policy_1: {name: workspaceName} as Policy, - policy_2: {name: `${workspaceName} 2`} as Policy, - policy_3: {name: `${workspaceName} 5`} as Policy, - policy_4: {name: 'Other Workspace'} as Policy, + [`${ONYXKEYS.COLLECTION.POLICY}1`]: {name: workspaceName} as Policy, + [`${ONYXKEYS.COLLECTION.POLICY}2`]: {name: `${workspaceName} 2`} as Policy, + [`${ONYXKEYS.COLLECTION.POLICY}3`]: {name: `${workspaceName} 5`} as Policy, + [`${ONYXKEYS.COLLECTION.POLICY}4`]: {name: 'Other Workspace'} as Policy, }; expect(lastWorkspaceNumberSelector(policies, email)).toBe(5); }); @@ -43,16 +44,16 @@ describe('lastWorkspaceNumberSelector', () => { const smsEmail = `+15555555555${CONST.SMS.DOMAIN}`; const smsDisplayName = 'My Group Workspace'; const policies = { - policy_1: {name: smsDisplayName} as Policy, - policy_2: {name: `${smsDisplayName} 3`} as Policy, + [`${ONYXKEYS.COLLECTION.POLICY}1`]: {name: smsDisplayName} as Policy, + [`${ONYXKEYS.COLLECTION.POLICY}2`]: {name: `${smsDisplayName} 3`} as Policy, }; expect(lastWorkspaceNumberSelector(policies, smsEmail)).toBe(3); }); it('should ignore case when matching workspace names', () => { const policies = { - policy_1: {name: workspaceName.toLowerCase()} as Policy, - policy_2: {name: `${workspaceName.toUpperCase()} 4`} as Policy, + [`${ONYXKEYS.COLLECTION.POLICY}1`]: {name: workspaceName.toLowerCase()} as Policy, + [`${ONYXKEYS.COLLECTION.POLICY}2`]: {name: `${workspaceName.toUpperCase()} 4`} as Policy, }; expect(lastWorkspaceNumberSelector(policies, email)).toBe(4); }); From e14eb1458d50a798681f15632ad36ead503a03bb Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 20 Mar 2026 11:05:00 +0800 Subject: [PATCH 4/8] remove unused param (yet) --- src/components/WorkspaceConfirmationForm.tsx | 4 ++-- src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx | 5 +---- src/libs/actions/App.ts | 3 --- .../BaseOnboardingWorkspaceConfirmation.tsx | 2 +- .../BaseOnboardingWorkspaceOptional.tsx | 2 +- src/pages/workspace/WorkspaceConfirmationPage.tsx | 1 - 6 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/components/WorkspaceConfirmationForm.tsx b/src/components/WorkspaceConfirmationForm.tsx index 604c4cb4aaa8..0c67f647f73c 100644 --- a/src/components/WorkspaceConfirmationForm.tsx +++ b/src/components/WorkspaceConfirmationForm.tsx @@ -10,7 +10,7 @@ import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import useWorkspaceConfirmationAvatar from '@hooks/useWorkspaceConfirmationAvatar'; import {clearDraftValues} from '@libs/actions/FormActions'; -import {newGenerateDefaultWorkspaceName, generatePolicyID} from '@libs/actions/Policy/Policy'; +import {generatePolicyID, newGenerateDefaultWorkspaceName} from '@libs/actions/Policy/Policy'; import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types'; import {addErrorMessage} from '@libs/ErrorUtils'; import getFirstAlphaNumericCharacter from '@libs/getFirstAlphaNumericCharacter'; @@ -21,6 +21,7 @@ import {isRequiredFulfilled} from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES'; +import {lastWorkspaceNumberSelector} from '@src/selectors/Policy'; import INPUT_IDS from '@src/types/form/WorkspaceConfirmationForm'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import AvatarWithImagePicker from './AvatarWithImagePicker'; @@ -35,7 +36,6 @@ import ScrollView from './ScrollView'; import Switch from './Switch'; import Text from './Text'; import TextInput from './TextInput'; -import { lastWorkspaceNumberSelector } from '@src/selectors/Policy'; type PolicyType = typeof CONST.POLICY.TYPE.TEAM | typeof CONST.POLICY.TYPE.CORPORATE; diff --git a/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx b/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx index 36dcc282d6f0..887f4861c425 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx @@ -2,7 +2,6 @@ import {hasSeenTourSelector} from '@selectors/Onboarding'; import {useEffect, useRef} from 'react'; import {useInitialURLActions, useInitialURLState} from '@components/InitialURLContextProvider'; import useHasActiveAdminPolicies from '@hooks/useHasActiveAdminPolicies'; -import useLastWorkspaceNumber from '@hooks/useLastWorkspaceNumber'; import useOnyx from '@hooks/useOnyx'; import {init, isClientTheLeader} from '@libs/ActiveClientManager'; import Log from '@libs/Log'; @@ -64,8 +63,6 @@ function AuthScreensInitHandler() { const lastUpdateIDAppliedToClientRef = useRef(lastUpdateIDAppliedToClient); const isLoadingAppRef = useRef(isLoadingApp); - const lastWorkspaceNumber = useLastWorkspaceNumber(); - lastUpdateIDAppliedToClientRef.current = lastUpdateIDAppliedToClient; isLoadingAppRef.current = isLoadingApp; @@ -137,7 +134,7 @@ function AuthScreensInitHandler() { App.reconnectApp(initialLastUpdateIDAppliedToClient); } - App.setUpPoliciesAndNavigate(session, introSelected, activePolicyID, isSelfTourViewed, hasActiveAdminPolicies, lastWorkspaceNumber); + App.setUpPoliciesAndNavigate(session, introSelected, activePolicyID, isSelfTourViewed, hasActiveAdminPolicies); Download.clearDownloads(); diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index 7da2cc6fdc19..0714e6464d2d 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -570,7 +570,6 @@ type CreateWorkspaceWithPolicyDraftParams = { // TODO: Remove optional (?) once allBetas Onyx.connect is removed (https://github.com/Expensify/App/issues/66417) betas?: OnyxEntry; hasActiveAdminPolicies: boolean; - lastWorkspaceNumber: number | undefined; }; /** @@ -756,7 +755,6 @@ function setUpPoliciesAndNavigate( activePolicyID: string | undefined, isSelfTourViewed: boolean | undefined, hasActiveAdminPolicies: boolean, - lastWorkspaceNumber: number | undefined, ) { const currentUrl = getCurrentUrl(); if (!session || !currentUrl?.includes('exitTo')) { @@ -789,7 +787,6 @@ function setUpPoliciesAndNavigate( currentUserEmailParam: currentSessionData.email ?? '', isSelfTourViewed, hasActiveAdminPolicies, - lastWorkspaceNumber, }); return; } diff --git a/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx b/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx index afbae8fc86d1..542085a100d8 100644 --- a/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx +++ b/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx @@ -29,11 +29,11 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; +import {lastWorkspaceNumberSelector} from '@src/selectors/Policy'; import INPUT_IDS from '@src/types/form/WorkspaceConfirmationForm'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import OnboardingCurrencyPicker from './OnboardingCurrencyPicker'; import type {BaseOnboardingWorkspaceConfirmationProps} from './types'; -import { lastWorkspaceNumberSelector } from '@src/selectors/Policy'; function BaseOnboardingWorkspaceConfirmation({shouldUseNativeStyles}: BaseOnboardingWorkspaceConfirmationProps) { const styles = useThemeStyles(); diff --git a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx index 51f2d336b18a..cf415d09a890 100644 --- a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx +++ b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx @@ -34,10 +34,10 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; +import {lastWorkspaceNumberSelector} from '@src/selectors/Policy'; import type {OnboardingPurpose} from '@src/types/onyx'; import type IconAsset from '@src/types/utils/IconAsset'; import type {BaseOnboardingWorkspaceOptionalProps} from './types'; -import { lastWorkspaceNumberSelector } from '@src/selectors/Policy'; type Item = { icon: IconAsset; diff --git a/src/pages/workspace/WorkspaceConfirmationPage.tsx b/src/pages/workspace/WorkspaceConfirmationPage.tsx index 3548e804f86a..d113d9a961bd 100644 --- a/src/pages/workspace/WorkspaceConfirmationPage.tsx +++ b/src/pages/workspace/WorkspaceConfirmationPage.tsx @@ -42,7 +42,6 @@ function WorkspaceConfirmationPage() { makeMeAdmin: params.makeMeAdmin, backTo: '', policyID, - lastWorkspaceNumber: undefined, currency: params.currency, file: params.avatarFile as File, routeToNavigateAfterCreate: routeToNavigate, From e0d8ed19551c8992c9a8fcced9e64862541c387b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 21 Mar 2026 15:10:55 +0800 Subject: [PATCH 5/8] lint --- src/components/WorkspaceConfirmationForm.tsx | 2 +- src/hooks/useLastWorkspaceNumber.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/WorkspaceConfirmationForm.tsx b/src/components/WorkspaceConfirmationForm.tsx index 0c67f647f73c..1a2a2f1260a8 100644 --- a/src/components/WorkspaceConfirmationForm.tsx +++ b/src/components/WorkspaceConfirmationForm.tsx @@ -115,7 +115,7 @@ function WorkspaceConfirmationForm({onSubmit, policyOwnerEmail = '', onBackButto const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [draftValues] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_CONFIRMATION_FORM_DRAFT); - const email = policyOwnerEmail || session?.email || ''; + const email = policyOwnerEmail || (session?.email ?? ''); const lastWorkspaceNumber = lastWorkspaceNumberSelector(policies, email); const defaultWorkspaceName = newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber); const [workspaceNameFirstCharacter, setWorkspaceNameFirstCharacter] = useState(defaultWorkspaceName ?? ''); diff --git a/src/hooks/useLastWorkspaceNumber.ts b/src/hooks/useLastWorkspaceNumber.ts index 9a17691bf58e..30f0785cb3cc 100644 --- a/src/hooks/useLastWorkspaceNumber.ts +++ b/src/hooks/useLastWorkspaceNumber.ts @@ -1,9 +1,9 @@ import type {OnyxCollection} from 'react-native-onyx'; -import useOnyx from '@hooks/useOnyx'; import ONYXKEYS from '@src/ONYXKEYS'; import {lastWorkspaceNumberSelector} from '@src/selectors/Policy'; import {emailSelector} from '@src/selectors/Session'; import type {Policy} from '@src/types/onyx'; +import useOnyx from './useOnyx'; function useLastWorkspaceNumber() { const [sessionEmail] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector}); From c4002b60580e3734673270c14e33c661ce1cfcb9 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 21 Mar 2026 15:11:00 +0800 Subject: [PATCH 6/8] fix test --- tests/actions/PolicyTest.ts | 4 ++-- tests/unit/PolicySelectorTest.ts | 3 +++ tests/unit/useLastWorkspaceNumberTest.ts | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 61a9fe15e6e5..2e676a5a18cf 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -3047,7 +3047,7 @@ describe('actions/Policy', () => { await Onyx.set(ONYXKEYS.COLLECTION.POLICY, existingPolicies); - const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, undefined); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, 1); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', TEST_DISPLAY_NAME, 2)); }); @@ -3080,7 +3080,7 @@ describe('actions/Policy', () => { await Onyx.set(ONYXKEYS.COLLECTION.POLICY, existingPolicies); - const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, undefined); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, 1); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', TEST_DISPLAY_NAME, 2)); }); }); diff --git a/tests/unit/PolicySelectorTest.ts b/tests/unit/PolicySelectorTest.ts index f3c73d8058fb..8fd534e5cf77 100644 --- a/tests/unit/PolicySelectorTest.ts +++ b/tests/unit/PolicySelectorTest.ts @@ -1,4 +1,5 @@ import CONST from '@src/CONST'; +import IntlStore from '@src/languages/IntlStore'; import ONYXKEYS from '@src/ONYXKEYS'; import {lastWorkspaceNumberSelector} from '@src/selectors/Policy'; import type {Policy} from '@src/types/onyx'; @@ -8,6 +9,8 @@ describe('lastWorkspaceNumberSelector', () => { const displayName = 'Expensify'; const workspaceName = `${displayName} Workspace`; + beforeAll(() => IntlStore.load(CONST.LOCALES.DEFAULT)); + it('should return undefined when there are no policies', () => { expect(lastWorkspaceNumberSelector({}, email)).toBeUndefined(); }); diff --git a/tests/unit/useLastWorkspaceNumberTest.ts b/tests/unit/useLastWorkspaceNumberTest.ts index 8d1578dc2c84..e6341a553199 100644 --- a/tests/unit/useLastWorkspaceNumberTest.ts +++ b/tests/unit/useLastWorkspaceNumberTest.ts @@ -1,6 +1,8 @@ import {renderHook} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; import useLastWorkspaceNumber from '@hooks/useLastWorkspaceNumber'; +import CONST from '@src/CONST'; +import IntlStore from '@src/languages/IntlStore'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy} from '@src/types/onyx'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -12,6 +14,7 @@ describe('useLastWorkspaceNumber', () => { beforeAll(() => { Onyx.init({keys: ONYXKEYS}); + return IntlStore.load(CONST.LOCALES.DEFAULT); }); beforeEach(() => { From b304ce83fdf9b08f457a14f0458af5f17d7b8d8b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 27 Mar 2026 12:12:06 +0800 Subject: [PATCH 7/8] pass translate from param --- src/components/WorkspaceConfirmationForm.tsx | 2 +- src/libs/actions/Policy/Policy.ts | 10 ++++------ .../BaseOnboardingWorkspaceConfirmation.tsx | 2 +- .../BaseOnboardingWorkspaceOptional.tsx | 3 ++- tests/actions/PolicyTest.ts | 14 +++++++------- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/WorkspaceConfirmationForm.tsx b/src/components/WorkspaceConfirmationForm.tsx index 1a2a2f1260a8..b0809d36f30d 100644 --- a/src/components/WorkspaceConfirmationForm.tsx +++ b/src/components/WorkspaceConfirmationForm.tsx @@ -117,7 +117,7 @@ function WorkspaceConfirmationForm({onSubmit, policyOwnerEmail = '', onBackButto const email = policyOwnerEmail || (session?.email ?? ''); const lastWorkspaceNumber = lastWorkspaceNumberSelector(policies, email); - const defaultWorkspaceName = newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber); + const defaultWorkspaceName = newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber, translate); const [workspaceNameFirstCharacter, setWorkspaceNameFirstCharacter] = useState(defaultWorkspaceName ?? ''); const userCurrency = draftValues?.currency ?? currentUserPersonalDetails?.localCurrencyCode ?? CONST.CURRENCY.USD; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index b6df7b635526..ae2b85c7f2c4 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -5,7 +5,7 @@ import type {OnyxCollection, OnyxCollectionInputValue, OnyxEntry, OnyxUpdate} fr import Onyx from 'react-native-onyx'; import type {TupleToUnion, ValueOf} from 'type-fest'; import type {ReportExportType} from '@components/ButtonWithDropdownMenu/types'; -import type {LocaleContextProps} from '@components/LocaleContextProvider'; +import type {LocaleContextProps, LocalizedTranslate} from '@components/LocaleContextProvider'; import type PolicyData from '@hooks/usePolicyData/types'; import * as API from '@libs/API'; import type { @@ -2178,7 +2178,7 @@ function getDisplayNameForWorkspace(email: string) { * @param [email] the email to base the workspace name on. If not passed, will use the logged-in user's email instead * @param [lastWorkspaceNumber] the last workspace number */ -function newGenerateDefaultWorkspaceName(email: string, lastWorkspaceNumber: number | undefined): string { +function newGenerateDefaultWorkspaceName(email: string, lastWorkspaceNumber: number | undefined, translate: LocalizedTranslate): string { const emailParts = email ? email.split('@') : deprecatedSessionEmail.split('@'); if (!emailParts || emailParts.length !== 2) { return ''; @@ -2187,14 +2187,12 @@ function newGenerateDefaultWorkspaceName(email: string, lastWorkspaceNumber: num const isSMSDomain = `@${domain}` === CONST.SMS.DOMAIN; if (isSMSDomain) { - // eslint-disable-next-line @typescript-eslint/no-deprecated - return translateLocal('workspace.new.myGroupWorkspace', {workspaceNumber: lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined}); + return translate('workspace.new.myGroupWorkspace', {workspaceNumber: lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined}); } const displayNameForWorkspace = getDisplayNameForWorkspace(email || deprecatedSessionEmail); - // eslint-disable-next-line @typescript-eslint/no-deprecated - return translateLocal('workspace.new.workspaceName', displayNameForWorkspace, lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined); + return translate('workspace.new.workspaceName', displayNameForWorkspace, lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined); } /** diff --git a/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx b/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx index 542085a100d8..991f7743745b 100644 --- a/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx +++ b/src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx @@ -60,7 +60,7 @@ function BaseOnboardingWorkspaceConfirmation({shouldUseNativeStyles}: BaseOnboar const email = session?.email ?? ''; const lastWorkspaceNumber = lastWorkspaceNumberSelector(allPolicies, email); - const defaultWorkspaceName = draftValues?.name ?? newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber); + const defaultWorkspaceName = draftValues?.name ?? newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber, translate); const defaultCurrency = draftValues?.currency ?? currentUserPersonalDetails?.localCurrencyCode ?? CONST.CURRENCY.USD; useEffect(() => { diff --git a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx index cf415d09a890..cd881577b987 100644 --- a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx +++ b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx @@ -165,7 +165,7 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding ? createWorkspace({ policyOwnerEmail: undefined, makeMeAdmin: true, - policyName: newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber), + policyName: newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber, translate), policyID: generatePolicyID(), engagementChoice: CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE, currency: currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD, @@ -203,6 +203,7 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding isSelfTourViewed, hasActiveAdminPolicies, completeOnboarding, + translate, ]); return ( diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index def8e00a49c5..d01850b52752 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -1786,7 +1786,7 @@ describe('actions/Policy', () => { await waitForBatchedUpdates(); const policyID = Policy.generatePolicyID(); - const expectedName = Policy.newGenerateDefaultWorkspaceName(ESH_EMAIL, undefined); + const expectedName = Policy.newGenerateDefaultWorkspaceName(ESH_EMAIL, undefined, TestHelper.translateLocal); Policy.createDraftInitialWorkspace({choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, ESH_EMAIL, '', policyID, false); await waitForBatchedUpdates(); @@ -3054,7 +3054,7 @@ describe('actions/Policy', () => { accountID: TEST_ACCOUNT_ID, }); - const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_NON_PUBLIC_DOMAIN_EMAIL, undefined); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_NON_PUBLIC_DOMAIN_EMAIL, undefined, TestHelper.translateLocal); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', displayNameForWorkspace)); }); @@ -3067,7 +3067,7 @@ describe('actions/Policy', () => { accountID: TEST_ACCOUNT_ID, }); - const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, undefined); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, undefined, TestHelper.translateLocal); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', displayNameForWorkspace)); }); @@ -3082,7 +3082,7 @@ describe('actions/Policy', () => { accountID: TEST_ACCOUNT_ID, }); - const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL_2, undefined); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL_2, undefined, TestHelper.translateLocal); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', displayNameForWorkspace)); }); @@ -3100,7 +3100,7 @@ describe('actions/Policy', () => { await Onyx.set(ONYXKEYS.COLLECTION.POLICY, existingPolicies); - const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, 1); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, 1, TestHelper.translateLocal); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', TEST_DISPLAY_NAME, 2)); }); @@ -3111,7 +3111,7 @@ describe('actions/Policy', () => { accountID: TEST_ACCOUNT_ID, }); - const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_SMS_DOMAIN_EMAIL, undefined); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_SMS_DOMAIN_EMAIL, undefined, TestHelper.translateLocal); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.myGroupWorkspace', {})); }); @@ -3133,7 +3133,7 @@ describe('actions/Policy', () => { await Onyx.set(ONYXKEYS.COLLECTION.POLICY, existingPolicies); - const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, 1); + const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, 1, TestHelper.translateLocal); expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', TEST_DISPLAY_NAME, 2)); }); }); From 8ee3b6fc0f3b766d0ffc810e9cebe287668f672e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 30 Mar 2026 21:18:24 +0800 Subject: [PATCH 8/8] fix redeclaring issue --- src/libs/actions/Policy/Policy.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index ae2b85c7f2c4..859243e006b2 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -2178,7 +2178,7 @@ function getDisplayNameForWorkspace(email: string) { * @param [email] the email to base the workspace name on. If not passed, will use the logged-in user's email instead * @param [lastWorkspaceNumber] the last workspace number */ -function newGenerateDefaultWorkspaceName(email: string, lastWorkspaceNumber: number | undefined, translate: LocalizedTranslate): string { +function newGenerateDefaultWorkspaceName(email: string, lastWorkspaceNumber: number | undefined, localeTranslate: LocalizedTranslate): string { const emailParts = email ? email.split('@') : deprecatedSessionEmail.split('@'); if (!emailParts || emailParts.length !== 2) { return ''; @@ -2187,12 +2187,12 @@ function newGenerateDefaultWorkspaceName(email: string, lastWorkspaceNumber: num const isSMSDomain = `@${domain}` === CONST.SMS.DOMAIN; if (isSMSDomain) { - return translate('workspace.new.myGroupWorkspace', {workspaceNumber: lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined}); + return localeTranslate('workspace.new.myGroupWorkspace', {workspaceNumber: lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined}); } const displayNameForWorkspace = getDisplayNameForWorkspace(email || deprecatedSessionEmail); - return translate('workspace.new.workspaceName', displayNameForWorkspace, lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined); + return localeTranslate('workspace.new.workspaceName', displayNameForWorkspace, lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined); } /**