prettier
This commit is contained in:
parent
e03b0d1b9b
commit
a62deeef5d
@ -10,6 +10,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@linaria/esbuild": "^3.0.0-beta.13",
|
"@linaria/esbuild": "^3.0.0-beta.13",
|
||||||
"@linaria/shaker": "^3.0.0-beta.13",
|
"@linaria/shaker": "^3.0.0-beta.13",
|
||||||
"esbuild": "^0.12.29"
|
"esbuild": "^0.12.29",
|
||||||
|
"prettier": "^2.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,14 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "preact build --no-sw --no-esm",
|
"build": "preact build --no-sw --no-esm",
|
||||||
"serve": "sirv build --port 8080 --cors --single",
|
"serve": "sirv build --port ${PORT:=8080} --cors --single",
|
||||||
"dev": "preact watch --no-sw --no-esm",
|
"dev": "preact watch --port ${PORT:=8080} --no-sw --no-esm",
|
||||||
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
|
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
|
||||||
"test": "jest ./tests",
|
"test": "jest ./tests",
|
||||||
"build-storybook": "build-storybook",
|
"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"
|
"storybook": "start-storybook -p 6006"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
@ -25,6 +28,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gnu-taler/taler-util": "workspace:^0.8.3",
|
"@gnu-taler/taler-util": "workspace:^0.8.3",
|
||||||
"anastasis-core": "workspace:^0.0.1",
|
"anastasis-core": "workspace:^0.0.1",
|
||||||
|
"base64-inline-loader": "1.1.1",
|
||||||
"date-fns": "2.25.0",
|
"date-fns": "2.25.0",
|
||||||
"jed": "1.1.1",
|
"jed": "1.1.1",
|
||||||
"preact": "^10.5.15",
|
"preact": "^10.5.15",
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
{
|
{
|
||||||
"presets": [
|
"presets": ["preact-cli/babel"]
|
||||||
"preact-cli/babel"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentChildren, h, VNode } from "preact";
|
import { ComponentChildren, h, VNode } from "preact";
|
||||||
// import { LoadingModal } from "../modal";
|
// import { LoadingModal } from "../modal";
|
||||||
@ -31,7 +31,12 @@ type Props = {
|
|||||||
[rest: string]: any;
|
[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);
|
const { isLoading, request } = useAsync(onClick);
|
||||||
|
|
||||||
// if (isSlow) {
|
// if (isSlow) {
|
||||||
@ -41,9 +46,11 @@ export function AsyncButton({ onClick, disabled, children, ...rest }: Props): VN
|
|||||||
return <button class="button">Loading...</button>;
|
return <button class="button">Loading...</button>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <span data-tooltip={rest['data-tooltip']} style={{marginLeft: 5}}>
|
return (
|
||||||
|
<span data-tooltip={rest["data-tooltip"]} style={{ marginLeft: 5 }}>
|
||||||
<button {...rest} onClick={request} disabled={disabled}>
|
<button {...rest} onClick={request} disabled={disabled}>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
</span>;
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ export interface Notification {
|
|||||||
type: MessageType;
|
type: MessageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageType = 'INFO' | 'WARN' | 'ERROR' | 'SUCCESS'
|
export type MessageType = "INFO" | "WARN" | "ERROR" | "SUCCESS";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
notifications: Notification[];
|
notifications: Notification[];
|
||||||
@ -36,24 +36,39 @@ interface Props {
|
|||||||
|
|
||||||
function messageStyle(type: MessageType): string {
|
function messageStyle(type: MessageType): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "INFO": return "message is-info";
|
case "INFO":
|
||||||
case "WARN": return "message is-warning";
|
return "message is-info";
|
||||||
case "ERROR": return "message is-danger";
|
case "WARN":
|
||||||
case "SUCCESS": return "message is-success";
|
return "message is-warning";
|
||||||
default: return "message"
|
case "ERROR":
|
||||||
|
return "message is-danger";
|
||||||
|
case "SUCCESS":
|
||||||
|
return "message is-success";
|
||||||
|
default:
|
||||||
|
return "message";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Notifications({ notifications, removeNotification }: Props): VNode {
|
export function Notifications({
|
||||||
return <div class="block">
|
notifications,
|
||||||
{notifications.map((n, i) => <article key={i} class={messageStyle(n.type)}>
|
removeNotification,
|
||||||
|
}: Props): VNode {
|
||||||
|
return (
|
||||||
|
<div class="block">
|
||||||
|
{notifications.map((n, i) => (
|
||||||
|
<article key={i} class={messageStyle(n.type)}>
|
||||||
<div class="message-header">
|
<div class="message-header">
|
||||||
<p>{n.message}</p>
|
<p>{n.message}</p>
|
||||||
{removeNotification && <button class="delete" onClick={() => removeNotification && removeNotification(n)} />}
|
{removeNotification && (
|
||||||
|
<button
|
||||||
|
class="delete"
|
||||||
|
onClick={() => removeNotification && removeNotification(n)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{n.description && <div class="message-body">
|
{n.description && <div class="message-body">{n.description}</div>}
|
||||||
{n.description}
|
</article>
|
||||||
</div>}
|
))}
|
||||||
</article>)}
|
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
@ -21,15 +21,28 @@ import qrcode from "qrcode-generator";
|
|||||||
export function QR({ text }: { text: string }): VNode {
|
export function QR({ text }: { text: string }): VNode {
|
||||||
const divRef = useRef<HTMLDivElement>(null);
|
const divRef = useRef<HTMLDivElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const qr = qrcode(0, 'L');
|
const qr = qrcode(0, "L");
|
||||||
qr.addData(text);
|
qr.addData(text);
|
||||||
qr.make();
|
qr.make();
|
||||||
if (divRef.current) divRef.current.innerHTML = qr.createSvgTag({
|
if (divRef.current)
|
||||||
|
divRef.current.innerHTML = qr.createSvgTag({
|
||||||
scalable: true,
|
scalable: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
return (
|
||||||
<div style={{ width: '50%', minWidth: 200, maxWidth: 300 }} ref={divRef} />
|
<div
|
||||||
</div>;
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{ width: "50%", minWidth: 200, maxWidth: 300 }}
|
||||||
|
ref={divRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { FunctionalComponent, h } from "preact";
|
import { FunctionalComponent, h } from "preact";
|
||||||
import { TranslationProvider } from "../context/translation";
|
import { TranslationProvider } from "../context/translation";
|
||||||
|
|
||||||
import AnastasisClient from "../pages/home";
|
import AnastasisClient from "../pages/home";
|
||||||
|
|
||||||
const App: FunctionalComponent = () => {
|
const App: FunctionalComponent = () => {
|
||||||
|
@ -19,38 +19,49 @@ export function DateInput(props: DateInputProps): VNode {
|
|||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
}
|
}
|
||||||
}, [props.grabFocus]);
|
}, [props.grabFocus]);
|
||||||
const [opened, setOpened] = useState(false)
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
const value = props.bind[0] || "";
|
const value = props.bind[0] || "";
|
||||||
const [dirty, setDirty] = useState(false)
|
const [dirty, setDirty] = useState(false);
|
||||||
const showError = dirty && props.error
|
const showError = dirty && props.error;
|
||||||
|
|
||||||
const calendar = subYears(new Date(), 30)
|
const calendar = subYears(new Date(), 30);
|
||||||
|
|
||||||
return <div class="field">
|
return (
|
||||||
|
<div class="field">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
{props.label}
|
{props.label}
|
||||||
{props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
{props.tooltip && (
|
||||||
|
<span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
||||||
<i class="mdi mdi-information" />
|
<i class="mdi mdi-information" />
|
||||||
</span>}
|
</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class={showError ? 'input is-danger' : 'input'}
|
class={showError ? "input is-danger" : "input"}
|
||||||
value={value}
|
value={value}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
const text = e.currentTarget.value
|
const text = e.currentTarget.value;
|
||||||
setDirty(true)
|
setDirty(true);
|
||||||
props.bind[1](text);
|
props.bind[1](text);
|
||||||
}}
|
}}
|
||||||
ref={inputRef} />
|
ref={inputRef}
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<a class="button" onClick={() => { setOpened(true) }}>
|
<a
|
||||||
<span class="icon"><i class="mdi mdi-calendar" /></span>
|
class="button"
|
||||||
|
onClick={() => {
|
||||||
|
setOpened(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<i class="mdi mdi-calendar" />
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -63,12 +74,11 @@ export function DateInput(props: DateInputProps): VNode {
|
|||||||
years={props.years}
|
years={props.years}
|
||||||
closeFunction={() => setOpened(false)}
|
closeFunction={() => setOpened(false)}
|
||||||
dateReceiver={(d) => {
|
dateReceiver={(d) => {
|
||||||
setDirty(true)
|
setDirty(true);
|
||||||
const v = format(d, 'yyyy-MM-dd')
|
const v = format(d, "yyyy-MM-dd");
|
||||||
props.bind[1](v);
|
props.bind[1](v);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
;
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,17 @@ export function EmailInput(props: TextInputProps): VNode {
|
|||||||
}
|
}
|
||||||
}, [props.grabFocus]);
|
}, [props.grabFocus]);
|
||||||
const value = props.bind[0];
|
const value = props.bind[0];
|
||||||
const [dirty, setDirty] = useState(false)
|
const [dirty, setDirty] = useState(false);
|
||||||
const showError = dirty && props.error
|
const showError = dirty && props.error;
|
||||||
return (<div class="field">
|
return (
|
||||||
|
<div class="field">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
{props.label}
|
{props.label}
|
||||||
{props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
{props.tooltip && (
|
||||||
|
<span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
||||||
<i class="mdi mdi-information" />
|
<i class="mdi mdi-information" />
|
||||||
</span>}
|
</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<div class="control has-icons-right">
|
<div class="control has-icons-right">
|
||||||
<input
|
<input
|
||||||
@ -33,10 +36,14 @@ export function EmailInput(props: TextInputProps): VNode {
|
|||||||
required
|
required
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
type="email"
|
type="email"
|
||||||
class={showError ? 'input is-danger' : 'input'}
|
class={showError ? "input is-danger" : "input"}
|
||||||
onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}}
|
onInput={(e) => {
|
||||||
|
setDirty(true);
|
||||||
|
props.bind[1]((e.target as HTMLInputElement).value);
|
||||||
|
}}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
style={{ display: "block" }} />
|
style={{ display: "block" }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{showError && <p class="help is-danger">{props.error}</p>}
|
{showError && <p class="help is-danger">{props.error}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,14 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||||
import { TextInputProps } from "./TextInput";
|
import { TextInputProps } from "./TextInput";
|
||||||
|
|
||||||
const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024
|
const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024;
|
||||||
|
|
||||||
export function FileInput(props: TextInputProps): VNode {
|
export function FileInput(props: TextInputProps): VNode {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -34,48 +34,54 @@ export function FileInput(props: TextInputProps): VNode {
|
|||||||
|
|
||||||
const value = props.bind[0];
|
const value = props.bind[0];
|
||||||
// const [dirty, setDirty] = useState(false)
|
// const [dirty, setDirty] = useState(false)
|
||||||
const image = useRef<HTMLInputElement>(null)
|
const image = useRef<HTMLInputElement>(null);
|
||||||
const [sizeError, setSizeError] = useState(false)
|
const [sizeError, setSizeError] = useState(false);
|
||||||
function onChange(v: string): void {
|
function onChange(v: string): void {
|
||||||
// setDirty(true);
|
// setDirty(true);
|
||||||
props.bind[1](v);
|
props.bind[1](v);
|
||||||
}
|
}
|
||||||
return <div class="field">
|
return (
|
||||||
|
<div class="field">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<a onClick={() => image.current?.click()}>
|
<a onClick={() => image.current?.click()}>{props.label}</a>
|
||||||
{props.label}
|
{props.tooltip && (
|
||||||
</a>
|
<span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
||||||
{props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
|
||||||
<i class="mdi mdi-information" />
|
<i class="mdi mdi-information" />
|
||||||
</span>}
|
</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input
|
<input
|
||||||
ref={image} style={{ display: 'none' }}
|
ref={image}
|
||||||
type="file" name={String(name)}
|
style={{ display: "none" }}
|
||||||
onChange={e => {
|
type="file"
|
||||||
const f: FileList | null = e.currentTarget.files
|
name={String(name)}
|
||||||
|
onChange={(e) => {
|
||||||
|
const f: FileList | null = e.currentTarget.files;
|
||||||
if (!f || f.length != 1) {
|
if (!f || f.length != 1) {
|
||||||
return onChange("")
|
return onChange("");
|
||||||
}
|
}
|
||||||
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
|
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
|
||||||
setSizeError(true)
|
setSizeError(true);
|
||||||
return onChange("")
|
return onChange("");
|
||||||
}
|
}
|
||||||
setSizeError(false)
|
setSizeError(false);
|
||||||
return f[0].arrayBuffer().then(b => {
|
return f[0].arrayBuffer().then((b) => {
|
||||||
const b64 = btoa(
|
const b64 = btoa(
|
||||||
new Uint8Array(b)
|
new Uint8Array(b).reduce(
|
||||||
.reduce((data, byte) => data + String.fromCharCode(byte), '')
|
(data, byte) => data + String.fromCharCode(byte),
|
||||||
)
|
"",
|
||||||
return onChange(`data:${f[0].type};base64,${b64}` as any)
|
),
|
||||||
})
|
);
|
||||||
}} />
|
return onChange(`data:${f[0].type};base64,${b64}` as any);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{props.error && <p class="help is-danger">{props.error}</p>}
|
{props.error && <p class="help is-danger">{props.error}</p>}
|
||||||
{sizeError && <p class="help is-danger">
|
{sizeError && (
|
||||||
File should be smaller than 1 MB
|
<p class="help is-danger">File should be smaller than 1 MB</p>
|
||||||
</p>}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,15 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||||
import emptyImage from "../../assets/empty.png";
|
import emptyImage from "../../assets/empty.png";
|
||||||
import { TextInputProps } from "./TextInput";
|
import { TextInputProps } from "./TextInput";
|
||||||
|
|
||||||
const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024
|
const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024;
|
||||||
|
|
||||||
export function ImageInput(props: TextInputProps): VNode {
|
export function ImageInput(props: TextInputProps): VNode {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -35,47 +35,59 @@ export function ImageInput(props: TextInputProps): VNode {
|
|||||||
|
|
||||||
const value = props.bind[0];
|
const value = props.bind[0];
|
||||||
// const [dirty, setDirty] = useState(false)
|
// const [dirty, setDirty] = useState(false)
|
||||||
const image = useRef<HTMLInputElement>(null)
|
const image = useRef<HTMLInputElement>(null);
|
||||||
const [sizeError, setSizeError] = useState(false)
|
const [sizeError, setSizeError] = useState(false);
|
||||||
function onChange(v: string): void {
|
function onChange(v: string): void {
|
||||||
// setDirty(true);
|
// setDirty(true);
|
||||||
props.bind[1](v);
|
props.bind[1](v);
|
||||||
}
|
}
|
||||||
return <div class="field">
|
return (
|
||||||
|
<div class="field">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
{props.label}
|
{props.label}
|
||||||
{props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
{props.tooltip && (
|
||||||
|
<span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
||||||
<i class="mdi mdi-information" />
|
<i class="mdi mdi-information" />
|
||||||
</span>}
|
</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<img src={!value ? emptyImage : value} style={{ width: 200, height: 200 }} onClick={() => image.current?.click()} />
|
<img
|
||||||
|
src={!value ? emptyImage : value}
|
||||||
|
style={{ width: 200, height: 200 }}
|
||||||
|
onClick={() => image.current?.click()}
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
ref={image} style={{ display: 'none' }}
|
ref={image}
|
||||||
type="file" name={String(name)}
|
style={{ display: "none" }}
|
||||||
onChange={e => {
|
type="file"
|
||||||
const f: FileList | null = e.currentTarget.files
|
name={String(name)}
|
||||||
|
onChange={(e) => {
|
||||||
|
const f: FileList | null = e.currentTarget.files;
|
||||||
if (!f || f.length != 1) {
|
if (!f || f.length != 1) {
|
||||||
return onChange(emptyImage)
|
return onChange(emptyImage);
|
||||||
}
|
}
|
||||||
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
|
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
|
||||||
setSizeError(true)
|
setSizeError(true);
|
||||||
return onChange(emptyImage)
|
return onChange(emptyImage);
|
||||||
}
|
}
|
||||||
setSizeError(false)
|
setSizeError(false);
|
||||||
return f[0].arrayBuffer().then(b => {
|
return f[0].arrayBuffer().then((b) => {
|
||||||
const b64 = btoa(
|
const b64 = btoa(
|
||||||
new Uint8Array(b)
|
new Uint8Array(b).reduce(
|
||||||
.reduce((data, byte) => data + String.fromCharCode(byte), '')
|
(data, byte) => data + String.fromCharCode(byte),
|
||||||
)
|
"",
|
||||||
return onChange(`data:${f[0].type};base64,${b64}` as any)
|
),
|
||||||
})
|
);
|
||||||
}} />
|
return onChange(`data:${f[0].type};base64,${b64}` as any);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{props.error && <p class="help is-danger">{props.error}</p>}
|
{props.error && <p class="help is-danger">{props.error}</p>}
|
||||||
{sizeError && <p class="help is-danger">
|
{sizeError && (
|
||||||
Image should be smaller than 1 MB
|
<p class="help is-danger">Image should be smaller than 1 MB</p>
|
||||||
</p>}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,23 +18,30 @@ export function TextInput(props: TextInputProps): VNode {
|
|||||||
}
|
}
|
||||||
}, [props.grabFocus]);
|
}, [props.grabFocus]);
|
||||||
const value = props.bind[0];
|
const value = props.bind[0];
|
||||||
const [dirty, setDirty] = useState(false)
|
const [dirty, setDirty] = useState(false);
|
||||||
const showError = dirty && props.error
|
const showError = dirty && props.error;
|
||||||
return (<div class="field">
|
return (
|
||||||
|
<div class="field">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
{props.label}
|
{props.label}
|
||||||
{props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
{props.tooltip && (
|
||||||
|
<span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
||||||
<i class="mdi mdi-information" />
|
<i class="mdi mdi-information" />
|
||||||
</span>}
|
</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<div class="control has-icons-right">
|
<div class="control has-icons-right">
|
||||||
<input
|
<input
|
||||||
value={value}
|
value={value}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
class={showError ? 'input is-danger' : 'input'}
|
class={showError ? "input is-danger" : "input"}
|
||||||
onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}}
|
onInput={(e) => {
|
||||||
|
setDirty(true);
|
||||||
|
props.bind[1]((e.target as HTMLInputElement).value);
|
||||||
|
}}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
style={{ display: "block" }} />
|
style={{ display: "block" }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{showError && <p class="help is-danger">{props.error}</p>}
|
{showError && <p class="help is-danger">{props.error}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,44 +15,48 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
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 { useTranslationContext } from "../../context/translation";
|
||||||
import { strings as messages } from '../../i18n/strings'
|
import { strings as messages } from "../../i18n/strings";
|
||||||
|
|
||||||
type LangsNames = {
|
type LangsNames = {
|
||||||
[P in keyof typeof messages]: string
|
[P in keyof typeof messages]: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const names: LangsNames = {
|
const names: LangsNames = {
|
||||||
es: 'Español [es]',
|
es: "Español [es]",
|
||||||
en: 'English [en]',
|
en: "English [en]",
|
||||||
fr: 'Français [fr]',
|
fr: "Français [fr]",
|
||||||
de: 'Deutsch [de]',
|
de: "Deutsch [de]",
|
||||||
sv: 'Svenska [sv]',
|
sv: "Svenska [sv]",
|
||||||
it: 'Italiano [it]',
|
it: "Italiano [it]",
|
||||||
}
|
};
|
||||||
|
|
||||||
function getLangName(s: keyof LangsNames | string): string {
|
function getLangName(s: keyof LangsNames | string): string {
|
||||||
if (names[s]) return names[s]
|
if (names[s]) return names[s];
|
||||||
return String(s)
|
return String(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LangSelector(): VNode {
|
export function LangSelector(): VNode {
|
||||||
const [updatingLang, setUpdatingLang] = useState(false)
|
const [updatingLang, setUpdatingLang] = useState(false);
|
||||||
const { lang, changeLanguage } = useTranslationContext()
|
const { lang, changeLanguage } = useTranslationContext();
|
||||||
|
|
||||||
return <div class="dropdown is-active ">
|
return (
|
||||||
|
<div class="dropdown is-active ">
|
||||||
<div class="dropdown-trigger">
|
<div class="dropdown-trigger">
|
||||||
<button class="button has-tooltip-left"
|
<button
|
||||||
|
class="button has-tooltip-left"
|
||||||
data-tooltip="change language selection"
|
data-tooltip="change language selection"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-controls="dropdown-menu" onClick={() => setUpdatingLang(!updatingLang)}>
|
aria-controls="dropdown-menu"
|
||||||
|
onClick={() => setUpdatingLang(!updatingLang)}
|
||||||
|
>
|
||||||
<div class="icon is-small is-left">
|
<div class="icon is-small is-left">
|
||||||
<img src={langIcon} />
|
<img src={langIcon} />
|
||||||
</div>
|
</div>
|
||||||
@ -62,12 +66,27 @@ export function LangSelector(): VNode {
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{updatingLang && <div class="dropdown-menu" id="dropdown-menu" role="menu">
|
{updatingLang && (
|
||||||
|
<div class="dropdown-menu" id="dropdown-menu" role="menu">
|
||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
{Object.keys(messages)
|
{Object.keys(messages)
|
||||||
.filter((l) => l !== lang)
|
.filter((l) => l !== lang)
|
||||||
.map(l => <a key={l} class="dropdown-item" value={l} onClick={() => { changeLanguage(l); setUpdatingLang(false) }}>{getLangName(l)}</a>)}
|
.map((l) => (
|
||||||
|
<a
|
||||||
|
key={l}
|
||||||
|
class="dropdown-item"
|
||||||
|
value={l}
|
||||||
|
onClick={() => {
|
||||||
|
changeLanguage(l);
|
||||||
|
setUpdatingLang(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getLangName(l)}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
@ -15,16 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { Fragment, h, VNode } from 'preact';
|
import { BackupStates, RecoveryStates } from "../../../../anastasis-core/lib";
|
||||||
import { BackupStates, RecoveryStates } from '../../../../anastasis-core/lib';
|
import { useAnastasisContext } from "../../context/anastasis";
|
||||||
import { useAnastasisContext } from '../../context/anastasis';
|
import { Translate } from "../../i18n";
|
||||||
import { Translate } from '../../i18n';
|
import { LangSelector } from "./LangSelector";
|
||||||
import { LangSelector } from './LangSelector';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mobile?: boolean;
|
mobile?: boolean;
|
||||||
@ -32,10 +31,10 @@ interface Props {
|
|||||||
|
|
||||||
export function Sidebar({ mobile }: Props): VNode {
|
export function Sidebar({ mobile }: Props): VNode {
|
||||||
// const config = useConfigContext();
|
// const config = useConfigContext();
|
||||||
const config = { version: 'none' }
|
const config = { version: "none" };
|
||||||
// FIXME: add replacement for __VERSION__ with the current version
|
// FIXME: add replacement for __VERSION__ with the current version
|
||||||
const process = { env: { __VERSION__: '0.0.0' } }
|
const process = { env: { __VERSION__: "0.0.0" } };
|
||||||
const reducer = useAnastasisContext()!
|
const reducer = useAnastasisContext()!;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside class="aside is-placed-left is-expanded">
|
<aside class="aside is-placed-left is-expanded">
|
||||||
@ -44,54 +43,106 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
</div>} */}
|
</div>} */}
|
||||||
<div class="aside-tools">
|
<div class="aside-tools">
|
||||||
<div class="aside-tools-label">
|
<div class="aside-tools-label">
|
||||||
<div><b>Anastasis</b></div>
|
<div>
|
||||||
<div class="is-size-7 has-text-right" style={{ lineHeight: 0, marginTop: -10 }}>
|
<b>Anastasis</b>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="is-size-7 has-text-right"
|
||||||
|
style={{ lineHeight: 0, marginTop: -10 }}
|
||||||
|
>
|
||||||
Version {process.env.__VERSION__} ({config.version})
|
Version {process.env.__VERSION__} ({config.version})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu is-menu-main">
|
<div class="menu is-menu-main">
|
||||||
{!reducer.currentReducerState &&
|
{!reducer.currentReducerState && (
|
||||||
<p class="menu-label">
|
<p class="menu-label">
|
||||||
<Translate>Backup or Recorver</Translate>
|
<Translate>Backup or Recorver</Translate>
|
||||||
</p>
|
</p>
|
||||||
}
|
)}
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
{!reducer.currentReducerState &&
|
{!reducer.currentReducerState && (
|
||||||
<li>
|
<li>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label"><Translate>Select one option</Translate></span>
|
<span class="menu-item-label">
|
||||||
|
<Translate>Select one option</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
)}
|
||||||
|
{reducer.currentReducerState &&
|
||||||
|
reducer.currentReducerState.backup_state ? (
|
||||||
|
<Fragment>
|
||||||
|
<li
|
||||||
|
class={
|
||||||
|
reducer.currentReducerState.backup_state ===
|
||||||
|
BackupStates.ContinentSelecting ||
|
||||||
|
reducer.currentReducerState.backup_state ===
|
||||||
|
BackupStates.CountrySelecting
|
||||||
|
? "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">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label"><Translate>Location</Translate></span>
|
<span class="menu-item-label">
|
||||||
|
<Translate>Location</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class={reducer.currentReducerState.backup_state === BackupStates.UserAttributesCollecting ? 'is-active' : ''}>
|
<li
|
||||||
|
class={
|
||||||
|
reducer.currentReducerState.backup_state ===
|
||||||
|
BackupStates.UserAttributesCollecting
|
||||||
|
? "is-active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label"><Translate>Personal information</Translate></span>
|
<span class="menu-item-label">
|
||||||
|
<Translate>Personal information</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class={reducer.currentReducerState.backup_state === BackupStates.AuthenticationsEditing ? 'is-active' : ''}>
|
<li
|
||||||
|
class={
|
||||||
|
reducer.currentReducerState.backup_state ===
|
||||||
|
BackupStates.AuthenticationsEditing
|
||||||
|
? "is-active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
|
<span class="menu-item-label">
|
||||||
<span class="menu-item-label"><Translate>Authorization methods</Translate></span>
|
<Translate>Authorization methods</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesReviewing ? 'is-active' : ''}>
|
<li
|
||||||
|
class={
|
||||||
|
reducer.currentReducerState.backup_state ===
|
||||||
|
BackupStates.PoliciesReviewing
|
||||||
|
? "is-active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
|
<span class="menu-item-label">
|
||||||
<span class="menu-item-label"><Translate>Policies</Translate></span>
|
<Translate>Policies</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class={reducer.currentReducerState.backup_state === BackupStates.SecretEditing ? 'is-active' : ''}>
|
<li
|
||||||
|
class={
|
||||||
|
reducer.currentReducerState.backup_state ===
|
||||||
|
BackupStates.SecretEditing
|
||||||
|
? "is-active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
|
<span class="menu-item-label">
|
||||||
<span class="menu-item-label"><Translate>Secret input</Translate></span>
|
<Translate>Secret input</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}>
|
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}>
|
||||||
@ -100,10 +151,18 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
<span class="menu-item-label"><Translate>Payment (optional)</Translate></span>
|
<span class="menu-item-label"><Translate>Payment (optional)</Translate></span>
|
||||||
</div>
|
</div>
|
||||||
</li> */}
|
</li> */}
|
||||||
<li class={reducer.currentReducerState.backup_state === BackupStates.BackupFinished ? 'is-active' : ''}>
|
<li
|
||||||
|
class={
|
||||||
|
reducer.currentReducerState.backup_state ===
|
||||||
|
BackupStates.BackupFinished
|
||||||
|
? "is-active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
|
<span class="menu-item-label">
|
||||||
<span class="menu-item-label"><Translate>Backup completed</Translate></span>
|
<Translate>Backup completed</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}>
|
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}>
|
||||||
@ -112,46 +171,107 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
<span class="menu-item-label"><Translate>Truth Paying</Translate></span>
|
<span class="menu-item-label"><Translate>Truth Paying</Translate></span>
|
||||||
</div>
|
</div>
|
||||||
</li> */}
|
</li> */}
|
||||||
</Fragment> : (reducer.currentReducerState && reducer.currentReducerState?.recovery_state && <Fragment>
|
</Fragment>
|
||||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting ||
|
) : (
|
||||||
reducer.currentReducerState.recovery_state === RecoveryStates.CountrySelecting ? 'is-active' : ''}>
|
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">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label"><Translate>Location</Translate></span>
|
<span class="menu-item-label">
|
||||||
|
<Translate>Location</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.UserAttributesCollecting ? 'is-active' : ''}>
|
<li
|
||||||
|
class={
|
||||||
|
reducer.currentReducerState.recovery_state ===
|
||||||
|
RecoveryStates.UserAttributesCollecting
|
||||||
|
? "is-active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label"><Translate>Personal information</Translate></span>
|
<span class="menu-item-label">
|
||||||
|
<Translate>Personal information</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.SecretSelecting ? 'is-active' : ''}>
|
<li
|
||||||
|
class={
|
||||||
|
reducer.currentReducerState.recovery_state ===
|
||||||
|
RecoveryStates.SecretSelecting
|
||||||
|
? "is-active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label"><Translate>Secret selection</Translate></span>
|
<span class="menu-item-label">
|
||||||
|
<Translate>Secret selection</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.ChallengeSelecting ||
|
<li
|
||||||
reducer.currentReducerState.recovery_state === RecoveryStates.ChallengeSolving ? 'is-active' : ''}>
|
class={
|
||||||
|
reducer.currentReducerState.recovery_state ===
|
||||||
|
RecoveryStates.ChallengeSelecting ||
|
||||||
|
reducer.currentReducerState.recovery_state ===
|
||||||
|
RecoveryStates.ChallengeSolving
|
||||||
|
? "is-active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label"><Translate>Solve Challenges</Translate></span>
|
<span class="menu-item-label">
|
||||||
|
<Translate>Solve Challenges</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.RecoveryFinished ? 'is-active' : ''}>
|
<li
|
||||||
|
class={
|
||||||
|
reducer.currentReducerState.recovery_state ===
|
||||||
|
RecoveryStates.RecoveryFinished
|
||||||
|
? "is-active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label"><Translate>Secret recovered</Translate></span>
|
<span class="menu-item-label">
|
||||||
|
<Translate>Secret recovered</Translate>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</Fragment>)}
|
</Fragment>
|
||||||
{reducer.currentReducerState &&
|
)
|
||||||
|
)}
|
||||||
|
{reducer.currentReducerState && (
|
||||||
<li>
|
<li>
|
||||||
<div class="buttons ml-4">
|
<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>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
}
|
)}
|
||||||
|
{/* <li>
|
||||||
|
<div class="buttons ml-4">
|
||||||
|
<button class="button is-info is-right" >Manage providers</button>
|
||||||
|
</div>
|
||||||
|
</li> */}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { h, Component } from "preact";
|
import { h, Component } from "preact";
|
||||||
|
|
||||||
@ -34,54 +34,44 @@ interface State {
|
|||||||
selectYearMode: boolean;
|
selectYearMode: boolean;
|
||||||
currentDate: Date;
|
currentDate: Date;
|
||||||
}
|
}
|
||||||
const now = new Date()
|
const now = new Date();
|
||||||
|
|
||||||
const monthArrShortFull = [
|
const monthArrShortFull = [
|
||||||
'January',
|
"January",
|
||||||
'February',
|
"February",
|
||||||
'March',
|
"March",
|
||||||
'April',
|
"April",
|
||||||
'May',
|
"May",
|
||||||
'June',
|
"June",
|
||||||
'July',
|
"July",
|
||||||
'August',
|
"August",
|
||||||
'September',
|
"September",
|
||||||
'October',
|
"October",
|
||||||
'November',
|
"November",
|
||||||
'December'
|
"December",
|
||||||
]
|
];
|
||||||
|
|
||||||
const monthArrShort = [
|
const monthArrShort = [
|
||||||
'Jan',
|
"Jan",
|
||||||
'Feb',
|
"Feb",
|
||||||
'Mar',
|
"Mar",
|
||||||
'Apr',
|
"Apr",
|
||||||
'May',
|
"May",
|
||||||
'Jun',
|
"Jun",
|
||||||
'Jul',
|
"Jul",
|
||||||
'Aug',
|
"Aug",
|
||||||
'Sep',
|
"Sep",
|
||||||
'Oct',
|
"Oct",
|
||||||
'Nov',
|
"Nov",
|
||||||
'Dec'
|
"Dec",
|
||||||
]
|
];
|
||||||
|
|
||||||
const dayArr = [
|
const dayArr = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
'Sun',
|
|
||||||
'Mon',
|
|
||||||
'Tue',
|
|
||||||
'Wed',
|
|
||||||
'Thu',
|
|
||||||
'Fri',
|
|
||||||
'Sat'
|
|
||||||
]
|
|
||||||
|
|
||||||
const yearArr: number[] = []
|
|
||||||
|
|
||||||
|
const yearArr: number[] = [];
|
||||||
|
|
||||||
// inspired by https://codepen.io/m4r1vs/pen/MOOxyE
|
// inspired by https://codepen.io/m4r1vs/pen/MOOxyE
|
||||||
export class DatePicker extends Component<Props, State> {
|
export class DatePicker extends Component<Props, State> {
|
||||||
|
|
||||||
closeDatePicker() {
|
closeDatePicker() {
|
||||||
this.props.closeFunction && this.props.closeFunction(); // Function gets passed by parent
|
this.props.closeFunction && this.props.closeFunction(); // Function gets passed by parent
|
||||||
}
|
}
|
||||||
@ -91,17 +81,16 @@ export class DatePicker extends Component<Props, State> {
|
|||||||
* @param {object} e The event thrown by the <span /> element clicked
|
* @param {object} e The event thrown by the <span /> element clicked
|
||||||
*/
|
*/
|
||||||
dayClicked(e: any) {
|
dayClicked(e: any) {
|
||||||
|
|
||||||
const element = e.target; // the actual element clicked
|
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)
|
// 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
|
// update the state
|
||||||
this.setState({ currentDate: date });
|
this.setState({ currentDate: date });
|
||||||
this.passDateToParent(date)
|
this.passDateToParent(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,7 +99,6 @@ export class DatePicker extends Component<Props, State> {
|
|||||||
* @param {number} year the year to display
|
* @param {number} year the year to display
|
||||||
*/
|
*/
|
||||||
getDaysByMonth(month: number, year: number) {
|
getDaysByMonth(month: number, year: number) {
|
||||||
|
|
||||||
const calendar = [];
|
const calendar = [];
|
||||||
|
|
||||||
const date = new Date(year, month, 1); // month to display
|
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
|
// the calendar is 7*6 fields big, so 42 loops
|
||||||
for (let i = 0; i < 42; i++) {
|
for (let i = 0; i < 42; i++) {
|
||||||
|
|
||||||
if (i >= firstDay && day !== null) day = day + 1;
|
if (i >= firstDay && day !== null) day = day + 1;
|
||||||
if (day !== null && day > lastDate) day = null;
|
if (day !== null && day > lastDate) day = null;
|
||||||
|
|
||||||
// append the calendar Array
|
// append the calendar Array
|
||||||
calendar.push({
|
calendar.push({
|
||||||
day: (day === 0 || day === null) ? null : day, // null or number
|
day: day === 0 || day === null ? null : day, // null or number
|
||||||
date: (day === 0 || day === null) ? null : new Date(year, month, day), // null or Date()
|
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
|
today:
|
||||||
|
day === now.getDate() &&
|
||||||
|
month === now.getMonth() &&
|
||||||
|
year === now.getFullYear(), // boolean
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,12 +134,11 @@ export class DatePicker extends Component<Props, State> {
|
|||||||
if (this.state.displayedMonth <= 0) {
|
if (this.state.displayedMonth <= 0) {
|
||||||
this.setState({
|
this.setState({
|
||||||
displayedMonth: 11,
|
displayedMonth: 11,
|
||||||
displayedYear: this.state.displayedYear - 1
|
displayedYear: this.state.displayedYear - 1,
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
displayedMonth: this.state.displayedMonth - 1
|
displayedMonth: this.state.displayedMonth - 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,12 +150,11 @@ export class DatePicker extends Component<Props, State> {
|
|||||||
if (this.state.displayedMonth >= 11) {
|
if (this.state.displayedMonth >= 11) {
|
||||||
this.setState({
|
this.setState({
|
||||||
displayedMonth: 0,
|
displayedMonth: 0,
|
||||||
displayedYear: this.state.displayedYear + 1
|
displayedYear: this.state.displayedYear + 1,
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
displayedMonth: this.state.displayedMonth + 1
|
displayedMonth: this.state.displayedMonth + 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,12 +165,11 @@ export class DatePicker extends Component<Props, State> {
|
|||||||
displaySelectedMonth() {
|
displaySelectedMonth() {
|
||||||
if (this.state.selectYearMode) {
|
if (this.state.selectYearMode) {
|
||||||
this.toggleYearSelector();
|
this.toggleYearSelector();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (!this.state.currentDate) return false;
|
if (!this.state.currentDate) return false;
|
||||||
this.setState({
|
this.setState({
|
||||||
displayedMonth: this.state.currentDate.getMonth(),
|
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) {
|
changeDisplayedYear(e: any) {
|
||||||
const element = e.target;
|
const element = e.target;
|
||||||
this.toggleYearSelector();
|
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() {
|
passSavedDateDateToParent() {
|
||||||
this.passDateToParent(this.state.currentDate)
|
this.passDateToParent(this.state.currentDate);
|
||||||
}
|
}
|
||||||
passDateToParent(date: Date) {
|
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();
|
this.closeDatePicker();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,94 +224,133 @@ export class DatePicker extends Component<Props, State> {
|
|||||||
currentDate: initial,
|
currentDate: initial,
|
||||||
displayedMonth: initial.getMonth(),
|
displayedMonth: initial.getMonth(),
|
||||||
displayedYear: initial.getFullYear(),
|
displayedYear: initial.getFullYear(),
|
||||||
selectYearMode: false
|
selectYearMode: false,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
const { currentDate, displayedMonth, displayedYear, selectYearMode } = this.state;
|
currentDate,
|
||||||
|
displayedMonth,
|
||||||
|
displayedYear,
|
||||||
|
selectYearMode,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div class={`datePicker ${ this.props.opened && "datePicker--opened"}`}>
|
<div class={`datePicker ${this.props.opened && "datePicker--opened"}`}>
|
||||||
|
|
||||||
<div class="datePicker--titles">
|
<div class="datePicker--titles">
|
||||||
<h3 style={{
|
<h3
|
||||||
color: selectYearMode ? 'rgba(255,255,255,.87)' : 'rgba(255,255,255,.57)'
|
style={{
|
||||||
}} onClick={this.toggleYearSelector}>{currentDate.getFullYear()}</h3>
|
color: selectYearMode
|
||||||
<h2 style={{
|
? "rgba(255,255,255,.87)"
|
||||||
color: !selectYearMode ? 'rgba(255,255,255,.87)' : 'rgba(255,255,255,.57)'
|
: "rgba(255,255,255,.57)",
|
||||||
}} onClick={this.displaySelectedMonth}>
|
}}
|
||||||
{dayArr[currentDate.getDay()]}, {monthArrShort[currentDate.getMonth()]} {currentDate.getDate()}
|
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>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!selectYearMode && <nav>
|
{!selectYearMode && (
|
||||||
<span onClick={this.displayPrevMonth} class="icon"><i style={{ transform: 'rotate(180deg)' }} class="mdi mdi-forward" /></span>
|
<nav>
|
||||||
<h4>{monthArrShortFull[displayedMonth]} {displayedYear}</h4>
|
<span onClick={this.displayPrevMonth} class="icon">
|
||||||
<span onClick={this.displayNextMonth} class="icon"><i class="mdi mdi-forward" /></span>
|
<i
|
||||||
</nav>}
|
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">
|
<div class="datePicker--scroll">
|
||||||
|
{!selectYearMode && (
|
||||||
{!selectYearMode && <div class="datePicker--calendar" >
|
<div class="datePicker--calendar">
|
||||||
|
|
||||||
<div class="datePicker--dayNames">
|
<div class="datePicker--dayNames">
|
||||||
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day,i) => <span key={i}>{day}</span>)}
|
{["S", "M", "T", "W", "T", "F", "S"].map((day, i) => (
|
||||||
|
<span key={i}>{day}</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div onClick={this.dayClicked} class="datePicker--days">
|
<div onClick={this.dayClicked} class="datePicker--days">
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
Loop through the calendar object returned by getDaysByMonth().
|
Loop through the calendar object returned by getDaysByMonth().
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
{this.getDaysByMonth(this.state.displayedMonth, this.state.displayedYear)
|
{this.getDaysByMonth(
|
||||||
.map(
|
this.state.displayedMonth,
|
||||||
day => {
|
this.state.displayedYear,
|
||||||
|
).map((day) => {
|
||||||
let selected = false;
|
let selected = false;
|
||||||
|
|
||||||
if (currentDate && day.date) selected = (currentDate.toLocaleDateString() === day.date.toLocaleDateString());
|
if (currentDate && day.date)
|
||||||
|
selected =
|
||||||
|
currentDate.toLocaleDateString() ===
|
||||||
|
day.date.toLocaleDateString();
|
||||||
|
|
||||||
return (<span key={day.day}
|
return (
|
||||||
class={(day.today ? 'datePicker--today ' : '') + (selected ? 'datePicker--selected' : '')}
|
<span
|
||||||
|
key={day.day}
|
||||||
|
class={
|
||||||
|
(day.today ? "datePicker--today " : "") +
|
||||||
|
(selected ? "datePicker--selected" : "")
|
||||||
|
}
|
||||||
disabled={!day.date}
|
disabled={!day.date}
|
||||||
data-value={day.date}
|
data-value={day.date}
|
||||||
>
|
>
|
||||||
{day.day}
|
{day.day}
|
||||||
</span>)
|
</span>
|
||||||
}
|
);
|
||||||
)
|
})}
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>}
|
{selectYearMode && (
|
||||||
|
<div class="datePicker--selectYear">
|
||||||
{selectYearMode && <div class="datePicker--selectYear">
|
{(this.props.years || yearArr).map((year) => (
|
||||||
{(this.props.years || yearArr).map(year => (
|
<span
|
||||||
<span key={year} class={(year === displayedYear) ? 'selected' : ''} onClick={this.changeDisplayedYear}>
|
key={year}
|
||||||
|
class={year === displayedYear ? "selected" : ""}
|
||||||
|
onClick={this.changeDisplayedYear}
|
||||||
|
>
|
||||||
{year}
|
{year}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
</div>}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="datePicker--background" onClick={this.closeDatePicker} style={{
|
<div
|
||||||
display: this.props.opened ? 'block' : 'none',
|
class="datePicker--background"
|
||||||
|
onClick={this.closeDatePicker}
|
||||||
|
style={{
|
||||||
|
display: this.props.opened ? "block" : "none",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for (let i = 2010; i <= now.getFullYear() + 10; i++) {
|
for (let i = 2010; i <= now.getFullYear() + 10; i++) {
|
||||||
yearArr.push(i);
|
yearArr.push(i);
|
||||||
}
|
}
|
||||||
|
@ -15,36 +15,41 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { h, FunctionalComponent } from 'preact';
|
|
||||||
import { useState } from 'preact/hooks';
|
|
||||||
import { DurationPicker as TestedComponent } from './DurationPicker';
|
|
||||||
|
|
||||||
|
import { h, FunctionalComponent } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { DurationPicker as TestedComponent } from "./DurationPicker";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Components/Picker/Duration',
|
title: "Components/Picker/Duration",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onCreate: { action: 'onCreate' },
|
onCreate: { action: "onCreate" },
|
||||||
goBack: { action: 'goBack' },
|
goBack: { action: "goBack" },
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) {
|
function createExample<Props>(
|
||||||
const r = (args: any) => <Component {...args} />
|
Component: FunctionalComponent<Props>,
|
||||||
r.args = props
|
props: Partial<Props>,
|
||||||
return r
|
) {
|
||||||
|
const r = (args: any) => <Component {...args} />;
|
||||||
|
r.args = props;
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Example = createExample(TestedComponent, {
|
export const Example = createExample(TestedComponent, {
|
||||||
days: true, minutes: true, hours: true, seconds: true,
|
days: true,
|
||||||
value: 10000000
|
minutes: true,
|
||||||
|
hours: true,
|
||||||
|
seconds: true,
|
||||||
|
value: 10000000,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithState = () => {
|
export const WithState = () => {
|
||||||
const [v,s] = useState<number>(1000000)
|
const [v, s] = useState<number>(1000000);
|
||||||
return <TestedComponent value={v} onChange={s} days minutes hours seconds />
|
return <TestedComponent value={v} onChange={s} days minutes hours seconds />;
|
||||||
}
|
};
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
@ -30,75 +30,123 @@ export interface Props {
|
|||||||
seconds?: boolean;
|
seconds?: boolean;
|
||||||
days?: boolean;
|
days?: boolean;
|
||||||
onChange: (value: number) => void;
|
onChange: (value: number) => void;
|
||||||
value: number
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// inspiration taken from https://github.com/flurmbo/react-duration-picker
|
// inspiration taken from https://github.com/flurmbo/react-duration-picker
|
||||||
export function DurationPicker({ days, hours, minutes, seconds, onChange, value }: Props): VNode {
|
export function DurationPicker({
|
||||||
const ss = 1000
|
days,
|
||||||
const ms = ss * 60
|
hours,
|
||||||
const hs = ms * 60
|
minutes,
|
||||||
const ds = hs * 24
|
seconds,
|
||||||
const i18n = useTranslator()
|
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">
|
return (
|
||||||
{days && <DurationColumn unit={i18n`days`} max={99}
|
<div class="rdp-picker">
|
||||||
|
{days && (
|
||||||
|
<DurationColumn
|
||||||
|
unit={i18n`days`}
|
||||||
|
max={99}
|
||||||
value={Math.floor(value / ds)}
|
value={Math.floor(value / ds)}
|
||||||
onDecrease={value >= ds ? () => onChange(value - ds) : undefined}
|
onDecrease={value >= ds ? () => onChange(value - ds) : undefined}
|
||||||
onIncrease={value < 99 * ds ? () => onChange(value + ds) : undefined}
|
onIncrease={value < 99 * ds ? () => onChange(value + ds) : undefined}
|
||||||
onChange={diff => onChange(value + diff * ds)}
|
onChange={(diff) => onChange(value + diff * ds)}
|
||||||
/>}
|
/>
|
||||||
{hours && <DurationColumn unit={i18n`hours`} max={23} min={1}
|
)}
|
||||||
|
{hours && (
|
||||||
|
<DurationColumn
|
||||||
|
unit={i18n`hours`}
|
||||||
|
max={23}
|
||||||
|
min={1}
|
||||||
value={Math.floor(value / hs) % 24}
|
value={Math.floor(value / hs) % 24}
|
||||||
onDecrease={value >= hs ? () => onChange(value - hs) : undefined}
|
onDecrease={value >= hs ? () => onChange(value - hs) : undefined}
|
||||||
onIncrease={value < 99 * ds ? () => onChange(value + hs) : undefined}
|
onIncrease={value < 99 * ds ? () => onChange(value + hs) : undefined}
|
||||||
onChange={diff => onChange(value + diff * hs)}
|
onChange={(diff) => onChange(value + diff * hs)}
|
||||||
/>}
|
/>
|
||||||
{minutes && <DurationColumn unit={i18n`minutes`} max={59} min={1}
|
)}
|
||||||
|
{minutes && (
|
||||||
|
<DurationColumn
|
||||||
|
unit={i18n`minutes`}
|
||||||
|
max={59}
|
||||||
|
min={1}
|
||||||
value={Math.floor(value / ms) % 60}
|
value={Math.floor(value / ms) % 60}
|
||||||
onDecrease={value >= ms ? () => onChange(value - ms) : undefined}
|
onDecrease={value >= ms ? () => onChange(value - ms) : undefined}
|
||||||
onIncrease={value < 99 * ds ? () => onChange(value + ms) : undefined}
|
onIncrease={value < 99 * ds ? () => onChange(value + ms) : undefined}
|
||||||
onChange={diff => onChange(value + diff * ms)}
|
onChange={(diff) => onChange(value + diff * ms)}
|
||||||
/>}
|
/>
|
||||||
{seconds && <DurationColumn unit={i18n`seconds`} max={59}
|
)}
|
||||||
|
{seconds && (
|
||||||
|
<DurationColumn
|
||||||
|
unit={i18n`seconds`}
|
||||||
|
max={59}
|
||||||
value={Math.floor(value / ss) % 60}
|
value={Math.floor(value / ss) % 60}
|
||||||
onDecrease={value >= ss ? () => onChange(value - ss) : undefined}
|
onDecrease={value >= ss ? () => onChange(value - ss) : undefined}
|
||||||
onIncrease={value < 99 * ds ? () => onChange(value + ss) : undefined}
|
onIncrease={value < 99 * ds ? () => onChange(value + ss) : undefined}
|
||||||
onChange={diff => onChange(value + diff * ss)}
|
onChange={(diff) => onChange(value + diff * ss)}
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ColProps {
|
interface ColProps {
|
||||||
unit: string,
|
unit: string;
|
||||||
min?: number,
|
min?: number;
|
||||||
max: number,
|
max: number;
|
||||||
value: number,
|
value: number;
|
||||||
onIncrease?: () => void;
|
onIncrease?: () => void;
|
||||||
onDecrease?: () => void;
|
onDecrease?: () => void;
|
||||||
onChange?: (diff: number) => void;
|
onChange?: (diff: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputNumber({ initial, onChange }: { initial: number, onChange: (n: number) => void }) {
|
function InputNumber({
|
||||||
const [value, handler] = useState<{v:string}>({
|
initial,
|
||||||
v: toTwoDigitString(initial)
|
onChange,
|
||||||
})
|
}: {
|
||||||
|
initial: number;
|
||||||
|
onChange: (n: number) => void;
|
||||||
|
}) {
|
||||||
|
const [value, handler] = useState<{ v: string }>({
|
||||||
|
v: toTwoDigitString(initial),
|
||||||
|
});
|
||||||
|
|
||||||
return <input
|
return (
|
||||||
|
<input
|
||||||
value={value.v}
|
value={value.v}
|
||||||
onBlur={(e) => onChange(parseInt(value.v, 10))}
|
onBlur={(e) => onChange(parseInt(value.v, 10))}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
const n = Number.parseInt(e.currentTarget.value, 10);
|
const n = Number.parseInt(e.currentTarget.value, 10);
|
||||||
if (isNaN(n)) return handler({v:toTwoDigitString(initial)})
|
if (isNaN(n)) return handler({ v: toTwoDigitString(initial) });
|
||||||
return handler({v:toTwoDigitString(n)})
|
return handler({ v: toTwoDigitString(n) });
|
||||||
}}
|
}}
|
||||||
style={{ width: 50, border: 'none', fontSize: 'inherit', background: 'inherit' }} />
|
style={{
|
||||||
|
width: 50,
|
||||||
|
border: "none",
|
||||||
|
fontSize: "inherit",
|
||||||
|
background: "inherit",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DurationColumn({ unit, min = 0, max, value, onIncrease, onDecrease, onChange }: ColProps): VNode {
|
function DurationColumn({
|
||||||
|
unit,
|
||||||
const cellHeight = 35
|
min = 0,
|
||||||
|
max,
|
||||||
|
value,
|
||||||
|
onIncrease,
|
||||||
|
onDecrease,
|
||||||
|
onChange,
|
||||||
|
}: ColProps): VNode {
|
||||||
|
const cellHeight = 35;
|
||||||
return (
|
return (
|
||||||
<div class="rdp-column-container">
|
<div class="rdp-column-container">
|
||||||
<div class="rdp-masked-div">
|
<div class="rdp-masked-div">
|
||||||
@ -106,46 +154,55 @@ function DurationColumn({ unit, min = 0, max, value, onIncrease, onDecrease, onC
|
|||||||
<hr class="rdp-reticule" style={{ top: cellHeight * 3 - 1 }} />
|
<hr class="rdp-reticule" style={{ top: cellHeight * 3 - 1 }} />
|
||||||
|
|
||||||
<div class="rdp-column" style={{ top: 0 }}>
|
<div class="rdp-column" style={{ top: 0 }}>
|
||||||
|
|
||||||
<div class="rdp-cell" key={value - 2}>
|
<div class="rdp-cell" key={value - 2}>
|
||||||
{onDecrease && <button style={{ width: '100%', textAlign: 'center', margin: 5 }}
|
{onDecrease && (
|
||||||
onClick={onDecrease}>
|
<button
|
||||||
|
style={{ width: "100%", textAlign: "center", margin: 5 }}
|
||||||
|
onClick={onDecrease}
|
||||||
|
>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="mdi mdi-chevron-up" />
|
<i class="mdi mdi-chevron-up" />
|
||||||
</span>
|
</span>
|
||||||
</button>}
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="rdp-cell" key={value - 1}>
|
<div class="rdp-cell" key={value - 1}>
|
||||||
{value > min ? toTwoDigitString(value - 1) : ''}
|
{value > min ? toTwoDigitString(value - 1) : ""}
|
||||||
</div>
|
</div>
|
||||||
<div class="rdp-cell rdp-center" key={value}>
|
<div class="rdp-cell rdp-center" key={value}>
|
||||||
{onChange ?
|
{onChange ? (
|
||||||
<InputNumber initial={value} onChange={(n) => onChange(n - value)} /> :
|
<InputNumber
|
||||||
|
initial={value}
|
||||||
|
onChange={(n) => onChange(n - value)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
toTwoDigitString(value)
|
toTwoDigitString(value)
|
||||||
}
|
)}
|
||||||
<div>{unit}</div>
|
<div>{unit}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rdp-cell" key={value + 1}>
|
<div class="rdp-cell" key={value + 1}>
|
||||||
{value < max ? toTwoDigitString(value + 1) : ''}
|
{value < max ? toTwoDigitString(value + 1) : ""}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rdp-cell" key={value + 2}>
|
<div class="rdp-cell" key={value + 2}>
|
||||||
{onIncrease && <button style={{ width: '100%', textAlign: 'center', margin: 5 }}
|
{onIncrease && (
|
||||||
onClick={onIncrease}>
|
<button
|
||||||
|
style={{ width: "100%", textAlign: "center", margin: 5 }}
|
||||||
|
onClick={onIncrease}
|
||||||
|
>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="mdi mdi-chevron-down" />
|
<i class="mdi mdi-chevron-down" />
|
||||||
</span>
|
</span>
|
||||||
</button>}
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function toTwoDigitString(n: number) {
|
function toTwoDigitString(n: number) {
|
||||||
if (n < 10) {
|
if (n < 10) {
|
||||||
return `0${n}`;
|
return `0${n}`;
|
||||||
|
@ -15,19 +15,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createContext, h, VNode } from 'preact';
|
import { createContext, h, VNode } from "preact";
|
||||||
import { useContext } from 'preact/hooks';
|
import { useContext } from "preact/hooks";
|
||||||
import { AnastasisReducerApi } from '../hooks/use-anastasis-reducer';
|
import { AnastasisReducerApi } from "../hooks/use-anastasis-reducer";
|
||||||
|
|
||||||
type Type = AnastasisReducerApi | undefined;
|
type Type = AnastasisReducerApi | undefined;
|
||||||
|
|
||||||
const initial = undefined
|
const initial = undefined;
|
||||||
|
|
||||||
const Context = createContext<Type>(initial)
|
const Context = createContext<Type>(initial);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: AnastasisReducerApi;
|
value: AnastasisReducerApi;
|
||||||
@ -36,6 +36,6 @@ interface Props {
|
|||||||
|
|
||||||
export const AnastasisProvider = ({ value, children }: Props): VNode => {
|
export const AnastasisProvider = ({ value, children }: Props): VNode => {
|
||||||
return h(Context.Provider, { value, children });
|
return h(Context.Provider, { value, children });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useAnastasisContext = (): Type => useContext(Context);
|
export const useAnastasisContext = (): Type => useContext(Context);
|
@ -15,13 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createContext, h, VNode } from 'preact'
|
import { createContext, h, VNode } from "preact";
|
||||||
import { useContext, useEffect } from 'preact/hooks'
|
import { useContext, useEffect } from "preact/hooks";
|
||||||
import { useLang } from '../hooks'
|
import { useLang } from "../hooks";
|
||||||
import * as jedLib from "jed";
|
import * as jedLib from "jed";
|
||||||
import { strings } from "../i18n/strings";
|
import { strings } from "../i18n/strings";
|
||||||
|
|
||||||
@ -31,13 +31,13 @@ interface Type {
|
|||||||
changeLanguage: (l: string) => void;
|
changeLanguage: (l: string) => void;
|
||||||
}
|
}
|
||||||
const initial = {
|
const initial = {
|
||||||
lang: 'en',
|
lang: "en",
|
||||||
handler: null,
|
handler: null,
|
||||||
changeLanguage: () => {
|
changeLanguage: () => {
|
||||||
// do not change anything
|
// do not change anything
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
const Context = createContext<Type>(initial)
|
const Context = createContext<Type>(initial);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
initial?: string;
|
initial?: string;
|
||||||
@ -45,15 +45,22 @@ interface Props {
|
|||||||
forceLang?: string;
|
forceLang?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TranslationProvider = ({ initial, children, forceLang }: Props): VNode => {
|
export const TranslationProvider = ({
|
||||||
const [lang, changeLanguage] = useLang(initial)
|
initial,
|
||||||
|
children,
|
||||||
|
forceLang,
|
||||||
|
}: Props): VNode => {
|
||||||
|
const [lang, changeLanguage] = useLang(initial);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (forceLang) {
|
if (forceLang) {
|
||||||
changeLanguage(forceLang)
|
changeLanguage(forceLang);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
const handler = new jedLib.Jed(strings[lang] || strings['en']);
|
const handler = new jedLib.Jed(strings[lang] || strings["en"]);
|
||||||
return h(Context.Provider, { value: { lang, handler, changeLanguage }, children });
|
return h(Context.Provider, {
|
||||||
}
|
value: { lang, handler, changeLanguage },
|
||||||
|
children,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useTranslationContext = (): Type => useContext(Context);
|
export const useTranslationContext = (): Type => useContext(Context);
|
@ -2,19 +2,19 @@ declare module "*.css" {
|
|||||||
const mapping: Record<string, string>;
|
const mapping: Record<string, string>;
|
||||||
export default mapping;
|
export default mapping;
|
||||||
}
|
}
|
||||||
declare module '*.svg' {
|
declare module "*.svg" {
|
||||||
const content: any;
|
const content: any;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
declare module '*.jpeg' {
|
declare module "*.jpeg" {
|
||||||
const content: any;
|
const content: any;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
declare module '*.png' {
|
declare module "*.png" {
|
||||||
const content: any;
|
const content: any;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
declare module 'jed' {
|
declare module "jed" {
|
||||||
const x: any;
|
const x: any;
|
||||||
export = x;
|
export = x;
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
// import { cancelPendingRequest } from "./backend";
|
// import { cancelPendingRequest } from "./backend";
|
||||||
|
|
||||||
@ -34,36 +34,39 @@ export interface AsyncOperationApi<T> {
|
|||||||
error: string | undefined;
|
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 [data, setData] = useState<T | undefined>(undefined);
|
||||||
const [isLoading, setLoading] = useState<boolean>(false);
|
const [isLoading, setLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<any>(undefined);
|
const [error, setError] = useState<any>(undefined);
|
||||||
const [isSlow, setSlow] = useState(false)
|
const [isSlow, setSlow] = useState(false);
|
||||||
|
|
||||||
const request = async (...args: any) => {
|
const request = async (...args: any) => {
|
||||||
if (!fn) return;
|
if (!fn) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const handler = setTimeout(() => {
|
const handler = setTimeout(() => {
|
||||||
setSlow(true)
|
setSlow(true);
|
||||||
}, tooLong)
|
}, tooLong);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log("calling async", args)
|
console.log("calling async", args);
|
||||||
const result = await fn(...args);
|
const result = await fn(...args);
|
||||||
console.log("async back", result)
|
console.log("async back", result);
|
||||||
setData(result);
|
setData(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error);
|
setError(error);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setSlow(false)
|
setSlow(false);
|
||||||
clearTimeout(handler)
|
clearTimeout(handler);
|
||||||
};
|
};
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
// cancelPendingRequest()
|
// cancelPendingRequest()
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setSlow(false)
|
setSlow(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -72,6 +75,6 @@ export function useAsync<T>(fn?: (...args: any) => Promise<T>, { slowTolerance:
|
|||||||
data,
|
data,
|
||||||
isSlow,
|
isSlow,
|
||||||
isLoading,
|
isLoading,
|
||||||
error
|
error,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -15,81 +15,110 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { StateUpdater, useState } from "preact/hooks";
|
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 calculateRootPath = () => {
|
||||||
const rootPath = typeof window !== undefined ? window.location.origin + window.location.pathname : '/'
|
const rootPath =
|
||||||
return rootPath
|
typeof window !== undefined
|
||||||
}
|
? window.location.origin + window.location.pathname
|
||||||
|
: "/";
|
||||||
|
return rootPath;
|
||||||
|
};
|
||||||
|
|
||||||
export function useBackendURL(url?: string): [string, boolean, StateUpdater<string>, () => void] {
|
export function useBackendURL(
|
||||||
const [value, setter] = useNotNullLocalStorage('backend-url', url || calculateRootPath())
|
url?: string,
|
||||||
const [triedToLog, setTriedToLog] = useLocalStorage('tried-login')
|
): [string, boolean, StateUpdater<string>, () => void] {
|
||||||
|
const [value, setter] = useNotNullLocalStorage(
|
||||||
|
"backend-url",
|
||||||
|
url || calculateRootPath(),
|
||||||
|
);
|
||||||
|
const [triedToLog, setTriedToLog] = useLocalStorage("tried-login");
|
||||||
|
|
||||||
const checkedSetter = (v: ValueOrFunction<string>) => {
|
const checkedSetter = (v: ValueOrFunction<string>) => {
|
||||||
setTriedToLog('yes')
|
setTriedToLog("yes");
|
||||||
return setter(p => (v instanceof Function ? v(p) : v).replace(/\/$/, ''))
|
return setter((p) => (v instanceof Function ? v(p) : v).replace(/\/$/, ""));
|
||||||
}
|
};
|
||||||
|
|
||||||
const resetBackend = () => {
|
const resetBackend = () => {
|
||||||
setTriedToLog(undefined)
|
setTriedToLog(undefined);
|
||||||
}
|
};
|
||||||
return [value, !!triedToLog, checkedSetter, resetBackend]
|
return [value, !!triedToLog, checkedSetter, resetBackend];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useBackendDefaultToken(): [string | undefined, StateUpdater<string | undefined>] {
|
export function useBackendDefaultToken(): [
|
||||||
return useLocalStorage('backend-token')
|
string | undefined,
|
||||||
|
StateUpdater<string | undefined>,
|
||||||
|
] {
|
||||||
|
return useLocalStorage("backend-token");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useBackendInstanceToken(id: string): [string | undefined, StateUpdater<string | undefined>] {
|
export function useBackendInstanceToken(
|
||||||
const [token, setToken] = useLocalStorage(`backend-token-${id}`)
|
id: string,
|
||||||
const [defaultToken, defaultSetToken] = useBackendDefaultToken()
|
): [string | undefined, StateUpdater<string | undefined>] {
|
||||||
|
const [token, setToken] = useLocalStorage(`backend-token-${id}`);
|
||||||
|
const [defaultToken, defaultSetToken] = useBackendDefaultToken();
|
||||||
|
|
||||||
// instance named 'default' use the default token
|
// instance named 'default' use the default token
|
||||||
if (id === 'default') {
|
if (id === "default") {
|
||||||
return [defaultToken, defaultSetToken]
|
return [defaultToken, defaultSetToken];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [token, setToken]
|
return [token, setToken];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLang(initial?: string): [string, StateUpdater<string>] {
|
export function useLang(initial?: string): [string, StateUpdater<string>] {
|
||||||
const browserLang = typeof window !== "undefined" ? navigator.language || (navigator as any).userLanguage : undefined;
|
const browserLang =
|
||||||
const defaultLang = (browserLang || initial || 'en').substring(0, 2)
|
typeof window !== "undefined"
|
||||||
return useNotNullLocalStorage('lang-preference', defaultLang)
|
? 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>] {
|
export function useLocalStorage(
|
||||||
const [storedValue, setStoredValue] = useState<string | undefined>((): string | undefined => {
|
key: string,
|
||||||
return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue;
|
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)) => {
|
const setValue = (
|
||||||
setStoredValue(p => {
|
value?: string | ((val?: string) => string | undefined),
|
||||||
const toStore = value instanceof Function ? value(p) : value
|
) => {
|
||||||
|
setStoredValue((p) => {
|
||||||
|
const toStore = value instanceof Function ? value(p) : value;
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
if (!toStore) {
|
if (!toStore) {
|
||||||
window.localStorage.removeItem(key)
|
window.localStorage.removeItem(key);
|
||||||
} else {
|
} else {
|
||||||
window.localStorage.setItem(key, toStore);
|
window.localStorage.setItem(key, toStore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toStore
|
return toStore;
|
||||||
})
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return [storedValue, setValue];
|
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 => {
|
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)) => {
|
const setValue = (value: string | ((val: string) => string)) => {
|
||||||
@ -97,7 +126,7 @@ export function useNotNullLocalStorage(key: string, initialValue: string): [stri
|
|||||||
setStoredValue(valueToStore);
|
setStoredValue(valueToStore);
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
if (!valueToStore) {
|
if (!valueToStore) {
|
||||||
window.localStorage.removeItem(key)
|
window.localStorage.removeItem(key);
|
||||||
} else {
|
} else {
|
||||||
window.localStorage.setItem(key, valueToStore);
|
window.localStorage.setItem(key, valueToStore);
|
||||||
}
|
}
|
||||||
@ -106,5 +135,3 @@ export function useNotNullLocalStorage(key: string, initialValue: string): [stri
|
|||||||
|
|
||||||
return [storedValue, setValue];
|
return [storedValue, setValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,23 +27,25 @@ import { useTranslationContext } from "../context/translation";
|
|||||||
|
|
||||||
export function useTranslator() {
|
export function useTranslator() {
|
||||||
const ctx = useTranslationContext();
|
const ctx = useTranslationContext();
|
||||||
const jed = ctx.handler
|
const jed = ctx.handler;
|
||||||
return function str(stringSeq: TemplateStringsArray, ...values: any[]): string {
|
return function str(
|
||||||
|
stringSeq: TemplateStringsArray,
|
||||||
|
...values: any[]
|
||||||
|
): string {
|
||||||
const s = toI18nString(stringSeq);
|
const s = toI18nString(stringSeq);
|
||||||
if (!s) return s
|
if (!s) return s;
|
||||||
const tr = jed
|
const tr = jed
|
||||||
.translate(s)
|
.translate(s)
|
||||||
.ifPlural(1, s)
|
.ifPlural(1, s)
|
||||||
.fetch(...values);
|
.fetch(...values);
|
||||||
return tr;
|
return tr;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert template strings to a msgid
|
* Convert template strings to a msgid
|
||||||
*/
|
*/
|
||||||
function toI18nString(stringSeq: ReadonlyArray<string>): string {
|
function toI18nString(stringSeq: ReadonlyArray<string>): string {
|
||||||
let s = "";
|
let s = "";
|
||||||
for (let i = 0; i < stringSeq.length; i++) {
|
for (let i = 0; i < stringSeq.length; i++) {
|
||||||
s += stringSeq[i];
|
s += stringSeq[i];
|
||||||
@ -54,7 +56,6 @@ export function useTranslator() {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface TranslateSwitchProps {
|
interface TranslateSwitchProps {
|
||||||
target: number;
|
target: number;
|
||||||
children: ComponentChildren;
|
children: ComponentChildren;
|
||||||
@ -110,7 +111,7 @@ function getTranslatedChildren(
|
|||||||
// Text
|
// Text
|
||||||
result.push(tr[i]);
|
result.push(tr[i]);
|
||||||
} else {
|
} else {
|
||||||
const childIdx = Number.parseInt(tr[i],10) - 1;
|
const childIdx = Number.parseInt(tr[i], 10) - 1;
|
||||||
result.push(placeholderChildren[childIdx]);
|
result.push(placeholderChildren[childIdx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,9 +132,9 @@ function getTranslatedChildren(
|
|||||||
*/
|
*/
|
||||||
export function Translate({ children }: TranslateProps): VNode {
|
export function Translate({ children }: TranslateProps): VNode {
|
||||||
const s = stringifyChildren(children);
|
const s = stringifyChildren(children);
|
||||||
const ctx = useTranslationContext()
|
const ctx = useTranslationContext();
|
||||||
const translation: string = ctx.handler.ngettext(s, s, 1);
|
const translation: string = ctx.handler.ngettext(s, s, 1);
|
||||||
const result = getTranslatedChildren(translation, children)
|
const result = getTranslatedChildren(translation, children);
|
||||||
return <Fragment>{result}</Fragment>;
|
return <Fragment>{result}</Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,14 +155,16 @@ export function TranslateSwitch({ children, target }: TranslateSwitchProps) {
|
|||||||
let plural: VNode<TranslationPluralProps> | undefined;
|
let plural: VNode<TranslationPluralProps> | undefined;
|
||||||
// const children = this.props.children;
|
// const children = this.props.children;
|
||||||
if (children) {
|
if (children) {
|
||||||
(children instanceof Array ? children : [children]).forEach((child: any) => {
|
(children instanceof Array ? children : [children]).forEach(
|
||||||
|
(child: any) => {
|
||||||
if (child.type === TranslatePlural) {
|
if (child.type === TranslatePlural) {
|
||||||
plural = child;
|
plural = child;
|
||||||
}
|
}
|
||||||
if (child.type === TranslateSingular) {
|
if (child.type === TranslateSingular) {
|
||||||
singular = child;
|
singular = child;
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!singular || !plural) {
|
if (!singular || !plural) {
|
||||||
console.error("translation not found");
|
console.error("translation not found");
|
||||||
@ -182,9 +185,12 @@ interface TranslationPluralProps {
|
|||||||
/**
|
/**
|
||||||
* See [[TranslateSwitch]].
|
* See [[TranslateSwitch]].
|
||||||
*/
|
*/
|
||||||
export function TranslatePlural({ children, target }: TranslationPluralProps): VNode {
|
export function TranslatePlural({
|
||||||
|
children,
|
||||||
|
target,
|
||||||
|
}: TranslationPluralProps): VNode {
|
||||||
const s = stringifyChildren(children);
|
const s = stringifyChildren(children);
|
||||||
const ctx = useTranslationContext()
|
const ctx = useTranslationContext();
|
||||||
const translation = ctx.handler.ngettext(s, s, 1);
|
const translation = ctx.handler.ngettext(s, s, 1);
|
||||||
const result = getTranslatedChildren(translation, children);
|
const result = getTranslatedChildren(translation, children);
|
||||||
return <Fragment>{result}</Fragment>;
|
return <Fragment>{result}</Fragment>;
|
||||||
@ -193,11 +199,13 @@ export function TranslatePlural({ children, target }: TranslationPluralProps): V
|
|||||||
/**
|
/**
|
||||||
* See [[TranslateSwitch]].
|
* See [[TranslateSwitch]].
|
||||||
*/
|
*/
|
||||||
export function TranslateSingular({ children, target }: TranslationPluralProps): VNode {
|
export function TranslateSingular({
|
||||||
|
children,
|
||||||
|
target,
|
||||||
|
}: TranslationPluralProps): VNode {
|
||||||
const s = stringifyChildren(children);
|
const s = stringifyChildren(children);
|
||||||
const ctx = useTranslationContext()
|
const ctx = useTranslationContext();
|
||||||
const translation = ctx.handler.ngettext(s, s, target);
|
const translation = ctx.handler.ngettext(s, s, target);
|
||||||
const result = getTranslatedChildren(translation, children);
|
const result = getTranslatedChildren(translation, children);
|
||||||
return <Fragment>{result}</Fragment>;
|
return <Fragment>{result}</Fragment>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,30 +15,30 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*eslint quote-props: ["error", "consistent"]*/
|
/*eslint quote-props: ["error", "consistent"]*/
|
||||||
export const strings: {[s: string]: any} = {};
|
export const strings: { [s: string]: any } = {};
|
||||||
|
|
||||||
strings['de'] = {
|
strings["de"] = {
|
||||||
"domain": "messages",
|
domain: "messages",
|
||||||
"locale_data": {
|
locale_data: {
|
||||||
"messages": {
|
messages: {
|
||||||
"": {
|
"": {
|
||||||
"domain": "messages",
|
domain: "messages",
|
||||||
"plural_forms": "nplurals=2; plural=(n != 1);",
|
plural_forms: "nplurals=2; plural=(n != 1);",
|
||||||
"lang": ""
|
lang: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
strings['en'] = {
|
strings["en"] = {
|
||||||
"domain": "messages",
|
domain: "messages",
|
||||||
"locale_data": {
|
locale_data: {
|
||||||
"messages": {
|
messages: {
|
||||||
"": {
|
"": {
|
||||||
"domain": "messages",
|
domain: "messages",
|
||||||
"plural_forms": "nplurals=2; plural=(n != 1);",
|
plural_forms: "nplurals=2; plural=(n != 1);",
|
||||||
"lang": ""
|
lang: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import App from './components/app';
|
import App from "./components/app";
|
||||||
import './scss/main.scss';
|
import "./scss/main.scss";
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -15,24 +15,23 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { AddingProviderScreen as TestedComponent } from './AddingProviderScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { AddingProviderScreen as TestedComponent } from "./AddingProviderScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/ManageProvider',
|
title: "Pages/ManageProvider",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,20 +39,31 @@ export const NewProvider = createExample(TestedComponent, {
|
|||||||
...reducerStatesExample.authEditing,
|
...reducerStatesExample.authEditing,
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
|
|
||||||
export const NewProviderWithoutProviderList = createExample(TestedComponent, {
|
export const NewProviderWithoutProviderList = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.authEditing,
|
...reducerStatesExample.authEditing,
|
||||||
authentication_providers: {}
|
authentication_providers: {},
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const NewVideoProvider = createExample(TestedComponent, {
|
export const NewVideoProvider = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
{
|
||||||
...reducerStatesExample.authEditing,
|
...reducerStatesExample.authEditing,
|
||||||
} as ReducerState, { providerType: 'video'});
|
} as ReducerState,
|
||||||
|
{ providerType: "video" },
|
||||||
|
);
|
||||||
|
|
||||||
export const NewSmsProvider = createExample(TestedComponent, {
|
export const NewSmsProvider = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
{
|
||||||
...reducerStatesExample.authEditing,
|
...reducerStatesExample.authEditing,
|
||||||
} as ReducerState, { providerType: 'sms'});
|
} as ReducerState,
|
||||||
|
{ providerType: "sms" },
|
||||||
|
);
|
||||||
|
|
||||||
export const NewIBANProvider = createExample(TestedComponent, {
|
export const NewIBANProvider = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
{
|
||||||
...reducerStatesExample.authEditing,
|
...reducerStatesExample.authEditing,
|
||||||
} as ReducerState, { providerType: 'iban' });
|
} as ReducerState,
|
||||||
|
{ providerType: "iban" },
|
||||||
|
);
|
||||||
|
@ -11,185 +11,250 @@ interface Props {
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function testProvider(
|
||||||
async function testProvider(url: string, expectedMethodType?: string): Promise<void> {
|
url: string,
|
||||||
|
expectedMethodType?: string,
|
||||||
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(new URL("config", url).href)
|
const response = await fetch(new URL("config", url).href);
|
||||||
const json = await (response.json().catch(d => ({})))
|
const json = await response.json().catch((d) => ({}));
|
||||||
if (!("methods" in json) || !Array.isArray(json.methods)) {
|
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) {
|
if (!expectedMethodType) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
let found = false
|
let found = false;
|
||||||
for (let i = 0; i < json.methods.length && !found; i++) {
|
for (let i = 0; i < json.methods.length && !found; i++) {
|
||||||
found = json.methods[i].type === expectedMethodType
|
found = json.methods[i].type === expectedMethodType;
|
||||||
}
|
}
|
||||||
if (!found) {
|
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) {
|
} catch (e) {
|
||||||
console.log("error", e)
|
console.log("error", e);
|
||||||
const error = e instanceof Error ?
|
const error =
|
||||||
Error(`There was an error testing this provider, try another one. ${e.message}`) :
|
e instanceof Error
|
||||||
Error(`There was an error testing this provider, try another one.`)
|
? Error(
|
||||||
throw 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 {
|
export function AddingProviderScreen({ providerType, onCancel }: Props): VNode {
|
||||||
const reducer = useAnastasisContext();
|
const reducer = useAnastasisContext();
|
||||||
|
|
||||||
const [providerURL, setProviderURL] = useState("");
|
const [providerURL, setProviderURL] = useState("");
|
||||||
const [error, setError] = useState<string | undefined>()
|
const [error, setError] = useState<string | undefined>();
|
||||||
const [testing, setTesting] = useState(false)
|
const [testing, setTesting] = useState(false);
|
||||||
const providerLabel = providerType ? authMethods[providerType].label : undefined
|
const providerLabel = providerType
|
||||||
|
? authMethods[providerType].label
|
||||||
|
: undefined;
|
||||||
|
|
||||||
//FIXME: move this timeout logic into a hook
|
//FIXME: move this timeout logic into a hook
|
||||||
const timeout = useRef<number | undefined>(undefined);
|
const timeout = useRef<number | undefined>(undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timeout) window.clearTimeout(timeout.current)
|
if (timeout) window.clearTimeout(timeout.current);
|
||||||
timeout.current = window.setTimeout(async () => {
|
timeout.current = window.setTimeout(async () => {
|
||||||
const url = providerURL.endsWith('/') ? providerURL : (providerURL + '/')
|
const url = providerURL.endsWith("/") ? providerURL : providerURL + "/";
|
||||||
if (!providerURL || authProviders.includes(url)) return;
|
if (!providerURL || authProviders.includes(url)) return;
|
||||||
try {
|
try {
|
||||||
setTesting(true)
|
setTesting(true);
|
||||||
await testProvider(url, providerType)
|
await testProvider(url, providerType);
|
||||||
// this is use as tested but everything when ok
|
// this is use as tested but everything when ok
|
||||||
// undefined will mean that the field is not dirty
|
// undefined will mean that the field is not dirty
|
||||||
setError("")
|
setError("");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("tuvieja", e)
|
console.log("tuvieja", e);
|
||||||
if (e instanceof Error) setError(e.message)
|
if (e instanceof Error) setError(e.message);
|
||||||
}
|
}
|
||||||
setTesting(false)
|
setTesting(false);
|
||||||
}, 200);
|
}, 200);
|
||||||
}, [providerURL, reducer])
|
}, [providerURL, reducer]);
|
||||||
|
|
||||||
|
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>;
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reducer.currentReducerState || !("authentication_providers" in reducer.currentReducerState)) {
|
if (
|
||||||
return <div>invalid state</div>
|
!reducer.currentReducerState ||
|
||||||
|
!("authentication_providers" in reducer.currentReducerState)
|
||||||
|
) {
|
||||||
|
return <div>invalid state</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addProvider(provider_url: string): Promise<void> {
|
async function addProvider(provider_url: string): Promise<void> {
|
||||||
await reducer?.transition("add_provider", { provider_url })
|
await reducer?.transition("add_provider", { provider_url });
|
||||||
onCancel()
|
onCancel();
|
||||||
}
|
}
|
||||||
function deleteProvider(provider_url: string): void {
|
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 allAuthProviders =
|
||||||
const authProviders = Object.keys(allAuthProviders).filter(provUrl => {
|
reducer.currentReducerState.authentication_providers || {};
|
||||||
|
const authProviders = Object.keys(allAuthProviders).filter((provUrl) => {
|
||||||
const p = allAuthProviders[provUrl];
|
const p = allAuthProviders[provUrl];
|
||||||
if (!providerLabel) {
|
if (!providerLabel) {
|
||||||
return p && ("currency" in p)
|
return p && "currency" in p;
|
||||||
} else {
|
} 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;
|
let url: string | undefined;
|
||||||
try {
|
try {
|
||||||
url = new URL("",providerURL).href
|
url = new URL("", providerURL).href;
|
||||||
} catch {
|
} catch {
|
||||||
errors = 'Check the URL'
|
errors = "Check the URL";
|
||||||
}
|
}
|
||||||
if (!!error && !errors) {
|
if (!!error && !errors) {
|
||||||
errors = error
|
errors = error;
|
||||||
}
|
}
|
||||||
if (!errors && authProviders.includes(url!)) {
|
if (!errors && authProviders.includes(url!)) {
|
||||||
errors = 'That provider is already known'
|
errors = "That provider is already known";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav
|
<AnastasisClientFrame
|
||||||
|
hideNav
|
||||||
title="Backup: Manage providers"
|
title="Backup: Manage providers"
|
||||||
hideNext={errors}>
|
hideNext={errors}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
{!providerLabel ?
|
{!providerLabel ? (
|
||||||
<p>
|
<p>Add a provider url</p>
|
||||||
Add a provider url
|
) : (
|
||||||
</p> :
|
<p>Add a provider url for a {providerLabel} service</p>
|
||||||
<p>
|
)}
|
||||||
Add a provider url for a {providerLabel} service
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Provider URL"
|
label="Provider URL"
|
||||||
placeholder="https://provider.com"
|
placeholder="https://provider.com"
|
||||||
grabFocus
|
grabFocus
|
||||||
error={errors}
|
error={errors}
|
||||||
bind={[providerURL, setProviderURL]} />
|
bind={[providerURL, setProviderURL]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p class="block">
|
<p class="block">Example: https://kudos.demo.anastasis.lu</p>
|
||||||
Example: https://kudos.demo.anastasis.lu
|
|
||||||
</p>
|
|
||||||
{testing && <p class="has-text-info">Testing</p>}
|
{testing && <p class="has-text-info">Testing</p>}
|
||||||
|
|
||||||
<div class="block" style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={onCancel}>Cancel</button>
|
class="block"
|
||||||
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={onCancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
<span data-tooltip={errors}>
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{authProviders.length > 0 ? (
|
{authProviders.length > 0 ? (
|
||||||
!providerLabel ?
|
!providerLabel ? (
|
||||||
|
<p class="subtitle">Current providers</p>
|
||||||
|
) : (
|
||||||
<p class="subtitle">
|
<p class="subtitle">
|
||||||
Current providers
|
|
||||||
</p> : <p class="subtitle">
|
|
||||||
Current providers for {providerLabel} service
|
Current providers for {providerLabel} service
|
||||||
</p>
|
</p>
|
||||||
|
)
|
||||||
|
) : !providerLabel ? (
|
||||||
|
<p class="subtitle">No known providers, add one.</p>
|
||||||
) : (
|
) : (
|
||||||
!providerLabel ? <p class="subtitle">
|
<p class="subtitle">No known providers for {providerLabel} service</p>
|
||||||
No known providers, add one.
|
|
||||||
</p> : <p class="subtitle">
|
|
||||||
No known providers for {providerLabel} service
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{authProviders.map(k => {
|
{authProviders.map((k) => {
|
||||||
const p = allAuthProviders[k] as AuthenticationProviderStatusOk
|
const p = allAuthProviders[k] as AuthenticationProviderStatusOk;
|
||||||
return <TableRow url={k} info={p} onDelete={deleteProvider} />
|
return <TableRow url={k} info={p} onDelete={deleteProvider} />;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function TableRow({ url, info, onDelete }: { onDelete: (s: string) => void, url: string, info: AuthenticationProviderStatusOk }) {
|
function TableRow({
|
||||||
const [status, setStatus] = useState("checking")
|
url,
|
||||||
|
info,
|
||||||
|
onDelete,
|
||||||
|
}: {
|
||||||
|
onDelete: (s: string) => void;
|
||||||
|
url: string;
|
||||||
|
info: AuthenticationProviderStatusOk;
|
||||||
|
}) {
|
||||||
|
const [status, setStatus] = useState("checking");
|
||||||
useEffect(function () {
|
useEffect(function () {
|
||||||
testProvider(url.endsWith('/') ? url.substring(0, url.length - 1) : url)
|
testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url)
|
||||||
.then(function () { setStatus('responding') })
|
.then(function () {
|
||||||
.catch(function () { setStatus('failed to contact') })
|
setStatus("responding");
|
||||||
})
|
})
|
||||||
return <div class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
.catch(function () {
|
||||||
|
setStatus("failed to contact");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class="box"
|
||||||
|
style={{ display: "flex", justifyContent: "space-between" }}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<div class="subtitle">{url}</div>
|
<div class="subtitle">{url}</div>
|
||||||
<dl>
|
<dl>
|
||||||
<dt><b>Business Name</b></dt>
|
<dt>
|
||||||
|
<b>Business Name</b>
|
||||||
|
</dt>
|
||||||
<dd>{info.business_name}</dd>
|
<dd>{info.business_name}</dd>
|
||||||
<dt><b>Supported methods</b></dt>
|
<dt>
|
||||||
<dd>{info.methods.map(m => m.type).join(',')}</dd>
|
<b>Supported methods</b>
|
||||||
<dt><b>Maximum storage</b></dt>
|
</dt>
|
||||||
|
<dd>{info.methods.map((m) => m.type).join(",")}</dd>
|
||||||
|
<dt>
|
||||||
|
<b>Maximum storage</b>
|
||||||
|
</dt>
|
||||||
<dd>{info.storage_limit_in_megabytes} Mb</dd>
|
<dd>{info.storage_limit_in_megabytes} Mb</dd>
|
||||||
<dt><b>Status</b></dt>
|
<dt>
|
||||||
|
<b>Status</b>
|
||||||
|
</dt>
|
||||||
<dd>{status}</dd>
|
<dd>{status}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div class="block" style={{ marginTop: 'auto', marginBottom: 'auto', display: 'flex', justifyContent: 'space-between', flexDirection: 'column' }}>
|
<div
|
||||||
<button class="button is-danger" onClick={() => onDelete(url)}>Remove</button>
|
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>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
@ -15,76 +15,83 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { AttributeEntryScreen as TestedComponent } from './AttributeEntryScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { AttributeEntryScreen as TestedComponent } from "./AttributeEntryScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/PersonalInformation',
|
title: "Pages/PersonalInformation",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 3,
|
order: 3,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Backup = createExample(TestedComponent, {
|
export const Backup = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.backupAttributeEditing,
|
...reducerStatesExample.backupAttributeEditing,
|
||||||
required_attributes: [{
|
required_attributes: [
|
||||||
name: 'first name',
|
{
|
||||||
label: 'first',
|
name: "first name",
|
||||||
type: 'string',
|
label: "first",
|
||||||
uuid: 'asdasdsa1',
|
type: "string",
|
||||||
widget: 'wid',
|
uuid: "asdasdsa1",
|
||||||
}, {
|
widget: "wid",
|
||||||
name: 'last name',
|
},
|
||||||
label: 'second',
|
{
|
||||||
type: 'string',
|
name: "last name",
|
||||||
uuid: 'asdasdsa2',
|
label: "second",
|
||||||
widget: 'wid',
|
type: "string",
|
||||||
}, {
|
uuid: "asdasdsa2",
|
||||||
name: 'birthdate',
|
widget: "wid",
|
||||||
label: 'birthdate',
|
},
|
||||||
type: 'date',
|
{
|
||||||
uuid: 'asdasdsa3',
|
name: "birthdate",
|
||||||
widget: 'calendar',
|
label: "birthdate",
|
||||||
}]
|
type: "date",
|
||||||
|
uuid: "asdasdsa3",
|
||||||
|
widget: "calendar",
|
||||||
|
},
|
||||||
|
],
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const Recovery = createExample(TestedComponent, {
|
export const Recovery = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.recoveryAttributeEditing,
|
...reducerStatesExample.recoveryAttributeEditing,
|
||||||
required_attributes: [{
|
required_attributes: [
|
||||||
name: 'first',
|
{
|
||||||
label: 'first',
|
name: "first",
|
||||||
type: 'string',
|
label: "first",
|
||||||
uuid: 'asdasdsa1',
|
type: "string",
|
||||||
widget: 'wid',
|
uuid: "asdasdsa1",
|
||||||
}, {
|
widget: "wid",
|
||||||
name: 'pepe',
|
},
|
||||||
label: 'second',
|
{
|
||||||
type: 'string',
|
name: "pepe",
|
||||||
uuid: 'asdasdsa2',
|
label: "second",
|
||||||
widget: 'wid',
|
type: "string",
|
||||||
}, {
|
uuid: "asdasdsa2",
|
||||||
name: 'pepe2',
|
widget: "wid",
|
||||||
label: 'third',
|
},
|
||||||
type: 'date',
|
{
|
||||||
uuid: 'asdasdsa3',
|
name: "pepe2",
|
||||||
widget: 'calendar',
|
label: "third",
|
||||||
}]
|
type: "date",
|
||||||
|
uuid: "asdasdsa3",
|
||||||
|
widget: "calendar",
|
||||||
|
},
|
||||||
|
],
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const WithNoRequiredAttribute = createExample(TestedComponent, {
|
export const WithNoRequiredAttribute = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.backupAttributeEditing,
|
...reducerStatesExample.backupAttributeEditing,
|
||||||
required_attributes: undefined
|
required_attributes: undefined,
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
const allWidgets = [
|
const allWidgets = [
|
||||||
@ -107,23 +114,22 @@ const allWidgets = [
|
|||||||
"anastasis_gtk_ia_tax_de",
|
"anastasis_gtk_ia_tax_de",
|
||||||
"anastasis_gtk_xx_prime",
|
"anastasis_gtk_xx_prime",
|
||||||
"anastasis_gtk_xx_square",
|
"anastasis_gtk_xx_square",
|
||||||
]
|
];
|
||||||
|
|
||||||
function typeForWidget(name: string): string {
|
function typeForWidget(name: string): string {
|
||||||
if (["anastasis_gtk_xx_prime",
|
if (["anastasis_gtk_xx_prime", "anastasis_gtk_xx_square"].includes(name))
|
||||||
"anastasis_gtk_xx_square",
|
return "number";
|
||||||
].includes(name)) return "number";
|
if (["anastasis_gtk_ia_birthdate"].includes(name)) return "date";
|
||||||
if (["anastasis_gtk_ia_birthdate"].includes(name)) return "date"
|
|
||||||
return "string";
|
return "string";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WithAllPosibleWidget = createExample(TestedComponent, {
|
export const WithAllPosibleWidget = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.backupAttributeEditing,
|
...reducerStatesExample.backupAttributeEditing,
|
||||||
required_attributes: allWidgets.map(w => ({
|
required_attributes: allWidgets.map((w) => ({
|
||||||
name: w,
|
name: w,
|
||||||
label: `widget: ${w}`,
|
label: `widget: ${w}`,
|
||||||
type: typeForWidget(w),
|
type: typeForWidget(w),
|
||||||
uuid: `uuid-${w}`,
|
uuid: `uuid-${w}`,
|
||||||
widget: w
|
widget: w,
|
||||||
}))
|
})),
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
@ -9,24 +9,32 @@ import { useAnastasisContext } from "../../context/anastasis";
|
|||||||
import { AnastasisClientFrame, withProcessLabel } from "./index";
|
import { AnastasisClientFrame, withProcessLabel } from "./index";
|
||||||
|
|
||||||
export function AttributeEntryScreen(): VNode {
|
export function AttributeEntryScreen(): VNode {
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext();
|
||||||
const state = reducer?.currentReducerState
|
const state = reducer?.currentReducerState;
|
||||||
const currentIdentityAttributes = state && "identity_attributes" in state ? (state.identity_attributes || {}) : {}
|
const currentIdentityAttributes =
|
||||||
const [attrs, setAttrs] = useState<Record<string, string>>(currentIdentityAttributes);
|
state && "identity_attributes" in state
|
||||||
|
? state.identity_attributes || {}
|
||||||
|
: {};
|
||||||
|
const [attrs, setAttrs] = useState<Record<string, string>>(
|
||||||
|
currentIdentityAttributes,
|
||||||
|
);
|
||||||
|
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || !("required_attributes" in reducer.currentReducerState)) {
|
if (
|
||||||
return <div>invalid state</div>
|
!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;
|
let hasErrors = false;
|
||||||
|
|
||||||
const fieldList: VNode[] = reqAttr.map((spec, i: number) => {
|
const fieldList: VNode[] = reqAttr.map((spec, i: number) => {
|
||||||
const value = attrs[spec.name]
|
const value = attrs[spec.name];
|
||||||
const error = checkIfValid(value, spec)
|
const error = checkIfValid(value, spec);
|
||||||
hasErrors = hasErrors || error !== undefined
|
hasErrors = hasErrors || error !== undefined;
|
||||||
return (
|
return (
|
||||||
<AttributeEntryField
|
<AttributeEntryField
|
||||||
key={i}
|
key={i}
|
||||||
@ -34,23 +42,24 @@ export function AttributeEntryScreen(): VNode {
|
|||||||
setValue={(v: string) => setAttrs({ ...attrs, [spec.name]: v })}
|
setValue={(v: string) => setAttrs({ ...attrs, [spec.name]: v })}
|
||||||
spec={spec}
|
spec={spec}
|
||||||
errorMessage={error}
|
errorMessage={error}
|
||||||
value={value} />
|
value={value}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame
|
<AnastasisClientFrame
|
||||||
title={withProcessLabel(reducer, "Who are you?")}
|
title={withProcessLabel(reducer, "Who are you?")}
|
||||||
hideNext={hasErrors ? "Complete the form." : undefined}
|
hideNext={hasErrors ? "Complete the form." : undefined}
|
||||||
onNext={() => reducer.transition("enter_user_attributes", {
|
onNext={() =>
|
||||||
|
reducer.transition("enter_user_attributes", {
|
||||||
identity_attributes: attrs,
|
identity_attributes: attrs,
|
||||||
})}
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div class="columns" style={{ maxWidth: 'unset' }}>
|
<div class="columns" style={{ maxWidth: "unset" }}>
|
||||||
|
<div class="column">{fieldList}</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{fieldList}
|
|
||||||
</div>
|
|
||||||
<div class="column" >
|
|
||||||
<p>This personal information will help to locate your secret.</p>
|
<p>This personal information will help to locate your secret.</p>
|
||||||
<h1 class="title">This stays private</h1>
|
<h1 class="title">This stays private</h1>
|
||||||
<p>The information you have entered here:</p>
|
<p>The information you have entered here:</p>
|
||||||
@ -61,9 +70,12 @@ export function AttributeEntryScreen(): VNode {
|
|||||||
</span>
|
</span>
|
||||||
Will be hashed, and therefore unreadable
|
Will be hashed, and therefore unreadable
|
||||||
</li>
|
</li>
|
||||||
<li><span class="icon is-right">
|
<li>
|
||||||
|
<span class="icon is-right">
|
||||||
<i class="mdi mdi-circle-small" />
|
<i class="mdi mdi-circle-small" />
|
||||||
</span>The non-hashed version is not shared</li>
|
</span>
|
||||||
|
The non-hashed version is not shared
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -78,22 +90,22 @@ interface AttributeEntryFieldProps {
|
|||||||
spec: UserAttributeSpec;
|
spec: UserAttributeSpec;
|
||||||
errorMessage: string | undefined;
|
errorMessage: string | undefined;
|
||||||
}
|
}
|
||||||
const possibleBirthdayYear: Array<number> = []
|
const possibleBirthdayYear: Array<number> = [];
|
||||||
for (let i = 0; i < 100; i++) {
|
for (let i = 0; i < 100; i++) {
|
||||||
possibleBirthdayYear.push(2020 - i)
|
possibleBirthdayYear.push(2020 - i);
|
||||||
}
|
}
|
||||||
function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
|
function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{props.spec.type === 'date' &&
|
{props.spec.type === "date" &&
|
||||||
<DateInput
|
<DateInput
|
||||||
grabFocus={props.isFirst}
|
grabFocus={props.isFirst}
|
||||||
label={props.spec.label}
|
label={props.spec.label}
|
||||||
years={possibleBirthdayYear}
|
years={possibleBirthdayYear}
|
||||||
error={props.errorMessage}
|
error={props.errorMessage}
|
||||||
bind={[props.value, props.setValue]}
|
bind={[props.value, props.setValue]}
|
||||||
/>}
|
/>
|
||||||
|
}
|
||||||
{props.spec.type === 'number' &&
|
{props.spec.type === 'number' &&
|
||||||
<PhoneNumberInput
|
<PhoneNumberInput
|
||||||
grabFocus={props.isFirst}
|
grabFocus={props.isFirst}
|
||||||
@ -102,14 +114,14 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
|
|||||||
bind={[props.value, props.setValue]}
|
bind={[props.value, props.setValue]}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{props.spec.type === 'string' &&
|
{props.spec.type === "string" && (
|
||||||
<TextInput
|
<TextInput
|
||||||
grabFocus={props.isFirst}
|
grabFocus={props.isFirst}
|
||||||
label={props.spec.label}
|
label={props.spec.label}
|
||||||
error={props.errorMessage}
|
error={props.errorMessage}
|
||||||
bind={[props.value, props.setValue]}
|
bind={[props.value, props.setValue]}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
This stays private
|
This stays private
|
||||||
<span class="icon is-right">
|
<span class="icon is-right">
|
||||||
@ -119,40 +131,43 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/
|
const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/;
|
||||||
|
|
||||||
|
function checkIfValid(
|
||||||
function checkIfValid(value: string, spec: UserAttributeSpec): string | undefined {
|
value: string,
|
||||||
const pattern = spec['validation-regex']
|
spec: UserAttributeSpec,
|
||||||
|
): string | undefined {
|
||||||
|
const pattern = spec["validation-regex"];
|
||||||
if (pattern) {
|
if (pattern) {
|
||||||
const re = new RegExp(pattern)
|
const re = new RegExp(pattern);
|
||||||
if (!re.test(value)) return 'The value is invalid'
|
if (!re.test(value)) return "The value is invalid";
|
||||||
}
|
}
|
||||||
const logic = spec['validation-logic']
|
const logic = spec["validation-logic"];
|
||||||
if (logic) {
|
if (logic) {
|
||||||
const func = (validators as any)[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) {
|
if (!optional && !value) {
|
||||||
return 'This value is required'
|
return "This value is required";
|
||||||
}
|
}
|
||||||
if ("date" === spec.type) {
|
if ("date" === spec.type) {
|
||||||
if (!YEAR_REGEX.test(value)) {
|
if (!YEAR_REGEX.test(value)) {
|
||||||
return "The date doesn't follow the format"
|
return "The date doesn't follow the format";
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const v = parse(value, 'yyyy-MM-dd', new Date());
|
const v = parse(value, "yyyy-MM-dd", new Date());
|
||||||
if (Number.isNaN(v.getTime())) {
|
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())) {
|
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) {
|
} catch (e) {
|
||||||
return "Could not parse the date"
|
return "Could not parse the date";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -15,73 +15,84 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { AuthenticationEditorScreen as TestedComponent } from './AuthenticationEditorScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { AuthenticationEditorScreen as TestedComponent } from "./AuthenticationEditorScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/AuthorizationMethod',
|
title: "Pages/backup/AuthorizationMethod",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 4,
|
order: 4,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InitialState = createExample(TestedComponent, reducerStatesExample.authEditing);
|
export const InitialState = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
reducerStatesExample.authEditing,
|
||||||
|
);
|
||||||
export const OneAuthMethodConfigured = createExample(TestedComponent, {
|
export const OneAuthMethodConfigured = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.authEditing,
|
...reducerStatesExample.authEditing,
|
||||||
authentication_methods: [{
|
authentication_methods: [
|
||||||
type: 'question',
|
{
|
||||||
instructions: 'what time is it?',
|
type: "question",
|
||||||
challenge: 'asd',
|
instructions: "what time is it?",
|
||||||
}]
|
challenge: "asd",
|
||||||
|
},
|
||||||
|
],
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
|
|
||||||
export const SomeMoreAuthMethodConfigured = createExample(TestedComponent, {
|
export const SomeMoreAuthMethodConfigured = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.authEditing,
|
...reducerStatesExample.authEditing,
|
||||||
authentication_methods: [{
|
authentication_methods: [
|
||||||
type: 'question',
|
{
|
||||||
instructions: 'what time is it?',
|
type: "question",
|
||||||
challenge: 'asd',
|
instructions: "what time is it?",
|
||||||
},{
|
challenge: "asd",
|
||||||
type: 'question',
|
},
|
||||||
instructions: 'what time is it?',
|
{
|
||||||
challenge: 'qwe',
|
type: "question",
|
||||||
},{
|
instructions: "what time is it?",
|
||||||
type: 'sms',
|
challenge: "qwe",
|
||||||
instructions: 'what time is it?',
|
},
|
||||||
challenge: 'asd',
|
{
|
||||||
},{
|
type: "sms",
|
||||||
type: 'email',
|
instructions: "what time is it?",
|
||||||
instructions: 'what time is it?',
|
challenge: "asd",
|
||||||
challenge: 'asd',
|
},
|
||||||
},{
|
{
|
||||||
type: 'email',
|
type: "email",
|
||||||
instructions: 'what time is it?',
|
instructions: "what time is it?",
|
||||||
challenge: 'asd',
|
challenge: "asd",
|
||||||
},{
|
},
|
||||||
type: 'email',
|
{
|
||||||
instructions: 'what time is it?',
|
type: "email",
|
||||||
challenge: 'asd',
|
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);
|
} as ReducerState);
|
||||||
|
|
||||||
export const NoAuthMethodProvided = createExample(TestedComponent, {
|
export const NoAuthMethodProvided = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.authEditing,
|
...reducerStatesExample.authEditing,
|
||||||
authentication_providers: {},
|
authentication_providers: {},
|
||||||
authentication_methods: []
|
authentication_methods: [],
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
@ -20,7 +20,9 @@ export function AuthenticationEditorScreen(): VNode {
|
|||||||
KnownAuthMethods | undefined
|
KnownAuthMethods | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
const [tooFewAuths, setTooFewAuths] = useState(false);
|
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 [addingProvider, setAddingProvider] = useState<string | undefined>(undefined)
|
||||||
const reducer = useAnastasisContext();
|
const reducer = useAnastasisContext();
|
||||||
@ -68,11 +70,14 @@ export function AuthenticationEditorScreen(): VNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (manageProvider !== undefined) {
|
if (manageProvider !== undefined) {
|
||||||
|
return (
|
||||||
return <AddingProviderScreen
|
<AddingProviderScreen
|
||||||
onCancel={() => setManageProvider(undefined)}
|
onCancel={() => setManageProvider(undefined)}
|
||||||
providerType={isKnownAuthMethods(manageProvider) ? manageProvider : undefined}
|
providerType={
|
||||||
|
isKnownAuthMethods(manageProvider) ? manageProvider : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedMethod) {
|
if (selectedMethod) {
|
||||||
@ -100,7 +105,7 @@ export function AuthenticationEditorScreen(): VNode {
|
|||||||
description="No providers founds"
|
description="No providers founds"
|
||||||
label="Add a provider manually"
|
label="Add a provider manually"
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
setManageProvider(selectedMethod)
|
setManageProvider(selectedMethod);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
@ -193,7 +198,7 @@ export function AuthenticationEditorScreen(): VNode {
|
|||||||
description="No providers founds"
|
description="No providers founds"
|
||||||
label="Add a provider manually"
|
label="Add a provider manually"
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
setManageProvider("")
|
setManageProvider("");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
@ -214,7 +219,10 @@ export function AuthenticationEditorScreen(): VNode {
|
|||||||
authentication method is defined by the backup provider list.
|
authentication method is defined by the backup provider list.
|
||||||
</p>
|
</p>
|
||||||
<p class="block">
|
<p class="block">
|
||||||
<button class="button is-info" onClick={() => setManageProvider("")}>
|
<button
|
||||||
|
class="button is-info"
|
||||||
|
onClick={() => setManageProvider("")}
|
||||||
|
>
|
||||||
Manage backup providers
|
Manage backup providers
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
@ -15,48 +15,51 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { BackupFinishedScreen as TestedComponent } from './BackupFinishedScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { BackupFinishedScreen as TestedComponent } from "./BackupFinishedScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/Finished',
|
title: "Pages/backup/Finished",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 8,
|
order: 8,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithoutName = createExample(TestedComponent, reducerStatesExample.backupFinished);
|
export const WithoutName = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
reducerStatesExample.backupFinished,
|
||||||
|
);
|
||||||
|
|
||||||
export const WithName = createExample(TestedComponent, {...reducerStatesExample.backupFinished,
|
export const WithName = createExample(TestedComponent, {
|
||||||
secret_name: 'super_secret',
|
...reducerStatesExample.backupFinished,
|
||||||
|
secret_name: "super_secret",
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const WithDetails = createExample(TestedComponent, {
|
export const WithDetails = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.backupFinished,
|
...reducerStatesExample.backupFinished,
|
||||||
secret_name: 'super_secret',
|
secret_name: "super_secret",
|
||||||
success_details: {
|
success_details: {
|
||||||
'http://anastasis.net': {
|
"http://anastasis.net": {
|
||||||
policy_expiration: {
|
policy_expiration: {
|
||||||
t_ms: 'never'
|
t_ms: "never",
|
||||||
},
|
},
|
||||||
policy_version: 0
|
policy_version: 0,
|
||||||
},
|
},
|
||||||
'http://taler.net': {
|
"http://taler.net": {
|
||||||
policy_expiration: {
|
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);
|
} as ReducerState);
|
||||||
|
@ -4,25 +4,31 @@ import { useAnastasisContext } from "../../context/anastasis";
|
|||||||
import { AnastasisClientFrame } from "./index";
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
export function BackupFinishedScreen(): VNode {
|
export function BackupFinishedScreen(): VNode {
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext();
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
|
if (
|
||||||
return <div>invalid state</div>
|
!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">
|
return (
|
||||||
{reducer.currentReducerState.secret_name ? <p>
|
<AnastasisClientFrame hideNav title="Backup finished">
|
||||||
Your backup of secret <b>"{reducer.currentReducerState.secret_name}"</b> was
|
{reducer.currentReducerState.secret_name ? (
|
||||||
successful.
|
|
||||||
</p> :
|
|
||||||
<p>
|
<p>
|
||||||
Your secret was successfully backed up.
|
Your backup of secret{" "}
|
||||||
</p>}
|
<b>"{reducer.currentReducerState.secret_name}"</b> was successful.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p>Your secret was successfully backed up.</p>
|
||||||
|
)}
|
||||||
|
|
||||||
{details && <div class="block">
|
{details && (
|
||||||
|
<div class="block">
|
||||||
<p>The backup is stored by the following providers:</p>
|
<p>The backup is stored by the following providers:</p>
|
||||||
{Object.keys(details).map((x, i) => {
|
{Object.keys(details).map((x, i) => {
|
||||||
const sd = details[x];
|
const sd = details[x];
|
||||||
@ -31,14 +37,29 @@ export function BackupFinishedScreen(): VNode {
|
|||||||
{x}
|
{x}
|
||||||
<p>
|
<p>
|
||||||
version {sd.policy_version}
|
version {sd.policy_version}
|
||||||
{sd.policy_expiration.t_ms !== 'never' ? ` expires at: ${format(sd.policy_expiration.t_ms, 'dd-MM-yyyy')}` : ' without expiration date'}
|
{sd.policy_expiration.t_ms !== "never"
|
||||||
|
? ` expires at: ${format(
|
||||||
|
sd.policy_expiration.t_ms,
|
||||||
|
"dd-MM-yyyy",
|
||||||
|
)}`
|
||||||
|
: " without expiration date"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>}
|
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>);
|
)}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,11 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @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 { createExample, reducerStatesExample } from "../../utils";
|
||||||
import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen";
|
import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen";
|
||||||
|
|
||||||
@ -247,20 +251,20 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample(
|
|||||||
"uuid-1": { state: ChallengeFeedbackStatus.Solved.toString() },
|
"uuid-1": { state: ChallengeFeedbackStatus.Solved.toString() },
|
||||||
"uuid-2": {
|
"uuid-2": {
|
||||||
state: ChallengeFeedbackStatus.Message.toString(),
|
state: ChallengeFeedbackStatus.Message.toString(),
|
||||||
message: 'Challenge should be solved'
|
message: "Challenge should be solved",
|
||||||
},
|
},
|
||||||
"uuid-3": {
|
"uuid-3": {
|
||||||
state: ChallengeFeedbackStatus.AuthIban.toString(),
|
state: ChallengeFeedbackStatus.AuthIban.toString(),
|
||||||
challenge_amount: "EUR:1",
|
challenge_amount: "EUR:1",
|
||||||
credit_iban: "DE12345789000",
|
credit_iban: "DE12345789000",
|
||||||
business_name: "Data Loss Incorporated",
|
business_name: "Data Loss Incorporated",
|
||||||
wire_transfer_subject: "Anastasis 987654321"
|
wire_transfer_subject: "Anastasis 987654321",
|
||||||
},
|
},
|
||||||
"uuid-4": {
|
"uuid-4": {
|
||||||
state: ChallengeFeedbackStatus.Payment.toString(),
|
state: ChallengeFeedbackStatus.Payment.toString(),
|
||||||
taler_pay_uri: "taler://pay/...",
|
taler_pay_uri: "taler://pay/...",
|
||||||
provider: "https://localhost:8080/",
|
provider: "https://localhost:8080/",
|
||||||
payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG"
|
payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG",
|
||||||
},
|
},
|
||||||
"uuid-5": {
|
"uuid-5": {
|
||||||
state: ChallengeFeedbackStatus.RateLimitExceeded.toString(),
|
state: ChallengeFeedbackStatus.RateLimitExceeded.toString(),
|
||||||
@ -269,7 +273,7 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample(
|
|||||||
"uuid-6": {
|
"uuid-6": {
|
||||||
state: ChallengeFeedbackStatus.Redirect.toString(),
|
state: ChallengeFeedbackStatus.Redirect.toString(),
|
||||||
redirect_url: "https://videoconf.example.com/",
|
redirect_url: "https://videoconf.example.com/",
|
||||||
http_status: 303
|
http_status: 303,
|
||||||
},
|
},
|
||||||
"uuid-7": {
|
"uuid-7": {
|
||||||
state: ChallengeFeedbackStatus.ServerFailure.toString(),
|
state: ChallengeFeedbackStatus.ServerFailure.toString(),
|
||||||
|
@ -11,23 +11,34 @@ function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) {
|
|||||||
}
|
}
|
||||||
switch (feedback.state) {
|
switch (feedback.state) {
|
||||||
case ChallengeFeedbackStatus.Message:
|
case ChallengeFeedbackStatus.Message:
|
||||||
return (
|
return <div class="block has-text-danger">{feedback.message}</div>;
|
||||||
<div class="block has-text-danger">{feedback.message}</div>
|
|
||||||
);
|
|
||||||
case ChallengeFeedbackStatus.Solved:
|
case ChallengeFeedbackStatus.Solved:
|
||||||
return <div />
|
return <div />;
|
||||||
case ChallengeFeedbackStatus.Pending:
|
case ChallengeFeedbackStatus.Pending:
|
||||||
case ChallengeFeedbackStatus.Solved:
|
|
||||||
case ChallengeFeedbackStatus.AuthIban:
|
case ChallengeFeedbackStatus.AuthIban:
|
||||||
return null;
|
return null;
|
||||||
case ChallengeFeedbackStatus.ServerFailure:
|
case ChallengeFeedbackStatus.ServerFailure:
|
||||||
return <div class="block has-text-danger">Server error.</div>;
|
return <div class="block has-text-danger">Server error.</div>;
|
||||||
case ChallengeFeedbackStatus.RateLimitExceeded:
|
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:
|
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:
|
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:
|
case ChallengeFeedbackStatus.Redirect:
|
||||||
default:
|
default:
|
||||||
return <div />;
|
return <div />;
|
||||||
@ -70,7 +81,8 @@ export function ChallengeOverviewScreen(): VNode {
|
|||||||
feedback: challengeFeedback[ch.uuid],
|
feedback: challengeFeedback[ch.uuid],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const policiesWithInfo = policies.map((row) => {
|
const policiesWithInfo = policies
|
||||||
|
.map((row) => {
|
||||||
let isPolicySolved = true;
|
let isPolicySolved = true;
|
||||||
const challenges = row
|
const challenges = row
|
||||||
.map(({ uuid }) => {
|
.map(({ uuid }) => {
|
||||||
@ -81,8 +93,13 @@ export function ChallengeOverviewScreen(): VNode {
|
|||||||
})
|
})
|
||||||
.filter((ch) => ch.info !== undefined);
|
.filter((ch) => ch.info !== undefined);
|
||||||
|
|
||||||
return { isPolicySolved, challenges };
|
return {
|
||||||
});
|
isPolicySolved,
|
||||||
|
challenges,
|
||||||
|
corrupted: row.length > challenges.length,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((p) => !p.corrupted);
|
||||||
|
|
||||||
const atLeastThereIsOnePolicySolved =
|
const atLeastThereIsOnePolicySolved =
|
||||||
policiesWithInfo.find((p) => p.isPolicySolved) !== undefined;
|
policiesWithInfo.find((p) => p.isPolicySolved) !== undefined;
|
||||||
@ -92,19 +109,19 @@ export function ChallengeOverviewScreen(): VNode {
|
|||||||
: undefined;
|
: undefined;
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNext={errors} title="Recovery: Solve challenges">
|
<AnastasisClientFrame hideNext={errors} title="Recovery: Solve challenges">
|
||||||
{!policies.length ? (
|
{!policiesWithInfo.length ? (
|
||||||
<p class="block">
|
<p class="block">
|
||||||
No policies found, try with another version of the secret
|
No policies found, try with another version of the secret
|
||||||
</p>
|
</p>
|
||||||
) : policies.length === 1 ? (
|
) : policiesWithInfo.length === 1 ? (
|
||||||
<p class="block">
|
<p class="block">
|
||||||
One policy found for this secret. You need to solve all the challenges
|
One policy found for this secret. You need to solve all the challenges
|
||||||
in order to recover your secret.
|
in order to recover your secret.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p class="block">
|
<p class="block">
|
||||||
We have found {policies.length} polices. You need to solve all the
|
We have found {policiesWithInfo.length} polices. You need to solve all
|
||||||
challenges from one policy in order to recover your secret.
|
the challenges from one policy in order to recover your secret.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{policiesWithInfo.map((policy, policy_index) => {
|
{policiesWithInfo.map((policy, policy_index) => {
|
||||||
@ -113,7 +130,8 @@ export function ChallengeOverviewScreen(): VNode {
|
|||||||
const method = authMethods[info.type as KnownAuthMethods];
|
const method = authMethods[info.type as KnownAuthMethods];
|
||||||
|
|
||||||
if (!method) {
|
if (!method) {
|
||||||
return <div
|
return (
|
||||||
|
<div
|
||||||
key={uuid}
|
key={uuid}
|
||||||
class="block"
|
class="block"
|
||||||
style={{ display: "flex", justifyContent: "space-between" }}
|
style={{ display: "flex", justifyContent: "space-between" }}
|
||||||
@ -121,66 +139,91 @@ export function ChallengeOverviewScreen(): VNode {
|
|||||||
<div style={{ display: "flex", alignItems: "center" }}>
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
<span>unknown challenge</span>
|
<span>unknown challenge</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChallengeButton({ id, feedback }: { id: string; feedback?: ChallengeFeedback }): VNode {
|
function ChallengeButton({
|
||||||
|
id,
|
||||||
|
feedback,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
feedback?: ChallengeFeedback;
|
||||||
|
}): VNode {
|
||||||
function selectChallenge(): void {
|
function selectChallenge(): void {
|
||||||
if (reducer) reducer.transition("select_challenge", { uuid: id })
|
if (reducer) reducer.transition("select_challenge", { uuid: id });
|
||||||
}
|
}
|
||||||
if (!feedback) {
|
if (!feedback) {
|
||||||
return <div>
|
return (
|
||||||
<button class="button" onClick={selectChallenge}>
|
<div>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
disabled={
|
||||||
|
atLeastThereIsOnePolicySolved && !policy.isPolicySolved
|
||||||
|
}
|
||||||
|
onClick={selectChallenge}
|
||||||
|
>
|
||||||
Solve
|
Solve
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
switch (feedback.state) {
|
switch (feedback.state) {
|
||||||
case ChallengeFeedbackStatus.ServerFailure:
|
case ChallengeFeedbackStatus.ServerFailure:
|
||||||
case ChallengeFeedbackStatus.Unsupported:
|
case ChallengeFeedbackStatus.Unsupported:
|
||||||
case ChallengeFeedbackStatus.TruthUnknown:
|
case ChallengeFeedbackStatus.TruthUnknown:
|
||||||
case ChallengeFeedbackStatus.RateLimitExceeded: return <div />
|
case ChallengeFeedbackStatus.RateLimitExceeded:
|
||||||
|
return <div />;
|
||||||
case ChallengeFeedbackStatus.AuthIban:
|
case ChallengeFeedbackStatus.AuthIban:
|
||||||
case ChallengeFeedbackStatus.Payment: return <div>
|
case ChallengeFeedbackStatus.Payment:
|
||||||
<button class="button" onClick={selectChallenge}>
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
disabled={
|
||||||
|
atLeastThereIsOnePolicySolved && !policy.isPolicySolved
|
||||||
|
}
|
||||||
|
onClick={selectChallenge}
|
||||||
|
>
|
||||||
Pay
|
Pay
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
case ChallengeFeedbackStatus.Redirect: return <div>
|
);
|
||||||
<button class="button" onClick={selectChallenge}>
|
case ChallengeFeedbackStatus.Redirect:
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
disabled={
|
||||||
|
atLeastThereIsOnePolicySolved && !policy.isPolicySolved
|
||||||
|
}
|
||||||
|
onClick={selectChallenge}
|
||||||
|
>
|
||||||
Go to {feedback.redirect_url}
|
Go to {feedback.redirect_url}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
case ChallengeFeedbackStatus.Solved: return <div>
|
);
|
||||||
<div class="tag is-success is-large">
|
case ChallengeFeedbackStatus.Solved:
|
||||||
Solved
|
return (
|
||||||
|
<div>
|
||||||
|
<div class="tag is-success is-large">Solved</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
default: return <div>
|
default:
|
||||||
<button class="button" onClick={selectChallenge}>
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
disabled={
|
||||||
|
atLeastThereIsOnePolicySolved && !policy.isPolicySolved
|
||||||
|
}
|
||||||
|
onClick={selectChallenge}
|
||||||
|
>
|
||||||
Solve
|
Solve
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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 (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -202,7 +245,6 @@ export function ChallengeOverviewScreen(): VNode {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ChallengeButton id={uuid} feedback={info.feedback} />
|
<ChallengeButton id={uuid} feedback={info.feedback} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -210,11 +252,13 @@ export function ChallengeOverviewScreen(): VNode {
|
|||||||
const policyName = policy.challenges
|
const policyName = policy.challenges
|
||||||
.map((x) => x.info.type)
|
.map((x) => x.info.type)
|
||||||
.join(" + ");
|
.join(" + ");
|
||||||
|
|
||||||
const opa = !atLeastThereIsOnePolicySolved
|
const opa = !atLeastThereIsOnePolicySolved
|
||||||
? undefined
|
? undefined
|
||||||
: policy.isPolicySolved
|
: policy.isPolicySolved
|
||||||
? undefined
|
? undefined
|
||||||
: "0.6";
|
: "0.6";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={policy_index}
|
key={policy_index}
|
||||||
|
@ -15,24 +15,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { ChallengePayingScreen as TestedComponent } from './ChallengePayingScreen';
|
|
||||||
|
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { ChallengePayingScreen as TestedComponent } from "./ChallengePayingScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/__ChallengePaying',
|
title: "Pages/recovery/__ChallengePaying",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 10,
|
order: 10,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Example = createExample(TestedComponent, reducerStatesExample.challengePaying);
|
export const Example = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
reducerStatesExample.challengePaying,
|
||||||
|
);
|
||||||
|
@ -3,19 +3,19 @@ import { useAnastasisContext } from "../../context/anastasis";
|
|||||||
import { AnastasisClientFrame } from "./index";
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
export function ChallengePayingScreen(): VNode {
|
export function ChallengePayingScreen(): VNode {
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext();
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
|
if (
|
||||||
return <div>invalid state</div>
|
!reducer.currentReducerState ||
|
||||||
|
reducer.currentReducerState.recovery_state === undefined
|
||||||
|
) {
|
||||||
|
return <div>invalid state</div>;
|
||||||
}
|
}
|
||||||
const payments = ['']; //reducer.currentReducerState.payments ??
|
const payments = [""]; //reducer.currentReducerState.payments ??
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame
|
<AnastasisClientFrame hideNav title="Recovery: Challenge Paying">
|
||||||
hideNav
|
|
||||||
title="Recovery: Challenge Paying"
|
|
||||||
>
|
|
||||||
<p>
|
<p>
|
||||||
Some of the providers require a payment to store the encrypted
|
Some of the providers require a payment to store the encrypted
|
||||||
authentication information.
|
authentication information.
|
||||||
|
@ -16,37 +16,42 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { ContinentSelectionScreen as TestedComponent } from './ContinentSelectionScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { ContinentSelectionScreen as TestedComponent } from "./ContinentSelectionScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/Location',
|
title: "Pages/Location",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BackupSelectContinent = createExample(TestedComponent, reducerStatesExample.backupSelectContinent);
|
export const BackupSelectContinent = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
reducerStatesExample.backupSelectContinent,
|
||||||
|
);
|
||||||
|
|
||||||
export const BackupSelectCountry = createExample(TestedComponent, {
|
export const BackupSelectCountry = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.backupSelectContinent,
|
...reducerStatesExample.backupSelectContinent,
|
||||||
selected_continent: 'Testcontinent',
|
selected_continent: "Testcontinent",
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const RecoverySelectContinent = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent);
|
export const RecoverySelectContinent = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
reducerStatesExample.recoverySelectContinent,
|
||||||
|
);
|
||||||
|
|
||||||
export const RecoverySelectCountry = createExample(TestedComponent, {
|
export const RecoverySelectCountry = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.recoverySelectContinent,
|
...reducerStatesExample.recoverySelectContinent,
|
||||||
selected_continent: 'Testcontinent',
|
selected_continent: "Testcontinent",
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
@ -16,94 +16,126 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { EditPoliciesScreen as TestedComponent } from './EditPoliciesScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { EditPoliciesScreen as TestedComponent } from "./EditPoliciesScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/ReviewPolicies/EditPolicies',
|
title: "Pages/backup/ReviewPolicies/EditPolicies",
|
||||||
args: {
|
args: {
|
||||||
order: 6,
|
order: 6,
|
||||||
},
|
},
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EditingAPolicy = createExample(TestedComponent, {
|
export const EditingAPolicy = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
{
|
||||||
...reducerStatesExample.policyReview,
|
...reducerStatesExample.policyReview,
|
||||||
policies: [{
|
policies: [
|
||||||
methods: [{
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
authentication_method: 1,
|
authentication_method: 1,
|
||||||
provider: 'https://anastasis.demo.taler.net/'
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
authentication_method: 2,
|
authentication_method: 2,
|
||||||
provider: 'http://localhost:8086/'
|
provider: "http://localhost:8086/",
|
||||||
}]
|
},
|
||||||
}, {
|
],
|
||||||
methods: [{
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
authentication_method: 1,
|
authentication_method: 1,
|
||||||
provider: 'http://localhost:8086/'
|
provider: "http://localhost:8086/",
|
||||||
}]
|
},
|
||||||
}],
|
],
|
||||||
authentication_methods: [{
|
},
|
||||||
|
],
|
||||||
|
authentication_methods: [
|
||||||
|
{
|
||||||
type: "email",
|
type: "email",
|
||||||
instructions: "Email to qwe@asd.com",
|
instructions: "Email to qwe@asd.com",
|
||||||
challenge: "E5VPA"
|
challenge: "E5VPA",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "totp",
|
type: "totp",
|
||||||
instructions: "Response code for 'Anastasis'",
|
instructions: "Response code for 'Anastasis'",
|
||||||
challenge: "E5VPA"
|
challenge: "E5VPA",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "sms",
|
type: "sms",
|
||||||
instructions: "SMS to 6666-6666",
|
instructions: "SMS to 6666-6666",
|
||||||
challenge: ""
|
challenge: "",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "question",
|
type: "question",
|
||||||
instructions: "How did the chicken cross the road?",
|
instructions: "How did the chicken cross the road?",
|
||||||
challenge: "C5SP8"
|
challenge: "C5SP8",
|
||||||
}]
|
},
|
||||||
} as ReducerState, { index : 0});
|
],
|
||||||
|
} as ReducerState,
|
||||||
|
{ index: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
export const CreatingAPolicy = createExample(TestedComponent, {
|
export const CreatingAPolicy = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
{
|
||||||
...reducerStatesExample.policyReview,
|
...reducerStatesExample.policyReview,
|
||||||
policies: [{
|
policies: [
|
||||||
methods: [{
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
authentication_method: 1,
|
authentication_method: 1,
|
||||||
provider: 'https://anastasis.demo.taler.net/'
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
authentication_method: 2,
|
authentication_method: 2,
|
||||||
provider: 'http://localhost:8086/'
|
provider: "http://localhost:8086/",
|
||||||
}]
|
},
|
||||||
}, {
|
],
|
||||||
methods: [{
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
authentication_method: 1,
|
authentication_method: 1,
|
||||||
provider: 'http://localhost:8086/'
|
provider: "http://localhost:8086/",
|
||||||
}]
|
},
|
||||||
}],
|
],
|
||||||
authentication_methods: [{
|
},
|
||||||
|
],
|
||||||
|
authentication_methods: [
|
||||||
|
{
|
||||||
type: "email",
|
type: "email",
|
||||||
instructions: "Email to qwe@asd.com",
|
instructions: "Email to qwe@asd.com",
|
||||||
challenge: "E5VPA"
|
challenge: "E5VPA",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "totp",
|
type: "totp",
|
||||||
instructions: "Response code for 'Anastasis'",
|
instructions: "Response code for 'Anastasis'",
|
||||||
challenge: "E5VPA"
|
challenge: "E5VPA",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "sms",
|
type: "sms",
|
||||||
instructions: "SMS to 6666-6666",
|
instructions: "SMS to 6666-6666",
|
||||||
challenge: ""
|
challenge: "",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "question",
|
type: "question",
|
||||||
instructions: "How did the chicken cross the road?",
|
instructions: "How did the chicken cross the road?",
|
||||||
challenge: "C5SP8"
|
challenge: "C5SP8",
|
||||||
}]
|
},
|
||||||
} as ReducerState, { index : 3});
|
],
|
||||||
|
} as ReducerState,
|
||||||
|
{ index: 3 },
|
||||||
|
);
|
||||||
|
@ -20,7 +20,6 @@ interface Props {
|
|||||||
index: number;
|
index: number;
|
||||||
cancel: () => void;
|
cancel: () => void;
|
||||||
confirm: (changes: MethodProvider[]) => void;
|
confirm: (changes: MethodProvider[]) => void;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MethodProvider {
|
export interface MethodProvider {
|
||||||
@ -28,89 +27,121 @@ export interface MethodProvider {
|
|||||||
provider: string;
|
provider: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EditPoliciesScreen({ index: policy_index, cancel, confirm }: Props): VNode {
|
export function EditPoliciesScreen({
|
||||||
const [changedProvider, setChangedProvider] = useState<Array<string>>([])
|
index: policy_index,
|
||||||
|
cancel,
|
||||||
|
confirm,
|
||||||
|
}: Props): VNode {
|
||||||
|
const [changedProvider, setChangedProvider] = useState<Array<string>>([]);
|
||||||
|
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext();
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
|
if (
|
||||||
return <div>invalid state</div>
|
!reducer.currentReducerState ||
|
||||||
|
reducer.currentReducerState.backup_state === undefined
|
||||||
|
) {
|
||||||
|
return <div>invalid state</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectableProviders: ProviderInfoByType = {}
|
const selectableProviders: ProviderInfoByType = {};
|
||||||
const allProviders = Object.entries(reducer.currentReducerState.authentication_providers || {})
|
const allProviders = Object.entries(
|
||||||
|
reducer.currentReducerState.authentication_providers || {},
|
||||||
|
);
|
||||||
for (let index = 0; index < allProviders.length; index++) {
|
for (let index = 0; index < allProviders.length; index++) {
|
||||||
const [url, status] = allProviders[index]
|
const [url, status] = allProviders[index];
|
||||||
if ("methods" in status) {
|
if ("methods" in status) {
|
||||||
status.methods.map(m => {
|
status.methods.map((m) => {
|
||||||
const type: KnownAuthMethods = m.type as KnownAuthMethods
|
const type: KnownAuthMethods = m.type as KnownAuthMethods;
|
||||||
const values = selectableProviders[type] || []
|
const values = selectableProviders[type] || [];
|
||||||
const isFree = !m.usage_fee || m.usage_fee.endsWith(":0")
|
const isFree = !m.usage_fee || m.usage_fee.endsWith(":0");
|
||||||
values.push({ url, cost: m.usage_fee, isFree })
|
values.push({ url, cost: m.usage_fee, isFree });
|
||||||
selectableProviders[type] = values
|
selectableProviders[type] = values;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allAuthMethods = reducer.currentReducerState.authentication_methods ?? [];
|
const allAuthMethods =
|
||||||
|
reducer.currentReducerState.authentication_methods ?? [];
|
||||||
const policies = reducer.currentReducerState.policies ?? [];
|
const policies = reducer.currentReducerState.policies ?? [];
|
||||||
const policy = policies[policy_index]
|
const policy = policies[policy_index];
|
||||||
|
|
||||||
for(let method_index = 0; method_index < allAuthMethods.length; method_index++ ) {
|
for (
|
||||||
policy?.methods.find(m => m.authentication_method === method_index)?.provider
|
let method_index = 0;
|
||||||
|
method_index < allAuthMethods.length;
|
||||||
|
method_index++
|
||||||
|
) {
|
||||||
|
policy?.methods.find((m) => m.authentication_method === method_index)
|
||||||
|
?.provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendChanges(): void {
|
function sendChanges(): void {
|
||||||
const newMethods: MethodProvider[] = []
|
const newMethods: MethodProvider[] = [];
|
||||||
allAuthMethods.forEach((method, index) => {
|
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) {
|
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({
|
newMethods.push({
|
||||||
authentication_method: index,
|
authentication_method: index,
|
||||||
provider: changedProvider[index]
|
provider: changedProvider[index],
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
confirm(newMethods)
|
confirm(newMethods);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <AnastasisClientFrame hideNav title={!policy ? "Backup: New Policy" : "Backup: Edit Policy"}>
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
hideNav
|
||||||
|
title={!policy ? "Backup: New Policy" : "Backup: Edit Policy"}
|
||||||
|
>
|
||||||
<section class="section">
|
<section class="section">
|
||||||
{!policy ? <p>
|
{!policy ? (
|
||||||
Creating a new policy #{policy_index}
|
<p>Creating a new policy #{policy_index}</p>
|
||||||
</p> : <p>
|
) : (
|
||||||
Editing policy #{policy_index}
|
<p>Editing policy #{policy_index}</p>
|
||||||
</p>}
|
)}
|
||||||
{allAuthMethods.map((method, index) => {
|
{allAuthMethods.map((method, index) => {
|
||||||
//take the url from the updated change or from the policy
|
//take the url from the updated change or from the policy
|
||||||
const providerURL = changedProvider[index] === undefined ?
|
const providerURL =
|
||||||
policy?.methods.find(m => m.authentication_method === index)?.provider :
|
changedProvider[index] === undefined
|
||||||
changedProvider[index];
|
? policy?.methods.find((m) => m.authentication_method === index)
|
||||||
|
?.provider
|
||||||
|
: changedProvider[index];
|
||||||
|
|
||||||
const type: KnownAuthMethods = method.type as KnownAuthMethods
|
const type: KnownAuthMethods = method.type as KnownAuthMethods;
|
||||||
function changeProviderTo(url: string): void {
|
function changeProviderTo(url: string): void {
|
||||||
const copy = [...changedProvider]
|
const copy = [...changedProvider];
|
||||||
copy[index] = url
|
copy[index] = url;
|
||||||
setChangedProvider(copy)
|
setChangedProvider(copy);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div key={index} class="block" style={{ display: 'flex', alignItems: 'center' }}>
|
<div
|
||||||
<span class="icon">
|
key={index}
|
||||||
{authMethods[type]?.icon}
|
class="block"
|
||||||
</span>
|
style={{ display: "flex", alignItems: "center" }}
|
||||||
|
>
|
||||||
|
<span class="icon">{authMethods[type]?.icon}</span>
|
||||||
|
<span>{method.instructions}</span>
|
||||||
<span>
|
<span>
|
||||||
{method.instructions}
|
<span class="select ">
|
||||||
</span>
|
<select
|
||||||
<span>
|
onChange={(e) => changeProviderTo(e.currentTarget.value)}
|
||||||
<span class="select " >
|
value={providerURL ?? ""}
|
||||||
<select onChange={(e) => changeProviderTo(e.currentTarget.value)} value={providerURL ?? ""}>
|
>
|
||||||
<option key="none" value=""> << off >> </option>
|
<option key="none" value="">
|
||||||
{selectableProviders[type]?.map(prov => (
|
{" "}
|
||||||
|
<< off >>{" "}
|
||||||
|
</option>
|
||||||
|
{selectableProviders[type]?.map((prov) => (
|
||||||
<option key={prov.url} value={prov.url}>
|
<option key={prov.url} value={prov.url}>
|
||||||
{prov.url}
|
{prov.url}
|
||||||
</option>
|
</option>
|
||||||
@ -121,13 +152,26 @@ export function EditPoliciesScreen({ index: policy_index, cancel, confirm }: Pro
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={cancel}>Cancel</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={cancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
<span class="buttons">
|
<span class="buttons">
|
||||||
<button class="button" onClick={() => setChangedProvider([])}>Reset</button>
|
<button class="button" onClick={() => setChangedProvider([])}>
|
||||||
<button class="button is-info" onClick={sendChanges}>Confirm</button>
|
Reset
|
||||||
|
</button>
|
||||||
|
<button class="button is-info" onClick={sendChanges}>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,35 +15,40 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { PoliciesPayingScreen as TestedComponent } from './PoliciesPayingScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { PoliciesPayingScreen as TestedComponent } from "./PoliciesPayingScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/__PoliciesPaying',
|
title: "Pages/backup/__PoliciesPaying",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 9,
|
order: 9,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Example = createExample(TestedComponent, reducerStatesExample.policyPay);
|
export const Example = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
reducerStatesExample.policyPay,
|
||||||
|
);
|
||||||
export const WithSomePaymentRequest = createExample(TestedComponent, {
|
export const WithSomePaymentRequest = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.policyPay,
|
...reducerStatesExample.policyPay,
|
||||||
policy_payment_requests: [{
|
policy_payment_requests: [
|
||||||
payto: 'payto://x-taler-bank/bank.taler/account-a',
|
{
|
||||||
provider: 'provider1'
|
payto: "payto://x-taler-bank/bank.taler/account-a",
|
||||||
}, {
|
provider: "provider1",
|
||||||
payto: 'payto://x-taler-bank/bank.taler/account-b',
|
},
|
||||||
provider: 'provider2'
|
{
|
||||||
}]
|
payto: "payto://x-taler-bank/bank.taler/account-b",
|
||||||
|
provider: "provider2",
|
||||||
|
},
|
||||||
|
],
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
@ -3,20 +3,23 @@ import { useAnastasisContext } from "../../context/anastasis";
|
|||||||
import { AnastasisClientFrame } from "./index";
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
export function PoliciesPayingScreen(): VNode {
|
export function PoliciesPayingScreen(): VNode {
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext();
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
|
if (
|
||||||
return <div>invalid state</div>
|
!reducer.currentReducerState ||
|
||||||
|
reducer.currentReducerState.backup_state === undefined
|
||||||
|
) {
|
||||||
|
return <div>invalid state</div>;
|
||||||
}
|
}
|
||||||
const payments = reducer.currentReducerState.policy_payment_requests ?? [];
|
const payments = reducer.currentReducerState.policy_payment_requests ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Backup: Recovery Document Payments">
|
<AnastasisClientFrame hideNav title="Backup: Recovery Document Payments">
|
||||||
<p>
|
<p>
|
||||||
Some of the providers require a payment to store the encrypted
|
Some of the providers require a payment to store the encrypted recovery
|
||||||
recovery document.
|
document.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
{payments.map((x, i) => {
|
{payments.map((x, i) => {
|
||||||
|
@ -16,30 +16,32 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { RecoveryFinishedScreen as TestedComponent } from './RecoveryFinishedScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { RecoveryFinishedScreen as TestedComponent } from "./RecoveryFinishedScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/Finished',
|
title: "Pages/recovery/Finished",
|
||||||
args: {
|
args: {
|
||||||
order: 7,
|
order: 7,
|
||||||
},
|
},
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GoodEnding = createExample(TestedComponent, {
|
export const GoodEnding = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.recoveryFinished,
|
...reducerStatesExample.recoveryFinished,
|
||||||
core_secret: { mime: 'text/plain', value: 'hello' }
|
core_secret: { mime: "text/plain", value: "hello" },
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const BadEnding = createExample(TestedComponent, reducerStatesExample.recoveryFinished);
|
export const BadEnding = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
reducerStatesExample.recoveryFinished,
|
||||||
|
);
|
||||||
|
@ -1,39 +1,53 @@
|
|||||||
import {
|
import { bytesToString, decodeCrock } from "@gnu-taler/taler-util";
|
||||||
bytesToString,
|
|
||||||
decodeCrock
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
import { useAnastasisContext } from "../../context/anastasis";
|
||||||
import { AnastasisClientFrame } from "./index";
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
export function RecoveryFinishedScreen(): VNode {
|
export function RecoveryFinishedScreen(): VNode {
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext();
|
||||||
|
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
|
if (
|
||||||
return <div>invalid state</div>
|
!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) {
|
if (!encodedSecret) {
|
||||||
return <AnastasisClientFrame title="Recovery Problem" hideNav>
|
return (
|
||||||
<p>
|
<AnastasisClientFrame title="Recovery Problem" hideNav>
|
||||||
Secret not found
|
<p>Secret not found</p>
|
||||||
</p>
|
<div
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
style={{
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const secret = bytesToString(decodeCrock(encodedSecret.value))
|
const secret = bytesToString(decodeCrock(encodedSecret.value));
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame title="Recovery Finished" hideNav>
|
<AnastasisClientFrame title="Recovery Finished" hideNav>
|
||||||
<p>
|
<p>Your secret: {secret}</p>
|
||||||
Secret: {secret}
|
<div
|
||||||
</p>
|
style={{
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
marginTop: "2em",
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
|
@ -15,44 +15,51 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { ReviewPoliciesScreen as TestedComponent } from './ReviewPoliciesScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { ReviewPoliciesScreen as TestedComponent } from "./ReviewPoliciesScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/ReviewPolicies',
|
title: "Pages/backup/ReviewPolicies",
|
||||||
args: {
|
args: {
|
||||||
order: 6,
|
order: 6,
|
||||||
},
|
},
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HasPoliciesButMethodListIsEmpty = createExample(TestedComponent, {
|
export const HasPoliciesButMethodListIsEmpty = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.policyReview,
|
...reducerStatesExample.policyReview,
|
||||||
policies: [{
|
policies: [
|
||||||
methods: [{
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
authentication_method: 0,
|
authentication_method: 0,
|
||||||
provider: 'asd'
|
provider: "asd",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
authentication_method: 1,
|
authentication_method: 1,
|
||||||
provider: 'asd'
|
provider: "asd",
|
||||||
}]
|
},
|
||||||
}, {
|
],
|
||||||
methods: [{
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
authentication_method: 1,
|
authentication_method: 1,
|
||||||
provider: 'asd'
|
provider: "asd",
|
||||||
}]
|
},
|
||||||
}],
|
],
|
||||||
authentication_methods: []
|
},
|
||||||
|
],
|
||||||
|
authentication_methods: [],
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const SomePoliciesWithMethods = createExample(TestedComponent, {
|
export const SomePoliciesWithMethods = createExample(TestedComponent, {
|
||||||
@ -62,186 +69,193 @@ export const SomePoliciesWithMethods = createExample(TestedComponent, {
|
|||||||
methods: [
|
methods: [
|
||||||
{
|
{
|
||||||
authentication_method: 0,
|
authentication_method: 0,
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
authentication_method: 1,
|
authentication_method: 1,
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
authentication_method: 2,
|
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/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 1,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 3,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
methods: [
|
|
||||||
{
|
|
||||||
authentication_method: 0,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 1,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 4,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
methods: [
|
|
||||||
{
|
|
||||||
authentication_method: 0,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 2,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 3,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
methods: [
|
|
||||||
{
|
|
||||||
authentication_method: 0,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 2,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 4,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
methods: [
|
|
||||||
{
|
|
||||||
authentication_method: 0,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 3,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 4,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
methods: [
|
|
||||||
{
|
|
||||||
authentication_method: 1,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 2,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 3,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
methods: [
|
|
||||||
{
|
|
||||||
authentication_method: 1,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 2,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 4,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
methods: [
|
|
||||||
{
|
|
||||||
authentication_method: 1,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 3,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 4,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
methods: [
|
|
||||||
{
|
|
||||||
authentication_method: 2,
|
|
||||||
provider: "https://kudos.demo.anastasis.lu/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 3,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
authentication_method: 4,
|
|
||||||
provider: "https://anastasis.demo.taler.net/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
authentication_methods: [{
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
authentication_method: 0,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 1,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 3,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
authentication_method: 0,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 1,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 4,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
authentication_method: 0,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 2,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 3,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
authentication_method: 0,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 2,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 4,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
authentication_method: 0,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 3,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 4,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
authentication_method: 1,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 2,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 3,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
authentication_method: 1,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 2,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 4,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
authentication_method: 1,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 3,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 4,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
authentication_method: 2,
|
||||||
|
provider: "https://kudos.demo.anastasis.lu/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 3,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authentication_method: 4,
|
||||||
|
provider: "https://anastasis.demo.taler.net/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
authentication_methods: [
|
||||||
|
{
|
||||||
type: "email",
|
type: "email",
|
||||||
instructions: "Email to qwe@asd.com",
|
instructions: "Email to qwe@asd.com",
|
||||||
challenge: "E5VPA"
|
challenge: "E5VPA",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "sms",
|
type: "sms",
|
||||||
instructions: "SMS to 555-555",
|
instructions: "SMS to 555-555",
|
||||||
challenge: ""
|
challenge: "",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "question",
|
type: "question",
|
||||||
instructions: "Does P equal NP?",
|
instructions: "Does P equal NP?",
|
||||||
challenge: "C5SP8"
|
challenge: "C5SP8",
|
||||||
},{
|
},
|
||||||
|
{
|
||||||
type: "totp",
|
type: "totp",
|
||||||
instructions: "Response code for 'Anastasis'",
|
instructions: "Response code for 'Anastasis'",
|
||||||
challenge: "E5VPA"
|
challenge: "E5VPA",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "sms",
|
type: "sms",
|
||||||
instructions: "SMS to 6666-6666",
|
instructions: "SMS to 6666-6666",
|
||||||
challenge: ""
|
challenge: "",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "question",
|
type: "question",
|
||||||
instructions: "How did the chicken cross the road?",
|
instructions: "How did the chicken cross the road?",
|
||||||
challenge: "C5SP8"
|
challenge: "C5SP8",
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
@ -6,16 +6,20 @@ import { EditPoliciesScreen } from "./EditPoliciesScreen";
|
|||||||
import { AnastasisClientFrame } from "./index";
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
export function ReviewPoliciesScreen(): VNode {
|
export function ReviewPoliciesScreen(): VNode {
|
||||||
const [editingPolicy, setEditingPolicy] = useState<number | undefined>()
|
const [editingPolicy, setEditingPolicy] = useState<number | undefined>();
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext();
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
|
if (
|
||||||
return <div>invalid state</div>
|
!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 ?? [];
|
const policies = reducer.currentReducerState.policies ?? [];
|
||||||
|
|
||||||
if (editingPolicy !== undefined) {
|
if (editingPolicy !== undefined) {
|
||||||
@ -28,58 +32,109 @@ export function ReviewPoliciesScreen(): VNode {
|
|||||||
policy_index: editingPolicy,
|
policy_index: editingPolicy,
|
||||||
policy: newMethods,
|
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 (
|
return (
|
||||||
<AnastasisClientFrame hideNext={errors} title="Backup: Review Recovery Policies">
|
<AnastasisClientFrame
|
||||||
{policies.length > 0 && <p class="block">
|
hideNext={errors}
|
||||||
Based on your configured authentication method you have created, some policies
|
title="Backup: Review Recovery Policies"
|
||||||
have been configured. In order to recover your secret you have to solve all the
|
>
|
||||||
challenges of at least one policy.
|
{policies.length > 0 && (
|
||||||
</p>}
|
<p class="block">
|
||||||
{policies.length < 1 && <p class="block">
|
Based on your configured authentication method you have created, some
|
||||||
No policies had been created. Go back and add more authentication methods.
|
policies have been configured. In order to recover your secret you
|
||||||
</p>}
|
have to solve all the challenges of at least one policy.
|
||||||
<div class="block" style={{ justifyContent: 'flex-end' }} >
|
</p>
|
||||||
<button class="button is-success" onClick={() => setEditingPolicy(policies.length + 1)}>Add new policy</button>
|
)}
|
||||||
|
{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>
|
</div>
|
||||||
{policies.map((p, policy_index) => {
|
{policies.map((p, policy_index) => {
|
||||||
const methods = p.methods
|
const methods = p.methods
|
||||||
.map(x => configuredAuthMethods[x.authentication_method] && ({ ...configuredAuthMethods[x.authentication_method], provider: x.provider }))
|
.map(
|
||||||
.filter(x => !!x)
|
(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 (
|
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>
|
<div>
|
||||||
<h3 class="subtitle">
|
<h3 class="subtitle">
|
||||||
Policy #{policy_index + 1}: {policyName}
|
Policy #{policy_index + 1}: {policyName}
|
||||||
</h3>
|
</h3>
|
||||||
{!methods.length && <p>
|
{!methods.length && <p>No auth method found</p>}
|
||||||
No auth method found
|
|
||||||
</p>}
|
|
||||||
{methods.map((m, i) => {
|
{methods.map((m, i) => {
|
||||||
return (
|
return (
|
||||||
<p key={i} class="block" style={{ display: 'flex', alignItems: 'center' }}>
|
<p
|
||||||
|
key={i}
|
||||||
|
class="block"
|
||||||
|
style={{ display: "flex", alignItems: "center" }}
|
||||||
|
>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
{authMethods[m.type as KnownAuthMethods]?.icon}
|
{authMethods[m.type as KnownAuthMethods]?.icon}
|
||||||
</span>
|
</span>
|
||||||
<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>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 'auto', marginBottom: 'auto', display: 'flex', justifyContent: 'space-between', flexDirection: 'column' }}>
|
<div
|
||||||
<button class="button is-info block" onClick={() => setEditingPolicy(policy_index)}>Edit</button>
|
style={{
|
||||||
<button class="button is-danger block" onClick={() => reducer.transition("delete_policy", { policy_index })}>Delete</button>
|
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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -15,30 +15,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { SecretEditorScreen as TestedComponent } from './SecretEditorScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { SecretEditorScreen as TestedComponent } from "./SecretEditorScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/SecretInput',
|
title: "Pages/backup/SecretInput",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 7,
|
order: 7,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithSecretNamePreselected = createExample(TestedComponent, {
|
export const WithSecretNamePreselected = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.secretEdition,
|
...reducerStatesExample.secretEdition,
|
||||||
secret_name: 'someSecretName',
|
secret_name: "someSecretName",
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const WithoutName = createExample(TestedComponent, {
|
export const WithoutName = createExample(TestedComponent, {
|
||||||
|
@ -15,37 +15,35 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { SecretSelectionScreen as TestedComponent } from './SecretSelectionScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { SecretSelectionScreen as TestedComponent } from "./SecretSelectionScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SecretSelection',
|
title: "Pages/recovery/SecretSelection",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 4,
|
order: 4,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Example = createExample(TestedComponent, {
|
export const Example = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.secretSelection,
|
...reducerStatesExample.secretSelection,
|
||||||
recovery_document: {
|
recovery_document: {
|
||||||
provider_url: 'https://kudos.demo.anastasis.lu/',
|
provider_url: "https://kudos.demo.anastasis.lu/",
|
||||||
secret_name: 'secretName',
|
secret_name: "secretName",
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
|
|
||||||
export const NoRecoveryDocumentFound = createExample(TestedComponent, {
|
export const NoRecoveryDocumentFound = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.secretSelection,
|
...reducerStatesExample.secretSelection,
|
||||||
recovery_document: undefined,
|
recovery_document: undefined,
|
||||||
|
@ -8,18 +8,23 @@ import { AnastasisClientFrame } from "./index";
|
|||||||
|
|
||||||
export function SecretSelectionScreen(): VNode {
|
export function SecretSelectionScreen(): VNode {
|
||||||
const [selectingVersion, setSelectingVersion] = useState<boolean>(false);
|
const [selectingVersion, setSelectingVersion] = useState<boolean>(false);
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext();
|
||||||
|
|
||||||
const [manageProvider, setManageProvider] = useState(false)
|
const [manageProvider, setManageProvider] = useState(false);
|
||||||
const currentVersion = (reducer?.currentReducerState
|
const currentVersion =
|
||||||
&& ("recovery_document" in reducer.currentReducerState)
|
(reducer?.currentReducerState &&
|
||||||
&& reducer.currentReducerState.recovery_document?.version) || 0;
|
"recovery_document" in reducer.currentReducerState &&
|
||||||
|
reducer.currentReducerState.recovery_document?.version) ||
|
||||||
|
0;
|
||||||
|
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
|
if (
|
||||||
return <div>invalid state</div>
|
!reducer.currentReducerState ||
|
||||||
|
reducer.currentReducerState.recovery_state === undefined
|
||||||
|
) {
|
||||||
|
return <div>invalid state</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doSelectVersion(p: string, n: number): Promise<void> {
|
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 providerList = Object.keys(
|
||||||
const recoveryDocument = reducer.currentReducerState.recovery_document
|
reducer.currentReducerState.authentication_providers ?? {},
|
||||||
|
);
|
||||||
|
const recoveryDocument = reducer.currentReducerState.recovery_document;
|
||||||
|
|
||||||
if (!recoveryDocument) {
|
if (!recoveryDocument) {
|
||||||
return <ChooseAnotherProviderScreen
|
return (
|
||||||
providers={providerList} selected=""
|
<ChooseAnotherProviderScreen
|
||||||
|
providers={providerList}
|
||||||
|
selected=""
|
||||||
onChange={(newProv) => doSelectVersion(newProv, 0)}
|
onChange={(newProv) => doSelectVersion(newProv, 0)}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectingVersion) {
|
if (selectingVersion) {
|
||||||
return <SelectOtherVersionProviderScreen providers={providerList}
|
return (
|
||||||
provider={recoveryDocument.provider_url} version={recoveryDocument.version}
|
<SelectOtherVersionProviderScreen
|
||||||
|
providers={providerList}
|
||||||
|
provider={recoveryDocument.provider_url}
|
||||||
|
version={recoveryDocument.version}
|
||||||
onCancel={() => setSelectingVersion(false)}
|
onCancel={() => setSelectingVersion(false)}
|
||||||
onConfirm={doSelectVersion}
|
onConfirm={doSelectVersion}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manageProvider) {
|
if (manageProvider) {
|
||||||
return <AddingProviderScreen onCancel={() => setManageProvider(false)} />
|
return <AddingProviderScreen onCancel={() => setManageProvider(false)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame title="Recovery: Select secret">
|
<AnastasisClientFrame title="Recovery: Select secret">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<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>
|
<h1 class="subtitle">{recoveryDocument.provider_url}</h1>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{currentVersion === 0 ? <p>
|
{currentVersion === 0 ? (
|
||||||
Set to recover the latest version
|
<p>Set to recover the latest version</p>
|
||||||
</p> : <p>
|
) : (
|
||||||
Set to recover the version number {currentVersion}
|
<p>Set to recover the version number {currentVersion}</p>
|
||||||
</p>}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons is-right">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<p>Secret found, you can select another version or continue to the challenges solving</p>
|
<p>
|
||||||
<p class="block">
|
Secret found, you can select another version or continue to the
|
||||||
<button class="button is-info" onClick={() => setManageProvider(true)}>
|
challenges solving
|
||||||
Manage recovery providers
|
</p>
|
||||||
</button>
|
<p class="block">
|
||||||
|
<a onClick={() => setManageProvider(true)}>
|
||||||
|
Manage recovery providers
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ChooseAnotherProviderScreen({
|
||||||
function ChooseAnotherProviderScreen({ providers, selected, onChange }: { selected: string; providers: string[]; onChange: (prov: string) => void }): VNode {
|
providers,
|
||||||
|
selected,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
selected: string;
|
||||||
|
providers: string[];
|
||||||
|
onChange: (prov: string) => void;
|
||||||
|
}): VNode {
|
||||||
return (
|
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>
|
<p>No recovery document found, try with another provider</p>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Provider</label>
|
<label class="label">Provider</label>
|
||||||
<div class="control is-expanded has-icons-left">
|
<div class="control is-expanded has-icons-left">
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
<select onChange={(e) => onChange(e.currentTarget.value)} value={selected}>
|
<select
|
||||||
<option key="none" disabled selected value=""> Choose a provider </option>
|
onChange={(e) => onChange(e.currentTarget.value)}
|
||||||
{providers.map(prov => (
|
value={selected}
|
||||||
|
>
|
||||||
|
<option key="none" disabled selected value="">
|
||||||
|
{" "}
|
||||||
|
Choose a provider{" "}
|
||||||
|
</option>
|
||||||
|
{providers.map((prov) => (
|
||||||
<option key={prov} value={prov}>
|
<option key={prov} value={prov}>
|
||||||
{prov}
|
{prov}
|
||||||
</option>
|
</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 [otherProvider, setOtherProvider] = useState<string>(provider);
|
||||||
const [otherVersion, setOtherVersion] = useState(version > 0 ? String(version) : "");
|
const [otherVersion, setOtherVersion] = useState(
|
||||||
|
version > 0 ? String(version) : "",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery: Select secret">
|
<AnastasisClientFrame hideNav title="Recovery: Select secret">
|
||||||
@ -125,11 +173,11 @@ function SelectOtherVersionProviderScreen({ providers, provider, version, onConf
|
|||||||
<div class="box">
|
<div class="box">
|
||||||
<h1 class="subtitle">Provider {otherProvider}</h1>
|
<h1 class="subtitle">Provider {otherProvider}</h1>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{version === 0 ? <p>
|
{version === 0 ? (
|
||||||
Set to recover the latest version
|
<p>Set to recover the latest version</p>
|
||||||
</p> : <p>
|
) : (
|
||||||
Set to recover the version number {version}
|
<p>Set to recover the version number {version}</p>
|
||||||
</p>}
|
)}
|
||||||
<p>Specify other version below or use the latest</p>
|
<p>Specify other version below or use the latest</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -137,9 +185,15 @@ function SelectOtherVersionProviderScreen({ providers, provider, version, onConf
|
|||||||
<label class="label">Provider</label>
|
<label class="label">Provider</label>
|
||||||
<div class="control is-expanded has-icons-left">
|
<div class="control is-expanded has-icons-left">
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
<select onChange={(e) => setOtherProvider(e.currentTarget.value)} value={otherProvider}>
|
<select
|
||||||
<option key="none" disabled selected value=""> Choose a provider </option>
|
onChange={(e) => setOtherProvider(e.currentTarget.value)}
|
||||||
{providers.map(prov => (
|
value={otherProvider}
|
||||||
|
>
|
||||||
|
<option key="none" disabled selected value="">
|
||||||
|
{" "}
|
||||||
|
Choose a provider{" "}
|
||||||
|
</option>
|
||||||
|
{providers.map((prov) => (
|
||||||
<option key={prov} value={prov}>
|
<option key={prov} value={prov}>
|
||||||
{prov}
|
{prov}
|
||||||
</option>
|
</option>
|
||||||
@ -156,23 +210,40 @@ function SelectOtherVersionProviderScreen({ providers, provider, version, onConf
|
|||||||
label="Version"
|
label="Version"
|
||||||
placeholder="version number to recover"
|
placeholder="version number to recover"
|
||||||
grabFocus
|
grabFocus
|
||||||
bind={[otherVersion, setOtherVersion]} />
|
bind={[otherVersion, setOtherVersion]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={onCancel}>Cancel</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={onCancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<AsyncButton class="button" onClick={() => onConfirm(otherProvider, 0)}>Use latest</AsyncButton>
|
<AsyncButton
|
||||||
<AsyncButton class="button is-info" onClick={() => onConfirm(otherProvider, parseInt(otherVersion, 10))}>Confirm</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>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">.</div>
|
||||||
.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,55 +15,63 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChallengeFeedbackStatus, RecoveryStates, ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { SolveScreen as TestedComponent } from './SolveScreen';
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChallengeFeedbackStatus,
|
||||||
|
RecoveryStates,
|
||||||
|
ReducerState,
|
||||||
|
} from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { SolveScreen as TestedComponent } from "./SolveScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SolveChallenge/Solve',
|
title: "Pages/recovery/SolveChallenge/Solve",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 6,
|
order: 6,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoInformation = createExample(TestedComponent, reducerStatesExample.challengeSolving);
|
export const NoInformation = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
reducerStatesExample.challengeSolving,
|
||||||
|
);
|
||||||
|
|
||||||
export const NotSupportedChallenge = createExample(TestedComponent, {
|
export const NotSupportedChallenge = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'chall-type',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'ASDASDSAD!1'
|
type: "chall-type",
|
||||||
}],
|
uuid: "ASDASDSAD!1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'ASDASDSAD!1',
|
selected_challenge_uuid: "ASDASDSAD!1",
|
||||||
|
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const MismatchedChallengeId = createExample(TestedComponent, {
|
export const MismatchedChallengeId = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'chall-type',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'ASDASDSAD!1'
|
type: "chall-type",
|
||||||
}],
|
uuid: "ASDASDSAD!1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'no-no-no'
|
selected_challenge_uuid: "no-no-no",
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
|
@ -2,76 +2,126 @@ import { h, VNode } from "preact";
|
|||||||
import { AnastasisClientFrame } from ".";
|
import { AnastasisClientFrame } from ".";
|
||||||
import {
|
import {
|
||||||
ChallengeFeedback,
|
ChallengeFeedback,
|
||||||
ChallengeFeedbackStatus
|
ChallengeFeedbackStatus,
|
||||||
} from "../../../../anastasis-core/lib";
|
} from "../../../../anastasis-core/lib";
|
||||||
import { Notifications } from "../../components/Notifications";
|
import { Notifications } from "../../components/Notifications";
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
import { useAnastasisContext } from "../../context/anastasis";
|
||||||
import { authMethods, KnownAuthMethods } from "./authMethod";
|
import { authMethods, KnownAuthMethods } from "./authMethod";
|
||||||
|
|
||||||
export function SolveOverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }): VNode {
|
export function SolveOverviewFeedbackDisplay(props: {
|
||||||
|
feedback?: ChallengeFeedback;
|
||||||
|
}): VNode {
|
||||||
const { feedback } = props;
|
const { feedback } = props;
|
||||||
if (!feedback) {
|
if (!feedback) {
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
switch (feedback.state) {
|
switch (feedback.state) {
|
||||||
case ChallengeFeedbackStatus.Message:
|
case ChallengeFeedbackStatus.Message:
|
||||||
return (<Notifications notifications={[{
|
return (
|
||||||
|
<Notifications
|
||||||
|
notifications={[
|
||||||
|
{
|
||||||
type: "INFO",
|
type: "INFO",
|
||||||
message: `Message from provider`,
|
message: `Message from provider`,
|
||||||
description: feedback.message
|
description: feedback.message,
|
||||||
}]} />);
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case ChallengeFeedbackStatus.Payment:
|
case ChallengeFeedbackStatus.Payment:
|
||||||
return <Notifications notifications={[{
|
return (
|
||||||
|
<Notifications
|
||||||
|
notifications={[
|
||||||
|
{
|
||||||
type: "INFO",
|
type: "INFO",
|
||||||
message: `Message from provider`,
|
message: `Message from provider`,
|
||||||
description: <span>
|
description: (
|
||||||
|
<span>
|
||||||
To pay you can <a href={feedback.taler_pay_uri}>click here</a>
|
To pay you can <a href={feedback.taler_pay_uri}>click here</a>
|
||||||
</span>
|
</span>
|
||||||
}]} />
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case ChallengeFeedbackStatus.AuthIban:
|
case ChallengeFeedbackStatus.AuthIban:
|
||||||
return <Notifications notifications={[{
|
return (
|
||||||
|
<Notifications
|
||||||
|
notifications={[
|
||||||
|
{
|
||||||
type: "INFO",
|
type: "INFO",
|
||||||
message: `Message from provider`,
|
message: `Message from provider`,
|
||||||
description: `Need to send a wire transfer to "${feedback.business_name}"`
|
description: `Need to send a wire transfer to "${feedback.business_name}"`,
|
||||||
}]} />;
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case ChallengeFeedbackStatus.ServerFailure:
|
case ChallengeFeedbackStatus.ServerFailure:
|
||||||
return (<Notifications notifications={[{
|
return (
|
||||||
|
<Notifications
|
||||||
|
notifications={[
|
||||||
|
{
|
||||||
type: "ERROR",
|
type: "ERROR",
|
||||||
message: `Server error: Code ${feedback.http_status}`,
|
message: `Server error: Code ${feedback.http_status}`,
|
||||||
description: feedback.error_response
|
description: feedback.error_response,
|
||||||
}]} />);
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case ChallengeFeedbackStatus.RateLimitExceeded:
|
case ChallengeFeedbackStatus.RateLimitExceeded:
|
||||||
return (<Notifications notifications={[{
|
return (
|
||||||
|
<Notifications
|
||||||
|
notifications={[
|
||||||
|
{
|
||||||
type: "ERROR",
|
type: "ERROR",
|
||||||
message: `Message from provider`,
|
message: `Message from provider`,
|
||||||
description: "There were to many failed attempts."
|
description: "There were to many failed attempts.",
|
||||||
}]} />);
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case ChallengeFeedbackStatus.Redirect:
|
case ChallengeFeedbackStatus.Redirect:
|
||||||
return (<Notifications notifications={[{
|
return (
|
||||||
|
<Notifications
|
||||||
|
notifications={[
|
||||||
|
{
|
||||||
type: "INFO",
|
type: "INFO",
|
||||||
message: `Message from provider`,
|
message: `Message from provider`,
|
||||||
description: <span>
|
description: (
|
||||||
|
<span>
|
||||||
Please visit this link: <a>{feedback.redirect_url}</a>
|
Please visit this link: <a>{feedback.redirect_url}</a>
|
||||||
</span>
|
</span>
|
||||||
}]} />);
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case ChallengeFeedbackStatus.Unsupported:
|
case ChallengeFeedbackStatus.Unsupported:
|
||||||
return (<Notifications notifications={[{
|
return (
|
||||||
|
<Notifications
|
||||||
|
notifications={[
|
||||||
|
{
|
||||||
type: "ERROR",
|
type: "ERROR",
|
||||||
message: `This client doesn't support solving this type of challenge`,
|
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}"`
|
description: `Use another version or contact the provider. Type of challenge "${feedback.unsupported_method}"`,
|
||||||
}]} />);
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case ChallengeFeedbackStatus.TruthUnknown:
|
case ChallengeFeedbackStatus.TruthUnknown:
|
||||||
return (<Notifications notifications={[{
|
return (
|
||||||
|
<Notifications
|
||||||
|
notifications={[
|
||||||
|
{
|
||||||
type: "ERROR",
|
type: "ERROR",
|
||||||
message: `Provider doesn't recognize the type of challenge`,
|
message: `Provider doesn't recognize the type of challenge`,
|
||||||
description: "Contact the provider for further information"
|
description: "Contact the provider for further information",
|
||||||
}]} />);
|
},
|
||||||
default:
|
]}
|
||||||
return (
|
/>
|
||||||
<div>
|
|
||||||
<pre>{JSON.stringify(feedback)}</pre>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
default:
|
||||||
|
return <div />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +160,16 @@ export function SolveScreen(): VNode {
|
|||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery problem">
|
<AnastasisClientFrame hideNav title="Recovery problem">
|
||||||
<div>invalid state</div>
|
<div>invalid state</div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
@ -120,26 +178,36 @@ export function SolveScreen(): VNode {
|
|||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Not implemented">
|
<AnastasisClientFrame hideNav title="Not implemented">
|
||||||
<p>
|
<p>
|
||||||
The challenge selected is not supported for this UI. Please update this
|
The challenge selected is not supported for this UI. Please update
|
||||||
version or try using another policy.
|
this version or try using another policy.
|
||||||
</p>
|
</p>
|
||||||
{reducer &&
|
{reducer && (
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const chArr = reducer.currentReducerState.recovery_information.challenges;
|
const chArr = reducer.currentReducerState.recovery_information.challenges;
|
||||||
const selectedUuid = reducer.currentReducerState.selected_challenge_uuid;
|
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] ?
|
const SolveDialog =
|
||||||
SolveNotImplemented :
|
!selectedChallenge ||
|
||||||
authMethods[selectedChallenge.type as KnownAuthMethods].solve ?? SolveNotImplemented
|
!authMethods[selectedChallenge.type as KnownAuthMethods]
|
||||||
|
? SolveNotImplemented
|
||||||
|
: authMethods[selectedChallenge.type as KnownAuthMethods].solve ??
|
||||||
|
SolveNotImplemented;
|
||||||
|
|
||||||
return <SolveDialog id={selectedUuid} />
|
return <SolveDialog id={selectedUuid} />;
|
||||||
}
|
}
|
||||||
|
@ -15,24 +15,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { StartScreen as TestedComponent } from './StartScreen';
|
|
||||||
|
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { StartScreen as TestedComponent } from "./StartScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/Start',
|
title: "Pages/Start",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InitialState = createExample(TestedComponent, reducerStatesExample.initial);
|
export const InitialState = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
reducerStatesExample.initial,
|
||||||
|
);
|
||||||
|
@ -1,27 +1,36 @@
|
|||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
import { useAnastasisContext } from "../../context/anastasis";
|
||||||
import { AnastasisClientFrame } from "./index";
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
export function StartScreen(): VNode {
|
export function StartScreen(): VNode {
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext();
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Home">
|
<AnastasisClientFrame hideNav title="Home">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column" />
|
<div class="column" />
|
||||||
<div class="column is-four-fifths">
|
<div class="column is-four-fifths">
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button class="button is-success" autoFocus onClick={() => reducer.startBackup()}>
|
<button
|
||||||
<div class="icon"><i class="mdi mdi-arrow-up" /></div>
|
class="button is-success"
|
||||||
|
autoFocus
|
||||||
|
onClick={() => reducer.startBackup()}
|
||||||
|
>
|
||||||
|
<div class="icon">
|
||||||
|
<i class="mdi mdi-arrow-up" />
|
||||||
|
</div>
|
||||||
<span>Backup a secret</span>
|
<span>Backup a secret</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="button is-info" onClick={() => reducer.startRecover()}>
|
<button
|
||||||
<div class="icon"><i class="mdi mdi-arrow-down" /></div>
|
class="button is-info"
|
||||||
|
onClick={() => reducer.startRecover()}
|
||||||
|
>
|
||||||
|
<div class="icon">
|
||||||
|
<i class="mdi mdi-arrow-down" />
|
||||||
|
</div>
|
||||||
<span>Recover a secret</span>
|
<span>Recover a secret</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -30,7 +39,6 @@ export function StartScreen(): VNode {
|
|||||||
<span>Restore a session</span>
|
<span>Restore a session</span>
|
||||||
</button> */}
|
</button> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="column" />
|
<div class="column" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,29 +15,31 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../utils';
|
|
||||||
import { TruthsPayingScreen as TestedComponent } from './TruthsPayingScreen';
|
|
||||||
|
|
||||||
|
import { ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../utils";
|
||||||
|
import { TruthsPayingScreen as TestedComponent } from "./TruthsPayingScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/__TruthsPaying',
|
title: "Pages/backup/__TruthsPaying",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 10,
|
order: 10,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Example = createExample(TestedComponent, reducerStatesExample.truthsPaying);
|
export const Example = createExample(
|
||||||
|
TestedComponent,
|
||||||
|
reducerStatesExample.truthsPaying,
|
||||||
|
);
|
||||||
export const WithPaytoList = createExample(TestedComponent, {
|
export const WithPaytoList = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.truthsPaying,
|
...reducerStatesExample.truthsPaying,
|
||||||
payments: ['payto://x-taler-bank/bank/account']
|
payments: ["payto://x-taler-bank/bank/account"],
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
@ -3,19 +3,19 @@ import { useAnastasisContext } from "../../context/anastasis";
|
|||||||
import { AnastasisClientFrame } from "./index";
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
export function TruthsPayingScreen(): VNode {
|
export function TruthsPayingScreen(): VNode {
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext();
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
|
if (
|
||||||
return <div>invalid state</div>
|
!reducer.currentReducerState ||
|
||||||
|
reducer.currentReducerState.backup_state === undefined
|
||||||
|
) {
|
||||||
|
return <div>invalid state</div>;
|
||||||
}
|
}
|
||||||
const payments = reducer.currentReducerState.payments ?? [];
|
const payments = reducer.currentReducerState.payments ?? [];
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame
|
<AnastasisClientFrame hideNext={"FIXME"} title="Backup: Truths Paying">
|
||||||
hideNext={"FIXME"}
|
|
||||||
title="Backup: Truths Paying"
|
|
||||||
>
|
|
||||||
<p>
|
<p>
|
||||||
Some of the providers require a payment to store the encrypted
|
Some of the providers require a payment to store the encrypted
|
||||||
authentication information.
|
authentication information.
|
||||||
|
@ -15,51 +15,67 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/AuthorizationMethod/AuthMethods/email',
|
title: "Pages/backup/AuthorizationMethod/AuthMethods/email",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'email'
|
const type: KnownAuthMethods = "email";
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const Empty = createExample(
|
||||||
configured: []
|
TestedComponent[type].setup,
|
||||||
});
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Email to sebasjm@email.com ',
|
instructions: "Email to sebasjm@email.com ",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithMoreExamples = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Email to sebasjm@email.com',
|
instructions: "Email to sebasjm@email.com",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
},{
|
},
|
||||||
challenge: 'qwe',
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Email to someone@sebasjm.com',
|
instructions: "Email to someone@sebasjm.com",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -1,57 +1,90 @@
|
|||||||
import {
|
import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util";
|
||||||
encodeCrock,
|
|
||||||
stringToBytes
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { EmailInput } from "../../../components/fields/EmailInput";
|
import { EmailInput } from "../../../components/fields/EmailInput";
|
||||||
import { AnastasisClientFrame } from "../index";
|
import { AnastasisClientFrame } from "../index";
|
||||||
import { AuthMethodSetupProps } 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 [email, setEmail] = useState("");
|
||||||
const addEmailAuth = (): void => addAuthMethod({
|
const addEmailAuth = (): void =>
|
||||||
|
addAuthMethod({
|
||||||
authentication_method: {
|
authentication_method: {
|
||||||
type: "email",
|
type: "email",
|
||||||
instructions: `Email to ${email}`,
|
instructions: `Email to ${email}`,
|
||||||
challenge: encodeCrock(stringToBytes(email)),
|
challenge: encodeCrock(stringToBytes(email)),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const emailError = !EMAIL_PATTERN.test(email) ? 'Email address is not valid' : undefined
|
const emailError = !EMAIL_PATTERN.test(email)
|
||||||
const errors = !email ? 'Add your email' : emailError
|
? "Email address is not valid"
|
||||||
|
: undefined;
|
||||||
|
const errors = !email ? "Add your email" : emailError;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add email authentication">
|
<AnastasisClientFrame hideNav title="Add email authentication">
|
||||||
<p>
|
<p>
|
||||||
For email authentication, you need to provide an email address. When
|
For email authentication, you need to provide an email address. When
|
||||||
recovering your secret, you will need to enter the code you receive by
|
recovering your secret, you will need to enter the code you receive by
|
||||||
email.
|
email. Add the uuid from the challenge
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<EmailInput
|
<EmailInput
|
||||||
label="Email address"
|
label="Email address"
|
||||||
error={emailError}
|
error={emailError}
|
||||||
placeholder="email@domain.com"
|
placeholder="email@domain.com"
|
||||||
bind={[email, setEmail]} />
|
bind={[email, setEmail]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{configured.length > 0 && <section class="section">
|
{configured.length > 0 && (
|
||||||
|
<section class="section">
|
||||||
|
<div class="block">Your emails:</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
Your emails:
|
|
||||||
</div><div class="block">
|
|
||||||
{configured.map((c, i) => {
|
{configured.map((c, i) => {
|
||||||
return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
return (
|
||||||
<p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p>
|
<div
|
||||||
<div><button class="button is-danger" onClick={c.remove} >Delete</button></div>
|
key={i}
|
||||||
</div>
|
class="box"
|
||||||
})}
|
style={{ display: "flex", justifyContent: "space-between" }}
|
||||||
</div></section>}
|
>
|
||||||
|
<p style={{ marginBottom: "auto", marginTop: "auto" }}>
|
||||||
|
{c.instructions}
|
||||||
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<button class="button is-danger" onClick={c.remove}>
|
||||||
<button class="button" onClick={cancel}>Cancel</button>
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={cancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
<span data-tooltip={errors}>
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,66 +15,76 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SolveChallenge/AuthMethods/email',
|
title: "Pages/recovery/SolveChallenge/AuthMethods/email",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'email'
|
const type: KnownAuthMethods = "email";
|
||||||
|
|
||||||
export const WithoutFeedback = createExample(TestedComponent[type].solve, {
|
export const WithoutFeedback = createExample(
|
||||||
|
TestedComponent[type].solve,
|
||||||
|
{
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'uuid-1'
|
type: "question",
|
||||||
}],
|
uuid: "uuid-1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'uuid-1',
|
selected_challenge_uuid: "uuid-1",
|
||||||
} as ReducerState, {
|
} as ReducerState,
|
||||||
id: 'uuid-1',
|
{
|
||||||
});
|
id: "uuid-1",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const PaymentFeedback = createExample(TestedComponent[type].solve, {
|
export const PaymentFeedback = createExample(
|
||||||
|
TestedComponent[type].solve,
|
||||||
|
{
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'uuid-1'
|
type: "question",
|
||||||
}],
|
uuid: "uuid-1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'uuid-1',
|
selected_challenge_uuid: "uuid-1",
|
||||||
challenge_feedback: {
|
challenge_feedback: {
|
||||||
'uuid-1': {
|
"uuid-1": {
|
||||||
state: ChallengeFeedbackStatus.Payment,
|
state: ChallengeFeedbackStatus.Payment,
|
||||||
taler_pay_uri: "taler://pay/...",
|
taler_pay_uri: "taler://pay/...",
|
||||||
provider: "https://localhost:8080/",
|
provider: "https://localhost:8080/",
|
||||||
payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG"
|
payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
} as ReducerState, {
|
} as ReducerState,
|
||||||
id: 'uuid-1',
|
{
|
||||||
});
|
id: "uuid-1",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -44,8 +44,16 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery problem">
|
<AnastasisClientFrame hideNav title="Recovery problem">
|
||||||
<div>invalid state</div>
|
<div>invalid state</div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
@ -62,8 +70,7 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
challenges[ch.uuid] = ch;
|
challenges[ch.uuid] = ch;
|
||||||
}
|
}
|
||||||
const selectedChallenge = challenges[selectedUuid];
|
const selectedChallenge = challenges[selectedUuid];
|
||||||
const feedback = challengeFeedback[selectedUuid]
|
const feedback = challengeFeedback[selectedUuid];
|
||||||
|
|
||||||
|
|
||||||
async function onNext(): Promise<void> {
|
async function onNext(): Promise<void> {
|
||||||
return reducer?.transition("solve_challenge", { answer });
|
return reducer?.transition("solve_challenge", { answer });
|
||||||
@ -72,18 +79,19 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
reducer?.back();
|
reducer?.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldHideConfirm =
|
||||||
const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded
|
feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Redirect
|
feedback?.state === ChallengeFeedbackStatus.Redirect ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Unsupported
|
feedback?.state === ChallengeFeedbackStatus.Unsupported ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.TruthUnknown
|
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add email authentication">
|
<AnastasisClientFrame hideNav title="Add email authentication">
|
||||||
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
||||||
<p>
|
<p>
|
||||||
An email has been sent to "<b>{selectedChallenge.instructions}</b>". Type the
|
An email has been sent to "<b>{selectedChallenge.instructions}</b>".
|
||||||
code below
|
Type the code below.
|
||||||
|
<b>Here we need to add the code "{selectedUuid}"</b>
|
||||||
</p>
|
</p>
|
||||||
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
|
||||||
@ -97,9 +105,11 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
<button class="button" onClick={onCancel}>
|
<button class="button" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
{!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}>
|
{!shouldHideConfirm && (
|
||||||
|
<AsyncButton class="button is-info" onClick={onNext}>
|
||||||
Confirm
|
Confirm
|
||||||
</AsyncButton>}
|
</AsyncButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
|
@ -15,50 +15,66 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/AuthorizationMethod/AuthMethods/IBAN',
|
title: "Pages/backup/AuthorizationMethod/AuthMethods/IBAN",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'iban'
|
const type: KnownAuthMethods = "iban";
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const Empty = createExample(
|
||||||
configured: []
|
TestedComponent[type].setup,
|
||||||
});
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Wire transfer from QWEASD123123 with holder Sebastian',
|
instructions: "Wire transfer from QWEASD123123 with holder Sebastian",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
},
|
||||||
configured: [{
|
);
|
||||||
challenge: 'qwe',
|
export const WithMoreExamples = createExample(
|
||||||
|
TestedComponent[type].setup,
|
||||||
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Wire transfer from QWEASD123123 with holder Javier',
|
instructions: "Wire transfer from QWEASD123123 with holder Javier",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
},{
|
},
|
||||||
challenge: 'qwe',
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Wire transfer from QWEASD123123 with holder Sebastian',
|
instructions: "Wire transfer from QWEASD123123 with holder Sebastian",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
},);
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
canonicalJson,
|
canonicalJson,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
stringToBytes
|
stringToBytes,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
@ -9,56 +9,98 @@ import { AuthMethodSetupProps } from ".";
|
|||||||
import { TextInput } from "../../../components/fields/TextInput";
|
import { TextInput } from "../../../components/fields/TextInput";
|
||||||
import { AnastasisClientFrame } from "../index";
|
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 [name, setName] = useState("");
|
||||||
const [account, setAccount] = useState("");
|
const [account, setAccount] = useState("");
|
||||||
const addIbanAuth = (): void => addAuthMethod({
|
const addIbanAuth = (): void =>
|
||||||
|
addAuthMethod({
|
||||||
authentication_method: {
|
authentication_method: {
|
||||||
type: "iban",
|
type: "iban",
|
||||||
instructions: `Wire transfer from ${account} with holder ${name}`,
|
instructions: `Wire transfer from ${account} with holder ${name}`,
|
||||||
challenge: encodeCrock(stringToBytes(canonicalJson({
|
challenge: encodeCrock(
|
||||||
name, account
|
stringToBytes(
|
||||||
}))),
|
canonicalJson({
|
||||||
|
name,
|
||||||
|
account,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const errors = !name ? 'Add an account name' : (
|
const errors = !name
|
||||||
!account ? 'Add an account IBAN number' : undefined
|
? "Add an account name"
|
||||||
)
|
: !account
|
||||||
|
? "Add an account IBAN number"
|
||||||
|
: undefined;
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add bank transfer authentication">
|
<AnastasisClientFrame hideNav title="Add bank transfer authentication">
|
||||||
<p>
|
<p>
|
||||||
For bank transfer authentication, you need to provide a bank
|
For bank transfer authentication, you need to provide a bank account
|
||||||
account (account holder name and IBAN). When recovering your
|
(account holder name and IBAN). When recovering your secret, you will be
|
||||||
secret, you will be asked to pay the recovery fee via bank
|
asked to pay the recovery fee via bank transfer from the account you
|
||||||
transfer from the account you provided here.
|
provided here.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Bank account holder name"
|
label="Bank account holder name"
|
||||||
grabFocus
|
grabFocus
|
||||||
placeholder="John Smith"
|
placeholder="John Smith"
|
||||||
bind={[name, setName]} />
|
bind={[name, setName]}
|
||||||
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="IBAN"
|
label="IBAN"
|
||||||
placeholder="DE91100000000123456789"
|
placeholder="DE91100000000123456789"
|
||||||
bind={[account, setAccount]} />
|
bind={[account, setAccount]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{configured.length > 0 && <section class="section">
|
{configured.length > 0 && (
|
||||||
|
<section class="section">
|
||||||
|
<div class="block">Your bank accounts:</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
Your bank accounts:
|
|
||||||
</div><div class="block">
|
|
||||||
{configured.map((c, i) => {
|
{configured.map((c, i) => {
|
||||||
return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
return (
|
||||||
<p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p>
|
<div
|
||||||
<div><button class="button is-danger" onClick={c.remove} >Delete</button></div>
|
key={i}
|
||||||
</div>
|
class="box"
|
||||||
})}
|
style={{ display: "flex", justifyContent: "space-between" }}
|
||||||
</div></section>}
|
>
|
||||||
|
<p style={{ marginBottom: "auto", marginTop: "auto" }}>
|
||||||
|
{c.instructions}
|
||||||
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<button class="button is-danger" onClick={c.remove}>
|
||||||
<button class="button" onClick={cancel}>Cancel</button>
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={cancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
<span data-tooltip={errors}>
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,42 +15,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SolveChallenge/AuthMethods/Iban',
|
title: "Pages/recovery/SolveChallenge/AuthMethods/Iban",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'iban'
|
const type: KnownAuthMethods = "iban";
|
||||||
|
|
||||||
export const WithoutFeedback = createExample(TestedComponent[type].solve, {
|
export const WithoutFeedback = createExample(
|
||||||
|
TestedComponent[type].solve,
|
||||||
|
{
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'uuid-1'
|
type: "question",
|
||||||
}],
|
uuid: "uuid-1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'uuid-1',
|
selected_challenge_uuid: "uuid-1",
|
||||||
} as ReducerState, {
|
} as ReducerState,
|
||||||
id: 'uuid-1',
|
{
|
||||||
});
|
id: "uuid-1",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -44,8 +44,16 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery problem">
|
<AnastasisClientFrame hideNav title="Recovery problem">
|
||||||
<div>invalid state</div>
|
<div>invalid state</div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
@ -62,8 +70,7 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
challenges[ch.uuid] = ch;
|
challenges[ch.uuid] = ch;
|
||||||
}
|
}
|
||||||
const selectedChallenge = challenges[selectedUuid];
|
const selectedChallenge = challenges[selectedUuid];
|
||||||
const feedback = challengeFeedback[selectedUuid]
|
const feedback = challengeFeedback[selectedUuid];
|
||||||
|
|
||||||
|
|
||||||
async function onNext(): Promise<void> {
|
async function onNext(): Promise<void> {
|
||||||
return reducer?.transition("solve_challenge", { answer });
|
return reducer?.transition("solve_challenge", { answer });
|
||||||
@ -72,19 +79,17 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
reducer?.back();
|
reducer?.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldHideConfirm =
|
||||||
const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded
|
feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Redirect
|
feedback?.state === ChallengeFeedbackStatus.Redirect ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Unsupported
|
feedback?.state === ChallengeFeedbackStatus.Unsupported ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.TruthUnknown
|
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add email authentication">
|
<AnastasisClientFrame hideNav title="Add email authentication">
|
||||||
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
||||||
<p>
|
<p>Send a wire transfer to the address,</p>
|
||||||
Send a wire transfer to the address
|
<button class="button">Check</button>
|
||||||
</p>
|
|
||||||
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -96,9 +101,11 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
<button class="button" onClick={onCancel}>
|
<button class="button" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
{!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}>
|
{!shouldHideConfirm && (
|
||||||
|
<AsyncButton class="button is-info" onClick={onNext}>
|
||||||
Confirm
|
Confirm
|
||||||
</AsyncButton>}
|
</AsyncButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
|
@ -16,51 +16,67 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/AuthorizationMethod/AuthMethods/Post',
|
title: "Pages/backup/AuthorizationMethod/AuthMethods/Post",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'post'
|
const type: KnownAuthMethods = "post";
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const Empty = createExample(
|
||||||
configured: []
|
TestedComponent[type].setup,
|
||||||
});
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Letter to address in postal code QWE456',
|
instructions: "Letter to address in postal code QWE456",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithMoreExamples = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Letter to address in postal code QWE456',
|
instructions: "Letter to address in postal code QWE456",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
},{
|
},
|
||||||
challenge: 'qwe',
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Letter to address in postal code ABC123',
|
instructions: "Letter to address in postal code ABC123",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
canonicalJson, encodeCrock,
|
canonicalJson,
|
||||||
stringToBytes
|
encodeCrock,
|
||||||
|
stringToBytes,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
@ -8,7 +9,11 @@ import { AnastasisClientFrame } from "..";
|
|||||||
import { TextInput } from "../../../components/fields/TextInput";
|
import { TextInput } from "../../../components/fields/TextInput";
|
||||||
import { AuthMethodSetupProps } from "./index";
|
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 [fullName, setFullName] = useState("");
|
||||||
const [street, setStreet] = useState("");
|
const [street, setStreet] = useState("");
|
||||||
const [city, setCity] = useState("");
|
const [city, setCity] = useState("");
|
||||||
@ -32,68 +37,83 @@ export function AuthMethodPostSetup({ addAuthMethod, cancel, configured }: AuthM
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const errors = !fullName ? 'The full name is missing' : (
|
const errors = !fullName
|
||||||
!street ? 'The street is missing' : (
|
? "The full name is missing"
|
||||||
!city ? 'The city is missing' : (
|
: !street
|
||||||
!postcode ? 'The postcode is missing' : (
|
? "The street is missing"
|
||||||
!country ? 'The country is missing' : undefined
|
: !city
|
||||||
)
|
? "The city is missing"
|
||||||
)
|
: !postcode
|
||||||
)
|
? "The postcode is missing"
|
||||||
)
|
: !country
|
||||||
|
? "The country is missing"
|
||||||
|
: undefined;
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add postal authentication">
|
<AnastasisClientFrame hideNav title="Add postal authentication">
|
||||||
<p>
|
<p>
|
||||||
For postal letter authentication, you need to provide a postal
|
For postal letter authentication, you need to provide a postal address.
|
||||||
address. When recovering your secret, you will be asked to enter a
|
When recovering your secret, you will be asked to enter a code that you
|
||||||
code that you will receive in a letter to that address.
|
will receive in a letter to that address.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<TextInput
|
<TextInput grabFocus label="Full Name" bind={[fullName, setFullName]} />
|
||||||
grabFocus
|
|
||||||
label="Full Name"
|
|
||||||
bind={[fullName, setFullName]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TextInput
|
<TextInput label="Street" bind={[street, setStreet]} />
|
||||||
label="Street"
|
|
||||||
bind={[street, setStreet]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TextInput
|
<TextInput label="City" bind={[city, setCity]} />
|
||||||
label="City" bind={[city, setCity]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TextInput
|
<TextInput label="Postal Code" bind={[postcode, setPostcode]} />
|
||||||
label="Postal Code" bind={[postcode, setPostcode]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TextInput
|
<TextInput label="Country" bind={[country, setCountry]} />
|
||||||
label="Country"
|
|
||||||
bind={[country, setCountry]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{configured.length > 0 && <section class="section">
|
{configured.length > 0 && (
|
||||||
|
<section class="section">
|
||||||
|
<div class="block">Your postal code:</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
Your postal code:
|
|
||||||
</div><div class="block">
|
|
||||||
{configured.map((c, i) => {
|
{configured.map((c, i) => {
|
||||||
return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
return (
|
||||||
<p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p>
|
<div
|
||||||
<div><button class="button is-danger" onClick={c.remove} >Delete</button></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>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</section>}
|
</section>
|
||||||
<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}>
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
|
@ -15,42 +15,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SolveChallenge/AuthMethods/post',
|
title: "Pages/recovery/SolveChallenge/AuthMethods/post",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'post'
|
const type: KnownAuthMethods = "post";
|
||||||
|
|
||||||
export const WithoutFeedback = createExample(TestedComponent[type].solve, {
|
export const WithoutFeedback = createExample(
|
||||||
|
TestedComponent[type].solve,
|
||||||
|
{
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'uuid-1'
|
type: "question",
|
||||||
}],
|
uuid: "uuid-1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'uuid-1',
|
selected_challenge_uuid: "uuid-1",
|
||||||
} as ReducerState, {
|
} as ReducerState,
|
||||||
id: 'uuid-1',
|
{
|
||||||
});
|
id: "uuid-1",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -44,8 +44,16 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery problem">
|
<AnastasisClientFrame hideNav title="Recovery problem">
|
||||||
<div>invalid state</div>
|
<div>invalid state</div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
@ -62,8 +70,7 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
challenges[ch.uuid] = ch;
|
challenges[ch.uuid] = ch;
|
||||||
}
|
}
|
||||||
const selectedChallenge = challenges[selectedUuid];
|
const selectedChallenge = challenges[selectedUuid];
|
||||||
const feedback = challengeFeedback[selectedUuid]
|
const feedback = challengeFeedback[selectedUuid];
|
||||||
|
|
||||||
|
|
||||||
async function onNext(): Promise<void> {
|
async function onNext(): Promise<void> {
|
||||||
return reducer?.transition("solve_challenge", { answer });
|
return reducer?.transition("solve_challenge", { answer });
|
||||||
@ -72,18 +79,16 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
reducer?.back();
|
reducer?.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldHideConfirm =
|
||||||
const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded
|
feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Redirect
|
feedback?.state === ChallengeFeedbackStatus.Redirect ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Unsupported
|
feedback?.state === ChallengeFeedbackStatus.Unsupported ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.TruthUnknown
|
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add email authentication">
|
<AnastasisClientFrame hideNav title="Add email authentication">
|
||||||
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
||||||
<p>
|
<p>Wait for the answer</p>
|
||||||
Wait for the answer
|
|
||||||
</p>
|
|
||||||
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -96,9 +101,11 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
<button class="button" onClick={onCancel}>
|
<button class="button" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
{!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}>
|
{!shouldHideConfirm && (
|
||||||
|
<AsyncButton class="button is-info" onClick={onNext}>
|
||||||
Confirm
|
Confirm
|
||||||
</AsyncButton>}
|
</AsyncButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
|
@ -16,51 +16,69 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/AuthorizationMethod/AuthMethods/Question',
|
title: "Pages/backup/AuthorizationMethod/AuthMethods/Question",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'question'
|
const type: KnownAuthMethods = "question";
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const Empty = createExample(
|
||||||
configured: []
|
TestedComponent[type].setup,
|
||||||
});
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Is integer factorization polynomial? (non-quantum computer)',
|
instructions:
|
||||||
remove: () => null
|
"Is integer factorization polynomial? (non-quantum computer)",
|
||||||
}]
|
remove: () => null,
|
||||||
});
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithMoreExamples = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Does P equal NP?',
|
instructions: "Does P equal NP?",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
},{
|
},
|
||||||
challenge: 'asd',
|
{
|
||||||
|
challenge: "asd",
|
||||||
type,
|
type,
|
||||||
instructions: 'Are continuous groups automatically differential groups?',
|
instructions:
|
||||||
remove: () => null
|
"Are continuous groups automatically differential groups?",
|
||||||
}]
|
remove: () => null,
|
||||||
});
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
import {
|
import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util";
|
||||||
encodeCrock,
|
|
||||||
stringToBytes
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { AuthMethodSetupProps } from "./index";
|
import { AuthMethodSetupProps } from "./index";
|
||||||
import { AnastasisClientFrame } from "../index";
|
import { AnastasisClientFrame } from "../index";
|
||||||
import { TextInput } from "../../../components/fields/TextInput";
|
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 [questionText, setQuestionText] = useState("");
|
||||||
const [answerText, setAnswerText] = useState("");
|
const [answerText, setAnswerText] = useState("");
|
||||||
const addQuestionAuth = (): void => addAuthMethod({
|
const addQuestionAuth = (): void =>
|
||||||
|
addAuthMethod({
|
||||||
authentication_method: {
|
authentication_method: {
|
||||||
type: "question",
|
type: "question",
|
||||||
instructions: questionText,
|
instructions: questionText,
|
||||||
@ -19,9 +21,11 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const errors = !questionText ? "Add your security question" : (
|
const errors = !questionText
|
||||||
!answerText ? 'Add the answer to your question' : undefined
|
? "Add your security question"
|
||||||
)
|
: !answerText
|
||||||
|
? "Add the answer to your question"
|
||||||
|
: undefined;
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add Security Question">
|
<AnastasisClientFrame hideNav title="Add Security Question">
|
||||||
<div>
|
<div>
|
||||||
@ -36,7 +40,8 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A
|
|||||||
label="Security question"
|
label="Security question"
|
||||||
grabFocus
|
grabFocus
|
||||||
placeholder="Your question"
|
placeholder="Your question"
|
||||||
bind={[questionText, setQuestionText]} />
|
bind={[questionText, setQuestionText]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TextInput
|
<TextInput
|
||||||
@ -46,25 +51,53 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={cancel}>Cancel</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={cancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
<span data-tooltip={errors}>
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{configured.length > 0 && <section class="section">
|
{configured.length > 0 && (
|
||||||
|
<section class="section">
|
||||||
|
<div class="block">Your security questions:</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
Your security questions:
|
|
||||||
</div><div class="block">
|
|
||||||
{configured.map((c, i) => {
|
{configured.map((c, i) => {
|
||||||
return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
return (
|
||||||
<p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p>
|
<div
|
||||||
<div><button class="button is-danger" onClick={c.remove} >Delete</button></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>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</div></section>}
|
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame >
|
</section>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,186 +15,205 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SolveChallenge/AuthMethods/question',
|
title: "Pages/recovery/SolveChallenge/AuthMethods/question",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'question'
|
const type: KnownAuthMethods = "question";
|
||||||
|
|
||||||
export const WithoutFeedback = createExample(TestedComponent[type].solve, {
|
export const WithoutFeedback = createExample(
|
||||||
|
TestedComponent[type].solve,
|
||||||
|
{
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'uuid-1'
|
type: "question",
|
||||||
}],
|
uuid: "uuid-1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'uuid-1',
|
selected_challenge_uuid: "uuid-1",
|
||||||
} as ReducerState, {
|
} as ReducerState,
|
||||||
id: 'uuid-1',
|
{
|
||||||
});
|
id: "uuid-1",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const MessageFeedback = createExample(TestedComponent[type].solve, {
|
export const MessageFeedback = createExample(TestedComponent[type].solve, {
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'ASDASDSAD!1'
|
type: "question",
|
||||||
}],
|
uuid: "ASDASDSAD!1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'ASDASDSAD!1',
|
selected_challenge_uuid: "ASDASDSAD!1",
|
||||||
challenge_feedback: {
|
challenge_feedback: {
|
||||||
'ASDASDSAD!1': {
|
"ASDASDSAD!1": {
|
||||||
state: ChallengeFeedbackStatus.Message,
|
state: ChallengeFeedbackStatus.Message,
|
||||||
message: 'Challenge should be solved'
|
message: "Challenge should be solved",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const ServerFailureFeedback = createExample(TestedComponent[type].solve, {
|
export const ServerFailureFeedback = createExample(
|
||||||
|
TestedComponent[type].solve,
|
||||||
|
{
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'ASDASDSAD!1'
|
type: "question",
|
||||||
}],
|
uuid: "ASDASDSAD!1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'ASDASDSAD!1',
|
selected_challenge_uuid: "ASDASDSAD!1",
|
||||||
challenge_feedback: {
|
challenge_feedback: {
|
||||||
'ASDASDSAD!1': {
|
"ASDASDSAD!1": {
|
||||||
state: ChallengeFeedbackStatus.ServerFailure,
|
state: ChallengeFeedbackStatus.ServerFailure,
|
||||||
http_status: 500,
|
http_status: 500,
|
||||||
error_response: "Couldn't connect to mysql"
|
error_response: "Couldn't connect to mysql",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
} as ReducerState,
|
||||||
} as ReducerState);
|
);
|
||||||
|
|
||||||
export const RedirectFeedback = createExample(TestedComponent[type].solve, {
|
export const RedirectFeedback = createExample(TestedComponent[type].solve, {
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'ASDASDSAD!1'
|
type: "question",
|
||||||
}],
|
uuid: "ASDASDSAD!1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'ASDASDSAD!1',
|
selected_challenge_uuid: "ASDASDSAD!1",
|
||||||
challenge_feedback: {
|
challenge_feedback: {
|
||||||
'ASDASDSAD!1': {
|
"ASDASDSAD!1": {
|
||||||
state: ChallengeFeedbackStatus.Redirect,
|
state: ChallengeFeedbackStatus.Redirect,
|
||||||
http_status: 302,
|
http_status: 302,
|
||||||
redirect_url: 'http://video.taler.net'
|
redirect_url: "http://video.taler.net",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const MessageRateLimitExceededFeedback = createExample(TestedComponent[type].solve, {
|
export const MessageRateLimitExceededFeedback = createExample(
|
||||||
|
TestedComponent[type].solve,
|
||||||
|
{
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'ASDASDSAD!1'
|
type: "question",
|
||||||
}],
|
uuid: "ASDASDSAD!1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'ASDASDSAD!1',
|
selected_challenge_uuid: "ASDASDSAD!1",
|
||||||
challenge_feedback: {
|
challenge_feedback: {
|
||||||
'ASDASDSAD!1': {
|
"ASDASDSAD!1": {
|
||||||
state: ChallengeFeedbackStatus.RateLimitExceeded,
|
state: ChallengeFeedbackStatus.RateLimitExceeded,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
} as ReducerState,
|
||||||
} as ReducerState);
|
);
|
||||||
|
|
||||||
export const UnsupportedFeedback = createExample(TestedComponent[type].solve, {
|
export const UnsupportedFeedback = createExample(TestedComponent[type].solve, {
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'ASDASDSAD!1'
|
type: "question",
|
||||||
}],
|
uuid: "ASDASDSAD!1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'ASDASDSAD!1',
|
selected_challenge_uuid: "ASDASDSAD!1",
|
||||||
challenge_feedback: {
|
challenge_feedback: {
|
||||||
'ASDASDSAD!1': {
|
"ASDASDSAD!1": {
|
||||||
state: ChallengeFeedbackStatus.Unsupported,
|
state: ChallengeFeedbackStatus.Unsupported,
|
||||||
http_status: 500,
|
http_status: 500,
|
||||||
unsupported_method: 'Question'
|
unsupported_method: "Question",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const TruthUnknownFeedback = createExample(TestedComponent[type].solve, {
|
export const TruthUnknownFeedback = createExample(TestedComponent[type].solve, {
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'ASDASDSAD!1'
|
type: "question",
|
||||||
}],
|
uuid: "ASDASDSAD!1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'ASDASDSAD!1',
|
selected_challenge_uuid: "ASDASDSAD!1",
|
||||||
challenge_feedback: {
|
challenge_feedback: {
|
||||||
'ASDASDSAD!1': {
|
"ASDASDSAD!1": {
|
||||||
state: ChallengeFeedbackStatus.TruthUnknown,
|
state: ChallengeFeedbackStatus.TruthUnknown,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const AuthIbanFeedback = createExample(TestedComponent[type].solve, {
|
export const AuthIbanFeedback = createExample(TestedComponent[type].solve, {
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'ASDASDSAD!1'
|
type: "question",
|
||||||
}],
|
uuid: "ASDASDSAD!1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'ASDASDSAD!1',
|
selected_challenge_uuid: "ASDASDSAD!1",
|
||||||
challenge_feedback: {
|
challenge_feedback: {
|
||||||
'ASDASDSAD!1': {
|
"ASDASDSAD!1": {
|
||||||
state: ChallengeFeedbackStatus.AuthIban,
|
state: ChallengeFeedbackStatus.AuthIban,
|
||||||
challenge_amount: "EUR:1",
|
challenge_amount: "EUR:1",
|
||||||
credit_iban: "DE12345789000",
|
credit_iban: "DE12345789000",
|
||||||
@ -210,30 +229,30 @@ export const AuthIbanFeedback = createExample(TestedComponent[type].solve, {
|
|||||||
wire_transfer_subject: "foo",
|
wire_transfer_subject: "foo",
|
||||||
},
|
},
|
||||||
method: "iban",
|
method: "iban",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const PaymentFeedback = createExample(TestedComponent[type].solve, {
|
export const PaymentFeedback = createExample(TestedComponent[type].solve, {
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'ASDASDSAD!1'
|
type: "question",
|
||||||
}],
|
uuid: "ASDASDSAD!1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'ASDASDSAD!1',
|
selected_challenge_uuid: "ASDASDSAD!1",
|
||||||
challenge_feedback: {
|
challenge_feedback: {
|
||||||
'ASDASDSAD!1': {
|
"ASDASDSAD!1": {
|
||||||
state: ChallengeFeedbackStatus.Payment,
|
state: ChallengeFeedbackStatus.Payment,
|
||||||
taler_pay_uri : "taler://pay/...",
|
taler_pay_uri: "taler://pay/...",
|
||||||
provider : "https://localhost:8080/",
|
provider: "https://localhost:8080/",
|
||||||
payment_secret : "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG"
|
payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
|
@ -44,8 +44,16 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery problem">
|
<AnastasisClientFrame hideNav title="Recovery problem">
|
||||||
<div>invalid state</div>
|
<div>invalid state</div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
@ -62,8 +70,7 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
challenges[ch.uuid] = ch;
|
challenges[ch.uuid] = ch;
|
||||||
}
|
}
|
||||||
const selectedChallenge = challenges[selectedUuid];
|
const selectedChallenge = challenges[selectedUuid];
|
||||||
const feedback = challengeFeedback[selectedUuid]
|
const feedback = challengeFeedback[selectedUuid];
|
||||||
|
|
||||||
|
|
||||||
async function onNext(): Promise<void> {
|
async function onNext(): Promise<void> {
|
||||||
return reducer?.transition("solve_challenge", { answer });
|
return reducer?.transition("solve_challenge", { answer });
|
||||||
@ -72,18 +79,16 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
reducer?.back();
|
reducer?.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldHideConfirm =
|
||||||
const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded
|
feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Redirect
|
feedback?.state === ChallengeFeedbackStatus.Redirect ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Unsupported
|
feedback?.state === ChallengeFeedbackStatus.Unsupported ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.TruthUnknown
|
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add email authentication">
|
<AnastasisClientFrame hideNav title="Add email authentication">
|
||||||
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
||||||
<p>
|
<p>Answer the question please</p>
|
||||||
Answer the question please
|
|
||||||
</p>
|
|
||||||
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -96,9 +101,11 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
<button class="button" onClick={onCancel}>
|
<button class="button" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
{!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}>
|
{!shouldHideConfirm && (
|
||||||
|
<AsyncButton class="button is-info" onClick={onNext}>
|
||||||
Confirm
|
Confirm
|
||||||
</AsyncButton>}
|
</AsyncButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
|
@ -16,51 +16,67 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/AuthorizationMethod/AuthMethods/Sms',
|
title: "Pages/backup/AuthorizationMethod/AuthMethods/Sms",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'sms'
|
const type: KnownAuthMethods = "sms";
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const Empty = createExample(
|
||||||
configured: []
|
TestedComponent[type].setup,
|
||||||
});
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'SMS to +11-1234-2345',
|
instructions: "SMS to +11-1234-2345",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithMoreExamples = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'SMS to +11-1234-2345',
|
instructions: "SMS to +11-1234-2345",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
},{
|
},
|
||||||
challenge: 'qwe',
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'SMS to +11-5555-2345',
|
instructions: "SMS to +11-5555-2345",
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import {
|
import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util";
|
||||||
encodeCrock,
|
|
||||||
stringToBytes
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||||
import { AuthMethodSetupProps } from ".";
|
import { AuthMethodSetupProps } from ".";
|
||||||
import { PhoneNumberInput } from "../../../components/fields/NumberInput";
|
import { PhoneNumberInput } from "../../../components/fields/NumberInput";
|
||||||
import { AnastasisClientFrame } from "../index";
|
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 [mobileNumber, setMobileNumber] = useState("");
|
||||||
const addSmsAuth = (): void => {
|
const addSmsAuth = (): void => {
|
||||||
addAuthMethod({
|
addAuthMethod({
|
||||||
@ -23,7 +24,7 @@ export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMe
|
|||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
const errors = !mobileNumber ? 'Add a mobile number' : undefined
|
const errors = !mobileNumber ? "Add a mobile number" : undefined;
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add SMS authentication">
|
<AnastasisClientFrame hideNav title="Add SMS authentication">
|
||||||
<div>
|
<div>
|
||||||
@ -37,23 +38,52 @@ export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMe
|
|||||||
label="Mobile number"
|
label="Mobile number"
|
||||||
placeholder="Your mobile number"
|
placeholder="Your mobile number"
|
||||||
grabFocus
|
grabFocus
|
||||||
bind={[mobileNumber, setMobileNumber]} />
|
bind={[mobileNumber, setMobileNumber]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{configured.length > 0 && <section class="section">
|
{configured.length > 0 && (
|
||||||
|
<section class="section">
|
||||||
|
<div class="block">Your mobile numbers:</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
Your mobile numbers:
|
|
||||||
</div><div class="block">
|
|
||||||
{configured.map((c, i) => {
|
{configured.map((c, i) => {
|
||||||
return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
return (
|
||||||
<p style={{ marginTop: 'auto', marginBottom: 'auto' }}>{c.instructions}</p>
|
<div
|
||||||
<div><button class="button is-danger" onClick={c.remove}>Delete</button></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>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</div></section>}
|
</div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
</section>
|
||||||
<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}>
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,42 +15,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SolveChallenge/AuthMethods/sms',
|
title: "Pages/recovery/SolveChallenge/AuthMethods/sms",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'sms'
|
const type: KnownAuthMethods = "sms";
|
||||||
|
|
||||||
export const WithoutFeedback = createExample(TestedComponent[type].solve, {
|
export const WithoutFeedback = createExample(
|
||||||
|
TestedComponent[type].solve,
|
||||||
|
{
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'uuid-1'
|
type: "question",
|
||||||
}],
|
uuid: "uuid-1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'uuid-1',
|
selected_challenge_uuid: "uuid-1",
|
||||||
} as ReducerState, {
|
} as ReducerState,
|
||||||
id: 'uuid-1',
|
{
|
||||||
});
|
id: "uuid-1",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -44,8 +44,16 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery problem">
|
<AnastasisClientFrame hideNav title="Recovery problem">
|
||||||
<div>invalid state</div>
|
<div>invalid state</div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
@ -62,8 +70,7 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
challenges[ch.uuid] = ch;
|
challenges[ch.uuid] = ch;
|
||||||
}
|
}
|
||||||
const selectedChallenge = challenges[selectedUuid];
|
const selectedChallenge = challenges[selectedUuid];
|
||||||
const feedback = challengeFeedback[selectedUuid]
|
const feedback = challengeFeedback[selectedUuid];
|
||||||
|
|
||||||
|
|
||||||
async function onNext(): Promise<void> {
|
async function onNext(): Promise<void> {
|
||||||
return reducer?.transition("solve_challenge", { answer });
|
return reducer?.transition("solve_challenge", { answer });
|
||||||
@ -72,18 +79,18 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
reducer?.back();
|
reducer?.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldHideConfirm =
|
||||||
const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded
|
feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Redirect
|
feedback?.state === ChallengeFeedbackStatus.Redirect ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Unsupported
|
feedback?.state === ChallengeFeedbackStatus.Unsupported ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.TruthUnknown
|
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add email authentication">
|
<AnastasisClientFrame hideNav title="Add email authentication">
|
||||||
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
||||||
<p>
|
<p>
|
||||||
An sms has been sent to "<b>{selectedChallenge.instructions}</b>". Type the code
|
An sms has been sent to "<b>{selectedChallenge.instructions}</b>". Type
|
||||||
below
|
the code below
|
||||||
</p>
|
</p>
|
||||||
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
|
||||||
@ -97,9 +104,11 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
<button class="button" onClick={onCancel}>
|
<button class="button" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
{!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}>
|
{!shouldHideConfirm && (
|
||||||
|
<AsyncButton class="button is-info" onClick={onNext}>
|
||||||
Confirm
|
Confirm
|
||||||
</AsyncButton>}
|
</AsyncButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
|
@ -16,49 +16,65 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/AuthorizationMethod/AuthMethods/TOTP',
|
title: "Pages/backup/AuthorizationMethod/AuthMethods/TOTP",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'totp'
|
const type: KnownAuthMethods = "totp";
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const Empty = createExample(
|
||||||
configured: []
|
TestedComponent[type].setup,
|
||||||
});
|
reducerStatesExample.authEditing,
|
||||||
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
{
|
||||||
configured: [{
|
configured: [],
|
||||||
challenge: 'qwe',
|
},
|
||||||
|
);
|
||||||
|
export const WithOneExample = createExample(
|
||||||
|
TestedComponent[type].setup,
|
||||||
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Enter 8 digits code for "Anastasis"',
|
instructions: 'Enter 8 digits code for "Anastasis"',
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
export const WithMoreExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
},
|
||||||
configured: [{
|
);
|
||||||
challenge: 'qwe',
|
export const WithMoreExample = createExample(
|
||||||
|
TestedComponent[type].setup,
|
||||||
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Enter 8 digits code for "Anastasis1"',
|
instructions: 'Enter 8 digits code for "Anastasis1"',
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
},{
|
},
|
||||||
challenge: 'qwe',
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: 'Enter 8 digits code for "Anastasis2"',
|
instructions: 'Enter 8 digits code for "Anastasis2"',
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import {
|
import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util";
|
||||||
encodeCrock,
|
|
||||||
stringToBytes
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useMemo, useState } from "preact/hooks";
|
import { useMemo, useState } from "preact/hooks";
|
||||||
import { AuthMethodSetupProps } from "./index";
|
import { AuthMethodSetupProps } from "./index";
|
||||||
@ -10,18 +7,23 @@ import { TextInput } from "../../../components/fields/TextInput";
|
|||||||
import { QR } from "../../../components/QR";
|
import { QR } from "../../../components/QR";
|
||||||
import { base32enc, computeTOTPandCheck } from "./totp";
|
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 [name, setName] = useState("anastasis");
|
||||||
const [test, setTest] = useState("");
|
const [test, setTest] = useState("");
|
||||||
const digits = 8
|
const digits = 8;
|
||||||
const secretKey = useMemo(() => {
|
const secretKey = useMemo(() => {
|
||||||
const array = new Uint8Array(32)
|
const array = new Uint8Array(32);
|
||||||
return window.crypto.getRandomValues(array)
|
return window.crypto.getRandomValues(array);
|
||||||
}, [])
|
}, []);
|
||||||
const secret32 = base32enc(secretKey);
|
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({
|
const addTotpAuth = (): void =>
|
||||||
|
addAuthMethod({
|
||||||
authentication_method: {
|
authentication_method: {
|
||||||
type: "totp",
|
type: "totp",
|
||||||
instructions: `Enter ${digits} digits code for "${name}"`,
|
instructions: `Enter ${digits} digits code for "${name}"`,
|
||||||
@ -31,9 +33,11 @@ export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthM
|
|||||||
|
|
||||||
const testCodeMatches = computeTOTPandCheck(secretKey, 8, parseInt(test, 10));
|
const testCodeMatches = computeTOTPandCheck(secretKey, 8, parseInt(test, 10));
|
||||||
|
|
||||||
const errors = !name ? 'The TOTP name is missing' : (
|
const errors = !name
|
||||||
!testCodeMatches ? 'The test code doesnt match' : undefined
|
? "The TOTP name is missing"
|
||||||
);
|
: !testCodeMatches
|
||||||
|
? "The test code doesnt match"
|
||||||
|
: undefined;
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add TOTP authentication">
|
<AnastasisClientFrame hideNav title="Add TOTP authentication">
|
||||||
<p>
|
<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.
|
with your TOTP App to import the TOTP secret into your TOTP App.
|
||||||
</p>
|
</p>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<TextInput
|
<TextInput label="TOTP Name" grabFocus bind={[name, setName]} />
|
||||||
label="TOTP Name"
|
|
||||||
grabFocus
|
|
||||||
bind={[name, setName]} />
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ height: 300 }}>
|
<div style={{ height: 300 }}>
|
||||||
<QR text={totpURL} />
|
<QR text={totpURL} />
|
||||||
@ -53,25 +54,51 @@ export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthM
|
|||||||
<p>
|
<p>
|
||||||
After scanning the code with your TOTP App, test it in the input below.
|
After scanning the code with your TOTP App, test it in the input below.
|
||||||
</p>
|
</p>
|
||||||
<TextInput
|
<TextInput label="Test code" bind={[test, setTest]} />
|
||||||
label="Test code"
|
{configured.length > 0 && (
|
||||||
bind={[test, setTest]} />
|
<section class="section">
|
||||||
{configured.length > 0 && <section class="section">
|
<div class="block">Your TOTP numbers:</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
Your TOTP numbers:
|
|
||||||
</div><div class="block">
|
|
||||||
{configured.map((c, i) => {
|
{configured.map((c, i) => {
|
||||||
return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
return (
|
||||||
<p style={{ marginTop: 'auto', marginBottom: 'auto' }}>{c.instructions}</p>
|
<div
|
||||||
<div><button class="button is-danger" onClick={c.remove}>Delete</button></div>
|
key={i}
|
||||||
</div>
|
class="box"
|
||||||
})}
|
style={{ display: "flex", justifyContent: "space-between" }}
|
||||||
</div></section>}
|
>
|
||||||
|
<p style={{ marginTop: "auto", marginBottom: "auto" }}>
|
||||||
|
{c.instructions}
|
||||||
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<button class="button is-danger" onClick={c.remove}>
|
||||||
<button class="button" onClick={cancel}>Cancel</button>
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={cancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
<span data-tooltip={errors}>
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,42 +15,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SolveChallenge/AuthMethods/totp',
|
title: "Pages/recovery/SolveChallenge/AuthMethods/totp",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'totp'
|
const type: KnownAuthMethods = "totp";
|
||||||
|
|
||||||
export const WithoutFeedback = createExample(TestedComponent[type].solve, {
|
export const WithoutFeedback = createExample(
|
||||||
|
TestedComponent[type].solve,
|
||||||
|
{
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'uuid-1'
|
type: "question",
|
||||||
}],
|
uuid: "uuid-1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'uuid-1',
|
selected_challenge_uuid: "uuid-1",
|
||||||
} as ReducerState, {
|
} as ReducerState,
|
||||||
id: 'uuid-1',
|
{
|
||||||
});
|
id: "uuid-1",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -44,8 +44,16 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery problem">
|
<AnastasisClientFrame hideNav title="Recovery problem">
|
||||||
<div>invalid state</div>
|
<div>invalid state</div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
@ -62,8 +70,7 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
challenges[ch.uuid] = ch;
|
challenges[ch.uuid] = ch;
|
||||||
}
|
}
|
||||||
const selectedChallenge = challenges[selectedUuid];
|
const selectedChallenge = challenges[selectedUuid];
|
||||||
const feedback = challengeFeedback[selectedUuid]
|
const feedback = challengeFeedback[selectedUuid];
|
||||||
|
|
||||||
|
|
||||||
async function onNext(): Promise<void> {
|
async function onNext(): Promise<void> {
|
||||||
return reducer?.transition("solve_challenge", { answer });
|
return reducer?.transition("solve_challenge", { answer });
|
||||||
@ -72,18 +79,16 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
reducer?.back();
|
reducer?.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldHideConfirm =
|
||||||
const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded
|
feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Redirect
|
feedback?.state === ChallengeFeedbackStatus.Redirect ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Unsupported
|
feedback?.state === ChallengeFeedbackStatus.Unsupported ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.TruthUnknown
|
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add email authentication">
|
<AnastasisClientFrame hideNav title="Add email authentication">
|
||||||
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
||||||
<p>
|
<p>enter the totp solution</p>
|
||||||
enter the totp solution
|
|
||||||
</p>
|
|
||||||
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -96,9 +101,11 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
<button class="button" onClick={onCancel}>
|
<button class="button" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
{!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}>
|
{!shouldHideConfirm && (
|
||||||
|
<AsyncButton class="button is-info" onClick={onNext}>
|
||||||
Confirm
|
Confirm
|
||||||
</AsyncButton>}
|
</AsyncButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
|
@ -16,51 +16,68 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
import logoImage from '../../../assets/logo.jpeg'
|
import logoImage from "../../../assets/logo.jpeg";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/AuthorizationMethod/AuthMethods/Video',
|
title: "Pages/backup/AuthorizationMethod/AuthMethods/Video",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'video'
|
const type: KnownAuthMethods = "video";
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const Empty = createExample(
|
||||||
configured: []
|
TestedComponent[type].setup,
|
||||||
});
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: logoImage,
|
instructions: logoImage,
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
export const WithMoreExamples = createExample(
|
||||||
configured: [{
|
TestedComponent[type].setup,
|
||||||
challenge: 'qwe',
|
reducerStatesExample.authEditing,
|
||||||
|
{
|
||||||
|
configured: [
|
||||||
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: logoImage,
|
instructions: logoImage,
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
},{
|
},
|
||||||
challenge: 'qwe',
|
{
|
||||||
|
challenge: "qwe",
|
||||||
type,
|
type,
|
||||||
instructions: logoImage,
|
instructions: logoImage,
|
||||||
remove: () => null
|
remove: () => null,
|
||||||
}]
|
},
|
||||||
});
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -1,53 +1,86 @@
|
|||||||
import {
|
import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util";
|
||||||
encodeCrock,
|
|
||||||
stringToBytes
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { ImageInput } from "../../../components/fields/ImageInput";
|
import { ImageInput } from "../../../components/fields/ImageInput";
|
||||||
import { AuthMethodSetupProps } from "./index";
|
import { AuthMethodSetupProps } from "./index";
|
||||||
import { AnastasisClientFrame } 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 [image, setImage] = useState("");
|
||||||
const addVideoAuth = (): void => {
|
const addVideoAuth = (): void => {
|
||||||
addAuthMethod({
|
addAuthMethod({
|
||||||
authentication_method: {
|
authentication_method: {
|
||||||
type: "video",
|
type: "video",
|
||||||
instructions: 'Join a video call',
|
instructions: "Join a video call",
|
||||||
challenge: encodeCrock(stringToBytes(image)),
|
challenge: encodeCrock(stringToBytes(image)),
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add video authentication">
|
<AnastasisClientFrame hideNav title="Add video authentication">
|
||||||
<p>
|
<p>
|
||||||
For video identification, you need to provide a passport-style
|
For video identification, you need to provide a passport-style
|
||||||
photograph. When recovering your secret, you will be asked to join a
|
photograph. When recovering your secret, you will be asked to join a
|
||||||
video call. During that call, a human will use the photograph to
|
video call. During that call, a human will use the photograph to verify
|
||||||
verify your identity.
|
your identity.
|
||||||
</p>
|
</p>
|
||||||
<div style={{textAlign:'center'}}>
|
<div style={{ textAlign: "center" }}>
|
||||||
<ImageInput
|
<ImageInput
|
||||||
label="Choose photograph"
|
label="Choose photograph"
|
||||||
grabFocus
|
grabFocus
|
||||||
bind={[image, setImage]} />
|
bind={[image, setImage]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{configured.length > 0 && <section class="section">
|
{configured.length > 0 && (
|
||||||
|
<section class="section">
|
||||||
|
<div class="block">Your photographs:</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
Your photographs:
|
|
||||||
</div><div class="block">
|
|
||||||
{configured.map((c, i) => {
|
{configured.map((c, i) => {
|
||||||
return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
return (
|
||||||
<img style={{ marginTop: 'auto', marginBottom: 'auto', width: 100, height:100, border: 'solid 1px black' }} src={c.instructions} />
|
<div
|
||||||
<div style={{marginTop: 'auto', marginBottom: 'auto'}}><button class="button is-danger" onClick={c.remove}>Delete</button></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>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</div></section>}
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={cancel}>Cancel</button>
|
style={{
|
||||||
<button class="button is-info" onClick={addVideoAuth}>Add</button>
|
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>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
|
@ -15,42 +15,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core';
|
|
||||||
import { createExample, reducerStatesExample } from '../../../utils';
|
|
||||||
import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|
||||||
|
|
||||||
|
import { ChallengeFeedbackStatus, ReducerState } from "anastasis-core";
|
||||||
|
import { createExample, reducerStatesExample } from "../../../utils";
|
||||||
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SolveChallenge/AuthMethods/video',
|
title: "Pages/recovery/SolveChallenge/AuthMethods/video",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: "onUpdate" },
|
||||||
onBack: { action: 'onBack' },
|
onBack: { action: "onBack" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const type: KnownAuthMethods = 'video'
|
const type: KnownAuthMethods = "video";
|
||||||
|
|
||||||
export const WithoutFeedback = createExample(TestedComponent[type].solve, {
|
export const WithoutFeedback = createExample(
|
||||||
|
TestedComponent[type].solve,
|
||||||
|
{
|
||||||
...reducerStatesExample.challengeSolving,
|
...reducerStatesExample.challengeSolving,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
challenges: [{
|
challenges: [
|
||||||
cost: 'USD:1',
|
{
|
||||||
instructions: 'does P equals NP?',
|
cost: "USD:1",
|
||||||
type: 'question',
|
instructions: "does P equals NP?",
|
||||||
uuid: 'uuid-1'
|
type: "question",
|
||||||
}],
|
uuid: "uuid-1",
|
||||||
|
},
|
||||||
|
],
|
||||||
policies: [],
|
policies: [],
|
||||||
},
|
},
|
||||||
selected_challenge_uuid: 'uuid-1',
|
selected_challenge_uuid: "uuid-1",
|
||||||
} as ReducerState, {
|
} as ReducerState,
|
||||||
id: 'uuid-1',
|
{
|
||||||
});
|
id: "uuid-1",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -44,8 +44,16 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery problem">
|
<AnastasisClientFrame hideNav title="Recovery problem">
|
||||||
<div>invalid state</div>
|
<div>invalid state</div>
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div
|
||||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
@ -62,8 +70,7 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
challenges[ch.uuid] = ch;
|
challenges[ch.uuid] = ch;
|
||||||
}
|
}
|
||||||
const selectedChallenge = challenges[selectedUuid];
|
const selectedChallenge = challenges[selectedUuid];
|
||||||
const feedback = challengeFeedback[selectedUuid]
|
const feedback = challengeFeedback[selectedUuid];
|
||||||
|
|
||||||
|
|
||||||
async function onNext(): Promise<void> {
|
async function onNext(): Promise<void> {
|
||||||
return reducer?.transition("solve_challenge", { answer });
|
return reducer?.transition("solve_challenge", { answer });
|
||||||
@ -72,18 +79,16 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
reducer?.back();
|
reducer?.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldHideConfirm =
|
||||||
const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded
|
feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Redirect
|
feedback?.state === ChallengeFeedbackStatus.Redirect ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.Unsupported
|
feedback?.state === ChallengeFeedbackStatus.Unsupported ||
|
||||||
|| feedback?.state === ChallengeFeedbackStatus.TruthUnknown
|
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Add email authentication">
|
<AnastasisClientFrame hideNav title="Add email authentication">
|
||||||
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
<SolveOverviewFeedbackDisplay feedback={feedback} />
|
||||||
<p>
|
<p>You are gonna be called to check your identity</p>
|
||||||
You are gonna be called to check your identity
|
|
||||||
</p>
|
|
||||||
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -96,9 +101,11 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
<button class="button" onClick={onCancel}>
|
<button class="button" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
{!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}>
|
{!shouldHideConfirm && (
|
||||||
|
<AsyncButton class="button is-info" onClick={onNext}>
|
||||||
Confirm
|
Confirm
|
||||||
</AsyncButton>}
|
</AsyncButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { AuthMethod } from "anastasis-core";
|
import { AuthMethod } from "anastasis-core";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import postalIcon from '../../../assets/icons/auth_method/postal.svg';
|
import postalIcon from "../../../assets/icons/auth_method/postal.svg";
|
||||||
import questionIcon from '../../../assets/icons/auth_method/question.svg';
|
import questionIcon from "../../../assets/icons/auth_method/question.svg";
|
||||||
import smsIcon from '../../../assets/icons/auth_method/sms.svg';
|
import smsIcon from "../../../assets/icons/auth_method/sms.svg";
|
||||||
import videoIcon from '../../../assets/icons/auth_method/video.svg';
|
import videoIcon from "../../../assets/icons/auth_method/video.svg";
|
||||||
import { AuthMethodEmailSetup as EmailSetup } from "./AuthMethodEmailSetup";
|
import { AuthMethodEmailSetup as EmailSetup } from "./AuthMethodEmailSetup";
|
||||||
import { AuthMethodEmailSolve as EmailSolve } from "./AuthMethodEmailSolve";
|
import { AuthMethodEmailSolve as EmailSolve } from "./AuthMethodEmailSolve";
|
||||||
import { AuthMethodIbanSetup as IbanSetup } from "./AuthMethodIbanSetup";
|
import { AuthMethodIbanSetup as IbanSetup } from "./AuthMethodIbanSetup";
|
||||||
@ -20,8 +20,7 @@ import { AuthMethodSmsSolve as SmsSolve } from "./AuthMethodSmsSolve";
|
|||||||
import { AuthMethodTotpSolve as TotpSolve } from "./AuthMethodTotpSolve";
|
import { AuthMethodTotpSolve as TotpSolve } from "./AuthMethodTotpSolve";
|
||||||
import { AuthMethodVideoSolve as VideoSolve } from "./AuthMethodVideoSolve";
|
import { AuthMethodVideoSolve as VideoSolve } from "./AuthMethodVideoSolve";
|
||||||
|
|
||||||
|
export type AuthMethodWithRemove = AuthMethod & { remove: () => void };
|
||||||
export type AuthMethodWithRemove = AuthMethod & { remove: () => void }
|
|
||||||
|
|
||||||
export interface AuthMethodSetupProps {
|
export interface AuthMethodSetupProps {
|
||||||
method: string;
|
method: string;
|
||||||
@ -43,10 +42,18 @@ interface AuthMethodConfiguration {
|
|||||||
}
|
}
|
||||||
// export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban";
|
// export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban";
|
||||||
|
|
||||||
const ALL_METHODS = ['sms', 'email', 'post', 'question', 'video' , 'totp', 'iban'] as const;
|
const ALL_METHODS = [
|
||||||
export type KnownAuthMethods = (typeof ALL_METHODS)[number];
|
"sms",
|
||||||
|
"email",
|
||||||
|
"post",
|
||||||
|
"question",
|
||||||
|
"video",
|
||||||
|
"totp",
|
||||||
|
"iban",
|
||||||
|
] as const;
|
||||||
|
export type KnownAuthMethods = typeof ALL_METHODS[number];
|
||||||
export function isKnownAuthMethods(value: string): value is KnownAuthMethods {
|
export function isKnownAuthMethods(value: string): value is KnownAuthMethods {
|
||||||
return ALL_METHODS.includes(value as KnownAuthMethods)
|
return ALL_METHODS.includes(value as KnownAuthMethods);
|
||||||
}
|
}
|
||||||
|
|
||||||
type KnowMethodConfig = {
|
type KnowMethodConfig = {
|
||||||
@ -96,5 +103,5 @@ export const authMethods: KnowMethodConfig = {
|
|||||||
setup: VideoSetup,
|
setup: VideoSetup,
|
||||||
solve: VideoSolve,
|
solve: VideoSolve,
|
||||||
skip: true,
|
skip: true,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
@ -1,54 +1,61 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
import jssha from 'jssha'
|
import jssha from "jssha";
|
||||||
|
|
||||||
const SEARCH_RANGE = 16
|
const SEARCH_RANGE = 16;
|
||||||
const timeStep = 30
|
const timeStep = 30;
|
||||||
|
|
||||||
export function computeTOTPandCheck(secretKey: Uint8Array, digits: number, code: number): boolean {
|
export function computeTOTPandCheck(
|
||||||
const now = new Date().getTime()
|
secretKey: Uint8Array,
|
||||||
|
digits: number,
|
||||||
|
code: number,
|
||||||
|
): boolean {
|
||||||
|
const now = new Date().getTime();
|
||||||
const epoch = Math.floor(Math.round(now / 1000.0) / timeStep);
|
const epoch = Math.floor(Math.round(now / 1000.0) / timeStep);
|
||||||
|
|
||||||
for (let ms = -SEARCH_RANGE; ms < SEARCH_RANGE; ms++) {
|
for (let ms = -SEARCH_RANGE; ms < SEARCH_RANGE; ms++) {
|
||||||
const movingFactor = (epoch + ms).toString(16).padStart(16, "0");
|
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);
|
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 = ((
|
const otp =
|
||||||
(hmac_text[offset + 0] << 24) +
|
(((hmac_text[offset + 0] << 24) +
|
||||||
(hmac_text[offset + 1] << 16) +
|
(hmac_text[offset + 1] << 16) +
|
||||||
(hmac_text[offset + 2] << 8) +
|
(hmac_text[offset + 2] << 8) +
|
||||||
(hmac_text[offset + 3])
|
hmac_text[offset + 3]) &
|
||||||
) & 0x7fffffff) % Math.pow(10, digits)
|
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 {
|
export function base32enc(buffer: Uint8Array): string {
|
||||||
let rpos = 0
|
let rpos = 0;
|
||||||
let bits = 0
|
let bits = 0;
|
||||||
let vbit = 0
|
let vbit = 0;
|
||||||
|
|
||||||
let result = ""
|
let result = "";
|
||||||
while ((rpos < buffer.length) || (vbit > 0)) {
|
while (rpos < buffer.length || vbit > 0) {
|
||||||
if ((rpos < buffer.length) && (vbit < 5)) {
|
if (rpos < buffer.length && vbit < 5) {
|
||||||
bits = (bits << 8) | buffer[rpos++];
|
bits = (bits << 8) | buffer[rpos++];
|
||||||
vbit += 8;
|
vbit += 8;
|
||||||
}
|
}
|
||||||
if (vbit < 5) {
|
if (vbit < 5) {
|
||||||
bits <<= (5 - vbit);
|
bits <<= 5 - vbit;
|
||||||
vbit = 5;
|
vbit = 5;
|
||||||
}
|
}
|
||||||
result += encTable__[(bits >> (vbit - 5)) & 31];
|
result += encTable__[(bits >> (vbit - 5)) & 31];
|
||||||
vbit -= 5;
|
vbit -= 5;
|
||||||
}
|
}
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// const array = new Uint8Array(256)
|
// const array = new Uint8Array(256)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FunctionalComponent, h } from 'preact';
|
import { FunctionalComponent, h } from "preact";
|
||||||
import { Link } from 'preact-router/match';
|
import { Link } from "preact-router/match";
|
||||||
|
|
||||||
const Notfound: FunctionalComponent = () => {
|
const Notfound: FunctionalComponent = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FunctionalComponent, h } from 'preact';
|
import { FunctionalComponent, h } from "preact";
|
||||||
import { useEffect, useState } from 'preact/hooks';
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: string;
|
user: string;
|
||||||
@ -33,8 +33,7 @@ const Profile: FunctionalComponent<Props> = (props: Props) => {
|
|||||||
<div>Current time: {new Date(time).toLocaleString()}</div>
|
<div>Current time: {new Date(time).toLocaleString()}</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<button onClick={increment}>Click Me</button> Clicked {count}{' '}
|
<button onClick={increment}>Click Me</button> Clicked {count} times.
|
||||||
times.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded">
|
<html
|
||||||
|
lang="en"
|
||||||
|
class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"
|
||||||
|
>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<title><% preact.title %></title>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-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 %>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<% preact.bodyEnd %>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,45 +1,67 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
import { BackupStates, RecoveryStates, ReducerState } from 'anastasis-core';
|
import { BackupStates, RecoveryStates, ReducerState } from "anastasis-core";
|
||||||
import { FunctionalComponent, h, VNode } from 'preact';
|
import { FunctionalComponent, h, VNode } from "preact";
|
||||||
import { AnastasisProvider } from '../context/anastasis';
|
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 => {
|
const r = (args: Props): VNode => {
|
||||||
return <AnastasisProvider value={{
|
return (
|
||||||
|
<AnastasisProvider
|
||||||
|
value={{
|
||||||
currentReducerState,
|
currentReducerState,
|
||||||
currentError: undefined,
|
currentError: undefined,
|
||||||
back: async () => { null },
|
back: async () => {
|
||||||
dismissError: async () => { null },
|
null;
|
||||||
reset: () => { null },
|
},
|
||||||
runTransaction: async () => { null },
|
dismissError: async () => {
|
||||||
startBackup: () => { null },
|
null;
|
||||||
startRecover: () => { null },
|
},
|
||||||
transition: async () => { null },
|
reset: () => {
|
||||||
}}>
|
null;
|
||||||
|
},
|
||||||
|
runTransaction: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
|
startBackup: () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
|
startRecover: () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
|
transition: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Component {...args} />
|
<Component {...args} />
|
||||||
</AnastasisProvider>
|
</AnastasisProvider>
|
||||||
}
|
);
|
||||||
r.args = props
|
};
|
||||||
return r
|
r.args = props;
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
continents: [
|
continents: [
|
||||||
{
|
{
|
||||||
name: "Europe"
|
name: "Europe",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "India"
|
name: "India",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Asia"
|
name: "Asia",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "North America"
|
name: "North America",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Testcontinent"
|
name: "Testcontinent",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
countries: [
|
countries: [
|
||||||
{
|
{
|
||||||
@ -47,33 +69,33 @@ const base = {
|
|||||||
name: "Testland",
|
name: "Testland",
|
||||||
continent: "Testcontinent",
|
continent: "Testcontinent",
|
||||||
continent_i18n: {
|
continent_i18n: {
|
||||||
de_DE: "Testkontinent"
|
de_DE: "Testkontinent",
|
||||||
},
|
},
|
||||||
name_i18n: {
|
name_i18n: {
|
||||||
de_DE: "Testlandt",
|
de_DE: "Testlandt",
|
||||||
de_CH: "Testlandi",
|
de_CH: "Testlandi",
|
||||||
fr_FR: "Testpais",
|
fr_FR: "Testpais",
|
||||||
en_UK: "Testland"
|
en_UK: "Testland",
|
||||||
},
|
},
|
||||||
currency: "TESTKUDOS",
|
currency: "TESTKUDOS",
|
||||||
call_code: "+00"
|
call_code: "+00",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: "xy",
|
code: "xy",
|
||||||
name: "Demoland",
|
name: "Demoland",
|
||||||
continent: "Testcontinent",
|
continent: "Testcontinent",
|
||||||
continent_i18n: {
|
continent_i18n: {
|
||||||
de_DE: "Testkontinent"
|
de_DE: "Testkontinent",
|
||||||
},
|
},
|
||||||
name_i18n: {
|
name_i18n: {
|
||||||
de_DE: "Demolandt",
|
de_DE: "Demolandt",
|
||||||
de_CH: "Demolandi",
|
de_CH: "Demolandi",
|
||||||
fr_FR: "Demopais",
|
fr_FR: "Demopais",
|
||||||
en_UK: "Demoland"
|
en_UK: "Demoland",
|
||||||
},
|
},
|
||||||
currency: "KUDOS",
|
currency: "KUDOS",
|
||||||
call_code: "+01"
|
call_code: "+01",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
authentication_providers: {
|
authentication_providers: {
|
||||||
"http://localhost:8086/": {
|
"http://localhost:8086/": {
|
||||||
@ -85,18 +107,20 @@ const base = {
|
|||||||
methods: [
|
methods: [
|
||||||
{
|
{
|
||||||
type: "question",
|
type: "question",
|
||||||
usage_fee: "COL:0"
|
usage_fee: "COL:0",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "sms",
|
type: "sms",
|
||||||
usage_fee: "COL:0"
|
usage_fee: "COL:0",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "email",
|
type: "email",
|
||||||
usage_fee: "COL:0"
|
usage_fee: "COL:0",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
salt: "WBMDD76BR1E90YQ5AHBMKPH7GW",
|
salt: "WBMDD76BR1E90YQ5AHBMKPH7GW",
|
||||||
storage_limit_in_megabytes: 16,
|
storage_limit_in_megabytes: 16,
|
||||||
truth_upload_fee: "COL:0"
|
truth_upload_fee: "COL:0",
|
||||||
},
|
},
|
||||||
"https://kudos.demo.anastasis.lu/": {
|
"https://kudos.demo.anastasis.lu/": {
|
||||||
http_status: 200,
|
http_status: 200,
|
||||||
@ -107,15 +131,16 @@ const base = {
|
|||||||
methods: [
|
methods: [
|
||||||
{
|
{
|
||||||
type: "question",
|
type: "question",
|
||||||
usage_fee: "COL:0"
|
usage_fee: "COL:0",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "email",
|
type: "email",
|
||||||
usage_fee: "COL:0"
|
usage_fee: "COL:0",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
salt: "WBMDD76BR1E90YQ5AHBMKPH7GW",
|
salt: "WBMDD76BR1E90YQ5AHBMKPH7GW",
|
||||||
storage_limit_in_megabytes: 16,
|
storage_limit_in_megabytes: 16,
|
||||||
truth_upload_fee: "COL:0"
|
truth_upload_fee: "COL:0",
|
||||||
},
|
},
|
||||||
"https://anastasis.demo.taler.net/": {
|
"https://anastasis.demo.taler.net/": {
|
||||||
http_status: 200,
|
http_status: 200,
|
||||||
@ -126,43 +151,45 @@ const base = {
|
|||||||
methods: [
|
methods: [
|
||||||
{
|
{
|
||||||
type: "question",
|
type: "question",
|
||||||
usage_fee: "COL:0"
|
usage_fee: "COL:0",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "sms",
|
type: "sms",
|
||||||
usage_fee: "COL:0"
|
usage_fee: "COL:0",
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
type: "totp",
|
type: "totp",
|
||||||
usage_fee: "COL:0"
|
usage_fee: "COL:0",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
salt: "WBMDD76BR1E90YQ5AHBMKPH7GW",
|
salt: "WBMDD76BR1E90YQ5AHBMKPH7GW",
|
||||||
storage_limit_in_megabytes: 16,
|
storage_limit_in_megabytes: 16,
|
||||||
truth_upload_fee: "COL:0"
|
truth_upload_fee: "COL:0",
|
||||||
},
|
},
|
||||||
|
|
||||||
"http://localhost:8087/": {
|
"http://localhost:8087/": {
|
||||||
code: 8414,
|
code: 8414,
|
||||||
hint: "request to provider failed"
|
hint: "request to provider failed",
|
||||||
},
|
},
|
||||||
"http://localhost:8088/": {
|
"http://localhost:8088/": {
|
||||||
code: 8414,
|
code: 8414,
|
||||||
hint: "request to provider failed"
|
hint: "request to provider failed",
|
||||||
},
|
},
|
||||||
"http://localhost:8089/": {
|
"http://localhost:8089/": {
|
||||||
code: 8414,
|
code: 8414,
|
||||||
hint: "request to provider failed"
|
hint: "request to provider failed",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
// expiration: {
|
// expiration: {
|
||||||
// d_ms: 1792525051855 // check t_ms
|
// d_ms: 1792525051855 // check t_ms
|
||||||
// },
|
// },
|
||||||
} as Partial<ReducerState>
|
} as Partial<ReducerState>;
|
||||||
|
|
||||||
export const reducerStatesExample = {
|
export const reducerStatesExample = {
|
||||||
initial: undefined,
|
initial: undefined,
|
||||||
recoverySelectCountry: {
|
recoverySelectCountry: {
|
||||||
...base,
|
...base,
|
||||||
recovery_state: RecoveryStates.CountrySelecting
|
recovery_state: RecoveryStates.CountrySelecting,
|
||||||
} as ReducerState,
|
} as ReducerState,
|
||||||
recoverySelectContinent: {
|
recoverySelectContinent: {
|
||||||
...base,
|
...base,
|
||||||
@ -190,11 +217,11 @@ export const reducerStatesExample = {
|
|||||||
} as ReducerState,
|
} as ReducerState,
|
||||||
recoveryAttributeEditing: {
|
recoveryAttributeEditing: {
|
||||||
...base,
|
...base,
|
||||||
recovery_state: RecoveryStates.UserAttributesCollecting
|
recovery_state: RecoveryStates.UserAttributesCollecting,
|
||||||
} as ReducerState,
|
} as ReducerState,
|
||||||
backupSelectCountry: {
|
backupSelectCountry: {
|
||||||
...base,
|
...base,
|
||||||
backup_state: BackupStates.CountrySelecting
|
backup_state: BackupStates.CountrySelecting,
|
||||||
} as ReducerState,
|
} as ReducerState,
|
||||||
backupSelectContinent: {
|
backupSelectContinent: {
|
||||||
...base,
|
...base,
|
||||||
@ -218,15 +245,14 @@ export const reducerStatesExample = {
|
|||||||
} as ReducerState,
|
} as ReducerState,
|
||||||
authEditing: {
|
authEditing: {
|
||||||
...base,
|
...base,
|
||||||
backup_state: BackupStates.AuthenticationsEditing
|
backup_state: BackupStates.AuthenticationsEditing,
|
||||||
} as ReducerState,
|
} as ReducerState,
|
||||||
backupAttributeEditing: {
|
backupAttributeEditing: {
|
||||||
...base,
|
...base,
|
||||||
backup_state: BackupStates.UserAttributesCollecting
|
backup_state: BackupStates.UserAttributesCollecting,
|
||||||
} as ReducerState,
|
} as ReducerState,
|
||||||
truthsPaying: {
|
truthsPaying: {
|
||||||
...base,
|
...base,
|
||||||
backup_state: BackupStates.TruthsPaying
|
backup_state: BackupStates.TruthsPaying,
|
||||||
} as ReducerState,
|
} as ReducerState,
|
||||||
|
};
|
||||||
}
|
|
||||||
|
@ -7,10 +7,12 @@ importers:
|
|||||||
'@linaria/esbuild': ^3.0.0-beta.13
|
'@linaria/esbuild': ^3.0.0-beta.13
|
||||||
'@linaria/shaker': ^3.0.0-beta.13
|
'@linaria/shaker': ^3.0.0-beta.13
|
||||||
esbuild: ^0.12.29
|
esbuild: ^0.12.29
|
||||||
|
prettier: ^2.2.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@linaria/esbuild': 3.0.0-beta.13
|
'@linaria/esbuild': 3.0.0-beta.13
|
||||||
'@linaria/shaker': 3.0.0-beta.13
|
'@linaria/shaker': 3.0.0-beta.13
|
||||||
esbuild: 0.12.29
|
esbuild: 0.12.29
|
||||||
|
prettier: 2.2.1
|
||||||
|
|
||||||
packages/anastasis-core:
|
packages/anastasis-core:
|
||||||
specifiers:
|
specifiers:
|
||||||
@ -62,6 +64,7 @@ importers:
|
|||||||
'@typescript-eslint/eslint-plugin': ^5.3.0
|
'@typescript-eslint/eslint-plugin': ^5.3.0
|
||||||
'@typescript-eslint/parser': ^5.3.0
|
'@typescript-eslint/parser': ^5.3.0
|
||||||
anastasis-core: workspace:^0.0.1
|
anastasis-core: workspace:^0.0.1
|
||||||
|
base64-inline-loader: 1.1.1
|
||||||
bulma: ^0.9.3
|
bulma: ^0.9.3
|
||||||
bulma-checkbox: ^1.1.1
|
bulma-checkbox: ^1.1.1
|
||||||
bulma-radio: ^1.1.1
|
bulma-radio: ^1.1.1
|
||||||
@ -86,6 +89,7 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@gnu-taler/taler-util': link:../taler-util
|
'@gnu-taler/taler-util': link:../taler-util
|
||||||
anastasis-core: link:../anastasis-core
|
anastasis-core: link:../anastasis-core
|
||||||
|
base64-inline-loader: 1.1.1
|
||||||
date-fns: 2.25.0
|
date-fns: 2.25.0
|
||||||
jed: 1.1.1
|
jed: 1.1.1
|
||||||
preact: 10.5.15
|
preact: 10.5.15
|
||||||
@ -10800,7 +10804,6 @@ packages:
|
|||||||
ajv: ^6.9.1
|
ajv: ^6.9.1
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ajv/6.12.6:
|
/ajv/6.12.6:
|
||||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||||
@ -10809,7 +10812,6 @@ packages:
|
|||||||
fast-json-stable-stringify: 2.1.0
|
fast-json-stable-stringify: 2.1.0
|
||||||
json-schema-traverse: 0.4.1
|
json-schema-traverse: 0.4.1
|
||||||
uri-js: 4.4.1
|
uri-js: 4.4.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ajv/7.0.3:
|
/ajv/7.0.3:
|
||||||
resolution: {integrity: sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==}
|
resolution: {integrity: sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==}
|
||||||
@ -11871,6 +11873,17 @@ packages:
|
|||||||
pascalcase: 0.1.1
|
pascalcase: 0.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/base64-inline-loader/1.1.1:
|
||||||
|
resolution: {integrity: sha512-v/bHvXQ8sW28t9ZwBsFGgyqZw2bpT49/dtPTtlmixoSM/s9wnOngOKFVQLRH8BfMTy6jTl5m5CdvqpZt8y5d6g==}
|
||||||
|
engines: {node: '>=6.2', npm: '>=3.8'}
|
||||||
|
peerDependencies:
|
||||||
|
webpack: ^4.x
|
||||||
|
dependencies:
|
||||||
|
file-loader: 1.1.11
|
||||||
|
loader-utils: 1.4.0
|
||||||
|
mime-types: 2.1.33
|
||||||
|
dev: false
|
||||||
|
|
||||||
/base64-js/1.5.1:
|
/base64-js/1.5.1:
|
||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -11907,7 +11920,6 @@ packages:
|
|||||||
|
|
||||||
/big.js/5.2.2:
|
/big.js/5.2.2:
|
||||||
resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
|
resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/binary-extensions/1.13.1:
|
/binary-extensions/1.13.1:
|
||||||
resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==}
|
resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==}
|
||||||
@ -13794,7 +13806,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
globby: 11.0.4
|
globby: 11.0.4
|
||||||
graceful-fs: 4.2.8
|
graceful-fs: 4.2.8
|
||||||
is-glob: 4.0.1
|
is-glob: 4.0.3
|
||||||
is-path-cwd: 2.2.0
|
is-path-cwd: 2.2.0
|
||||||
is-path-inside: 3.0.3
|
is-path-inside: 3.0.3
|
||||||
p-map: 4.0.0
|
p-map: 4.0.0
|
||||||
@ -14177,7 +14189,6 @@ packages:
|
|||||||
/emojis-list/3.0.0:
|
/emojis-list/3.0.0:
|
||||||
resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
|
resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/emotion-theming/10.0.27_5f216699bc8c1f24088b3bf77b7cbbdf:
|
/emotion-theming/10.0.27_5f216699bc8c1f24088b3bf77b7cbbdf:
|
||||||
resolution: {integrity: sha512-MlF1yu/gYh8u+sLUqA0YuA9JX0P4Hb69WlKc/9OLo+WCXuX6sy/KoIa+qJimgmr2dWqnypYKYPX37esjDBbhdw==}
|
resolution: {integrity: sha512-MlF1yu/gYh8u+sLUqA0YuA9JX0P4Hb69WlKc/9OLo+WCXuX6sy/KoIa+qJimgmr2dWqnypYKYPX37esjDBbhdw==}
|
||||||
@ -15174,7 +15185,6 @@ packages:
|
|||||||
|
|
||||||
/fast-deep-equal/3.1.3:
|
/fast-deep-equal/3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/fast-diff/1.2.0:
|
/fast-diff/1.2.0:
|
||||||
resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==}
|
resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==}
|
||||||
@ -15217,7 +15227,6 @@ packages:
|
|||||||
|
|
||||||
/fast-json-stable-stringify/2.1.0:
|
/fast-json-stable-stringify/2.1.0:
|
||||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/fast-levenshtein/2.0.6:
|
/fast-levenshtein/2.0.6:
|
||||||
resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=}
|
resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=}
|
||||||
@ -15296,6 +15305,16 @@ packages:
|
|||||||
flat-cache: 3.0.4
|
flat-cache: 3.0.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/file-loader/1.1.11:
|
||||||
|
resolution: {integrity: sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==}
|
||||||
|
engines: {node: '>= 4.3 < 5.0.0 || >= 5.10'}
|
||||||
|
peerDependencies:
|
||||||
|
webpack: ^2.0.0 || ^3.0.0 || ^4.0.0
|
||||||
|
dependencies:
|
||||||
|
loader-utils: 1.4.0
|
||||||
|
schema-utils: 0.4.7
|
||||||
|
dev: false
|
||||||
|
|
||||||
/file-loader/6.2.0_webpack@4.46.0:
|
/file-loader/6.2.0_webpack@4.46.0:
|
||||||
resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==}
|
resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==}
|
||||||
engines: {node: '>= 10.13.0'}
|
engines: {node: '>= 10.13.0'}
|
||||||
@ -18694,7 +18713,6 @@ packages:
|
|||||||
|
|
||||||
/json-schema-traverse/0.4.1:
|
/json-schema-traverse/0.4.1:
|
||||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/json-schema-traverse/1.0.0:
|
/json-schema-traverse/1.0.0:
|
||||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||||
@ -18730,7 +18748,6 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist: 1.2.5
|
minimist: 1.2.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
/json5/2.1.3:
|
/json5/2.1.3:
|
||||||
resolution: {integrity: sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==}
|
resolution: {integrity: sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==}
|
||||||
@ -18970,7 +18987,6 @@ packages:
|
|||||||
big.js: 5.2.2
|
big.js: 5.2.2
|
||||||
emojis-list: 3.0.0
|
emojis-list: 3.0.0
|
||||||
json5: 1.0.1
|
json5: 1.0.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/loader-utils/2.0.0:
|
/loader-utils/2.0.0:
|
||||||
resolution: {integrity: sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==}
|
resolution: {integrity: sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==}
|
||||||
@ -19420,14 +19436,12 @@ packages:
|
|||||||
/mime-db/1.50.0:
|
/mime-db/1.50.0:
|
||||||
resolution: {integrity: sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==}
|
resolution: {integrity: sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/mime-types/2.1.33:
|
/mime-types/2.1.33:
|
||||||
resolution: {integrity: sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==}
|
resolution: {integrity: sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-db: 1.50.0
|
mime-db: 1.50.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/mime/1.6.0:
|
/mime/1.6.0:
|
||||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||||
@ -19501,7 +19515,6 @@ packages:
|
|||||||
|
|
||||||
/minimist/1.2.5:
|
/minimist/1.2.5:
|
||||||
resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==}
|
resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/minipass-collect/1.0.2:
|
/minipass-collect/1.0.2:
|
||||||
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
|
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
|
||||||
@ -22013,7 +22026,6 @@ packages:
|
|||||||
/punycode/2.1.1:
|
/punycode/2.1.1:
|
||||||
resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
|
resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/pupa/2.1.1:
|
/pupa/2.1.1:
|
||||||
resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==}
|
resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==}
|
||||||
@ -23312,6 +23324,14 @@ packages:
|
|||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/schema-utils/0.4.7:
|
||||||
|
resolution: {integrity: sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==}
|
||||||
|
engines: {node: '>= 4'}
|
||||||
|
dependencies:
|
||||||
|
ajv: 6.12.6
|
||||||
|
ajv-keywords: 3.5.2_ajv@6.12.6
|
||||||
|
dev: false
|
||||||
|
|
||||||
/schema-utils/1.0.0:
|
/schema-utils/1.0.0:
|
||||||
resolution: {integrity: sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==}
|
resolution: {integrity: sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
@ -25212,7 +25232,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.1.1
|
punycode: 2.1.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/urix/0.1.0:
|
/urix/0.1.0:
|
||||||
resolution: {integrity: sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=}
|
resolution: {integrity: sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=}
|
||||||
|
Loading…
Reference in New Issue
Block a user