/* This file is part of GNU Taler (C) 2022-2024 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 */ import { AbsoluteTime, AccessToken, Amounts, HttpStatusCode, KycRequirementInformation, assertUnreachable, } from "@gnu-taler/taler-util"; import { Button, FormMetadata, InternationalizationAPI, LocalNotificationBanner, RenderAllFieldsByUiConfig, UIFormElementConfig, UIHandlerId, convertUiField, getConverterById, useExchangeApiContext, useLocalNotificationHandler, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { preloadedForms } from "../forms/index.js"; import { FormErrors, useFormState, validateRequiredFields, } from "../hooks/form.js"; import { undefinedIfEmpty } from "./Start.js"; import { useUiFormsContext } from "../context/ui-forms.js"; type Props = { token: AccessToken; formId: string; requirement: KycRequirementInformation; onComplete: () => void; }; type FormType = { // state: TalerExchangeApi.AmlState; }; type KycFormMetadata = { id: string; version: number; when: AbsoluteTime; }; type KycForm = { header: KycFormMetadata; payload: object; }; export function FillForm({ token, formId, requirement, onComplete, }: Props): VNode { const { i18n } = useTranslationContext(); const { config, lib } = useExchangeApiContext(); // const { forms } = useUiFormsContext(); const [notification, withErrorHandler] = useLocalNotificationHandler(); const customForm = requirement.context && "form" in requirement.context ? ({ id: (requirement.context.form as any).id, config: requirement.context.form, label: "Officer defined form", version: 1, } as FormMetadata) : undefined; const { forms } = useUiFormsContext(); const allForms = customForm ? [...forms, customForm] : forms const theForm = searchForm( i18n, allForms, formId, requirement.context, ); if (!theForm) { return
form with id {formId} not found
; } const reqId = requirement.id; if (!reqId) { return
no id for this form, can't upload
; } const shape: Array = []; const requiredFields: Array = []; theForm.config.design.forEach((section) => { Array.prototype.push.apply(shape, getShapeFromFields(section.fields)); Array.prototype.push.apply( requiredFields, getRequiredFields(section.fields), ); }); const [form, state] = useFormState(shape, {}, (st) => { const partialErrors = undefinedIfEmpty>({}); const errors = undefinedIfEmpty | undefined>( validateRequiredFields(partialErrors, st, requiredFields), ); if (errors === undefined) { return { status: "ok", result: st as any, errors: undefined, }; } return { status: "fail", result: st as any, errors, }; }); const validatedForm = state.status !== "ok" ? undefined : state.result; const submitHandler = validatedForm === undefined ? undefined : withErrorHandler( async () => { const information: KycForm = { header: { id: theForm.id, version: theForm.version, when: AbsoluteTime.now(), }, payload: validatedForm, }; // const data = new FormData() // data.set("header", JSON.stringify(information.header)) // data.set("payload", JSON.stringify(information.payload)) const data = new URLSearchParams(); Object.entries(validatedForm as Record).forEach( ([key, value]) => { if (key === "money") { data.set(key, Amounts.stringify(value)); } else { data.set(key, value); } }, ); return lib.exchange.uploadKycForm(reqId, data); }, (res) => { onComplete(); }, (fail) => { switch (fail.case) { case HttpStatusCode.PayloadTooLarge: return i18n.str`The form is too big for the server, try uploading smaller files.`; case HttpStatusCode.NotFound: return i18n.str`The account was not found`; case HttpStatusCode.Conflict: return i18n.str`Officer disabled or more recent decision was already submitted.`; default: assertUnreachable(fail); } }, ); return (
{theForm.config.design.map((section, i) => { if (!section) return ; return (

{section.title}

{section.description && (

{section.description}

)}
); })}
); } function getRequiredFields(fields: UIFormElementConfig[]): Array { const shape: Array = []; fields.forEach((field) => { if ("id" in field) { // FIXME: this should be a validation when loading the form // consistency check if (shape.indexOf(field.id) !== -1) { throw Error(`already present: ${field.id}`); } if (!field.required) { return; } shape.push(field.id); } else if (field.type === "group") { Array.prototype.push.apply(shape, getRequiredFields(field.fields)); } }); return shape; } function getShapeFromFields(fields: UIFormElementConfig[]): Array { const shape: Array = []; fields.forEach((field) => { if ("id" in field) { // FIXME: this should be a validation when loading the form // consistency check if (shape.indexOf(field.id) !== -1) { throw Error(`already present: ${field.id}`); } shape.push(field.id); } else if (field.type === "group") { Array.prototype.push.apply(shape, getShapeFromFields(field.fields)); } }); return shape; } function searchForm( i18n: InternationalizationAPI, forms: FormMetadata[], formId: string, context: object | undefined, ): FormMetadata | undefined { { const found = forms.find((v) => v.id === formId); if (found) return found; } { const pf = preloadedForms(i18n, context); const found = pf.find((v) => v.id === formId); if (found) return found; } return undefined; }