From 15af6c619de70336bcdfbabbd32b9d93aabefc5b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 22 Sep 2023 18:34:49 -0300 Subject: towards new core bank api --- packages/demobank-ui/src/declaration.d.ts | 166 +++++++++++++++++++++++++----- 1 file changed, 140 insertions(+), 26 deletions(-) (limited to 'packages/demobank-ui/src/declaration.d.ts') diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts index 462287c59..a9573fbcd 100644 --- a/packages/demobank-ui/src/declaration.d.ts +++ b/packages/demobank-ui/src/declaration.d.ts @@ -99,11 +99,6 @@ type Amount = string; type UUID = string; type Integer = number; -interface Balance { - amount: Amount; - credit_debit_indicator: "credit" | "debit"; -} - namespace SandboxBackend { export interface Config { // Name of this API, always "circuit". @@ -126,7 +121,7 @@ namespace SandboxBackend { } export interface SandboxError { - error: SandboxErrorDetail; + error?: SandboxErrorDetail; } interface SandboxErrorDetail { // String enum classifying the error. @@ -152,26 +147,12 @@ namespace SandboxBackend { UtilError = "util-error", } - namespace Access { - interface PublicAccountsResponse { - publicAccounts: PublicAccount[]; - } - interface PublicAccount { - iban: string; - balance: string; - // The account name _and_ the username of the - // Sandbox customer that owns such a bank account. - accountLabel: string; - } - interface BankAccountBalanceResponse { - // Available balance on the account. - balance: Balance; - // payto://-URI of the account. (New) - paytoUri: string; - // Number indicating the max debit allowed for the requesting user. - debitThreshold: Amount; - } + type EmailAddress = string; + type PhoneNumber = string; + + namespace CoreBank { + interface BankAccountCreateWithdrawalRequest { // Amount to withdraw. amount: Amount; @@ -243,11 +224,144 @@ namespace SandboxBackend { amount?: string; } - interface BankRegistrationRequest { + interface RegisterAccountRequest { + // Username username: string; + // Password. password: string; + + // Legal name of the account owner + name: string; + + // Defaults to false. + is_public?: boolean; + + // Is this a taler exchange account? + // If true: + // - incoming transactions to the account that do not + // have a valid reserve public key are automatically + // - the account provides the taler-wire-gateway-api endpoints + // Defaults to false. + is_taler_exchange?: boolean; + + // Addresses where to send the TAN for transactions. + // Currently only used for cashouts. + // If missing, cashouts will fail. + // In the future, might be used for other transactions + // as well. + challenge_contact_data?: ChallengeContactData; + + // 'payto' address pointing a bank account + // external to the libeufin-bank. + // Payments will be sent to this bank account + // when the user wants to convert the local currency + // back to fiat currency outside libeufin-bank. + cashout_payto_uri?: string; + + // Internal payto URI of this bank account. + // Used mostly for testing. + internal_payto_uri?: string; + } + interface ChallengeContactData { + + // E-Mail address + email?: EmailAddress; + + // Phone number. + phone?: PhoneNumber; + } + + interface AccountReconfiguration { + + // Addresses where to send the TAN for transactions. + // Currently only used for cashouts. + // If missing, cashouts will fail. + // In the future, might be used for other transactions + // as well. + challenge_contact_data?: ChallengeContactData; + + // 'payto' address pointing a bank account + // external to the libeufin-bank. + // Payments will be sent to this bank account + // when the user wants to convert the local currency + // back to fiat currency outside libeufin-bank. + cashout_address?: string; + + // Legal name associated with $username. + // When missing, the old name is kept. + name?: string; + + // If present, change the is_exchange configuration. + // See RegisterAccountRequest + is_exchange?: boolean; + } + + + interface AccountPasswordChange { + + // New password. + new_password: string; + } + interface PublicAccountsResponse { + public_accounts: PublicAccount[]; + } + interface PublicAccount { + payto_uri: string; + + balance: Balance; + + // The account name (=username) of the + // libeufin-bank account. + account_name: string; + } + + interface ListBankAccountsResponse { + accounts: AccountMinimalData[]; + } + // interface Balance { + // amount: Amount; + // credit_debit_indicator: "credit" | "debit"; + // } + type Balance = Amount + interface AccountMinimalData { + // Username + username: string; + + // Legal name of the account owner. + name: string; + + // current balance of the account + balance: Balance; + + // Number indicating the max debit allowed for the requesting user. + debit_threshold: Amount; + } + + interface AccountData { + // Legal name of the account owner. + name: string; + + // Available balance on the account. + balance: Balance; + + // payto://-URI of the account. + payto_uri: string; + + // Number indicating the max debit allowed for the requesting user. + debit_threshold: Amount; + + contact_data?: ChallengeContactData; + + // 'payto' address pointing the bank account + // where to send cashouts. This field is optional + // because not all the accounts are required to participate + // in the merchants' circuit. One example is the exchange: + // that never cashouts. Registering these accounts can + // be done via the access API. + cashout_payto_uri?: string; } + } namespace Circuit { -- cgit v1.2.3 From 0b2c03dc5e1060cd229aeafb84263f171b5a9788 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 25 Sep 2023 09:31:17 -0300 Subject: new libeufin api --- packages/demobank-ui/src/declaration.d.ts | 9 ++--- packages/demobank-ui/src/hooks/backend.ts | 43 +++++----------------- packages/demobank-ui/src/hooks/circuit.ts | 23 +++++++----- .../demobank-ui/src/hooks/useCredentialsChecker.ts | 12 +++--- .../demobank-ui/src/pages/AccountPage/state.ts | 4 +- .../demobank-ui/src/pages/AccountPage/views.tsx | 10 ++--- packages/demobank-ui/src/pages/BankFrame.tsx | 6 +-- packages/demobank-ui/src/pages/LoginForm.tsx | 8 ++-- .../demobank-ui/src/pages/RegistrationPage.tsx | 12 +++++- packages/demobank-ui/src/pages/admin/Account.tsx | 7 +--- .../demobank-ui/src/pages/admin/RemoveAccount.tsx | 4 +- packages/demobank-ui/src/pages/business/Home.tsx | 7 +--- packages/demobank-ui/src/stories.test.ts | 3 +- 13 files changed, 60 insertions(+), 88 deletions(-) (limited to 'packages/demobank-ui/src/declaration.d.ts') diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts index a9573fbcd..0c083a939 100644 --- a/packages/demobank-ui/src/declaration.d.ts +++ b/packages/demobank-ui/src/declaration.d.ts @@ -319,11 +319,10 @@ namespace SandboxBackend { interface ListBankAccountsResponse { accounts: AccountMinimalData[]; } - // interface Balance { - // amount: Amount; - // credit_debit_indicator: "credit" | "debit"; - // } - type Balance = Amount + interface Balance { + amount: Amount; + credit_debit_indicator: "credit" | "debit"; + } interface AccountMinimalData { // Username username: string; diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts index f2be90f08..e6a3a1c0c 100644 --- a/packages/demobank-ui/src/hooks/backend.ts +++ b/packages/demobank-ui/src/hooks/backend.ts @@ -40,6 +40,7 @@ import { useCallback, useEffect, useState } from "preact/hooks"; import { useSWRConfig } from "swr"; import { useBackendContext } from "../context/backend.js"; import { bankUiSettings } from "../settings.js"; +import { AccessToken } from "./useCredentialsChecker.js"; /** * Has the information to reach and @@ -49,7 +50,7 @@ export type BackendState = LoggedIn | LoggedOut; export interface BackendCredentials { username: string; - password: string; + token: AccessToken; } interface LoggedIn extends BackendCredentials { @@ -64,7 +65,7 @@ export const codecForBackendStateLoggedIn = (): Codec => buildCodecForObject() .property("status", codecForConstString("loggedIn")) .property("username", codecForString()) - .property("password", codecForString()) + .property("token", codecForString() as Codec) .property("isUserAdministrator", codecForBoolean()) .build("BackendState.LoggedIn"); @@ -255,35 +256,11 @@ interface InvalidationResult { error: unknown; } -export function useCredentialsCheckerOld() { - const { request } = useApiContext(); - const baseUrl = getInitialBackendBaseURL(); - //check against account details endpoint - //while sandbox backend doesn't have a login endpoint - return async function testLogin( - username: string, - password: string, - ): Promise { - try { - await request(baseUrl, `access-api/accounts/${username}/`, { - basicAuth: { username, password }, - preventCache: true, - }); - return { valid: true }; - } catch (error) { - if (error instanceof RequestError) { - return { valid: false, requestError: true, cause: error.cause }; - } - return { valid: false, requestError: false, error }; - } - }; -} - export function useAuthenticatedBackend(): useBackendType { const { state } = useBackendContext(); const { request: requestHandler } = useApiContext(); - const creds = state.status === "loggedIn" ? state : undefined; + const creds = state.status === "loggedIn" ? state.token : undefined; const baseUrl = getInitialBackendBaseURL(); const request = useCallback( @@ -291,14 +268,14 @@ export function useAuthenticatedBackend(): useBackendType { path: string, options: RequestOptions = {}, ): Promise> { - return requestHandler(baseUrl, path, { basicAuth: creds, ...options }); + return requestHandler(baseUrl, path, { token: creds, ...options }); }, [baseUrl, creds], ); const fetcher = useCallback( function fetcherImpl(endpoint: string): Promise> { - return requestHandler(baseUrl, endpoint, { basicAuth: creds }); + return requestHandler(baseUrl, endpoint, { token: creds }); }, [baseUrl, creds], ); @@ -309,7 +286,7 @@ export function useAuthenticatedBackend(): useBackendType { number, ]): Promise> { return requestHandler(baseUrl, endpoint, { - basicAuth: creds, + token: creds, params: { delta: size, start: size * page }, }); }, @@ -321,7 +298,7 @@ export function useAuthenticatedBackend(): useBackendType { > { return Promise.all( endpoints.map((endpoint) => - requestHandler(baseUrl, endpoint, { basicAuth: creds }), + requestHandler(baseUrl, endpoint, { token: creds }), ), ); }, @@ -335,7 +312,7 @@ export function useAuthenticatedBackend(): useBackendType { string, ]): Promise> { return requestHandler(baseUrl, endpoint, { - basicAuth: creds, + token: creds, params: { page: page || 1, size }, }); }, @@ -347,7 +324,7 @@ export function useAuthenticatedBackend(): useBackendType { HttpResponseOk > { return requestHandler(baseUrl, endpoint, { - basicAuth: creds, + token: creds, params: { account }, }); }, diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts index 06557b77f..4ef80b055 100644 --- a/packages/demobank-ui/src/hooks/circuit.ts +++ b/packages/demobank-ui/src/hooks/circuit.ts @@ -33,6 +33,7 @@ import { // FIX default import https://github.com/microsoft/TypeScript/issues/49189 import _useSWR, { SWRHook } from "swr"; import { AmountJson, Amounts } from "@gnu-taler/taler-util"; +import { AccessToken } from "./useCredentialsChecker.js"; const useSWR = _useSWR as unknown as SWRHook; export function useAdminAccountAPI(): AdminAccountAPI { @@ -90,7 +91,8 @@ export function useAdminAccountAPI(): AdminAccountAPI { await mutateAll(/.*/); logIn({ username: account, - password: data.new_password, + //FIXME: change password api + token: data.new_password as AccessToken, }); } return res; @@ -215,14 +217,15 @@ export interface CircuitAccountAPI { async function getBusinessStatus( request: ReturnType["request"], - basicAuth: { username: string; password: string }, + username: string, + token: AccessToken, ): Promise { try { const url = getInitialBackendBaseURL(); const result = await request( url, - `circuit-api/accounts/${basicAuth.username}`, - { basicAuth }, + `circuit-api/accounts/${username}`, + { token }, ); return result.ok; } catch (error) { @@ -264,10 +267,10 @@ type CashoutEstimators = { export function useEstimator(): CashoutEstimators { const { state } = useBackendContext(); const { request } = useApiContext(); - const basicAuth = + const creds = state.status === "loggedOut" ? undefined - : { username: state.username, password: state.password }; + : state.token; return { estimateByCredit: async (amount, fee, rate) => { const zeroBalance = Amounts.zeroOfCurrency(fee.currency); @@ -282,7 +285,7 @@ export function useEstimator(): CashoutEstimators { url, `circuit-api/cashouts/estimates`, { - basicAuth, + token: creds, params: { amount_credit: Amounts.stringify(amount), }, @@ -313,7 +316,7 @@ export function useEstimator(): CashoutEstimators { url, `circuit-api/cashouts/estimates`, { - basicAuth, + token: creds, params: { amount_debit: Amounts.stringify(amount), }, @@ -339,11 +342,11 @@ export function useBusinessAccountFlag(): boolean | undefined { const creds = state.status === "loggedOut" ? undefined - : { username: state.username, password: state.password }; + : {user: state.username, token: state.token}; useEffect(() => { if (!creds) return; - getBusinessStatus(request, creds) + getBusinessStatus(request, creds.user, creds.token) .then((result) => { setIsBusiness(result); }) diff --git a/packages/demobank-ui/src/hooks/useCredentialsChecker.ts b/packages/demobank-ui/src/hooks/useCredentialsChecker.ts index 05954348f..f66a4a7c6 100644 --- a/packages/demobank-ui/src/hooks/useCredentialsChecker.ts +++ b/packages/demobank-ui/src/hooks/useCredentialsChecker.ts @@ -23,13 +23,13 @@ export function useCredentialsChecker() { const response = await request(baseUrl, `accounts/${username}/token`, { method: "POST", basicAuth: { - username: username, + username, password, }, data, contentType: "json" }); - return { valid: true, token: response.data.token, expiration: response.data.expiration }; + return { valid: true, token: `secret-token:${response.data.access_token}` as AccessToken, expiration: response.data.expiration }; } catch (error) { if (error instanceof RequestError) { return { valid: false, cause: error.cause }; @@ -76,13 +76,13 @@ export function useCredentialsChecker() { } } - return requestNewLoginToken(baseUrl, token.token as AccessToken) + return requestNewLoginToken(baseUrl, token.token) } return { requestNewLoginToken, refreshLoginToken } } export interface LoginToken { - token: string, + token: AccessToken, expiration: Timestamp, } // token used to get loginToken @@ -95,7 +95,7 @@ export type AccessToken = string & { type YesOrNo = "yes" | "no"; export type LoginResult = { valid: true; - token: string; + token: AccessToken; expiration: Timestamp; } | { valid: false; @@ -121,7 +121,7 @@ export interface LoginTokenSuccessResponse { // that are in scope for some time. Must be prefixed // with "Bearer " when used in the "Authorization" HTTP header. // Will already begin with the RFC 8959 prefix. - token: string; + access_token: AccessToken; // Scope of the token (which kinds of operations it will allow) scope: "readonly" | "write"; diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts index 3df463f4e..a23d3b569 100644 --- a/packages/demobank-ui/src/pages/AccountPage/state.ts +++ b/packages/demobank-ui/src/pages/AccountPage/state.ts @@ -63,9 +63,7 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe const { data } = result; - // FIXME: balance - // const balance = Amounts.parseOrThrow(data.balance.amount); - const balance = Amounts.parseOrThrow(data.balance); + const balance = Amounts.parseOrThrow(data.balance.amount); const debitThreshold = Amounts.parseOrThrow(data.debit_threshold); const payto = parsePaytoUri(data.payto_uri); diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx index b3996c5fc..32aedebf2 100644 --- a/packages/demobank-ui/src/pages/AccountPage/views.tsx +++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx @@ -14,16 +14,14 @@ GNU Taler; see the file COPYING. If not, see */ -import { Amounts, stringifyPaytoUri } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { Fragment, h, VNode } from "preact"; +import { Fragment, VNode, h } from "preact"; import { Transactions } from "../../components/Transactions/index.js"; -import { PaymentOptions } from "../PaymentOptions.js"; -import { State } from "./index.js"; -import { CopyButton } from "../../components/CopyButton.js"; -import { bankUiSettings } from "../../settings.js"; import { useBusinessAccountDetails } from "../../hooks/circuit.js"; import { useSettings } from "../../hooks/settings.js"; +import { bankUiSettings } from "../../settings.js"; +import { PaymentOptions } from "../PaymentOptions.js"; +import { State } from "./index.js"; export function InvalidIbanView({ error }: State.InvalidIban) { return ( diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index 214aac063..39ca09f1b 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -474,11 +474,9 @@ function AccountBalance({ account }: { account: string }): VNode { const result = useAccountDetails(account); if (!result.ok) return
- // FIXME: balance return
- {Amounts.currencyOf(result.data.balance)} {Amounts.stringifyValue(result.data.balance)} - {/* {Amounts.currencyOf(result.data.balance.amount)} + {Amounts.currencyOf(result.data.balance.amount)}  {result.data.balance.credit_debit_indicator === "debit" ? "-" : ""} - {Amounts.stringifyValue(result.data.balance.amount)} */} + {Amounts.stringifyValue(result.data.balance.amount)}
} diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index cfef71b9a..f1ba439ac 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -18,13 +18,11 @@ import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util"; import { ErrorType, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { useBackendContext } from "../context/backend.js"; +import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js"; import { bankUiSettings } from "../settings.js"; import { undefinedIfEmpty } from "../utils.js"; -import { USERNAME_REGEX } from "./RegistrationPage.js"; -import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; -import { AccessToken, useCredentialsChecker } from "../hooks/useCredentialsChecker.js"; -import { useCredentialsCheckerOld } from "../hooks/backend.js"; /** * Collect and submit login data. @@ -62,7 +60,7 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode { setBusy({}) const result = await requestNewLoginToken(username, password); if (result.valid) { - backend.logIn({ username, password }); + backend.logIn({ username, token: result.token }); } else { const { cause } = result; switch (cause.type) { diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx index 5325f43ab..a2543f977 100644 --- a/packages/demobank-ui/src/pages/RegistrationPage.tsx +++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx @@ -28,6 +28,7 @@ import { bankUiSettings } from "../settings.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { getRandomPassword, getRandomUsername } from "./rnd.js"; +import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js"; const logger = new Logger("RegistrationPage"); @@ -58,6 +59,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on const [name, setName] = useState(); const [password, setPassword] = useState(); const [repeatPassword, setRepeatPassword] = useState(); + const {requestNewLoginToken} = useCredentialsChecker() const { register } = useTestingAPI(); const { i18n } = useTranslationContext(); @@ -83,8 +85,11 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on if (!username || !password || !name) return; try { await register({ name, username, password }); + const resp = await requestNewLoginToken(username, password) setUsername(undefined); - backend.logIn({ username, password }); + if (resp.valid) { + backend.logIn({ username, token: resp.token }); + } onComplete(); } catch (error) { if (error instanceof RequestError) { @@ -125,7 +130,10 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on setRepeatPassword(undefined); const username = `_${user.first}-${user.second}_` await register({ username, name: `${user.first} ${user.second}`, password: pass }); - backend.logIn({ username, password: pass }); + const resp = await requestNewLoginToken(username, pass) + if (resp.valid) { + backend.logIn({ username, token: resp.token }); + } onComplete(); } catch (error) { if (error instanceof RequestError) { diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx b/packages/demobank-ui/src/pages/admin/Account.tsx index d368c4319..8ea59cdcb 100644 --- a/packages/demobank-ui/src/pages/admin/Account.tsx +++ b/packages/demobank-ui/src/pages/admin/Account.tsx @@ -17,11 +17,8 @@ export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode } const { data } = result; - //FIXME: libeufin does not follow the spec - const balance = Amounts.parseOrThrow(data.balance); - const balanceIsDebit = true; - // const balance = Amounts.parseOrThrow(data.balance.amount); - // const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit"; + const balance = Amounts.parseOrThrow(data.balance.amount); + const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit"; const debitThreshold = Amounts.parseOrThrow(result.data.debit_threshold); const limit = balanceIsDebit diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx index 63a7c79f3..1e5370afc 100644 --- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx @@ -41,9 +41,7 @@ export function RemoveAccount({ if (focus) ref.current?.focus(); }, [focus]); - //FIXME: libeufin does not follow the spec - const balance = Amounts.parse(result.data.balance); - // const balance = Amounts.parse(result.data.balance.amount); + const balance = Amounts.parse(result.data.balance.amount); if (!balance) { return
there was an error reading the balance
; } diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx index 427cfc656..628ae328d 100644 --- a/packages/demobank-ui/src/pages/business/Home.tsx +++ b/packages/demobank-ui/src/pages/business/Home.tsx @@ -236,11 +236,8 @@ function CreateCashout({ if (!ratiosResult.ok) return onLoadNotOk(ratiosResult); const config = ratiosResult.data; - //FIXME: libeufin does not follow the spec - const balance = Amounts.parseOrThrow(result.data.balance); - const balanceIsDebit = true; - // const balance = Amounts.parseOrThrow(result.data.balance.amount); - // const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit"; + const balance = Amounts.parseOrThrow(result.data.balance.amount); + const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit"; const debitThreshold = Amounts.parseOrThrow(result.data.debit_threshold); const zero = Amounts.zeroOfCurrency(balance.currency); diff --git a/packages/demobank-ui/src/stories.test.ts b/packages/demobank-ui/src/stories.test.ts index e68788f16..07db7d8cf 100644 --- a/packages/demobank-ui/src/stories.test.ts +++ b/packages/demobank-ui/src/stories.test.ts @@ -26,6 +26,7 @@ import * as pages from "./pages/index.stories.js"; import { ComponentChildren, VNode, h as create } from "preact"; import { BackendStateProviderTesting } from "./context/backend.js"; +import { AccessToken } from "./hooks/useCredentialsChecker.js"; setupI18n("en", { en: {} }); @@ -56,7 +57,7 @@ function DefaultTestingContext({ state: { status: "loggedIn", username: "test", - password: "pwd", + token: "pwd" as AccessToken, isUserAdministrator: false, }, }); -- cgit v1.2.3 From 4041a76a58503572c6fe8edc87658afc946a11e0 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 25 Sep 2023 11:58:17 -0300 Subject: more ui: pagination --- packages/demobank-ui/src/components/Routing.tsx | 3 +- .../src/components/Transactions/index.ts | 2 + .../src/components/Transactions/state.ts | 52 ++++++---------------- .../src/components/Transactions/views.tsx | 25 ++++++++++- packages/demobank-ui/src/declaration.d.ts | 41 ++++++++--------- packages/demobank-ui/src/hooks/access.ts | 23 ++++++---- packages/demobank-ui/src/hooks/backend.ts | 18 +++++--- .../demobank-ui/src/pages/AccountPage/state.ts | 2 +- packages/demobank-ui/src/pages/BankFrame.tsx | 17 +++++-- packages/demobank-ui/src/pages/HomePage.tsx | 5 +-- packages/demobank-ui/src/pages/LoginForm.tsx | 41 ++++++++++++++--- .../src/pages/WithdrawalConfirmationQuestion.tsx | 5 ++- packages/demobank-ui/src/pages/admin/Account.tsx | 2 +- .../demobank-ui/src/pages/admin/AccountList.tsx | 5 +-- packages/demobank-ui/src/pages/admin/Home.tsx | 8 ++-- packages/demobank-ui/src/pages/business/Home.tsx | 8 ++-- 16 files changed, 151 insertions(+), 106 deletions(-) (limited to 'packages/demobank-ui/src/declaration.d.ts') diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx index 2710069c2..05af1719b 100644 --- a/packages/demobank-ui/src/components/Routing.tsx +++ b/packages/demobank-ui/src/components/Routing.tsx @@ -17,7 +17,7 @@ import { createHashHistory } from "history"; import { VNode, h } from "preact"; import { Route, Router, route } from "preact-router"; -import { useEffect } from "preact/hooks"; +import { useEffect, useErrorBoundary } from "preact/hooks"; import { BankFrame } from "../pages/BankFrame.js"; import { BusinessAccount } from "../pages/business/Home.js"; import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js"; @@ -27,6 +27,7 @@ import { useBackendContext } from "../context/backend.js"; import { LoginForm } from "../pages/LoginForm.js"; import { AdminHome } from "../pages/admin/Home.js"; import { bankUiSettings } from "../settings.js"; +import { notifyError } from "@gnu-taler/web-util/browser"; export function Routing(): VNode { const history = createHashHistory(); diff --git a/packages/demobank-ui/src/components/Transactions/index.ts b/packages/demobank-ui/src/components/Transactions/index.ts index 46b38ce74..9df1a70e5 100644 --- a/packages/demobank-ui/src/components/Transactions/index.ts +++ b/packages/demobank-ui/src/components/Transactions/index.ts @@ -46,6 +46,8 @@ export namespace State { status: "ready"; error: undefined; transactions: Transaction[]; + onPrev?: () => void; + onNext?: () => void; } } diff --git a/packages/demobank-ui/src/components/Transactions/state.ts b/packages/demobank-ui/src/components/Transactions/state.ts index d2a512b08..30c48aa45 100644 --- a/packages/demobank-ui/src/components/Transactions/state.ts +++ b/packages/demobank-ui/src/components/Transactions/state.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ -import { AbsoluteTime, Amounts } from "@gnu-taler/taler-util"; +import { AbsoluteTime, Amounts, parsePaytoUri } from "@gnu-taler/taler-util"; import { useTransactions } from "../../hooks/access.js"; import { Props, State, Transaction } from "./index.js"; @@ -34,45 +34,19 @@ export function useComponentState({ account }: Props): State { } const transactions = result.data.transactions - .map((item: unknown) => { - if ( - !item || - typeof item !== "object" || - !("direction" in item) || - !("creditorIban" in item) || - !("debtorIban" in item) || - !("date" in item) || - !("subject" in item) || - !("currency" in item) || - !("amount" in item) - ) { - //not valid - return; - } - const anyItem = item as any; - if ( - !(typeof anyItem.creditorIban === "string") || - !(typeof anyItem.debtorIban === "string") || - !(typeof anyItem.date === "string") || - !(typeof anyItem.subject === "string") || - !(typeof anyItem.currency === "string") || - !(typeof anyItem.amount === "string") - ) { - return; - } + .map((tx) => { - const negative = anyItem.direction === "DEBIT"; - const counterpart = negative ? anyItem.creditorIban : anyItem.debtorIban; + const negative = tx.direction === "debit"; + const cp = parsePaytoUri(negative ? tx.creditor_payto_uri : tx.debtor_payto_uri); + const counterpart = (cp === undefined || !cp.isKnown ? undefined : + cp.targetType === "iban" ? cp.iban : + cp.targetType === "x-taler-bank" ? cp.account : + cp.targetType === "bitcoin" ? `${cp.targetPath.substring(0, 6)}...` : undefined) ?? + "unkown"; - let date = anyItem.date ? parseInt(anyItem.date, 10) : 0; - if (isNaN(date) || !isFinite(date)) { - date = 0; - } - const when: AbsoluteTime = !date - ? AbsoluteTime.never() - : AbsoluteTime.fromMilliseconds(date); - const amount = Amounts.parse(`${anyItem.currency}:${anyItem.amount}`); - const subject = anyItem.subject; + const when = AbsoluteTime.fromMilliseconds(tx.date / 1000); + const amount = Amounts.parse(tx.amount); + const subject = tx.subject; return { negative, counterpart, @@ -87,5 +61,7 @@ export function useComponentState({ account }: Props): State { status: "ready", error: undefined, transactions, + onNext: result.isReachingEnd ? undefined : result.loadMore, + onPrev: result.isReachingStart ? undefined : result.loadMorePrev, }; } diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx index e34120e34..f8b2e3113 100644 --- a/packages/demobank-ui/src/components/Transactions/views.tsx +++ b/packages/demobank-ui/src/components/Transactions/views.tsx @@ -30,7 +30,7 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode { ); } -export function ReadyView({ transactions }: State.Ready): VNode { +export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode { const { i18n } = useTranslationContext(); if (!transactions.length) return
const txByDate = transactions.reduce((prev, cur) => { @@ -50,7 +50,7 @@ export function ReadyView({ transactions }: State.Ready): VNode {

Latest transactions

-
+
@@ -89,9 +89,30 @@ export function ReadyView({ transactions }: State.Ready): VNode { ) })} + })} +
+ +
); diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts index 0c083a939..8d729c1f7 100644 --- a/packages/demobank-ui/src/declaration.d.ts +++ b/packages/demobank-ui/src/declaration.d.ts @@ -194,28 +194,25 @@ namespace SandboxBackend { } interface BankAccountTransactionInfo { - creditorIban: string; - creditorBic: string; // Optional - creditorName: string; + creditor_payto_uri: string; + debtor_payto_uri: string; - debtorIban: string; - debtorBic: string; - debtorName: string; + amount: Amount; + direction: "debit" | "credit"; - amount: number; - currency: string; subject: string; // Transaction unique ID. Matches // $transaction_id from the URI. - uid: string; - direction: "DBIT" | "CRDT"; - date: string; // milliseconds since the Unix epoch + row_id: number; + date: number; + // date: Timestamp; } + interface CreateBankAccountTransactionCreate { // Address in the Payto format of the wire transfer receiver. // It needs at least the 'message' query string parameter. - paytoUri: string; + payto_uri: string; // Transaction amount (in the $currency:x.y format), optional. // However, when not given, its value must occupy the 'amount' @@ -326,32 +323,32 @@ namespace SandboxBackend { interface AccountMinimalData { // Username username: string; - + // Legal name of the account owner. name: string; - + // current balance of the account balance: Balance; - + // Number indicating the max debit allowed for the requesting user. debit_threshold: Amount; } - + interface AccountData { // Legal name of the account owner. name: string; - + // Available balance on the account. balance: Balance; - + // payto://-URI of the account. payto_uri: string; - + // Number indicating the max debit allowed for the requesting user. debit_threshold: Amount; - + contact_data?: ChallengeContactData; - + // 'payto' address pointing the bank account // where to send cashouts. This field is optional // because not all the accounts are required to participate @@ -360,7 +357,7 @@ namespace SandboxBackend { // be done via the access API. cashout_payto_uri?: string; } - + } namespace Circuit { diff --git a/packages/demobank-ui/src/hooks/access.ts b/packages/demobank-ui/src/hooks/access.ts index 2f6848af8..20fd64bfa 100644 --- a/packages/demobank-ui/src/hooks/access.ts +++ b/packages/demobank-ui/src/hooks/access.ts @@ -255,7 +255,7 @@ export function useTransactionDetails( } interface PaginationFilter { - page: number; + // page: number; } export function usePublicAccounts( @@ -275,7 +275,7 @@ export function usePublicAccounts( } = useSWR< HttpResponseOk, RequestError - >([`public-accounts`, args?.page, PAGE_SIZE], paginatedFetcher); + >([`public-accounts`, page, PAGE_SIZE], paginatedFetcher); const [lastAfter, setLastAfter] = useState< HttpResponse< @@ -334,7 +334,7 @@ export function useTransactions( > { const { paginatedFetcher } = useAuthenticatedBackend(); - const [page, setPage] = useState(1); + const [start, setStart] = useState(); const { data: afterData, @@ -344,7 +344,7 @@ export function useTransactions( HttpResponseOk, RequestError >( - [`accounts/${account}/transactions`, args?.page, PAGE_SIZE], + [`accounts/${account}/transactions`, start, PAGE_SIZE], paginatedFetcher, { refreshInterval: 0, refreshWhenHidden: false, @@ -374,19 +374,24 @@ export function useTransactions( // if the query returns less that we ask, then we have reach the end or beginning const isReachingEnd = afterData && afterData.data.transactions.length < PAGE_SIZE; - const isReachingStart = false; + const isReachingStart = start == undefined; const pagination = { isReachingEnd, isReachingStart, loadMore: () => { if (!afterData || isReachingEnd) return; - if (afterData.data.transactions.length < MAX_RESULT_SIZE) { - setPage(page + 1); - } + // if (afterData.data.transactions.length < MAX_RESULT_SIZE) { + // console.log("load more", page) + const l = afterData.data.transactions[afterData.data.transactions.length-1] + setStart(String(l.row_id)); + // } }, loadMorePrev: () => { - null; + if (!afterData || isReachingStart) return; + // if (afterData.data.transactions.length < MAX_RESULT_SIZE) { + setStart(undefined) + // } }, }; diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts index e6a3a1c0c..3d5bfa360 100644 --- a/packages/demobank-ui/src/hooks/backend.ts +++ b/packages/demobank-ui/src/hooks/backend.ts @@ -159,7 +159,7 @@ interface useBackendType { fetcher: (endpoint: string) => Promise>; multiFetcher: (endpoint: string[][]) => Promise[]>; paginatedFetcher: ( - args: [string, number, number], + args: [string, string | undefined, number], ) => Promise>; sandboxAccountsFetcher: ( args: [string, number, number, string], @@ -188,13 +188,15 @@ export function usePublicBackend(): useBackendType { [baseUrl], ); const paginatedFetcher = useCallback( - function fetcherImpl([endpoint, page, size]: [ + function fetcherImpl([endpoint, start, size]: [ string, - number, + string | undefined, number, ]): Promise> { + const delta = -1 * size //descending order + const params = start ? { delta, start } : {delta} return requestHandler(baseUrl, endpoint, { - params: { page: page || 1, size }, + params, }); }, [baseUrl], @@ -280,14 +282,16 @@ export function useAuthenticatedBackend(): useBackendType { [baseUrl, creds], ); const paginatedFetcher = useCallback( - function fetcherImpl([endpoint, page = 1, size]: [ + function fetcherImpl([endpoint, start, size]: [ string, - number, + string | undefined, number, ]): Promise> { + const delta = -1 * size //descending order + const params = start ? { delta, start } : {delta} return requestHandler(baseUrl, endpoint, { token: creds, - params: { delta: size, start: size * page }, + params, }); }, [baseUrl, creds], diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts index a23d3b569..c212e7484 100644 --- a/packages/demobank-ui/src/pages/AccountPage/state.ts +++ b/packages/demobank-ui/src/pages/AccountPage/state.ts @@ -49,7 +49,7 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe }; } if (result.status === HttpStatusCode.Unauthorized) { - notifyError(i18n.str`Require login`, undefined); + notifyError(i18n.str`Authorization denied`, i18n.str`Maybe the session has expired, login again.`); return { status: "error-user-not-found", error: result, diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index 39ca09f1b..c4f872679 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -15,9 +15,9 @@ */ import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; -import { useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { notifyError, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; -import { StateUpdater, useEffect, useState } from "preact/hooks"; +import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks"; import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js"; import { useBackendContext } from "../context/backend.js"; import { useBusinessAccountDetails } from "../hooks/circuit.js"; @@ -50,6 +50,15 @@ export function BankFrame({ const [settings, updateSettings] = useSettings(); const [open, setOpen] = useState(false) + const [error, resetError] = useErrorBoundary(); + + useEffect(() => { + if (error) { + notifyError(i18n.str`Internal error, please report.`, (error instanceof Error ? error.message : String(error)) as TranslatedString) + resetError() + } + }, [error]) + const demo_sites = []; for (const i in bankUiSettings.demoSites) demo_sites.push( @@ -355,7 +364,9 @@ function StatusBanner(): VNode {

{n.message.title}

-

+

+
+

+ + +

:
-
+ } {bankUiSettings.allowRegistrations && onRegister && diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index 30fcbdff7..d160a88b3 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -317,8 +317,9 @@ export function WithdrawalConfirmationQuestion({
Amount
-
To be added
- {/* Amounts.stringifyValue(details.amount) */} +
+ {Amounts.stringifyValue(details.amount)} +
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx b/packages/demobank-ui/src/pages/admin/Account.tsx index 8ea59cdcb..521bc8eb3 100644 --- a/packages/demobank-ui/src/pages/admin/Account.tsx +++ b/packages/demobank-ui/src/pages/admin/Account.tsx @@ -13,7 +13,7 @@ export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode const result = useAccountDetails(account); if (!result.ok) { - return handleNotOkResult(i18n, onRegister)(result); + return handleNotOkResult(i18n)(result); } const { data } = result; diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx index eb5765533..f99b320a4 100644 --- a/packages/demobank-ui/src/pages/admin/AccountList.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx @@ -8,17 +8,16 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; interface Props { onAction: (type: AccountAction, account: string) => void; account: string | undefined; - onRegister: () => void; onCreateAccount: () => void; } -export function AccountList({ account, onAction, onCreateAccount, onRegister }: Props): VNode { +export function AccountList({ account, onAction, onCreateAccount }: Props): VNode { const result = useBusinessAccounts({ account }); const { i18n } = useTranslationContext(); if (result.loading) return
; if (!result.ok) { - return handleNotOkResult(i18n, onRegister)(result); + return handleNotOkResult(i18n)(result); } const { customers } = result.data; diff --git a/packages/demobank-ui/src/pages/admin/Home.tsx b/packages/demobank-ui/src/pages/admin/Home.tsx index 5033b7fdc..725820ada 100644 --- a/packages/demobank-ui/src/pages/admin/Home.tsx +++ b/packages/demobank-ui/src/pages/admin/Home.tsx @@ -37,7 +37,7 @@ export function AdminHome({ onRegister }: Props): VNode { switch (action.type) { case "show-cashouts-details": return { setAction(undefined); }} @@ -73,7 +73,7 @@ export function AdminHome({ onRegister }: Props): VNode { ) case "update-password": return { notifyInfo(i18n.str`Password changed`); setAction(undefined); @@ -84,7 +84,7 @@ export function AdminHome({ onRegister }: Props): VNode { /> case "remove-account": return { notifyInfo(i18n.str`Account removed`); setAction(undefined); @@ -95,7 +95,7 @@ export function AdminHome({ onRegister }: Props): VNode { /> case "show-details": return { setAction({ type: "update-password", diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx index 628ae328d..c9d798082 100644 --- a/packages/demobank-ui/src/pages/business/Home.tsx +++ b/packages/demobank-ui/src/pages/business/Home.tsx @@ -75,7 +75,7 @@ export function BusinessAccount({ return ( { setNewcashout(false); }} @@ -93,7 +93,7 @@ export function BusinessAccount({ return ( { setShowCashoutDetails(undefined); }} @@ -104,7 +104,7 @@ export function BusinessAccount({ return ( { notifyInfo(i18n.str`Password changed`); setUpdatePassword(false); @@ -119,7 +119,7 @@ export function BusinessAccount({
{ notifyInfo(i18n.str`Account updated`); }} -- cgit v1.2.3 From ea0738ccd585445d7e2080d9009025dde9cf22c5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 25 Sep 2023 14:49:47 -0300 Subject: better /config error --- .../demobank-ui/src/components/ErrorLoading.tsx | 3 ++ .../src/components/Transactions/state.ts | 2 +- packages/demobank-ui/src/components/app.tsx | 18 +++++++--- packages/demobank-ui/src/declaration.d.ts | 3 +- packages/demobank-ui/src/hooks/config.ts | 40 +++++++++------------- .../demobank-ui/src/pages/AccountPage/state.ts | 4 +-- packages/demobank-ui/src/pages/BankFrame.tsx | 14 ++++++-- .../demobank-ui/src/pages/WithdrawalQRCode.tsx | 1 - packages/web-util/src/hooks/index.ts | 1 + packages/web-util/src/hooks/useNotifications.ts | 11 ++++++ 10 files changed, 60 insertions(+), 37 deletions(-) (limited to 'packages/demobank-ui/src/declaration.d.ts') diff --git a/packages/demobank-ui/src/components/ErrorLoading.tsx b/packages/demobank-ui/src/components/ErrorLoading.tsx index a4faa4d5d..f83b61234 100644 --- a/packages/demobank-ui/src/components/ErrorLoading.tsx +++ b/packages/demobank-ui/src/components/ErrorLoading.tsx @@ -32,6 +32,9 @@ export function ErrorLoading({ error }: { error: HttpError{error.message}

+
+

Got status "{error.info.status}" on {error.info.url}

+
); diff --git a/packages/demobank-ui/src/components/Transactions/state.ts b/packages/demobank-ui/src/components/Transactions/state.ts index 30c48aa45..4b62b005e 100644 --- a/packages/demobank-ui/src/components/Transactions/state.ts +++ b/packages/demobank-ui/src/components/Transactions/state.ts @@ -44,7 +44,7 @@ export function useComponentState({ account }: Props): State { cp.targetType === "bitcoin" ? `${cp.targetPath.substring(0, 6)}...` : undefined) ?? "unkown"; - const when = AbsoluteTime.fromMilliseconds(tx.date / 1000); + const when = AbsoluteTime.fromProtocolTimestamp(tx.date); const amount = Amounts.parse(tx.amount); const subject = tx.subject; return { diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx index ebda31035..a587c6f1e 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/components/app.tsx @@ -29,6 +29,8 @@ import { useEffect, useState } from "preact/hooks"; import { Loading } from "./Loading.js"; import { getInitialBackendBaseURL } from "../hooks/backend.js"; import { BANK_INTEGRATION_PROTOCOL_VERSION, useConfigState } from "../hooks/config.js"; +import { ErrorLoading } from "./ErrorLoading.js"; +import { BankFrame } from "../pages/BankFrame.js"; const WITH_LOCAL_STORAGE_CACHE = false; /** @@ -76,12 +78,18 @@ function VersionCheck({ children }: { children: ComponentChildren }): VNode { if (checked === undefined) { return } - if (checked === false) { - return
- the bank backend is not supported. supported version "{BANK_INTEGRATION_PROTOCOL_VERSION}" -
+ if (typeof checked === "string") { + return + the bank backend is not supported. supported version "{BANK_INTEGRATION_PROTOCOL_VERSION}", server version "{checked}" + } - return {children} + if (checked === true) { + return {children} + } + + return + + } function localStorageProvider(): Map { diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts index 8d729c1f7..d3d9e02ef 100644 --- a/packages/demobank-ui/src/declaration.d.ts +++ b/packages/demobank-ui/src/declaration.d.ts @@ -205,8 +205,7 @@ namespace SandboxBackend { // Transaction unique ID. Matches // $transaction_id from the URI. row_id: number; - date: number; - // date: Timestamp; + date: Timestamp; } interface CreateBankAccountTransactionCreate { diff --git a/packages/demobank-ui/src/hooks/config.ts b/packages/demobank-ui/src/hooks/config.ts index 4b22e8ad3..4cf677d35 100644 --- a/packages/demobank-ui/src/hooks/config.ts +++ b/packages/demobank-ui/src/hooks/config.ts @@ -1,5 +1,5 @@ import { LibtoolVersion } from "@gnu-taler/taler-util"; -import { useApiContext } from "@gnu-taler/web-util/browser"; +import { ErrorType, HttpError, HttpResponseServerError, RequestError, useApiContext } from "@gnu-taler/web-util/browser"; import { useEffect, useState } from "preact/hooks"; import { getInitialBackendBaseURL } from "./backend.js"; @@ -12,38 +12,32 @@ export const BANK_INTEGRATION_PROTOCOL_VERSION = "0:0:0"; async function getConfigState( request: ReturnType["request"], -): Promise { - try { - const url = getInitialBackendBaseURL(); - const result = await request( - url, - `config`, - ); - return result.data; - } catch (error) { - return undefined; - } +): Promise { + const url = getInitialBackendBaseURL(); + const result = await request(url, `config`); + return result.data; } -export function useConfigState(): boolean | undefined { - const [checked, setChecked] = useState() +export function useConfigState(): undefined | true | string | HttpError { + const [checked, setChecked] = useState>() const { request } = useApiContext(); useEffect(() => { - getConfigState(request) - .then((result) => { - if (!result) { - setChecked(false) + .then((s) => { + const r = LibtoolVersion.compare(BANK_INTEGRATION_PROTOCOL_VERSION, s.version) + if (r?.compatible) { + setChecked(true); } else { - const r = LibtoolVersion.compare(BANK_INTEGRATION_PROTOCOL_VERSION, result.version) - setChecked(r?.compatible); + setChecked(s.version) } }) - .catch((error) => { - setChecked(false); + .catch((error: unknown) => { + if (error instanceof RequestError) { + setChecked(error.cause); + } }); - }); + }, []); return checked; } diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts index c212e7484..ca7e1d447 100644 --- a/packages/demobank-ui/src/pages/AccountPage/state.ts +++ b/packages/demobank-ui/src/pages/AccountPage/state.ts @@ -75,9 +75,7 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe }; } - // FIXME: balance - const balanceIsDebit = true; - // data.balance.credit_debit_indicator == "debit"; + const balanceIsDebit = data.balance.credit_debit_indicator == "debit"; const limit = balanceIsDebit ? Amounts.sub(debitThreshold, balance).amount : Amounts.add(balance, debitThreshold).amount; diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index c4f872679..5c43d2c3e 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -15,7 +15,7 @@ */ import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; -import { notifyError, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks"; import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js"; @@ -54,7 +54,12 @@ export function BankFrame({ useEffect(() => { if (error) { - notifyError(i18n.str`Internal error, please report.`, (error instanceof Error ? error.message : String(error)) as TranslatedString) + const desc = (error instanceof Error ? error.stack : String(error)) as TranslatedString + if (error instanceof Error) { + notifyException(i18n.str`Internal error, please report.`, error) + } else { + notifyError(i18n.str`Internal error, please report.`, String(error) as TranslatedString) + } resetError() } }, [error]) @@ -386,6 +391,11 @@ function StatusBanner(): VNode { {n.message.description} } + {n.message.debug && +
+ {n.message.debug} +
+ } case "info": return
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx index 25c571e28..8f4e175f6 100644 --- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx @@ -45,7 +45,6 @@ export function WithdrawalQRCode({ withdrawUri, onClose, }: Props): VNode { - const [settings, updateSettings] = useSettings(); const { i18n } = useTranslationContext(); const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId); diff --git a/packages/web-util/src/hooks/index.ts b/packages/web-util/src/hooks/index.ts index c29de9023..cc3267dbd 100644 --- a/packages/web-util/src/hooks/index.ts +++ b/packages/web-util/src/hooks/index.ts @@ -4,6 +4,7 @@ export { useMemoryStorage } from "./useMemoryStorage.js"; export { useNotifications, notifyError, + notifyException, notifyInfo, notify, ErrorNotification, diff --git a/packages/web-util/src/hooks/useNotifications.ts b/packages/web-util/src/hooks/useNotifications.ts index 2f9df24f9..792095b06 100644 --- a/packages/web-util/src/hooks/useNotifications.ts +++ b/packages/web-util/src/hooks/useNotifications.ts @@ -36,6 +36,17 @@ export function notifyError( debug, }); } +export function notifyException( + title: TranslatedString, + ex: Error, +) { + notify({ + type: "error" as const, + title, + description: ex.message as TranslatedString, + debug: ex.stack, + }); +} export function notifyInfo(title: TranslatedString) { notify({ type: "info" as const, -- cgit v1.2.3 From 1e4f21cc76345f3881ea8e5ea0e94d27d26da609 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 26 Sep 2023 15:18:43 -0300 Subject: lang selector and fix logout --- packages/demobank-ui/package.json | 2 +- .../demobank-ui/src/components/LangSelector.tsx | 78 ++++++------ packages/demobank-ui/src/components/app.tsx | 6 +- packages/demobank-ui/src/context/backend.ts | 4 + packages/demobank-ui/src/declaration.d.ts | 4 +- packages/demobank-ui/src/hooks/backend.ts | 44 +++++-- packages/demobank-ui/src/hooks/circuit.ts | 4 +- packages/demobank-ui/src/hooks/config.ts | 20 +-- .../demobank-ui/src/hooks/useCredentialsChecker.ts | 2 +- packages/demobank-ui/src/pages/BankFrame.tsx | 128 ++++++------------- packages/demobank-ui/src/pages/LoginForm.tsx | 27 ++-- .../demobank-ui/src/pages/OperationState/views.tsx | 17 +-- .../demobank-ui/src/pages/RegistrationPage.tsx | 138 +++++++++++++++------ packages/demobank-ui/src/pages/admin/Account.tsx | 2 +- packages/demobank-ui/src/pages/business/Home.tsx | 2 - packages/demobank-ui/src/settings.ts | 14 +-- 16 files changed, 272 insertions(+), 220 deletions(-) (limited to 'packages/demobank-ui/src/declaration.d.ts') diff --git a/packages/demobank-ui/package.json b/packages/demobank-ui/package.json index 744cb4180..b430ebc24 100644 --- a/packages/demobank-ui/package.json +++ b/packages/demobank-ui/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@gnu-taler/demobank-ui", - "version": "0.1.0", + "version": "0.9.3-dev.17", "license": "AGPL-3.0-OR-LATER", "type": "module", "scripts": { diff --git a/packages/demobank-ui/src/components/LangSelector.tsx b/packages/demobank-ui/src/components/LangSelector.tsx index ca4411682..c1d0f64ef 100644 --- a/packages/demobank-ui/src/components/LangSelector.tsx +++ b/packages/demobank-ui/src/components/LangSelector.tsx @@ -42,11 +42,11 @@ function getLangName(s: keyof LangsNames | string): string { return String(s); } -// FIXME: explain "like py". -export function LangSelectorLikePy(): VNode { +export function LangSelector(): VNode { const [updatingLang, setUpdatingLang] = useState(false); const { lang, changeLanguage } = useTranslationContext(); const [hidden, setHidden] = useState(true); + useEffect(() => { function bodyKeyPress(event: KeyboardEvent) { if (event.code === "Escape") setHidden(true); @@ -62,51 +62,49 @@ export function LangSelectorLikePy(): VNode { }; }, []); return ( - - { - ev.preventDefault(); - setHidden((h) => !h); - ev.stopPropagation(); - }} - > - {getLangName(lang)} - -
-
- - +
); } diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx index a587c6f1e..f15a9ee6a 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/components/app.tsx @@ -78,17 +78,17 @@ function VersionCheck({ children }: { children: ComponentChildren }): VNode { if (checked === undefined) { return } - if (typeof checked === "string") { + if (checked.type === "wrong") { return the bank backend is not supported. supported version "{BANK_INTEGRATION_PROTOCOL_VERSION}", server version "{checked}" } - if (checked === true) { + if (checked.type === "ok") { return {children} } return - + } diff --git a/packages/demobank-ui/src/context/backend.ts b/packages/demobank-ui/src/context/backend.ts index b311ddbb0..eae187c6d 100644 --- a/packages/demobank-ui/src/context/backend.ts +++ b/packages/demobank-ui/src/context/backend.ts @@ -34,6 +34,9 @@ const initial: Type = { logOut() { null; }, + expired() { + null; + }, logIn(info) { null; }, @@ -65,6 +68,7 @@ export const BackendStateProviderTesting = ({ const value: BackendStateHandler = { state, logIn: () => {}, + expired: () => {}, logOut: () => {}, }; diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts index d3d9e02ef..bd7edf033 100644 --- a/packages/demobank-ui/src/declaration.d.ts +++ b/packages/demobank-ui/src/declaration.d.ts @@ -74,7 +74,9 @@ type HashCode = string; type EddsaPublicKey = string; type EddsaSignature = string; type WireTransferIdentifierRawP = string; -type RelativeTime = Duration; +type RelativeTime = { + d_us: number | "forever" +}; type ImageDataUrl = string; interface WithId { diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts index 3d5bfa360..889618646 100644 --- a/packages/demobank-ui/src/hooks/backend.ts +++ b/packages/demobank-ui/src/hooks/backend.ts @@ -46,16 +46,18 @@ import { AccessToken } from "./useCredentialsChecker.js"; * Has the information to reach and * authenticate at the bank's backend. */ -export type BackendState = LoggedIn | LoggedOut; +export type BackendState = LoggedIn | LoggedOut | Expired; -export interface BackendCredentials { +interface LoggedIn { + status: "loggedIn"; + isUserAdministrator: boolean; username: string; token: AccessToken; } - -interface LoggedIn extends BackendCredentials { - status: "loggedIn"; +interface Expired { + status: "expired"; isUserAdministrator: boolean; + username: string; } interface LoggedOut { status: "loggedOut"; @@ -69,6 +71,13 @@ export const codecForBackendStateLoggedIn = (): Codec => .property("isUserAdministrator", codecForBoolean()) .build("BackendState.LoggedIn"); +export const codecForBackendStateExpired = (): Codec => + buildCodecForObject() + .property("status", codecForConstString("expired")) + .property("username", codecForString()) + .property("isUserAdministrator", codecForBoolean()) + .build("BackendState.Expired"); + export const codecForBackendStateLoggedOut = (): Codec => buildCodecForObject() .property("status", codecForConstString("loggedOut")) @@ -79,6 +88,7 @@ export const codecForBackendState = (): Codec => .discriminateOn("status") .alternative("loggedIn", codecForBackendStateLoggedIn()) .alternative("loggedOut", codecForBackendStateLoggedOut()) + .alternative("expired", codecForBackendStateExpired()) .build("BackendState"); export function getInitialBackendBaseURL(): string { @@ -94,8 +104,9 @@ export function getInitialBackendBaseURL(): string { "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'", ); result = window.origin + } else { + result = bankUiSettings.backendBaseURL; } - result = bankUiSettings.backendBaseURL; } else { // testing/development path result = overrideUrl @@ -115,7 +126,8 @@ export const defaultState: BackendState = { export interface BackendStateHandler { state: BackendState; logOut(): void; - logIn(info: BackendCredentials): void; + expired(): void; + logIn(info: {username: string, token: AccessToken}): void; } const BACKEND_STATE_KEY = buildStorageKey( @@ -133,12 +145,22 @@ export function useBackendState(): BackendStateHandler { BACKEND_STATE_KEY, defaultState, ); + const mutateAll = useMatchMutate(); return { state, logOut() { update(defaultState); }, + expired() { + if (state.status === "loggedOut") return; + const nextState: BackendState = { + status: "expired", + username: state.username, + isUserAdministrator: state.username === "admin", + }; + update(nextState); + }, logIn(info) { //admin is defined by the username const nextState: BackendState = { @@ -147,6 +169,7 @@ export function useBackendState(): BackendStateHandler { isUserAdministrator: info.username === "admin", }; update(nextState); + mutateAll(/.*/) }, }; } @@ -194,7 +217,7 @@ export function usePublicBackend(): useBackendType { number, ]): Promise> { const delta = -1 * size //descending order - const params = start ? { delta, start } : {delta} + const params = start ? { delta, start } : { delta } return requestHandler(baseUrl, endpoint, { params, }); @@ -262,7 +285,8 @@ export function useAuthenticatedBackend(): useBackendType { const { state } = useBackendContext(); const { request: requestHandler } = useApiContext(); - const creds = state.status === "loggedIn" ? state.token : undefined; + // FIXME: libeufin returns 400 insteand of 401 if there is no auth token + const creds = state.status === "loggedIn" ? state.token : "secret-token:a"; const baseUrl = getInitialBackendBaseURL(); const request = useCallback( @@ -288,7 +312,7 @@ export function useAuthenticatedBackend(): useBackendType { number, ]): Promise> { const delta = -1 * size //descending order - const params = start ? { delta, start } : {delta} + const params = start ? { delta, start } : { delta } return requestHandler(baseUrl, endpoint, { token: creds, params, diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts index 4ef80b055..82caafdf2 100644 --- a/packages/demobank-ui/src/hooks/circuit.ts +++ b/packages/demobank-ui/src/hooks/circuit.ts @@ -268,7 +268,7 @@ export function useEstimator(): CashoutEstimators { const { state } = useBackendContext(); const { request } = useApiContext(); const creds = - state.status === "loggedOut" + state.status !== "loggedIn" ? undefined : state.token; return { @@ -340,7 +340,7 @@ export function useBusinessAccountFlag(): boolean | undefined { const { state } = useBackendContext(); const { request } = useApiContext(); const creds = - state.status === "loggedOut" + state.status !== "loggedIn" ? undefined : {user: state.username, token: state.token}; diff --git a/packages/demobank-ui/src/hooks/config.ts b/packages/demobank-ui/src/hooks/config.ts index 4cf677d35..bb5134510 100644 --- a/packages/demobank-ui/src/hooks/config.ts +++ b/packages/demobank-ui/src/hooks/config.ts @@ -18,23 +18,29 @@ async function getConfigState( return result.data; } -export function useConfigState(): undefined | true | string | HttpError { - const [checked, setChecked] = useState>() +type Result = undefined + | { type: "ok", result: SandboxBackend.Config } + | { type: "wrong", result: SandboxBackend.Config } + | { type: "error", result: HttpError } + +export function useConfigState(): Result { + const [checked, setChecked] = useState() const { request } = useApiContext(); useEffect(() => { getConfigState(request) - .then((s) => { - const r = LibtoolVersion.compare(BANK_INTEGRATION_PROTOCOL_VERSION, s.version) + .then((result) => { + const r = LibtoolVersion.compare(BANK_INTEGRATION_PROTOCOL_VERSION, result.version) if (r?.compatible) { - setChecked(true); + setChecked({ type: "ok",result }); } else { - setChecked(s.version) + setChecked({ type: "wrong",result }) } }) .catch((error: unknown) => { if (error instanceof RequestError) { - setChecked(error.cause); + const result = error.cause + setChecked({ type:"error", result }); } }); }, []); diff --git a/packages/demobank-ui/src/hooks/useCredentialsChecker.ts b/packages/demobank-ui/src/hooks/useCredentialsChecker.ts index f66a4a7c6..02f4544db 100644 --- a/packages/demobank-ui/src/hooks/useCredentialsChecker.ts +++ b/packages/demobank-ui/src/hooks/useCredentialsChecker.ts @@ -15,7 +15,7 @@ export function useCredentialsChecker() { scope: "readwrite" as "write", //FIX: different than merchant duration: { // d_us: "forever" //FIX: should return shortest - d_us: 1000 * 60 * 60 * 23 + d_us: 1000 * 1000 * 5 //60 * 60 * 24 * 7 }, refreshable: true, } diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index 5c43d2c3e..3d09fcec7 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -18,7 +18,7 @@ import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringi import { notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks"; -import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js"; +import { LangSelector } from "../components/LangSelector.js"; import { useBackendContext } from "../context/backend.js"; import { useBusinessAccountDetails } from "../hooks/circuit.js"; import { bankUiSettings } from "../settings.js"; @@ -65,12 +65,14 @@ export function BankFrame({ }, [error]) const demo_sites = []; - for (const i in bankUiSettings.demoSites) - demo_sites.push( - - {bankUiSettings.demoSites[i][0]} - , - ); + if (bankUiSettings.demoSites) { + for (const i in bankUiSettings.demoSites) + demo_sites.push( + + {bankUiSettings.demoSites[i][0]} + , + ); + } return (
@@ -88,14 +90,16 @@ export function BankFrame({ />
-
@@ -166,26 +170,29 @@ export function BankFrame({ - Log out - {/* */} + Log out -
  • -
    - Sites -
    -
      - {bankUiSettings.demoSites.map(([name, url]) => { - return
    • - - > - {name} - -
    • - })} -
    +
  • +
  • - + {bankUiSettings.demoSites && +
  • +
    + Sites +
    +
      + {bankUiSettings.demoSites.map(([name, url]) => { + return
    • + + > + {name} + +
    • + })} +
    +
  • + }
    • @@ -291,63 +298,6 @@ export function BankFrame({
  • - // - //
    - // - //
    - // {maybeDemoContent( - //

    - // {IS_PUBLIC_ACCOUNT_ENABLED ? ( - // - // This part of the demo shows how a bank that supports Taler - // directly would work. In addition to using your own bank - // account, you can also see the transaction history of some{" "} - // Public Accounts. - // - // ) : ( - // - // This part of the demo shows how a bank that supports Taler - // directly would work. - // - // )} - //

    , - // )} - //
    - //
    - // - //
    - // - // {children} - //
    - //
    ); } @@ -393,7 +343,7 @@ function StatusBanner(): VNode { } {n.message.debug &&
    - {n.message.debug} + {n.message.debug}
    }
    diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index 786399d55..14d261622 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -30,14 +30,32 @@ import { undefinedIfEmpty } from "../utils.js"; */ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode { const backend = useBackendContext(); - const currentUser = backend.state.status === "loggedIn" ? backend.state.username : undefined + const currentUser = backend.state.status !== "loggedOut" ? backend.state.username : undefined const [username, setUsername] = useState(currentUser); const [password, setPassword] = useState(); const { i18n } = useTranslationContext(); const { requestNewLoginToken, refreshLoginToken } = useCredentialsChecker(); + + /** + * Register form may be shown in the initialization step. + * If this is an error when usgin the app the registration + * callback is not set + */ + const isSessionExpired = !onRegister + + // useEffect(() => { + // if (backend.state.status === "loggedIn") { + // backend.expired() + // } + // },[]) const ref = useRef(null); useEffect(function focusInput() { + //FIXME: show invalidate session and allow relogin + if (isSessionExpired) { + localStorage.removeItem("backend-state"); + window.location.reload() + } ref.current?.focus(); }, []); const [busy, setBusy] = useState>() @@ -124,13 +142,6 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode { setBusy(undefined) } - /** - * Register form may be shown in the initialization step. - * If this is an error when usgin the app the registration - * callback is not set - */ - const isSessionExpired = !onRegister - return (
    diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx index 681a3b94d..93b3694d7 100644 --- a/packages/demobank-ui/src/pages/OperationState/views.tsx +++ b/packages/demobank-ui/src/pages/OperationState/views.tsx @@ -14,20 +14,15 @@ GNU Taler; see the file COPYING. If not, see */ -import { Amounts, stringifyPaytoUri, stringifyWithdrawUri } from "@gnu-taler/taler-util"; +import { stringifyWithdrawUri } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { Fragment, h, VNode } from "preact"; -import { Transactions } from "../../components/Transactions/index.js"; -import { PaymentOptions } from "../PaymentOptions.js"; -import { State } from "./index.js"; -import { CopyButton } from "../../components/CopyButton.js"; -import { bankUiSettings } from "../../settings.js"; -import { useBusinessAccountDetails } from "../../hooks/circuit.js"; -import { useSettings } from "../../hooks/settings.js"; +import { Fragment, VNode, h } from "preact"; import { useEffect, useMemo, useState } from "preact/hooks"; -import { undefinedIfEmpty } from "../../utils.js"; -import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js"; import { QR } from "../../components/QR.js"; +import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js"; +import { useSettings } from "../../hooks/settings.js"; +import { undefinedIfEmpty } from "../../utils.js"; +import { State } from "./index.js"; export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) { return ( diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx index a2543f977..2e931a144 100644 --- a/packages/demobank-ui/src/pages/RegistrationPage.tsx +++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx @@ -49,6 +49,8 @@ export function RegistrationPage({ } export const USERNAME_REGEX = /^[a-z][a-zA-Z0-9-]*$/; +export const PHONE_REGEX = /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/; +export const EMAIL_REGEX = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/; /** * Collect and submit registration data. @@ -58,21 +60,33 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on const [username, setUsername] = useState(); const [name, setName] = useState(); const [password, setPassword] = useState(); + const [phone, setPhone] = useState(); + const [email, setEmail] = useState(); const [repeatPassword, setRepeatPassword] = useState(); - const {requestNewLoginToken} = useCredentialsChecker() + const { requestNewLoginToken } = useCredentialsChecker() const { register } = useTestingAPI(); const { i18n } = useTranslationContext(); const errors = undefinedIfEmpty({ - name: !name - ? i18n.str`Missing name` - : undefined, + // name: !name + // ? i18n.str`Missing name` + // : undefined, username: !username ? i18n.str`Missing username` : !USERNAME_REGEX.test(username) ? i18n.str`Use letters and numbers only, and start with a lowercase letter` : undefined, + phone: !phone + ? undefined + : !PHONE_REGEX.test(phone) + ? i18n.str`Use letters and numbers only, and start with a lowercase letter` + : undefined, + email: !email + ? undefined + : !EMAIL_REGEX.test(email) + ? i18n.str`Use letters and numbers only, and start with a lowercase letter` + : undefined, password: !password ? i18n.str`Missing password` : undefined, repeatPassword: !repeatPassword ? i18n.str`Missing password` @@ -82,9 +96,9 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on }); async function doRegistrationStep() { - if (!username || !password || !name) return; + if (!username || !password) return; try { - await register({ name, username, password }); + await register({ name: name ?? "", username, password }); const resp = await requestNewLoginToken(username, password) setUsername(undefined); if (resp.valid) { @@ -167,7 +181,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
    -

    {i18n.str`Sign up!`}

    +

    {i18n.str`Registration form`}

    @@ -178,34 +192,6 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on autoCapitalize="none" autoCorrect="off" > -
    - -
    - { - setName(e.currentTarget.value); - }} - /> - -
    -
    -
    } @@ -430,9 +431,8 @@ function AccountBalance({ account }: { account: string }): VNode { const result = useAccountDetails(account); if (!result.ok) return
    - return
    - {Amounts.currencyOf(result.data.balance.amount)} -  {result.data.balance.credit_debit_indicator === "debit" ? "-" : ""} - {Amounts.stringifyValue(result.data.balance.amount)} -
    + return } diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx index fef272831..f60ba3270 100644 --- a/packages/demobank-ui/src/pages/PaymentOptions.tsx +++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx @@ -30,7 +30,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ const { i18n } = useTranslationContext(); const [settings] = useSettings(); - const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>("wire-transfer"); + const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(); return (
    @@ -47,7 +47,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ setTab("charge-wallet") }} /> -
    💵
    +
    💵
    a Taler wallet @@ -76,7 +76,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ setTab("wire-transfer") }} /> -
    +
    another bank account diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index 785dc4264..b3fd51670 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -39,6 +39,8 @@ import { undefinedIfEmpty, validateIBAN, } from "../utils.js"; +import { useConfigState } from "../hooks/config.js"; +import { useConfigContext } from "../context/config.js"; const logger = new Logger("PaytoWireTransferForm"); @@ -55,7 +57,7 @@ export function PaytoWireTransferForm({ onCancel: (() => void) | undefined; limit: AmountJson; }): VNode { - const [isRawPayto, setIsRawPayto] = useState(true); + const [isRawPayto, setIsRawPayto] = useState(false); // FIXME: remove this const [iban, setIban] = useState("DE4745461198061"); const [subject, setSubject] = useState("ASD"); @@ -285,7 +287,7 @@ export function PaytoWireTransferForm({
    - , ): VNode { + const cfg = useConfigContext() return (
    @@ -409,10 +414,14 @@ export function Amount( autocomplete="off" value={value ?? ""} disabled={!onChange} - onInput={(e): void => { - if (onChange) { - onChange(e.currentTarget.value); + onInput={(e) => { + if (!onChange) return; + const l = e.currentTarget.value.length + const sep_pos = e.currentTarget.value.indexOf(FRAC_SEPARATOR) + if (sep_pos !== -1 && l - sep_pos - 1 > cfg.currency_fraction_limit) { + e.currentTarget.value = e.currentTarget.value.substring(0, sep_pos + cfg.currency_fraction_limit + 1) } + onChange(e.currentTarget.value); }} />
    @@ -421,3 +430,21 @@ export function Amount( ); } +export function RenderAmount({ value, negative }: { value: AmountJson, negative?: boolean }): VNode { + const cfg = useConfigContext() + const str = Amounts.stringifyValue(value) + const sep_pos = str.indexOf(FRAC_SEPARATOR) + if (sep_pos !== -1 && str.length - sep_pos - 1 > cfg.currency_fraction_digits) { + const limit = sep_pos + cfg.currency_fraction_digits + 1 + const normal = str.substring(0, limit) + const small = str.substring(limit) + return + {negative ? "-" : undefined} + {value.currency} {normal} {small} + + } + return + {negative ? "-" : undefined} + {value.currency} {str} + +} \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx index 7357223b7..da299b1c8 100644 --- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx +++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx @@ -34,13 +34,13 @@ import { forwardRef } from "preact/compat"; import { useEffect, useRef, useState } from "preact/hooks"; import { useAccessAPI } from "../hooks/access.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; -import { Amount, doAutoFocus } from "./PaytoWireTransferForm.js"; +import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js"; import { useSettings } from "../hooks/settings.js"; import { OperationState } from "./OperationState/index.js"; import { Attention } from "../components/Attention.js"; const logger = new Logger("WalletWithdrawForm"); -const RefAmount = forwardRef(Amount); +const RefAmount = forwardRef(InputAmount); function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: { diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index 208d4b859..ddcd2492d 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -38,6 +38,7 @@ import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { useAccessAnonAPI } from "../hooks/access.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; import { useSettings } from "../hooks/settings.js"; +import { RenderAmount } from "./PaytoWireTransferForm.js"; const logger = new Logger("WithdrawalConfirmationQuestion"); @@ -318,7 +319,7 @@ export function WithdrawalConfirmationQuestion({
    Amount
    - {Amounts.currencyOf(details.amount)} {Amounts.stringifyValue(details.amount)} +
    diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx index f99b320a4..a6899e679 100644 --- a/packages/demobank-ui/src/pages/admin/AccountList.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx @@ -4,6 +4,7 @@ import { handleNotOkResult } from "../HomePage.js"; import { AccountAction } from "./Home.js"; import { Amounts } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { RenderAmount } from "../PaytoWireTransferForm.js"; interface Props { onAction: (type: AccountAction, account: string) => void; @@ -88,12 +89,7 @@ export function AccountList({ account, onAction, onCreateAccount }: Props): VNod i18n.str`unknown` ) : ( - {balanceIsDebit ? - : null} - {`${Amounts.stringifyValue( - balance, - )}`} -   - {`${balance.currency}`} + )} diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx index 2945cb65a..1a84effcd 100644 --- a/packages/demobank-ui/src/pages/business/Home.tsx +++ b/packages/demobank-ui/src/pages/business/Home.tsx @@ -45,7 +45,7 @@ import { undefinedIfEmpty, } from "../../utils.js"; import { handleNotOkResult } from "../HomePage.js"; -import { Amount } from "../PaytoWireTransferForm.js"; +import { InputAmount } from "../PaytoWireTransferForm.js"; import { ShowAccountDetails } from "../ShowAccountDetails.js"; import { UpdateAccountPassword } from "../UpdateAccountPassword.js"; @@ -342,7 +342,7 @@ function CreateCashout({
    -
    - {i18n.str`Total cost`} -
    -
    - - {i18n.str`Total cashout transfer`} - @@ -88,8 +88,8 @@ export function HistoryItem(props: { tx: Transaction }): VNode { ? i18n.str`Need approval in the Bank` : i18n.str`Exchange is waiting the wire transfer` : tx.withdrawalDetails.type === WithdrawalType.ManualTransfer - ? i18n.str`Exchange is waiting the wire transfer` - : "" //pending but no message + ? i18n.str`Exchange is waiting the wire transfer` + : "" //pending but no message : undefined } /> @@ -267,14 +267,14 @@ function Layout(props: LayoutProps): VNode { style={{ backgroundColor: props.currentState === TransactionMajorState.Pending || - props.currentState === TransactionMajorState.Dialog + props.currentState === TransactionMajorState.Dialog ? "lightcyan" : props.currentState === TransactionMajorState.Failed - ? "#ff000040" - : props.currentState === TransactionMajorState.Aborted || - props.currentState === TransactionMajorState.Aborting - ? "#00000010" - : "inherit", + ? "#ff000040" + : props.currentState === TransactionMajorState.Aborted || + props.currentState === TransactionMajorState.Aborting + ? "#00000010" + : "inherit", alignItems: "center", }} > @@ -353,10 +353,10 @@ function TransactionAmount(props: TransactionAmountProps): VNode { props.currentState !== TransactionMajorState.Done ? "gray" : sign === "+" - ? "darkgreen" - : sign === "-" - ? "darkred" - : undefined, + ? "darkgreen" + : sign === "-" + ? "darkred" + : undefined, }} > -- cgit v1.2.3