gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: added core validators, worked


From: gnunet
Subject: [taler-wallet-core] branch master updated: added core validators, worked on look and feel
Date: Tue, 26 Oct 2021 17:08:25 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 21b60c8f added core validators, worked on look and feel
21b60c8f is described below

commit 21b60c8f6ff69bf114779a767a3ac3355f69a34f
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Oct 26 12:08:03 2021 -0300

    added core validators, worked on look and feel
---
 packages/anastasis-core/src/index.ts               |   1 +
 packages/anastasis-core/src/reducer-types.ts       |   3 +
 packages/anastasis-core/src/validators.ts          |  28 ++
 packages/anastasis-webui/.storybook/preview.js     |   6 +
 packages/anastasis-webui/package.json              |   1 +
 .../src/components/fields/DateInput.tsx            |  63 ++++
 .../src/components/fields/LabeledInput.tsx         |  40 +++
 .../src/components/menu/SideBar.tsx                |  18 +-
 .../src/components/picker/DatePicker.tsx           | 324 +++++++++++++++++++++
 .../picker/DurationPicker.stories.tsx}             |  42 +--
 .../src/components/picker/DurationPicker.tsx       | 154 ++++++++++
 .../pages/home/AttributeEntryScreen.stories.tsx    |  67 ++++-
 .../src/pages/home/AttributeEntryScreen.tsx        | 112 +++++--
 .../src/pages/home/AuthMethodEmailSetup.tsx        |   3 +-
 .../src/pages/home/AuthMethodPostSetup.tsx         |   2 +-
 .../src/pages/home/AuthMethodQuestionSetup.tsx     |   3 +-
 .../home/AuthenticationEditorScreen.stories.tsx    |   5 +-
 .../pages/home/BackupFinishedScreen.stories.tsx    |   5 +-
 .../pages/home/ChallengeOverviewScreen.stories.tsx |  35 ++-
 ...ories.tsx => ChallengePayingScreen.stories.tsx} |   9 +-
 ...sPayingScreen.tsx => ChallengePayingScreen.tsx} |   8 +-
 .../home/ContinentSelectionScreen.stories.tsx      |   7 +-
 .../src/pages/home/ContinentSelectionScreen.tsx    |   4 +-
 .../pages/home/CountrySelectionScreen.stories.tsx  |   3 +
 .../src/pages/home/CountrySelectionScreen.tsx      |  14 +-
 .../pages/home/PoliciesPayingScreen.stories.tsx    |   5 +-
 .../pages/home/RecoveryFinishedScreen.stories.tsx  |   5 +-
 .../pages/home/ReviewPoliciesScreen.stories.tsx    |   5 +-
 .../src/pages/home/SecretEditorScreen.stories.tsx  |   5 +-
 .../src/pages/home/SecretEditorScreen.tsx          |   5 +-
 .../pages/home/SecretSelectionScreen.stories.tsx   |   5 +-
 .../src/pages/home/SolveEmailEntry.tsx             |   3 +-
 .../src/pages/home/SolvePostEntry.tsx              |   3 +-
 .../src/pages/home/SolveQuestionEntry.tsx          |   3 +-
 .../src/pages/home/SolveScreen.stories.tsx         |   5 +-
 .../src/pages/home/SolveSmsEntry.tsx               |   3 +-
 .../src/pages/home/StartScreen.stories.tsx         |   3 +
 .../anastasis-webui/src/pages/home/StartScreen.tsx |  15 +-
 .../src/pages/home/TruthsPayingScreen.stories.tsx  |   5 +-
 .../src/pages/home/TruthsPayingScreen.tsx          |   2 +-
 packages/anastasis-webui/src/pages/home/index.tsx  |  42 +--
 .../anastasis-webui/src/scss/_custom-calendar.scss |   4 +
 packages/anastasis-webui/src/utils/index.tsx       |  20 +-
 pnpm-lock.yaml                                     |  27 +-
 44 files changed, 958 insertions(+), 164 deletions(-)

diff --git a/packages/anastasis-core/src/index.ts 
b/packages/anastasis-core/src/index.ts
index b4e911ff..c9e2bcf3 100644
--- a/packages/anastasis-core/src/index.ts
+++ b/packages/anastasis-core/src/index.ts
@@ -77,6 +77,7 @@ import { EscrowMethod, RecoveryDocument } from 
"./recovery-document-types.js";
 const { fetch, Request, Response, Headers } = fetchPonyfill({});
 
 export * from "./reducer-types.js";
+export * as validators from './validators.js';
 
 function getContinents(): ContinentInfo[] {
   const continentSet = new Set<string>();
diff --git a/packages/anastasis-core/src/reducer-types.ts 
b/packages/anastasis-core/src/reducer-types.ts
index 1a443bf9..2e1154fc 100644
--- a/packages/anastasis-core/src/reducer-types.ts
+++ b/packages/anastasis-core/src/reducer-types.ts
@@ -93,6 +93,9 @@ export interface UserAttributeSpec {
   type: string;
   uuid: string;
   widget: string;
+  optional?: boolean;
+  'validation-regex': string | undefined;
+  'validation-logic': string | undefined;
 }
 
 export interface RecoveryInternalData {
diff --git a/packages/anastasis-core/src/validators.ts 
b/packages/anastasis-core/src/validators.ts
new file mode 100644
index 00000000..1c04bfdb
--- /dev/null
+++ b/packages/anastasis-core/src/validators.ts
@@ -0,0 +1,28 @@
+function isPrime(num: number): boolean {
+  for (let i = 2, s = Math.sqrt(num); i <= s; i++)
+    if (num % i === 0) return false;
+  return num > 1;
+}
+
+export function AL_NID_check(s: string): boolean { return true }
+export function BE_NRN_check(s: string): boolean { return true }
+export function CH_AHV_check(s: string): boolean { return true }
+export function CZ_BN_check(s: string): boolean { return true }
+export function DE_TIN_check(s: string): boolean { return true }
+export function DE_SVN_check(s: string): boolean { return true }
+export function ES_DNI_check(s: string): boolean { return true }
+export function IN_AADHAR_check(s: string): boolean { return true }
+export function IT_CF_check(s: string): boolean {
+  return true
+}
+
+export function XX_SQUARE_check(s: string): boolean {
+  const n = parseInt(s, 10)
+  const r = Math.sqrt(n)
+  return n === r * r;
+}
+export function XY_PRIME_check(s: string): boolean {
+  const n = parseInt(s, 10)
+  return isPrime(n)
+}
+
diff --git a/packages/anastasis-webui/.storybook/preview.js 
b/packages/anastasis-webui/.storybook/preview.js
index 7cb9405b..9ab4d940 100644
--- a/packages/anastasis-webui/.storybook/preview.js
+++ b/packages/anastasis-webui/.storybook/preview.js
@@ -21,6 +21,12 @@ import { h } from 'preact';
 
 export const parameters = {
   controls: { expanded: true },
+  options: {
+    storySort: (a, b) => {
+      return (a[1].args.order ?? 0) - (b[1].args.order ?? 0)
+      // return a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, 
undefined, { numeric: true })
+    }
+  },
 }
 
 export const globalTypes = {
diff --git a/packages/anastasis-webui/package.json 
b/packages/anastasis-webui/package.json
index 57cfdd8d..4cdb0024 100644
--- a/packages/anastasis-webui/package.json
+++ b/packages/anastasis-webui/package.json
@@ -25,6 +25,7 @@
   "dependencies": {
     "@gnu-taler/taler-util": "workspace:^0.8.3",
     "anastasis-core": "workspace:^0.0.1",
+    "date-fns": "2.22.1",
     "jed": "1.1.1",
     "preact": "^10.3.1",
     "preact-render-to-string": "^5.1.4",
diff --git a/packages/anastasis-webui/src/components/fields/DateInput.tsx 
b/packages/anastasis-webui/src/components/fields/DateInput.tsx
new file mode 100644
index 00000000..c45acc6d
--- /dev/null
+++ b/packages/anastasis-webui/src/components/fields/DateInput.tsx
@@ -0,0 +1,63 @@
+import { format } from "date-fns";
+import { h, VNode } from "preact";
+import { useLayoutEffect, useRef, useState } from "preact/hooks";
+import { DatePicker } from "../picker/DatePicker";
+
+export interface DateInputProps {
+  label: string;
+  grabFocus?: boolean;
+  tooltip?: string;
+  error?: string;
+  bind: [string, (x: string) => void];
+}
+
+export function DateInput(props: DateInputProps): VNode {
+  const inputRef = useRef<HTMLInputElement>(null);
+  useLayoutEffect(() => {
+    if (props.grabFocus) {
+      inputRef.current?.focus();
+    }
+  }, [props.grabFocus]);
+  const [opened, setOpened2] = useState(false)
+  function setOpened(v: boolean) {
+    console.log('dale', v)
+    setOpened2(v)
+  }
+
+  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
+        type="text"
+        class={showError ? 'input is-danger' : 'input'}
+        onClick={() => { setOpened(true) }}
+        value={value}
+        ref={inputRef} />
+
+      <span class="control icon is-right">
+        <span class="icon"><i class="mdi mdi-calendar" /></span>
+      </span>
+    </div>
+    {showError && <p class="help is-danger">{props.error}</p>}
+    <DatePicker
+      opened={opened}
+      closeFunction={() => setOpened(false)}
+      dateReceiver={(d) => {
+        setDirty(true)
+        const v = format(d, 'yyyy/MM/dd')
+        props.bind[1](v);
+      }}
+    />
+  </div>
+    ;
+
+}
diff --git a/packages/anastasis-webui/src/components/fields/LabeledInput.tsx 
b/packages/anastasis-webui/src/components/fields/LabeledInput.tsx
new file mode 100644
index 00000000..96d634a4
--- /dev/null
+++ b/packages/anastasis-webui/src/components/fields/LabeledInput.tsx
@@ -0,0 +1,40 @@
+import { h, VNode } from "preact";
+import { useLayoutEffect, useRef, useState } from "preact/hooks";
+
+export interface LabeledInputProps {
+  label: string;
+  grabFocus?: boolean;
+  error?: string;
+  tooltip?: string;
+  bind: [string, (x: string) => void];
+}
+
+export function LabeledInput(props: LabeledInputProps): 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}
+        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>
+  );
+}
diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx 
b/packages/anastasis-webui/src/components/menu/SideBar.tsx
index df582a5d..12223d47 100644
--- a/packages/anastasis-webui/src/components/menu/SideBar.tsx
+++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx
@@ -59,25 +59,21 @@ export function Sidebar({ mobile }: Props): VNode {
           {!reducer.currentReducerState &&
             <li>
               <div class="ml-4">
-                <span class="menu-item-label"><Translate>Start one 
options</Translate></span>
+                <span class="menu-item-label"><Translate>Select one 
option</Translate></span>
               </div>
             </li>
           }
           {reducer.currentReducerState && 
reducer.currentReducerState.backup_state ? <Fragment>
-            <li class={reducer.currentReducerState.backup_state === 
BackupStates.ContinentSelecting ? 'is-active' : ''}>
+            <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>Continent 
selection</Translate></span>
-              </div>
-            </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>
+                <span class="menu-item-label"><Translate>Location &amp; 
Currency</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>
+                <span class="menu-item-label"><Translate>Personal 
information</Translate></span>
               </div>
             </li>
             <li class={reducer.currentReducerState.backup_state === 
BackupStates.AuthenticationsEditing ? 'is-active' : ''}>
@@ -119,7 +115,7 @@ export function Sidebar({ mobile }: Props): VNode {
           </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>
+                <span 
class="menu-item-label"><Translate>ContinentSelecting</Translate></span>
               </div>
             </li>
             <li class={reducer.currentReducerState.recovery_state === 
RecoveryStates.CountrySelecting ? 'is-active' : ''}>
diff --git a/packages/anastasis-webui/src/components/picker/DatePicker.tsx 
b/packages/anastasis-webui/src/components/picker/DatePicker.tsx
new file mode 100644
index 00000000..e51b3db6
--- /dev/null
+++ b/packages/anastasis-webui/src/components/picker/DatePicker.tsx
@@ -0,0 +1,324 @@
+/*
+ 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 { h, Component } from "preact";
+
+interface Props {
+  closeFunction?: () => void;
+  dateReceiver?: (d: Date) => void;
+  opened?: boolean;
+}
+interface State {
+  displayedMonth: number;
+  displayedYear: number;
+  selectYearMode: boolean;
+  currentDate: Date;
+}
+const now = new Date()
+
+const monthArrShortFull = [
+  'January',
+  'February',
+  'March',
+  'April',
+  'May',
+  'June',
+  'July',
+  'August',
+  'September',
+  'October',
+  'November',
+  'December'
+]
+
+const monthArrShort = [
+  'Jan',
+  'Feb',
+  'Mar',
+  'Apr',
+  'May',
+  'Jun',
+  'Jul',
+  'Aug',
+  'Sep',
+  'Oct',
+  'Nov',
+  'Dec'
+]
+
+const dayArr = [
+  'Sun',
+  'Mon',
+  'Tue',
+  'Wed',
+  'Thu',
+  'Fri',
+  'Sat'
+]
+
+const yearArr: number[] = []
+
+
+// inspired by https://codepen.io/m4r1vs/pen/MOOxyE
+export class DatePicker extends Component<Props, State> {
+
+  closeDatePicker() {
+    this.props.closeFunction && this.props.closeFunction(); // Function gets 
passed by parent
+  }
+
+  /**
+  * Gets fired when a day gets clicked.
+  * @param {object} e The event thrown by the <span /> element clicked
+  */
+  dayClicked(e: any) {
+
+    const element = e.target; // the actual element clicked
+
+    if (element.innerHTML === '') return false; // don't continue if <span /> 
empty
+
+    // get date from clicked element (gets attached when rendered)
+    const date = new Date(element.getAttribute('data-value'));
+
+    // update the state
+    this.setState({ currentDate: date });
+    this.passDateToParent(date)
+  }
+
+  /**
+  * returns days in month as array
+  * @param {number} month the month to display
+  * @param {number} year the year to display
+  */
+  getDaysByMonth(month: number, year: number) {
+
+    const calendar = [];
+
+    const date = new Date(year, month, 1); // month to display
+
+    const firstDay = new Date(year, month, 1).getDay(); // first weekday of 
month
+    const lastDate = new Date(year, month + 1, 0).getDate(); // last date of 
month
+
+    let day: number | null = 0;
+
+    // the calendar is 7*6 fields big, so 42 loops
+    for (let i = 0; i < 42; i++) {
+
+      if (i >= firstDay && day !== null) day = day + 1;
+      if (day !== null && day > lastDate) day = null;
+
+      // append the calendar Array
+      calendar.push({
+        day: (day === 0 || day === null) ? null : day, // null or number
+        date: (day === 0 || day === null) ? null : new Date(year, month, day), 
// null or Date()
+        today: (day === now.getDate() && month === now.getMonth() && year === 
now.getFullYear()) // boolean
+      });
+    }
+
+    return calendar;
+  }
+
+  /**
+  * Display previous month by updating state
+  */
+  displayPrevMonth() {
+    if (this.state.displayedMonth <= 0) {
+      this.setState({
+        displayedMonth: 11,
+        displayedYear: this.state.displayedYear - 1
+      });
+    }
+    else {
+      this.setState({
+        displayedMonth: this.state.displayedMonth - 1
+      });
+    }
+  }
+
+  /**
+  * Display next month by updating state
+  */
+  displayNextMonth() {
+    if (this.state.displayedMonth >= 11) {
+      this.setState({
+        displayedMonth: 0,
+        displayedYear: this.state.displayedYear + 1
+      });
+    }
+    else {
+      this.setState({
+        displayedMonth: this.state.displayedMonth + 1
+      });
+    }
+  }
+
+  /**
+  * Display the selected month (gets fired when clicking on the date string)
+  */
+  displaySelectedMonth() {
+    if (this.state.selectYearMode) {
+      this.toggleYearSelector();
+    }
+    else {
+      if (!this.state.currentDate) return false;
+      this.setState({
+        displayedMonth: this.state.currentDate.getMonth(),
+        displayedYear: this.state.currentDate.getFullYear()
+      });
+    }
+  }
+
+  toggleYearSelector() {
+    this.setState({ selectYearMode: !this.state.selectYearMode });
+  }
+
+  changeDisplayedYear(e: any) {
+    const element = e.target;
+    this.toggleYearSelector();
+    this.setState({ displayedYear: parseInt(element.innerHTML, 10), 
displayedMonth: 0 });
+  }
+
+  /**
+  * Pass the selected date to parent when 'OK' is clicked
+  */
+  passSavedDateDateToParent() {
+    this.passDateToParent(this.state.currentDate)
+  }
+  passDateToParent(date: Date) {
+    if (typeof this.props.dateReceiver === 'function') 
this.props.dateReceiver(date);
+    this.closeDatePicker();
+  }
+
+  componentDidUpdate() {
+    if (this.state.selectYearMode) {
+      document.getElementsByClassName('selected')[0].scrollIntoView(); // 
works in every browser incl. IE, replace with scrollIntoViewIfNeeded when 
browsers support it
+    }
+  }
+
+  constructor() {
+    super();
+
+    this.closeDatePicker = this.closeDatePicker.bind(this);
+    this.dayClicked = this.dayClicked.bind(this);
+    this.displayNextMonth = this.displayNextMonth.bind(this);
+    this.displayPrevMonth = this.displayPrevMonth.bind(this);
+    this.getDaysByMonth = this.getDaysByMonth.bind(this);
+    this.changeDisplayedYear = this.changeDisplayedYear.bind(this);
+    this.passDateToParent = this.passDateToParent.bind(this);
+    this.toggleYearSelector = this.toggleYearSelector.bind(this);
+    this.displaySelectedMonth = this.displaySelectedMonth.bind(this);
+
+
+    this.state = {
+      currentDate: now,
+      displayedMonth: now.getMonth(),
+      displayedYear: now.getFullYear(),
+      selectYearMode: false
+    }
+  }
+
+  render() {
+
+    const { currentDate, displayedMonth, displayedYear, selectYearMode } = 
this.state;
+
+    return (
+      <div>
+        <div class={`datePicker ${  this.props.opened && 
"datePicker--opened"}`}>
+
+          <div class="datePicker--titles">
+            <h3 style={{
+              color: selectYearMode ? 'rgba(255,255,255,.87)' : 
'rgba(255,255,255,.57)'
+            }} 
onClick={this.toggleYearSelector}>{currentDate.getFullYear()}</h3>
+            <h2 style={{
+              color: !selectYearMode ? 'rgba(255,255,255,.87)' : 
'rgba(255,255,255,.57)'
+            }} onClick={this.displaySelectedMonth}>
+              {dayArr[currentDate.getDay()]}, 
{monthArrShort[currentDate.getMonth()]} {currentDate.getDate()}
+            </h2>
+          </div>
+
+          {!selectYearMode && <nav>
+            <span onClick={this.displayPrevMonth} class="icon"><i style={{ 
transform: 'rotate(180deg)' }} class="mdi mdi-forward" /></span>
+            <h4>{monthArrShortFull[displayedMonth]} {displayedYear}</h4>
+            <span onClick={this.displayNextMonth} class="icon"><i class="mdi 
mdi-forward" /></span>
+          </nav>}
+
+          <div class="datePicker--scroll">
+
+            {!selectYearMode && <div class="datePicker--calendar" >
+
+              <div class="datePicker--dayNames">
+                {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day,i) => <span 
key={i}>{day}</span>)}
+              </div>
+
+              <div onClick={this.dayClicked} class="datePicker--days">
+
+                {/*
+                  Loop through the calendar object returned by 
getDaysByMonth().
+                */}
+
+                {this.getDaysByMonth(this.state.displayedMonth, 
this.state.displayedYear)
+                  .map(
+                    day => {
+                      let selected = false;
+
+                      if (currentDate && day.date) selected = 
(currentDate.toLocaleDateString() === day.date.toLocaleDateString());
+
+                      return (<span key={day.day}
+                        class={(day.today ? 'datePicker--today ' : '') + 
(selected ? 'datePicker--selected' : '')}
+                        disabled={!day.date}
+                        data-value={day.date}
+                      >
+                        {day.day}
+                      </span>)
+                    }
+                  )
+                }
+
+              </div>
+
+            </div>}
+
+            {selectYearMode && <div class="datePicker--selectYear">
+
+              {yearArr.map(year => (
+                <span key={year} class={(year === displayedYear) ? 'selected' 
: ''} onClick={this.changeDisplayedYear}>
+                  {year}
+                </span>
+              ))}
+
+            </div>}
+
+          </div>
+        </div>
+
+        <div class="datePicker--background" onClick={this.closeDatePicker} 
style={{
+          display: this.props.opened ? 'block' : 'none',
+        }}
+        />
+
+      </div>
+    )
+  }
+}
+
+
+for (let i = 2010; i <= now.getFullYear() + 10; i++) {
+  yearArr.push(i);
+}
diff --git 
a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx 
b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx
similarity index 54%
copy from 
packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
copy to 
packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx
index e9c59702..275c80fa 100644
--- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
+++ b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/camelcase */
 /*
  This file is part of GNU Taler
  (C) 2021 Taler Systems S.A.
@@ -20,31 +19,32 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 
-import { ReducerState } from 'anastasis-core';
-import { createExample, reducerStatesExample } from '../../utils';
-import { SecretSelectionScreen as TestedComponent } from 
'./SecretSelectionScreen';
+import { h, FunctionalComponent } from 'preact';
+import { useState } from 'preact/hooks';
+import { DurationPicker as TestedComponent } from './DurationPicker';
 
 
 export default {
-  title: 'Pages/SecretSelectionScreen',
+  title: 'Components/Picker/Duration',
   component: TestedComponent,
   argTypes: {
-    onUpdate: { action: 'onUpdate' },
-    onBack: { action: 'onBack' },
-  },
+    onCreate: { action: 'onCreate' },
+    goBack: { action: 'goBack' },
+  }
 };
 
+function createExample<Props>(Component: FunctionalComponent<Props>, props: 
Partial<Props>) {
+  const r = (args: any) => <Component {...args} />
+  r.args = props
+  return r
+}
+
 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);
+  days: true, minutes: true, hours: true, seconds: true,
+  value: 10000000
+});
+
+export const WithState = () => {
+  const [v,s] = useState<number>(1000000)
+  return <TestedComponent value={v} onChange={s} days minutes hours seconds />
+}
diff --git a/packages/anastasis-webui/src/components/picker/DurationPicker.tsx 
b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx
new file mode 100644
index 00000000..235a63e2
--- /dev/null
+++ b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx
@@ -0,0 +1,154 @@
+/*
+ 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 { h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { useTranslator } from "../../i18n";
+import "../../scss/DurationPicker.scss";
+
+export interface Props {
+  hours?: boolean;
+  minutes?: boolean;
+  seconds?: boolean;
+  days?: boolean;
+  onChange: (value: number) => void;
+  value: number
+}
+
+// inspiration taken from https://github.com/flurmbo/react-duration-picker
+export function DurationPicker({ days, hours, minutes, seconds, onChange, 
value }: Props): VNode {
+  const ss = 1000
+  const ms = ss * 60
+  const hs = ms * 60
+  const ds = hs * 24
+  const i18n = useTranslator()
+  
+  return <div class="rdp-picker">
+    {days && <DurationColumn unit={i18n`days`} max={99}
+      value={Math.floor(value / ds)}
+      onDecrease={value >= ds ? () => onChange(value - ds) : undefined}
+      onIncrease={value < 99 * ds ? () => onChange(value + ds) : undefined}
+      onChange={diff => onChange(value + diff * ds)}
+    />}
+    {hours && <DurationColumn unit={i18n`hours`} max={23} min={1}
+      value={Math.floor(value / hs) % 24}
+      onDecrease={value >= hs ? () => onChange(value - hs) : undefined}
+      onIncrease={value < 99 * ds ? () => onChange(value + hs) : undefined}
+      onChange={diff => onChange(value + diff * hs)}
+    />}
+    {minutes && <DurationColumn unit={i18n`minutes`} max={59} min={1}
+      value={Math.floor(value / ms) % 60}
+      onDecrease={value >= ms ? () => onChange(value - ms) : undefined}
+      onIncrease={value < 99 * ds ? () => onChange(value + ms) : undefined}
+      onChange={diff => onChange(value + diff * ms)}
+    />}
+    {seconds && <DurationColumn unit={i18n`seconds`} max={59}
+      value={Math.floor(value / ss) % 60}
+      onDecrease={value >= ss ? () => onChange(value - ss) : undefined}
+      onIncrease={value < 99 * ds ? () => onChange(value + ss) : undefined}
+      onChange={diff => onChange(value + diff * ss)}
+    />}
+  </div>
+}
+
+interface ColProps {
+  unit: string,
+  min?: number,
+  max: number,
+  value: number,
+  onIncrease?: () => void;
+  onDecrease?: () => void;
+  onChange?: (diff: number) => void;
+}
+
+function InputNumber({ initial, onChange }: { initial: number, onChange: (n: 
number) => void }) {
+  const [value, handler] = useState<{v:string}>({
+    v: toTwoDigitString(initial)
+  })
+
+  return <input
+    value={value.v}
+    onBlur={(e) => onChange(parseInt(value.v, 10))}
+    onInput={(e) => {
+      e.preventDefault()
+      const n = Number.parseInt(e.currentTarget.value, 10);
+      if (isNaN(n)) return handler({v:toTwoDigitString(initial)}) 
+      return handler({v:toTwoDigitString(n)})
+    }}
+    style={{ width: 50, border: 'none', fontSize: 'inherit', background: 
'inherit' }} />
+}
+
+function DurationColumn({ unit, min = 0, max, value, onIncrease, onDecrease, 
onChange }: ColProps): VNode {
+
+  const cellHeight = 35
+  return (
+    <div class="rdp-column-container">
+      <div class="rdp-masked-div">
+        <hr class="rdp-reticule" style={{ top: cellHeight * 2 - 1 }} />
+        <hr class="rdp-reticule" style={{ top: cellHeight * 3 - 1 }} />
+
+        <div class="rdp-column" style={{ top: 0 }}>
+
+          <div class="rdp-cell" key={value - 2}>
+            {onDecrease && <button style={{ width: '100%', textAlign: 
'center', margin: 5 }}
+              onClick={onDecrease}>
+              <span class="icon">
+                <i class="mdi mdi-chevron-up" />
+              </span>
+            </button>}
+          </div>
+          <div class="rdp-cell" key={value - 1}>
+            {value > min ? toTwoDigitString(value - 1) : ''}
+          </div>
+          <div class="rdp-cell rdp-center" key={value}>
+            {onChange ?
+              <InputNumber initial={value} onChange={(n) => onChange(n - 
value)} /> :
+              toTwoDigitString(value)
+            }
+            <div>{unit}</div>
+          </div>
+
+          <div class="rdp-cell" key={value + 1}>
+            {value < max ? toTwoDigitString(value + 1) : ''}
+          </div>
+
+          <div class="rdp-cell" key={value + 2}>
+            {onIncrease && <button style={{ width: '100%', textAlign: 
'center', margin: 5 }}
+              onClick={onIncrease}>
+              <span class="icon">
+                <i class="mdi mdi-chevron-down" />
+              </span>
+            </button>}
+          </div>
+
+        </div>
+      </div>
+    </div>
+  );
+}
+
+
+function toTwoDigitString(n: number) {
+  if (n < 10) {
+    return `0${n}`;
+  }
+  return `${n}`;
+}
\ No newline at end of file
diff --git 
a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
index d28a6df4..d9be48fb 100644
--- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
@@ -28,14 +28,40 @@ import { AttributeEntryScreen as TestedComponent } from 
'./AttributeEntryScreen'
 export default {
   title: 'Pages/AttributeEntryScreen',
   component: TestedComponent,
+  args: {
+    order: 4,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
   },
 };
 
-export const WithSomeAttributes = createExample(TestedComponent, {
-  ...reducerStatesExample.attributeEditing,
+export const Backup = createExample(TestedComponent, {
+  ...reducerStatesExample.backupAttributeEditing,
+  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 Recovery = createExample(TestedComponent, {
+  ...reducerStatesExample.recoveryAttributeEditing,
   required_attributes: [{
     name: 'first',
     label: 'first',
@@ -57,7 +83,40 @@ export const WithSomeAttributes = 
createExample(TestedComponent, {
   }]
 } as ReducerState);
 
-export const Empty = createExample(TestedComponent, {
-  ...reducerStatesExample.attributeEditing,
+export const WithNoRequiredAttribute = createExample(TestedComponent, {
+  ...reducerStatesExample.backupAttributeEditing,
   required_attributes: undefined
 } as ReducerState);
+
+const allWidgets = [
+  "anastasis_gtk_ia_aadhar_in",
+  "anastasis_gtk_ia_ahv",
+  "anastasis_gtk_ia_birthdate",
+  "anastasis_gtk_ia_birthnumber_cz",
+  "anastasis_gtk_ia_birthnumber_sk",
+  "anastasis_gtk_ia_birthplace",
+  "anastasis_gtk_ia_cf_it",
+  "anastasis_gtk_ia_cpr_dk",
+  "anastasis_gtk_ia_es_dni",
+  "anastasis_gtk_ia_es_ssn",
+  "anastasis_gtk_ia_full_name",
+  "anastasis_gtk_ia_my_jp",
+  "anastasis_gtk_ia_nid_al",
+  "anastasis_gtk_ia_nid_be",
+  "anastasis_gtk_ia_ssn_de",
+  "anastasis_gtk_ia_ssn_us",
+  "anastasis_gtk_ia_tax_de",
+  "anastasis_gtk_xx_prime",
+  "anastasis_gtk_xx_square",
+]
+
+export const WithAllPosibleWidget = createExample(TestedComponent, {
+  ...reducerStatesExample.backupAttributeEditing,
+  required_attributes: allWidgets.map(w => ({
+    name: w,
+    label: `widget: ${w}`,
+    type: 'type',
+    uuid: `uuid-${w}`,
+    widget: w
+  }))
+} as ReducerState);
diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx 
b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
index 2f804f94..3b39cf9c 100644
--- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
@@ -1,10 +1,11 @@
 /* eslint-disable @typescript-eslint/camelcase */
+import { UserAttributeSpec, validators } from "anastasis-core";
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { ReducerStateRecovery, ReducerStateBackup, UserAttributeSpec } from 
"anastasis-core/lib";
 import { useAnastasisContext } from "../../context/anastasis";
-import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer";
-import { AnastasisClientFrame, withProcessLabel, LabeledInput } from "./index";
+import { AnastasisClientFrame, withProcessLabel } from "./index";
+import { LabeledInput } from "../../components/fields/LabeledInput";
+import { DateInput } from "../../components/fields/DateInput";
 
 export function AttributeEntryScreen(): VNode {
   const reducer = useAnastasisContext()
@@ -18,48 +19,105 @@ export function AttributeEntryScreen(): VNode {
   if (!reducer.currentReducerState || !("required_attributes" in 
reducer.currentReducerState)) {
     return <div>invalid state</div>
   }
-  
+
+
   return (
     <AnastasisClientFrame
-      title={withProcessLabel(reducer, "Select Country")}
+      title={withProcessLabel(reducer, "Who are you?")}
       onNext={() => reducer.transition("enter_user_attributes", {
         identity_attributes: attrs,
       })}
     >
-      {reducer.currentReducerState.required_attributes?.map((x, i: number) => {
-        return (
-          <AttributeEntryField
-            key={i}
-            isFirst={i == 0}
-            setValue={(v: string) => setAttrs({ ...attrs, [x.name]: v })}
-            spec={x}
-            value={attrs[x.name]} />
-        );
-      })}
+      <div class="columns">
+        <div class="column is-half">
+
+          {reducer.currentReducerState.required_attributes?.map((x, i: number) 
=> {
+            const value = attrs[x.name]
+            function checkIfValid(): string | undefined {
+              const pattern = x['validation-regex']
+              if (pattern) {
+                const re = new RegExp(pattern)
+                if (!re.test(value)) return 'The value is invalid'
+              }
+              const logic = x['validation-logic']
+              if (logic) {
+                const func = (validators as any)[logic];
+                if (func && typeof func === 'function' && !func(value)) return 
'Please check the value'
+              }
+              const optional = x.optional
+              console.log('optiona', optional)
+              if (!optional && !value) {
+                return 'This value is required'
+              }
+              return undefined
+            }
+
+            return (
+              <AttributeEntryField
+                key={i}
+                isFirst={i == 0}
+                setValue={(v: string) => setAttrs({ ...attrs, [x.name]: v })}
+                spec={x}
+                isValid={checkIfValid}
+                value={value} />
+            );
+          })}
+
+        </div>
+        <div class="column is-half" >
+          <h1><b>This stay private</b></h1>
+          <p>The information you have entered here:
+          </p>
+          <ul>
+            <li>
+              <span class="icon is-right">
+                <i class="mdi mdi-circle-small" />
+              </span>
+              Will be hashed, and therefore unreadable
+            </li>
+            <li><span class="icon is-right">
+              <i class="mdi mdi-circle-small" />
+            </span>The non-hashed version is not shared</li>
+          </ul>
+        </div>
+      </div>
     </AnastasisClientFrame>
   );
 }
 
-interface AttributeEntryProps {
-  reducer: AnastasisReducerApi;
-  reducerState: ReducerStateRecovery | ReducerStateBackup;
-}
-
-export interface AttributeEntryFieldProps {
+interface AttributeEntryFieldProps {
   isFirst: boolean;
   value: string;
   setValue: (newValue: string) => void;
   spec: UserAttributeSpec;
+  isValid: () => string | undefined;
 }
 
-export function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
+function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
+  const errorMessage = props.isValid()
+
   return (
     <div>
-      <LabeledInput
-        grabFocus={props.isFirst}
-        label={props.spec.label}
-        bind={[props.value, props.setValue]}
-      />
+      {props.spec.type === 'date' ?
+        <DateInput
+          grabFocus={props.isFirst}
+          label={props.spec.label}
+          error={errorMessage}
+          bind={[props.value, props.setValue]}
+        /> :
+        <LabeledInput
+          grabFocus={props.isFirst}
+          label={props.spec.label}
+          error={errorMessage}
+          bind={[props.value, props.setValue]}
+        />
+      }
+      <span>
+        <span class="icon is-right">
+          <i class="mdi mdi-eye-off" />
+        </span>
+        This stay private
+      </span>
     </div>
   );
 }
diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx 
b/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx
index 9567e0ef..5243c525 100644
--- a/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx
+++ b/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx
@@ -6,7 +6,8 @@ import {
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
-import { AnastasisClientFrame, LabeledInput } from "./index";
+import { AnastasisClientFrame } from "./index";
+import { LabeledInput } from "../../components/fields/LabeledInput";
 
 export function AuthMethodEmailSetup(props: AuthMethodSetupProps): VNode {
   const [email, setEmail] = useState("");
diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx 
b/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx
index 55e37a96..1c2a9a92 100644
--- a/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx
+++ b/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx
@@ -6,7 +6,7 @@ import {
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
-import { LabeledInput } from "./index";
+import { LabeledInput } from "../../components/fields/LabeledInput";
 
 export function AuthMethodPostSetup(props: AuthMethodSetupProps): VNode {
   const [fullName, setFullName] = useState("");
diff --git 
a/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx 
b/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx
index 7699cdf3..c2bd24ef 100644
--- a/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx
+++ b/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx
@@ -6,7 +6,8 @@ import {
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { AuthMethodSetupProps } from "./AuthenticationEditorScreen";
-import { AnastasisClientFrame, LabeledInput } from "./index";
+import { AnastasisClientFrame } from "./index";
+import { LabeledInput } from "../../components/fields/LabeledInput";
 
 export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode {
   const [questionText, setQuestionText] = useState("");
diff --git 
a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
index 44d3795b..8f86831a 100644
--- 
a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
@@ -24,8 +24,11 @@ import { AuthenticationEditorScreen as TestedComponent } 
from './AuthenticationE
 
 
 export default {
-  title: 'Pages/AuthenticationEditorScreen',
+  title: 'Pages/backup/AuthenticationEditorScreen',
   component: TestedComponent,
+  args: {
+    order: 5,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
diff --git 
a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
index 65a2b7e1..0c9d007b 100644
--- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
@@ -26,8 +26,11 @@ import { BackupFinishedScreen as TestedComponent } from 
'./BackupFinishedScreen'
 
 
 export default {
-  title: 'Pages/BackupFinishedScreen',
+  title: 'Pages/backup/FinishedScreen',
   component: TestedComponent,
+  args: {
+    order: 9,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
diff --git 
a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx
index 4f186c03..def44c5a 100644
--- 
a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx
@@ -20,23 +20,27 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 
-import { ReducerState } from 'anastasis-core';
+import { RecoveryStates, ReducerState } from 'anastasis-core';
 import { createExample, reducerStatesExample } from '../../utils';
 import { ChallengeOverviewScreen as TestedComponent } from 
'./ChallengeOverviewScreen';
 
 
 export default {
-  title: 'Pages/ChallengeOverviewScreen',
+  title: 'Pages/recovery/ChallengeOverviewScreen',
   component: TestedComponent,
+  args: {
+    order: 5,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
   },
 };
 
-export const OneChallenge = createExample(TestedComponent, 
{...reducerStatesExample.challengeSelecting,
+export const OneChallenge = createExample(TestedComponent, {
+  ...reducerStatesExample.challengeSelecting,
   recovery_information: {
-    policies: [[{uuid:'1'}]],
+    policies: [[{ uuid: '1' }]],
     challenges: [{
       cost: 'USD:1',
       instructions: 'just go for it',
@@ -46,37 +50,44 @@ export const OneChallenge = createExample(TestedComponent, 
{...reducerStatesExam
   },
 } as ReducerState);
 
-export const MoreChallenges = createExample(TestedComponent, 
{...reducerStatesExample.challengeSelecting,
+export const MoreChallenges = createExample(TestedComponent, {
+  ...reducerStatesExample.challengeSelecting,
   recovery_information: {
-    policies: [[{uuid:'1'}, {uuid:'2'}],[{uuid:'3'}]],
+    policies: [[{ uuid: '1' }, { uuid: '2' }], [{ uuid: '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',
+      uuid: 'uuid-3',
     }]
   },
+  challenge_feedback: {
+    'uuid-3': {
+      state: 'solved'
+    }
+  }
 } as ReducerState);
 
-export const OneBadConfiguredPolicy = createExample(TestedComponent, 
{...reducerStatesExample.challengeSelecting,
+export const OneBadConfiguredPolicy = createExample(TestedComponent, {
+  ...reducerStatesExample.challengeSelecting,
   recovery_information: {
-    policies: [[{uuid:'2'}]],
+    policies: [[{ uuid: '2' }]],
     challenges: [{
       cost: 'USD:1',
       instructions: 'just go for it',
       type: 'sasd',
       uuid: '1',
-    }]
+    }],
   },
 } as ReducerState);
 
diff --git 
a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
 b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
similarity index 83%
copy from 
packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
copy to 
packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
index 44d3795b..e5fe09e9 100644
--- 
a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
@@ -20,16 +20,19 @@
 */
 
 import { createExample, reducerStatesExample } from '../../utils';
-import { AuthenticationEditorScreen as TestedComponent } from 
'./AuthenticationEditorScreen';
+import { ChallengePayingScreen as TestedComponent } from 
'./ChallengePayingScreen';
 
 
 export default {
-  title: 'Pages/AuthenticationEditorScreen',
+  title: 'Pages/recovery/__ChallengePayingScreen',
   component: TestedComponent,
+  args: {
+    order: 10,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
   },
 };
 
-export const Example = createExample(TestedComponent, 
reducerStatesExample.authEditing);
+export const Example = createExample(TestedComponent, 
reducerStatesExample.challengePaying);
diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx 
b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx
similarity index 80%
copy from packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx
copy to packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx
index 319f590a..d87afdf4 100644
--- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx
@@ -2,19 +2,19 @@ import { h, VNode } from "preact";
 import { useAnastasisContext } from "../../context/anastasis";
 import { AnastasisClientFrame } from "./index";
 
-export function TruthsPayingScreen(): VNode {
+export function ChallengePayingScreen(): VNode {
   const reducer = useAnastasisContext()
   if (!reducer) {
     return <div>no reducer in context</div>
   }
-  if (!reducer.currentReducerState || reducer.currentReducerState.backup_state 
=== undefined) {
+  if (!reducer.currentReducerState || 
reducer.currentReducerState.recovery_state === undefined) {
     return <div>invalid state</div>
   }
-  const payments = reducer.currentReducerState.payments ?? [];
+  const payments = ['']; //reducer.currentReducerState.payments ?? 
   return (
     <AnastasisClientFrame
       hideNext
-      title="Backup: Authentication Storage Payments"
+      title="Recovery: Challenge Paying"
     >
       <p>
         Some of the providers require a payment to store the encrypted
diff --git 
a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
index aad37cd7..8744a2b7 100644
--- 
a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
@@ -26,11 +26,14 @@ import { ContinentSelectionScreen as TestedComponent } from 
'./ContinentSelectio
 export default {
   title: 'Pages/ContinentSelectionScreen',
   component: TestedComponent,
+  args: {
+    order: 2,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
   },
 };
 
-export const Backup = createExample(TestedComponent, 
reducerStatesExample.backupSelectCountry);
-export const Recovery = createExample(TestedComponent, 
reducerStatesExample.recoverySelectCountry);
+export const Backup = createExample(TestedComponent, 
reducerStatesExample.backupSelectContinent);
+export const Recovery = createExample(TestedComponent, 
reducerStatesExample.recoverySelectContinent);
diff --git 
a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx 
b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
index ad529a4a..94c0409d 100644
--- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
@@ -7,11 +7,11 @@ export function ContinentSelectionScreen(): VNode {
   if (!reducer || !reducer.currentReducerState || !("continents" in 
reducer.currentReducerState)) {
     return <div />
   }
-  const sel = (x: string): void => reducer.transition("select_continent", { 
continent: x });
+  const select = (continent: string) => (): void => 
reducer.transition("select_continent", { continent });
   return (
     <AnastasisClientFrame hideNext title={withProcessLabel(reducer, "Select 
Continent")}>
       {reducer.currentReducerState.continents.map((x: any) => (
-        <button onClick={() => sel(x.name)} key={x.name}>
+        <button class="button" onClick={select(x.name)} key={x.name}>
           {x.name}
         </button>
       ))}
diff --git 
a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx
index adf36980..3a642748 100644
--- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx
@@ -26,6 +26,9 @@ import { CountrySelectionScreen as TestedComponent } from 
'./CountrySelectionScr
 export default {
   title: 'Pages/CountrySelectionScreen',
   component: TestedComponent,
+  args: {
+    order: 3,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
diff --git a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx 
b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx
index 555622c1..417c0863 100644
--- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx
@@ -17,11 +17,15 @@ export function CountrySelectionScreen(): VNode {
   });
   return (
     <AnastasisClientFrame hideNext title={withProcessLabel(reducer, "Select 
Country")} >
-      {reducer.currentReducerState.countries.map((x: any) => (
-        <button onClick={() => sel(x)} key={x.name}>
-          {x.name} ({x.currency})
-        </button>
-      ))}
+      <div style={{ display: 'flex', flexDirection: 'column' }}>
+        {reducer.currentReducerState.countries.map((x: any) => (
+          <div key={x.name}>
+            <button class="button" onClick={() => sel(x)} >
+              {x.name} ({x.currency})
+            </button>
+          </div>
+        ))}
+      </div>
     </AnastasisClientFrame>
   );
 }
diff --git 
a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx
index 1a9462b8..e952ab28 100644
--- a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx
@@ -26,8 +26,11 @@ import { PoliciesPayingScreen as TestedComponent } from 
'./PoliciesPayingScreen'
 
 
 export default {
-  title: 'Pages/PoliciesPayingScreen',
+  title: 'Pages/backup/PoliciesPayingScreen',
   component: TestedComponent,
+  args: {
+    order: 8,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
diff --git 
a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx
index 0c184242..b5933db1 100644
--- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx
@@ -26,7 +26,10 @@ import { RecoveryFinishedScreen as TestedComponent } from 
'./RecoveryFinishedScr
 
 
 export default {
-  title: 'Pages/RecoveryFinishedScreen',
+  title: 'Pages/recovery/FinishedScreen',
+  args: {
+    order: 7,
+  },
   component: TestedComponent,
   argTypes: {
     onUpdate: { action: 'onUpdate' },
diff --git 
a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
index b52699e7..91855b02 100644
--- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
@@ -26,7 +26,10 @@ import { ReviewPoliciesScreen as TestedComponent } from 
'./ReviewPoliciesScreen'
 
 
 export default {
-  title: 'Pages/ReviewPoliciesScreen',
+  title: 'Pages/backup/ReviewPoliciesScreen',
+  args: {
+    order: 6,
+  },
   component: TestedComponent,
   argTypes: {
     onUpdate: { action: 'onUpdate' },
diff --git 
a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx
index 18560356..49dd8fca 100644
--- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx
@@ -26,8 +26,11 @@ import { SecretEditorScreen as TestedComponent } from 
'./SecretEditorScreen';
 
 
 export default {
-  title: 'Pages/SecretEditorScreen',
+  title: 'Pages/backup/SecretEditorScreen',
   component: TestedComponent,
+  args: {
+    order: 7,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx 
b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
index a5235d66..f5fd7c0d 100644
--- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
@@ -4,9 +4,8 @@ import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useAnastasisContext } from "../../context/anastasis";
 import {
-  AnastasisClientFrame,
-  LabeledInput
-} from "./index";
+  AnastasisClientFrame} from "./index";
+import { LabeledInput } from "../../components/fields/LabeledInput";
 
 export function SecretEditorScreen(): VNode {
   const reducer = useAnastasisContext()
diff --git 
a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
index e9c59702..f5693909 100644
--- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
@@ -26,8 +26,11 @@ import { SecretSelectionScreen as TestedComponent } from 
'./SecretSelectionScree
 
 
 export default {
-  title: 'Pages/SecretSelectionScreen',
+  title: 'Pages/recovery/SecretSelectionScreen',
   component: TestedComponent,
+  args: {
+    order: 4,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
diff --git a/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx 
b/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx
index 2c27895c..0d70405e 100644
--- a/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx
+++ b/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx
@@ -1,7 +1,8 @@
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useAnastasisContext } from "../../context/anastasis";
-import { AnastasisClientFrame, LabeledInput } from "./index";
+import { AnastasisClientFrame } from "./index";
+import { LabeledInput } from "../../components/fields/LabeledInput";
 import { SolveEntryProps } from "./SolveScreen";
 
 export function SolveEmailEntry({ challenge, feedback }: SolveEntryProps): 
VNode {
diff --git a/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx 
b/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx
index 1a824acb..22b8d470 100644
--- a/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx
+++ b/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx
@@ -1,7 +1,8 @@
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useAnastasisContext } from "../../context/anastasis";
-import { AnastasisClientFrame, LabeledInput } from "./index";
+import { AnastasisClientFrame } from "./index";
+import { LabeledInput } from "../../components/fields/LabeledInput";
 import { SolveEntryProps } from "./SolveScreen";
 
 export function SolvePostEntry({ challenge, feedback }: SolveEntryProps): 
VNode {
diff --git a/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx 
b/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx
index 72dadbe8..31928938 100644
--- a/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx
+++ b/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx
@@ -1,7 +1,8 @@
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useAnastasisContext } from "../../context/anastasis";
-import { AnastasisClientFrame, LabeledInput } from "./index";
+import { AnastasisClientFrame } from "./index";
+import { LabeledInput } from "../../components/fields/LabeledInput";
 import { SolveEntryProps } from "./SolveScreen";
 
 export function SolveQuestionEntry({ challenge, feedback }: SolveEntryProps): 
VNode {
diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx
index 69af9be4..c05c36b0 100644
--- a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx
@@ -26,8 +26,11 @@ import { SolveScreen as TestedComponent } from 
'./SolveScreen';
 
 
 export default {
-  title: 'Pages/SolveScreen',
+  title: 'Pages/recovery/SolveScreen',
   component: TestedComponent,
+  args: {
+    order: 6,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
diff --git a/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx 
b/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx
index 163e0d1f..c4cf3a68 100644
--- a/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx
+++ b/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx
@@ -1,7 +1,8 @@
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useAnastasisContext } from "../../context/anastasis";
-import { AnastasisClientFrame, LabeledInput } from "./index";
+import { AnastasisClientFrame } from "./index";
+import { LabeledInput } from "../../components/fields/LabeledInput";
 import { SolveEntryProps } from "./SolveScreen";
 
 export function SolveSmsEntry({ challenge, feedback }: SolveEntryProps): VNode 
{
diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx
index ad84cd8f..657a2dd7 100644
--- a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx
@@ -26,6 +26,9 @@ import { StartScreen as TestedComponent } from 
'./StartScreen';
 export default {
   title: 'Pages/StartScreen',
   component: TestedComponent,
+  args: {
+    order: 1,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.tsx 
b/packages/anastasis-webui/src/pages/home/StartScreen.tsx
index 6625ec5b..c751ad9e 100644
--- a/packages/anastasis-webui/src/pages/home/StartScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/StartScreen.tsx
@@ -16,12 +16,21 @@ export function StartScreen(): VNode {
             <div class="column" />
             <div class="column is-four-fifths">
 
-              <div class="buttons is-right">
+              <div class="buttons">
                 <button class="button is-success" autoFocus onClick={() => 
reducer.startBackup()}>
-                  Backup
+                  <div class="icon"><i class="mdi mdi-arrow-up" /></div>
+                  <span>Backup a secret</span>
                 </button>
 
-                <button class="button is-info" onClick={() => 
reducer.startRecover()}>Recover</button>
+                <button class="button is-info" onClick={() => 
reducer.startRecover()}>
+                  <div class="icon"><i class="mdi mdi-arrow-down" /></div>
+                  <span>Recover a secret</span>
+                </button>
+
+                <button class="button">
+                  <div class="icon"><i class="mdi mdi-file" /></div>
+                  <span>Restore a session</span>
+                </button>
               </div>
 
             </div>
diff --git 
a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx
index e2f3d521..7568ccd6 100644
--- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx
@@ -25,8 +25,11 @@ import { TruthsPayingScreen as TestedComponent } from 
'./TruthsPayingScreen';
 
 
 export default {
-  title: 'Pages/TruthsPayingScreen',
+  title: 'Pages/backup/__TruthsPayingScreen',
   component: TestedComponent,
+  args: {
+    order: 10,
+  },
   argTypes: {
     onUpdate: { action: 'onUpdate' },
     onBack: { action: 'onBack' },
diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx 
b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx
index 319f590a..098a8ba8 100644
--- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx
@@ -14,7 +14,7 @@ export function TruthsPayingScreen(): VNode {
   return (
     <AnastasisClientFrame
       hideNext
-      title="Backup: Authentication Storage Payments"
+      title="Backup: Truths Paying"
     >
       <p>
         Some of the providers require a payment to store the encrypted
diff --git a/packages/anastasis-webui/src/pages/home/index.tsx 
b/packages/anastasis-webui/src/pages/home/index.tsx
index 4cec47ec..5cef4ee9 100644
--- a/packages/anastasis-webui/src/pages/home/index.tsx
+++ b/packages/anastasis-webui/src/pages/home/index.tsx
@@ -11,10 +11,7 @@ import {
   VNode
 } from "preact";
 import {
-  useErrorBoundary,
-  useLayoutEffect,
-  useRef
-} from "preact/hooks";
+  useErrorBoundary} from "preact/hooks";
 import { Menu } from "../../components/menu";
 import { AnastasisProvider, useAnastasisContext } from 
"../../context/anastasis";
 import {
@@ -25,6 +22,7 @@ import { AttributeEntryScreen } from "./AttributeEntryScreen";
 import { AuthenticationEditorScreen } from "./AuthenticationEditorScreen";
 import { BackupFinishedScreen } from "./BackupFinishedScreen";
 import { ChallengeOverviewScreen } from "./ChallengeOverviewScreen";
+import { ChallengePayingScreen } from "./ChallengePayingScreen";
 import { ContinentSelectionScreen } from "./ContinentSelectionScreen";
 import { CountrySelectionScreen } from "./CountrySelectionScreen";
 import { PoliciesPayingScreen } from "./PoliciesPayingScreen";
@@ -118,9 +116,9 @@ export function AnastasisClientFrame(props: 
AnastasisClientFrameProps): VNode {
           <ErrorBanner />
           {props.children}
           {!props.hideNav ? (
-            <div>
-              <button onClick={() => reducer.back()}>Back</button>
-              {!props.hideNext ? <button onClick={next}>Next</button> : null}
+            <div style={{marginTop: '2em', display:'flex', 
justifyContent:'space-between'}}>
+              <button class="button" onClick={() => 
reducer.back()}>Back</button>
+              {!props.hideNext ? <button class="button 
is-info"onClick={next}>Next</button> : null}
             </div>
           ) : null}
         </div>
@@ -222,7 +220,9 @@ const AnastasisClientImpl: FunctionalComponent = () => {
       <RecoveryFinishedScreen />
     );
   }
-
+  if (state.recovery_state === RecoveryStates.ChallengePaying) {
+    return <ChallengePayingScreen />;
+  }
   console.log("unknown state", reducer.currentReducerState);
   return (
     <AnastasisClientFrame hideNav title="Bug">
@@ -234,32 +234,6 @@ const AnastasisClientImpl: FunctionalComponent = () => {
   );
 };
 
-interface LabeledInputProps {
-  label: string;
-  grabFocus?: boolean;
-  bind: [string, (x: string) => void];
-}
-
-export function LabeledInput(props: LabeledInputProps): VNode {
-  const inputRef = useRef<HTMLInputElement>(null);
-  useLayoutEffect(() => {
-    if (props.grabFocus) {
-      inputRef.current?.focus();
-    }
-  }, [props.grabFocus]);
-  return (
-    <label>
-      {props.label}
-      <input
-        value={props.bind[0]}
-        onChange={(e) => props.bind[1]((e.target as HTMLInputElement).value)}
-        ref={inputRef}
-        style={{ display: "block" }}
-      />
-    </label>
-  );
-}
-
 /**
  * Show a dismissible error banner if there is a current error.
  */
diff --git a/packages/anastasis-webui/src/scss/_custom-calendar.scss 
b/packages/anastasis-webui/src/scss/_custom-calendar.scss
index 9ac877ce..bff68cf7 100644
--- a/packages/anastasis-webui/src/scss/_custom-calendar.scss
+++ b/packages/anastasis-webui/src/scss/_custom-calendar.scss
@@ -41,6 +41,10 @@
 }
 
 
+.home .datePicker div {
+  margin-top: 0px;
+  margin-bottom: 0px;
+}
 .datePicker {
   text-align: left;
   background: var(--primary-card-color);
diff --git a/packages/anastasis-webui/src/utils/index.tsx 
b/packages/anastasis-webui/src/utils/index.tsx
index d1d86146..670e229c 100644
--- a/packages/anastasis-webui/src/utils/index.tsx
+++ b/packages/anastasis-webui/src/utils/index.tsx
@@ -115,15 +115,9 @@ export const reducerStatesExample = {
   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,
@@ -136,6 +130,18 @@ export const reducerStatesExample = {
   challengeSolving: {...base,
     recovery_state: RecoveryStates.ChallengeSolving,
   } as ReducerState,
+  challengePaying: {...base,
+    recovery_state: RecoveryStates.ChallengePaying,
+  } as ReducerState,
+  recoveryAttributeEditing: {...base,
+    recovery_state: RecoveryStates.UserAttributesCollecting
+  } as ReducerState,
+  backupSelectCountry: {...base,
+    backup_state: BackupStates.CountrySelecting
+  } as ReducerState,
+  backupSelectContinent: {...base,
+    backup_state: BackupStates.ContinentSelecting,
+  } as ReducerState,
   secretEdition: {...base,
     backup_state: BackupStates.SecretEditing,
   } as ReducerState,
@@ -151,7 +157,7 @@ export const reducerStatesExample = {
   authEditing: {...base,
     backup_state: BackupStates.AuthenticationsEditing
   } as ReducerState,
-  attributeEditing: {...base,
+  backupAttributeEditing: {...base,
     backup_state: BackupStates.UserAttributesCollecting
   } as ReducerState,
   truthsPaying: {...base,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cd6d7ae5..283dafaf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -49,6 +49,7 @@ importers:
       bulma: ^0.9.3
       bulma-checkbox: ^1.1.1
       bulma-radio: ^1.1.1
+      date-fns: 2.22.1
       enzyme: ^3.11.0
       enzyme-adapter-preact-pure: ^3.1.0
       eslint: ^6.8.0
@@ -67,6 +68,7 @@ importers:
     dependencies:
       '@gnu-taler/taler-util': link:../taler-util
       anastasis-core: link:../anastasis-core
+      date-fns: 2.22.1
       jed: 1.1.1
       preact: 10.5.14
       preact-render-to-string: 5.1.19_preact@10.5.14
@@ -8821,7 +8823,7 @@ packages:
   /axios/0.21.1:
     resolution: {integrity: 
sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==}
     dependencies:
-      follow-redirects: 1.14.2
+      follow-redirects: 1.14.2_debug@4.3.2
     transitivePeerDependencies:
       - debug
 
@@ -10877,6 +10879,11 @@ packages:
       whatwg-url: 8.7.0
     dev: true
 
+  /date-fns/2.22.1:
+    resolution: {integrity: 
sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==}
+    engines: {node: '>=0.11'}
+    dev: false
+
   /date-fns/2.23.0:
     resolution: {integrity: 
sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA==}
     engines: {node: '>=0.11'}
@@ -12618,7 +12625,7 @@ packages:
       readable-stream: 2.3.7
     dev: true
 
-  /follow-redirects/1.14.2:
+  /follow-redirects/1.14.2_debug@4.3.2:
     resolution: {integrity: 
sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==}
     engines: {node: '>=4.0'}
     peerDependencies:
@@ -12626,6 +12633,8 @@ packages:
     peerDependenciesMeta:
       debug:
         optional: true
+    dependencies:
+      debug: 4.3.2_supports-color@6.1.0
 
   /for-each/0.3.3:
     resolution: {integrity: 
sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -13660,7 +13669,7 @@ packages:
     engines: {node: '>=8.0.0'}
     dependencies:
       eventemitter3: 4.0.7
-      follow-redirects: 1.14.2
+      follow-redirects: 1.14.2_debug@4.3.2
       requires-port: 1.0.0
     transitivePeerDependencies:
       - debug
@@ -17139,7 +17148,7 @@ packages:
     resolution: {integrity: 
sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==}
     engines: {node: '>=6'}
     dependencies:
-      ts-pnp: 1.2.0_typescript@3.9.10
+      ts-pnp: 1.2.0_typescript@4.4.3
     transitivePeerDependencies:
       - typescript
     dev: true
@@ -17157,7 +17166,7 @@ packages:
     resolution: {integrity: 
sha512-2Rb3vm+EXble/sMXNSu6eoBx8e79gKqhNq9F5ZWW6ERNCTE/Q0wQNne5541tE5vKjfM8hpNCYL+LGc1YTfI0dg==}
     engines: {node: '>=6'}
     dependencies:
-      ts-pnp: 1.2.0_typescript@4.3.5
+      ts-pnp: 1.2.0_typescript@4.4.3
     transitivePeerDependencies:
       - typescript
     dev: true
@@ -20910,7 +20919,7 @@ packages:
     resolution: {integrity: 
sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==}
     dev: true
 
-  /ts-pnp/1.2.0_typescript@3.9.10:
+  /ts-pnp/1.2.0_typescript@4.3.5:
     resolution: {integrity: 
sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==}
     engines: {node: '>=6'}
     peerDependencies:
@@ -20919,10 +20928,10 @@ packages:
       typescript:
         optional: true
     dependencies:
-      typescript: 3.9.10
+      typescript: 4.3.5
     dev: true
 
-  /ts-pnp/1.2.0_typescript@4.3.5:
+  /ts-pnp/1.2.0_typescript@4.4.3:
     resolution: {integrity: 
sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==}
     engines: {node: '>=6'}
     peerDependencies:
@@ -20931,7 +20940,7 @@ packages:
       typescript:
         optional: true
     dependencies:
-      typescript: 4.3.5
+      typescript: 4.4.3
     dev: true
 
   /tsconfig-paths/3.9.0:

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]