feedback from meeting and editing policy
This commit is contained in:
parent
9fb6536fbc
commit
a82b5a6992
@ -4,9 +4,9 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "preact build",
|
||||
"build": "preact build --no-sw --no-esm",
|
||||
"serve": "sirv build --port 8080 --cors --single",
|
||||
"dev": "preact watch",
|
||||
"dev": "preact watch --no-sw --no-esm",
|
||||
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
|
||||
"test": "jest ./tests",
|
||||
"build-storybook": "build-storybook",
|
||||
|
51
packages/anastasis-webui/src/components/AsyncButton.tsx
Normal file
51
packages/anastasis-webui/src/components/AsyncButton.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
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 (sebasjm)
|
||||
*/
|
||||
|
||||
import { ComponentChildren, h, VNode } from "preact";
|
||||
// import { LoadingModal } from "../modal";
|
||||
import { useAsync } from "../hooks/async";
|
||||
// import { Translate } from "../../i18n";
|
||||
|
||||
type Props = {
|
||||
children: ComponentChildren;
|
||||
disabled: boolean;
|
||||
onClick?: () => Promise<void>;
|
||||
[rest: string]: any;
|
||||
};
|
||||
|
||||
export function AsyncButton({ onClick, disabled, children, ...rest }: Props): VNode {
|
||||
const { isLoading, request } = useAsync(onClick);
|
||||
|
||||
// if (isSlow) {
|
||||
// return <LoadingModal onCancel={cancel} />;
|
||||
// }
|
||||
console.log(isLoading)
|
||||
if (isLoading) {
|
||||
|
||||
return <button class="button">Loading...</button>;
|
||||
}
|
||||
|
||||
return <span {...rest}>
|
||||
<button class="button is-info" onClick={request} disabled={disabled}>
|
||||
{children}
|
||||
</button>
|
||||
</span>;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { format } from "date-fns";
|
||||
import { format, isAfter, parse, sub, subYears } from "date-fns";
|
||||
import { h, VNode } from "preact";
|
||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
import { DatePicker } from "../picker/DatePicker";
|
||||
@ -19,16 +19,14 @@ export function DateInput(props: DateInputProps): VNode {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [props.grabFocus]);
|
||||
const [opened, setOpened2] = useState(false)
|
||||
function setOpened(v: boolean): void {
|
||||
console.log('dale', v)
|
||||
setOpened2(v)
|
||||
}
|
||||
const [opened, setOpened] = useState(false)
|
||||
|
||||
const value = props.bind[0] || "";
|
||||
const [dirty, setDirty] = useState(false)
|
||||
const showError = dirty && props.error
|
||||
|
||||
const calendar = subYears(new Date(), 30)
|
||||
|
||||
return <div class="field">
|
||||
<label class="label">
|
||||
{props.label}
|
||||
@ -36,27 +34,37 @@ export function DateInput(props: DateInputProps): VNode {
|
||||
<i class="mdi mdi-information" />
|
||||
</span>}
|
||||
</label>
|
||||
<div class="control has-icons-right">
|
||||
<input
|
||||
type="text"
|
||||
class={showError ? 'input is-danger' : 'input'}
|
||||
readonly
|
||||
onFocus={() => { setOpened(true) } }
|
||||
value={value}
|
||||
ref={inputRef} />
|
||||
|
||||
<span class="control icon is-right">
|
||||
<span class="icon"><i class="mdi mdi-calendar" /></span>
|
||||
</span>
|
||||
<div class="control">
|
||||
<div class="field has-addons">
|
||||
<p class="control">
|
||||
<input
|
||||
type="text"
|
||||
class={showError ? 'input is-danger' : 'input'}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
const text = e.currentTarget.value
|
||||
setDirty(true)
|
||||
props.bind[1](text);
|
||||
}}
|
||||
ref={inputRef} />
|
||||
</p>
|
||||
<p class="control">
|
||||
<a class="button" onClick={() => { setOpened(true) }}>
|
||||
<span class="icon"><i class="mdi mdi-calendar" /></span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">Using the format yyyy-mm-dd</p>
|
||||
{showError && <p class="help is-danger">{props.error}</p>}
|
||||
<DatePicker
|
||||
opened={opened}
|
||||
initialDate={calendar}
|
||||
years={props.years}
|
||||
closeFunction={() => setOpened(false)}
|
||||
dateReceiver={(d) => {
|
||||
setDirty(true)
|
||||
const v = format(d, 'yyyy/MM/dd')
|
||||
const v = format(d, 'yyyy-MM-dd')
|
||||
props.bind[1](v);
|
||||
}}
|
||||
/>
|
||||
|
@ -49,7 +49,7 @@ export function NavigationBar({ onMobileMenu, title }: Props): VNode {
|
||||
</a>
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}>
|
||||
<LangSelector />
|
||||
{/* <LangSelector /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -39,9 +39,9 @@ export function Sidebar({ mobile }: Props): VNode {
|
||||
|
||||
return (
|
||||
<aside class="aside is-placed-left is-expanded">
|
||||
{mobile && <div class="footer" onClick={(e) => { return e.stopImmediatePropagation() }}>
|
||||
{/* {mobile && <div class="footer" onClick={(e) => { return e.stopImmediatePropagation() }}>
|
||||
<LangSelector />
|
||||
</div>}
|
||||
</div>} */}
|
||||
<div class="aside-tools">
|
||||
<div class="aside-tools-label">
|
||||
<div><b>Anastasis</b> Reducer</div>
|
||||
@ -68,7 +68,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting ||
|
||||
reducer.currentReducerState.backup_state === BackupStates.CountrySelecting ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
<span class="menu-item-label"><Translate>Location & Currency</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Location</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.UserAttributesCollecting ? 'is-active' : ''}>
|
||||
@ -85,7 +85,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesReviewing ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
|
||||
<span class="menu-item-label"><Translate>Policies reviewing</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Policies</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.SecretEditing ? 'is-active' : ''}>
|
||||
@ -94,12 +94,12 @@ export function Sidebar({ mobile }: Props): VNode {
|
||||
<span class="menu-item-label"><Translate>Secret input</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}>
|
||||
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
|
||||
<span class="menu-item-label"><Translate>Payment (optional)</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
</li> */}
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.BackupFinished ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
|
||||
@ -116,7 +116,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting ||
|
||||
reducer.currentReducerState.recovery_state === RecoveryStates.CountrySelecting ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
<span class="menu-item-label"><Translate>Location & Currency</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Location</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.UserAttributesCollecting ? 'is-active' : ''}>
|
||||
|
@ -24,6 +24,7 @@ import { h, Component } from "preact";
|
||||
interface Props {
|
||||
closeFunction?: () => void;
|
||||
dateReceiver?: (d: Date) => void;
|
||||
initialDate?: Date;
|
||||
years?: Array<number>;
|
||||
opened?: boolean;
|
||||
}
|
||||
@ -213,8 +214,8 @@ export class DatePicker extends Component<Props, State> {
|
||||
// }
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.closeDatePicker = this.closeDatePicker.bind(this);
|
||||
this.dayClicked = this.dayClicked.bind(this);
|
||||
@ -226,11 +227,12 @@ export class DatePicker extends Component<Props, State> {
|
||||
this.toggleYearSelector = this.toggleYearSelector.bind(this);
|
||||
this.displaySelectedMonth = this.displaySelectedMonth.bind(this);
|
||||
|
||||
const initial = props.initialDate || now;
|
||||
|
||||
this.state = {
|
||||
currentDate: now,
|
||||
displayedMonth: now.getMonth(),
|
||||
displayedYear: now.getFullYear(),
|
||||
currentDate: initial,
|
||||
displayedMonth: initial.getMonth(),
|
||||
displayedYear: initial.getFullYear(),
|
||||
selectYearMode: false
|
||||
}
|
||||
}
|
||||
|
77
packages/anastasis-webui/src/hooks/async.ts
Normal file
77
packages/anastasis-webui/src/hooks/async.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
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 (sebasjm)
|
||||
*/
|
||||
import { useState } from "preact/hooks";
|
||||
// import { cancelPendingRequest } from "./backend";
|
||||
|
||||
export interface Options {
|
||||
slowTolerance: number;
|
||||
}
|
||||
|
||||
export interface AsyncOperationApi<T> {
|
||||
request: (...a: any) => void;
|
||||
cancel: () => void;
|
||||
data: T | undefined;
|
||||
isSlow: boolean;
|
||||
isLoading: boolean;
|
||||
error: string | undefined;
|
||||
}
|
||||
|
||||
export function useAsync<T>(fn?: (...args: any) => Promise<T>, { slowTolerance: tooLong }: Options = { slowTolerance: 1000 }): AsyncOperationApi<T> {
|
||||
const [data, setData] = useState<T | undefined>(undefined);
|
||||
const [isLoading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<any>(undefined);
|
||||
const [isSlow, setSlow] = useState(false)
|
||||
|
||||
const request = async (...args: any) => {
|
||||
if (!fn) return;
|
||||
setLoading(true);
|
||||
console.log("loading true")
|
||||
const handler = setTimeout(() => {
|
||||
setSlow(true)
|
||||
}, tooLong)
|
||||
|
||||
try {
|
||||
const result = await fn(...args);
|
||||
console.log(result)
|
||||
setData(result);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
}
|
||||
setLoading(false);
|
||||
setSlow(false)
|
||||
clearTimeout(handler)
|
||||
};
|
||||
|
||||
function cancel() {
|
||||
// cancelPendingRequest()
|
||||
setLoading(false);
|
||||
setSlow(false)
|
||||
}
|
||||
|
||||
return {
|
||||
request,
|
||||
cancel,
|
||||
data,
|
||||
isSlow,
|
||||
isLoading,
|
||||
error
|
||||
};
|
||||
}
|
@ -52,8 +52,8 @@ export const Backup = createExample(TestedComponent, {
|
||||
uuid: 'asdasdsa2',
|
||||
widget: 'wid',
|
||||
}, {
|
||||
name: 'date',
|
||||
label: 'third',
|
||||
name: 'birthdate',
|
||||
label: 'birthdate',
|
||||
type: 'date',
|
||||
uuid: 'asdasdsa3',
|
||||
widget: 'calendar',
|
||||
|
@ -7,6 +7,7 @@ import { AnastasisClientFrame, withProcessLabel } from "./index";
|
||||
import { TextInput } from "../../components/fields/TextInput";
|
||||
import { DateInput } from "../../components/fields/DateInput";
|
||||
import { NumberInput } from "../../components/fields/NumberInput";
|
||||
import { isAfter, parse } from "date-fns";
|
||||
|
||||
export function AttributeEntryScreen(): VNode {
|
||||
const reducer = useAnastasisContext()
|
||||
@ -46,15 +47,14 @@ export function AttributeEntryScreen(): VNode {
|
||||
identity_attributes: attrs,
|
||||
})}
|
||||
>
|
||||
<div class="columns">
|
||||
<div class="column is-half">
|
||||
<div class="columns" style={{ maxWidth: 'unset' }}>
|
||||
<div class="column is-one-third">
|
||||
{fieldList}
|
||||
</div>
|
||||
<div class="column is-half" >
|
||||
<div class="column is-two-third" >
|
||||
<p>This personal information will help to locate your secret.</p>
|
||||
<h1><b>This stay private</b></h1>
|
||||
<p>The information you have entered here:
|
||||
</p>
|
||||
<h1 class="title">This stays private</h1>
|
||||
<p>The information you have entered here:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="icon is-right">
|
||||
@ -111,15 +111,17 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
|
||||
bind={[props.value, props.setValue]}
|
||||
/>
|
||||
}
|
||||
<span>
|
||||
<div class="block">
|
||||
This stays private
|
||||
<span class="icon is-right">
|
||||
<i class="mdi mdi-eye-off" />
|
||||
</span>
|
||||
This stay private
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/
|
||||
|
||||
|
||||
function checkIfValid(value: string, spec: UserAttributeSpec): string | undefined {
|
||||
const pattern = spec['validation-regex']
|
||||
@ -136,5 +138,22 @@ function checkIfValid(value: string, spec: UserAttributeSpec): string | undefine
|
||||
if (!optional && !value) {
|
||||
return 'This value is required'
|
||||
}
|
||||
if ("date" === spec.type) {
|
||||
if (!YEAR_REGEX.test(value)) {
|
||||
return "The date doesn't follow the format"
|
||||
}
|
||||
|
||||
try {
|
||||
const v = parse(value, 'yyyy-MM-dd', new Date());
|
||||
if (Number.isNaN(v.getTime())) {
|
||||
return "Some numeric values seems out of range for a date"
|
||||
}
|
||||
if ("birthdate" === spec.name && isAfter(v, new Date())) {
|
||||
return "A birthdate cannot be in the future"
|
||||
}
|
||||
} catch (e) {
|
||||
return "Could not parse the date"
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
@ -142,6 +142,10 @@ export function AuthenticationEditorScreen(): VNode {
|
||||
</div>
|
||||
<div class="column is-half">
|
||||
When recovering your wallet, you will be asked to verify your identity via the methods you configure here.
|
||||
|
||||
<b>Explain the exclamation marks</b>
|
||||
|
||||
<a>Explain how to add providers</a>
|
||||
</div>
|
||||
</div>
|
||||
</AnastasisClientFrame>
|
||||
|
@ -23,7 +23,7 @@ export function BackupFinishedScreen(): VNode {
|
||||
</p>}
|
||||
|
||||
{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) => {
|
||||
const sd = details[x];
|
||||
return (
|
||||
@ -31,11 +31,14 @@ export function BackupFinishedScreen(): VNode {
|
||||
{x}
|
||||
<p>
|
||||
version {sd.policy_version}
|
||||
{sd.policy_expiration.t_ms !== 'never' ? ` expires at: ${format(sd.policy_expiration.t_ms, 'dd/MM/yyyy')}` : ' without expiration date'}
|
||||
{sd.policy_expiration.t_ms !== 'never' ? ` expires at: ${format(sd.policy_expiration.t_ms, 'dd-MM-yyyy')}` : ' without expiration date'}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>}
|
||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
||||
</div>
|
||||
</AnastasisClientFrame>);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2021 Taler Systems S.A.
|
||||
@ -19,12 +20,13 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { ReducerState } from 'anastasis-core';
|
||||
import { createExample, reducerStatesExample } from '../../utils';
|
||||
import { ContinentSelectionScreen as TestedComponent } from './ContinentSelectionScreen';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'Pages/ContinentSelectionScreen',
|
||||
title: 'Pages/Location',
|
||||
component: TestedComponent,
|
||||
args: {
|
||||
order: 2,
|
||||
@ -35,6 +37,16 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectContinent);
|
||||
export const BackupSelectContinent = createExample(TestedComponent, reducerStatesExample.backupSelectContinent);
|
||||
|
||||
export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent);
|
||||
export const BackupSelectCountry = createExample(TestedComponent, {
|
||||
...reducerStatesExample.backupSelectContinent,
|
||||
selected_continent: 'Testcontinent',
|
||||
} as ReducerState);
|
||||
|
||||
export const RecoverySelectContinent = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent);
|
||||
|
||||
export const RecoverySelectCountry = createExample(TestedComponent, {
|
||||
...reducerStatesExample.recoverySelectContinent,
|
||||
selected_continent: 'Testcontinent',
|
||||
} as ReducerState);
|
||||
|
@ -36,20 +36,21 @@ export function ContinentSelectionScreen(): VNode {
|
||||
})
|
||||
}
|
||||
|
||||
const step1 = reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting ||
|
||||
reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting;
|
||||
// const step1 = reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting ||
|
||||
// reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting;
|
||||
|
||||
const errors = !theCountry ? "Select a country" : undefined
|
||||
|
||||
return (
|
||||
<AnastasisClientFrame hideNext={errors} title={withProcessLabel(reducer, "Select location")} onNext={selectCountryAction}>
|
||||
<div class="columns">
|
||||
<div class="column is-half">
|
||||
<AnastasisClientFrame hideNext={errors} title={withProcessLabel(reducer, "Where do you live?")} onNext={selectCountryAction}>
|
||||
|
||||
<div class="columns" >
|
||||
<div class="column is-one-third">
|
||||
<div class="field">
|
||||
<label class="label">Continent</label>
|
||||
<div class="control has-icons-left">
|
||||
<div class="select " >
|
||||
<select onChange={(e) => selectContinent(e.currentTarget.value)} value={theContinent} disabled={!step1}>
|
||||
<div class="control is-expanded has-icons-left">
|
||||
<div class="select is-fullwidth" >
|
||||
<select onChange={(e) => selectContinent(e.currentTarget.value)} value={theContinent} >
|
||||
<option key="none" disabled selected value=""> Choose a continent </option>
|
||||
{continentList.map(prov => (
|
||||
<option key={prov.name} value={prov.name}>
|
||||
@ -61,18 +62,13 @@ export function ContinentSelectionScreen(): VNode {
|
||||
<i class="mdi mdi-earth" />
|
||||
</div>
|
||||
</div>
|
||||
{!step1 && <span class="control">
|
||||
<a class="button is-danger" onClick={() => reducer.back()}>
|
||||
X
|
||||
</a>
|
||||
</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Country</label>
|
||||
<div class="control has-icons-left">
|
||||
<div class="select" >
|
||||
<div class="control is-expanded has-icons-left">
|
||||
<div class="select is-fullwidth" >
|
||||
<select onChange={(e) => selectCountry((e.target as any).value)} disabled={!theContinent} value={theCountry?.code || ""}>
|
||||
<option key="none" disabled selected value=""> Choose a country </option>
|
||||
{countryList.map(prov => (
|
||||
@ -88,17 +84,17 @@ export function ContinentSelectionScreen(): VNode {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{theCountry && <div class="field">
|
||||
{/* {theCountry && <div class="field">
|
||||
<label class="label">Available currencies:</label>
|
||||
<div class="control">
|
||||
<input class="input is-small" type="text" readonly value={theCountry.currency} />
|
||||
</div>
|
||||
</div>}
|
||||
</div>} */}
|
||||
</div>
|
||||
<div class="column is-half">
|
||||
<div class="column is-two-third">
|
||||
<p>
|
||||
A location will help to define a common information that will be use to locate your secret and a currency
|
||||
for payments if needed.
|
||||
Your location will help us to determine which personal information
|
||||
ask you for the next step.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
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 (sebasjm)
|
||||
*/
|
||||
|
||||
import { createExample, reducerStatesExample } from '../../utils';
|
||||
import { CountrySelectionScreen as TestedComponent } from './CountrySelectionScreen';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'Pages/CountrySelectionScreen',
|
||||
component: TestedComponent,
|
||||
args: {
|
||||
order: 3,
|
||||
},
|
||||
argTypes: {
|
||||
onUpdate: { action: 'onUpdate' },
|
||||
onBack: { action: 'onBack' },
|
||||
},
|
||||
};
|
||||
|
||||
export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectCountry);
|
||||
export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectCountry);
|
@ -1,31 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { h, VNode } from "preact";
|
||||
import { useAnastasisContext } from "../../context/anastasis";
|
||||
import { AnastasisClientFrame, withProcessLabel } from "./index";
|
||||
|
||||
export function CountrySelectionScreen(): VNode {
|
||||
const reducer = useAnastasisContext()
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>
|
||||
}
|
||||
if (!reducer.currentReducerState || !("countries" in reducer.currentReducerState)) {
|
||||
return <div>invalid state</div>
|
||||
}
|
||||
const sel = (x: any): void => reducer.transition("select_country", {
|
||||
country_code: x.code,
|
||||
currencies: [x.currency],
|
||||
});
|
||||
return (
|
||||
<AnastasisClientFrame hideNext={"FIXME"} title={withProcessLabel(reducer, "Select Country")} >
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{reducer.currentReducerState.countries!.map((x: any) => (
|
||||
<div key={x.name}>
|
||||
<button class="button" onClick={() => sel(x)} >
|
||||
{x.name} ({x.currency})
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</AnastasisClientFrame>
|
||||
);
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
/*
|
||||
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 (sebasjm)
|
||||
*/
|
||||
|
||||
import { ReducerState } from 'anastasis-core';
|
||||
import { createExample, reducerStatesExample } from '../../utils';
|
||||
import { EditPoliciesScreen as TestedComponent } from './EditPoliciesScreen';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'Pages/backup/ReviewPoliciesScreen/EditPoliciesScreen',
|
||||
args: {
|
||||
order: 6,
|
||||
},
|
||||
component: TestedComponent,
|
||||
argTypes: {
|
||||
onUpdate: { action: 'onUpdate' },
|
||||
onBack: { action: 'onBack' },
|
||||
},
|
||||
};
|
||||
|
||||
export const EditingAPolicy = createExample(TestedComponent, {
|
||||
...reducerStatesExample.policyReview,
|
||||
policies: [{
|
||||
methods: [{
|
||||
authentication_method: 1,
|
||||
provider: 'https://anastasis.demo.taler.net/'
|
||||
}, {
|
||||
authentication_method: 2,
|
||||
provider: 'http://localhost:8086/'
|
||||
}]
|
||||
}, {
|
||||
methods: [{
|
||||
authentication_method: 1,
|
||||
provider: 'http://localhost:8086/'
|
||||
}]
|
||||
}],
|
||||
authentication_methods: [{
|
||||
type: "email",
|
||||
instructions: "Email to qwe@asd.com",
|
||||
challenge: "E5VPA"
|
||||
}, {
|
||||
type: "totp",
|
||||
instructions: "Response code for 'Anastasis'",
|
||||
challenge: "E5VPA"
|
||||
}, {
|
||||
type: "sms",
|
||||
instructions: "SMS to 6666-6666",
|
||||
challenge: ""
|
||||
}, {
|
||||
type: "question",
|
||||
instructions: "How did the chicken cross the road?",
|
||||
challenge: "C5SP8"
|
||||
}]
|
||||
} as ReducerState, { index : 0});
|
||||
|
||||
export const CreatingAPolicy = createExample(TestedComponent, {
|
||||
...reducerStatesExample.policyReview,
|
||||
policies: [{
|
||||
methods: [{
|
||||
authentication_method: 1,
|
||||
provider: 'https://anastasis.demo.taler.net/'
|
||||
}, {
|
||||
authentication_method: 2,
|
||||
provider: 'http://localhost:8086/'
|
||||
}]
|
||||
}, {
|
||||
methods: [{
|
||||
authentication_method: 1,
|
||||
provider: 'http://localhost:8086/'
|
||||
}]
|
||||
}],
|
||||
authentication_methods: [{
|
||||
type: "email",
|
||||
instructions: "Email to qwe@asd.com",
|
||||
challenge: "E5VPA"
|
||||
}, {
|
||||
type: "totp",
|
||||
instructions: "Response code for 'Anastasis'",
|
||||
challenge: "E5VPA"
|
||||
}, {
|
||||
type: "sms",
|
||||
instructions: "SMS to 6666-6666",
|
||||
challenge: ""
|
||||
}, {
|
||||
type: "question",
|
||||
instructions: "How did the chicken cross the road?",
|
||||
challenge: "C5SP8"
|
||||
}]
|
||||
} as ReducerState, { index : 3});
|
||||
|
133
packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx
Normal file
133
packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { AuthMethod, Policy } from "anastasis-core";
|
||||
import { h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAnastasisContext } from "../../context/anastasis";
|
||||
import { authMethods, KnownAuthMethods } from "./authMethod";
|
||||
import { AnastasisClientFrame } from "./index";
|
||||
|
||||
export interface ProviderInfo {
|
||||
url: string;
|
||||
cost: string;
|
||||
isFree: boolean;
|
||||
}
|
||||
|
||||
export type ProviderInfoByType = {
|
||||
[type in KnownAuthMethods]?: ProviderInfo[];
|
||||
};
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
cancel: () => void;
|
||||
confirm: (changes: MethodProvider[]) => void;
|
||||
|
||||
}
|
||||
|
||||
export interface MethodProvider {
|
||||
authentication_method: number;
|
||||
provider: string;
|
||||
}
|
||||
|
||||
export function EditPoliciesScreen({ index: policy_index, cancel, confirm }: Props): VNode {
|
||||
const [changedProvider, setChangedProvider] = useState<Array<string>>([])
|
||||
|
||||
const reducer = useAnastasisContext()
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>
|
||||
}
|
||||
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
|
||||
return <div>invalid state</div>
|
||||
}
|
||||
|
||||
const selectableProviders: ProviderInfoByType = {}
|
||||
const allProviders = Object.entries(reducer.currentReducerState.authentication_providers || {})
|
||||
for (let index = 0; index < allProviders.length; index++) {
|
||||
const [url, status] = allProviders[index]
|
||||
if ("methods" in status) {
|
||||
status.methods.map(m => {
|
||||
const type: KnownAuthMethods = m.type as KnownAuthMethods
|
||||
const values = selectableProviders[type] || []
|
||||
const isFree = !m.usage_fee || m.usage_fee.endsWith(":0")
|
||||
values.push({ url, cost: m.usage_fee, isFree })
|
||||
selectableProviders[type] = values
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const allAuthMethods = reducer.currentReducerState.authentication_methods ?? [];
|
||||
const policies = reducer.currentReducerState.policies ?? [];
|
||||
const policy = policies[policy_index]
|
||||
|
||||
for(let method_index = 0; method_index < allAuthMethods.length; method_index++ ) {
|
||||
policy?.methods.find(m => m.authentication_method === method_index)?.provider
|
||||
}
|
||||
|
||||
function sendChanges(): void {
|
||||
const newMethods: MethodProvider[] = []
|
||||
allAuthMethods.forEach((method, index) => {
|
||||
const oldValue = policy?.methods.find(m => m.authentication_method === index)
|
||||
if (changedProvider[index] === undefined && oldValue !== undefined) {
|
||||
newMethods.push(oldValue)
|
||||
}
|
||||
if (changedProvider[index] !== undefined && changedProvider[index] !== "") {
|
||||
newMethods.push({
|
||||
authentication_method: index,
|
||||
provider: changedProvider[index]
|
||||
})
|
||||
}
|
||||
})
|
||||
confirm(newMethods)
|
||||
}
|
||||
|
||||
return <AnastasisClientFrame hideNav title={!policy ? "Backup: New Policy" : "Backup: Edit Policy"}>
|
||||
<section class="section">
|
||||
{!policy ? <p>
|
||||
Creating a new policy #{policy_index}
|
||||
</p> : <p>
|
||||
Editing policy #{policy_index}
|
||||
</p>}
|
||||
{allAuthMethods.map((method, index) => {
|
||||
//take the url from the updated change or from the policy
|
||||
const providerURL = changedProvider[index] === undefined ?
|
||||
policy?.methods.find(m => m.authentication_method === index)?.provider :
|
||||
changedProvider[index];
|
||||
|
||||
const type: KnownAuthMethods = method.type as KnownAuthMethods
|
||||
function changeProviderTo(url: string): void {
|
||||
const copy = [...changedProvider]
|
||||
copy[index] = url
|
||||
setChangedProvider(copy)
|
||||
}
|
||||
return (
|
||||
<div key={index} class="block" style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span class="icon">
|
||||
{authMethods[type]?.icon}
|
||||
</span>
|
||||
<span>
|
||||
{method.instructions}
|
||||
</span>
|
||||
<span>
|
||||
<span class="select " >
|
||||
<select onChange={(e) => changeProviderTo(e.currentTarget.value)} value={providerURL ?? ""}>
|
||||
<option key="none" value=""> << off >> </option>
|
||||
{selectableProviders[type]?.map(prov => (
|
||||
<option key={prov.url} value={prov.url}>
|
||||
{prov.url}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<button class="button" onClick={cancel}>Cancel</button>
|
||||
<span class="buttons">
|
||||
<button class="button" onClick={() => setChangedProvider([])}>Reset</button>
|
||||
<button class="button is-info" onClick={sendChanges}>Confirm</button>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
</AnastasisClientFrame>
|
||||
}
|
@ -233,16 +233,16 @@ export const SomePoliciesWithMethods = createExample(TestedComponent, {
|
||||
instructions: "Does P equal NP?",
|
||||
challenge: "C5SP8"
|
||||
},{
|
||||
type: "email",
|
||||
instructions: "Email to qwe@asd.com",
|
||||
type: "totp",
|
||||
instructions: "Response code for 'Anastasis'",
|
||||
challenge: "E5VPA"
|
||||
}, {
|
||||
type: "sms",
|
||||
instructions: "SMS to 555-555",
|
||||
instructions: "SMS to 6666-6666",
|
||||
challenge: ""
|
||||
}, {
|
||||
type: "question",
|
||||
instructions: "Does P equal NP?",
|
||||
instructions: "How did the chicken cross the road?",
|
||||
challenge: "C5SP8"
|
||||
}]
|
||||
} as ReducerState);
|
||||
|
@ -1,10 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { AuthMethod } from "anastasis-core";
|
||||
import { h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAnastasisContext } from "../../context/anastasis";
|
||||
import { AnastasisClientFrame } from "./index";
|
||||
import { authMethods, KnownAuthMethods } from "./authMethod";
|
||||
import { EditPoliciesScreen } from "./EditPoliciesScreen";
|
||||
import { AnastasisClientFrame } from "./index";
|
||||
|
||||
export function ReviewPoliciesScreen(): VNode {
|
||||
const [editingPolicy, setEditingPolicy] = useState<number | undefined>()
|
||||
const reducer = useAnastasisContext()
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>
|
||||
@ -12,20 +16,44 @@ export function ReviewPoliciesScreen(): VNode {
|
||||
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
|
||||
return <div>invalid state</div>
|
||||
}
|
||||
|
||||
const configuredAuthMethods = reducer.currentReducerState.authentication_methods ?? [];
|
||||
const policies = reducer.currentReducerState.policies ?? [];
|
||||
|
||||
if (editingPolicy !== undefined) {
|
||||
return (
|
||||
<EditPoliciesScreen
|
||||
index={editingPolicy}
|
||||
cancel={() => setEditingPolicy(undefined)}
|
||||
confirm={(newMethods) => {
|
||||
reducer.runTransaction(async (tx) => {
|
||||
await tx.transition("delete_policy", {
|
||||
policy_index: editingPolicy
|
||||
});
|
||||
await tx.transition("add_policy", {
|
||||
policy: newMethods
|
||||
});
|
||||
});
|
||||
setEditingPolicy(undefined)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const errors = policies.length < 1 ? 'Need more policies' : undefined
|
||||
return (
|
||||
<AnastasisClientFrame hideNext={errors} title="Backup: Review Recovery Policies">
|
||||
{policies.length > 0 && <p class="block">
|
||||
Based on your configured authentication method you have created, some policies
|
||||
have been configured. In order to recover your secret you have to solve all the
|
||||
have been configured. In order to recover your secret you have to solve all the
|
||||
challenges of at least one policy.
|
||||
</p> }
|
||||
</p>}
|
||||
{policies.length < 1 && <p class="block">
|
||||
No policies had been created. Go back and add more authentication methods.
|
||||
</p> }
|
||||
</p>}
|
||||
<div class="block" onClick={() => setEditingPolicy(policies.length + 1)}>
|
||||
<button class="button is-success">Add new policy</button>
|
||||
</div>
|
||||
{policies.map((p, policy_index) => {
|
||||
const methods = p.methods
|
||||
.map(x => configuredAuthMethods[x.authentication_method] && ({ ...configuredAuthMethods[x.authentication_method], provider: x.provider }))
|
||||
@ -44,18 +72,21 @@ export function ReviewPoliciesScreen(): VNode {
|
||||
</p>}
|
||||
{methods.map((m, i) => {
|
||||
return (
|
||||
<p key={i} class="block" style={{display:'flex', alignItems:'center'}}>
|
||||
<span class="icon">
|
||||
{authMethods[m.type as KnownAuthMethods]?.icon}
|
||||
</span>
|
||||
<span>
|
||||
{m.instructions} recovery provided by <a href={m.provider}>{m.provider}</a>
|
||||
</span>
|
||||
</p>
|
||||
<p key={i} class="block" style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span class="icon">
|
||||
{authMethods[m.type as KnownAuthMethods]?.icon}
|
||||
</span>
|
||||
<span>
|
||||
{m.instructions} recovery provided by <a href={m.provider}>{m.provider}</a>
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div style={{ marginTop: 'auto', marginBottom: 'auto' }}><button class="button is-danger" onClick={() => reducer.transition("delete_policy", { policy_index })}>Delete</button></div>
|
||||
<div style={{ marginTop: 'auto', marginBottom: 'auto', display: 'flex', justifyContent: 'space-between', flexDirection: 'column' }}>
|
||||
<button class="button is-info block" onClick={() => setEditingPolicy(policy_index)}>Edit</button>
|
||||
<button class="button is-danger block" onClick={() => reducer.transition("delete_policy", { policy_index })}>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -10,33 +10,29 @@ export function StartScreen(): VNode {
|
||||
}
|
||||
return (
|
||||
<AnastasisClientFrame hideNav title="Home">
|
||||
<div>
|
||||
<section class="section is-main-section">
|
||||
<div class="columns">
|
||||
<div class="column" />
|
||||
<div class="column is-four-fifths">
|
||||
<div class="columns">
|
||||
<div class="column" />
|
||||
<div class="column is-four-fifths">
|
||||
|
||||
<div class="buttons">
|
||||
<button class="button is-success" autoFocus onClick={() => reducer.startBackup()}>
|
||||
<div class="icon"><i class="mdi mdi-arrow-up" /></div>
|
||||
<span>Backup a secret</span>
|
||||
</button>
|
||||
<div class="buttons">
|
||||
<button class="button is-success" autoFocus onClick={() => reducer.startBackup()}>
|
||||
<div class="icon"><i class="mdi mdi-arrow-up" /></div>
|
||||
<span>Backup a secret</span>
|
||||
</button>
|
||||
|
||||
<button class="button is-info" onClick={() => reducer.startRecover()}>
|
||||
<div class="icon"><i class="mdi mdi-arrow-down" /></div>
|
||||
<span>Recover a secret</span>
|
||||
</button>
|
||||
<button class="button is-info" onClick={() => reducer.startRecover()}>
|
||||
<div class="icon"><i class="mdi mdi-arrow-down" /></div>
|
||||
<span>Recover a secret</span>
|
||||
</button>
|
||||
|
||||
<button class="button">
|
||||
<div class="icon"><i class="mdi mdi-file" /></div>
|
||||
<span>Restore a session</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="column" />
|
||||
<button class="button">
|
||||
<div class="icon"><i class="mdi mdi-file" /></div>
|
||||
<span>Restore a session</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<div class="column" />
|
||||
</div>
|
||||
</AnastasisClientFrame>
|
||||
);
|
||||
|
@ -27,7 +27,7 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A
|
||||
<AnastasisClientFrame hideNav title="Add Security Question">
|
||||
<div>
|
||||
<p>
|
||||
For security question authentication, you need to provide a question
|
||||
For2 security question authentication, you need to provide a question
|
||||
and its answer. When recovering your secret, you will be shown the
|
||||
question and you will need to type the answer exactly as you typed it
|
||||
here.
|
||||
@ -47,6 +47,13 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<button class="button" onClick={cancel}>Cancel</button>
|
||||
<span data-tooltip={errors}>
|
||||
<button class="button is-info" disabled={errors !== undefined} onClick={addQuestionAuth}>Add</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{configured.length > 0 && <section class="section">
|
||||
<div class="block">
|
||||
Your security questions:
|
||||
@ -58,12 +65,6 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A
|
||||
</div>
|
||||
})}
|
||||
</div></section>}
|
||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<button class="button" onClick={cancel}>Cancel</button>
|
||||
<span data-tooltip={errors}>
|
||||
<button class="button is-info" disabled={errors !== undefined} onClick={addQuestionAuth}>Add</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</AnastasisClientFrame >
|
||||
);
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
import {
|
||||
useErrorBoundary
|
||||
} from "preact/hooks";
|
||||
import { AsyncButton } from "../../components/AsyncButton";
|
||||
import { Menu } from "../../components/menu";
|
||||
import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis";
|
||||
import {
|
||||
@ -25,7 +26,6 @@ import { BackupFinishedScreen } from "./BackupFinishedScreen";
|
||||
import { ChallengeOverviewScreen } from "./ChallengeOverviewScreen";
|
||||
import { ChallengePayingScreen } from "./ChallengePayingScreen";
|
||||
import { ContinentSelectionScreen } from "./ContinentSelectionScreen";
|
||||
import { CountrySelectionScreen } from "./CountrySelectionScreen";
|
||||
import { PoliciesPayingScreen } from "./PoliciesPayingScreen";
|
||||
import { RecoveryFinishedScreen } from "./RecoveryFinishedScreen";
|
||||
import { ReviewPoliciesScreen } from "./ReviewPoliciesScreen";
|
||||
@ -95,12 +95,19 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
|
||||
if (!reducer) {
|
||||
return <p>Fatal: Reducer must be in context.</p>;
|
||||
}
|
||||
const next = (): void => {
|
||||
if (props.onNext) {
|
||||
props.onNext();
|
||||
} else {
|
||||
reducer.transition("next", {});
|
||||
}
|
||||
const next = async (): Promise<void> => {
|
||||
return new Promise((res, rej) => {
|
||||
try {
|
||||
if (props.onNext) {
|
||||
props.onNext();
|
||||
} else {
|
||||
reducer.transition("next", {});
|
||||
}
|
||||
res()
|
||||
} catch {
|
||||
rej()
|
||||
}
|
||||
})
|
||||
};
|
||||
const handleKeyPress = (
|
||||
e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>,
|
||||
@ -111,20 +118,18 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
|
||||
return (
|
||||
<Fragment>
|
||||
<Menu title="Anastasis" />
|
||||
<div>
|
||||
<div class="home" onKeyPress={(e) => handleKeyPress(e)}>
|
||||
<h1 class="title">{props.title}</h1>
|
||||
<div class="home" onKeyPress={(e) => handleKeyPress(e)}>
|
||||
<h1 class="title">{props.title}</h1>
|
||||
<section class="section is-main-section">
|
||||
<ErrorBanner />
|
||||
{props.children}
|
||||
{!props.hideNav ? (
|
||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<button class="button" onClick={() => reducer.back()}>Back</button>
|
||||
<span data-tooltip={props.hideNext}>
|
||||
<button class="button is-info" onClick={next} disabled={props.hideNext !== undefined}>Next</button>
|
||||
</span>
|
||||
<AsyncButton data-tooltip={props.hideNext} onClick={next} disabled={props.hideNext !== undefined}>Next</AsyncButton>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
@ -195,7 +195,7 @@ div[data-tooltip]::before {
|
||||
padding: 1em 1em;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
max-width: 40em;
|
||||
// max-width: 40em;
|
||||
}
|
||||
|
||||
// .home div {
|
||||
|
@ -86,10 +86,10 @@ const base = {
|
||||
{
|
||||
type: "question",
|
||||
usage_fee: "COL:0"
|
||||
},{
|
||||
}, {
|
||||
type: "sms",
|
||||
usage_fee: "COL:0"
|
||||
},{
|
||||
}, {
|
||||
type: "email",
|
||||
usage_fee: "COL:0"
|
||||
},
|
||||
@ -98,6 +98,48 @@ const base = {
|
||||
storage_limit_in_megabytes: 16,
|
||||
truth_upload_fee: "COL:0"
|
||||
},
|
||||
"https://kudos.demo.anastasis.lu/": {
|
||||
http_status: 200,
|
||||
annual_fee: "COL:0",
|
||||
business_name: "ana",
|
||||
currency: "COL",
|
||||
liability_limit: "COL:10",
|
||||
methods: [
|
||||
{
|
||||
type: "question",
|
||||
usage_fee: "COL:0"
|
||||
}, {
|
||||
type: "email",
|
||||
usage_fee: "COL:0"
|
||||
},
|
||||
],
|
||||
salt: "WBMDD76BR1E90YQ5AHBMKPH7GW",
|
||||
storage_limit_in_megabytes: 16,
|
||||
truth_upload_fee: "COL:0"
|
||||
},
|
||||
"https://anastasis.demo.taler.net/": {
|
||||
http_status: 200,
|
||||
annual_fee: "COL:0",
|
||||
business_name: "ana",
|
||||
currency: "COL",
|
||||
liability_limit: "COL:10",
|
||||
methods: [
|
||||
{
|
||||
type: "question",
|
||||
usage_fee: "COL:0"
|
||||
}, {
|
||||
type: "sms",
|
||||
usage_fee: "COL:0"
|
||||
}, {
|
||||
type: "totp",
|
||||
usage_fee: "COL:0"
|
||||
},
|
||||
],
|
||||
salt: "WBMDD76BR1E90YQ5AHBMKPH7GW",
|
||||
storage_limit_in_megabytes: 16,
|
||||
truth_upload_fee: "COL:0"
|
||||
},
|
||||
|
||||
"http://localhost:8087/": {
|
||||
code: 8414,
|
||||
hint: "request to provider failed"
|
||||
@ -118,55 +160,72 @@ const base = {
|
||||
|
||||
export const reducerStatesExample = {
|
||||
initial: undefined,
|
||||
recoverySelectCountry: {...base,
|
||||
recoverySelectCountry: {
|
||||
...base,
|
||||
recovery_state: RecoveryStates.CountrySelecting
|
||||
} as ReducerState,
|
||||
recoverySelectContinent: {...base,
|
||||
recoverySelectContinent: {
|
||||
...base,
|
||||
recovery_state: RecoveryStates.ContinentSelecting,
|
||||
} as ReducerState,
|
||||
secretSelection: {...base,
|
||||
secretSelection: {
|
||||
...base,
|
||||
recovery_state: RecoveryStates.SecretSelecting,
|
||||
} as ReducerState,
|
||||
recoveryFinished: {...base,
|
||||
recoveryFinished: {
|
||||
...base,
|
||||
recovery_state: RecoveryStates.RecoveryFinished,
|
||||
} as ReducerState,
|
||||
challengeSelecting: {...base,
|
||||
challengeSelecting: {
|
||||
...base,
|
||||
recovery_state: RecoveryStates.ChallengeSelecting,
|
||||
} as ReducerState,
|
||||
challengeSolving: {...base,
|
||||
challengeSolving: {
|
||||
...base,
|
||||
recovery_state: RecoveryStates.ChallengeSolving,
|
||||
} as ReducerState,
|
||||
challengePaying: {...base,
|
||||
challengePaying: {
|
||||
...base,
|
||||
recovery_state: RecoveryStates.ChallengePaying,
|
||||
} as ReducerState,
|
||||
recoveryAttributeEditing: {...base,
|
||||
recoveryAttributeEditing: {
|
||||
...base,
|
||||
recovery_state: RecoveryStates.UserAttributesCollecting
|
||||
} as ReducerState,
|
||||
backupSelectCountry: {...base,
|
||||
backupSelectCountry: {
|
||||
...base,
|
||||
backup_state: BackupStates.CountrySelecting
|
||||
} as ReducerState,
|
||||
backupSelectContinent: {...base,
|
||||
backupSelectContinent: {
|
||||
...base,
|
||||
backup_state: BackupStates.ContinentSelecting,
|
||||
} as ReducerState,
|
||||
secretEdition: {...base,
|
||||
secretEdition: {
|
||||
...base,
|
||||
backup_state: BackupStates.SecretEditing,
|
||||
} as ReducerState,
|
||||
policyReview: {...base,
|
||||
policyReview: {
|
||||
...base,
|
||||
backup_state: BackupStates.PoliciesReviewing,
|
||||
} as ReducerState,
|
||||
policyPay: {...base,
|
||||
policyPay: {
|
||||
...base,
|
||||
backup_state: BackupStates.PoliciesPaying,
|
||||
} as ReducerState,
|
||||
backupFinished: {...base,
|
||||
backupFinished: {
|
||||
...base,
|
||||
backup_state: BackupStates.BackupFinished,
|
||||
} as ReducerState,
|
||||
authEditing: {...base,
|
||||
authEditing: {
|
||||
...base,
|
||||
backup_state: BackupStates.AuthenticationsEditing
|
||||
} as ReducerState,
|
||||
backupAttributeEditing: {...base,
|
||||
backupAttributeEditing: {
|
||||
...base,
|
||||
backup_state: BackupStates.UserAttributesCollecting
|
||||
} as ReducerState,
|
||||
truthsPaying: {...base,
|
||||
truthsPaying: {
|
||||
...base,
|
||||
backup_state: BackupStates.TruthsPaying
|
||||
} as ReducerState,
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user