aboutsummaryrefslogtreecommitdiff
path: root/packages/aml-backoffice-ui/src/context/ui-forms.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/aml-backoffice-ui/src/context/ui-forms.ts')
-rw-r--r--packages/aml-backoffice-ui/src/context/ui-forms.ts507
1 files changed, 507 insertions, 0 deletions
diff --git a/packages/aml-backoffice-ui/src/context/ui-forms.ts b/packages/aml-backoffice-ui/src/context/ui-forms.ts
new file mode 100644
index 000000000..2e0b8a76d
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/context/ui-forms.ts
@@ -0,0 +1,507 @@
+/*
+ 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 <http://www.gnu.org/licenses/>
+ */
+
+import {
+ buildCodecForObject,
+ buildCodecForUnion,
+ Codec,
+ codecForBoolean,
+ codecForConstString,
+ codecForList,
+ codecForNumber,
+ codecForString,
+ codecForTimestamp,
+ codecOptional,
+ Integer,
+ TalerProtocolTimestamp,
+} from "@gnu-taler/taler-util";
+import { ComponentChildren, createContext, h, VNode } from "preact";
+import { useContext } from "preact/hooks";
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+export type Type = UiForms;
+
+const defaultForms: UiForms = {
+ forms: []
+};
+const Context = createContext<Type>(defaultForms);
+
+export type BaseForm = Record<string, unknown>;
+
+export const useUiFormsContext = (): Type => useContext(Context);
+
+export const UiFormsProvider = ({
+ children,
+ value,
+}: {
+ value: UiForms;
+ children: ComponentChildren;
+}): VNode => {
+ return h(Context.Provider, {
+ value,
+ children,
+ });
+};
+
+export type FormMetadata = {
+ label: string;
+ id: string;
+ version: number;
+ config: FlexibleForm;
+};
+
+type FlexibleForm = DoubleColumnForm;
+
+export interface DoubleColumnForm {
+ type: "double-column";
+ design: Array<DoubleColumnFormSection>;
+ // behavior?: (form: Partial<T>) => FormState<T>;
+}
+
+export type DoubleColumnFormSection = {
+ title: string;
+ description?: string;
+ fields: UIFormFieldConfig[];
+};
+
+// export interface BaseForm {
+// state: TalerExchangeApi.AmlState;
+// threshold: AmountJson;
+// }
+
+export interface UiForms {
+ // Where libeufin backend is localted
+ // default: window.origin without "webui/"
+ forms: Array<FormMetadata>;
+}
+
+export type UIFormFieldConfig =
+ | UIFormFieldConfigAbsoluteTime
+ | UIFormFieldConfigAmount
+ | UIFormFieldConfigArray
+ | UIFormFieldConfigCaption
+ | UIFormFieldConfigChoiseHorizontal
+ | UIFormFieldConfigChoiseStacked
+ | UIFormFieldConfigFile
+ | UIFormFieldConfigGroup
+ | UIFormFieldConfigInteger
+ | UIFormFieldConfigSelectMultiple
+ | UIFormFieldConfigSelectOne
+ | UIFormFieldConfigText
+ | UIFormFieldConfigTextArea
+ | UIFormFieldConfigToggle;
+
+type UIFormFieldConfigAbsoluteTime = {
+ type: "absoluteTime";
+ properties: UIFormFieldBaseConfig & {
+ max?: TalerProtocolTimestamp;
+ min?: TalerProtocolTimestamp;
+ pattern: string;
+ };
+};
+
+type UIFormFieldConfigAmount = {
+ type: "amount";
+ properties: UIFormFieldBaseConfig & {
+ max?: Integer;
+ min?: Integer;
+ currency: string;
+ };
+};
+
+type UIFormFieldConfigArray = {
+ type: "array";
+ properties: UIFormFieldBaseConfig & {
+ // id of the field shown when the array is collapsed
+ labelFieldId: UIHandlerId;
+ fields: UIFormFieldConfig[];
+ };
+};
+
+type UIFormFieldConfigCaption = {
+ type: "caption";
+ properties: UIFieldBaseDescription;
+};
+
+type UIFormFieldConfigGroup = {
+ type: "group";
+ properties: UIFormFieldBaseConfig & {
+ fields: UIFormFieldConfig[];
+ };
+};
+
+type UIFormFieldConfigChoiseHorizontal = {
+ type: "choiceHorizontal";
+ properties: UIFormFieldBaseConfig & {
+ choices: Array<SelectUiChoice>;
+ };
+};
+
+type UIFormFieldConfigChoiseStacked = {
+ type: "choiceStacked";
+ properties: UIFormFieldBaseConfig & {
+ choices: Array<SelectUiChoice>;
+ };
+};
+
+type UIFormFieldConfigFile = {
+ type: "file";
+ properties: UIFormFieldBaseConfig & {
+ maxBytes?: Integer;
+ minBytes?: Integer;
+ // comma-separated list of one or more file types
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers
+ accept?: string;
+ };
+};
+type UIFormFieldConfigInteger = {
+ type: "integer";
+ properties: UIFormFieldBaseConfig & {
+ max?: Integer;
+ min?: Integer;
+ };
+};
+
+interface SelectUiChoice {
+ label: string;
+ description?: string;
+ value: string;
+}
+
+type UIFormFieldConfigSelectMultiple = {
+ type: "selectMultiple";
+ properties: UIFormFieldBaseConfig & {
+ max?: Integer;
+ min?: Integer;
+ unique?: boolean;
+ choices: Array<SelectUiChoice>;
+ };
+};
+type UIFormFieldConfigSelectOne = {
+ type: "selectOne";
+ properties: UIFormFieldBaseConfig & {
+ choices: Array<SelectUiChoice>;
+ };
+};
+type UIFormFieldConfigText = {
+ type: "text";
+ properties: UIFormFieldBaseConfig;
+};
+type UIFormFieldConfigTextArea = {
+ type: "textArea";
+ properties: UIFormFieldBaseConfig;
+};
+type UIFormFieldConfigToggle = {
+ type: "toggle";
+ properties: UIFormFieldBaseConfig;
+};
+
+type UIFieldBaseDescription = {
+ /* label if the field, visible for the user */
+ label: string;
+ /* long text to be shown on user demand */
+ tooltip?: string;
+
+ /* short text to be shown close to the field */
+ help?: string;
+
+ /* if the field should be initialy hidden */
+ hidden?: boolean;
+ /* ui element to show before */
+ addonBeforeId?: string;
+ /* ui element to show after */
+ addonAfterId?: string;
+};
+
+type UIFormFieldBaseConfig = UIFieldBaseDescription & {
+ /* example to be shown inside the field */
+ placeholder?: string;
+
+ /* show a mark as required */
+ required?: boolean;
+
+ /* readonly and dim */
+ disabled?: boolean;
+
+ /* name of the field, useful for a11y */
+ name: string;
+
+ /* conversion id to conver the string into the value type
+ the id should be known to the ui impl
+ */
+ converterId?: string;
+
+ /* property id of the form */
+ id: UIHandlerId;
+};
+
+declare const __handlerId: unique symbol;
+export type UIHandlerId = string & { [__handlerId]: true };
+
+// FIXME: validate well formed ui field id
+const codecForUiFieldId = codecForString as () => Codec<UIHandlerId>;
+
+const codecForUIFormFieldBaseConfigTemplate = <
+ T extends UIFormFieldBaseConfig,
+>() =>
+ buildCodecForObject<T>()
+ .property("id", codecForUiFieldId())
+ .property("addonAfterId", codecOptional(codecForString()))
+ .property("addonBeforeId", codecOptional(codecForString()))
+ .property("converterId", codecOptional(codecForString()))
+ .property("disabled", codecOptional(codecForBoolean()))
+ .property("hidden", codecOptional(codecForBoolean()))
+ .property("required", codecOptional(codecForBoolean()))
+ .property("help", codecOptional(codecForString()))
+ .property("label", codecForString())
+ .property("name", codecForString())
+ .property("placeholder", codecOptional(codecForString()))
+ .property("tooltip", codecOptional(codecForString()));
+
+const codecForUIFormFieldBaseConfig = (): Codec<UIFormFieldBaseConfig> =>
+ codecForUIFormFieldBaseConfigTemplate().build("UIFieldToggleProperties");
+
+const codecForUIFormFieldAbsoluteTimeConfig = (): Codec<
+ UIFormFieldConfigAbsoluteTime["properties"]
+> =>
+ codecForUIFormFieldBaseConfigTemplate<
+ UIFormFieldConfigAbsoluteTime["properties"]
+ >()
+ .property("pattern", codecForString())
+ .property("max", codecOptional(codecForTimestamp))
+ .property("min", codecOptional(codecForTimestamp))
+ .build("UIFormFieldConfigAbsoluteTime.properties");
+
+const codecForUiFormFieldAbsoluteTime =
+ (): Codec<UIFormFieldConfigAbsoluteTime> =>
+ buildCodecForObject<UIFormFieldConfigAbsoluteTime>()
+ .property("type", codecForConstString("absoluteTime"))
+ .property("properties", codecForUIFormFieldAbsoluteTimeConfig())
+ .build("UIFormFieldConfigAbsoluteTime");
+
+const codecForUIFormFieldAmountConfig = (): Codec<
+ UIFormFieldConfigAmount["properties"]
+> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldConfigAmount["properties"]>()
+ .property("currency", codecForString())
+ .property("max", codecOptional(codecForNumber()))
+ .property("min", codecOptional(codecForNumber()))
+ .build("UIFormFieldConfigAmount.properties");
+
+const codecForUiFormFieldAmount = (): Codec<UIFormFieldConfigAmount> =>
+ buildCodecForObject<UIFormFieldConfigAmount>()
+ .property("type", codecForConstString("amount"))
+ .property("properties", codecForUIFormFieldAmountConfig())
+ .build("UIFormFieldConfigAmount");
+
+const codecForUIFormFieldArrayConfig = (): Codec<
+ UIFormFieldConfigArray["properties"]
+> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldConfigArray["properties"]>()
+ .property("labelFieldId", codecForUiFieldId())
+ .property("fields", codecForList(codecForUiFormField()))
+ .build("UIFormFieldConfigArray.properties");
+
+const codecForUiFormFieldArray = (): Codec<UIFormFieldConfigArray> =>
+ buildCodecForObject<UIFormFieldConfigArray>()
+ .property("type", codecForConstString("array"))
+ .property("properties", codecForUIFormFieldArrayConfig())
+ .build("UIFormFieldConfigArray");
+
+const codecForUiFormFieldCaption = (): Codec<UIFormFieldConfigCaption> =>
+ buildCodecForObject<UIFormFieldConfigCaption>()
+ .property("type", codecForConstString("caption"))
+ .property("properties", codecForUIFormFieldBaseConfig())
+ .build("UIFormFieldConfigCaption");
+
+const codecForUiFormSelectUiChoice = (): Codec<SelectUiChoice> =>
+ buildCodecForObject<SelectUiChoice>()
+ .property("description", codecForString())
+ .property("label", codecForString())
+ .property("value", codecForString())
+ .build("SelectUiChoice");
+
+const codecForUIFormFieldWithChoiseConfig = (): Codec<
+ UIFormFieldConfigChoiseHorizontal["properties"]
+> =>
+ codecForUIFormFieldBaseConfigTemplate<
+ UIFormFieldConfigChoiseHorizontal["properties"]
+ >()
+ .property("choices", codecForList(codecForUiFormSelectUiChoice()))
+ .build("UIFormFieldConfigChoiseHorizontal.properties");
+
+const codecForUiFormFieldChoiceHorizontal =
+ (): Codec<UIFormFieldConfigChoiseHorizontal> =>
+ buildCodecForObject<UIFormFieldConfigChoiseHorizontal>()
+ .property("type", codecForConstString("choiceHorizontal"))
+ .property("properties", codecForUIFormFieldWithChoiseConfig())
+ .build("UIFormFieldConfigChoiseHorizontal");
+
+const codecForUiFormFieldChoiceStacked =
+ (): Codec<UIFormFieldConfigChoiseStacked> =>
+ buildCodecForObject<UIFormFieldConfigChoiseStacked>()
+ .property("type", codecForConstString("choiceStacked"))
+ .property("properties", codecForUIFormFieldWithChoiseConfig())
+ .build("UIFormFieldConfigChoiseStacked");
+
+const codecForUiFormFieldFile = (): Codec<UIFormFieldConfigFile> =>
+ buildCodecForObject<UIFormFieldConfigFile>()
+ .property("type", codecForConstString("file"))
+ .property("properties", codecForUIFormFieldBaseConfig())
+ .build("UIFormFieldConfigFile");
+
+const codecForUIFormFieldWithFieldsConfig = (): Codec<
+ UIFormFieldConfigGroup["properties"]
+> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldConfigGroup["properties"]>()
+ .property("fields", codecForList(codecForUiFormField()))
+ .build("UIFormFieldConfigGroup.properties");
+
+const codecForUiFormFieldGroup = (): Codec<UIFormFieldConfigGroup> =>
+ buildCodecForObject<UIFormFieldConfigGroup>()
+ .property("type", codecForConstString("group"))
+ .property("properties", codecForUIFormFieldWithFieldsConfig())
+ .build("UiFormFieldGroup");
+
+const codecForUiFormFieldInteger = (): Codec<UIFormFieldConfigInteger> =>
+ buildCodecForObject<UIFormFieldConfigInteger>()
+ .property("type", codecForConstString("integer"))
+ .property("properties", codecForUIFormFieldBaseConfig())
+ .build("UIFormFieldConfigInteger");
+
+const codecForUIFormFieldSelectMultipleConfig = (): Codec<
+ UIFormFieldConfigSelectMultiple["properties"]
+> =>
+ codecForUIFormFieldBaseConfigTemplate<
+ UIFormFieldConfigSelectMultiple["properties"]
+ >()
+ .property("max", codecOptional(codecForNumber()))
+ .property("min", codecOptional(codecForNumber()))
+ .property("unique", codecOptional(codecForBoolean()))
+ .property("choices", codecForList(codecForUiFormSelectUiChoice()))
+ .build("UIFormFieldConfigSelectMultiple.properties");
+
+const codecForUiFormFieldSelectMultiple =
+ (): Codec<UIFormFieldConfigSelectMultiple> =>
+ buildCodecForObject<UIFormFieldConfigSelectMultiple>()
+ .property("type", codecForConstString("selectMultiple"))
+ .property("properties", codecForUIFormFieldSelectMultipleConfig())
+ .build("UiFormFieldSelectMultiple");
+
+const codecForUiFormFieldSelectOne = (): Codec<UIFormFieldConfigSelectOne> =>
+ buildCodecForObject<UIFormFieldConfigSelectOne>()
+ .property("type", codecForConstString("selectOne"))
+ .property("properties", codecForUIFormFieldWithChoiseConfig())
+ .build("UIFormFieldConfigSelectOne");
+
+const codecForUiFormFieldText = (): Codec<UIFormFieldConfigText> =>
+ buildCodecForObject<UIFormFieldConfigText>()
+ .property("type", codecForConstString("text"))
+ .property("properties", codecForUIFormFieldBaseConfig())
+ .build("UIFormFieldConfigText");
+
+const codecForUiFormFieldTextArea = (): Codec<UIFormFieldConfigTextArea> =>
+ buildCodecForObject<UIFormFieldConfigTextArea>()
+ .property("type", codecForConstString("textArea"))
+ .property("properties", codecForUIFormFieldBaseConfig())
+ .build("UIFormFieldConfigTextArea");
+
+const codecForUiFormFieldToggle = (): Codec<UIFormFieldConfigToggle> =>
+ buildCodecForObject<UIFormFieldConfigToggle>()
+ .property("type", codecForConstString("toggle"))
+ .property("properties", codecForUIFormFieldBaseConfig())
+ .build("UIFormFieldConfigToggle");
+
+const codecForUiFormField = (): Codec<UIFormFieldConfig> =>
+ buildCodecForUnion<UIFormFieldConfig>()
+ .discriminateOn("type")
+ .alternative("absoluteTime", codecForUiFormFieldAbsoluteTime())
+ .alternative("amount", codecForUiFormFieldAmount())
+ .alternative("array", codecForUiFormFieldArray())
+ .alternative("caption", codecForUiFormFieldCaption())
+ .alternative("choiceHorizontal", codecForUiFormFieldChoiceHorizontal())
+ .alternative("choiceStacked", codecForUiFormFieldChoiceStacked())
+ .alternative("file", codecForUiFormFieldFile())
+ .alternative("group", codecForUiFormFieldGroup())
+ .alternative("integer", codecForUiFormFieldInteger())
+ .alternative("selectMultiple", codecForUiFormFieldSelectMultiple())
+ .alternative("selectOne", codecForUiFormFieldSelectOne())
+ .alternative("text", codecForUiFormFieldText())
+ .alternative("textArea", codecForUiFormFieldTextArea())
+ .alternative("toggle", codecForUiFormFieldToggle())
+ .build("UIFormField");
+
+const codecForDoubleColumnFormSection = (): Codec<DoubleColumnFormSection> =>
+ buildCodecForObject<DoubleColumnFormSection>()
+ .property("title", codecForString())
+ .property("description", codecForString())
+ .property("fields", codecForList(codecForUiFormField()))
+ .build("DoubleColumnFormSection");
+
+const codecForDoubleColumnForm = (): Codec<DoubleColumnForm> =>
+ buildCodecForObject<DoubleColumnForm>()
+ .property("type", codecForConstString("double-column"))
+ .property("design", codecForList(codecForDoubleColumnFormSection()))
+ .build("DoubleColumnForm");
+
+const codecForFlexibleForm = (): Codec<FlexibleForm> =>
+ buildCodecForUnion<FlexibleForm>()
+ .discriminateOn("type")
+ .alternative("double-column", codecForDoubleColumnForm())
+ .build<FlexibleForm>("FlexibleForm");
+
+const codecForFormMetadata = (): Codec<FormMetadata> =>
+ buildCodecForObject<FormMetadata>()
+ .property("label", codecForString())
+ .property("id", codecForString())
+ .property("version", codecForNumber())
+ .property("config", codecForFlexibleForm())
+ .build("FormMetadata");
+
+const codecForUIForms = (): Codec<UiForms> =>
+ buildCodecForObject<UiForms>()
+ .property("forms", codecForList(codecForFormMetadata()))
+ .build("UiForms");
+
+function removeUndefineField<T extends object>(obj: T): T {
+ const keys = Object.keys(obj) as Array<keyof T>;
+ return keys.reduce((prev, cur) => {
+ if (typeof prev[cur] === "undefined") {
+ delete prev[cur];
+ }
+ return prev;
+ }, obj);
+}
+
+export function fetchUiForms(listener: (s: UiForms) => void): void {
+ fetch("./forms.json")
+ .then((resp) => resp.json())
+ .then((json) => codecForUIForms().decode(json))
+ .then((result) =>
+ listener({
+ ...defaultForms,
+ ...removeUndefineField(result),
+ }),
+ )
+ .catch((e) => {
+ console.log("failed to fetch forms", e);
+ listener(defaultForms);
+ });
+}