aboutsummaryrefslogtreecommitdiff
path: root/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx')
-rw-r--r--packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx150
1 files changed, 122 insertions, 28 deletions
diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
index 2f804f940..f86994c97 100644
--- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
@@ -1,10 +1,13 @@
/* eslint-disable @typescript-eslint/camelcase */
-import { h, VNode } from "preact";
+import { UserAttributeSpec, validators } from "anastasis-core";
+import { Fragment, 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 { TextInput } from "../../components/fields/TextInput";
+import { DateInput } from "../../components/fields/DateInput";
+import { NumberInput } from "../../components/fields/NumberInput";
+import { isAfter, parse } from "date-fns";
export function AttributeEntryScreen(): VNode {
const reducer = useAnastasisContext()
@@ -18,48 +21,139 @@ export function AttributeEntryScreen(): VNode {
if (!reducer.currentReducerState || !("required_attributes" in reducer.currentReducerState)) {
return <div>invalid state</div>
}
-
+ const reqAttr = reducer.currentReducerState.required_attributes || []
+ let hasErrors = false;
+
+ const fieldList: VNode[] = reqAttr.map((spec, i: number) => {
+ const value = attrs[spec.name]
+ const error = checkIfValid(value, spec)
+ hasErrors = hasErrors || error !== undefined
+ return (
+ <AttributeEntryField
+ key={i}
+ isFirst={i == 0}
+ setValue={(v: string) => setAttrs({ ...attrs, [spec.name]: v })}
+ spec={spec}
+ errorMessage={error}
+ value={value} />
+ );
+ })
+
return (
<AnastasisClientFrame
- title={withProcessLabel(reducer, "Select Country")}
+ title={withProcessLabel(reducer, "Who are you?")}
+ hideNext={hasErrors ? "Complete the form." : undefined}
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" style={{ maxWidth: 'unset' }}>
+ <div class="column is-half">
+ {fieldList}
+ </div>
+ <div class="column is-is-half" >
+ <p>This personal information will help to locate your secret.</p>
+ <h1 class="title">This stays private</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;
+ errorMessage: string | undefined;
+}
+const possibleBirthdayYear: Array<number> = []
+for (let i = 0; i < 100; i++) {
+ possibleBirthdayYear.push(2020 - i)
}
+function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
-export function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
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}
+ years={possibleBirthdayYear}
+ error={props.errorMessage}
+ bind={[props.value, props.setValue]}
+ />}
+ {props.spec.type === 'number' &&
+ <NumberInput
+ grabFocus={props.isFirst}
+ label={props.spec.label}
+ error={props.errorMessage}
+ bind={[props.value, props.setValue]}
+ />
+ }
+ {props.spec.type === 'string' &&
+ <TextInput
+ grabFocus={props.isFirst}
+ label={props.spec.label}
+ error={props.errorMessage}
+ bind={[props.value, props.setValue]}
+ />
+ }
+ <div class="block">
+ This stays private
+ <span class="icon is-right">
+ <i class="mdi mdi-eye-off" />
+ </span>
+ </div>
</div>
);
}
+const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/
+
+
+function checkIfValid(value: string, spec: UserAttributeSpec): string | undefined {
+ const pattern = spec['validation-regex']
+ if (pattern) {
+ const re = new RegExp(pattern)
+ if (!re.test(value)) return 'The value is invalid'
+ }
+ const logic = spec['validation-logic']
+ if (logic) {
+ const func = (validators as any)[logic];
+ if (func && typeof func === 'function' && !func(value)) return 'Please check the value'
+ }
+ const optional = spec.optional
+ if (!optional && !value) {
+ return 'This value is required'
+ }
+ if ("date" === spec.type) {
+ if (!YEAR_REGEX.test(value)) {
+ return "The date doesn't follow the format"
+ }
+
+ try {
+ const v = parse(value, 'yyyy-MM-dd', new Date());
+ if (Number.isNaN(v.getTime())) {
+ return "Some numeric values seems out of range for a date"
+ }
+ if ("birthdate" === spec.name && isAfter(v, new Date())) {
+ return "A birthdate cannot be in the future"
+ }
+ } catch (e) {
+ return "Could not parse the date"
+ }
+ }
+ return undefined
+}