diff options
Diffstat (limited to 'packages/demobank-ui')
-rw-r--r-- | packages/demobank-ui/src/pages/LoginForm.tsx | 81 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/QrCodeSection.tsx | 72 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/RegistrationPage.tsx | 95 |
3 files changed, 71 insertions, 177 deletions
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index b351785d9..09c0a8785 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -20,9 +20,11 @@ import { assertUnreachable, } from "@gnu-taler/taler-util"; import { + Button, LocalNotificationBanner, ShowInputErrorLabel, useLocalNotification, + useLocalNotificationHandler, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; @@ -55,7 +57,7 @@ export function LoginForm({ const [password, setPassword] = useState<string | undefined>(); const { i18n } = useTranslationContext(); const { api } = useBankCoreApiContext(); - const [notification, notify, handleError] = useLocalNotification(); + const [notification, withErrorHandler] = useLocalNotificationHandler(); const { config } = useBankCoreApiContext(); const ref = useRef<HTMLInputElement>(null); @@ -63,8 +65,6 @@ export function LoginForm({ ref.current?.focus(); }, []); - const [busy, setBusy] = useState<Record<string, undefined>>(); - const errors = undefinedIfEmpty({ username: !username @@ -73,50 +73,31 @@ export function LoginForm({ // ? i18n.str`Use letters and numbers only, and start with a lowercase letter` undefined, password: !password ? i18n.str`Missing password` : undefined, - }) ?? busy; + }); async function doLogout() { backend.logOut(); } - async function doLogin() { - if (!username || !password) return; - setBusy({}); - await handleError(async () => { - const resp = await api - .getAuthenticationAPI(username) - .createAccessToken(password, { - // scope: "readwrite" as "write", // FIX: different than merchant - 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 HttpStatusCode.Unauthorized: - return notify({ - type: "error", - title: i18n.str`Wrong credentials for "${username}"`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case HttpStatusCode.NotFound: - return notify({ - type: "error", - title: i18n.str`Account not found`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - default: - assertUnreachable(resp); - } + const loginHandler = !username || !password ? undefined : withErrorHandler( + async () => api + .getAuthenticationAPI(username) + .createAccessToken(password, { + // scope: "readwrite" as "write", // FIX: different than merchant + scope: "readwrite", + duration: { d_us: "forever" }, + refreshable: true, + }), + (result) => { + backend.logIn({ username, token: result.body.access_token }) + }, + (fail) => { + switch (fail.case) { + case HttpStatusCode.Unauthorized: return i18n.str`Wrong credentials for "${username}"`; + case HttpStatusCode.NotFound: return i18n.str`Account not found`; } - }); - setPassword(undefined); - setBusy(undefined); - } + } + ) return ( <div class="flex min-h-full flex-col justify-center "> @@ -209,33 +190,27 @@ export function LoginForm({ <i18n.Translate>Cancel</i18n.Translate> </button> - <button + <Button type="submit" name="check" 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(); - doLogin(); - }} + handler={loginHandler} > <i18n.Translate>Check</i18n.Translate> - </button> + </Button> </div> ) : ( <div> - <button + <Button type="submit" name="login" 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(); - doLogin(); - }} + handler={loginHandler} > <i18n.Translate>Log in</i18n.Translate> - </button> + </Button> </div> )} </form> diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx index d20b269a8..7091214e0 100644 --- a/packages/demobank-ui/src/pages/QrCodeSection.tsx +++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx @@ -15,16 +15,15 @@ */ import { - assertUnreachable, HttpStatusCode, stringifyWithdrawUri, - TranslatedString, - WithdrawUriResult, + WithdrawUriResult } from "@gnu-taler/taler-util"; import { + Button, LocalNotificationBanner, - useLocalNotification, - useTranslationContext, + useLocalNotificationHandler, + useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { useEffect } from "preact/hooks"; @@ -50,47 +49,27 @@ export function QrCodeSection({ walletInegrationApi.publishTalerAction(withdrawUri); }, []); - const [notification, notify, handleError] = useLocalNotification(); + const [notification, handleError] = useLocalNotificationHandler(); const { api } = useBankCoreApiContext(); - async function doAbort() { - await handleError(async () => { - if (!creds) return; - const resp = await api.abortWithdrawalById( + const onAbortHandler = handleError( + async () => { + if (!creds) return undefined; + return api.abortWithdrawalById( creds, withdrawUri.withdrawalOperationId, - ); - if (resp.type === "ok") { - onAborted(); - } else { - switch (resp.case) { - case HttpStatusCode.Conflict: - return notify({ - type: "error", - title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`, - }); - case HttpStatusCode.BadRequest: - return notify({ - type: "error", - title: i18n.str`The operation id is invalid.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case HttpStatusCode.NotFound: - return notify({ - type: "error", - title: i18n.str`The operation was not found.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - default: { - assertUnreachable(resp); - } - } + ) + }, + onAborted, + (fail) => { + switch (fail.case) { + case HttpStatusCode.BadRequest: return i18n.str`The operation id is invalid.`; + case HttpStatusCode.NotFound: return i18n.str`The operation was not found.`; + case HttpStatusCode.Conflict: return i18n.str`The reserve operation has been confirmed previously and can't be aborted`; } - }); - } + } + ) return ( <Fragment> @@ -120,15 +99,14 @@ export function QrCodeSection({ </p> </div> <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 pt-2 mt-2 "> - <button + <Button type="button" name="cancel" - // class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md px-3 py-2 text-sm font-semibold text-black shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600" class="text-sm font-semibold leading-6 text-gray-900" - onClick={doAbort} + handler={onAbortHandler} > <i18n.Translate>Cancel</i18n.Translate> - </button> + </Button> <a href={talerWithdrawUri} name="withdraw" @@ -157,14 +135,14 @@ export function QrCodeSection({ </div> </div> <div class="flex items-center justify-center gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8"> - <button + <Button type="button" // class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md px-3 py-2 text-sm font-semibold text-black shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600" class="text-sm font-semibold leading-6 text-gray-900" - onClick={doAbort} + handler={onAbortHandler} > <i18n.Translate>Cancel</i18n.Translate> - </button> + </Button> </div> </div> </Fragment> diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx index 87e284411..18b4c470b 100644 --- a/packages/demobank-ui/src/pages/RegistrationPage.tsx +++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx @@ -16,6 +16,7 @@ import { AccessToken, HttpStatusCode, + OperationFail, TalerErrorCode, TranslatedString, assertUnreachable, @@ -30,7 +31,7 @@ import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { useBankCoreApiContext } from "../context/config.js"; import { useSettingsContext } from "../context/settings.js"; -import { EmptyObject, RouteDefinition } from "../route.js"; +import { RouteDefinition } from "../route.js"; import { undefinedIfEmpty } from "../utils.js"; import { getRandomPassword, getRandomUsername } from "./rnd.js"; @@ -76,7 +77,7 @@ function RegistrationForm({ // const [phone, setPhone] = useState<string | undefined>(); // const [email, setEmail] = useState<string | undefined>(); const [repeatPassword, setRepeatPassword] = useState<string | undefined>(); - const [notification, notify, handleError] = useLocalNotification(); + const [notification, _, handleError] = useLocalNotification(); const settings = useSettingsContext(); const { api } = useBankCoreApiContext(); @@ -114,7 +115,7 @@ function RegistrationForm({ password: string, onComplete: () => void, ) { - await handleError(async () => { + await handleError(async (onError) => { const resp = await api.createAccount("" as AccessToken, { name, username, @@ -123,80 +124,20 @@ function RegistrationForm({ if (resp.type === "ok") { onComplete(); } else { - switch (resp.case) { - case HttpStatusCode.BadRequest: - return notify({ - type: "error", - title: i18n.str`Server replied with invalid phone or email.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_UNALLOWED_DEBIT: - return notify({ - type: "error", - title: i18n.str`Registration is disabled because the bank ran out of bonus credit.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case HttpStatusCode.Unauthorized: - return notify({ - type: "error", - title: i18n.str`No enough permission to create that account.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: - return notify({ - type: "error", - title: i18n.str`That account id is already taken.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: - return notify({ - type: "error", - title: i18n.str`That username is already taken.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: - return notify({ - type: "error", - title: i18n.str`That username can't be used because is reserved.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: - return notify({ - type: "error", - title: i18n.str`Only admin is allow to set debt limit.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_MISSING_TAN_INFO: - return notify({ - type: "error", - title: i18n.str`No information for the selected authentication channel.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: - return notify({ - type: "error", - title: i18n.str`Authentication channel is not supported.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL: - return notify({ - type: "error", - title: i18n.str`Only admin can create accounts with second factor authentication.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - default: - assertUnreachable(resp); - } + onError(resp, (_case) => { + switch(_case) { + case HttpStatusCode.BadRequest: return i18n.str`Server replied with invalid phone or email.`; + case HttpStatusCode.Unauthorized: return i18n.str`No enough permission to create that account.`; + case TalerErrorCode.BANK_UNALLOWED_DEBIT: return i18n.str`Registration is disabled because the bank ran out of bonus credit.`; + case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return i18n.str`That username can't be used because is reserved.`; + case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return i18n.str`That username is already taken.`; + case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return i18n.str`That account id is already taken.`; + case TalerErrorCode.BANK_MISSING_TAN_INFO: return i18n.str`No information for the selected authentication channel.`; + case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: return i18n.str`Authentication channel is not supported.`; + case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return i18n.str`Only admin is allow to set debt limit.`; + case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL: return i18n.str`Only admin can create accounts with second factor authentication.`; + } + }) } }); } |