From 7f4059318df09e59acd3ce996fcf62049d171fa5 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 10 Dec 2024 15:59:50 +0530 Subject: [PATCH 01/20] Provide education/confirmation before creating workspaces in New Workspace flows. Signed-off-by: krishna2323 --- src/ONYXKEYS.ts | 3 ++ src/ROUTES.ts | 1 + src/SCREENS.ts | 3 ++ src/languages/en.ts | 3 +- src/languages/es.ts | 1 + .../API/parameters/CreateWorkspaceParams.ts | 2 + src/libs/CurrencyUtils.ts | 7 +++ .../ModalStackNavigators/index.tsx | 6 +++ .../Navigators/RightModalNavigator.tsx | 4 ++ src/libs/Navigation/linkingConfig/config.ts | 5 ++ src/libs/Navigation/types.ts | 6 +++ src/libs/actions/App.ts | 23 +++++++--- src/libs/actions/Policy/Policy.ts | 46 +++++++++++++++---- .../WorkspaceCardCreateAWorkspace.tsx | 5 +- .../FloatingActionButtonAndPopover.tsx | 3 +- src/pages/workspace/WorkspaceNamePage.tsx | 2 +- src/pages/workspace/WorkspacesListPage.tsx | 5 +- src/types/form/index.ts | 1 + 18 files changed, 100 insertions(+), 26 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 45d636c0b1df5..dc0af7334cda7 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -554,6 +554,8 @@ const ONYXKEYS = { ADD_PAYMENT_CARD_FORM_DRAFT: 'addPaymentCardFormDraft', WORKSPACE_SETTINGS_FORM: 'workspaceSettingsForm', WORKSPACE_CATEGORY_FORM: 'workspaceCategoryForm', + WORKSPACE_CONFIRMATION_FORM: 'workspaceConfirmationForm', + WORKSPACE_CONFIRMATION_FORM_DRAFT: 'workspaceConfirmationFormDraft', WORKSPACE_CATEGORY_FORM_DRAFT: 'workspaceCategoryFormDraft', WORKSPACE_CATEGORY_DESCRIPTION_HINT_FORM: 'workspaceCategoryDescriptionHintForm', WORKSPACE_CATEGORY_DESCRIPTION_HINT_FORM_DRAFT: 'workspaceCategoryDescriptionHintFormDraft', @@ -733,6 +735,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM]: FormTypes.AddPaymentCardForm; [ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM]: FormTypes.WorkspaceSettingsForm; [ONYXKEYS.FORMS.WORKSPACE_CATEGORY_FORM]: FormTypes.WorkspaceCategoryForm; + [ONYXKEYS.FORMS.WORKSPACE_CONFIRMATION_FORM]: FormTypes.WorkspaceConfirmationForm; [ONYXKEYS.FORMS.WORKSPACE_TAG_FORM]: FormTypes.WorkspaceTagForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_CUSTOM_NAME]: FormTypes.WorkspaceTaxCustomName; [ONYXKEYS.FORMS.WORKSPACE_COMPANY_CARD_FEED_NAME]: FormTypes.WorkspaceCompanyCardFeedName; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 4abd5c6d3d49f..afa421be31c3d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1387,6 +1387,7 @@ const ROUTES = { }, WELCOME_VIDEO_ROOT: 'onboarding/welcome-video', EXPLANATION_MODAL_ROOT: 'onboarding/explanation', + WORKSPACE_CONFIRMATION: 'workspace/confirmation', MIGRATED_USER_WELCOME_MODAL: 'onboarding/migrated-user-welcome', TRANSACTION_RECEIPT: { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 47090bd7075bd..7fc11413ca955 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -156,6 +156,7 @@ const SCREENS = { DETAILS: 'Details', PROFILE: 'Profile', REPORT_DETAILS: 'Report_Details', + WORKSPACE_CONFIRMATION: 'Workspace_Confirmation', REPORT_SETTINGS: 'Report_Settings', REPORT_DESCRIPTION: 'Report_Description', PARTICIPANTS: 'Participants', @@ -317,6 +318,8 @@ const SCREENS = { EXPORT: 'Report_Details_Export', }, + WORKSPACE_CONFIRMATION: {ROOT: 'Workspace_Confirmation_Root'}, + WORKSPACE: { ACCOUNTING: { ROOT: 'Policy_Accounting', diff --git a/src/languages/en.ts b/src/languages/en.ts index 5c2cb34dc251f..26f7875e53eeb 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3716,7 +3716,7 @@ const translations = { }, emptyWorkspace: { title: 'Create a workspace', - subtitle: 'Create a workspace to track receipts, reimburse expenses, send invoices, and more -- all at the speed of chat.', + subtitle: 'Create a workspace to track receipts, reimburse expenses, send invoices, and more — all at the speed of chat.', createAWorkspaceCTA: 'Get Started', features: { trackAndCollect: 'Track and collect receipts', @@ -3734,6 +3734,7 @@ const translations = { new: { newWorkspace: 'New workspace', getTheExpensifyCardAndMore: 'Get the Expensify Card and more', + confirmWorkspace: 'Confirm Workspace', }, people: { genericFailureMessage: 'An error occurred removing a member from the workspace, please try again.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 2370fd0a1a9df..66940ef87c6f4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3779,6 +3779,7 @@ const translations = { new: { newWorkspace: 'Nuevo espacio de trabajo', getTheExpensifyCardAndMore: 'Consigue la Tarjeta Expensify y más', + confirmWorkspace: 'Confirmar espacio de trabajo', }, people: { genericFailureMessage: 'Se ha producido un error al intentar eliminar a un miembro del espacio de trabajo. Por favor, inténtalo más tarde.', diff --git a/src/libs/API/parameters/CreateWorkspaceParams.ts b/src/libs/API/parameters/CreateWorkspaceParams.ts index 91c1039169aae..313ef1bd62680 100644 --- a/src/libs/API/parameters/CreateWorkspaceParams.ts +++ b/src/libs/API/parameters/CreateWorkspaceParams.ts @@ -11,6 +11,8 @@ type CreateWorkspaceParams = { customUnitID: string; customUnitRateID: string; engagementChoice?: string; + currency: string; + file?: File; }; export default CreateWorkspaceParams; diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 26a5d209b0ff1..32fc1c11984c6 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -2,6 +2,7 @@ import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import type {OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Currency} from '@src/types/onyx'; import BaseLocaleListener from './Localize/LocaleListener/BaseLocaleListener'; import * as NumberFormatUtils from './NumberFormatUtils'; @@ -30,6 +31,11 @@ function getCurrencyDecimals(currency: string = CONST.CURRENCY.USD): number { return decimals ?? 2; } +function getCurrency(currency: string = CONST.CURRENCY.USD): Currency | null { + const currencyItem = currencyList?.[currency]; + return currencyItem; +} + /** * Returns the currency's minor unit quantity * e.g. Cent in USD @@ -216,5 +222,6 @@ export { convertToDisplayStringWithoutCurrency, isValidCurrencyCode, convertToShortDisplayString, + getCurrency, sanitizeCurrencyCode, }; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index edc32bb705b6e..3f78b39c596c9 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -31,6 +31,7 @@ import type { TransactionDuplicateNavigatorParamList, TravelNavigatorParamList, WalletStatementNavigatorParamList, + WorkspaceConfirmationNavigatorParamList, } from '@navigation/types'; import type {Screen} from '@src/SCREENS'; import SCREENS from '@src/SCREENS'; @@ -129,6 +130,10 @@ const ReportSettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Report/VisibilityPage').default, }); +const WorkspaceConfirmationModalStackNavigator = createModalStackNavigator({ + [SCREENS.WORKSPACE_CONFIRMATION.ROOT]: () => require('../../../../pages/workspace/WorkspaceConfirmationPage').default, +}); + const TaskModalStackNavigator = createModalStackNavigator({ [SCREENS.TASK.TITLE]: () => require('../../../../pages/tasks/TaskTitlePage').default, [SCREENS.TASK.ASSIGNEE]: () => require('../../../../pages/tasks/TaskAssigneeSelectorModal').default, @@ -715,4 +720,5 @@ export { SearchSavedSearchModalStackNavigator, MissingPersonalDetailsModalStackNavigator, DebugModalStackNavigator, + WorkspaceConfirmationModalStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index c996f380e98c9..535232de4d172 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -134,6 +134,10 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { name={SCREENS.RIGHT_MODAL.MONEY_REQUEST} component={ModalStackNavigators.MoneyRequestModalStackNavigator} /> + ['config'] = { }, }, }, + [SCREENS.RIGHT_MODAL.WORKSPACE_CONFIRMATION]: { + screens: { + [SCREENS.WORKSPACE_CONFIRMATION.ROOT]: ROUTES.WORKSPACE_CONFIRMATION, + }, + }, [SCREENS.RIGHT_MODAL.NEW_TASK]: { screens: { [SCREENS.NEW_TASK.ROOT]: ROUTES.NEW_TASK.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 86948ea7f099d..486fc54583d1b 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1206,6 +1206,10 @@ type MoneyRequestNavigatorParamList = { }; }; +type WorkspaceConfirmationNavigatorParamList = { + [SCREENS.WORKSPACE_CONFIRMATION.ROOT]: undefined; +}; + type NewTaskNavigatorParamList = { [SCREENS.NEW_TASK.ROOT]: { backTo?: Routes; @@ -1382,6 +1386,7 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.PARTICIPANTS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.ROOM_MEMBERS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.MONEY_REQUEST]: NavigatorScreenParams; + [SCREENS.RIGHT_MODAL.WORKSPACE_CONFIRMATION]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.NEW_TASK]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.TEACHERS_UNITE]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.TASK_DETAILS]: NavigatorScreenParams; @@ -1772,4 +1777,5 @@ export type { MissingPersonalDetailsParamList, DebugParamList, MigratedUserModalNavigatorParamList, + WorkspaceConfirmationNavigatorParamList, }; diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index 61ce04655ae5a..a5dcf92e72dc3 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -367,10 +367,21 @@ function endSignOnTransition() { * @param [transitionFromOldDot] Optional, if the user is transitioning from old dot * @param [makeMeAdmin] Optional, leave the calling account as an admin on the policy * @param [backTo] An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. + * @param [policyID] Optional, Policy id. + * @param [file],file */ -function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', policyName = '', transitionFromOldDot = false, makeMeAdmin = false, backTo = '') { - const policyID = Policy.generatePolicyID(); - Policy.createDraftInitialWorkspace(policyOwnerEmail, policyName, policyID, makeMeAdmin); +function createWorkspaceWithPolicyDraftAndNavigateToIt( + policyOwnerEmail = '', + policyName = '', + transitionFromOldDot = false, + makeMeAdmin = false, + backTo = '', + policyID = '', + currency?: string, + file?: File, +) { + const policyIDWithDefault = policyID || Policy.generatePolicyID(); + Policy.createDraftInitialWorkspace(policyOwnerEmail, policyName, policyIDWithDefault, makeMeAdmin, currency, file); Navigation.isNavigationReady() .then(() => { @@ -378,7 +389,7 @@ function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', po // We must call goBack() to remove the /transition route from history Navigation.goBack(); } - savePolicyDraftByNewWorkspace(policyID, policyName, policyOwnerEmail, makeMeAdmin); + savePolicyDraftByNewWorkspace(policyIDWithDefault, policyName, policyOwnerEmail, makeMeAdmin, currency, file); Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID, backTo)); }) .then(endSignOnTransition); @@ -392,8 +403,8 @@ function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', po * @param [policyOwnerEmail] Optional, the email of the account to make the owner of the policy * @param [makeMeAdmin] Optional, leave the calling account as an admin on the policy */ -function savePolicyDraftByNewWorkspace(policyID?: string, policyName?: string, policyOwnerEmail = '', makeMeAdmin = false) { - Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID); +function savePolicyDraftByNewWorkspace(policyID?: string, policyName?: string, policyOwnerEmail = '', makeMeAdmin = false, currency = '', file?: File) { + Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID, '', currency, file); } /** diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 35136a1691cf2..ec7b57d1fa6ba 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1524,7 +1524,8 @@ function generateCustomUnitID(): string { } function buildOptimisticDistanceRateCustomUnits(reportCurrency?: string): OptimisticCustomUnits { - const currency = reportCurrency ?? allPersonalDetails?.[sessionAccountID]?.localCurrencyCode ?? CONST.CURRENCY.USD; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Disabling this line for safeness as nullish coalescing works only if the value is undefined or null + const currency = reportCurrency || (allPersonalDetails?.[sessionAccountID]?.localCurrencyCode ?? CONST.CURRENCY.USD); const customUnitID = generateCustomUnitID(); const customUnitRateID = generateCustomUnitID(); @@ -1563,9 +1564,9 @@ function buildOptimisticDistanceRateCustomUnits(reportCurrency?: string): Optimi * @param [policyID] custom policy id we will use for created workspace * @param [makeMeAdmin] leave the calling account as an admin on the policy */ -function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', policyID = generatePolicyID(), makeMeAdmin = false) { +function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', policyID = generatePolicyID(), makeMeAdmin = false, currency = '', file?: File) { const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); - const {customUnits, outputCurrency} = buildOptimisticDistanceRateCustomUnits(); + const {customUnits, outputCurrency} = buildOptimisticDistanceRateCustomUnits(currency); const optimisticData: OnyxUpdate[] = [ { @@ -1580,12 +1581,14 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol ownerAccountID: sessionAccountID, isPolicyExpenseChatEnabled: true, areCategoriesEnabled: true, - outputCurrency, + outputCurrency: currency || outputCurrency, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, customUnits, makeMeAdmin, autoReporting: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT, + avatarURL: file?.uri ?? null, + originalFileName: file?.name, employeeList: { [sessionEmail]: { role: CONST.POLICY.ROLE.ADMIN, @@ -1617,10 +1620,19 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol * @param [policyID] custom policy id we will use for created workspace * @param [expenseReportId] the reportID of the expense report that is being used to create the workspace */ -function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), expenseReportId?: string, engagementChoice?: string) { +function buildPolicyData( + policyOwnerEmail = '', + makeMeAdmin = false, + policyName = '', + policyID = generatePolicyID(), + expenseReportId?: string, + engagementChoice?: string, + currency = '', + file?: File, +) { const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); - const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticDistanceRateCustomUnits(); + const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticDistanceRateCustomUnits(currency); const { adminsChatReportID, @@ -1680,6 +1692,8 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName address: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, description: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, + avatarURL: file?.uri, + originalFileName: file?.name, }, }, { @@ -1870,6 +1884,8 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName customUnitID, customUnitRateID, engagementChoice, + currency: outputCurrency, + file, }; return {successData, optimisticData, failureData, params}; @@ -1884,8 +1900,16 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName * @param [policyID] custom policy id we will use for created workspace * @param [engagementChoice] Purpose of using application selected by user in guided setup flow */ -function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), engagementChoice = ''): CreateWorkspaceParams { - const {optimisticData, failureData, successData, params} = buildPolicyData(policyOwnerEmail, makeMeAdmin, policyName, policyID, undefined, engagementChoice); +function createWorkspace( + policyOwnerEmail = '', + makeMeAdmin = false, + policyName = '', + policyID = generatePolicyID(), + engagementChoice = '', + currency = '', + file?: File, +): CreateWorkspaceParams { + const {optimisticData, failureData, successData, params} = buildPolicyData(policyOwnerEmail, makeMeAdmin, policyName, policyID, undefined, engagementChoice, currency, file); API.write(WRITE_COMMANDS.CREATE_WORKSPACE, params, {optimisticData, successData, failureData}); // Publish a workspace created event if this is their first policy @@ -1904,10 +1928,10 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName * @param [policyName] custom policy name we will use for created workspace * @param [policyID] custom policy id we will use for created workspace */ -function createDraftWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID()): CreateWorkspaceParams { +function createDraftWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), currency = '', file?: File): CreateWorkspaceParams { const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); - const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticDistanceRateCustomUnits(); + const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticDistanceRateCustomUnits(currency); const {expenseChatData, adminsChatReportID, adminsCreatedReportActionID, expenseChatReportID, expenseCreatedReportActionID} = ReportUtils.buildOptimisticWorkspaceChats( policyID, @@ -1986,6 +2010,8 @@ function createDraftWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policy expenseCreatedReportActionID, customUnitID, customUnitRateID, + currency: outputCurrency, + file, }; Onyx.update(optimisticData); diff --git a/src/pages/WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx b/src/pages/WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx index 4a6b6a4731881..bf250a063582d 100644 --- a/src/pages/WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx +++ b/src/pages/WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx @@ -5,7 +5,7 @@ import Section, {CARD_LAYOUT} from '@components/Section'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as App from '@userActions/App'; +import ROUTES from '@src/ROUTES'; function WorkspaceCardCreateAWorkspace() { const styles = useThemeStyles(); @@ -22,8 +22,7 @@ function WorkspaceCardCreateAWorkspace() { >