taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit cb0386ea00063a1569cba2d79ffc2d7bd0e8a44e
parent 896eccc3240d20bffbf8cde4eaaf2eb28fa27b50
Author: Sebastian <sebasjm@gmail.com>
Date:   Mon, 15 Jul 2024 16:02:28 -0300

show transfer details on fast form

Diffstat:
Mpackages/bank-ui/src/Routing.tsx | 8++------
Mpackages/bank-ui/src/pages/OperationState/index.ts | 7+++++++
Mpackages/bank-ui/src/pages/OperationState/state.ts | 16++++++++++++++--
Mpackages/bank-ui/src/pages/OperationState/views.tsx | 168++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mpackages/bank-ui/src/pages/PaymentOptions.tsx | 14+++++---------
Mpackages/bank-ui/src/pages/WalletWithdrawForm.tsx | 2+-
6 files changed, 196 insertions(+), 19 deletions(-)

diff --git a/packages/bank-ui/src/Routing.tsx b/packages/bank-ui/src/Routing.tsx @@ -148,9 +148,7 @@ function PublicRounting({ } switch (location.name) { - case undefined: { - return <Fragment />; - } + case undefined: case "login": { return ( <Fragment> @@ -281,9 +279,6 @@ function PrivateRouting({ }, [location]); switch (location.name) { - case undefined: { - return <Fragment />; - } case "operationDetails": { return ( <WithdrawalOperationPage @@ -472,6 +467,7 @@ function PrivateRouting({ /> ); } + case undefined: case "home": { if (isAdmin) { return ( diff --git a/packages/bank-ui/src/pages/OperationState/index.ts b/packages/bank-ui/src/pages/OperationState/index.ts @@ -17,6 +17,7 @@ import { AbsoluteTime, AmountJson, + PaytoUri, TalerCoreBankErrorsByMethod, TalerError, WithdrawUriResult, @@ -118,6 +119,12 @@ export namespace State { TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined >); error: undefined; + details: { + account: PaytoUri; + reserve: string; + username: string; + amount?: AmountJson; + }; id: string; } export interface Aborted { diff --git a/packages/bank-ui/src/pages/OperationState/state.ts b/packages/bank-ui/src/pages/OperationState/state.ts @@ -140,13 +140,19 @@ export function useComponentState({ return (): utils.RecursiveState<State> => { const result = useWithdrawalDetails(withdrawalOperationId); - const shouldCreateNewOperation = result && !(result instanceof TalerError); + + const shouldCreateNewOperation = + result && + (result instanceof TalerError || + result.type === "fail" || + result.body.status === "aborted" || + result.body.status === "confirmed"); useEffect(() => { if (shouldCreateNewOperation) { doSilentStart(); } - }, []); + }, [shouldCreateNewOperation]); if (!result) { return { status: "loading", @@ -236,6 +242,12 @@ export function useComponentState({ status: "need-confirmation", error: undefined, routeHere, + details: { + account, + reserve: data.selected_reserve_pub, + username: data.username, + amount: !data.amount ? undefined : Amounts.parse(data.amount), + }, onAuthorizationRequired, account: data.username, id: withdrawalOperationId, diff --git a/packages/bank-ui/src/pages/OperationState/views.tsx b/packages/bank-ui/src/pages/OperationState/views.tsx @@ -16,6 +16,7 @@ import { AbsoluteTime, + Amounts, HttpStatusCode, TalerErrorCode, TranslatedString, @@ -26,6 +27,7 @@ import { Attention, LocalNotificationBanner, notifyInfo, + useBankCoreApiContext, useLocalNotification, useTalerWalletIntegrationAPI, useTranslationContext, @@ -37,7 +39,7 @@ import { useBankState } from "../../hooks/bank-state.js"; import { usePreferences } from "../../hooks/preferences.js"; import { ShouldBeSameUser } from "../WithdrawalConfirmationQuestion.js"; import { State } from "./index.js"; -import { doAutoFocus } from "../PaytoWireTransferForm.js"; +import { RenderAmount, doAutoFocus } from "../PaytoWireTransferForm.js"; export function InvalidPaytoView({ payto }: State.InvalidPayto) { return <div>Payto from server is not valid &quot;{payto}&quot;</div>; @@ -54,6 +56,7 @@ export function NeedConfirmationView({ onConfirm: doConfirm, routeHere, account, + details, id, onAuthorizationRequired, }: State.NeedConfirmation) { @@ -61,6 +64,11 @@ export function NeedConfirmationView({ const [settings] = usePreferences(); const [notification, notify, errorHandler] = useLocalNotification(); const [, updateBankState] = useBankState(); + const { config } = useBankCoreApiContext(); + const wireFee = + config.wire_transfer_fees === undefined + ? Amounts.zeroOfCurrency(config.currency) + : Amounts.parseOrThrow(config.wire_transfer_fees); async function onCancel() { errorHandler(async () => { @@ -182,6 +190,164 @@ export function NeedConfirmationView({ e.preventDefault(); }} > + <div class="px-4 mt-4"> + <div class="w-full"> + <dl class=""> + {((): VNode => { + if (!details.account.isKnown) { + return ( + <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> + <dt class="text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate> + Payment provider's account + </i18n.Translate> + </dt> + <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"> + {details.account.targetPath} + </dd> + </div> + ); + } + switch (details.account.targetType) { + case "iban": { + const name = details.account.params["receiver-name"]; + return ( + <Fragment> + <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> + <dt class="text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate> + Payment provider's account number + </i18n.Translate> + </dt> + <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"> + {details.account.iban} + </dd> + </div> + {name && ( + <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> + <dt class="text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate> + Payment provider's name + </i18n.Translate> + </dt> + <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"> + {name} + </dd> + </div> + )} + </Fragment> + ); + } + case "x-taler-bank": { + const name = details.account.params["receiver-name"]; + return ( + <Fragment> + <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> + <dt class="text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate> + Payment provider's account bank hostname + </i18n.Translate> + </dt> + <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"> + {details.account.host} + </dd> + </div> + <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> + <dt class="text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate> + Payment provider's account id + </i18n.Translate> + </dt> + <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"> + {details.account.account} + </dd> + </div> + {name && ( + <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> + <dt class="text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate> + Payment provider's name + </i18n.Translate> + </dt> + <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"> + {name} + </dd> + </div> + )} + </Fragment> + ); + } + case "bitcoin": { + const name = details.account.params["receiver-name"]; + return ( + <Fragment> + <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> + <dt class="text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate> + Payment provider's account address + </i18n.Translate> + </dt> + <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"> + {details.account.address} + </dd> + </div> + {name && ( + <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> + <dt class="text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate> + Payment provider's name + </i18n.Translate> + </dt> + <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"> + {name} + </dd> + </div> + )} + </Fragment> + ); + } + default: { + assertUnreachable(details.account); + } + } + })()} + <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> + <dt class="text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate>Amount</i18n.Translate> + </dt> + <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"> + {details.amount !== undefined ? ( + <RenderAmount + value={details.amount} + spec={config.currency_specification} + /> + ) : ( + <i18n.Translate> + No amount specified yet. + </i18n.Translate> + )} + </dd> + </div> + {Amounts.isZero(wireFee) ? undefined : ( + <Fragment> + <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> + <dt class="text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate>Cost</i18n.Translate> + </dt> + <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"> + <RenderAmount + value={wireFee} + negative + withColor + spec={config.currency_specification} + /> + </dd> + </div> + </Fragment> + )} + </dl> + </div> + </div> <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8"> <button type="button" diff --git a/packages/bank-ui/src/pages/PaymentOptions.tsx b/packages/bank-ui/src/pages/PaymentOptions.tsx @@ -41,29 +41,25 @@ function ShowOperationPendingTag({ const pending = !loading && !error && - (result.body.status === "pending" || result.body.status === "selected") && + result.body.status === "selected" && + // (result.body.status === "pending" || result.body.status === "selected") && credentials.status === "loggedIn" && credentials.username === result.body.username; - useEffect(() => { - if (!loading && !pending && onOperationAlreadyCompleted) { - onOperationAlreadyCompleted(); - } - }, [loading, pending]); if (error || !pending) { return <Fragment />; } return ( - <span class="flex items-center gap-x-1.5 w-fit rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-700 whitespace-pre"> + <span class="flex items-center gap-x-1.5 w-fit rounded-md bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-700 whitespace-pre"> <svg - class="h-1.5 w-1.5 fill-green-500" + class="h-1.5 w-1.5 fill-yellow-500" viewBox="0 0 6 6" aria-hidden="true" > <circle cx="3" cy="3" r="3" /> </svg> - <i18n.Translate>Operation ready</i18n.Translate> + <i18n.Translate>Pending operation</i18n.Translate> </span> ); } diff --git a/packages/bank-ui/src/pages/WalletWithdrawForm.tsx b/packages/bank-ui/src/pages/WalletWithdrawForm.tsx @@ -355,7 +355,7 @@ export function WalletWithdrawForm({ <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-6 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg"> <div class="px-4 sm:px-0"> <h2 class="text-base font-semibold leading-7 text-gray-900"> - <i18n.Translate>Prepare your Taler wallet</i18n.Translate> + <i18n.Translate>Use your Taler wallet</i18n.Translate> </h2> <p class="mt-1 text-sm text-gray-500"> <i18n.Translate>