From de8468fcd7f1c74b820486fb6d8854c758458780 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 26 Feb 2024 17:50:49 -0300 Subject: wip conversion UI --- packages/demobank-ui/src/Routing.tsx | 21 ++ .../src/components/Transactions/index.ts | 4 +- .../src/components/Transactions/state.ts | 8 +- .../src/components/Transactions/views.tsx | 12 +- packages/demobank-ui/src/hooks/access.ts | 54 ++-- packages/demobank-ui/src/hooks/circuit.ts | 37 +-- .../demobank-ui/src/pages/ConversionConfig.tsx | 333 +++++++++++++++++++++ packages/demobank-ui/src/pages/LoginForm.tsx | 5 +- .../demobank-ui/src/pages/ProfileNavigation.tsx | 50 +++- .../src/pages/account/CashoutListForAccount.tsx | 3 + .../src/pages/account/ShowAccountDetails.tsx | 3 + .../src/pages/account/UpdateAccountPassword.tsx | 3 + .../demobank-ui/src/pages/admin/AccountList.tsx | 32 +- .../src/pages/admin/CreateNewAccount.tsx | 12 - packages/demobank-ui/src/utils.ts | 3 +- packages/taler-util/src/http-client/types.ts | 10 + 16 files changed, 504 insertions(+), 86 deletions(-) create mode 100644 packages/demobank-ui/src/pages/ConversionConfig.tsx diff --git a/packages/demobank-ui/src/Routing.tsx b/packages/demobank-ui/src/Routing.tsx index 14df5a274..880a8135b 100644 --- a/packages/demobank-ui/src/Routing.tsx +++ b/packages/demobank-ui/src/Routing.tsx @@ -50,6 +50,7 @@ import { ShowCashoutDetails } from "./pages/business/ShowCashoutDetails.js"; import { urlPattern, useCurrentLocation } from "./route.js"; import { useNavigationContext } from "./context/navigation.js"; import { useEffect } from "preact/hooks"; +import { ConversionConfig } from "./pages/ConversionConfig.js"; export function Routing(): VNode { const backend = useBackendState(); @@ -230,6 +231,7 @@ export const privatePages = { myAccountDetails: urlPattern(/\/my-profile/, () => "#/my-profile"), myAccountPassword: urlPattern(/\/my-password/, () => "#/my-password"), myAccountCashouts: urlPattern(/\/my-cashouts/, () => "#/my-cashouts"), + conversionConfig: urlPattern(/\/conversion/, () => "#/conversion"), accountDetails: urlPattern<{ account: string }>( /\/profile\/(?[a-zA-Z0-9_-]+)\/details/, ({ account }) => `#/profile/${account}/details`, @@ -338,6 +340,7 @@ function PrivateRouting({ routeMyAccountDelete={privatePages.myAccountDelete} routeMyAccountDetails={privatePages.myAccountDetails} routeMyAccountPassword={privatePages.myAccountPassword} + routeConversionConfig={privatePages.conversionConfig} onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) } @@ -356,6 +359,7 @@ function PrivateRouting({ routeMyAccountDelete={privatePages.myAccountDelete} routeMyAccountDetails={privatePages.myAccountDetails} routeMyAccountPassword={privatePages.myAccountPassword} + routeConversionConfig={privatePages.conversionConfig} onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) } @@ -387,6 +391,7 @@ function PrivateRouting({ routeMyAccountDelete={privatePages.myAccountDelete} routeMyAccountDetails={privatePages.myAccountDetails} routeMyAccountPassword={privatePages.myAccountPassword} + routeConversionConfig={privatePages.conversionConfig} onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) } @@ -413,6 +418,7 @@ function PrivateRouting({ routeHere={privatePages.accountDetails} onUpdateSuccess={() => navigateTo(privatePages.home.url({}))} routeMyAccountCashout={privatePages.myAccountCashouts} + routeConversionConfig={privatePages.conversionConfig} routeMyAccountDelete={privatePages.myAccountDelete} routeMyAccountDetails={privatePages.myAccountDetails} routeMyAccountPassword={privatePages.myAccountPassword} @@ -434,6 +440,7 @@ function PrivateRouting({ routeMyAccountDelete={privatePages.myAccountDelete} routeMyAccountDetails={privatePages.myAccountDetails} routeMyAccountPassword={privatePages.myAccountPassword} + routeConversionConfig={privatePages.conversionConfig} onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) } @@ -451,6 +458,7 @@ function PrivateRouting({ routeMyAccountDelete={privatePages.myAccountDelete} routeMyAccountDetails={privatePages.myAccountDetails} routeMyAccountPassword={privatePages.myAccountPassword} + routeConversionConfig={privatePages.conversionConfig} onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) } @@ -553,6 +561,19 @@ function PrivateRouting({ /> ); } + case "conversionConfig": { + return { + navigateTo(privatePages.home.url({})) + }} + />; + } case "homeWireTransfer": { return ( | undefined; transactions: Transaction[]; - onPrev?: () => void; - onNext?: () => void; + onGoStart?: () => void; + onGoNext?: () => void; } } diff --git a/packages/demobank-ui/src/components/Transactions/state.ts b/packages/demobank-ui/src/components/Transactions/state.ts index 2d217989c..40e1b0ced 100644 --- a/packages/demobank-ui/src/components/Transactions/state.ts +++ b/packages/demobank-ui/src/components/Transactions/state.ts @@ -39,9 +39,7 @@ export function useComponentState({ account, routeCreateWireTransfer }: Props): } const transactions = - txResult.data.type === "fail" - ? [] - : txResult.data.body.transactions + txResult.result .map((tx) => { const negative = tx.direction === "debit"; const cp = parsePaytoUri( @@ -76,7 +74,7 @@ export function useComponentState({ account, routeCreateWireTransfer }: Props): error: undefined, routeCreateWireTransfer, transactions, - onNext: txResult.isLastPage ? undefined : txResult.loadMore, - onPrev: txResult.isFirstPage ? undefined : txResult.loadMorePrev, + onGoNext: txResult.isLastPage ? undefined : txResult.loadNext, + onGoStart: txResult.isFirstPage ? undefined : txResult.loadFirst, }; } diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx index 321a6ff3a..ba400b37a 100644 --- a/packages/demobank-ui/src/components/Transactions/views.tsx +++ b/packages/demobank-ui/src/components/Transactions/views.tsx @@ -25,8 +25,8 @@ import { useAccountDetails } from "../../hooks/access.js"; export function ReadyView({ transactions, routeCreateWireTransfer, - onNext, - onPrev, + onGoNext, + onGoStart, }: State.Ready): VNode { const { i18n, dateLocale } = useTranslationContext(); const { config } = useBankCoreApiContext() @@ -219,16 +219,16 @@ export function ReadyView({ diff --git a/packages/demobank-ui/src/hooks/access.ts b/packages/demobank-ui/src/hooks/access.ts index e07a3d1b1..a101dc83e 100644 --- a/packages/demobank-ui/src/hooks/access.ts +++ b/packages/demobank-ui/src/hooks/access.ts @@ -21,7 +21,7 @@ import { WithdrawalOperationStatus, } from "@gnu-taler/taler-util"; import { useEffect, useState } from "preact/hooks"; -import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js"; +import { PAGE_SIZE } from "../utils.js"; import { useBackendState } from "./backend.js"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 @@ -156,8 +156,7 @@ export function usePublicAccounts( filterAccount: string | undefined, initial?: number, ) { - // const [offset, setOffset] = useState(initial); - const offset = undefined; + const [offset, setOffset] = useState(initial); const { api } = useBankCoreApiContext(); @@ -168,7 +167,7 @@ export function usePublicAccounts( return await api.getPublicAccounts( { account }, { - limit: MAX_RESULT_SIZE, + limit: PAGE_SIZE, offset: txid ? String(txid) : undefined, order: "asc", }, @@ -190,21 +189,24 @@ export function usePublicAccounts( keepPreviousData: true, }); - const isLastPage = data && data.body.public_accounts.length < PAGE_SIZE; - const isFirstPage = !initial; + const isLastPage = + data && data.type === "ok" && data.body.public_accounts.length <= PAGE_SIZE; + const isFirstPage = !offset; + const result = data && data.type == "ok" ? structuredClone(data.body.public_accounts) : [] + if (result.length == PAGE_SIZE+1) { + result.pop() + } const pagination = { + result, isLastPage, isFirstPage, - loadMore: () => { - if (isLastPage || data?.type !== "ok") return; - const list = data.body.public_accounts; - if (list.length < MAX_RESULT_SIZE) { - // setOffset(list[list.length-1].account_name); - } + loadNext: () => { + if (!result.length) return; + setOffset(result[result.length - 1].row_id); }, - loadMorePrev: () => { - null; + loadFirst: () => { + setOffset(0); }, }; @@ -241,7 +243,7 @@ export function useTransactions(account: string, initial?: number) { return await api.getTransactions( { username, token }, { - limit: MAX_RESULT_SIZE, + limit: PAGE_SIZE +1 , offset: txid ? String(txid) : undefined, order: "dec", }, @@ -262,21 +264,23 @@ export function useTransactions(account: string, initial?: number) { }); const isLastPage = - data && data.type === "ok" && data.body.transactions.length < PAGE_SIZE; - const isFirstPage = true; + data && data.type === "ok" && data.body.transactions.length <= PAGE_SIZE; + const isFirstPage = !offset; + const result = data && data.type == "ok" ? structuredClone(data.body.transactions) : [] + if (result.length == PAGE_SIZE+1) { + result.pop() + } const pagination = { + result, isLastPage, isFirstPage, - loadMore: () => { - if (isLastPage || data?.type !== "ok") return; - const list = data.body.transactions; - if (list.length < MAX_RESULT_SIZE) { - setOffset(list[list.length - 1].row_id); - } + loadNext: () => { + if (!result.length) return; + setOffset(result[result.length - 1].row_id); }, - loadMorePrev: () => { - null; + loadFirst: () => { + setOffset(0); }, }; diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts index 88ca7b947..7d8884797 100644 --- a/packages/demobank-ui/src/hooks/circuit.ts +++ b/packages/demobank-ui/src/hooks/circuit.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ -import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js"; +import { PAGE_SIZE } from "../utils.js"; import { useBackendState } from "./backend.js"; import { @@ -32,6 +32,7 @@ import { } from "@gnu-taler/taler-util"; import _useSWR, { SWRHook, mutate } from "swr"; import { useBankCoreApiContext } from "../context/config.js"; +import { useState } from "preact/hooks"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 const useSWR = _useSWR as unknown as SWRHook; @@ -138,17 +139,16 @@ export function useBusinessAccounts() { credentials.status !== "loggedIn" ? undefined : credentials.token; const { api } = useBankCoreApiContext(); - // const [offset, setOffset] = useState(); - const offset = undefined; + const [offset, setOffset] = useState(); - function fetcher([token, offset]: [AccessToken, string]) { + function fetcher([token, offset]: [AccessToken, number]) { // FIXME: add account name filter return api.getAccounts( token, {}, { - limit: MAX_RESULT_SIZE, - offset, + limit: PAGE_SIZE+1, + offset: String(offset), order: "asc", }, ); @@ -157,7 +157,7 @@ export function useBusinessAccounts() { const { data, error } = useSWR< TalerCoreBankResultByMethod<"getAccounts">, TalerHttpError - >([token, offset, "getAccounts"], fetcher, { + >([token, offset ?? 0, "getAccounts"], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, @@ -170,22 +170,23 @@ export function useBusinessAccounts() { }); const isLastPage = - data && data.type === "ok" && data.body.accounts.length < PAGE_SIZE; - const isFirstPage = false; + data && data.type === "ok" && data.body.accounts.length <= PAGE_SIZE; + const isFirstPage = !offset; + const result = data && data.type == "ok" ? structuredClone(data.body.accounts) : [] + if (result.length == PAGE_SIZE+1) { + result.pop() + } const pagination = { + result, isLastPage, isFirstPage, - loadMore: () => { - if (isLastPage || data?.type !== "ok") return; - const list = data.body.accounts; - if (list.length < MAX_RESULT_SIZE) { - // FIXME: define pagination - // setOffset(list[list.length - 1].row_id); - } + loadNext: () => { + if (!result.length) return; + setOffset(result[result.length - 1].row_id); }, - loadMorePrev: () => { - null; + loadFirst: () => { + setOffset(0); }, }; diff --git a/packages/demobank-ui/src/pages/ConversionConfig.tsx b/packages/demobank-ui/src/pages/ConversionConfig.tsx new file mode 100644 index 000000000..73a6ab3ee --- /dev/null +++ b/packages/demobank-ui/src/pages/ConversionConfig.tsx @@ -0,0 +1,333 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +import { + AmountString, + Amounts, + HttpStatusCode, + OperationOk, + OperationResult, + TalerBankConversionApi, + TranslatedString, + assertUnreachable +} from "@gnu-taler/taler-util"; +import { + LocalNotificationBanner, + ShowInputErrorLabel, + useLocalNotification, + useTranslationContext +} from "@gnu-taler/web-util/browser"; +import { VNode, h } from "preact"; +import { useBankCoreApiContext } from "../context/config.js"; +import { useBackendState } from "../hooks/backend.js"; +import { RouteDefinition } from "../route.js"; +import { ProfileNavigation } from "./ProfileNavigation.js"; +import { useState } from "preact/hooks"; +import { undefinedIfEmpty } from "../utils.js"; +import { InputAmount } from "./PaytoWireTransferForm.js"; + +interface Props { + routeMyAccountDetails: RouteDefinition; + routeMyAccountDelete: RouteDefinition; + routeMyAccountPassword: RouteDefinition; + routeMyAccountCashout: RouteDefinition; + routeConversionConfig: RouteDefinition; + routeCancel: RouteDefinition; + onUpdateSuccess: () => void; +} + +type FormType = { + [k in keyof T]: string | undefined; +} + +type ErrorsType = { + [k in keyof T]?: TranslatedString; +} + + +type FormHandler = { + [k in keyof T]?: { + value: string | undefined; + onUpdate: (s: string) => void; + error: TranslatedString | undefined; + } +} +function useFormState(defaultValue: FormType, validate: (f: FormType) => ErrorsType): FormHandler { + const [form, updateForm] = useState>(defaultValue) + + const errors = undefinedIfEmpty>(validate(form)) + + const p = (Object.keys(form) as Array) + console.log("FORM", p) + const handler = p.reduce((prev, fieldName) => { + console.log("fie;d", fieldName) + const currentValue = form[fieldName] + const currentError = errors !== undefined ? errors[fieldName] : undefined + prev[fieldName] = { + error: currentError, + value: currentValue, + onUpdate: (newValue) => { + updateForm({ ...form, [fieldName]: newValue }) + } + } + return prev + }, {} as FormHandler) + + return handler +} + +/** + * Show histories of public accounts. + */ +export function ConversionConfig({ + routeMyAccountCashout, + routeMyAccountDelete, + routeMyAccountDetails, + routeMyAccountPassword, + routeConversionConfig, + routeCancel, + onUpdateSuccess, +}: Props): VNode { + const { i18n } = useTranslationContext(); + + const { state: credentials } = useBackendState(); + const creds = + credentials.status !== "loggedIn" || !credentials.isUserAdministrator + ? undefined + : credentials; + const { api, config } = useBankCoreApiContext(); + + const [notification, notify, handleError] = useLocalNotification(); + + if (!creds) { + return
only admin can setup conversion
; + } + + const form = useFormState({ + cashin_min_amount: undefined, + cashin_tiny_amount: undefined, + cashin_fee: undefined, + cashin_ratio: undefined, + cashin_rounding_mode: undefined, + cashout_min_amount: undefined, + cashout_tiny_amount: undefined, + cashout_fee: undefined, + cashout_ratio: undefined, + cashout_rounding_mode: undefined, + }, (state) => { + return ({ + cashin_min_amount: !state.cashin_min_amount ? i18n.str`required` : + !Amounts.parse(`${config.currency}:${state.cashin_min_amount}`) ? i18n.str`invalid` : + undefined, + + }) + }) + + + async function doUpdate() { + if (!creds) return + await handleError(async () => { + const resp = await api + .getConversionInfoAPI() + .updateConversionRate(creds.token, { + + } as any) + if (resp.type === "ok") { + onUpdateSuccess() + } else { + switch (resp.case) { + case HttpStatusCode.Unauthorized: { + return notify({ + type: "error", + title: i18n.str`Wrong credentials`, + description: resp.detail.hint as TranslatedString, + debug: resp.detail, + }); + } + case HttpStatusCode.NotImplemented: { + return notify({ + type: "error", + title: i18n.str`Conversion is disabled`, + description: resp.detail.hint as TranslatedString, + debug: resp.detail, + }); + } + default: + assertUnreachable(resp); + } + } + }); + } + + + return ( +
+ + +
+ + +
+

+ Conversion +

+
+ +
{ + e.preventDefault(); + }} + > +
+
+
+ + + +

+ Only cashout operation above this threshold will be allowed +

+
+
+
+ +
+
+
+ + + +

+ Smallest difference between two amounts +

+
+
+
+ +
+
+
+ + + +

+ Operation fee +

+
+
+
+ +
+ +
+ { + form.cashout_ratio?.onUpdate(e.currentTarget.value); + }} + autocomplete="off" + /> + +
+

+ + Your current password, for security + +

+
+ +
+ + Cancel + + +
+
+
+
+ ); +} + diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index f90b985e6..b351785d9 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -88,10 +88,7 @@ export function LoginForm({ .createAccessToken(password, { // scope: "readwrite" as "write", // FIX: different than merchant scope: "readwrite", - duration: { - d_us: "forever", // FIX: should return shortest - // d_us: 60 * 60 * 24 * 7 * 1000 * 1000 - }, + duration: { d_us: "forever" }, refreshable: true, }); if (resp.type === "ok") { diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx b/packages/demobank-ui/src/pages/ProfileNavigation.tsx index ba02d07b9..8b7a8205f 100644 --- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx +++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx @@ -13,13 +13,13 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ +import { assertUnreachable } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { VNode, h } from "preact"; +import { Fragment, VNode, h } from "preact"; 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 { EmptyObject, RouteDefinition } from "../route.js"; +import { useBackendState } from "../hooks/backend.js"; +import { RouteDefinition } from "../route.js"; export function ProfileNavigation({ current, @@ -27,20 +27,24 @@ export function ProfileNavigation({ routeMyAccountDelete, routeMyAccountDetails, routeMyAccountPassword, + routeConversionConfig }: { - current: "details" | "delete" | "credentials" | "cashouts", + current: "details" | "delete" | "credentials" | "cashouts" | "conversion", routeMyAccountDetails: RouteDefinition; routeMyAccountDelete: RouteDefinition; routeMyAccountPassword: RouteDefinition; routeMyAccountCashout: RouteDefinition; + routeConversionConfig: RouteDefinition; }): VNode { const { i18n } = useTranslationContext(); const { config } = useBankCoreApiContext(); const { state: credentials } = useBackendState(); - const nonAdminUser = + const isAdminUser = credentials.status !== "loggedIn" ? false - : !credentials.isUserAdministrator; + : credentials.isUserAdministrator; + const nonAdminUser = !isAdminUser; + const { navigateTo } = useNavigationContext(); return (
@@ -71,6 +75,10 @@ export function ProfileNavigation({ navigateTo(routeMyAccountCashout.url({})); return; } + case "conversion": { + navigateTo(routeConversionConfig.url({})); + return; + } default: assertUnreachable(op); } @@ -88,9 +96,14 @@ export function ProfileNavigation({ Credentials {config.allow_conversion ? ( - + + + + ) : undefined}
@@ -165,6 +178,23 @@ export function ProfileNavigation({ > ) : undefined} + {config.allow_conversion && isAdminUser ? ( + + + Conversion + + + + ) : undefined} diff --git a/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx b/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx index 14f4acdb8..fe64778dd 100644 --- a/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx +++ b/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx @@ -31,6 +31,7 @@ interface Props { routeMyAccountPassword: RouteDefinition; routeMyAccountCashout: RouteDefinition; routeCreateCashout: RouteDefinition; + routeConversionConfig:RouteDefinition; } export function CashoutListForAccount({ @@ -41,6 +42,7 @@ export function CashoutListForAccount({ routeMyAccountCashout, routeMyAccountDelete, routeMyAccountDetails, + routeConversionConfig, routeMyAccountPassword, routeClose, }: Props): VNode { @@ -61,6 +63,7 @@ export function CashoutListForAccount({ routeMyAccountDelete={routeMyAccountDelete} routeMyAccountDetails={routeMyAccountDetails} routeMyAccountPassword={routeMyAccountPassword} + routeConversionConfig={routeConversionConfig} /> ) : (

diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx index db76e83d9..6aad8997a 100644 --- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx +++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx @@ -51,6 +51,7 @@ export function ShowAccountDetails({ routeMyAccountDetails, routeHere, routeMyAccountPassword, + routeConversionConfig, }: { routeClose: RouteDefinition; routeHere: RouteDefinition<{ account: string }>; @@ -58,6 +59,7 @@ export function ShowAccountDetails({ routeMyAccountDelete: RouteDefinition; routeMyAccountPassword: RouteDefinition; routeMyAccountCashout: RouteDefinition; + routeConversionConfig: RouteDefinition; onUpdateSuccess: () => void; onAuthorizationRequired: () => void; account: string; @@ -184,6 +186,7 @@ export function ShowAccountDetails({ diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx index dfa0adf17..305f041ec 100644 --- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx +++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx @@ -45,6 +45,7 @@ export function UpdateAccountPassword({ routeMyAccountDelete, routeMyAccountDetails, routeMyAccountPassword, + routeConversionConfig, focus, routeHere, }: { @@ -54,6 +55,7 @@ export function UpdateAccountPassword({ routeMyAccountDelete: RouteDefinition; routeMyAccountPassword: RouteDefinition; routeMyAccountCashout: RouteDefinition; + routeConversionConfig: RouteDefinition; focus?: boolean; onAuthorizationRequired: () => void; onUpdateSuccess: () => void; @@ -152,6 +154,7 @@ export function UpdateAccountPassword({ routeMyAccountDelete={routeMyAccountDelete} routeMyAccountDetails={routeMyAccountDetails} routeMyAccountPassword={routeMyAccountPassword} + routeConversionConfig={routeConversionConfig} /> ) : (

diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx index d8c129507..811c3e37a 100644 --- a/packages/demobank-ui/src/pages/admin/AccountList.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx @@ -62,8 +62,10 @@ export function AccountList({ } } + const onGoStart = result.isFirstPage ? undefined : result.loadFirst + const onGoNext = result.isLastPage ? undefined : result.loadNext - const { accounts } = result.data.body; + const accounts = result.result; return (
@@ -93,7 +95,9 @@ export function AccountList({
{!accounts.length ? ( -
+
+ {/* FIXME: ADDD empty list */} +
) : ( @@ -208,6 +212,30 @@ export function AccountList({
)}
+ +
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx index 8e353b5e7..e09164ffb 100644 --- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx @@ -55,18 +55,6 @@ export function CreateNewAccount({ async function doCreate() { if (!submitAccount || !token) return; await handleError(async () => { - // const account: TalerCorebankApi.RegisterAccountRequest = { - // cashout_payto_uri: submitAccount.cashout_payto_uri, - // challenge_contact_data: submitAccount.challenge_contact_data, - // internal_payto_uri: submitAccount.internal_payto_uri, - // debit_threshold: submitAccount.debit_threshold, - // is_public: submitAccount.is_public, - // is_taler_exchange: submitAccount.is_taler_exchange, - // name: submitAccount.name, - // username: submitAccount.username, - // password: getRandomPassword(), - // }; - const resp = await api.createAccount(token, submitAccount); if (resp.type === "ok") { notifyInfo( diff --git a/packages/demobank-ui/src/utils.ts b/packages/demobank-ui/src/utils.ts index ab0b60d72..8b0febe42 100644 --- a/packages/demobank-ui/src/utils.ts +++ b/packages/demobank-ui/src/utils.ts @@ -119,8 +119,7 @@ export enum CashoutStatus { PENDING = "pending", } -export const PAGE_SIZE = 20; -export const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1; +export const PAGE_SIZE = 5; type Translator = ReturnType["i18n"]; diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index 05fce4a49..3d8814998 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -344,6 +344,7 @@ const codecForPublicAccount = (): Codec => .property("balance", codecForBalance()) .property("payto_uri", codecForPaytoString()) .property("is_taler_exchange", codecForBoolean()) + .property("row_id", codecOptional(codecForNumber())) .build("TalerCorebankApi.PublicAccount"); export const codecForPublicAccountsResponse = @@ -362,6 +363,7 @@ export const codecForAccountMinimalData = .property("debit_threshold", codecForAmountString()) .property("is_public", codecForBoolean()) .property("is_taler_exchange", codecForBoolean()) + .property("row_id", codecOptional(codecForNumber())) .build("TalerCorebankApi.AccountMinimalData"); export const codecForListBankAccountsResponse = @@ -1549,6 +1551,10 @@ export namespace TalerCorebankApi { // Is this a taler exchange account? is_taler_exchange: boolean; + + // Opaque unique ID used for pagination. + // @since v4, will become mandatory in the future. + row_id?: Integer; } export interface ListBankAccountsResponse { @@ -1579,6 +1585,10 @@ export namespace TalerCorebankApi { // Is this a taler exchange account? is_taler_exchange: boolean; + + // Opaque unique ID used for pagination. + // @since v4, will become mandatory in the future. + row_id?: Integer; } export interface AccountData { -- cgit v1.2.3