diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/LoginForm.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/LoginForm.tsx | 335 |
1 files changed, 179 insertions, 156 deletions
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index f0ae97d60..46039005a 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -14,16 +14,14 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { HttpStatusCode } from "@gnu-taler/taler-util"; -import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util"; +import { ErrorType, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; import { useBackendContext } from "../context/backend.js"; import { useCredentialsChecker } from "../hooks/backend.js"; -import { ErrorMessage } from "../hooks/notification.js"; import { bankUiSettings } from "../settings.js"; import { undefinedIfEmpty } from "../utils.js"; -import { ErrorBannerFloat } from "./BankFrame.js"; import { USERNAME_REGEX } from "./RegistrationPage.js"; import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; @@ -36,177 +34,202 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode { const [password, setPassword] = useState<string | undefined>(); const { i18n } = useTranslationContext(); const testLogin = useCredentialsChecker(); - const [error, saveError] = useState<ErrorMessage | undefined>(); const ref = useRef<HTMLInputElement>(null); useEffect(function focusInput() { ref.current?.focus(); }, []); + const [busy, setBusy] = useState<Record<string, undefined>>() 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, + ? i18n.str`Use letters and numbers only, and start with a lowercase letter` + : undefined, password: !password ? i18n.str`Missing password` : undefined, - }); + }) ?? busy; + + function saveError({ title, description, debug }: { title: TranslatedString, description?: TranslatedString, debug?: any }) { + notifyError(title, description, debug) + } + + async function doLogin() { + if (!username || !password) return; + setBusy({}) + const testResult = await testLogin(username, password); + if (testResult.valid) { + backend.logIn({ username, password }); + } else { + if (testResult.requestError) { + const { cause } = testResult; + switch (cause.type) { + case ErrorType.CLIENT: { + if (cause.status === HttpStatusCode.Unauthorized) { + saveError({ + title: i18n.str`Wrong credentials for "${username}"`, + }); + } else + if (cause.status === HttpStatusCode.NotFound) { + saveError({ + title: i18n.str`Account not found`, + }); + } else { + saveError({ + title: i18n.str`Could not load due to a client error`, + description: cause.payload.error.description, + debug: JSON.stringify(cause.payload), + }); + } + break; + } + case ErrorType.SERVER: { + saveError({ + title: i18n.str`Server had a problem, try again later or report.`, + description: cause.payload.error.description, + debug: JSON.stringify(cause.payload), + }); + break; + } + case ErrorType.TIMEOUT: { + saveError({ + title: i18n.str`Request timeout, try again later.`, + }); + break; + } + case ErrorType.UNREADABLE: { + saveError({ + title: i18n.str`Unexpected error.`, + description: `Response from ${cause.info?.url} is unreadable, http status: ${cause.status}` as TranslatedString, + debug: JSON.stringify(cause), + }); + break; + } + default: { + saveError({ + title: i18n.str`Unexpected error, please report.`, + description: `Diagnostic from ${cause.info?.url} is "${cause.message}"` as TranslatedString, + debug: JSON.stringify(cause), + }); + break; + } + } + } else { + saveError({ + title: i18n.str`Unexpected error, please report.`, + debug: JSON.stringify(testResult.error), + }); + } + backend.logOut(); + } + setPassword(undefined); + setBusy(undefined) + } return ( <Fragment> - <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1> - {error && ( + <h1 class="nav"></h1> + {/* {error && ( <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} /> - )} - <div class="login-div"> - <form - class="login-form" - noValidate - onSubmit={(e) => { - e.preventDefault(); - }} - autoCapitalize="none" - autoCorrect="off" - > - <div class="pure-form"> - <h2>{i18n.str`Please login!`}</h2> - <p class="unameFieldLabel loginFieldLabel formFieldLabel"> - <label for="username">{i18n.str`Username:`}</label> - </p> - <input - ref={ref} - autoFocus - type="text" - name="username" - id="username" - value={username ?? ""} - enterkeyhint="next" - placeholder="Username" - autocomplete="username" - required - onInput={(e): void => { - setUsername(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errors?.username} - isDirty={username !== undefined} - /> - <p class="passFieldLabel loginFieldLabel formFieldLabel"> - <label for="password">{i18n.str`Password:`}</label> - </p> - <input - type="password" - name="password" - id="password" - autocomplete="current-password" - enterkeyhint="send" - value={password ?? ""} - placeholder="Password" - required - onInput={(e): void => { - setPassword(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errors?.password} - isDirty={password !== undefined} - /> - <br /> - <button - type="submit" - class="pure-button pure-button-primary" - disabled={!!errors} - onClick={async (e) => { - e.preventDefault(); - if (!username || !password) return; - const testResult = await testLogin(username, password); - if (testResult.valid) { - backend.logIn({ username, password }); - } else { - if (testResult.requestError) { - const { cause } = testResult; - switch (cause.type) { - case ErrorType.CLIENT: { - if (cause.status === HttpStatusCode.Unauthorized) { - saveError({ - title: i18n.str`Wrong credentials for "${username}"`, - }); - } - if (cause.status === HttpStatusCode.NotFound) { - saveError({ - title: i18n.str`Account not found`, - }); - } else { - saveError({ - title: i18n.str`Could not load due to a client error`, - description: cause.payload.error.description, - debug: JSON.stringify(cause.payload), - }); - } - break; - } - case ErrorType.SERVER: { - saveError({ - title: i18n.str`Server had a problem, try again later or report.`, - description: cause.payload.error.description, - debug: JSON.stringify(cause.payload), - }); - break; - } - case ErrorType.TIMEOUT: { - saveError({ - title: i18n.str`Request timeout, try again later.`, - }); - break; - } - case ErrorType.UNREADABLE: { - saveError({ - title: i18n.str`Unexpected error.`, - description: `Response from ${cause.info?.url} is unreadable, http status: ${cause.status}`, - debug: JSON.stringify(cause), - }); - break; - } - default: { - saveError({ - title: i18n.str`Unexpected error, please report.`, - description: `Diagnostic from ${cause.info?.url} is "${cause.message}"`, - debug: JSON.stringify(cause), - }); - break; - } - } - } else { - saveError({ - title: i18n.str`Unexpected error, please report.`, - debug: JSON.stringify(testResult.error), - }); - } - backend.logOut(); - } - setUsername(undefined); - setPassword(undefined); - }} - > - {i18n.str`Login`} - </button> - - {bankUiSettings.allowRegistrations && onRegister ? ( - <button - class="pure-button pure-button-secondary btn-cancel" + )} */} + + <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`Welcome to ${bankUiSettings.bankName}!`}</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> + </label> + <div class="mt-2"> + <input + ref={ref} + 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">Password</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> + <button type="submit" + class="flex w-full justify-center 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={(e) => { - e.preventDefault(); - onRegister(); + e.preventDefault() + doLogin() }} > - {i18n.str`Register`} + <i18n.Translate>Log in</i18n.Translate> </button> - ) : ( - <div /> - )} - </div> - </form> + </div> + </form> + + {bankUiSettings.allowRegistrations && onRegister && + <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() + onRegister() + }} + > + <i18n.Translate>Register</i18n.Translate> + </button> + </p> + } + </div> </div> + + </Fragment> ); } |