diff options
Diffstat (limited to 'packages/anastasis-webui')
90 files changed, 4183 insertions, 2794 deletions
diff --git a/packages/anastasis-webui/package.json b/packages/anastasis-webui/package.json index 96d2d65f9..7a92afd43 100644 --- a/packages/anastasis-webui/package.json +++ b/packages/anastasis-webui/package.json @@ -5,11 +5,14 @@ "license": "MIT", "scripts": { "build": "preact build --no-sw --no-esm", - "serve": "sirv build --port 8080 --cors --single", - "dev": "preact watch --no-sw --no-esm", + "serve": "sirv build --port ${PORT:=8080} --cors --single", + "dev": "preact watch --port ${PORT:=8080} --no-sw --no-esm", "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'", "test": "jest ./tests", "build-storybook": "build-storybook", + "build-single": "preact build --no-sw --no-esm -c preact.single-config.js --dest single && sh remove-link-stylesheet.sh", + "serve-single": "sirv single --port ${PORT:=8080} --cors --single", + "pretty": "prettier --write src", "storybook": "start-storybook -p 6006" }, "eslintConfig": { @@ -25,6 +28,7 @@ "dependencies": { "@gnu-taler/taler-util": "workspace:^0.8.3", "anastasis-core": "workspace:^0.0.1", + "base64-inline-loader": "1.1.1", "date-fns": "2.25.0", "jed": "1.1.1", "preact": "^10.5.15", diff --git a/packages/anastasis-webui/src/.babelrc b/packages/anastasis-webui/src/.babelrc index 123002210..05f4dcc81 100644 --- a/packages/anastasis-webui/src/.babelrc +++ b/packages/anastasis-webui/src/.babelrc @@ -1,5 +1,3 @@ { - "presets": [ - "preact-cli/babel" - ] + "presets": ["preact-cli/babel"] } diff --git a/packages/anastasis-webui/src/components/AsyncButton.tsx b/packages/anastasis-webui/src/components/AsyncButton.tsx index 92bef2219..33f3a7258 100644 --- a/packages/anastasis-webui/src/components/AsyncButton.tsx +++ b/packages/anastasis-webui/src/components/AsyncButton.tsx @@ -15,9 +15,9 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { ComponentChildren, h, VNode } from "preact"; // import { LoadingModal } from "../modal"; @@ -31,19 +31,26 @@ type Props = { [rest: string]: any; }; -export function AsyncButton({ onClick, disabled, children, ...rest }: Props): VNode { +export function AsyncButton({ + onClick, + disabled, + children, + ...rest +}: Props): VNode { const { isLoading, request } = useAsync(onClick); // if (isSlow) { // return <LoadingModal onCancel={cancel} />; // } - if (isLoading) { + if (isLoading) { return <button class="button">Loading...</button>; } - return <span data-tooltip={rest['data-tooltip']} style={{marginLeft: 5}}> - <button {...rest} onClick={request} disabled={disabled}> - {children} - </button> - </span>; + return ( + <span data-tooltip={rest["data-tooltip"]} style={{ marginLeft: 5 }}> + <button {...rest} onClick={request} disabled={disabled}> + {children} + </button> + </span> + ); } diff --git a/packages/anastasis-webui/src/components/Notifications.tsx b/packages/anastasis-webui/src/components/Notifications.tsx index 097ebb4de..e34550386 100644 --- a/packages/anastasis-webui/src/components/Notifications.tsx +++ b/packages/anastasis-webui/src/components/Notifications.tsx @@ -15,9 +15,9 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; @@ -27,7 +27,7 @@ export interface Notification { type: MessageType; } -export type MessageType = 'INFO' | 'WARN' | 'ERROR' | 'SUCCESS' +export type MessageType = "INFO" | "WARN" | "ERROR" | "SUCCESS"; interface Props { notifications: Notification[]; @@ -36,24 +36,39 @@ interface Props { function messageStyle(type: MessageType): string { switch (type) { - case "INFO": return "message is-info"; - case "WARN": return "message is-warning"; - case "ERROR": return "message is-danger"; - case "SUCCESS": return "message is-success"; - default: return "message" + case "INFO": + return "message is-info"; + case "WARN": + return "message is-warning"; + case "ERROR": + return "message is-danger"; + case "SUCCESS": + return "message is-success"; + default: + return "message"; } } -export function Notifications({ notifications, removeNotification }: Props): VNode { - return <div class="block"> - {notifications.map((n, i) => <article key={i} class={messageStyle(n.type)}> - <div class="message-header"> - <p>{n.message}</p> - {removeNotification && <button class="delete" onClick={() => removeNotification && removeNotification(n)} />} - </div> - {n.description && <div class="message-body"> - {n.description} - </div>} - </article>)} - </div> -}
\ No newline at end of file +export function Notifications({ + notifications, + removeNotification, +}: Props): VNode { + return ( + <div class="block"> + {notifications.map((n, i) => ( + <article key={i} class={messageStyle(n.type)}> + <div class="message-header"> + <p>{n.message}</p> + {removeNotification && ( + <button + class="delete" + onClick={() => removeNotification && removeNotification(n)} + /> + )} + </div> + {n.description && <div class="message-body">{n.description}</div>} + </article> + ))} + </div> + ); +} diff --git a/packages/anastasis-webui/src/components/QR.tsx b/packages/anastasis-webui/src/components/QR.tsx index 48f1a7c12..9a05f6097 100644 --- a/packages/anastasis-webui/src/components/QR.tsx +++ b/packages/anastasis-webui/src/components/QR.tsx @@ -21,15 +21,28 @@ import qrcode from "qrcode-generator"; export function QR({ text }: { text: string }): VNode { const divRef = useRef<HTMLDivElement>(null); useEffect(() => { - const qr = qrcode(0, 'L'); + const qr = qrcode(0, "L"); qr.addData(text); qr.make(); - if (divRef.current) divRef.current.innerHTML = qr.createSvgTag({ - scalable: true, - }); + if (divRef.current) + divRef.current.innerHTML = qr.createSvgTag({ + scalable: true, + }); }); - return <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}> - <div style={{ width: '50%', minWidth: 200, maxWidth: 300 }} ref={divRef} /> - </div>; + return ( + <div + style={{ + width: "100%", + display: "flex", + flexDirection: "column", + alignItems: "center", + }} + > + <div + style={{ width: "50%", minWidth: 200, maxWidth: 300 }} + ref={divRef} + /> + </div> + ); } diff --git a/packages/anastasis-webui/src/components/app.tsx b/packages/anastasis-webui/src/components/app.tsx index c6b4cfc14..4c6683c0c 100644 --- a/packages/anastasis-webui/src/components/app.tsx +++ b/packages/anastasis-webui/src/components/app.tsx @@ -1,6 +1,5 @@ import { FunctionalComponent, h } from "preact"; import { TranslationProvider } from "../context/translation"; - import AnastasisClient from "../pages/home"; const App: FunctionalComponent = () => { diff --git a/packages/anastasis-webui/src/components/fields/DateInput.tsx b/packages/anastasis-webui/src/components/fields/DateInput.tsx index 3148c953f..0b6a7e316 100644 --- a/packages/anastasis-webui/src/components/fields/DateInput.tsx +++ b/packages/anastasis-webui/src/components/fields/DateInput.tsx @@ -19,56 +19,66 @@ export function DateInput(props: DateInputProps): VNode { inputRef.current?.focus(); } }, [props.grabFocus]); - const [opened, setOpened] = useState(false) + const [opened, setOpened] = useState(false); const value = props.bind[0] || ""; - const [dirty, setDirty] = useState(false) - const showError = dirty && props.error + const [dirty, setDirty] = useState(false); + const showError = dirty && props.error; - const calendar = subYears(new Date(), 30) - - return <div class="field"> - <label class="label"> - {props.label} - {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - <div class="control"> - <div class="field has-addons"> - <p class="control"> - <input - type="text" - class={showError ? 'input is-danger' : 'input'} - value={value} - onInput={(e) => { - const text = e.currentTarget.value - setDirty(true) - props.bind[1](text); - }} - ref={inputRef} /> - </p> - <p class="control"> - <a class="button" onClick={() => { setOpened(true) }}> - <span class="icon"><i class="mdi mdi-calendar" /></span> - </a> - </p> + const calendar = subYears(new Date(), 30); + + return ( + <div class="field"> + <label class="label"> + {props.label} + {props.tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + <div class="control"> + <div class="field has-addons"> + <p class="control"> + <input + type="text" + class={showError ? "input is-danger" : "input"} + value={value} + onInput={(e) => { + const text = e.currentTarget.value; + setDirty(true); + props.bind[1](text); + }} + ref={inputRef} + /> + </p> + <p class="control"> + <a + class="button" + onClick={() => { + setOpened(true); + }} + > + <span class="icon"> + <i class="mdi mdi-calendar" /> + </span> + </a> + </p> + </div> </div> + <p class="help">Using the format yyyy-mm-dd</p> + {showError && <p class="help is-danger">{props.error}</p>} + <DatePicker + opened={opened} + initialDate={calendar} + years={props.years} + closeFunction={() => setOpened(false)} + dateReceiver={(d) => { + setDirty(true); + const v = format(d, "yyyy-MM-dd"); + props.bind[1](v); + }} + /> </div> - <p class="help">Using the format yyyy-mm-dd</p> - {showError && <p class="help is-danger">{props.error}</p>} - <DatePicker - opened={opened} - initialDate={calendar} - years={props.years} - closeFunction={() => setOpened(false)} - dateReceiver={(d) => { - setDirty(true) - const v = format(d, 'yyyy-MM-dd') - props.bind[1](v); - }} - /> - </div> - ; - + ); } diff --git a/packages/anastasis-webui/src/components/fields/EmailInput.tsx b/packages/anastasis-webui/src/components/fields/EmailInput.tsx index e21418fea..fe676f284 100644 --- a/packages/anastasis-webui/src/components/fields/EmailInput.tsx +++ b/packages/anastasis-webui/src/components/fields/EmailInput.tsx @@ -18,27 +18,34 @@ export function EmailInput(props: TextInputProps): VNode { } }, [props.grabFocus]); const value = props.bind[0]; - const [dirty, setDirty] = useState(false) - const showError = dirty && props.error - return (<div class="field"> - <label class="label"> - {props.label} - {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - <div class="control has-icons-right"> - <input - value={value} - required - placeholder={props.placeholder} - type="email" - class={showError ? 'input is-danger' : 'input'} - onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} - ref={inputRef} - style={{ display: "block" }} /> + const [dirty, setDirty] = useState(false); + const showError = dirty && props.error; + return ( + <div class="field"> + <label class="label"> + {props.label} + {props.tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + <div class="control has-icons-right"> + <input + value={value} + required + placeholder={props.placeholder} + type="email" + class={showError ? "input is-danger" : "input"} + onInput={(e) => { + setDirty(true); + props.bind[1]((e.target as HTMLInputElement).value); + }} + ref={inputRef} + style={{ display: "block" }} + /> + </div> + {showError && <p class="help is-danger">{props.error}</p>} </div> - {showError && <p class="help is-danger">{props.error}</p>} - </div> ); } diff --git a/packages/anastasis-webui/src/components/fields/FileInput.tsx b/packages/anastasis-webui/src/components/fields/FileInput.tsx index 8b144ea43..52d6eab4a 100644 --- a/packages/anastasis-webui/src/components/fields/FileInput.tsx +++ b/packages/anastasis-webui/src/components/fields/FileInput.tsx @@ -15,14 +15,14 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; import { useLayoutEffect, useRef, useState } from "preact/hooks"; import { TextInputProps } from "./TextInput"; -const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024 +const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024; export function FileInput(props: TextInputProps): VNode { const inputRef = useRef<HTMLInputElement>(null); @@ -34,48 +34,54 @@ export function FileInput(props: TextInputProps): VNode { const value = props.bind[0]; // const [dirty, setDirty] = useState(false) - const image = useRef<HTMLInputElement>(null) - const [sizeError, setSizeError] = useState(false) + const image = useRef<HTMLInputElement>(null); + const [sizeError, setSizeError] = useState(false); function onChange(v: string): void { // setDirty(true); props.bind[1](v); } - return <div class="field"> - <label class="label"> - <a onClick={() => image.current?.click()}> - {props.label} - </a> - {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - <div class="control"> - <input - ref={image} style={{ display: 'none' }} - type="file" name={String(name)} - onChange={e => { - const f: FileList | null = e.currentTarget.files - if (!f || f.length != 1) { - return onChange("") - } - if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { - setSizeError(true) - return onChange("") - } - setSizeError(false) - return f[0].arrayBuffer().then(b => { - const b64 = btoa( - new Uint8Array(b) - .reduce((data, byte) => data + String.fromCharCode(byte), '') - ) - return onChange(`data:${f[0].type};base64,${b64}` as any) - }) - }} /> - {props.error && <p class="help is-danger">{props.error}</p>} - {sizeError && <p class="help is-danger"> - File should be smaller than 1 MB - </p>} + return ( + <div class="field"> + <label class="label"> + <a onClick={() => image.current?.click()}>{props.label}</a> + {props.tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + <div class="control"> + <input + ref={image} + style={{ display: "none" }} + type="file" + name={String(name)} + onChange={(e) => { + const f: FileList | null = e.currentTarget.files; + if (!f || f.length != 1) { + return onChange(""); + } + if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { + setSizeError(true); + return onChange(""); + } + setSizeError(false); + return f[0].arrayBuffer().then((b) => { + const b64 = btoa( + new Uint8Array(b).reduce( + (data, byte) => data + String.fromCharCode(byte), + "", + ), + ); + return onChange(`data:${f[0].type};base64,${b64}` as any); + }); + }} + /> + {props.error && <p class="help is-danger">{props.error}</p>} + {sizeError && ( + <p class="help is-danger">File should be smaller than 1 MB</p> + )} + </div> </div> - </div> + ); } - diff --git a/packages/anastasis-webui/src/components/fields/ImageInput.tsx b/packages/anastasis-webui/src/components/fields/ImageInput.tsx index d5bf643d4..3f8cc58dd 100644 --- a/packages/anastasis-webui/src/components/fields/ImageInput.tsx +++ b/packages/anastasis-webui/src/components/fields/ImageInput.tsx @@ -15,15 +15,15 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; import { useLayoutEffect, useRef, useState } from "preact/hooks"; import emptyImage from "../../assets/empty.png"; import { TextInputProps } from "./TextInput"; -const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024 +const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024; export function ImageInput(props: TextInputProps): VNode { const inputRef = useRef<HTMLInputElement>(null); @@ -35,47 +35,59 @@ export function ImageInput(props: TextInputProps): VNode { const value = props.bind[0]; // const [dirty, setDirty] = useState(false) - const image = useRef<HTMLInputElement>(null) - const [sizeError, setSizeError] = useState(false) + const image = useRef<HTMLInputElement>(null); + const [sizeError, setSizeError] = useState(false); function onChange(v: string): void { // setDirty(true); props.bind[1](v); } - return <div class="field"> - <label class="label"> - {props.label} - {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - <div class="control"> - <img src={!value ? emptyImage : value} style={{ width: 200, height: 200 }} onClick={() => image.current?.click()} /> - <input - ref={image} style={{ display: 'none' }} - type="file" name={String(name)} - onChange={e => { - const f: FileList | null = e.currentTarget.files - if (!f || f.length != 1) { - return onChange(emptyImage) - } - if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { - setSizeError(true) - return onChange(emptyImage) - } - setSizeError(false) - return f[0].arrayBuffer().then(b => { - const b64 = btoa( - new Uint8Array(b) - .reduce((data, byte) => data + String.fromCharCode(byte), '') - ) - return onChange(`data:${f[0].type};base64,${b64}` as any) - }) - }} /> - {props.error && <p class="help is-danger">{props.error}</p>} - {sizeError && <p class="help is-danger"> - Image should be smaller than 1 MB - </p>} + return ( + <div class="field"> + <label class="label"> + {props.label} + {props.tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + <div class="control"> + <img + src={!value ? emptyImage : value} + style={{ width: 200, height: 200 }} + onClick={() => image.current?.click()} + /> + <input + ref={image} + style={{ display: "none" }} + type="file" + name={String(name)} + onChange={(e) => { + const f: FileList | null = e.currentTarget.files; + if (!f || f.length != 1) { + return onChange(emptyImage); + } + if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { + setSizeError(true); + return onChange(emptyImage); + } + setSizeError(false); + return f[0].arrayBuffer().then((b) => { + const b64 = btoa( + new Uint8Array(b).reduce( + (data, byte) => data + String.fromCharCode(byte), + "", + ), + ); + return onChange(`data:${f[0].type};base64,${b64}` as any); + }); + }} + /> + {props.error && <p class="help is-danger">{props.error}</p>} + {sizeError && ( + <p class="help is-danger">Image should be smaller than 1 MB</p> + )} + </div> </div> - </div> + ); } - diff --git a/packages/anastasis-webui/src/components/fields/TextInput.tsx b/packages/anastasis-webui/src/components/fields/TextInput.tsx index c093689c5..fd0c658ed 100644 --- a/packages/anastasis-webui/src/components/fields/TextInput.tsx +++ b/packages/anastasis-webui/src/components/fields/TextInput.tsx @@ -18,25 +18,32 @@ export function TextInput(props: TextInputProps): VNode { } }, [props.grabFocus]); const value = props.bind[0]; - const [dirty, setDirty] = useState(false) - const showError = dirty && props.error - return (<div class="field"> - <label class="label"> - {props.label} - {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - <div class="control has-icons-right"> - <input - value={value} - placeholder={props.placeholder} - class={showError ? 'input is-danger' : 'input'} - onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} - ref={inputRef} - style={{ display: "block" }} /> + const [dirty, setDirty] = useState(false); + const showError = dirty && props.error; + return ( + <div class="field"> + <label class="label"> + {props.label} + {props.tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + <div class="control has-icons-right"> + <input + value={value} + placeholder={props.placeholder} + class={showError ? "input is-danger" : "input"} + onInput={(e) => { + setDirty(true); + props.bind[1]((e.target as HTMLInputElement).value); + }} + ref={inputRef} + style={{ display: "block" }} + /> + </div> + {showError && <p class="help is-danger">{props.error}</p>} </div> - {showError && <p class="help is-danger">{props.error}</p>} - </div> ); } diff --git a/packages/anastasis-webui/src/components/menu/LangSelector.tsx b/packages/anastasis-webui/src/components/menu/LangSelector.tsx index 0f91abd7e..fa22a29c0 100644 --- a/packages/anastasis-webui/src/components/menu/LangSelector.tsx +++ b/packages/anastasis-webui/src/components/menu/LangSelector.tsx @@ -15,59 +15,78 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import langIcon from '../../assets/icons/languageicon.svg'; +import langIcon from "../../assets/icons/languageicon.svg"; import { useTranslationContext } from "../../context/translation"; -import { strings as messages } from '../../i18n/strings' +import { strings as messages } from "../../i18n/strings"; type LangsNames = { - [P in keyof typeof messages]: string -} + [P in keyof typeof messages]: string; +}; const names: LangsNames = { - es: 'Español [es]', - en: 'English [en]', - fr: 'Français [fr]', - de: 'Deutsch [de]', - sv: 'Svenska [sv]', - it: 'Italiano [it]', -} + es: "Español [es]", + en: "English [en]", + fr: "Français [fr]", + de: "Deutsch [de]", + sv: "Svenska [sv]", + it: "Italiano [it]", +}; function getLangName(s: keyof LangsNames | string): string { - if (names[s]) return names[s] - return String(s) + if (names[s]) return names[s]; + return String(s); } export function LangSelector(): VNode { - const [updatingLang, setUpdatingLang] = useState(false) - const { lang, changeLanguage } = useTranslationContext() + const [updatingLang, setUpdatingLang] = useState(false); + const { lang, changeLanguage } = useTranslationContext(); - return <div class="dropdown is-active "> - <div class="dropdown-trigger"> - <button class="button has-tooltip-left" - data-tooltip="change language selection" - aria-haspopup="true" - aria-controls="dropdown-menu" onClick={() => setUpdatingLang(!updatingLang)}> - <div class="icon is-small is-left"> - <img src={langIcon} /> - </div> - <span>{getLangName(lang)}</span> - <div class="icon is-right"> - <i class="mdi mdi-chevron-down" /> + return ( + <div class="dropdown is-active "> + <div class="dropdown-trigger"> + <button + class="button has-tooltip-left" + data-tooltip="change language selection" + aria-haspopup="true" + aria-controls="dropdown-menu" + onClick={() => setUpdatingLang(!updatingLang)} + > + <div class="icon is-small is-left"> + <img src={langIcon} /> + </div> + <span>{getLangName(lang)}</span> + <div class="icon is-right"> + <i class="mdi mdi-chevron-down" /> + </div> + </button> + </div> + {updatingLang && ( + <div class="dropdown-menu" id="dropdown-menu" role="menu"> + <div class="dropdown-content"> + {Object.keys(messages) + .filter((l) => l !== lang) + .map((l) => ( + <a + key={l} + class="dropdown-item" + value={l} + onClick={() => { + changeLanguage(l); + setUpdatingLang(false); + }} + > + {getLangName(l)} + </a> + ))} + </div> </div> - </button> + )} </div> - {updatingLang && <div class="dropdown-menu" id="dropdown-menu" role="menu"> - <div class="dropdown-content"> - {Object.keys(messages) - .filter((l) => l !== lang) - .map(l => <a key={l} class="dropdown-item" value={l} onClick={() => { changeLanguage(l); setUpdatingLang(false) }}>{getLangName(l)}</a>)} - </div> - </div>} - </div> -}
\ No newline at end of file + ); +} diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx index a40f4be09..c73369dd6 100644 --- a/packages/anastasis-webui/src/components/menu/SideBar.tsx +++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx @@ -15,16 +15,15 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { Fragment, h, VNode } from 'preact'; -import { BackupStates, RecoveryStates } from '../../../../anastasis-core/lib'; -import { useAnastasisContext } from '../../context/anastasis'; -import { Translate } from '../../i18n'; -import { LangSelector } from './LangSelector'; +import { Fragment, h, VNode } from "preact"; +import { BackupStates, RecoveryStates } from "../../../../anastasis-core/lib"; +import { useAnastasisContext } from "../../context/anastasis"; +import { Translate } from "../../i18n"; +import { LangSelector } from "./LangSelector"; interface Props { mobile?: boolean; @@ -32,10 +31,10 @@ interface Props { export function Sidebar({ mobile }: Props): VNode { // const config = useConfigContext(); - const config = { version: 'none' } + const config = { version: "none" }; // FIXME: add replacement for __VERSION__ with the current version - const process = { env: { __VERSION__: '0.0.0' } } - const reducer = useAnastasisContext()! + const process = { env: { __VERSION__: "0.0.0" } }; + const reducer = useAnastasisContext()!; return ( <aside class="aside is-placed-left is-expanded"> @@ -44,114 +43,235 @@ export function Sidebar({ mobile }: Props): VNode { </div>} */} <div class="aside-tools"> <div class="aside-tools-label"> - <div><b>Anastasis</b></div> - <div class="is-size-7 has-text-right" style={{ lineHeight: 0, marginTop: -10 }}> + <div> + <b>Anastasis</b> + </div> + <div + class="is-size-7 has-text-right" + style={{ lineHeight: 0, marginTop: -10 }} + > Version {process.env.__VERSION__} ({config.version}) </div> </div> </div> <div class="menu is-menu-main"> - {!reducer.currentReducerState && + {!reducer.currentReducerState && ( <p class="menu-label"> <Translate>Backup or Recorver</Translate> </p> - } + )} <ul class="menu-list"> - {!reducer.currentReducerState && + {!reducer.currentReducerState && ( <li> <div class="ml-4"> - <span class="menu-item-label"><Translate>Select one option</Translate></span> - </div> - </li> - } - {reducer.currentReducerState && reducer.currentReducerState.backup_state ? <Fragment> - <li class={reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting || - reducer.currentReducerState.backup_state === BackupStates.CountrySelecting ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Location</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.backup_state === BackupStates.UserAttributesCollecting ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Personal information</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.backup_state === BackupStates.AuthenticationsEditing ? 'is-active' : ''}> - <div class="ml-4"> - - <span class="menu-item-label"><Translate>Authorization methods</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesReviewing ? 'is-active' : ''}> - <div class="ml-4"> - - <span class="menu-item-label"><Translate>Policies</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.backup_state === BackupStates.SecretEditing ? 'is-active' : ''}> - <div class="ml-4"> - - <span class="menu-item-label"><Translate>Secret input</Translate></span> + <span class="menu-item-label"> + <Translate>Select one option</Translate> + </span> </div> </li> - {/* <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}> + )} + {reducer.currentReducerState && + reducer.currentReducerState.backup_state ? ( + <Fragment> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.ContinentSelecting || + reducer.currentReducerState.backup_state === + BackupStates.CountrySelecting + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Location</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.UserAttributesCollecting + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Personal information</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.AuthenticationsEditing + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Authorization methods</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.PoliciesReviewing + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Policies</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.SecretEditing + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Secret input</Translate> + </span> + </div> + </li> + {/* <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}> <div class="ml-4"> <span class="menu-item-label"><Translate>Payment (optional)</Translate></span> </div> </li> */} - <li class={reducer.currentReducerState.backup_state === BackupStates.BackupFinished ? 'is-active' : ''}> - <div class="ml-4"> - - <span class="menu-item-label"><Translate>Backup completed</Translate></span> - </div> - </li> - {/* <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.BackupFinished + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Backup completed</Translate> + </span> + </div> + </li> + {/* <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}> <div class="ml-4"> <span class="menu-item-label"><Translate>Truth Paying</Translate></span> </div> </li> */} - </Fragment> : (reducer.currentReducerState && reducer.currentReducerState?.recovery_state && <Fragment> - <li class={reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting || - reducer.currentReducerState.recovery_state === RecoveryStates.CountrySelecting ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Location</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.recovery_state === RecoveryStates.UserAttributesCollecting ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Personal information</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.recovery_state === RecoveryStates.SecretSelecting ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Secret selection</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.recovery_state === RecoveryStates.ChallengeSelecting || - reducer.currentReducerState.recovery_state === RecoveryStates.ChallengeSolving ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Solve Challenges</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.recovery_state === RecoveryStates.RecoveryFinished ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Secret recovered</Translate></span> - </div> - </li> - </Fragment>)} - {reducer.currentReducerState && + </Fragment> + ) : ( + reducer.currentReducerState && + reducer.currentReducerState?.recovery_state && ( + <Fragment> + <li + class={ + reducer.currentReducerState.recovery_state === + RecoveryStates.ContinentSelecting || + reducer.currentReducerState.recovery_state === + RecoveryStates.CountrySelecting + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Location</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.recovery_state === + RecoveryStates.UserAttributesCollecting + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Personal information</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.recovery_state === + RecoveryStates.SecretSelecting + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Secret selection</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.recovery_state === + RecoveryStates.ChallengeSelecting || + reducer.currentReducerState.recovery_state === + RecoveryStates.ChallengeSolving + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Solve Challenges</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.recovery_state === + RecoveryStates.RecoveryFinished + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Secret recovered</Translate> + </span> + </div> + </li> + </Fragment> + ) + )} + {reducer.currentReducerState && ( <li> <div class="buttons ml-4"> - <button class="button is-danger is-right" onClick={() => reducer.reset()}>Reset session</button> + <button + class="button is-danger is-right" + onClick={() => reducer.reset()} + > + Reset session + </button> </div> </li> - } - + )} + {/* <li> + <div class="buttons ml-4"> + <button class="button is-info is-right" >Manage providers</button> + </div> + </li> */} </ul> </div> </aside> ); } - diff --git a/packages/anastasis-webui/src/components/menu/index.tsx b/packages/anastasis-webui/src/components/menu/index.tsx index fd4aab149..99d0f7646 100644 --- a/packages/anastasis-webui/src/components/menu/index.tsx +++ b/packages/anastasis-webui/src/components/menu/index.tsx @@ -85,8 +85,8 @@ export function NotificationCard({ n.type === "ERROR" ? "message is-danger" : n.type === "WARN" - ? "message is-warning" - : "message is-info" + ? "message is-warning" + : "message is-info" } > <div class="message-header"> @@ -113,7 +113,7 @@ export function NotYetReadyAppMenu({ return ( <div class="has-aside-mobile-expanded" - // class={mobileOpen ? "has-aside-mobile-expanded" : ""} + // class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() => setMobileOpen(false)} > <NavigationBar diff --git a/packages/anastasis-webui/src/components/picker/DatePicker.tsx b/packages/anastasis-webui/src/components/picker/DatePicker.tsx index eb5d8145d..d689db386 100644 --- a/packages/anastasis-webui/src/components/picker/DatePicker.tsx +++ b/packages/anastasis-webui/src/components/picker/DatePicker.tsx @@ -15,9 +15,9 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, Component } from "preact"; @@ -34,83 +34,71 @@ interface State { selectYearMode: boolean; currentDate: Date; } -const now = new Date() +const now = new Date(); const monthArrShortFull = [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December' -] + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; const monthArrShort = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' -] - -const dayArr = [ - 'Sun', - 'Mon', - 'Tue', - 'Wed', - 'Thu', - 'Fri', - 'Sat' -] - -const yearArr: number[] = [] - + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +]; + +const dayArr = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +const yearArr: number[] = []; // inspired by https://codepen.io/m4r1vs/pen/MOOxyE export class DatePicker extends Component<Props, State> { - closeDatePicker() { this.props.closeFunction && this.props.closeFunction(); // Function gets passed by parent } /** - * Gets fired when a day gets clicked. - * @param {object} e The event thrown by the <span /> element clicked - */ + * Gets fired when a day gets clicked. + * @param {object} e The event thrown by the <span /> element clicked + */ dayClicked(e: any) { - const element = e.target; // the actual element clicked - if (element.innerHTML === '') return false; // don't continue if <span /> empty + if (element.innerHTML === "") return false; // don't continue if <span /> empty // get date from clicked element (gets attached when rendered) - const date = new Date(element.getAttribute('data-value')); + const date = new Date(element.getAttribute("data-value")); // update the state this.setState({ currentDate: date }); - this.passDateToParent(date) + this.passDateToParent(date); } /** - * returns days in month as array - * @param {number} month the month to display - * @param {number} year the year to display - */ + * returns days in month as array + * @param {number} month the month to display + * @param {number} year the year to display + */ getDaysByMonth(month: number, year: number) { - const calendar = []; const date = new Date(year, month, 1); // month to display @@ -122,15 +110,17 @@ export class DatePicker extends Component<Props, State> { // the calendar is 7*6 fields big, so 42 loops for (let i = 0; i < 42; i++) { - if (i >= firstDay && day !== null) day = day + 1; if (day !== null && day > lastDate) day = null; // append the calendar Array calendar.push({ - day: (day === 0 || day === null) ? null : day, // null or number - date: (day === 0 || day === null) ? null : new Date(year, month, day), // null or Date() - today: (day === now.getDate() && month === now.getMonth() && year === now.getFullYear()) // boolean + day: day === 0 || day === null ? null : day, // null or number + date: day === 0 || day === null ? null : new Date(year, month, day), // null or Date() + today: + day === now.getDate() && + month === now.getMonth() && + year === now.getFullYear(), // boolean }); } @@ -138,51 +128,48 @@ export class DatePicker extends Component<Props, State> { } /** - * Display previous month by updating state - */ + * Display previous month by updating state + */ displayPrevMonth() { if (this.state.displayedMonth <= 0) { this.setState({ displayedMonth: 11, - displayedYear: this.state.displayedYear - 1 + displayedYear: this.state.displayedYear - 1, }); - } - else { + } else { this.setState({ - displayedMonth: this.state.displayedMonth - 1 + displayedMonth: this.state.displayedMonth - 1, }); } } /** - * Display next month by updating state - */ + * Display next month by updating state + */ displayNextMonth() { if (this.state.displayedMonth >= 11) { this.setState({ displayedMonth: 0, - displayedYear: this.state.displayedYear + 1 + displayedYear: this.state.displayedYear + 1, }); - } - else { + } else { this.setState({ - displayedMonth: this.state.displayedMonth + 1 + displayedMonth: this.state.displayedMonth + 1, }); } } /** - * Display the selected month (gets fired when clicking on the date string) - */ + * Display the selected month (gets fired when clicking on the date string) + */ displaySelectedMonth() { if (this.state.selectYearMode) { this.toggleYearSelector(); - } - else { + } else { if (!this.state.currentDate) return false; this.setState({ displayedMonth: this.state.currentDate.getMonth(), - displayedYear: this.state.currentDate.getFullYear() + displayedYear: this.state.currentDate.getFullYear(), }); } } @@ -194,17 +181,21 @@ export class DatePicker extends Component<Props, State> { changeDisplayedYear(e: any) { const element = e.target; this.toggleYearSelector(); - this.setState({ displayedYear: parseInt(element.innerHTML, 10), displayedMonth: 0 }); + this.setState({ + displayedYear: parseInt(element.innerHTML, 10), + displayedMonth: 0, + }); } /** - * Pass the selected date to parent when 'OK' is clicked - */ + * Pass the selected date to parent when 'OK' is clicked + */ passSavedDateDateToParent() { - this.passDateToParent(this.state.currentDate) + this.passDateToParent(this.state.currentDate); } passDateToParent(date: Date) { - if (typeof this.props.dateReceiver === 'function') this.props.dateReceiver(date); + if (typeof this.props.dateReceiver === "function") + this.props.dateReceiver(date); this.closeDatePicker(); } @@ -233,94 +224,133 @@ export class DatePicker extends Component<Props, State> { currentDate: initial, displayedMonth: initial.getMonth(), displayedYear: initial.getFullYear(), - selectYearMode: false - } + selectYearMode: false, + }; } render() { - - const { currentDate, displayedMonth, displayedYear, selectYearMode } = this.state; + const { + currentDate, + displayedMonth, + displayedYear, + selectYearMode, + } = this.state; return ( <div> - <div class={`datePicker ${ this.props.opened && "datePicker--opened"}`}> - + <div class={`datePicker ${this.props.opened && "datePicker--opened"}`}> <div class="datePicker--titles"> - <h3 style={{ - color: selectYearMode ? 'rgba(255,255,255,.87)' : 'rgba(255,255,255,.57)' - }} onClick={this.toggleYearSelector}>{currentDate.getFullYear()}</h3> - <h2 style={{ - color: !selectYearMode ? 'rgba(255,255,255,.87)' : 'rgba(255,255,255,.57)' - }} onClick={this.displaySelectedMonth}> - {dayArr[currentDate.getDay()]}, {monthArrShort[currentDate.getMonth()]} {currentDate.getDate()} + <h3 + style={{ + color: selectYearMode + ? "rgba(255,255,255,.87)" + : "rgba(255,255,255,.57)", + }} + onClick={this.toggleYearSelector} + > + {currentDate.getFullYear()} + </h3> + <h2 + style={{ + color: !selectYearMode + ? "rgba(255,255,255,.87)" + : "rgba(255,255,255,.57)", + }} + onClick={this.displaySelectedMonth} + > + {dayArr[currentDate.getDay()]},{" "} + {monthArrShort[currentDate.getMonth()]} {currentDate.getDate()} </h2> </div> - {!selectYearMode && <nav> - <span onClick={this.displayPrevMonth} class="icon"><i style={{ transform: 'rotate(180deg)' }} class="mdi mdi-forward" /></span> - <h4>{monthArrShortFull[displayedMonth]} {displayedYear}</h4> - <span onClick={this.displayNextMonth} class="icon"><i class="mdi mdi-forward" /></span> - </nav>} + {!selectYearMode && ( + <nav> + <span onClick={this.displayPrevMonth} class="icon"> + <i + style={{ transform: "rotate(180deg)" }} + class="mdi mdi-forward" + /> + </span> + <h4> + {monthArrShortFull[displayedMonth]} {displayedYear} + </h4> + <span onClick={this.displayNextMonth} class="icon"> + <i class="mdi mdi-forward" /> + </span> + </nav> + )} <div class="datePicker--scroll"> - - {!selectYearMode && <div class="datePicker--calendar" > - - <div class="datePicker--dayNames"> - {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day,i) => <span key={i}>{day}</span>)} - </div> - - <div onClick={this.dayClicked} class="datePicker--days"> - - {/* + {!selectYearMode && ( + <div class="datePicker--calendar"> + <div class="datePicker--dayNames"> + {["S", "M", "T", "W", "T", "F", "S"].map((day, i) => ( + <span key={i}>{day}</span> + ))} + </div> + + <div onClick={this.dayClicked} class="datePicker--days"> + {/* Loop through the calendar object returned by getDaysByMonth(). */} - {this.getDaysByMonth(this.state.displayedMonth, this.state.displayedYear) - .map( - day => { - let selected = false; - - if (currentDate && day.date) selected = (currentDate.toLocaleDateString() === day.date.toLocaleDateString()); - - return (<span key={day.day} - class={(day.today ? 'datePicker--today ' : '') + (selected ? 'datePicker--selected' : '')} + {this.getDaysByMonth( + this.state.displayedMonth, + this.state.displayedYear, + ).map((day) => { + let selected = false; + + if (currentDate && day.date) + selected = + currentDate.toLocaleDateString() === + day.date.toLocaleDateString(); + + return ( + <span + key={day.day} + class={ + (day.today ? "datePicker--today " : "") + + (selected ? "datePicker--selected" : "") + } disabled={!day.date} data-value={day.date} > {day.day} - </span>) - } - ) - } - + </span> + ); + })} + </div> </div> - - </div>} - - {selectYearMode && <div class="datePicker--selectYear"> - {(this.props.years || yearArr).map(year => ( - <span key={year} class={(year === displayedYear) ? 'selected' : ''} onClick={this.changeDisplayedYear}> - {year} - </span> - ))} - - </div>} - + )} + + {selectYearMode && ( + <div class="datePicker--selectYear"> + {(this.props.years || yearArr).map((year) => ( + <span + key={year} + class={year === displayedYear ? "selected" : ""} + onClick={this.changeDisplayedYear} + > + {year} + </span> + ))} + </div> + )} </div> </div> - <div class="datePicker--background" onClick={this.closeDatePicker} style={{ - display: this.props.opened ? 'block' : 'none', - }} + <div + class="datePicker--background" + onClick={this.closeDatePicker} + style={{ + display: this.props.opened ? "block" : "none", + }} /> - </div> - ) + ); } } - for (let i = 2010; i <= now.getFullYear() + 10; i++) { yearArr.push(i); } diff --git a/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx index 275c80fa6..7f96cc15b 100644 --- a/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx +++ b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx @@ -15,36 +15,41 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, FunctionalComponent } from 'preact'; -import { useState } from 'preact/hooks'; -import { DurationPicker as TestedComponent } from './DurationPicker'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { h, FunctionalComponent } from "preact"; +import { useState } from "preact/hooks"; +import { DurationPicker as TestedComponent } from "./DurationPicker"; export default { - title: 'Components/Picker/Duration', + title: "Components/Picker/Duration", component: TestedComponent, argTypes: { - onCreate: { action: 'onCreate' }, - goBack: { action: 'goBack' }, - } + onCreate: { action: "onCreate" }, + goBack: { action: "goBack" }, + }, }; -function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { - const r = (args: any) => <Component {...args} /> - r.args = props - return r +function createExample<Props>( + Component: FunctionalComponent<Props>, + props: Partial<Props>, +) { + const r = (args: any) => <Component {...args} />; + r.args = props; + return r; } export const Example = createExample(TestedComponent, { - days: true, minutes: true, hours: true, seconds: true, - value: 10000000 + days: true, + minutes: true, + hours: true, + seconds: true, + value: 10000000, }); export const WithState = () => { - const [v,s] = useState<number>(1000000) - return <TestedComponent value={v} onChange={s} days minutes hours seconds /> -} + const [v, s] = useState<number>(1000000); + return <TestedComponent value={v} onChange={s} days minutes hours seconds />; +}; diff --git a/packages/anastasis-webui/src/components/picker/DurationPicker.tsx b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx index 235a63e2d..8a1faf4d0 100644 --- a/packages/anastasis-webui/src/components/picker/DurationPicker.tsx +++ b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx @@ -15,9 +15,9 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -30,75 +30,123 @@ export interface Props { seconds?: boolean; days?: boolean; onChange: (value: number) => void; - value: number + value: number; } // inspiration taken from https://github.com/flurmbo/react-duration-picker -export function DurationPicker({ days, hours, minutes, seconds, onChange, value }: Props): VNode { - const ss = 1000 - const ms = ss * 60 - const hs = ms * 60 - const ds = hs * 24 - const i18n = useTranslator() - - return <div class="rdp-picker"> - {days && <DurationColumn unit={i18n`days`} max={99} - value={Math.floor(value / ds)} - onDecrease={value >= ds ? () => onChange(value - ds) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + ds) : undefined} - onChange={diff => onChange(value + diff * ds)} - />} - {hours && <DurationColumn unit={i18n`hours`} max={23} min={1} - value={Math.floor(value / hs) % 24} - onDecrease={value >= hs ? () => onChange(value - hs) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + hs) : undefined} - onChange={diff => onChange(value + diff * hs)} - />} - {minutes && <DurationColumn unit={i18n`minutes`} max={59} min={1} - value={Math.floor(value / ms) % 60} - onDecrease={value >= ms ? () => onChange(value - ms) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + ms) : undefined} - onChange={diff => onChange(value + diff * ms)} - />} - {seconds && <DurationColumn unit={i18n`seconds`} max={59} - value={Math.floor(value / ss) % 60} - onDecrease={value >= ss ? () => onChange(value - ss) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + ss) : undefined} - onChange={diff => onChange(value + diff * ss)} - />} - </div> +export function DurationPicker({ + days, + hours, + minutes, + seconds, + onChange, + value, +}: Props): VNode { + const ss = 1000; + const ms = ss * 60; + const hs = ms * 60; + const ds = hs * 24; + const i18n = useTranslator(); + + return ( + <div class="rdp-picker"> + {days && ( + <DurationColumn + unit={i18n`days`} + max={99} + value={Math.floor(value / ds)} + onDecrease={value >= ds ? () => onChange(value - ds) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + ds) : undefined} + onChange={(diff) => onChange(value + diff * ds)} + /> + )} + {hours && ( + <DurationColumn + unit={i18n`hours`} + max={23} + min={1} + value={Math.floor(value / hs) % 24} + onDecrease={value >= hs ? () => onChange(value - hs) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + hs) : undefined} + onChange={(diff) => onChange(value + diff * hs)} + /> + )} + {minutes && ( + <DurationColumn + unit={i18n`minutes`} + max={59} + min={1} + value={Math.floor(value / ms) % 60} + onDecrease={value >= ms ? () => onChange(value - ms) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + ms) : undefined} + onChange={(diff) => onChange(value + diff * ms)} + /> + )} + {seconds && ( + <DurationColumn + unit={i18n`seconds`} + max={59} + value={Math.floor(value / ss) % 60} + onDecrease={value >= ss ? () => onChange(value - ss) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + ss) : undefined} + onChange={(diff) => onChange(value + diff * ss)} + /> + )} + </div> + ); } interface ColProps { - unit: string, - min?: number, - max: number, - value: number, + unit: string; + min?: number; + max: number; + value: number; onIncrease?: () => void; onDecrease?: () => void; onChange?: (diff: number) => void; } -function InputNumber({ initial, onChange }: { initial: number, onChange: (n: number) => void }) { - const [value, handler] = useState<{v:string}>({ - v: toTwoDigitString(initial) - }) - - return <input - value={value.v} - onBlur={(e) => onChange(parseInt(value.v, 10))} - onInput={(e) => { - e.preventDefault() - const n = Number.parseInt(e.currentTarget.value, 10); - if (isNaN(n)) return handler({v:toTwoDigitString(initial)}) - return handler({v:toTwoDigitString(n)}) - }} - style={{ width: 50, border: 'none', fontSize: 'inherit', background: 'inherit' }} /> -} +function InputNumber({ + initial, + onChange, +}: { + initial: number; + onChange: (n: number) => void; +}) { + const [value, handler] = useState<{ v: string }>({ + v: toTwoDigitString(initial), + }); -function DurationColumn({ unit, min = 0, max, value, onIncrease, onDecrease, onChange }: ColProps): VNode { + return ( + <input + value={value.v} + onBlur={(e) => onChange(parseInt(value.v, 10))} + onInput={(e) => { + e.preventDefault(); + const n = Number.parseInt(e.currentTarget.value, 10); + if (isNaN(n)) return handler({ v: toTwoDigitString(initial) }); + return handler({ v: toTwoDigitString(n) }); + }} + style={{ + width: 50, + border: "none", + fontSize: "inherit", + background: "inherit", + }} + /> + ); +} - const cellHeight = 35 +function DurationColumn({ + unit, + min = 0, + max, + value, + onIncrease, + onDecrease, + onChange, +}: ColProps): VNode { + const cellHeight = 35; return ( <div class="rdp-column-container"> <div class="rdp-masked-div"> @@ -106,49 +154,58 @@ function DurationColumn({ unit, min = 0, max, value, onIncrease, onDecrease, onC <hr class="rdp-reticule" style={{ top: cellHeight * 3 - 1 }} /> <div class="rdp-column" style={{ top: 0 }}> - <div class="rdp-cell" key={value - 2}> - {onDecrease && <button style={{ width: '100%', textAlign: 'center', margin: 5 }} - onClick={onDecrease}> - <span class="icon"> - <i class="mdi mdi-chevron-up" /> - </span> - </button>} + {onDecrease && ( + <button + style={{ width: "100%", textAlign: "center", margin: 5 }} + onClick={onDecrease} + > + <span class="icon"> + <i class="mdi mdi-chevron-up" /> + </span> + </button> + )} </div> <div class="rdp-cell" key={value - 1}> - {value > min ? toTwoDigitString(value - 1) : ''} + {value > min ? toTwoDigitString(value - 1) : ""} </div> <div class="rdp-cell rdp-center" key={value}> - {onChange ? - <InputNumber initial={value} onChange={(n) => onChange(n - value)} /> : + {onChange ? ( + <InputNumber + initial={value} + onChange={(n) => onChange(n - value)} + /> + ) : ( toTwoDigitString(value) - } + )} <div>{unit}</div> </div> <div class="rdp-cell" key={value + 1}> - {value < max ? toTwoDigitString(value + 1) : ''} + {value < max ? toTwoDigitString(value + 1) : ""} </div> <div class="rdp-cell" key={value + 2}> - {onIncrease && <button style={{ width: '100%', textAlign: 'center', margin: 5 }} - onClick={onIncrease}> - <span class="icon"> - <i class="mdi mdi-chevron-down" /> - </span> - </button>} + {onIncrease && ( + <button + style={{ width: "100%", textAlign: "center", margin: 5 }} + onClick={onIncrease} + > + <span class="icon"> + <i class="mdi mdi-chevron-down" /> + </span> + </button> + )} </div> - </div> </div> </div> ); } - function toTwoDigitString(n: number) { if (n < 10) { return `0${n}`; } return `${n}`; -}
\ No newline at end of file +} diff --git a/packages/anastasis-webui/src/context/anastasis.ts b/packages/anastasis-webui/src/context/anastasis.ts index e7f93ed43..c2e7b2a47 100644 --- a/packages/anastasis-webui/src/context/anastasis.ts +++ b/packages/anastasis-webui/src/context/anastasis.ts @@ -15,19 +15,19 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createContext, h, VNode } from 'preact'; -import { useContext } from 'preact/hooks'; -import { AnastasisReducerApi } from '../hooks/use-anastasis-reducer'; +import { createContext, h, VNode } from "preact"; +import { useContext } from "preact/hooks"; +import { AnastasisReducerApi } from "../hooks/use-anastasis-reducer"; type Type = AnastasisReducerApi | undefined; -const initial = undefined +const initial = undefined; -const Context = createContext<Type>(initial) +const Context = createContext<Type>(initial); interface Props { value: AnastasisReducerApi; @@ -36,6 +36,6 @@ interface Props { export const AnastasisProvider = ({ value, children }: Props): VNode => { return h(Context.Provider, { value, children }); -} +}; -export const useAnastasisContext = (): Type => useContext(Context);
\ No newline at end of file +export const useAnastasisContext = (): Type => useContext(Context); diff --git a/packages/anastasis-webui/src/context/translation.ts b/packages/anastasis-webui/src/context/translation.ts index 5ceb5d428..a47864d75 100644 --- a/packages/anastasis-webui/src/context/translation.ts +++ b/packages/anastasis-webui/src/context/translation.ts @@ -15,13 +15,13 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createContext, h, VNode } from 'preact' -import { useContext, useEffect } from 'preact/hooks' -import { useLang } from '../hooks' +import { createContext, h, VNode } from "preact"; +import { useContext, useEffect } from "preact/hooks"; +import { useLang } from "../hooks"; import * as jedLib from "jed"; import { strings } from "../i18n/strings"; @@ -31,13 +31,13 @@ interface Type { changeLanguage: (l: string) => void; } const initial = { - lang: 'en', + lang: "en", handler: null, changeLanguage: () => { // do not change anything - } -} -const Context = createContext<Type>(initial) + }, +}; +const Context = createContext<Type>(initial); interface Props { initial?: string; @@ -45,15 +45,22 @@ interface Props { forceLang?: string; } -export const TranslationProvider = ({ initial, children, forceLang }: Props): VNode => { - const [lang, changeLanguage] = useLang(initial) +export const TranslationProvider = ({ + initial, + children, + forceLang, +}: Props): VNode => { + const [lang, changeLanguage] = useLang(initial); useEffect(() => { if (forceLang) { - changeLanguage(forceLang) + changeLanguage(forceLang); } - }) - const handler = new jedLib.Jed(strings[lang] || strings['en']); - return h(Context.Provider, { value: { lang, handler, changeLanguage }, children }); -} + }); + const handler = new jedLib.Jed(strings[lang] || strings["en"]); + return h(Context.Provider, { + value: { lang, handler, changeLanguage }, + children, + }); +}; -export const useTranslationContext = (): Type => useContext(Context);
\ No newline at end of file +export const useTranslationContext = (): Type => useContext(Context); diff --git a/packages/anastasis-webui/src/declaration.d.ts b/packages/anastasis-webui/src/declaration.d.ts index 2c4b7cb3a..00b3d41d5 100644 --- a/packages/anastasis-webui/src/declaration.d.ts +++ b/packages/anastasis-webui/src/declaration.d.ts @@ -1,20 +1,20 @@ declare module "*.css" { - const mapping: Record<string, string>; - export default mapping; + const mapping: Record<string, string>; + export default mapping; } -declare module '*.svg' { - const content: any; - export default content; +declare module "*.svg" { + const content: any; + export default content; } -declare module '*.jpeg' { - const content: any; - export default content; +declare module "*.jpeg" { + const content: any; + export default content; } -declare module '*.png' { - const content: any; - export default content; +declare module "*.png" { + const content: any; + export default content; } -declare module 'jed' { - const x: any; - export = x; +declare module "jed" { + const x: any; + export = x; } diff --git a/packages/anastasis-webui/src/hooks/async.ts b/packages/anastasis-webui/src/hooks/async.ts index ea3ff6acf..0fc197554 100644 --- a/packages/anastasis-webui/src/hooks/async.ts +++ b/packages/anastasis-webui/src/hooks/async.ts @@ -15,9 +15,9 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { useState } from "preact/hooks"; // import { cancelPendingRequest } from "./backend"; @@ -34,36 +34,39 @@ export interface AsyncOperationApi<T> { error: string | undefined; } -export function useAsync<T>(fn?: (...args: any) => Promise<T>, { slowTolerance: tooLong }: Options = { slowTolerance: 1000 }): AsyncOperationApi<T> { +export function useAsync<T>( + fn?: (...args: any) => Promise<T>, + { slowTolerance: tooLong }: Options = { slowTolerance: 1000 }, +): AsyncOperationApi<T> { const [data, setData] = useState<T | undefined>(undefined); const [isLoading, setLoading] = useState<boolean>(false); const [error, setError] = useState<any>(undefined); - const [isSlow, setSlow] = useState(false) + const [isSlow, setSlow] = useState(false); const request = async (...args: any) => { if (!fn) return; setLoading(true); const handler = setTimeout(() => { - setSlow(true) - }, tooLong) + setSlow(true); + }, tooLong); try { - console.log("calling async", args) + console.log("calling async", args); const result = await fn(...args); - console.log("async back", result) + console.log("async back", result); setData(result); } catch (error) { setError(error); } setLoading(false); - setSlow(false) - clearTimeout(handler) + setSlow(false); + clearTimeout(handler); }; function cancel() { // cancelPendingRequest() setLoading(false); - setSlow(false) + setSlow(false); } return { @@ -72,6 +75,6 @@ export function useAsync<T>(fn?: (...args: any) => Promise<T>, { slowTolerance: data, isSlow, isLoading, - error + error, }; } diff --git a/packages/anastasis-webui/src/hooks/index.ts b/packages/anastasis-webui/src/hooks/index.ts index 15df4f154..9a1b50a11 100644 --- a/packages/anastasis-webui/src/hooks/index.ts +++ b/packages/anastasis-webui/src/hooks/index.ts @@ -15,81 +15,110 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { StateUpdater, useState } from "preact/hooks"; -export type ValueOrFunction<T> = T | ((p: T) => T) - +export type ValueOrFunction<T> = T | ((p: T) => T); const calculateRootPath = () => { - const rootPath = typeof window !== undefined ? window.location.origin + window.location.pathname : '/' - return rootPath -} - -export function useBackendURL(url?: string): [string, boolean, StateUpdater<string>, () => void] { - const [value, setter] = useNotNullLocalStorage('backend-url', url || calculateRootPath()) - const [triedToLog, setTriedToLog] = useLocalStorage('tried-login') + const rootPath = + typeof window !== undefined + ? window.location.origin + window.location.pathname + : "/"; + return rootPath; +}; + +export function useBackendURL( + url?: string, +): [string, boolean, StateUpdater<string>, () => void] { + const [value, setter] = useNotNullLocalStorage( + "backend-url", + url || calculateRootPath(), + ); + const [triedToLog, setTriedToLog] = useLocalStorage("tried-login"); const checkedSetter = (v: ValueOrFunction<string>) => { - setTriedToLog('yes') - return setter(p => (v instanceof Function ? v(p) : v).replace(/\/$/, '')) - } + setTriedToLog("yes"); + return setter((p) => (v instanceof Function ? v(p) : v).replace(/\/$/, "")); + }; const resetBackend = () => { - setTriedToLog(undefined) - } - return [value, !!triedToLog, checkedSetter, resetBackend] + setTriedToLog(undefined); + }; + return [value, !!triedToLog, checkedSetter, resetBackend]; } -export function useBackendDefaultToken(): [string | undefined, StateUpdater<string | undefined>] { - return useLocalStorage('backend-token') +export function useBackendDefaultToken(): [ + string | undefined, + StateUpdater<string | undefined>, +] { + return useLocalStorage("backend-token"); } -export function useBackendInstanceToken(id: string): [string | undefined, StateUpdater<string | undefined>] { - const [token, setToken] = useLocalStorage(`backend-token-${id}`) - const [defaultToken, defaultSetToken] = useBackendDefaultToken() +export function useBackendInstanceToken( + id: string, +): [string | undefined, StateUpdater<string | undefined>] { + const [token, setToken] = useLocalStorage(`backend-token-${id}`); + const [defaultToken, defaultSetToken] = useBackendDefaultToken(); // instance named 'default' use the default token - if (id === 'default') { - return [defaultToken, defaultSetToken] + if (id === "default") { + return [defaultToken, defaultSetToken]; } - return [token, setToken] + return [token, setToken]; } export function useLang(initial?: string): [string, StateUpdater<string>] { - const browserLang = typeof window !== "undefined" ? navigator.language || (navigator as any).userLanguage : undefined; - const defaultLang = (browserLang || initial || 'en').substring(0, 2) - return useNotNullLocalStorage('lang-preference', defaultLang) + const browserLang = + typeof window !== "undefined" + ? navigator.language || (navigator as any).userLanguage + : undefined; + const defaultLang = (browserLang || initial || "en").substring(0, 2); + return useNotNullLocalStorage("lang-preference", defaultLang); } -export function useLocalStorage(key: string, initialValue?: string): [string | undefined, StateUpdater<string | undefined>] { - const [storedValue, setStoredValue] = useState<string | undefined>((): string | undefined => { - return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue; +export function useLocalStorage( + key: string, + initialValue?: string, +): [string | undefined, StateUpdater<string | undefined>] { + const [storedValue, setStoredValue] = useState<string | undefined>((): + | string + | undefined => { + return typeof window !== "undefined" + ? window.localStorage.getItem(key) || initialValue + : initialValue; }); - const setValue = (value?: string | ((val?: string) => string | undefined)) => { - setStoredValue(p => { - const toStore = value instanceof Function ? value(p) : value + const setValue = ( + value?: string | ((val?: string) => string | undefined), + ) => { + setStoredValue((p) => { + const toStore = value instanceof Function ? value(p) : value; if (typeof window !== "undefined") { if (!toStore) { - window.localStorage.removeItem(key) + window.localStorage.removeItem(key); } else { window.localStorage.setItem(key, toStore); } } - return toStore - }) + return toStore; + }); }; return [storedValue, setValue]; } -export function useNotNullLocalStorage(key: string, initialValue: string): [string, StateUpdater<string>] { +export function useNotNullLocalStorage( + key: string, + initialValue: string, +): [string, StateUpdater<string>] { const [storedValue, setStoredValue] = useState<string>((): string => { - return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue; + return typeof window !== "undefined" + ? window.localStorage.getItem(key) || initialValue + : initialValue; }); const setValue = (value: string | ((val: string) => string)) => { @@ -97,7 +126,7 @@ export function useNotNullLocalStorage(key: string, initialValue: string): [stri setStoredValue(valueToStore); if (typeof window !== "undefined") { if (!valueToStore) { - window.localStorage.removeItem(key) + window.localStorage.removeItem(key); } else { window.localStorage.setItem(key, valueToStore); } @@ -106,5 +135,3 @@ export function useNotNullLocalStorage(key: string, initialValue: string): [stri return [storedValue, setValue]; } - - diff --git a/packages/anastasis-webui/src/i18n/index.tsx b/packages/anastasis-webui/src/i18n/index.tsx index 63c8e1934..6e2c4e79a 100644 --- a/packages/anastasis-webui/src/i18n/index.tsx +++ b/packages/anastasis-webui/src/i18n/index.tsx @@ -27,23 +27,25 @@ import { useTranslationContext } from "../context/translation"; export function useTranslator() { const ctx = useTranslationContext(); - const jed = ctx.handler - return function str(stringSeq: TemplateStringsArray, ...values: any[]): string { + const jed = ctx.handler; + return function str( + stringSeq: TemplateStringsArray, + ...values: any[] + ): string { const s = toI18nString(stringSeq); - if (!s) return s + if (!s) return s; const tr = jed .translate(s) .ifPlural(1, s) .fetch(...values); return tr; - } + }; } - /** * Convert template strings to a msgid */ - function toI18nString(stringSeq: ReadonlyArray<string>): string { +function toI18nString(stringSeq: ReadonlyArray<string>): string { let s = ""; for (let i = 0; i < stringSeq.length; i++) { s += stringSeq[i]; @@ -54,7 +56,6 @@ export function useTranslator() { return s; } - interface TranslateSwitchProps { target: number; children: ComponentChildren; @@ -110,7 +111,7 @@ function getTranslatedChildren( // Text result.push(tr[i]); } else { - const childIdx = Number.parseInt(tr[i],10) - 1; + const childIdx = Number.parseInt(tr[i], 10) - 1; result.push(placeholderChildren[childIdx]); } } @@ -131,9 +132,9 @@ function getTranslatedChildren( */ export function Translate({ children }: TranslateProps): VNode { const s = stringifyChildren(children); - const ctx = useTranslationContext() + const ctx = useTranslationContext(); const translation: string = ctx.handler.ngettext(s, s, 1); - const result = getTranslatedChildren(translation, children) + const result = getTranslatedChildren(translation, children); return <Fragment>{result}</Fragment>; } @@ -154,14 +155,16 @@ export function TranslateSwitch({ children, target }: TranslateSwitchProps) { let plural: VNode<TranslationPluralProps> | undefined; // const children = this.props.children; if (children) { - (children instanceof Array ? children : [children]).forEach((child: any) => { - if (child.type === TranslatePlural) { - plural = child; - } - if (child.type === TranslateSingular) { - singular = child; - } - }); + (children instanceof Array ? children : [children]).forEach( + (child: any) => { + if (child.type === TranslatePlural) { + plural = child; + } + if (child.type === TranslateSingular) { + singular = child; + } + }, + ); } if (!singular || !plural) { console.error("translation not found"); @@ -182,9 +185,12 @@ interface TranslationPluralProps { /** * See [[TranslateSwitch]]. */ -export function TranslatePlural({ children, target }: TranslationPluralProps): VNode { +export function TranslatePlural({ + children, + target, +}: TranslationPluralProps): VNode { const s = stringifyChildren(children); - const ctx = useTranslationContext() + const ctx = useTranslationContext(); const translation = ctx.handler.ngettext(s, s, 1); const result = getTranslatedChildren(translation, children); return <Fragment>{result}</Fragment>; @@ -193,11 +199,13 @@ export function TranslatePlural({ children, target }: TranslationPluralProps): V /** * See [[TranslateSwitch]]. */ -export function TranslateSingular({ children, target }: TranslationPluralProps): VNode { +export function TranslateSingular({ + children, + target, +}: TranslationPluralProps): VNode { const s = stringifyChildren(children); - const ctx = useTranslationContext() + const ctx = useTranslationContext(); const translation = ctx.handler.ngettext(s, s, target); const result = getTranslatedChildren(translation, children); return <Fragment>{result}</Fragment>; - } diff --git a/packages/anastasis-webui/src/i18n/strings.ts b/packages/anastasis-webui/src/i18n/strings.ts index b4f376ce0..d12e63e88 100644 --- a/packages/anastasis-webui/src/i18n/strings.ts +++ b/packages/anastasis-webui/src/i18n/strings.ts @@ -15,30 +15,30 @@ */ /*eslint quote-props: ["error", "consistent"]*/ -export const strings: {[s: string]: any} = {}; +export const strings: { [s: string]: any } = {}; -strings['de'] = { - "domain": "messages", - "locale_data": { - "messages": { +strings["de"] = { + domain: "messages", + locale_data: { + messages: { "": { - "domain": "messages", - "plural_forms": "nplurals=2; plural=(n != 1);", - "lang": "" + domain: "messages", + plural_forms: "nplurals=2; plural=(n != 1);", + lang: "", }, - } - } + }, + }, }; -strings['en'] = { - "domain": "messages", - "locale_data": { - "messages": { +strings["en"] = { + domain: "messages", + locale_data: { + messages: { "": { - "domain": "messages", - "plural_forms": "nplurals=2; plural=(n != 1);", - "lang": "" + domain: "messages", + plural_forms: "nplurals=2; plural=(n != 1);", + lang: "", }, - } - } + }, + }, }; diff --git a/packages/anastasis-webui/src/index.ts b/packages/anastasis-webui/src/index.ts index e78b9c194..4bd7b28f3 100644 --- a/packages/anastasis-webui/src/index.ts +++ b/packages/anastasis-webui/src/index.ts @@ -1,4 +1,4 @@ -import App from './components/app'; -import './scss/main.scss'; +import App from "./components/app"; +import "./scss/main.scss"; export default App; diff --git a/packages/anastasis-webui/src/manifest.json b/packages/anastasis-webui/src/manifest.json index 6b44a2b31..2752dad77 100644 --- a/packages/anastasis-webui/src/manifest.json +++ b/packages/anastasis-webui/src/manifest.json @@ -18,4 +18,4 @@ "sizes": "512x512" } ] -}
\ No newline at end of file +} diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx index 08e2b4371..9b067127d 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx @@ -15,24 +15,23 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { AddingProviderScreen as TestedComponent } from './AddingProviderScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { AddingProviderScreen as TestedComponent } from "./AddingProviderScreen"; export default { - title: 'Pages/ManageProvider', + title: "Pages/ManageProvider", component: TestedComponent, args: { order: 1, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; @@ -40,20 +39,31 @@ export const NewProvider = createExample(TestedComponent, { ...reducerStatesExample.authEditing, } as ReducerState); - export const NewProviderWithoutProviderList = createExample(TestedComponent, { ...reducerStatesExample.authEditing, - authentication_providers: {} + authentication_providers: {}, } as ReducerState); -export const NewVideoProvider = createExample(TestedComponent, { - ...reducerStatesExample.authEditing, -} as ReducerState, { providerType: 'video'}); +export const NewVideoProvider = createExample( + TestedComponent, + { + ...reducerStatesExample.authEditing, + } as ReducerState, + { providerType: "video" }, +); -export const NewSmsProvider = createExample(TestedComponent, { - ...reducerStatesExample.authEditing, -} as ReducerState, { providerType: 'sms'}); +export const NewSmsProvider = createExample( + TestedComponent, + { + ...reducerStatesExample.authEditing, + } as ReducerState, + { providerType: "sms" }, +); -export const NewIBANProvider = createExample(TestedComponent, { - ...reducerStatesExample.authEditing, -} as ReducerState, { providerType: 'iban' }); +export const NewIBANProvider = createExample( + TestedComponent, + { + ...reducerStatesExample.authEditing, + } as ReducerState, + { providerType: "iban" }, +); diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx index 7504f4d2b..96b38e92d 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx @@ -11,185 +11,250 @@ interface Props { onCancel: () => void; } - -async function testProvider(url: string, expectedMethodType?: string): Promise<void> { +async function testProvider( + url: string, + expectedMethodType?: string, +): Promise<void> { try { - const response = await fetch(new URL("config", url).href) - const json = await (response.json().catch(d => ({}))) + const response = await fetch(new URL("config", url).href); + const json = await response.json().catch((d) => ({})); if (!("methods" in json) || !Array.isArray(json.methods)) { - throw Error("This provider doesn't have authentication method. Check the provider URL") + throw Error( + "This provider doesn't have authentication method. Check the provider URL", + ); } - console.log("expected", expectedMethodType) + console.log("expected", expectedMethodType); if (!expectedMethodType) { - return + return; } - let found = false + let found = false; for (let i = 0; i < json.methods.length && !found; i++) { - found = json.methods[i].type === expectedMethodType + found = json.methods[i].type === expectedMethodType; } if (!found) { - throw Error(`This provider does not support authentication method ${expectedMethodType}`) + throw Error( + `This provider does not support authentication method ${expectedMethodType}`, + ); } - return + return; } catch (e) { - console.log("error", e) - const error = e instanceof Error ? - Error(`There was an error testing this provider, try another one. ${e.message}`) : - Error(`There was an error testing this provider, try another one.`) - throw error + console.log("error", e); + const error = + e instanceof Error + ? Error( + `There was an error testing this provider, try another one. ${e.message}`, + ) + : Error(`There was an error testing this provider, try another one.`); + throw error; } - } export function AddingProviderScreen({ providerType, onCancel }: Props): VNode { const reducer = useAnastasisContext(); const [providerURL, setProviderURL] = useState(""); - const [error, setError] = useState<string | undefined>() - const [testing, setTesting] = useState(false) - const providerLabel = providerType ? authMethods[providerType].label : undefined + const [error, setError] = useState<string | undefined>(); + const [testing, setTesting] = useState(false); + const providerLabel = providerType + ? authMethods[providerType].label + : undefined; //FIXME: move this timeout logic into a hook const timeout = useRef<number | undefined>(undefined); useEffect(() => { - if (timeout) window.clearTimeout(timeout.current) + if (timeout) window.clearTimeout(timeout.current); timeout.current = window.setTimeout(async () => { - const url = providerURL.endsWith('/') ? providerURL : (providerURL + '/') + const url = providerURL.endsWith("/") ? providerURL : providerURL + "/"; if (!providerURL || authProviders.includes(url)) return; try { - setTesting(true) - await testProvider(url, providerType) + setTesting(true); + await testProvider(url, providerType); // this is use as tested but everything when ok // undefined will mean that the field is not dirty - setError("") + setError(""); } catch (e) { - console.log("tuvieja", e) - if (e instanceof Error) setError(e.message) + console.log("tuvieja", e); + if (e instanceof Error) setError(e.message); } - setTesting(false) + setTesting(false); }, 200); - }, [providerURL, reducer]) - + }, [providerURL, reducer]); if (!reducer) { return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || !("authentication_providers" in reducer.currentReducerState)) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + !("authentication_providers" in reducer.currentReducerState) + ) { + return <div>invalid state</div>; } async function addProvider(provider_url: string): Promise<void> { - await reducer?.transition("add_provider", { provider_url }) - onCancel() + await reducer?.transition("add_provider", { provider_url }); + onCancel(); } function deleteProvider(provider_url: string): void { - reducer?.transition("delete_provider", { provider_url }) + reducer?.transition("delete_provider", { provider_url }); } - const allAuthProviders = reducer.currentReducerState.authentication_providers || {} - const authProviders = Object.keys(allAuthProviders).filter(provUrl => { + const allAuthProviders = + reducer.currentReducerState.authentication_providers || {}; + const authProviders = Object.keys(allAuthProviders).filter((provUrl) => { const p = allAuthProviders[provUrl]; if (!providerLabel) { - return p && ("currency" in p) + return p && "currency" in p; } else { - return p && ("currency" in p) && p.methods.findIndex(m => m.type === providerType) !== -1 + return ( + p && + "currency" in p && + p.methods.findIndex((m) => m.type === providerType) !== -1 + ); } - }) + }); - let errors = !providerURL ? 'Add provider URL' : undefined + let errors = !providerURL ? "Add provider URL" : undefined; let url: string | undefined; try { - url = new URL("",providerURL).href + url = new URL("", providerURL).href; } catch { - errors = 'Check the URL' + errors = "Check the URL"; } if (!!error && !errors) { - errors = error + errors = error; } if (!errors && authProviders.includes(url!)) { - errors = 'That provider is already known' + errors = "That provider is already known"; } return ( - <AnastasisClientFrame hideNav + <AnastasisClientFrame + hideNav title="Backup: Manage providers" - hideNext={errors}> + hideNext={errors} + > <div> - {!providerLabel ? - <p> - Add a provider url - </p> : - <p> - Add a provider url for a {providerLabel} service - </p> - } + {!providerLabel ? ( + <p>Add a provider url</p> + ) : ( + <p>Add a provider url for a {providerLabel} service</p> + )} <div class="container"> <TextInput label="Provider URL" placeholder="https://provider.com" grabFocus error={errors} - bind={[providerURL, setProviderURL]} /> + bind={[providerURL, setProviderURL]} + /> </div> - <p class="block"> - Example: https://kudos.demo.anastasis.lu - </p> + <p class="block">Example: https://kudos.demo.anastasis.lu</p> {testing && <p class="has-text-info">Testing</p>} - - <div class="block" style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={onCancel}>Cancel</button> + + <div + class="block" + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> <span data-tooltip={errors}> - <button class="button is-info" disabled={error !== "" || testing} onClick={() => addProvider(url!)}>Add</button> + <button + class="button is-info" + disabled={error !== "" || testing} + onClick={() => addProvider(url!)} + > + Add + </button> </span> </div> {authProviders.length > 0 ? ( - !providerLabel ? + !providerLabel ? ( + <p class="subtitle">Current providers</p> + ) : ( <p class="subtitle"> - Current providers - </p> : <p class="subtitle"> Current providers for {providerLabel} service </p> + ) + ) : !providerLabel ? ( + <p class="subtitle">No known providers, add one.</p> ) : ( - !providerLabel ? <p class="subtitle"> - No known providers, add one. - </p> : <p class="subtitle"> - No known providers for {providerLabel} service - </p> + <p class="subtitle">No known providers for {providerLabel} service</p> )} - {authProviders.map(k => { - const p = allAuthProviders[k] as AuthenticationProviderStatusOk - return <TableRow url={k} info={p} onDelete={deleteProvider} /> + {authProviders.map((k) => { + const p = allAuthProviders[k] as AuthenticationProviderStatusOk; + return <TableRow url={k} info={p} onDelete={deleteProvider} />; })} </div> </AnastasisClientFrame> ); } -function TableRow({ url, info, onDelete }: { onDelete: (s: string) => void, url: string, info: AuthenticationProviderStatusOk }) { - const [status, setStatus] = useState("checking") +function TableRow({ + url, + info, + onDelete, +}: { + onDelete: (s: string) => void; + url: string; + info: AuthenticationProviderStatusOk; +}) { + const [status, setStatus] = useState("checking"); useEffect(function () { - testProvider(url.endsWith('/') ? url.substring(0, url.length - 1) : url) - .then(function () { setStatus('responding') }) - .catch(function () { setStatus('failed to contact') }) - }) - return <div class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <div> - <div class="subtitle">{url}</div> - <dl> - <dt><b>Business Name</b></dt> - <dd>{info.business_name}</dd> - <dt><b>Supported methods</b></dt> - <dd>{info.methods.map(m => m.type).join(',')}</dd> - <dt><b>Maximum storage</b></dt> - <dd>{info.storage_limit_in_megabytes} Mb</dd> - <dt><b>Status</b></dt> - <dd>{status}</dd> - </dl> - </div> - <div class="block" style={{ marginTop: 'auto', marginBottom: 'auto', display: 'flex', justifyContent: 'space-between', flexDirection: 'column' }}> - <button class="button is-danger" onClick={() => onDelete(url)}>Remove</button> + testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url) + .then(function () { + setStatus("responding"); + }) + .catch(function () { + setStatus("failed to contact"); + }); + }); + return ( + <div + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > + <div> + <div class="subtitle">{url}</div> + <dl> + <dt> + <b>Business Name</b> + </dt> + <dd>{info.business_name}</dd> + <dt> + <b>Supported methods</b> + </dt> + <dd>{info.methods.map((m) => m.type).join(",")}</dd> + <dt> + <b>Maximum storage</b> + </dt> + <dd>{info.storage_limit_in_megabytes} Mb</dd> + <dt> + <b>Status</b> + </dt> + <dd>{status}</dd> + </dl> + </div> + <div + class="block" + style={{ + marginTop: "auto", + marginBottom: "auto", + display: "flex", + justifyContent: "space-between", + flexDirection: "column", + }} + > + <button class="button is-danger" onClick={() => onDelete(url)}> + Remove + </button> + </div> </div> - </div> -}
\ No newline at end of file + ); +} diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx index 9cdd132ef..d48e94403 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx @@ -15,76 +15,83 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { AttributeEntryScreen as TestedComponent } from './AttributeEntryScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { AttributeEntryScreen as TestedComponent } from "./AttributeEntryScreen"; export default { - title: 'Pages/PersonalInformation', + title: "Pages/PersonalInformation", component: TestedComponent, args: { order: 3, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const Backup = createExample(TestedComponent, { ...reducerStatesExample.backupAttributeEditing, - required_attributes: [{ - name: 'first name', - label: 'first', - type: 'string', - uuid: 'asdasdsa1', - widget: 'wid', - }, { - name: 'last name', - label: 'second', - type: 'string', - uuid: 'asdasdsa2', - widget: 'wid', - }, { - name: 'birthdate', - label: 'birthdate', - type: 'date', - uuid: 'asdasdsa3', - widget: 'calendar', - }] + required_attributes: [ + { + name: "first name", + label: "first", + type: "string", + uuid: "asdasdsa1", + widget: "wid", + }, + { + name: "last name", + label: "second", + type: "string", + uuid: "asdasdsa2", + widget: "wid", + }, + { + name: "birthdate", + label: "birthdate", + type: "date", + uuid: "asdasdsa3", + widget: "calendar", + }, + ], } as ReducerState); export const Recovery = createExample(TestedComponent, { ...reducerStatesExample.recoveryAttributeEditing, - required_attributes: [{ - name: 'first', - label: 'first', - type: 'string', - uuid: 'asdasdsa1', - widget: 'wid', - }, { - name: 'pepe', - label: 'second', - type: 'string', - uuid: 'asdasdsa2', - widget: 'wid', - }, { - name: 'pepe2', - label: 'third', - type: 'date', - uuid: 'asdasdsa3', - widget: 'calendar', - }] + required_attributes: [ + { + name: "first", + label: "first", + type: "string", + uuid: "asdasdsa1", + widget: "wid", + }, + { + name: "pepe", + label: "second", + type: "string", + uuid: "asdasdsa2", + widget: "wid", + }, + { + name: "pepe2", + label: "third", + type: "date", + uuid: "asdasdsa3", + widget: "calendar", + }, + ], } as ReducerState); export const WithNoRequiredAttribute = createExample(TestedComponent, { ...reducerStatesExample.backupAttributeEditing, - required_attributes: undefined + required_attributes: undefined, } as ReducerState); const allWidgets = [ @@ -107,23 +114,22 @@ const allWidgets = [ "anastasis_gtk_ia_tax_de", "anastasis_gtk_xx_prime", "anastasis_gtk_xx_square", -] +]; function typeForWidget(name: string): string { - if (["anastasis_gtk_xx_prime", - "anastasis_gtk_xx_square", - ].includes(name)) return "number"; - if (["anastasis_gtk_ia_birthdate"].includes(name)) return "date" + if (["anastasis_gtk_xx_prime", "anastasis_gtk_xx_square"].includes(name)) + return "number"; + if (["anastasis_gtk_ia_birthdate"].includes(name)) return "date"; return "string"; } export const WithAllPosibleWidget = createExample(TestedComponent, { ...reducerStatesExample.backupAttributeEditing, - required_attributes: allWidgets.map(w => ({ + required_attributes: allWidgets.map((w) => ({ name: w, label: `widget: ${w}`, type: typeForWidget(w), uuid: `uuid-${w}`, - widget: w - })) + widget: w, + })), } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx index 557718458..0918c2db5 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx @@ -9,24 +9,32 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame, withProcessLabel } from "./index"; export function AttributeEntryScreen(): VNode { - const reducer = useAnastasisContext() - const state = reducer?.currentReducerState - const currentIdentityAttributes = state && "identity_attributes" in state ? (state.identity_attributes || {}) : {} - const [attrs, setAttrs] = useState<Record<string, string>>(currentIdentityAttributes); + const reducer = useAnastasisContext(); + const state = reducer?.currentReducerState; + const currentIdentityAttributes = + state && "identity_attributes" in state + ? state.identity_attributes || {} + : {}; + const [attrs, setAttrs] = useState<Record<string, string>>( + currentIdentityAttributes, + ); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || !("required_attributes" in reducer.currentReducerState)) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + !("required_attributes" in reducer.currentReducerState) + ) { + return <div>invalid state</div>; } - const reqAttr = reducer.currentReducerState.required_attributes || [] + const reqAttr = reducer.currentReducerState.required_attributes || []; let hasErrors = false; const fieldList: VNode[] = reqAttr.map((spec, i: number) => { - const value = attrs[spec.name] - const error = checkIfValid(value, spec) - hasErrors = hasErrors || error !== undefined + const value = attrs[spec.name]; + const error = checkIfValid(value, spec); + hasErrors = hasErrors || error !== undefined; return ( <AttributeEntryField key={i} @@ -34,23 +42,24 @@ export function AttributeEntryScreen(): VNode { setValue={(v: string) => setAttrs({ ...attrs, [spec.name]: v })} spec={spec} errorMessage={error} - value={value} /> + value={value} + /> ); - }) + }); return ( <AnastasisClientFrame title={withProcessLabel(reducer, "Who are you?")} hideNext={hasErrors ? "Complete the form." : undefined} - onNext={() => reducer.transition("enter_user_attributes", { - identity_attributes: attrs, - })} + onNext={() => + reducer.transition("enter_user_attributes", { + identity_attributes: attrs, + }) + } > - <div class="columns" style={{ maxWidth: 'unset' }}> + <div class="columns" style={{ maxWidth: "unset" }}> + <div class="column">{fieldList}</div> <div class="column"> - {fieldList} - </div> - <div class="column" > <p>This personal information will help to locate your secret.</p> <h1 class="title">This stays private</h1> <p>The information you have entered here:</p> @@ -61,9 +70,12 @@ export function AttributeEntryScreen(): VNode { </span> Will be hashed, and therefore unreadable </li> - <li><span class="icon is-right"> - <i class="mdi mdi-circle-small" /> - </span>The non-hashed version is not shared</li> + <li> + <span class="icon is-right"> + <i class="mdi mdi-circle-small" /> + </span> + The non-hashed version is not shared + </li> </ul> </div> </div> @@ -78,22 +90,22 @@ interface AttributeEntryFieldProps { spec: UserAttributeSpec; errorMessage: string | undefined; } -const possibleBirthdayYear: Array<number> = [] +const possibleBirthdayYear: Array<number> = []; for (let i = 0; i < 100; i++) { - possibleBirthdayYear.push(2020 - i) + possibleBirthdayYear.push(2020 - i); } function AttributeEntryField(props: AttributeEntryFieldProps): VNode { - return ( <div> - {props.spec.type === 'date' && + {props.spec.type === "date" && <DateInput grabFocus={props.isFirst} label={props.spec.label} years={possibleBirthdayYear} error={props.errorMessage} bind={[props.value, props.setValue]} - />} + /> + } {props.spec.type === 'number' && <PhoneNumberInput grabFocus={props.isFirst} @@ -102,14 +114,14 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode { bind={[props.value, props.setValue]} /> } - {props.spec.type === 'string' && + {props.spec.type === "string" && ( <TextInput grabFocus={props.isFirst} label={props.spec.label} error={props.errorMessage} bind={[props.value, props.setValue]} /> - } + )} <div class="block"> This stays private <span class="icon is-right"> @@ -119,40 +131,43 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode { </div> ); } -const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/ - +const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/; -function checkIfValid(value: string, spec: UserAttributeSpec): string | undefined { - const pattern = spec['validation-regex'] +function checkIfValid( + value: string, + spec: UserAttributeSpec, +): string | undefined { + const pattern = spec["validation-regex"]; if (pattern) { - const re = new RegExp(pattern) - if (!re.test(value)) return 'The value is invalid' + const re = new RegExp(pattern); + if (!re.test(value)) return "The value is invalid"; } - const logic = spec['validation-logic'] + const logic = spec["validation-logic"]; if (logic) { const func = (validators as any)[logic]; - if (func && typeof func === 'function' && !func(value)) return 'Please check the value' + if (func && typeof func === "function" && !func(value)) + return "Please check the value"; } - const optional = spec.optional + const optional = spec.optional; if (!optional && !value) { - return 'This value is required' + return "This value is required"; } if ("date" === spec.type) { if (!YEAR_REGEX.test(value)) { - return "The date doesn't follow the format" + return "The date doesn't follow the format"; } try { - const v = parse(value, 'yyyy-MM-dd', new Date()); + const v = parse(value, "yyyy-MM-dd", new Date()); if (Number.isNaN(v.getTime())) { - return "Some numeric values seems out of range for a date" + return "Some numeric values seems out of range for a date"; } if ("birthdate" === spec.name && isAfter(v, new Date())) { - return "A birthdate cannot be in the future" + return "A birthdate cannot be in the future"; } } catch (e) { - return "Could not parse the date" + return "Could not parse the date"; } } - return undefined + return undefined; } diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx index 2712522ce..8acf1c8c8 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx @@ -15,73 +15,84 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { AuthenticationEditorScreen as TestedComponent } from './AuthenticationEditorScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { AuthenticationEditorScreen as TestedComponent } from "./AuthenticationEditorScreen"; export default { - title: 'Pages/backup/AuthorizationMethod', + title: "Pages/backup/AuthorizationMethod", component: TestedComponent, args: { order: 4, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const InitialState = createExample(TestedComponent, reducerStatesExample.authEditing); +export const InitialState = createExample( + TestedComponent, + reducerStatesExample.authEditing, +); export const OneAuthMethodConfigured = createExample(TestedComponent, { ...reducerStatesExample.authEditing, - authentication_methods: [{ - type: 'question', - instructions: 'what time is it?', - challenge: 'asd', - }] + authentication_methods: [ + { + type: "question", + instructions: "what time is it?", + challenge: "asd", + }, + ], } as ReducerState); - export const SomeMoreAuthMethodConfigured = createExample(TestedComponent, { ...reducerStatesExample.authEditing, - authentication_methods: [{ - type: 'question', - instructions: 'what time is it?', - challenge: 'asd', - },{ - type: 'question', - instructions: 'what time is it?', - challenge: 'qwe', - },{ - type: 'sms', - instructions: 'what time is it?', - challenge: 'asd', - },{ - type: 'email', - instructions: 'what time is it?', - challenge: 'asd', - },{ - type: 'email', - instructions: 'what time is it?', - challenge: 'asd', - },{ - type: 'email', - instructions: 'what time is it?', - challenge: 'asd', - },{ - type: 'email', - instructions: 'what time is it?', - challenge: 'asd', - }] + authentication_methods: [ + { + type: "question", + instructions: "what time is it?", + challenge: "asd", + }, + { + type: "question", + instructions: "what time is it?", + challenge: "qwe", + }, + { + type: "sms", + instructions: "what time is it?", + challenge: "asd", + }, + { + type: "email", + instructions: "what time is it?", + challenge: "asd", + }, + { + type: "email", + instructions: "what time is it?", + challenge: "asd", + }, + { + type: "email", + instructions: "what time is it?", + challenge: "asd", + }, + { + type: "email", + instructions: "what time is it?", + challenge: "asd", + }, + ], } as ReducerState); export const NoAuthMethodProvided = createExample(TestedComponent, { ...reducerStatesExample.authEditing, authentication_providers: {}, - authentication_methods: [] + authentication_methods: [], } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index 00eb54d4d..1ef326773 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -20,7 +20,9 @@ export function AuthenticationEditorScreen(): VNode { KnownAuthMethods | undefined >(undefined); const [tooFewAuths, setTooFewAuths] = useState(false); - const [manageProvider, setManageProvider] = useState<string | undefined>(undefined) + const [manageProvider, setManageProvider] = useState<string | undefined>( + undefined, + ); // const [addingProvider, setAddingProvider] = useState<string | undefined>(undefined) const reducer = useAnastasisContext(); @@ -68,11 +70,14 @@ export function AuthenticationEditorScreen(): VNode { } if (manageProvider !== undefined) { - - return <AddingProviderScreen - onCancel={() => setManageProvider(undefined)} - providerType={isKnownAuthMethods(manageProvider) ? manageProvider : undefined} - /> + return ( + <AddingProviderScreen + onCancel={() => setManageProvider(undefined)} + providerType={ + isKnownAuthMethods(manageProvider) ? manageProvider : undefined + } + /> + ); } if (selectedMethod) { @@ -100,7 +105,7 @@ export function AuthenticationEditorScreen(): VNode { description="No providers founds" label="Add a provider manually" onConfirm={() => { - setManageProvider(selectedMethod) + setManageProvider(selectedMethod); }} > <p> @@ -193,7 +198,7 @@ export function AuthenticationEditorScreen(): VNode { description="No providers founds" label="Add a provider manually" onConfirm={() => { - setManageProvider("") + setManageProvider(""); }} > <p> @@ -214,7 +219,10 @@ export function AuthenticationEditorScreen(): VNode { authentication method is defined by the backup provider list. </p> <p class="block"> - <button class="button is-info" onClick={() => setManageProvider("")}> + <button + class="button is-info" + onClick={() => setManageProvider("")} + > Manage backup providers </button> </p> diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx index 306adacbb..0789ee6ad 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx @@ -15,48 +15,51 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { BackupFinishedScreen as TestedComponent } from './BackupFinishedScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { BackupFinishedScreen as TestedComponent } from "./BackupFinishedScreen"; export default { - title: 'Pages/backup/Finished', + title: "Pages/backup/Finished", component: TestedComponent, args: { order: 8, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const WithoutName = createExample(TestedComponent, reducerStatesExample.backupFinished); +export const WithoutName = createExample( + TestedComponent, + reducerStatesExample.backupFinished, +); -export const WithName = createExample(TestedComponent, {...reducerStatesExample.backupFinished, - secret_name: 'super_secret', +export const WithName = createExample(TestedComponent, { + ...reducerStatesExample.backupFinished, + secret_name: "super_secret", } as ReducerState); export const WithDetails = createExample(TestedComponent, { ...reducerStatesExample.backupFinished, - secret_name: 'super_secret', + secret_name: "super_secret", success_details: { - 'http://anastasis.net': { + "http://anastasis.net": { policy_expiration: { - t_ms: 'never' + t_ms: "never", }, - policy_version: 0 + policy_version: 0, }, - 'http://taler.net': { + "http://taler.net": { policy_expiration: { - t_ms: new Date().getTime() + 60*60*24*1000 + t_ms: new Date().getTime() + 60 * 60 * 24 * 1000, }, - policy_version: 1 + policy_version: 1, }, - } + }, } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx index 7938baca4..825ec5dc0 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx @@ -4,41 +4,62 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function BackupFinishedScreen(): VNode { - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.backup_state === undefined + ) { + return <div>invalid state</div>; } - const details = reducer.currentReducerState.success_details + const details = reducer.currentReducerState.success_details; - return (<AnastasisClientFrame hideNav title="Backup finished"> - {reducer.currentReducerState.secret_name ? <p> - Your backup of secret <b>"{reducer.currentReducerState.secret_name}"</b> was - successful. - </p> : - <p> - Your secret was successfully backed up. - </p>} + return ( + <AnastasisClientFrame hideNav title="Backup finished"> + {reducer.currentReducerState.secret_name ? ( + <p> + Your backup of secret{" "} + <b>"{reducer.currentReducerState.secret_name}"</b> was successful. + </p> + ) : ( + <p>Your secret was successfully backed up.</p> + )} - {details && <div class="block"> - <p>The backup is stored by the following providers:</p> - {Object.keys(details).map((x, i) => { - const sd = details[x]; - return ( - <div key={i} class="box"> - {x} - <p> - version {sd.policy_version} - {sd.policy_expiration.t_ms !== 'never' ? ` expires at: ${format(sd.policy_expiration.t_ms, 'dd-MM-yyyy')}` : ' without expiration date'} - </p> - </div> - ); - })} - </div>} - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> - </div> - </AnastasisClientFrame>); + {details && ( + <div class="block"> + <p>The backup is stored by the following providers:</p> + {Object.keys(details).map((x, i) => { + const sd = details[x]; + return ( + <div key={i} class="box"> + {x} + <p> + version {sd.policy_version} + {sd.policy_expiration.t_ms !== "never" + ? ` expires at: ${format( + sd.policy_expiration.t_ms, + "dd-MM-yyyy", + )}` + : " without expiration date"} + </p> + </div> + ); + })} + </div> + )} + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> + </div> + </AnastasisClientFrame> + ); } diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx index 46c574cf2..56aee8763 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -19,7 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { ChallengeFeedbackStatus, RecoveryStates, ReducerState } from "anastasis-core"; +import { + ChallengeFeedbackStatus, + RecoveryStates, + ReducerState, +} from "anastasis-core"; import { createExample, reducerStatesExample } from "../../utils"; import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen"; @@ -247,20 +251,20 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample( "uuid-1": { state: ChallengeFeedbackStatus.Solved.toString() }, "uuid-2": { state: ChallengeFeedbackStatus.Message.toString(), - message: 'Challenge should be solved' + message: "Challenge should be solved", }, "uuid-3": { state: ChallengeFeedbackStatus.AuthIban.toString(), challenge_amount: "EUR:1", credit_iban: "DE12345789000", business_name: "Data Loss Incorporated", - wire_transfer_subject: "Anastasis 987654321" + wire_transfer_subject: "Anastasis 987654321", }, "uuid-4": { state: ChallengeFeedbackStatus.Payment.toString(), taler_pay_uri: "taler://pay/...", provider: "https://localhost:8080/", - payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG" + payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", }, "uuid-5": { state: ChallengeFeedbackStatus.RateLimitExceeded.toString(), @@ -269,7 +273,7 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample( "uuid-6": { state: ChallengeFeedbackStatus.Redirect.toString(), redirect_url: "https://videoconf.example.com/", - http_status: 303 + http_status: 303, }, "uuid-7": { state: ChallengeFeedbackStatus.ServerFailure.toString(), diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index ae03dd4d4..c6de00e98 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -11,23 +11,34 @@ function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) { } switch (feedback.state) { case ChallengeFeedbackStatus.Message: - return ( - <div class="block has-text-danger">{feedback.message}</div> - ); + return <div class="block has-text-danger">{feedback.message}</div>; case ChallengeFeedbackStatus.Solved: - return <div /> + return <div />; case ChallengeFeedbackStatus.Pending: - case ChallengeFeedbackStatus.Solved: case ChallengeFeedbackStatus.AuthIban: return null; case ChallengeFeedbackStatus.ServerFailure: return <div class="block has-text-danger">Server error.</div>; case ChallengeFeedbackStatus.RateLimitExceeded: - return <div class="block has-text-danger">There were to many failed attempts.</div>; + return ( + <div class="block has-text-danger"> + There were to many failed attempts. + </div> + ); case ChallengeFeedbackStatus.Unsupported: - return <div class="block has-text-danger">This client doesn't support solving this type of challenge. Use another version or contact the provider.</div>; + return ( + <div class="block has-text-danger"> + This client doesn't support solving this type of challenge. Use + another version or contact the provider. + </div> + ); case ChallengeFeedbackStatus.TruthUnknown: - return <div class="block has-text-danger">Provider doesn't recognize the challenge of the policy. Contact the provider for further information.</div>; + return ( + <div class="block has-text-danger"> + Provider doesn't recognize the challenge of the policy. Contact the + provider for further information. + </div> + ); case ChallengeFeedbackStatus.Redirect: default: return <div />; @@ -70,19 +81,25 @@ export function ChallengeOverviewScreen(): VNode { feedback: challengeFeedback[ch.uuid], }; } - const policiesWithInfo = policies.map((row) => { - let isPolicySolved = true; - const challenges = row - .map(({ uuid }) => { - const info = knownChallengesMap[uuid]; - const isChallengeSolved = info?.feedback?.state === "solved"; - isPolicySolved = isPolicySolved && isChallengeSolved; - return { info, uuid, isChallengeSolved }; - }) - .filter((ch) => ch.info !== undefined); + const policiesWithInfo = policies + .map((row) => { + let isPolicySolved = true; + const challenges = row + .map(({ uuid }) => { + const info = knownChallengesMap[uuid]; + const isChallengeSolved = info?.feedback?.state === "solved"; + isPolicySolved = isPolicySolved && isChallengeSolved; + return { info, uuid, isChallengeSolved }; + }) + .filter((ch) => ch.info !== undefined); - return { isPolicySolved, challenges }; - }); + return { + isPolicySolved, + challenges, + corrupted: row.length > challenges.length, + }; + }) + .filter((p) => !p.corrupted); const atLeastThereIsOnePolicySolved = policiesWithInfo.find((p) => p.isPolicySolved) !== undefined; @@ -92,19 +109,19 @@ export function ChallengeOverviewScreen(): VNode { : undefined; return ( <AnastasisClientFrame hideNext={errors} title="Recovery: Solve challenges"> - {!policies.length ? ( + {!policiesWithInfo.length ? ( <p class="block"> No policies found, try with another version of the secret </p> - ) : policies.length === 1 ? ( + ) : policiesWithInfo.length === 1 ? ( <p class="block"> One policy found for this secret. You need to solve all the challenges in order to recover your secret. </p> ) : ( <p class="block"> - We have found {policies.length} polices. You need to solve all the - challenges from one policy in order to recover your secret. + We have found {policiesWithInfo.length} polices. You need to solve all + the challenges from one policy in order to recover your secret. </p> )} {policiesWithInfo.map((policy, policy_index) => { @@ -113,74 +130,100 @@ export function ChallengeOverviewScreen(): VNode { const method = authMethods[info.type as KnownAuthMethods]; if (!method) { - return <div - key={uuid} - class="block" - style={{ display: "flex", justifyContent: "space-between" }} - > - <div style={{ display: "flex", alignItems: "center" }}> - <span>unknown challenge</span> + return ( + <div + key={uuid} + class="block" + style={{ display: "flex", justifyContent: "space-between" }} + > + <div style={{ display: "flex", alignItems: "center" }}> + <span>unknown challenge</span> + </div> </div> - - </div> + ); } - function ChallengeButton({ id, feedback }: { id: string; feedback?: ChallengeFeedback }): VNode { + function ChallengeButton({ + id, + feedback, + }: { + id: string; + feedback?: ChallengeFeedback; + }): VNode { function selectChallenge(): void { - if (reducer) reducer.transition("select_challenge", { uuid: id }) + if (reducer) reducer.transition("select_challenge", { uuid: id }); } if (!feedback) { - return <div> - <button class="button" onClick={selectChallenge}> - Solve - </button> - </div> + return ( + <div> + <button + class="button" + disabled={ + atLeastThereIsOnePolicySolved && !policy.isPolicySolved + } + onClick={selectChallenge} + > + Solve + </button> + </div> + ); } switch (feedback.state) { case ChallengeFeedbackStatus.ServerFailure: case ChallengeFeedbackStatus.Unsupported: case ChallengeFeedbackStatus.TruthUnknown: - case ChallengeFeedbackStatus.RateLimitExceeded: return <div /> + case ChallengeFeedbackStatus.RateLimitExceeded: + return <div />; case ChallengeFeedbackStatus.AuthIban: - case ChallengeFeedbackStatus.Payment: return <div> - <button class="button" onClick={selectChallenge}> - Pay - </button> - </div> - case ChallengeFeedbackStatus.Redirect: return <div> - <button class="button" onClick={selectChallenge}> - Go to {feedback.redirect_url} - </button> - </div> - case ChallengeFeedbackStatus.Solved: return <div> - <div class="tag is-success is-large"> - Solved - </div> - </div> - default: return <div> - <button class="button" onClick={selectChallenge}> - Solve - </button> - </div> - + case ChallengeFeedbackStatus.Payment: + return ( + <div> + <button + class="button" + disabled={ + atLeastThereIsOnePolicySolved && !policy.isPolicySolved + } + onClick={selectChallenge} + > + Pay + </button> + </div> + ); + case ChallengeFeedbackStatus.Redirect: + return ( + <div> + <button + class="button" + disabled={ + atLeastThereIsOnePolicySolved && !policy.isPolicySolved + } + onClick={selectChallenge} + > + Go to {feedback.redirect_url} + </button> + </div> + ); + case ChallengeFeedbackStatus.Solved: + return ( + <div> + <div class="tag is-success is-large">Solved</div> + </div> + ); + default: + return ( + <div> + <button + class="button" + disabled={ + atLeastThereIsOnePolicySolved && !policy.isPolicySolved + } + onClick={selectChallenge} + > + Solve + </button> + </div> + ); } - // return <div> - // {feedback.state !== "solved" ? ( - // <a - // class="button" - // onClick={() => - - // } - // > - // {isFree ? "Solve" : `Pay and Solve`} - // </a> - // ) : null} - // {feedback.state === "solved" ? ( - // // <div class="block is-success" > Solved </div> - // <div class="tag is-success is-large">Solved</div> - - // ) : null} - // </div> } return ( <div @@ -202,7 +245,6 @@ export function ChallengeOverviewScreen(): VNode { </div> <ChallengeButton id={uuid} feedback={info.feedback} /> - </div> ); }); @@ -210,11 +252,13 @@ export function ChallengeOverviewScreen(): VNode { const policyName = policy.challenges .map((x) => x.info.type) .join(" + "); + const opa = !atLeastThereIsOnePolicySolved ? undefined : policy.isPolicySolved - ? undefined - : "0.6"; + ? undefined + : "0.6"; + return ( <div key={policy_index} diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx index fbcaa0e95..8c788e556 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx @@ -15,24 +15,26 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../utils'; -import { ChallengePayingScreen as TestedComponent } from './ChallengePayingScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../utils"; +import { ChallengePayingScreen as TestedComponent } from "./ChallengePayingScreen"; export default { - title: 'Pages/recovery/__ChallengePaying', + title: "Pages/recovery/__ChallengePaying", component: TestedComponent, args: { order: 10, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const Example = createExample(TestedComponent, reducerStatesExample.challengePaying); +export const Example = createExample( + TestedComponent, + reducerStatesExample.challengePaying, +); diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx index 84896a2ec..ffcc8fafc 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx @@ -3,19 +3,19 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function ChallengePayingScreen(): VNode { - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return <div>invalid state</div>; } - const payments = ['']; //reducer.currentReducerState.payments ?? + const payments = [""]; //reducer.currentReducerState.payments ?? return ( - <AnastasisClientFrame - hideNav - title="Recovery: Challenge Paying" - > + <AnastasisClientFrame hideNav title="Recovery: Challenge Paying"> <p> Some of the providers require a payment to store the encrypted authentication information. diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx index 6bdb3515d..0948d603e 100644 --- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx @@ -16,37 +16,42 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { ContinentSelectionScreen as TestedComponent } from './ContinentSelectionScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { ContinentSelectionScreen as TestedComponent } from "./ContinentSelectionScreen"; export default { - title: 'Pages/Location', + title: "Pages/Location", component: TestedComponent, args: { order: 2, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const BackupSelectContinent = createExample(TestedComponent, reducerStatesExample.backupSelectContinent); +export const BackupSelectContinent = createExample( + TestedComponent, + reducerStatesExample.backupSelectContinent, +); export const BackupSelectCountry = createExample(TestedComponent, { ...reducerStatesExample.backupSelectContinent, - selected_continent: 'Testcontinent', + selected_continent: "Testcontinent", } as ReducerState); -export const RecoverySelectContinent = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent); +export const RecoverySelectContinent = createExample( + TestedComponent, + reducerStatesExample.recoverySelectContinent, +); export const RecoverySelectCountry = createExample(TestedComponent, { ...reducerStatesExample.recoverySelectContinent, - selected_continent: 'Testcontinent', + selected_continent: "Testcontinent", } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx index 3d5fcce55..4cbeb8308 100644 --- a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx @@ -16,94 +16,126 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { EditPoliciesScreen as TestedComponent } from './EditPoliciesScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { EditPoliciesScreen as TestedComponent } from "./EditPoliciesScreen"; export default { - title: 'Pages/backup/ReviewPolicies/EditPolicies', + title: "Pages/backup/ReviewPolicies/EditPolicies", args: { order: 6, }, component: TestedComponent, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const EditingAPolicy = createExample(TestedComponent, { - ...reducerStatesExample.policyReview, - policies: [{ - methods: [{ - authentication_method: 1, - provider: 'https://anastasis.demo.taler.net/' - }, { - authentication_method: 2, - provider: 'http://localhost:8086/' - }] - }, { - methods: [{ - authentication_method: 1, - provider: 'http://localhost:8086/' - }] - }], - authentication_methods: [{ - type: "email", - instructions: "Email to qwe@asd.com", - challenge: "E5VPA" - }, { - type: "totp", - instructions: "Response code for 'Anastasis'", - challenge: "E5VPA" - }, { - type: "sms", - instructions: "SMS to 6666-6666", - challenge: "" - }, { - type: "question", - instructions: "How did the chicken cross the road?", - challenge: "C5SP8" - }] -} as ReducerState, { index : 0}); - -export const CreatingAPolicy = createExample(TestedComponent, { - ...reducerStatesExample.policyReview, - policies: [{ - methods: [{ - authentication_method: 1, - provider: 'https://anastasis.demo.taler.net/' - }, { - authentication_method: 2, - provider: 'http://localhost:8086/' - }] - }, { - methods: [{ - authentication_method: 1, - provider: 'http://localhost:8086/' - }] - }], - authentication_methods: [{ - type: "email", - instructions: "Email to qwe@asd.com", - challenge: "E5VPA" - }, { - type: "totp", - instructions: "Response code for 'Anastasis'", - challenge: "E5VPA" - }, { - type: "sms", - instructions: "SMS to 6666-6666", - challenge: "" - }, { - type: "question", - instructions: "How did the chicken cross the road?", - challenge: "C5SP8" - }] -} as ReducerState, { index : 3}); +export const EditingAPolicy = createExample( + TestedComponent, + { + ...reducerStatesExample.policyReview, + policies: [ + { + methods: [ + { + authentication_method: 1, + provider: "https://anastasis.demo.taler.net/", + }, + { + authentication_method: 2, + provider: "http://localhost:8086/", + }, + ], + }, + { + methods: [ + { + authentication_method: 1, + provider: "http://localhost:8086/", + }, + ], + }, + ], + authentication_methods: [ + { + type: "email", + instructions: "Email to qwe@asd.com", + challenge: "E5VPA", + }, + { + type: "totp", + instructions: "Response code for 'Anastasis'", + challenge: "E5VPA", + }, + { + type: "sms", + instructions: "SMS to 6666-6666", + challenge: "", + }, + { + type: "question", + instructions: "How did the chicken cross the road?", + challenge: "C5SP8", + }, + ], + } as ReducerState, + { index: 0 }, +); +export const CreatingAPolicy = createExample( + TestedComponent, + { + ...reducerStatesExample.policyReview, + policies: [ + { + methods: [ + { + authentication_method: 1, + provider: "https://anastasis.demo.taler.net/", + }, + { + authentication_method: 2, + provider: "http://localhost:8086/", + }, + ], + }, + { + methods: [ + { + authentication_method: 1, + provider: "http://localhost:8086/", + }, + ], + }, + ], + authentication_methods: [ + { + type: "email", + instructions: "Email to qwe@asd.com", + challenge: "E5VPA", + }, + { + type: "totp", + instructions: "Response code for 'Anastasis'", + challenge: "E5VPA", + }, + { + type: "sms", + instructions: "SMS to 6666-6666", + challenge: "", + }, + { + type: "question", + instructions: "How did the chicken cross the road?", + challenge: "C5SP8", + }, + ], + } as ReducerState, + { index: 3 }, +); diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx index 85cc96c46..198209399 100644 --- a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx @@ -20,7 +20,6 @@ interface Props { index: number; cancel: () => void; confirm: (changes: MethodProvider[]) => void; - } export interface MethodProvider { @@ -28,106 +27,151 @@ export interface MethodProvider { provider: string; } -export function EditPoliciesScreen({ index: policy_index, cancel, confirm }: Props): VNode { - const [changedProvider, setChangedProvider] = useState<Array<string>>([]) +export function EditPoliciesScreen({ + index: policy_index, + cancel, + confirm, +}: Props): VNode { + const [changedProvider, setChangedProvider] = useState<Array<string>>([]); - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.backup_state === undefined + ) { + return <div>invalid state</div>; } - const selectableProviders: ProviderInfoByType = {} - const allProviders = Object.entries(reducer.currentReducerState.authentication_providers || {}) + const selectableProviders: ProviderInfoByType = {}; + const allProviders = Object.entries( + reducer.currentReducerState.authentication_providers || {}, + ); for (let index = 0; index < allProviders.length; index++) { - const [url, status] = allProviders[index] + const [url, status] = allProviders[index]; if ("methods" in status) { - status.methods.map(m => { - const type: KnownAuthMethods = m.type as KnownAuthMethods - const values = selectableProviders[type] || [] - const isFree = !m.usage_fee || m.usage_fee.endsWith(":0") - values.push({ url, cost: m.usage_fee, isFree }) - selectableProviders[type] = values - }) + status.methods.map((m) => { + const type: KnownAuthMethods = m.type as KnownAuthMethods; + const values = selectableProviders[type] || []; + const isFree = !m.usage_fee || m.usage_fee.endsWith(":0"); + values.push({ url, cost: m.usage_fee, isFree }); + selectableProviders[type] = values; + }); } } - const allAuthMethods = reducer.currentReducerState.authentication_methods ?? []; + const allAuthMethods = + reducer.currentReducerState.authentication_methods ?? []; const policies = reducer.currentReducerState.policies ?? []; - const policy = policies[policy_index] - - for(let method_index = 0; method_index < allAuthMethods.length; method_index++ ) { - policy?.methods.find(m => m.authentication_method === method_index)?.provider + const policy = policies[policy_index]; + + for ( + let method_index = 0; + method_index < allAuthMethods.length; + method_index++ + ) { + policy?.methods.find((m) => m.authentication_method === method_index) + ?.provider; } function sendChanges(): void { - const newMethods: MethodProvider[] = [] + const newMethods: MethodProvider[] = []; allAuthMethods.forEach((method, index) => { - const oldValue = policy?.methods.find(m => m.authentication_method === index) + const oldValue = policy?.methods.find( + (m) => m.authentication_method === index, + ); if (changedProvider[index] === undefined && oldValue !== undefined) { - newMethods.push(oldValue) + newMethods.push(oldValue); } - if (changedProvider[index] !== undefined && changedProvider[index] !== "") { + if ( + changedProvider[index] !== undefined && + changedProvider[index] !== "" + ) { newMethods.push({ authentication_method: index, - provider: changedProvider[index] - }) + provider: changedProvider[index], + }); } - }) - confirm(newMethods) + }); + confirm(newMethods); } - return <AnastasisClientFrame hideNav title={!policy ? "Backup: New Policy" : "Backup: Edit Policy"}> - <section class="section"> - {!policy ? <p> - Creating a new policy #{policy_index} - </p> : <p> - Editing policy #{policy_index} - </p>} - {allAuthMethods.map((method, index) => { - //take the url from the updated change or from the policy - const providerURL = changedProvider[index] === undefined ? - policy?.methods.find(m => m.authentication_method === index)?.provider : - changedProvider[index]; + return ( + <AnastasisClientFrame + hideNav + title={!policy ? "Backup: New Policy" : "Backup: Edit Policy"} + > + <section class="section"> + {!policy ? ( + <p>Creating a new policy #{policy_index}</p> + ) : ( + <p>Editing policy #{policy_index}</p> + )} + {allAuthMethods.map((method, index) => { + //take the url from the updated change or from the policy + const providerURL = + changedProvider[index] === undefined + ? policy?.methods.find((m) => m.authentication_method === index) + ?.provider + : changedProvider[index]; - const type: KnownAuthMethods = method.type as KnownAuthMethods - function changeProviderTo(url: string): void { - const copy = [...changedProvider] - copy[index] = url - setChangedProvider(copy) - } - return ( - <div key={index} class="block" style={{ display: 'flex', alignItems: 'center' }}> - <span class="icon"> - {authMethods[type]?.icon} - </span> - <span> - {method.instructions} - </span> - <span> - <span class="select " > - <select onChange={(e) => changeProviderTo(e.currentTarget.value)} value={providerURL ?? ""}> - <option key="none" value=""> << off >> </option> - {selectableProviders[type]?.map(prov => ( - <option key={prov.url} value={prov.url}> - {prov.url} + const type: KnownAuthMethods = method.type as KnownAuthMethods; + function changeProviderTo(url: string): void { + const copy = [...changedProvider]; + copy[index] = url; + setChangedProvider(copy); + } + return ( + <div + key={index} + class="block" + style={{ display: "flex", alignItems: "center" }} + > + <span class="icon">{authMethods[type]?.icon}</span> + <span>{method.instructions}</span> + <span> + <span class="select "> + <select + onChange={(e) => changeProviderTo(e.currentTarget.value)} + value={providerURL ?? ""} + > + <option key="none" value=""> + {" "} + << off >>{" "} </option> - ))} - </select> + {selectableProviders[type]?.map((prov) => ( + <option key={prov.url} value={prov.url}> + {prov.url} + </option> + ))} + </select> + </span> </span> - </span> - </div> - ); - })} - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> - <span class="buttons"> - <button class="button" onClick={() => setChangedProvider([])}>Reset</button> - <button class="button is-info" onClick={sendChanges}>Confirm</button> - </span> - </div> - </section> - </AnastasisClientFrame> + </div> + ); + })} + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={cancel}> + Cancel + </button> + <span class="buttons"> + <button class="button" onClick={() => setChangedProvider([])}> + Reset + </button> + <button class="button is-info" onClick={sendChanges}> + Confirm + </button> + </span> + </div> + </section> + </AnastasisClientFrame> + ); } diff --git a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx index 3ddf8011e..9bebcfbc9 100644 --- a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx @@ -15,35 +15,40 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { PoliciesPayingScreen as TestedComponent } from './PoliciesPayingScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { PoliciesPayingScreen as TestedComponent } from "./PoliciesPayingScreen"; export default { - title: 'Pages/backup/__PoliciesPaying', + title: "Pages/backup/__PoliciesPaying", component: TestedComponent, args: { order: 9, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const Example = createExample(TestedComponent, reducerStatesExample.policyPay); +export const Example = createExample( + TestedComponent, + reducerStatesExample.policyPay, +); export const WithSomePaymentRequest = createExample(TestedComponent, { ...reducerStatesExample.policyPay, - policy_payment_requests: [{ - payto: 'payto://x-taler-bank/bank.taler/account-a', - provider: 'provider1' - }, { - payto: 'payto://x-taler-bank/bank.taler/account-b', - provider: 'provider2' - }] + policy_payment_requests: [ + { + payto: "payto://x-taler-bank/bank.taler/account-a", + provider: "provider1", + }, + { + payto: "payto://x-taler-bank/bank.taler/account-b", + provider: "provider2", + }, + ], } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx index a470f5155..c3568b32d 100644 --- a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx @@ -3,20 +3,23 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function PoliciesPayingScreen(): VNode { - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.backup_state === undefined + ) { + return <div>invalid state</div>; } const payments = reducer.currentReducerState.policy_payment_requests ?? []; - + return ( <AnastasisClientFrame hideNav title="Backup: Recovery Document Payments"> <p> - Some of the providers require a payment to store the encrypted - recovery document. + Some of the providers require a payment to store the encrypted recovery + document. </p> <ul> {payments.map((x, i) => { diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx index e92a231a8..47860db29 100644 --- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx @@ -16,30 +16,32 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { RecoveryFinishedScreen as TestedComponent } from './RecoveryFinishedScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { RecoveryFinishedScreen as TestedComponent } from "./RecoveryFinishedScreen"; export default { - title: 'Pages/recovery/Finished', + title: "Pages/recovery/Finished", args: { order: 7, }, component: TestedComponent, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const GoodEnding = createExample(TestedComponent, { ...reducerStatesExample.recoveryFinished, - core_secret: { mime: 'text/plain', value: 'hello' } + core_secret: { mime: "text/plain", value: "hello" }, } as ReducerState); -export const BadEnding = createExample(TestedComponent, reducerStatesExample.recoveryFinished); +export const BadEnding = createExample( + TestedComponent, + reducerStatesExample.recoveryFinished, +); diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx index a61ef9efa..11ae09d4f 100644 --- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx @@ -1,39 +1,53 @@ -import { - bytesToString, - decodeCrock -} from "@gnu-taler/taler-util"; +import { bytesToString, decodeCrock } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function RecoveryFinishedScreen(): VNode { - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return <div>invalid state</div>; } - const encodedSecret = reducer.currentReducerState.core_secret + const encodedSecret = reducer.currentReducerState.core_secret; if (!encodedSecret) { - return <AnastasisClientFrame title="Recovery Problem" hideNav> - <p> - Secret not found - </p> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> - </div> - </AnastasisClientFrame> + return ( + <AnastasisClientFrame title="Recovery Problem" hideNav> + <p>Secret not found</p> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> + </div> + </AnastasisClientFrame> + ); } - const secret = bytesToString(decodeCrock(encodedSecret.value)) + const secret = bytesToString(decodeCrock(encodedSecret.value)); return ( <AnastasisClientFrame title="Recovery Finished" hideNav> - <p> - Secret: {secret} - </p> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + <p>Your secret: {secret}</p> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx index e348101ee..4a1cba6a8 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx @@ -15,44 +15,51 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { ReviewPoliciesScreen as TestedComponent } from './ReviewPoliciesScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { ReviewPoliciesScreen as TestedComponent } from "./ReviewPoliciesScreen"; export default { - title: 'Pages/backup/ReviewPolicies', + title: "Pages/backup/ReviewPolicies", args: { order: 6, }, component: TestedComponent, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const HasPoliciesButMethodListIsEmpty = createExample(TestedComponent, { ...reducerStatesExample.policyReview, - policies: [{ - methods: [{ - authentication_method: 0, - provider: 'asd' - }, { - authentication_method: 1, - provider: 'asd' - }] - }, { - methods: [{ - authentication_method: 1, - provider: 'asd' - }] - }], - authentication_methods: [] + policies: [ + { + methods: [ + { + authentication_method: 0, + provider: "asd", + }, + { + authentication_method: 1, + provider: "asd", + }, + ], + }, + { + methods: [ + { + authentication_method: 1, + provider: "asd", + }, + ], + }, + ], + authentication_methods: [], } as ReducerState); export const SomePoliciesWithMethods = createExample(TestedComponent, { @@ -62,186 +69,193 @@ export const SomePoliciesWithMethods = createExample(TestedComponent, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" - } - ] + provider: "https://kudos.demo.anastasis.lu/", + }, + ], }, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" + provider: "https://anastasis.demo.taler.net/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" + provider: "https://anastasis.demo.taler.net/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" + provider: "https://anastasis.demo.taler.net/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] - } + provider: "https://anastasis.demo.taler.net/", + }, + ], + }, + ], + authentication_methods: [ + { + type: "email", + instructions: "Email to qwe@asd.com", + challenge: "E5VPA", + }, + { + type: "sms", + instructions: "SMS to 555-555", + challenge: "", + }, + { + type: "question", + instructions: "Does P equal NP?", + challenge: "C5SP8", + }, + { + type: "totp", + instructions: "Response code for 'Anastasis'", + challenge: "E5VPA", + }, + { + type: "sms", + instructions: "SMS to 6666-6666", + challenge: "", + }, + { + type: "question", + instructions: "How did the chicken cross the road?", + challenge: "C5SP8", + }, ], - authentication_methods: [{ - type: "email", - instructions: "Email to qwe@asd.com", - challenge: "E5VPA" - }, { - type: "sms", - instructions: "SMS to 555-555", - challenge: "" - }, { - type: "question", - instructions: "Does P equal NP?", - challenge: "C5SP8" - },{ - type: "totp", - instructions: "Response code for 'Anastasis'", - challenge: "E5VPA" - }, { - type: "sms", - instructions: "SMS to 6666-6666", - challenge: "" - }, { - type: "question", - instructions: "How did the chicken cross the road?", - challenge: "C5SP8" -}] } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx index aa98b5dd9..c43f0cdea 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx @@ -6,16 +6,20 @@ import { EditPoliciesScreen } from "./EditPoliciesScreen"; import { AnastasisClientFrame } from "./index"; export function ReviewPoliciesScreen(): VNode { - const [editingPolicy, setEditingPolicy] = useState<number | undefined>() - const reducer = useAnastasisContext() + const [editingPolicy, setEditingPolicy] = useState<number | undefined>(); + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.backup_state === undefined + ) { + return <div>invalid state</div>; } - const configuredAuthMethods = reducer.currentReducerState.authentication_methods ?? []; + const configuredAuthMethods = + reducer.currentReducerState.authentication_methods ?? []; const policies = reducer.currentReducerState.policies ?? []; if (editingPolicy !== undefined) { @@ -28,58 +32,109 @@ export function ReviewPoliciesScreen(): VNode { policy_index: editingPolicy, policy: newMethods, }); - setEditingPolicy(undefined) + setEditingPolicy(undefined); }} /> - ) + ); } - const errors = policies.length < 1 ? 'Need more policies' : undefined + const errors = policies.length < 1 ? "Need more policies" : undefined; return ( - <AnastasisClientFrame hideNext={errors} title="Backup: Review Recovery Policies"> - {policies.length > 0 && <p class="block"> - Based on your configured authentication method you have created, some policies - have been configured. In order to recover your secret you have to solve all the - challenges of at least one policy. - </p>} - {policies.length < 1 && <p class="block"> - No policies had been created. Go back and add more authentication methods. - </p>} - <div class="block" style={{ justifyContent: 'flex-end' }} > - <button class="button is-success" onClick={() => setEditingPolicy(policies.length + 1)}>Add new policy</button> + <AnastasisClientFrame + hideNext={errors} + title="Backup: Review Recovery Policies" + > + {policies.length > 0 && ( + <p class="block"> + Based on your configured authentication method you have created, some + policies have been configured. In order to recover your secret you + have to solve all the challenges of at least one policy. + </p> + )} + {policies.length < 1 && ( + <p class="block"> + No policies had been created. Go back and add more authentication + methods. + </p> + )} + <div class="block" style={{ justifyContent: "flex-end" }}> + <button + class="button is-success" + onClick={() => setEditingPolicy(policies.length + 1)} + > + Add new policy + </button> </div> {policies.map((p, policy_index) => { const methods = p.methods - .map(x => configuredAuthMethods[x.authentication_method] && ({ ...configuredAuthMethods[x.authentication_method], provider: x.provider })) - .filter(x => !!x) + .map( + (x) => + configuredAuthMethods[x.authentication_method] && { + ...configuredAuthMethods[x.authentication_method], + provider: x.provider, + }, + ) + .filter((x) => !!x); - const policyName = methods.map(x => x.type).join(" + "); + const policyName = methods.map((x) => x.type).join(" + "); + + if (p.methods.length > methods.length) { + //there is at least one authentication method that is corrupted + return null; + } return ( - <div key={policy_index} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> + <div + key={policy_index} + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > <div> <h3 class="subtitle"> Policy #{policy_index + 1}: {policyName} </h3> - {!methods.length && <p> - No auth method found - </p>} + {!methods.length && <p>No auth method found</p>} {methods.map((m, i) => { return ( - <p key={i} class="block" style={{ display: 'flex', alignItems: 'center' }}> + <p + key={i} + class="block" + style={{ display: "flex", alignItems: "center" }} + > <span class="icon"> {authMethods[m.type as KnownAuthMethods]?.icon} </span> <span> - {m.instructions} recovery provided by <a href={m.provider}>{m.provider}</a> + {m.instructions} recovery provided by{" "} + <a href={m.provider}>{m.provider}</a> </span> </p> ); })} </div> - <div style={{ marginTop: 'auto', marginBottom: 'auto', display: 'flex', justifyContent: 'space-between', flexDirection: 'column' }}> - <button class="button is-info block" onClick={() => setEditingPolicy(policy_index)}>Edit</button> - <button class="button is-danger block" onClick={() => reducer.transition("delete_policy", { policy_index })}>Delete</button> + <div + style={{ + marginTop: "auto", + marginBottom: "auto", + display: "flex", + justifyContent: "space-between", + flexDirection: "column", + }} + > + <button + class="button is-info block" + onClick={() => setEditingPolicy(policy_index)} + > + Edit + </button> + <button + class="button is-danger block" + onClick={() => + reducer.transition("delete_policy", { policy_index }) + } + > + Delete + </button> </div> </div> ); diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx index db061d936..3f2c6a245 100644 --- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx @@ -15,30 +15,29 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { SecretEditorScreen as TestedComponent } from './SecretEditorScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { SecretEditorScreen as TestedComponent } from "./SecretEditorScreen"; export default { - title: 'Pages/backup/SecretInput', + title: "Pages/backup/SecretInput", component: TestedComponent, args: { order: 7, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const WithSecretNamePreselected = createExample(TestedComponent, { ...reducerStatesExample.secretEdition, - secret_name: 'someSecretName', + secret_name: "someSecretName", } as ReducerState); export const WithoutName = createExample(TestedComponent, { diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx index 8d02ebfbe..01ce3f0a7 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx @@ -15,37 +15,35 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { SecretSelectionScreen as TestedComponent } from './SecretSelectionScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { SecretSelectionScreen as TestedComponent } from "./SecretSelectionScreen"; export default { - title: 'Pages/recovery/SecretSelection', + title: "Pages/recovery/SecretSelection", component: TestedComponent, args: { order: 4, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const Example = createExample(TestedComponent, { ...reducerStatesExample.secretSelection, recovery_document: { - provider_url: 'https://kudos.demo.anastasis.lu/', - secret_name: 'secretName', + provider_url: "https://kudos.demo.anastasis.lu/", + secret_name: "secretName", version: 1, }, } as ReducerState); - export const NoRecoveryDocumentFound = createExample(TestedComponent, { ...reducerStatesExample.secretSelection, recovery_document: undefined, diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx index 398393619..4000f9bfe 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx @@ -8,18 +8,23 @@ import { AnastasisClientFrame } from "./index"; export function SecretSelectionScreen(): VNode { const [selectingVersion, setSelectingVersion] = useState<boolean>(false); - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); - const [manageProvider, setManageProvider] = useState(false) - const currentVersion = (reducer?.currentReducerState - && ("recovery_document" in reducer.currentReducerState) - && reducer.currentReducerState.recovery_document?.version) || 0; + const [manageProvider, setManageProvider] = useState(false); + const currentVersion = + (reducer?.currentReducerState && + "recovery_document" in reducer.currentReducerState && + reducer.currentReducerState.recovery_document?.version) || + 0; if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return <div>invalid state</div>; } async function doSelectVersion(p: string, n: number): Promise<void> { @@ -33,72 +38,101 @@ export function SecretSelectionScreen(): VNode { }); } - const providerList = Object.keys(reducer.currentReducerState.authentication_providers ?? {}) - const recoveryDocument = reducer.currentReducerState.recovery_document + const providerList = Object.keys( + reducer.currentReducerState.authentication_providers ?? {}, + ); + const recoveryDocument = reducer.currentReducerState.recovery_document; if (!recoveryDocument) { - return <ChooseAnotherProviderScreen - providers={providerList} selected="" - onChange={(newProv) => doSelectVersion(newProv, 0)} - /> + return ( + <ChooseAnotherProviderScreen + providers={providerList} + selected="" + onChange={(newProv) => doSelectVersion(newProv, 0)} + /> + ); } if (selectingVersion) { - return <SelectOtherVersionProviderScreen providers={providerList} - provider={recoveryDocument.provider_url} version={recoveryDocument.version} - onCancel={() => setSelectingVersion(false)} - onConfirm={doSelectVersion} - /> + return ( + <SelectOtherVersionProviderScreen + providers={providerList} + provider={recoveryDocument.provider_url} + version={recoveryDocument.version} + onCancel={() => setSelectingVersion(false)} + onConfirm={doSelectVersion} + /> + ); } if (manageProvider) { - return <AddingProviderScreen onCancel={() => setManageProvider(false)} /> + return <AddingProviderScreen onCancel={() => setManageProvider(false)} />; } return ( <AnastasisClientFrame title="Recovery: Select secret"> <div class="columns"> <div class="column"> - <div class="box" style={{ border: '2px solid green' }}> + <div class="box" style={{ border: "2px solid green" }}> <h1 class="subtitle">{recoveryDocument.provider_url}</h1> <div class="block"> - {currentVersion === 0 ? <p> - Set to recover the latest version - </p> : <p> - Set to recover the version number {currentVersion} - </p>} + {currentVersion === 0 ? ( + <p>Set to recover the latest version</p> + ) : ( + <p>Set to recover the version number {currentVersion}</p> + )} </div> <div class="buttons is-right"> - <button class="button" onClick={(e) => setSelectingVersion(true)}>Change secret's version</button> + <button class="button" onClick={(e) => setSelectingVersion(true)}> + Change secret's version + </button> </div> </div> </div> <div class="column"> - <p>Secret found, you can select another version or continue to the challenges solving</p> + <p> + Secret found, you can select another version or continue to the + challenges solving + </p> <p class="block"> - <button class="button is-info" onClick={() => setManageProvider(true)}> + <a onClick={() => setManageProvider(true)}> Manage recovery providers - </button> + </a> </p> - </div> </div> </AnastasisClientFrame> ); } - -function ChooseAnotherProviderScreen({ providers, selected, onChange }: { selected: string; providers: string[]; onChange: (prov: string) => void }): VNode { +function ChooseAnotherProviderScreen({ + providers, + selected, + onChange, +}: { + selected: string; + providers: string[]; + onChange: (prov: string) => void; +}): VNode { return ( - <AnastasisClientFrame hideNext="Recovery document not found" title="Recovery: Problem"> + <AnastasisClientFrame + hideNext="Recovery document not found" + title="Recovery: Problem" + > <p>No recovery document found, try with another provider</p> <div class="field"> <label class="label">Provider</label> <div class="control is-expanded has-icons-left"> <div class="select is-fullwidth"> - <select onChange={(e) => onChange(e.currentTarget.value)} value={selected}> - <option key="none" disabled selected value=""> Choose a provider </option> - {providers.map(prov => ( + <select + onChange={(e) => onChange(e.currentTarget.value)} + value={selected} + > + <option key="none" disabled selected value=""> + {" "} + Choose a provider{" "} + </option> + {providers.map((prov) => ( <option key={prov} value={prov}> {prov} </option> @@ -114,9 +148,23 @@ function ChooseAnotherProviderScreen({ providers, selected, onChange }: { select ); } -function SelectOtherVersionProviderScreen({ providers, provider, version, onConfirm, onCancel }: { onCancel: () => void; provider: string; version: number; providers: string[]; onConfirm: (prov: string, v: number) => Promise<void>; }): VNode { +function SelectOtherVersionProviderScreen({ + providers, + provider, + version, + onConfirm, + onCancel, +}: { + onCancel: () => void; + provider: string; + version: number; + providers: string[]; + onConfirm: (prov: string, v: number) => Promise<void>; +}): VNode { const [otherProvider, setOtherProvider] = useState<string>(provider); - const [otherVersion, setOtherVersion] = useState(version > 0 ? String(version) : ""); + const [otherVersion, setOtherVersion] = useState( + version > 0 ? String(version) : "", + ); return ( <AnastasisClientFrame hideNav title="Recovery: Select secret"> @@ -125,11 +173,11 @@ function SelectOtherVersionProviderScreen({ providers, provider, version, onConf <div class="box"> <h1 class="subtitle">Provider {otherProvider}</h1> <div class="block"> - {version === 0 ? <p> - Set to recover the latest version - </p> : <p> - Set to recover the version number {version} - </p>} + {version === 0 ? ( + <p>Set to recover the latest version</p> + ) : ( + <p>Set to recover the version number {version}</p> + )} <p>Specify other version below or use the latest</p> </div> @@ -137,9 +185,15 @@ function SelectOtherVersionProviderScreen({ providers, provider, version, onConf <label class="label">Provider</label> <div class="control is-expanded has-icons-left"> <div class="select is-fullwidth"> - <select onChange={(e) => setOtherProvider(e.currentTarget.value)} value={otherProvider}> - <option key="none" disabled selected value=""> Choose a provider </option> - {providers.map(prov => ( + <select + onChange={(e) => setOtherProvider(e.currentTarget.value)} + value={otherProvider} + > + <option key="none" disabled selected value=""> + {" "} + Choose a provider{" "} + </option> + {providers.map((prov) => ( <option key={prov} value={prov}> {prov} </option> @@ -156,23 +210,40 @@ function SelectOtherVersionProviderScreen({ providers, provider, version, onConf label="Version" placeholder="version number to recover" grabFocus - bind={[otherVersion, setOtherVersion]} /> + bind={[otherVersion, setOtherVersion]} + /> </div> </div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={onCancel}>Cancel</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> <div class="buttons"> - <AsyncButton class="button" onClick={() => onConfirm(otherProvider, 0)}>Use latest</AsyncButton> - <AsyncButton class="button is-info" onClick={() => onConfirm(otherProvider, parseInt(otherVersion, 10))}>Confirm</AsyncButton> + <AsyncButton + class="button" + onClick={() => onConfirm(otherProvider, 0)} + > + Use latest + </AsyncButton> + <AsyncButton + class="button is-info" + onClick={() => + onConfirm(otherProvider, parseInt(otherVersion, 10)) + } + > + Confirm + </AsyncButton> </div> </div> </div> - <div class="column"> - . - </div> + <div class="column">.</div> </div> - </AnastasisClientFrame> ); - } diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx index f1f2802ae..76d0700db 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx @@ -15,55 +15,63 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, RecoveryStates, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { SolveScreen as TestedComponent } from './SolveScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { + ChallengeFeedbackStatus, + RecoveryStates, + ReducerState, +} from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { SolveScreen as TestedComponent } from "./SolveScreen"; export default { - title: 'Pages/recovery/SolveChallenge/Solve', + title: "Pages/recovery/SolveChallenge/Solve", component: TestedComponent, args: { order: 6, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const NoInformation = createExample(TestedComponent, reducerStatesExample.challengeSolving); +export const NoInformation = createExample( + TestedComponent, + reducerStatesExample.challengeSolving, +); export const NotSupportedChallenge = createExample(TestedComponent, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'chall-type', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "chall-type", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', - + selected_challenge_uuid: "ASDASDSAD!1", } as ReducerState); export const MismatchedChallengeId = createExample(TestedComponent, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'chall-type', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "chall-type", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'no-no-no' + selected_challenge_uuid: "no-no-no", } as ReducerState); - diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx index 5e17c9aa1..b87dad2ce 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx @@ -2,76 +2,126 @@ import { h, VNode } from "preact"; import { AnastasisClientFrame } from "."; import { ChallengeFeedback, - ChallengeFeedbackStatus + ChallengeFeedbackStatus, } from "../../../../anastasis-core/lib"; import { Notifications } from "../../components/Notifications"; import { useAnastasisContext } from "../../context/anastasis"; import { authMethods, KnownAuthMethods } from "./authMethod"; -export function SolveOverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }): VNode { +export function SolveOverviewFeedbackDisplay(props: { + feedback?: ChallengeFeedback; +}): VNode { const { feedback } = props; if (!feedback) { return <div />; } switch (feedback.state) { case ChallengeFeedbackStatus.Message: - return (<Notifications notifications={[{ - type: "INFO", - message: `Message from provider`, - description: feedback.message - }]} />); + return ( + <Notifications + notifications={[ + { + type: "INFO", + message: `Message from provider`, + description: feedback.message, + }, + ]} + /> + ); case ChallengeFeedbackStatus.Payment: - return <Notifications notifications={[{ - type: "INFO", - message: `Message from provider`, - description: <span> - To pay you can <a href={feedback.taler_pay_uri}>click here</a> - </span> - }]} /> + return ( + <Notifications + notifications={[ + { + type: "INFO", + message: `Message from provider`, + description: ( + <span> + To pay you can <a href={feedback.taler_pay_uri}>click here</a> + </span> + ), + }, + ]} + /> + ); case ChallengeFeedbackStatus.AuthIban: - return <Notifications notifications={[{ - type: "INFO", - message: `Message from provider`, - description: `Need to send a wire transfer to "${feedback.business_name}"` - }]} />; + return ( + <Notifications + notifications={[ + { + type: "INFO", + message: `Message from provider`, + description: `Need to send a wire transfer to "${feedback.business_name}"`, + }, + ]} + /> + ); case ChallengeFeedbackStatus.ServerFailure: - return (<Notifications notifications={[{ - type: "ERROR", - message: `Server error: Code ${feedback.http_status}`, - description: feedback.error_response - }]} />); + return ( + <Notifications + notifications={[ + { + type: "ERROR", + message: `Server error: Code ${feedback.http_status}`, + description: feedback.error_response, + }, + ]} + /> + ); case ChallengeFeedbackStatus.RateLimitExceeded: - return (<Notifications notifications={[{ - type: "ERROR", - message: `Message from provider`, - description: "There were to many failed attempts." - }]} />); + return ( + <Notifications + notifications={[ + { + type: "ERROR", + message: `Message from provider`, + description: "There were to many failed attempts.", + }, + ]} + /> + ); case ChallengeFeedbackStatus.Redirect: - return (<Notifications notifications={[{ - type: "INFO", - message: `Message from provider`, - description: <span> - Please visit this link: <a>{feedback.redirect_url}</a> - </span> - }]} />); + return ( + <Notifications + notifications={[ + { + type: "INFO", + message: `Message from provider`, + description: ( + <span> + Please visit this link: <a>{feedback.redirect_url}</a> + </span> + ), + }, + ]} + /> + ); case ChallengeFeedbackStatus.Unsupported: - return (<Notifications notifications={[{ - type: "ERROR", - message: `This client doesn't support solving this type of challenge`, - description: `Use another version or contact the provider. Type of challenge "${feedback.unsupported_method}"` - }]} />); + return ( + <Notifications + notifications={[ + { + type: "ERROR", + message: `This client doesn't support solving this type of challenge`, + description: `Use another version or contact the provider. Type of challenge "${feedback.unsupported_method}"`, + }, + ]} + /> + ); case ChallengeFeedbackStatus.TruthUnknown: - return (<Notifications notifications={[{ - type: "ERROR", - message: `Provider doesn't recognize the type of challenge`, - description: "Contact the provider for further information" - }]} />); - default: return ( - <div> - <pre>{JSON.stringify(feedback)}</pre> - </div> + <Notifications + notifications={[ + { + type: "ERROR", + message: `Provider doesn't recognize the type of challenge`, + description: "Contact the provider for further information", + }, + ]} + /> ); + default: + return <div />; } } @@ -110,8 +160,16 @@ export function SolveScreen(): VNode { return ( <AnastasisClientFrame hideNav title="Recovery problem"> <div>invalid state</div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -120,26 +178,36 @@ export function SolveScreen(): VNode { return ( <AnastasisClientFrame hideNav title="Not implemented"> <p> - The challenge selected is not supported for this UI. Please update this - version or try using another policy. + The challenge selected is not supported for this UI. Please update + this version or try using another policy. </p> - {reducer && - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + {reducer && ( + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> - } + )} </AnastasisClientFrame> ); } - const chArr = reducer.currentReducerState.recovery_information.challenges; const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; - const selectedChallenge = chArr.find(ch => ch.uuid === selectedUuid) + const selectedChallenge = chArr.find((ch) => ch.uuid === selectedUuid); - const SolveDialog = !selectedChallenge || !authMethods[selectedChallenge.type as KnownAuthMethods] ? - SolveNotImplemented : - authMethods[selectedChallenge.type as KnownAuthMethods].solve ?? SolveNotImplemented + const SolveDialog = + !selectedChallenge || + !authMethods[selectedChallenge.type as KnownAuthMethods] + ? SolveNotImplemented + : authMethods[selectedChallenge.type as KnownAuthMethods].solve ?? + SolveNotImplemented; - return <SolveDialog id={selectedUuid} /> + return <SolveDialog id={selectedUuid} />; } diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx index 41082c128..fcddaf87a 100644 --- a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx @@ -15,24 +15,26 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../utils'; -import { StartScreen as TestedComponent } from './StartScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../utils"; +import { StartScreen as TestedComponent } from "./StartScreen"; export default { - title: 'Pages/Start', + title: "Pages/Start", component: TestedComponent, args: { order: 1, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const InitialState = createExample(TestedComponent, reducerStatesExample.initial);
\ No newline at end of file +export const InitialState = createExample( + TestedComponent, + reducerStatesExample.initial, +); diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.tsx index d53df4cae..8b24ef684 100644 --- a/packages/anastasis-webui/src/pages/home/StartScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/StartScreen.tsx @@ -1,27 +1,36 @@ - import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function StartScreen(): VNode { - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } return ( <AnastasisClientFrame hideNav title="Home"> <div class="columns"> <div class="column" /> <div class="column is-four-fifths"> - <div class="buttons"> - <button class="button is-success" autoFocus onClick={() => reducer.startBackup()}> - <div class="icon"><i class="mdi mdi-arrow-up" /></div> + <button + class="button is-success" + autoFocus + onClick={() => reducer.startBackup()} + > + <div class="icon"> + <i class="mdi mdi-arrow-up" /> + </div> <span>Backup a secret</span> </button> - <button class="button is-info" onClick={() => reducer.startRecover()}> - <div class="icon"><i class="mdi mdi-arrow-down" /></div> + <button + class="button is-info" + onClick={() => reducer.startRecover()} + > + <div class="icon"> + <i class="mdi mdi-arrow-down" /> + </div> <span>Recover a secret</span> </button> @@ -30,7 +39,6 @@ export function StartScreen(): VNode { <span>Restore a session</span> </button> */} </div> - </div> <div class="column" /> </div> diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx index 38b71bc36..245ad8889 100644 --- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx @@ -15,29 +15,31 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { TruthsPayingScreen as TestedComponent } from './TruthsPayingScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { TruthsPayingScreen as TestedComponent } from "./TruthsPayingScreen"; export default { - title: 'Pages/backup/__TruthsPaying', + title: "Pages/backup/__TruthsPaying", component: TestedComponent, args: { order: 10, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const Example = createExample(TestedComponent, reducerStatesExample.truthsPaying); +export const Example = createExample( + TestedComponent, + reducerStatesExample.truthsPaying, +); export const WithPaytoList = createExample(TestedComponent, { ...reducerStatesExample.truthsPaying, - payments: ['payto://x-taler-bank/bank/account'] + payments: ["payto://x-taler-bank/bank/account"], } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx index 0b32e0db5..6f95fa93b 100644 --- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx @@ -3,19 +3,19 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function TruthsPayingScreen(): VNode { - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.backup_state === undefined + ) { + return <div>invalid state</div>; } const payments = reducer.currentReducerState.payments ?? []; return ( - <AnastasisClientFrame - hideNext={"FIXME"} - title="Backup: Truths Paying" - > + <AnastasisClientFrame hideNext={"FIXME"} title="Backup: Truths Paying"> <p> Some of the providers require a payment to store the encrypted authentication information. diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx index da87b7a8b..080a7ab31 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx @@ -15,51 +15,67 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/email', + title: "Pages/backup/AuthorizationMethod/AuthMethods/email", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'email' +const type: KnownAuthMethods = "email"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Email to sebasjm@email.com ', - remove: () => null - }] -}); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Email to sebasjm@email.com ", + remove: () => null, + }, + ], + }, +); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Email to sebasjm@email.com', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Email to someone@sebasjm.com', - remove: () => null - }] -}); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Email to sebasjm@email.com", + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: "Email to someone@sebasjm.com", + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx index 27a0685b2..61c66c8c8 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx @@ -1,57 +1,90 @@ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; +import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { EmailInput } from "../../../components/fields/EmailInput"; import { AnastasisClientFrame } from "../index"; import { AuthMethodSetupProps } from "./index"; -const EMAIL_PATTERN = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ +const EMAIL_PATTERN = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; -export function AuthMethodEmailSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { +export function AuthMethodEmailSetup({ + cancel, + addAuthMethod, + configured, +}: AuthMethodSetupProps): VNode { const [email, setEmail] = useState(""); - const addEmailAuth = (): void => addAuthMethod({ - authentication_method: { - type: "email", - instructions: `Email to ${email}`, - challenge: encodeCrock(stringToBytes(email)), - }, - }); - const emailError = !EMAIL_PATTERN.test(email) ? 'Email address is not valid' : undefined - const errors = !email ? 'Add your email' : emailError + const addEmailAuth = (): void => + addAuthMethod({ + authentication_method: { + type: "email", + instructions: `Email to ${email}`, + challenge: encodeCrock(stringToBytes(email)), + }, + }); + const emailError = !EMAIL_PATTERN.test(email) + ? "Email address is not valid" + : undefined; + const errors = !email ? "Add your email" : emailError; return ( <AnastasisClientFrame hideNav title="Add email authentication"> <p> For email authentication, you need to provide an email address. When recovering your secret, you will need to enter the code you receive by - email. + email. Add the uuid from the challenge </p> <div> <EmailInput label="Email address" error={emailError} placeholder="email@domain.com" - bind={[email, setEmail]} /> + bind={[email, setEmail]} + /> </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your emails: - </div><div class="block"> - {configured.map((c, i) => { - return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p> - <div><button class="button is-danger" onClick={c.remove} >Delete</button></div> - </div> - })} - </div></section>} + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your emails:</div> + <div class="block"> + {configured.map((c, i) => { + return ( + <div + key={i} + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > + <p style={{ marginBottom: "auto", marginTop: "auto" }}> + {c.instructions} + </p> + <div> + <button class="button is-danger" onClick={c.remove}> + Delete + </button> + </div> + </div> + ); + })} + </div> + </section> + )} <div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={cancel}> + Cancel + </button> <span data-tooltip={errors}> - <button class="button is-info" disabled={errors !== undefined} onClick={addEmailAuth}>Add</button> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addEmailAuth} + > + Add + </button> </span> </div> </div> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx index 525cd2b07..6a8a2a347 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx @@ -15,66 +15,76 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/recovery/SolveChallenge/AuthMethods/email', + title: "Pages/recovery/SolveChallenge/AuthMethods/email", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'email' +const type: KnownAuthMethods = "email"; -export const WithoutFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'uuid-1' - }], - policies: [], +export const WithoutFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "uuid-1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "uuid-1", + } as ReducerState, + { + id: "uuid-1", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); +); -export const PaymentFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'uuid-1' - }], - policies: [], +export const PaymentFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "uuid-1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "uuid-1", + challenge_feedback: { + "uuid-1": { + state: ChallengeFeedbackStatus.Payment, + taler_pay_uri: "taler://pay/...", + provider: "https://localhost:8080/", + payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", + }, + }, + } as ReducerState, + { + id: "uuid-1", }, - selected_challenge_uuid: 'uuid-1', - challenge_feedback: { - 'uuid-1': { - state: ChallengeFeedbackStatus.Payment, - taler_pay_uri: "taler://pay/...", - provider: "https://localhost:8080/", - payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG" - } - } -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx index bd4f43740..ff6c51d1c 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { return ( <AnastasisClientFrame hideNav title="Recovery problem"> <div>invalid state</div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,19 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + const shouldHideConfirm = + feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || + feedback?.state === ChallengeFeedbackStatus.Redirect || + feedback?.state === ChallengeFeedbackStatus.Unsupported || + feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( <AnastasisClientFrame hideNav title="Add email authentication"> <SolveOverviewFeedbackDisplay feedback={feedback} /> <p> - An email has been sent to "<b>{selectedChallenge.instructions}</b>". Type the - code below + An email has been sent to "<b>{selectedChallenge.instructions}</b>". + Type the code below. + <b>Here we need to add the code "{selectedUuid}"</b> </p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> @@ -97,9 +105,11 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx index be0a04847..c521e18fd 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx @@ -15,50 +15,66 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/IBAN', + title: "Pages/backup/AuthorizationMethod/AuthMethods/IBAN", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'iban' +const type: KnownAuthMethods = "iban"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Wire transfer from QWEASD123123 with holder Sebastian', - remove: () => null - }] -}); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Wire transfer from QWEASD123123 with holder Javier', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Wire transfer from QWEASD123123 with holder Sebastian', - remove: () => null - }] -},); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Wire transfer from QWEASD123123 with holder Sebastian", + remove: () => null, + }, + ], + }, +); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Wire transfer from QWEASD123123 with holder Javier", + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: "Wire transfer from QWEASD123123 with holder Sebastian", + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx index 87969ab27..dee550e5b 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx @@ -1,7 +1,7 @@ import { canonicalJson, encodeCrock, - stringToBytes + stringToBytes, } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -9,56 +9,98 @@ import { AuthMethodSetupProps } from "."; import { TextInput } from "../../../components/fields/TextInput"; import { AnastasisClientFrame } from "../index"; -export function AuthMethodIbanSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { +export function AuthMethodIbanSetup({ + addAuthMethod, + cancel, + configured, +}: AuthMethodSetupProps): VNode { const [name, setName] = useState(""); const [account, setAccount] = useState(""); - const addIbanAuth = (): void => addAuthMethod({ - authentication_method: { - type: "iban", - instructions: `Wire transfer from ${account} with holder ${name}`, - challenge: encodeCrock(stringToBytes(canonicalJson({ - name, account - }))), - }, - }); - const errors = !name ? 'Add an account name' : ( - !account ? 'Add an account IBAN number' : undefined - ) + const addIbanAuth = (): void => + addAuthMethod({ + authentication_method: { + type: "iban", + instructions: `Wire transfer from ${account} with holder ${name}`, + challenge: encodeCrock( + stringToBytes( + canonicalJson({ + name, + account, + }), + ), + ), + }, + }); + const errors = !name + ? "Add an account name" + : !account + ? "Add an account IBAN number" + : undefined; return ( <AnastasisClientFrame hideNav title="Add bank transfer authentication"> <p> - For bank transfer authentication, you need to provide a bank - account (account holder name and IBAN). When recovering your - secret, you will be asked to pay the recovery fee via bank - transfer from the account you provided here. + For bank transfer authentication, you need to provide a bank account + (account holder name and IBAN). When recovering your secret, you will be + asked to pay the recovery fee via bank transfer from the account you + provided here. </p> <div> <TextInput label="Bank account holder name" grabFocus placeholder="John Smith" - bind={[name, setName]} /> + bind={[name, setName]} + /> <TextInput label="IBAN" placeholder="DE91100000000123456789" - bind={[account, setAccount]} /> + bind={[account, setAccount]} + /> </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your bank accounts: - </div><div class="block"> - {configured.map((c, i) => { - return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p> - <div><button class="button is-danger" onClick={c.remove} >Delete</button></div> - </div> - })} - </div></section>} + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your bank accounts:</div> + <div class="block"> + {configured.map((c, i) => { + return ( + <div + key={i} + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > + <p style={{ marginBottom: "auto", marginTop: "auto" }}> + {c.instructions} + </p> + <div> + <button class="button is-danger" onClick={c.remove}> + Delete + </button> + </div> + </div> + ); + })} + </div> + </section> + )} <div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={cancel}> + Cancel + </button> <span data-tooltip={errors}> - <button class="button is-info" disabled={errors !== undefined} onClick={addIbanAuth}>Add</button> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addIbanAuth} + > + Add + </button> </span> </div> </div> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx index df73a9214..cbbc253e9 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx @@ -15,42 +15,46 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/recovery/SolveChallenge/AuthMethods/Iban', + title: "Pages/recovery/SolveChallenge/AuthMethods/Iban", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'iban' - -export const WithoutFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'uuid-1' - }], - policies: [], +const type: KnownAuthMethods = "iban"; + +export const WithoutFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "uuid-1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "uuid-1", + } as ReducerState, + { + id: "uuid-1", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx index 1e4353da6..46cf0502c 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { return ( <AnastasisClientFrame hideNav title="Recovery problem"> <div>invalid state</div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,19 +79,17 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + const shouldHideConfirm = + feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || + feedback?.state === ChallengeFeedbackStatus.Redirect || + feedback?.state === ChallengeFeedbackStatus.Unsupported || + feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( <AnastasisClientFrame hideNav title="Add email authentication"> <SolveOverviewFeedbackDisplay feedback={feedback} /> - <p> - Send a wire transfer to the address - </p> - <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> + <p>Send a wire transfer to the address,</p> + <button class="button">Check</button> <div style={{ @@ -96,9 +101,11 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx index adc83d6fe..2977586ac 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx @@ -16,51 +16,67 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/Post', + title: "Pages/backup/AuthorizationMethod/AuthMethods/Post", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'post' +const type: KnownAuthMethods = "post"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Letter to address in postal code QWE456', - remove: () => null - }] -}); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Letter to address in postal code QWE456", + remove: () => null, + }, + ], + }, +); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Letter to address in postal code QWE456', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Letter to address in postal code ABC123', - remove: () => null - }] -}); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Letter to address in postal code QWE456", + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: "Letter to address in postal code ABC123", + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx index 692421d74..6c8d36bcf 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx @@ -1,6 +1,7 @@ import { - canonicalJson, encodeCrock, - stringToBytes + canonicalJson, + encodeCrock, + stringToBytes, } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -8,7 +9,11 @@ import { AnastasisClientFrame } from ".."; import { TextInput } from "../../../components/fields/TextInput"; import { AuthMethodSetupProps } from "./index"; -export function AuthMethodPostSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { +export function AuthMethodPostSetup({ + addAuthMethod, + cancel, + configured, +}: AuthMethodSetupProps): VNode { const [fullName, setFullName] = useState(""); const [street, setStreet] = useState(""); const [city, setCity] = useState(""); @@ -32,68 +37,83 @@ export function AuthMethodPostSetup({ addAuthMethod, cancel, configured }: AuthM }); }; - const errors = !fullName ? 'The full name is missing' : ( - !street ? 'The street is missing' : ( - !city ? 'The city is missing' : ( - !postcode ? 'The postcode is missing' : ( - !country ? 'The country is missing' : undefined - ) - ) - ) - ) + const errors = !fullName + ? "The full name is missing" + : !street + ? "The street is missing" + : !city + ? "The city is missing" + : !postcode + ? "The postcode is missing" + : !country + ? "The country is missing" + : undefined; return ( <AnastasisClientFrame hideNav title="Add postal authentication"> <p> - For postal letter authentication, you need to provide a postal - address. When recovering your secret, you will be asked to enter a - code that you will receive in a letter to that address. + For postal letter authentication, you need to provide a postal address. + When recovering your secret, you will be asked to enter a code that you + will receive in a letter to that address. </p> <div> - <TextInput - grabFocus - label="Full Name" - bind={[fullName, setFullName]} - /> + <TextInput grabFocus label="Full Name" bind={[fullName, setFullName]} /> </div> <div> - <TextInput - label="Street" - bind={[street, setStreet]} - /> + <TextInput label="Street" bind={[street, setStreet]} /> </div> <div> - <TextInput - label="City" bind={[city, setCity]} - /> + <TextInput label="City" bind={[city, setCity]} /> </div> <div> - <TextInput - label="Postal Code" bind={[postcode, setPostcode]} - /> + <TextInput label="Postal Code" bind={[postcode, setPostcode]} /> </div> <div> - <TextInput - label="Country" - bind={[country, setCountry]} - /> + <TextInput label="Country" bind={[country, setCountry]} /> </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your postal code: - </div><div class="block"> - {configured.map((c, i) => { - return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p> - <div><button class="button is-danger" onClick={c.remove} >Delete</button></div> - </div> - })} - </div> - </section>} - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your postal code:</div> + <div class="block"> + {configured.map((c, i) => { + return ( + <div + key={i} + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > + <p style={{ marginBottom: "auto", marginTop: "auto" }}> + {c.instructions} + </p> + <div> + <button class="button is-danger" onClick={c.remove}> + Delete + </button> + </div> + </div> + ); + })} + </div> + </section> + )} + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={cancel}> + Cancel + </button> <span data-tooltip={errors}> - <button class="button is-info" disabled={errors !== undefined} onClick={addPostAuth}>Add</button> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addPostAuth} + > + Add + </button> </span> </div> </AnastasisClientFrame> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx index 99451090b..3b67ee884 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx @@ -15,42 +15,46 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/recovery/SolveChallenge/AuthMethods/post', + title: "Pages/recovery/SolveChallenge/AuthMethods/post", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'post' - -export const WithoutFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'uuid-1' - }], - policies: [], +const type: KnownAuthMethods = "post"; + +export const WithoutFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "uuid-1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "uuid-1", + } as ReducerState, + { + id: "uuid-1", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx index 7e3c45abe..ee001ebe9 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { return ( <AnastasisClientFrame hideNav title="Recovery problem"> <div>invalid state</div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,16 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + const shouldHideConfirm = + feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || + feedback?.state === ChallengeFeedbackStatus.Redirect || + feedback?.state === ChallengeFeedbackStatus.Unsupported || + feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( <AnastasisClientFrame hideNav title="Add email authentication"> <SolveOverviewFeedbackDisplay feedback={feedback} /> - <p> - Wait for the answer - </p> + <p>Wait for the answer</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <div @@ -96,9 +101,11 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx index 0c3ee2b77..991301cbf 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx @@ -16,51 +16,69 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/Question', + title: "Pages/backup/AuthorizationMethod/AuthMethods/Question", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'question' +const type: KnownAuthMethods = "question"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Is integer factorization polynomial? (non-quantum computer)', - remove: () => null - }] -}); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: + "Is integer factorization polynomial? (non-quantum computer)", + remove: () => null, + }, + ], + }, +); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Does P equal NP?', - remove: () => null - },{ - challenge: 'asd', - type, - instructions: 'Are continuous groups automatically differential groups?', - remove: () => null - }] -}); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Does P equal NP?", + remove: () => null, + }, + { + challenge: "asd", + type, + instructions: + "Are continuous groups automatically differential groups?", + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx index 780bfcb82..0a14021dd 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx @@ -1,27 +1,31 @@ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; +import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AuthMethodSetupProps } from "./index"; import { AnastasisClientFrame } from "../index"; import { TextInput } from "../../../components/fields/TextInput"; -export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { +export function AuthMethodQuestionSetup({ + cancel, + addAuthMethod, + configured, +}: AuthMethodSetupProps): VNode { const [questionText, setQuestionText] = useState(""); const [answerText, setAnswerText] = useState(""); - const addQuestionAuth = (): void => addAuthMethod({ - authentication_method: { - type: "question", - instructions: questionText, - challenge: encodeCrock(stringToBytes(answerText)), - }, - }); + const addQuestionAuth = (): void => + addAuthMethod({ + authentication_method: { + type: "question", + instructions: questionText, + challenge: encodeCrock(stringToBytes(answerText)), + }, + }); - const errors = !questionText ? "Add your security question" : ( - !answerText ? 'Add the answer to your question' : undefined - ) + const errors = !questionText + ? "Add your security question" + : !answerText + ? "Add the answer to your question" + : undefined; return ( <AnastasisClientFrame hideNav title="Add Security Question"> <div> @@ -36,7 +40,8 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A label="Security question" grabFocus placeholder="Your question" - bind={[questionText, setQuestionText]} /> + bind={[questionText, setQuestionText]} + /> </div> <div> <TextInput @@ -46,25 +51,53 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A /> </div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={cancel}> + Cancel + </button> <span data-tooltip={errors}> - <button class="button is-info" disabled={errors !== undefined} onClick={addQuestionAuth}>Add</button> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addQuestionAuth} + > + Add + </button> </span> </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your security questions: - </div><div class="block"> - {configured.map((c, i) => { - return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p> - <div><button class="button is-danger" onClick={c.remove} >Delete</button></div> - </div> - })} - </div></section>} + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your security questions:</div> + <div class="block"> + {configured.map((c, i) => { + return ( + <div + key={i} + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > + <p style={{ marginBottom: "auto", marginTop: "auto" }}> + {c.instructions} + </p> + <div> + <button class="button is-danger" onClick={c.remove}> + Delete + </button> + </div> + </div> + ); + })} + </div> + </section> + )} </div> - </AnastasisClientFrame > + </AnastasisClientFrame> ); } diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx index f0ec92d4d..1fa9fd6ec 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx @@ -15,186 +15,205 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/recovery/SolveChallenge/AuthMethods/question', + title: "Pages/recovery/SolveChallenge/AuthMethods/question", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'question' +const type: KnownAuthMethods = "question"; -export const WithoutFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'uuid-1' - }], - policies: [], +export const WithoutFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "uuid-1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "uuid-1", + } as ReducerState, + { + id: "uuid-1", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); +); export const MessageFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.Message, - message: 'Challenge should be solved' - } - } - -} as ReducerState); - -export const ServerFailureFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], - policies: [], + message: "Challenge should be solved", + }, }, - selected_challenge_uuid: 'ASDASDSAD!1', - challenge_feedback: { - 'ASDASDSAD!1': { - state: ChallengeFeedbackStatus.ServerFailure, - http_status: 500, - error_response: "Couldn't connect to mysql" - } - } - } as ReducerState); +export const ServerFailureFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "ASDASDSAD!1", + challenge_feedback: { + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.ServerFailure, + http_status: 500, + error_response: "Couldn't connect to mysql", + }, + }, + } as ReducerState, +); + export const RedirectFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.Redirect, http_status: 302, - redirect_url: 'http://video.taler.net' - } - } - -} as ReducerState); - -export const MessageRateLimitExceededFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], - policies: [], + redirect_url: "http://video.taler.net", + }, }, - selected_challenge_uuid: 'ASDASDSAD!1', - challenge_feedback: { - 'ASDASDSAD!1': { - state: ChallengeFeedbackStatus.RateLimitExceeded, - } - } - } as ReducerState); +export const MessageRateLimitExceededFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "ASDASDSAD!1", + challenge_feedback: { + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.RateLimitExceeded, + }, + }, + } as ReducerState, +); + export const UnsupportedFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.Unsupported, http_status: 500, - unsupported_method: 'Question' - } - } - + unsupported_method: "Question", + }, + }, } as ReducerState); export const TruthUnknownFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.TruthUnknown, - } - } - + }, + }, } as ReducerState); export const AuthIbanFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.AuthIban, challenge_amount: "EUR:1", credit_iban: "DE12345789000", @@ -210,30 +229,30 @@ export const AuthIbanFeedback = createExample(TestedComponent[type].solve, { wire_transfer_subject: "foo", }, method: "iban", - } - } - + }, + }, } as ReducerState); export const PaymentFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.Payment, - taler_pay_uri : "taler://pay/...", - provider : "https://localhost:8080/", - payment_secret : "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG" - } - } + taler_pay_uri: "taler://pay/...", + provider: "https://localhost:8080/", + payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", + }, + }, } as ReducerState); - diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx index ee1c0028f..222789507 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { return ( <AnastasisClientFrame hideNav title="Recovery problem"> <div>invalid state</div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,16 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + const shouldHideConfirm = + feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || + feedback?.state === ChallengeFeedbackStatus.Redirect || + feedback?.state === ChallengeFeedbackStatus.Unsupported || + feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( <AnastasisClientFrame hideNav title="Add email authentication"> <SolveOverviewFeedbackDisplay feedback={feedback} /> - <p> - Answer the question please - </p> + <p>Answer the question please</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <div @@ -96,9 +101,11 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx index da2087ce1..3a44c7ad0 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx @@ -16,51 +16,67 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/Sms', + title: "Pages/backup/AuthorizationMethod/AuthMethods/Sms", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'sms' +const type: KnownAuthMethods = "sms"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'SMS to +11-1234-2345', - remove: () => null - }] -}); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "SMS to +11-1234-2345", + remove: () => null, + }, + ], + }, +); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'SMS to +11-1234-2345', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'SMS to +11-5555-2345', - remove: () => null - }] -}); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "SMS to +11-1234-2345", + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: "SMS to +11-5555-2345", + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx index 9a0459d78..056b1b175 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx @@ -1,14 +1,15 @@ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; +import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useLayoutEffect, useRef, useState } from "preact/hooks"; import { AuthMethodSetupProps } from "."; import { PhoneNumberInput } from "../../../components/fields/NumberInput"; import { AnastasisClientFrame } from "../index"; -export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { +export function AuthMethodSmsSetup({ + addAuthMethod, + cancel, + configured, +}: AuthMethodSetupProps): VNode { const [mobileNumber, setMobileNumber] = useState(""); const addSmsAuth = (): void => { addAuthMethod({ @@ -23,7 +24,7 @@ export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMe useLayoutEffect(() => { inputRef.current?.focus(); }, []); - const errors = !mobileNumber ? 'Add a mobile number' : undefined + const errors = !mobileNumber ? "Add a mobile number" : undefined; return ( <AnastasisClientFrame hideNav title="Add SMS authentication"> <div> @@ -37,23 +38,52 @@ export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMe label="Mobile number" placeholder="Your mobile number" grabFocus - bind={[mobileNumber, setMobileNumber]} /> + bind={[mobileNumber, setMobileNumber]} + /> </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your mobile numbers: - </div><div class="block"> - {configured.map((c, i) => { - return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <p style={{ marginTop: 'auto', marginBottom: 'auto' }}>{c.instructions}</p> - <div><button class="button is-danger" onClick={c.remove}>Delete</button></div> - </div> - })} - </div></section>} - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your mobile numbers:</div> + <div class="block"> + {configured.map((c, i) => { + return ( + <div + key={i} + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > + <p style={{ marginTop: "auto", marginBottom: "auto" }}> + {c.instructions} + </p> + <div> + <button class="button is-danger" onClick={c.remove}> + Delete + </button> + </div> + </div> + ); + })} + </div> + </section> + )} + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={cancel}> + Cancel + </button> <span data-tooltip={errors}> - <button class="button is-info" disabled={errors !== undefined} onClick={addSmsAuth}>Add</button> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addSmsAuth} + > + Add + </button> </span> </div> </div> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx index 76e769303..3dc3adb2b 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx @@ -15,42 +15,46 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/recovery/SolveChallenge/AuthMethods/sms', + title: "Pages/recovery/SolveChallenge/AuthMethods/sms", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'sms' - -export const WithoutFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'uuid-1' - }], - policies: [], +const type: KnownAuthMethods = "sms"; + +export const WithoutFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "uuid-1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "uuid-1", + } as ReducerState, + { + id: "uuid-1", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx index ce7159bd0..8ee4d600a 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { return ( <AnastasisClientFrame hideNav title="Recovery problem"> <div>invalid state</div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,18 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + const shouldHideConfirm = + feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || + feedback?.state === ChallengeFeedbackStatus.Redirect || + feedback?.state === ChallengeFeedbackStatus.Unsupported || + feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( <AnastasisClientFrame hideNav title="Add email authentication"> <SolveOverviewFeedbackDisplay feedback={feedback} /> <p> - An sms has been sent to "<b>{selectedChallenge.instructions}</b>". Type the code - below + An sms has been sent to "<b>{selectedChallenge.instructions}</b>". Type + the code below </p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> @@ -97,9 +104,11 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx index c0a52924c..bc4628828 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx @@ -16,49 +16,65 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/TOTP', + title: "Pages/backup/AuthorizationMethod/AuthMethods/TOTP", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'totp' +const type: KnownAuthMethods = "totp"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Enter 8 digits code for "Anastasis"', - remove: () => null - }] -}); -export const WithMoreExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Enter 8 digits code for "Anastasis1"', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Enter 8 digits code for "Anastasis2"', - remove: () => null - }] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: 'Enter 8 digits code for "Anastasis"', + remove: () => null, + }, + ], + }, +); +export const WithMoreExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: 'Enter 8 digits code for "Anastasis1"', + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: 'Enter 8 digits code for "Anastasis2"', + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx index a8ac499b2..1451aadc8 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx @@ -1,7 +1,4 @@ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; +import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useMemo, useState } from "preact/hooks"; import { AuthMethodSetupProps } from "./index"; @@ -10,30 +7,37 @@ import { TextInput } from "../../../components/fields/TextInput"; import { QR } from "../../../components/QR"; import { base32enc, computeTOTPandCheck } from "./totp"; -export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { +export function AuthMethodTotpSetup({ + addAuthMethod, + cancel, + configured, +}: AuthMethodSetupProps): VNode { const [name, setName] = useState("anastasis"); const [test, setTest] = useState(""); - const digits = 8 + const digits = 8; const secretKey = useMemo(() => { - const array = new Uint8Array(32) - return window.crypto.getRandomValues(array) - }, []) + const array = new Uint8Array(32); + return window.crypto.getRandomValues(array); + }, []); const secret32 = base32enc(secretKey); - const totpURL = `otpauth://totp/${name}?digits=${digits}&secret=${secret32}` + const totpURL = `otpauth://totp/${name}?digits=${digits}&secret=${secret32}`; - const addTotpAuth = (): void => addAuthMethod({ - authentication_method: { - type: "totp", - instructions: `Enter ${digits} digits code for "${name}"`, - challenge: encodeCrock(stringToBytes(totpURL)), - }, - }); + const addTotpAuth = (): void => + addAuthMethod({ + authentication_method: { + type: "totp", + instructions: `Enter ${digits} digits code for "${name}"`, + challenge: encodeCrock(stringToBytes(totpURL)), + }, + }); const testCodeMatches = computeTOTPandCheck(secretKey, 8, parseInt(test, 10)); - const errors = !name ? 'The TOTP name is missing' : ( - !testCodeMatches ? 'The test code doesnt match' : undefined - ); + const errors = !name + ? "The TOTP name is missing" + : !testCodeMatches + ? "The test code doesnt match" + : undefined; return ( <AnastasisClientFrame hideNav title="Add TOTP authentication"> <p> @@ -42,10 +46,7 @@ export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthM with your TOTP App to import the TOTP secret into your TOTP App. </p> <div class="block"> - <TextInput - label="TOTP Name" - grabFocus - bind={[name, setName]} /> + <TextInput label="TOTP Name" grabFocus bind={[name, setName]} /> </div> <div style={{ height: 300 }}> <QR text={totpURL} /> @@ -53,25 +54,51 @@ export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthM <p> After scanning the code with your TOTP App, test it in the input below. </p> - <TextInput - label="Test code" - bind={[test, setTest]} /> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your TOTP numbers: - </div><div class="block"> - {configured.map((c, i) => { - return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <p style={{ marginTop: 'auto', marginBottom: 'auto' }}>{c.instructions}</p> - <div><button class="button is-danger" onClick={c.remove}>Delete</button></div> - </div> - })} - </div></section>} + <TextInput label="Test code" bind={[test, setTest]} /> + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your TOTP numbers:</div> + <div class="block"> + {configured.map((c, i) => { + return ( + <div + key={i} + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > + <p style={{ marginTop: "auto", marginBottom: "auto" }}> + {c.instructions} + </p> + <div> + <button class="button is-danger" onClick={c.remove}> + Delete + </button> + </div> + </div> + ); + })} + </div> + </section> + )} <div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={cancel}> + Cancel + </button> <span data-tooltip={errors}> - <button class="button is-info" disabled={errors !== undefined} onClick={addTotpAuth}>Add</button> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addTotpAuth} + > + Add + </button> </span> </div> </div> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx index a301931b2..8743c5a73 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx @@ -15,42 +15,46 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/recovery/SolveChallenge/AuthMethods/totp', + title: "Pages/recovery/SolveChallenge/AuthMethods/totp", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'totp' - -export const WithoutFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'uuid-1' - }], - policies: [], +const type: KnownAuthMethods = "totp"; + +export const WithoutFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "uuid-1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "uuid-1", + } as ReducerState, + { + id: "uuid-1", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx index 30fc44f0e..98c2e51df 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { return ( <AnastasisClientFrame hideNav title="Recovery problem"> <div>invalid state</div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,16 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + const shouldHideConfirm = + feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || + feedback?.state === ChallengeFeedbackStatus.Redirect || + feedback?.state === ChallengeFeedbackStatus.Unsupported || + feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( <AnastasisClientFrame hideNav title="Add email authentication"> <SolveOverviewFeedbackDisplay feedback={feedback} /> - <p> - enter the totp solution - </p> + <p>enter the totp solution</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <div @@ -96,9 +101,11 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx index 52e897c60..4aad0a097 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx @@ -16,51 +16,68 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; -import logoImage from '../../../assets/logo.jpeg' +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; +import logoImage from "../../../assets/logo.jpeg"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/Video', + title: "Pages/backup/AuthorizationMethod/AuthMethods/Video", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'video' +const type: KnownAuthMethods = "video"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: logoImage, - remove: () => null - }] -}); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: logoImage, + remove: () => null, + }, + ], + }, +); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: logoImage, - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: logoImage, - remove: () => null - }] -}); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: logoImage, + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: logoImage, + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx index 22abe4a49..672b23500 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx @@ -1,53 +1,86 @@ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; +import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { ImageInput } from "../../../components/fields/ImageInput"; import { AuthMethodSetupProps } from "./index"; import { AnastasisClientFrame } from "../index"; -export function AuthMethodVideoSetup({cancel, addAuthMethod, configured}: AuthMethodSetupProps): VNode { +export function AuthMethodVideoSetup({ + cancel, + addAuthMethod, + configured, +}: AuthMethodSetupProps): VNode { const [image, setImage] = useState(""); const addVideoAuth = (): void => { addAuthMethod({ authentication_method: { type: "video", - instructions: 'Join a video call', + instructions: "Join a video call", challenge: encodeCrock(stringToBytes(image)), }, - }) + }); }; return ( <AnastasisClientFrame hideNav title="Add video authentication"> <p> - For video identification, you need to provide a passport-style - photograph. When recovering your secret, you will be asked to join a - video call. During that call, a human will use the photograph to - verify your identity. + For video identification, you need to provide a passport-style + photograph. When recovering your secret, you will be asked to join a + video call. During that call, a human will use the photograph to verify + your identity. </p> - <div style={{textAlign:'center'}}> + <div style={{ textAlign: "center" }}> <ImageInput label="Choose photograph" grabFocus - bind={[image, setImage]} /> + bind={[image, setImage]} + /> </div> - {configured.length > 0 && <section class="section"> + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your photographs:</div> <div class="block"> - Your photographs: - </div><div class="block"> {configured.map((c, i) => { - return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <img style={{ marginTop: 'auto', marginBottom: 'auto', width: 100, height:100, border: 'solid 1px black' }} src={c.instructions} /> - <div style={{marginTop: 'auto', marginBottom: 'auto'}}><button class="button is-danger" onClick={c.remove}>Delete</button></div> - </div> + return ( + <div + key={i} + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > + <img + style={{ + marginTop: "auto", + marginBottom: "auto", + width: 100, + height: 100, + border: "solid 1px black", + }} + src={c.instructions} + /> + <div style={{ marginTop: "auto", marginBottom: "auto" }}> + <button class="button is-danger" onClick={c.remove}> + Delete + </button> + </div> + </div> + ); })} - </div></section>} + </div> + </section> + )} <div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> - <button class="button is-info" onClick={addVideoAuth}>Add</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={cancel}> + Cancel + </button> + <button class="button is-info" onClick={addVideoAuth}> + Add + </button> </div> </div> </AnastasisClientFrame> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx index 5c4976b87..7c5511c5a 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx @@ -15,42 +15,46 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/recovery/SolveChallenge/AuthMethods/video', + title: "Pages/recovery/SolveChallenge/AuthMethods/video", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'video' - -export const WithoutFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'uuid-1' - }], - policies: [], +const type: KnownAuthMethods = "video"; + +export const WithoutFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "uuid-1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "uuid-1", + } as ReducerState, + { + id: "uuid-1", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx index 79401028a..efadb9a9a 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode { return ( <AnastasisClientFrame hideNav title="Recovery problem"> <div>invalid state</div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,16 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + const shouldHideConfirm = + feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded || + feedback?.state === ChallengeFeedbackStatus.Redirect || + feedback?.state === ChallengeFeedbackStatus.Unsupported || + feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( <AnastasisClientFrame hideNav title="Add email authentication"> <SolveOverviewFeedbackDisplay feedback={feedback} /> - <p> - You are gonna be called to check your identity - </p> + <p>You are gonna be called to check your identity</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <div @@ -96,9 +101,11 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx index 8b0126ce7..b4f649488 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx @@ -1,9 +1,9 @@ import { AuthMethod } from "anastasis-core"; import { h, VNode } from "preact"; -import postalIcon from '../../../assets/icons/auth_method/postal.svg'; -import questionIcon from '../../../assets/icons/auth_method/question.svg'; -import smsIcon from '../../../assets/icons/auth_method/sms.svg'; -import videoIcon from '../../../assets/icons/auth_method/video.svg'; +import postalIcon from "../../../assets/icons/auth_method/postal.svg"; +import questionIcon from "../../../assets/icons/auth_method/question.svg"; +import smsIcon from "../../../assets/icons/auth_method/sms.svg"; +import videoIcon from "../../../assets/icons/auth_method/video.svg"; import { AuthMethodEmailSetup as EmailSetup } from "./AuthMethodEmailSetup"; import { AuthMethodEmailSolve as EmailSolve } from "./AuthMethodEmailSolve"; import { AuthMethodIbanSetup as IbanSetup } from "./AuthMethodIbanSetup"; @@ -20,8 +20,7 @@ import { AuthMethodSmsSolve as SmsSolve } from "./AuthMethodSmsSolve"; import { AuthMethodTotpSolve as TotpSolve } from "./AuthMethodTotpSolve"; import { AuthMethodVideoSolve as VideoSolve } from "./AuthMethodVideoSolve"; - -export type AuthMethodWithRemove = AuthMethod & { remove: () => void } +export type AuthMethodWithRemove = AuthMethod & { remove: () => void }; export interface AuthMethodSetupProps { method: string; @@ -43,10 +42,18 @@ interface AuthMethodConfiguration { } // export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban"; -const ALL_METHODS = ['sms', 'email', 'post', 'question', 'video' , 'totp', 'iban'] as const; -export type KnownAuthMethods = (typeof ALL_METHODS)[number]; +const ALL_METHODS = [ + "sms", + "email", + "post", + "question", + "video", + "totp", + "iban", +] as const; +export type KnownAuthMethods = typeof ALL_METHODS[number]; export function isKnownAuthMethods(value: string): value is KnownAuthMethods { - return ALL_METHODS.includes(value as KnownAuthMethods) + return ALL_METHODS.includes(value as KnownAuthMethods); } type KnowMethodConfig = { @@ -96,5 +103,5 @@ export const authMethods: KnowMethodConfig = { setup: VideoSetup, solve: VideoSolve, skip: true, - } -}
\ No newline at end of file + }, +}; diff --git a/packages/anastasis-webui/src/pages/home/authMethod/totp.ts b/packages/anastasis-webui/src/pages/home/authMethod/totp.ts index 0bc3feaf8..c2288671c 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/totp.ts +++ b/packages/anastasis-webui/src/pages/home/authMethod/totp.ts @@ -1,54 +1,61 @@ /* eslint-disable @typescript-eslint/camelcase */ -import jssha from 'jssha' +import jssha from "jssha"; -const SEARCH_RANGE = 16 -const timeStep = 30 +const SEARCH_RANGE = 16; +const timeStep = 30; -export function computeTOTPandCheck(secretKey: Uint8Array, digits: number, code: number): boolean { - const now = new Date().getTime() +export function computeTOTPandCheck( + secretKey: Uint8Array, + digits: number, + code: number, +): boolean { + const now = new Date().getTime(); const epoch = Math.floor(Math.round(now / 1000.0) / timeStep); for (let ms = -SEARCH_RANGE; ms < SEARCH_RANGE; ms++) { const movingFactor = (epoch + ms).toString(16).padStart(16, "0"); - const hmacSha = new jssha('SHA-1', 'HEX', { hmacKey: { value: secretKey, format: 'UINT8ARRAY' } }); + const hmacSha = new jssha("SHA-1", "HEX", { + hmacKey: { value: secretKey, format: "UINT8ARRAY" }, + }); hmacSha.update(movingFactor); - const hmac_text = hmacSha.getHMAC('UINT8ARRAY'); + const hmac_text = hmacSha.getHMAC("UINT8ARRAY"); - const offset = (hmac_text[hmac_text.length - 1] & 0xf) + const offset = hmac_text[hmac_text.length - 1] & 0xf; - const otp = (( - (hmac_text[offset + 0] << 24) + - (hmac_text[offset + 1] << 16) + - (hmac_text[offset + 2] << 8) + - (hmac_text[offset + 3]) - ) & 0x7fffffff) % Math.pow(10, digits) + const otp = + (((hmac_text[offset + 0] << 24) + + (hmac_text[offset + 1] << 16) + + (hmac_text[offset + 2] << 8) + + hmac_text[offset + 3]) & + 0x7fffffff) % + Math.pow(10, digits); - if (otp == code) return true + if (otp == code) return true; } - return false + return false; } -const encTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".split('') +const encTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".split(""); export function base32enc(buffer: Uint8Array): string { - let rpos = 0 - let bits = 0 - let vbit = 0 + let rpos = 0; + let bits = 0; + let vbit = 0; - let result = "" - while ((rpos < buffer.length) || (vbit > 0)) { - if ((rpos < buffer.length) && (vbit < 5)) { + let result = ""; + while (rpos < buffer.length || vbit > 0) { + if (rpos < buffer.length && vbit < 5) { bits = (bits << 8) | buffer[rpos++]; vbit += 8; } if (vbit < 5) { - bits <<= (5 - vbit); + bits <<= 5 - vbit; vbit = 5; } result += encTable__[(bits >> (vbit - 5)) & 31]; vbit -= 5; } - return result + return result; } // const array = new Uint8Array(256) diff --git a/packages/anastasis-webui/src/pages/notfound/index.tsx b/packages/anastasis-webui/src/pages/notfound/index.tsx index 4e74d1d9f..bb22429b0 100644 --- a/packages/anastasis-webui/src/pages/notfound/index.tsx +++ b/packages/anastasis-webui/src/pages/notfound/index.tsx @@ -1,16 +1,16 @@ -import { FunctionalComponent, h } from 'preact'; -import { Link } from 'preact-router/match'; +import { FunctionalComponent, h } from "preact"; +import { Link } from "preact-router/match"; const Notfound: FunctionalComponent = () => { - return ( - <div> - <h1>Error 404</h1> - <p>That page doesn't exist.</p> - <Link href="/"> - <h4>Back to Home</h4> - </Link> - </div> - ); + return ( + <div> + <h1>Error 404</h1> + <p>That page doesn't exist.</p> + <Link href="/"> + <h4>Back to Home</h4> + </Link> + </div> + ); }; export default Notfound; diff --git a/packages/anastasis-webui/src/pages/profile/index.tsx b/packages/anastasis-webui/src/pages/profile/index.tsx index 859a83ed4..bcd26370e 100644 --- a/packages/anastasis-webui/src/pages/profile/index.tsx +++ b/packages/anastasis-webui/src/pages/profile/index.tsx @@ -1,43 +1,42 @@ -import { FunctionalComponent, h } from 'preact'; -import { useEffect, useState } from 'preact/hooks'; +import { FunctionalComponent, h } from "preact"; +import { useEffect, useState } from "preact/hooks"; interface Props { - user: string; + user: string; } const Profile: FunctionalComponent<Props> = (props: Props) => { - const { user } = props; - const [time, setTime] = useState<number>(Date.now()); - const [count, setCount] = useState<number>(0); - - // gets called when this route is navigated to - useEffect(() => { - const timer = window.setInterval(() => setTime(Date.now()), 1000); - - // gets called just before navigating away from the route - return (): void => { - clearInterval(timer); - }; - }, []); - - // update the current time - const increment = (): void => { - setCount(count + 1); + const { user } = props; + const [time, setTime] = useState<number>(Date.now()); + const [count, setCount] = useState<number>(0); + + // gets called when this route is navigated to + useEffect(() => { + const timer = window.setInterval(() => setTime(Date.now()), 1000); + + // gets called just before navigating away from the route + return (): void => { + clearInterval(timer); }; + }, []); + + // update the current time + const increment = (): void => { + setCount(count + 1); + }; - return ( - <div> - <h1>Profile: {user}</h1> - <p>This is the user profile for a user named {user}.</p> + return ( + <div> + <h1>Profile: {user}</h1> + <p>This is the user profile for a user named {user}.</p> - <div>Current time: {new Date(time).toLocaleString()}</div> + <div>Current time: {new Date(time).toLocaleString()}</div> - <p> - <button onClick={increment}>Click Me</button> Clicked {count}{' '} - times. - </p> - </div> - ); + <p> + <button onClick={increment}>Click Me</button> Clicked {count} times. + </p> + </div> + ); }; export default Profile; diff --git a/packages/anastasis-webui/src/template.html b/packages/anastasis-webui/src/template.html index 351f1829c..2a216916e 100644 --- a/packages/anastasis-webui/src/template.html +++ b/packages/anastasis-webui/src/template.html @@ -1,15 +1,56 @@ +<!-- + This file is part of GNU Taler + (C) 2021 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="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"> - <head> - <meta charset="utf-8"> - <title><% preact.title %></title> - <meta name="viewport" content="width=device-width,initial-scale=1"> - <meta name="mobile-web-app-capable" content="yes"> - <meta name="apple-mobile-web-app-capable" content="yes"> - <link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png"> - <% preact.headEnd %> - </head> - <body> - <% preact.bodyEnd %> - </body> +<html + lang="en" + class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded" +> + <head> + <meta charset="utf-8" /> + <title><%= htmlWebpackPlugin.options.title %></title> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <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" /> + + <% if (htmlWebpackPlugin.options.manifest.theme_color) { %> + <meta + name="theme-color" + content="<%= htmlWebpackPlugin.options.manifest.theme_color %>" + /> + <% } %> <% for (const index in htmlWebpackPlugin.files.css) { %> <% const + file = htmlWebpackPlugin.files.css[index] %> + <style data-href="<%= file %>"> + <%= compilation.assets[file.substr(htmlWebpackPlugin.files.publicPath.length)].source() %> + </style> + <% } %> + </head> + <body> + <script> + <%= compilation.assets[htmlWebpackPlugin.files.chunks["polyfills"].entry.substr(htmlWebpackPlugin.files.publicPath.length)].source() %> + </script> + <script> + <%= compilation.assets[htmlWebpackPlugin.files.chunks["bundle"].entry.substr(htmlWebpackPlugin.files.publicPath.length)].source() %> + </script> + </body> </html> diff --git a/packages/anastasis-webui/src/utils/index.tsx b/packages/anastasis-webui/src/utils/index.tsx index 9c01aa6ba..04cc8c921 100644 --- a/packages/anastasis-webui/src/utils/index.tsx +++ b/packages/anastasis-webui/src/utils/index.tsx @@ -1,45 +1,67 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { BackupStates, RecoveryStates, ReducerState } from 'anastasis-core'; -import { FunctionalComponent, h, VNode } from 'preact'; -import { AnastasisProvider } from '../context/anastasis'; +import { BackupStates, RecoveryStates, ReducerState } from "anastasis-core"; +import { FunctionalComponent, h, VNode } from "preact"; +import { AnastasisProvider } from "../context/anastasis"; -export function createExample<Props>(Component: FunctionalComponent<Props>, currentReducerState?: ReducerState, props?: Partial<Props>): { (args: Props): VNode } { +export function createExample<Props>( + Component: FunctionalComponent<Props>, + currentReducerState?: ReducerState, + props?: Partial<Props>, +): { (args: Props): VNode } { const r = (args: Props): VNode => { - return <AnastasisProvider value={{ - currentReducerState, - currentError: undefined, - back: async () => { null }, - dismissError: async () => { null }, - reset: () => { null }, - runTransaction: async () => { null }, - startBackup: () => { null }, - startRecover: () => { null }, - transition: async () => { null }, - }}> - <Component {...args} /> - </AnastasisProvider> - } - r.args = props - return r + return ( + <AnastasisProvider + value={{ + currentReducerState, + currentError: undefined, + back: async () => { + null; + }, + dismissError: async () => { + null; + }, + reset: () => { + null; + }, + runTransaction: async () => { + null; + }, + startBackup: () => { + null; + }, + startRecover: () => { + null; + }, + transition: async () => { + null; + }, + }} + > + <Component {...args} /> + </AnastasisProvider> + ); + }; + r.args = props; + return r; } const base = { continents: [ { - name: "Europe" + name: "Europe", }, { - name: "India" + name: "India", }, { - name: "Asia" + name: "Asia", }, { - name: "North America" + name: "North America", }, { - name: "Testcontinent" - } + name: "Testcontinent", + }, ], countries: [ { @@ -47,33 +69,33 @@ const base = { name: "Testland", continent: "Testcontinent", continent_i18n: { - de_DE: "Testkontinent" + de_DE: "Testkontinent", }, name_i18n: { de_DE: "Testlandt", de_CH: "Testlandi", fr_FR: "Testpais", - en_UK: "Testland" + en_UK: "Testland", }, currency: "TESTKUDOS", - call_code: "+00" + call_code: "+00", }, { code: "xy", name: "Demoland", continent: "Testcontinent", continent_i18n: { - de_DE: "Testkontinent" + de_DE: "Testkontinent", }, name_i18n: { de_DE: "Demolandt", de_CH: "Demolandi", fr_FR: "Demopais", - en_UK: "Demoland" + en_UK: "Demoland", }, currency: "KUDOS", - call_code: "+01" - } + call_code: "+01", + }, ], authentication_providers: { "http://localhost:8086/": { @@ -85,18 +107,20 @@ const base = { methods: [ { type: "question", - usage_fee: "COL:0" - }, { + usage_fee: "COL:0", + }, + { type: "sms", - usage_fee: "COL:0" - }, { + usage_fee: "COL:0", + }, + { type: "email", - usage_fee: "COL:0" + usage_fee: "COL:0", }, ], salt: "WBMDD76BR1E90YQ5AHBMKPH7GW", storage_limit_in_megabytes: 16, - truth_upload_fee: "COL:0" + truth_upload_fee: "COL:0", }, "https://kudos.demo.anastasis.lu/": { http_status: 200, @@ -107,15 +131,16 @@ const base = { methods: [ { type: "question", - usage_fee: "COL:0" - }, { + usage_fee: "COL:0", + }, + { type: "email", - usage_fee: "COL:0" + usage_fee: "COL:0", }, ], salt: "WBMDD76BR1E90YQ5AHBMKPH7GW", storage_limit_in_megabytes: 16, - truth_upload_fee: "COL:0" + truth_upload_fee: "COL:0", }, "https://anastasis.demo.taler.net/": { http_status: 200, @@ -126,43 +151,45 @@ const base = { methods: [ { type: "question", - usage_fee: "COL:0" - }, { + usage_fee: "COL:0", + }, + { type: "sms", - usage_fee: "COL:0" - }, { + usage_fee: "COL:0", + }, + { type: "totp", - usage_fee: "COL:0" + usage_fee: "COL:0", }, ], salt: "WBMDD76BR1E90YQ5AHBMKPH7GW", storage_limit_in_megabytes: 16, - truth_upload_fee: "COL:0" + truth_upload_fee: "COL:0", }, "http://localhost:8087/": { code: 8414, - hint: "request to provider failed" + hint: "request to provider failed", }, "http://localhost:8088/": { code: 8414, - hint: "request to provider failed" + hint: "request to provider failed", }, "http://localhost:8089/": { code: 8414, - hint: "request to provider failed" - } + hint: "request to provider failed", + }, }, // expiration: { // d_ms: 1792525051855 // check t_ms // }, -} as Partial<ReducerState> +} as Partial<ReducerState>; export const reducerStatesExample = { initial: undefined, recoverySelectCountry: { ...base, - recovery_state: RecoveryStates.CountrySelecting + recovery_state: RecoveryStates.CountrySelecting, } as ReducerState, recoverySelectContinent: { ...base, @@ -190,11 +217,11 @@ export const reducerStatesExample = { } as ReducerState, recoveryAttributeEditing: { ...base, - recovery_state: RecoveryStates.UserAttributesCollecting + recovery_state: RecoveryStates.UserAttributesCollecting, } as ReducerState, backupSelectCountry: { ...base, - backup_state: BackupStates.CountrySelecting + backup_state: BackupStates.CountrySelecting, } as ReducerState, backupSelectContinent: { ...base, @@ -218,15 +245,14 @@ export const reducerStatesExample = { } as ReducerState, authEditing: { ...base, - backup_state: BackupStates.AuthenticationsEditing + backup_state: BackupStates.AuthenticationsEditing, } as ReducerState, backupAttributeEditing: { ...base, - backup_state: BackupStates.UserAttributesCollecting + backup_state: BackupStates.UserAttributesCollecting, } as ReducerState, truthsPaying: { ...base, - backup_state: BackupStates.TruthsPaying + backup_state: BackupStates.TruthsPaying, } as ReducerState, - -} +}; |