diff options
Diffstat (limited to 'packages/challenger-ui/src/pages')
-rw-r--r-- | packages/challenger-ui/src/pages/AnswerChallenge.tsx | 87 | ||||
-rw-r--r-- | packages/challenger-ui/src/pages/AskChallenge.tsx | 138 | ||||
-rw-r--r-- | packages/challenger-ui/src/pages/StartChallenge.tsx | 138 |
3 files changed, 59 insertions, 304 deletions
diff --git a/packages/challenger-ui/src/pages/AnswerChallenge.tsx b/packages/challenger-ui/src/pages/AnswerChallenge.tsx index 69600e2ba..bad6d70de 100644 --- a/packages/challenger-ui/src/pages/AnswerChallenge.tsx +++ b/packages/challenger-ui/src/pages/AnswerChallenge.tsx @@ -16,15 +16,16 @@ import { ChallengerApi, HttpStatusCode, - assertUnreachable, + assertUnreachable } from "@gnu-taler/taler-util"; import { + Attention, Button, LocalNotificationBanner, ShowInputErrorLabel, useChallengerApiContext, useLocalNotificationHandler, - useTranslationContext, + useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; @@ -37,13 +38,7 @@ type Props = { onComplete: () => void; }; -function SolveChallengeForm({ - nonce, - onComplete, -}: { - nonce: string; - onComplete: () => void; -}): VNode { +export function AnswerChallenge({ nonce, onComplete }: Props): VNode { const { lib } = useChallengerApiContext(); const { i18n } = useTranslationContext(); const { state, accepted, completed } = useSessionState(); @@ -64,8 +59,8 @@ function SolveChallengeForm({ return await lib.bank.challenge(nonce, { email: state.email }); }, (ok) => { - if ('redirectURL' in ok.body) { - completed(ok.body.redirectURL) + if ("redirectURL" in ok.body) { + completed(ok.body.redirectURL); } else { accepted(state.email!, { attemptsLeft: ok.body.attempts_left, @@ -99,7 +94,7 @@ function SolveChallengeForm({ return lib.bank.solve(nonce, { pin: pin! }); }, (ok) => { - completed(ok.body.redirectURL as URL) + completed(ok.body.redirectURL as URL); onComplete(); }, (fail) => { @@ -149,11 +144,13 @@ function SolveChallengeForm({ A TAN was sent to your address "{state.email}". </i18n.Translate> ) : ( - <i18n.Translate> - We recently already sent a TAN to your address " - {state.email}". A new TAN will not be transmitted again - before {state.lastTry.nextSend}. - </i18n.Translate> + <Attention title={i18n.str`Resend failed`} type="warning"> + <i18n.Translate> + We recently already sent a TAN to your address " + {state.email}". A new TAN will not be transmitted again + before "{state.lastTry.nextSend}". + </i18n.Translate> + </Attention> )} </p> {!lastTryError ? undefined : ( @@ -230,61 +227,7 @@ function SolveChallengeForm({ </form> </div> </Fragment> - ); -} - -export function AnswerChallenge({ nonce, onComplete }: Props): VNode { - const { i18n } = useTranslationContext(); - - // const result = useChallengeSession(nonce, clientId, redirectURI, state); - - // if (!result) { - // return <Loading />; - // } - // if (result instanceof TalerError) { - // return <div />; - // } - - // if (result.type === "fail") { - // switch (result.case) { - // case HttpStatusCode.BadRequest: { - // return ( - // <Attention type="danger" title={i18n.str`Bad request`}> - // <i18n.Translate> - // Could not start the challenge, check configuration. - // </i18n.Translate> - // </Attention> - // ); - // } - // case HttpStatusCode.NotFound: { - // return ( - // <Attention type="danger" title={i18n.str`Not found`}> - // <i18n.Translate>Nonce not found</i18n.Translate> - // </Attention> - // ); - // } - // case HttpStatusCode.NotAcceptable: { - // return ( - // <Attention type="danger" title={i18n.str`Not acceptable`}> - // <i18n.Translate> - // Server has wrong template configuration - // </i18n.Translate> - // </Attention> - // ); - // } - // case HttpStatusCode.InternalServerError: { - // return ( - // <Attention type="danger" title={i18n.str`Internal error`}> - // <i18n.Translate>Check logs</i18n.Translate> - // </Attention> - // ); - // } - // default: - // assertUnreachable(result); - // } - // } - - return <SolveChallengeForm nonce={nonce} onComplete={onComplete} />; + ) } export function undefinedIfEmpty<T extends object>(obj: T): T | undefined { diff --git a/packages/challenger-ui/src/pages/AskChallenge.tsx b/packages/challenger-ui/src/pages/AskChallenge.tsx index 71f45dde3..675e2b869 100644 --- a/packages/challenger-ui/src/pages/AskChallenge.tsx +++ b/packages/challenger-ui/src/pages/AskChallenge.tsx @@ -14,24 +14,20 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ import { + HttpStatusCode +} from "@gnu-taler/taler-util"; +import { Attention, Button, - Loading, LocalNotificationBanner, + RouteDefinition, ShowInputErrorLabel, useChallengerApiContext, useLocalNotificationHandler, - useTranslationContext, + useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { useChallengeSession } from "../hooks/challenge.js"; -import { - ChallengerApi, - HttpStatusCode, - TalerError, - assertUnreachable, -} from "@gnu-taler/taler-util"; import { useSessionState } from "../hooks/session.js"; type Form = { @@ -42,33 +38,29 @@ export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/; type Props = { nonce: string; onSendSuccesful: () => void; + routeSolveChallenge: RouteDefinition<{nonce:string}>, }; -function ChallengeForm({ - nonce, - status, - onSendSuccesful, -}: { - nonce: string; - status: ChallengerApi.ChallengeStatus; - onSendSuccesful: () => void; -}): VNode { - const prevEmail = !status.last_address - ? undefined - : ((status.last_address as any)["email"] as string); - const regexEmail = !status.restrictions - ? undefined - : ((status.restrictions as any)["email"] as { - regex?: string; - hint?: string; - hint_i18n?: string; - }); +export function AskChallenge({ nonce, onSendSuccesful,routeSolveChallenge }: Props): VNode { + const { state, accepted, completed } = useSessionState(); + const status = state?.challengeStatus; + const prevEmail = + !status || !status.last_address + ? undefined + : ((status.last_address as any)["email"] as string); + const regexEmail = + !status || !status.restrictions + ? undefined + : ((status.restrictions as any)["email"] as { + regex?: string; + hint?: string; + hint_i18n?: string; + }); const { lib } = useChallengerApiContext(); const { i18n } = useTranslationContext(); const [notification, withErrorHandler] = useLocalNotificationHandler(); const [email, setEmail] = useState<string | undefined>(prevEmail); - const { accepted, completed } = useSessionState(); const [repeat, setRepeat] = useState<string | undefined>(); const errors = undefinedIfEmpty({ @@ -82,10 +74,12 @@ function ChallengeForm({ : undefined : !EMAIL_REGEX.test(email) ? i18n.str`invalid email` - : email !== repeat - ? i18n.str`emails don't match` - : undefined, - repeat: !repeat ? i18n.str`required` : undefined, + : undefined, + repeat: !repeat + ? i18n.str`required` + : email !== repeat + ? i18n.str`emails doesn't match` + : undefined, }); const onSend = withErrorHandler( @@ -120,6 +114,10 @@ function ChallengeForm({ }, ); + if (!status) { + return <div>no status loaded</div>; + } + return ( <Fragment> <LocalNotificationBanner notification={notification} /> @@ -136,6 +134,15 @@ function ChallengeForm({ </i18n.Translate> </p> </div> + {state.lastTry && ( + <Fragment> + <Attention title={i18n.str`A code has been sent to ${state.email}`}> + <i18n.Translate> + You can change the destination or <a href={routeSolveChallenge.url({nonce })}><i18n.Translate>complete the challenge here</i18n.Translate></a>. + </i18n.Translate> + </Attention> + </Fragment> + )} <form method="POST" class="mx-auto mt-16 max-w-xl sm:mt-20" @@ -191,6 +198,10 @@ function ChallengeForm({ autocomplete="email" class="block w-full rounded-md border-0 px-3.5 py-2 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" /> + <ShowInputErrorLabel + message={errors?.repeat} + isDirty={repeat !== undefined} + /> </div> </div> @@ -217,67 +228,6 @@ function ChallengeForm({ ); } -export function AskChallenge({ nonce, onSendSuccesful }: Props): VNode { - const { i18n } = useTranslationContext(); - const { state } = useSessionState(); - - const result = useChallengeSession(nonce, state); - - if (!result) { - return <Loading />; - } - if (result instanceof TalerError) { - return <div />; - } - - if (result.type === "fail") { - switch (result.case) { - case HttpStatusCode.BadRequest: { - return ( - <Attention type="danger" title={i18n.str`Bad request`}> - <i18n.Translate> - Could not start the challenge, check configuration. - </i18n.Translate> - </Attention> - ); - } - case HttpStatusCode.NotFound: { - return ( - <Attention type="danger" title={i18n.str`Not found`}> - <i18n.Translate>Nonce not found</i18n.Translate> - </Attention> - ); - } - case HttpStatusCode.NotAcceptable: { - return ( - <Attention type="danger" title={i18n.str`Not acceptable`}> - <i18n.Translate> - Server has wrong template configuration - </i18n.Translate> - </Attention> - ); - } - case HttpStatusCode.InternalServerError: { - return ( - <Attention type="danger" title={i18n.str`Internal error`}> - <i18n.Translate>Check logs</i18n.Translate> - </Attention> - ); - } - default: - assertUnreachable(result); - } - } - - return ( - <ChallengeForm - nonce={nonce} - status={result.body} - onSendSuccesful={onSendSuccesful} - /> - ); -} - export function undefinedIfEmpty<T extends object>(obj: T): T | undefined { return Object.keys(obj).some( (k) => (obj as Record<string, T>)[k] !== undefined, diff --git a/packages/challenger-ui/src/pages/StartChallenge.tsx b/packages/challenger-ui/src/pages/StartChallenge.tsx deleted file mode 100644 index 6cf982a3d..000000000 --- a/packages/challenger-ui/src/pages/StartChallenge.tsx +++ /dev/null @@ -1,138 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022-2024 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 { - Attention, - Button, - Loading, - LocalNotificationBanner, - ShowInputErrorLabel, - useChallengerApiContext, - useLocalNotificationHandler, - useTranslationContext, -} from "@gnu-taler/web-util/browser"; -import { Fragment, VNode, h } from "preact"; -import { useEffect, useState } from "preact/hooks"; -import { useChallengeSession } from "../hooks/challenge.js"; -import { - ChallengerApi, - HttpStatusCode, - TalerError, - assertUnreachable, -} from "@gnu-taler/taler-util"; -import { useSessionState } from "../hooks/session.js"; - -type Form = { - email: string; -}; -export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/; - -type Props = { - nonce: string; - clientId: string; - redirectURL: URL; - state: string; - onSendSuccesful: () => void; -}; - - -export function StartChallenge({ - nonce, - clientId, - redirectURL, - state, - onSendSuccesful, -}: Props): VNode { - const { i18n } = useTranslationContext(); - const { start } = useSessionState(); - - const result = useChallengeSession(nonce, { - clientId, - redirectURL: redirectURL.href, - state, - }); - - const session = - result && !(result instanceof TalerError) && result.type === "ok" - ? result.body - : undefined; - - useEffect(() => { - if (session) { - start({ - clientId, - redirectURL: redirectURL.href, - state, - }); - onSendSuccesful(); - } - }, [session]); - - if (!result) { - return <Loading />; - } - if (result instanceof TalerError) { - return <div />; - } - - if (result.type === "fail") { - switch (result.case) { - case HttpStatusCode.BadRequest: { - return ( - <Attention type="danger" title={i18n.str`Bad request`}> - <i18n.Translate> - Could not start the challenge, check configuration. - </i18n.Translate> - </Attention> - ); - } - case HttpStatusCode.NotFound: { - return ( - <Attention type="danger" title={i18n.str`Not found`}> - <i18n.Translate>Nonce not found</i18n.Translate> - </Attention> - ); - } - case HttpStatusCode.NotAcceptable: { - return ( - <Attention type="danger" title={i18n.str`Not acceptable`}> - <i18n.Translate> - Server has wrong template configuration - </i18n.Translate> - </Attention> - ); - } - case HttpStatusCode.InternalServerError: { - return ( - <Attention type="danger" title={i18n.str`Internal error`}> - <i18n.Translate>Check logs</i18n.Translate> - </Attention> - ); - } - default: - assertUnreachable(result); - } - } - - return <Loading />; -} - -export function undefinedIfEmpty<T extends object>(obj: T): T | undefined { - return Object.keys(obj).some( - (k) => (obj as Record<string, T>)[k] !== undefined, - ) - ? obj - : undefined; -} |