lang selector and fix logout
This commit is contained in:
parent
dcdf8fb6a0
commit
1e4f21cc76
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "@gnu-taler/demobank-ui",
|
"name": "@gnu-taler/demobank-ui",
|
||||||
"version": "0.1.0",
|
"version": "0.9.3-dev.17",
|
||||||
"license": "AGPL-3.0-OR-LATER",
|
"license": "AGPL-3.0-OR-LATER",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -42,11 +42,11 @@ function getLangName(s: keyof LangsNames | string): string {
|
|||||||
return String(s);
|
return String(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: explain "like py".
|
export function LangSelector(): VNode {
|
||||||
export function LangSelectorLikePy(): VNode {
|
|
||||||
const [updatingLang, setUpdatingLang] = useState(false);
|
const [updatingLang, setUpdatingLang] = useState(false);
|
||||||
const { lang, changeLanguage } = useTranslationContext();
|
const { lang, changeLanguage } = useTranslationContext();
|
||||||
const [hidden, setHidden] = useState(true);
|
const [hidden, setHidden] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function bodyKeyPress(event: KeyboardEvent) {
|
function bodyKeyPress(event: KeyboardEvent) {
|
||||||
if (event.code === "Escape") setHidden(true);
|
if (event.code === "Escape") setHidden(true);
|
||||||
@ -62,51 +62,49 @@ export function LangSelectorLikePy(): VNode {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<div>
|
||||||
<a
|
<div class="relative mt-2">
|
||||||
href="#"
|
<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"
|
||||||
class="pure-button"
|
onClick={() => {
|
||||||
name="language"
|
setHidden((h) => !h);
|
||||||
onClick={(ev) => {
|
}}>
|
||||||
ev.preventDefault();
|
<span class="flex items-center">
|
||||||
setHidden((h) => !h);
|
<img src="https://taler.net/images/languageicon.svg" alt="" class="h-5 w-5 flex-shrink-0 rounded-full" />
|
||||||
ev.stopPropagation();
|
<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">
|
||||||
{getLangName(lang)}
|
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
</a>
|
<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" />
|
||||||
<div
|
</svg>
|
||||||
id="lang"
|
</span>
|
||||||
class={hidden ? "hide" : ""}
|
</button>
|
||||||
style={{
|
|
||||||
display: "inline-block",
|
{!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">
|
||||||
>
|
|
||||||
<div style="position: relative; overflow: visible;">
|
|
||||||
<div
|
|
||||||
class="nav"
|
|
||||||
style="position: absolute; max-height: 60vh; overflow-y: auto; margin-left: -120px; margin-top: 20px"
|
|
||||||
>
|
|
||||||
{Object.keys(messages)
|
{Object.keys(messages)
|
||||||
.filter((l) => l !== lang)
|
.filter((l) => l !== lang)
|
||||||
.map((l) => (
|
.map((lang) => (
|
||||||
<a
|
<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"
|
||||||
key={l}
|
|
||||||
href="#"
|
|
||||||
class="navbtn langbtn"
|
|
||||||
value={l}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
changeLanguage(l);
|
changeLanguage(lang);
|
||||||
setUpdatingLang(false);
|
setUpdatingLang(false);
|
||||||
|
setHidden(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{getLangName(l)}
|
<span class="font-normal block truncate">{getLangName(lang)}</span>
|
||||||
</a>
|
|
||||||
|
<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>
|
</ul>
|
||||||
</div>
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -78,17 +78,17 @@ function VersionCheck({ children }: { children: ComponentChildren }): VNode {
|
|||||||
if (checked === undefined) {
|
if (checked === undefined) {
|
||||||
return <Loading />
|
return <Loading />
|
||||||
}
|
}
|
||||||
if (typeof checked === "string") {
|
if (checked.type === "wrong") {
|
||||||
return <BankFrame>
|
return <BankFrame>
|
||||||
the bank backend is not supported. supported version "{BANK_INTEGRATION_PROTOCOL_VERSION}", server version "{checked}"
|
the bank backend is not supported. supported version "{BANK_INTEGRATION_PROTOCOL_VERSION}", server version "{checked}"
|
||||||
</BankFrame>
|
</BankFrame>
|
||||||
}
|
}
|
||||||
if (checked === true) {
|
if (checked.type === "ok") {
|
||||||
return <Fragment>{children}</Fragment>
|
return <Fragment>{children}</Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <BankFrame>
|
return <BankFrame>
|
||||||
<ErrorLoading error={checked}/>
|
<ErrorLoading error={checked.result}/>
|
||||||
</BankFrame>
|
</BankFrame>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,9 @@ const initial: Type = {
|
|||||||
logOut() {
|
logOut() {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
|
expired() {
|
||||||
|
null;
|
||||||
|
},
|
||||||
logIn(info) {
|
logIn(info) {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
@ -65,6 +68,7 @@ export const BackendStateProviderTesting = ({
|
|||||||
const value: BackendStateHandler = {
|
const value: BackendStateHandler = {
|
||||||
state,
|
state,
|
||||||
logIn: () => {},
|
logIn: () => {},
|
||||||
|
expired: () => {},
|
||||||
logOut: () => {},
|
logOut: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
4
packages/demobank-ui/src/declaration.d.ts
vendored
4
packages/demobank-ui/src/declaration.d.ts
vendored
@ -74,7 +74,9 @@ type HashCode = string;
|
|||||||
type EddsaPublicKey = string;
|
type EddsaPublicKey = string;
|
||||||
type EddsaSignature = string;
|
type EddsaSignature = string;
|
||||||
type WireTransferIdentifierRawP = string;
|
type WireTransferIdentifierRawP = string;
|
||||||
type RelativeTime = Duration;
|
type RelativeTime = {
|
||||||
|
d_us: number | "forever"
|
||||||
|
};
|
||||||
type ImageDataUrl = string;
|
type ImageDataUrl = string;
|
||||||
|
|
||||||
interface WithId {
|
interface WithId {
|
||||||
|
@ -46,16 +46,18 @@ import { AccessToken } from "./useCredentialsChecker.js";
|
|||||||
* Has the information to reach and
|
* Has the information to reach and
|
||||||
* authenticate at the bank's backend.
|
* 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;
|
username: string;
|
||||||
token: AccessToken;
|
token: AccessToken;
|
||||||
}
|
}
|
||||||
|
interface Expired {
|
||||||
interface LoggedIn extends BackendCredentials {
|
status: "expired";
|
||||||
status: "loggedIn";
|
|
||||||
isUserAdministrator: boolean;
|
isUserAdministrator: boolean;
|
||||||
|
username: string;
|
||||||
}
|
}
|
||||||
interface LoggedOut {
|
interface LoggedOut {
|
||||||
status: "loggedOut";
|
status: "loggedOut";
|
||||||
@ -69,6 +71,13 @@ export const codecForBackendStateLoggedIn = (): Codec<LoggedIn> =>
|
|||||||
.property("isUserAdministrator", codecForBoolean())
|
.property("isUserAdministrator", codecForBoolean())
|
||||||
.build("BackendState.LoggedIn");
|
.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> =>
|
export const codecForBackendStateLoggedOut = (): Codec<LoggedOut> =>
|
||||||
buildCodecForObject<LoggedOut>()
|
buildCodecForObject<LoggedOut>()
|
||||||
.property("status", codecForConstString("loggedOut"))
|
.property("status", codecForConstString("loggedOut"))
|
||||||
@ -79,6 +88,7 @@ export const codecForBackendState = (): Codec<BackendState> =>
|
|||||||
.discriminateOn("status")
|
.discriminateOn("status")
|
||||||
.alternative("loggedIn", codecForBackendStateLoggedIn())
|
.alternative("loggedIn", codecForBackendStateLoggedIn())
|
||||||
.alternative("loggedOut", codecForBackendStateLoggedOut())
|
.alternative("loggedOut", codecForBackendStateLoggedOut())
|
||||||
|
.alternative("expired", codecForBackendStateExpired())
|
||||||
.build("BackendState");
|
.build("BackendState");
|
||||||
|
|
||||||
export function getInitialBackendBaseURL(): string {
|
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'",
|
"ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'",
|
||||||
);
|
);
|
||||||
result = window.origin
|
result = window.origin
|
||||||
|
} else {
|
||||||
|
result = bankUiSettings.backendBaseURL;
|
||||||
}
|
}
|
||||||
result = bankUiSettings.backendBaseURL;
|
|
||||||
} else {
|
} else {
|
||||||
// testing/development path
|
// testing/development path
|
||||||
result = overrideUrl
|
result = overrideUrl
|
||||||
@ -115,7 +126,8 @@ export const defaultState: BackendState = {
|
|||||||
export interface BackendStateHandler {
|
export interface BackendStateHandler {
|
||||||
state: BackendState;
|
state: BackendState;
|
||||||
logOut(): void;
|
logOut(): void;
|
||||||
logIn(info: BackendCredentials): void;
|
expired(): void;
|
||||||
|
logIn(info: {username: string, token: AccessToken}): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BACKEND_STATE_KEY = buildStorageKey(
|
const BACKEND_STATE_KEY = buildStorageKey(
|
||||||
@ -133,12 +145,22 @@ export function useBackendState(): BackendStateHandler {
|
|||||||
BACKEND_STATE_KEY,
|
BACKEND_STATE_KEY,
|
||||||
defaultState,
|
defaultState,
|
||||||
);
|
);
|
||||||
|
const mutateAll = useMatchMutate();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
logOut() {
|
logOut() {
|
||||||
update(defaultState);
|
update(defaultState);
|
||||||
},
|
},
|
||||||
|
expired() {
|
||||||
|
if (state.status === "loggedOut") return;
|
||||||
|
const nextState: BackendState = {
|
||||||
|
status: "expired",
|
||||||
|
username: state.username,
|
||||||
|
isUserAdministrator: state.username === "admin",
|
||||||
|
};
|
||||||
|
update(nextState);
|
||||||
|
},
|
||||||
logIn(info) {
|
logIn(info) {
|
||||||
//admin is defined by the username
|
//admin is defined by the username
|
||||||
const nextState: BackendState = {
|
const nextState: BackendState = {
|
||||||
@ -147,6 +169,7 @@ export function useBackendState(): BackendStateHandler {
|
|||||||
isUserAdministrator: info.username === "admin",
|
isUserAdministrator: info.username === "admin",
|
||||||
};
|
};
|
||||||
update(nextState);
|
update(nextState);
|
||||||
|
mutateAll(/.*/)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -194,7 +217,7 @@ export function usePublicBackend(): useBackendType {
|
|||||||
number,
|
number,
|
||||||
]): Promise<HttpResponseOk<T>> {
|
]): Promise<HttpResponseOk<T>> {
|
||||||
const delta = -1 * size //descending order
|
const delta = -1 * size //descending order
|
||||||
const params = start ? { delta, start } : {delta}
|
const params = start ? { delta, start } : { delta }
|
||||||
return requestHandler<T>(baseUrl, endpoint, {
|
return requestHandler<T>(baseUrl, endpoint, {
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
@ -262,7 +285,8 @@ export function useAuthenticatedBackend(): useBackendType {
|
|||||||
const { state } = useBackendContext();
|
const { state } = useBackendContext();
|
||||||
const { request: requestHandler } = useApiContext();
|
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 baseUrl = getInitialBackendBaseURL();
|
||||||
|
|
||||||
const request = useCallback(
|
const request = useCallback(
|
||||||
@ -288,7 +312,7 @@ export function useAuthenticatedBackend(): useBackendType {
|
|||||||
number,
|
number,
|
||||||
]): Promise<HttpResponseOk<T>> {
|
]): Promise<HttpResponseOk<T>> {
|
||||||
const delta = -1 * size //descending order
|
const delta = -1 * size //descending order
|
||||||
const params = start ? { delta, start } : {delta}
|
const params = start ? { delta, start } : { delta }
|
||||||
return requestHandler<T>(baseUrl, endpoint, {
|
return requestHandler<T>(baseUrl, endpoint, {
|
||||||
token: creds,
|
token: creds,
|
||||||
params,
|
params,
|
||||||
|
@ -268,7 +268,7 @@ export function useEstimator(): CashoutEstimators {
|
|||||||
const { state } = useBackendContext();
|
const { state } = useBackendContext();
|
||||||
const { request } = useApiContext();
|
const { request } = useApiContext();
|
||||||
const creds =
|
const creds =
|
||||||
state.status === "loggedOut"
|
state.status !== "loggedIn"
|
||||||
? undefined
|
? undefined
|
||||||
: state.token;
|
: state.token;
|
||||||
return {
|
return {
|
||||||
@ -340,7 +340,7 @@ export function useBusinessAccountFlag(): boolean | undefined {
|
|||||||
const { state } = useBackendContext();
|
const { state } = useBackendContext();
|
||||||
const { request } = useApiContext();
|
const { request } = useApiContext();
|
||||||
const creds =
|
const creds =
|
||||||
state.status === "loggedOut"
|
state.status !== "loggedIn"
|
||||||
? undefined
|
? undefined
|
||||||
: {user: state.username, token: state.token};
|
: {user: state.username, token: state.token};
|
||||||
|
|
||||||
|
@ -18,23 +18,29 @@ async function getConfigState(
|
|||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useConfigState(): undefined | true | string | HttpError<SandboxBackend.SandboxError> {
|
type Result = undefined
|
||||||
const [checked, setChecked] = useState<true | string | HttpError<SandboxBackend.SandboxError>>()
|
| { 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();
|
const { request } = useApiContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getConfigState(request)
|
getConfigState(request)
|
||||||
.then((s) => {
|
.then((result) => {
|
||||||
const r = LibtoolVersion.compare(BANK_INTEGRATION_PROTOCOL_VERSION, s.version)
|
const r = LibtoolVersion.compare(BANK_INTEGRATION_PROTOCOL_VERSION, result.version)
|
||||||
if (r?.compatible) {
|
if (r?.compatible) {
|
||||||
setChecked(true);
|
setChecked({ type: "ok",result });
|
||||||
} else {
|
} else {
|
||||||
setChecked(s.version)
|
setChecked({ type: "wrong",result })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: unknown) => {
|
.catch((error: unknown) => {
|
||||||
if (error instanceof RequestError) {
|
if (error instanceof RequestError) {
|
||||||
setChecked(error.cause);
|
const result = error.cause
|
||||||
|
setChecked({ type:"error", result });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -15,7 +15,7 @@ export function useCredentialsChecker() {
|
|||||||
scope: "readwrite" as "write", //FIX: different than merchant
|
scope: "readwrite" as "write", //FIX: different than merchant
|
||||||
duration: {
|
duration: {
|
||||||
// d_us: "forever" //FIX: should return shortest
|
// d_us: "forever" //FIX: should return shortest
|
||||||
d_us: 1000 * 60 * 60 * 23
|
d_us: 1000 * 1000 * 5 //60 * 60 * 24 * 7
|
||||||
},
|
},
|
||||||
refreshable: true,
|
refreshable: true,
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringi
|
|||||||
import { notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
|
import { notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||||
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
||||||
import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks";
|
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 { useBackendContext } from "../context/backend.js";
|
||||||
import { useBusinessAccountDetails } from "../hooks/circuit.js";
|
import { useBusinessAccountDetails } from "../hooks/circuit.js";
|
||||||
import { bankUiSettings } from "../settings.js";
|
import { bankUiSettings } from "../settings.js";
|
||||||
@ -65,12 +65,14 @@ export function BankFrame({
|
|||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const demo_sites = [];
|
const demo_sites = [];
|
||||||
for (const i in bankUiSettings.demoSites)
|
if (bankUiSettings.demoSites) {
|
||||||
demo_sites.push(
|
for (const i in bankUiSettings.demoSites)
|
||||||
<a href={bankUiSettings.demoSites[i][1]}>
|
demo_sites.push(
|
||||||
{bankUiSettings.demoSites[i][0]}
|
<a href={bankUiSettings.demoSites[i][1]}>
|
||||||
</a>,
|
{bankUiSettings.demoSites[i][0]}
|
||||||
);
|
</a>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (<div class="min-h-full flex flex-col m-0" style="min-height: 100vh;">
|
return (<div class="min-h-full flex flex-col m-0" style="min-height: 100vh;">
|
||||||
<div class="bg-indigo-600 pb-32">
|
<div class="bg-indigo-600 pb-32">
|
||||||
@ -88,14 +90,16 @@ export function BankFrame({
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden sm:block lg:ml-10 ">
|
{bankUiSettings.demoSites &&
|
||||||
<div class="flex space-x-4">
|
<div class="hidden sm:block lg:ml-10 ">
|
||||||
{/* <!-- Current: "bg-indigo-700 text-white", Default: "text-white hover:bg-indigo-500 hover:bg-opacity-75" --> */}
|
<div class="flex space-x-4">
|
||||||
{bankUiSettings.demoSites.map(([name, url]) => {
|
{/* <!-- Current: "bg-indigo-700 text-white", Default: "text-white hover:bg-indigo-500 hover:bg-opacity-75" --> */}
|
||||||
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.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>
|
</div>
|
||||||
|
|
||||||
<div class="flex">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
Log out
|
<i18n.Translate>Log out</i18n.Translate>
|
||||||
{/* <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> */}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="sm:hidden">
|
<li>
|
||||||
<div class="text-xs font-semibold leading-6 text-gray-400">
|
<LangSelector />
|
||||||
<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>
|
||||||
|
{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>
|
<li>
|
||||||
<ul role="list" class="space-y-1">
|
<ul role="list" class="space-y-1">
|
||||||
<li class="mt-2">
|
<li class="mt-2">
|
||||||
@ -291,63 +298,6 @@ export function BankFrame({
|
|||||||
<Footer />
|
<Footer />
|
||||||
</div >
|
</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 &&
|
{n.message.debug &&
|
||||||
<div class="mt-2 text-sm text-red-700 font-mono break-all">
|
<div class="mt-2 text-sm text-red-700 font-mono break-all">
|
||||||
{n.message.debug}
|
{n.message.debug}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,14 +30,32 @@ import { undefinedIfEmpty } from "../utils.js";
|
|||||||
*/
|
*/
|
||||||
export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
||||||
const backend = useBackendContext();
|
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 [username, setUsername] = useState<string | undefined>(currentUser);
|
||||||
const [password, setPassword] = useState<string | undefined>();
|
const [password, setPassword] = useState<string | undefined>();
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const { requestNewLoginToken, refreshLoginToken } = useCredentialsChecker();
|
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);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
useEffect(function focusInput() {
|
useEffect(function focusInput() {
|
||||||
|
//FIXME: show invalidate session and allow relogin
|
||||||
|
if (isSessionExpired) {
|
||||||
|
localStorage.removeItem("backend-state");
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
ref.current?.focus();
|
ref.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
const [busy, setBusy] = useState<Record<string, undefined>>()
|
const [busy, setBusy] = useState<Record<string, undefined>>()
|
||||||
@ -124,13 +142,6 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
|||||||
setBusy(undefined)
|
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 (
|
return (
|
||||||
<div class="flex min-h-full flex-col justify-center">
|
<div class="flex min-h-full flex-col justify-center">
|
||||||
|
|
||||||
|
@ -14,20 +14,15 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
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 { useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, VNode, h } from "preact";
|
||||||
import { Transactions } from "../../components/Transactions/index.js";
|
|
||||||
import { PaymentOptions } from "../PaymentOptions.js";
|
|
||||||
import { State } from "./index.js";
|
|
||||||
import { CopyButton } from "../../components/CopyButton.js";
|
|
||||||
import { bankUiSettings } from "../../settings.js";
|
|
||||||
import { useBusinessAccountDetails } from "../../hooks/circuit.js";
|
|
||||||
import { useSettings } from "../../hooks/settings.js";
|
|
||||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
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 { 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) {
|
export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) {
|
||||||
return (
|
return (
|
||||||
|
@ -49,6 +49,8 @@ export function RegistrationPage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const USERNAME_REGEX = /^[a-z][a-zA-Z0-9-]*$/;
|
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.
|
* Collect and submit registration data.
|
||||||
@ -58,21 +60,33 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
|
|||||||
const [username, setUsername] = useState<string | undefined>();
|
const [username, setUsername] = useState<string | undefined>();
|
||||||
const [name, setName] = useState<string | undefined>();
|
const [name, setName] = useState<string | undefined>();
|
||||||
const [password, setPassword] = 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 [repeatPassword, setRepeatPassword] = useState<string | undefined>();
|
||||||
const {requestNewLoginToken} = useCredentialsChecker()
|
const { requestNewLoginToken } = useCredentialsChecker()
|
||||||
|
|
||||||
const { register } = useTestingAPI();
|
const { register } = useTestingAPI();
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
const errors = undefinedIfEmpty({
|
const errors = undefinedIfEmpty({
|
||||||
name: !name
|
// name: !name
|
||||||
? i18n.str`Missing name`
|
// ? i18n.str`Missing name`
|
||||||
: undefined,
|
// : undefined,
|
||||||
username: !username
|
username: !username
|
||||||
? i18n.str`Missing username`
|
? i18n.str`Missing username`
|
||||||
: !USERNAME_REGEX.test(username)
|
: !USERNAME_REGEX.test(username)
|
||||||
? i18n.str`Use letters and numbers only, and start with a lowercase letter`
|
? i18n.str`Use letters and numbers only, and start with a lowercase letter`
|
||||||
: undefined,
|
: 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,
|
password: !password ? i18n.str`Missing password` : undefined,
|
||||||
repeatPassword: !repeatPassword
|
repeatPassword: !repeatPassword
|
||||||
? i18n.str`Missing password`
|
? i18n.str`Missing password`
|
||||||
@ -82,9 +96,9 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function doRegistrationStep() {
|
async function doRegistrationStep() {
|
||||||
if (!username || !password || !name) return;
|
if (!username || !password) return;
|
||||||
try {
|
try {
|
||||||
await register({ name, username, password });
|
await register({ name: name ?? "", username, password });
|
||||||
const resp = await requestNewLoginToken(username, password)
|
const resp = await requestNewLoginToken(username, password)
|
||||||
setUsername(undefined);
|
setUsername(undefined);
|
||||||
if (resp.valid) {
|
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="flex min-h-full flex-col justify-center">
|
||||||
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
|
<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>
|
||||||
|
|
||||||
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
<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"
|
autoCapitalize="none"
|
||||||
autoCorrect="off"
|
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>
|
<div>
|
||||||
<label for="username" class="block text-sm font-medium leading-6 text-gray-900">
|
<label for="username" class="block text-sm font-medium leading-6 text-gray-900">
|
||||||
<i18n.Translate>Username</i18n.Translate>
|
<i18n.Translate>Username</i18n.Translate>
|
||||||
@ -237,7 +223,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<label for="password" class="block text-sm font-medium leading-6 text-gray-900">
|
<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>
|
<b style={{ color: "red" }}> *</b>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -266,7 +252,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<label for="register-repeat" class="block text-sm font-medium leading-6 text-gray-900">
|
<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>
|
<b style={{ color: "red" }}> *</b>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -292,6 +278,84 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="flex w-full justify-between">
|
||||||
<button type="submit"
|
<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"
|
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"
|
||||||
|
@ -9,7 +9,7 @@ import { Fragment, h, VNode } from "preact";
|
|||||||
export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode {
|
export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const r = useBackendContext();
|
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);
|
const result = useAccountDetails(account);
|
||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
|
@ -32,7 +32,6 @@ import { Fragment, VNode, h } from "preact";
|
|||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { Cashouts } from "../../components/Cashouts/index.js";
|
import { Cashouts } from "../../components/Cashouts/index.js";
|
||||||
import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
|
import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
|
||||||
import { useBackendContext } from "../../context/backend.js";
|
|
||||||
import { useAccountDetails } from "../../hooks/access.js";
|
import { useAccountDetails } from "../../hooks/access.js";
|
||||||
import {
|
import {
|
||||||
useCashoutDetails,
|
useCashoutDetails,
|
||||||
@ -46,7 +45,6 @@ import {
|
|||||||
undefinedIfEmpty,
|
undefinedIfEmpty,
|
||||||
} from "../../utils.js";
|
} from "../../utils.js";
|
||||||
import { handleNotOkResult } from "../HomePage.js";
|
import { handleNotOkResult } from "../HomePage.js";
|
||||||
import { LoginForm } from "../LoginForm.js";
|
|
||||||
import { Amount } from "../PaytoWireTransferForm.js";
|
import { Amount } from "../PaytoWireTransferForm.js";
|
||||||
import { ShowAccountDetails } from "../ShowAccountDetails.js";
|
import { ShowAccountDetails } from "../ShowAccountDetails.js";
|
||||||
import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
|
import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
|
||||||
|
@ -15,13 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export interface BankUiSettings {
|
export interface BankUiSettings {
|
||||||
backendBaseURL: string;
|
backendBaseURL?: string;
|
||||||
allowRegistrations: boolean;
|
allowRegistrations?: boolean;
|
||||||
showDemoNav: boolean;
|
showDemoNav?: boolean;
|
||||||
simplePasswordForRandomAccounts: boolean;
|
simplePasswordForRandomAccounts?: boolean;
|
||||||
allowRandomAccountCreation: boolean;
|
allowRandomAccountCreation?: boolean;
|
||||||
bankName: string;
|
bankName?: string;
|
||||||
demoSites: [string, string][];
|
demoSites?: [string, string][];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user