From aa730068a4cd317f6015d66589e1b258e4f75db0 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Tue, 28 May 2024 10:52:05 +0200 Subject: [PATCH 01/10] feat: Subscription size screen UI --- src/ONYXKEYS.ts | 3 + src/ROUTES.ts | 1 + src/SCREENS.ts | 1 + src/languages/en.ts | 17 ++++ src/languages/es.ts | 17 ++++ .../ModalStackNavigators/index.tsx | 1 + .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 3 + .../SubscriptionSize/SubscriptionSizePage.tsx | 79 ++++++++++++++++++ .../substeps/Confirmation.tsx | 80 +++++++++++++++++++ .../SubscriptionSize/substeps/Size.tsx | 54 +++++++++++++ .../Subscription/SubscriptionSize/utils.ts | 18 +++++ src/types/form/SubscriptionSizeForm.ts | 13 +++ src/types/form/index.ts | 1 + src/types/onyx/Account.ts | 3 + 15 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 src/pages/settings/Subscription/SubscriptionSize/SubscriptionSizePage.tsx create mode 100644 src/pages/settings/Subscription/SubscriptionSize/substeps/Confirmation.tsx create mode 100644 src/pages/settings/Subscription/SubscriptionSize/substeps/Size.tsx create mode 100644 src/pages/settings/Subscription/SubscriptionSize/utils.ts create mode 100644 src/types/form/SubscriptionSizeForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index ddf37fba2354..fe368670b130 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -476,6 +476,8 @@ const ONYXKEYS = { WORKSPACE_TAX_VALUE_FORM_DRAFT: 'workspaceTaxValueFormDraft', NEW_CHAT_NAME_FORM: 'newChatNameForm', NEW_CHAT_NAME_FORM_DRAFT: 'newChatNameFormDraft', + SUBSCRIPTION_SIZE_FORM: 'subscriptionSizeForm', + SUBSCRIPTION_SIZE_FORM_DRAFT: 'subscriptionSizeFormDraft', }, } as const; @@ -533,6 +535,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM]: FormTypes.WorkspaceTaxNameForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm; [ONYXKEYS.FORMS.NEW_CHAT_NAME_FORM]: FormTypes.NewChatNameForm; + [ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM]: FormTypes.SubscriptionSizeForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 47e96f0a501a..586bb8fa83e4 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -89,6 +89,7 @@ const ROUTES = { SETTINGS_PRONOUNS: 'settings/profile/pronouns', SETTINGS_PREFERENCES: 'settings/preferences', SETTINGS_SUBSCRIPTION: 'settings/subscription', + SETTINGS_SUBSCRIPTIONS_SIZE: 'settings/subscription-size', SETTINGS_PRIORITY_MODE: 'settings/preferences/priority-mode', SETTINGS_LANGUAGE: 'settings/preferences/language', SETTINGS_THEME: 'settings/preferences/theme', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 4e7243d0eb2c..265eea287ced 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -106,6 +106,7 @@ const SCREENS = { SUBSCRIPTION: { ROOT: 'Settings_Subscription', + SIZE: 'Settings_Subscription_Size', }, }, SAVE_THE_WORLD: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 635248caaf88..2e4a6902f275 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3106,4 +3106,21 @@ export default { systemMessage: { mergedWithCashTransaction: 'matched a receipt to this transaction.', }, + subscriptionSize: { + title: 'Subscription size', + yourSize: 'Your subscription size is the number of open seats that can be filled by any active member in a given month.', + eachMonth: + 'Each month, your subscription covers up to the number of active members set above. Any time you increase your subscription size, you’ll start a new 12-month subscription at that new size.', + note: 'Note: An active member is anyone who has created, edited, submitted, approved, reimbursed, or exported expense data tied to your company workspace.', + confirmDetails: 'Confirm your new annual subscription details', + subscriptionSize: 'Subscription size', + activeMembers: ({size}) => `${size} active members/month`, + subscriptionRenews: 'Subscription renews', + youCantDowngrade: 'You can’t downgrade during your annual subscription', + youAlreadyCommitted: ({size, date}) => + `You already committed to an annual subscription size of ${size} active members per month until ${date}. You can switch to a pay-per-use subscription on ${date} by disabling auto-renew.`, + error: { + size: 'Please enter a valid subscription size.', + }, + }, } satisfies TranslationBase; diff --git a/src/languages/es.ts b/src/languages/es.ts index 0886e614273a..53d62be2cbad 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3610,4 +3610,21 @@ export default { systemMessage: { mergedWithCashTransaction: 'encontró un recibo para esta transacción.', }, + subscriptionSize: { + title: 'Tamaño de suscripción', + yourSize: 'El tamaño de tu suscripción es el número de plazas abiertas que puede ocupar cualquier miembro activo en un mes determinado.', + eachMonth: + 'Cada mes, tu suscripción cubre hasta el número de miembros activos establecido anteriormente. Cada vez que aumentes el tamaño de tu suscripción, iniciarás una nueva suscripción de 12 meses con ese nuevo tamaño.', + note: 'Nota: Un miembro activo es cualquiera que haya creado, editado, enviado, aprobado, reembolsado, o exportado datos de gastos vinculados al espacio de trabajo de tu empresa.', + confirmDetails: 'Confirma los datos de tu nueva suscripción anual', + subscriptionSize: 'Tamaño de suscripción', + activeMembers: ({size}) => `${size} miembros activos/mes`, + subscriptionRenews: 'Renovación de la suscripción', + youCantDowngrade: 'YNo puedes bajar de categoría durante tu suscripción anual', + youAlreadyCommitted: ({size, date}) => + `Ya se ha comprometido a un tamaño de suscripción anual de ${size} miembros activos al mes hasta el ${date}. Puede cambiar a una suscripción de pago por uso en ${date} desactivando la auto-renovación.`, + error: { + size: 'Please enter a valid subscription size.', + }, + }, } satisfies EnglishTranslation; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 462145e56907..eb4dcb6a43ce 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -215,6 +215,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Profile/CustomStatus/StatusClearAfterPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: () => require('../../../../pages/settings/Profile/CustomStatus/SetDatePage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: () => require('../../../../pages/settings/Profile/CustomStatus/SetTimePage').default as React.ComponentType, + [SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: () => require('../../../../pages/settings/Subscription/SubscriptionSize/SubscriptionSizePage').default as React.ComponentType, [SCREENS.WORKSPACE.RATE_AND_UNIT]: () => require('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage').default as React.ComponentType, [SCREENS.WORKSPACE.RATE_AND_UNIT_RATE]: () => require('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage').default as React.ComponentType, [SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT]: () => require('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 154ab63aad77..4e77edeaa633 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -39,7 +39,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = [SCREENS.SETTINGS.SAVE_THE_WORLD]: [SCREENS.I_KNOW_A_TEACHER, SCREENS.INTRO_SCHOOL_PRINCIPAL, SCREENS.I_AM_A_TEACHER], [SCREENS.SETTINGS.TROUBLESHOOT]: [SCREENS.SETTINGS.CONSOLE], [SCREENS.SEARCH.CENTRAL_PANE]: [SCREENS.SEARCH.REPORT_RHP], - [SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: [], + [SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: [SCREENS.SETTINGS.SUBSCRIPTION.SIZE], }; export default CENTRAL_PANE_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index e9b86993ad43..d001cae3814e 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -277,6 +277,9 @@ const config: LinkingOptions['config'] = { [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: { path: ROUTES.SETTINGS_STATUS_CLEAR_AFTER_TIME, }, + [SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: { + path: ROUTES.SETTINGS_SUBSCRIPTIONS_SIZE, + }, [SCREENS.WORKSPACE.CURRENCY]: { path: ROUTES.WORKSPACE_PROFILE_CURRENCY.route, }, diff --git a/src/pages/settings/Subscription/SubscriptionSize/SubscriptionSizePage.tsx b/src/pages/settings/Subscription/SubscriptionSize/SubscriptionSizePage.tsx new file mode 100644 index 000000000000..1dfc38a1798a --- /dev/null +++ b/src/pages/settings/Subscription/SubscriptionSize/SubscriptionSizePage.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useSubStep from '@hooks/useSubStep'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import Navigation from '@libs/Navigation/Navigation'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {SubscriptionSizeForm} from '@src/types/form'; +import Confirmation from './substeps/Confirmation'; +import Size from './substeps/Size'; + +type SubscriptionSizePageOnyxProps = { + /** The draft values from subscription size form */ + subscriptionSizeForm: OnyxEntry; +}; + +type SubscriptionSizePageProps = SubscriptionSizePageOnyxProps; + +const bodyContent: Array> = [Size, Confirmation]; + +function SubscriptionSizePage({subscriptionSizeForm}: SubscriptionSizePageProps) { + const {translate} = useLocalize(); + // TODO update logic to + // const startFrom = account?.canDowngrade ? 0 : 1; + const CAN_DOWNGRADE = true; + const startFrom = CAN_DOWNGRADE ? 0 : 1; + + const onFinished = () => { + if (CAN_DOWNGRADE) { + // TODO API call will be implemented in next phase + // eslint-disable-next-line no-console + console.log(subscriptionSizeForm); + return; + } + + Navigation.goBack(); + }; + + const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo} = useSubStep({bodyContent, startFrom, onFinished}); + + const onBackButtonPress = () => { + if (screenIndex === 0 && startFrom === 0) { + prevScreen(); + return; + } + + Navigation.goBack(); + }; + + return ( + + + + + ); +} + +SubscriptionSizePage.displayName = 'SubscriptionSizePage'; + +export default withOnyx({ + subscriptionSizeForm: { + key: ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM_DRAFT, + }, +})(SubscriptionSizePage); diff --git a/src/pages/settings/Subscription/SubscriptionSize/substeps/Confirmation.tsx b/src/pages/settings/Subscription/SubscriptionSize/substeps/Confirmation.tsx new file mode 100644 index 000000000000..36015e5d4b88 --- /dev/null +++ b/src/pages/settings/Subscription/SubscriptionSize/substeps/Confirmation.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import Button from '@components/Button'; +import FixedFooter from '@components/FixedFooter'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {getNewSubscriptionRenewalDate} from '@pages/settings/Subscription/SubscriptionSize/utils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {SubscriptionSizeForm} from '@src/types/form'; +import INPUT_IDS from '@src/types/form/SubscriptionSizeForm'; + +type ConfirmationOnyxProps = { + /** The draft values from subscription size form */ + subscriptionSizeForm: OnyxEntry; +}; +type ConfirmationProps = ConfirmationOnyxProps & SubStepProps; + +function Confirmation({subscriptionSizeForm, onNext}: ConfirmationProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {isOffline} = useNetwork(); + const subscriptionRenewalDate = getNewSubscriptionRenewalDate(); + + // TODO remove that in next phase and replace with canDowngrade from account + // we also have to check if the amount of active members is less than the current amount of active members and if acccount?.canDowngrade is true + // if so then we can't downgrade + const CAN_DOWNGRADE = true; + // TODO replace with real data once BE is ready + const SUBSCRIPTION_UNTIL = subscriptionRenewalDate; + + return ( + + {CAN_DOWNGRADE ? ( + <> + {translate('subscriptionSize.confirmDetails')} + + + + ) : ( + <> + {translate('subscriptionSize.youCantDowngrade')} + + {translate('subscriptionSize.youAlreadyCommitted', {size: subscriptionSizeForm ? subscriptionSizeForm[INPUT_IDS.SUBSCRIPTION_SIZE] : 0, date: SUBSCRIPTION_UNTIL})} + + + )} + +