gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 01/02: change to enable new forms in KYC/AML:


From: gnunet
Subject: [taler-wallet-core] 01/02: change to enable new forms in KYC/AML:
Date: Sun, 05 Jan 2025 19:53:43 +0100

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

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

commit 8fabd3c811f95179650e621994f82335c9b7733d
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Sun Jan 5 15:52:33 2025 -0300

    change to enable new forms in KYC/AML:
---
 packages/web-util/src/components/Footer.tsx       | 56 +++++++++++++-----
 packages/web-util/src/components/LangSelector.tsx |  2 +-
 packages/web-util/src/context/api.ts              |  2 +-
 packages/web-util/src/forms/DownloadLink.tsx      | 71 +++++++++++++++++++++++
 packages/web-util/src/forms/forms.ts              | 16 +++++
 packages/web-util/src/forms/ui-form.ts            | 28 ++++++++-
 packages/web-util/src/hooks/useForm.ts            | 34 +++++++----
 packages/web-util/src/utils/base64.ts             | 14 ++---
 packages/web-util/src/utils/request.ts            |  2 +-
 9 files changed, 188 insertions(+), 37 deletions(-)

diff --git a/packages/web-util/src/components/Footer.tsx 
b/packages/web-util/src/components/Footer.tsx
index 58dd2a60a..4c0f4e044 100644
--- a/packages/web-util/src/components/Footer.tsx
+++ b/packages/web-util/src/components/Footer.tsx
@@ -1,34 +1,60 @@
 import { useTranslationContext } from "../index.browser.js";
 import { h } from "preact";
 
-export function Footer({ testingUrlKey, VERSION, GIT_HASH }: { VERSION?: 
string, GIT_HASH?: string, testingUrlKey?: string }) {
-  const { i18n } = useTranslationContext()
+export function Footer({
+  testingUrlKey,
+  VERSION,
+  GIT_HASH,
+}: {
+  VERSION?: string;
+  GIT_HASH?: string;
+  testingUrlKey?: string;
+}) {
+  const { i18n } = useTranslationContext();
 
-  const testingUrl = (testingUrlKey && typeof localStorage !== "undefined") && 
localStorage.getItem(testingUrlKey) ?
-    localStorage.getItem(testingUrlKey) ?? undefined :
-    undefined
-  const versionText = VERSION
-    ? GIT_HASH
-      ? <a href={`https://git.taler.net/wallet-core.git/tree/?id=${GIT_HASH}`} 
target="_blank" rel="noreferrer noopener">
+  const testingUrl =
+    testingUrlKey &&
+    typeof localStorage !== "undefined" &&
+    localStorage.getItem(testingUrlKey)
+      ? localStorage.getItem(testingUrlKey) ?? undefined
+      : undefined;
+  const versionText = VERSION ? (
+    GIT_HASH ? (
+      <a
+        href={`https://git.taler.net/wallet-core.git/tree/?id=${GIT_HASH}`}
+        target="_blank"
+        rel="noreferrer noopener"
+      >
         Version {VERSION} ({GIT_HASH.substring(0, 8)})
       </a>
-      : VERSION
-    : "";
+    ) : (
+      VERSION
+    )
+  ) : (
+    ""
+  );
   return (
     <footer class="bottom-4 my-4 mx-8 bg-slate-200">
       <div>
         <p class="text-xs leading-5 text-gray-400">
           <i18n.Translate>
-            Learn more about <a target="_blank" rel="noreferrer noopener" 
class="font-semibold text-gray-500 hover:text-gray-400" 
href="https://taler.net";>GNU Taler</a>
+            Learn more about{" "}
+            <a
+              target="_blank"
+              rel="noreferrer noopener"
+              class="font-semibold text-gray-500 hover:text-gray-400"
+              href="https://taler.net";
+            >
+              GNU Taler
+            </a>
           </i18n.Translate>
         </p>
       </div>
       <div style="flex-grow:1" />
       <p class="text-xs leading-5 text-gray-400">
-        Copyright &copy; 2014&mdash;2023 Taler Systems SA. {versionText}{" "}
+        Copyright &copy; 2014&mdash;2025 Taler Systems SA. {versionText}{" "}
       </p>
-      {testingUrlKey && testingUrl &&
-
+      {testingUrlKey && testingUrl && (
         <p class="text-xs leading-5 text-gray-300">
           Testing with {testingUrl}{" "}
           <a
@@ -42,7 +68,7 @@ export function Footer({ testingUrlKey, VERSION, GIT_HASH }: 
{ VERSION?: string,
             stop testing
           </a>
         </p>
-      }
+      )}
     </footer>
   );
 }
diff --git a/packages/web-util/src/components/LangSelector.tsx 
b/packages/web-util/src/components/LangSelector.tsx
index b08a1bbd2..4a5b0ea93 100644
--- a/packages/web-util/src/components/LangSelector.tsx
+++ b/packages/web-util/src/components/LangSelector.tsx
@@ -121,7 +121,7 @@ export function LangSelector({
                   e.stopPropagation();
                 }}
               >
-                <div class="flex">
+                <div class="flex h-7 w-7">
                   <img
                     alt="language"
                     class="h-7 w-7 flex-shrink-0 rounded-full"
diff --git a/packages/web-util/src/context/api.ts 
b/packages/web-util/src/context/api.ts
index 89561e239..09e5d1add 100644
--- a/packages/web-util/src/context/api.ts
+++ b/packages/web-util/src/context/api.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
+ (C) 2021-2025 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/web-util/src/forms/DownloadLink.tsx 
b/packages/web-util/src/forms/DownloadLink.tsx
new file mode 100644
index 000000000..f4f8c6894
--- /dev/null
+++ b/packages/web-util/src/forms/DownloadLink.tsx
@@ -0,0 +1,71 @@
+import { TranslatedString } from "@gnu-taler/taler-util";
+import { VNode, h } from "preact";
+import { LabelWithTooltipMaybeRequired, RenderAddon } from "./InputLine.js";
+import { Addon } from "./FormProvider.js";
+
+interface Props {
+  label: TranslatedString;
+  url: string;
+  media?: string;
+  tooltip?: TranslatedString;
+  help?: TranslatedString;
+  before?: Addon;
+  after?: Addon;
+}
+
+export function DownloadLink({
+  before,
+  after,
+  label,
+  url,
+  media,
+  tooltip,
+  help,
+}: Props): VNode {
+  return (
+    <div class="sm:col-span-6">
+      {before !== undefined && <RenderAddon addon={before} />}
+      <a
+        href="#"
+        onClick={(e) => {
+          return (
+            fetch(url, {
+              headers: {
+                "Content-Type": media ?? "text/html",
+              },
+              cache: "no-cache",
+            })
+              // .then((r) => r.text())
+              .then((r) => r.arrayBuffer())
+              .then((r) => {
+                const b64 = window.btoa(
+                  new Uint8Array(r).reduce(
+                    (data, byte) => data + String.fromCharCode(byte),
+                    "",
+                  ),
+                );
+
+                const a = document.createElement("a");
+                a.href = `data:${media ?? "text/html"};base64,${b64}`;
+                a.download = "";
+                document.body.appendChild(a);
+                a.click();
+                document.body.removeChild(a);
+                return;
+              })
+          );
+        }}
+        media={media}
+        download
+      >
+        {label}
+      </a>
+      {after !== undefined && <RenderAddon addon={after} />}
+      {help && (
+        <p class="mt-2 text-sm text-gray-500" id="email-description">
+          {help}
+        </p>
+      )}
+    </div>
+  );
+}
diff --git a/packages/web-util/src/forms/forms.ts 
b/packages/web-util/src/forms/forms.ts
index e43459aa4..178a3b626 100644
--- a/packages/web-util/src/forms/forms.ts
+++ b/packages/web-util/src/forms/forms.ts
@@ -21,12 +21,14 @@ import {
 import { assertUnreachable, TranslatedString } from "@gnu-taler/taler-util";
 import { UIFormFieldBaseConfig, UIFormElementConfig } from "./ui-form.js";
 import { HtmlIframe } from "./HtmlIframe.js";
+import { DownloadLink } from "./DownloadLink.js";
 /**
  * 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];
+  "download-link": Parameters<typeof DownloadLink>[0];
   htmlIframe: Parameters<typeof HtmlIframe>[0];
   array: Parameters<typeof InputArray<T, K>>[0];
   file: Parameters<typeof InputFile<T, K>>[0];
@@ -48,6 +50,7 @@ type FieldType<T extends object = any, K extends keyof T = 
any> = {
 export type UIFormField =
   | { type: "group"; properties: FieldType["group"] }
   | { type: "caption"; properties: FieldType["caption"] }
+  | { type: "download-link"; properties: FieldType["download-link"] }
   | { type: "htmlIframe"; properties: FieldType["htmlIframe"] }
   | { type: "array"; properties: FieldType["array"] }
   | { type: "file"; properties: FieldType["file"] }
@@ -87,6 +90,7 @@ type UIFormFieldMap = {
  */
 const UIFormConfiguration: UIFormFieldMap = {
   group: Group,
+  "download-link": DownloadLink,
   caption: Caption,
   htmlIframe: HtmlIframe,
   //@ts-ignore
@@ -177,6 +181,18 @@ export function convertUiField(
         };
         return resp;
       }
+      case "download-link": {
+        const resp: UIFormField = {
+          type: config.type,
+          properties: {
+            ...converBaseFieldsProps(i18n_, config),
+            label: i18n_.str`${config.label}`,
+            url: config.url,
+            media: config.media,
+          },
+        };
+        return resp;
+      }
       case "htmlIframe": {
         const resp: UIFormField = {
           type: config.type,
diff --git a/packages/web-util/src/forms/ui-form.ts 
b/packages/web-util/src/forms/ui-form.ts
index ad5604ef7..14f22cc1f 100644
--- a/packages/web-util/src/forms/ui-form.ts
+++ b/packages/web-util/src/forms/ui-form.ts
@@ -46,6 +46,7 @@ export type DoubleColumnFormSection = {
 export type UIFormElementConfig =
   | UIFormElementGroup
   | UIFormElementCaption
+  | UIFormElementDownloadLink
   | UIFormElementHtmlIframe
   | UIFormFieldAbsoluteTime
   | UIFormFieldAmount
@@ -82,6 +83,11 @@ type UIFormFieldArray = {
 } & UIFormFieldBaseConfig;
 
 type UIFormElementCaption = { type: "caption" } & UIFieldElementDescription;
+type UIFormElementDownloadLink = {
+  type: "download-link";
+  url: string;
+  media?: string;
+} & UIFieldElementDescription;
 type UIFormElementHtmlIframe = {
   type: "htmlIframe";
   url: string;
@@ -95,11 +101,13 @@ type UIFormElementGroup = {
 type UIFormFieldChoiseHorizontal = {
   type: "choiceHorizontal";
   choices: Array<SelectUiChoice>;
+  allowFreeForm?: boolean;
 } & UIFormFieldBaseConfig;
 
 type UIFormFieldChoiseStacked = {
   type: "choiceStacked";
   choices: Array<SelectUiChoice>;
+  allowFreeForm?: boolean;
 } & UIFormFieldBaseConfig;
 
 type UIFormFieldFile = {
@@ -117,7 +125,7 @@ type UIFormFieldInteger = {
   min?: Integer;
 } & UIFormFieldBaseConfig;
 
-interface SelectUiChoice {
+export interface SelectUiChoice {
   label: string;
   description?: string;
   value: string;
@@ -129,17 +137,19 @@ type UIFormFieldSelectMultiple = {
   min?: Integer;
   unique?: boolean;
   choices: Array<SelectUiChoice>;
+  allowFreeForm?: boolean;
 } & UIFormFieldBaseConfig;
 
 type UIFormFieldSelectOne = {
   type: "selectOne";
   choices: Array<SelectUiChoice>;
+  allowFreeForm?: boolean;
 } & UIFormFieldBaseConfig;
 type UIFormFieldText = { type: "text" } & UIFormFieldBaseConfig;
 type UIFormFieldTextArea = { type: "textArea" } & UIFormFieldBaseConfig;
 type UIFormFieldToggle = {
   type: "toggle";
-  threeState: boolean;
+  threeState?: boolean;
 } & UIFormFieldBaseConfig;
 
 export type UIFieldElementDescription = {
@@ -243,6 +253,13 @@ const codecForUiFormFieldCaption = (): 
Codec<UIFormElementCaption> =>
     .property("type", codecForConstString("caption"))
     .build("UIFormFieldCaption");
 
+const codecForUIFormElementLink = (): Codec<UIFormElementDownloadLink> =>
+  codecForUIFormFieldBaseDescriptionTemplate<UIFormElementDownloadLink>()
+    .property("type", codecForConstString("download-link"))
+    .property("url", codecForString())
+    .property("media", codecOptional(codecForString()))
+    .build("UIFormElementLink");
+
 const codecForUiFormFieldHtmlIFrame = (): Codec<UIFormElementHtmlIframe> =>
   codecForUIFormFieldBaseDescriptionTemplate<UIFormElementHtmlIframe>()
     .property("type", codecForConstString("htmlIframe"))
@@ -260,12 +277,14 @@ const codecForUiFormFieldChoiceHorizontal =
   (): Codec<UIFormFieldChoiseHorizontal> =>
     codecForUIFormFieldBaseConfigTemplate<UIFormFieldChoiseHorizontal>()
       .property("type", codecForConstString("choiceHorizontal"))
+      .property("allowFreeForm", codecOptional(codecForBoolean()))
       .property("choices", codecForList(codecForUiFormSelectUiChoice()))
       .build("UIFormFieldChoiseHorizontal");
 
 const codecForUiFormFieldChoiceStacked = (): Codec<UIFormFieldChoiseStacked> =>
   codecForUIFormFieldBaseConfigTemplate<UIFormFieldChoiseStacked>()
     .property("type", codecForConstString("choiceStacked"))
+    .property("allowFreeForm", codecOptional(codecForBoolean()))
     .property("choices", codecForList(codecForUiFormSelectUiChoice()))
     .build("UIFormFieldChoiseStacked");
 
@@ -299,12 +318,14 @@ const codecForUiFormFieldSelectMultiple =
       .property("max", codecOptional(codecForNumber()))
       .property("min", codecOptional(codecForNumber()))
       .property("unique", codecOptional(codecForBoolean()))
+      .property("allowFreeForm", codecOptional(codecForBoolean()))
       .property("choices", codecForList(codecForUiFormSelectUiChoice()))
       .build("UiFormFieldSelectMultiple");
 
 const codecForUiFormFieldSelectOne = (): Codec<UIFormFieldSelectOne> =>
   codecForUIFormFieldBaseConfigTemplate<UIFormFieldSelectOne>()
     .property("type", codecForConstString("selectOne"))
+    .property("allowFreeForm", codecOptional(codecForBoolean()))
     .property("choices", codecForList(codecForUiFormSelectUiChoice()))
     .build("UIFormFieldSelectOne");
 
@@ -329,6 +350,7 @@ const codecForUiFormField = (): Codec<UIFormElementConfig> 
=>
     .discriminateOn("type")
     .alternative("array", codecForLazy(codecForUiFormFieldArray))
     .alternative("group", codecForLazy(codecForUiFormFieldGroup))
+    .alternative("download-link", codecForUIFormElementLink())
     .alternative("absoluteTimeText", codecForUiFormFieldAbsoluteTime())
     .alternative("amount", codecForUiFormFieldAmount())
     .alternative("caption", codecForUiFormFieldCaption())
@@ -373,6 +395,7 @@ const codecForFormConfiguration = (): 
Codec<FormConfiguration> =>
 const codecForFormMetadata = (): Codec<FormMetadata> =>
   buildCodecForObject<FormMetadata>()
     .property("label", codecForString())
+    .property("description", codecOptional(codecForString()))
     .property("id", codecForString())
     .property("version", codecForNumber())
     .property("config", codecForFormConfiguration())
@@ -385,6 +408,7 @@ export const codecForUIForms = (): Codec<UiForms> =>
 
 export type FormMetadata = {
   label: string;
+  description?: string;
   id: string;
   version: number;
   config: FormConfiguration;
diff --git a/packages/web-util/src/hooks/useForm.ts 
b/packages/web-util/src/hooks/useForm.ts
index 608faa5eb..a8ce98d11 100644
--- a/packages/web-util/src/hooks/useForm.ts
+++ b/packages/web-util/src/hooks/useForm.ts
@@ -138,11 +138,21 @@ export function useFormStateFromConfig<T>(
     return undefined;
   }
   // check required fields
-  const requiredCheckResult = requiredFields.length > 0 ? 
defaultCheckAllRequired(form) : undefined;
+  const requiredCheckResult =
+    requiredFields.length > 0 ? defaultCheckAllRequired(form) : undefined;
   // verify if there is a custom check function and all required fields are ok
   // if there no custom check return "ok"
-  const status = requiredCheckResult ?? (check ? check(form) : {status: "ok" 
as const, result: form as any, errors: undefined})
-  const handler = constructFormHandler(shape, form, updateForm, 
requiredCheckResult?.errors);
+  const status =
+    requiredCheckResult ??
+    (check
+      ? check(form)
+      : { status: "ok" as const, result: form as any, errors: undefined });
+  const handler = constructFormHandler(
+    shape,
+    form,
+    updateForm,
+    requiredCheckResult?.errors,
+  );
 
   return [handler, status];
 }
@@ -220,9 +230,9 @@ export function getShapeFromFields(
     if ("id" in field) {
       // FIXME: this should be a validation when loading the form
       // consistency check
-      if (shape.indexOf(field.id) !== -1) {
-        throw Error(`already present: ${field.id}`);
-      }
+      // if (shape.indexOf(field.id) !== -1) {
+      //   throw Error(`already present: ${field.id}`);
+      // }
       shape.push(field.id);
     } else if (field.type === "group") {
       Array.prototype.push.apply(shape, getShapeFromFields(field.fields));
@@ -239,9 +249,9 @@ export function getRequiredFields(
     if ("id" in field) {
       // FIXME: this should be a validation when loading the form
       // consistency check
-      if (shape.indexOf(field.id) !== -1) {
-        throw Error(`already present: ${field.id}`);
-      }
+      // if (shape.indexOf(field.id) !== -1) {
+      //   throw Error(`already present: ${field.id}`);
+      // }
       if (!field.required) {
         return;
       }
@@ -261,7 +271,11 @@ export function validateRequiredFields<FormType>(
   fields.forEach((f) => {
     const path = f.split(".");
     const v = getValueDeeper(form as any, path);
-    result = setValueDeeper(result, path, v === undefined ? "required" : 
undefined);
+    result = setValueDeeper(
+      result,
+      path,
+      v === undefined ? "required" : undefined,
+    );
   });
   return result;
 }
diff --git a/packages/web-util/src/utils/base64.ts 
b/packages/web-util/src/utils/base64.ts
index e51591df6..4feff61be 100644
--- a/packages/web-util/src/utils/base64.ts
+++ b/packages/web-util/src/utils/base64.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
+ (C) 2021-2025 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -128,10 +128,10 @@ function base64EncArr(aBytes: Uint8Array): string {
 
 /**
  * UTF-8 array to JS string and vice versa
- * 
- * @param aBytes 
+ *
+ * @param aBytes
  * @deprecated use textEncoder
- * @returns 
+ * @returns
  */
 function UTF8ArrToStr(aBytes: Uint8Array): string {
   let sView = "";
@@ -177,10 +177,10 @@ function UTF8ArrToStr(aBytes: Uint8Array): string {
 }
 
 /**
- * 
- * @param sDOMStr 
+ *
+ * @param sDOMStr
  * @deprecated use textEncoder
- * @returns 
+ * @returns
  */
 function strToUTF8Arr(sDOMStr: string): Uint8Array {
   let nChr;
diff --git a/packages/web-util/src/utils/request.ts 
b/packages/web-util/src/utils/request.ts
index 0c11c8c8a..c8bf35238 100644
--- a/packages/web-util/src/utils/request.ts
+++ b/packages/web-util/src/utils/request.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
+ (C) 2021-2025 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software

-- 
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]