lang selector and fix logout

This commit is contained in:
Sebastian 2023-09-26 15:18:43 -03:00
parent dcdf8fb6a0
commit 1e4f21cc76
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
16 changed files with 272 additions and 220 deletions

View File

@ -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": {

View File

@ -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>
);
}

View File

@ -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>
}

View File

@ -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: () => {},
};

View File

@ -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 {

View File

@ -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,

View File

@ -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};

View File

@ -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 });
}
});
}, []);

View File

@ -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,
}

View File

@ -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">&gt;</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">&gt;</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>

View File

@ -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">

View File

@ -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 (

View File

@ -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">
@ -178,34 +192,6 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
autoCapitalize="none"
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>
@ -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"

View File

@ -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) {

View File

@ -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";

View File

@ -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][];
}
/**