diff --git a/assets/images/arrow-collapse.svg b/assets/images/arrow-collapse.svg new file mode 100644 index 000000000000..3a28e2bb4768 --- /dev/null +++ b/assets/images/arrow-collapse.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/running-turtle.svg b/assets/images/running-turtle.svg new file mode 100644 index 000000000000..0ef49b49e1db --- /dev/null +++ b/assets/images/running-turtle.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CONST.ts b/src/CONST.ts index ba3f24be43d7..ae42767e21f8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -21,6 +21,7 @@ const EMPTY_ARRAY = Object.freeze([]); const EMPTY_OBJECT = Object.freeze({}); const DEFAULT_NUMBER_ID = 0; +const DEFAULT_STRING_NEGATIVE_ID = '-1'; const CLOUDFRONT_DOMAIN = 'cloudfront.net'; const CLOUDFRONT_URL = `https://d2k5nsl2zxldvw.${CLOUDFRONT_DOMAIN}`; @@ -975,6 +976,7 @@ const CONST = { EMPTY_ARRAY, EMPTY_OBJECT, DEFAULT_NUMBER_ID, + DEFAULT_STRING_NEGATIVE_ID, USE_EXPENSIFY_URL, EXPENSIFY_URL, GOOGLE_MEET_URL_ANDROID: 'https://meet.google.com', @@ -1038,6 +1040,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:', @@ -4861,6 +4864,17 @@ 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', + }, DELEGATE_ROLE: { ALL: 'all', SUBMITTER: 'submitter', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 72cbcee20e34..b65f0ada760b 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -759,6 +759,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', }, @@ -861,6 +863,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; }; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 41fd4b830f12..c91ce9b68615 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -161,6 +161,15 @@ 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: 'settings/security/merge-accounts', + 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) => `settings/security/merge-accounts/${encodeURIComponent(login)}/result/${result}` as const, + }, SETTINGS_ADD_DELEGATE: 'settings/security/delegate', SETTINGS_DELEGATE_ROLE: { route: 'settings/security/delegate/:login/role/:role', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 7e09c0277fe4..3dd002f7bf54 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -144,6 +144,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', diff --git a/src/components/ConfirmationPage.tsx b/src/components/ConfirmationPage.tsx index e4fc029ea31e..dc2ce751583e 100644 --- a/src/components/ConfirmationPage.tsx +++ b/src/components/ConfirmationPage.tsx @@ -22,15 +22,24 @@ type ConfirmationPageProps = { /** Description of the confirmation page */ description: React.ReactNode; - /** The text for the button label */ + /** 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; @@ -40,6 +49,9 @@ type ConfirmationPageProps = { /** Additional style for the description */ descriptionStyle?: TextStyle; + /** Additional style for the footer */ + footerStyle?: ViewStyle; + /** Additional style for the container */ containerStyle?: ViewStyle; }; @@ -51,9 +63,13 @@ function ConfirmationPage({ buttonText = '', onButtonPress = () => {}, shouldShowButton = false, + secondaryButtonText = '', + onSecondaryButtonPress = () => {}, + shouldShowSecondaryButton = false, headingStyle, illustrationStyle, descriptionStyle, + footerStyle, containerStyle, }: ConfirmationPageProps) { const styles = useThemeStyles(); @@ -68,6 +84,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, + }} /> ) : ( @@ -80,17 +100,28 @@ function ConfirmationPage({ {heading} {description} - {shouldShowButton && ( - -