gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-typescript-core] branch master updated: fix #9736


From: Admin
Subject: [taler-typescript-core] branch master updated: fix #9736
Date: Thu, 12 Jun 2025 21:20:45 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository taler-typescript-core.

The following commit(s) were added to refs/heads/master by this push:
     new cbf261e2b fix #9736
cbf261e2b is described below

commit cbf261e2b81b4db6a0e55620a8e85b70f21bb52e
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Jun 12 10:19:26 2025 -0300

    fix #9736
---
 .../aml-backoffice-ui/src/pages/NewMeasure.tsx     | 783 +++++++++++++++------
 .../src/pages/decision/Events.tsx                  |   2 +-
 .../src/pages/decision/Justification.tsx           |   2 +-
 .../src/pages/decision/Measures.tsx                | 238 +++++--
 .../src/pages/decision/Properties.tsx              |   2 +-
 .../aml-backoffice-ui/src/pages/decision/Rules.tsx |   4 +-
 packages/challenger-ui/src/pages/AskChallenge.tsx  |  16 +-
 packages/web-util/src/forms/forms-types.ts         |   2 +-
 .../web-util/src/forms/gana/challenger_email.ts    |  22 +-
 .../web-util/src/forms/gana/challenger_postal.ts   |  52 +-
 packages/web-util/src/forms/gana/challenger_sms.ts |  22 +-
 packages/web-util/src/hooks/useForm.ts             |   2 +-
 12 files changed, 806 insertions(+), 341 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/pages/NewMeasure.tsx 
b/packages/aml-backoffice-ui/src/pages/NewMeasure.tsx
index edc8688c3..47c905c4b 100644
--- a/packages/aml-backoffice-ui/src/pages/NewMeasure.tsx
+++ b/packages/aml-backoffice-ui/src/pages/NewMeasure.tsx
@@ -1,5 +1,6 @@
 import {
   AmlProgramRequirement,
+  assertUnreachable,
   AvailableMeasureSummary,
   KycCheckInformation,
   KycRule,
@@ -7,16 +8,23 @@ import {
   TranslatedString,
 } from "@gnu-taler/taler-util";
 import {
+  design_challenger_email,
+  design_challenger_phone,
+  design_challenger_postal,
   ErrorsSummary,
+  form_challenger_email,
   FormDesign,
   FormUI,
+  InputToggle,
   InternationalizationAPI,
+  RecursivePartial,
   useForm,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, h, VNode } from "preact";
 import { useCurrentDecisionRequest } from "../hooks/decision-request.js";
 import { useServerMeasures } from "../hooks/server-info.js";
+import { useState } from "preact/hooks";
 
 export type MeasureDefinition = {
   name: string;
@@ -29,6 +37,12 @@ export type MeasureDefinition = {
   }[];
 };
 
+type VerificationMeasureDefinition = {
+  name: string;
+  readOnly: boolean;
+  address: any;
+};
+
 /**
  * Defined new limits for the account
  * @param param0
@@ -78,7 +92,7 @@ export function NewMeasure({
   );
 }
 
-export function MeasureForm({
+function NormalMeasureForm({
   summary,
   onCancel,
   onAdded,
@@ -94,9 +108,9 @@ export function MeasureForm({
   onAdded: (name: string) => void;
   onChanged: (name: string) => void;
   onRemoved: (name: string) => void;
-}) {
-  const { i18n } = useTranslationContext();
+}): VNode {
   const [request, updateRequest] = useCurrentDecisionRequest();
+  const { i18n } = useTranslationContext();
 
   const names = {
     measures: Object.entries(summary.roots).map(([key, value]) => ({
@@ -125,37 +139,64 @@ export function MeasureForm({
 
   const name = !form.status.result ? undefined : form.status.result.name;
 
-  const program =
-    !form.status.result ||
-    !form.status.result.program ||
-    !summary.programs[form.status.result.program]
-      ? undefined
-      : {
-          ...summary.programs[form.status.result.program],
-          name: form.status.result.program,
-        };
+  function addNewCustomMeasure() {
+    const newMeasure = form.status.result as MeasureDefinition;
+    const currentMeasures = { ...request.custom_measures };
+    currentMeasures[newMeasure.name] = {
+      check_name: newMeasure.check,
+      prog_name: newMeasure.program,
+      context: (newMeasure.context ?? []).reduce(
+        (prev, cur) => {
+          prev[cur.key] = getContextValueByType(cur.type, cur.value);
+          return prev;
+        },
+        {} as Record<string, any>,
+      ),
+    };
+    updateRequest("add new measure", {
+      custom_measures: currentMeasures,
+    });
+    if (onAdded) {
+      onAdded(newMeasure.name);
+    }
+  }
 
-  const check =
-    !form.status.result ||
-    !form.status.result.check ||
-    !summary.checks[form.status.result.check]
-      ? undefined
-      : {
-          ...summary.checks[form.status.result.check],
-          name: form.status.result.check,
-        };
+  function updateCurrentCustomMeasure() {
+    const newMeasure = form.status.result as MeasureDefinition;
 
-  const context =
-    !form.status.result || !form.status.result.context
-      ? []
-      : (form.status.result.context as MeasureDefinition["context"]);
+    const CURRENT_MEASURES = { ...request.custom_measures };
+    CURRENT_MEASURES[newMeasure.name] = {
+      check_name: newMeasure.check,
+      prog_name: newMeasure.program,
+      context: (newMeasure.context ?? []).reduce(
+        (prev, cur) => {
+          prev[cur.key] = getContextValueByType(cur.type, cur.value);
+          return prev;
+        },
+        {} as Record<string, any>,
+      ),
+    };
+    updateRequest("update measure", {
+      custom_measures: CURRENT_MEASURES,
+    });
+    if (onChanged) {
+      onChanged(newMeasure.name);
+    }
+  }
 
-  return (
-    <div>
-      <h2 class="mt-4 mb-2">
-        <i18n.Translate>Add measure</i18n.Translate>
-      </h2>
+  function removeCustomMeasure() {
+    const currentMeasures = { ...request.custom_measures };
+    delete currentMeasures[name!];
+    updateRequest("remove measure", {
+      custom_measures: currentMeasures,
+    });
+    if (onRemoved) {
+      onRemoved(name!);
+    }
+  }
 
+  return (
+    <Fragment>
       <FormUI design={design} model={form.model} />
 
       <button
@@ -170,27 +211,7 @@ export function MeasureForm({
       {addingNew ? (
         <button
           disabled={form.status.status === "fail"}
-          onClick={() => {
-            const newMeasure = form.status.result as MeasureDefinition;
-            const currentMeasures = { ...request.custom_measures };
-            currentMeasures[newMeasure.name] = {
-              check_name: newMeasure.check,
-              prog_name: newMeasure.program,
-              context: (newMeasure.context ?? []).reduce(
-                (prev, cur) => {
-                  prev[cur.key] = getContextValueByType(cur.type, cur.value);
-                  return prev;
-                },
-                {} as Record<string, any>,
-              ),
-            };
-            updateRequest("add new measure", {
-              custom_measures: currentMeasures,
-            });
-            if (onAdded) {
-              onAdded(newMeasure.name);
-            }
-          }}
+          onClick={addNewCustomMeasure}
           class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm 
bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
         >
           <i18n.Translate>Add</i18n.Translate>
@@ -199,43 +220,14 @@ export function MeasureForm({
         <Fragment>
           <button
             disabled={form.status.status === "fail"}
-            onClick={() => {
-              const newMeasure = form.status.result as MeasureDefinition;
-
-              const CURRENT_MEASURES = { ...request.custom_measures };
-              CURRENT_MEASURES[newMeasure.name] = {
-                check_name: newMeasure.check,
-                prog_name: newMeasure.program,
-                context: (newMeasure.context ?? []).reduce(
-                  (prev, cur) => {
-                    prev[cur.key] = getContextValueByType(cur.type, cur.value);
-                    return prev;
-                  },
-                  {} as Record<string, any>,
-                ),
-              };
-              updateRequest("update measure", {
-                custom_measures: CURRENT_MEASURES,
-              });
-              if (onChanged) {
-                onChanged(newMeasure.name);
-              }
-            }}
+            onClick={updateCurrentCustomMeasure}
             class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center 
text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 
disabled:bg-gray-600"
           >
             <i18n.Translate>Update</i18n.Translate>
           </button>
+
           <button
-            onClick={() => {
-              const currentMeasures = { ...request.custom_measures };
-              delete currentMeasures[name!];
-              updateRequest("remove measure", {
-                custom_measures: currentMeasures,
-              });
-              if (onRemoved) {
-                onRemoved(name!);
-              }
-            }}
+            onClick={removeCustomMeasure}
             class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center 
text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 
disabled:bg-gray-600"
           >
             <i18n.Translate>Remove</i18n.Translate>
@@ -243,123 +235,287 @@ export function MeasureForm({
         </Fragment>
       )}
 
-      <h2 class="mt-4 mb-2">
-        <i18n.Translate>Description</i18n.Translate>
-      </h2>
+      <DescribeMeasure measure={form.status.result} summary={summary} />
+    </Fragment>
+  );
+}
+function VerificationMeasureForm({
+  summary,
+  onCancel,
+  onAdded,
+  onChanged,
+  onRemoved,
+  initial,
+  addingNew,
+  challengeType,
+}: {
+  initial?: Partial<MeasureDefinition>;
+  addingNew?: boolean;
+  summary: AvailableMeasureSummary;
+  onCancel: () => void;
+  onAdded: (name: string) => void;
+  onChanged: (name: string) => void;
+  onRemoved: (name: string) => void;
+  challengeType: "email" | "phone" | "postal";
+}): VNode {
+  const [request, updateRequest] = useCurrentDecisionRequest();
+  const { i18n } = useTranslationContext();
 
-      {!program ? undefined : (
-        <div class="rounded-lg bg-gray-150 ring-1 shadow-lg border-indigo-700 
border ring-gray-900/5 ">
-          <dl class="flex flex-wrap">
-            <div class="flex-auto pt-4 pl-4 bg-indigo-600 rounded-t-lg">
-              <dt class="text-sm/6  text-white">
-                <i18n.Translate>Program</i18n.Translate>
-              </dt>
-              <dd class="mt-1 text-base font-semibold text-white">
-                {program.name}
-              </dd>
-            </div>
-            <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
-              <dt class="flex-none text-gray-500">
-                <i18n.Translate>Description</i18n.Translate>
-              </dt>
-              <dd class="text-sm/6 ">
-                <i18n.Translate>{program.description}</i18n.Translate>
-              </dd>
-            </div>
-            <div class="mt-2 flex w-full flex-none gap-x-4 border-t 
border-gray-900/5 px-6 pt-2">
-              <dt class="flex-none text-gray-500">
-                <i18n.Translate>Context</i18n.Translate>
-              </dt>
-              <dd class="text-sm/6 font-medium text-gray-900">
-                <pre>{program.context.join(",")}</pre>
-              </dd>
-            </div>
-            <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
-              <dt class="flex-none text-gray-500">
-                <i18n.Translate>Inputs</i18n.Translate>
-              </dt>
-              <dd class="text-sm/6 ">
-                <pre class="whitespace-pre-wrap">
-                  {program.inputs.join(",")}
-                </pre>
-              </dd>
-            </div>
-          </dl>
-          <div class="px-4 pb-2"></div>
-        </div>
+  const design = verificationFormDesign(
+    i18n,
+    summary,
+    !addingNew,
+    challengeType,
+  );
+
+  const initAddr = (initial?.context ?? []).find(
+    (d) => d.key === "initial_address",
+  );
+
+  let readOnly: boolean | undefined;
+  let rest = {};
+  if (initAddr && initAddr.value) {
+    const va = JSON.parse(initAddr.value);
+    readOnly = va.read_only;
+    delete va.read_only;
+    rest = { ...va };
+  }
+
+  const template: Partial<VerificationMeasureDefinition> = {
+    name: initial?.name,
+    readOnly,
+    address: rest,
+  };
+
+  const form = useForm<VerificationMeasureDefinition>(design, template ?? {});
+
+  // const name = !form.status.result ? undefined : form.status.result.name;
+
+  if (!initial) {
+    throw Error("verification doesn't have initial value");
+  }
+  if (!initial.check) {
+    throw Error("verification doesn't have check");
+  }
+  if (!initial.program) {
+    throw Error("verification doesn't have program");
+  }
+  if (!initial.context) {
+    throw Error("verification doesn't have program");
+  }
+  if (!initial.name) {
+    throw Error("verification doesn't have name");
+  }
+
+  const check_name = initial.check;
+  const measure_name = initial.name;
+  const prog_name = initial.program;
+  const context = initial.context.reduce(
+    (prev, cur) => {
+      prev[cur.key] = getContextValueByType(cur.type, cur.value);
+      return prev;
+    },
+    {} as Record<string, any>,
+  );
+
+  function addNewCustomMeasure() {
+    const newMeasure = form.status.result as VerificationMeasureDefinition;
+    const currentMeasures = { ...request.custom_measures };
+    delete currentMeasures[measure_name];
+
+    currentMeasures[newMeasure.name] = {
+      check_name,
+      prog_name,
+      context: {
+        ...context,
+        initial_address: {
+          read_only: newMeasure.readOnly,
+          ...newMeasure.address,
+        },
+      },
+    };
+    updateRequest("add new measure", {
+      custom_measures: currentMeasures,
+    });
+    if (onAdded) {
+      onAdded(newMeasure.name);
+    }
+  }
+
+  function updateCurrentCustomMeasure() {
+    const newMeasure = form.status.result as VerificationMeasureDefinition;
+
+    const CURRENT_MEASURES = { ...request.custom_measures };
+    CURRENT_MEASURES[newMeasure.name] = {
+      check_name,
+      prog_name,
+      context: {
+        ...context,
+        initial_address: {
+          read_only: newMeasure.readOnly,
+          ...newMeasure.address,
+        },
+      },
+    };
+    updateRequest("update measure", {
+      custom_measures: CURRENT_MEASURES,
+    });
+    if (onChanged) {
+      onChanged(newMeasure.name);
+    }
+  }
+
+  function removeCustomMeasure() {
+    const newMeasure = form.status.result as VerificationMeasureDefinition;
+    const currentMeasures = { ...request.custom_measures };
+    delete currentMeasures[newMeasure.name];
+    updateRequest("remove measure", {
+      custom_measures: currentMeasures,
+    });
+    if (onRemoved) {
+      onRemoved(name!);
+    }
+  }
+
+  return (
+    <Fragment>
+      <FormUI design={design} model={form.model} />
+
+      <button
+        onClick={() => {
+          onCancel();
+        }}
+        class="m-4  rounded-md w-fit border-1 px-3 py-2 text-center text-sm 
shadow-sm "
+      >
+        <i18n.Translate>Cancel</i18n.Translate>
+      </button>
+
+      {addingNew ? (
+        <button
+          disabled={form.status.status === "fail"}
+          onClick={addNewCustomMeasure}
+          class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm 
bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
+        >
+          <i18n.Translate>Add</i18n.Translate>
+        </button>
+      ) : (
+        <Fragment>
+          <button
+            disabled={form.status.status === "fail"}
+            onClick={updateCurrentCustomMeasure}
+            class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center 
text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 
disabled:bg-gray-600"
+          >
+            <i18n.Translate>Update</i18n.Translate>
+          </button>
+
+          <button
+            onClick={removeCustomMeasure}
+            class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center 
text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 
disabled:bg-gray-600"
+          >
+            <i18n.Translate>Remove</i18n.Translate>
+          </button>
+        </Fragment>
       )}
-      {!check ? undefined : (
-        <div class="mt-6 rounded-lg bg-gray-150 ring-1 shadow-lg 
border-indigo-700 border ring-gray-900/5 ">
-          <dl class="flex flex-wrap">
-            <div class="flex-auto pt-4 pl-4 bg-indigo-600 rounded-t-lg">
-              <dt class="text-sm/6  text-white">
-                <i18n.Translate>Check</i18n.Translate>
-              </dt>
-              <dd class="mt-1 text-base font-semibold text-white">
-                {check.name}
-              </dd>
-            </div>
-            <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
-              <dt class="flex-none text-gray-500">Description</dt>
-              <dd class="text-sm/6 ">
-                <i18n.Translate>{check.description}</i18n.Translate>
-              </dd>
-            </div>
-            <div class="mt-2 flex w-full flex-none gap-x-4 border-t 
border-gray-900/5 px-6 pt-2">
-              <dt class="flex-none text-gray-500">
-                <i18n.Translate>Output</i18n.Translate>
-              </dt>
-              <dd class="text-sm/6 font-medium ">
-                <pre class="whitespace-break-spaces">
-                  {check.outputs.join(", ")}
-                </pre>
-              </dd>
-            </div>
-            <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
-              <dt class="flex-none text-gray-500">
-                <i18n.Translate>Requires</i18n.Translate>
-              </dt>
-              <dd class="text-sm/6 ">
-                <pre>{check.requires.join(",")}</pre>
-              </dd>
-            </div>
-            <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
-              <dt class="flex-none text-gray-500">
-                <i18n.Translate>Fallback</i18n.Translate>
-              </dt>
-              <dd class="text-sm/6 ">
-                <pre>{check.fallback}</pre>
-              </dd>
-            </div>
-          </dl>
-          <div class="px-4 pb-2"></div>
+
+      <DescribeMeasure measure={form.status.result} summary={summary} />
+    </Fragment>
+  );
+}
+
+export function MeasureForm({
+  summary,
+  onCancel,
+  onAdded,
+  onChanged,
+  onRemoved,
+  initial,
+  addingNew,
+}: {
+  initial?: Partial<MeasureDefinition>;
+  addingNew?: boolean;
+  summary: AvailableMeasureSummary;
+  onCancel: () => void;
+  onAdded: (name: string) => void;
+  onChanged: (name: string) => void;
+  onRemoved: (name: string) => void;
+}) {
+  const challengeType = (initial?.context ?? []).find(
+    (c) => c.key === "challenge-type",
+  );
+  const measureIsVerificationType = challengeType !== undefined;
+  const [formType, setFormType] = useState<"verification" | "normal">(
+    measureIsVerificationType ? "verification" : "normal",
+  );
+
+  const { i18n } = useTranslationContext();
+
+  switch (formType) {
+    case "verification": {
+      const cType = JSON.parse(challengeType?.value as any)
+      return (
+        <div>
+          <h2 class="mt-4 mb-2">
+            <i18n.Translate>Configure verification type: 
{cType}</i18n.Translate>
+          </h2>
+          <div>
+            <button
+              onClick={async () => {
+                setFormType("normal");
+              }}
+              class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center 
text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
+            >
+              <i18n.Translate>Show complete form</i18n.Translate>
+            </button>
+          </div>
+
+          <VerificationMeasureForm
+            onAdded={onAdded}
+            onCancel={onCancel}
+            onChanged={onChanged}
+            onRemoved={onRemoved}
+            summary={summary}
+            addingNew={addingNew}
+            initial={initial}
+            challengeType={cType}
+          />
         </div>
-      )}
-      {!context || !context.length ? undefined : (
-        <div class="mt-6 rounded-lg bg-gray-150 ring-1 shadow-lg 
border-indigo-700 border ring-gray-900/5 ">
-          <dl class="flex flex-wrap">
-            <div class="flex-auto pt-4 pl-4 bg-indigo-600 rounded-t-lg">
-              <dt class="text-sm/6  text-white">
-                <i18n.Translate>Context</i18n.Translate>
-              </dt>
-              <dd class="mt-1 text-base font-semibold text-white"></dd>
+      );
+    }
+    case "normal": {
+      return (
+        <div>
+          <h2 class="mt-4 mb-2">
+            <i18n.Translate>Configure measure</i18n.Translate>
+          </h2>
+          {measureIsVerificationType ? (
+            <div>
+              <button
+                onClick={async () => {
+                  setFormType("verification");
+                }}
+                class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center 
text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
+              >
+                <i18n.Translate>Show as verification</i18n.Translate>
+              </button>
             </div>
-            {context.map(({ key, value }) => {
-              return (
-                <div key={key} class="mt-4 flex w-full flex-none gap-x-4 px-6">
-                  <dt class="flex-none text-gray-500">{key}</dt>
-                  <dd class="text-sm/6 ">
-                    <i18n.Translate>{value}</i18n.Translate>
-                  </dd>
-                </div>
-              );
-            })}
-          </dl>
-          <div class="px-4 pb-2"></div>
+          ) : undefined}
+
+          <NormalMeasureForm
+            onAdded={onAdded}
+            onCancel={onCancel}
+            onChanged={onChanged}
+            onRemoved={onRemoved}
+            summary={summary}
+            addingNew={addingNew}
+            initial={initial}
+          />
         </div>
-      )}
-    </div>
-  );
+      );
+    }
+    default: {
+      assertUnreachable(formType);
+    }
+  }
 }
 
 const formDesign = (
@@ -368,7 +524,7 @@ const formDesign = (
   checks: { key: string; value: KycCheckInformation }[],
   summary: AvailableMeasureSummary,
   cantChangeName: boolean,
-): FormDesign<KycRule> => ({
+): FormDesign => ({
   type: "single-column",
   fields: [
     {
@@ -601,3 +757,238 @@ function validateContextValueByType(
   }
   return undefined;
 }
+
+function DescribeProgram({
+  name,
+  program,
+}: {
+  name: string;
+  program: AmlProgramRequirement;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  return (
+    <div class="rounded-lg bg-gray-150 ring-1 shadow-lg border-indigo-700 
border ring-gray-900/5 ">
+      <dl class="flex flex-wrap">
+        <div class="flex-auto pt-4 pl-4 bg-indigo-600 rounded-t-lg">
+          <dt class="text-sm/6  text-white">
+            <i18n.Translate>Program</i18n.Translate>
+          </dt>
+          <dd class="mt-1 text-base font-semibold text-white">{name}</dd>
+        </div>
+        <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
+          <dt class="flex-none text-gray-500">
+            <i18n.Translate>Description</i18n.Translate>
+          </dt>
+          <dd class="text-sm/6 ">
+            <i18n.Translate>{program.description}</i18n.Translate>
+          </dd>
+        </div>
+        <div class="mt-2 flex w-full flex-none gap-x-4 border-t 
border-gray-900/5 px-6 pt-2">
+          <dt class="flex-none text-gray-500">
+            <i18n.Translate>Context</i18n.Translate>
+          </dt>
+          <dd class="text-sm/6 font-medium text-gray-900">
+            <pre>{program.context.join(",")}</pre>
+          </dd>
+        </div>
+        <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
+          <dt class="flex-none text-gray-500">
+            <i18n.Translate>Inputs</i18n.Translate>
+          </dt>
+          <dd class="text-sm/6 ">
+            <pre class="whitespace-pre-wrap">{program.inputs.join(",")}</pre>
+          </dd>
+        </div>
+      </dl>
+      <div class="px-4 pb-2"></div>
+    </div>
+  );
+}
+function DescribeCheck({
+  name,
+  check,
+}: {
+  name: string;
+  check: KycCheckInformation;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  return (
+    <div class="mt-6 rounded-lg bg-gray-150 ring-1 shadow-lg border-indigo-700 
border ring-gray-900/5 ">
+      <dl class="flex flex-wrap">
+        <div class="flex-auto pt-4 pl-4 bg-indigo-600 rounded-t-lg">
+          <dt class="text-sm/6  text-white">
+            <i18n.Translate>Check</i18n.Translate>
+          </dt>
+          <dd class="mt-1 text-base font-semibold text-white">{name}</dd>
+        </div>
+        <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
+          <dt class="flex-none text-gray-500">Description</dt>
+          <dd class="text-sm/6 ">
+            <i18n.Translate>{check.description}</i18n.Translate>
+          </dd>
+        </div>
+        <div class="mt-2 flex w-full flex-none gap-x-4 border-t 
border-gray-900/5 px-6 pt-2">
+          <dt class="flex-none text-gray-500">
+            <i18n.Translate>Output</i18n.Translate>
+          </dt>
+          <dd class="text-sm/6 font-medium ">
+            <pre class="whitespace-break-spaces">
+              {check.outputs.join(", ")}
+            </pre>
+          </dd>
+        </div>
+        <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
+          <dt class="flex-none text-gray-500">
+            <i18n.Translate>Requires</i18n.Translate>
+          </dt>
+          <dd class="text-sm/6 ">
+            <pre>{check.requires.join(",")}</pre>
+          </dd>
+        </div>
+        <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
+          <dt class="flex-none text-gray-500">
+            <i18n.Translate>Fallback</i18n.Translate>
+          </dt>
+          <dd class="text-sm/6 ">
+            <pre>{check.fallback}</pre>
+          </dd>
+        </div>
+      </dl>
+      <div class="px-4 pb-2"></div>
+    </div>
+  );
+}
+function DescribeContext({
+  context,
+}: {
+  context: {
+    key: string;
+    type: "string" | "number" | "boolean" | "json";
+    value: string;
+  }[];
+}): VNode {
+  const { i18n } = useTranslationContext();
+  return (
+    <div class="mt-6 rounded-lg bg-gray-150 ring-1 shadow-lg border-indigo-700 
border ring-gray-900/5 ">
+      <dl class="flex flex-wrap">
+        <div class="flex-auto pt-4 pl-4 bg-indigo-600 rounded-t-lg">
+          <dt class="text-sm/6  text-white">
+            <i18n.Translate>Context</i18n.Translate>
+          </dt>
+          <dd class="mt-1 text-base font-semibold text-white"></dd>
+        </div>
+        {context.map(({ key, value }) => {
+          return (
+            <div key={key} class="mt-4 flex w-full flex-none gap-x-4 px-6">
+              <dt class="flex-none text-gray-500">{key}</dt>
+              <dd class="text-sm/6 ">
+                <i18n.Translate>{value}</i18n.Translate>
+              </dd>
+            </div>
+          );
+        })}
+      </dl>
+      <div class="px-4 pb-2"></div>
+    </div>
+  );
+}
+function DescribeMeasure({
+  measure,
+  summary,
+}: {
+  measure: RecursivePartial<MeasureDefinition>;
+  summary: AvailableMeasureSummary;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  const programName: string | undefined = measure.program;
+  const program: AmlProgramRequirement | undefined =
+    !programName || !summary.programs[programName]
+      ? undefined
+      : summary.programs[programName];
+
+  const checkName: string | undefined = measure.check;
+  const check =
+    !checkName || !summary.checks[checkName]
+      ? undefined
+      : summary.checks[checkName];
+
+  const context =
+    !measure || !measure.context
+      ? []
+      : (measure.context as MeasureDefinition["context"]);
+
+  return (
+    <Fragment>
+      <h2 class="mt-4 mb-2">
+        <i18n.Translate>Description</i18n.Translate>
+      </h2>
+
+      {!program || !programName ? undefined : (
+        <DescribeProgram name={programName} program={program} />
+      )}
+      {!check || !checkName ? undefined : (
+        <DescribeCheck name={checkName} check={check} />
+      )}
+      {!context || !context.length ? undefined : (
+        <DescribeContext context={context} />
+      )}
+    </Fragment>
+  );
+}
+
+const verificationFormDesign = (
+  i18n: InternationalizationAPI,
+  summary: AvailableMeasureSummary,
+  cantChangeName: boolean,
+  challengeType: "email" | "phone" | "postal",
+): FormDesign => {
+  const em =
+    challengeType === "email"
+      ? design_challenger_email(i18n)
+      : challengeType === "phone"
+        ? design_challenger_phone(i18n)
+        : challengeType === "postal"
+          ? design_challenger_postal(i18n)
+          : undefined;
+
+  if (!em) {
+    throw Error(`unkown challenge type ${challengeType} `);
+  }
+
+  const fields = em.fields.map((f) => {
+    f.disabled = false;
+    f.required = false;
+    if ("id" in f) {
+      f.id = `address.${f.id}`;
+    }
+    return f;
+  });
+
+  return {
+    type: "single-column",
+    fields: [
+      {
+        id: "name",
+        type: "text",
+        required: true,
+        disabled: cantChangeName,
+        label: i18n.str`Name`,
+        help: i18n.str`Name of the verfication measure`,
+        validator(value) {
+          return !value
+            ? i18n.str`required`
+            : summary.roots[value]
+              ? i18n.str`There is already a measure with that name`
+              : undefined;
+        },
+      },
+      {
+        type: "toggle",
+        id: "readOnly",
+        label: i18n.str`Read only`,
+        help: i18n.str`Prevent the customer of changing the address`,
+      },
+      ...fields,
+    ],
+  };
+};
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Events.tsx 
b/packages/aml-backoffice-ui/src/pages/decision/Events.tsx
index 0786ef96a..468f0e272 100644
--- a/packages/aml-backoffice-ui/src/pages/decision/Events.tsx
+++ b/packages/aml-backoffice-ui/src/pages/decision/Events.tsx
@@ -108,7 +108,7 @@ const formDesign = (
     triggered: string[];
     rest: string[];
   },
-): FormDesign<MeasureInformation> => ({
+): FormDesign => ({
   type: "double-column",
   sections: [
     {
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Justification.tsx 
b/packages/aml-backoffice-ui/src/pages/decision/Justification.tsx
index 35eb7a7ff..da0d16a72 100644
--- a/packages/aml-backoffice-ui/src/pages/decision/Justification.tsx
+++ b/packages/aml-backoffice-ui/src/pages/decision/Justification.tsx
@@ -54,7 +54,7 @@ type FormType = {
 const formDesign = (
   i18n: InternationalizationAPI,
   unknownAccount: boolean,
-): FormDesign<FormType> => ({
+): FormDesign => ({
   type: "single-column",
   fields: [
     {
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Measures.tsx 
b/packages/aml-backoffice-ui/src/pages/decision/Measures.tsx
index 3d503ee55..175798866 100644
--- a/packages/aml-backoffice-ui/src/pages/decision/Measures.tsx
+++ b/packages/aml-backoffice-ui/src/pages/decision/Measures.tsx
@@ -1,5 +1,6 @@
 import {
   assertUnreachable,
+  KycCheckInformation,
   MeasureInformation,
   TalerError,
   TalerExchangeApi,
@@ -35,6 +36,34 @@ export function Measures({}: {}): VNode {
     isNew: boolean;
     template: Partial<MeasureDefinition>;
   }>(); //test;
+
+  const measures = useServerMeasures();
+
+  const measureBody =
+    !measures || measures instanceof TalerError || measures.type === "fail"
+      ? undefined
+      : measures.body;
+
+  const measureList = (
+    !measureBody ? [] : Object.entries(measureBody.roots)
+  ).map(convertToMeasureType);
+
+  const requestCustomMeasures = request?.custom_measures ?? {};
+  const customMeasures = Object.entries(requestCustomMeasures).map(
+    convertToMeasureType,
+  );
+
+  const checkList = !measureBody ? [] : Object.entries(measureBody.checks);
+  const simpleChecks = checkList
+    .map(convertCheckToMeasureType)
+    .filter((d): d is MeasureType => d !== undefined);
+
+  const allMeasures: MeasureType[] = [
+    ...measureList,
+    ...customMeasures,
+    ...simpleChecks,
+  ];
+
   if (addMeasure) {
     return (
       <NewMeasure
@@ -67,7 +96,19 @@ export function Measures({}: {}): VNode {
 
   return (
     <Fragment>
-      <ActiveMeasureForm />
+      {!allMeasures.length ? undefined : (
+        <ActiveMeasureForm
+          editMeasure={(template) => {
+            template.name!
+            setAddMeasure({
+              isNew: true,
+              template,
+            });
+          }}
+          measures={allMeasures}
+          newMeasures={!request.new_measures ? [] : request.new_measures}
+        />
+      )}
       <ShowAllMeasures
         addNewMeasure={(template) => {
           setAddMeasure({
@@ -86,83 +127,76 @@ export function Measures({}: {}): VNode {
   );
 }
 
-function ActiveMeasureForm(): VNode {
-  const { i18n } = useTranslationContext();
-  const [request, updateRequest] = useCurrentDecisionRequest();
-  const measures = useServerMeasures();
-
-  const measureBody =
-    !measures || measures instanceof TalerError || measures.type === "fail"
-      ? undefined
-      : measures.body;
-
-  const measureList = (!measureBody ? [] : Object.keys(measureBody.roots)).map(
-    (m) =>
-      ({
-        type: "normal",
-        name: m,
-      }) satisfies NormalMeasure,
-  );
-
-  const requestCustomMeasures = request?.custom_measures ?? {}
-  const customMeasures = Object.keys(requestCustomMeasures).map(
-    (m) =>
-      ({
-        type: "normal",
-        name: m,
-      }) satisfies NormalMeasure,
-  );
-
-  const checkList = !measureBody ? [] : Object.entries(measureBody.checks);
-  const simpleChecks = checkList
-    .filter(
-      ([, check]) => check.outputs.length > 0 && check.requires.length > 0,
-    )
-    .map(
-      ([key]) =>
-        ({
-          type: "simple-check-form",
-          checkName: key,
-        }) satisfies SimpleCheckMeasure,
-    );
+function convertCheckToMeasureType([checkName, check]: [
+  string,
+  KycCheckInformation,
+]): MeasureType | undefined {
+  if (check.outputs.length === 0 && check.requires.length === 0) {
+    return {
+      type: "simple-check-form",
+      name: `check-${checkName}`,
+      checkName,
+    };
+  }
+  return undefined;
+}
 
-  const allMeasures: MeasureType[] = [
-    ...measureList,
-    ...customMeasures,
-    ...simpleChecks,
-  ];
+const validChallengeType = ["email", "phone", "postal"];
+function convertToMeasureType([name, measure]: [
+  string,
+  MeasureInformation,
+]): MeasureType {
+  if (measure.context) {
+    // @ts-expect-error
+    const challengeType = measure.context["challenge-type"] as string;
+    if (validChallengeType.indexOf(challengeType) !== -1) {
+      return {
+        type: "verify-template",
+        name,
+        measure,
+        // @ts-expect-error challenger type is validated
+        challengerType: challengeType,
+      };
+    }
+  }
+  return {
+    type: "normal",
+    name,
+  };
+}
 
-  const design = formDesign(i18n, allMeasures);
+function ActiveMeasureForm({
+  measures,
+  editMeasure,
+  newMeasures,
+}: {
+  measures: MeasureType[];
+  newMeasures: string[];
+  editMeasure: (m: Partial<MeasureDefinition>) => void;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  const [request, updateRequest] = useCurrentDecisionRequest();
 
-  const nm = (!request.new_measures ? [] : request.new_measures).map(
-    (m) =>
-      ({
-        type: "normal",
-        name: m,
-      }) satisfies NormalMeasure,
-  );
+  const design = formDesign(i18n, measures);
 
-  const initValue = useMemo<FormType>(
-    () => ({ measures: nm }),
-    [request.new_measures],
-  );
+  const form = useForm<FormType>(design, { measures: newMeasures });
+  const requestCustomMeasures = request?.custom_measures ?? {};
 
-  const form = useForm<FormType>(design, initValue);
   onComponentUnload(() => {
     const newMeasures: string[] = [];
     const formMeasures = form.status.result.measures ?? [];
-    for (const m of formMeasures) {
+    for (const name of formMeasures) {
+      newMeasures.push(name);
+      const m = measures.find((d) => d.name === name)!;
       switch (m.type) {
-        case "normal": {
-          newMeasures.push(m.name)
-          break;
-        }
         case "simple-check-form": {
-          const generatedId = `check-${m.checkName}`
-          requestCustomMeasures[generatedId] = {
+          requestCustomMeasures[m.name] = {
             check_name: m.checkName,
-          }
-          newMeasures.push(generatedId)
+          };
+          break;
+        }
+        case "normal":
+        case "verify-template": {
           break;
         }
         default: {
@@ -176,7 +210,44 @@ function ActiveMeasureForm(): VNode {
     });
   });
 
-  return <FormUI design={design} model={form.model} />;
+  const selected = form.status.result.measures ?? [];
+
+  const selectedVerifyMeasure = selected
+    .map((s) => measures.find((d) => d.name === s))
+    .filter((d) => d !== undefined && d.type === "verify-template")
+    .filter((c) => requestCustomMeasures[c.name] === undefined);
+
+  return (
+    <Fragment>
+      <FormUI design={design} model={form.model} />
+
+      <div>
+        {selectedVerifyMeasure.map((ver) => {
+          return (
+            <button
+              onClick={() => {
+                editMeasure({
+                  check: ver.measure.check_name,
+                  context: !ver.measure.context
+                    ? []
+                    : Object.entries(ver.measure.context).map(([key, value]) 
=> ({
+                        key,
+                        type: "json",
+                        value: JSON.stringify(value),
+                      })),
+                  name: ver.name,
+                  program: ver.measure.prog_name,
+                });
+              }}
+              class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center 
text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 
disabled:bg-gray-600"
+            >
+              <i18n.Translate>Configure verfication measure: "{ver.name}" 
</i18n.Translate>
+            </button>
+          );
+        })}
+      </div>
+    </Fragment>
+  );
 }
 
 function ShowAllMeasures({
@@ -269,7 +340,7 @@ function ShowAllMeasures({
   );
 }
 
-type MeasureType = NormalMeasure | SimpleCheckMeasure;
+type MeasureType = NormalMeasure | SimpleCheckMeasure | VerifyMeasure;
 
 /**
  * Normal measures are custom measures or server defined measure.
@@ -288,17 +359,25 @@ type NormalMeasure = {
  */
 type SimpleCheckMeasure = {
   type: "simple-check-form";
+  name: string;
   checkName: string;
 };
 
+type VerifyMeasure = {
+  type: "verify-template";
+  name: string;
+  measure: MeasureInformation,
+  challengeType: "email" | "phone" | "postal";
+};
+
 type FormType = {
-  measures: MeasureType[];
+  measures: string[];
 };
 
 function formDesign(
   i18n: InternationalizationAPI,
   measureNames: MeasureType[],
-): FormDesign<FormType> {
+): FormDesign {
   return {
     type: "single-column",
     fields: [
@@ -310,19 +389,26 @@ function formDesign(
             case "normal": {
               return {
                 label: me.name,
-                value: me,
+                value: me.name,
               };
             }
             case "simple-check-form": {
               return {
                 label: `CHECK: ${me.checkName}`,
-                value: me,
+                value: `check-${me.checkName}`,
               };
             }
+            case "verify-template": {
+              return {
+                label: me.name,
+                value: me.name,
+              };
+            }
+            default: {
+              assertUnreachable(me);
+            }
           }
-          // FIXME: choises should allow value to be any type 
-          // check: why do we require value to be string?
-        }) as any,
+        }),
         id: "measures",
         label: i18n.str`Active measures`,
         help: i18n.str`Measures that the customer will need to satisfy while 
the rules are active.`,
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx 
b/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx
index 4ac956254..cdf84047f 100644
--- a/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx
+++ b/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx
@@ -144,7 +144,7 @@ export type PropertiesForm = {
 export const propertiesForm = (
   i18n: InternationalizationAPI,
   props: UIFormElementConfig[],
-): FormDesign<PropertiesForm> => ({
+): FormDesign => ({
   type: "double-column",
   sections: [
     {
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx 
b/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx
index d95bf8a7f..3f83f7e28 100644
--- a/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx
+++ b/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx
@@ -535,7 +535,7 @@ const ruleFormDesignTemplate = (
   currency: string,
   measureNames: string[],
   isWallet: boolean,
-): FormDesign<KycRule> => ({
+): FormDesign => ({
   type: "single-column",
   fields: [
     {
@@ -595,7 +595,7 @@ const ruleFormDesignTemplate = (
 const expirationFormDesignTemplate = (
   i18n: InternationalizationAPI,
   measureNames: string[],
-): FormDesign<KycRule> => ({
+): FormDesign => ({
   type: "single-column",
   fields: [
     {
diff --git a/packages/challenger-ui/src/pages/AskChallenge.tsx 
b/packages/challenger-ui/src/pages/AskChallenge.tsx
index 4f3f58c07..bb6e934ee 100644
--- a/packages/challenger-ui/src/pages/AskChallenge.tsx
+++ b/packages/challenger-ui/src/pages/AskChallenge.tsx
@@ -578,10 +578,10 @@ function getFormDesignBasedOnAddressType(
               if (restriction.regex && !restriction.regex.test(text)) {
                 return restriction.hint;
               }
-              const prev = prevValue[TalerFormAttributes.CONTACT_EMAIL];
-              if (prev === text) {
-                return i18n.str`Can't use the same address`;
-              }
+              // const prev = prevValue[TalerFormAttributes.CONTACT_EMAIL];
+              // if (prev === text) {
+              //   return i18n.str`Can't use the same address`;
+              // }
               return undefined;
             },
           },
@@ -606,10 +606,10 @@ function getFormDesignBasedOnAddressType(
               if (restriction.regex && !restriction.regex.test(text)) {
                 return restriction.hint;
               }
-              const prev = prevValue[TalerFormAttributes.CONTACT_PHONE];
-              if (prev === text) {
-                return i18n.str`Can't use the same number`;
-              }
+              // const prev = prevValue[TalerFormAttributes.CONTACT_PHONE];
+              // if (prev === text) {
+              //   return i18n.str`Can't use the same number`;
+              // }
               return undefined;
             },
           },
diff --git a/packages/web-util/src/forms/forms-types.ts 
b/packages/web-util/src/forms/forms-types.ts
index 1b54b973f..f85cd82e0 100644
--- a/packages/web-util/src/forms/forms-types.ts
+++ b/packages/web-util/src/forms/forms-types.ts
@@ -34,7 +34,7 @@ import {
   TranslatedString,
 } from "@gnu-taler/taler-util";
 
-export type FormDesign<T = unknown> =
+export type FormDesign =
   | DoubleColumnFormDesign
   | SingleColumnFormDesign;
 
diff --git a/packages/web-util/src/forms/gana/challenger_email.ts 
b/packages/web-util/src/forms/gana/challenger_email.ts
index 5794e10a5..3920ae706 100644
--- a/packages/web-util/src/forms/gana/challenger_email.ts
+++ b/packages/web-util/src/forms/gana/challenger_email.ts
@@ -20,6 +20,7 @@ import {
   DoubleColumnFormDesign,
   FormMetadata,
   InternationalizationAPI,
+  SingleColumnFormDesign,
 } from "../../index.browser.js";
 
 export const form_challenger_email = (
@@ -37,23 +38,18 @@ export const form_challenger_email = (
  */
 export function design_challenger_email(
   i18n: InternationalizationAPI,
-): DoubleColumnFormDesign {
+): SingleColumnFormDesign {
   const today = format(new Date(), "yyyy-MM-dd");
 
   return {
-    type: "double-column",
-    sections: [
+    type: "single-column",
+    fields: [
       {
-        title: i18n.str`Challenge`,
-        fields: [
-          {
-            id: TalerFormAttributes.CONTACT_EMAIL,
-            label: i18n.str`E-Mail`,
-            type: "text",
-            required: true,
-            disabled: true,
-          },
-        ],
+        id: TalerFormAttributes.CONTACT_EMAIL,
+        label: i18n.str`E-Mail`,
+        type: "text",
+        required: true,
+        disabled: true,
       },
     ],
   };
diff --git a/packages/web-util/src/forms/gana/challenger_postal.ts 
b/packages/web-util/src/forms/gana/challenger_postal.ts
index 54b3125cf..7178e0085 100644
--- a/packages/web-util/src/forms/gana/challenger_postal.ts
+++ b/packages/web-util/src/forms/gana/challenger_postal.ts
@@ -21,6 +21,7 @@ import {
   DoubleColumnFormDesign,
   FormMetadata,
   InternationalizationAPI,
+  SingleColumnFormDesign,
 } from "../../index.browser.js";
 
 export const form_challenger_postal = (
@@ -38,38 +39,33 @@ export const form_challenger_postal = (
  */
 export function design_challenger_postal(
   i18n: InternationalizationAPI,
-): DoubleColumnFormDesign {
+): SingleColumnFormDesign {
   const today = format(new Date(), "yyyy-MM-dd");
 
   return {
-    type: "double-column",
-    sections: [
+    type: "single-column",
+    fields: [
       {
-        title: i18n.str`Challenge`,
-        fields: [
-          {
-            id: TalerFormAttributes.CONTACT_NAME,
-            label: i18n.str`Name`,
-            type: "text",
-            required: true,
-            disabled: true,
-          },
-          {
-            id: TalerFormAttributes.ADDRESS_LINES,
-            label: i18n.str`Address`,
-            type: "textArea",
-            required: true,
-            disabled: true,
-          },
-          {
-            id: TalerFormAttributes.ADDRESS_COUNTRY,
-            label: i18n.str`Country`,
-            type: "selectOne",
-            choices: countryNameList(i18n),
-            required: true,
-            disabled: true,
-          },
-        ],
+        id: TalerFormAttributes.CONTACT_NAME,
+        label: i18n.str`Name`,
+        type: "text",
+        required: true,
+        disabled: true,
+      },
+      {
+        id: TalerFormAttributes.ADDRESS_LINES,
+        label: i18n.str`Address`,
+        type: "textArea",
+        required: true,
+        disabled: true,
+      },
+      {
+        id: TalerFormAttributes.ADDRESS_COUNTRY,
+        label: i18n.str`Country`,
+        type: "selectOne",
+        choices: countryNameList(i18n),
+        required: true,
+        disabled: true,
       },
     ],
   };
diff --git a/packages/web-util/src/forms/gana/challenger_sms.ts 
b/packages/web-util/src/forms/gana/challenger_sms.ts
index d98d65ed4..050a0552c 100644
--- a/packages/web-util/src/forms/gana/challenger_sms.ts
+++ b/packages/web-util/src/forms/gana/challenger_sms.ts
@@ -20,6 +20,7 @@ import {
   DoubleColumnFormDesign,
   FormMetadata,
   InternationalizationAPI,
+  SingleColumnFormDesign,
 } from "../../index.browser.js";
 
 export const form_challenger_sms = (
@@ -37,23 +38,18 @@ export const form_challenger_sms = (
  */
 export function design_challenger_phone(
   i18n: InternationalizationAPI,
-): DoubleColumnFormDesign {
+): SingleColumnFormDesign {
   const today = format(new Date(), "yyyy-MM-dd");
 
   return {
-    type: "double-column",
-    sections: [
+    type: "single-column",
+    fields: [
       {
-        title: i18n.str`Challenge`,
-        fields: [
-          {
-            id: TalerFormAttributes.CONTACT_PHONE,
-            label: i18n.str`Phone`,
-            type: "text",
-            required: true,
-            disabled: true,
-          },
-        ],
+        id: TalerFormAttributes.CONTACT_PHONE,
+        label: i18n.str`Phone`,
+        type: "text",
+        required: true,
+        disabled: true,
       },
     ],
   };
diff --git a/packages/web-util/src/hooks/useForm.ts 
b/packages/web-util/src/hooks/useForm.ts
index 1e5b150d5..b6e1462bf 100644
--- a/packages/web-util/src/hooks/useForm.ts
+++ b/packages/web-util/src/hooks/useForm.ts
@@ -140,7 +140,7 @@ export type FormState<T> = {
  * Hook to instantiate a form from its design.
  */
 export function useForm<T>(
-  design: FormDesign<T>,
+  design: FormDesign,
   initialValue: RecursivePartial<FormValues<T>>,
 ): FormState<T> {
   const { i18n } = useTranslationContext();

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]