From accc8a138100172748051974350c18deeae406bf Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 13 Sep 2024 17:44:06 +0700 Subject: [PATCH 01/41] feat: add employee and accounting page to onboarding flow --- src/CONST.ts | 25 ++- src/ONYXKEYS.ts | 6 +- src/ROUTES.ts | 8 + src/SCREENS.ts | 2 + src/languages/en.ts | 13 ++ src/languages/es.ts | 14 ++ .../Navigators/OnboardingModalNavigator.tsx | 10 ++ src/libs/Navigation/linkingConfig/config.ts | 8 + src/libs/Navigation/types.ts | 6 + src/libs/NavigationUtils.ts | 9 +- src/libs/actions/Welcome/OnboardingFlow.ts | 4 + src/libs/actions/Welcome/index.ts | 7 +- .../BaseOnboardingAccounting.tsx | 156 ++++++++++++++++++ .../OnboardingAccounting/index.native.tsx | 17 ++ src/pages/OnboardingAccounting/index.tsx | 25 +++ src/pages/OnboardingAccounting/types.ts | 12 ++ .../BaseOnboardingEmployees.tsx | 103 ++++++++++++ .../OnboardingEmployees/index.native.tsx | 17 ++ src/pages/OnboardingEmployees/index.tsx | 25 +++ src/pages/OnboardingEmployees/types.ts | 12 ++ .../BaseOnboardingPurpose.tsx | 2 +- 21 files changed, 476 insertions(+), 5 deletions(-) create mode 100644 src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx create mode 100644 src/pages/OnboardingAccounting/index.native.tsx create mode 100644 src/pages/OnboardingAccounting/index.tsx create mode 100644 src/pages/OnboardingAccounting/types.ts create mode 100644 src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx create mode 100644 src/pages/OnboardingEmployees/index.native.tsx create mode 100644 src/pages/OnboardingEmployees/index.tsx create mode 100644 src/pages/OnboardingEmployees/types.ts diff --git a/src/CONST.ts b/src/CONST.ts index cd4d2b24e97a..1462fc7f7af1 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -130,12 +130,22 @@ const onboardingEmployerOrSubmitMessage: OnboardingMessageType = { type OnboardingPurposeType = ValueOf; +type OnboardingCompanySizeType = ValueOf; + const onboardingInviteTypes = { IOU: 'iou', INVOICE: 'invoice', CHAT: 'chat', } as const; +const onboardingCompanySize = { + MICRO: 'micro', + SMALL: 'small', + MEDIUM_SMALL: 'mediumSmall', + MEDIUM: 'medium', + LARGE: 'large', +}; + type OnboardingInviteType = ValueOf; type OnboardingTaskType = { @@ -4431,6 +4441,7 @@ const CONST = { ONBOARDING_CHOICES: {...onboardingChoices}, SELECTABLE_ONBOARDING_CHOICES: {...selectableOnboardingChoices}, ONBOARDING_INVITE_TYPES: {...onboardingInviteTypes}, + ONBOARDING_COMPANY_SIZE: {...onboardingCompanySize}, ACTIONABLE_TRACK_EXPENSE_WHISPER_MESSAGE: 'What would you like to do with this expense?', ONBOARDING_CONCIERGE: { [onboardingChoices.EMPLOYER]: @@ -5731,6 +5742,18 @@ type FeedbackSurveyOptionID = ValueOf; type CancellationType = ValueOf; -export type {Country, IOUAction, IOUType, RateAndUnit, OnboardingPurposeType, IOURequestType, SubscriptionType, FeedbackSurveyOptionID, CancellationType, OnboardingInviteType}; +export type { + Country, + IOUAction, + IOUType, + RateAndUnit, + OnboardingPurposeType, + OnboardingCompanySizeType, + IOURequestType, + SubscriptionType, + FeedbackSurveyOptionID, + CancellationType, + OnboardingInviteType, +}; export default CONST; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 1d33b6892d51..940d6bff46e1 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1,6 +1,6 @@ import type {ValueOf} from 'type-fest'; import type CONST from './CONST'; -import type {OnboardingPurposeType} from './CONST'; +import type {OnboardingCompanySizeType, OnboardingPurposeType} from './CONST'; import type * as FormTypes from './types/form'; import type * as OnyxTypes from './types/onyx'; import type Onboarding from './types/onyx/Onboarding'; @@ -340,6 +340,9 @@ const ONYXKEYS = { /** Onboarding policyID selected by the user during Onboarding flow */ ONBOARDING_POLICY_ID: 'onboardingPolicyID', + /** Onboarding company size selected by the user during Onboarding flow */ + ONBOARDING_COMPANY_SIZE: 'onboardingCompanySize', + /** Onboarding Purpose selected by the user during Onboarding flow */ ONBOARDING_ADMINS_CHAT_REPORT_ID: 'onboardingAdminsChatReportID', @@ -934,6 +937,7 @@ type OnyxValuesMapping = { [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; [ONYXKEYS.MAX_CANVAS_WIDTH]: number; [ONYXKEYS.ONBOARDING_PURPOSE_SELECTED]: OnboardingPurposeType; + [ONYXKEYS.ONBOARDING_COMPANY_SIZE]: OnboardingCompanySizeType; [ONYXKEYS.ONBOARDING_ERROR_MESSAGE]: string; [ONYXKEYS.ONBOARDING_POLICY_ID]: string; [ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID]: string; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2b6268c05b3a..bf3cd00cd87b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1102,6 +1102,14 @@ const ROUTES = { route: 'onboarding/work', getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/work`, backTo), }, + ONBOARDING_EMPLOYEES: { + route: 'onboarding/employees', + getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/employees`, backTo), + }, + ONBOARDING_ACCOUNTING: { + route: 'onboarding/accounting', + getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/accounting`, backTo), + }, ONBOARDING_PURPOSE: { route: 'onboarding/purpose', getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/purpose`, backTo), diff --git a/src/SCREENS.ts b/src/SCREENS.ts index e8a3698e9a95..343818ad50c0 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -502,6 +502,8 @@ const SCREENS = { PERSONAL_DETAILS: 'Onboarding_Personal_Details', PURPOSE: 'Onboarding_Purpose', WORK: 'Onboarding_Work', + EMPLOYEES: 'Onboarding_Employees', + ACCOUNTING: 'Onboarding_accounting', }, WELCOME_VIDEO: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 5a1e242519c2..a34dddb3ecc6 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1626,6 +1626,19 @@ export default { [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: 'Chat and split expenses with friends', [CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: 'Something else', }, + employees: { + title: 'How many employees do you have?', + [CONST.ONBOARDING_COMPANY_SIZE.MICRO]: '1-10 employees', + [CONST.ONBOARDING_COMPANY_SIZE.SMALL]: '11-50 employees', + [CONST.ONBOARDING_COMPANY_SIZE.MEDIUM_SMALL]: '51-100 employees', + [CONST.ONBOARDING_COMPANY_SIZE.MEDIUM]: '101-1,000 employees', + [CONST.ONBOARDING_COMPANY_SIZE.LARGE]: 'More than 1,000 employees', + }, + accounting: { + title: 'Do you use any accounting software?', + description: 'Connect your accounting software directly to Expensify', + noneOfAbove: 'None of the above', + }, error: { requiredFirstName: 'Please input your first name to continue.', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 0ce9c70a9b56..625d25c8ba4e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1635,6 +1635,20 @@ export default { [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: 'Chatea y divide gastos con tus amigos', [CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: 'Algo más', }, + employees: { + title: 'How many empleados do you have?', + description: `Don't worry, you can change this later.`, + [CONST.ONBOARDING_COMPANY_SIZE.MICRO]: '1-10 empleados', + [CONST.ONBOARDING_COMPANY_SIZE.SMALL]: '11-50 empleados', + [CONST.ONBOARDING_COMPANY_SIZE.MEDIUM_SMALL]: '51-100 empleados', + [CONST.ONBOARDING_COMPANY_SIZE.MEDIUM]: '101-1,000 empleados', + [CONST.ONBOARDING_COMPANY_SIZE.LARGE]: 'More than 1,000 empleados', + }, + accounting: { + title: 'Do you use any accounting software?', + description: 'Connect your accounting software directly to Expensify', + noneOfAbove: 'None of the above', + }, error: { requiredFirstName: 'Introduce tu nombre para continuar.', }, diff --git a/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx index 4eb1409f192d..eb852eb6a108 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx @@ -12,6 +12,8 @@ import OnboardingModalNavigatorScreenOptions from '@libs/Navigation/AppNavigator import Navigation from '@libs/Navigation/Navigation'; import type {OnboardingModalNavigatorParamList} from '@libs/Navigation/types'; import OnboardingRefManager from '@libs/OnboardingRefManager'; +import OnboardingAccounting from '@pages/OnboardingAccounting'; +import OnboardingEmployees from '@pages/OnboardingEmployees'; import OnboardingPersonalDetails from '@pages/OnboardingPersonalDetails'; import OnboardingPurpose from '@pages/OnboardingPurpose'; import OnboardingWork from '@pages/OnboardingWork'; @@ -87,6 +89,14 @@ function OnboardingModalNavigator() { name={SCREENS.ONBOARDING.WORK} component={OnboardingWork} /> + + diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 87656c5997db..e23edad21ec9 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -123,6 +123,14 @@ const config: LinkingOptions['config'] = { path: ROUTES.ONBOARDING_WORK.route, exact: true, }, + [SCREENS.ONBOARDING.EMPLOYEES]: { + path: ROUTES.ONBOARDING_EMPLOYEES.route, + exact: true, + }, + [SCREENS.ONBOARDING.ACCOUNTING]: { + path: ROUTES.ONBOARDING_ACCOUNTING.route, + exact: true, + }, }, }, [NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3dc7c708f0b6..6ee26850f4e8 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1289,6 +1289,12 @@ type OnboardingModalNavigatorParamList = { [SCREENS.ONBOARDING.WORK]: { backTo?: string; }; + [SCREENS.ONBOARDING.EMPLOYEES]: { + backTo?: string; + }; + [SCREENS.ONBOARDING.ACCOUNTING]: { + backTo?: string; + }; }; type WelcomeVideoModalNavigatorParamList = { diff --git a/src/libs/NavigationUtils.ts b/src/libs/NavigationUtils.ts index ea1710b9931c..f4ce80ac3a4e 100644 --- a/src/libs/NavigationUtils.ts +++ b/src/libs/NavigationUtils.ts @@ -17,7 +17,14 @@ const CENTRAL_PANE_SCREEN_NAMES = new Set([ SCREENS.REPORT, ]); -const ONBOARDING_SCREEN_NAMES = new Set([SCREENS.ONBOARDING.PERSONAL_DETAILS, SCREENS.ONBOARDING.PURPOSE, SCREENS.ONBOARDING.WORK, SCREENS.ONBOARDING_MODAL.ONBOARDING]); +const ONBOARDING_SCREEN_NAMES = new Set([ + SCREENS.ONBOARDING.PERSONAL_DETAILS, + SCREENS.ONBOARDING.PURPOSE, + SCREENS.ONBOARDING.WORK, + SCREENS.ONBOARDING_MODAL.ONBOARDING, + SCREENS.ONBOARDING.EMPLOYEES, + SCREENS.ONBOARDING.ACCOUNTING, +]); const removePolicyIDParamFromState = (state: State) => { const stateCopy = cloneDeep(state); diff --git a/src/libs/actions/Welcome/OnboardingFlow.ts b/src/libs/actions/Welcome/OnboardingFlow.ts index 4e780090299d..3e16c36415bc 100644 --- a/src/libs/actions/Welcome/OnboardingFlow.ts +++ b/src/libs/actions/Welcome/OnboardingFlow.ts @@ -64,6 +64,10 @@ function adaptOnboardingRouteState() { name: SCREENS.ONBOARDING.WORK, params: currentRoute?.params, }, + { + name: SCREENS.ONBOARDING.EMPLOYEES, + params: currentRoute?.params, + }, {...currentRoute}, ], } as Readonly>; diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts index f5995aa1e2a9..22a087264c59 100644 --- a/src/libs/actions/Welcome/index.ts +++ b/src/libs/actions/Welcome/index.ts @@ -5,7 +5,7 @@ import * as API from '@libs/API'; import {WRITE_COMMANDS} from '@libs/API/types'; import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; -import type {OnboardingPurposeType} from '@src/CONST'; +import type {OnboardingCompanySizeType, OnboardingPurposeType} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type Onboarding from '@src/types/onyx/Onboarding'; @@ -144,6 +144,10 @@ function setOnboardingPurposeSelected(value: OnboardingPurposeType) { Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, value ?? null); } +function setOnboardingCompanySize(value: OnboardingCompanySizeType) { + Onyx.set(ONYXKEYS.ONBOARDING_COMPANY_SIZE, value); +} + function setOnboardingErrorMessage(value: string) { Onyx.set(ONYXKEYS.ONBOARDING_ERROR_MESSAGE, value ?? null); } @@ -236,4 +240,5 @@ export { completeHybridAppOnboarding, handleHybridAppOnboarding, setOnboardingErrorMessage, + setOnboardingCompanySize, }; diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx new file mode 100644 index 000000000000..bb05a456ba60 --- /dev/null +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -0,0 +1,156 @@ +import React, {useMemo, useState} from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import FormHelpMessage from '@components/FormHelpMessage'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import type {ListItem} from '@components/SelectionList/types'; +import UserListItem from '@components/SelectionList/UserListItem'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import * as OnboardingFlow from '@userActions/Welcome/OnboardingFlow'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type {Icon} from '@src/types/onyx/OnyxCommon'; +import type {BaseOnboardingAccountingProps} from './types'; + +function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboardingAccountingProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); + const [userReportedIntegration, setUserReportedIntegration] = useState(null); + const [error, setError] = useState(''); + + const accountingOptions: ListItem[] = useMemo(() => { + const policyAccountingOptions: ListItem[] = Object.values(CONST.POLICY.CONNECTIONS.NAME).map((connectionName): ListItem => { + let text; + let icons: Icon[]; + switch (connectionName) { + case CONST.POLICY.CONNECTIONS.NAME.QBO: { + text = translate('workspace.accounting.qbo'); + icons = [ + { + source: Expensicons.QBOCircle, + type: 'avatar', + }, + ]; + break; + } + case CONST.POLICY.CONNECTIONS.NAME.XERO: { + text = translate('workspace.accounting.xero'); + icons = [ + { + source: Expensicons.XeroCircle, + type: 'avatar', + }, + ]; + break; + } + case CONST.POLICY.CONNECTIONS.NAME.NETSUITE: { + text = translate('workspace.accounting.netsuite'); + icons = [ + { + source: Expensicons.NetSuiteSquare, + type: 'avatar', + }, + ]; + break; + } + default: { + text = translate('workspace.accounting.intacct'); + icons = [ + { + source: Expensicons.IntacctSquare, + type: 'avatar', + }, + ]; + break; + } + } + return { + keyForList: connectionName, + text, + icons, + }; + }); + const noneAccountingOption: ListItem = { + text: translate('onboarding.accounting.noneOfAbove'), + keyForList: null, + icons: [ + { + source: Expensicons.Close, + type: 'avatar', + }, + ], + }; + return [...policyAccountingOptions, noneAccountingOption]; + }, [translate]); + + const footerContent = ( + <> + {!!error && ( + + )} +