Skip to content

Add merge account feature to ND #56911

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 55 commits into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
cda8dad
draft version of merge account
allroundexperts Dec 16, 2024
e4bdee3
account details page draft
allroundexperts Dec 17, 2024
16d5fdc
add translations and forms
allroundexperts Dec 17, 2024
ec445e1
more files
allroundexperts Dec 18, 2024
06c15e5
Merge branch 'main' into feat-47073
allroundexperts Jan 8, 2025
717bc14
WIP: ad6007b6398 onboarding flow without errors
allroundexperts Feb 16, 2025
217ceb4
more finetunings
allroundexperts Feb 16, 2025
353a410
fix a bunch of lint errors
allroundexperts Feb 18, 2025
e79174a
working version
allroundexperts Feb 21, 2025
89ecf4e
remove delegate access wrappers
allroundexperts Feb 21, 2025
61ff190
merge with main
allroundexperts Feb 21, 2025
478de42
add confirmation screen
allroundexperts Feb 23, 2025
20f900e
fix spanish translations
allroundexperts Feb 23, 2025
3281bc5
merge with main
allroundexperts Feb 23, 2025
c2eeb1e
handle lint issues
allroundexperts Feb 23, 2025
305253c
more lint errors
allroundexperts Feb 23, 2025
78a5563
lint fix
allroundexperts Feb 23, 2025
6501b6d
fix: footer styles
allroundexperts Feb 23, 2025
a4800c2
merge with main
allroundexperts Feb 26, 2025
c210eda
Merge branch 'main' into feat-47073
allroundexperts Mar 7, 2025
9368bec
fix all the bugs
allroundexperts Mar 7, 2025
89f3947
fix lint errors
allroundexperts Mar 7, 2025
4474019
prettier
allroundexperts Mar 7, 2025
f4e5d37
fix type checks
allroundexperts Mar 7, 2025
6a8d5b8
revert back to onButton press
allroundexperts Mar 7, 2025
025b2a0
change order of menu
allroundexperts Mar 7, 2025
5b995df
prettier
allroundexperts Mar 7, 2025
54b1340
remove help button
allroundexperts Mar 7, 2025
c8f1ad0
update more designs
allroundexperts Mar 7, 2025
9cd1765
prettier
allroundexperts Mar 7, 2025
907e1eb
more edge cases fix
allroundexperts Mar 7, 2025
4f1c885
merge with main
allroundexperts Mar 18, 2025
aa49ff8
remove un-needed changes
allroundexperts Mar 18, 2025
abd253c
handle comments
allroundexperts Mar 18, 2025
d0c75e3
fix bugs
allroundexperts Mar 18, 2025
5a13183
sync mobile exfy
allroundexperts Mar 18, 2025
626a201
prettier + typechecks
allroundexperts Mar 18, 2025
06ad4b4
Merge branch 'main' into feat-47073
allroundexperts Mar 25, 2025
ff4cb72
add too many attempts page
allroundexperts Mar 25, 2025
a237ac2
prettier
allroundexperts Mar 25, 2025
f68a77f
clear form on starting
allroundexperts Mar 25, 2025
2adda02
prettier
allroundexperts Mar 25, 2025
3430d81
Type fix
allroundexperts Mar 25, 2025
5d3e750
handle comments
allroundexperts Mar 26, 2025
e23f9c9
handle comments
allroundexperts Mar 28, 2025
c1d9c8f
lint fixes
allroundexperts Mar 28, 2025
2c411da
Merge branch 'main' into feat-47073
allroundexperts Mar 30, 2025
24d2819
handle comments
allroundexperts Mar 31, 2025
244772c
Merge branch 'main' into feat-47073
allroundexperts Apr 1, 2025
1fc1783
fix more bugs
allroundexperts Apr 1, 2025
4ced55d
add another case of error
allroundexperts Apr 1, 2025
397b2a1
fix err_2fa
allroundexperts Apr 3, 2025
cb653ec
Merge branch 'main' into feat-47073
allroundexperts Apr 5, 2025
3ceb6d6
minor fixes
allroundexperts Apr 4, 2025
ddce6cf
change variable names
allroundexperts Apr 5, 2025
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
3 changes: 3 additions & 0 deletions assets/images/arrow-collapse.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
96 changes: 96 additions & 0 deletions assets/images/running-turtle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 14 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const EMPTY_ARRAY = Object.freeze([]);
const EMPTY_OBJECT = Object.freeze({});

const DEFAULT_NUMBER_ID = 0;

const CLOUDFRONT_DOMAIN = 'cloudfront.net';
const CLOUDFRONT_URL = `https://d2k5nsl2zxldvw.${CLOUDFRONT_DOMAIN}`;
const ACTIVE_EXPENSIFY_URL = addTrailingForwardSlash(Config?.NEW_EXPENSIFY_URL ?? 'https://new.expensify.com');
Expand Down Expand Up @@ -1060,6 +1059,7 @@ const CONST = {
DELAYED_SUBMISSION_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/reports/Automatically-submit-employee-reports',
ENCRYPTION_AND_SECURITY_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security',
PLAN_TYPES_AND_PRICING_HELP_URL: 'https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing',
MERGE_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/settings/Merge-accounts',
TEST_RECEIPT_URL: `${CLOUDFRONT_URL}/images/fake-receipt__tacotodds.png`,
// Use Environment.getEnvironmentURL to get the complete URL with port number
DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:',
Expand Down Expand Up @@ -4977,6 +4977,19 @@ const CONST = {
DISABLED: 'DISABLED',
DISABLE: 'DISABLE',
},
MERGE_ACCOUNT_RESULTS: {
SUCCESS: 'success',
ERR_2FA: 'err_2fa',
ERR_NO_EXIST: 'err_no_exist',
ERR_SMART_SCANNER: 'err_smart_scanner',
ERR_INVOICING: 'err_invoicing',
ERR_SAML_PRIMARY_LOGIN: 'err_saml_primary_login',
ERR_SAML_DOMAIN_CONTROL: 'err_saml_domain_control',
ERR_SAML_NOT_SUPPORTED: 'err_saml_not_supported',
ERR_ACCOUNT_LOCKED: 'err_account_locked',
TOO_MANY_ATTEMPTS: 'too_many_attempts',
ACCOUNT_UNVALIDATED: 'account_unvalidated',
},
DELEGATE_ROLE: {
ALL: 'all',
SUBMITTER: 'submitter',
Expand Down
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,8 @@ const ONYXKEYS = {
RULES_CUSTOM_FORM_DRAFT: 'rulesCustomFormDraft',
DEBUG_DETAILS_FORM: 'debugDetailsForm',
DEBUG_DETAILS_FORM_DRAFT: 'debugDetailsFormDraft',
MERGE_ACCOUNT_DETAILS_FORM: 'mergeAccountDetailsForm',
MERGE_ACCOUNT_DETAILS_FORM_DRAFT: 'mergeAccountDetailsFormDraft',
WORKSPACE_PER_DIEM_FORM: 'workspacePerDiemForm',
WORKSPACE_PER_DIEM_FORM_DRAFT: 'workspacePerDiemFormDraft',
},
Expand Down Expand Up @@ -869,6 +871,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.RULES_CUSTOM_FORM]: FormTypes.RulesCustomForm;
[ONYXKEYS.FORMS.SEARCH_SAVED_SEARCH_RENAME_FORM]: FormTypes.SearchSavedSearchRenameForm;
[ONYXKEYS.FORMS.DEBUG_DETAILS_FORM]: FormTypes.DebugReportForm | FormTypes.DebugReportActionForm | FormTypes.DebugTransactionForm | FormTypes.DebugTransactionViolationForm;
[ONYXKEYS.FORMS.MERGE_ACCOUNT_DETAILS_FORM]: FormTypes.MergeAccountDetailsForm;
[ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM]: FormTypes.InternationalBankAccountForm;
[ONYXKEYS.FORMS.WORKSPACE_PER_DIEM_FORM]: FormTypes.WorkspacePerDiemForm;
};
Expand Down
12 changes: 12 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ const ROUTES = {
SETTINGS_WORKSPACES: {route: 'settings/workspaces', getRoute: (backTo?: string) => getUrlWithBackToParam('settings/workspaces', backTo)},
SETTINGS_SECURITY: 'settings/security',
SETTINGS_CLOSE: 'settings/security/closeAccount',
SETTINGS_MERGE_ACCOUNTS: {
route: 'settings/security/merge-accounts',
getRoute: (email?: string) => `settings/security/merge-accounts${email ? `?email=${encodeURIComponent(email)}` : ''}` as const,
},
SETTINGS_MERGE_ACCOUNTS_MAGIC_CODE: {
route: 'settings/security/merge-accounts/:login/magic-code',
getRoute: (login: string) => `settings/security/merge-accounts/${encodeURIComponent(login)}/magic-code` as const,
},
SETTINGS_MERGE_ACCOUNTS_RESULT: {
route: 'settings/security/merge-accounts/:login/result/:result',
getRoute: (login: string, result: string, backTo?: string) => getUrlWithBackToParam(`settings/security/merge-accounts/${encodeURIComponent(login)}/result/${result}`, backTo),
},
SETTINGS_ADD_DELEGATE: 'settings/security/delegate',
SETTINGS_DELEGATE_ROLE: {
route: 'settings/security/delegate/:login/role/:role',
Expand Down
5 changes: 5 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ const SCREENS = {
DELEGATE_CONFIRM: 'Settings_Delegate_Confirm',
UPDATE_DELEGATE_ROLE: 'Settings_Delegate_Update_Role',
},
MERGE_ACCOUNTS: {
ACCOUNT_DETAILS: 'Settings_MergeAccounts_AccountDetails',
ACCOUNT_VALIDATE: 'Settings_MergeAccounts_AccountValidate',
MERGE_RESULT: 'Settings_MergeAccounts_MergeResult',
},
},
TWO_FACTOR_AUTH: {
ROOT: 'Settings_TwoFactorAuth_Root',
Expand Down
70 changes: 56 additions & 14 deletions src/components/ConfirmationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,27 @@ type ConfirmationPageProps = {
/** Description of the confirmation page */
description: React.ReactNode;

/** The text for the button label */
/** The text for the call to action */
cta?: React.ReactNode;

/** The text for the primary button label */
buttonText?: string;

/** A function that is called when the button is clicked on */
/** A function that is called when the primary button is clicked on */
onButtonPress?: () => void;

/** Whether we should show a confirmation button */
/** Whether we should show a primary confirmation button */
shouldShowButton?: boolean;

/** The text for the secondary button label */
secondaryButtonText?: string;

/** A function that is called when the secondary button is clicked on */
onSecondaryButtonPress?: () => void;

/** Whether we should show a secondary confirmation button */
shouldShowSecondaryButton?: boolean;

/** Additional style for the heading */
headingStyle?: TextStyle;

Expand All @@ -40,6 +52,12 @@ type ConfirmationPageProps = {
/** Additional style for the description */
descriptionStyle?: TextStyle;

/** Additional style for the cta */
ctaStyle?: TextStyle;

/** Additional style for the footer */
footerStyle?: ViewStyle;

/** Additional style for the container */
containerStyle?: ViewStyle;
};
Expand All @@ -48,12 +66,18 @@ function ConfirmationPage({
illustration = LottieAnimations.Fireworks,
heading,
description,
cta,
buttonText = '',
onButtonPress = () => {},
shouldShowButton = false,
secondaryButtonText = '',
onSecondaryButtonPress = () => {},
shouldShowSecondaryButton = false,
headingStyle,
illustrationStyle,
descriptionStyle,
ctaStyle,
footerStyle,
containerStyle,
}: ConfirmationPageProps) {
const styles = useThemeStyles();
Expand All @@ -68,6 +92,10 @@ function ConfirmationPage({
autoPlay
loop
style={[styles.confirmationAnimation, illustrationStyle]}
webStyle={{
width: (illustrationStyle?.width as number) ?? styles.confirmationAnimation.width,
height: (illustrationStyle?.height as number) ?? styles.confirmationAnimation.height,
}}
/>
) : (
<View style={[styles.confirmationAnimation, illustrationStyle]}>
Expand All @@ -79,18 +107,30 @@ function ConfirmationPage({
)}
<Text style={[styles.textHeadline, styles.textAlignCenter, styles.mv2, headingStyle]}>{heading}</Text>
<Text style={[styles.textAlignCenter, descriptionStyle]}>{description}</Text>
{cta ? <Text style={[styles.textAlignCenter, ctaStyle]}>{cta}</Text> : null}
</View>
{shouldShowButton && (
<FixedFooter>
<Button
success
large
text={buttonText}
testID="confirmation-button"
style={styles.mt6}
pressOnEnter
onPress={onButtonPress}
/>
{(shouldShowSecondaryButton || shouldShowButton) && (
<FixedFooter style={footerStyle}>
{shouldShowSecondaryButton && (
<Button
large
text={secondaryButtonText}
testID="confirmation-secondary-button"
style={styles.mt3}
onPress={onSecondaryButtonPress}
/>
)}
{shouldShowButton && (
<Button
success
large
text={buttonText}
testID="confirmation-primary-button"
style={styles.mt3}
pressOnEnter
onPress={onButtonPress}
/>
)}
</FixedFooter>
)}
</View>
Expand All @@ -100,3 +140,5 @@ function ConfirmationPage({
ConfirmationPage.displayName = 'ConfirmationPage';

export default ConfirmationPage;

export type {ConfirmationPageProps};
1 change: 1 addition & 0 deletions src/components/Form/FormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ function FormProvider(
resetForm,
resetErrors,
resetFormFieldError,
submit,
}));

const registerInput = useCallback<RegisterInput>(
Expand Down
1 change: 1 addition & 0 deletions src/components/Form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ type FormRef<TFormID extends OnyxFormKey = OnyxFormKey> = {
resetForm: (optionalValue: FormOnyxValues<TFormID>) => void;
resetErrors: () => void;
resetFormFieldError: (fieldID: keyof Form) => void;
submit: () => void;
};

type InputRefs = Record<string, MutableRefObject<InputComponentBaseProps>>;
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import AddReaction from '@assets/images/add-reaction.svg';
import All from '@assets/images/all.svg';
import Android from '@assets/images/android.svg';
import Apple from '@assets/images/apple.svg';
import ArrowCollapse from '@assets/images/arrow-collapse.svg';
import ArrowDownLong from '@assets/images/arrow-down-long.svg';
import ArrowRightLong from '@assets/images/arrow-right-long.svg';
import ArrowRight from '@assets/images/arrow-right.svg';
Expand Down Expand Up @@ -228,6 +229,7 @@ export {
AnnounceRoomAvatar,
Apple,
AppleLogo,
ArrowCollapse,
ArrowRight,
ArrowRightLong,
ArrowsUpDown,
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Illustrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import TeleScope from '@assets/images/product-illustrations/telescope.svg';
import ThreeLeggedLaptopWoman from '@assets/images/product-illustrations/three_legged_laptop_woman.svg';
import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg';
import ToddWithPhones from '@assets/images/product-illustrations/todd-with-phones.svg';
import RunningTurtle from '@assets/images/running-turtle.svg';
import ReportReceipt from '@assets/images/simple-illustration__report-receipt.svg';
import BigVault from '@assets/images/simple-illustrations/emptystate__big-vault.svg';
import Puzzle from '@assets/images/simple-illustrations/emptystate__puzzlepieces.svg';
Expand Down Expand Up @@ -280,6 +281,7 @@ export {
Filters,
MagnifyingGlassMoney,
Rules,
RunningTurtle,
CompanyCardsEmptyState,
AmexCompanyCards,
MasterCardCompanyCards,
Expand Down
9 changes: 7 additions & 2 deletions src/components/ValidateCodeActionForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ function ValidateCodeActionForm({
sendValidateCode,
hasMagicCodeBeenSent,
isLoading,
submitButtonText,
forwardedRef,
shouldSkipInitialValidation,
}: ValidateCodeActionFormProps) {
const themeStyles = useThemeStyles();

Expand All @@ -27,13 +29,15 @@ function ValidateCodeActionForm({
const isUnmounted = useRef(false);

useEffect(() => {
sendValidateCode();
if (!shouldSkipInitialValidation) {
sendValidateCode();
}

return () => {
isUnmounted.current = true;
};
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, []);
}, [shouldSkipInitialValidation]);

useEffect(() => {
return () => {
Expand All @@ -59,6 +63,7 @@ function ValidateCodeActionForm({
buttonStyles={[themeStyles.justifyContentEnd, themeStyles.flex1]}
ref={forwardedRef}
hasMagicCodeBeenSent={hasMagicCodeBeenSent}
submitButtonText={submitButtonText}
/>
</View>
);
Expand Down
12 changes: 9 additions & 3 deletions src/components/ValidateCodeActionForm/type.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type {ForwardedRef} from 'react';
import type {ForwardedRef, ReactNode} from 'react';
import type {ValidateCodeFormHandle} from '@components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm';
import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon';

type ValidateCodeActionFormProps = {
/** Primary description of the modal */
descriptionPrimary: string;
descriptionPrimary: ReactNode;

/** Secondary description of the modal */
descriptionSecondary?: string | null;
descriptionSecondary?: ReactNode;

/** The pending action for submitting form */
validatePendingAction?: PendingAction | null;
Expand All @@ -32,6 +32,12 @@ type ValidateCodeActionFormProps = {

/** Ref for validate code form */
forwardedRef: ForwardedRef<ValidateCodeFormHandle>;

/** Text for submit button */
submitButtonText?: string;

/** Skip the call to sendValidateCode fn on initial render */
shouldSkipInitialValidation?: boolean;
};

// eslint-disable-next-line import/prefer-default-export
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ type ValidateCodeFormProps = {
/** Whether to show the verify button */
hideSubmitButton?: boolean;

/** Text for the verify button */
submitButtonText?: string;

/** Function is called when validate code modal is mounted and on magic code resend */
sendValidateCode: () => void;

Expand All @@ -86,6 +89,7 @@ function BaseValidateCodeForm({
sendValidateCode,
buttonStyles,
hideSubmitButton,
submitButtonText,
isLoading,
}: ValidateCodeFormProps) {
const {translate} = useLocalize();
Expand Down Expand Up @@ -284,7 +288,7 @@ function BaseValidateCodeForm({
{!hideSubmitButton && (
<Button
isDisabled={isOffline}
text={translate('common.verify')}
text={submitButtonText ?? translate('common.verify')}
onPress={validateAndSubmitForm}
style={[styles.mt4]}
success
Expand Down
Loading