/*
This file is part of GNU Anastasis
(C) 2021-2022 Anastasis SARL
GNU Anastasis is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Anastasis 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
GNU Anastasis; see the file COPYING. If not, see
*/
import { UserAttributeSpec, validators } from "@gnu-taler/anastasis-core";
import { isAfter, parse } from "date-fns";
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
import { DateInput } from "../../components/fields/DateInput.js";
import { PhoneNumberInput } from "../../components/fields/NumberInput.js";
import { TextInput } from "../../components/fields/TextInput.js";
import { useAnastasisContext } from "../../context/anastasis.js";
import { ConfirmModal } from "./ConfirmModal.js";
import { AnastasisClientFrame, withProcessLabel } from "./index.js";
export function AttributeEntryScreen(): VNode {
const reducer = useAnastasisContext();
const state = reducer?.currentReducerState;
const currentIdentityAttributes =
state && "identity_attributes" in state
? state.identity_attributes || {}
: {};
const [attrs, setAttrs] = useState>(
currentIdentityAttributes,
);
const isBackup = state?.reducer_type === "backup";
const [askUserIfSure, setAskUserIfSure] = useState(false);
if (!reducer) {
return
no reducer in context
;
}
if (
!reducer.currentReducerState ||
!("required_attributes" in reducer.currentReducerState)
) {
return
invalid state
;
}
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);
function addAutocomplete(newValue: string): string {
const ac = spec.autocomplete;
if (!ac || ac.length <= newValue.length || ac[newValue.length] === "?")
return newValue;
if (!value || newValue.length < value.length) {
return newValue.slice(0, -1);
}
return newValue + ac[newValue.length];
}
hasErrors = hasErrors || error !== undefined;
return (
setAttrs({ ...attrs, [spec.name]: addAutocomplete(v) })
}
spec={spec}
errorMessage={error}
onConfirm={() => {
if (!hasErrors) {
setAskUserIfSure(true);
}
}}
value={value}
/>
);
});
const doConfirm = async () => {
await reducer.transition("enter_user_attributes", {
identity_attributes: {
application_id: "anastasis-standalone",
...attrs,
},
});
};
return (
(isBackup ? setAskUserIfSure(true) : doConfirm())}
>
{askUserIfSure ? (
setAskUserIfSure(false)}
description="The values in the form must be correct"
label="I am sure"
cancelLabel="Wait, I want to check"
onConfirm={() => doConfirm().then(() => setAskUserIfSure(false))}
>
You personal information is used to define the location where your
secret will be safely stored. If you forget what you have entered or
if there is a misspell you will be unable to recover your secret.
) : null}
{fieldList}
This personal information will help to locate your secret.
This field is case-sensitive. You must enter exactly the same value
during recovery.
)}
{props.spec.name === "full_name" && (
If possible, use "LASTNAME, Firstname(s)" without
abbreviations.
)}
This stays private
);
}
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;
}