some storybook exmaples

This commit is contained in:
Sebastian 2021-10-22 01:31:46 -03:00
parent 3740010117
commit 2ec2161a7e
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
43 changed files with 2737 additions and 295 deletions

View File

@ -51,7 +51,7 @@ export interface ReducerStateBackup {
identity_attributes?: { [n: string]: string }; identity_attributes?: { [n: string]: string };
authentication_providers?: { [url: string]: AuthenticationProviderStatus }; authentication_providers?: { [url: string]: AuthenticationProviderStatus };
authentication_methods?: AuthMethod[]; authentication_methods?: AuthMethod[];
required_attributes?: any; required_attributes?: UserAttributeSpec[];
selected_continent?: string; selected_continent?: string;
selected_country?: string; selected_country?: string;
secret_name?: string; secret_name?: string;
@ -133,7 +133,7 @@ export interface ReducerStateRecovery {
selected_country?: string; selected_country?: string;
currencies?: string[]; currencies?: string[];
required_attributes?: any; required_attributes?: UserAttributeSpec[];
/** /**
* Recovery information, used by the UI. * Recovery information, used by the UI.

View File

@ -2,3 +2,4 @@ node_modules
/build /build
/*.log /*.log
/size-plugin.json /size-plugin.json
/storybook-static/

View File

@ -0,0 +1,25 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
{
"presets": [
"preact-cli/babel"
]
}

View File

@ -0,0 +1,57 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/preset-scss",
"@storybook/addon-a11y",
"@storybook/addon-essentials" //docs, control, actions, viewpot, toolbar, background
],
// sb does not yet support new jsx transform by default
// https://github.com/storybookjs/storybook/issues/12881
// https://github.com/storybookjs/storybook/issues/12952
babel: async (options) => ({
...options,
presets: [
...options.presets,
[
'@babel/preset-react', {
runtime: 'automatic',
},
'preset-react-jsx-transform'
],
],
}),
webpackFinal: (config) => {
// should be removed after storybook 6.3
// https://github.com/storybookjs/storybook/issues/12853#issuecomment-821576113
config.resolve.alias = {
react: "preact/compat",
"react-dom": "preact/compat",
};
return config;
},
}

View File

@ -0,0 +1,49 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import "../src/scss/main.scss"
import { TranslationProvider } from '../src/context/translation'
import { h } from 'preact';
export const parameters = {
controls: { expanded: true },
}
export const globalTypes = {
locale: {
name: 'Locale',
description: 'Internationalization locale',
defaultValue: 'en',
toolbar: {
icon: 'globe',
items: [
{ value: 'en', right: '🇺🇸', title: 'English' },
{ value: 'es', right: '🇪🇸', title: 'Spanish' },
],
},
},
};
export const decorators = [
(Story, { globals }) => {
document.body.parentElement.classList = "has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"
return <Story />
},
(Story, { globals }) => <TranslationProvider initial='en' forceLang={globals.locale}>
<Story />
</TranslationProvider>,
];

View File

@ -8,7 +8,9 @@
"serve": "sirv build --port 8080 --cors --single", "serve": "sirv build --port 8080 --cors --single",
"dev": "preact watch", "dev": "preact watch",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'", "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
"test": "jest ./tests" "test": "jest ./tests",
"build-storybook": "build-storybook",
"storybook": "start-storybook -p 6006"
}, },
"eslintConfig": { "eslintConfig": {
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
@ -30,6 +32,12 @@
}, },
"devDependencies": { "devDependencies": {
"@creativebulma/bulma-tooltip": "^1.2.0", "@creativebulma/bulma-tooltip": "^1.2.0",
"@storybook/addon-a11y": "^6.2.9",
"@storybook/addon-actions": "^6.2.9",
"@storybook/addon-essentials": "^6.2.9",
"@storybook/addon-links": "^6.2.9",
"@storybook/preact": "^6.2.9",
"@storybook/preset-scss": "^1.0.3",
"@types/enzyme": "^3.10.5", "@types/enzyme": "^3.10.5",
"@types/jest": "^26.0.8", "@types/jest": "^26.0.8",
"@typescript-eslint/eslint-plugin": "^2.25.0", "@typescript-eslint/eslint-plugin": "^2.25.0",

View File

@ -20,7 +20,9 @@
*/ */
import { h, VNode } from 'preact'; import { Fragment, h, VNode } from 'preact';
import { BackupStates, RecoveryStates } from '../../../../anastasis-core/lib';
import { useAnastasisContext } from '../../context/anastasis';
import { Translate } from '../../i18n'; import { Translate } from '../../i18n';
import { LangSelector } from './LangSelector'; import { LangSelector } from './LangSelector';
@ -32,6 +34,7 @@ export function Sidebar({ mobile }: Props): VNode {
// const config = useConfigContext(); // const config = useConfigContext();
const config = { version: 'none' } const config = { version: 'none' }
const process = { env: { __VERSION__: '0.0.0' } } const process = { env: { __VERSION__: '0.0.0' } }
const reducer = useAnastasisContext()!
return ( return (
<aside class="aside is-placed-left is-expanded"> <aside class="aside is-placed-left is-expanded">
@ -47,52 +50,117 @@ export function Sidebar({ mobile }: Props): VNode {
</div> </div>
</div> </div>
<div class="menu is-menu-main"> <div class="menu is-menu-main">
{!reducer.currentReducerState &&
<p class="menu-label"> <p class="menu-label">
<Translate>Back up a secret</Translate> <Translate>Backup or Recorver</Translate>
</p> </p>
}
<ul class="menu-list"> <ul class="menu-list">
{!reducer.currentReducerState &&
<li> <li>
<div class="has-icon"> <div class="ml-4">
<span class="icon"><i class="mdi mdi-square-edit-outline" /></span> <span class="menu-item-label"><Translate>Start one options</Translate></span>
<span class="menu-item-label"><Translate>Location &amp; Currency</Translate></span>
</div> </div>
</li> </li>
<li class="is-active"> }
<div class="has-icon"> {reducer.currentReducerState && reducer.currentReducerState.backup_state ? <Fragment>
<span class="icon"><i class="mdi mdi-cash-register" /></span> <li class={reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting ? 'is-active' : ''}>
<span class="menu-item-label"><Translate>Personal information</Translate></span> <div class="ml-4">
<span class="menu-item-label"><Translate>Continent selection</Translate></span>
</div> </div>
</li> </li>
<li class={reducer.currentReducerState.backup_state === BackupStates.CountrySelecting ? 'is-active' : ''}>
<div class="ml-4">
<span class="menu-item-label"><Translate>Country selection</Translate></span>
</div>
</li>
<li class={reducer.currentReducerState.backup_state === BackupStates.UserAttributesCollecting ? 'is-active' : ''}>
<div class="ml-4">
<span class="menu-item-label"><Translate>User attributes</Translate></span>
</div>
</li>
<li class={reducer.currentReducerState.backup_state === BackupStates.AuthenticationsEditing ? 'is-active' : ''}>
<div class="ml-4">
<span class="menu-item-label"><Translate>Auth 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>
</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>
</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>
</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>
</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>
</div>
</li>
</Fragment> : (reducer.currentReducerState && reducer.currentReducerState?.recovery_state && <Fragment>
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting ? 'is-active' : ''}>
<div class="ml-4">
<span class="menu-item-label"><Translate>TruthsPaying</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>
</li>
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.UserAttributesCollecting ? 'is-active' : ''}>
<div class="ml-4">
<span class="menu-item-label"><Translate>UserAttributesCollecting</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>
</div>
</li>
<li class={reducer.currentReducerState.recovery_state === RecoveryStates.ChallengeSelecting ? '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>
</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>
</div>
</li>
</Fragment>)}
{reducer.currentReducerState &&
<li> <li>
<div class="has-icon"> <div class="buttons ml-4">
<span class="icon"><i class="mdi mdi-shopping" /></span> <button class="button is-danger is-right" onClick={() => reducer.reset()}>Reset session</button>
<span class="menu-item-label"><Translate>Authorization methods</Translate></span>
</div>
</li>
<li>
<div class="has-icon">
<span class="icon"><i class="mdi mdi-bank" /></span>
<span class="menu-item-label"><Translate>Recovery policies</Translate></span>
</div>
</li>
<li>
<div class="has-icon">
<span class="icon"><i class="mdi mdi-bank" /></span>
<span class="menu-item-label"><Translate>Enter secrets</Translate></span>
</div>
</li>
<li>
<div class="has-icon">
<span class="icon"><i class="mdi mdi-bank" /></span>
<span class="menu-item-label"><Translate>Payment (optional)</Translate></span>
</div>
</li>
<li>
<div class="has-icon">
<span class="icon"><i class="mdi mdi-cash" /></span>
<span class="menu-item-label">Backup completed</span>
</div> </div>
</li> </li>
}
</ul> </ul>
</div> </div>
</aside> </aside>

View File

@ -0,0 +1,41 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createContext, h, VNode } from 'preact';
import { useContext } from 'preact/hooks';
import { AnastasisReducerApi } from '../hooks/use-anastasis-reducer';
type Type = AnastasisReducerApi | undefined;
const initial = undefined
const Context = createContext<Type>(initial)
interface Props {
value: AnastasisReducerApi;
children: any;
}
export const AnastasisProvider = ({ value, children }: Props): VNode => {
return h(Context.Provider, { value, children });
}
export const useAnastasisContext = (): Type => useContext(Context);

View File

@ -0,0 +1,63 @@
/* eslint-disable @typescript-eslint/camelcase */
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { AttributeEntryScreen as TestedComponent } from './AttributeEntryScreen';
export default {
title: 'Pages/AttributeEntryScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const WithSomeAttributes = createExample(TestedComponent, {
...reducerStatesExample.attributeEditing,
required_attributes: [{
name: 'first',
label: 'first',
type: 'type',
uuid: 'asdasdsa1',
widget: 'wid',
}, {
name: 'pepe',
label: 'second',
type: 'type',
uuid: 'asdasdsa2',
widget: 'wid',
}, {
name: 'pepe2',
label: 'third',
type: 'type',
uuid: 'asdasdsa3',
widget: 'calendar',
}]
} as ReducerState);
export const Empty = createExample(TestedComponent, {
...reducerStatesExample.attributeEditing,
required_attributes: undefined
} as ReducerState);

View File

@ -1,15 +1,24 @@
/* eslint-disable @typescript-eslint/camelcase */ /* eslint-disable @typescript-eslint/camelcase */
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { ReducerStateRecovery, ReducerStateBackup } from "../../../../anastasis-core/lib"; import { ReducerStateRecovery, ReducerStateBackup, UserAttributeSpec } from "anastasis-core/lib";
import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer"; import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer";
import { AnastasisClientFrame, withProcessLabel, LabeledInput } from "./index"; import { AnastasisClientFrame, withProcessLabel, LabeledInput } from "./index";
export function AttributeEntryScreen(props: AttributeEntryProps): VNode { export function AttributeEntryScreen(): VNode {
const { reducer, reducerState: backupState } = props; const reducer = useAnastasisContext()
const [attrs, setAttrs] = useState<Record<string, string>>( const state = reducer?.currentReducerState
props.reducerState.identity_attributes ?? {} const currentIdentityAttributes = state && "identity_attributes" in state ? (state.identity_attributes || {}) : {}
); const [attrs, setAttrs] = useState<Record<string, string>>(currentIdentityAttributes);
if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || !("required_attributes" in reducer.currentReducerState)) {
return <div>invalid state</div>
}
return ( return (
<AnastasisClientFrame <AnastasisClientFrame
title={withProcessLabel(reducer, "Select Country")} title={withProcessLabel(reducer, "Select Country")}
@ -17,7 +26,7 @@ export function AttributeEntryScreen(props: AttributeEntryProps): VNode {
identity_attributes: attrs, identity_attributes: attrs,
})} })}
> >
{backupState.required_attributes.map((x: any, i: number) => { {reducer.currentReducerState.required_attributes?.map((x, i: number) => {
return ( return (
<AttributeEntryField <AttributeEntryField
key={i} key={i}
@ -40,7 +49,7 @@ export interface AttributeEntryFieldProps {
isFirst: boolean; isFirst: boolean;
value: string; value: string;
setValue: (newValue: string) => void; setValue: (newValue: string) => void;
spec: any; spec: UserAttributeSpec;
} }
export function AttributeEntryField(props: AttributeEntryFieldProps): VNode { export function AttributeEntryField(props: AttributeEntryFieldProps): VNode {

View File

@ -0,0 +1,35 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample, reducerStatesExample } from '../../utils';
import { AuthenticationEditorScreen as TestedComponent } from './AuthenticationEditorScreen';
export default {
title: 'Pages/AuthenticationEditorScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const Example = createExample(TestedComponent, reducerStatesExample.authEditing);

View File

@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/camelcase */ /* eslint-disable @typescript-eslint/camelcase */
import { AuthMethod, ReducerStateBackup } from "anastasis-core";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { AuthMethod, ReducerStateBackup } from "anastasis-core"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer"; import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer";
import { AuthMethodEmailSetup } from "./AuthMethodEmailSetup"; import { AuthMethodEmailSetup } from "./AuthMethodEmailSetup";
import { AuthMethodPostSetup } from "./AuthMethodPostSetup"; import { AuthMethodPostSetup } from "./AuthMethodPostSetup";
@ -9,12 +10,18 @@ import { AuthMethodQuestionSetup } from "./AuthMethodQuestionSetup";
import { AuthMethodSmsSetup } from "./AuthMethodSmsSetup"; import { AuthMethodSmsSetup } from "./AuthMethodSmsSetup";
import { AnastasisClientFrame } from "./index"; import { AnastasisClientFrame } from "./index";
export function AuthenticationEditorScreen(props: AuthenticationEditorProps): VNode { export function AuthenticationEditorScreen(): VNode {
const [selectedMethod, setSelectedMethod] = useState<string | undefined>( const [selectedMethod, setSelectedMethod] = useState<string | undefined>(
undefined undefined
); );
const { reducer, backupState } = props; const reducer = useAnastasisContext()
const providers = backupState.authentication_providers!; if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
return <div>invalid state</div>
}
const providers = reducer.currentReducerState.authentication_providers!;
const authAvailableSet = new Set<string>(); const authAvailableSet = new Set<string>();
for (const provKey of Object.keys(providers)) { for (const provKey of Object.keys(providers)) {
const p = providers[provKey]; const p = providers[provKey];
@ -52,14 +59,14 @@ export function AuthenticationEditorScreen(props: AuthenticationEditorProps): VN
disabled={!authAvailableSet.has(props.method)} disabled={!authAvailableSet.has(props.method)}
onClick={() => { onClick={() => {
setSelectedMethod(props.method); setSelectedMethod(props.method);
reducer.dismissError(); if (reducer) reducer.dismissError();
}} }}
> >
{props.label} {props.label}
</button> </button>
); );
} }
const configuredAuthMethods: AuthMethod[] = backupState.authentication_methods ?? []; const configuredAuthMethods: AuthMethod[] = reducer.currentReducerState.authentication_methods ?? [];
const haveMethodsConfigured = configuredAuthMethods.length; const haveMethodsConfigured = configuredAuthMethods.length;
return ( return (
<AnastasisClientFrame title="Backup: Configure Authentication Methods"> <AnastasisClientFrame title="Backup: Configure Authentication Methods">

View File

@ -0,0 +1,60 @@
/* eslint-disable @typescript-eslint/camelcase */
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { BackupFinishedScreen as TestedComponent } from './BackupFinishedScreen';
export default {
title: 'Pages/BackupFinishedScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const Simple = createExample(TestedComponent, reducerStatesExample.backupFinished);
export const WithName = createExample(TestedComponent, {...reducerStatesExample.backupFinished,
secret_name: 'super_secret',
} as ReducerState);
export const WithDetails = createExample(TestedComponent, {
...reducerStatesExample.backupFinished,
secret_name: 'super_secret',
success_details: {
'http://anastasis.net': {
policy_expiration: {
t_ms: 'never'
},
policy_version: 0
},
'http://taler.net': {
policy_expiration: {
t_ms: new Date().getTime() + 60*60*24*1000
},
policy_version: 1
},
}
} as ReducerState);

View File

@ -1,23 +1,33 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { BackupReducerProps, AnastasisClientFrame } from "./index"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index";
export function BackupFinishedScreen(props: BackupReducerProps): VNode { export function BackupFinishedScreen(): VNode {
const reducer = useAnastasisContext()
if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
return <div>invalid state</div>
}
const details = reducer.currentReducerState.success_details
return (<AnastasisClientFrame hideNext title="Backup finished"> return (<AnastasisClientFrame hideNext title="Backup finished">
<p> <p>
Your backup of secret "{props.backupState.secret_name ?? "??"}" was Your backup of secret "{reducer.currentReducerState.secret_name ?? "??"}" was
successful. successful.
</p> </p>
<p>The backup is stored by the following providers:</p> <p>The backup is stored by the following providers:</p>
<ul>
{Object.keys(props.backupState.success_details!).map((x, i) => { {details && <ul>
const sd = props.backupState.success_details![x]; {Object.keys(details).map((x, i) => {
const sd = details[x];
return ( return (
<li key={i}> <li key={i}>
{x} (Policy version {sd.policy_version}) {x} (Policy version {sd.policy_version})
</li> </li>
); );
})} })}
</ul> </ul>}
<button onClick={() => props.reducer.reset()}>Back to start</button> <button onClick={() => reducer.reset()}>Back to start</button>
</AnastasisClientFrame>); </AnastasisClientFrame>);
} }

View File

@ -0,0 +1,83 @@
/* eslint-disable @typescript-eslint/camelcase */
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { ChallengeOverviewScreen as TestedComponent } from './ChallengeOverviewScreen';
export default {
title: 'Pages/ChallengeOverviewScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const OneChallenge = createExample(TestedComponent, {...reducerStatesExample.challengeSelecting,
recovery_information: {
policies: [[{uuid:'1'}]],
challenges: [{
cost: 'USD:1',
instructions: 'just go for it',
type: 'question',
uuid: '1',
}]
},
} as ReducerState);
export const MoreChallenges = createExample(TestedComponent, {...reducerStatesExample.challengeSelecting,
recovery_information: {
policies: [[{uuid:'1'}, {uuid:'2'}],[{uuid:'3'}]],
challenges: [{
cost: 'USD:1',
instructions: 'just go for it',
type: 'question',
uuid: '1',
},{
cost: 'USD:1',
instructions: 'just go for it',
type: 'question',
uuid: '2',
},{
cost: 'USD:1',
instructions: 'just go for it',
type: 'question',
uuid: '3',
}]
},
} as ReducerState);
export const OneBadConfiguredPolicy = createExample(TestedComponent, {...reducerStatesExample.challengeSelecting,
recovery_information: {
policies: [[{uuid:'2'}]],
challenges: [{
cost: 'USD:1',
instructions: 'just go for it',
type: 'sasd',
uuid: '1',
}]
},
} as ReducerState);
export const NoPolicies = createExample(TestedComponent, reducerStatesExample.challengeSelecting);

View File

@ -1,10 +1,21 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { RecoveryReducerProps, AnastasisClientFrame } from "./index"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index";
export function ChallengeOverviewScreen(): VNode {
const reducer = useAnastasisContext()
if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
return <div>invalid state</div>
}
const policies = reducer.currentReducerState.recovery_information?.policies ?? [];
const chArr = reducer.currentReducerState.recovery_information?.challenges ?? [];
const challengeFeedback = reducer.currentReducerState?.challenge_feedback;
export function ChallengeOverviewScreen(props: RecoveryReducerProps): VNode {
const { recoveryState, reducer } = props;
const policies = recoveryState.recovery_information!.policies;
const chArr = recoveryState.recovery_information!.challenges;
const challenges: { const challenges: {
[uuid: string]: { [uuid: string]: {
type: string; type: string;
@ -22,15 +33,21 @@ export function ChallengeOverviewScreen(props: RecoveryReducerProps): VNode {
return ( return (
<AnastasisClientFrame title="Recovery: Solve challenges"> <AnastasisClientFrame title="Recovery: Solve challenges">
<h2>Policies</h2> <h2>Policies</h2>
{policies.map((x, i) => { {!policies.length && <p>
No policies found
</p>}
{policies.map((row, i) => {
return ( return (
<div key={i}> <div key={i}>
<h3>Policy #{i + 1}</h3> <h3>Policy #{i + 1}</h3>
{x.map((x, j) => { {row.map(column => {
const ch = challenges[x.uuid]; const ch = challenges[column.uuid];
const feedback = recoveryState.challenge_feedback?.[x.uuid]; if (!ch) return <div>
There is no challenge for this policy
</div>
const feedback = challengeFeedback?.[column.uuid];
return ( return (
<div key={j} <div key={column.uuid}
style={{ style={{
borderLeft: "2px solid gray", borderLeft: "2px solid gray",
paddingLeft: "0.5em", paddingLeft: "0.5em",
@ -46,7 +63,7 @@ export function ChallengeOverviewScreen(props: RecoveryReducerProps): VNode {
{feedback?.state !== "solved" ? ( {feedback?.state !== "solved" ? (
<button <button
onClick={() => reducer.transition("select_challenge", { onClick={() => reducer.transition("select_challenge", {
uuid: x.uuid, uuid: column.uuid,
})} })}
> >
Solve Solve

View File

@ -0,0 +1,36 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample, reducerStatesExample } from '../../utils';
import { ContinentSelectionScreen as TestedComponent } from './ContinentSelectionScreen';
export default {
title: 'Pages/ContinentSelectionScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectCountry);
export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectCountry);

View File

@ -1,15 +1,16 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { CommonReducerProps, AnastasisClientFrame, withProcessLabel } from "./index"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame, withProcessLabel } from "./index";
export function ContinentSelectionScreen(props: CommonReducerProps): VNode { export function ContinentSelectionScreen(): VNode {
const { reducer, reducerState } = props; const reducer = useAnastasisContext()
if (!reducer || !reducer.currentReducerState || !("continents" in reducer.currentReducerState)) {
return <div />
}
const sel = (x: string): void => reducer.transition("select_continent", { continent: x }); const sel = (x: string): void => reducer.transition("select_continent", { continent: x });
return ( return (
<AnastasisClientFrame <AnastasisClientFrame hideNext title={withProcessLabel(reducer, "Select Continent")}>
hideNext {reducer.currentReducerState.continents.map((x: any) => (
title={withProcessLabel(reducer, "Select Continent")}
>
{reducerState.continents.map((x: any) => (
<button onClick={() => sel(x.name)} key={x.name}> <button onClick={() => sel(x.name)} key={x.name}>
{x.name} {x.name}
</button> </button>

View File

@ -0,0 +1,36 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample, reducerStatesExample } from '../../utils';
import { CountrySelectionScreen as TestedComponent } from './CountrySelectionScreen';
export default {
title: 'Pages/CountrySelectionScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectCountry);
export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectCountry);

View File

@ -1,19 +1,23 @@
/* eslint-disable @typescript-eslint/camelcase */ /* eslint-disable @typescript-eslint/camelcase */
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { CommonReducerProps, AnastasisClientFrame, withProcessLabel } from "./index"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame, withProcessLabel } from "./index";
export function CountrySelectionScreen(props: CommonReducerProps): VNode { export function CountrySelectionScreen(): VNode {
const { reducer, reducerState } = props; const reducer = useAnastasisContext()
if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || !("countries" in reducer.currentReducerState)) {
return <div>invalid state</div>
}
const sel = (x: any): void => reducer.transition("select_country", { const sel = (x: any): void => reducer.transition("select_country", {
country_code: x.code, country_code: x.code,
currencies: [x.currency], currencies: [x.currency],
}); });
return ( return (
<AnastasisClientFrame <AnastasisClientFrame hideNext title={withProcessLabel(reducer, "Select Country")} >
hideNext {reducer.currentReducerState.countries.map((x: any) => (
title={withProcessLabel(reducer, "Select Country")}
>
{reducerState.countries.map((x: any) => (
<button onClick={() => sel(x)} key={x.name}> <button onClick={() => sel(x)} key={x.name}>
{x.name} ({x.currency}) {x.name} ({x.currency})
</button> </button>

View File

@ -0,0 +1,47 @@
/* eslint-disable @typescript-eslint/camelcase */
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { PoliciesPayingScreen as TestedComponent } from './PoliciesPayingScreen';
export default {
title: 'Pages/PoliciesPayingScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const Example = createExample(TestedComponent, reducerStatesExample.policyPay);
export const WithSomePaymentRequest = createExample(TestedComponent, {
...reducerStatesExample.policyPay,
policy_payment_requests: [{
payto: 'payto://x-taler-bank/bank.taler/account-a',
provider: 'provider1'
}, {
payto: 'payto://x-taler-bank/bank.taler/account-b',
provider: 'provider2'
}]
} as ReducerState);

View File

@ -1,8 +1,16 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { BackupReducerProps, AnastasisClientFrame } from "./index"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index";
export function PoliciesPayingScreen(props: BackupReducerProps): VNode { export function PoliciesPayingScreen(): VNode {
const payments = props.backupState.policy_payment_requests ?? []; const reducer = useAnastasisContext()
if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
return <div>invalid state</div>
}
const payments = reducer.currentReducerState.policy_payment_requests ?? [];
return ( return (
<AnastasisClientFrame hideNext title="Backup: Recovery Document Payments"> <AnastasisClientFrame hideNext title="Backup: Recovery Document Payments">
@ -19,7 +27,7 @@ export function PoliciesPayingScreen(props: BackupReducerProps): VNode {
); );
})} })}
</ul> </ul>
<button onClick={() => props.reducer.transition("pay", {})}> <button onClick={() => reducer.transition("pay", {})}>
Check payment status now Check payment status now
</button> </button>
</AnastasisClientFrame> </AnastasisClientFrame>

View File

@ -0,0 +1,42 @@
/* eslint-disable @typescript-eslint/camelcase */
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { RecoveryFinishedScreen as TestedComponent } from './RecoveryFinishedScreen';
export default {
title: 'Pages/RecoveryFinishedScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const NormalEnding = createExample(TestedComponent, {
...reducerStatesExample.recoveryFinished,
core_secret: { mime: 'text/plain', value: 'hello' }
} as ReducerState);
export const BadEnding = createExample(TestedComponent, reducerStatesExample.recoveryFinished);

View File

@ -3,13 +3,31 @@ import {
decodeCrock decodeCrock
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { RecoveryReducerProps, AnastasisClientFrame } from "./index"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index";
export function RecoveryFinishedScreen(props: RecoveryReducerProps): VNode { export function RecoveryFinishedScreen(): VNode {
const reducer = useAnastasisContext()
if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
return <div>invalid state</div>
}
const encodedSecret = reducer.currentReducerState.core_secret?.value
if (!encodedSecret) {
return <AnastasisClientFrame title="Recovery Problem" hideNext>
<p>
Secret not found
</p>
</AnastasisClientFrame>
}
const secret = bytesToString(decodeCrock(encodedSecret))
return ( return (
<AnastasisClientFrame title="Recovery Finished" hideNext> <AnastasisClientFrame title="Recovery Finished" hideNext>
<p> <p>
Secret: {bytesToString(decodeCrock(props.recoveryState.core_secret?.value!))} Secret: {secret}
</p> </p>
</AnastasisClientFrame> </AnastasisClientFrame>
); );

View File

@ -0,0 +1,81 @@
/* eslint-disable @typescript-eslint/camelcase */
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { ReviewPoliciesScreen as TestedComponent } from './ReviewPoliciesScreen';
export default {
title: 'Pages/ReviewPoliciesScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const HasPoliciesButMethodListIsEmpty = createExample(TestedComponent, {
...reducerStatesExample.policyReview,
policies: [{
methods: [{
authentication_method: 0,
provider: 'asd'
},{
authentication_method: 1,
provider: 'asd'
}]
},{
methods: [{
authentication_method: 1,
provider: 'asd'
}]
}],
authentication_methods: []
} as ReducerState);
export const SomePoliciesWithMethods = createExample(TestedComponent, {
...reducerStatesExample.policyReview,
policies: [{
methods: [{
authentication_method: 0,
provider: 'asd'
},{
authentication_method: 1,
provider: 'asd'
}]
},{
methods: [{
authentication_method: 1,
provider: 'asd'
}]
}],
authentication_methods: [{
challenge: 'asd',
instructions: 'ins',
type: 'type',
},{
challenge: 'asd2',
instructions: 'ins2',
type: 'type2',
}]
} as ReducerState);

View File

@ -1,35 +1,49 @@
/* eslint-disable @typescript-eslint/camelcase */ /* eslint-disable @typescript-eslint/camelcase */
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { BackupReducerProps, AnastasisClientFrame } from "./index"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index";
export function ReviewPoliciesScreen(): VNode {
const reducer = useAnastasisContext()
if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
return <div>invalid state</div>
}
const authMethods = reducer.currentReducerState.authentication_methods ?? [];
const policies = reducer.currentReducerState.policies ?? [];
export function ReviewPoliciesScreen(props: BackupReducerProps): VNode {
const { reducer, backupState } = props;
const authMethods = backupState.authentication_methods!;
return ( return (
<AnastasisClientFrame title="Backup: Review Recovery Policies"> <AnastasisClientFrame title="Backup: Review Recovery Policies">
{backupState.policies?.map((p, i) => { {policies.map((p, policy_index) => {
const policyName = p.methods const methods = p.methods
.map((x, i) => authMethods[x.authentication_method].type) .map(x => authMethods[x.authentication_method] && ({ ...authMethods[x.authentication_method], provider: x.provider }))
.join(" + "); .filter(x => !!x)
const policyName = methods.map(x => x.type).join(" + ");
return ( return (
<div key={i} class="policy"> <div key={policy_index} class="policy">
<h3> <h3>
Policy #{i + 1}: {policyName} Policy #{policy_index + 1}: {policyName}
</h3> </h3>
Required Authentications: Required Authentications:
{!methods.length && <p>
No auth method found
</p>}
<ul> <ul>
{p.methods.map((x, i) => { {methods.map((m, i) => {
const m = authMethods[x.authentication_method];
return ( return (
<li key={i}> <li key={i}>
{m.type} ({m.instructions}) at provider {x.provider} {m.type} ({m.instructions}) at provider {m.provider}
</li> </li>
); );
})} })}
</ul> </ul>
<div> <div>
<button <button
onClick={() => reducer.transition("delete_policy", { policy_index: i })} onClick={() => reducer.transition("delete_policy", { policy_index })}
> >
Delete Policy Delete Policy
</button> </button>

View File

@ -0,0 +1,44 @@
/* eslint-disable @typescript-eslint/camelcase */
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { SecretEditorScreen as TestedComponent } from './SecretEditorScreen';
export default {
title: 'Pages/SecretEditorScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const WithSecretNamePreselected = createExample(TestedComponent, {
...reducerStatesExample.secretEdition,
secret_name: 'someSecretName',
} as ReducerState);
export const WithoutName = createExample(TestedComponent, {
...reducerStatesExample.secretEdition,
} as ReducerState);

View File

@ -2,18 +2,29 @@
import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAnastasisContext } from "../../context/anastasis";
import { import {
BackupReducerProps,
AnastasisClientFrame, AnastasisClientFrame,
LabeledInput, LabeledInput
} from "./index"; } from "./index";
export function SecretEditorScreen(props: BackupReducerProps): VNode { export function SecretEditorScreen(): VNode {
const { reducer } = props; const reducer = useAnastasisContext()
const [secretName, setSecretName] = useState(
props.backupState.secret_name ?? "",
);
const [secretValue, setSecretValue] = useState(""); const [secretValue, setSecretValue] = useState("");
const currentSecretName = reducer?.currentReducerState
&& ("secret_name" in reducer.currentReducerState)
&& reducer.currentReducerState.secret_name;
const [secretName, setSecretName] = useState(currentSecretName || "");
if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
return <div>invalid state</div>
}
const secretNext = (): void => { const secretNext = (): void => {
reducer.runTransaction(async (tx) => { reducer.runTransaction(async (tx) => {
await tx.transition("enter_secret_name", { await tx.transition("enter_secret_name", {

View File

@ -0,0 +1,50 @@
/* eslint-disable @typescript-eslint/camelcase */
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { SecretSelectionScreen as TestedComponent } from './SecretSelectionScreen';
export default {
title: 'Pages/SecretSelectionScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const Example = createExample(TestedComponent, {
...reducerStatesExample.secretSelection,
recovery_document: {
provider_url: 'http://anastasis.url/',
secret_name: 'secretName',
version: 1,
},
} as ReducerState);
export const NoRecoveryDocumentFound = createExample(TestedComponent, {
...reducerStatesExample.secretSelection,
recovery_document: undefined,
} as ReducerState);

View File

@ -1,17 +1,29 @@
/* eslint-disable @typescript-eslint/camelcase */ /* eslint-disable @typescript-eslint/camelcase */
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { RecoveryReducerProps, AnastasisClientFrame } from "./index"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index";
export function SecretSelectionScreen(props: RecoveryReducerProps): VNode { export function SecretSelectionScreen(): VNode {
const { reducer, recoveryState } = props;
const [selectingVersion, setSelectingVersion] = useState<boolean>(false); const [selectingVersion, setSelectingVersion] = useState<boolean>(false);
const [otherVersion, setOtherVersion] = useState<number>(
recoveryState.recovery_document?.version ?? 0
);
const recoveryDocument = recoveryState.recovery_document!;
const [otherProvider, setOtherProvider] = useState<string>(""); const [otherProvider, setOtherProvider] = useState<string>("");
const reducer = useAnastasisContext()
const currentVersion = reducer?.currentReducerState
&& ("recovery_document" in reducer.currentReducerState)
&& reducer.currentReducerState.recovery_document?.version;
const [otherVersion, setOtherVersion] = useState<number>(currentVersion || 0);
if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
return <div>invalid state</div>
}
function selectVersion(p: string, n: number): void { function selectVersion(p: string, n: number): void {
if (!reducer) return;
reducer.runTransaction(async (tx) => { reducer.runTransaction(async (tx) => {
await tx.transition("change_version", { await tx.transition("change_version", {
version: n, version: n,
@ -20,12 +32,21 @@ export function SecretSelectionScreen(props: RecoveryReducerProps): VNode {
setSelectingVersion(false); setSelectingVersion(false);
}); });
} }
const recoveryDocument = reducer.currentReducerState.recovery_document
if (!recoveryDocument) {
return (
<AnastasisClientFrame hideNav title="Recovery: Problem">
<p>No recovery document found</p>
</AnastasisClientFrame>
)
}
if (selectingVersion) { if (selectingVersion) {
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)}> <select onChange={(e) => setOtherProvider((e.target as any).value)}>
{Object.keys(recoveryState.authentication_providers ?? {}).map( {Object.keys(reducer.currentReducerState.authentication_providers ?? {}).map(
(x, i) => ( (x, i) => (
<option key={i} selected={x === recoveryDocument.provider_url} value={x}> <option key={i} selected={x === recoveryDocument.provider_url} value={x}>
{x} {x}

View File

@ -1,14 +1,17 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame, LabeledInput } from "./index"; import { AnastasisClientFrame, LabeledInput } from "./index";
import { SolveEntryProps } from "./SolveScreen"; import { SolveEntryProps } from "./SolveScreen";
export function SolveEmailEntry(props: SolveEntryProps): VNode { export function SolveEmailEntry({ challenge, feedback }: SolveEntryProps): VNode {
const [answer, setAnswer] = useState(""); const [answer, setAnswer] = useState("");
const { reducer, challenge, feedback } = props; const reducer = useAnastasisContext()
const next = (): void => reducer.transition("solve_challenge", { const next = (): void => {
if (reducer) reducer.transition("solve_challenge", {
answer, answer,
}); })
};
return ( return (
<AnastasisClientFrame <AnastasisClientFrame
title="Recovery: Solve challenge" title="Recovery: Solve challenge"

View File

@ -1,14 +1,15 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame, LabeledInput } from "./index"; import { AnastasisClientFrame, LabeledInput } from "./index";
import { SolveEntryProps } from "./SolveScreen"; import { SolveEntryProps } from "./SolveScreen";
export function SolvePostEntry(props: SolveEntryProps): VNode { export function SolvePostEntry({ challenge, feedback }: SolveEntryProps): VNode {
const [answer, setAnswer] = useState(""); const [answer, setAnswer] = useState("");
const { reducer, challenge, feedback } = props; const reducer = useAnastasisContext()
const next = (): void => reducer.transition("solve_challenge", { const next = (): void => {
answer, if (reducer) reducer.transition("solve_challenge", { answer })
}); };
return ( return (
<AnastasisClientFrame <AnastasisClientFrame
title="Recovery: Solve challenge" title="Recovery: Solve challenge"

View File

@ -1,14 +1,15 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame, LabeledInput } from "./index"; import { AnastasisClientFrame, LabeledInput } from "./index";
import { SolveEntryProps } from "./SolveScreen"; import { SolveEntryProps } from "./SolveScreen";
export function SolveQuestionEntry(props: SolveEntryProps): VNode { export function SolveQuestionEntry({ challenge, feedback }: SolveEntryProps): VNode {
const [answer, setAnswer] = useState(""); const [answer, setAnswer] = useState("");
const { reducer, challenge, feedback } = props; const reducer = useAnastasisContext()
const next = (): void => reducer.transition("solve_challenge", { const next = (): void => {
answer, if (reducer) reducer.transition("solve_challenge", { answer })
}); };
return ( return (
<AnastasisClientFrame <AnastasisClientFrame
title="Recovery: Solve challenge" title="Recovery: Solve challenge"

View File

@ -0,0 +1,121 @@
/* eslint-disable @typescript-eslint/camelcase */
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { SolveScreen as TestedComponent } from './SolveScreen';
export default {
title: 'Pages/SolveScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const NoInformation = createExample(TestedComponent, reducerStatesExample.challengeSolving);
export const NotSupportedChallenge = createExample(TestedComponent, {
...reducerStatesExample.challengeSolving,
recovery_information: {
challenges: [{
cost: 'USD:1',
instructions: 'follow htis instructions',
type: 'chall-type',
uuid: 'ASDASDSAD!1'
}],
policies: [],
},
selected_challenge_uuid: 'ASDASDSAD!1'
} as ReducerState);
export const MismatchedChallengeId = createExample(TestedComponent, {
...reducerStatesExample.challengeSolving,
recovery_information: {
challenges: [{
cost: 'USD:1',
instructions: 'follow htis instructions',
type: 'chall-type',
uuid: 'ASDASDSAD!1'
}],
policies: [],
},
selected_challenge_uuid: 'no-no-no'
} as ReducerState);
export const SmsChallenge = createExample(TestedComponent, {
...reducerStatesExample.challengeSolving,
recovery_information: {
challenges: [{
cost: 'USD:1',
instructions: 'follow htis instructions',
type: 'sms',
uuid: 'ASDASDSAD!1'
}],
policies: [],
},
selected_challenge_uuid: 'ASDASDSAD!1'
} as ReducerState);
export const QuestionChallenge = createExample(TestedComponent, {
...reducerStatesExample.challengeSolving,
recovery_information: {
challenges: [{
cost: 'USD:1',
instructions: 'follow htis instructions',
type: 'question',
uuid: 'ASDASDSAD!1'
}],
policies: [],
},
selected_challenge_uuid: 'ASDASDSAD!1'
} as ReducerState);
export const EmailChallenge = createExample(TestedComponent, {
...reducerStatesExample.challengeSolving,
recovery_information: {
challenges: [{
cost: 'USD:1',
instructions: 'follow htis instructions',
type: 'email',
uuid: 'ASDASDSAD!1'
}],
policies: [],
},
selected_challenge_uuid: 'ASDASDSAD!1'
} as ReducerState);
export const PostChallenge = createExample(TestedComponent, {
...reducerStatesExample.challengeSolving,
recovery_information: {
challenges: [{
cost: 'USD:1',
instructions: 'follow htis instructions',
type: 'post',
uuid: 'ASDASDSAD!1'
}],
policies: [],
},
selected_challenge_uuid: 'ASDASDSAD!1'
} as ReducerState);

View File

@ -1,17 +1,31 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer"; import { ChallengeFeedback, ChallengeInfo } from "../../../../anastasis-core/lib";
import { useAnastasisContext } from "../../context/anastasis";
import { SolveEmailEntry } from "./SolveEmailEntry"; import { SolveEmailEntry } from "./SolveEmailEntry";
import { SolvePostEntry } from "./SolvePostEntry"; import { SolvePostEntry } from "./SolvePostEntry";
import { SolveQuestionEntry } from "./SolveQuestionEntry"; import { SolveQuestionEntry } from "./SolveQuestionEntry";
import { SolveSmsEntry } from "./SolveSmsEntry"; import { SolveSmsEntry } from "./SolveSmsEntry";
import { SolveUnsupportedEntry } from "./SolveUnsupportedEntry"; import { SolveUnsupportedEntry } from "./SolveUnsupportedEntry";
import { RecoveryReducerProps } from "./index";
import { ChallengeInfo, ChallengeFeedback } from "../../../../anastasis-core/lib";
export function SolveScreen(props: RecoveryReducerProps): VNode { export function SolveScreen(): VNode {
const chArr = props.recoveryState.recovery_information!.challenges; const reducer = useAnastasisContext()
const challengeFeedback = props.recoveryState.challenge_feedback ?? {};
const selectedUuid = props.recoveryState.selected_challenge_uuid!; if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
return <div>invalid state</div>
}
if (!reducer.currentReducerState.recovery_information) {
return <div>no recovery information found</div>
}
if (!reducer.currentReducerState.selected_challenge_uuid) {
return <div>no selected uuid</div>
}
const chArr = reducer.currentReducerState.recovery_information.challenges;
const challengeFeedback = reducer.currentReducerState.challenge_feedback ?? {};
const selectedUuid = reducer.currentReducerState.selected_challenge_uuid;
const challenges: { const challenges: {
[uuid: string]: ChallengeInfo; [uuid: string]: ChallengeInfo;
} = {}; } = {};
@ -25,17 +39,15 @@ export function SolveScreen(props: RecoveryReducerProps): VNode {
email: SolveEmailEntry, email: SolveEmailEntry,
post: SolvePostEntry, post: SolvePostEntry,
}; };
const SolveDialog = dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry; const SolveDialog = dialogMap[selectedChallenge?.type] ?? SolveUnsupportedEntry;
return ( return (
<SolveDialog <SolveDialog
challenge={selectedChallenge} challenge={selectedChallenge}
reducer={props.reducer}
feedback={challengeFeedback[selectedUuid]} /> feedback={challengeFeedback[selectedUuid]} />
); );
} }
export interface SolveEntryProps { export interface SolveEntryProps {
reducer: AnastasisReducerApi;
challenge: ChallengeInfo; challenge: ChallengeInfo;
feedback?: ChallengeFeedback; feedback?: ChallengeFeedback;
} }

View File

@ -1,14 +1,17 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame, LabeledInput } from "./index"; import { AnastasisClientFrame, LabeledInput } from "./index";
import { SolveEntryProps } from "./SolveScreen"; import { SolveEntryProps } from "./SolveScreen";
export function SolveSmsEntry(props: SolveEntryProps): VNode { export function SolveSmsEntry({ challenge, feedback }: SolveEntryProps): VNode {
const [answer, setAnswer] = useState(""); const [answer, setAnswer] = useState("");
const { reducer, challenge, feedback } = props; const reducer = useAnastasisContext()
const next = (): void => reducer.transition("solve_challenge", { const next = (): void => {
if (reducer) reducer.transition("solve_challenge", {
answer, answer,
}); })
};
return ( return (
<AnastasisClientFrame <AnastasisClientFrame
title="Recovery: Solve challenge" title="Recovery: Solve challenge"

View File

@ -0,0 +1,35 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample, reducerStatesExample } from '../../utils';
import { StartScreen as TestedComponent } from './StartScreen';
export default {
title: 'Pages/StartScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const InitialState = createExample(TestedComponent, reducerStatesExample.initial);

View File

@ -1,14 +1,34 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index"; import { AnastasisClientFrame } from "./index";
export function StartScreen(props: { reducer: AnastasisReducerApi; }): VNode { export function StartScreen(): VNode {
const reducer = useAnastasisContext()
if (!reducer) {
return <div>no reducer in context</div>
}
return ( return (
<AnastasisClientFrame hideNav title="Home"> <AnastasisClientFrame hideNav title="Home">
<button autoFocus onClick={() => props.reducer.startBackup()}> <div>
<section class="section is-main-section">
<div class="columns">
<div class="column" />
<div class="column is-four-fifths">
<div class="buttons is-right">
<button class="button is-success" autoFocus onClick={() => reducer.startBackup()}>
Backup Backup
</button> </button>
<button onClick={() => props.reducer.startRecover()}>Recover</button>
<button class="button is-info" onClick={() => reducer.startRecover()}>Recover</button>
</div>
</div>
<div class="column" />
</div>
</section>
</div>
</AnastasisClientFrame> </AnastasisClientFrame>
); );
} }

View File

@ -0,0 +1,40 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { TruthsPayingScreen as TestedComponent } from './TruthsPayingScreen';
export default {
title: 'Pages/TruthsPayingScreen',
component: TestedComponent,
argTypes: {
onUpdate: { action: 'onUpdate' },
onBack: { action: 'onBack' },
},
};
export const Example = createExample(TestedComponent, reducerStatesExample.truthsPaying);
export const WithPaytoList = createExample(TestedComponent, {
...reducerStatesExample.truthsPaying,
payments: ['payto://x-taler-bank/bank/account']
} as ReducerState);

View File

@ -1,8 +1,16 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { BackupReducerProps, AnastasisClientFrame } from "./index"; import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index";
export function TruthsPayingScreen(props: BackupReducerProps): VNode { export function TruthsPayingScreen(): VNode {
const payments = props.backupState.payments ?? []; const reducer = useAnastasisContext()
if (!reducer) {
return <div>no reducer in context</div>
}
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
return <div>invalid state</div>
}
const payments = reducer.currentReducerState.payments ?? [];
return ( return (
<AnastasisClientFrame <AnastasisClientFrame
hideNext hideNext
@ -17,7 +25,7 @@ export function TruthsPayingScreen(props: BackupReducerProps): VNode {
return <li key={i}>{x}</li>; return <li key={i}>{x}</li>;
})} })}
</ul> </ul>
<button onClick={() => props.reducer.transition("pay", {})}> <button onClick={() => reducer.transition("pay", {})}>
Check payment status now Check payment status now
</button> </button>
</AnastasisClientFrame> </AnastasisClientFrame>

View File

@ -1,28 +1,25 @@
import {
Component,
ComponentChildren,
createContext,
Fragment,
FunctionalComponent,
h,
VNode,
} from "preact";
import {
useContext,
useErrorBoundary,
useLayoutEffect,
useRef,
} from "preact/hooks";
import { Menu } from "../../components/menu";
import { import {
BackupStates, BackupStates,
RecoveryStates, RecoveryStates,
ReducerStateBackup, ReducerStateBackup,
ReducerStateRecovery, ReducerStateRecovery
} from "anastasis-core"; } from "anastasis-core";
import {
ComponentChildren, Fragment,
FunctionalComponent,
h,
VNode
} from "preact";
import {
useErrorBoundary,
useLayoutEffect,
useRef
} from "preact/hooks";
import { Menu } from "../../components/menu";
import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis";
import { import {
AnastasisReducerApi, AnastasisReducerApi,
useAnastasisReducer, useAnastasisReducer
} from "../../hooks/use-anastasis-reducer"; } from "../../hooks/use-anastasis-reducer";
import { AttributeEntryScreen } from "./AttributeEntryScreen"; import { AttributeEntryScreen } from "./AttributeEntryScreen";
import { AuthenticationEditorScreen } from "./AuthenticationEditorScreen"; import { AuthenticationEditorScreen } from "./AuthenticationEditorScreen";
@ -38,19 +35,11 @@ import { SecretSelectionScreen } from "./SecretSelectionScreen";
import { SolveScreen } from "./SolveScreen"; import { SolveScreen } from "./SolveScreen";
import { StartScreen } from "./StartScreen"; import { StartScreen } from "./StartScreen";
import { TruthsPayingScreen } from "./TruthsPayingScreen"; import { TruthsPayingScreen } from "./TruthsPayingScreen";
import "./../home/style";
const WithReducer = createContext<AnastasisReducerApi | undefined>(undefined);
function isBackup(reducer: AnastasisReducerApi): boolean { function isBackup(reducer: AnastasisReducerApi): boolean {
return !!reducer.currentReducerState?.backup_state; return !!reducer.currentReducerState?.backup_state;
} }
export interface CommonReducerProps {
reducer: AnastasisReducerApi;
reducerState: ReducerStateBackup | ReducerStateRecovery;
}
export function withProcessLabel( export function withProcessLabel(
reducer: AnastasisReducerApi, reducer: AnastasisReducerApi,
text: string, text: string,
@ -61,16 +50,6 @@ export function withProcessLabel(
return `Recovery: ${text}`; return `Recovery: ${text}`;
} }
export interface BackupReducerProps {
reducer: AnastasisReducerApi;
backupState: ReducerStateBackup;
}
export interface RecoveryReducerProps {
reducer: AnastasisReducerApi;
recoveryState: ReducerStateRecovery;
}
interface AnastasisClientFrameProps { interface AnastasisClientFrameProps {
onNext?(): void; onNext?(): void;
title: string; title: string;
@ -88,7 +67,7 @@ interface AnastasisClientFrameProps {
function ErrorBoundary(props: { function ErrorBoundary(props: {
reducer: AnastasisReducerApi; reducer: AnastasisReducerApi;
children: ComponentChildren; children: ComponentChildren;
}) { }): VNode {
const [error, resetError] = useErrorBoundary((error) => const [error, resetError] = useErrorBoundary((error) =>
console.log("got error", error), console.log("got error", error),
); );
@ -113,7 +92,7 @@ function ErrorBoundary(props: {
} }
export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode { export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
const reducer = useContext(WithReducer); const reducer = useAnastasisContext();
if (!reducer) { if (!reducer) {
return <p>Fatal: Reducer must be in context.</p>; return <p>Fatal: Reducer must be in context.</p>;
} }
@ -135,9 +114,8 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
<Menu title="Anastasis" /> <Menu title="Anastasis" />
<div> <div>
<div class="home" onKeyPress={(e) => handleKeyPress(e)}> <div class="home" onKeyPress={(e) => handleKeyPress(e)}>
<button onClick={() => reducer.reset()}>Reset session</button>
<h1>{props.title}</h1> <h1>{props.title}</h1>
<ErrorBanner reducer={reducer} /> <ErrorBanner />
{props.children} {props.children}
{!props.hideNav ? ( {!props.hideNav ? (
<div> <div>
@ -154,96 +132,94 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
const AnastasisClient: FunctionalComponent = () => { const AnastasisClient: FunctionalComponent = () => {
const reducer = useAnastasisReducer(); const reducer = useAnastasisReducer();
return ( return (
<WithReducer.Provider value={reducer}> <AnastasisProvider value={reducer}>
<ErrorBoundary reducer={reducer}> <ErrorBoundary reducer={reducer}>
<AnastasisClientImpl /> <AnastasisClientImpl />
</ErrorBoundary> </ErrorBoundary>
</WithReducer.Provider> </AnastasisProvider>
); );
}; };
const AnastasisClientImpl: FunctionalComponent = () => { const AnastasisClientImpl: FunctionalComponent = () => {
const reducer = useContext(WithReducer)!; const reducer = useAnastasisContext()
const reducerState = reducer.currentReducerState; if (!reducer) {
if (!reducerState) { return <p>Fatal: Reducer must be in context.</p>;
return <StartScreen reducer={reducer} />; }
const state = reducer.currentReducerState;
if (!state) {
return <StartScreen />;
} }
console.log("state", reducer.currentReducerState); console.log("state", reducer.currentReducerState);
if ( if (
reducerState.backup_state === BackupStates.ContinentSelecting || state.backup_state === BackupStates.ContinentSelecting ||
reducerState.recovery_state === RecoveryStates.ContinentSelecting state.recovery_state === RecoveryStates.ContinentSelecting
) { ) {
return ( return (
<ContinentSelectionScreen reducer={reducer} reducerState={reducerState} /> <ContinentSelectionScreen />
); );
} }
if ( if (
reducerState.backup_state === BackupStates.CountrySelecting || state.backup_state === BackupStates.CountrySelecting ||
reducerState.recovery_state === RecoveryStates.CountrySelecting state.recovery_state === RecoveryStates.CountrySelecting
) { ) {
return ( return (
<CountrySelectionScreen reducer={reducer} reducerState={reducerState} /> <CountrySelectionScreen />
); );
} }
if ( if (
reducerState.backup_state === BackupStates.UserAttributesCollecting || state.backup_state === BackupStates.UserAttributesCollecting ||
reducerState.recovery_state === RecoveryStates.UserAttributesCollecting state.recovery_state === RecoveryStates.UserAttributesCollecting
) { ) {
return ( return (
<AttributeEntryScreen reducer={reducer} reducerState={reducerState} /> <AttributeEntryScreen />
); );
} }
if (reducerState.backup_state === BackupStates.AuthenticationsEditing) { if (state.backup_state === BackupStates.AuthenticationsEditing) {
return ( return (
<AuthenticationEditorScreen <AuthenticationEditorScreen />
backupState={reducerState}
reducer={reducer}
/>
); );
} }
if (reducerState.backup_state === BackupStates.PoliciesReviewing) { if (state.backup_state === BackupStates.PoliciesReviewing) {
return ( return (
<ReviewPoliciesScreen reducer={reducer} backupState={reducerState} /> <ReviewPoliciesScreen />
); );
} }
if (reducerState.backup_state === BackupStates.SecretEditing) { if (state.backup_state === BackupStates.SecretEditing) {
return <SecretEditorScreen reducer={reducer} backupState={reducerState} />; return <SecretEditorScreen />;
} }
if (reducerState.backup_state === BackupStates.BackupFinished) { if (state.backup_state === BackupStates.BackupFinished) {
const backupState: ReducerStateBackup = reducerState; return <BackupFinishedScreen />;
return <BackupFinishedScreen reducer={reducer} backupState={backupState} />;
} }
if (reducerState.backup_state === BackupStates.TruthsPaying) { if (state.backup_state === BackupStates.TruthsPaying) {
return <TruthsPayingScreen reducer={reducer} backupState={reducerState} />; return <TruthsPayingScreen />;
} }
if (reducerState.backup_state === BackupStates.PoliciesPaying) { if (state.backup_state === BackupStates.PoliciesPaying) {
const backupState: ReducerStateBackup = reducerState; return <PoliciesPayingScreen />;
return <PoliciesPayingScreen reducer={reducer} backupState={backupState} />;
} }
if (reducerState.recovery_state === RecoveryStates.SecretSelecting) { if (state.recovery_state === RecoveryStates.SecretSelecting) {
return ( return (
<SecretSelectionScreen reducer={reducer} recoveryState={reducerState} /> <SecretSelectionScreen />
); );
} }
if (reducerState.recovery_state === RecoveryStates.ChallengeSelecting) { if (state.recovery_state === RecoveryStates.ChallengeSelecting) {
return ( return (
<ChallengeOverviewScreen reducer={reducer} recoveryState={reducerState} /> <ChallengeOverviewScreen />
); );
} }
if (reducerState.recovery_state === RecoveryStates.ChallengeSolving) { if (state.recovery_state === RecoveryStates.ChallengeSolving) {
return <SolveScreen reducer={reducer} recoveryState={reducerState} />; return <SolveScreen />;
} }
if (reducerState.recovery_state === RecoveryStates.RecoveryFinished) { if (state.recovery_state === RecoveryStates.RecoveryFinished) {
return ( return (
<RecoveryFinishedScreen reducer={reducer} recoveryState={reducerState} /> <RecoveryFinishedScreen />
); );
} }
@ -251,7 +227,9 @@ const AnastasisClientImpl: FunctionalComponent = () => {
return ( return (
<AnastasisClientFrame hideNav title="Bug"> <AnastasisClientFrame hideNav title="Bug">
<p>Bug: Unknown state.</p> <p>Bug: Unknown state.</p>
<button onClick={() => reducer.reset()}>Reset</button> <div class="buttons is-right">
<button class="button" onClick={() => reducer.reset()}>Reset</button>
</div>
</AnastasisClientFrame> </AnastasisClientFrame>
); );
}; };
@ -282,26 +260,20 @@ export function LabeledInput(props: LabeledInputProps): VNode {
); );
} }
interface ErrorBannerProps {
reducer: AnastasisReducerApi;
}
/** /**
* Show a dismissable error banner if there is a current error. * Show a dismissible error banner if there is a current error.
*/ */
function ErrorBanner(props: ErrorBannerProps): VNode | null { function ErrorBanner(): VNode | null {
const currentError = props.reducer.currentError; const reducer = useAnastasisContext();
if (currentError) { if (!reducer || !reducer.currentError) return null;
return ( return (
<div id="error"> <div id="error">
<p>Error: {JSON.stringify(currentError)}</p> <p>Error: {JSON.stringify(reducer.currentError)}</p>
<button onClick={() => props.reducer.dismissError()}> <button onClick={() => reducer.dismissError()}>
Dismiss Error Dismiss Error
</button> </button>
</div> </div>
); );
} }
return null;
}
export default AnastasisClient; export default AnastasisClient;

View File

@ -0,0 +1,161 @@
/* eslint-disable @typescript-eslint/camelcase */
import { BackupStates, RecoveryStates, ReducerState } from 'anastasis-core';
import { FunctionalComponent, h, VNode } from 'preact';
import { AnastasisProvider } from '../context/anastasis';
export function createExample<Props>(Component: FunctionalComponent<Props>, currentReducerState?: ReducerState, props?: Partial<Props>): { (args: Props): VNode } {
const r = (args: Props): VNode => {
return <AnastasisProvider value={{
currentReducerState,
currentError: undefined,
back: () => { null },
dismissError: () => { null },
reset: () => { null },
runTransaction: () => { null },
startBackup: () => { null },
startRecover: () => { null },
transition: () => { null },
}}>
<Component {...args} />
</AnastasisProvider>
}
r.args = props
return r
}
const base = {
continents: [
{
name: "Europe"
},
{
name: "India"
},
{
name: "Asia"
},
{
name: "North America"
},
{
name: "Testcontinent"
}
],
countries: [
{
code: "xx",
name: "Testland",
continent: "Testcontinent",
continent_i18n: {
de_DE: "Testkontinent"
},
name_i18n: {
de_DE: "Testlandt",
de_CH: "Testlandi",
fr_FR: "Testpais",
en_UK: "Testland"
},
currency: "TESTKUDOS",
call_code: "+00"
},
{
code: "xy",
name: "Demoland",
continent: "Testcontinent",
continent_i18n: {
de_DE: "Testkontinent"
},
name_i18n: {
de_DE: "Demolandt",
de_CH: "Demolandi",
fr_FR: "Demopais",
en_UK: "Demoland"
},
currency: "KUDOS",
call_code: "+01"
}
],
authentication_providers: {
"http://localhost:8086/": {
http_status: 200,
annual_fee: "COL:0",
business_name: "ana",
currency: "COL",
liability_limit: "COL:10",
methods: [
{
type: "question",
usage_fee: "COL:0"
}
],
salt: "WBMDD76BR1E90YQ5AHBMKPH7GW",
storage_limit_in_megabytes: 16,
truth_upload_fee: "COL:0"
},
"http://localhost:8087/": {
code: 8414,
hint: "request to provider failed"
},
"http://localhost:8088/": {
code: 8414,
hint: "request to provider failed"
},
"http://localhost:8089/": {
code: 8414,
hint: "request to provider failed"
}
},
// expiration: {
// d_ms: 1792525051855 // check t_ms
// },
} as Partial<ReducerState>
export const reducerStatesExample = {
initial: undefined,
recoverySelectCountry: {...base,
recovery_state: RecoveryStates.CountrySelecting
} as ReducerState,
backupSelectCountry: {...base,
backup_state: BackupStates.CountrySelecting
} as ReducerState,
recoverySelectContinent: {...base,
recovery_state: RecoveryStates.ContinentSelecting,
} as ReducerState,
backupSelectContinent: {...base,
backup_state: BackupStates.ContinentSelecting,
} as ReducerState,
secretSelection: {...base,
recovery_state: RecoveryStates.SecretSelecting,
} as ReducerState,
recoveryFinished: {...base,
recovery_state: RecoveryStates.RecoveryFinished,
} as ReducerState,
challengeSelecting: {...base,
recovery_state: RecoveryStates.ChallengeSelecting,
} as ReducerState,
challengeSolving: {...base,
recovery_state: RecoveryStates.ChallengeSolving,
} as ReducerState,
secretEdition: {...base,
backup_state: BackupStates.SecretEditing,
} as ReducerState,
policyReview: {...base,
backup_state: BackupStates.PoliciesReviewing,
} as ReducerState,
policyPay: {...base,
backup_state: BackupStates.PoliciesPaying,
} as ReducerState,
backupFinished: {...base,
backup_state: BackupStates.BackupFinished,
} as ReducerState,
authEditing: {...base,
backup_state: BackupStates.AuthenticationsEditing
} as ReducerState,
attributeEditing: {...base,
backup_state: BackupStates.UserAttributesCollecting
} as ReducerState,
truthsPaying: {...base,
backup_state: BackupStates.TruthsPaying
} as ReducerState,
}

File diff suppressed because it is too large Load Diff