aboutsummaryrefslogtreecommitdiff
path: root/packages/exchange-backoffice-ui/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'packages/exchange-backoffice-ui/src/handlers')
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/Caption.tsx35
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/FormProvider.tsx99
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/Group.tsx41
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputAmount.tsx34
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputArray.tsx183
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx86
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx111
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputDate.tsx37
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputFile.tsx101
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputInteger.tsx23
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputLine.tsx282
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx151
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx134
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputText.tsx8
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputTextArea.tsx8
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/forms.ts135
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/useField.ts93
17 files changed, 0 insertions, 1561 deletions
diff --git a/packages/exchange-backoffice-ui/src/handlers/Caption.tsx b/packages/exchange-backoffice-ui/src/handlers/Caption.tsx
deleted file mode 100644
index fbf154d89..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/Caption.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { VNode, h } from "preact";
-import {
- IconAddon,
- InputLine,
- LabelWithTooltipMaybeRequired,
- UIFormProps,
-} from "./InputLine.js";
-import { TranslatedString } from "@gnu-taler/taler-util";
-
-interface Props {
- label: TranslatedString;
- tooltip?: TranslatedString;
- help?: TranslatedString;
- before?: VNode;
- after?: VNode;
-}
-
-export function Caption({ before, after, label, tooltip, help }: Props): VNode {
- return (
- <div class="sm:col-span-6 flex">
- {before !== undefined && (
- <span class="pointer-events-none flex items-center pr-2">{before}</span>
- )}
- <LabelWithTooltipMaybeRequired label={label} tooltip={tooltip} />
- {after !== undefined && (
- <span class="pointer-events-none flex items-center pl-2">{after}</span>
- )}
- {help && (
- <p class="mt-2 text-sm text-gray-500" id="email-description">
- {help}
- </p>
- )}
- </div>
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/FormProvider.tsx b/packages/exchange-backoffice-ui/src/handlers/FormProvider.tsx
deleted file mode 100644
index a195c2051..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/FormProvider.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import {
- AbsoluteTime,
- AmountJson,
- TranslatedString,
-} from "@gnu-taler/taler-util";
-import { ComponentChildren, VNode, createContext, h } from "preact";
-import {
- MutableRef,
- StateUpdater,
- useEffect,
- useRef,
- useState,
-} from "preact/hooks";
-
-export interface FormType<T> {
- value: MutableRef<Partial<T>>;
- initialValue?: Partial<T>;
- onUpdate?: StateUpdater<T>;
- computeFormState?: (v: T) => FormState<T>;
-}
-
-//@ts-ignore
-export const FormContext = createContext<FormType<any>>({});
-
-export type FormState<T> = {
- [field in keyof T]?: T[field] extends AbsoluteTime
- ? Partial<InputFieldState>
- : T[field] extends AmountJson
- ? Partial<InputFieldState>
- : T[field] extends Array<infer P>
- ? Partial<InputArrayFieldState<P>>
- : T[field] extends object
- ? FormState<T[field]>
- : Partial<InputFieldState>;
-};
-
-export interface InputFieldState {
- /* should show the error */
- error?: TranslatedString;
- /* should not allow to edit */
- readonly: boolean;
- /* should show as disable */
- disabled: boolean;
- /* should not show */
- hidden: boolean;
-}
-
-export interface InputArrayFieldState<T> extends InputFieldState {
- elements: FormState<T>[];
-}
-
-export function FormProvider<T>({
- children,
- initialValue,
- onUpdate: notify,
- onSubmit,
- computeFormState,
-}: {
- initialValue?: Partial<T>;
- onUpdate?: (v: Partial<T>) => void;
- onSubmit?: (v: Partial<T>, s: FormState<T> | undefined) => void;
- computeFormState?: (v: Partial<T>) => FormState<T>;
- children: ComponentChildren;
-}): VNode {
- // const value = useRef(initialValue ?? {});
- // useEffect(() => {
- // return function onUnload() {
- // value.current = initialValue ?? {};
- // };
- // });
- // const onUpdate = notify
- const [state, setState] = useState<Partial<T>>(initialValue ?? {});
- const value = { current: state };
- // console.log("RENDER", initialValue, value);
- const onUpdate = (v: typeof state) => {
- // console.log("updated");
- setState(v);
- if (notify) notify(v);
- };
- return (
- <FormContext.Provider
- value={{ initialValue, value, onUpdate, computeFormState }}
- >
- <form
- onSubmit={(e) => {
- e.preventDefault();
- //@ts-ignore
- if (onSubmit)
- onSubmit(
- value.current,
- !computeFormState ? undefined : computeFormState(value.current),
- );
- }}
- >
- {children}
- </form>
- </FormContext.Provider>
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/Group.tsx b/packages/exchange-backoffice-ui/src/handlers/Group.tsx
deleted file mode 100644
index 0645f6d97..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/Group.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { TranslatedString } from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
-import { LabelWithTooltipMaybeRequired } from "./InputLine.js";
-import { RenderAllFieldsByUiConfig, UIFormField } from "./forms.js";
-
-interface Props {
- before?: TranslatedString;
- after?: TranslatedString;
- tooltipBefore?: TranslatedString;
- tooltipAfter?: TranslatedString;
- fields: UIFormField[];
-}
-
-export function Group({
- before,
- after,
- tooltipAfter,
- tooltipBefore,
- fields,
-}: Props): VNode {
- return (
- <div class="sm:col-span-6 p-4 rounded-lg border-r-2 border-2 bg-gray-50">
- <div class="pb-4">
- {before && (
- <LabelWithTooltipMaybeRequired
- label={before}
- tooltip={tooltipBefore}
- />
- )}
- </div>
- <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-2 sm:grid-cols-6">
- <RenderAllFieldsByUiConfig fields={fields} />
- </div>
- <div class="pt-4">
- {after && (
- <LabelWithTooltipMaybeRequired label={after} tooltip={tooltipAfter} />
- )}
- </div>
- </div>
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputAmount.tsx b/packages/exchange-backoffice-ui/src/handlers/InputAmount.tsx
deleted file mode 100644
index 9be9dd4d0..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputAmount.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { AmountJson, Amounts, TranslatedString } from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
-import { InputLine, UIFormProps } from "./InputLine.js";
-import { useField } from "./useField.js";
-
-export function InputAmount<T extends object, K extends keyof T>(
- props: { currency?: string } & UIFormProps<T, K>,
-): VNode {
- const { value } = useField<T, K>(props.name);
- const currency =
- !value || !(value as any).currency
- ? props.currency
- : (value as any).currency;
- return (
- <InputLine<T, K>
- type="text"
- before={{
- type: "text",
- text: currency as TranslatedString,
- }}
- converter={{
- //@ts-ignore
- fromStringUI: (v): AmountJson => {
- return Amounts.parseOrThrow(`${currency}:${v}`);
- },
- //@ts-ignore
- toStringUI: (v: AmountJson) => {
- return v === undefined ? "" : Amounts.stringifyValue(v);
- },
- }}
- {...props}
- />
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputArray.tsx b/packages/exchange-backoffice-ui/src/handlers/InputArray.tsx
deleted file mode 100644
index 00379bed6..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputArray.tsx
+++ /dev/null
@@ -1,183 +0,0 @@
-import { Fragment, VNode, h } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { FormProvider, InputArrayFieldState } from "./FormProvider.js";
-import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js";
-import { RenderAllFieldsByUiConfig, UIFormField } from "./forms.js";
-import { useField } from "./useField.js";
-import { TranslatedString } from "@gnu-taler/taler-util";
-
-function Option({
- label,
- disabled,
- isFirst,
- isLast,
- isSelected,
- onClick,
-}: {
- label: TranslatedString;
- isFirst?: boolean;
- isLast?: boolean;
- isSelected?: boolean;
- disabled?: boolean;
- onClick: () => void;
-}): VNode {
- let clazz = "relative flex border p-4 focus:outline-none disabled:text-grey";
- if (isFirst) {
- clazz += " rounded-tl-md rounded-tr-md ";
- }
- if (isLast) {
- clazz += " rounded-bl-md rounded-br-md ";
- }
- if (isSelected) {
- clazz += " z-10 border-indigo-200 bg-indigo-50 ";
- } else {
- clazz += " border-gray-200";
- }
- if (disabled) {
- clazz +=
- " cursor-not-allowed bg-gray-50 text-gray-500 ring-gray-200 text-gray";
- } else {
- clazz += " cursor-pointer";
- }
- return (
- <label class={clazz}>
- <input
- type="radio"
- name="privacy-setting"
- checked={isSelected}
- disabled={disabled}
- onClick={onClick}
- class="mt-0.5 h-4 w-4 shrink-0 text-indigo-600 disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500 disabled:ring-gray-200 focus:ring-indigo-600"
- aria-labelledby="privacy-setting-0-label"
- aria-describedby="privacy-setting-0-description"
- />
- <span class="ml-3 flex flex-col">
- <span
- id="privacy-setting-0-label"
- disabled
- class="block text-sm font-medium"
- >
- {label}
- </span>
- {/* <!-- Checked: "text-indigo-700", Not Checked: "text-gray-500" --> */}
- {/* <span
- id="privacy-setting-0-description"
- class="block text-sm"
- >
- This project would be available to anyone who has the link
- </span> */}
- </span>
- </label>
- );
-}
-
-export function InputArray<T extends object, K extends keyof T>(
- props: {
- fields: UIFormField[];
- labelField: string;
- } & UIFormProps<T, K>,
-): VNode {
- const { fields, labelField, name, label, required, tooltip } = props;
- const { value, onChange, state } = useField<T, K>(name);
- const list = (value ?? []) as Array<Record<string, string | undefined>>;
- const [selectedIndex, setSelected] = useState<number | undefined>(undefined);
- const selected =
- selectedIndex === undefined ? undefined : list[selectedIndex];
-
- return (
- <div class="sm:col-span-6">
- <LabelWithTooltipMaybeRequired
- label={label}
- required={required}
- tooltip={tooltip}
- />
-
- <div class="-space-y-px rounded-md bg-white ">
- {list.map((v, idx) => {
- return (
- <Option
- label={v[labelField] as TranslatedString}
- isSelected={selectedIndex === idx}
- isLast={idx === list.length - 1}
- disabled={selectedIndex !== undefined && selectedIndex !== idx}
- isFirst={idx === 0}
- onClick={() => {
- setSelected(selectedIndex === idx ? undefined : idx);
- }}
- />
- );
- })}
- <div class="pt-2">
- <Option
- label={"Add..." as TranslatedString}
- isSelected={selectedIndex === list.length}
- isLast
- isFirst
- disabled={
- selectedIndex !== undefined && selectedIndex !== list.length
- }
- onClick={() => {
- setSelected(
- selectedIndex === list.length ? undefined : list.length,
- );
- }}
- />
- </div>
- </div>
- {selectedIndex !== undefined && (
- /**
- * This form provider act as a substate of the parent form
- * Consider creating an InnerFormProvider since not every feature is expected
- */
- <FormProvider
- initialValue={selected}
- computeFormState={(v) => {
- // current state is ignored
- // the state is defined by the parent form
-
- // elements should be present in the state object since this is expected to be an array
- //@ts-ignore
- return state.elements[selectedIndex];
- }}
- onSubmit={(v) => {
- const newValue = [...list];
- newValue.splice(selectedIndex, 1, v);
- onChange(newValue as T[K]);
- setSelected(undefined);
- }}
- onUpdate={(v) => {
- const newValue = [...list];
- newValue.splice(selectedIndex, 1, v);
- onChange(newValue as T[K]);
- }}
- >
- <div class="px-4 py-6">
- <div class="grid grid-cols-1 gap-y-8 ">
- <RenderAllFieldsByUiConfig fields={fields} />
- </div>
- </div>
- </FormProvider>
- )}
- {selectedIndex !== undefined && (
- <div class="flex items-center pt-3">
- <div class="flex-auto">
- {selected !== undefined && (
- <button
- type="button"
- onClick={() => {
- const newValue = [...list];
- newValue.splice(selectedIndex, 1);
- onChange(newValue as T[K]);
- setSelected(undefined);
- }}
- class="block rounded-md bg-red-600 px-3 py-2 text-center text-sm text-white shadow-sm hover:bg-red-500 "
- >
- Remove
- </button>
- )}
- </div>
- </div>
- )}
- </div>
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx b/packages/exchange-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx
deleted file mode 100644
index fdee35447..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { TranslatedString } from "@gnu-taler/taler-util";
-import { Fragment, VNode, h } from "preact";
-import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js";
-import { useField } from "./useField.js";
-
-export interface Choice<V> {
- label: TranslatedString;
- value: V;
-}
-
-export function InputChoiceHorizontal<T extends object, K extends keyof T>(
- props: {
- choices: Choice<T[K]>[];
- } & UIFormProps<T, K>,
-): VNode {
- const {
- choices,
- name,
- label,
- tooltip,
- help,
- placeholder,
- required,
- before,
- after,
- converter,
- } = props;
- const { value, onChange, state, isDirty } = useField<T, K>(name);
- if (state.hidden) {
- return <Fragment />;
- }
-
- return (
- <div class="sm:col-span-6">
- <LabelWithTooltipMaybeRequired
- label={label}
- required={required}
- tooltip={tooltip}
- />
- <fieldset class="mt-2">
- <div class="isolate inline-flex rounded-md shadow-sm">
- {choices.map((choice, idx) => {
- const isFirst = idx === 0;
- const isLast = idx === choices.length - 1;
- let clazz =
- "relative inline-flex items-center px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 focus:z-10";
- if (choice.value === value) {
- clazz +=
- " text-white bg-indigo-600 hover:bg-indigo-500 ring-2 ring-indigo-600 hover:ring-indigo-500";
- } else {
- clazz += " hover:bg-gray-100 border-gray-300";
- }
- if (isFirst) {
- clazz += " rounded-l-md";
- } else {
- clazz += " -ml-px";
- }
- if (isLast) {
- clazz += " rounded-r-md";
- }
- return (
- <button
- type="button"
- class={clazz}
- onClick={(e) => {
- onChange(
- (value === choice.value ? undefined : choice.value) as T[K],
- );
- }}
- >
- {(!converter
- ? (choice.value as string)
- : converter?.toStringUI(choice.value)) ?? ""}
- </button>
- );
- })}
- </div>
- </fieldset>
- {help && (
- <p class="mt-2 text-sm text-gray-500" id="email-description">
- {help}
- </p>
- )}
- </div>
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx b/packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx
deleted file mode 100644
index c37984368..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import { TranslatedString } from "@gnu-taler/taler-util";
-import { Fragment, VNode, h } from "preact";
-import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js";
-import { useField } from "./useField.js";
-
-export interface Choice<V> {
- label: TranslatedString;
- description?: TranslatedString;
- value: V;
-}
-
-export function InputChoiceStacked<T extends object, K extends keyof T>(
- props: {
- choices: Choice<T[K]>[];
- } & UIFormProps<T, K>,
-): VNode {
- const {
- choices,
- name,
- label,
- tooltip,
- help,
- placeholder,
- required,
- before,
- after,
- converter,
- } = props;
- const { value, onChange, state, isDirty } = useField<T, K>(name);
- if (state.hidden) {
- return <Fragment />;
- }
-
- return (
- <div class="sm:col-span-6">
- <LabelWithTooltipMaybeRequired
- label={label}
- required={required}
- tooltip={tooltip}
- />
- <fieldset class="mt-2">
- <div class="space-y-4">
- {choices.map((choice) => {
- // const currentValue = !converter
- // ? choice.value
- // : converter.fromStringUI(choice.value) ?? "";
-
- let clazz =
- "border relative block cursor-pointer rounded-lg bg-white px-6 py-4 shadow-sm focus:outline-none sm:flex sm:justify-between";
- if (choice.value === value) {
- clazz +=
- " border-transparent border-indigo-600 ring-2 ring-indigo-600";
- } else {
- clazz += " border-gray-300";
- }
-
- return (
- <label class={clazz}>
- <input
- type="radio"
- name="server-size"
- // defaultValue={choice.value}
- value={
- (!converter
- ? (choice.value as string)
- : converter?.toStringUI(choice.value)) ?? ""
- }
- onClick={(e) => {
- onChange(
- (value === choice.value
- ? undefined
- : choice.value) as T[K],
- );
- }}
- class="sr-only"
- aria-labelledby="server-size-0-label"
- aria-describedby="server-size-0-description-0 server-size-0-description-1"
- />
- <span class="flex items-center">
- <span class="flex flex-col text-sm">
- <span
- id="server-size-0-label"
- class="font-medium text-gray-900"
- >
- {choice.label}
- </span>
- {choice.description !== undefined && (
- <span
- id="server-size-0-description-0"
- class="text-gray-500"
- >
- <span class="block sm:inline">
- {choice.description}
- </span>
- </span>
- )}
- </span>
- </span>
- </label>
- );
- })}
- </div>
- </fieldset>
- {help && (
- <p class="mt-2 text-sm text-gray-500" id="email-description">
- {help}
- </p>
- )}
- </div>
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx b/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx
deleted file mode 100644
index 1fd81aad9..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { AbsoluteTime } from "@gnu-taler/taler-util";
-import { InputLine, UIFormProps } from "./InputLine.js";
-import { CalendarIcon } from "@heroicons/react/24/outline";
-import { VNode, h } from "preact";
-import { format, parse } from "date-fns";
-
-export function InputDate<T extends object, K extends keyof T>(
- props: { pattern?: string } & UIFormProps<T, K>,
-): VNode {
- const pattern = props.pattern ?? "dd/MM/yyyy";
- return (
- <InputLine<T, K>
- type="text"
- after={{
- type: "icon",
- icon: <CalendarIcon class="h-6 w-6" />,
- }}
- converter={{
- //@ts-ignore
- fromStringUI: (v): AbsoluteTime => {
- if (!v) return AbsoluteTime.never();
- const t_ms = parse(v, pattern, Date.now()).getTime();
- return AbsoluteTime.fromMilliseconds(t_ms);
- },
- //@ts-ignore
- toStringUI: (v: AbsoluteTime) => {
- return !v || !v.t_ms
- ? ""
- : v.t_ms === "never"
- ? "never"
- : format(v.t_ms, pattern);
- },
- }}
- {...props}
- />
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputFile.tsx b/packages/exchange-backoffice-ui/src/handlers/InputFile.tsx
deleted file mode 100644
index 0d89a98a3..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputFile.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { Fragment, VNode, h } from "preact";
-import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js";
-import { useField } from "./useField.js";
-
-export function InputFile<T extends object, K extends keyof T>(
- props: { maxBites: number; accept?: string } & UIFormProps<T, K>,
-): VNode {
- const {
- name,
- label,
- placeholder,
- tooltip,
- required,
- help,
- maxBites,
- accept,
- } = props;
- const { value, onChange, state } = useField<T, K>(name);
-
- if (state.hidden) {
- return <div />;
- }
- return (
- <div class="col-span-full">
- <LabelWithTooltipMaybeRequired
- label={label}
- tooltip={tooltip}
- required={required}
- />
- {!value || !(value as string).startsWith("data:image/") ? (
- <div class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 py-1">
- <div class="text-center">
- <svg
- class="mx-auto h-12 w-12 text-gray-300"
- viewBox="0 0 24 24"
- fill="currentColor"
- aria-hidden="true"
- >
- <path
- fill-rule="evenodd"
- d="M1.5 6a2.25 2.25 0 012.25-2.25h16.5A2.25 2.25 0 0122.5 6v12a2.25 2.25 0 01-2.25 2.25H3.75A2.25 2.25 0 011.5 18V6zM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0021 18v-1.94l-2.69-2.689a1.5 1.5 0 00-2.12 0l-.88.879.97.97a.75.75 0 11-1.06 1.06l-5.16-5.159a1.5 1.5 0 00-2.12 0L3 16.061zm10.125-7.81a1.125 1.125 0 112.25 0 1.125 1.125 0 01-2.25 0z"
- clip-rule="evenodd"
- />
- </svg>
- <div class="my-2 flex text-sm leading-6 text-gray-600">
- <label
- for="file-upload"
- class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500"
- >
- <span>Upload a file</span>
- <input
- id="file-upload"
- name="file-upload"
- type="file"
- class="sr-only"
- accept={accept}
- onChange={(e) => {
- const f: FileList | null = e.currentTarget.files;
- if (!f || f.length != 1) {
- return onChange(undefined!);
- }
- if (f[0].size > maxBites) {
- return onChange(undefined!);
- }
- return f[0].arrayBuffer().then((b) => {
- const b64 = window.btoa(
- new Uint8Array(b).reduce(
- (data, byte) => data + String.fromCharCode(byte),
- "",
- ),
- );
- return onChange(`data:${f[0].type};base64,${b64}` as any);
- });
- }}
- />
- </label>
- {/* <p class="pl-1">or drag and drop</p> */}
- </div>
- </div>
- </div>
- ) : (
- <div class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 relative">
- <img
- src={value as string}
- class=" h-24 w-full object-cover relative"
- />
-
- <div
- class="opacity-0 hover:opacity-70 duration-300 absolute rounded-lg border inset-0 z-10 flex justify-center text-xl items-center bg-black text-white cursor-pointer "
- onClick={() => {
- onChange(undefined!);
- }}
- >
- Clear
- </div>
- </div>
- )}
- {help && <p class="text-xs leading-5 text-gray-600 mt-2">{help}</p>}
- </div>
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputInteger.tsx b/packages/exchange-backoffice-ui/src/handlers/InputInteger.tsx
deleted file mode 100644
index fb04e3852..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputInteger.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { VNode, h } from "preact";
-import { InputLine, UIFormProps } from "./InputLine.js";
-
-export function InputInteger<T extends object, K extends keyof T>(
- props: UIFormProps<T, K>,
-): VNode {
- return (
- <InputLine
- type="number"
- converter={{
- //@ts-ignore
- fromStringUI: (v): number => {
- return !v ? 0 : Number.parseInt(v, 10);
- },
- //@ts-ignore
- toStringUI: (v?: number): string => {
- return v === undefined ? "" : String(v);
- },
- }}
- {...props}
- />
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputLine.tsx b/packages/exchange-backoffice-ui/src/handlers/InputLine.tsx
deleted file mode 100644
index 9448ef5e4..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputLine.tsx
+++ /dev/null
@@ -1,282 +0,0 @@
-import { TranslatedString } from "@gnu-taler/taler-util";
-import { ComponentChildren, Fragment, VNode, h } from "preact";
-import { useField } from "./useField.js";
-
-export interface IconAddon {
- type: "icon";
- icon: VNode;
-}
-interface ButtonAddon {
- type: "button";
- onClick: () => void;
- children: ComponentChildren;
-}
-interface TextAddon {
- type: "text";
- text: TranslatedString;
-}
-type Addon = IconAddon | ButtonAddon | TextAddon;
-
-interface StringConverter<T> {
- toStringUI: (v?: T) => string;
- fromStringUI: (v?: string) => T;
-}
-
-export interface UIFormProps<T extends object, K extends keyof T> {
- name: K;
- label: TranslatedString;
- placeholder?: TranslatedString;
- tooltip?: TranslatedString;
- help?: TranslatedString;
- before?: Addon;
- after?: Addon;
- required?: boolean;
- converter?: StringConverter<T[K]>;
-}
-
-export type FormErrors<T> = {
- [P in keyof T]?: string | FormErrors<T[P]>;
-};
-
-//@ts-ignore
-const TooltipIcon = (
- <svg
- class="w-5 h-5"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- >
- <path
- fill-rule="evenodd"
- d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z"
- clip-rule="evenodd"
- />
- </svg>
-);
-
-export function LabelWithTooltipMaybeRequired({
- label,
- required,
- tooltip,
-}: {
- label: TranslatedString;
- required?: boolean;
- tooltip?: TranslatedString;
-}): VNode {
- const Label = (
- <Fragment>
- <div class="flex justify-between">
- <label
- htmlFor="email"
- class="block text-sm font-medium leading-6 text-gray-900"
- >
- {label}
- </label>
- </div>
- </Fragment>
- );
- const WithTooltip = tooltip ? (
- <div class="relative flex flex-grow items-stretch focus-within:z-10">
- {Label}
- <span class="relative flex items-center group pl-2">
- {TooltipIcon}
- <div class="absolute bottom-0 flex flex-col items-center hidden mb-6 group-hover:flex">
- <span class="relative z-10 p-2 text-xs leading-none text-white whitespace-no-wrap bg-black shadow-lg">
- {tooltip}
- </span>
- <div class="w-3 h-3 -mt-2 rotate-45 bg-black"></div>
- </div>
- </span>
- </div>
- ) : (
- Label
- );
- if (required) {
- return (
- <div class="flex justify-between">
- {WithTooltip}
- <span class="text-sm leading-6 text-red-600">*</span>
- </div>
- );
- }
- return WithTooltip;
-}
-
-function InputWrapper<T extends object, K extends keyof T>({
- children,
- label,
- tooltip,
- before,
- after,
- help,
- error,
- required,
-}: { error?: string; children: ComponentChildren } & UIFormProps<T, K>): VNode {
- return (
- <div class="sm:col-span-6">
- <LabelWithTooltipMaybeRequired
- label={label}
- required={required}
- tooltip={tooltip}
- />
- <div class="relative mt-2 flex rounded-md shadow-sm">
- {before &&
- (before.type === "text" ? (
- <span class="inline-flex items-center rounded-l-md border border-r-0 border-gray-300 px-3 text-gray-500 sm:text-sm">
- {before.text}
- </span>
- ) : before.type === "icon" ? (
- <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
- {before.icon}
- </div>
- ) : before.type === "button" ? (
- <button
- type="button"
- onClick={before.onClick}
- class="relative -ml-px inline-flex items-center gap-x-1.5 rounded-l-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
- >
- {before.children}
- </button>
- ) : undefined)}
-
- {children}
-
- {after &&
- (after.type === "text" ? (
- <span class="inline-flex items-center rounded-r-md border border-l-0 border-gray-300 px-3 text-gray-500 sm:text-sm">
- {after.text}
- </span>
- ) : after.type === "icon" ? (
- <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
- {after.icon}
- </div>
- ) : after.type === "button" ? (
- <button
- type="button"
- onClick={after.onClick}
- class="relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
- >
- {after.children}
- </button>
- ) : undefined)}
- </div>
- {error && (
- <p class="mt-2 text-sm text-red-600" id="email-error">
- {error}
- </p>
- )}
- {help && (
- <p class="mt-2 text-sm text-gray-500" id="email-description">
- {help}
- </p>
- )}
- </div>
- );
-}
-
-function defaultToString(v: unknown) {
- return v === undefined ? "" : typeof v !== "object" ? String(v) : "";
-}
-function defaultFromString(v: string) {
- return v;
-}
-
-type InputType = "text" | "text-area" | "password" | "email" | "number";
-
-export function InputLine<T extends object, K extends keyof T>(
- props: { type: InputType } & UIFormProps<T, K>,
-): VNode {
- const { name, placeholder, before, after, converter, type } = props;
- const { value, onChange, state, isDirty } = useField<T, K>(name);
-
- if (state.hidden) return <div />;
-
- let clazz =
- "block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6 disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500 disabled:ring-gray-200";
- if (before) {
- switch (before.type) {
- case "icon": {
- clazz += " pl-10";
- break;
- }
- case "button": {
- clazz += " rounded-none rounded-r-md ";
- break;
- }
- case "text": {
- clazz += " min-w-0 flex-1 rounded-r-md rounded-none ";
- break;
- }
- }
- }
- if (after) {
- switch (after.type) {
- case "icon": {
- clazz += " pr-10";
- break;
- }
- case "button": {
- clazz += " rounded-none rounded-l-md";
- break;
- }
- case "text": {
- clazz += " min-w-0 flex-1 rounded-l-md rounded-none ";
- break;
- }
- }
- }
- const showError = isDirty && state.error;
- if (showError) {
- clazz +=
- " text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500";
- } else {
- clazz +=
- " text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-indigo-600";
- }
- const fromString: (s: string) => any =
- converter?.fromStringUI ?? defaultFromString;
- const toString: (s: any) => string = converter?.toStringUI ?? defaultToString;
-
- if (type === "text-area") {
- return (
- <InputWrapper<T, K>
- {...props}
- error={showError ? state.error : undefined}
- >
- <textarea
- rows={4}
- name={String(name)}
- onChange={(e) => {
- onChange(fromString(e.currentTarget.value));
- }}
- placeholder={placeholder ? placeholder : undefined}
- value={toString(value) ?? ""}
- // defaultValue={toString(value)}
- disabled={state.disabled}
- aria-invalid={showError}
- // aria-describedby="email-error"
- class={clazz}
- />
- </InputWrapper>
- );
- }
-
- return (
- <InputWrapper<T, K> {...props} error={showError ? state.error : undefined}>
- <input
- name={String(name)}
- type={type}
- onChange={(e) => {
- onChange(fromString(e.currentTarget.value));
- }}
- placeholder={placeholder ? placeholder : undefined}
- value={toString(value) ?? ""}
- // defaultValue={toString(value)}
- disabled={state.disabled}
- aria-invalid={showError}
- // aria-describedby="email-error"
- class={clazz}
- />
- </InputWrapper>
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx b/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx
deleted file mode 100644
index 837744827..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-import { Fragment, VNode, h } from "preact";
-import { Choice } from "./InputChoiceStacked.js";
-import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js";
-import { useField } from "./useField.js";
-import { useState } from "preact/hooks";
-
-export function InputSelectMultiple<T extends object, K extends keyof T>(
- props: {
- choices: Choice<T[K]>[];
- unique?: boolean;
- max?: number;
- } & UIFormProps<T, K>,
-): VNode {
- const { name, label, choices, placeholder, tooltip, required, unique, max } =
- props;
- const { value, onChange } = useField<T, K>(name);
-
- const [filter, setFilter] = useState<string | undefined>(undefined);
- const regex = new RegExp(`.*${filter}.*`, "i");
- const choiceMap = choices.reduce((prev, curr) => {
- return { ...prev, [curr.value as string]: curr.label };
- }, {} as Record<string, string>);
-
- const list = (value ?? []) as string[];
- const filteredChoices =
- filter === undefined
- ? undefined
- : choices.filter((v) => {
- return regex.test(v.label);
- });
- return (
- <div class="sm:col-span-6">
- <LabelWithTooltipMaybeRequired
- label={label}
- required={required}
- tooltip={tooltip}
- />
- {list.map((v, idx) => {
- return (
- <span class="inline-flex items-center gap-x-0.5 rounded-md bg-gray-100 p-1 mr-2 text-xs font-medium text-gray-600">
- {choiceMap[v]}
- <button
- type="button"
- onClick={() => {
- const newValue = [...list];
- newValue.splice(idx, 1);
- onChange(newValue as T[K]);
- setFilter(undefined);
- }}
- class="group relative h-5 w-5 rounded-sm hover:bg-gray-500/20"
- >
- <span class="sr-only">Remove</span>
- <svg
- viewBox="0 0 14 14"
- class="h-5 w-5 stroke-gray-700/50 group-hover:stroke-gray-700/75"
- >
- <path d="M4 4l6 6m0-6l-6 6" />
- </svg>
- <span class="absolute -inset-1"></span>
- </button>
- </span>
- );
- })}
-
- <div class="relative mt-2">
- <input
- id="combobox"
- type="text"
- value={filter ?? ""}
- onChange={(e) => {
- setFilter(e.currentTarget.value);
- }}
- placeholder={placeholder}
- class="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
- role="combobox"
- aria-controls="options"
- aria-expanded="false"
- />
- <button
- type="button"
- onClick={() => {
- setFilter(filter === undefined ? "" : undefined);
- }}
- class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none"
- >
- <svg
- class="h-5 w-5 text-gray-400"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
- >
- <path
- fill-rule="evenodd"
- d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z"
- clip-rule="evenodd"
- />
- </svg>
- </button>
-
- {filteredChoices !== undefined && (
- <ul
- class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
- id="options"
- role="listbox"
- >
- {filteredChoices.map((v, idx) => {
- return (
- <li
- class="relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600"
- id="option-0"
- role="option"
- onClick={() => {
- setFilter(undefined);
- if (unique && list.indexOf(v.value as string) !== -1) {
- return;
- }
- if (max !== undefined && list.length >= max) {
- return;
- }
- const newValue = [...list];
- newValue.splice(0, 0, v.value as string);
- onChange(newValue as T[K]);
- }}
-
- // tabindex="-1"
- >
- {/* <!-- Selected: "font-semibold" --> */}
- <span class="block truncate">{v.label}</span>
-
- {/* <!--
- Checkmark, only display for selected option.
-
- Active: "text-white", Not Active: "text-indigo-600"
- --> */}
- </li>
- );
- })}
-
- {/* <!--
- Combobox option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation.
-
- Active: "text-white bg-indigo-600", Not Active: "text-gray-900"
- --> */}
-
- {/* <!-- More items... --> */}
- </ul>
- )}
- </div>
- </div>
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx b/packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx
deleted file mode 100644
index b0e2277d3..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import { Fragment, VNode, h } from "preact";
-import { Choice } from "./InputChoiceStacked.js";
-import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js";
-import { useField } from "./useField.js";
-import { useState } from "preact/hooks";
-
-export function InputSelectOne<T extends object, K extends keyof T>(
- props: {
- choices: Choice<T[K]>[];
- } & UIFormProps<T, K>,
-): VNode {
- const { name, label, choices, placeholder, tooltip, required } = props;
- const { value, onChange } = useField<T, K>(name);
-
- const [filter, setFilter] = useState<string | undefined>(undefined);
- const regex = new RegExp(`.*${filter}.*`, "i");
- const choiceMap = choices.reduce((prev, curr) => {
- return { ...prev, [curr.value as string]: curr.label };
- }, {} as Record<string, string>);
-
- const filteredChoices =
- filter === undefined
- ? undefined
- : choices.filter((v) => {
- return regex.test(v.label);
- });
- return (
- <div class="sm:col-span-6">
- <LabelWithTooltipMaybeRequired
- label={label}
- required={required}
- tooltip={tooltip}
- />
- {value ? (
- <span class="inline-flex items-center gap-x-0.5 rounded-md bg-gray-100 p-1 mr-2 font-medium text-gray-600">
- {choiceMap[value as string]}
- <button
- type="button"
- onClick={() => {
- onChange(undefined!);
- }}
- class="group relative h-5 w-5 rounded-sm hover:bg-gray-500/20"
- >
- <span class="sr-only">Remove</span>
- <svg
- viewBox="0 0 14 14"
- class="h-5 w-5 stroke-gray-700/50 group-hover:stroke-gray-700/75"
- >
- <path d="M4 4l6 6m0-6l-6 6" />
- </svg>
- <span class="absolute -inset-1"></span>
- </button>
- </span>
- ) : (
- <div class="relative mt-2">
- <input
- id="combobox"
- type="text"
- value={filter ?? ""}
- onChange={(e) => {
- setFilter(e.currentTarget.value);
- }}
- placeholder={placeholder}
- class="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
- role="combobox"
- aria-controls="options"
- aria-expanded="false"
- />
- <button
- type="button"
- onClick={() => {
- setFilter(filter === undefined ? "" : undefined);
- }}
- class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none"
- >
- <svg
- class="h-5 w-5 text-gray-400"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
- >
- <path
- fill-rule="evenodd"
- d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z"
- clip-rule="evenodd"
- />
- </svg>
- </button>
-
- {filteredChoices !== undefined && (
- <ul
- class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
- id="options"
- role="listbox"
- >
- {filteredChoices.map((v, idx) => {
- return (
- <li
- class="relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600"
- id="option-0"
- role="option"
- onClick={() => {
- setFilter(undefined);
- onChange(v.value as T[K]);
- }}
-
- // tabindex="-1"
- >
- {/* <!-- Selected: "font-semibold" --> */}
- <span class="block truncate">{v.label}</span>
-
- {/* <!--
- Checkmark, only display for selected option.
-
- Active: "text-white", Not Active: "text-indigo-600"
- --> */}
- </li>
- );
- })}
-
- {/* <!--
- Combobox option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation.
-
- Active: "text-white bg-indigo-600", Not Active: "text-gray-900"
- --> */}
-
- {/* <!-- More items... --> */}
- </ul>
- )}
- </div>
- )}
- </div>
- );
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputText.tsx b/packages/exchange-backoffice-ui/src/handlers/InputText.tsx
deleted file mode 100644
index 1b37ee6fb..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputText.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { VNode, h } from "preact";
-import { InputLine, UIFormProps } from "./InputLine.js";
-
-export function InputText<T extends object, K extends keyof T>(
- props: UIFormProps<T, K>,
-): VNode {
- return <InputLine type="text" {...props} />;
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputTextArea.tsx b/packages/exchange-backoffice-ui/src/handlers/InputTextArea.tsx
deleted file mode 100644
index 45229951e..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/InputTextArea.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { VNode, h } from "preact";
-import { InputLine, UIFormProps } from "./InputLine.js";
-
-export function InputTextArea<T extends object, K extends keyof T>(
- props: UIFormProps<T, K>,
-): VNode {
- return <InputLine type="text-area" {...props} />;
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/forms.ts b/packages/exchange-backoffice-ui/src/handlers/forms.ts
deleted file mode 100644
index 2c90a69ed..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/forms.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-import { TranslatedString } from "@gnu-taler/taler-util";
-import { InputText } from "./InputText.js";
-import { InputDate } from "./InputDate.js";
-import { InputInteger } from "./InputInteger.js";
-import { h as create, Fragment, VNode } from "preact";
-import { InputChoiceStacked } from "./InputChoiceStacked.js";
-import { InputArray } from "./InputArray.js";
-import { InputSelectMultiple } from "./InputSelectMultiple.js";
-import { InputTextArea } from "./InputTextArea.js";
-import { InputFile } from "./InputFile.js";
-import { Caption } from "./Caption.js";
-import { Group } from "./Group.js";
-import { InputSelectOne } from "./InputSelectOne.js";
-import { FormProvider } from "./FormProvider.js";
-import { InputLine } from "./InputLine.js";
-import { InputAmount } from "./InputAmount.js";
-import { InputChoiceHorizontal } from "./InputChoiceHorizontal.js";
-
-export type DoubleColumnForm = Array<DoubleColumnFormSection | undefined>;
-
-export type DoubleColumnFormSection = {
- title: TranslatedString;
- description?: TranslatedString;
- fields: UIFormField[];
-};
-
-/**
- * Constrain the type with the ui props
- */
-type FieldType<T extends object = any, K extends keyof T = any> = {
- group: Parameters<typeof Group>[0];
- caption: Parameters<typeof Caption>[0];
- array: Parameters<typeof InputArray<T, K>>[0];
- file: Parameters<typeof InputFile<T, K>>[0];
- selectOne: Parameters<typeof InputSelectOne<T, K>>[0];
- selectMultiple: Parameters<typeof InputSelectMultiple<T, K>>[0];
- text: Parameters<typeof InputText<T, K>>[0];
- textArea: Parameters<typeof InputTextArea<T, K>>[0];
- choiceStacked: Parameters<typeof InputChoiceStacked<T, K>>[0];
- choiceHorizontal: Parameters<typeof InputChoiceHorizontal<T, K>>[0];
- date: Parameters<typeof InputDate<T, K>>[0];
- integer: Parameters<typeof InputInteger<T, K>>[0];
- amount: Parameters<typeof InputAmount<T, K>>[0];
-};
-
-/**
- * List all the form fields so typescript can type-check the form instance
- */
-export type UIFormField =
- | { type: "group"; props: FieldType["group"] }
- | { type: "caption"; props: FieldType["caption"] }
- | { type: "array"; props: FieldType["array"] }
- | { type: "file"; props: FieldType["file"] }
- | { type: "amount"; props: FieldType["amount"] }
- | { type: "selectOne"; props: FieldType["selectOne"] }
- | { type: "selectMultiple"; props: FieldType["selectMultiple"] }
- | { type: "text"; props: FieldType["text"] }
- | { type: "textArea"; props: FieldType["textArea"] }
- | { type: "choiceStacked"; props: FieldType["choiceStacked"] }
- | { type: "choiceHorizontal"; props: FieldType["choiceHorizontal"] }
- | { type: "integer"; props: FieldType["integer"] }
- | { type: "date"; props: FieldType["date"] };
-
-type FieldComponentFunction<key extends keyof FieldType> = (
- props: FieldType[key],
-) => VNode;
-
-type UIFormFieldMap = {
- [key in keyof FieldType]: FieldComponentFunction<key>;
-};
-
-/**
- * Maps input type with component implementation
- */
-const UIFormConfiguration: UIFormFieldMap = {
- group: Group,
- caption: Caption,
- //@ts-ignore
- array: InputArray,
- text: InputText,
- //@ts-ignore
- file: InputFile,
- textArea: InputTextArea,
- //@ts-ignore
- date: InputDate,
- //@ts-ignore
- choiceStacked: InputChoiceStacked,
- //@ts-ignore
- choiceHorizontal: InputChoiceHorizontal,
- integer: InputInteger,
- //@ts-ignore
- selectOne: InputSelectOne,
- //@ts-ignore
- selectMultiple: InputSelectMultiple,
- //@ts-ignore
- amount: InputAmount,
-};
-
-export function RenderAllFieldsByUiConfig({
- fields,
-}: {
- fields: UIFormField[];
-}): VNode {
- return create(
- Fragment,
- {},
- fields.map((field, i) => {
- const Component = UIFormConfiguration[
- field.type
- ] as FieldComponentFunction<any>;
- return Component(field.props);
- }),
- );
-}
-
-type FormSet<T extends object> = {
- Provider: typeof FormProvider<T>;
- InputLine: <K extends keyof T>() => typeof InputLine<T, K>;
- InputChoiceHorizontal: <K extends keyof T>() => typeof InputChoiceHorizontal<
- T,
- K
- >;
-};
-export function createNewForm<T extends object>() {
- const res: FormSet<T> = {
- Provider: FormProvider,
- InputLine: () => InputLine,
- InputChoiceHorizontal: () => InputChoiceHorizontal,
- };
- return {
- Provider: res.Provider,
- InputLine: res.InputLine(),
- InputChoiceHorizontal: res.InputChoiceHorizontal(),
- };
-}
diff --git a/packages/exchange-backoffice-ui/src/handlers/useField.ts b/packages/exchange-backoffice-ui/src/handlers/useField.ts
deleted file mode 100644
index bf94d2f5d..000000000
--- a/packages/exchange-backoffice-ui/src/handlers/useField.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import { useContext, useState } from "preact/compat";
-import { FormContext, InputFieldState } from "./FormProvider.js";
-
-export interface InputFieldHandler<Type> {
- value: Type;
- onChange: (s: Type) => void;
- state: InputFieldState;
- isDirty: boolean;
-}
-
-export function useField<T extends object, K extends keyof T>(
- name: K,
-): InputFieldHandler<T[K]> {
- const {
- initialValue,
- value: formValue,
- computeFormState,
- onUpdate: notifyUpdate,
- } = useContext(FormContext);
-
- type P = typeof name;
- type V = T[P];
- const formState = computeFormState ? computeFormState(formValue.current) : {};
-
- const fieldValue = readField(formValue.current, String(name)) as V;
- // console.log("USE FIELD", String(name), formValue.current, fieldValue);
- const [currentValue, setCurrentValue] = useState<any | undefined>(fieldValue);
- const fieldState =
- readField<Partial<InputFieldState>>(formState, String(name)) ?? {};
-
- //compute default state
- const state = {
- disabled: fieldState.disabled ?? false,
- readonly: fieldState.readonly ?? false,
- hidden: fieldState.hidden ?? false,
- error: fieldState.error,
- elements: "elements" in fieldState ? fieldState.elements ?? [] : [],
- };
-
- function onChange(value: V): void {
- setCurrentValue(value);
- formValue.current = setValueDeeper(
- formValue.current,
- String(name).split("."),
- value,
- );
- if (notifyUpdate) {
- notifyUpdate(formValue.current);
- }
- }
-
- return {
- value: fieldValue,
- onChange,
- isDirty: currentValue !== undefined,
- state,
- };
-}
-
-/**
- * read the field of an object an support accessing it using '.'
- *
- * @param object
- * @param name
- * @returns
- */
-function readField<T>(
- object: any,
- name: string,
- debug?: boolean,
-): T | undefined {
- return name.split(".").reduce((prev, current) => {
- if (debug) {
- console.log(
- "READ",
- name,
- prev,
- current,
- prev ? prev[current] : undefined,
- );
- }
- return prev ? prev[current] : undefined;
- }, object);
-}
-
-function setValueDeeper(object: any, names: string[], value: any): any {
- if (names.length === 0) return value;
- const [head, ...rest] = names;
- if (object === undefined) {
- return { [head]: setValueDeeper({}, rest, value) };
- }
- return { ...object, [head]: setValueDeeper(object[head] ?? {}, rest, value) };
-}