working version with improved ui
This commit is contained in:
parent
21b60c8f6f
commit
32318a80f4
@ -8,6 +8,7 @@ export interface DateInputProps {
|
|||||||
grabFocus?: boolean;
|
grabFocus?: boolean;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
years?: Array<number>;
|
||||||
bind: [string, (x: string) => void];
|
bind: [string, (x: string) => void];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ export function DateInput(props: DateInputProps): VNode {
|
|||||||
}
|
}
|
||||||
}, [props.grabFocus]);
|
}, [props.grabFocus]);
|
||||||
const [opened, setOpened2] = useState(false)
|
const [opened, setOpened2] = useState(false)
|
||||||
function setOpened(v: boolean) {
|
function setOpened(v: boolean): void {
|
||||||
console.log('dale', v)
|
console.log('dale', v)
|
||||||
setOpened2(v)
|
setOpened2(v)
|
||||||
}
|
}
|
||||||
@ -50,6 +51,7 @@ export function DateInput(props: DateInputProps): VNode {
|
|||||||
{showError && <p class="help is-danger">{props.error}</p>}
|
{showError && <p class="help is-danger">{props.error}</p>}
|
||||||
<DatePicker
|
<DatePicker
|
||||||
opened={opened}
|
opened={opened}
|
||||||
|
years={props.years}
|
||||||
closeFunction={() => setOpened(false)}
|
closeFunction={() => setOpened(false)}
|
||||||
dateReceiver={(d) => {
|
dateReceiver={(d) => {
|
||||||
setDirty(true)
|
setDirty(true)
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
export interface TextInputProps {
|
||||||
|
label: string;
|
||||||
|
grabFocus?: boolean;
|
||||||
|
error?: string;
|
||||||
|
tooltip?: string;
|
||||||
|
bind: [string, (x: string) => void];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NumberInput(props: TextInputProps): VNode {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (props.grabFocus) {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}
|
||||||
|
}, [props.grabFocus]);
|
||||||
|
const value = props.bind[0];
|
||||||
|
const [dirty, setDirty] = useState(false)
|
||||||
|
const showError = dirty && props.error
|
||||||
|
return (<div class="field">
|
||||||
|
<label class="label">
|
||||||
|
{props.label}
|
||||||
|
{props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
|
||||||
|
<i class="mdi mdi-information" />
|
||||||
|
</span>}
|
||||||
|
</label>
|
||||||
|
<div class="control has-icons-right">
|
||||||
|
<input
|
||||||
|
value={value}
|
||||||
|
type="number"
|
||||||
|
class={showError ? 'input is-danger' : 'input'}
|
||||||
|
onChange={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}}
|
||||||
|
ref={inputRef}
|
||||||
|
style={{ display: "block" }} />
|
||||||
|
</div>
|
||||||
|
{showError && <p class="help is-danger">{props.error}</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||||
|
|
||||||
export interface LabeledInputProps {
|
export interface TextInputProps {
|
||||||
label: string;
|
label: string;
|
||||||
grabFocus?: boolean;
|
grabFocus?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
@ -9,7 +9,7 @@ export interface LabeledInputProps {
|
|||||||
bind: [string, (x: string) => void];
|
bind: [string, (x: string) => void];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LabeledInput(props: LabeledInputProps): VNode {
|
export function TextInput(props: TextInputProps): VNode {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (props.grabFocus) {
|
if (props.grabFocus) {
|
@ -64,9 +64,8 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
{reducer.currentReducerState && reducer.currentReducerState.backup_state ? <Fragment>
|
{reducer.currentReducerState && reducer.currentReducerState.backup_state ? <Fragment>
|
||||||
<li class={
|
<li class={reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting ||
|
||||||
reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting ||
|
reducer.currentReducerState.backup_state === BackupStates.CountrySelecting ? 'is-active' : ''}>
|
||||||
reducer.currentReducerState.backup_state === BackupStates.CountrySelecting ? 'is-active' : ''}>
|
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label"><Translate>Location & Currency</Translate></span>
|
<span class="menu-item-label"><Translate>Location & Currency</Translate></span>
|
||||||
</div>
|
</div>
|
||||||
@ -79,73 +78,65 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
<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"><Translate>Auth methods</Translate></span>
|
<span class="menu-item-label"><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"><Translate>PoliciesReviewing</Translate></span>
|
<span class="menu-item-label"><Translate>Policies reviewing</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"><Translate>SecretEditing</Translate></span>
|
<span class="menu-item-label"><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' : ''}>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
|
|
||||||
<span class="menu-item-label"><Translate>PoliciesPaying</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"><Translate>BackupFinished</Translate></span>
|
<span class="menu-item-label"><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' : ''}>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
|
|
||||||
<span class="menu-item-label"><Translate>TruthsPaying</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> : (reducer.currentReducerState && reducer.currentReducerState?.recovery_state && <Fragment>
|
||||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting ? 'is-active' : ''}>
|
<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>ContinentSelecting</Translate></span>
|
<span class="menu-item-label"><Translate>Location & Currency</Translate></span>
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.CountrySelecting ? 'is-active' : ''}>
|
|
||||||
<div class="ml-4">
|
|
||||||
<span class="menu-item-label"><Translate>CountrySelecting</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>UserAttributesCollecting</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>SecretSelecting</Translate></span>
|
<span class="menu-item-label"><Translate>Secret selection</Translate></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.ChallengeSelecting ? 'is-active' : ''}>
|
<li 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>ChallengeSelecting</Translate></span>
|
<span class="menu-item-label"><Translate>Solve Challenges</Translate></span>
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.ChallengeSolving ? 'is-active' : ''}>
|
|
||||||
<div class="ml-4">
|
|
||||||
<span class="menu-item-label"><Translate>ChallengeSolving</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>RecoveryFinished</Translate></span>
|
<span class="menu-item-label"><Translate>Secret recovered</Translate></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</Fragment>)}
|
</Fragment>)}
|
||||||
|
@ -24,6 +24,7 @@ import { h, Component } from "preact";
|
|||||||
interface Props {
|
interface Props {
|
||||||
closeFunction?: () => void;
|
closeFunction?: () => void;
|
||||||
dateReceiver?: (d: Date) => void;
|
dateReceiver?: (d: Date) => void;
|
||||||
|
years?: Array<number>;
|
||||||
opened?: boolean;
|
opened?: boolean;
|
||||||
}
|
}
|
||||||
interface State {
|
interface State {
|
||||||
@ -207,9 +208,9 @@ export class DatePicker extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (this.state.selectYearMode) {
|
// if (this.state.selectYearMode) {
|
||||||
document.getElementsByClassName('selected')[0].scrollIntoView(); // works in every browser incl. IE, replace with scrollIntoViewIfNeeded when browsers support it
|
// document.getElementsByClassName('selected')[0].scrollIntoView(); // works in every browser incl. IE, replace with scrollIntoViewIfNeeded when browsers support it
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -296,8 +297,7 @@ export class DatePicker extends Component<Props, State> {
|
|||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
{selectYearMode && <div class="datePicker--selectYear">
|
{selectYearMode && <div class="datePicker--selectYear">
|
||||||
|
{(this.props.years || yearArr).map(year => (
|
||||||
{yearArr.map(year => (
|
|
||||||
<span key={year} class={(year === displayedYear) ? 'selected' : ''} onClick={this.changeDisplayedYear}>
|
<span key={year} class={(year === displayedYear) ? 'selected' : ''} onClick={this.changeDisplayedYear}>
|
||||||
{year}
|
{year}
|
||||||
</span>
|
</span>
|
||||||
|
@ -40,21 +40,21 @@ export default {
|
|||||||
export const Backup = createExample(TestedComponent, {
|
export const Backup = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.backupAttributeEditing,
|
...reducerStatesExample.backupAttributeEditing,
|
||||||
required_attributes: [{
|
required_attributes: [{
|
||||||
name: 'first',
|
name: 'first name',
|
||||||
label: 'first',
|
label: 'first',
|
||||||
type: 'type',
|
type: 'string',
|
||||||
uuid: 'asdasdsa1',
|
uuid: 'asdasdsa1',
|
||||||
widget: 'wid',
|
widget: 'wid',
|
||||||
}, {
|
}, {
|
||||||
name: 'pepe',
|
name: 'last name',
|
||||||
label: 'second',
|
label: 'second',
|
||||||
type: 'type',
|
type: 'string',
|
||||||
uuid: 'asdasdsa2',
|
uuid: 'asdasdsa2',
|
||||||
widget: 'wid',
|
widget: 'wid',
|
||||||
}, {
|
}, {
|
||||||
name: 'pepe2',
|
name: 'date',
|
||||||
label: 'third',
|
label: 'third',
|
||||||
type: 'type',
|
type: 'date',
|
||||||
uuid: 'asdasdsa3',
|
uuid: 'asdasdsa3',
|
||||||
widget: 'calendar',
|
widget: 'calendar',
|
||||||
}]
|
}]
|
||||||
@ -65,19 +65,19 @@ export const Recovery = createExample(TestedComponent, {
|
|||||||
required_attributes: [{
|
required_attributes: [{
|
||||||
name: 'first',
|
name: 'first',
|
||||||
label: 'first',
|
label: 'first',
|
||||||
type: 'type',
|
type: 'string',
|
||||||
uuid: 'asdasdsa1',
|
uuid: 'asdasdsa1',
|
||||||
widget: 'wid',
|
widget: 'wid',
|
||||||
}, {
|
}, {
|
||||||
name: 'pepe',
|
name: 'pepe',
|
||||||
label: 'second',
|
label: 'second',
|
||||||
type: 'type',
|
type: 'string',
|
||||||
uuid: 'asdasdsa2',
|
uuid: 'asdasdsa2',
|
||||||
widget: 'wid',
|
widget: 'wid',
|
||||||
}, {
|
}, {
|
||||||
name: 'pepe2',
|
name: 'pepe2',
|
||||||
label: 'third',
|
label: 'third',
|
||||||
type: 'type',
|
type: 'date',
|
||||||
uuid: 'asdasdsa3',
|
uuid: 'asdasdsa3',
|
||||||
widget: 'calendar',
|
widget: 'calendar',
|
||||||
}]
|
}]
|
||||||
@ -110,12 +110,20 @@ const allWidgets = [
|
|||||||
"anastasis_gtk_xx_square",
|
"anastasis_gtk_xx_square",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
function typeForWidget(name: string): string {
|
||||||
|
if (["anastasis_gtk_xx_prime",
|
||||||
|
"anastasis_gtk_xx_square",
|
||||||
|
].includes(name)) return "number";
|
||||||
|
if (["anastasis_gtk_ia_birthdate"].includes(name)) return "date"
|
||||||
|
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: 'type',
|
type: typeForWidget(w),
|
||||||
uuid: `uuid-${w}`,
|
uuid: `uuid-${w}`,
|
||||||
widget: w
|
widget: w
|
||||||
}))
|
}))
|
||||||
|
@ -4,8 +4,9 @@ import { h, VNode } from "preact";
|
|||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
import { useAnastasisContext } from "../../context/anastasis";
|
||||||
import { AnastasisClientFrame, withProcessLabel } from "./index";
|
import { AnastasisClientFrame, withProcessLabel } from "./index";
|
||||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
import { TextInput } from "../../components/fields/TextInput";
|
||||||
import { DateInput } from "../../components/fields/DateInput";
|
import { DateInput } from "../../components/fields/DateInput";
|
||||||
|
import { NumberInput } from "../../components/fields/NumberInput";
|
||||||
|
|
||||||
export function AttributeEntryScreen(): VNode {
|
export function AttributeEntryScreen(): VNode {
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext()
|
||||||
@ -65,6 +66,7 @@ export function AttributeEntryScreen(): VNode {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-half" >
|
<div class="column is-half" >
|
||||||
|
<p>This personal information will help to locate your secret in the first place</p>
|
||||||
<h1><b>This stay private</b></h1>
|
<h1><b>This stay private</b></h1>
|
||||||
<p>The information you have entered here:
|
<p>The information you have entered here:
|
||||||
</p>
|
</p>
|
||||||
@ -92,20 +94,33 @@ interface AttributeEntryFieldProps {
|
|||||||
spec: UserAttributeSpec;
|
spec: UserAttributeSpec;
|
||||||
isValid: () => string | undefined;
|
isValid: () => string | undefined;
|
||||||
}
|
}
|
||||||
|
const possibleBirthdayYear: Array<number> = []
|
||||||
|
for (let i = 0; i < 100; i++ ) {
|
||||||
|
possibleBirthdayYear.push(2020 - i)
|
||||||
|
}
|
||||||
function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
|
function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
|
||||||
const errorMessage = props.isValid()
|
const errorMessage = props.isValid()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{props.spec.type === 'date' ?
|
{props.spec.type === 'date' &&
|
||||||
<DateInput
|
<DateInput
|
||||||
|
grabFocus={props.isFirst}
|
||||||
|
label={props.spec.label}
|
||||||
|
years={possibleBirthdayYear}
|
||||||
|
error={errorMessage}
|
||||||
|
bind={[props.value, props.setValue]}
|
||||||
|
/>}
|
||||||
|
{props.spec.type === 'number' &&
|
||||||
|
<NumberInput
|
||||||
grabFocus={props.isFirst}
|
grabFocus={props.isFirst}
|
||||||
label={props.spec.label}
|
label={props.spec.label}
|
||||||
error={errorMessage}
|
error={errorMessage}
|
||||||
bind={[props.value, props.setValue]}
|
bind={[props.value, props.setValue]}
|
||||||
/> :
|
/>
|
||||||
<LabeledInput
|
}
|
||||||
|
{props.spec.type === 'string' &&
|
||||||
|
<TextInput
|
||||||
grabFocus={props.isFirst}
|
grabFocus={props.isFirst}
|
||||||
label={props.spec.label}
|
label={props.spec.label}
|
||||||
error={errorMessage}
|
error={errorMessage}
|
||||||
|
@ -7,7 +7,7 @@ import { h, VNode } from "preact";
|
|||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
|
import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
|
||||||
import { AnastasisClientFrame } from "./index";
|
import { AnastasisClientFrame } from "./index";
|
||||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
import { TextInput } from "../../components/fields/TextInput";
|
||||||
|
|
||||||
export function AuthMethodEmailSetup(props: AuthMethodSetupProps): VNode {
|
export function AuthMethodEmailSetup(props: AuthMethodSetupProps): VNode {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
@ -19,7 +19,7 @@ export function AuthMethodEmailSetup(props: AuthMethodSetupProps): VNode {
|
|||||||
email.
|
email.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput
|
<TextInput
|
||||||
label="Email address"
|
label="Email address"
|
||||||
grabFocus
|
grabFocus
|
||||||
bind={[email, setEmail]} />
|
bind={[email, setEmail]} />
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
|
import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
|
||||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
import { TextInput } from "../../components/fields/TextInput";
|
||||||
|
|
||||||
export function AuthMethodPostSetup(props: AuthMethodSetupProps): VNode {
|
export function AuthMethodPostSetup(props: AuthMethodSetupProps): VNode {
|
||||||
const [fullName, setFullName] = useState("");
|
const [fullName, setFullName] = useState("");
|
||||||
@ -42,22 +42,22 @@ export function AuthMethodPostSetup(props: AuthMethodSetupProps): VNode {
|
|||||||
code that you will receive in a letter to that address.
|
code that you will receive in a letter to that address.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput
|
<TextInput
|
||||||
grabFocus
|
grabFocus
|
||||||
label="Full Name"
|
label="Full Name"
|
||||||
bind={[fullName, setFullName]} />
|
bind={[fullName, setFullName]} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput label="Street" bind={[street, setStreet]} />
|
<TextInput label="Street" bind={[street, setStreet]} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput label="City" bind={[city, setCity]} />
|
<TextInput label="City" bind={[city, setCity]} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput label="Postal Code" bind={[postcode, setPostcode]} />
|
<TextInput label="Postal Code" bind={[postcode, setPostcode]} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput label="Country" bind={[country, setCountry]} />
|
<TextInput label="Country" bind={[country, setCountry]} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => props.cancel()}>Cancel</button>
|
<button onClick={() => props.cancel()}>Cancel</button>
|
||||||
|
@ -7,7 +7,7 @@ import { h, VNode } from "preact";
|
|||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
|
import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
|
||||||
import { AnastasisClientFrame } from "./index";
|
import { AnastasisClientFrame } from "./index";
|
||||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
import { TextInput } from "../../components/fields/TextInput";
|
||||||
|
|
||||||
export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode {
|
export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode {
|
||||||
const [questionText, setQuestionText] = useState("");
|
const [questionText, setQuestionText] = useState("");
|
||||||
@ -29,13 +29,13 @@ export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode {
|
|||||||
here.
|
here.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput
|
<TextInput
|
||||||
label="Security question"
|
label="Security question"
|
||||||
grabFocus
|
grabFocus
|
||||||
bind={[questionText, setQuestionText]} />
|
bind={[questionText, setQuestionText]} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput label="Answer" bind={[answerText, setAnswerText]} />
|
<TextInput label="Answer" bind={[answerText, setAnswerText]} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => props.cancel()}>Cancel</button>
|
<button onClick={() => props.cancel()}>Cancel</button>
|
||||||
|
@ -37,7 +37,7 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OneChallenge = createExample(TestedComponent, {
|
export const OneUnsolvedPolicy = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.challengeSelecting,
|
...reducerStatesExample.challengeSelecting,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
policies: [[{ uuid: '1' }]],
|
policies: [[{ uuid: '1' }]],
|
||||||
@ -50,7 +50,7 @@ export const OneChallenge = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const MoreChallenges = createExample(TestedComponent, {
|
export const SomePoliciesOneSolved = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.challengeSelecting,
|
...reducerStatesExample.challengeSelecting,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
policies: [[{ uuid: '1' }, { uuid: '2' }], [{ uuid: 'uuid-3' }]],
|
policies: [[{ uuid: '1' }, { uuid: '2' }], [{ uuid: 'uuid-3' }]],
|
||||||
@ -75,13 +75,13 @@ export const MoreChallenges = createExample(TestedComponent, {
|
|||||||
'uuid-3': {
|
'uuid-3': {
|
||||||
state: 'solved'
|
state: 'solved'
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
export const OneBadConfiguredPolicy = createExample(TestedComponent, {
|
export const OneBadConfiguredPolicy = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.challengeSelecting,
|
...reducerStatesExample.challengeSelecting,
|
||||||
recovery_information: {
|
recovery_information: {
|
||||||
policies: [[{ uuid: '2' }]],
|
policies: [[{ uuid: '1' }, { uuid: '2' }]],
|
||||||
challenges: [{
|
challenges: [{
|
||||||
cost: 'USD:1',
|
cost: 'USD:1',
|
||||||
instructions: 'just go for it',
|
instructions: 'just go for it',
|
||||||
@ -91,4 +91,130 @@ export const OneBadConfiguredPolicy = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
|
export const OnePolicyWithAllTheChallenges = createExample(TestedComponent, {
|
||||||
|
...reducerStatesExample.challengeSelecting,
|
||||||
|
recovery_information: {
|
||||||
|
policies: [[
|
||||||
|
{ uuid: '1' },
|
||||||
|
{ uuid: '2' },
|
||||||
|
{ uuid: '3' },
|
||||||
|
{ uuid: '4' },
|
||||||
|
{ uuid: '5' },
|
||||||
|
{ uuid: '6' },
|
||||||
|
]],
|
||||||
|
challenges: [{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '1',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'enter a text received by a sms',
|
||||||
|
type: 'sms',
|
||||||
|
uuid: '2',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'enter a text received by a email',
|
||||||
|
type: 'email',
|
||||||
|
uuid: '3',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'enter a code based on a time-based one-time password',
|
||||||
|
type: 'totp',
|
||||||
|
uuid: '4',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'send a wire transfer to an account',
|
||||||
|
type: 'iban',
|
||||||
|
uuid: '5',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'just go for it',
|
||||||
|
type: 'new-type-of-challenge',
|
||||||
|
uuid: '6',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
} as ReducerState);
|
||||||
|
|
||||||
|
|
||||||
|
export const OnePolicyWithAllTheChallengesInDifferentState = createExample(TestedComponent, {
|
||||||
|
...reducerStatesExample.challengeSelecting,
|
||||||
|
recovery_information: {
|
||||||
|
policies: [[
|
||||||
|
{ uuid: '1' },
|
||||||
|
{ uuid: '2' },
|
||||||
|
{ uuid: '3' },
|
||||||
|
{ uuid: '4' },
|
||||||
|
{ uuid: '5' },
|
||||||
|
{ uuid: '6' },
|
||||||
|
{ uuid: '7' },
|
||||||
|
{ uuid: '8' },
|
||||||
|
{ uuid: '9' },
|
||||||
|
{ uuid: '10' },
|
||||||
|
]],
|
||||||
|
challenges: [{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '1',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '2',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '3',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '4',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '5',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '6',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '7',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '8',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '9',
|
||||||
|
},{
|
||||||
|
cost: 'USD:1',
|
||||||
|
instructions: 'answer the a question correctly',
|
||||||
|
type: 'question',
|
||||||
|
uuid: '10',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
challenge_feedback: {
|
||||||
|
1: { state: 'solved' },
|
||||||
|
2: { state: 'hint' },
|
||||||
|
3: { state: 'details' },
|
||||||
|
4: { state: 'body' },
|
||||||
|
5: { state: 'redirect' },
|
||||||
|
6: { state: 'server-failure' },
|
||||||
|
7: { state: 'truth-unknown' },
|
||||||
|
8: { state: 'rate-limit-exceeded' },
|
||||||
|
9: { state: 'authentication-timeout' },
|
||||||
|
10: { state: 'external-instructions' },
|
||||||
|
}
|
||||||
|
} as ReducerState);
|
||||||
export const NoPolicies = createExample(TestedComponent, reducerStatesExample.challengeSelecting);
|
export const NoPolicies = createExample(TestedComponent, reducerStatesExample.challengeSelecting);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ChallengeFeedback } from "anastasis-core";
|
||||||
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";
|
||||||
@ -13,65 +14,94 @@ export function ChallengeOverviewScreen(): VNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const policies = reducer.currentReducerState.recovery_information?.policies ?? [];
|
const policies = reducer.currentReducerState.recovery_information?.policies ?? [];
|
||||||
const chArr = reducer.currentReducerState.recovery_information?.challenges ?? [];
|
const knownChallengesArray = reducer.currentReducerState.recovery_information?.challenges ?? [];
|
||||||
const challengeFeedback = reducer.currentReducerState?.challenge_feedback;
|
const challengeFeedback = reducer.currentReducerState?.challenge_feedback ?? {};
|
||||||
|
|
||||||
const challenges: {
|
const knownChallengesMap: {
|
||||||
[uuid: string]: {
|
[uuid: string]: {
|
||||||
type: string;
|
type: string;
|
||||||
instructions: string;
|
instructions: string;
|
||||||
cost: string;
|
cost: string;
|
||||||
|
feedback: ChallengeFeedback | undefined;
|
||||||
};
|
};
|
||||||
} = {};
|
} = {};
|
||||||
for (const ch of chArr) {
|
for (const ch of knownChallengesArray) {
|
||||||
challenges[ch.uuid] = {
|
knownChallengesMap[ch.uuid] = {
|
||||||
type: ch.type,
|
type: ch.type,
|
||||||
cost: ch.cost,
|
cost: ch.cost,
|
||||||
instructions: ch.instructions,
|
instructions: ch.instructions,
|
||||||
|
feedback: challengeFeedback[ch.uuid]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const policiesWithInfo = policies.map(row => {
|
||||||
|
let isPolicySolved = true
|
||||||
|
const challenges = row.map(({ uuid }) => {
|
||||||
|
const info = knownChallengesMap[uuid];
|
||||||
|
const isChallengeSolved = info?.feedback?.state === 'solved'
|
||||||
|
isPolicySolved = isPolicySolved && isChallengeSolved
|
||||||
|
return { info, uuid, isChallengeSolved }
|
||||||
|
}).filter(ch => ch.info !== undefined)
|
||||||
|
|
||||||
|
return { isPolicySolved, challenges }
|
||||||
|
})
|
||||||
|
|
||||||
|
const atLeastThereIsOnePolicySolved = policiesWithInfo.find(p => p.isPolicySolved) !== undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame title="Recovery: Solve challenges">
|
<AnastasisClientFrame hideNext={!atLeastThereIsOnePolicySolved} title="Recovery: Solve challenges">
|
||||||
<h2>Policies</h2>
|
{!policies.length ? <p>
|
||||||
{!policies.length && <p>
|
No policies found, try with another version of the secret
|
||||||
No policies found
|
</p> : (policies.length === 1 ? <p>
|
||||||
</p>}
|
One policy found for this secret. You need to solve all the challenges in order to recover your secret.
|
||||||
{policies.map((row, i) => {
|
</p> : <p>
|
||||||
|
We have found {policies.length} polices. You need to solve all the challenges from one policy in order
|
||||||
|
to recover your secret.
|
||||||
|
</p>)}
|
||||||
|
{policiesWithInfo.map((row, i) => {
|
||||||
|
const tableBody = row.challenges.map(({ info, uuid }) => {
|
||||||
|
return (
|
||||||
|
<tr key={uuid}>
|
||||||
|
<td>{info.type}</td>
|
||||||
|
<td>
|
||||||
|
{info.instructions}
|
||||||
|
</td>
|
||||||
|
<td>{info.feedback?.state ?? "unknown"}</td>
|
||||||
|
<td>{info.cost}</td>
|
||||||
|
<td>
|
||||||
|
{info.feedback?.state !== "solved" ? (
|
||||||
|
<a onClick={() => reducer.transition("select_challenge", { uuid })}>
|
||||||
|
Solve
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
return (
|
return (
|
||||||
<div key={i}>
|
<div key={i}>
|
||||||
<h3>Policy #{i + 1}</h3>
|
<b>Policy #{i + 1}</b>
|
||||||
{row.map(column => {
|
{row.challenges.length === 0 && <p>
|
||||||
const ch = challenges[column.uuid];
|
This policy doesn't have challenges
|
||||||
if (!ch) return <div>
|
</p>}
|
||||||
There is no challenge for this policy
|
{row.challenges.length === 1 && <p>
|
||||||
</div>
|
This policy just have one challenge to be solved
|
||||||
const feedback = challengeFeedback?.[column.uuid];
|
</p>}
|
||||||
return (
|
{row.challenges.length > 1 && <p>
|
||||||
<div key={column.uuid}
|
This policy have {row.challenges.length} challenges
|
||||||
style={{
|
</p>}
|
||||||
borderLeft: "2px solid gray",
|
<table class="table">
|
||||||
paddingLeft: "0.5em",
|
<thead>
|
||||||
borderRadius: "0.5em",
|
<tr>
|
||||||
marginTop: "0.5em",
|
<td>Challenge type</td>
|
||||||
marginBottom: "0.5em",
|
<td>Description</td>
|
||||||
}}
|
<td>Status</td>
|
||||||
>
|
<td>Cost</td>
|
||||||
<h4>
|
</tr>
|
||||||
{ch.type} ({ch.instructions})
|
</thead>
|
||||||
</h4>
|
<tbody>
|
||||||
<p>Status: {feedback?.state ?? "unknown"}</p>
|
{tableBody}
|
||||||
{feedback?.state !== "solved" ? (
|
</tbody>
|
||||||
<button
|
</table>
|
||||||
onClick={() => reducer.transition("select_challenge", {
|
|
||||||
uuid: column.uuid,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
Solve
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -36,4 +36,5 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectContinent);
|
export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectContinent);
|
||||||
|
|
||||||
export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent);
|
export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent);
|
||||||
|
@ -37,7 +37,7 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NormalEnding = 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);
|
||||||
|
@ -5,7 +5,7 @@ import { useState } from "preact/hooks";
|
|||||||
import { useAnastasisContext } from "../../context/anastasis";
|
import { useAnastasisContext } from "../../context/anastasis";
|
||||||
import {
|
import {
|
||||||
AnastasisClientFrame} from "./index";
|
AnastasisClientFrame} from "./index";
|
||||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
import { TextInput } from "../../components/fields/TextInput";
|
||||||
|
|
||||||
export function SecretEditorScreen(): VNode {
|
export function SecretEditorScreen(): VNode {
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext()
|
||||||
@ -47,14 +47,14 @@ export function SecretEditorScreen(): VNode {
|
|||||||
onNext={() => secretNext()}
|
onNext={() => secretNext()}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput
|
<TextInput
|
||||||
label="Secret Name:"
|
label="Secret Name:"
|
||||||
grabFocus
|
grabFocus
|
||||||
bind={[secretName, setSecretName]}
|
bind={[secretName, setSecretName]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput
|
<TextInput
|
||||||
label="Secret Value:"
|
label="Secret Value:"
|
||||||
bind={[secretValue, setSecretValue]}
|
bind={[secretValue, setSecretValue]}
|
||||||
/>
|
/>
|
||||||
|
@ -29,15 +29,31 @@ export function SecretSelectionScreen(): VNode {
|
|||||||
version: n,
|
version: n,
|
||||||
provider_url: p,
|
provider_url: p,
|
||||||
});
|
});
|
||||||
setSelectingVersion(false);
|
|
||||||
});
|
});
|
||||||
|
setSelectingVersion(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const providerList = Object.keys(reducer.currentReducerState.authentication_providers ?? {})
|
||||||
const recoveryDocument = reducer.currentReducerState.recovery_document
|
const recoveryDocument = reducer.currentReducerState.recovery_document
|
||||||
if (!recoveryDocument) {
|
if (!recoveryDocument) {
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery: Problem">
|
<AnastasisClientFrame hideNext title="Recovery: Problem">
|
||||||
<p>No recovery document found</p>
|
<p>No recovery document found, try with another provider</p>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<td><b>Provider</b></td>
|
||||||
|
<td>
|
||||||
|
<select onChange={(e) => setOtherProvider((e.target as any).value)}>
|
||||||
|
<option key="none" disabled selected > Choose another provider </option>
|
||||||
|
{providerList.map(prov => (
|
||||||
|
<option key={prov} value={prov}>
|
||||||
|
{prov}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -45,43 +61,75 @@ export function SecretSelectionScreen(): VNode {
|
|||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title="Recovery: Select secret">
|
<AnastasisClientFrame hideNav title="Recovery: Select secret">
|
||||||
<p>Select a different version of the secret</p>
|
<p>Select a different version of the secret</p>
|
||||||
<select onChange={(e) => setOtherProvider((e.target as any).value)}>
|
<table class="table">
|
||||||
{Object.keys(reducer.currentReducerState.authentication_providers ?? {}).map(
|
<tr>
|
||||||
(x, i) => (
|
<td><b>Provider</b></td>
|
||||||
<option key={i} selected={x === recoveryDocument.provider_url} value={x}>
|
<td>
|
||||||
{x}
|
<select onChange={(e) => setOtherProvider((e.target as any).value)}>
|
||||||
</option>
|
{providerList.map(prov => (
|
||||||
)
|
<option key={prov} selected={prov === recoveryDocument.provider_url} value={prov}>
|
||||||
)}
|
{prov}
|
||||||
</select>
|
</option>
|
||||||
<div>
|
))}
|
||||||
<input
|
</select>
|
||||||
value={otherVersion}
|
</td>
|
||||||
onChange={(e) => setOtherVersion(Number((e.target as HTMLInputElement).value))}
|
</tr>
|
||||||
type="number" />
|
<tr>
|
||||||
<button onClick={() => selectVersion(otherProvider, otherVersion)}>
|
<td><b>Version</b></td>
|
||||||
Use this version
|
<td>
|
||||||
</button>
|
<input
|
||||||
</div>
|
value={otherVersion}
|
||||||
<div>
|
onChange={(e) => setOtherVersion(Number((e.target as HTMLInputElement).value))}
|
||||||
<button onClick={() => selectVersion(otherProvider, 0)}>
|
type="number" />
|
||||||
Use latest version
|
</td>
|
||||||
</button>
|
<td>
|
||||||
</div>
|
<a onClick={() => setOtherVersion(0)}>set to latest version</a>
|
||||||
<div>
|
</td>
|
||||||
<button onClick={() => setSelectingVersion(false)}>Cancel</button>
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<button class="button" onClick={() => setSelectingVersion(false)}>Cancel</button>
|
||||||
|
<button class="button is-info" onClick={() => selectVersion(otherProvider, otherVersion)}>Confirm</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame title="Recovery: Select secret">
|
<AnastasisClientFrame title="Recovery: Select secret">
|
||||||
<p>Provider: {recoveryDocument.provider_url}</p>
|
<p>Secret found, you can select another version or continue to the challenges solving</p>
|
||||||
<p>Secret version: {recoveryDocument.version}</p>
|
<table class="table">
|
||||||
<p>Secret name: {recoveryDocument.secret_name}</p>
|
<tr>
|
||||||
<button onClick={() => setSelectingVersion(true)}>
|
<td>
|
||||||
Select different secret
|
<b>Provider</b>
|
||||||
</button>
|
<span class="icon has-tooltip-right" data-tooltip="Service provider backing up your secret">
|
||||||
|
<i class="mdi mdi-information" />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>{recoveryDocument.provider_url}</td>
|
||||||
|
<td><a onClick={() => setSelectingVersion(true)}>use another provider</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<b>Secret version</b>
|
||||||
|
<span class="icon has-tooltip-right" data-tooltip="Secret version to be recovered">
|
||||||
|
<i class="mdi mdi-information" />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>{recoveryDocument.version}</td>
|
||||||
|
<td><a onClick={() => setSelectingVersion(true)}>use another version</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<b>Secret name</b>
|
||||||
|
<span class="icon has-tooltip-right" data-tooltip="Secret identifier">
|
||||||
|
<i class="mdi mdi-information" />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>{recoveryDocument.secret_name}</td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import { h, VNode } from "preact";
|
|
||||||
import { useState } from "preact/hooks";
|
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
|
||||||
import { AnastasisClientFrame } from "./index";
|
|
||||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
|
||||||
import { SolveEntryProps } from "./SolveScreen";
|
|
||||||
|
|
||||||
export function SolveEmailEntry({ challenge, feedback }: SolveEntryProps): VNode {
|
|
||||||
const [answer, setAnswer] = useState("");
|
|
||||||
const reducer = useAnastasisContext()
|
|
||||||
const next = (): void => {
|
|
||||||
if (reducer) reducer.transition("solve_challenge", {
|
|
||||||
answer,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<AnastasisClientFrame
|
|
||||||
title="Recovery: Solve challenge"
|
|
||||||
onNext={() => next()}
|
|
||||||
>
|
|
||||||
<p>Feedback: {JSON.stringify(feedback)}</p>
|
|
||||||
<p>{challenge.instructions}</p>
|
|
||||||
<LabeledInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
|
||||||
</AnastasisClientFrame>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
import { h, VNode } from "preact";
|
|
||||||
import { useState } from "preact/hooks";
|
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
|
||||||
import { AnastasisClientFrame } from "./index";
|
|
||||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
|
||||||
import { SolveEntryProps } from "./SolveScreen";
|
|
||||||
|
|
||||||
export function SolvePostEntry({ challenge, feedback }: SolveEntryProps): VNode {
|
|
||||||
const [answer, setAnswer] = useState("");
|
|
||||||
const reducer = useAnastasisContext()
|
|
||||||
const next = (): void => {
|
|
||||||
if (reducer) reducer.transition("solve_challenge", { answer })
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<AnastasisClientFrame
|
|
||||||
title="Recovery: Solve challenge"
|
|
||||||
onNext={() => next()}
|
|
||||||
>
|
|
||||||
<p>Feedback: {JSON.stringify(feedback)}</p>
|
|
||||||
<p>{challenge.instructions}</p>
|
|
||||||
<LabeledInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
|
||||||
</AnastasisClientFrame>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
import { h, VNode } from "preact";
|
|
||||||
import { useState } from "preact/hooks";
|
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
|
||||||
import { AnastasisClientFrame } from "./index";
|
|
||||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
|
||||||
import { SolveEntryProps } from "./SolveScreen";
|
|
||||||
|
|
||||||
export function SolveQuestionEntry({ challenge, feedback }: SolveEntryProps): VNode {
|
|
||||||
const [answer, setAnswer] = useState("");
|
|
||||||
const reducer = useAnastasisContext()
|
|
||||||
const next = (): void => {
|
|
||||||
if (reducer) reducer.transition("solve_challenge", { answer })
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<AnastasisClientFrame
|
|
||||||
title="Recovery: Solve challenge"
|
|
||||||
onNext={() => next()}
|
|
||||||
>
|
|
||||||
<p>Feedback: {JSON.stringify(feedback)}</p>
|
|
||||||
<p>Question: {challenge.instructions}</p>
|
|
||||||
<LabeledInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
|
||||||
</AnastasisClientFrame>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,28 +1,36 @@
|
|||||||
import { h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AnastasisClientFrame } from ".";
|
||||||
import { ChallengeFeedback, ChallengeInfo } from "../../../../anastasis-core/lib";
|
import { ChallengeFeedback, ChallengeInfo } from "../../../../anastasis-core/lib";
|
||||||
|
import { TextInput } from "../../components/fields/TextInput";
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
import { useAnastasisContext } from "../../context/anastasis";
|
||||||
import { SolveEmailEntry } from "./SolveEmailEntry";
|
|
||||||
import { SolvePostEntry } from "./SolvePostEntry";
|
|
||||||
import { SolveQuestionEntry } from "./SolveQuestionEntry";
|
|
||||||
import { SolveSmsEntry } from "./SolveSmsEntry";
|
|
||||||
import { SolveUnsupportedEntry } from "./SolveUnsupportedEntry";
|
|
||||||
|
|
||||||
export function SolveScreen(): VNode {
|
export function SolveScreen(): VNode {
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext()
|
||||||
|
const [answer, setAnswer] = useState("");
|
||||||
|
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <AnastasisClientFrame hideNext title="Recovery problem">
|
||||||
|
<div>no reducer in context</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
|
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
|
||||||
return <div>invalid state</div>
|
return <AnastasisClientFrame hideNext title="Recovery problem">
|
||||||
|
<div>invalid state</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reducer.currentReducerState.recovery_information) {
|
if (!reducer.currentReducerState.recovery_information) {
|
||||||
return <div>no recovery information found</div>
|
return <AnastasisClientFrame hideNext title="Recovery problem">
|
||||||
|
<div>no recovery information found</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState.selected_challenge_uuid) {
|
if (!reducer.currentReducerState.selected_challenge_uuid) {
|
||||||
return <div>no selected uuid</div>
|
return <AnastasisClientFrame hideNext title="Recovery problem">
|
||||||
|
<div>no selected uuid</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
}
|
}
|
||||||
|
|
||||||
const chArr = reducer.currentReducerState.recovery_information.challenges;
|
const chArr = reducer.currentReducerState.recovery_information.challenges;
|
||||||
const challengeFeedback = reducer.currentReducerState.challenge_feedback ?? {};
|
const challengeFeedback = reducer.currentReducerState.challenge_feedback ?? {};
|
||||||
const selectedUuid = reducer.currentReducerState.selected_challenge_uuid;
|
const selectedUuid = reducer.currentReducerState.selected_challenge_uuid;
|
||||||
@ -39,16 +47,99 @@ export function SolveScreen(): VNode {
|
|||||||
email: SolveEmailEntry,
|
email: SolveEmailEntry,
|
||||||
post: SolvePostEntry,
|
post: SolvePostEntry,
|
||||||
};
|
};
|
||||||
const SolveDialog = dialogMap[selectedChallenge?.type] ?? SolveUnsupportedEntry;
|
const SolveDialog = selectedChallenge === undefined ? SolveUndefinedEntry : dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry;
|
||||||
|
|
||||||
|
function onNext(): void {
|
||||||
|
reducer?.transition("solve_challenge", { answer })
|
||||||
|
}
|
||||||
|
function onCancel(): void {
|
||||||
|
reducer?.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SolveDialog
|
<AnastasisClientFrame
|
||||||
challenge={selectedChallenge}
|
hideNav
|
||||||
feedback={challengeFeedback[selectedUuid]} />
|
title="Recovery: Solve challenge"
|
||||||
|
>
|
||||||
|
<SolveDialog
|
||||||
|
id={selectedUuid}
|
||||||
|
answer={answer}
|
||||||
|
setAnswer={setAnswer}
|
||||||
|
challenge={selectedChallenge}
|
||||||
|
feedback={challengeFeedback[selectedUuid]} />
|
||||||
|
|
||||||
|
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<button class="button" onClick={onCancel}>Cancel</button>
|
||||||
|
<button class="button is-info" onClick={onNext} >Confirm</button>
|
||||||
|
</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SolveEntryProps {
|
export interface SolveEntryProps {
|
||||||
|
id: string;
|
||||||
challenge: ChallengeInfo;
|
challenge: ChallengeInfo;
|
||||||
feedback?: ChallengeFeedback;
|
feedback?: ChallengeFeedback;
|
||||||
|
answer: string;
|
||||||
|
setAnswer: (s:string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SolveSmsEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode {
|
||||||
|
return (<Fragment>
|
||||||
|
<p>An sms has been sent to "<b>{challenge.instructions}</b>". Type the code below</p>
|
||||||
|
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function SolveQuestionEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<p>Type the answer to the following question:</p>
|
||||||
|
<pre>
|
||||||
|
{challenge.instructions}
|
||||||
|
</pre>
|
||||||
|
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SolvePostEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<p>instruction for post type challenge "<b>{challenge.instructions}</b>"</p>
|
||||||
|
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SolveEmailEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<p>An email has been sent to "<b>{challenge.instructions}</b>". Type the code below</p>
|
||||||
|
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SolveUnsupportedEntry(props: SolveEntryProps): VNode {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<p>
|
||||||
|
The challenge selected is not supported for this UI. Please update this version or try using another policy.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Challenge type:</b> {props.challenge.type}
|
||||||
|
</p>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function SolveUndefinedEntry(props: SolveEntryProps): VNode {
|
||||||
|
return (
|
||||||
|
<Fragment >
|
||||||
|
<p>
|
||||||
|
There is no challenge information for id <b>"{props.id}"</b>. Try resetting the recovery session.
|
||||||
|
</p>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import { h, VNode } from "preact";
|
|
||||||
import { useState } from "preact/hooks";
|
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
|
||||||
import { AnastasisClientFrame } from "./index";
|
|
||||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
|
||||||
import { SolveEntryProps } from "./SolveScreen";
|
|
||||||
|
|
||||||
export function SolveSmsEntry({ challenge, feedback }: SolveEntryProps): VNode {
|
|
||||||
const [answer, setAnswer] = useState("");
|
|
||||||
const reducer = useAnastasisContext()
|
|
||||||
const next = (): void => {
|
|
||||||
if (reducer) reducer.transition("solve_challenge", {
|
|
||||||
answer,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<AnastasisClientFrame
|
|
||||||
title="Recovery: Solve challenge"
|
|
||||||
onNext={() => next()}
|
|
||||||
>
|
|
||||||
<p>Feedback: {JSON.stringify(feedback)}</p>
|
|
||||||
<p>{challenge.instructions}</p>
|
|
||||||
<LabeledInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
|
||||||
</AnastasisClientFrame>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import { h, VNode } from "preact";
|
|
||||||
import { AnastasisClientFrame } from "./index";
|
|
||||||
import { SolveEntryProps } from "./SolveScreen";
|
|
||||||
|
|
||||||
export function SolveUnsupportedEntry(props: SolveEntryProps): VNode {
|
|
||||||
return (
|
|
||||||
<AnastasisClientFrame hideNext title="Recovery: Solve challenge">
|
|
||||||
<p>{JSON.stringify(props.challenge)}</p>
|
|
||||||
<p>Challenge not supported.</p>
|
|
||||||
</AnastasisClientFrame>
|
|
||||||
);
|
|
||||||
}
|
|
@ -14,17 +14,17 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AmountJson, AmountLike, Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util";
|
import { AmountLike, Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Fragment, JSX, VNode, h } from "preact";
|
import { JSX, VNode } from "preact";
|
||||||
import { route } from 'preact-router';
|
import { route } from 'preact-router';
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import * as wxApi from "../wxApi";
|
import emptyImg from "../../static/img/empty.png";
|
||||||
import { Pages } from "../NavigationBar";
|
|
||||||
import emptyImg from "../../static/img/empty.png"
|
|
||||||
import { Button, ButtonBox, ButtonBoxDestructive, ButtonDestructive, ButtonPrimary, ExtraLargeText, FontIcon, LargeText, ListOfProducts, PopupBox, Row, RowBorderGray, SmallLightText, WalletBox, WarningBox } from "../components/styled";
|
|
||||||
import { ErrorMessage } from "../components/ErrorMessage";
|
import { ErrorMessage } from "../components/ErrorMessage";
|
||||||
import { Part } from "../components/Part";
|
import { Part } from "../components/Part";
|
||||||
|
import { ButtonBox, ButtonBoxDestructive, ButtonPrimary, FontIcon, ListOfProducts, RowBorderGray, SmallLightText, WalletBox, WarningBox } from "../components/styled";
|
||||||
|
import { Pages } from "../NavigationBar";
|
||||||
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
export function TransactionPage({ tid }: { tid: string; }): JSX.Element {
|
export function TransactionPage({ tid }: { tid: string; }): JSX.Element {
|
||||||
const [transaction, setTransaction] = useState<
|
const [transaction, setTransaction] = useState<
|
||||||
@ -42,7 +42,7 @@ export function TransactionPage({ tid }: { tid: string; }): JSX.Element {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, [tid]);
|
||||||
|
|
||||||
if (!transaction) {
|
if (!transaction) {
|
||||||
return <div><i18n.Translate>Loading ...</i18n.Translate></div>;
|
return <div><i18n.Translate>Loading ...</i18n.Translate></div>;
|
||||||
|
Loading…
Reference in New Issue
Block a user