diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index bcdd4c83849d..3685c5d6f98d 100644
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -103,9 +103,12 @@ const chatTypes = {
const ONBOARDING_ACCOUNTING_MAPPING = {
quickbooksOnline: 'QuickBooks Online',
+ intuitEnterpriseSuite: 'Intuit Enterprise Suite',
xero: 'Xero',
netsuite: 'NetSuite',
intacct: 'Sage Intacct',
+ financialforce: 'Certinia',
+ rillet: 'Rillet',
quickbooksDesktop: 'QuickBooks Desktop',
sap: 'SAP',
oracle: 'Oracle',
diff --git a/src/languages/de.ts b/src/languages/de.ts
index c3f2566239ed..955d61a1e28d 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -3346,8 +3346,7 @@ ${amount} für ${merchant} – ${date}`,
},
accounting: {
title: 'Verwendest du eine Buchhaltungssoftware?',
- none: 'Keine',
- otherAccountingSoftware: 'Deine Buchhaltungssoftware',
+ otherAccountingSoftware: 'Name der Software',
},
interestedFeatures: {
title: 'An welchen Funktionen bist du interessiert?',
diff --git a/src/languages/el.ts b/src/languages/el.ts
index 8f7c78a93910..422a52f3f6e4 100644
--- a/src/languages/el.ts
+++ b/src/languages/el.ts
@@ -3399,8 +3399,7 @@ ${amount} για ${merchant} - ${date}`,
},
accounting: {
title: 'Χρησιμοποιείτε κάποιο λογιστικό λογισμικό;',
- none: 'Κανένα',
- otherAccountingSoftware: 'Το λογιστικό σας λογισμικό',
+ otherAccountingSoftware: 'Όνομα λογισμικού',
},
interestedFeatures: {
title: 'Σε ποιες δυνατότητες ενδιαφέρεστε;',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 0ab827f6b67a..aaa67f9dcbb0 100644
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -3440,8 +3440,7 @@ const translations = {
},
accounting: {
title: 'Do you use any accounting software?',
- none: 'None',
- otherAccountingSoftware: 'Your accounting software',
+ otherAccountingSoftware: 'Name of software',
},
interestedFeatures: {
title: 'What features are you interested in?',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 4eaad6f99764..530be84f1235 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -3254,8 +3254,7 @@ ${amount} para ${merchant} - ${date}`,
},
accounting: {
title: '¿Utilizas algún software de contabilidad?',
- none: 'Ninguno',
- otherAccountingSoftware: 'Tu software de contabilidad',
+ otherAccountingSoftware: 'Nombre del software',
},
interestedFeatures: {
title: '¿Qué funciones te interesan?',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index cb157ffffb4c..70bf85ce6f6a 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -3354,8 +3354,7 @@ ${amount} pour ${merchant} - ${date}`,
},
accounting: {
title: 'Utilisez-vous un logiciel de comptabilité ?',
- none: 'Aucun',
- otherAccountingSoftware: 'Votre logiciel de comptabilité',
+ otherAccountingSoftware: 'Nom du logiciel',
},
interestedFeatures: {
title: 'Quelles fonctionnalités vous intéressent ?',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 8276553f8ee2..3ddfad9c1063 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -3334,8 +3334,7 @@ ${amount} per ${merchant} - ${date}`,
},
accounting: {
title: 'Usi un software di contabilità?',
- none: 'Nessuno',
- otherAccountingSoftware: 'Il tuo software di contabilità',
+ otherAccountingSoftware: 'Nome del software',
},
interestedFeatures: {
title: 'A quali funzionalità sei interessato?',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index a00d7c5b0eb8..9afe4766c6e7 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -3306,8 +3306,7 @@ ${date} の ${merchant} への ${amount}`,
},
accounting: {
title: '会計ソフトを利用していますか?',
- none: 'なし',
- otherAccountingSoftware: 'ご利用の会計ソフト',
+ otherAccountingSoftware: 'ソフトウェア名',
},
interestedFeatures: {
title: 'どの機能にご興味がありますか?',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index c30e3f205c6d..eaa666012078 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -3336,8 +3336,7 @@ ${amount} voor ${merchant} - ${date}`,
},
accounting: {
title: 'Gebruikt u een boekhoudprogramma?',
- none: 'Geen',
- otherAccountingSoftware: 'Je boekhoudsoftware',
+ otherAccountingSoftware: 'Naam van de software',
},
interestedFeatures: {
title: 'In welke functies ben je geïnteresseerd?',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 885864be4592..2e9366674c0d 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -3366,8 +3366,7 @@ ${amount} dla ${merchant} - ${date}`,
},
accounting: {
title: 'Czy korzystasz z jakiegoś oprogramowania księgowego?',
- none: 'Brak',
- otherAccountingSoftware: 'Twoje oprogramowanie księgowe',
+ otherAccountingSoftware: 'Nazwa oprogramowania',
},
interestedFeatures: {
title: 'Jakie funkcje Cię interesują?',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 3a44db440220..bb25b9d5c3ea 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -3325,8 +3325,7 @@ ${amount} para ${merchant} - ${date}`,
},
accounting: {
title: 'Você usa algum software de contabilidade?',
- none: 'Nenhum',
- otherAccountingSoftware: 'Seu software de contabilidade',
+ otherAccountingSoftware: 'Nome do software',
},
interestedFeatures: {
title: 'Em quais recursos você tem interesse?',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 0519fa4dfff9..424eac3f1365 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -3223,8 +3223,7 @@ ${amount},商户:${merchant} - 日期:${date}`,
},
accounting: {
title: '你是否使用任何会计软件?',
- none: '无',
- otherAccountingSoftware: '你的会计软件',
+ otherAccountingSoftware: '软件名称',
},
interestedFeatures: {
title: '你对哪些功能感兴趣?',
diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx
index 7e592572ff88..d1b27cc9c325 100644
--- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx
+++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx
@@ -6,7 +6,7 @@ import FormHelpMessage from '@components/FormHelpMessage';
import Icon from '@components/Icon';
import OnboardingHeader from '@components/OnboardingHeader';
import {PressableWithoutFeedback} from '@components/Pressable';
-import RadioButtonWithLabel from '@components/RadioButtonWithLabel';
+import RadioButton from '@components/RadioButton';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import type {ListItem} from '@components/SelectionList/types';
@@ -15,12 +15,11 @@ import TextInput from '@components/TextInput';
import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
import useCompleteOnboarding from '@hooks/useCompleteOnboarding';
-import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
+import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
-import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {setOnboardingAccountingEnabled, setOnboardingAdminsChatReportID, setOnboardingPolicyID, setOnboardingUserReportedIntegration} from '@libs/actions/Welcome';
@@ -47,19 +46,37 @@ import {View} from 'react-native';
import type {BaseOnboardingAccountingProps} from './types';
type Integration = {
- key: keyof typeof CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY;
- iconName: 'QBOCircle' | 'QBDSquare' | 'XeroCircle' | 'NetSuiteSquare' | 'IntacctSquare' | 'SapSquare' | 'OracleSquare' | 'MicrosoftDynamicsSquare';
+ key: keyof typeof CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY | typeof CONST.POLICY.CONNECTIONS.ACCOUNTING_INTEGRATION_ALIASES.INTUIT_ENTERPRISE_SUITE;
+ iconName:
+ | 'QBOCircle'
+ | 'IntuitSquare'
+ | 'QBDSquare'
+ | 'XeroCircle'
+ | 'NetSuiteSquare'
+ | 'IntacctSquare'
+ | 'CertiniaSquare'
+ | 'RilletSquare'
+ | 'SapSquare'
+ | 'OracleSquare'
+ | 'MicrosoftDynamicsSquare';
translationKey: TranslationPaths;
};
type AccountingOptionKey = Integration['key'] | 'other';
+// Ordered to match the design mocks, which lay the tiles out row by row in three columns on wide layouts and two on
+// narrow ones. DualEntry and Campfire are connections we support but deliberately do not offer here yet.
const integrations: Integration[] = [
{
key: 'quickbooksOnline',
iconName: 'QBOCircle',
translationKey: 'workspace.accounting.qbo',
},
+ {
+ key: CONST.POLICY.CONNECTIONS.ACCOUNTING_INTEGRATION_ALIASES.INTUIT_ENTERPRISE_SUITE,
+ iconName: 'IntuitSquare',
+ translationKey: 'workspace.accounting.intuitEnterpriseSuite',
+ },
{
key: 'quickbooksDesktop',
iconName: 'QBDSquare',
@@ -80,6 +97,17 @@ const integrations: Integration[] = [
iconName: 'IntacctSquare',
translationKey: 'workspace.accounting.intacct',
},
+ {
+ key: 'financialforce',
+ iconName: 'CertiniaSquare',
+ // Certinia has no `workspace.accounting` entry, but its connection page already translates the brand name.
+ translationKey: 'workspace.certinia.title',
+ },
+ {
+ key: 'rillet',
+ iconName: 'RilletSquare',
+ translationKey: 'workspace.accounting.rillet',
+ },
{
key: 'sap',
iconName: 'SapSquare',
@@ -107,20 +135,22 @@ type OnboardingListItem = ListItem & {
function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccountingProps) {
const styles = useThemeStyles();
- const theme = useTheme();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const expensifyIcons = useMemoizedLazyExpensifyIcons([
- 'Connect',
'QBOCircle',
+ 'IntuitSquare',
'QBDSquare',
'XeroCircle',
'NetSuiteSquare',
'IntacctSquare',
+ 'CertiniaSquare',
+ 'RilletSquare',
'SapSquare',
'OracleSquare',
'MicrosoftDynamicsSquare',
]);
+ const illustrations = useMemoizedLazyIllustrations(['Pencil']);
// We need to use isSmallScreenWidth, see navigateAfterOnboarding function comment
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {onboardingIsMediumOrLargerScreenWidth, isSmallScreenWidth, isInLandscapeMode} = useResponsiveLayout();
@@ -167,7 +197,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount
src={icon}
width={variables.iconSizeExtraLarge}
height={variables.iconSizeExtraLarge}
- additionalStyles={[StyleUtils.getAvatarBorderStyle(CONST.AVATAR_SIZE.DEFAULT, CONST.AVATAR_SHAPE.CIRCLE), styles.mr3]}
+ additionalStyles={[StyleUtils.getAvatarBorderStyle(CONST.AVATAR_SIZE.DEFAULT, CONST.AVATAR_SHAPE.CIRCLE)]}
/>
),
isSelected: selectedIntegration === integration.key,
@@ -179,11 +209,9 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount
text: translate('workspace.accounting.other'),
leftElement: (
),
isSelected: isOtherSelected,
@@ -242,22 +270,24 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount
sentryLabel={CONST.SENTRY_LABEL.ONBOARDING.ACCOUNTING_SELECT_INTEGRATION}
accessible={false}
hoverStyle={styles.hoveredComponentBG}
- style={[styles.onboardingAccountingItem, isSmallScreenWidth && styles.flexBasis100]}
+ style={[
+ styles.onboardingAccountingItem,
+ isSmallScreenWidth ? styles.onboardingAccountingItemNarrow : styles.onboardingAccountingItemWide,
+ !!item.isSelected && styles.onboardingAccountingItemSelected,
+ ]}
>
- handleIntegrationSelect(item.keyForList)}
- accessibilityLabel={item.text}
- style={[styles.flexRowReverse]}
- wrapperStyle={[styles.ml0]}
- labelElement={
-
- {item.leftElement}
- {item.text}
-
- }
- shouldBlendOpacity
+ accessibilityLabel={item.text ?? ''}
+ containerBorderRadius={variables.componentBorderRadiusSmall}
+ style={styles.onboardingAccountingItemRadioButton}
/>
+
+ {item.leftElement}
+ {item.text}
+
);
}
@@ -310,16 +340,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount
onContentSizeChange={handleContentSizeChange}
keyboardShouldPersistTaps="handled"
>
-
- {accountingOptions.map(renderOption)}
- {/* Keep Other from expanding across the empty second column on wide layouts. */}
- {!isSmallScreenWidth && (
-
- )}
-
+ {accountingOptions.map(renderOption)}
{isOtherSelected && (
backgroundColor: theme.highlightBG,
},
- onboardingSmallIcon: {
- padding: 10,
- },
-
sidebarStatusAvatarContainer: {
height: variables.avatarSizeMedium,
width: variables.avatarSizeMedium,
@@ -5681,8 +5677,28 @@ const staticStyles = (theme: ThemeColors) =>
paddingVertical: 20,
flexGrow: 1,
flexShrink: 1,
+ minHeight: 144,
+ },
- flexBasis: '35%',
+ // The basis and maxWidth handle the gap between columns and keep items aligned when the scrollbar appears
+ onboardingAccountingItemWide: {
+ flexBasis: '32%',
+ maxWidth: '32%',
+ },
+
+ onboardingAccountingItemNarrow: {
+ flexBasis: '48.5%',
+ maxWidth: '48.5%',
+ },
+
+ onboardingAccountingItemSelected: {
+ backgroundColor: theme.reactionActiveBackground,
+ },
+
+ onboardingAccountingItemRadioButton: {
+ position: 'absolute',
+ top: 16,
+ left: 16,
},
onboardingInterestedFeaturesItem: {
diff --git a/tests/ui/OnboardingAccountingAndInterestedFeatures.tsx b/tests/ui/OnboardingAccountingAndInterestedFeatures.tsx
index 49800908bd4d..916bbe8d3f9c 100644
--- a/tests/ui/OnboardingAccountingAndInterestedFeatures.tsx
+++ b/tests/ui/OnboardingAccountingAndInterestedFeatures.tsx
@@ -17,6 +17,7 @@ import BaseOnboardingAccounting from '@pages/OnboardingAccounting/BaseOnboarding
import BaseOnboardingInterestedFeatures from '@pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures';
import CONST from '@src/CONST';
+import type {TranslationPaths} from '@src/languages/types';
import type {OnboardingModalNavigatorParamList} from '@src/libs/Navigation/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -153,14 +154,50 @@ describe('Onboarding interested features and accounting pages', () => {
expect(navigate).not.toHaveBeenCalledWith(ROUTES.ONBOARDING_ACCOUNTING.getRoute());
});
- it('keeps Other half-width, auto-focuses its input, and completes with a trimmed integration name', async () => {
+ it('renders every accounting tile from the mocks', async () => {
+ renderAccountingPage();
+
+ await waitForBatchedUpdatesWithAct();
+ const expectedTiles: TranslationPaths[] = [
+ 'workspace.accounting.qbo',
+ 'workspace.accounting.intuitEnterpriseSuite',
+ 'workspace.accounting.qbd',
+ 'workspace.accounting.xero',
+ 'workspace.accounting.netsuite',
+ 'workspace.accounting.intacct',
+ 'workspace.certinia.title',
+ 'workspace.accounting.rillet',
+ 'workspace.accounting.sap',
+ 'workspace.accounting.oracle',
+ 'workspace.accounting.microsoftDynamics',
+ 'workspace.accounting.other',
+ ];
+ for (const translationKey of expectedTiles) {
+ expect(screen.getByText(TestHelper.translateLocal(translationKey))).toBeOnTheScreen();
+ }
+ });
+
+ it('completes onboarding with an integration that was previously missing from this screen', async () => {
+ renderAccountingPage();
+
+ await waitForBatchedUpdatesWithAct();
+ fireEvent.press(screen.getByText(TestHelper.translateLocal('workspace.certinia.title')));
+ fireEvent.press(screen.getByText(TestHelper.translateLocal('common.continue')));
+
+ await waitFor(() => {
+ expect(mockCompleteOnboardingFlow).toHaveBeenCalledWith({
+ featuresMap: expect.arrayContaining([{id: CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED, enabled: true, enabledByDefault: true}]),
+ userReportedIntegration: CONST.POLICY.CONNECTIONS.NAME.CERTINIA,
+ userReportedIntegrationName: undefined,
+ });
+ });
+ });
+
+ it('auto-focuses the Other input and completes with a trimmed integration name', async () => {
const scrollToEndSpy = jest.spyOn(ScrollView.prototype, 'scrollToEnd');
const renderResult = renderAccountingPage();
await waitForBatchedUpdatesWithAct();
- expect(screen.queryByText(TestHelper.translateLocal('onboarding.accounting.none'))).not.toBeOnTheScreen();
- expect(screen.getByTestId('onboarding-accounting-wide-layout-spacer')).toHaveStyle({backgroundColor: 'transparent', flexBasis: '35%', flexGrow: 1});
-
fireEvent.press(screen.getByText(TestHelper.translateLocal('workspace.accounting.other')));
const otherAccountingSoftwareLabel = TestHelper.translateLocal('onboarding.accounting.otherAccountingSoftware');
const otherAccountingSoftwareInput = screen.getByLabelText(otherAccountingSoftwareLabel);