216 lines
7.5 KiB
TypeScript
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");
|
|
}
|