diff options
Diffstat (limited to 'packages/challenger-ui/src')
-rw-r--r-- | packages/challenger-ui/src/Routing.tsx | 1 | ||||
-rw-r--r-- | packages/challenger-ui/src/app.tsx | 25 | ||||
-rw-r--r-- | packages/challenger-ui/src/hooks/challenge.ts | 2 | ||||
-rw-r--r-- | packages/challenger-ui/src/pages/AnswerChallenge.tsx | 44 | ||||
-rw-r--r-- | packages/challenger-ui/src/pages/AskChallenge.tsx | 135 | ||||
-rw-r--r-- | packages/challenger-ui/src/pages/Setup.tsx | 2 |
6 files changed, 132 insertions, 77 deletions
diff --git a/packages/challenger-ui/src/Routing.tsx b/packages/challenger-ui/src/Routing.tsx index f1f4d82d2..6711e4cae 100644 --- a/packages/challenger-ui/src/Routing.tsx +++ b/packages/challenger-ui/src/Routing.tsx @@ -234,6 +234,7 @@ function PublicRounting(): VNode { > <AnswerChallenge nonce={location.values.nonce} + routeAsk={publicPages.ask} onComplete={() => { navigateTo( publicPages.completed.url({ diff --git a/packages/challenger-ui/src/app.tsx b/packages/challenger-ui/src/app.tsx index d85893c07..2b5c5c815 100644 --- a/packages/challenger-ui/src/app.tsx +++ b/packages/challenger-ui/src/app.tsx @@ -15,6 +15,9 @@ */ import { + CacheEvictor, + ChallengerCacheEviction, + assertUnreachable, canonicalizeBaseUrl, getGlobalLogLevel, setGlobalLogLevelFromString, @@ -33,13 +36,28 @@ import { Routing } from "./Routing.js"; // import { BrowserHashNavigationProvider } from "./context/navigation.js"; import { SettingsProvider } from "./context/settings.js"; // import { TalerWalletIntegrationBrowserProvider } from "./context/wallet-integration.js"; -import { h } from "preact"; +import { VNode, h } from "preact"; import { strings } from "./i18n/strings.js"; import { ChallengerUiSettings, fetchSettings } from "./settings.js"; import { Frame } from "./pages/Frame.js"; +import { revalidateChallengeSession } from "./hooks/challenge.js"; const WITH_LOCAL_STORAGE_CACHE = false; -export function App() { +const evictBankSwrCache: CacheEvictor<ChallengerCacheEviction> = { + async notifySuccess(op) { + switch (op) { + case ChallengerCacheEviction.CREATE_CHALLENGE: { + await Promise.all([revalidateChallengeSession()]); + return; + } + default: { + assertUnreachable(op); + } + } + }, +}; + +export function App(): VNode { const [settings, setSettings] = useState<ChallengerUiSettings>(); useEffect(() => { fetchSettings(setSettings); @@ -60,6 +78,9 @@ export function App() { <ChallengerApiProvider baseUrl={new URL("/", baseUrl)} frameOnError={Frame} + evictors={{ + challenger: evictBankSwrCache, + }} > <SWRConfig value={{ diff --git a/packages/challenger-ui/src/hooks/challenge.ts b/packages/challenger-ui/src/hooks/challenge.ts index 3df10e21e..846242816 100644 --- a/packages/challenger-ui/src/hooks/challenge.ts +++ b/packages/challenger-ui/src/hooks/challenge.ts @@ -35,7 +35,7 @@ export function useChallengeSession( session: SessionId | undefined, ) { const { - lib: { bank: api }, + lib: { challenger: api }, } = useChallengerApiContext(); async function fetcher([n, c, r, s]: [string, string, string, string]) { diff --git a/packages/challenger-ui/src/pages/AnswerChallenge.tsx b/packages/challenger-ui/src/pages/AnswerChallenge.tsx index 62b7e775d..5e7973b3d 100644 --- a/packages/challenger-ui/src/pages/AnswerChallenge.tsx +++ b/packages/challenger-ui/src/pages/AnswerChallenge.tsx @@ -22,6 +22,7 @@ import { Attention, Button, LocalNotificationBanner, + RouteDefinition, ShowInputErrorLabel, useChallengerApiContext, useLocalNotificationHandler, @@ -36,9 +37,10 @@ export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/; type Props = { nonce: string; onComplete: () => void; + routeAsk: RouteDefinition<{ nonce: string }>; }; -export function AnswerChallenge({ nonce, onComplete }: Props): VNode { +export function AnswerChallenge({ nonce, onComplete, routeAsk }: Props): VNode { const { lib } = useChallengerApiContext(); const { i18n } = useTranslationContext(); const { state, accepted, completed } = useSessionState(); @@ -54,7 +56,9 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode { ? undefined : !state.lastStatus ? undefined - : ((state.lastStatus.last_address as any)["email"] as string); + : !state.lastStatus.last_address + ? undefined + : state.lastStatus.last_address["email"]; const onSendAgain = !state || lastEmail === undefined @@ -62,7 +66,7 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode { : withErrorHandler( async () => { if (!lastEmail) return; - return await lib.bank.challenge(nonce, { email: lastEmail }); + return await lib.challenger.challenge(nonce, { email: lastEmail }); }, (ok) => { if ("redirectURL" in ok.body) { @@ -93,11 +97,11 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode { ); const onCheck = - lastTryError && lastTryError.exhausted + errors !== undefined || (lastTryError && lastTryError.exhausted) ? undefined : withErrorHandler( async () => { - return lib.bank.solve(nonce, { pin: pin! }); + return lib.challenger.solve(nonce, { pin: pin! }); }, (ok) => { completed(ok.body.redirectURL as URL); @@ -141,7 +145,7 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode { <div class="mx-auto max-w-2xl text-center"> <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl"> <i18n.Translate> - Please enter the TAN you received to authenticate. + Enter the TAN you received to authenticate. </i18n.Translate> </h2> <p class="mt-2 text-lg leading-8 text-gray-600"> @@ -220,15 +224,25 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode { <i18n.Translate>Check</i18n.Translate> </Button> </div> - <div class="mt-10"> - <Button - type="submit" - disabled={!onSendAgain} - class="block w-full disabled:bg-gray-300 rounded-md bg-indigo-600 px-3.5 py-2.5 text-center text-sm font-semibold 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" - handler={onSendAgain} - > - <i18n.Translate>Send again</i18n.Translate> - </Button> + <div class="mt-10 flex justify-between"> + <div> + <a + href={routeAsk.url({ nonce })} + class="relative disabled:bg-gray-100 disabled:text-gray-500 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0" + > + <i18n.Translate>Change email</i18n.Translate> + </a> + </div> + <div> + <Button + type="submit" + disabled={!onSendAgain} + class="block w-full disabled:bg-gray-300 rounded-md bg-indigo-600 px-3.5 py-2.5 text-center text-sm font-semibold 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" + handler={onSendAgain} + > + <i18n.Translate>Send code again</i18n.Translate> + </Button> + </div> </div> </form> </div> diff --git a/packages/challenger-ui/src/pages/AskChallenge.tsx b/packages/challenger-ui/src/pages/AskChallenge.tsx index 76fe6f00a..aaca51db7 100644 --- a/packages/challenger-ui/src/pages/AskChallenge.tsx +++ b/packages/challenger-ui/src/pages/AskChallenge.tsx @@ -49,27 +49,26 @@ export function AskChallenge({ const prevEmail = !status || !status.last_address ? undefined : status.last_address["email"]; const regexEmail = - !status || !status.restrictions - ? undefined - : status.restrictions["email"]; + !status || !status.restrictions ? undefined : status.restrictions["email"]; const { lib } = useChallengerApiContext(); const { i18n } = useTranslationContext(); const [notification, withErrorHandler] = useLocalNotificationHandler(); - const [email, setEmail] = useState<string | undefined>(prevEmail); + const [email, setEmail] = useState<string | undefined>(); const [repeat, setRepeat] = useState<string | undefined>(); + const regexTest = + regexEmail && regexEmail.regex ? new RegExp(regexEmail.regex) : EMAIL_REGEX; + const regexHint = + regexEmail && regexEmail.hint ? regexEmail.hint : i18n.str`invalid email`; + const errors = undefinedIfEmpty({ email: !email ? i18n.str`required` - : regexEmail && regexEmail.regex - ? !new RegExp(regexEmail.regex).test(email) - ? regexEmail.hint - ? regexEmail.hint - : `invalid` - : undefined - : !EMAIL_REGEX.test(email) - ? i18n.str`invalid email` + : !regexTest.test(email) + ? regexHint + : prevEmail !== undefined && email === prevEmail + ? i18n.str`email should be different` : undefined, repeat: !repeat ? i18n.str`required` @@ -82,7 +81,7 @@ export function AskChallenge({ ? undefined : withErrorHandler( async () => { - return lib.bank.challenge(nonce, { email: email! }); + return lib.challenger.challenge(nonce, { email: email! }); }, (ok) => { if ("redirectURL" in ok.body) { @@ -136,11 +135,9 @@ export function AskChallenge({ <Fragment> <Attention title={i18n.str`A code has been sent to ${prevEmail}`}> <i18n.Translate> - You can change the destination or{" "} - <a href={routeSolveChallenge.url({ nonce })}> - <i18n.Translate>complete the challenge here</i18n.Translate> + <a href={routeSolveChallenge.url({ nonce })} class="underline"> + <i18n.Translate>Complete the challenge here.</i18n.Translate> </a> - . </i18n.Translate> </Attention> </Fragment> @@ -171,8 +168,9 @@ export function AskChallenge({ onChange={(e) => { setEmail(e.currentTarget.value); }} + placeholder={prevEmail} readOnly={status.fix_address} - 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" + class="block w-full read-only:bg-slate-200 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?.email} @@ -181,50 +179,71 @@ export function AskChallenge({ </div> </div> - <div class="sm:col-span-2"> - <label - for="repeat-email" - class="block text-sm font-semibold leading-6 text-gray-900" - > - <i18n.Translate>Repeat email</i18n.Translate> - </label> - <div class="mt-2.5"> - <input - type="email" - name="repeat-email" - id="repeat-email" - value={repeat} - onChange={(e) => { - setRepeat(e.currentTarget.value); - }} - 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} - /> + {status.fix_address ? undefined : ( + <div class="sm:col-span-2"> + <label + for="repeat-email" + class="block text-sm font-semibold leading-6 text-gray-900" + > + <i18n.Translate>Repeat email</i18n.Translate> + </label> + <div class="mt-2.5"> + <input + type="email" + name="repeat-email" + id="repeat-email" + value={repeat} + onChange={(e) => { + setRepeat(e.currentTarget.value); + }} + 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> - </div> + )} - <p class="mt-3 text-sm leading-6 text-gray-400"> - <i18n.Translate> - You can change your email address another {status.changes_left}{" "} - times. - </i18n.Translate> - </p> + {!status.changes_left ? ( + <p class="mt-3 text-sm leading-6 text-gray-400"> + <i18n.Translate>No more changes left</i18n.Translate> + </p> + ) : ( + <p class="mt-3 text-sm leading-6 text-gray-400"> + <i18n.Translate> + You can change your email address another{" "} + {status.changes_left} times. + </i18n.Translate> + </p> + )} </div> - <div class="mt-10"> - <Button - type="submit" - disabled={!onSend} - class="block w-full disabled:bg-gray-300 rounded-md bg-indigo-600 px-3.5 py-2.5 text-center text-sm font-semibold 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" - handler={onSend} - > - <i18n.Translate>Send email</i18n.Translate> - </Button> - </div> + {!prevEmail ? ( + <div class="mt-10"> + <Button + type="submit" + disabled={!onSend} + class="block w-full disabled:bg-gray-300 rounded-md bg-indigo-600 px-3.5 py-2.5 text-center text-sm font-semibold 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" + handler={onSend} + > + <i18n.Translate>Send email</i18n.Translate> + </Button> + </div> + ) : ( + <div class="mt-10"> + <Button + type="submit" + disabled={!onSend} + class="block w-full disabled:bg-gray-300 rounded-md bg-indigo-600 px-3.5 py-2.5 text-center text-sm font-semibold 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" + handler={onSend} + > + <i18n.Translate>Change email</i18n.Translate> + </Button> + </div> + )} </form> </div> </Fragment> diff --git a/packages/challenger-ui/src/pages/Setup.tsx b/packages/challenger-ui/src/pages/Setup.tsx index 400c9b780..f431835aa 100644 --- a/packages/challenger-ui/src/pages/Setup.tsx +++ b/packages/challenger-ui/src/pages/Setup.tsx @@ -36,7 +36,7 @@ export function Setup({ clientId, onCreated }: Props): VNode { const onStart = withErrorHandler( async () => { - return lib.bank.setup(clientId, "secret-token:chal-secret" as AccessToken); + return lib.challenger.setup(clientId, "secret-token:chal-secret" as AccessToken); }, (ok) => { start({ |