wallet-core/packages/demobank-ui/src/pages/RegistrationPage.tsx

216 lines
7.5 KiB
TypeScript

/*
This file is part of GNU Taler
(C) 2022 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { HttpStatusCode, Logger } from "@gnu-taler/taler-util";
import {
RequestError,
useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { useBackendContext } from "../context/backend.js";
import {
PageStateType,
notifyError,
usePageContext,
} from "../context/pageState.js";
import { useTestingAPI } from "../hooks/access.js";
import { bankUiSettings } from "../settings.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
const logger = new Logger("RegistrationPage");
export function RegistrationPage({
onComplete,
}: {
onComplete: () => void;
}): VNode {
const { i18n } = useTranslationContext();
if (!bankUiSettings.allowRegistrations) {
return (
<p>{i18n.str`Currently, the bank is not accepting new registrations!`}</p>
);
}
return <RegistrationForm onComplete={onComplete} />;
}
export const USERNAME_REGEX = /^[a-z][a-zA-Z0-9]*$/;
/**
* Collect and submit registration data.
*/
function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode {
const backend = useBackendContext();
const [username, setUsername] = useState<string | undefined>();
const [password, setPassword] = useState<string | undefined>();
const [repeatPassword, setRepeatPassword] = useState<string | undefined>();
const { register } = useTestingAPI();
const { i18n } = useTranslationContext();
const errors = undefinedIfEmpty({
username: !username
? i18n.str`Missing username`
: !USERNAME_REGEX.test(username)
? 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`
: repeatPassword !== password
? i18n.str`Passwords don't match`
: undefined,
});
return (
<Fragment>
<h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
<article>
<div class="register-div">
<form
class="register-form"
noValidate
onSubmit={(e) => {
e.preventDefault();
}}
autoCapitalize="none"
autoCorrect="off"
>
<div class="pure-form">
<h2>{i18n.str`Please register!`}</h2>
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
<label for="register-un">{i18n.str`Username:`}</label>
</p>
<input
id="register-un"
name="register-un"
type="text"
placeholder="Username"
autocomplete="username"
value={username ?? ""}
onInput={(e): void => {
setUsername(e.currentTarget.value);
}}
/>
<ShowInputErrorLabel
message={errors?.username}
isDirty={username !== undefined}
/>
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
<label for="register-pw">{i18n.str`Password:`}</label>
</p>
<input
type="password"
name="register-pw"
id="register-pw"
placeholder="Password"
autocomplete="new-password"
value={password ?? ""}
required
onInput={(e): void => {
setPassword(e.currentTarget.value);
}}
/>
<ShowInputErrorLabel
message={errors?.password}
isDirty={password !== undefined}
/>
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
<label for="register-repeat">{i18n.str`Repeat Password:`}</label>
</p>
<input
type="password"
style={{ marginBottom: 8 }}
name="register-repeat"
id="register-repeat"
autocomplete="new-password"
placeholder="Same password"
value={repeatPassword ?? ""}
required
onInput={(e): void => {
setRepeatPassword(e.currentTarget.value);
}}
/>
<ShowInputErrorLabel
message={errors?.repeatPassword}
isDirty={repeatPassword !== undefined}
/>
<br />
<button
class="pure-button pure-button-primary btn-register"
type="submit"
disabled={!!errors}
onClick={async (e) => {
e.preventDefault();
if (!username || !password) return;
try {
const credentials = { username, password };
await register(credentials);
setUsername(undefined);
setPassword(undefined);
setRepeatPassword(undefined);
backend.logIn(credentials);
onComplete();
} catch (error) {
if (error instanceof RequestError) {
notifyError(
buildRequestErrorMessage(i18n, error.cause, {
onClientError: (status) =>
status === HttpStatusCode.Conflict
? i18n.str`That username is already taken`
: undefined,
}),
);
} else {
notifyError({
title: i18n.str`Operation failed, please report`,
description:
error instanceof Error
? error.message
: JSON.stringify(error),
});
}
}
}}
>
{i18n.str`Register`}
</button>
{/* FIXME: should use a different color */}
<button
class="pure-button pure-button-secondary btn-cancel"
onClick={(e) => {
e.preventDefault();
setUsername(undefined);
setPassword(undefined);
setRepeatPassword(undefined);
onComplete();
}}
>
{i18n.str`Cancel`}
</button>
</div>
</form>
</div>
</article>
</Fragment>
);
}
export function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}