[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 © 2014—2023 Taler Systems SA. {versionText}{" "}
+ Copyright © 2014—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.