aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/anastasis-core/src/index.ts1
-rw-r--r--packages/anastasis-core/src/reducer-types.ts3
-rw-r--r--packages/anastasis-core/src/validators.ts28
-rw-r--r--packages/anastasis-webui/.storybook/preview.js6
-rw-r--r--packages/anastasis-webui/package.json1
-rw-r--r--packages/anastasis-webui/src/components/fields/DateInput.tsx63
-rw-r--r--packages/anastasis-webui/src/components/fields/LabeledInput.tsx40
-rw-r--r--packages/anastasis-webui/src/components/menu/SideBar.tsx18
-rw-r--r--packages/anastasis-webui/src/components/picker/DatePicker.tsx324
-rw-r--r--packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx50
-rw-r--r--packages/anastasis-webui/src/components/picker/DurationPicker.tsx154
-rw-r--r--packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx67
-rw-r--r--packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx112
-rw-r--r--packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx3
-rw-r--r--packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx2
-rw-r--r--packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx3
-rw-r--r--packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx5
-rw-r--r--packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx5
-rw-r--r--packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx35
-rw-r--r--packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx38
-rw-r--r--packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx33
-rw-r--r--packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx7
-rw-r--r--packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx4
-rw-r--r--packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx3
-rw-r--r--packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx14
-rw-r--r--packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx5
-rw-r--r--packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx5
-rw-r--r--packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx5
-rw-r--r--packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx5
-rw-r--r--packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx5
-rw-r--r--packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx5
-rw-r--r--packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx3
-rw-r--r--packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx3
-rw-r--r--packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx3
-rw-r--r--packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx5
-rw-r--r--packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx3
-rw-r--r--packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx3
-rw-r--r--packages/anastasis-webui/src/pages/home/StartScreen.tsx15
-rw-r--r--packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx5
-rw-r--r--packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx2
-rw-r--r--packages/anastasis-webui/src/pages/home/index.tsx42
-rw-r--r--packages/anastasis-webui/src/scss/_custom-calendar.scss4
-rw-r--r--packages/anastasis-webui/src/utils/index.tsx20
43 files changed, 1030 insertions, 127 deletions
diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts
index b4e911ffb..c9e2bcf36 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 1a443bf9b..2e1154fca 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 000000000..1c04bfdb3
--- /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 7cb9405ba..9ab4d9404 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 57cfdd8d4..4cdb00243 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 000000000..c45acc6d2
--- /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 000000000..96d634a4f
--- /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 df582a5d0..12223d473 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 000000000..e51b3db68
--- /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/components/picker/DurationPicker.stories.tsx b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx
new file mode 100644
index 000000000..275c80fa6
--- /dev/null
+++ b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx
@@ -0,0 +1,50 @@
+/*
+ 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, FunctionalComponent } from 'preact';
+import { useState } from 'preact/hooks';
+import { DurationPicker as TestedComponent } from './DurationPicker';
+
+
+export default {
+ title: 'Components/Picker/Duration',
+ component: TestedComponent,
+ argTypes: {
+ 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, {
+ 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 000000000..235a63e2d
--- /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 d28a6df43..d9be48fb4 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 2f804f940..3b39cf9c4 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 9567e0ef7..5243c5259 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 55e37a968..1c2a9a92e 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 7699cdf34..c2bd24ef9 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 44d3795b2..8f86831a9 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 65a2b7e13..0c9d007bc 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 4f186c031..def44c5a6 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/ChallengePayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
new file mode 100644
index 000000000..e5fe09e99
--- /dev/null
+++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
@@ -0,0 +1,38 @@
+/*
+ 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 { ChallengePayingScreen as TestedComponent } from './ChallengePayingScreen';
+
+
+export default {
+ title: 'Pages/recovery/__ChallengePayingScreen',
+ component: TestedComponent,
+ args: {
+ order: 10,
+ },
+ argTypes: {
+ onUpdate: { action: 'onUpdate' },
+ onBack: { action: 'onBack' },
+ },
+};
+
+export const Example = createExample(TestedComponent, reducerStatesExample.challengePaying);
diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx
new file mode 100644
index 000000000..d87afdf46
--- /dev/null
+++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx
@@ -0,0 +1,33 @@
+import { h, VNode } from "preact";
+import { useAnastasisContext } from "../../context/anastasis";
+import { AnastasisClientFrame } from "./index";
+
+export function ChallengePayingScreen(): 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 payments = ['']; //reducer.currentReducerState.payments ??
+ return (
+ <AnastasisClientFrame
+ hideNext
+ title="Recovery: Challenge Paying"
+ >
+ <p>
+ Some of the providers require a payment to store the encrypted
+ authentication information.
+ </p>
+ <ul>
+ {payments.map((x, i) => {
+ return <li key={i}>{x}</li>;
+ })}
+ </ul>
+ <button onClick={() => reducer.transition("pay", {})}>
+ Check payment status now
+ </button>
+ </AnastasisClientFrame>
+ );
+}
diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
index aad37cd7f..8744a2b79 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 ad529a4a7..94c0409da 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 adf36980f..3a642748a 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 555622c1d..417c08633 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 1a9462b88..e952ab28d 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 0c1842420..b5933db17 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 b52699e7b..91855b023 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 18560356a..49dd8fca8 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 a5235d66c..f5fd7c0d1 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 e9c597023..f56939090 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 2c27895c2..0d70405e5 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 1a824acb8..22b8d470b 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 72dadbe89..319289381 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 69af9be42..c05c36b07 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 163e0d1f3..c4cf3a680 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 ad84cd8f2..657a2dd74 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 6625ec5b8..c751ad9e4 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 e2f3d521e..7568ccd69 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 319f590a0..098a8ba8d 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 4cec47ec8..5cef4ee9c 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 9ac877ce0..bff68cf79 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 d1d861469..670e229cd 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,