diff --git a/assets/images/integrationicons/circle-slash.svg b/assets/images/integrationicons/circle-slash.svg
new file mode 100644
index 000000000000..ff09f67d3a88
--- /dev/null
+++ b/assets/images/integrationicons/circle-slash.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/assets/images/integrationicons/microsoft-dynamics-icon-square.svg b/assets/images/integrationicons/microsoft-dynamics-icon-square.svg
new file mode 100644
index 000000000000..37dc57c5ae66
--- /dev/null
+++ b/assets/images/integrationicons/microsoft-dynamics-icon-square.svg
@@ -0,0 +1,30 @@
+
+
\ No newline at end of file
diff --git a/assets/images/integrationicons/oracle-icon-square.svg b/assets/images/integrationicons/oracle-icon-square.svg
new file mode 100644
index 000000000000..427e34ad2d69
--- /dev/null
+++ b/assets/images/integrationicons/oracle-icon-square.svg
@@ -0,0 +1,17 @@
+
+
\ No newline at end of file
diff --git a/assets/images/integrationicons/sap-icon-square.svg b/assets/images/integrationicons/sap-icon-square.svg
new file mode 100644
index 000000000000..6043a3ae07b5
--- /dev/null
+++ b/assets/images/integrationicons/sap-icon-square.svg
@@ -0,0 +1,22 @@
+
+
\ No newline at end of file
diff --git a/src/CONST.ts b/src/CONST.ts
index 448af0b44569..92b4d2eaacf5 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -69,6 +69,10 @@ const ONBOARDING_ACCOUNTING_MAPPING = {
netsuite: 'NetSuite',
intacct: 'Sage Intacct',
quickbooksDesktop: 'QuickBooks Desktop',
+ sap: 'SAP',
+ oracle: 'Oracle',
+ microsoftDynamics: 'Microsoft Dynamics',
+ other: 'Other',
};
const connectionsVideoPaths = {
@@ -273,7 +277,7 @@ type OnboardingPurpose = ValueOf;
type OnboardingCompanySize = ValueOf;
-type OnboardingAccounting = ValueOf | null;
+type OnboardingAccounting = keyof typeof CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY | null;
const onboardingInviteTypes = {
IOU: 'iou',
@@ -3005,6 +3009,10 @@ const CONST = {
financialForce: 'FinancialForce',
billCom: 'Bill.com',
zenefits: 'Zenefits',
+ sap: 'SAP',
+ oracle: 'Oracle',
+ microsoftDynamics: 'Microsoft Dynamics',
+ other: 'Other',
},
AUTH_HELP_LINKS: {
intacct:
diff --git a/src/components/FeatureTrainingModal.tsx b/src/components/FeatureTrainingModal.tsx
index cc3b7f71e826..4832e71ce248 100644
--- a/src/components/FeatureTrainingModal.tsx
+++ b/src/components/FeatureTrainingModal.tsx
@@ -137,7 +137,7 @@ function FeatureTrainingModal({
illustrationAspectRatio: illustrationAspectRatioProp,
image,
contentFitImage,
- width = variables.onboardingModalWidth,
+ width = variables.featureTrainingModalWidth,
title = '',
description = '',
secondaryDescription = '',
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index 28e133620756..ac81fbb3d55c 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -118,11 +118,15 @@ import ImageCropCircleMask from '@assets/images/image-crop-circle-mask.svg';
import ImageCropSquareMask from '@assets/images/image-crop-square-mask.svg';
import Inbox from '@assets/images/inbox.svg';
import Info from '@assets/images/info.svg';
+import CircleSlash from '@assets/images/integrationicons/circle-slash.svg';
+import MicrosoftDynamicsSquare from '@assets/images/integrationicons/microsoft-dynamics-icon-square.svg';
import NetSuiteSquare from '@assets/images/integrationicons/netsuite-icon-square.svg';
+import OracleSquare from '@assets/images/integrationicons/oracle-icon-square.svg';
import QBDSquare from '@assets/images/integrationicons/qbd-icon-square.svg';
import QBOCircle from '@assets/images/integrationicons/qbo-icon-circle.svg';
import QBOSquare from '@assets/images/integrationicons/qbo-icon-square.svg';
import SageIntacctSquare from '@assets/images/integrationicons/sage-intacct-icon-square.svg';
+import SapSquare from '@assets/images/integrationicons/sap-icon-square.svg';
import XeroCircle from '@assets/images/integrationicons/xero-icon-circle.svg';
import XeroSquare from '@assets/images/integrationicons/xero-icon-square.svg';
import InvoiceGeneric from '@assets/images/invoice-generic.svg';
@@ -274,6 +278,7 @@ export {
CreditCard,
CreditCardHourglass,
CreditCardExclamation,
+ CircleSlash,
DeletedRoomAvatar,
Document,
DocumentSlash,
@@ -423,6 +428,9 @@ export {
NetSuiteSquare,
XeroCircle,
QBOCircle,
+ MicrosoftDynamicsSquare,
+ OracleSquare,
+ SapSquare,
Filters,
CalendarSolid,
Filter,
diff --git a/src/components/RadioButtonWithLabel.tsx b/src/components/RadioButtonWithLabel.tsx
index 43f44065f5d9..d51c8102b0b2 100644
--- a/src/components/RadioButtonWithLabel.tsx
+++ b/src/components/RadioButtonWithLabel.tsx
@@ -1,4 +1,4 @@
-import type {ComponentType} from 'react';
+import type {ReactNode} from 'react';
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
@@ -21,24 +21,27 @@ type RadioButtonWithLabelProps = {
/** Text that appears next to check box */
label?: string;
- /** Component to display for label */
- LabelComponent?: ComponentType;
+ /** React element to display for the label */
+ labelElement?: ReactNode;
- /** Should the input be styled for errors */
+ /** Should the input be styled for errors */
hasError?: boolean;
/** Error text to display */
errorText?: string;
+
+ /** Additional styles to apply to the wrapper */
+ wrapperStyle?: StyleProp;
};
const PressableWithFeedback = Pressables.PressableWithFeedback;
-function RadioButtonWithLabel({LabelComponent, style, label = '', hasError = false, errorText = '', isChecked, onPress}: RadioButtonWithLabelProps) {
+function RadioButtonWithLabel({labelElement, style, label = '', hasError = false, errorText = '', isChecked, onPress, wrapperStyle}: RadioButtonWithLabelProps) {
const styles = useThemeStyles();
const defaultStyles = [styles.flexRow, styles.alignItemsCenter];
- if (!label && !LabelComponent) {
- throw new Error('Must provide at least label or LabelComponent prop');
+ if (!label && !labelElement) {
+ throw new Error('Must provide at least label or labelComponent prop');
}
return (
<>
@@ -54,13 +57,13 @@ function RadioButtonWithLabel({LabelComponent, style, label = '', hasError = fal
accessible={false}
onPress={onPress}
style={[styles.flexRow, styles.flexWrap, styles.flexShrink1, styles.alignItemsCenter]}
- wrapperStyle={[styles.flex1, styles.ml3, styles.pr2]}
+ wrapperStyle={[styles.flex1, styles.ml3, styles.pr2, wrapperStyle]}
// disable hover style when disabled
hoverDimmingValue={0.8}
pressDimmingValue={0.5}
>
{!!label && {label}}
- {!!LabelComponent && }
+ {!!labelElement && labelElement}
diff --git a/src/languages/en.ts b/src/languages/en.ts
index b5adf3db2d2e..abb72fad0543 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -2039,7 +2039,7 @@ const translations = {
},
accounting: {
title: 'Do you use any accounting software?',
- noneOfAbove: 'None of the above',
+ none: 'None',
},
error: {
requiredFirstName: 'Please input your first name to continue',
@@ -4337,6 +4337,9 @@ const translations = {
xero: 'Xero',
netsuite: 'NetSuite',
intacct: 'Sage Intacct',
+ sap: 'SAP',
+ oracle: 'Oracle',
+ microsoftDynamics: 'Microsoft Dynamics',
talkYourOnboardingSpecialist: 'Chat with your setup specialist.',
talkYourAccountManager: 'Chat with your account manager.',
talkToConcierge: 'Chat with Concierge.',
@@ -4365,7 +4368,7 @@ const translations = {
import: 'Import',
export: 'Export',
advanced: 'Advanced',
- other: 'Other integrations',
+ other: 'Other',
syncNow: 'Sync now',
disconnect: 'Disconnect',
reinstall: 'Reinstall connector',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index dc4cdc9fa59a..90409fe5a934 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2043,7 +2043,7 @@ const translations = {
},
accounting: {
title: '¿Utilizas algún software de contabilidad?',
- noneOfAbove: 'Ninguno de los anteriores',
+ none: 'Ninguno',
},
error: {
requiredFirstName: 'Introduce tu nombre para continuar',
@@ -4348,6 +4348,9 @@ const translations = {
xero: 'Xero',
netsuite: 'NetSuite',
intacct: 'Sage Intacct',
+ sap: 'SAP',
+ oracle: 'Oracle',
+ microsoftDynamics: 'Microsoft Dynamics',
talkYourOnboardingSpecialist: 'Chatea con tu especialista asignado.',
talkYourAccountManager: 'Chatea con tu gestor de cuenta.',
talkToConcierge: 'Chatear con Concierge.',
@@ -4376,7 +4379,7 @@ const translations = {
import: 'Importar',
export: 'Exportar',
advanced: 'Avanzado',
- other: 'Otras integraciones',
+ other: 'Otro',
syncNow: 'Sincronizar ahora',
disconnect: 'Desconectar',
reinstall: 'Reinstalar el conector',
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 9d27087b7a1b..0535c1928804 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -9687,7 +9687,7 @@ function prepareOnboardingOnyxData(
return;
}
- const integrationName = userReportedIntegration ? CONST.ONBOARDING_ACCOUNTING_MAPPING[userReportedIntegration] : '';
+ const integrationName = userReportedIntegration ? CONST.ONBOARDING_ACCOUNTING_MAPPING[userReportedIntegration as keyof typeof CONST.ONBOARDING_ACCOUNTING_MAPPING] : '';
const assignedGuideEmail = getPolicy(targetChatPolicyID)?.assignedGuide?.email ?? 'Setup Specialist';
const assignedGuidePersonalDetail = Object.values(allPersonalDetails ?? {}).find((personalDetail) => personalDetail?.login === assignedGuideEmail);
let assignedGuideAccountID: number;
@@ -10076,7 +10076,7 @@ function prepareOnboardingOnyxData(
key: `${ONYXKEYS.COLLECTION.POLICY}${onboardingPolicyID}`,
value: {
areConnectionsEnabled: true,
- ...(requiresControlPlan.includes(userReportedIntegration)
+ ...(requiresControlPlan.includes(userReportedIntegration as AllConnectionName)
? {
type: CONST.POLICY.TYPE.CORPORATE,
}
diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx
index 25bd0b3a31c6..c178947fac9e 100644
--- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx
+++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx
@@ -1,16 +1,19 @@
import HybridAppModule from '@expensify/react-native-hybrid-app';
-import React, {useContext, useEffect, useMemo, useState} from 'react';
-import {InteractionManager} from 'react-native';
+import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
+import {InteractionManager, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
+import type {SvgProps} from 'react-native-svg';
import Button from '@components/Button';
import CustomStatusBarAndBackgroundContext from '@components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext';
+import FixedFooter from '@components/FixedFooter';
import FormHelpMessage from '@components/FormHelpMessage';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
+import {PressableWithoutFeedback} from '@components/Pressable';
+import RadioButtonWithLabel from '@components/RadioButtonWithLabel';
import ScreenWrapper from '@components/ScreenWrapper';
-import SelectionList from '@components/SelectionList';
-import RadioListItem from '@components/SelectionList/RadioListItem';
+import ScrollView from '@components/ScrollView';
import type {ListItem} from '@components/SelectionList/types';
import Text from '@components/Text';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
@@ -32,12 +35,61 @@ import Navigation from '@libs/Navigation/Navigation';
import {isPaidGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import variables from '@styles/variables';
import CONFIG from '@src/CONFIG';
-import CONST from '@src/CONST';
import type {OnboardingAccounting} from '@src/CONST';
+import CONST from '@src/CONST';
+import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {} from '@src/types/onyx/Bank';
import type {BaseOnboardingAccountingProps} from './types';
+type Integration = {
+ key: OnboardingAccounting;
+ icon: React.FC;
+ translationKey: TranslationPaths;
+};
+
+const integrations: Integration[] = [
+ {
+ key: 'quickbooksOnline',
+ icon: Expensicons.QBOCircle,
+ translationKey: 'workspace.accounting.qbo',
+ },
+ {
+ key: 'quickbooksDesktop',
+ icon: Expensicons.QBDSquare,
+ translationKey: 'workspace.accounting.qbd',
+ },
+ {
+ key: 'xero',
+ icon: Expensicons.XeroCircle,
+ translationKey: 'workspace.accounting.xero',
+ },
+ {
+ key: 'netsuite',
+ icon: Expensicons.NetSuiteSquare,
+ translationKey: 'workspace.accounting.netsuite',
+ },
+ {
+ key: 'intacct',
+ icon: Expensicons.IntacctSquare,
+ translationKey: 'workspace.accounting.intacct',
+ },
+ {
+ key: 'sap',
+ icon: Expensicons.SapSquare,
+ translationKey: 'workspace.accounting.sap',
+ },
+ {
+ key: 'oracle',
+ icon: Expensicons.OracleSquare,
+ translationKey: 'workspace.accounting.oracle',
+ },
+ {
+ key: 'microsoftDynamics',
+ icon: Expensicons.MicrosoftDynamicsSquare,
+ translationKey: 'workspace.accounting.microsoftDynamics',
+ },
+];
+
type OnboardingListItem = ListItem & {
keyForList: OnboardingAccounting;
};
@@ -89,177 +141,208 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount
}, [isLoading, prevIsLoading, setRootStatusBarEnabled]);
const accountingOptions: OnboardingListItem[] = useMemo(() => {
- const policyAccountingOptions = Object.values(CONST.POLICY.CONNECTIONS.NAME)
- .map((connectionName): OnboardingListItem | undefined => {
- let text;
- let accountingIcon;
- switch (connectionName) {
- case CONST.POLICY.CONNECTIONS.NAME.QBO: {
- text = translate('workspace.accounting.qbo');
- accountingIcon = Expensicons.QBOCircle;
- break;
- }
- case CONST.POLICY.CONNECTIONS.NAME.QBD: {
- text = translate('workspace.accounting.qbd');
- accountingIcon = Expensicons.QBDSquare;
- break;
- }
- case CONST.POLICY.CONNECTIONS.NAME.XERO: {
- text = translate('workspace.accounting.xero');
- accountingIcon = Expensicons.XeroCircle;
- break;
- }
- case CONST.POLICY.CONNECTIONS.NAME.NETSUITE: {
- text = translate('workspace.accounting.netsuite');
- accountingIcon = Expensicons.NetSuiteSquare;
- break;
- }
- case CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT: {
- text = translate('workspace.accounting.intacct');
- accountingIcon = Expensicons.IntacctSquare;
- break;
- }
- default: {
- return;
- }
- }
- return {
- keyForList: connectionName,
- text,
- leftElement: (
-
- ),
- isSelected: userReportedIntegration === connectionName,
- };
- })
- .filter((item): item is OnboardingListItem => !!item);
+ const createAccountingOption = (integration: Integration): OnboardingListItem => ({
+ keyForList: integration.key,
+ text: translate(integration.translationKey),
+ leftElement: (
+
+ ),
+ isSelected: userReportedIntegration === integration.key,
+ });
+
const noneAccountingOption: OnboardingListItem = {
keyForList: null,
- text: translate('onboarding.accounting.noneOfAbove'),
+ text: translate('onboarding.accounting.none'),
leftElement: (
),
isSelected: userReportedIntegration === null,
};
- return [...policyAccountingOptions, noneAccountingOption];
- }, [StyleUtils, styles.mr3, styles.onboardingSmallIcon, theme.success, translate, userReportedIntegration]);
- const footerContent = (
- <>
- {!!error && (
-
- )}
-