diff --git a/clients/banking/src/components/CardsArea.tsx b/clients/banking/src/components/CardsArea.tsx index 83b7899d0..b9d2d8d7a 100644 --- a/clients/banking/src/components/CardsArea.tsx +++ b/clients/banking/src/components/CardsArea.tsx @@ -1,4 +1,4 @@ -import { Option } from "@swan-io/boxed"; +import { AsyncData, Option, Result } from "@swan-io/boxed"; import { Breadcrumbs, BreadcrumbsRoot } from "@swan-io/lake/src/components/Breadcrumbs"; import { FullViewportLayer } from "@swan-io/lake/src/components/FullViewportLayer"; import { LakeButton } from "@swan-io/lake/src/components/LakeButton"; @@ -7,11 +7,11 @@ import { ResponsiveContainer } from "@swan-io/lake/src/components/ResponsiveCont import { Space } from "@swan-io/lake/src/components/Space"; import { commonStyles } from "@swan-io/lake/src/constants/commonStyles"; import { breakpoints, colors, spacings } from "@swan-io/lake/src/constants/design"; +import { useDeferredUrqlQuery } from "@swan-io/lake/src/hooks/useUrqlQuery"; import { isNotNullish, isNullish } from "@swan-io/lake/src/utils/nullish"; -import { Suspense, useMemo } from "react"; +import { Suspense, useEffect, useMemo } from "react"; import { StyleSheet, View } from "react-native"; import { P, match } from "ts-pattern"; -import { useQuery } from "urql"; import { AccountAreaQuery, CardCountWithAccountDocument, @@ -23,6 +23,7 @@ import { t } from "../utils/i18n"; import { Router } from "../utils/routes"; import { CardItemArea } from "./CardItemArea"; import { CardWizard } from "./CardWizard"; +import { ErrorView } from "./ErrorView"; import { Redirect } from "./Redirect"; const styles = StyleSheet.create({ @@ -91,43 +92,45 @@ const useDisplayableCardsInformation = ({ } as const; }, [accountId]); - const [withAccountQuery] = useQuery({ - query: CardCountWithAccountDocument, - pause: !hasAccountId, - variables: { - first: 1, - filters: filtersWithAccount, - }, - }); + const { data: withAccountQuery, query: queryWithAccount } = useDeferredUrqlQuery( + CardCountWithAccountDocument, + ); - const [withoutAccountQuery] = useQuery({ - query: CardCountWithoutAccountDocument, - pause: hasAccountId, - variables: { - accountMembershipId, - first: 1, - filters: relevantCardsFilter, - }, - }); + const { data: withoutAccountQuery, query: queryWithoutAccount } = useDeferredUrqlQuery( + CardCountWithoutAccountDocument, + ); + + useEffect(() => { + if (hasAccountId) { + queryWithAccount({ + first: 1, + filters: filtersWithAccount, + }); + } else { + queryWithoutAccount({ + accountMembershipId, + first: 1, + filters: relevantCardsFilter, + }); + } + }, [accountMembershipId, accountId, filtersWithAccount]); if (hasAccountId) { - return { + return withAccountQuery.mapOk(data => ({ onlyCardId: - withAccountQuery.data?.cards.totalCount === 1 - ? Option.fromNullable(withAccountQuery.data?.cards.edges[0]?.node.id) + data?.cards.totalCount === 1 + ? Option.fromNullable(data?.cards.edges[0]?.node.id) : Option.None(), - totalDisplayableCardCount: withAccountQuery.data?.cards.totalCount ?? 0, - }; + totalDisplayableCardCount: data?.cards.totalCount ?? 0, + })); } else { - return { + return withoutAccountQuery.mapOk(data => ({ onlyCardId: - withoutAccountQuery.data?.accountMembership?.cards.totalCount === 1 - ? Option.fromNullable( - withoutAccountQuery.data?.accountMembership?.cards.edges[0]?.node.id, - ) + data?.accountMembership?.cards.totalCount === 1 + ? Option.fromNullable(data?.accountMembership?.cards.edges[0]?.node.id) : Option.None(), - totalDisplayableCardCount: withoutAccountQuery.data?.accountMembership?.cards.totalCount ?? 0, - }; + totalDisplayableCardCount: data?.accountMembership?.cards.totalCount ?? 0, + })); } }; @@ -145,7 +148,7 @@ export const CardsArea = ({ }: Props) => { const route = Router.useRoute(["AccountCardsList", "AccountCardsItemArea"]); - const { onlyCardId, totalDisplayableCardCount } = useDisplayableCardsInformation({ + const data = useDisplayableCardsInformation({ accountMembershipId, accountId, }); @@ -160,111 +163,123 @@ export const CardsArea = ({ [accountMembershipId], ); - if (onlyCardId.isSome() && route?.name !== "AccountCardsItemArea") { - return ( - - ); - } - if (isNullish(route?.name)) { return ; } - return ( - - {({ large }) => ( - - - {totalDisplayableCardCount > 1 ? ( - - - - ) : null} + return match(data) + .with(AsyncData.P.NotAsked, AsyncData.P.Loading, () => ) + .with(AsyncData.P.Done(Result.P.Error(P.select())), error => ) + .with( + AsyncData.P.Done(Result.P.Ok(P.select())), + ({ onlyCardId, totalDisplayableCardCount }) => { + if (onlyCardId.isSome() && route?.name !== "AccountCardsItemArea") { + return ( + + ); + } - {onlyCardId.isSome() ? : null} + return ( + + {({ large }) => ( + + + {totalDisplayableCardCount > 1 ? ( + + + + ) : null} - }> - - {match(route) - .with( - { name: "AccountCardsList" }, - ({ params: { accountMembershipId, new: _, ...params } }) => ( - - ), - ) - .with({ name: "AccountCardsItemArea" }, ({ params: { cardId } }) => ( - <> - {canAddCard && cardOrderVisible && onlyCardId.isSome() ? ( - - - Router.push("AccountCardsItem", { - cardId, - accountMembershipId, - new: "", - }) - } - > - {t("common.new")} - - - ) : null} + {onlyCardId.isSome() ? : null} - - - )) - .with(P.nullish, () => null) - .exhaustive()} - - + }> + + {match(route) + .with( + { name: "AccountCardsList" }, + ({ params: { accountMembershipId, new: _, ...params } }) => ( + + ), + ) + .with({ name: "AccountCardsItemArea" }, ({ params: { cardId } }) => ( + <> + {canAddCard && cardOrderVisible && onlyCardId.isSome() ? ( + + + Router.push("AccountCardsItem", { + cardId, + accountMembershipId, + new: "", + }) + } + > + {t("common.new")} + + + ) : null} - - { - match(route) - .with({ name: P.string }, ({ name, params }) => { - Router.push(name === "AccountCardsItemArea" ? "AccountCardsItem" : name, { - ...params, - new: undefined, - }); - }) - .otherwise(() => {}); - }} - /> - - - - )} - - ); + + + )) + .with(P.nullish, () => null) + .exhaustive()} + + + + + { + match(route) + .with({ name: P.string }, ({ name, params }) => { + Router.push( + name === "AccountCardsItemArea" ? "AccountCardsItem" : name, + { + ...params, + new: undefined, + }, + ); + }) + .otherwise(() => {}); + }} + /> + + + + )} + + ); + }, + ) + .exhaustive(); }; diff --git a/clients/banking/src/components/MembershipDetailArea.tsx b/clients/banking/src/components/MembershipDetailArea.tsx index 1b99edd3a..66a2e17ff 100644 --- a/clients/banking/src/components/MembershipDetailArea.tsx +++ b/clients/banking/src/components/MembershipDetailArea.tsx @@ -1,21 +1,23 @@ +import { AsyncData, Result } from "@swan-io/boxed"; import { Box } from "@swan-io/lake/src/components/Box"; import { LakeAlert } from "@swan-io/lake/src/components/LakeAlert"; import { LakeHeading } from "@swan-io/lake/src/components/LakeHeading"; import { LakeText } from "@swan-io/lake/src/components/LakeText"; import { ListRightPanelContent } from "@swan-io/lake/src/components/ListRightPanel"; +import { LoadingView } from "@swan-io/lake/src/components/LoadingView"; import { Space } from "@swan-io/lake/src/components/Space"; import { TabView } from "@swan-io/lake/src/components/TabView"; import { Tag } from "@swan-io/lake/src/components/Tag"; import { Tile } from "@swan-io/lake/src/components/Tile"; import { commonStyles } from "@swan-io/lake/src/constants/commonStyles"; import { colors, negativeSpacings, spacings } from "@swan-io/lake/src/constants/design"; +import { useUrqlQuery } from "@swan-io/lake/src/hooks/useUrqlQuery"; import { isNotNullishOrEmpty } from "@swan-io/lake/src/utils/nullish"; import { CountryCCA3 } from "@swan-io/shared-business/src/constants/countries"; import dayjs from "dayjs"; import { useMemo } from "react"; import { ScrollView, StyleSheet, View } from "react-native"; import { P, match } from "ts-pattern"; -import { useQuery } from "urql"; import { AccountMembershipFragment, MembershipDetailDocument } from "../graphql/partner"; import { getMemberName } from "../utils/accountMembership"; import { t } from "../utils/i18n"; @@ -90,290 +92,297 @@ export const MembershipDetailArea = ({ }: Props) => { const route = Router.useRoute(membershipsDetailRoutes); - const [{ data }, reload] = useQuery({ + const { data, reload } = useUrqlQuery({ query: MembershipDetailDocument, variables: { accountMembershipId: editingAccountMembershipId }, }); const accountMembership = useMemo(() => { - return match(data) - .returnType() - .with( - { - accountMembership: { - canManageAccountMembership: false, - canInitiatePayments: false, - canManageBeneficiaries: false, - canViewAccount: false, - canManageCards: false, - statusInfo: { - __typename: "AccountMembershipBindingUserErrorStatusInfo", - idVerifiedMatchError: true, + return data.mapOk(data => + match(data) + .returnType() + .with( + { + accountMembership: { + canManageAccountMembership: false, + canInitiatePayments: false, + canManageBeneficiaries: false, + canViewAccount: false, + canManageCards: false, + statusInfo: { + __typename: "AccountMembershipBindingUserErrorStatusInfo", + idVerifiedMatchError: true, + }, }, + projectInfo: { B2BMembershipIDVerification: false }, }, - projectInfo: { B2BMembershipIDVerification: false }, - }, - ({ accountMembership }) => ({ - ...accountMembership, - statusInfo: { - ...accountMembership.statusInfo, - idVerifiedMatchError: false, - }, - }), - ) - .otherwise(() => data?.accountMembership ?? undefined); + ({ accountMembership }) => ({ + ...accountMembership, + statusInfo: { + ...accountMembership.statusInfo, + idVerifiedMatchError: false, + }, + }), + ) + .otherwise(() => data?.accountMembership ?? undefined), + ); }, [data]); - if (accountMembership == null) { - return null; - } - - const requiresIdentityVerification = - shouldDisplayIdVerification && accountMembership.hasRequiredIdentificationLevel === false; + return match(data) + .with(AsyncData.P.NotAsked, AsyncData.P.Loading, () => ( + + )) + .with(AsyncData.P.Done(Result.P.Error(P.select())), error => ) + .with(AsyncData.P.Done(Result.P.Ok(P.select())), ({ accountMembership }) => { + if (accountMembership == null) { + return null; + } - return ( - - - - ( - - ), - ) - .with({ __typename: "AccountMembershipBindingUserErrorStatusInfo" }, () => ( - - )) - .otherwise(() => null)} - > - - {match(accountMembership.statusInfo) - .with({ __typename: "AccountMembershipEnabledStatusInfo" }, () => ( - {t("memberships.status.active")} - )) + const requiresIdentityVerification = + shouldDisplayIdVerification && accountMembership.hasRequiredIdentificationLevel === false; + + + + {t("memberships.status.limitedAccess")}, + () => ( + + ), ) .with({ __typename: "AccountMembershipBindingUserErrorStatusInfo" }, () => ( - {t("memberships.status.conflict")} - )) - .with({ __typename: "AccountMembershipInvitationSentStatusInfo" }, () => ( - {t("memberships.status.invitationSent")} - )) - .with({ __typename: "AccountMembershipSuspendedStatusInfo" }, () => ( - {t("memberships.status.temporarilyBlocked")} - )) - .with({ __typename: "AccountMembershipDisabledStatusInfo" }, () => ( - {t("memberships.status.permanentlyBlocked")} + )) - .with({ __typename: "AccountMembershipConsentPendingStatusInfo" }, () => null) - .exhaustive()} + .otherwise(() => null)} + > + + {match(accountMembership.statusInfo) + .with({ __typename: "AccountMembershipEnabledStatusInfo" }, () => ( + {t("memberships.status.active")} + )) + .with( + { + __typename: "AccountMembershipBindingUserErrorStatusInfo", + idVerifiedMatchError: true, + }, + () => {t("memberships.status.limitedAccess")}, + ) + .with({ __typename: "AccountMembershipBindingUserErrorStatusInfo" }, () => ( + {t("memberships.status.conflict")} + )) + .with({ __typename: "AccountMembershipInvitationSentStatusInfo" }, () => ( + {t("memberships.status.invitationSent")} + )) + .with({ __typename: "AccountMembershipSuspendedStatusInfo" }, () => ( + {t("memberships.status.temporarilyBlocked")} + )) + .with({ __typename: "AccountMembershipDisabledStatusInfo" }, () => ( + {t("memberships.status.permanentlyBlocked")} + )) + .with({ __typename: "AccountMembershipConsentPendingStatusInfo" }, () => null) + .exhaustive()} - + - - {getMemberName({ accountMembership })} - + + {getMemberName({ accountMembership })} + - + - - {t("membershipDetail.addedAt", { - date: dayjs(accountMembership.createdAt).format("LL"), - })} - - - - + + {t("membershipDetail.addedAt", { + date: dayjs(accountMembership.createdAt).format("LL"), + })} + + + + - + - {match(accountMembership) - .with( - { - statusInfo: { - __typename: "AccountMembershipBindingUserErrorStatusInfo", - idVerifiedMatchError: P.not(true), + {match(accountMembership) + .with( + { + statusInfo: { + __typename: "AccountMembershipBindingUserErrorStatusInfo", + idVerifiedMatchError: P.not(true), + }, + user: P.nonNullable, }, - user: P.nonNullable, - }, - accountMembership => ( - - { - onAccountMembershipUpdate(); - reload(); - }} - /> - - ), - ) - .with( - { - statusInfo: { - __typename: P.union( - "AccountMembershipDisabledStatusInfo", - "AccountMembershipEnabledStatusInfo", - "AccountMembershipBindingUserErrorStatusInfo", - "AccountMembershipInvitationSentStatusInfo", - "AccountMembershipSuspendedStatusInfo", - ), + accountMembership => ( + + { + onAccountMembershipUpdate(); + reload(); + }} + /> + + ), + ) + .with( + { + statusInfo: { + __typename: P.union( + "AccountMembershipDisabledStatusInfo", + "AccountMembershipEnabledStatusInfo", + "AccountMembershipBindingUserErrorStatusInfo", + "AccountMembershipInvitationSentStatusInfo", + "AccountMembershipSuspendedStatusInfo", + ), + }, }, - }, - accountMembership => ( - <> - ( + <> + [ + { + label: t("membershipDetail.cards"), + url: Router.AccountMembersDetailsCardList({ + ...params, + accountMembershipId: currentUserAccountMembershipId, + editingAccountMembershipId, + }), + }, + ], + ) + .otherwise(() => []), + ]} + otherLabel={t("common.tabs.other")} + /> + + + {match({ route, currentUserAccountMembership, accountMembership }) .with( - P.union( - { - currentUserAccountMembership: { canManageCards: true }, - }, - { - accountMembership: { id: currentUserAccountMembershipId }, + { route: { name: "AccountMembersDetailsRoot" } }, + ({ + route: { + params: { showInvitationLink }, }, + }) => ( + { + reload(); + onRefreshRequest(); + }} + large={large} + showInvitationLink={isNotNullishOrEmpty(showInvitationLink)} + /> ), - () => [ - { - label: t("membershipDetail.cards"), - url: Router.AccountMembersDetailsCardList({ - ...params, - accountMembershipId: currentUserAccountMembershipId, - editingAccountMembershipId, - }), - }, - ], ) - .otherwise(() => []), - ]} - otherLabel={t("common.tabs.other")} - /> - - - {match({ route, currentUserAccountMembership, accountMembership }) - .with( - { route: { name: "AccountMembersDetailsRoot" } }, - ({ - route: { - params: { showInvitationLink }, - }, - }) => ( - ( + { reload(); onRefreshRequest(); }} large={large} - showInvitationLink={isNotNullishOrEmpty(showInvitationLink)} /> - ), - ) - .with({ route: { name: "AccountMembersDetailsRights" } }, () => ( - { - reload(); - onRefreshRequest(); - }} - large={large} - /> - )) - .with( - P.union( - { - route: { name: "AccountMembersDetailsCardList" }, - currentUserAccountMembership: { canManageCards: true }, - }, - { - route: { name: "AccountMembersDetailsCardList" }, - accountMembership: { id: currentUserAccountMembershipId }, - }, - ), - ({ - route: { - params: { - accountMembershipId, - editingAccountMembershipId, - newCard: isCardWizardOpen, - ...params + )) + .with( + P.union( + { + route: { name: "AccountMembersDetailsCardList" }, + currentUserAccountMembership: { canManageCards: true }, }, - }, - }) => ( - - - - ), - ) - .otherwise(() => null)} - - - ), - ) - .otherwise(() => ( - - ))} - - - ); + { + route: { name: "AccountMembersDetailsCardList" }, + accountMembership: { id: currentUserAccountMembershipId }, + }, + ), + ({ + route: { + params: { + accountMembershipId, + editingAccountMembershipId, + newCard: isCardWizardOpen, + ...params + }, + }, + }) => ( + + + + ), + ) + .otherwise(() => null)} + + + ), + ) + .otherwise(() => ( + + ))} + + ; + }) + .exhaustive(); }; diff --git a/clients/banking/src/components/MembershipInvitationLinkModal.tsx b/clients/banking/src/components/MembershipInvitationLinkModal.tsx index c3012dcc1..00ee99420 100644 --- a/clients/banking/src/components/MembershipInvitationLinkModal.tsx +++ b/clients/banking/src/components/MembershipInvitationLinkModal.tsx @@ -3,9 +3,10 @@ import { Box } from "@swan-io/lake/src/components/Box"; import { LakeLabel } from "@swan-io/lake/src/components/LakeLabel"; import { LakeTextInput } from "@swan-io/lake/src/components/LakeTextInput"; import { Space } from "@swan-io/lake/src/components/Space"; +import { useDeferredUrqlQuery } from "@swan-io/lake/src/hooks/useUrqlQuery"; import { LakeModal } from "@swan-io/shared-business/src/components/LakeModal"; +import { useEffect } from "react"; import { P, match } from "ts-pattern"; -import { useQuery } from "urql"; import { MembershipDetailDocument } from "../graphql/partner"; import { getMemberName } from "../utils/accountMembership"; import { t } from "../utils/i18n"; @@ -17,11 +18,14 @@ type Props = { onPressClose: () => void; }; export const MembershipInvitationLinkModal = ({ accountMembershipId, onPressClose }: Props) => { - const [{ data }] = useQuery({ - query: MembershipDetailDocument, - variables: { accountMembershipId: accountMembershipId as string }, - pause: accountMembershipId == null, - }); + const { data, query } = useDeferredUrqlQuery(MembershipDetailDocument); + + useEffect(() => { + if (accountMembershipId != null) { + const request = query({ accountMembershipId }); + return () => request.cancel(); + } + }, [accountMembershipId]); const value = match(projectConfiguration) .with( @@ -31,20 +35,21 @@ export const MembershipInvitationLinkModal = ({ accountMembershipId, onPressClos ) .otherwise(() => `${__env.BANKING_URL}/api/invitation/${accountMembershipId ?? ""}`); - const accountMembership = data?.accountMembership; - return ( result.toOption()) + .flatMap(data => Option.fromNullable(data.accountMembership)) + .map(accountMembership => + t("members.invitationTitle.name", { + fullName: getMemberName({ accountMembership }), + }), + ) + .getWithDefault(t("members.invitationTitle"))} > { const accountCountry = account.country ?? "FRA"; - const [, updateAccount] = useMutation(UpdateAccountDocument); + const [, updateAccount] = useUrqlMutation(UpdateAccountDocument); const holderInfo = account.holder.info; const isCompany = holderInfo?.__typename === "AccountHolderCompanyInfo"; @@ -344,16 +344,16 @@ const UpdateAccountForm = ({ taxIdentificationNumber, }, }) - .then(parseOperationResult) - .then(({ updateAccount, updateAccountHolder }) => - Promise.all([ - filterRejectionsToPromise(updateAccount), - filterRejectionsToPromise(updateAccountHolder), + .mapOkToResult(({ updateAccount, updateAccountHolder }) => + Result.all([ + filterRejectionsToResult(updateAccount), + filterRejectionsToResult(updateAccountHolder), ]), ) - .catch((error: unknown) => { + .tapError((error: unknown) => { showToast({ variant: "error", error, title: translateError(error) }); - }); + }) + .toPromise(); } }); }} diff --git a/clients/banking/src/pages/AccountDetailsVirtualIbansPage.tsx b/clients/banking/src/pages/AccountDetailsVirtualIbansPage.tsx index 4a38e2a23..f531a9a78 100644 --- a/clients/banking/src/pages/AccountDetailsVirtualIbansPage.tsx +++ b/clients/banking/src/pages/AccountDetailsVirtualIbansPage.tsx @@ -1,3 +1,4 @@ +import { Option } from "@swan-io/boxed"; import { FixedListViewEmpty, PlainListViewPlaceholder, @@ -17,17 +18,17 @@ import { commonStyles } from "@swan-io/lake/src/constants/commonStyles"; import { breakpoints, spacings } from "@swan-io/lake/src/constants/design"; import { useBoolean } from "@swan-io/lake/src/hooks/useBoolean"; import { useResponsive } from "@swan-io/lake/src/hooks/useResponsive"; +import { useUrqlMutation } from "@swan-io/lake/src/hooks/useUrqlMutation"; import { useUrqlPaginatedQuery } from "@swan-io/lake/src/hooks/useUrqlQuery"; import { showToast } from "@swan-io/lake/src/state/toasts"; import { GetEdge } from "@swan-io/lake/src/utils/types"; -import { filterRejectionsToPromise, parseOperationResult } from "@swan-io/lake/src/utils/urql"; +import { filterRejectionsToResult } from "@swan-io/lake/src/utils/urql"; import { LakeModal } from "@swan-io/shared-business/src/components/LakeModal"; import { translateError } from "@swan-io/shared-business/src/utils/i18n"; import { printIbanFormat } from "@swan-io/shared-business/src/utils/validation"; import { useMemo } from "react"; import { StyleSheet, View } from "react-native"; import { match } from "ts-pattern"; -import { useMutation } from "urql"; import { ErrorView } from "../components/ErrorView"; import { AccountDetailsVirtualIbansPageDocument, @@ -176,18 +177,17 @@ const smallColumns: ColumnConfig[] = [ const Actions = ({ onCancel, virtualIbanId }: { onCancel: () => void; virtualIbanId: string }) => { const [modalVisible, setModalVisible] = useBoolean(false); - const [{ fetching }, cancelVirtualIban] = useMutation(CancelVirtualIbanDocument); + const [virtualIbanCancelation, cancelVirtualIban] = useUrqlMutation(CancelVirtualIbanDocument); const onPressCancel = () => { cancelVirtualIban({ virtualIbanId }) - .then(parseOperationResult) - .then(data => data.cancelVirtualIbanEntry) - .then(filterRejectionsToPromise) - .then(onCancel) - .catch((error: unknown) => + .mapOkToResult(data => Option.fromNullable(data.cancelVirtualIbanEntry).toResult(undefined)) + .mapOkToResult(filterRejectionsToResult) + .tapOk(onCancel) + .tapError((error: unknown) => showToast({ variant: "error", error, title: translateError(error) }), ) - .finally(setModalVisible.off); + .tap(setModalVisible.off); }; return ( @@ -211,7 +211,12 @@ const Actions = ({ onCancel, virtualIbanId }: { onCancel: () => void; virtualIba - + {t("accountDetails.virtualIbans.cancelVirtualIban")} @@ -225,7 +230,7 @@ const keyExtractor = ({ node: { id } }: Edge) => id; export const AccountDetailsVirtualIbansPage = ({ accountId }: Props) => { // use useResponsive to fit with scroll behavior set in AccountArea const { desktop } = useResponsive(); - const [{ fetching: adding }, addVirtualIban] = useMutation(AddVirtualIbanDocument); + const [virtualIbanAddition, addVirtualIban] = useUrqlMutation(AddVirtualIbanDocument); const { data, nextData, reload, setAfter } = useUrqlPaginatedQuery( { @@ -237,12 +242,10 @@ export const AccountDetailsVirtualIbansPage = ({ accountId }: Props) => { const onPressNew = () => { addVirtualIban({ accountId }) - .then(parseOperationResult) - .then(data => data.addVirtualIbanEntry) - .then(data => data ?? Promise.reject()) - .then(filterRejectionsToPromise) - .then(reload) - .catch((error: unknown) => { + .mapOkToResult(data => Option.fromNullable(data.addVirtualIbanEntry).toResult(undefined)) + .mapOkToResult(filterRejectionsToResult) + .tapOk(reload) + .tapError((error: unknown) => { showToast({ variant: "error", error, title: translateError(error) }); }); }; @@ -273,7 +276,7 @@ export const AccountDetailsVirtualIbansPage = ({ accountId }: Props) => { {edges.length > 0 && unlimited && ( { {unlimited && (