diff --git a/src/CONST.ts b/src/CONST.ts index ef5eaf4e403b..36ef40f0e2cf 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6136,6 +6136,14 @@ const CONST = { description: 'workspace.upgrade.reportFields.description' as const, icon: 'Pencil', }, + categories: { + id: 'categories' as const, + alias: 'categories', + name: 'Categories', + title: 'workspace.upgrade.categories.title' as const, + description: 'workspace.upgrade.categories.description' as const, + icon: 'FolderOpen', + }, [this.POLICY.CONNECTIONS.NAME.NETSUITE]: { id: this.POLICY.CONNECTIONS.NAME.NETSUITE, alias: 'netsuite', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index bdf4d4774ec1..f559115c4466 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -471,6 +471,11 @@ const ROUTES = { getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => getUrlWithBackToParam(`${action as string}/${iouType as string}/attendees/${transactionID}/${reportID}`, backTo), }, + MONEY_REQUEST_UPGRADE: { + route: ':action/:iouType/upgrade/:transactionID/:reportID', + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/upgrade/${transactionID}/${reportID}`, backTo), + }, SETTINGS_TAGS_ROOT: { route: 'settings/:policyID/tags', getRoute: (policyID: string, backTo = '') => getUrlWithBackToParam(`settings/${policyID}/tags`, backTo), diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 543e8708fea3..b0a917657867 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -199,6 +199,7 @@ const SCREENS = { HOLD: 'Money_Request_Hold_Reason', STEP_CONFIRMATION: 'Money_Request_Step_Confirmation', START: 'Money_Request_Start', + STEP_UPGRADE: 'Money_Request_Step_Upgrade', STEP_AMOUNT: 'Money_Request_Step_Amount', STEP_CATEGORY: 'Money_Request_Step_Category', STEP_CURRENCY: 'Money_Request_Step_Currency', diff --git a/src/languages/en.ts b/src/languages/en.ts index 591f7eb0ed42..02d96c5390a9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4192,6 +4192,11 @@ const translations = { description: `If you want to add more layers of approval to the mix – or just make sure the largest expenses get another set of eyes – we’ve got you covered. Advanced approvals help you put the right checks in place at every level so you keep your team’s spend under control.`, onlyAvailableOnPlan: 'Advanced approvals are only available on the Control plan, which starts at ', }, + categories: { + title: 'Categories', + description: `Categories help you better organize expenses to keep track of where you're spending your money. Use our suggested categories list or create your own.`, + onlyAvailableOnPlan: 'Categories are available on the Collect plan, starting at ', + }, glCodes: { title: 'GL codes', description: `Add GL codes to your categories and tags for easy export of expenses to your accounting and payroll systems.`, @@ -4224,6 +4229,7 @@ const translations = { onlyAvailableOnPlan: 'Per diem are only available on the Control plan, starting at ', }, pricing: { + collect: '$5 ', amount: '$9 ', perActiveMember: 'per active member per month.', }, @@ -4236,6 +4242,7 @@ const translations = { completed: { headline: `You've upgraded your workspace!`, successMessage: ({policyName}: ReportPolicyNameParams) => `You've successfully upgraded ${policyName} to the Control plan!`, + categorizeMessage: `You've successfully upgraded to a workspace on the Collect plan. Now you can categorize your expenses!`, viewSubscription: 'View your subscription', moreDetails: 'for more details.', gotIt: 'Got it, thanks', diff --git a/src/languages/es.ts b/src/languages/es.ts index 91557c9defbf..45949b0b4f9c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4240,6 +4240,11 @@ const translations = { description: `Si quieres añadir más niveles de aprobación, o simplemente asegurarte de que los gastos más importantes reciben otro vistazo, no hay problema. Las aprobaciones avanzadas ayudan a realizar las comprobaciones adecuadas a cada nivel para mantener los gastos de tu equipo bajo control.`, onlyAvailableOnPlan: 'Las aprobaciones avanzadas sólo están disponibles en el plan Controlar, con precios desde ', }, + categories: { + title: 'Categorías', + description: `Las categorías te ayudan a organizar mejor los gastos y a llevar un seguimiento de en qué estás gastando tu dinero. Utiliza nuestra lista de categorías sugeridas o crea las tuyas propias.`, + onlyAvailableOnPlan: 'Las categorías están disponibles en el plan Recopilar, a partir de ', + }, glCodes: { title: 'Códigos de libro mayor', description: `Añada códigos de libro mayor a sus categorías para exportar fácilmente los gastos a sus sistemas de contabilidad y nómina.`, @@ -4277,12 +4282,14 @@ const translations = { aboutOurPlans: 'sobre nuestros planes y precios.', }, pricing: { + collect: '$5 ', amount: '$9 ', perActiveMember: 'por miembro activo al mes.', }, upgradeToUnlock: 'Desbloquear esta función', completed: { headline: 'Has mejorado tu espacio de trabajo.', + categorizeMessage: `Has actualizado con éxito a un espacio de trabajo en el plan Recopilar. ¡Ahora puedes categorizar tus gastos!`, successMessage: ({policyName}: ReportPolicyNameParams) => `Has actualizado con éxito ${policyName} al plan Controlar.`, viewSubscription: 'Ver su suscripción', moreDetails: 'para obtener más información.', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 8c0d45e8c313..33b1d18390ba 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -100,6 +100,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../../pages/EnablePayments/EnablePaymentsPage').default, [SCREENS.MONEY_REQUEST.STATE_SELECTOR]: () => require('../../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default, [SCREENS.MONEY_REQUEST.STEP_ATTENDEES]: () => require('../../../../pages/iou/request/step/IOURequestStepAttendees').default, + [SCREENS.MONEY_REQUEST.STEP_UPGRADE]: () => require('../../../../pages/iou/request/step/IOURequestStepUpgrade').default, }); const TravelModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index e0cd018086bd..55ff8349c75a 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1212,6 +1212,7 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STATE_SELECTOR]: {path: ROUTES.MONEY_REQUEST_STATE_SELECTOR.route, exact: true}, [SCREENS.MONEY_REQUEST.STEP_SPLIT_PAYER]: ROUTES.MONEY_REQUEST_STEP_SPLIT_PAYER.route, [SCREENS.MONEY_REQUEST.STEP_ATTENDEES]: ROUTES.MONEY_REQUEST_ATTENDEE.route, + [SCREENS.MONEY_REQUEST.STEP_UPGRADE]: ROUTES.MONEY_REQUEST_UPGRADE.route, [SCREENS.IOU_SEND.ENABLE_PAYMENTS]: ROUTES.IOU_SEND_ENABLE_PAYMENTS, [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: ROUTES.IOU_SEND_ADD_BANK_ACCOUNT, [SCREENS.IOU_SEND.ADD_DEBIT_CARD]: ROUTES.IOU_SEND_ADD_DEBIT_CARD, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 798e77d86ecc..1baf5f20ea6a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1174,6 +1174,13 @@ type MoneyRequestNavigatorParamList = { reportID: string; backTo: Routes; }; + [SCREENS.MONEY_REQUEST.STEP_UPGRADE]: { + action: IOUAction; + iouType: Exclude; + transactionID: string; + reportID: string; + backTo: Routes; + }; }; type NewTaskNavigatorParamList = { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 373a861f7c2e..1612cd2c6933 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -720,6 +720,12 @@ Onyx.connect({ }, }); +let activePolicyID: OnyxEntry; +Onyx.connect({ + key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, + callback: (value) => (activePolicyID = value), +}); + function getCurrentUserAvatar(): AvatarSource | undefined { return currentUserPersonalDetails?.avatar; } @@ -8211,6 +8217,44 @@ function createDraftTransactionAndNavigateToParticipantSelector(transactionID: s (policy) => policy && policy.type !== CONST.POLICY.TYPE.PERSONAL && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); + if (actionName === CONST.IOU.ACTION.CATEGORIZE) { + const activePolicy = getPolicy(activePolicyID); + if (activePolicy && activePolicy?.type !== CONST.POLICY.TYPE.PERSONAL && activePolicy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { + const policyExpenseReportID = getPolicyExpenseChat(currentUserAccountID ?? -1, activePolicyID ?? '-1')?.reportID ?? '-1'; + IOU.setMoneyRequestParticipants(transactionID, [ + { + selected: true, + accountID: 0, + isPolicyExpenseChat: true, + reportID: policyExpenseReportID, + policyID: activePolicyID ?? '-1', + searchText: activePolicy?.name, + }, + ]); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(actionName, CONST.IOU.TYPE.SUBMIT, transactionID, policyExpenseReportID)); + return; + } + if (filteredPolicies.length === 0 || filteredPolicies.length > 1) { + Navigation.navigate(ROUTES.MONEY_REQUEST_UPGRADE.getRoute(actionName, CONST.IOU.TYPE.SUBMIT, transactionID, reportID)); + return; + } + + const policyID = filteredPolicies.at(0)?.id; + const policyExpenseReportID = getPolicyExpenseChat(currentUserAccountID ?? -1, policyID ?? '-1')?.reportID ?? '-1'; + IOU.setMoneyRequestParticipants(transactionID, [ + { + selected: true, + accountID: 0, + isPolicyExpenseChat: true, + reportID: policyExpenseReportID, + policyID: policyID ?? '-1', + searchText: activePolicy?.name, + }, + ]); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(actionName, CONST.IOU.TYPE.SUBMIT, transactionID, policyExpenseReportID)); + return; + } + if (actionName === CONST.IOU.ACTION.SUBMIT || (allPolicies && filteredPolicies.length > 0)) { Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(CONST.IOU.TYPE.SUBMIT, transactionID, reportID, undefined, actionName)); return; diff --git a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx new file mode 100644 index 000000000000..0bf0b679998e --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx @@ -0,0 +1,82 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useRef, useState} from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type CreateWorkspaceParams from '@libs/API/parameters/CreateWorkspaceParams'; +import Navigation from '@libs/Navigation/Navigation'; +import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; +import UpgradeConfirmation from '@pages/workspace/upgrade/UpgradeConfirmation'; +import UpgradeIntro from '@pages/workspace/upgrade/UpgradeIntro'; +import * as IOU from '@userActions/IOU'; +import CONST from '@src/CONST'; +import * as Policy from '@src/libs/actions/Policy/Policy'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; + +type IOURequestStepUpgradeProps = StackScreenProps; + +function IOURequestStepUpgrade({ + route: { + params: {transactionID, action}, + }, +}: IOURequestStepUpgradeProps) { + const styles = useThemeStyles(); + const feature = CONST.UPGRADE_FEATURE_INTRO_MAPPING.categories; + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); + + const [isUpgraded, setIsUpgraded] = useState(false); + const policyDataRef = useRef(null); + + return ( + + { + Navigation.goBack(); + }} + /> + {!!isUpgraded && ( + { + IOU.setMoneyRequestParticipants(transactionID, [ + { + selected: true, + accountID: 0, + isPolicyExpenseChat: true, + reportID: policyDataRef.current?.expenseChatReportID ?? '-1', + policyID: policyDataRef.current?.policyID, + searchText: policyDataRef.current?.policyName, + }, + ]); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, CONST.IOU.TYPE.SUBMIT, transactionID, policyDataRef.current?.expenseChatReportID ?? '-1')); + }} + policyName="" + isCategorizing + /> + )} + {!isUpgraded && ( + { + const policyData = Policy.createWorkspace(); + setIsUpgraded(true); + policyDataRef.current = policyData; + }} + buttonDisabled={isOffline} + loading={false} + isCategorizing + /> + )} + + ); +} + +export default IOURequestStepUpgrade; diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx index c4fd8bb2eec7..779c09964b75 100644 --- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx +++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx @@ -41,7 +41,8 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_SCAN | typeof SCREENS.MONEY_REQUEST.STEP_SEND_FROM | typeof SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO - | typeof SCREENS.MONEY_REQUEST.STEP_ATTENDEES; + | typeof SCREENS.MONEY_REQUEST.STEP_ATTENDEES + | typeof SCREENS.MONEY_REQUEST.STEP_UPGRADE; type Route = RouteProp; diff --git a/src/pages/workspace/upgrade/UpgradeConfirmation.tsx b/src/pages/workspace/upgrade/UpgradeConfirmation.tsx index 2472e0fca48e..c983f27a4813 100644 --- a/src/pages/workspace/upgrade/UpgradeConfirmation.tsx +++ b/src/pages/workspace/upgrade/UpgradeConfirmation.tsx @@ -1,5 +1,6 @@ import React from 'react'; import ConfirmationPage from '@components/ConfirmationPage'; +import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -9,9 +10,10 @@ import ROUTES from '@src/ROUTES'; type Props = { policyName: string; onConfirmUpgrade: () => void; + isCategorizing?: boolean; }; -function UpgradeConfirmation({policyName, onConfirmUpgrade}: Props) { +function UpgradeConfirmation({policyName, onConfirmUpgrade, isCategorizing}: Props) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -19,16 +21,20 @@ function UpgradeConfirmation({policyName, onConfirmUpgrade}: Props) { - {translate('workspace.upgrade.completed.successMessage', {policyName})}{' '} - Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION)} - > - {translate('workspace.upgrade.completed.viewSubscription')} - {' '} - {translate('workspace.upgrade.completed.moreDetails')} - + isCategorizing ? ( + {translate('workspace.upgrade.completed.categorizeMessage')} + ) : ( + <> + {translate('workspace.upgrade.completed.successMessage', {policyName})}{' '} + Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION)} + > + {translate('workspace.upgrade.completed.viewSubscription')} + {' '} + {translate('workspace.upgrade.completed.moreDetails')} + + ) } shouldShowButton onButtonPress={onConfirmUpgrade} diff --git a/src/pages/workspace/upgrade/UpgradeIntro.tsx b/src/pages/workspace/upgrade/UpgradeIntro.tsx index f8bdbe5440e3..16361f99f52c 100644 --- a/src/pages/workspace/upgrade/UpgradeIntro.tsx +++ b/src/pages/workspace/upgrade/UpgradeIntro.tsx @@ -21,9 +21,10 @@ type Props = { loading?: boolean; feature: ValueOf; onUpgrade: () => void; + isCategorizing?: boolean; }; -function UpgradeIntro({feature, onUpgrade, buttonDisabled, loading}: Props) { +function UpgradeIntro({feature, onUpgrade, buttonDisabled, loading, isCategorizing}: Props) { const styles = useThemeStyles(); const {isExtraSmallScreenWidth} = useResponsiveLayout(); const {translate} = useLocalize(); @@ -59,7 +60,9 @@ function UpgradeIntro({feature, onUpgrade, buttonDisabled, loading}: Props) { {translate(feature.description)} {translate(`workspace.upgrade.${feature.id}.onlyAvailableOnPlan`)} - {translate(`workspace.upgrade.pricing.amount`)} + + {isCategorizing ? translate(`workspace.upgrade.pricing.collect`) : translate(`workspace.upgrade.pricing.amount`)} + {translate(`workspace.upgrade.pricing.perActiveMember`)}