diff options
author | Sebastian <sebasjm@gmail.com> | 2023-09-26 15:18:43 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-09-26 15:18:43 -0300 |
commit | 1e4f21cc76345f3881ea8e5ea0e94d27d26da609 (patch) | |
tree | 4f921a15b8b146adf479e0068639a49553da4cc1 | |
parent | dcdf8fb6a067b4e40b13f1c0f106758cfba76309 (diff) |
lang selector and fix logout
-rw-r--r-- | packages/demobank-ui/package.json | 2 | ||||
-rw-r--r-- | packages/demobank-ui/src/components/LangSelector.tsx | 78 | ||||
-rw-r--r-- | packages/demobank-ui/src/components/app.tsx | 6 | ||||
-rw-r--r-- | packages/demobank-ui/src/context/backend.ts | 4 | ||||
-rw-r--r-- | packages/demobank-ui/src/declaration.d.ts | 4 | ||||
-rw-r--r-- | packages/demobank-ui/src/hooks/backend.ts | 44 | ||||
-rw-r--r-- | packages/demobank-ui/src/hooks/circuit.ts | 4 | ||||
-rw-r--r-- | packages/demobank-ui/src/hooks/config.ts | 20 | ||||
-rw-r--r-- | packages/demobank-ui/src/hooks/useCredentialsChecker.ts | 2 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/BankFrame.tsx | 128 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/LoginForm.tsx | 27 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/OperationState/views.tsx | 17 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/RegistrationPage.tsx | 138 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/admin/Account.tsx | 2 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/business/Home.tsx | 2 | ||||
-rw-r--r-- | packages/demobank-ui/src/settings.ts | 14 |
16 files changed, 272 insertions, 220 deletions
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 ( - <Fragment> - <a - href="#" - class="pure-button" - name="language" - onClick={(ev) => { - ev.preventDefault(); - setHidden((h) => !h); - ev.stopPropagation(); - }} - > - {getLangName(lang)} - </a> - <div - id="lang" - class={hidden ? "hide" : ""} - style={{ - display: "inline-block", - }} - > - <div style="position: relative; overflow: visible;"> - <div - class="nav" - style="position: absolute; max-height: 60vh; overflow-y: auto; margin-left: -120px; margin-top: 20px" - > + <div> + <div class="relative mt-2"> + <button type="button" class="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" + onClick={() => { + setHidden((h) => !h); + }}> + <span class="flex items-center"> + <img src="https://taler.net/images/languageicon.svg" alt="" class="h-5 w-5 flex-shrink-0 rounded-full" /> + <span class="ml-3 block truncate">{getLangName(lang)}</span> + </span> + <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> + <svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" clip-rule="evenodd" /> + </svg> + </span> + </button> + + {!hidden && + <ul class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" tabIndex={-1} role="listbox" aria-labelledby="listbox-label" aria-activedescendant="listbox-option-3"> {Object.keys(messages) .filter((l) => l !== lang) - .map((l) => ( - <a - key={l} - href="#" - class="navbtn langbtn" - value={l} + .map((lang) => ( + <li class="text-gray-900 hover:bg-indigo-600 hover:text-white cursor-pointer relative select-none py-2 pl-3 pr-9" role="option" onClick={() => { - changeLanguage(l); + changeLanguage(lang); setUpdatingLang(false); + setHidden(true) }} > - {getLangName(l)} - </a> + <span class="font-normal block truncate">{getLangName(lang)}</span> + + <span class="text-indigo-600 absolute inset-y-0 right-0 flex items-center pr-4"> + {/* <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" /> + </svg> */} + </span> + </li> ))} - <br /> - </div> - </div> + + </ul> + } + </div> - </Fragment> + </div> ); } 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 <Loading /> } - if (typeof checked === "string") { + if (checked.type === "wrong") { return <BankFrame> the bank backend is not supported. supported version "{BANK_INTEGRATION_PROTOCOL_VERSION}", server version "{checked}" </BankFrame> } - if (checked === true) { + if (checked.type === "ok") { return <Fragment>{children}</Fragment> } return <BankFrame> - <ErrorLoading error={checked}/> + <ErrorLoading error={checked.result}/> </BankFrame> } 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<LoggedIn> => .property("isUserAdministrator", codecForBoolean()) .build("BackendState.LoggedIn"); +export const codecForBackendStateExpired = (): Codec<Expired> => + buildCodecForObject<Expired>() + .property("status", codecForConstString("expired")) + .property("username", codecForString()) + .property("isUserAdministrator", codecForBoolean()) + .build("BackendState.Expired"); + export const codecForBackendStateLoggedOut = (): Codec<LoggedOut> => buildCodecForObject<LoggedOut>() .property("status", codecForConstString("loggedOut")) @@ -79,6 +88,7 @@ export const codecForBackendState = (): Codec<BackendState> => .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<HttpResponseOk<T>> { const delta = -1 * size //descending order - const params = start ? { delta, start } : {delta} + const params = start ? { delta, start } : { delta } return requestHandler<T>(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<HttpResponseOk<T>> { const delta = -1 * size //descending order - const params = start ? { delta, start } : {delta} + const params = start ? { delta, start } : { delta } return requestHandler<T>(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<SandboxBackend.SandboxError> { - const [checked, setChecked] = useState<true | string | HttpError<SandboxBackend.SandboxError>>() +type Result = undefined + | { type: "ok", result: SandboxBackend.Config } + | { type: "wrong", result: SandboxBackend.Config } + | { type: "error", result: HttpError<SandboxBackend.SandboxError> } + +export function useConfigState(): Result { + const [checked, setChecked] = useState<Result>() 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( - <a href={bankUiSettings.demoSites[i][1]}> - {bankUiSettings.demoSites[i][0]} - </a>, - ); + if (bankUiSettings.demoSites) { + for (const i in bankUiSettings.demoSites) + demo_sites.push( + <a href={bankUiSettings.demoSites[i][1]}> + {bankUiSettings.demoSites[i][0]} + </a>, + ); + } return (<div class="min-h-full flex flex-col m-0" style="min-height: 100vh;"> <div class="bg-indigo-600 pb-32"> @@ -88,14 +90,16 @@ export function BankFrame({ /> </a> </div> - <div class="hidden sm:block lg:ml-10 "> - <div class="flex space-x-4"> - {/* <!-- Current: "bg-indigo-700 text-white", Default: "text-white hover:bg-indigo-500 hover:bg-opacity-75" --> */} - {bankUiSettings.demoSites.map(([name, url]) => { - return <a href={url} class="text-white hover:bg-indigo-500 hover:bg-opacity-75 rounded-md py-2 px-3 text-sm font-medium">{name}</a> - })} + {bankUiSettings.demoSites && + <div class="hidden sm:block lg:ml-10 "> + <div class="flex space-x-4"> + {/* <!-- Current: "bg-indigo-700 text-white", Default: "text-white hover:bg-indigo-500 hover:bg-opacity-75" --> */} + {bankUiSettings.demoSites.map(([name, url]) => { + return <a href={url} class="text-white hover:bg-indigo-500 hover:bg-opacity-75 rounded-md py-2 px-3 text-sm font-medium">{name}</a> + })} + </div> </div> - </div> + } </div> <div class="flex"> @@ -166,26 +170,29 @@ export function BankFrame({ <svg class="h-6 w-6 shrink-0 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" /> </svg> - Log out - {/* <span class="ml-auto w-9 min-w-max whitespace-nowrap rounded-full bg-gray-50 px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-gray-600 ring-1 ring-inset ring-gray-200" aria-hidden="true">5</span> */} + <i18n.Translate>Log out</i18n.Translate> </a> </li> - <li class="sm:hidden"> - <div class="text-xs font-semibold leading-6 text-gray-400"> - <i18n.Translate>Sites</i18n.Translate> - </div> - <ul role="list" class="-mx-2 mt-2 space-y-1"> - {bankUiSettings.demoSites.map(([name, url]) => { - return <li> - <a href={url} target="_blank" rel="noopener noreferrer" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"> - <span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">></span> - <span class="truncate">{name}</span> - </a> - </li> - })} - </ul> + <li> + <LangSelector /> </li> - + {bankUiSettings.demoSites && + <li class="sm:hidden"> + <div class="text-xs font-semibold leading-6 text-gray-400"> + <i18n.Translate>Sites</i18n.Translate> + </div> + <ul role="list" class="-mx-2 mt-2 space-y-1"> + {bankUiSettings.demoSites.map(([name, url]) => { + return <li> + <a href={url} target="_blank" rel="noopener noreferrer" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"> + <span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">></span> + <span class="truncate">{name}</span> + </a> + </li> + })} + </ul> + </li> + } <li> <ul role="list" class="space-y-1"> <li class="mt-2"> @@ -291,63 +298,6 @@ export function BankFrame({ <Footer /> </div > - // <Fragment> - // <header - // class="demobar" - // style="display: flex; flex-direction: row; justify-content: space-between;" - // > - // <a href="#main" class="skip">{i18n.str`Skip to main content`}</a> - // <div style="max-width: 50em; margin-left: 2em; margin-right: 2em;"> - // {maybeDemoContent( - // <p> - // {IS_PUBLIC_ACCOUNT_ENABLED ? ( - // <i18n.Translate> - // 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{" "} - // <a href="/public-accounts">Public Accounts</a>. - // </i18n.Translate> - // ) : ( - // <i18n.Translate> - // This part of the demo shows how a bank that supports Taler - // directly would work. - // </i18n.Translate> - // )} - // </p>, - // )} - // </div> - // </header> - // <div style="display:flex; flex-direction: column;" class="navcontainer"> - // <nav class="demolist"> - // {maybeDemoContent(<Fragment>{demo_sites}</Fragment>)} - // {backend.state.status === "loggedIn" ? ( - // <Fragment> - // {goToBusinessAccount && !backend.state.isUserAdministrator ? ( - // <MaybeBusinessButton - // account={backend.state.username} - // onClick={goToBusinessAccount} - // /> - // ) : undefined} - - // <LangSelector /> - - // <a - // href="#" - // class="pure-button logout-button" - // onClick={() => { - // backend.logOut(); - // updateSettings("currentWithdrawalOperationId", undefined); - // }} - // >{i18n.str`Logout`}</a> - // </Fragment> - // ) : undefined} - // </nav> - // </div> - // <section id="main" class="content"> - // <StatusBanner /> - // {children} - // </section> - // </Fragment> ); } @@ -393,7 +343,7 @@ function StatusBanner(): VNode { } {n.message.debug && <div class="mt-2 text-sm text-red-700 font-mono break-all"> - {n.message.debug} + {n.message.debug} </div> } </div> 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<string | undefined>(currentUser); const [password, setPassword] = useState<string | undefined>(); 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<HTMLInputElement>(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<Record<string, undefined>>() @@ -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 ( <div class="flex min-h-full flex-col justify-center"> 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 <http://www.gnu.org/licenses/> */ -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<string | undefined>(); const [name, setName] = useState<string | undefined>(); const [password, setPassword] = useState<string | undefined>(); + const [phone, setPhone] = useState<string | undefined>(); + const [email, setEmail] = useState<string | undefined>(); const [repeatPassword, setRepeatPassword] = useState<string | undefined>(); - 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 <div class="flex min-h-full flex-col justify-center"> <div class="sm:mx-auto sm:w-full sm:max-w-sm"> - <h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Sign up!`}</h2> + <h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Registration form`}</h2> </div> <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm"> @@ -179,34 +193,6 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on autoCorrect="off" > <div> - <label for="name" class="block text-sm font-medium leading-6 text-gray-900"> - <i18n.Translate>Name</i18n.Translate> - <b style={{ color: "red" }}> *</b> - </label> - <div class="mt-2"> - <input - autoFocus - type="text" - name="name" - id="name" - class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - value={name ?? ""} - enterkeyhint="next" - placeholder="your name" - autocomplete="name" - required - onInput={(e): void => { - setName(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errors?.name} - isDirty={name !== undefined} - /> - </div> - </div> - - <div> <label for="username" class="block text-sm font-medium leading-6 text-gray-900"> <i18n.Translate>Username</i18n.Translate> <b style={{ color: "red" }}> *</b> @@ -237,7 +223,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on <div> <div class="flex items-center justify-between"> <label for="password" class="block text-sm font-medium leading-6 text-gray-900"> - Password + <i18n.Translate>Password</i18n.Translate> <b style={{ color: "red" }}> *</b> </label> </div> @@ -266,7 +252,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on <div> <div class="flex items-center justify-between"> <label for="register-repeat" class="block text-sm font-medium leading-6 text-gray-900"> - Repeat assword + <i18n.Translate>Repeat password</i18n.Translate> <b style={{ color: "red" }}> *</b> </label> </div> @@ -292,6 +278,84 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on </div> </div> + <div> + <label for="name" class="block text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate>Name</i18n.Translate> + </label> + <div class="mt-2"> + <input + autoFocus + type="text" + name="name" + id="name" + class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + value={name ?? ""} + enterkeyhint="next" + placeholder="your name" + autocomplete="name" + required + onInput={(e): void => { + setName(e.currentTarget.value); + }} + /> + {/* <ShowInputErrorLabel + message={errors?.name} + isDirty={name !== undefined} + /> */} + </div> + </div> + + {/* <div> + <label for="phone" class="block text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate>Phone</i18n.Translate> + </label> + <div class="mt-2"> + <input + autoFocus + type="text" + name="phone" + id="phone" + class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + value={phone ?? ""} + enterkeyhint="next" + placeholder="your phone" + autocomplete="none" + onInput={(e): void => { + setPhone(e.currentTarget.value); + }} + /> + <ShowInputErrorLabel + message={errors?.phone} + isDirty={phone !== undefined} + /> + </div> + </div> + <div> + <label for="email" class="block text-sm font-medium leading-6 text-gray-900"> + <i18n.Translate>Email</i18n.Translate> + </label> + <div class="mt-2"> + <input + autoFocus + type="text" + name="email" + id="email" + class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + value={email ?? ""} + enterkeyhint="next" + placeholder="your email" + autocomplete="email" + onInput={(e): void => { + setEmail(e.currentTarget.value); + }} + /> + <ShowInputErrorLabel + message={errors?.email} + isDirty={email !== undefined} + /> + </div> + </div> */} + <div class="flex w-full justify-between"> <button type="submit" class="ring-1 ring-gray-600 rounded-md bg-white disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 text-black shadow-sm hover:bg-white-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2" diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx b/packages/demobank-ui/src/pages/admin/Account.tsx index 521bc8eb3..676fc43d0 100644 --- a/packages/demobank-ui/src/pages/admin/Account.tsx +++ b/packages/demobank-ui/src/pages/admin/Account.tsx @@ -9,7 +9,7 @@ import { Fragment, h, VNode } from "preact"; export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode { const { i18n } = useTranslationContext(); const r = useBackendContext(); - const account = r.state.status === "loggedIn" ? r.state.username : "admin"; + const account = r.state.status !== "loggedOut" ? r.state.username : "admin"; const result = useAccountDetails(account); if (!result.ok) { diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx index c9d798082..2945cb65a 100644 --- a/packages/demobank-ui/src/pages/business/Home.tsx +++ b/packages/demobank-ui/src/pages/business/Home.tsx @@ -32,7 +32,6 @@ import { Fragment, VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { Cashouts } from "../../components/Cashouts/index.js"; import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js"; -import { useBackendContext } from "../../context/backend.js"; import { useAccountDetails } from "../../hooks/access.js"; import { useCashoutDetails, @@ -46,7 +45,6 @@ import { undefinedIfEmpty, } from "../../utils.js"; import { handleNotOkResult } from "../HomePage.js"; -import { LoginForm } from "../LoginForm.js"; import { Amount } from "../PaytoWireTransferForm.js"; import { ShowAccountDetails } from "../ShowAccountDetails.js"; import { UpdateAccountPassword } from "../UpdateAccountPassword.js"; diff --git a/packages/demobank-ui/src/settings.ts b/packages/demobank-ui/src/settings.ts index f33bf07e2..c4b510b30 100644 --- a/packages/demobank-ui/src/settings.ts +++ b/packages/demobank-ui/src/settings.ts @@ -15,13 +15,13 @@ */ export interface BankUiSettings { - backendBaseURL: string; - allowRegistrations: boolean; - showDemoNav: boolean; - simplePasswordForRandomAccounts: boolean; - allowRandomAccountCreation: boolean; - bankName: string; - demoSites: [string, string][]; + backendBaseURL?: string; + allowRegistrations?: boolean; + showDemoNav?: boolean; + simplePasswordForRandomAccounts?: boolean; + allowRandomAccountCreation?: boolean; + bankName?: string; + demoSites?: [string, string][]; } /** |