diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/RegistrationPage.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/RegistrationPage.tsx | 426 |
1 files changed, 0 insertions, 426 deletions
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx deleted file mode 100644 index e1d32002b..000000000 --- a/packages/demobank-ui/src/pages/RegistrationPage.tsx +++ /dev/null @@ -1,426 +0,0 @@ -/* - 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 { AccessToken, Logger, TranslatedString } from "@gnu-taler/taler-util"; -import { - LocalNotificationBanner, - ShowInputErrorLabel, - useLocalNotification, - useTranslationContext -} from "@gnu-taler/web-util/browser"; -import { Fragment, VNode, h } from "preact"; -import { useState } from "preact/hooks"; -import { useBankCoreApiContext } from "../context/config.js"; -import { useBackendState } from "../hooks/backend.js"; -import { undefinedIfEmpty } from "../utils.js"; -import { getRandomPassword, getRandomUsername } from "./rnd.js"; -import { useSettingsContext } from "../context/settings.js"; - -const logger = new Logger("RegistrationPage"); - -export function RegistrationPage({ - onComplete, - onCancel -}: { - onComplete: () => void; - onCancel: () => void; -}): VNode { - const { i18n } = useTranslationContext(); - const { config } = useBankCoreApiContext(); - if (!config.allow_registrations) { - return ( - <p>{i18n.str`Currently, the bank is not accepting new registrations!`}</p> - ); - } - return <RegistrationForm onComplete={onComplete} onCancel={onCancel} />; -} - -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. - */ -function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, onCancel: () => void }): VNode { - const backend = useBackendState(); - 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 [notification, notify, handleError] = useLocalNotification() - const settings = useSettingsContext(); - - const { api } = useBankCoreApiContext() - // const { register } = useTestingAPI(); - const { i18n } = useTranslationContext(); - - const errors = undefinedIfEmpty({ - 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` - : repeatPassword !== password - ? i18n.str`Passwords don't match` - : undefined, - }); - - async function doRegistrationAndLogin(name: string, username: string, password: string, onComplete: () => void) { - await handleError(async () => { - const creationResponse = await api.createAccount("" as AccessToken, { name, username, password }); - if (creationResponse.type === "fail") { - switch (creationResponse.case) { - case "invalid-phone-or-email": return notify({ - type: "error", - title: i18n.str`Server replied with invalid phone or email.`, - description: creationResponse.detail.hint as TranslatedString, - debug: creationResponse.detail, - }) - case "insufficient-funds": return notify({ - type: "error", - title: i18n.str`Registration is disabled because the bank ran out of bonus credit.`, - description: creationResponse.detail.hint as TranslatedString, - debug: creationResponse.detail, - }) - case "unauthorized": return notify({ - type: "error", - title: i18n.str`No enough permission to create that account.`, - description: creationResponse.detail.hint as TranslatedString, - debug: creationResponse.detail, - }) - case "payto-already-exists": return notify({ - type: "error", - title: i18n.str`That account id is already taken.`, - description: creationResponse.detail.hint as TranslatedString, - debug: creationResponse.detail, - }) - case "username-already-exists": return notify({ - type: "error", - title: i18n.str`That username is already taken.`, - description: creationResponse.detail.hint as TranslatedString, - debug: creationResponse.detail, - }) - case "username-reserved": return notify({ - type: "error", - title: i18n.str`That username can't be used because is reserved.`, - description: creationResponse.detail.hint as TranslatedString, - debug: creationResponse.detail, - }) - case "user-cant-set-debt": return notify({ - type: "error", - title: i18n.str`Only admin is allow to set debt limit.`, - description: creationResponse.detail.hint as TranslatedString, - debug: creationResponse.detail, - }) - default: assertUnreachable(creationResponse) - } - } - const resp = await api.getAuthenticationAPI(username).createAccessToken(password, { - scope: "readwrite", - duration: { d_us: "forever" }, - refreshable: true, - }) - - if (resp.type === "ok") { - backend.logIn({ username, token: resp.body.access_token }); - } else { - switch (resp.case) { - case "wrong-credentials": return notify({ - type: "error", - title: i18n.str`Wrong credentials for "${username}"`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }) - case "not-found": return notify({ - type: "error", - title: i18n.str`Account not found`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }) - default: assertUnreachable(resp) - } - } - onComplete() - }) - } - - async function doRegistrationStep() { - if (!username || !password || !name) return; - await doRegistrationAndLogin(name, username, password, () => { - setUsername(undefined); - setPassword(undefined); - setRepeatPassword(undefined); - onComplete(); - }) - } - - async function doRandomRegistration(tries: number = 3) { - const user = getRandomUsername(); - - const pass = settings.simplePasswordForRandomAccounts ? "123" : getRandomPassword(); - const username = `_${user.first}-${user.second}_` - const name = `${user.first}, ${user.second}` - await doRegistrationAndLogin(name, username, pass, onComplete) - - } - - return ( - <Fragment> - <LocalNotificationBanner notification={notification} /> - - <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`Account registration`}</h2> - </div> - - <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm"> - <form class="space-y-6" noValidate - onSubmit={(e) => { - e.preventDefault(); - }} - autoCapitalize="none" - autoCorrect="off" - > - <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> - </label> - <div class="mt-2"> - <input - autoFocus - type="text" - name="username" - id="username" - 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={username ?? ""} - enterkeyhint="next" - placeholder="identification" - autocomplete="username" - required - onInput={(e): void => { - setUsername(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errors?.username} - isDirty={username !== undefined} - /> - </div> - </div> - - <div> - <div class="flex items-center justify-between"> - <label for="password" class="block text-sm font-medium leading-6 text-gray-900"> - <i18n.Translate>Password</i18n.Translate> - <b style={{ color: "red" }}> *</b> - </label> - </div> - <div class="mt-2"> - <input - type="password" - name="password" - id="password" - autocomplete="current-password" - 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" - enterkeyhint="send" - value={password ?? ""} - placeholder="Password" - required - onInput={(e): void => { - setPassword(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errors?.password} - isDirty={password !== undefined} - /> - </div> - </div> - - <div> - <div class="flex items-center justify-between"> - <label for="register-repeat" class="block text-sm font-medium leading-6 text-gray-900"> - <i18n.Translate>Repeat password</i18n.Translate> - <b style={{ color: "red" }}> *</b> - </label> - </div> - <div class="mt-2"> - <input - type="password" - name="register-repeat" - id="register-repeat" - autocomplete="current-password" - 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" - enterkeyhint="send" - value={repeatPassword ?? ""} - placeholder="Same password" - required - onInput={(e): void => { - setRepeatPassword(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errors?.repeatPassword} - isDirty={repeatPassword !== undefined} - /> - </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" - onClick={(e) => { - e.preventDefault() - onCancel() - }} - > - <i18n.Translate>Cancel</i18n.Translate> - </button> - <button type="submit" - class=" rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" - disabled={!!errors} - onClick={async (e) => { - e.preventDefault() - - doRegistrationStep() - }} - > - <i18n.Translate>Register</i18n.Translate> - </button> - </div> - - </form> - - {settings.allowRandomAccountCreation && - <p class="mt-10 text-center text-sm text-gray-500 border-t"> - <button type="submit" - class="flex mt-4 w-full justify-center rounded-md bg-green-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" - onClick={(e) => { - e.preventDefault() - doRandomRegistration() - }} - > - <i18n.Translate>Create a random user</i18n.Translate> - </button> - </p> - } - </div> - </div> - - </Fragment> - ); -} - -export function assertUnreachable(x: never): never { - throw new Error("Didn't expect to get here"); -} |