diff --git a/assets/images/user-minus.svg b/assets/images/user-minus.svg new file mode 100644 index 000000000000..f819734464a8 --- /dev/null +++ b/assets/images/user-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cspell.json b/cspell.json index 1c61490d481e..b0c8c5baaa54 100644 --- a/cspell.json +++ b/cspell.json @@ -737,6 +737,8 @@ "Venmo", "viewability", "viewport", + "Unsharing", + "unsharing", "viewports", "VMPD", "voidings", diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 91b60f5acff8..5bf1bcf1f2c0 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -125,6 +125,9 @@ const ONYXKEYS = { /* Contains meta data for the call to the API to get the joinable policies */ VALIDATE_USER_AND_GET_ACCESSIBLE_POLICIES: 'validateUserAndGetAccessiblePolicies', + /** Stores details relating to unsharing a given bank account */ + UNSHARE_BANK_ACCOUNT: 'unshareBankAccount', + /** Information about the current session (authToken, accountID, email, loading, error) */ SESSION: 'session', STASHED_SESSION: 'stashedSession', @@ -1258,6 +1261,7 @@ type OnyxValuesMapping = { [ONYXKEYS.PURCHASE_LIST]: OnyxTypes.PurchaseList; [ONYXKEYS.PERSONAL_BANK_ACCOUNT]: OnyxTypes.PersonalBankAccount; [ONYXKEYS.SHARE_BANK_ACCOUNT]: OnyxTypes.ShareBankAccount; + [ONYXKEYS.UNSHARE_BANK_ACCOUNT]: OnyxTypes.UnshareBankAccount; [ONYXKEYS.REIMBURSEMENT_ACCOUNT]: OnyxTypes.ReimbursementAccount; [ONYXKEYS.REIMBURSEMENT_ACCOUNT_OPTION_PRESSED]: ValueOf; [ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE]: number; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 87cf0b33472d..1a2fe9651178 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -344,6 +344,10 @@ const ROUTES = { SETTINGS_ADD_US_BANK_ACCOUNT: 'settings/wallet/add-us-bank-account', SETTINGS_ADD_BANK_ACCOUNT_SELECT_COUNTRY_VERIFY_ACCOUNT: `settings/wallet/add-bank-account/select-country/${VERIFY_ACCOUNT}`, SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments', + SETTINGS_WALLET_UNSHARE_BANK_ACCOUNT: { + route: 'settings/wallet/:bankAccountID/unshare-bank-account', + getRoute: (bankAccountID: number | undefined) => `settings/wallet/${bankAccountID}/unshare-bank-account` as const, + }, SETTINGS_WALLET_ENABLE_GLOBAL_REIMBURSEMENTS: { route: 'settings/wallet/:bankAccountID/enable-global-reimbursements', getRoute: (bankAccountID: number | undefined) => `settings/wallet/${bankAccountID}/enable-global-reimbursements` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 1393fcf63841..85c9b8346785 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -166,6 +166,7 @@ const SCREENS = { REPORT_VIRTUAL_CARD_FRAUD_CONFIRM_MAGIC_CODE: 'Settings_Wallet_ReportVirtualCardFraud_ConfirmMagicCode', REPORT_VIRTUAL_CARD_FRAUD_CONFIRMATION: 'Settings_Wallet_ReportVirtualCardFraudConfirmation', CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS: 'Settings_Wallet_Cards_Digital_Details_Update_Address', + UNSHARE_BANK_ACCOUNT: 'Settings_Wallet_Unshare_Bank_Account', ENABLE_GLOBAL_REIMBURSEMENTS: 'Settings_Wallet_Enable_Global_Reimbursements', SHARE_BANK_ACCOUNT: 'Settings_Wallet_Share_Bank_Account', }, diff --git a/src/components/Icon/chunks/expensify-icons.chunk.ts b/src/components/Icon/chunks/expensify-icons.chunk.ts index 3c2e66df07a6..71447b27039c 100644 --- a/src/components/Icon/chunks/expensify-icons.chunk.ts +++ b/src/components/Icon/chunks/expensify-icons.chunk.ts @@ -222,6 +222,7 @@ import Upload from '@assets/images/upload.svg'; import UserCheck from '@assets/images/user-check.svg'; import UserEye from '@assets/images/user-eye.svg'; import UserLock from '@assets/images/user-lock.svg'; +import UserMinus from '@assets/images/user-minus.svg'; import UserPlus from '@assets/images/user-plus.svg'; import User from '@assets/images/user.svg'; import Users from '@assets/images/users.svg'; @@ -342,6 +343,7 @@ const Expensicons = { LinkCopy, Location, Lock, + UserMinus, Luggage, MagnifyingGlass, Mail, diff --git a/src/languages/de.ts b/src/languages/de.ts index e8c7b7d93eb9..587618929151 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -254,6 +254,7 @@ const translations: TranslationDeepObject = { dismiss: 'Schließen', // @context Used on a button to continue an action or workflow, not the formal or procedural sense of “to proceed.” proceed: 'Fortfahren', + unshare: 'Nicht teilen', yes: 'Ja', no: 'Nein', // @context Universal confirmation button. Keep the UI-standard term “OK” unless the locale strongly prefers an alternative. @@ -2115,6 +2116,12 @@ const translations: TranslationDeepObject = { shareBankAccountEmptyTitle: 'Keine Administratoren verfügbar', shareBankAccountEmptyDescription: 'Es gibt keine Workspace-Administratoren, mit denen Sie dieses Bankkonto teilen können.', shareBankAccountNoAdminsSelected: 'Bitte wählen Sie einen Administrator aus, bevor Sie fortfahren', + unshareBankAccount: 'Bankkonto freigeben', + unshareBankAccountDescription: + 'Alle unten aufgeführten Personen haben Zugriff auf dieses Bankkonto. Sie können den Zugriff jederzeit entfernen. Laufende Zahlungen werden weiterhin ausgeführt.', + unshareBankAccountWarning: ({admin}: {admin?: string | null}) => `${admin} verliert den Zugriff auf dieses Geschäftskonto. Laufende Zahlungen werden weiterhin ausgeführt.`, + reachOutForHelp: 'Dieses Konto wird mit der Expensify Card verwendet. Wenden Sie sich an den Concierge, wenn Sie die Freigabe aufheben möchten.', + unshareErrorModalTitle: 'Bankkonto kann nicht freigegeben werden', }, cardPage: { expensifyCard: 'Expensify Card', diff --git a/src/languages/en.ts b/src/languages/en.ts index 877231521b32..108885fde5f1 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -245,6 +245,7 @@ const translations = { dismiss: 'Dismiss', // @context Used on a button to continue an action or workflow, not the formal or procedural sense of “to proceed.” proceed: 'Proceed', + unshare: 'Unshare', yes: 'Yes', no: 'No', // @context Universal confirmation button. Keep the UI-standard term “OK” unless the locale strongly prefers an alternative. @@ -2103,6 +2104,11 @@ const translations = { shareBankAccountEmptyTitle: 'No admins available', shareBankAccountEmptyDescription: 'There are no workspace admins you can share this bank account with.', shareBankAccountNoAdminsSelected: 'Please select an admin before continuing', + unshareBankAccount: 'Unshare bank account', + unshareBankAccountDescription: 'Everyone below has access to this bank account. You can remove access at any point. We’ll still complete any payments in process.', + unshareBankAccountWarning: ({admin}: {admin?: string | null}) => `${admin} will lose access to this business bank account. We’ll still complete any payments in process.`, + reachOutForHelp: 'It’s being used with the Expensify Card. Reach out to Concierge if you need to unshare it.', + unshareErrorModalTitle: 'Can’t unshare bank account', }, cardPage: { expensifyCard: 'Expensify Card', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6f3276c0078c..f7239e05feb6 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -12,6 +12,7 @@ const translations: TranslationDeepObject = { cancel: 'Cancelar', dismiss: 'Descartar', proceed: 'Proceder', + unshare: 'Dejar de compartir', yes: 'Sí', no: 'No', ok: 'OK', @@ -1811,6 +1812,12 @@ const translations: TranslationDeepObject = { shareBankAccountEmptyTitle: 'No hay administradores disponibles', shareBankAccountEmptyDescription: 'No hay administradores del espacio de trabajo con los que puedas compartir esta cuenta bancaria', shareBankAccountNoAdminsSelected: 'Seleccione un administrador antes de continuar', + unshareBankAccount: 'Dejar de compartir la cuenta bancaria', + unshareBankAccountDescription: + 'Todas las personas a continuación tienen acceso a esta cuenta bancaria. Puede retirar el acceso en cualquier momento. Seguiremos completando los pagos en proceso.', + unshareBankAccountWarning: ({admin}: {admin?: string | null}) => `${admin} perderá el acceso a esta cuenta bancaria comercial. Seguiremos completando los pagos en proceso.`, + reachOutForHelp: 'Se está usando con la tarjeta Expensify. Contacte con Concierge si necesita dejar de compartirla.', + unshareErrorModalTitle: 'No se puede dejar de compartir la cuenta bancaria', }, cardPage: { expensifyCard: 'Tarjeta Expensify', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 148731c79798..bda734c4fc40 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -254,6 +254,7 @@ const translations: TranslationDeepObject = { dismiss: 'Fermer', // @context Used on a button to continue an action or workflow, not the formal or procedural sense of “to proceed.” proceed: 'Continuer', + unshare: 'Partager', yes: 'Oui', no: 'Non', // @context Universal confirmation button. Keep the UI-standard term “OK” unless the locale strongly prefers an alternative. @@ -2120,6 +2121,11 @@ const translations: TranslationDeepObject = { shareBankAccountEmptyTitle: 'Aucun administrateur disponible', shareBankAccountEmptyDescription: "Aucun administrateur d'espace de travail n'est disponible pour partager ce compte bancaire.", shareBankAccountNoAdminsSelected: 'Veuillez sélectionner un administrateur avant de continuer', + unshareBankAccount: 'Retirer le partage du compte bancaire', + unshareBankAccountDescription: 'Toutes les personnes ci-dessous ont accès à ce compte bancaire. Vous pouvez révoquer l’accès à tout moment. Les paiements en cours seront honorés.', + unshareBankAccountWarning: ({admin}: {admin?: string | null}) => `${admin} perdra l’accès à ce compte bancaire professionnel. Les paiements en cours seront honorés.`, + reachOutForHelp: 'Ce compte est utilisé avec la carte Expensify. Contactez le service de conciergerie si vous souhaitez le retirer du partage.', + unshareErrorModalTitle: 'Impossible de retirer le partage du compte bancaire', }, cardPage: { expensifyCard: 'Carte Expensify', diff --git a/src/languages/it.ts b/src/languages/it.ts index 4cba9c8c0fa7..b0d5ae6ee457 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -254,6 +254,7 @@ const translations: TranslationDeepObject = { dismiss: 'Chiudi', // @context Used on a button to continue an action or workflow, not the formal or procedural sense of “to proceed.” proceed: 'Continua', + unshare: 'Non condividere', yes: 'Sì', no: 'No', // @context Universal confirmation button. Keep the UI-standard term “OK” unless the locale strongly prefers an alternative. @@ -2110,6 +2111,12 @@ const translations: TranslationDeepObject = { shareBankAccountEmptyTitle: 'Nessun amministratore disponibile', shareBankAccountEmptyDescription: "Non ci sono amministratori dell'area di lavoro con cui puoi condividere questo conto bancario.", shareBankAccountNoAdminsSelected: 'Seleziona un amministratore prima di continuare', + unshareBankAccount: 'Annulla condivisione conto bancario', + unshareBankAccountDescription: + "Tutti i seguenti hanno accesso a questo conto bancario. Puoi revocare l'accesso in qualsiasi momento. Completeremo comunque tutti i pagamenti in corso.", + unshareBankAccountWarning: ({admin}: {admin?: string | null}) => `${admin} perderà l'accesso a questo conto bancario aziendale. Completeremo comunque tutti i pagamenti in corso.`, + reachOutForHelp: 'È in uso con la carta Expensify. Contatta il Concierge se devi revocare la condivisione.', + unshareErrorModalTitle: 'Impossibile revocare la condivisione del conto bancario', }, cardPage: { expensifyCard: 'Carta Expensify', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index f59de15ab3da..980f0686df3a 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -254,6 +254,7 @@ const translations: TranslationDeepObject = { dismiss: '閉じる', // @context Used on a button to continue an action or workflow, not the formal or procedural sense of “to proceed.” proceed: '続行', + unshare: '共有解除', yes: 'はい', no: 'いいえ', // @context Universal confirmation button. Keep the UI-standard term “OK” unless the locale strongly prefers an alternative. @@ -2107,6 +2108,11 @@ const translations: TranslationDeepObject = { shareBankAccountEmptyTitle: '管理者がいません', shareBankAccountEmptyDescription: 'この銀行口座を共有できるワークスペース管理者がいません', shareBankAccountNoAdminsSelected: '続行する前に管理者を選択してください', + unshareBankAccount: '銀行口座の共有を解除してください', + unshareBankAccountDescription: '以下の全員がこの銀行口座にアクセスできます。いつでもアクセスを削除できます。処理中のお支払いは引き続き完了します。', + unshareBankAccountWarning: ({admin}: {admin?: string | null}) => `${admin} はこのビジネス銀行口座にアクセスできなくなります。処理中のお支払いは引き続き完了します。`, + reachOutForHelp: 'この口座は Expensify カードで使用されています。共有を解除する必要がある場合は、コンシェルジュまでお問い合わせください。', + unshareErrorModalTitle: '銀行口座の共有を解除できません', }, cardPage: { expensifyCard: 'Expensify Card', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 900be1b8e3f4..4e1dab33b6f6 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -254,6 +254,7 @@ const translations: TranslationDeepObject = { dismiss: 'Sluiten', // @context Used on a button to continue an action or workflow, not the formal or procedural sense of “to proceed.” proceed: 'Doorgaan', + unshare: 'Niet meer delen', yes: 'Ja', no: 'Nee', // @context Universal confirmation button. Keep the UI-standard term “OK” unless the locale strongly prefers an alternative. @@ -2108,6 +2109,13 @@ const translations: TranslationDeepObject = { shareBankAccountEmptyTitle: 'Geen beheerders beschikbaar', shareBankAccountEmptyDescription: 'Er zijn geen werkruimtebeheerders waarmee u deze bankrekening kunt delen.', shareBankAccountNoAdminsSelected: 'Selecteer een beheerder voordat u verdergaat', + unshareBankAccount: 'Deelname bankrekening ongedaan maken', + unshareBankAccountDescription: + 'Iedereen hieronder heeft toegang tot deze bankrekening. U kunt de toegang op elk moment intrekken. We zullen alle betalingen die in behandeling zijn nog steeds voltooien.', + unshareBankAccountWarning: ({admin}: {admin?: string | null}) => + `${admin} verliest de toegang tot deze zakelijke bankrekening. We zullen alle betalingen die in behandeling zijn nog steeds voltooien.`, + reachOutForHelp: 'Deze wordt gebruikt met de Expensify Card. Neem contact op met Concierge als u de deling ongedaan wilt maken.', + unshareErrorModalTitle: 'Deling bankrekening kan niet ongedaan worden gemaakt', }, cardPage: { expensifyCard: 'Expensify Card', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 9f7e9a85d497..f76e4623ee5f 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -254,6 +254,7 @@ const translations: TranslationDeepObject = { dismiss: 'Zamknij', // @context Used on a button to continue an action or workflow, not the formal or procedural sense of “to proceed.” proceed: 'Kontynuuj', + unshare: 'Cofnij udostępnianie', yes: 'Tak', no: 'Nie', // @context Universal confirmation button. Keep the UI-standard term “OK” unless the locale strongly prefers an alternative. @@ -2105,6 +2106,11 @@ const translations: TranslationDeepObject = { shareBankAccountEmptyTitle: 'Brak dostępnych administratorów', shareBankAccountEmptyDescription: 'Brak administratorów obszaru roboczego, z którymi można udostępnić to konto bankowe.', shareBankAccountNoAdminsSelected: 'Wybierz administratora przed kontynuowaniem', + unshareBankAccount: 'Anuluj udostępnianie konta bankowego', + unshareBankAccountDescription: 'Wszyscy poniżej mają dostęp do tego konta bankowego. Możesz go usunąć w dowolnym momencie. Nadal będziemy realizować wszystkie płatności w toku.', + unshareBankAccountWarning: ({admin}: {admin?: string | null}) => `${admin} utraci dostęp do tego firmowego konta bankowego. Nadal będziemy realizować wszystkie płatności w toku.`, + reachOutForHelp: 'Jest ono używane z kartą Expensify. Skontaktuj się z Concierge, jeśli chcesz je anulować.', + unshareErrorModalTitle: 'Nie można anulować udostępniania konta bankowego', }, cardPage: { expensifyCard: 'Karta Expensify', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f3c9bb6255be..5773fb99d947 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -254,6 +254,7 @@ const translations: TranslationDeepObject = { dismiss: 'Fechar', // @context Used on a button to continue an action or workflow, not the formal or procedural sense of “to proceed.” proceed: 'Prosseguir', + unshare: 'Deixar de compartilhar', yes: 'Sim', no: 'Não', // @context Universal confirmation button. Keep the UI-standard term “OK” unless the locale strongly prefers an alternative. @@ -2105,6 +2106,11 @@ const translations: TranslationDeepObject = { shareBankAccountEmptyTitle: 'Nenhum administrador disponível', shareBankAccountEmptyDescription: 'Não há administradores de espaço de trabalho com quem você possa compartilhar esta conta bancária.', shareBankAccountNoAdminsSelected: 'Selecione um administrador antes de continuar', + unshareBankAccount: 'Descompartilhar conta bancária', + unshareBankAccountDescription: 'Todos abaixo têm acesso a esta conta bancária. Você pode remover o acesso a qualquer momento. Ainda concluiremos quaisquer pagamentos em andamento.', + unshareBankAccountWarning: ({admin}: {admin?: string | null}) => `${admin} perderá o acesso a esta conta bancária comercial. Ainda concluiremos quaisquer pagamentos em andamento.`, + reachOutForHelp: 'Está sendo usada com o Cartão Expensify. Entre em contato com o Concierge se precisar descompartilhar.', + unshareErrorModalTitle: 'Não foi possível descompartilhar a conta bancária', }, cardPage: { expensifyCard: 'Cartão Expensify', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index faf82a4c4e3d..0dc7382cd4fa 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -260,6 +260,7 @@ const translations: TranslationDeepObject = { ok: 'OK', notNow: '现在不要', noThanks: '不用,谢谢', + unshare: '取消分享', learnMore: '了解更多', buttonConfirm: '明白了', name: '名称', @@ -2078,6 +2079,11 @@ const translations: TranslationDeepObject = { shareBankAccountEmptyTitle: '暂无管理员可用', shareBankAccountEmptyDescription: '没有可与您共享此银行账户的工作区管理员。', shareBankAccountNoAdminsSelected: '请先选择一位管理员再继续', + unshareBankAccount: '取消共享银行账户', + unshareBankAccountDescription: '以下所有用户均可访问此银行账户。您可以随时取消访问权限。我们仍将完成所有正在进行的付款。', + unshareBankAccountWarning: ({admin}: {admin?: string | null}) => `${admin} 将失去对此企业银行账户的访问权限。我们仍将完成所有正在进行的付款。`, + reachOutForHelp: '此账户正在与 Expensify 卡一起使用。如果您需要取消共享,请联系礼宾部。', + unshareErrorModalTitle: '无法取消共享银行账户', }, cardPage: { expensifyCard: 'Expensify Card', diff --git a/src/libs/API/parameters/UnshareBankAccountParams.ts b/src/libs/API/parameters/UnshareBankAccountParams.ts new file mode 100644 index 000000000000..ef5f938b6fa4 --- /dev/null +++ b/src/libs/API/parameters/UnshareBankAccountParams.ts @@ -0,0 +1,6 @@ +type UnshareBankAccountParams = { + bankAccountID: number; + ownerEmail: string; +}; + +export default UnshareBankAccountParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index b63e384818b2..3db225ba1ef7 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -137,6 +137,7 @@ export type {default as TogglePolicyUberAutoRemovePageParams} from './TogglePoli export type {default as InviteToRoomParams} from './InviteToRoomParams'; export type {default as InviteToGroupChatParams} from './InviteToGroupChatParams'; export type {default as InviteWorkspaceEmployeesToUberParams} from './InviteWorkspaceEmployeesToUberParams'; +export type {default as UnshareBankAccountParams} from './UnshareBankAccountParams'; export type {default as RemoveFromRoomParams} from './RemoveFromRoomParams'; export type {default as RemoveFromGroupChatParams} from './RemoveFromGroupChatParams'; export type {default as FlagCommentParams} from './FlagCommentParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a0a92b0bfe58..d0bb988e9c3d 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -100,6 +100,7 @@ const WRITE_COMMANDS = { SIGN_IN_USER: 'SigninUser', SIGN_IN_USER_WITH_LINK: 'SigninUserWithLink', REQUEST_UNLINK_VALIDATION_LINK: 'RequestUnlinkValidationLink', + UNSHARE_BANK_ACCOUNT: 'UnshareBankAccount', UNLINK_LOGIN: 'UnlinkLogin', ENABLE_TWO_FACTOR_AUTH: 'EnableTwoFactorAuth', DISABLE_TWO_FACTOR_AUTH: 'DisableTwoFactorAuth', @@ -889,6 +890,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.RESOLVE_DUPLICATES]: Parameters.ResolveDuplicatesParams; [WRITE_COMMANDS.MERGE_TRANSACTION]: Parameters.MergeTransactionParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_TYPE]: Parameters.UpdateSubscriptionTypeParams; + [WRITE_COMMANDS.UNSHARE_BANK_ACCOUNT]: Parameters.UnshareBankAccountParams; [WRITE_COMMANDS.SIGN_UP_USER]: Parameters.SignUpUserParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_AUTO_RENEW]: Parameters.UpdateSubscriptionAutoRenewParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY]: Parameters.UpdateSubscriptionAddNewUsersAutomaticallyParams; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 74b9b20d7b44..0ccc117e8a9b 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -388,6 +388,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Wallet/TransferBalancePage').default, [SCREENS.SETTINGS.WALLET.CHOOSE_TRANSFER_ACCOUNT]: () => require('../../../../pages/settings/Wallet/ChooseTransferAccountPage').default, [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS]: () => require('../../../../pages/EnablePayments/EnablePayments').default, + [SCREENS.SETTINGS.WALLET.UNSHARE_BANK_ACCOUNT]: () => require('../../../../pages/settings/Wallet/UnshareBankAccount/UnshareBankAccount').default, [SCREENS.SETTINGS.WALLET.ENABLE_GLOBAL_REIMBURSEMENTS]: () => require('../../../../pages/settings/Wallet/EnableGlobalReimbursements').default, [SCREENS.SETTINGS.WALLET.SHARE_BANK_ACCOUNT]: () => require('../../../../pages/settings/Wallet/ShareBankAccount/ShareBankAccount').default, [SCREENS.SETTINGS.ADD_DEBIT_CARD]: () => require('../../../../pages/settings/Wallet/AddDebitCardPage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts index f4ffc0191c74..95e867346e8a 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts @@ -38,6 +38,7 @@ const SETTINGS_TO_RHP: Partial['config'] = { path: ROUTES.SETTINGS_ENABLE_PAYMENTS, exact: true, }, + [SCREENS.SETTINGS.WALLET.UNSHARE_BANK_ACCOUNT]: { + path: ROUTES.SETTINGS_WALLET_UNSHARE_BANK_ACCOUNT.route, + exact: true, + }, [SCREENS.SETTINGS.WALLET.ENABLE_GLOBAL_REIMBURSEMENTS]: { path: ROUTES.SETTINGS_WALLET_ENABLE_GLOBAL_REIMBURSEMENTS.route, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 30eaaae910ff..58e320b05a4f 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -201,6 +201,9 @@ type SettingsNavigatorParamList = { [SCREENS.SETTINGS.WALLET.TRANSFER_BALANCE]: undefined; [SCREENS.SETTINGS.WALLET.CHOOSE_TRANSFER_ACCOUNT]: undefined; [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS]: undefined; + [SCREENS.SETTINGS.WALLET.UNSHARE_BANK_ACCOUNT]: { + bankAccountID: string; + }; [SCREENS.SETTINGS.WALLET.ENABLE_GLOBAL_REIMBURSEMENTS]: { bankAccountID: string; }; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 4d033fae568d..1fbdcaf9292f 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -15,6 +15,7 @@ import type { SaveCorpayOnboardingBeneficialOwnerParams, SendReminderForCorpaySignerInformationParams, ShareBankAccountParams, + UnshareBankAccountParams, ValidateBankAccountWithTransactionsParams, VerifyIdentityForBankAccountParams, } from '@libs/API/parameters'; @@ -1220,6 +1221,56 @@ function fetchCorpayFields(bankCountry: string, bankCurrency?: string, isWithdra ); } +function clearUnshareBankAccountErrors(bankAccountID: number) { + Onyx.merge(ONYXKEYS.UNSHARE_BANK_ACCOUNT, {errors: null}); + Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, {[bankAccountID]: {errors: null}}); +} + +function unshareBankAccount(bankAccountID: number, ownerEmail: string) { + const parameters: UnshareBankAccountParams = { + bankAccountID, + ownerEmail, + }; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.UNSHARE_BANK_ACCOUNT, + value: { + isLoading: true, + errors: null, + email: ownerEmail, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.UNSHARE_BANK_ACCOUNT, + value: { + isLoading: false, + errors: null, + shouldShowSuccess: true, + email: null, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.UNSHARE_BANK_ACCOUNT, + value: { + isLoading: false, + errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + }, + }, + ], + }; + + API.write(WRITE_COMMANDS.UNSHARE_BANK_ACCOUNT, parameters, onyxData); +} + function createCorpayBankAccountForWalletFlow(data: InternationalBankAccountForm, classification: string, destinationCountry: string, preferredMethod: string) { const inputData = { ...data, @@ -1410,6 +1461,8 @@ export { createCorpayBankAccountForWalletFlow, getCorpayOnboardingFields, saveCorpayOnboardingCompanyDetails, + unshareBankAccount, + clearUnshareBankAccountErrors, clearReimbursementAccountSaveCorpayOnboardingCompanyDetails, saveCorpayOnboardingBeneficialOwners, saveCorpayOnboardingDirectorInformation, diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index 2c9432845785..d3be766079bf 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -132,6 +132,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr const hasActivatedWallet = ([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM] as string[]).includes(userWallet?.tierName ?? ''); const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, {canBeMissing: true}); const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, {canBeMissing: true}); + const [unsharedBankAccount] = useOnyx(ONYXKEYS.UNSHARE_BANK_ACCOUNT, {canBeMissing: true}); const privateSubscription = usePrivateSubscription(); const subscriptionPlan = useSubscriptionPlan(); const previousUserPersonalDetails = usePrevious(currentUserPersonalDetails); @@ -146,7 +147,13 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr const hasBrokenFeedConnection = checkIfFeedConnectionIsBroken(allCards, CONST.EXPENSIFY_CARD.BANK); const hasPendingCardAction = hasPendingExpensifyCardAction(allCards, privatePersonalDetails); const walletBrickRoadIndicator = useMemo(() => { - if (hasPaymentMethodError(bankAccountList, fundList, allCards) || !isEmptyObject(userWallet?.errors) || !isEmptyObject(walletTerms?.errors) || hasBrokenFeedConnection) { + if ( + hasPaymentMethodError(bankAccountList, fundList, allCards) || + !isEmptyObject(userWallet?.errors) || + !isEmptyObject(walletTerms?.errors) || + !isEmptyObject(unsharedBankAccount?.errors) || + hasBrokenFeedConnection + ) { return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } if (hasPartiallySetupBankAccount(bankAccountList)) { @@ -156,7 +163,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr return CONST.BRICK_ROAD_INDICATOR_STATUS.INFO; } return undefined; - }, [allCards, bankAccountList, fundList, hasBrokenFeedConnection, hasPendingCardAction, userWallet?.errors, walletTerms?.errors]); + }, [allCards, bankAccountList, fundList, hasBrokenFeedConnection, hasPendingCardAction, unsharedBankAccount?.errors, userWallet?.errors, walletTerms?.errors]); const [shouldShowSignoutConfirmModal, setShouldShowSignoutConfirmModal] = useState(false); diff --git a/src/pages/settings/Wallet/UnshareBankAccount/UnshareBankAccount.tsx b/src/pages/settings/Wallet/UnshareBankAccount/UnshareBankAccount.tsx new file mode 100644 index 000000000000..acbbad521004 --- /dev/null +++ b/src/pages/settings/Wallet/UnshareBankAccount/UnshareBankAccount.tsx @@ -0,0 +1,189 @@ +import React, {useEffect, useState} from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import ConfirmModal from '@components/ConfirmModal'; +import ErrorMessageRow from '@components/ErrorMessageRow'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import RenderHTML from '@components/RenderHTML'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import UserListItem from '@components/SelectionList/ListItem/UserListItem'; +import type {ListItem} from '@components/SelectionList/types'; +import Text from '@components/Text'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useDebouncedState from '@hooks/useDebouncedState'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {formatMemberForList, getHeaderMessage, getSearchValueForPhoneOrEmail} from '@libs/OptionsListUtils'; +import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; +import tokenizedSearch from '@libs/tokenizedSearch'; +import Navigation from '@navigation/Navigation'; +import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import {clearUnshareBankAccountErrors, unshareBankAccount} from '@userActions/BankAccounts'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; + +type ShareBankAccountProps = PlatformStackScreenProps; + +function UnshareBankAccount({route}: ShareBankAccountProps) { + const bankAccountID = route.params?.bankAccountID; + const styles = useThemeStyles(); + const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true}); + const [showExpensifyCardErrorModal, setShowExpensifyCardErrorModal] = useState(false); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [unsharedBankAccountData] = useOnyx(ONYXKEYS.UNSHARE_BANK_ACCOUNT, {canBeMissing: true}); + const [unshareUser, setUnshareUser] = useState<{login?: string | null; text?: string | null} | undefined>(undefined); + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); + const {translate} = useLocalize(); + const admins = bankAccountList?.[bankAccountID]?.accountData?.sharees; + const totalAdmins = bankAccountList?.[bankAccountID]?.accountData?.sharees?.length; + const isExpensifyCardSettlementAccount = bankAccountList?.[bankAccountID]?.isExpensifyCardSettlementAccount ?? false; + const shouldShowTextInput = Number(totalAdmins) >= CONST.STANDARD_LIST_ITEM_LIMIT; + const textInputLabel = shouldShowTextInput ? translate('common.search') : undefined; + const isLoading = unsharedBankAccountData?.isLoading ?? false; + const shouldShowSuccess = unsharedBankAccountData?.shouldShowSuccess ?? false; + + useEffect(() => { + if (!shouldShowSuccess) { + return; + } + if (!totalAdmins) { + Navigation.goBack(); + } + }, [totalAdmins, shouldShowSuccess]); + + const handleUnshare = () => { + if (!bankAccountID || !unshareUser?.login) { + return; + } + + // Unsharing a bank account isn’t possible if the selected user’s copy of the bank account is set as an Expensify Card settlement account. + if (isExpensifyCardSettlementAccount) { + setUnshareUser(undefined); + setShowExpensifyCardErrorModal(true); + return; + } + unshareBankAccount(Number(bankAccountID), unshareUser.login); + setUnshareUser(undefined); + }; + + const getAdminsList = () => { + if (admins?.length === 0) { + return []; + } + const adminsWithInfo = + admins + ?.filter((admin) => admin !== currentUserPersonalDetails?.email) + .map((admin) => { + const personalDetails = getPersonalDetailByEmail(admin); + const formattedAdmin = formatMemberForList({ + text: personalDetails?.displayName, + alternateText: personalDetails?.login, + keyForList: personalDetails?.login, + accountID: personalDetails?.accountID, + login: personalDetails?.login, + pendingAction: personalDetails?.pendingAction, + reportID: '', + }); + return {...formattedAdmin, isInteractive: false}; + }) ?? []; + + let adminsToDisplay = [...adminsWithInfo]; + if (debouncedSearchTerm) { + const searchValue = getSearchValueForPhoneOrEmail(debouncedSearchTerm, countryCode).toLowerCase(); + adminsToDisplay = tokenizedSearch(adminsWithInfo, searchValue, (option) => [option.text ?? '', option.alternateText ?? '']); + } + return adminsToDisplay; + }; + + const hideUnshareErrorModal = () => setShowExpensifyCardErrorModal(false); + + const itemRightSideComponent = (item: ListItem) => { + return ( +