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/hooks/circuit.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'packages/demobank-ui/src/hooks/circuit.ts') 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); }) -- 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/hooks/circuit.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); - }} - /> - -
    -
    -
    +
    + +
    +} diff --git a/packages/demobank-ui/src/components/ErrorLoading.tsx b/packages/demobank-ui/src/components/ErrorLoading.tsx index f83b61234..ee62671ce 100644 --- a/packages/demobank-ui/src/components/ErrorLoading.tsx +++ b/packages/demobank-ui/src/components/ErrorLoading.tsx @@ -17,25 +17,13 @@ import { HttpError, useTranslationContext } from "@gnu-taler/web-util/browser"; import { h, VNode } from "preact"; +import { Attention } from "./Attention.js"; +import { TranslatedString } from "@gnu-taler/taler-util"; export function ErrorLoading({ error }: { error: HttpError }): VNode { const { i18n } = useTranslationContext() - return ( -
    -
    -
    - -
    -
    -

    {error.message}

    -
    -
    -
    -

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

    -
    -
    -
    + return ( +

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

    +
    ); } diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx index f8b2e3113..f92c874f3 100644 --- a/packages/demobank-ui/src/components/Transactions/views.tsx +++ b/packages/demobank-ui/src/components/Transactions/views.tsx @@ -19,6 +19,7 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { State } from "./index.js"; import { format, isToday } from "date-fns"; import { Amounts } from "@gnu-taler/taler-util"; +import { useEffect, useRef } from "preact/hooks"; export function LoadingUriView({ error }: State.LoadingUriError): VNode { const { i18n } = useTranslationContext(); @@ -55,9 +56,9 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode {i18n.str`Date`} - {i18n.str`Amount`} - {i18n.str`Counterpart`} - {i18n.str`Subject`} + {i18n.str`Amount`} + {i18n.str`Counterpart`} + {i18n.str`Subject`} @@ -69,22 +70,38 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode {txs.map(item => { + const time = item.when.t_ms === "never" ? "" : format(item.when.t_ms, "HH:mm:ss") + const amount = + {item.negative ? "-" : ""} + {item.amount ? ( + `${Amounts.stringifyValue(item.amount)} ${item.amount.currency + }` + ) : ( + <{i18n.str`invalid value`}> + )} + return ( -
    {item.when.t_ms === "never" - ? "" - : format(item.when.t_ms, "HH:mm:ss")}
    +
    {time}
    +
    +
    Amount
    +
    + {item.negative ? i18n.str`sent` : i18n.str`received`} {item.amount ? ( + `${Amounts.stringifyValue(item.amount)}` + ) : ( + <{i18n.str`invalid value`}> + )}
    +
    Counterpart
    +
    + {item.negative ? i18n.str`to` : i18n.str`from`} {item.counterpart} +
    +
    - {item.negative ? "-" : ""} - {item.amount ? ( - `${Amounts.stringifyValue(item.amount)} ${item.amount.currency - }` - ) : ( - <{i18n.str`invalid value`}> - )} - {item.counterpart} + class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 data-[negative=false]:text-green-600 data-[negative=true]:text-red-600"> + {amount} + + {item.counterpart} {item.subject} ) })} @@ -94,8 +111,8 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode - -
    -
    - + return { + updateSettings("showDemoDescription", false); + }}> + {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. + + )} + } export function ReadyView({ account, limit, goToBusinessAccount, goToConfirmOperation }: State.Ready): VNode<{}> { diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index 15ef8a036..29334cae4 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, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { NotificationMessage, 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 { LangSelector } from "../components/LangSelector.js"; @@ -26,6 +26,7 @@ import { useSettings } from "../hooks/settings.js"; import { CopyButton, CopyIcon } from "../components/CopyButton.js"; import logo from "../assets/logo-2021.svg"; import { useAccountDetails } from "../hooks/access.js"; +import { Attention } from "../components/Attention.js"; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; @@ -108,7 +109,7 @@ export function BankFrame({ setOpen(!open) }}> - Open main menu + Open settings @@ -227,6 +228,22 @@ export function BankFrame({ +
  • +
    + + + Show debug info + + + +
    +
  • @@ -286,10 +303,10 @@ export function BankFrame({ }
    +
    - {children}
    @@ -301,79 +318,46 @@ export function BankFrame({ ); } +function MaybeShowDebugInfo({ info }: { info: any }): VNode { + const [settings] = useSettings() + if (settings.showDebugInfo) { + return
    +    {info}
    +  
    + } + return +} + function StatusBanner(): VNode { const notifs = useNotifications() - return
    { + if (notifs.length === 0) return + return
    { notifs.map(n => { switch (n.message.type) { case "error": - return
    -
    -
    - -
    -
    -

    {n.message.title}

    -
    -
    -

    - -

    -
    -
    + return { + n.remove() + }}> {n.message.description &&
    {n.message.description}
    } + + {/* + show debug info + {n.message.debug &&
    {n.message.debug}
    - } -
    + } */} + case "info": - return
    -
    -
    - -
    -
    -

    {n.message.title}

    - -

    - -

    -
    - -
    -
    + return { + n.remove(); + }} /> } })}
    diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx index d945d80d1..95144f086 100644 --- a/packages/demobank-ui/src/pages/HomePage.tsx +++ b/packages/demobank-ui/src/pages/HomePage.tsx @@ -137,8 +137,8 @@ export function handleNotOkResult( const errorData = result.payload; notify({ type: "error", - title: i18n.str`Could not load due to a client error`, - description: errorData?.error?.description as TranslatedString, + title: i18n.str`Could not load due to a request error`, + description: i18n.str`Request to url "${result.info.url}" returned ${result.info.status}`, debug: JSON.stringify(result), }); break; @@ -174,7 +174,7 @@ export function handleNotOkResult( assertUnreachable(result); } } - route("/") + // route("/") return
    error
    ; } return
    ; diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index 14d261622..3ea94b899 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -23,6 +23,7 @@ import { useBackendContext } from "../context/backend.js"; import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js"; import { bankUiSettings } from "../settings.js"; import { undefinedIfEmpty } from "../utils.js"; +import { doAutoFocus } from "./PaytoWireTransferForm.js"; /** @@ -98,8 +99,8 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode { }); } else { saveError({ - title: i18n.str`Could not load due to a client error`, - // description: cause.payload.error.description, + title: i18n.str`Could not load due to a request error`, + description: i18n.str`Request to url "${cause.info.url}" returned ${cause.info.status}`, debug: JSON.stringify(cause.payload), }); } @@ -159,8 +160,7 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {

    - The wire transfer to the Taler exchange bank's account is completed, now the - exchange will send the requested amount into your GNU Taler wallet. + The wire transfer to the Taler operator has been initiated. You will soon receive the requested amount in your Taler wallet.

    diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx index 49419d0dc..fef272831 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>(); + const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>("wire-transfer"); return (
    @@ -82,7 +82,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ another bank account - Make a wire transfer to an account which you know the address. + Make a wire transfer to an account which you know the bank account number @@ -108,6 +108,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ limit={limit} onSuccess={() => { notifyInfo(i18n.str`Wire transfer created!`); + setTab(undefined) }} onCancel={() => { setTab(undefined) diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index 5f5a6ce3b..785dc4264 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -55,10 +55,11 @@ export function PaytoWireTransferForm({ onCancel: (() => void) | undefined; limit: AmountJson; }): VNode { - const [isRawPayto, setIsRawPayto] = useState(false); - const [iban, setIban] = useState(undefined); - const [subject, setSubject] = useState(undefined); - const [amount, setAmount] = useState(undefined); + const [isRawPayto, setIsRawPayto] = useState(true); + // FIXME: remove this + const [iban, setIban] = useState("DE4745461198061"); + const [subject, setSubject] = useState("ASD"); + const [amount, setAmount] = useState("1.00001"); const [rawPaytoInput, rawPaytoInputSetter] = useState( undefined, @@ -76,17 +77,17 @@ export function PaytoWireTransferForm({ const errorsWire = undefinedIfEmpty({ iban: !iban - ? i18n.str`Missing IBAN` + ? i18n.str`required` : !IBAN_REGEX.test(iban) ? i18n.str`IBAN should have just uppercased letters and numbers` : validateIBAN(iban, i18n), - subject: !subject ? i18n.str`Missing subject` : undefined, + subject: !subject ? i18n.str`required` : undefined, amount: !trimmedAmountStr - ? i18n.str`Missing amount` + ? i18n.str`required` : !parsedAmount - ? i18n.str`Amount is not valid` + ? i18n.str`not valid` : Amounts.isZero(parsedAmount) - ? i18n.str`Should be greater than 0` + ? i18n.str`should be greater than 0` : Amounts.cmp(limit, parsedAmount) === -1 ? i18n.str`balance is not enough` : undefined, @@ -101,14 +102,14 @@ export function PaytoWireTransferForm({ ? i18n.str`required` : !parsed ? i18n.str`does not follow the pattern` - : !parsed.params.amount - ? i18n.str`use the "amount" parameter to specify the amount to be transferred` - : Amounts.parse(parsed.params.amount) === undefined - ? i18n.str`the amount is not valid` - : !parsed.params.message - ? i18n.str`use the "message" parameter to specify a reference text for the transfer` - : !parsed.isKnown || parsed.targetType !== "iban" - ? i18n.str`only "IBAN" target are supported` + : !parsed.isKnown || parsed.targetType !== "iban" + ? i18n.str`only "IBAN" target are supported` + : !parsed.params.amount + ? i18n.str`use the "amount" parameter to specify the amount to be transferred` + : Amounts.parse(parsed.params.amount) === undefined + ? i18n.str`the amount is not valid` + : !parsed.params.message + ? i18n.str`use the "message" parameter to specify a reference text for the transfer` : !IBAN_REGEX.test(parsed.iban) ? i18n.str`IBAN should have just uppercased letters and numbers` : validateIBAN(parsed.iban, i18n), @@ -159,6 +160,9 @@ export function PaytoWireTransferForm({ } return (
    + {/** + * FIXME: Scan a qr code + */}

    {title} @@ -167,6 +171,17 @@ export function PaytoWireTransferForm({
    { @@ -203,105 +228,106 @@ export function PaytoWireTransferForm({ }} >
    -
    - {!isRawPayto ? - - -
    - -
    - { - setIban(e.currentTarget.value); - }} - /> - -
    -

    the receiver of the money

    -
    + {!isRawPayto ? +
    -
    - -
    - { - setSubject(e.currentTarget.value); - }} - /> - -
    -

    some text to identify the transfer

    +
    + +
    + { + setIban(e.currentTarget.value.toUpperCase()); + }} + /> +
    +

    + IBAN of the recipient's account +

    +
    -
    - - { - setAmount(d) +
    + +
    + { + setSubject(e.currentTarget.value); }} /> -

    amount to transfer

    +

    some text to identify the transfer

    +
    - : - -
    - -
    - { - rawPaytoInputSetter(e.currentTarget.value); - }} - /> - -
    -
    +
    + + { + setAmount(d) + }} + /> + +

    amount to transfer

    +
    -
    - } -
    +
    : +
    +
    + +
    +