diff options
Diffstat (limited to 'packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx')
-rw-r--r-- | packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx | 150 |
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 +} |