diff options
Diffstat (limited to 'packages/challenger-ui')
46 files changed, 3478 insertions, 0 deletions
diff --git a/packages/challenger-ui/.gitignore b/packages/challenger-ui/.gitignore new file mode 100644 index 000000000..30cb2774c --- /dev/null +++ b/packages/challenger-ui/.gitignore @@ -0,0 +1,4 @@ +node_modules +/build +/*.log +/demobank-ui-settings.js diff --git a/packages/challenger-ui/Makefile b/packages/challenger-ui/Makefile new file mode 100644 index 000000000..64f9f83d1 --- /dev/null +++ b/packages/challenger-ui/Makefile @@ -0,0 +1,36 @@ +# This Makefile has been placed in the public domain + +ifeq ($(TOPLEVEL), yes) + $(info top-level build) + -include ../../.config.mk + override DESTDIR := $(TOP_DESTDIR) +else + $(info package-level build) + -include ../../.config.mk + -include .config.mk +endif + +$(info prefix is $(prefix)) + +.PHONY: all +all: + @echo run \'make install\' to install + +spa_dir=$(DESTDIR)$(prefix)/share/taler/aml-backoffice-ui + +.PHONY: install-nodeps +install-nodeps: + install -d $(spa_dir) + install ./dist/prod/* $(spa_dir) + +.PHONY: deps +deps: + pnpm install --frozen-lockfile --filter @gnu-taler/aml-backoffice-ui... + pnpm run check + pnpm run build + +.PHONY: install +install: + $(MAKE) deps + $(MAKE) install-nodeps + diff --git a/packages/challenger-ui/README.md b/packages/challenger-ui/README.md new file mode 100644 index 000000000..ac589ace6 --- /dev/null +++ b/packages/challenger-ui/README.md @@ -0,0 +1,4 @@ +# Challenger Backoffice UI + +Web-based user interface for the GNU Challenger. + diff --git a/packages/challenger-ui/build.mjs b/packages/challenger-ui/build.mjs new file mode 100755 index 000000000..166647f79 --- /dev/null +++ b/packages/challenger-ui/build.mjs @@ -0,0 +1,43 @@ +#!/usr/bin/env node +/* + 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 { build } from "@gnu-taler/web-util/build"; + +await build({ + type: "production", + source: { + js: ["src/main.js","src/index.tsx"], + assets: [{ + base: "src", + files: [ + "src/index.html", + "src/attempts-exhausted.html", + "src/enter-address-form.html", + "src/enter-email-form.html", + "src/enter-file-access-form.html", + "src/enter-phone-form.html", + "src/enter-tan-form.html", + "src/internal-error.html", + "src/invalid-pin.html", + "src/invalid-request.html", + "src/validation-unknown.html", + ] + }], + }, + destination: "./dist/prod", + css: "postcss", +}); diff --git a/packages/challenger-ui/copyleft-header.js b/packages/challenger-ui/copyleft-header.js new file mode 100644 index 000000000..7fa276bea --- /dev/null +++ b/packages/challenger-ui/copyleft-header.js @@ -0,0 +1,15 @@ +/* + 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/> + */ diff --git a/packages/challenger-ui/create_must.sh b/packages/challenger-ui/create_must.sh new file mode 100755 index 000000000..a4d78b2cc --- /dev/null +++ b/packages/challenger-ui/create_must.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# This file is in the public domain. + +# After the compilation succeeded +# some changes needs to be made +# in the html/js files to match the +# what the service expects + +cd dist/prod + +for file in *.html; do + + # 1. remove the js reference used for dev + sed /main.js/d -i $file + + # 2. change the location of css since + #challenger backend wants them in the root path + sed 's/="main.css"/="..\/main.css"/' -i $file + + # 3. rename the extension to must template + mv $file ${file:0:-5}.en.must +done + +#delete unused files +rm *.js *.map diff --git a/packages/challenger-ui/dev.mjs b/packages/challenger-ui/dev.mjs new file mode 100755 index 000000000..595c3e99e --- /dev/null +++ b/packages/challenger-ui/dev.mjs @@ -0,0 +1,56 @@ +#!/usr/bin/env node +/* + 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 { serve } from "@gnu-taler/web-util/node"; +import { initializeDev } from "@gnu-taler/web-util/build"; + +const devEntryPoints = ["src/index.tsx", "src/main.js"]; + +const build = initializeDev({ + type: "development", + source: { + js: devEntryPoints, + assets: [{ + base: "src", + files: [ + "src/index.html", + "src/attempts-exhausted.html", + "src/enter-address-form.html", + "src/enter-email-form.html", + "src/enter-file-access-form.html", + "src/enter-phone-form.html", + "src/enter-tan-form.html", + "src/internal-error.html", + "src/invalid-pin.html", + "src/invalid-request.html", + "src/validation-unknown.html", + ] + }], + }, + destination: "./dist/dev", + public: "/app", + css: "postcss", +}); + +await build(); + +serve({ + folder: "./dist/dev", + port: 8080, + source: "./src", + onSourceUpdate: build, +}); diff --git a/packages/challenger-ui/package.json b/packages/challenger-ui/package.json new file mode 100644 index 000000000..8234e2385 --- /dev/null +++ b/packages/challenger-ui/package.json @@ -0,0 +1,67 @@ +{ + "private": true, + "name": "@gnu-taler/challenger-ui", + "version": "0.10.7", + "author": "sebasjm", + "license": "AGPL-3.0-OR-LATER", + "description": "UI for GNU Challenger.", + "type": "module", + "scripts": { + "build": "./build.mjs && ./create_must.sh", + "check": "tsc", + "compile": "tsc && ./build.mjs", + "test": "./test.mjs && mocha --require source-map-support/register 'dist/test/**/*.test.js' 'dist/test/**/test.js'", + "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'", + "clean": "rm -rf dist lib tsconfig.tsbuildinfo", + "i18n:strings": "pogen extract && pogen merge", + "i18n:translations": "pogen emit", + "pretty": "prettier --write src" + }, + "eslintConfig": { + "plugins": [ + "header" + ], + "rules": { + "header/header": [ + 2, + "copyleft-header.js" + ] + }, + "extends": [ + "prettier" + ] + }, + "devDependencies": { + "eslint": "^8.56.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.33.2", + "@gnu-taler/pogen": "workspace:*", + "@tailwindcss/forms": "^0.5.3", + "@tailwindcss/typography": "^0.5.9", + "@types/chai": "^4.3.0", + "@types/history": "^4.7.8", + "@types/mocha": "^10.0.1", + "@types/node": "^18.11.17", + "autoprefixer": "^10.4.14", + "chai": "^4.3.6", + "esbuild": "^0.19.9", + "mocha": "9.2.0", + "po2json": "^0.4.5", + "tailwindcss": "^3.3.2", + "typescript": "5.3.3" + }, + "pogen": { + "domain": "challenger-ui" + }, + "dependencies": { + "swr": "2.0.3", + "@gnu-taler/taler-util": "workspace:*", + "@gnu-taler/web-util": "workspace:*", + "date-fns": "2.29.3", + "jed": "1.1.1", + "qrcode-generator": "^1.4.4", + "preact": "10.11.3" + } +} diff --git a/packages/challenger-ui/postcss.config.js b/packages/challenger-ui/postcss.config.js new file mode 100644 index 000000000..c9a60a43c --- /dev/null +++ b/packages/challenger-ui/postcss.config.js @@ -0,0 +1,21 @@ +/* + 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/> + */ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/packages/challenger-ui/src/Routing.tsx b/packages/challenger-ui/src/Routing.tsx new file mode 100644 index 000000000..6166f159a --- /dev/null +++ b/packages/challenger-ui/src/Routing.tsx @@ -0,0 +1,270 @@ +/* + 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 { + Loading, + urlPattern, + useCurrentLocation, + useNavigationContext, +} from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; + +import { assertUnreachable } from "@gnu-taler/taler-util"; +import { CheckChallengeIsUpToDate } from "./components/CheckChallengeIsUpToDate.js"; +import { SessionId, useSessionState } from "./hooks/session.js"; +import { AnswerChallenge } from "./pages/AnswerChallenge.js"; +import { AskChallenge } from "./pages/AskChallenge.js"; +import { CallengeCompleted } from "./pages/CallengeCompleted.js"; +import { Frame } from "./pages/Frame.js"; +import { MissingParams } from "./pages/MissingParams.js"; +import { NonceNotFound } from "./pages/NonceNotFound.js"; +import { Setup } from "./pages/Setup.js"; + +export function Routing(): VNode { + // check session and defined if this is + // public routing or private + return ( + <Frame> + <PublicRounting /> + </Frame> + ); +} + +const publicPages = { + noinfo: urlPattern<{ nonce: string }>( + /\/noinfo\/(?<nonce>[a-zA-Z0-9]+)/, + ({ nonce }) => `#/noinfo/${nonce}`, + ), + authorize: urlPattern<{ nonce: string }>( + /\/authorize\/(?<nonce>[a-zA-Z0-9]+)/, + ({ nonce }) => `#/authorize/${nonce}`, + ), + ask: urlPattern<{ nonce: string }>( + /\/ask\/(?<nonce>[a-zA-Z0-9]+)/, + ({ nonce }) => `#/ask/${nonce}`, + ), + answer: urlPattern<{ nonce: string }>( + /\/answer\/(?<nonce>[a-zA-Z0-9]+)/, + ({ nonce }) => `#/answer/${nonce}`, + ), + completed: urlPattern<{ nonce: string }>( + /\/completed\/(?<nonce>[a-zA-Z0-9]+)/, + ({ nonce }) => `#/completed/${nonce}`, + ), + setup: urlPattern<{ client: string }>( + /\/setup\/(?<client>[0-9]+)/, + ({ client }) => `#/setup/${client}`, + ), +}; + +function safeGetParam( + ps: Record<string, string[]>, + n: string, +): string | undefined { + if (!ps[n] || ps[n].length == 0) return undefined; + return ps[n][0]; +} + +function safeToURL(s: string | undefined): URL | undefined { + if (s === undefined) return undefined; + try { + return new URL(s); + } catch (e) { + return undefined; + } +} + +function PublicRounting(): VNode { + const location = useCurrentLocation(publicPages); + const { navigateTo } = useNavigationContext(); + const { start } = useSessionState(); + + if (location === undefined) { + return <NonceNotFound />; + } + + switch (location.name) { + case "noinfo": { + return <div>no info</div>; + } + case "setup": { + return ( + <Setup + clientId={location.values.client} + onCreated={(nonce) => { + navigateTo(publicPages.ask.url({ nonce })); + //response_type=code + //client_id=1 + //redirect_uri=http://exchange.taler.test:1180/kyc-proof/kyc-provider-wallet + //state=123 + }} + /> + ); + } + case "authorize": { + const responseType = safeGetParam(location.params, "response_type"); + const clientId = safeGetParam(location.params, "client_id"); + const redirectURL = safeToURL( + safeGetParam(location.params, "redirect_uri"), + ); + const state = safeGetParam(location.params, "state"); + // http://localhost:8080/app/#/authorize/ASDASD123?response_type=code&client_id=1&redirect_uri=goog.ecom&state=123 + // + + // http://localhost:8080/app/?response_type=code&client_id=1&redirect_uri=http://exchange.taler.test:1180/kyc-proof/kyc-provider-wallet&state=123#/authorize/X9668AR2CFC26X55H0M87GJZXGM45VD4SZE05C5SNS5FADPWN220 + + if ( + !responseType || + !clientId || + !redirectURL || + !state || + responseType !== "code" + ) { + return <MissingParams />; + } + const sessionId: SessionId = { + clientId, + redirectURL: redirectURL.href, + state, + }; + return ( + <CheckChallengeIsUpToDate + sessionId={sessionId} + nonce={location.values.nonce} + onNoInfo={() => { + navigateTo( + publicPages.noinfo.url({ + nonce: location.values.nonce, + }), + ); + }} + onCompleted={() => { + start(sessionId); + navigateTo( + publicPages.completed.url({ + nonce: location.values.nonce, + }), + ); + }} + onChangeLeft={() => { + start(sessionId); + navigateTo( + publicPages.ask.url({ + nonce: location.values.nonce, + }), + ); + }} + onNoMoreChanges={() => { + start(sessionId); + navigateTo( + publicPages.ask.url({ + nonce: location.values.nonce, + }), + ); + }} + > + <Loading /> + </CheckChallengeIsUpToDate> + ); + } + case "ask": { + return ( + <CheckChallengeIsUpToDate + nonce={location.values.nonce} + onNoInfo={() => { + navigateTo( + publicPages.noinfo.url({ + nonce: location.values.nonce, + }), + ); + }} + onCompleted={() => { + navigateTo( + publicPages.completed.url({ + nonce: location.values.nonce, + }), + ); + }} + > + <AskChallenge + focus + nonce={location.values.nonce} + routeSolveChallenge={publicPages.answer} + onSendSuccesful={() => { + navigateTo( + publicPages.answer.url({ + nonce: location.values.nonce, + }), + ); + }} + /> + </CheckChallengeIsUpToDate> + ); + } + case "answer": { + return ( + <CheckChallengeIsUpToDate + nonce={location.values.nonce} + onNoInfo={() => { + navigateTo( + publicPages.noinfo.url({ + nonce: location.values.nonce, + }), + ); + }} + onCompleted={() => { + navigateTo( + publicPages.completed.url({ + nonce: location.values.nonce, + }), + ); + }} + > + <AnswerChallenge + focus + nonce={location.values.nonce} + routeAsk={publicPages.ask} + onComplete={() => { + navigateTo( + publicPages.completed.url({ + nonce: location.values.nonce, + }), + ); + }} + /> + </CheckChallengeIsUpToDate> + ); + } + case "completed": { + return ( + <CheckChallengeIsUpToDate + nonce={location.values.nonce} + onNoInfo={() => { + navigateTo( + publicPages.noinfo.url({ + nonce: location.values.nonce, + }), + ); + }} + > + <CallengeCompleted nonce={location.values.nonce} /> + </CheckChallengeIsUpToDate> + ); + } + default: + assertUnreachable(location); + } +} diff --git a/packages/challenger-ui/src/app.tsx b/packages/challenger-ui/src/app.tsx new file mode 100644 index 000000000..2b5c5c815 --- /dev/null +++ b/packages/challenger-ui/src/app.tsx @@ -0,0 +1,168 @@ +/* + 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 { + CacheEvictor, + ChallengerCacheEviction, + assertUnreachable, + canonicalizeBaseUrl, + getGlobalLogLevel, + setGlobalLogLevelFromString, +} from "@gnu-taler/taler-util"; +import { + BrowserHashNavigationProvider, + ChallengerApiProvider, + Loading, + TalerWalletIntegrationBrowserProvider, + TranslationProvider, +} from "@gnu-taler/web-util/browser"; +import { useEffect, useState } from "preact/hooks"; +import { SWRConfig } from "swr"; +import { Routing } from "./Routing.js"; +// import { BankCoreApiProvider } from "./context/config.js"; +// import { BrowserHashNavigationProvider } from "./context/navigation.js"; +import { SettingsProvider } from "./context/settings.js"; +// import { TalerWalletIntegrationBrowserProvider } from "./context/wallet-integration.js"; +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; + +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); + }, []); + if (!settings) return <Loading />; + + const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL); + return ( + <SettingsProvider value={settings}> + <TranslationProvider + source={strings} + forceLang="en" + completeness={{ + es: strings["es"].completeness, + de: strings["de"].completeness, + }} + > + <ChallengerApiProvider + baseUrl={new URL("/", baseUrl)} + frameOnError={Frame} + evictors={{ + challenger: evictBankSwrCache, + }} + > + <SWRConfig + value={{ + provider: WITH_LOCAL_STORAGE_CACHE + ? localStorageProvider + : undefined, + // normally, do not revalidate + revalidateOnFocus: false, + revalidateOnReconnect: false, + revalidateIfStale: false, + revalidateOnMount: undefined, + focusThrottleInterval: undefined, + + // normally, do not refresh + refreshInterval: undefined, + dedupingInterval: 2000, + refreshWhenHidden: false, + refreshWhenOffline: false, + + // ignore errors + shouldRetryOnError: false, + errorRetryCount: 0, + errorRetryInterval: undefined, + + // do not go to loading again if already has data + keepPreviousData: true, + }} + > + <TalerWalletIntegrationBrowserProvider> + <BrowserHashNavigationProvider> + <Routing /> + </BrowserHashNavigationProvider> + </TalerWalletIntegrationBrowserProvider> + </SWRConfig> + </ChallengerApiProvider> + </TranslationProvider> + </SettingsProvider> + ); +} + +// @ts-expect-error creating a new property for window object +window.setGlobalLogLevelFromString = setGlobalLogLevelFromString; +// @ts-expect-error creating a new property for window object +window.getGlobalLevel = getGlobalLogLevel; + +function localStorageProvider(): Map<unknown, unknown> { + const map = new Map(JSON.parse(localStorage.getItem("app-cache") || "[]")); + + window.addEventListener("beforeunload", () => { + const appCache = JSON.stringify(Array.from(map.entries())); + localStorage.setItem("app-cache", appCache); + }); + return map; +} + +function getInitialBackendBaseURL( + backendFromSettings: string | undefined, +): string { + const overrideUrl = + typeof localStorage !== "undefined" + ? localStorage.getItem("challenger-base-url") + : undefined; + let result: string; + + if (!overrideUrl) { + // normal path + if (!backendFromSettings) { + console.error( + "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'", + ); + result = window.origin; + } else { + result = backendFromSettings; + } + } else { + // testing/development path + result = overrideUrl; + } + try { + return canonicalizeBaseUrl(result); + } catch (e) { + // fall back + return canonicalizeBaseUrl(window.origin); + } +} diff --git a/packages/challenger-ui/src/assets/home.svg b/packages/challenger-ui/src/assets/home.svg new file mode 100644 index 000000000..35f340162 --- /dev/null +++ b/packages/challenger-ui/src/assets/home.svg @@ -0,0 +1,3 @@ +<svg class="h-6 w-6 shrink-0 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true"> + <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" /> +</svg>
\ No newline at end of file diff --git a/packages/challenger-ui/src/assets/logo-2021.svg b/packages/challenger-ui/src/assets/logo-2021.svg new file mode 100644 index 000000000..8c5ff3e5b --- /dev/null +++ b/packages/challenger-ui/src/assets/logo-2021.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90"> + <g fill="#0042b3" fill-rule="evenodd" stroke-width=".3"> + <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" /> + <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" /> + <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" /> + </g> + <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" /> +</svg>
\ No newline at end of file diff --git a/packages/challenger-ui/src/assets/people.svg b/packages/challenger-ui/src/assets/people.svg new file mode 100644 index 000000000..1dc878b81 --- /dev/null +++ b/packages/challenger-ui/src/assets/people.svg @@ -0,0 +1,3 @@ +<svg class="h-6 w-6 shrink-0 text-indigo-200 group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true"> + <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> +</svg>
\ No newline at end of file diff --git a/packages/challenger-ui/src/attempts-exhausted.html b/packages/challenger-ui/src/attempts-exhausted.html new file mode 100644 index 000000000..c2468b98b --- /dev/null +++ b/packages/challenger-ui/src/attempts-exhausted.html @@ -0,0 +1,88 @@ +<!-- + This file is part of GNU Taler + (C) 2021--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/> + + @author Sebastian Javier Marchano +--> +<!DOCTYPE html> +<html lang="en" class="h-full"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <link rel="stylesheet" href="main.css" /> + <script type="module" src="main.js"></script> + <title>Attempts exhausted (#{{ec}})</title> +</head> + +<body class="min-h-full flex flex-col"> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"><a href="#"><img class="h-8 w-auto" + src="data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>" + alt="GNU Taler" style="height: 1.5rem; margin: 0.5rem;"></a></div><span + class="flex items-center text-white text-lg font-bold ml-4">Challenger</span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"> + </div> + </div> + </header> + + <main class="flex-1"> + <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 items-center mt-4"> + <div class="rounded-md bg-red-50 p-4 shadow-xl"> + <div class="flex"> + <div class="flex-shrink-0"> + <svg xmlns="http://www.w3.org/2000/svg" stroke="none" viewBox="0 0 24 24" fill="currentColor" + class="w-8 h-8 text-red-400"> + <path fill-rule="evenodd" + d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" /> + </svg> + </div> + <div class="ml-3"> + <h3 class="text-sm font-medium text-red-800"> + You have tried too many times + </h3> + <div class="mt-2 text-sm text-red-700"> + <p>More attempts are not allowed</p> + </div> + </div> + </div> + </div> + </div> + </main> + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400">Learn more about <a target="_blank" rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">GNU Taler</a></p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400">Copyright © 2014—2023 Taler Systems SA. </p> + </div> + </footer> + +</body> + +</html>
\ No newline at end of file diff --git a/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx b/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx new file mode 100644 index 000000000..70e41bf1e --- /dev/null +++ b/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx @@ -0,0 +1,132 @@ +/* + 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 { + HttpStatusCode, + TalerError, + assertUnreachable, +} from "@gnu-taler/taler-util"; +import { + Attention, + Loading, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { ComponentChildren, Fragment, VNode, h } from "preact"; +import { useChallengeSession } from "../hooks/challenge.js"; +import { SessionId, useSessionState } from "../hooks/session.js"; + +interface Props { + nonce: string; + children: ComponentChildren; + sessionId?: SessionId; + onCompleted?: () => void; + onChangeLeft?: () => void; + onNoMoreChanges?: () => void; + onNoInfo: () => void; +} +export function CheckChallengeIsUpToDate({ + sessionId: sessionFromParam, + nonce, + children, + onCompleted, + onChangeLeft, + onNoMoreChanges, + onNoInfo, +}: Props): VNode { + const { state, updateStatus } = useSessionState(); + const { i18n } = useTranslationContext(); + + const sessionId = sessionFromParam + ? sessionFromParam + : !state + ? undefined + : { + clientId: state.clientId, + redirectURL: state.redirectURL, + state: state.state, + }; + + const result = useChallengeSession(nonce, sessionId); + console.log("asd"); + if (!sessionId) { + onNoInfo(); + return <Loading />; + } + + if (!result) { + return <Loading />; + } + if (result instanceof TalerError) { + return <pre>{JSON.stringify(result, undefined, 2)}</pre>; + } + + 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); + } + } + + updateStatus(result.body); + + if (onCompleted && "redirectURL" in result.body) { + onCompleted(); + return <Loading />; + } + + if (onNoMoreChanges && !result.body.changes_left) { + onNoMoreChanges(); + return <Loading />; + } + + if (onChangeLeft && !result.body.changes_left) { + onChangeLeft(); + return <Loading />; + } + + return <Fragment>{children}</Fragment>; +} diff --git a/packages/challenger-ui/src/context/settings.ts b/packages/challenger-ui/src/context/settings.ts new file mode 100644 index 000000000..679359200 --- /dev/null +++ b/packages/challenger-ui/src/context/settings.ts @@ -0,0 +1,44 @@ +/* + 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 { ComponentChildren, createContext, h, VNode } from "preact"; +import { useContext } from "preact/hooks"; +import { ChallengerUiSettings } from "../settings.js"; + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +export type Type = ChallengerUiSettings; + +const initial: ChallengerUiSettings = {}; +const Context = createContext<Type>(initial); + +export const useSettingsContext = (): Type => useContext(Context); + +export const SettingsProvider = ({ + children, + value, +}: { + value: ChallengerUiSettings; + children: ComponentChildren; +}): VNode => { + return h(Context.Provider, { + value, + children, + }); +}; diff --git a/packages/challenger-ui/src/enter-address-form.html b/packages/challenger-ui/src/enter-address-form.html new file mode 100644 index 000000000..76b4d2262 --- /dev/null +++ b/packages/challenger-ui/src/enter-address-form.html @@ -0,0 +1,133 @@ +<!-- + This file is part of GNU Taler + (C) 2021--2023 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/> + + @author Sebastian Javier Marchano +--> +<!DOCTYPE html> +<html lang="en" class="h-full"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <link rel="stylesheet" href="main.css" /> + <script type="module" src="main.js"></script> + <title>Enter contact details</title> +</head> + +<body class="min-h-full flex flex-col"> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"><a href="#"><img class="h-8 w-auto" + src="data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>" + alt="GNU Taler" style="height: 1.5rem; margin: 0.5rem;"></a></div><span + class="flex items-center text-white text-lg font-bold ml-4">Challenger</span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"> + </div> + </div> + </header> + + <main class="flex-1"> + + <div class="isolate bg-white px-6 py-12"> + <div class="mx-auto max-w-2xl text-center"> + <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl"> + Enter contact details + </h2> + <p class="mt-2 text-lg leading-8 text-gray-600"> + You will receive a letter with a TAN code that must be provided on the next page. + </p> + </div> + <form action="/challenge/{{nonce}}" method="POST" class="mx-auto mt-16 max-w-xl sm:mt-20"> + <div class="grid grid-cols-1 gap-x-8 gap-y-6"> + <div class="sm:col-span-2"> + <label for="address" class="block text-sm font-semibold leading-6 text-gray-900"> + Street address + </label> + <div class="mt-2.5"> + <textarea name="address" id="address" rows="3" autocomplete="shipping street-address" + 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"></textarea> + + </div> + </div> + + <div class="sm:col-span-2"> + <label for="city" class="block text-sm font-semibold leading-6 text-gray-900"> + City + </label> + <div class="mt-2.5"> + <input type="text" name="city" id="city" maxlength="512" autocomplete="address-level2" + 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"> + </div> + </div> + + <div class="sm:col-span-2"> + <label for="postal-code" class="block text-sm font-semibold leading-6 text-gray-900"> + Postal code + </label> + <div class="mt-2.5"> + <input type="text" name="postal-code" id="postal-code" maxlength="512" autocomplete="postal-code" + 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"> + </div> + </div> + + <div class="sm:col-span-2"> + <label for="country" class="block text-sm font-semibold leading-6 text-gray-900"> + Country + </label> + <div class="mt-2.5"> + <input type="text" name="country" id="country" maxlength="512" autocomplete="country" + 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"> + </div> + </div> + + <p class="mt-3 text-sm leading-6 text-gray-400"> + You can change address another {{changes_left}} times. + </p> + </div> + + <div class="mt-10"> + <button type="submit" + class="block w-full 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"> + Send mail + </button> + </div> + </form> + </div> + </main> + + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400">Learn more about <a target="_blank" rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">GNU Taler</a></p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400">Copyright © 2014—2023 Taler Systems SA. </p> + </div> + </footer> +</body> + +</html> diff --git a/packages/challenger-ui/src/enter-email-form.html b/packages/challenger-ui/src/enter-email-form.html new file mode 100644 index 000000000..3b8720244 --- /dev/null +++ b/packages/challenger-ui/src/enter-email-form.html @@ -0,0 +1,127 @@ +<!-- + This file is part of GNU Taler + (C) 2021--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/> + + @author Sebastian Javier Marchano +--> +<!DOCTYPE html> +<html lang="en" class="h-full"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <link rel="stylesheet" href="main.css" /> + <script type="module" src="main.js"></script> + <title>Enter contact details</title> +</head> + +<body class="min-h-full flex flex-col"> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"><a href="#"><img class="h-8 w-auto" + src="data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>" + alt="GNU Taler" style="height: 1.5rem; margin: 0.5rem;"></a></div><span + class="flex items-center text-white text-lg font-bold ml-4">Challenger</span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"> + </div> + </div> + </header> + + <main class="flex-1"> + + <div class="isolate bg-white px-6 py-12"> + <div class="mx-auto max-w-2xl text-center"> + <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl"> + Enter contact details + </h2> + <p class="mt-2 text-lg leading-8 text-gray-600"> + You will receive an email with a TAN code that must be provided on the next page. + </p> + </div> + <form action="/challenge/{{nonce}}" method="POST" class="mx-auto mt-16 max-w-xl sm:mt-20"> + <div class="grid grid-cols-1 gap-x-8 gap-y-6"> + <div class="sm:col-span-2"> + <label for="email" class="block text-sm font-semibold leading-6 text-gray-900"> + Email + </label> + <div class="mt-2.5"> + <input type="email" name="email" id="email" maxlength="512" autocomplete="email" value="{{last_address}}" + {{#fixed_address}}readonly{{/fixed_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"> + </div> + </div> + + <script> + function check() { + var email = document.getElementById('email'); + var emailRepeat = document.getElementById('repeat-email'); + + if (email.value != emailRepeat.value) { + emailRepeat.setCustomValidity('The two email addresses must match.'); + } else { + // input is valid -- reset the error message + emailRepeat.setCustomValidity(''); + } + } + </script> + + <div class="sm:col-span-2"> + <label for="repeat-email" class="block text-sm font-semibold leading-6 text-gray-900"> + Repeat email + </label> + <div class="mt-2.5"> + <input oninput="check(this)" type="email" name="repeat-email" id="repeat-email" 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"> + </div> + </div> + + <p class="mt-3 text-sm leading-6 text-gray-400"> + You can change your email address another {{changes_left}} times. + </p> + </div> + + <div class="mt-10"> + <button type="submit" + class="block w-full 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"> + Send email + </button> + </div> + </form> + </div> + </main> + + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400">Learn more about <a target="_blank" rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">GNU Taler</a></p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400">Copyright © 2014—2023 Taler Systems SA. </p> + </div> + </footer> +</body> + +</html> diff --git a/packages/challenger-ui/src/enter-file-access-form.html b/packages/challenger-ui/src/enter-file-access-form.html new file mode 100644 index 000000000..b79d1dada --- /dev/null +++ b/packages/challenger-ui/src/enter-file-access-form.html @@ -0,0 +1,102 @@ +<!-- + This file is part of GNU Taler + (C) 2021--2023 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/> + + @author Sebastian Javier Marchano +--> +<!DOCTYPE html> +<html lang="en" class="h-full"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <link rel="stylesheet" href="main.css" /> + <script type="module" src="main.js"></script> + <title>Enter local file name</title> +</head> + +<body class="min-h-full flex flex-col"> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"><a href="#"><img class="h-8 w-auto" + src="data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>" + alt="GNU Taler" style="height: 1.5rem; margin: 0.5rem;"></a></div><span + class="flex items-center text-white text-lg font-bold ml-4">Challenger</span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"> + </div> + </div> + </header> + + <main class="flex-1"> + + <div class="isolate bg-white px-6 py-12"> + <div class="mx-auto max-w-2xl text-center"> + <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl"> + Enter file name + </h2> + <p class="mt-2 text-lg leading-8 text-gray-600"> + The file will be overwritten with a code which need to be entered in the next page. + </p> + </div> + <form action="/challenge/{{nonce}}" method="POST" class="mx-auto mt-16 max-w-xl sm:mt-20"> + <div class="grid grid-cols-1 gap-x-8 gap-y-6"> + <div class="sm:col-span-2"> + <label for="phone" class="block text-sm font-semibold leading-6 text-gray-900"> + Phone number + </label> + <div class="mt-2.5"> + <input type="filename" name="filename" id="filename" maxlength="200" + 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"> + </div> + </div> + + <p class="mt-3 text-sm leading-6 text-gray-400"> + You can change the filename another {{changes_left}} times. + </p> + </div> + + <div class="mt-10"> + <button type="submit" + class="block w-full 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"> + Write file + </button> + </div> + </form> + </div> + </main> + + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400">Learn more about <a target="_blank" rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">GNU Taler</a></p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400">Copyright © 2014—2023 Taler Systems SA. </p> + </div> + </footer> +</body> + +</html> diff --git a/packages/challenger-ui/src/enter-phone-form.html b/packages/challenger-ui/src/enter-phone-form.html new file mode 100644 index 000000000..ca06fb94e --- /dev/null +++ b/packages/challenger-ui/src/enter-phone-form.html @@ -0,0 +1,126 @@ +<!-- + This file is part of GNU Taler + (C) 2021--2023 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/> + + @author Sebastian Javier Marchano +--> +<!DOCTYPE html> +<html lang="en" class="h-full"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <link rel="stylesheet" href="main.css" /> + <script type="module" src="main.js"></script> + <title>Enter contact details</title> +</head> + +<body class="min-h-full flex flex-col"> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"><a href="#"><img class="h-8 w-auto" + src="data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>" + alt="GNU Taler" style="height: 1.5rem; margin: 0.5rem;"></a></div><span + class="flex items-center text-white text-lg font-bold ml-4">Challenger</span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"> + </div> + </div> + </header> + + <main class="flex-1"> + + <div class="isolate bg-white px-6 py-12"> + <div class="mx-auto max-w-2xl text-center"> + <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl"> + Enter contact details + </h2> + <p class="mt-2 text-lg leading-8 text-gray-600"> + You will receive an SMS with a TAN code that must be provided on the next page. + </p> + </div> + <form action="/challenge/{{nonce}}" method="POST" class="mx-auto mt-16 max-w-xl sm:mt-20"> + <div class="grid grid-cols-1 gap-x-8 gap-y-6"> + <div class="sm:col-span-2"> + <label for="phone" class="block text-sm font-semibold leading-6 text-gray-900"> + Phone number + </label> + <div class="mt-2.5"> + <input type="phone" name="phone" id="phone" maxlength="20" autocomplete="tel" + 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"> + </div> + </div> + + <script> + function check() { + var phone = document.getElementById('phone'); + var phoneRepeat = document.getElementById('repeat-phone'); + + if (phone.value != phoneRepeat.value) { + phoneRepeat.setCustomValidity('The two phone numbers must match.'); + } else { + // input is valid -- reset the error message + phoneRepeat.setCustomValidity(''); + } + } + </script> + + <div class="sm:col-span-2"> + <label for="repeat-phone" class="block text-sm font-semibold leading-6 text-gray-900"> + Repeat phone + </label> + <div class="mt-2.5"> + <input oninput="check(this)" type="number" name="repeat-phone" id="repeat-phone" autocomplete="tel" + 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"> + </div> + </div> + + <p class="mt-3 text-sm leading-6 text-gray-400"> + You can change your phone number another {{changes_left}} times. + </p> + </div> + + <div class="mt-10"> + <button type="submit" + class="block w-full 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"> + Send SMS + </button> + </div> + </form> + </div> + </main> + + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400">Learn more about <a target="_blank" rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">GNU Taler</a></p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400">Copyright © 2014—2023 Taler Systems SA. </p> + </div> + </footer> +</body> + +</html> diff --git a/packages/challenger-ui/src/enter-tan-form.html b/packages/challenger-ui/src/enter-tan-form.html new file mode 100644 index 000000000..965f8e9d2 --- /dev/null +++ b/packages/challenger-ui/src/enter-tan-form.html @@ -0,0 +1,117 @@ +<!-- + This file is part of GNU Taler + (C) 2021--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/> + + @author Sebastian Javier Marchano +--> +<!DOCTYPE html> +<html lang="en" class="h-full"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <link rel="stylesheet" href="main.css" /> + <script type="module" src="main.js"></script> + <title>Enter your TAN</title> +</head> + +<body class="min-h-full flex flex-col"> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"><a href="#"><img class="h-8 w-auto" + src="data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>" + alt="GNU Taler" style="height: 1.5rem; margin: 0.5rem;"></a></div><span + class="flex items-center text-white text-lg font-bold ml-4">Challenger</span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"> + </div> + </div> + </header> + + <main class="flex-1"> + + <div class="isolate bg-white px-6 py-12"> + <div class="mx-auto max-w-2xl text-center"> + <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl"> + Please enter the TAN you received to authenticate. + </h2> + <p class="mt-2 text-lg leading-8 text-gray-600"> + <!-- {{#transmitted}} + A TAN was sent to your address "{{address}}". + {{/transmitted}} --> + <!-- {{^transmitted}} --> + We recently already sent a TAN to your address "{{address}}". + A new TAN will not be transmitted again before {{next_tx_time}}. + <!-- {{/transmitted}} --> + </p> + </div> + + + <form action="/solve/{{nonce}}" method="POST" class="mx-auto mt-16 max-w-xl sm:mt-20"> + <div class="grid grid-cols-1 gap-x-8 gap-y-6"> + <div class="sm:col-span-2"> + <label for="pin" class="block text-sm font-semibold leading-6 text-gray-900"> + TAN code + </label> + <div class="mt-2.5"> + <div + class="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600"> + <span class="flex select-none items-center pl-3 text-gray-500 sm:text-sm">TAN:</span> + <input type="number" name="pin" id="pin" maxlength="64" + class="block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6" + placeholder="12345678"> + </div> + + </div> + </div> + + <p class="mt-3 text-sm leading-6 text-gray-400"> + You have {{attempts_left}} attempts left. + </p> + </div> + + <div class="mt-10"> + <button type="submit" + class="block w-full 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"> + Check + </button> + </div> + </form> + + </div> + </main> + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400">Learn more about <a target="_blank" rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">GNU Taler</a></p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400">Copyright © 2014—2023 Taler Systems SA. </p> + </div> + </footer> + +</body> + +</html> diff --git a/packages/challenger-ui/src/hooks/challenge.ts b/packages/challenger-ui/src/hooks/challenge.ts new file mode 100644 index 000000000..846242816 --- /dev/null +++ b/packages/challenger-ui/src/hooks/challenge.ts @@ -0,0 +1,58 @@ +/* + 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 { + ChallengerResultByMethod, + TalerHttpError, +} from "@gnu-taler/taler-util"; +import { useChallengerApiContext } from "@gnu-taler/web-util/browser"; +import _useSWR, { SWRHook, mutate } from "swr"; +import { SessionId } from "./session.js"; +const useSWR = _useSWR as unknown as SWRHook; + +export function revalidateChallengeSession() { + return mutate( + (key) => Array.isArray(key) && key[key.length - 1] === "login", + undefined, + { revalidate: true }, + ); +} + +export function useChallengeSession( + nonce: string, + session: SessionId | undefined, +) { + const { + lib: { challenger: api }, + } = useChallengerApiContext(); + + async function fetcher([n, c, r, s]: [string, string, string, string]) { + return await api.login(n, c, r, s); + } + const { data, error } = useSWR< + ChallengerResultByMethod<"login">, + TalerHttpError + >( + !session + ? undefined + : [nonce, session.clientId, session.redirectURL, session.state, "login"], + fetcher, + {}, + ); + + if (data) return data; + if (error) return error; + return undefined; +} diff --git a/packages/challenger-ui/src/hooks/session.ts b/packages/challenger-ui/src/hooks/session.ts new file mode 100644 index 000000000..ed7ea8986 --- /dev/null +++ b/packages/challenger-ui/src/hooks/session.ts @@ -0,0 +1,143 @@ +/* + 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 { + ChallengerApi, + Codec, + buildCodecForObject, + codecForBoolean, + codecForChallengeStatus, + codecForNumber, + codecForString, + codecForStringURL, + codecOptional, +} from "@gnu-taler/taler-util"; +import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; +import { mutate } from "swr"; + +/** + * Has the information to reach and + * authenticate at the bank's backend. + */ +export type SessionId = { + clientId: string; + redirectURL: string; + state: string; +}; + +export type LastChallengeResponse = { + attemptsLeft: number; + nextSend: string; + transmitted: boolean; +}; + +export type SessionState = SessionId & { + lastTry: LastChallengeResponse | undefined; + lastStatus: ChallengerApi.ChallengeStatus | undefined; + completedURL: string | undefined; +}; +export const codecForLastChallengeResponse = (): Codec<LastChallengeResponse> => + buildCodecForObject<LastChallengeResponse>() + .property("attemptsLeft", codecForNumber()) + .property("nextSend", codecForString()) + .property("transmitted", codecForBoolean()) + .build("LastChallengeResponse"); + +export const codecForSessionState = (): Codec<SessionState> => + buildCodecForObject<SessionState>() + .property("clientId", codecForString()) + .property("redirectURL", codecForStringURL()) + .property("completedURL", codecOptional(codecForStringURL())) + .property("state", codecForString()) + .property("lastStatus", codecOptional(codecForChallengeStatus())) + .property("lastTry", codecOptional(codecForLastChallengeResponse())) + .build("SessionState"); + +export interface SessionStateHandler { + state: SessionState | undefined; + start(s: SessionId): void; + accepted(l: LastChallengeResponse): void; + completed(e: URL): void; + updateStatus(s: ChallengerApi.ChallengeStatus): void; +} + +const SESSION_STATE_KEY = buildStorageKey( + "challenger-session", + codecForSessionState(), +); + +/** + * Return getters and setters for + * login credentials and backend's + * base URL. + */ +export function useSessionState(): SessionStateHandler { + const { value: state, update } = useLocalStorage(SESSION_STATE_KEY); + + return { + state, + start(info) { + update({ + ...info, + lastTry: undefined, + completedURL: undefined, + lastStatus: undefined, + }); + cleanAllCache(); + }, + accepted(lastTry) { + if (!state) return; + update({ + ...state, + lastTry, + }); + }, + completed(url) { + if (!state) return; + update({ + ...state, + completedURL: url.href, + }); + }, + updateStatus(st: ChallengerApi.ChallengeStatus) { + if (!state) return; + if (!state.lastStatus) { + update({ + ...state, + lastStatus: st, + }); + return; + } + // current status + const ls = state.lastStatus; + if ( + ls.changes_left !== st.changes_left || + ls.fix_address !== st.fix_address || + ls.last_address !== st.last_address + ) { + update({ + ...state, + lastStatus: st, + }); + return; + } + }, + }; +} + +function cleanAllCache(): void { + mutate(() => true, undefined, { revalidate: false }); +} diff --git a/packages/challenger-ui/src/i18n/challenger-ui.pot b/packages/challenger-ui/src/i18n/challenger-ui.pot new file mode 100644 index 000000000..5d2497acf --- /dev/null +++ b/packages/challenger-ui/src/i18n/challenger-ui.pot @@ -0,0 +1,199 @@ +# 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/> +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Taler Bank\n" +"Report-Msgid-Bugs-To: taler@gnu.org\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +#: src/pages/AnswerChallenge.tsx:55 +#, c-format +msgid "Can't be empty" +msgstr "" + +#: src/pages/AnswerChallenge.tsx:81 +#, c-format +msgstr "" + +#: src/pages/AnswerChallenge.tsx:108 +#, c-format +msgid "Invalid request" +msgstr "" + +#: src/pages/AnswerChallenge.tsx:111 +#, c-format +msgid "Invalid pin" +msgstr "" + +#: src/pages/AnswerChallenge.tsx:142 +#, c-format +msgid "Please enter the TAN you received to authenticate." +msgstr "" + +#: src/pages/AnswerChallenge.tsx:147 +#, c-format +msgid "A TAN was sent to your address "%1$s"." +msgstr "" + +#: src/pages/AnswerChallenge.tsx:151 +#, c-format +msgid "" +"We recently already sent a TAN to your address " %1$s". A new TAN will " +"not be transmitted again before %2$s." +msgstr "" + +#: src/pages/AnswerChallenge.tsx:161 +#, c-format +msgid "You can try another PIN but just %1$s times more." +msgstr "" + +#: src/pages/AnswerChallenge.tsx:181 +#, c-format +msgid "TAN code" +msgstr "" + +#: src/pages/AnswerChallenge.tsx:204 +#, c-format +msgid "You have %1$s attempts left." +msgstr "" + +#: src/pages/AnswerChallenge.tsx:217 +#, c-format +msgid "Check" +msgstr "" + +#: src/pages/AnswerChallenge.tsx:227 +#, c-format +msgid "Send again" +msgstr "" + +#: src/pages/AskChallenge.tsx:76 +#, c-format +msgid "required" +msgstr "" + +#: src/pages/AskChallenge.tsx:84 +#, c-format +msgid "invalid email" +msgstr "" + +#: src/pages/AskChallenge.tsx:86 +#, c-format +msgid "emails don't match" +msgstr "" + +#: src/pages/AskChallenge.tsx:130 +#, c-format +msgid "Enter contact details" +msgstr "" + +#: src/pages/AskChallenge.tsx:133 +#, c-format +msgid "" +"You will receive an email with a TAN code that must be provided on the next " +"page." +msgstr "" + +#: src/pages/AskChallenge.tsx:152 +#, c-format +msgid "Email" +msgstr "" + +#: src/pages/AskChallenge.tsx:180 +#, c-format +msgid "Repeat email" +msgstr "" + +#: src/pages/AskChallenge.tsx:198 +#, c-format +msgid "You can change your email address another %1$s times." +msgstr "" + +#: src/pages/AskChallenge.tsx:211 +#, c-format +msgid "Send email" +msgstr "" + +#: src/pages/AskChallenge.tsx:237 +#, c-format +msgid "Bad request" +msgstr "" + +#: src/pages/AskChallenge.tsx:238 +#, c-format +msgid "Could not start the challenge, check configuration." +msgstr "" + +#: src/pages/AskChallenge.tsx:246 +#, c-format +msgid "Not found" +msgstr "" + +#: src/pages/AskChallenge.tsx:247 +#, c-format +msgid "Nonce not found" +msgstr "" + +#: src/pages/AskChallenge.tsx:253 +#, c-format +msgid "Not acceptable" +msgstr "" + +#: src/pages/AskChallenge.tsx:254 +#, c-format +msgid "Server has wrong template configuration" +msgstr "" + +#: src/pages/AskChallenge.tsx:262 +#, c-format +msgid "Internal error" +msgstr "" + +#: src/pages/AskChallenge.tsx:263 +#, c-format +msgid "Check logs" +msgstr "" + +#: src/pages/NonceNotFound.tsx:33 +#, c-format +msgid "The URL is wrong" +msgstr "" + +#: src/pages/NonceNotFound.tsx:36 +#, c-format +msgid "Maybe the validation check expired." +msgstr "" + +#: src/pages/Setup.tsx:53 +#, c-format +msgid "Client doesn't exist." +msgstr "" + +#: src/pages/Setup.tsx:65 +#, c-format +msgid "Setup new challenge with client ID: "%1$s"" +msgstr "" + +#: src/pages/Setup.tsx:76 +#, c-format +msgid "Start" +msgstr "" + diff --git a/packages/challenger-ui/src/i18n/poheader b/packages/challenger-ui/src/i18n/poheader new file mode 100644 index 000000000..f2c9d10dd --- /dev/null +++ b/packages/challenger-ui/src/i18n/poheader @@ -0,0 +1,26 @@ +# 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/> +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Challenger UI\n" +"Report-Msgid-Bugs-To: taler@gnu.org\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" diff --git a/packages/challenger-ui/src/i18n/strings.ts b/packages/challenger-ui/src/i18n/strings.ts new file mode 100644 index 000000000..ea13fed2e --- /dev/null +++ b/packages/challenger-ui/src/i18n/strings.ts @@ -0,0 +1,90 @@ +/* + 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/> + */ +export interface StringsType { + // X-Domain or 'messages' + domain: string; + lang: string; + completeness: number; + plural_forms: string; + locale_data: { + messages: Record<string, unknown>; + }; +} +export const strings: Record<string, StringsType> = {}; + +strings["it"] = { + locale_data: { + messages: { + "": { + domain: "messages", + plural_forms: "nplurals=2; plural=n != 1;", + lang: "it", + }, + } + }, + domain: "messages", + plural_forms: "nplurals=2; plural=n != 1;", + lang: "it", + completeness: 14, +}; + +strings["es"] = { + locale_data: { + messages: { + "": { + domain: "messages", + plural_forms: "nplurals=2; plural=n != 1;", + lang: "es", + }, + } + }, + domain: "messages", + plural_forms: "nplurals=2; plural=n != 1;", + lang: "es", + completeness: 100, +}; + +strings["en"] = { + locale_data: { + messages: { + "": { + domain: "messages", + plural_forms: "nplurals=2; plural=n != 1;", + lang: "en", + }, + } + }, + domain: "messages", + plural_forms: "nplurals=2; plural=(n != 1);", + lang: "en", + completeness: 100, +}; + +strings["de"] = { + locale_data: { + messages: { + "": { + domain: "messages", + plural_forms: "nplurals=2; plural=n != 1;", + lang: "de", + }, + } + }, + domain: "messages", + plural_forms: "nplurals=2; plural=n != 1;", + lang: "de", + completeness: 4, +}; diff --git a/packages/challenger-ui/src/index.html b/packages/challenger-ui/src/index.html new file mode 100644 index 000000000..18f472045 --- /dev/null +++ b/packages/challenger-ui/src/index.html @@ -0,0 +1,41 @@ +<!-- + This file is part of GNU Taler + (C) 2021--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/> + + @author Sebastian Javier Marchano +--> +<!doctype html> +<html lang="en" class="h-full bg-gray-100"> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri,api" /> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link + rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <title>Challenger</title> + <!-- Entry point for the SPA. --> + <script type="module" src="index.js"></script> + <link rel="stylesheet" href="index.css" /> + </head> + + <body class="h-full"> + <div id="app"></div> + </body> +</html> diff --git a/packages/challenger-ui/src/index.tsx b/packages/challenger-ui/src/index.tsx new file mode 100644 index 000000000..f559288a3 --- /dev/null +++ b/packages/challenger-ui/src/index.tsx @@ -0,0 +1,27 @@ +/* + 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 { App } from "./app.js"; +import { h, render } from "preact"; +import "./scss/main.css"; + +const app = document.getElementById("app"); + +if (app) { + render(<App />, app); +} else { + console.error("HTML element with id 'app' not found."); +} diff --git a/packages/challenger-ui/src/internal-error.html b/packages/challenger-ui/src/internal-error.html new file mode 100644 index 000000000..521a2b69e --- /dev/null +++ b/packages/challenger-ui/src/internal-error.html @@ -0,0 +1,89 @@ +<!-- + This file is part of GNU Taler + (C) 2021--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/> + + @author Sebastian Javier Marchano +--> +<!DOCTYPE html> +<html lang="en" class="h-full"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <link rel="stylesheet" href="main.css" /> + <script type="module" src="main.js"></script> + <title>Internal server error (#{{ec}})</title> +</head> + +<body class="min-h-full flex flex-col"> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"><a href="#"><img class="h-8 w-auto" + src="data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>" + alt="GNU Taler" style="height: 1.5rem; margin: 0.5rem;"></a></div><span + class="flex items-center text-white text-lg font-bold ml-4">Challenger</span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"> + </div> + </div> + </header> + + <main class="flex-1"> + <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 items-center mt-4"> + <div class="rounded-md bg-red-50 p-4 shadow-xl"> + <div class="flex"> + <div class="flex-shrink-0"> + <svg xmlns="http://www.w3.org/2000/svg" stroke="none" viewBox="0 0 24 24" fill="currentColor" + class="w-8 h-8 text-red-400"> + <path fill-rule="evenodd" + d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" /> + </svg> + </div> + <div class="ml-3"> + <h3 class="text-sm font-medium text-red-800"> + Internal error + </h3> + <div class="mt-2 text-sm text-red-700"> + <p>{{hint}} ({{detail}})</p> + </div> + </div> + </div> + </div> + </div> + + </main> + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400">Learn more about <a target="_blank" rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">GNU Taler</a></p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400">Copyright © 2014—2023 Taler Systems SA. </p> + </div> + </footer> + +</body> + +</html>
\ No newline at end of file diff --git a/packages/challenger-ui/src/invalid-pin.html b/packages/challenger-ui/src/invalid-pin.html new file mode 100644 index 000000000..1229b8095 --- /dev/null +++ b/packages/challenger-ui/src/invalid-pin.html @@ -0,0 +1,87 @@ +<!-- + This file is part of GNU Taler + (C) 2021--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/> + + @author Sebastian Javier Marchano +--> +<!DOCTYPE html> +<html lang="en" class="h-full"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <link rel="stylesheet" href="main.css" /> + <script type="module" src="main.js"></script> + <title>Invalid solution (#{{ec}})</title> +</head> + +<body class="min-h-full flex flex-col"> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"><a href="#"><img class="h-8 w-auto" + src="data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>" + alt="GNU Taler" style="height: 1.5rem; margin: 0.5rem;"></a></div><span + class="flex items-center text-white text-lg font-bold ml-4">Challenger</span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"> + </div> + </div> + </header> + + <main class="flex-1"> + <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 items-center mt-4"> + <div class="rounded-md bg-red-50 p-4 shadow-xl"> + <div class="flex"> + <div class="flex-shrink-0"> + <svg xmlns="http://www.w3.org/2000/svg" stroke="none" viewBox="0 0 24 24" fill="currentColor" + class="w-8 h-8 text-red-400"> + <path fill-rule="evenodd" + d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" /> + </svg> + </div> + <div class="ml-3"> + <h3 class="text-sm font-medium text-red-800"> + Invalid PIN + </h3> + <div class="mt-2 text-sm text-red-700"> + <p>{{hint}}</p> + </div> + </div> + </div> + </div> + </div> + </main> + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400">Learn more about <a target="_blank" rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">GNU Taler</a></p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400">Copyright © 2014—2023 Taler Systems SA. </p> + </div> + </footer> +</body> + +</html>
\ No newline at end of file diff --git a/packages/challenger-ui/src/invalid-request.html b/packages/challenger-ui/src/invalid-request.html new file mode 100644 index 000000000..89e6b125c --- /dev/null +++ b/packages/challenger-ui/src/invalid-request.html @@ -0,0 +1,88 @@ +<!-- + This file is part of GNU Taler + (C) 2021--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/> + + @author Sebastian Javier Marchano +--> +<!DOCTYPE html> +<html lang="en" class="h-full"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <link rel="stylesheet" href="main.css" /> + <script type="module" src="main.js"></script> + <title>Invalid request (#{{ec}})</title> +</head> + +<body class="min-h-full flex flex-col"> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"><a href="#"><img class="h-8 w-auto" + src="data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>" + alt="GNU Taler" style="height: 1.5rem; margin: 0.5rem;"></a></div><span + class="flex items-center text-white text-lg font-bold ml-4">Challenger</span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"> + </div> + </div> + </header> + + <main class="flex-1"> + + <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 items-center mt-4"> + <div class="rounded-md bg-red-50 p-4 shadow-xl"> + <div class="flex"> + <div class="flex-shrink-0"> + <svg xmlns="http://www.w3.org/2000/svg" stroke="none" viewBox="0 0 24 24" fill="currentColor" + class="w-8 h-8 text-red-400"> + <path fill-rule="evenodd" + d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" /> + </svg> + </div> + <div class="ml-3"> + <h3 class="text-sm font-medium text-red-800"> + Request error + </h3> + <div class="mt-2 text-sm text-red-700"> + <p>{{hint}}</p> + </div> + </div> + </div> + </div> + </div> + </main> + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400">Learn more about <a target="_blank" rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">GNU Taler</a></p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400">Copyright © 2014—2023 Taler Systems SA. </p> + </div> + </footer> +</body> + +</html>
\ No newline at end of file diff --git a/packages/challenger-ui/src/main.js b/packages/challenger-ui/src/main.js new file mode 100644 index 000000000..3272bd0bb --- /dev/null +++ b/packages/challenger-ui/src/main.js @@ -0,0 +1,2 @@ +// intentionally empty +import "./scss/main.css";
\ No newline at end of file diff --git a/packages/challenger-ui/src/pages/AnswerChallenge.tsx b/packages/challenger-ui/src/pages/AnswerChallenge.tsx new file mode 100644 index 000000000..73a79c51f --- /dev/null +++ b/packages/challenger-ui/src/pages/AnswerChallenge.tsx @@ -0,0 +1,279 @@ +/* + 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 { + ChallengerApi, + HttpStatusCode, + assertUnreachable, +} from "@gnu-taler/taler-util"; +import { + Attention, + Button, + LocalNotificationBanner, + RouteDefinition, + ShowInputErrorLabel, + useChallengerApiContext, + useLocalNotificationHandler, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; +import { useState } from "preact/hooks"; +import { useSessionState } from "../hooks/session.js"; + +export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/; + +type Props = { + nonce: string; + focus?: boolean; + onComplete: () => void; + routeAsk: RouteDefinition<{ nonce: string }>; +}; + +export function AnswerChallenge({ focus, nonce, onComplete, routeAsk }: Props): VNode { + const { lib } = useChallengerApiContext(); + const { i18n } = useTranslationContext(); + const { state, accepted, completed } = useSessionState(); + const [notification, withErrorHandler] = useLocalNotificationHandler(); + const [pin, setPin] = useState<string | undefined>(); + const [lastTryError, setLastTryError] = + useState<ChallengerApi.InvalidPinResponse>(); + const errors = undefinedIfEmpty({ + pin: !pin ? i18n.str`Can't be empty` : undefined, + }); + + const lastEmail = !state + ? undefined + : !state.lastStatus + ? undefined + : !state.lastStatus.last_address + ? undefined + : state.lastStatus.last_address["email"]; + + const onSendAgain = + !state || lastEmail === undefined + ? undefined + : withErrorHandler( + async () => { + if (!lastEmail) return; + return await lib.challenger.challenge(nonce, { email: lastEmail }); + }, + (ok) => { + if ("redirectURL" in ok.body) { + completed(ok.body.redirectURL); + } else { + accepted({ + attemptsLeft: ok.body.attempts_left, + nextSend: ok.body.next_tx_time, + transmitted: ok.body.transmitted, + }); + } + return undefined; + }, + (fail) => { + switch (fail.case) { + case HttpStatusCode.BadRequest: + return i18n.str``; + case HttpStatusCode.NotFound: + return i18n.str``; + case HttpStatusCode.NotAcceptable: + return i18n.str``; + case HttpStatusCode.TooManyRequests: + return i18n.str``; + case HttpStatusCode.InternalServerError: + return i18n.str``; + } + }, + ); + + const onCheck = + errors !== undefined || (lastTryError && lastTryError.exhausted) + ? undefined + : withErrorHandler( + async () => { + return lib.challenger.solve(nonce, { pin: pin! }); + }, + (ok) => { + completed(ok.body.redirectURL as URL); + onComplete(); + }, + (fail) => { + switch (fail.case) { + case HttpStatusCode.BadRequest: + return i18n.str`Invalid request`; + case HttpStatusCode.Forbidden: { + setLastTryError(fail.body); + return i18n.str`Invalid pin`; + } + case HttpStatusCode.NotFound: + return i18n.str``; + case HttpStatusCode.NotAcceptable: + return i18n.str``; + case HttpStatusCode.TooManyRequests: + return i18n.str``; + case HttpStatusCode.InternalServerError: + return i18n.str``; + default: + assertUnreachable(fail); + } + }, + ); + + if (!state) { + return <div>no state</div>; + } + + if (!state.lastTry) { + return <div>you should do a challenge first</div>; + } + + return ( + <Fragment> + <LocalNotificationBanner notification={notification} /> + + <div class="isolate bg-white px-6 py-12"> + <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> + Enter the TAN you received to authenticate. + </i18n.Translate> + </h2> + <p class="mt-2 text-lg leading-8 text-gray-600"> + {state.lastTry.transmitted ? ( + <i18n.Translate> + A TAN was sent to your address "{lastEmail}". + </i18n.Translate> + ) : ( + <Attention title={i18n.str`Resend failed`} type="warning"> + <i18n.Translate> + We recently already sent a TAN to your address " + {lastEmail}". A new TAN will not be transmitted again + before "{state.lastTry.nextSend}". + </i18n.Translate> + </Attention> + )} + </p> + {!lastTryError ? undefined : ( + <p class="mt-2 text-lg leading-8 text-gray-600"> + <i18n.Translate> + You can try another PIN but just{" "} + {lastTryError.auth_attempts_left} times more. + </i18n.Translate> + </p> + )} + </div> + <form + method="POST" + class="mx-auto mt-16 max-w-xl sm:mt-20" + onSubmit={(e) => { + e.preventDefault(); + }} + > + <div class="grid grid-cols-1 gap-x-8 gap-y-6"> + <div class="sm:col-span-2"> + <label + for="pin" + class="block text-sm font-semibold leading-6 text-gray-900" + > + <i18n.Translate>TAN code</i18n.Translate> + </label> + <div class="mt-2.5"> + <input + autoFocus + ref={focus ? doAutoFocus : undefined} + type="number" + name="pin" + id="pin" + maxLength={64} + value={pin} + onChange={(e) => { + setPin(e.currentTarget.value); + }} + placeholder="12345678" + 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?.pin} + isDirty={pin !== undefined} + /> + </div> + </div> + + <p class="mt-3 text-sm leading-6 text-gray-400"> + <i18n.Translate> + You have {state.lastTry.attemptsLeft} attempts left. + </i18n.Translate> + </p> + </div> + + <div class="mt-10"> + <Button + type="submit" + 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" + disabled={!onCheck} + handler={onCheck} + > + <i18n.Translate>Check</i18n.Translate> + </Button> + </div> + <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> + </Fragment> + ); +} + +/** + * Show the element when the load ended + * @param element + */ +export function doAutoFocus(element: HTMLElement | null): void { + if (element) { + setTimeout(() => { + element.focus({ preventScroll: true }); + element.scrollIntoView({ + behavior: "smooth", + block: "center", + inline: "center", + }); + }, 100); + } +} + +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; +} diff --git a/packages/challenger-ui/src/pages/AskChallenge.tsx b/packages/challenger-ui/src/pages/AskChallenge.tsx new file mode 100644 index 000000000..30b50d707 --- /dev/null +++ b/packages/challenger-ui/src/pages/AskChallenge.tsx @@ -0,0 +1,263 @@ +/* + 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 { HttpStatusCode } from "@gnu-taler/taler-util"; +import { + Attention, + Button, + LocalNotificationBanner, + RouteDefinition, + ShowInputErrorLabel, + useChallengerApiContext, + useLocalNotificationHandler, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; +import { useState } from "preact/hooks"; +import { useSessionState } from "../hooks/session.js"; +import { doAutoFocus } from "./AnswerChallenge.js"; + +type Form = { + email: string; +}; +export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/; + +type Props = { + nonce: string; + onSendSuccesful: () => void; + routeSolveChallenge: RouteDefinition<{ nonce: string }>; + focus?: boolean; +}; + +export function AskChallenge({ + nonce, + onSendSuccesful, + routeSolveChallenge, + focus, +}: Props): VNode { + const { state, accepted, completed } = useSessionState(); + const status = state?.lastStatus; + const prevEmail = + !status || !status.last_address ? undefined : status.last_address["email"]; + const regexEmail = + !status || !status.restrictions ? undefined : status.restrictions["email"]; + + const { lib } = useChallengerApiContext(); + const { i18n } = useTranslationContext(); + const [notification, withErrorHandler] = useLocalNotificationHandler(); + 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` + : !regexTest.test(email) + ? regexHint + : prevEmail !== undefined && email === prevEmail + ? i18n.str`email should be different` + : undefined, + repeat: !repeat + ? i18n.str`required` + : email !== repeat + ? i18n.str`emails doesn't match` + : undefined, + }); + + const onSend = errors + ? undefined + : withErrorHandler( + async () => { + return lib.challenger.challenge(nonce, { email: email! }); + }, + (ok) => { + if ("redirectURL" in ok.body) { + completed(ok.body.redirectURL); + } else { + accepted({ + attemptsLeft: ok.body.attempts_left, + nextSend: ok.body.next_tx_time, + transmitted: ok.body.transmitted, + }); + } + onSendSuccesful(); + }, + (fail) => { + switch (fail.case) { + case HttpStatusCode.BadRequest: + return i18n.str``; + case HttpStatusCode.NotFound: + return i18n.str``; + case HttpStatusCode.NotAcceptable: + return i18n.str``; + case HttpStatusCode.TooManyRequests: + return i18n.str``; + case HttpStatusCode.InternalServerError: + return i18n.str``; + } + }, + ); + + if (!status) { + return <div>no status loaded</div>; + } + + return ( + <Fragment> + <LocalNotificationBanner notification={notification} /> + + <div class="isolate bg-white px-6 py-12"> + <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>Enter contact details</i18n.Translate> + </h2> + <p class="mt-2 text-lg leading-8 text-gray-600"> + <i18n.Translate> + You will receive an email with a TAN code that must be provided on + the next page. + </i18n.Translate> + </p> + </div> + {state.lastTry && ( + <Fragment> + <Attention title={i18n.str`A code has been sent to ${prevEmail}`}> + <i18n.Translate> + <a href={routeSolveChallenge.url({ nonce })} class="underline"> + <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" + onSubmit={(e) => { + e.preventDefault(); + }} + > + <div class="grid grid-cols-1 gap-x-8 gap-y-6"> + <div class="sm:col-span-2"> + <label + for="email" + class="block text-sm font-semibold leading-6 text-gray-900" + > + <i18n.Translate>Email</i18n.Translate> + </label> + <div class="mt-2.5"> + <input + type="email" + name="email" + id="email" + ref={focus ? doAutoFocus : undefined} + maxLength={512} + autocomplete="email" + value={email} + onChange={(e) => { + setEmail(e.currentTarget.value); + }} + placeholder={prevEmail} + readOnly={status.fix_address} + 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} + isDirty={email !== undefined} + /> + </div> + </div> + + {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> + )} + + {!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> + + {!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> + ); +} + +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; +} diff --git a/packages/challenger-ui/src/pages/CallengeCompleted.tsx b/packages/challenger-ui/src/pages/CallengeCompleted.tsx new file mode 100644 index 000000000..f8cd7ce60 --- /dev/null +++ b/packages/challenger-ui/src/pages/CallengeCompleted.tsx @@ -0,0 +1,26 @@ +/* + 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 { VNode, h } from "preact"; + +type Props = { + nonce: string; +} +export function CallengeCompleted({nonce}:Props):VNode { + + return <div> + completed {nonce} + </div> +}
\ No newline at end of file diff --git a/packages/challenger-ui/src/pages/Frame.tsx b/packages/challenger-ui/src/pages/Frame.tsx new file mode 100644 index 000000000..612eced0b --- /dev/null +++ b/packages/challenger-ui/src/pages/Frame.tsx @@ -0,0 +1,69 @@ +/* + 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 { ComponentChildren, Fragment, h, VNode } from "preact"; + +export function Frame({ children }: { children: ComponentChildren }): VNode { + return ( + <Fragment> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"> + <a href="#"> + <img + class="h-8 w-auto" + src='data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>' + alt="GNU Taler" + style="height: 1.5rem; margin: 0.5rem;" + /> + </a> + </div> + <span class="flex items-center text-white text-lg font-bold ml-4"> + Challenger + </span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"></div> + </div> + </header> + + <main class="flex-1">{children}</main> + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400"> + Learn more about{" "} + <a + target="_blank" + rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" + href="https://taler.net" + > + GNU Taler + </a> + </p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400"> + Copyright © 2014—2023 Taler Systems SA.{" "} + </p> + </div> + </footer> + </Fragment> + ); +} diff --git a/packages/challenger-ui/src/pages/MissingParams.tsx b/packages/challenger-ui/src/pages/MissingParams.tsx new file mode 100644 index 000000000..5eb1e434e --- /dev/null +++ b/packages/challenger-ui/src/pages/MissingParams.tsx @@ -0,0 +1,22 @@ +/* + 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 { VNode, h } from "preact"; + +export function MissingParams() :VNode { + return <div> + missing params: {window.location.href} + </div> +}
\ No newline at end of file diff --git a/packages/challenger-ui/src/pages/NonceNotFound.tsx b/packages/challenger-ui/src/pages/NonceNotFound.tsx new file mode 100644 index 000000000..16b3d90ef --- /dev/null +++ b/packages/challenger-ui/src/pages/NonceNotFound.tsx @@ -0,0 +1,42 @@ +/* + 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 { + useTranslationContext +} from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; + +type Form = { + email: string; +}; + +export function NonceNotFound(): VNode { + const { i18n } = useTranslationContext(); + + return ( + <Fragment> + <div class="isolate bg-white px-6 py-12"> + <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>The URL is wrong</i18n.Translate> + </h2> + <p class="mt-2 text-lg leading-8 text-gray-600"> + <i18n.Translate>Maybe the validation check expired.</i18n.Translate> + </p> + </div> + </div> + </Fragment> + ); +} diff --git a/packages/challenger-ui/src/pages/Setup.tsx b/packages/challenger-ui/src/pages/Setup.tsx new file mode 100644 index 000000000..f431835aa --- /dev/null +++ b/packages/challenger-ui/src/pages/Setup.tsx @@ -0,0 +1,82 @@ +/* + 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 { AccessToken, HttpStatusCode, encodeCrock, randomBytes } from "@gnu-taler/taler-util"; +import { + Button, + LocalNotificationBanner, + useChallengerApiContext, + useLocalNotificationHandler, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; +import { useSessionState } from "../hooks/session.js"; + +type Props = { + clientId: string; + onCreated: (nonce:string) => void; +}; +export function Setup({ clientId, onCreated }: Props): VNode { + const { i18n } = useTranslationContext(); + const [notification, withErrorHandler] = useLocalNotificationHandler(); + const { lib } = useChallengerApiContext(); + const { start } = useSessionState(); + + const onStart = withErrorHandler( + async () => { + return lib.challenger.setup(clientId, "secret-token:chal-secret" as AccessToken); + }, + (ok) => { + start({ + clientId, + redirectURL: "http://exchange.taler.test:1180/kyc-proof/kyc-provider-wallet", + state: encodeCrock(randomBytes(32)), + }); + + onCreated(ok.body.nonce); + }, + (fail) => { + switch (fail.case) { + case HttpStatusCode.NotFound: + return i18n.str`Client doesn't exist.`; + } + }, + ); + + return ( + <Fragment> + <LocalNotificationBanner notification={notification} /> + + <div class="isolate bg-white px-6 py-12"> + <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> + Setup new challenge with client ID: "{clientId}" + </i18n.Translate> + </h2> + </div> + <div class="mt-10"> + <Button + type="submit" + 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={onStart} + > + <i18n.Translate>Start</i18n.Translate> + </Button> + </div> + </div> + </Fragment> + ); +} diff --git a/packages/challenger-ui/src/scss/main.css b/packages/challenger-ui/src/scss/main.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/packages/challenger-ui/src/scss/main.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/challenger-ui/src/settings.json b/packages/challenger-ui/src/settings.json new file mode 100644 index 000000000..b3d0476aa --- /dev/null +++ b/packages/challenger-ui/src/settings.json @@ -0,0 +1,3 @@ +{ + "backendBaseURL": "http://challenger.taler.test:1180/" +} diff --git a/packages/challenger-ui/src/settings.ts b/packages/challenger-ui/src/settings.ts new file mode 100644 index 000000000..61d2117fa --- /dev/null +++ b/packages/challenger-ui/src/settings.ts @@ -0,0 +1,83 @@ +/* + 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 { + Codec, + buildCodecForObject, + canonicalizeBaseUrl, + codecForString, + codecOptional +} from "@gnu-taler/taler-util"; + +export interface ChallengerUiSettings { + // Where challenger backend is localted + // default: window.origin without "webui/" + backendBaseURL?: string; + +} + +/** + * Global settings for the bank UI. + */ +const defaultSettings: ChallengerUiSettings = { + backendBaseURL: buildDefaultBackendBaseURL(), +}; + +const codecForChallengerUISettings = (): Codec<ChallengerUiSettings> => + buildCodecForObject<ChallengerUiSettings>() + .property("backendBaseURL", codecOptional(codecForString())) + .build("codecForChallengerUISettings"); + +function removeUndefineField<T extends object>(obj: T): T { + const keys = Object.keys(obj) as Array<keyof T>; + return keys.reduce((prev, cur) => { + if (typeof prev[cur] === "undefined") { + delete prev[cur]; + } + return prev; + }, obj); +} + +export function fetchSettings(listener: (s: ChallengerUiSettings) => void): void { + fetch("./settings.json") + .then((resp) => resp.json()) + .then((json) => codecForChallengerUISettings().decode(json)) + .then((result) => + listener({ + ...defaultSettings, + ...removeUndefineField(result), + }), + ) + .catch((e) => { + console.log("failed to fetch settings", e); + listener(defaultSettings); + }); +} + +function buildDefaultBackendBaseURL(): string | undefined { + if (typeof window !== "undefined") { + const currentLocation = new URL( + window.location.pathname, + window.location.origin, + ).href; + /** + * By default, bank backend serves the html content + * from the /webui root. + */ + return canonicalizeBaseUrl(currentLocation.replace("/webui", "")); + } + throw Error("No default URL"); +} diff --git a/packages/challenger-ui/src/validation-unknown.html b/packages/challenger-ui/src/validation-unknown.html new file mode 100644 index 000000000..56c8d156c --- /dev/null +++ b/packages/challenger-ui/src/validation-unknown.html @@ -0,0 +1,89 @@ +<!-- + This file is part of GNU Taler + (C) 2021--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/> + + @author Sebastian Javier Marchano +--> +<!DOCTYPE html> +<html lang="en" class="h-full"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="taler-support" content="uri"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <link rel="icon" + href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + <link rel="stylesheet" href="main.css" /> + <script type="module" src="main.js"></script> + <title>Validation process unknown (#{{ec}})</title> +</head> + +<body class="min-h-full flex flex-col"> + <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> + <div class="flex flex-row h-16 items-center "> + <div class="flex px-2 justify-start"> + <div class="flex-shrink-0 bg-white rounded-lg"><a href="#"><img class="h-8 w-auto" + src="data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">%0A <g fill="%230042b3" fill-rule="evenodd" stroke-width=".3">%0A <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />%0A <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />%0A <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />%0A </g>%0A <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />%0A</svg>" + alt="GNU Taler" style="height: 1.5rem; margin: 0.5rem;"></a></div><span + class="flex items-center text-white text-lg font-bold ml-4">Challenger</span> + </div> + <div class="block flex-1 ml-6 "></div> + <div class="flex justify-end"> + </div> + </div> + </header> + + <main class="flex-1"> + + <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 items-center mt-4"> + <div class="rounded-md bg-red-50 p-4 shadow-xl"> + <div class="flex"> + <div class="flex-shrink-0"> + <svg xmlns="http://www.w3.org/2000/svg" stroke="none" viewBox="0 0 24 24" fill="currentColor" + class="w-8 h-8 text-red-400"> + <path fill-rule="evenodd" + d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" /> + </svg> + </div> + <div class="ml-3"> + <h3 class="text-sm font-medium text-red-800"> + Validation error + </h3> + <div class="mt-2 text-sm text-red-700"> + <p>{{hint}}</p> + </div> + </div> + </div> + </div> + </div> + + </main> + + <footer class="bottom-4 mb-4"> + <div class="mt-8 mx-8 md:order-1 md:mt-0"> + <div> + <p class="text-xs leading-5 text-gray-400">Learn more about <a target="_blank" rel="noreferrer noopener" + class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">GNU Taler</a></p> + </div> + <div style="flex-grow: 1;"></div> + <p class="text-xs leading-5 text-gray-400">Copyright © 2014—2023 Taler Systems SA. </p> + </div> + </footer> +</body> + +</html>
\ No newline at end of file diff --git a/packages/challenger-ui/tailwind.config.js b/packages/challenger-ui/tailwind.config.js new file mode 100644 index 000000000..d384690e2 --- /dev/null +++ b/packages/challenger-ui/tailwind.config.js @@ -0,0 +1,28 @@ +/* + 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/> + */ +export default { + content: { + relative: true, + files: [ + "./src/**/*.{html,tsx}", + "./node_modules/@gnu-taler/web-util/src/**/*.{html,tsx}" + ], + }, + theme: { + extend: {}, + }, + plugins: [require("@tailwindcss/typography"), require("@tailwindcss/forms")], +}; diff --git a/packages/challenger-ui/tsconfig.json b/packages/challenger-ui/tsconfig.json new file mode 100644 index 000000000..9826fac07 --- /dev/null +++ b/packages/challenger-ui/tsconfig.json @@ -0,0 +1,46 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "ES2020", + "module": "Node16", + "lib": ["DOM", "ES2020"], + "allowJs": true /* Allow javascript files to be compiled. */, + // "checkJs": true, /* Report errors in .js files. */ + "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "noEmit": true /* Do not emit outputs. */, + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + "moduleResolution": "Node16" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, + "esModuleInterop": true /* */, + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + /* Source Map Options */ + // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */ + }, + "include": ["src/**/*"] +} |