Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/hooks/useCompleteOnboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import useResponsiveLayout from './useResponsiveLayout';
type CompleteOnboardingParams = {
featuresMap: OnboardingFeatureMapItem[];
userReportedIntegration?: OnboardingAccounting;
userReportedIntegrationName?: string;
};

function useCompleteOnboarding() {
Expand Down Expand Up @@ -60,7 +61,7 @@ function useCompleteOnboarding() {

const groupPolicy = Object.values(allPolicies ?? {}).find((policy) => isGroupPolicy(policy) && isPolicyAdmin(policy, session?.email));

const completeOnboardingFlow = async ({featuresMap, userReportedIntegration}: CompleteOnboardingParams) => {
const completeOnboardingFlow = async ({featuresMap, userReportedIntegration, userReportedIntegrationName}: CompleteOnboardingParams) => {
if (!onboardingPurposeSelected || !onboardingCompanySize) {
return;
}
Expand All @@ -71,6 +72,7 @@ function useCompleteOnboarding() {
const shouldCreateWorkspace = !onboardingPolicyID && !groupPolicy;
const isAccountingEnabled = featuresMap.some((feature) => feature.id === CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED && feature.enabled);
const resolvedIntegration = isAccountingEnabled ? userReportedIntegration : undefined;
const resolvedIntegrationName = resolvedIntegration === 'other' ? userReportedIntegrationName : undefined;
const email = currentUserPersonalDetails.email ?? '';

const {adminsChatReportID, policyID} = shouldCreateWorkspace
Expand All @@ -85,6 +87,7 @@ function useCompleteOnboarding() {
shouldAddOnboardingTasks: false,
companySize: onboardingCompanySize,
userReportedIntegration: resolvedIntegration,
userReportedIntegrationName: resolvedIntegrationName,
featuresMap,
introSelected,
activePolicy,
Expand All @@ -110,6 +113,7 @@ function useCompleteOnboarding() {
onboardingPolicyID: policyID,
companySize: onboardingCompanySize,
userReportedIntegration: resolvedIntegration,
userReportedIntegrationName: resolvedIntegrationName,
firstName: currentUserPersonalDetails?.firstName,
lastName: currentUserPersonalDetails?.lastName,
selectedInterestedFeatures: featuresMap.filter((feature) => feature.enabled).map((feature) => feature.id),
Expand Down
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3190,6 +3190,7 @@ ${amount} für ${merchant} – ${date}`,
accounting: {
title: 'Verwendest du eine Buchhaltungssoftware?',
none: 'Keine',
otherAccountingSoftware: 'Deine Buchhaltungssoftware',
},
interestedFeatures: {
title: 'An welchen Funktionen bist du interessiert?',
Expand Down
1 change: 1 addition & 0 deletions src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3248,6 +3248,7 @@ ${amount} για ${merchant} - ${date}`,
accounting: {
title: 'Χρησιμοποιείτε κάποιο λογιστικό λογισμικό;',
none: 'Κανένα',
otherAccountingSoftware: 'Το λογιστικό σας λογισμικό',
},
interestedFeatures: {
title: 'Σε ποιες δυνατότητες ενδιαφέρεστε;',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3282,6 +3282,7 @@ const translations = {
accounting: {
title: 'Do you use any accounting software?',
none: 'None',
otherAccountingSoftware: 'Your accounting software',
},
interestedFeatures: {
title: 'What features are you interested in?',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3096,6 +3096,7 @@ ${amount} para ${merchant} - ${date}`,
accounting: {
title: '¿Utilizas algún software de contabilidad?',
none: 'Ninguno',
otherAccountingSoftware: 'Tu software de contabilidad',
},
interestedFeatures: {
title: '¿Qué funciones te interesan?',
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3197,6 +3197,7 @@ ${amount} pour ${merchant} - ${date}`,
accounting: {
title: 'Utilisez-vous un logiciel de comptabilité ?',
none: 'Aucun',
otherAccountingSoftware: 'Votre logiciel de comptabilité',
},
interestedFeatures: {
title: 'Quelles fonctionnalités vous intéressent ?',
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3179,6 +3179,7 @@ ${amount} per ${merchant} - ${date}`,
accounting: {
title: 'Usi un software di contabilità?',
none: 'Nessuno',
otherAccountingSoftware: 'Il tuo software di contabilità',
},
interestedFeatures: {
title: 'A quali funzionalità sei interessato?',
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3151,6 +3151,7 @@ ${date} の ${merchant} への ${amount}`,
accounting: {
title: '会計ソフトを利用していますか?',
none: 'なし',
otherAccountingSoftware: 'ご利用の会計ソフト',
},
interestedFeatures: {
title: 'どの機能にご興味がありますか?',
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3180,6 +3180,7 @@ ${amount} voor ${merchant} - ${date}`,
accounting: {
title: 'Gebruikt u een boekhoudprogramma?',
none: 'Geen',
otherAccountingSoftware: 'Je boekhoudsoftware',
},
interestedFeatures: {
title: 'In welke functies ben je geïnteresseerd?',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3166,6 +3166,7 @@ ${amount} dla ${merchant} - ${date}`,
accounting: {
title: 'Czy korzystasz z jakiegoś oprogramowania księgowego?',
none: 'Brak',
otherAccountingSoftware: 'Twoje oprogramowanie księgowe',
},
interestedFeatures: {
title: 'Jakie funkcje Cię interesują?',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3169,6 +3169,7 @@ ${amount} para ${merchant} - ${date}`,
accounting: {
title: 'Você usa algum software de contabilidade?',
none: 'Nenhum',
otherAccountingSoftware: 'Seu software de contabilidade',
},
interestedFeatures: {
title: 'Em quais recursos você tem interesse?',
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3071,6 +3071,7 @@ ${amount},商户:${merchant} - 日期:${date}`,
accounting: {
title: '你是否使用任何会计软件?',
none: '无',
otherAccountingSoftware: '你的会计软件',
},
interestedFeatures: {
title: '你对哪些功能感兴趣?',
Expand Down
1 change: 1 addition & 0 deletions src/libs/API/parameters/CompleteGuidedSetupParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type CompleteGuidedSetupParams = {
paymentSelected?: string;
companySize?: OnboardingCompanySize;
userReportedIntegration?: OnboardingAccounting;
userReportedIntegrationName?: string;
policyID?: string;
selfDMReportID?: string;
selfDMCreatedReportActionID?: string;
Expand Down
1 change: 1 addition & 0 deletions src/libs/API/parameters/CreateWorkspaceParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type CreateWorkspaceParams = {
file?: File;
companySize?: string;
userReportedIntegration?: string;
userReportedIntegrationName?: string;
memberData?: string;
features?: string;
shouldAddGuideWelcomeMessage?: boolean;
Expand Down
3 changes: 3 additions & 0 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ type BuildPolicyDataOptions = {
shouldAddOnboardingTasks?: boolean;
companySize?: OnboardingCompanySize;
userReportedIntegration?: OnboardingAccounting;
userReportedIntegrationName?: string;
isAnnualSubscription?: boolean;
featuresMap?: Array<Pick<Feature, 'id' | 'enabled' | 'enabledByDefault' | 'requiresUpdate'>>;
lastUsedPaymentMethod?: LastPaymentMethodType;
Expand Down Expand Up @@ -2692,6 +2693,7 @@ function buildPolicyData(options: BuildPolicyDataOptions): OnyxData<BuildPolicyD
conciergeChat,
companySize,
userReportedIntegration,
userReportedIntegrationName,
isAnnualSubscription = false,
featuresMap,
lastUsedPaymentMethod,
Expand Down Expand Up @@ -3160,6 +3162,7 @@ function buildPolicyData(options: BuildPolicyDataOptions): OnyxData<BuildPolicyD
file: clonedFile,
companySize,
userReportedIntegration: userReportedIntegration ?? undefined,
userReportedIntegrationName,
features: features ? JSON.stringify(features) : undefined,
shouldAddGuideWelcomeMessage,
areDistanceRatesEnabled,
Expand Down
3 changes: 3 additions & 0 deletions src/libs/actions/Report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5784,6 +5784,7 @@ type CompleteOnboardingProps = {
paymentSelected?: string;
companySize?: OnboardingCompanySize;
userReportedIntegration?: OnboardingAccounting;
userReportedIntegrationName?: string;
wasInvited?: boolean;
selectedInterestedFeatures?: string[];
isInvitedAccountant?: boolean;
Expand All @@ -5810,6 +5811,7 @@ async function completeOnboarding({
paymentSelected,
companySize,
userReportedIntegration,
userReportedIntegrationName,
wasInvited,
selectedInterestedFeatures,
isInvitedAccountant,
Expand Down Expand Up @@ -5853,6 +5855,7 @@ async function completeOnboarding({
paymentSelected,
companySize,
userReportedIntegration,
userReportedIntegrationName,
policyID: onboardingPolicyID,
selfDMReportID: selfDMParameters.reportID,
selfDMCreatedReportActionID: selfDMParameters.createdReportActionID,
Expand Down
68 changes: 63 additions & 5 deletions src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import type {ListItem} from '@components/SelectionList/types';
import Text from '@components/Text';
import TextInput from '@components/TextInput';
import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';

import useCompleteOnboarding from '@hooks/useCompleteOnboarding';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
Expand All @@ -22,6 +24,7 @@ import useThemeStyles from '@hooks/useThemeStyles';

import {setOnboardingAccountingEnabled, setOnboardingAdminsChatReportID, setOnboardingPolicyID, setOnboardingUserReportedIntegration} from '@libs/actions/Welcome';
import {getDefaultOnboardingFeaturesMap} from '@libs/actions/Welcome/OnboardingFeatures';
import {isMobileSafari} from '@libs/Browser';
import Navigation from '@libs/Navigation/Navigation';
import {isGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils';

Expand All @@ -34,7 +37,10 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type IconAsset from '@src/types/utils/IconAsset';

import React, {useEffect, useState} from 'react';
// eslint-disable-next-line no-restricted-imports
import type {ScrollView as RNScrollView} from 'react-native';

import React, {useCallback, useEffect, useRef, useState} from 'react';
import {View} from 'react-native';

import type {BaseOnboardingAccountingProps} from './types';
Expand Down Expand Up @@ -131,7 +137,12 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount
initialSelectedIntegration = 'other';
}
const [selectedIntegration, setSelectedIntegration] = useState<AccountingOptionKey | undefined>(initialSelectedIntegration);
const [userReportedIntegrationName, setUserReportedIntegrationName] = useState('');
const [shouldScrollToOtherInput, setShouldScrollToOtherInput] = useState(initialSelectedIntegration === 'other');
const [error, setError] = useState('');
const scrollViewRef = useRef<RNScrollView>(null);
const otherAccountingSoftwareInputRef = useRef<BaseTextInputRef | null>(null);
const isOtherSelected = selectedIntegration === 'other';

const groupPolicy = Object.values(allPolicies ?? {}).find((policy) => isGroupPolicy(policy) && isPolicyAdmin(policy, session?.email));
const {isOffline} = useNetwork();
Expand Down Expand Up @@ -175,7 +186,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount
additionalStyles={[StyleUtils.getAvatarBorderStyle(CONST.AVATAR_SIZE.DEFAULT, CONST.ICON_TYPE_AVATAR), styles.mr3, styles.onboardingSmallIcon]}
/>
),
isSelected: selectedIntegration === 'other',
isSelected: isOtherSelected,
};

const accountingOptions: OnboardingListItem[] = [...integrations.map(createAccountingOption), othersAccountingOption];
Expand All @@ -187,16 +198,41 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount
}

const integrationValue: OnboardingAccounting = selectedIntegration;
const trimmedIntegrationName = userReportedIntegrationName.trim();
const integrationName = integrationValue === 'other' && trimmedIntegrationName ? trimmedIntegrationName : undefined;
setOnboardingAccountingEnabled(true);
setOnboardingUserReportedIntegration(integrationValue);
await completeOnboardingFlow({featuresMap: onboardingFeaturesMap ?? getDefaultOnboardingFeaturesMap(), userReportedIntegration: integrationValue});
await completeOnboardingFlow({
featuresMap: onboardingFeaturesMap ?? getDefaultOnboardingFeaturesMap(),
userReportedIntegration: integrationValue,
userReportedIntegrationName: integrationName,
});
};

const handleIntegrationSelect = (integrationKey: OnboardingListItem['keyForList']) => {
if (integrationKey === 'other' && isOtherSelected) {
otherAccountingSoftwareInputRef.current?.focus();
setError('');
return;
}

setSelectedIntegration(integrationKey);
setShouldScrollToOtherInput(integrationKey === 'other');
if (integrationKey !== 'other') {
setUserReportedIntegrationName('');
}
setError('');
};

const handleContentSizeChange = useCallback(() => {
if (!shouldScrollToOtherInput) {
return;
}

scrollViewRef.current?.scrollToEnd({animated: false});
setShouldScrollToOtherInput(false);
}, [shouldScrollToOtherInput]);

function renderOption(item: OnboardingListItem) {
return (
<PressableWithoutFeedback
Expand Down Expand Up @@ -230,7 +266,8 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount
<ScreenWrapper
testID="BaseOnboardingAccounting"
style={[styles.defaultModalContainer, shouldUseNativeStyles && styles.pt8]}
shouldEnableMaxHeight
shouldEnableMaxHeight={!isMobileSafari()}
shouldAvoidScrollOnVirtualViewport={!isMobileSafari()}
>
<OnboardingHeader onBackButtonPress={() => Navigation.goBack(ROUTES.ONBOARDING_INTERESTED_FEATURES.getRoute())} />
<View style={[onboardingIsMediumOrLargerScreenWidth && styles.mt5, onboardingIsMediumOrLargerScreenWidth ? styles.mh8 : styles.mh5]}>
Expand All @@ -242,10 +279,31 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount
</Text>
</View>
<ScrollView
ref={scrollViewRef}
style={[onboardingIsMediumOrLargerScreenWidth ? styles.mh8 : styles.mh5]}
contentContainerStyle={[styles.pt3, styles.pb5]}
onContentSizeChange={handleContentSizeChange}
>
<View style={[styles.flexRow, styles.flexWrap, styles.gap3, styles.mb3]}>{accountingOptions.map(renderOption)}</View>
<View style={[styles.flexRow, styles.flexWrap, styles.gap3, styles.mb3]}>
{accountingOptions.map(renderOption)}
{/* Keep Other from expanding across the empty second column on wide layouts. */}
{!isSmallScreenWidth && (
<View
testID="onboarding-accounting-wide-layout-spacer"
style={[styles.onboardingAccountingItem, styles.bgTransparent, styles.p0]}
/>
)}
</View>
{isOtherSelected && (
<TextInput
ref={otherAccountingSoftwareInputRef}
accessibilityLabel={translate('onboarding.accounting.otherAccountingSoftware')}
label={translate('onboarding.accounting.otherAccountingSoftware')}
value={userReportedIntegrationName}
onChangeText={setUserReportedIntegrationName}
autoFocus
/>
)}
</ScrollView>
<FixedFooter style={[styles.pt3, styles.ph5]}>
{!!error && (
Expand Down
63 changes: 63 additions & 0 deletions tests/actions/PolicyTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,69 @@ describe('actions/Policy', () => {
mockFetch?.resume?.();
});

it('forwards the user-reported integration name to CreateWorkspace', async () => {
const apiWriteSpy = jest.spyOn(require('@libs/API'), 'write').mockImplementation(() => Promise.resolve());

Policy.createWorkspace({
conciergeChat: undefined,
policyOwnerEmail: ESH_EMAIL,
makeMeAdmin: true,
policyName: WORKSPACE_NAME,
policyID: Policy.generatePolicyID(),
engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM,
introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM},
currentUserAccountIDParam: ESH_ACCOUNT_ID,
currentUserEmailParam: ESH_EMAIL,
currency: CONST.CURRENCY.USD,
userReportedIntegration: 'other',
userReportedIntegrationName: 'Acme Books',
isSelfTourViewed: false,
betas: undefined,
hasActiveAdminPolicies: false,
activePolicy: undefined,
});
await waitForBatchedUpdates();

expect(apiWriteSpy).toHaveBeenCalledWith(
WRITE_COMMANDS.CREATE_WORKSPACE,
expect.objectContaining({
userReportedIntegration: 'other',
userReportedIntegrationName: 'Acme Books',
}),
expect.anything(),
);

apiWriteSpy.mockRestore();
});

it('omits the user-reported integration name from CreateWorkspace when it is undefined', async () => {
Policy.createWorkspace({
conciergeChat: undefined,
policyOwnerEmail: ESH_EMAIL,
makeMeAdmin: true,
policyName: WORKSPACE_NAME,
policyID: Policy.generatePolicyID(),
engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM,
introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM},
currentUserAccountIDParam: ESH_ACCOUNT_ID,
currentUserEmailParam: ESH_EMAIL,
currency: CONST.CURRENCY.USD,
userReportedIntegration: 'other',
isSelfTourViewed: false,
betas: undefined,
hasActiveAdminPolicies: false,
activePolicy: undefined,
});
await waitForBatchedUpdates();

const calls = TestHelper.getFetchMockCalls(WRITE_COMMANDS.CREATE_WORKSPACE);
expect(calls.length).toBeGreaterThan(0);
const body = calls.at(-1)?.[1]?.body;
expect(body).toBeInstanceOf(FormData);
const formEntries = body instanceof FormData ? Object.fromEntries(body) : {};
expect(formEntries.userReportedIntegrationName).toBeUndefined();
});

it('creates a new workspace', async () => {
mockFetch?.pause?.();
await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID});
Expand Down
Loading
Loading