working version with improved ui
This commit is contained in:
parent
21b60c8f6f
commit
32318a80f4
@ -8,6 +8,7 @@ export interface DateInputProps {
|
||||
grabFocus?: boolean;
|
||||
tooltip?: string;
|
||||
error?: string;
|
||||
years?: Array<number>;
|
||||
bind: [string, (x: string) => void];
|
||||
}
|
||||
|
||||
@ -19,7 +20,7 @@ export function DateInput(props: DateInputProps): VNode {
|
||||
}
|
||||
}, [props.grabFocus]);
|
||||
const [opened, setOpened2] = useState(false)
|
||||
function setOpened(v: boolean) {
|
||||
function setOpened(v: boolean): void {
|
||||
console.log('dale', v)
|
||||
setOpened2(v)
|
||||
}
|
||||
@ -50,6 +51,7 @@ export function DateInput(props: DateInputProps): VNode {
|
||||
{showError && <p class="help is-danger">{props.error}</p>}
|
||||
<DatePicker
|
||||
opened={opened}
|
||||
years={props.years}
|
||||
closeFunction={() => setOpened(false)}
|
||||
dateReceiver={(d) => {
|
||||
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 { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
export interface LabeledInputProps {
|
||||
export interface TextInputProps {
|
||||
label: string;
|
||||
grabFocus?: boolean;
|
||||
error?: string;
|
||||
@ -9,7 +9,7 @@ export interface LabeledInputProps {
|
||||
bind: [string, (x: string) => void];
|
||||
}
|
||||
|
||||
export function LabeledInput(props: LabeledInputProps): VNode {
|
||||
export function TextInput(props: TextInputProps): VNode {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (props.grabFocus) {
|
@ -64,8 +64,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
||||
</li>
|
||||
}
|
||||
{reducer.currentReducerState && reducer.currentReducerState.backup_state ? <Fragment>
|
||||
<li class={
|
||||
reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting ||
|
||||
<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>
|
||||
@ -79,73 +78,65 @@ export function Sidebar({ mobile }: Props): VNode {
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.AuthenticationsEditing ? 'is-active' : ''}>
|
||||
<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>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesReviewing ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
|
||||
<span class="menu-item-label"><Translate>PoliciesReviewing</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Policies reviewing</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.SecretEditing ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
|
||||
<span class="menu-item-label"><Translate>SecretEditing</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Secret input</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
|
||||
<span class="menu-item-label"><Translate>PoliciesPaying</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Payment (optional)</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.BackupFinished ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
|
||||
<span class="menu-item-label"><Translate>BackupFinished</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Backup completed</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
|
||||
<span class="menu-item-label"><Translate>TruthsPaying</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Truth Paying</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
</Fragment> : (reducer.currentReducerState && reducer.currentReducerState?.recovery_state && <Fragment>
|
||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting ? 'is-active' : ''}>
|
||||
<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>ContinentSelecting</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>
|
||||
<span class="menu-item-label"><Translate>Location & Currency</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.UserAttributesCollecting ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
<span class="menu-item-label"><Translate>UserAttributesCollecting</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Personal information</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.SecretSelecting ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
<span class="menu-item-label"><Translate>SecretSelecting</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Secret selection</Translate></span>
|
||||
</div>
|
||||
</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">
|
||||
<span class="menu-item-label"><Translate>ChallengeSelecting</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>
|
||||
<span class="menu-item-label"><Translate>Solve Challenges</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.RecoveryFinished ? 'is-active' : ''}>
|
||||
<div class="ml-4">
|
||||
<span class="menu-item-label"><Translate>RecoveryFinished</Translate></span>
|
||||
<span class="menu-item-label"><Translate>Secret recovered</Translate></span>
|
||||
</div>
|
||||
</li>
|
||||
</Fragment>)}
|
||||
|
@ -24,6 +24,7 @@ import { h, Component } from "preact";
|
||||
interface Props {
|
||||
closeFunction?: () => void;
|
||||
dateReceiver?: (d: Date) => void;
|
||||
years?: Array<number>;
|
||||
opened?: boolean;
|
||||
}
|
||||
interface State {
|
||||
@ -207,9 +208,9 @@ export class DatePicker extends Component<Props, State> {
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.selectYearMode) {
|
||||
document.getElementsByClassName('selected')[0].scrollIntoView(); // works in every browser incl. IE, replace with scrollIntoViewIfNeeded when browsers support it
|
||||
}
|
||||
// if (this.state.selectYearMode) {
|
||||
// document.getElementsByClassName('selected')[0].scrollIntoView(); // works in every browser incl. IE, replace with scrollIntoViewIfNeeded when browsers support it
|
||||
// }
|
||||
}
|
||||
|
||||
constructor() {
|
||||
@ -296,8 +297,7 @@ export class DatePicker extends Component<Props, State> {
|
||||
</div>}
|
||||
|
||||
{selectYearMode && <div class="datePicker--selectYear">
|
||||
|
||||
{yearArr.map(year => (
|
||||
{(this.props.years || yearArr).map(year => (
|
||||
<span key={year} class={(year === displayedYear) ? 'selected' : ''} onClick={this.changeDisplayedYear}>
|
||||
{year}
|
||||
</span>
|
||||
|
@ -40,21 +40,21 @@ export default {
|
||||
export const Backup = createExample(TestedComponent, {
|
||||
...reducerStatesExample.backupAttributeEditing,
|
||||
required_attributes: [{
|
||||
name: 'first',
|
||||
name: 'first name',
|
||||
label: 'first',
|
||||
type: 'type',
|
||||
type: 'string',
|
||||
uuid: 'asdasdsa1',
|
||||
widget: 'wid',
|
||||
}, {
|
||||
name: 'pepe',
|
||||
name: 'last name',
|
||||
label: 'second',
|
||||
type: 'type',
|
||||
type: 'string',
|
||||
uuid: 'asdasdsa2',
|
||||
widget: 'wid',
|
||||
}, {
|
||||
name: 'pepe2',
|
||||
name: 'date',
|
||||
label: 'third',
|
||||
type: 'type',
|
||||
type: 'date',
|
||||
uuid: 'asdasdsa3',
|
||||
widget: 'calendar',
|
||||
}]
|
||||
@ -65,19 +65,19 @@ export const Recovery = createExample(TestedComponent, {
|
||||
required_attributes: [{
|
||||
name: 'first',
|
||||
label: 'first',
|
||||
type: 'type',
|
||||
type: 'string',
|
||||
uuid: 'asdasdsa1',
|
||||
widget: 'wid',
|
||||
}, {
|
||||
name: 'pepe',
|
||||
label: 'second',
|
||||
type: 'type',
|
||||
type: 'string',
|
||||
uuid: 'asdasdsa2',
|
||||
widget: 'wid',
|
||||
}, {
|
||||
name: 'pepe2',
|
||||
label: 'third',
|
||||
type: 'type',
|
||||
type: 'date',
|
||||
uuid: 'asdasdsa3',
|
||||
widget: 'calendar',
|
||||
}]
|
||||
@ -110,12 +110,20 @@ const allWidgets = [
|
||||
"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, {
|
||||
...reducerStatesExample.backupAttributeEditing,
|
||||
required_attributes: allWidgets.map(w => ({
|
||||
name: w,
|
||||
label: `widget: ${w}`,
|
||||
type: 'type',
|
||||
type: typeForWidget(w),
|
||||
uuid: `uuid-${w}`,
|
||||
widget: w
|
||||
}))
|
||||
|
@ -4,8 +4,9 @@ import { h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAnastasisContext } from "../../context/anastasis";
|
||||
import { AnastasisClientFrame, withProcessLabel } from "./index";
|
||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
||||
import { TextInput } from "../../components/fields/TextInput";
|
||||
import { DateInput } from "../../components/fields/DateInput";
|
||||
import { NumberInput } from "../../components/fields/NumberInput";
|
||||
|
||||
export function AttributeEntryScreen(): VNode {
|
||||
const reducer = useAnastasisContext()
|
||||
@ -65,6 +66,7 @@ export function AttributeEntryScreen(): VNode {
|
||||
|
||||
</div>
|
||||
<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>
|
||||
<p>The information you have entered here:
|
||||
</p>
|
||||
@ -92,20 +94,33 @@ interface AttributeEntryFieldProps {
|
||||
spec: UserAttributeSpec;
|
||||
isValid: () => string | undefined;
|
||||
}
|
||||
|
||||
const possibleBirthdayYear: Array<number> = []
|
||||
for (let i = 0; i < 100; i++ ) {
|
||||
possibleBirthdayYear.push(2020 - i)
|
||||
}
|
||||
function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
|
||||
const errorMessage = props.isValid()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{props.spec.type === 'date' ?
|
||||
{props.spec.type === 'date' &&
|
||||
<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}
|
||||
label={props.spec.label}
|
||||
error={errorMessage}
|
||||
bind={[props.value, props.setValue]}
|
||||
/> :
|
||||
<LabeledInput
|
||||
/>
|
||||
}
|
||||
{props.spec.type === 'string' &&
|
||||
<TextInput
|
||||
grabFocus={props.isFirst}
|
||||
label={props.spec.label}
|
||||
error={errorMessage}
|
||||
|
@ -7,7 +7,7 @@ import { h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
|
||||
import { AnastasisClientFrame } from "./index";
|
||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
||||
import { TextInput } from "../../components/fields/TextInput";
|
||||
|
||||
export function AuthMethodEmailSetup(props: AuthMethodSetupProps): VNode {
|
||||
const [email, setEmail] = useState("");
|
||||
@ -19,7 +19,7 @@ export function AuthMethodEmailSetup(props: AuthMethodSetupProps): VNode {
|
||||
email.
|
||||
</p>
|
||||
<div>
|
||||
<LabeledInput
|
||||
<TextInput
|
||||
label="Email address"
|
||||
grabFocus
|
||||
bind={[email, setEmail]} />
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
import { h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
|
||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
||||
import { TextInput } from "../../components/fields/TextInput";
|
||||
|
||||
export function AuthMethodPostSetup(props: AuthMethodSetupProps): VNode {
|
||||
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.
|
||||
</p>
|
||||
<div>
|
||||
<LabeledInput
|
||||
<TextInput
|
||||
grabFocus
|
||||
label="Full Name"
|
||||
bind={[fullName, setFullName]} />
|
||||
</div>
|
||||
<div>
|
||||
<LabeledInput label="Street" bind={[street, setStreet]} />
|
||||
<TextInput label="Street" bind={[street, setStreet]} />
|
||||
</div>
|
||||
<div>
|
||||
<LabeledInput label="City" bind={[city, setCity]} />
|
||||
<TextInput label="City" bind={[city, setCity]} />
|
||||
</div>
|
||||
<div>
|
||||
<LabeledInput label="Postal Code" bind={[postcode, setPostcode]} />
|
||||
<TextInput label="Postal Code" bind={[postcode, setPostcode]} />
|
||||
</div>
|
||||
<div>
|
||||
<LabeledInput label="Country" bind={[country, setCountry]} />
|
||||
<TextInput label="Country" bind={[country, setCountry]} />
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={() => props.cancel()}>Cancel</button>
|
||||
|
@ -7,7 +7,7 @@ import { h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
|
||||
import { AnastasisClientFrame } from "./index";
|
||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
||||
import { TextInput } from "../../components/fields/TextInput";
|
||||
|
||||
export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode {
|
||||
const [questionText, setQuestionText] = useState("");
|
||||
@ -29,13 +29,13 @@ export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode {
|
||||
here.
|
||||
</p>
|
||||
<div>
|
||||
<LabeledInput
|
||||
<TextInput
|
||||
label="Security question"
|
||||
grabFocus
|
||||
bind={[questionText, setQuestionText]} />
|
||||
</div>
|
||||
<div>
|
||||
<LabeledInput label="Answer" bind={[answerText, setAnswerText]} />
|
||||
<TextInput label="Answer" bind={[answerText, setAnswerText]} />
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={() => props.cancel()}>Cancel</button>
|
||||
|
@ -37,7 +37,7 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
export const OneChallenge = createExample(TestedComponent, {
|
||||
export const OneUnsolvedPolicy = createExample(TestedComponent, {
|
||||
...reducerStatesExample.challengeSelecting,
|
||||
recovery_information: {
|
||||
policies: [[{ uuid: '1' }]],
|
||||
@ -50,7 +50,7 @@ export const OneChallenge = createExample(TestedComponent, {
|
||||
},
|
||||
} as ReducerState);
|
||||
|
||||
export const MoreChallenges = createExample(TestedComponent, {
|
||||
export const SomePoliciesOneSolved = createExample(TestedComponent, {
|
||||
...reducerStatesExample.challengeSelecting,
|
||||
recovery_information: {
|
||||
policies: [[{ uuid: '1' }, { uuid: '2' }], [{ uuid: 'uuid-3' }]],
|
||||
@ -75,13 +75,13 @@ export const MoreChallenges = createExample(TestedComponent, {
|
||||
'uuid-3': {
|
||||
state: 'solved'
|
||||
}
|
||||
}
|
||||
},
|
||||
} as ReducerState);
|
||||
|
||||
export const OneBadConfiguredPolicy = createExample(TestedComponent, {
|
||||
...reducerStatesExample.challengeSelecting,
|
||||
recovery_information: {
|
||||
policies: [[{ uuid: '2' }]],
|
||||
policies: [[{ uuid: '1' }, { uuid: '2' }]],
|
||||
challenges: [{
|
||||
cost: 'USD:1',
|
||||
instructions: 'just go for it',
|
||||
@ -91,4 +91,130 @@ export const OneBadConfiguredPolicy = createExample(TestedComponent, {
|
||||
},
|
||||
} 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);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ChallengeFeedback } from "anastasis-core";
|
||||
import { h, VNode } from "preact";
|
||||
import { useAnastasisContext } from "../../context/anastasis";
|
||||
import { AnastasisClientFrame } from "./index";
|
||||
@ -13,65 +14,94 @@ export function ChallengeOverviewScreen(): VNode {
|
||||
}
|
||||
|
||||
const policies = reducer.currentReducerState.recovery_information?.policies ?? [];
|
||||
const chArr = reducer.currentReducerState.recovery_information?.challenges ?? [];
|
||||
const challengeFeedback = reducer.currentReducerState?.challenge_feedback;
|
||||
const knownChallengesArray = reducer.currentReducerState.recovery_information?.challenges ?? [];
|
||||
const challengeFeedback = reducer.currentReducerState?.challenge_feedback ?? {};
|
||||
|
||||
const challenges: {
|
||||
const knownChallengesMap: {
|
||||
[uuid: string]: {
|
||||
type: string;
|
||||
instructions: string;
|
||||
cost: string;
|
||||
feedback: ChallengeFeedback | undefined;
|
||||
};
|
||||
} = {};
|
||||
for (const ch of chArr) {
|
||||
challenges[ch.uuid] = {
|
||||
for (const ch of knownChallengesArray) {
|
||||
knownChallengesMap[ch.uuid] = {
|
||||
type: ch.type,
|
||||
cost: ch.cost,
|
||||
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 (
|
||||
<AnastasisClientFrame title="Recovery: Solve challenges">
|
||||
<h2>Policies</h2>
|
||||
{!policies.length && <p>
|
||||
No policies found
|
||||
</p>}
|
||||
{policies.map((row, i) => {
|
||||
<AnastasisClientFrame hideNext={!atLeastThereIsOnePolicySolved} title="Recovery: Solve challenges">
|
||||
{!policies.length ? <p>
|
||||
No policies found, try with another version of the secret
|
||||
</p> : (policies.length === 1 ? <p>
|
||||
One policy found for this secret. You need to solve all the challenges in order to recover your secret.
|
||||
</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 (
|
||||
<div key={i}>
|
||||
<h3>Policy #{i + 1}</h3>
|
||||
{row.map(column => {
|
||||
const ch = challenges[column.uuid];
|
||||
if (!ch) return <div>
|
||||
There is no challenge for this policy
|
||||
</div>
|
||||
const feedback = challengeFeedback?.[column.uuid];
|
||||
return (
|
||||
<div key={column.uuid}
|
||||
style={{
|
||||
borderLeft: "2px solid gray",
|
||||
paddingLeft: "0.5em",
|
||||
borderRadius: "0.5em",
|
||||
marginTop: "0.5em",
|
||||
marginBottom: "0.5em",
|
||||
}}
|
||||
>
|
||||
<h4>
|
||||
{ch.type} ({ch.instructions})
|
||||
</h4>
|
||||
<p>Status: {feedback?.state ?? "unknown"}</p>
|
||||
{feedback?.state !== "solved" ? (
|
||||
<button
|
||||
onClick={() => reducer.transition("select_challenge", {
|
||||
uuid: column.uuid,
|
||||
})}
|
||||
>
|
||||
Solve
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<b>Policy #{i + 1}</b>
|
||||
{row.challenges.length === 0 && <p>
|
||||
This policy doesn't have challenges
|
||||
</p>}
|
||||
{row.challenges.length === 1 && <p>
|
||||
This policy just have one challenge to be solved
|
||||
</p>}
|
||||
{row.challenges.length > 1 && <p>
|
||||
This policy have {row.challenges.length} challenges
|
||||
</p>}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Challenge type</td>
|
||||
<td>Description</td>
|
||||
<td>Status</td>
|
||||
<td>Cost</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tableBody}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -36,4 +36,5 @@ export default {
|
||||
};
|
||||
|
||||
export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectContinent);
|
||||
|
||||
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,
|
||||
core_secret: { mime: 'text/plain', value: 'hello' }
|
||||
} as ReducerState);
|
||||
|
@ -5,7 +5,7 @@ import { useState } from "preact/hooks";
|
||||
import { useAnastasisContext } from "../../context/anastasis";
|
||||
import {
|
||||
AnastasisClientFrame} from "./index";
|
||||
import { LabeledInput } from "../../components/fields/LabeledInput";
|
||||
import { TextInput } from "../../components/fields/TextInput";
|
||||
|
||||
export function SecretEditorScreen(): VNode {
|
||||
const reducer = useAnastasisContext()
|
||||
@ -47,14 +47,14 @@ export function SecretEditorScreen(): VNode {
|
||||
onNext={() => secretNext()}
|
||||
>
|
||||
<div>
|
||||
<LabeledInput
|
||||
<TextInput
|
||||
label="Secret Name:"
|
||||
grabFocus
|
||||
bind={[secretName, setSecretName]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<LabeledInput
|
||||
<TextInput
|
||||
label="Secret Value:"
|
||||
bind={[secretValue, setSecretValue]}
|
||||
/>
|
||||
|
@ -29,15 +29,31 @@ export function SecretSelectionScreen(): VNode {
|
||||
version: n,
|
||||
provider_url: p,
|
||||
});
|
||||
setSelectingVersion(false);
|
||||
});
|
||||
setSelectingVersion(false);
|
||||
}
|
||||
|
||||
const providerList = Object.keys(reducer.currentReducerState.authentication_providers ?? {})
|
||||
const recoveryDocument = reducer.currentReducerState.recovery_document
|
||||
if (!recoveryDocument) {
|
||||
return (
|
||||
<AnastasisClientFrame hideNav title="Recovery: Problem">
|
||||
<p>No recovery document found</p>
|
||||
<AnastasisClientFrame hideNext title="Recovery: Problem">
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@ -45,43 +61,75 @@ export function SecretSelectionScreen(): VNode {
|
||||
return (
|
||||
<AnastasisClientFrame hideNav title="Recovery: Select secret">
|
||||
<p>Select a different version of the secret</p>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td><b>Provider</b></td>
|
||||
<td>
|
||||
<select onChange={(e) => setOtherProvider((e.target as any).value)}>
|
||||
{Object.keys(reducer.currentReducerState.authentication_providers ?? {}).map(
|
||||
(x, i) => (
|
||||
<option key={i} selected={x === recoveryDocument.provider_url} value={x}>
|
||||
{x}
|
||||
{providerList.map(prov => (
|
||||
<option key={prov} selected={prov === recoveryDocument.provider_url} value={prov}>
|
||||
{prov}
|
||||
</option>
|
||||
)
|
||||
)}
|
||||
))}
|
||||
</select>
|
||||
<div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Version</b></td>
|
||||
<td>
|
||||
<input
|
||||
value={otherVersion}
|
||||
onChange={(e) => setOtherVersion(Number((e.target as HTMLInputElement).value))}
|
||||
type="number" />
|
||||
<button onClick={() => selectVersion(otherProvider, otherVersion)}>
|
||||
Use this version
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={() => selectVersion(otherProvider, 0)}>
|
||||
Use latest version
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={() => setSelectingVersion(false)}>Cancel</button>
|
||||
</td>
|
||||
<td>
|
||||
<a onClick={() => setOtherVersion(0)}>set to latest version</a>
|
||||
</td>
|
||||
</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>
|
||||
|
||||
</AnastasisClientFrame>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<AnastasisClientFrame title="Recovery: Select secret">
|
||||
<p>Provider: {recoveryDocument.provider_url}</p>
|
||||
<p>Secret version: {recoveryDocument.version}</p>
|
||||
<p>Secret name: {recoveryDocument.secret_name}</p>
|
||||
<button onClick={() => setSelectingVersion(true)}>
|
||||
Select different secret
|
||||
</button>
|
||||
<p>Secret found, you can select another version or continue to the challenges solving</p>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td>
|
||||
<b>Provider</b>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
@ -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 { TextInput } from "../../components/fields/TextInput";
|
||||
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 {
|
||||
const reducer = useAnastasisContext()
|
||||
const [answer, setAnswer] = useState("");
|
||||
|
||||
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) {
|
||||
return <div>invalid state</div>
|
||||
return <AnastasisClientFrame hideNext title="Recovery problem">
|
||||
<div>invalid state</div>
|
||||
</AnastasisClientFrame>
|
||||
}
|
||||
|
||||
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) {
|
||||
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 challengeFeedback = reducer.currentReducerState.challenge_feedback ?? {};
|
||||
const selectedUuid = reducer.currentReducerState.selected_challenge_uuid;
|
||||
@ -39,16 +47,99 @@ export function SolveScreen(): VNode {
|
||||
email: SolveEmailEntry,
|
||||
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 (
|
||||
<AnastasisClientFrame
|
||||
hideNav
|
||||
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 {
|
||||
id: string;
|
||||
challenge: ChallengeInfo;
|
||||
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/>
|
||||
*/
|
||||
|
||||
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 { Fragment, JSX, VNode, h } from "preact";
|
||||
import { JSX, VNode } from "preact";
|
||||
import { route } from 'preact-router';
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import * as wxApi from "../wxApi";
|
||||
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 emptyImg from "../../static/img/empty.png";
|
||||
import { ErrorMessage } from "../components/ErrorMessage";
|
||||
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 {
|
||||
const [transaction, setTransaction] = useState<
|
||||
@ -42,7 +42,7 @@ export function TransactionPage({ tid }: { tid: string; }): JSX.Element {
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
}, [tid]);
|
||||
|
||||
if (!transaction) {
|
||||
return <div><i18n.Translate>Loading ...</i18n.Translate></div>;
|
||||
|
Loading…
Reference in New Issue
Block a user