diff options
author | Sebastian <sebasjm@gmail.com> | 2024-02-23 08:30:43 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2024-02-23 08:30:43 -0300 |
commit | 73e36c99037cef1ccc43bb80b67b19e0f44326fd (patch) | |
tree | c076b0084a3fb5004b8f3ffa32cc09a9b1a42d22 /packages | |
parent | 271488f041890391e1c6d946fd1aef00c2113fd0 (diff) |
return to form after 2fa cancelled
Diffstat (limited to 'packages')
33 files changed, 324 insertions, 198 deletions
diff --git a/packages/demobank-ui/src/Routing.tsx b/packages/demobank-ui/src/Routing.tsx index 096b6039b..14df5a274 100644 --- a/packages/demobank-ui/src/Routing.tsx +++ b/packages/demobank-ui/src/Routing.tsx @@ -47,7 +47,7 @@ import { CreateNewAccount } from "./pages/admin/CreateNewAccount.js"; import { RemoveAccount } from "./pages/admin/RemoveAccount.js"; import { CreateCashout } from "./pages/business/CreateCashout.js"; import { ShowCashoutDetails } from "./pages/business/ShowCashoutDetails.js"; -import { RouteParamsType, urlPattern, useCurrentLocation } from "./route.js"; +import { urlPattern, useCurrentLocation } from "./route.js"; import { useNavigationContext } from "./context/navigation.js"; import { useEffect } from "preact/hooks"; @@ -155,14 +155,10 @@ function PublicRounting({ return <PublicHistoriesPage />; } case "operationDetails": { - const { wopid } = location.values as RouteParamsType< - typeof location.parent, - typeof location.name - >; - return ( <WithdrawalOperationPage - operationId={wopid} + operationId={location.values.wopid} + routeWithdrawalDetails={publicPages.operationDetails} purpose="after-confirmation" onOperationAborted={() => navigateTo(publicPages.login.url({}))} routeClose={publicPages.login} @@ -192,7 +188,7 @@ function PublicRounting({ ); } default: - assertUnreachable(location.name); + assertUnreachable(location); } } @@ -201,7 +197,11 @@ export const privatePages = { /\/account\/charge-wallet/, () => "#/account/charge-wallet", ), - homeWireTransfer: urlPattern( + homeWireTransfer: urlPattern<{ + account?: string, + subject?: string, + amount?: string, + }>( /\/account\/wire-transfer/, () => "#/account/wire-transfer", ), @@ -212,9 +212,13 @@ export const privatePages = { /\/cashout\/(?<cid>[a-zA-Z0-9]+)/, ({ cid }) => `#/cashout/${cid}`, ), - wireTranserCreate: urlPattern<{ destination: string }>( - /\/wire-transfer\/(?<destination>[a-zA-Z0-9]+)/, - ({ destination }) => `#/wire-transfer/${destination}`, + wireTranserCreate: urlPattern<{ + account?: string, + subject?: string, + amount?: string, + }>( + /\/wire-transfer\/(?<account>[a-zA-Z0-9]+)/, + ({ account }) => `#/wire-transfer/${account}`, ), publicAccountList: urlPattern(/\/public-accounts/, () => "#/public-accounts"), statsDownload: urlPattern(/\/download-stats/, () => "#/download-stats"), @@ -273,14 +277,11 @@ function PrivateRouting({ switch (location.name) { case "operationDetails": { - const { wopid } = location.values as RouteParamsType< - typeof location.parent, - typeof location.name - >; return ( <WithdrawalOperationPage - operationId={wopid} + operationId={location.values.wopid} + routeWithdrawalDetails={privatePages.operationDetails} purpose="after-confirmation" onOperationAborted={() => navigateTo(privatePages.home.url({}))} routeClose={privatePages.home} @@ -291,14 +292,11 @@ function PrivateRouting({ ); } case "startOperation": { - const { wopid } = location.values as RouteParamsType< - typeof location.parent, - typeof location.name - >; return ( <WithdrawalOperationPage - operationId={wopid} + operationId={location.values.wopid} + routeWithdrawalDetails={privatePages.operationDetails} purpose="after-creation" onOperationAborted={() => navigateTo(privatePages.home.url({}))} routeClose={privatePages.home} @@ -331,14 +329,11 @@ function PrivateRouting({ ); } case "accountDetails": { - const { account } = location.values as RouteParamsType< - typeof location.parent, - typeof location.name - >; return ( <ShowAccountDetails - account={account} + account={location.values.account} onUpdateSuccess={() => navigateTo(privatePages.home.url({}))} + routeHere={privatePages.accountDetails} routeMyAccountCashout={privatePages.myAccountCashouts} routeMyAccountDelete={privatePages.myAccountDelete} routeMyAccountDetails={privatePages.myAccountDetails} @@ -351,14 +346,11 @@ function PrivateRouting({ ); } case "accountChangePassword": { - const { account } = location.values as RouteParamsType< - typeof location.parent, - typeof location.name - >; return ( <UpdateAccountPassword focus - account={account} + account={location.values.account} + routeHere={privatePages.accountChangePassword} onUpdateSuccess={() => navigateTo(privatePages.home.url({}))} routeMyAccountCashout={privatePages.myAccountCashouts} routeMyAccountDelete={privatePages.myAccountDelete} @@ -372,13 +364,10 @@ function PrivateRouting({ ); } case "accountDelete": { - const { account } = location.values as RouteParamsType< - typeof location.parent, - typeof location.name - >; return ( <RemoveAccount - account={account} + account={location.values.account} + routeHere={privatePages.accountDelete} onUpdateSuccess={() => navigateTo(privatePages.home.url({}))} onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) @@ -388,13 +377,10 @@ function PrivateRouting({ ); } case "accountCashouts": { - const { account } = location.values as RouteParamsType< - typeof location.parent, - typeof location.name - >; return ( <CashoutListForAccount - account={account} + account={location.values.account} + routeCreateCashout={privatePages.cashoutCreate} routeCashoutDetails={privatePages.cashoutDetails} routeClose={privatePages.home} routeMyAccountCashout={privatePages.myAccountCashouts} @@ -411,6 +397,7 @@ function PrivateRouting({ return ( <RemoveAccount account={username} + routeHere={privatePages.accountDelete} onUpdateSuccess={() => navigateTo(privatePages.home.url({}))} onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) @@ -423,6 +410,7 @@ function PrivateRouting({ return ( <ShowAccountDetails account={username} + routeHere={privatePages.accountDetails} onUpdateSuccess={() => navigateTo(privatePages.home.url({}))} routeMyAccountCashout={privatePages.myAccountCashouts} routeMyAccountDelete={privatePages.myAccountDelete} @@ -440,6 +428,7 @@ function PrivateRouting({ <UpdateAccountPassword focus account={username} + routeHere={privatePages.accountChangePassword} onUpdateSuccess={() => navigateTo(privatePages.home.url({}))} routeMyAccountCashout={privatePages.myAccountCashouts} routeMyAccountDelete={privatePages.myAccountDelete} @@ -457,6 +446,7 @@ function PrivateRouting({ <CashoutListForAccount account={username} routeCashoutDetails={privatePages.cashoutDetails} + routeCreateCashout={privatePages.cashoutCreate} routeMyAccountCashout={privatePages.myAccountCashouts} routeMyAccountDelete={privatePages.myAccountDelete} routeMyAccountDetails={privatePages.myAccountDetails} @@ -510,6 +500,7 @@ function PrivateRouting({ return ( <CreateCashout account={username} + routeHere={privatePages.cashoutCreate} onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) } @@ -518,25 +509,20 @@ function PrivateRouting({ ); } case "cashoutDetails": { - const { cid } = location.values as RouteParamsType< - typeof location.parent, - typeof location.name - >; return ( <ShowCashoutDetails - id={cid} + id={location.values.cid} routeClose={privatePages.myAccountCashouts} /> ); } case "wireTranserCreate": { - const { destination } = location.values as RouteParamsType< - typeof location.parent, - typeof location.name - >; return ( <WireTransfer - toAccount={destination} + toAccount={location.values.account} + withAmount={location.values.amount} + withSubject={location.values.subject} + routeHere={privatePages.wireTranserCreate} onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) } @@ -590,6 +576,6 @@ function PrivateRouting({ ); } default: - assertUnreachable(location.name); + assertUnreachable(location); } } diff --git a/packages/demobank-ui/src/components/Transactions/index.ts b/packages/demobank-ui/src/components/Transactions/index.ts index f4e799839..42b12ac65 100644 --- a/packages/demobank-ui/src/components/Transactions/index.ts +++ b/packages/demobank-ui/src/components/Transactions/index.ts @@ -23,7 +23,11 @@ import { RouteDefinition } from "../../route.js"; export interface Props { account: string; - routeCreateWireTransfer: RouteDefinition<{ destination: string }> | undefined; + routeCreateWireTransfer: RouteDefinition<{ + account?: string, + subject?: string, + amount?: string, + }> | undefined; } export type State = State.Loading | State.LoadingUriError | State.Ready; @@ -45,7 +49,11 @@ export namespace State { export interface Ready extends BaseInfo { status: "ready"; error: undefined; - routeCreateWireTransfer: RouteDefinition<{ destination: string }> | undefined; + routeCreateWireTransfer: RouteDefinition<{ + account?: string, + subject?: string, + amount?: string, + }> | undefined; transactions: Transaction[]; onPrev?: () => void; onNext?: () => void; diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx index a1ec0fa00..321a6ff3a 100644 --- a/packages/demobank-ui/src/components/Transactions/views.tsx +++ b/packages/demobank-ui/src/components/Transactions/views.tsx @@ -154,7 +154,7 @@ export function ReadyView({ <a name={`transfer to ${item.counterpart}`} href={routeCreateWireTransfer.url({ - destination: item.counterpart, + account: item.counterpart, })} class="text-indigo-600 hover:text-indigo-900" > @@ -191,7 +191,7 @@ export function ReadyView({ <a name={`wire transfer to ${item.counterpart}`} href={routeCreateWireTransfer.url({ - destination: item.counterpart, + account: item.counterpart, })} class="text-indigo-600 hover:text-indigo-900" > diff --git a/packages/demobank-ui/src/context/navigation.ts b/packages/demobank-ui/src/context/navigation.ts index 27fdf4649..9552bf899 100644 --- a/packages/demobank-ui/src/context/navigation.ts +++ b/packages/demobank-ui/src/context/navigation.ts @@ -16,6 +16,7 @@ import { ComponentChildren, createContext, h, VNode } from "preact"; import { useContext, useEffect, useState } from "preact/hooks"; +import { AppLocation } from "../route.js"; /** * @@ -25,7 +26,7 @@ import { useContext, useEffect, useState } from "preact/hooks"; export type Type = { path: string; params: Record<string, string>; - navigateTo: (path: string) => void; + navigateTo: (path: AppLocation) => void; // addNavigationListener: (listener: (path: string, params: Record<string, string>) => void) => (() => void); }; diff --git a/packages/demobank-ui/src/hooks/bank-state.ts b/packages/demobank-ui/src/hooks/bank-state.ts index 87f7a49f0..15daf9180 100644 --- a/packages/demobank-ui/src/hooks/bank-state.ts +++ b/packages/demobank-ui/src/hooks/bank-state.ts @@ -28,6 +28,7 @@ import { codecOptional, } from "@gnu-taler/taler-util"; import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; +import { AppLocation } from "../route.js"; export type ChallengeInProgess = | DeleteAccountChallenge @@ -41,6 +42,7 @@ type BaseChallenge<OpType extends string, ReqType> = { id: string; operation: OpType; sent: AbsoluteTime; + location: AppLocation; info?: TalerCorebankApi.TanTransmission; request: ReqType; }; @@ -68,6 +70,7 @@ const codecForChallengeUpdatePassword = (): Codec<UpdatePasswordChallenge> => buildCodecForObject<UpdatePasswordChallenge>() .property("operation", codecForConstString("update-password")) .property("id", codecForString()) + .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("info", codecOptional(codecForTanTransmission())) .property("request", codecForAny()) @@ -77,6 +80,7 @@ const codecForChallengeDeleteAccount = (): Codec<DeleteAccountChallenge> => buildCodecForObject<DeleteAccountChallenge>() .property("operation", codecForConstString("delete-account")) .property("id", codecForString()) + .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("request", codecForString()) .property("info", codecOptional(codecForTanTransmission())) @@ -86,6 +90,7 @@ const codecForChallengeUpdateAccount = (): Codec<UpdateAccountChallenge> => buildCodecForObject<UpdateAccountChallenge>() .property("operation", codecForConstString("update-account")) .property("id", codecForString()) + .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("info", codecOptional(codecForTanTransmission())) .property("request", codecForAny()) @@ -96,6 +101,7 @@ const codecForChallengeCreateTransaction = buildCodecForObject<CreateTransactionChallenge>() .property("operation", codecForConstString("create-transaction")) .property("id", codecForString()) + .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("info", codecOptional(codecForTanTransmission())) .property("request", codecForAny()) @@ -106,15 +112,19 @@ const codecForChallengeConfirmWithdrawal = buildCodecForObject<ConfirmWithdrawalChallenge>() .property("operation", codecForConstString("confirm-withdrawal")) .property("id", codecForString()) + .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("info", codecOptional(codecForTanTransmission())) .property("request", codecForString()) .build("ConfirmWithdrawalChallenge"); +const codecForAppLocation = codecForString as () => Codec<AppLocation> + const codecForChallengeCashout = (): Codec<CashoutChallenge> => buildCodecForObject<CashoutChallenge>() .property("operation", codecForConstString("create-cashout")) .property("id", codecForString()) + .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("info", codecOptional(codecForTanTransmission())) .property("request", codecForAny()) diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts index 6c67f6d90..7a85f59fe 100644 --- a/packages/demobank-ui/src/pages/AccountPage/index.ts +++ b/packages/demobank-ui/src/pages/AccountPage/index.ts @@ -33,13 +33,21 @@ export interface Props { onOperationCreated: (wopid: string) => void; onClose: () => void; tab: "charge-wallet" | "wire-transfer" | undefined; - routeClose: RouteDefinition<Record<string, never>>; - routeChargeWallet: RouteDefinition<Record<string, never>>; - routeWireTransfer: RouteDefinition<Record<string, never>>; - routePublicAccounts: RouteDefinition<Record<string, never>>; - routeCreateWireTransfer: RouteDefinition<{ destination: string }>; + routeClose: RouteDefinition; + routeChargeWallet: RouteDefinition; + routeWireTransfer: RouteDefinition<{ + account?: string; + subject?: string; + amount?: string; + }>; + routePublicAccounts: RouteDefinition; + routeCreateWireTransfer: RouteDefinition<{ + account?: string; + subject?: string; + amount?: string; + }>; routeOperationDetails: RouteDefinition<{ wopid: string }>; - routeSolveSecondFactor: RouteDefinition<Record<string, never>>; + routeSolveSecondFactor: RouteDefinition; } export type State = @@ -73,13 +81,21 @@ export namespace State { onAuthorizationRequired: () => void; onOperationCreated: (wopid: string) => void; onClose: () => void; - routeClose: RouteDefinition<Record<string, never>>; - routeChargeWallet: RouteDefinition<Record<string, never>>; - routeWireTransfer: RouteDefinition<Record<string, never>>; - routePublicAccounts: RouteDefinition<Record<string, never>>; - routeCreateWireTransfer: RouteDefinition<{ destination: string }>; + routeClose: RouteDefinition; + routeChargeWallet: RouteDefinition; + routePublicAccounts: RouteDefinition; + routeWireTransfer: RouteDefinition<{ + account?: string, + subject?: string, + amount?: string, + }>; + routeCreateWireTransfer: RouteDefinition<{ + account?: string, + subject?: string, + amount?: string, + }>; routeOperationDetails: RouteDefinition<{ wopid: string }>; - routeSolveSecondFactor: RouteDefinition<Record<string, never>>; + routeSolveSecondFactor: RouteDefinition; } export interface InvalidIban { @@ -90,7 +106,7 @@ export namespace State { export interface UserNotFound { status: "login"; reason: "not-found" | "forbidden"; - routeRegister?: RouteDefinition<Record<string, never>>; + routeRegister?: RouteDefinition; } } diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx index 4f411940f..cb98c5f2a 100644 --- a/packages/demobank-ui/src/pages/AccountPage/views.tsx +++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx @@ -33,7 +33,7 @@ export function InvalidIbanView({ error }: State.InvalidIban) { const IS_PUBLIC_ACCOUNT_ENABLED = false; function ShowDemoInfo({ routePublicAccounts }: { - routePublicAccounts: RouteDefinition<Record<string, never>>; + routePublicAccounts: RouteDefinition; }): VNode { const { i18n } = useTranslationContext(); const [settings, updateSettings] = usePreferences(); @@ -63,7 +63,7 @@ function ShowDemoInfo({ routePublicAccounts }: { } function ShowPedingOperation({ routeSolveSecondFactor }: { - routeSolveSecondFactor: RouteDefinition<Record<string, never>>; + routeSolveSecondFactor: RouteDefinition; }): VNode { const { i18n } = useTranslationContext(); const [bankState, updateBankState] = useBankState(); diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index 266eab636..436076ba4 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -36,8 +36,8 @@ import { getLabelForPreferences, usePreferences, } from "../hooks/preferences.js"; -import { RenderAmount } from "./PaytoWireTransferForm.js"; import { RouteDefinition } from "../route.js"; +import { RenderAmount } from "./PaytoWireTransferForm.js"; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; @@ -48,7 +48,7 @@ export function BankFrame({ routeAccountDetails, }: { account?: string; - routeAccountDetails?: RouteDefinition<Record<string, never>>; + routeAccountDetails?: RouteDefinition; children: ComponentChildren; }): VNode { const { i18n } = useTranslationContext(); @@ -179,7 +179,7 @@ export function BankFrame({ function WelcomeAccount({ account, routeAccountDetails }: { account: string, - routeAccountDetails: RouteDefinition<Record<string, never>>; + routeAccountDetails: RouteDefinition; }): VNode { const { i18n } = useTranslationContext(); const result = useAccountDetails(account); diff --git a/packages/demobank-ui/src/pages/DownloadStats.tsx b/packages/demobank-ui/src/pages/DownloadStats.tsx index 9bdc04123..353238c13 100644 --- a/packages/demobank-ui/src/pages/DownloadStats.tsx +++ b/packages/demobank-ui/src/pages/DownloadStats.tsx @@ -31,11 +31,11 @@ import { VNode, h } from "preact"; import { useState } from "preact/hooks"; import { useBankCoreApiContext } from "../context/config.js"; import { useBackendState } from "../hooks/backend.js"; -import { RouteDefinition } from "../route.js"; +import { EmptyObject, RouteDefinition } from "../route.js"; import { getTimeframesForDate } from "./admin/AdminHome.js"; interface Props { - routeCancel: RouteDefinition<Record<string, never>>; + routeCancel: RouteDefinition; } type Options = { diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index 6d732f5e8..f90b985e6 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -31,7 +31,7 @@ import { useBankCoreApiContext } from "../context/config.js"; import { useBackendState } from "../hooks/backend.js"; import { undefinedIfEmpty } from "../utils.js"; import { doAutoFocus } from "./PaytoWireTransferForm.js"; -import { RouteDefinition } from "../route.js"; +import { EmptyObject, RouteDefinition } from "../route.js"; /** * Collect and submit login data. @@ -43,7 +43,7 @@ export function LoginForm({ }: { fixedUser?: boolean; currentUser?: string; - routeRegister?: RouteDefinition<Record<string, never>>; + routeRegister?: RouteDefinition; }): VNode { const backend = useBackendState(); diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts b/packages/demobank-ui/src/pages/OperationState/index.ts index 20cb1760f..e4d9d45e3 100644 --- a/packages/demobank-ui/src/pages/OperationState/index.ts +++ b/packages/demobank-ui/src/pages/OperationState/index.ts @@ -39,8 +39,9 @@ import { RouteDefinition } from "../../route.js"; export interface Props { currency: string; onAuthorizationRequired: () => void; - routeClose: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; onAbort: () => void; + routeHere: RouteDefinition<{ wopid: string }>; } export type State = @@ -81,7 +82,7 @@ export namespace State { onAbort: () => Promise< TalerCoreBankErrorsByMethod<"abortWithdrawalById"> | undefined >; - routeClose: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; } export interface InvalidPayto { @@ -103,28 +104,29 @@ export namespace State { status: "need-confirmation"; onAuthorizationRequired: () => void; account: string; + routeHere: RouteDefinition<{ wopid: string }>; onAbort: - | undefined - | (() => Promise< - TalerCoreBankErrorsByMethod<"abortWithdrawalById"> | undefined - >); + | undefined + | (() => Promise< + TalerCoreBankErrorsByMethod<"abortWithdrawalById"> | undefined + >); onConfirm: - | undefined - | (() => Promise< - TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined - >); + | undefined + | (() => Promise< + TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined + >); error: undefined; id: string; } export interface Aborted { status: "aborted"; error: undefined; - routeClose: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; } export interface Confirmed { status: "confirmed"; error: undefined; - routeClose: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; } } diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts index 20d66bbb1..ad2d91ee4 100644 --- a/packages/demobank-ui/src/pages/OperationState/state.ts +++ b/packages/demobank-ui/src/pages/OperationState/state.ts @@ -38,6 +38,7 @@ export function useComponentState({ currency, routeClose, onAbort, + routeHere, onAuthorizationRequired, }: Props): utils.RecursiveState<State> { const [settings] = usePreferences(); @@ -190,9 +191,9 @@ export function useComponentState({ routeClose, onAbort: !creds ? async () => { - onAbort(); - return undefined; - } + onAbort(); + return undefined; + } : doAbort, }; } @@ -220,6 +221,7 @@ export function useComponentState({ return { status: "need-confirmation", error: undefined, + routeHere, onAuthorizationRequired, account: data.username, id: withdrawalOperationId, diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx index d086c3dd1..6eee6daa9 100644 --- a/packages/demobank-ui/src/pages/OperationState/views.tsx +++ b/packages/demobank-ui/src/pages/OperationState/views.tsx @@ -51,6 +51,7 @@ export function InvalidReserveView({ reserve }: State.InvalidReserve) { export function NeedConfirmationView({ onAbort: doAbort, onConfirm: doConfirm, + routeHere, account, id, onAuthorizationRequired, @@ -144,7 +145,9 @@ export function NeedConfirmationView({ operation: "confirm-withdrawal", id: String(resp.body.challenge_id), sent: AbsoluteTime.never(), + location: routeHere.url({ wopid: id }), request: id, + }); return onAuthorizationRequired(); } @@ -331,7 +334,7 @@ export function ConfirmedView({ routeClose }: State.Confirmed) { export function ReadyView({ uri, onAbort: doAbort, -}: State.Ready): VNode<Record<string, never>> { +}: State.Ready): VNode { const { i18n } = useTranslationContext(); const walletInegrationApi = useTalerWalletIntegrationAPI(); const [notification, notify, errorHandler] = useLocalNotification(); diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx index 10be1245f..b13f7539f 100644 --- a/packages/demobank-ui/src/pages/PaymentOptions.tsx +++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx @@ -19,7 +19,7 @@ import { Fragment, VNode, h } from "preact"; import { useBankState } from "../hooks/bank-state.js"; import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js"; import { WalletWithdrawForm } from "./WalletWithdrawForm.js"; -import { RouteDefinition } from "../route.js"; +import { EmptyObject, RouteDefinition } from "../route.js"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { useWithdrawalDetails } from "../hooks/access.js"; import { useEffect } from "preact/hooks"; @@ -85,9 +85,13 @@ export function PaymentOptions({ onClose: () => void; routeOperationDetails: RouteDefinition<{ wopid: string }>; - routeClose: RouteDefinition<Record<string, never>>; - routeChargeWallet: RouteDefinition<Record<string, never>>; - routeWireTransfer: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; + routeChargeWallet: RouteDefinition; + routeWireTransfer: RouteDefinition<{ + account?: string, + subject?: string, + amount?: string, + }>; }): VNode { const { i18n } = useTranslationContext(); const [bankState, updateBankState] = useBankState(); @@ -213,6 +217,7 @@ export function PaymentOptions({ <PaytoWireTransferForm focus title={i18n.str`Transfer details`} + routeHere={routeWireTransfer} limit={limit} onAuthorizationRequired={onAuthorizationRequired} onSuccess={onClose} diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index 9fe3453a5..00b6767ac 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -45,35 +45,47 @@ import { mutate } from "swr"; import { useBankCoreApiContext } from "../context/config.js"; import { useBackendState } from "../hooks/backend.js"; import { useBankState } from "../hooks/bank-state.js"; -import { RouteDefinition } from "../route.js"; +import { EmptyObject, RouteDefinition } from "../route.js"; import { undefinedIfEmpty, validateIBAN, validateTalerBank } from "../utils.js"; +interface Props { + title: TranslatedString; + focus?: boolean; + withAccount?: string; + withSubject?: string; + withAmount?: string; + onSuccess: () => void; + onAuthorizationRequired: () => void; + routeCancel?: RouteDefinition; + routeHere: RouteDefinition<{ + account?: string, + subject?: string, + amount?: string, + }>; + limit: AmountJson; +} + export function PaytoWireTransferForm({ focus, title, - toAccount, + withAccount, + withSubject, + withAmount, onSuccess, routeCancel, + routeHere, onAuthorizationRequired, limit, -}: { - title: TranslatedString; - focus?: boolean; - toAccount?: string; - onSuccess: () => void; - onAuthorizationRequired: () => void; - routeCancel?: RouteDefinition<Record<string, never>>; - limit: AmountJson; -}): VNode { +}: Props): VNode { const [isRawPayto, setIsRawPayto] = useState(false); const { state: credentials } = useBackendState(); const { api, config, url } = useBankCoreApiContext(); - const sendingToFixedAccount = toAccount !== undefined; + const sendingToFixedAccount = withAccount !== undefined; - const [account, setAccount] = useState<string | undefined>(toAccount); - const [subject, setSubject] = useState<string | undefined>(); - const [amount, setAmount] = useState<string | undefined>(); + const [account, setAccount] = useState<string | undefined>(withAccount); + const [subject, setSubject] = useState<string | undefined>(withSubject); + const [amount, setAmount] = useState<string | undefined>(withAmount); const [, updateBankState] = useBankState(); const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>( @@ -200,6 +212,7 @@ export function PaytoWireTransferForm({ updateBankState("currentChallenge", { operation: "create-transaction", id: String(resp.body.challenge_id), + location: routeHere.url({ account: account ?? "", amount, subject }), sent: AbsoluteTime.never(), request, }); diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx b/packages/demobank-ui/src/pages/ProfileNavigation.tsx index 7c52f4eaa..ba02d07b9 100644 --- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx +++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx @@ -19,7 +19,7 @@ import { useBankCoreApiContext } from "../context/config.js"; import { useBackendState } from "../hooks/backend.js"; import { assertUnreachable } from "@gnu-taler/taler-util"; import { useNavigationContext } from "../context/navigation.js"; -import { RouteDefinition } from "../route.js"; +import { EmptyObject, RouteDefinition } from "../route.js"; export function ProfileNavigation({ current, @@ -29,10 +29,10 @@ export function ProfileNavigation({ routeMyAccountPassword, }: { current: "details" | "delete" | "credentials" | "cashouts", - routeMyAccountDetails: RouteDefinition<Record<string, never>>; - routeMyAccountDelete: RouteDefinition<Record<string, never>>; - routeMyAccountPassword: RouteDefinition<Record<string, never>>; - routeMyAccountCashout: RouteDefinition<Record<string, never>>; + routeMyAccountDetails: RouteDefinition; + routeMyAccountDelete: RouteDefinition; + routeMyAccountPassword: RouteDefinition; + routeMyAccountCashout: RouteDefinition; }): VNode { const { i18n } = useTranslationContext(); const { config } = useBankCoreApiContext(); diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx index 29e71413c..87e284411 100644 --- a/packages/demobank-ui/src/pages/RegistrationPage.tsx +++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx @@ -30,7 +30,7 @@ import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { useBankCoreApiContext } from "../context/config.js"; import { useSettingsContext } from "../context/settings.js"; -import { RouteDefinition } from "../route.js"; +import { EmptyObject, RouteDefinition } from "../route.js"; import { undefinedIfEmpty } from "../utils.js"; import { getRandomPassword, getRandomUsername } from "./rnd.js"; @@ -39,7 +39,7 @@ export function RegistrationPage({ routeCancel, }: { onRegistrationSuccesful: (user: string, password: string) => void; - routeCancel: RouteDefinition<Record<string, never>>; + routeCancel: RouteDefinition; }): VNode { const { i18n } = useTranslationContext(); const { config } = useBankCoreApiContext(); @@ -68,7 +68,7 @@ function RegistrationForm({ routeCancel, }: { onRegistrationSuccesful: (user: string, password: string) => void; - routeCancel: RouteDefinition<Record<string, never>>; + routeCancel: RouteDefinition; }): VNode { const [username, setUsername] = useState<string | undefined>(); const [name, setName] = useState<string | undefined>(); diff --git a/packages/demobank-ui/src/pages/SolveChallengePage.tsx b/packages/demobank-ui/src/pages/SolveChallengePage.tsx index 5ac622795..b7fc82a94 100644 --- a/packages/demobank-ui/src/pages/SolveChallengePage.tsx +++ b/packages/demobank-ui/src/pages/SolveChallengePage.tsx @@ -46,13 +46,14 @@ import { RouteDefinition } from "../route.js"; import { undefinedIfEmpty } from "../utils.js"; import { RenderAmount } from "./PaytoWireTransferForm.js"; import { OperationNotFound } from "./WithdrawalQRCode.js"; +import { useNavigationContext } from "../context/navigation.js"; export function SolveChallengePage({ onChallengeCompleted, routeClose, }: { onChallengeCompleted: () => void; - routeClose: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; }): VNode { const { api } = useBankCoreApiContext(); const { i18n } = useTranslationContext(); @@ -61,6 +62,7 @@ export function SolveChallengePage({ const [notification, notify, handleError] = useLocalNotification(); const { state } = useBackendState(); const creds = state.status !== "loggedIn" ? undefined : state; + const { navigateTo } = useNavigationContext(); if (!bankState.currentChallenge) { return ( @@ -209,6 +211,7 @@ export function SolveChallengePage({ updateBankState("currentChallenge", { operation: ch.operation, id: String(resp.body.challenge_id), + location: ch.location, sent: AbsoluteTime.never(), request: ch.request, }); @@ -262,7 +265,7 @@ export function SolveChallengePage({ onStart={startChallenge} onCancel={() => { updateBankState("currentChallenge", undefined); - onChallengeCompleted(); + navigateTo(ch.location) }} /> {ch.info && ( @@ -304,7 +307,7 @@ export function SolveChallengePage({ </div> </div> <div class="flex items-center justify-between border-gray-900/10 px-4 py-4 "> - <div /> + <div /> <button type="submit" name="confirm" @@ -344,7 +347,7 @@ function ChallengeDetails({ if (firstTime) { onStart() } - },[]) + }, []) return ( <div class="px-4 mt-4 "> <div class="w-full"> diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx index ecb3eb4fc..078f4e5f5 100644 --- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx +++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx @@ -54,7 +54,7 @@ function OldWithdrawalForm({ focus?: boolean; routeOperationDetails: RouteDefinition<{ wopid: string }>, onOperationCreated: (wopid: string) => void; - routeCancel: RouteDefinition<Record<string, never>>; + routeCancel: RouteDefinition; }): VNode { const { i18n } = useTranslationContext(); const [settings] = usePreferences(); @@ -300,7 +300,7 @@ export function WalletWithdrawForm({ onAuthorizationRequired: () => void; onOperationCreated: (wopid: string) => void; onOperationAborted: () => void; - routeCancel: RouteDefinition<Record<string, never>>; + routeCancel: RouteDefinition; }): VNode { const { i18n } = useTranslationContext(); const [settings, updateSettings] = usePreferences(); @@ -355,6 +355,7 @@ export function WalletWithdrawForm({ currency={limit.currency} onAuthorizationRequired={onAuthorizationRequired} routeClose={routeCancel} + routeHere={routeOperationDetails} onAbort={onOperationAborted} // route={routeCancel} /> diff --git a/packages/demobank-ui/src/pages/WireTransfer.tsx b/packages/demobank-ui/src/pages/WireTransfer.tsx index 190afd66e..927968304 100644 --- a/packages/demobank-ui/src/pages/WireTransfer.tsx +++ b/packages/demobank-ui/src/pages/WireTransfer.tsx @@ -34,13 +34,23 @@ import { RouteDefinition } from "../route.js"; export function WireTransfer({ toAccount, + withSubject, + withAmount, onAuthorizationRequired, routeCancel, + routeHere, onSuccess, }: { onSuccess?: () => void; + routeHere: RouteDefinition<{ + account?: string, + subject?: string, + amount?: string, + }>; toAccount?: string; - routeCancel?: RouteDefinition<Record<string, never>>; + withSubject?: string, + withAmount?: string, + routeCancel?: RouteDefinition; onAuthorizationRequired: () => void; }): VNode { const { i18n } = useTranslationContext(); @@ -77,7 +87,10 @@ export function WireTransfer({ return ( <PaytoWireTransferForm title={i18n.str`Make a wire transfer`} - toAccount={toAccount} + withAccount={toAccount} + withAmount={withAmount} + withSubject={withSubject} + routeHere={routeHere} limit={limit} onAuthorizationRequired={onAuthorizationRequired} onSuccess={() => { diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index e4631fcc8..39b94b349 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -39,12 +39,14 @@ import { useBankCoreApiContext } from "../context/config.js"; import { useBackendState } from "../hooks/backend.js"; import { useBankState } from "../hooks/bank-state.js"; import { usePreferences } from "../hooks/preferences.js"; +import { RouteDefinition } from "../route.js"; import { LoginForm } from "./LoginForm.js"; import { RenderAmount } from "./PaytoWireTransferForm.js"; interface Props { onAborted: () => void; withdrawUri: WithdrawUriResult; + routeHere: RouteDefinition<{ wopid: string }>; details: { account: PaytoUri; reserve: string; @@ -61,6 +63,7 @@ export function WithdrawalConfirmationQuestion({ onAborted, details, onAuthorizationRequired, + routeHere, withdrawUri, }: Props): VNode { const { i18n } = useTranslationContext(); @@ -126,6 +129,7 @@ export function WithdrawalConfirmationQuestion({ updateBankState("currentChallenge", { operation: "confirm-withdrawal", id: String(resp.body.challenge_id), + location: routeHere.url({ wopid: withdrawUri.withdrawalOperationId }), sent: AbsoluteTime.never(), request: withdrawUri.withdrawalOperationId, }); diff --git a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx index 87999a722..7075c34c6 100644 --- a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx @@ -27,12 +27,14 @@ export function WithdrawalOperationPage({ onAuthorizationRequired, onOperationAborted, routeClose, + routeWithdrawalDetails, }: { onAuthorizationRequired: () => void; operationId: string; purpose: "after-creation" | "after-confirmation", onOperationAborted: () => void; - routeClose: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; + routeWithdrawalDetails: RouteDefinition<{ wopid: string }>; }): VNode { const { api } = useBankCoreApiContext(); const uri = stringifyWithdrawUri({ @@ -57,6 +59,7 @@ export function WithdrawalOperationPage({ return ( <WithdrawalQRCode withdrawUri={parsedUri} + routeWithdrawalDetails={routeWithdrawalDetails} onAuthorizationRequired={onAuthorizationRequired} onOperationAborted={() => { updateBankState("currentWithdrawalOperationId", undefined); diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx index e6323631c..b128bd99f 100644 --- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx @@ -38,7 +38,8 @@ import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion interface Props { withdrawUri: WithdrawUriResult; onOperationAborted: () => void; - routeClose: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; + routeWithdrawalDetails: RouteDefinition<{ wopid: string }>; onAuthorizationRequired: () => void; } /** @@ -50,6 +51,7 @@ export function WithdrawalQRCode({ withdrawUri, onOperationAborted, routeClose, + routeWithdrawalDetails, onAuthorizationRequired, }: Props): VNode { const { i18n } = useTranslationContext(); @@ -232,6 +234,7 @@ export function WithdrawalQRCode({ return ( <WithdrawalConfirmationQuestion withdrawUri={withdrawUri} + routeHere={routeWithdrawalDetails} details={{ username: data.username, account, @@ -250,7 +253,7 @@ export function WithdrawalQRCode({ export function OperationNotFound({ routeClose, }: { - routeClose: RouteDefinition<Record<string, never>> | undefined; + routeClose: RouteDefinition | undefined; }): VNode { const { i18n } = useTranslationContext(); return ( diff --git a/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx b/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx index 656c2a52a..14f4acdb8 100644 --- a/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx +++ b/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx @@ -23,18 +23,20 @@ import { RouteDefinition } from "../../route.js"; interface Props { account: string; - routeClose: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; onAuthorizationRequired: () => void; routeCashoutDetails: RouteDefinition<{ cid: string }>; - routeMyAccountDetails: RouteDefinition<Record<string, never>>; - routeMyAccountDelete: RouteDefinition<Record<string, never>>; - routeMyAccountPassword: RouteDefinition<Record<string, never>>; - routeMyAccountCashout: RouteDefinition<Record<string, never>>; + routeMyAccountDetails: RouteDefinition; + routeMyAccountDelete: RouteDefinition; + routeMyAccountPassword: RouteDefinition; + routeMyAccountCashout: RouteDefinition; + routeCreateCashout: RouteDefinition; } export function CashoutListForAccount({ account, onAuthorizationRequired, + routeCreateCashout, routeCashoutDetails, routeMyAccountCashout, routeMyAccountDelete, @@ -68,6 +70,7 @@ export function CashoutListForAccount({ <CreateCashout focus + routeHere={routeCreateCashout} routeClose={routeClose} onAuthorizationRequired={onAuthorizationRequired} account={account} diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx index 8cd758c75..db76e83d9 100644 --- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx +++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx @@ -49,13 +49,15 @@ export function ShowAccountDetails({ routeMyAccountCashout, routeMyAccountDelete, routeMyAccountDetails, + routeHere, routeMyAccountPassword, }: { - routeClose: RouteDefinition<Record<string, never>>; - routeMyAccountDetails: RouteDefinition<Record<string, never>>; - routeMyAccountDelete: RouteDefinition<Record<string, never>>; - routeMyAccountPassword: RouteDefinition<Record<string, never>>; - routeMyAccountCashout: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; + routeHere: RouteDefinition<{ account: string }>; + routeMyAccountDetails: RouteDefinition; + routeMyAccountDelete: RouteDefinition; + routeMyAccountPassword: RouteDefinition; + routeMyAccountCashout: RouteDefinition; onUpdateSuccess: () => void; onAuthorizationRequired: () => void; account: string; @@ -154,6 +156,7 @@ export function ShowAccountDetails({ updateBankState("currentChallenge", { operation: "update-account", id: String(resp.body.challenge_id), + location: routeHere.url({ account }), sent: AbsoluteTime.never(), request: submitAccount, }); diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx index d15420b84..dfa0adf17 100644 --- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx +++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx @@ -46,12 +46,14 @@ export function UpdateAccountPassword({ routeMyAccountDetails, routeMyAccountPassword, focus, + routeHere, }: { - routeClose: RouteDefinition<Record<string, never>>; - routeMyAccountDetails: RouteDefinition<Record<string, never>>; - routeMyAccountDelete: RouteDefinition<Record<string, never>>; - routeMyAccountPassword: RouteDefinition<Record<string, never>>; - routeMyAccountCashout: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; + routeHere: RouteDefinition<{ account: string }>; + routeMyAccountDetails: RouteDefinition; + routeMyAccountDelete: RouteDefinition; + routeMyAccountPassword: RouteDefinition; + routeMyAccountCashout: RouteDefinition; focus?: boolean; onAuthorizationRequired: () => void; onUpdateSuccess: () => void; @@ -128,6 +130,7 @@ export function UpdateAccountPassword({ updateBankState("currentChallenge", { operation: "update-password", id: String(resp.body.challenge_id), + location: routeHere.url({ account: accountName }), sent: AbsoluteTime.never(), request, }); @@ -172,7 +175,7 @@ export function UpdateAccountPassword({ > <div class="px-4 py-6 sm:p-8"> <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> - {accountIsTheCurrentUser ? ( + {accountIsTheCurrentUser ? ( <div class="sm:col-span-5"> <label class="block text-sm font-medium leading-6 text-gray-900" @@ -205,7 +208,7 @@ export function UpdateAccountPassword({ </p> </div> ) : undefined} - + <div class="sm:col-span-5"> <label class="block text-sm font-medium leading-6 text-gray-900" diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx index 36e417171..d8c129507 100644 --- a/packages/demobank-ui/src/pages/admin/AccountList.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx @@ -28,7 +28,7 @@ import { RenderAmount } from "../PaytoWireTransferForm.js"; import { RouteDefinition } from "../../route.js"; interface Props { - routeCreate: RouteDefinition<Record<string, never>>; + routeCreate: RouteDefinition; routeShowAccount: RouteDefinition<{ account: string }>; routeRemoveAccount: RouteDefinition<{ account: string }>; diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx b/packages/demobank-ui/src/pages/admin/AdminHome.tsx index 1803f1b9c..4695d35cf 100644 --- a/packages/demobank-ui/src/pages/admin/AdminHome.tsx +++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx @@ -50,9 +50,13 @@ import { AccountList } from "./AccountList.js"; * Query account information and show QR code if there is pending withdrawal */ interface Props { - routeCreate: RouteDefinition<Record<string, never>>; - routeDownloadStats: RouteDefinition<Record<string, never>>; - routeCreateWireTransfer: RouteDefinition<{ destination: string }>; + routeCreate: RouteDefinition; + routeDownloadStats: RouteDefinition; + routeCreateWireTransfer: RouteDefinition<{ + account?: string, + subject?: string, + amount?: string, + }>; routeShowAccount: RouteDefinition<{ account: string }>; routeRemoveAccount: RouteDefinition<{ account: string }>; @@ -73,7 +77,7 @@ export function AdminHome({ return ( <Fragment> <Metrics routeDownloadStats={routeDownloadStats} /> - <WireTransfer onAuthorizationRequired={onAuthorizationRequired} /> + <WireTransfer routeHere={routeCreateWireTransfer} onAuthorizationRequired={onAuthorizationRequired} /> <Transactions account="admin" @@ -149,7 +153,7 @@ export function getTimeframesForDate( function Metrics({ routeDownloadStats, }: { - routeDownloadStats: RouteDefinition<Record<string, never>>; + routeDownloadStats: RouteDefinition; }): VNode { const { i18n, dateLocale } = useTranslationContext(); const [metricType, setMetricType] = @@ -351,7 +355,7 @@ function Metrics({ </div> <dl class="mt-5 grid grid-cols-1 md:grid-cols-2 divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow-lg md:divide-x md:divide-y-0"> {resp.current.body.type !== "with-conversions" || - resp.previous.body.type !== "with-conversions" ? undefined : ( + resp.previous.body.type !== "with-conversions" ? undefined : ( <Fragment> <div class="px-4 py-5 sm:p-6"> <dt class="text-base font-normal text-gray-900"> @@ -428,9 +432,9 @@ function MetricValue({ const rate = !currAmount || - Number.isNaN(currAmount) || - !prevAmount || - Number.isNaN(prevAmount) + Number.isNaN(currAmount) || + !prevAmount || + Number.isNaN(prevAmount) ? 0 : cmp === -1 ? 1 - Math.round(currAmount) / Math.round(prevAmount) diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx index 6b4307417..8e353b5e7 100644 --- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx @@ -38,7 +38,7 @@ export function CreateNewAccount({ routeCancel, onCreateSuccess, }: { - routeCancel: RouteDefinition<Record<string, never>>; + routeCancel: RouteDefinition; onCreateSuccess: () => void; }): VNode { const { i18n } = useTranslationContext(); diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx index 7fc5961cb..6f02eae8f 100644 --- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx @@ -49,10 +49,12 @@ export function RemoveAccount({ onUpdateSuccess, onAuthorizationRequired, focus, + routeHere, }: { focus?: boolean; + routeHere: RouteDefinition<{ account: string }>; onAuthorizationRequired: () => void; - routeCancel: RouteDefinition<Record<string, never>>; + routeCancel: RouteDefinition; onUpdateSuccess: () => void; account: string; }): VNode { @@ -152,6 +154,7 @@ export function RemoveAccount({ operation: "delete-account", id: String(resp.body.challenge_id), sent: AbsoluteTime.never(), + location: routeHere.url({ account }), request: account, }); return onAuthorizationRequired(); diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx index 8dcdf5296..1b51e3222 100644 --- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx +++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx @@ -55,7 +55,8 @@ interface Props { account: string; focus?: boolean; onAuthorizationRequired: () => void; - routeClose: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; + routeHere: RouteDefinition; } type FormType = { @@ -72,6 +73,7 @@ export function CreateCashout({ account: accountName, onAuthorizationRequired, focus, + routeHere, routeClose, }: Props): VNode { const { i18n } = useTranslationContext(); @@ -258,6 +260,7 @@ export function CreateCashout({ operation: "create-cashout", id: String(resp.body.challenge_id), sent: AbsoluteTime.never(), + location: routeHere.url({}), request, }); return onAuthorizationRequired(); diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx index d841ae319..f4b2130a9 100644 --- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx +++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx @@ -33,7 +33,7 @@ import { RenderAmount } from "../PaytoWireTransferForm.js"; interface Props { id: string; - routeClose: RouteDefinition<Record<string, never>>; + routeClose: RouteDefinition; } export function ShowCashoutDetails({ id, routeClose }: Props): VNode { const { i18n, dateLocale } = useTranslationContext(); diff --git a/packages/demobank-ui/src/route.ts b/packages/demobank-ui/src/route.ts index 4b021d762..72c7802fb 100644 --- a/packages/demobank-ui/src/route.ts +++ b/packages/demobank-ui/src/route.ts @@ -15,43 +15,49 @@ */ import { useNavigationContext } from "./context/navigation.js"; +declare const __location: unique symbol; +/** + * special string that defined a location in the application + * + * this help to prevent wrong path + */ +export type AppLocation = string & { + [__location]: true; +}; +export type EmptyObject = Record<string, never>; + export function urlPattern< - T extends Record<string, string> = Record<string, never>, + T extends Record<string, string | undefined> = EmptyObject, >(pattern: RegExp, reverse: (p: T) => string): RouteDefinition<T> { + const url = reverse as ((p: T) => AppLocation) return { pattern: new RegExp(pattern), - url: reverse, + url, }; } -export type RouteDefinition<T> = { +/** + * defines a location in the app + * + * pattern: how a string will trigger this location + * url(): how a state serialize to a location + */ + +export type ObjectOf<T> = Record<string, T> | EmptyObject; + +export type RouteDefinition<T extends ObjectOf<string | undefined> = EmptyObject> = { pattern: RegExp; - url: (p: T) => string; + url: (p: T) => AppLocation; }; const nullRountDef = { pattern: new RegExp(/.*/), - url: () => "", + url: () => "" as AppLocation, }; -export function buildNullRoutDefinition<T>(): RouteDefinition<T> { +export function buildNullRoutDefinition<T extends ObjectOf<string>>(): RouteDefinition<T> { return nullRountDef; } -export type RouteMap<T> = { - [n in keyof T]: RouteDefinition<T[n]>; -}; - -export type RouteParamsType< - P, - T extends keyof P, -> = P[T] extends RouteDefinition<infer ASD> ? ASD : never; - -type Location<E, T extends RouteMap<E>, NAME extends keyof T> = { - parent: T; - name: NAME; - values: RouteParamsType<T, NAME>; -}; - /** * Search path in the pageList * get the values from the path found @@ -60,23 +66,28 @@ type Location<E, T extends RouteMap<E>, NAME extends keyof T> = { * @param path * @param params */ -function findMatch<DEF, RM extends RouteMap<DEF>, ROUTES extends keyof RM>( - pagesMap: RM, - pageList: Array<ROUTES>, +function findMatch<T extends ObjectOf<RouteDefinition>>( + pagesMap: T, + pageList: Array<keyof T>, path: string, params: Record<string, string>, -): Location<DEF, RM, ROUTES> | undefined { +): Location<T> | undefined { for (let idx = 0; idx < pageList.length; idx++) { const name = pageList[idx]; const found = pagesMap[name].pattern.exec(path); if (found !== null) { - const values = - found.groups === undefined ? {} : structuredClone(found.groups); + const values = {} as Record<string, any> Object.entries(params).forEach(([key, value]) => { values[key] = value; }); + if (found.groups !== undefined) { + Object.entries(found.groups).forEach(([key, value]) => { + values[key] = value; + }); + } + // @ts-expect-error values is a map string which is equivalent to the RouteParamsType return { name, parent: pagesMap, values }; } @@ -84,12 +95,35 @@ function findMatch<DEF, RM extends RouteMap<DEF>, ROUTES extends keyof RM>( return undefined; } -export function useCurrentLocation< - DEF, - RM extends RouteMap<DEF>, - ROUTES extends keyof RM, ->(pagesMap: RM) { - const pageList = Object.keys(pagesMap) as Array<ROUTES>; +/** + * get the type of the params of a location + * + */ +type RouteParamsType< + RouteType, + Key extends keyof RouteType, +> = RouteType[Key] extends RouteDefinition<infer ParamType> ? ParamType : never; + +/** + * Helps to create a map of a type with the key + */ +type MapKeyValue<Type> = { + [Key in keyof Type]: Key extends string ? { + parent: Type, + name: Key, + values: RouteParamsType<Type, Key>; + } : never; +} + +/** + * create a enumaration of value of a mapped type + */ +type EnumerationOf<T> = T[keyof T] + +type Location<T> = EnumerationOf<MapKeyValue<T>> + +export function useCurrentLocation<T extends ObjectOf<RouteDefinition<any>>>(pagesMap: T): Location<T> | undefined { + const pageList = Object.keys(pagesMap as object) as Array<keyof T>; const { path, params } = useNavigationContext(); return findMatch(pagesMap, pageList, path, params); |