diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 6b26ecd73700..d39213943c82 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -558,6 +558,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', @@ -743,6 +745,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 ca869eb54d4b..4220569388ee 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1487,6 +1487,10 @@ const ROUTES = { }, WELCOME_VIDEO_ROOT: 'onboarding/welcome-video', EXPLANATION_MODAL_ROOT: 'onboarding/explanation', + WORKSPACE_CONFIRMATION: { + route: 'workspace/confirmation', + getRoute: (backTo?: string) => getUrlWithBackToParam(`workspace/confirmation`, backTo), + }, MIGRATED_USER_WELCOME_MODAL: 'onboarding/migrated-user-welcome', TRANSACTION_RECEIPT: { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index c204b05c90de..5a5635037d02 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -159,6 +159,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', @@ -326,6 +327,8 @@ const SCREENS = { EXPORT: 'Report_Details_Export', }, + WORKSPACE_CONFIRMATION: {ROOT: 'Workspace_Confirmation_Root'}, + WORKSPACE: { ACCOUNTING: { ROOT: 'Policy_Accounting', diff --git a/src/components/CurrencyPicker.tsx b/src/components/CurrencyPicker.tsx new file mode 100644 index 000000000000..f382542c6538 --- /dev/null +++ b/src/components/CurrencyPicker.tsx @@ -0,0 +1,89 @@ +import React, {forwardRef, useState} from 'react'; +import type {ForwardedRef} from 'react'; +import {View} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import CurrencySelectionListWithOnyx from './CurrencySelectionList'; +import HeaderWithBackButton from './HeaderWithBackButton'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; +import Modal from './Modal'; +import ScreenWrapper from './ScreenWrapper'; +import type {ValuePickerItem, ValuePickerProps} from './ValuePicker/types'; + +type CurrencyPickerProps = { + selectedCurrency?: string; +}; +function CurrencyPicker({selectedCurrency, label = '', errorText = '', value, onInputChange, furtherDetails}: ValuePickerProps & CurrencyPickerProps, forwardedRef: ForwardedRef) { + const StyleUtils = useStyleUtils(); + const styles = useThemeStyles(); + const [isPickerVisible, setIsPickerVisible] = useState(false); + const {translate} = useLocalize(); + + const showPickerModal = () => { + setIsPickerVisible(true); + }; + + const hidePickerModal = () => { + setIsPickerVisible(false); + }; + + const updateInput = (item: ValuePickerItem) => { + if (item.value !== selectedCurrency) { + onInputChange?.(item.value); + } + hidePickerModal(); + }; + + const descStyle = !selectedCurrency || selectedCurrency.length === 0 ? StyleUtils.getFontSizeStyle(variables.fontSizeLabel) : null; + + return ( + + + + hidePickerModal} + onModalHide={hidePickerModal} + hideModalContentWhileAnimating + useNativeDriver + onBackdropPress={hidePickerModal} + > + + + updateInput({value: item.currencyCode})} + searchInputLabel={translate('common.currency')} + initiallySelectedCurrencyCode={selectedCurrency} + /> + + + + ); +} + +CurrencyPicker.displayName = 'CurrencyPicker'; + +export default forwardRef(CurrencyPicker); diff --git a/src/languages/en.ts b/src/languages/en.ts index 22969905bd03..fc29e0b6e63c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3817,7 +3817,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', @@ -3835,6 +3835,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 438e55921079..43db4f9f3eb5 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3879,6 +3879,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 91c1039169aa..313ef1bd6268 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 26a5d209b0ff..cb5363225a4c 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -2,8 +2,9 @@ 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'; +import {format, formatToParts} from './NumberFormatUtils'; let currencyList: OnyxValues[typeof ONYXKEYS.CURRENCY_LIST] = {}; @@ -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 @@ -44,7 +50,7 @@ function getCurrencyUnit(currency: string = CONST.CURRENCY.USD): number { * Get localized currency symbol for currency(ISO 4217) Code */ function getLocalizedCurrencySymbol(currencyCode: string): string | undefined { - const parts = NumberFormatUtils.formatToParts(BaseLocaleListener.getPreferredLocale(), 0, { + const parts = formatToParts(BaseLocaleListener.getPreferredLocale(), 0, { style: 'currency', currency: currencyCode, }); @@ -62,7 +68,7 @@ function getCurrencySymbol(currencyCode: string): string | undefined { * Whether the currency symbol is left-to-right. */ function isCurrencySymbolLTR(currencyCode: string): boolean { - const parts = NumberFormatUtils.formatToParts(BaseLocaleListener.getPreferredLocale(), 0, { + const parts = formatToParts(BaseLocaleListener.getPreferredLocale(), 0, { style: 'currency', currency: currencyCode, }); @@ -121,7 +127,7 @@ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURR if (!currency) { currencyWithFallback = CONST.CURRENCY.USD; } - return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { + return format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency: currencyWithFallback, @@ -143,7 +149,7 @@ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURR function convertToShortDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD): string { const convertedAmount = convertToFrontendAmountAsInteger(amountInCents, currency); - return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { + return format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, @@ -161,7 +167,7 @@ function convertToShortDisplayString(amountInCents = 0, currency: string = CONST */ function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRENCY.USD): string { const convertedAmount = amount / 100.0; - return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { + return format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, minimumFractionDigits: CONST.MIN_TAX_RATE_DECIMAL_PLACES, @@ -174,7 +180,7 @@ function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRE */ function convertToDisplayStringWithoutCurrency(amountInCents: number, currency: string = CONST.CURRENCY.USD) { const convertedAmount = convertToFrontendAmountAsInteger(amountInCents, currency); - return NumberFormatUtils.formatToParts(BaseLocaleListener.getPreferredLocale(), convertedAmount, { + return formatToParts(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, @@ -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 e05d316bd8ca..cf6bb5eae810 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'; @@ -137,6 +138,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, @@ -730,4 +735,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 95f82f4a2fdf..ac53ad3b64d2 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -138,6 +138,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.route, + }, + }, [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 994178139675..41e8c8cc7824 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1282,6 +1282,12 @@ type MoneyRequestNavigatorParamList = { }; }; +type WorkspaceConfirmationNavigatorParamList = { + [SCREENS.WORKSPACE_CONFIRMATION.ROOT]: { + backTo?: Routes; + }; +}; + type NewTaskNavigatorParamList = { [SCREENS.NEW_TASK.ROOT]: { backTo?: Routes; @@ -1455,6 +1461,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; @@ -1863,4 +1870,5 @@ export type { MissingPersonalDetailsParamList, DebugParamList, MigratedUserModalNavigatorParamList, + WorkspaceConfirmationNavigatorParamList, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 674708fb4f9f..f5a1e71cea78 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -7597,7 +7597,10 @@ function getWorkspaceChats(policyID: string, accountIDs: number[], reports: Onyx * * @param policyID - the workspace ID to get all associated reports */ -function getAllWorkspaceReports(policyID: string): Array> { +function getAllWorkspaceReports(policyID?: string): Array> { + if (!policyID) { + return []; + } return Object.values(allReports ?? {}).filter((report) => report?.policyID === policyID); } diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index 51093e759e1a..ddf9701a6c8e 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -384,10 +384,22 @@ 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 [currency] Optional, selected currency for the workspace + * @param [file], avatar file for workspace */ -function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', policyName = '', transitionFromOldDot = false, makeMeAdmin = false, backTo = '') { - const policyID = generatePolicyID(); - createDraftInitialWorkspace(policyOwnerEmail, policyName, policyID, makeMeAdmin); +function createWorkspaceWithPolicyDraftAndNavigateToIt( + policyOwnerEmail = '', + policyName = '', + transitionFromOldDot = false, + makeMeAdmin = false, + backTo = '', + policyID = '', + currency?: string, + file?: File, +) { + const policyIDWithDefault = policyID || generatePolicyID(); + createDraftInitialWorkspace(policyOwnerEmail, policyName, policyIDWithDefault, makeMeAdmin, currency, file); Navigation.isNavigationReady() .then(() => { @@ -395,8 +407,8 @@ function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', po // We must call goBack() to remove the /transition route from history Navigation.goBack(); } - savePolicyDraftByNewWorkspace(policyID, policyName, policyOwnerEmail, makeMeAdmin); - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID, backTo)); + savePolicyDraftByNewWorkspace(policyIDWithDefault, policyName, policyOwnerEmail, makeMeAdmin, currency, file); + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyIDWithDefault, backTo)); }) .then(endSignOnTransition); } @@ -408,9 +420,11 @@ function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', po * @param [policyName] custom policy name we will use for created workspace * @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 + * @param [currency] Optional, selected currency for the workspace + * @param [file] Optional, avatar file for workspace */ -function savePolicyDraftByNewWorkspace(policyID?: string, policyName?: string, policyOwnerEmail = '', makeMeAdmin = false) { - createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID); +function savePolicyDraftByNewWorkspace(policyID?: string, policyName?: string, policyOwnerEmail = '', makeMeAdmin = false, currency = '', file?: File) { + createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID, '', currency, file); } /** diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index f28a82bea9bb..eb302fd7fda5 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -64,6 +64,7 @@ import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; +import {createFile} from '@libs/fileDownload/FileUtils'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import GoogleTagManager from '@libs/GoogleTagManager'; import Log from '@libs/Log'; @@ -1596,7 +1597,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(); @@ -1634,10 +1636,12 @@ function buildOptimisticDistanceRateCustomUnits(reportCurrency?: string): Optimi * @param [policyName] custom policy name we will use for created workspace * @param [policyID] custom policy id we will use for created workspace * @param [makeMeAdmin] leave the calling account as an admin on the policy + * @param [currency] Optional, selected currency for the workspace + * @param [file], avatar file for workspace */ -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[] = [ { @@ -1658,6 +1662,8 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol makeMeAdmin, autoReporting: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT, + avatarURL: file?.uri ?? null, + originalFileName: file?.name, employeeList: { [sessionEmail]: { role: CONST.POLICY.ROLE.ADMIN, @@ -1687,12 +1693,24 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol * @param [makeMeAdmin] leave the calling account as an admin on the policy * @param [policyName] custom policy name we will use for created workspace * @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 + * @param [expenseReportId] Optional, Purpose of using application selected by user in guided setup flow + * @param [engagementChoice] Purpose of using application selected by user in guided setup flow + * @param [currency] Optional, selected currency for the workspace + * @param [file] Optional, avatar file for 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, @@ -1754,6 +1772,8 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName description: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, type: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, + avatarURL: file?.uri, + originalFileName: file?.name, }, }, { @@ -1939,6 +1959,9 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName successData.push(...optimisticCategoriesData.successData); } + // We need to clone the file to prevent non-indexable errors. + const clonedFile = file ? (createFile(file) as File) : undefined; + const params: CreateWorkspaceParams = { policyID, adminsChatReportID, @@ -1952,6 +1975,8 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName customUnitID, customUnitRateID, engagementChoice, + currency: outputCurrency, + file: clonedFile, }; return {successData, optimisticData, failureData, params}; @@ -1965,9 +1990,19 @@ function buildPolicyData(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 * @param [engagementChoice] Purpose of using application selected by user in guided setup flow + * @param [currency] Optional, selected currency for the workspace + * @param [file], avatar file for workspace */ -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 @@ -1986,10 +2021,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, @@ -2056,6 +2091,9 @@ function createDraftWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policy }, ]; + // We need to clone the file to prevent non-indexable errors. + const clonedFile = file ? (createFile(file) as File) : undefined; + const params: CreateWorkspaceParams = { policyID, adminsChatReportID, @@ -2068,6 +2106,8 @@ function createDraftWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policy expenseCreatedReportActionID, customUnitID, customUnitRateID, + currency: outputCurrency, + file: clonedFile, }; Onyx.update(optimisticData); diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts index f209e46930cb..cc43850f12f3 100644 --- a/src/libs/fileDownload/FileUtils.ts +++ b/src/libs/fileDownload/FileUtils.ts @@ -3,6 +3,7 @@ import {Alert, Linking, Platform} from 'react-native'; import ImageSize from 'react-native-image-size'; import type {FileObject} from '@components/AttachmentModal'; import DateUtils from '@libs/DateUtils'; +import getPlatform from '@libs/getPlatform'; import * as Localize from '@libs/Localize'; import Log from '@libs/Log'; import saveLastRoute from '@libs/saveLastRoute'; @@ -330,6 +331,21 @@ const resizeImageIfNeeded = (file: FileObject) => { } return getImageDimensionsAfterResize(file).then(({width, height}) => getImageManipulator({fileUri: file.uri ?? '', width, height, fileName: file.name ?? '', type: file.type})); }; + +const createFile = (file: File): FileObject => { + if (getPlatform() === CONST.PLATFORM.ANDROID || getPlatform() === CONST.PLATFORM.IOS) { + return { + uri: file.uri, + name: file.name, + type: file.type, + }; + } + return new File([file], file.name, { + type: file.type, + lastModified: file.lastModified, + }); +}; + export { showGeneralErrorAlert, showSuccessAlert, @@ -350,4 +366,5 @@ export { verifyFileFormat, getImageDimensionsAfterResize, resizeImageIfNeeded, + createFile, }; diff --git a/src/pages/WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx b/src/pages/WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx index 4a6b6a473188..0fa006601097 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() { >