aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-09-08 18:10:26 -0300
committerSebastian <sebasjm@gmail.com>2024-09-08 18:10:40 -0300
commit19c75d414fe8427b4dc72f3021e23cf48017f249 (patch)
tree3131cf6d9a95a21bb4010f3ae8cd2fafe97cb7b3
parentf9502ad5a529e7b5d011dd0cc948473c25c3aff5 (diff)
payto form
-rw-r--r--packages/aml-backoffice-ui/src/ExchangeAmlFrame.tsx2
-rw-r--r--packages/aml-backoffice-ui/src/pages/Cases.tsx4
-rw-r--r--packages/aml-backoffice-ui/src/pages/Search.tsx357
-rw-r--r--packages/taler-util/src/payto.ts10
-rw-r--r--packages/web-util/src/forms/InputLine.tsx6
-rw-r--r--packages/web-util/src/forms/forms.ts2
6 files changed, 309 insertions, 72 deletions
diff --git a/packages/aml-backoffice-ui/src/ExchangeAmlFrame.tsx b/packages/aml-backoffice-ui/src/ExchangeAmlFrame.tsx
index b7741d4c7..a74cd09b9 100644
--- a/packages/aml-backoffice-ui/src/ExchangeAmlFrame.tsx
+++ b/packages/aml-backoffice-ui/src/ExchangeAmlFrame.tsx
@@ -215,7 +215,7 @@ export function ExchangeAmlFrame({
<div class="flex mx-auto my-4">
<main
class="block rounded-lg bg-white px-5 py-6 shadow "
- style={{ minWidth: 300 }}
+ style={{ minWidth: 600 }}
>
{children}
</main>
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index c7191332a..c274cbcb0 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -232,7 +232,7 @@ export function Cases() {
return (
<CasesUI
- filtered={true}
+ filtered={false}
records={list.body}
onFirstPage={list.isFirstPage ? undefined : list.loadFirst}
onNext={list.isLastPage ? undefined : list.loadNext}
@@ -301,7 +301,7 @@ export function CasesUnderInvestigation() {
return (
<CasesUI
- filtered={false}
+ filtered={true}
records={list.body}
onFirstPage={list.isFirstPage ? undefined : list.loadFirst}
onNext={list.isLastPage ? undefined : list.loadNext}
diff --git a/packages/aml-backoffice-ui/src/pages/Search.tsx b/packages/aml-backoffice-ui/src/pages/Search.tsx
index 047e56180..bcdca0243 100644
--- a/packages/aml-backoffice-ui/src/pages/Search.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Search.tsx
@@ -14,6 +14,15 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import {
+ buildPayto,
+ encodeCrock,
+ hashPaytoUri,
+ parsePaytoUri,
+ PaytoUri,
+ stringifyPaytoUri,
+ TranslatedString,
+} from "@gnu-taler/taler-util";
+import {
convertUiField,
getConverterById,
InternationalizationAPI,
@@ -22,7 +31,8 @@ import {
UIHandlerId,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
-import { h } from "preact";
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
import {
FormErrors,
FormStatus,
@@ -34,34 +44,16 @@ import {
import { useOfficer } from "../hooks/officer.js";
import { undefinedIfEmpty } from "./CreateAccount.js";
import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
-import { TranslatedString } from "@gnu-taler/taler-util";
-
-interface FormType {
- paytoType: "generic" | "iban" | "x-taler-bank";
-}
export function Search() {
const officer = useOfficer();
const { i18n } = useTranslationContext();
+ const [paytoUri, setPayto] = useState<PaytoUri | undefined>(undefined);
+
const paytoForm = useFormState(
getShapeFromFields(paytoTypeField(i18n)),
- { paytoType: "generic" },
- createFormValidator(i18n),
- );
-
- const secondFieldSet =
- paytoForm.status.status !== "ok"
- ? []
- : paytoForm.status.result.paytoType === "iban"
- ? ibanFields(i18n)
- : paytoForm.status.result.paytoType === "x-taler-bank"
- ? talerBankFields(i18n)
- : genericFields(i18n);
-
- const secondForm = useFormState(
- getShapeFromFields(secondFieldSet),
- {},
+ { paytoType: "iban" },
createFormValidator(i18n),
);
@@ -95,40 +87,169 @@ export function Search() {
</div>
</form>
- <form
- class="space-y-6"
- noValidate
- onSubmit={(e) => {
- e.preventDefault();
- }}
- autoCapitalize="none"
- autoCorrect="off"
- >
- <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3">
- <RenderAllFieldsByUiConfig
- fields={convertUiField(
- i18n,
- secondFieldSet,
- secondForm.handler,
- getConverterById,
- )}
- />
- </div>
- </form>
+ {paytoForm.status.status !== "ok" ? undefined : paytoForm.status.result
+ .paytoType === "x-taler-bank" ? (
+ <XTalerBankForm onSearch={setPayto} />
+ ) : paytoForm.status.result.paytoType === "iban" ? (
+ <IbanForm onSearch={setPayto} />
+ ) : (
+ <GenericForm onSearch={setPayto} />
+ )}
+ <pre>{!paytoUri ? undefined : stringifyPaytoUri(paytoUri)}</pre>
+ <pre>{!paytoUri ? undefined : encodeCrock(hashPaytoUri(paytoUri))}</pre>
</div>
);
}
+function XTalerBankForm({
+ onSearch,
+}: {
+ onSearch: (p: PaytoUri | undefined) => void;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ const fields = talerBankFields(i18n);
+ const form = useFormState(
+ getShapeFromFields(fields),
+ {},
+ createTalerBankPaytoValidator(i18n),
+ );
+ const paytoUri =
+ form.status.status === "fail"
+ ? undefined
+ : buildPayto(
+ "x-taler-bank",
+ form.status.result.hostname,
+ form.status.result.account,
+ {
+ "receiver-name": form.status.result.name,
+ },
+ );
+
+ return (
+ <form
+ class="space-y-6"
+ noValidate
+ onSubmit={(e) => {
+ e.preventDefault();
+ }}
+ autoCapitalize="none"
+ autoCorrect="off"
+ >
+ <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3">
+ <RenderAllFieldsByUiConfig
+ fields={convertUiField(i18n, fields, form.handler, getConverterById)}
+ />
+ </div>
+ <button
+ disabled={form.status.status === "fail"}
+ class="disabled:bg-gray-100 disabled:text-gray-500 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"
+ onClick={() => onSearch(paytoUri)}
+ >
+ Search
+ </button>
+ </form>
+ );
+}
+function IbanForm({
+ onSearch,
+}: {
+ onSearch: (p: PaytoUri | undefined) => void;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ const fields = ibanFields(i18n);
+ const form = useFormState(
+ getShapeFromFields(fields),
+ {},
+ createIbanPaytoValidator(i18n),
+ );
+ const paytoUri =
+ form.status.status === "fail"
+ ? undefined
+ : buildPayto("iban", form.status.result.account, undefined, {
+ "receiver-name": form.status.result.name,
+ });
+
+ return (
+ <form
+ class="space-y-6"
+ noValidate
+ onSubmit={(e) => {
+ e.preventDefault();
+ }}
+ autoCapitalize="none"
+ autoCorrect="off"
+ >
+ <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3">
+ <RenderAllFieldsByUiConfig
+ fields={convertUiField(i18n, fields, form.handler, getConverterById)}
+ />
+ </div>
+ <button
+ disabled={form.status.status === "fail"}
+ class="disabled:bg-gray-100 disabled:text-gray-500 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"
+ onClick={() => onSearch(paytoUri)}
+ >
+ Search
+ </button>
+ </form>
+ );
+}
+function GenericForm({
+ onSearch,
+}: {
+ onSearch: (p: PaytoUri | undefined) => void;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ const fields = genericFields(i18n);
+ const form = useFormState(
+ getShapeFromFields(fields),
+ {},
+ createGenericPaytoValidator(i18n),
+ );
+ const paytoUri =
+ form.status.status === "fail"
+ ? undefined
+ : parsePaytoUri(form.status.result.payto);
+ return (
+ <form
+ class="space-y-6"
+ noValidate
+ onSubmit={(e) => {
+ e.preventDefault();
+ }}
+ autoCapitalize="none"
+ autoCorrect="off"
+ >
+ <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3">
+ <RenderAllFieldsByUiConfig
+ fields={convertUiField(i18n, fields, form.handler, getConverterById)}
+ />
+ </div>
+ <button
+ disabled={form.status.status === "fail"}
+ class="disabled:bg-gray-100 disabled:text-gray-500 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"
+ onClick={() => onSearch(paytoUri)}
+ >
+ Search
+ </button>
+ </form>
+ );
+}
+
+interface FormPayto {
+ paytoType: "generic" | "iban" | "x-taler-bank";
+}
+
function createFormValidator(i18n: InternationalizationAPI) {
return function check(
- state: RecursivePartial<FormValues<FormType>>,
- ): FormStatus<FormType> {
- const errors = undefinedIfEmpty<FormErrors<FormType>>({
+ state: RecursivePartial<FormValues<FormPayto>>,
+ ): FormStatus<FormPayto> {
+ const errors = undefinedIfEmpty<FormErrors<FormPayto>>({
paytoType: !state?.paytoType ? i18n.str`required` : undefined,
});
if (errors === undefined) {
- const result: FormType = {
+ const result: FormPayto = {
paytoType: state.paytoType! as any,
};
return {
@@ -137,7 +258,7 @@ function createFormValidator(i18n: InternationalizationAPI) {
errors,
};
}
- const result: RecursivePartial<FormType> = {
+ const result: RecursivePartial<FormPayto> = {
paytoType: state?.paytoType,
};
return {
@@ -148,6 +269,119 @@ function createFormValidator(i18n: InternationalizationAPI) {
};
}
+interface PaytoUriGenericForm {
+ payto: string;
+}
+
+function createGenericPaytoValidator(i18n: InternationalizationAPI) {
+ return function check(
+ state: RecursivePartial<FormValues<PaytoUriGenericForm>>,
+ ): FormStatus<PaytoUriGenericForm> {
+ const errors = undefinedIfEmpty<FormErrors<PaytoUriGenericForm>>({
+ payto: !state.payto
+ ? i18n.str`required`
+ : parsePaytoUri(state.payto) === undefined
+ ? i18n.str`invalid`
+ : undefined,
+ });
+
+ if (errors === undefined) {
+ const result: PaytoUriGenericForm = {
+ payto: state.payto! as any,
+ };
+ return {
+ status: "ok",
+ result,
+ errors,
+ };
+ }
+ const result: RecursivePartial<PaytoUriGenericForm> = {
+ // targetType: state.iban
+ };
+ return {
+ status: "fail",
+ result,
+ errors,
+ };
+ };
+}
+
+interface PaytoUriIBANForm {
+ account: string;
+ name: string;
+}
+
+function createIbanPaytoValidator(i18n: InternationalizationAPI) {
+ return function check(
+ state: RecursivePartial<FormValues<PaytoUriIBANForm>>,
+ ): FormStatus<PaytoUriIBANForm> {
+ const errors = undefinedIfEmpty<FormErrors<PaytoUriIBANForm>>({
+ account: !state.account ? i18n.str`required` : undefined,
+ name: !state.name ? i18n.str`required` : undefined,
+ });
+
+ if (errors === undefined) {
+ const result: PaytoUriIBANForm = {
+ account: state.account!,
+ name: state.name!,
+ };
+ return {
+ status: "ok",
+ result,
+ errors,
+ };
+ }
+ const result: RecursivePartial<PaytoUriIBANForm> = {
+ account: state.account,
+ name: state.name,
+ };
+ return {
+ status: "fail",
+ result,
+ errors,
+ };
+ };
+}
+interface PaytoUriTalerBankForm {
+ hostname: string;
+ account: string;
+ name: string;
+}
+function createTalerBankPaytoValidator(i18n: InternationalizationAPI) {
+ return function check(
+ state: RecursivePartial<FormValues<PaytoUriTalerBankForm>>,
+ ): FormStatus<PaytoUriTalerBankForm> {
+ const errors = undefinedIfEmpty<FormErrors<PaytoUriTalerBankForm>>({
+ account: !state.account ? i18n.str`required` : undefined,
+ hostname: !state.hostname ? i18n.str`required` : undefined,
+ name: !state.name ? i18n.str`required` : undefined,
+ });
+
+ if (errors === undefined) {
+ const result: PaytoUriTalerBankForm = {
+ account: state.account!,
+ hostname: state.hostname!,
+ name: state.name!,
+ };
+ return {
+ status: "ok",
+ result,
+ errors,
+ };
+ }
+ const result: RecursivePartial<PaytoUriTalerBankForm> = {
+ account: state.account,
+ hostname: state.hostname,
+ name: state.name,
+ };
+ return {
+ status: "fail",
+ result,
+ errors,
+ };
+ };
+}
+
const paytoTypeField: (
i18n: InternationalizationAPI,
) => UIFormElementConfig[] = (i18n) => [
@@ -157,10 +391,6 @@ const paytoTypeField: (
required: true,
choices: [
{
- value: "generic",
- label: i18n.str`Generic Payto:// URI`,
- },
- {
value: "iban",
label: i18n.str`IBAN`,
},
@@ -168,18 +398,22 @@ const paytoTypeField: (
value: "x-taler-bank",
label: i18n.str`Taler Bank`,
},
+ {
+ value: "generic",
+ label: i18n.str`Generic Payto:// URI`,
+ },
],
- label: `Account type`,
+ label: i18n.str`Account type`,
},
];
const receiverName: (i18n: InternationalizationAPI) => UIFormElementConfig = (
i18n,
) => ({
- id: "receiverName" as UIHandlerId,
+ id: "name" as UIHandlerId,
type: "text",
required: true,
- label: `Owner's name`,
+ label: i18n.str`Owner's name`,
help: i18n.str`It should match the bank account name.`,
placeholder: i18n.str`John Doe`,
});
@@ -188,14 +422,13 @@ const genericFields: (
i18n: InternationalizationAPI,
) => UIFormElementConfig[] = (i18n) => [
{
- id: "paytoText" as UIHandlerId,
+ id: "payto" as UIHandlerId,
type: "textArea",
required: true,
- label: `Payto URI`,
+ label: i18n.str`Payto URI`,
help: i18n.str`As defined by RFC 8905`,
placeholder: i18n.str`payto://`,
},
- receiverName(i18n),
];
const ibanFields: (i18n: InternationalizationAPI) => UIFormElementConfig[] = (
i18n,
@@ -204,10 +437,10 @@ const ibanFields: (i18n: InternationalizationAPI) => UIFormElementConfig[] = (
id: "account" as UIHandlerId,
type: "text",
required: true,
- label: `Account`,
+ label: i18n.str`Account`,
help: i18n.str`International Bank Account Number`,
placeholder: i18n.str`DE1231231231`,
- validator: (value) => validateIBAN(value, i18n),
+ // validator: (value) => validateIBAN(value, i18n),
},
receiverName(i18n),
];
@@ -219,7 +452,7 @@ const talerBankFields: (
id: "account" as UIHandlerId,
type: "text",
required: true,
- label: `Bank account`,
+ label: i18n.str`Bank account`,
help: i18n.str`Bank account id`,
placeholder: i18n.str`DE123123123`,
},
@@ -227,10 +460,10 @@ const talerBankFields: (
id: "hostname" as UIHandlerId,
type: "text",
required: true,
- label: `Hostname`,
- validator: (value) => validateTalerBank(value, i18n),
+ label: i18n.str`Hostname`,
help: i18n.str`Without the scheme, may include subpath: bank.com, bank.com/path/`,
placeholder: i18n.str`bank.demo.taler.net`,
+ // validator: (value) => validateTalerBank(value, i18n),
},
receiverName(i18n),
];
diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts
index 76b5ff0cf..3c28a9ad0 100644
--- a/packages/taler-util/src/payto.ts
+++ b/packages/taler-util/src/payto.ts
@@ -99,21 +99,25 @@ export function buildPayto(
type: "iban",
iban: string,
bic: string | undefined,
+ params?: Record<string, string>,
): PaytoUriIBAN;
export function buildPayto(
type: "bitcoin",
address: string,
reserve: string | undefined,
+ params?: Record<string, string>,
): PaytoUriBitcoin;
export function buildPayto(
type: "x-taler-bank",
host: string,
account: string,
+ params?: Record<string, string>,
): PaytoUriTalerBank;
export function buildPayto(
type: PaytoType,
first: string,
second?: string,
+ params: Record<string, string> = {},
): PaytoUriGeneric {
switch (type) {
case "bitcoin": {
@@ -123,7 +127,7 @@ export function buildPayto(
targetType: "bitcoin",
targetPath: first,
address: uppercased,
- params: {},
+ params,
segwitAddrs: !second ? [] : generateFakeSegwitAddress(second, first),
};
return result;
@@ -134,7 +138,7 @@ export function buildPayto(
isKnown: true,
targetType: "iban",
iban: uppercased,
- params: {},
+ params,
targetPath: !second ? uppercased : `${second}/${uppercased}`,
};
return result;
@@ -146,7 +150,7 @@ export function buildPayto(
targetType: "x-taler-bank",
host: first,
account: second,
- params: {},
+ params,
targetPath: `${first}/${second}`,
};
return result;
diff --git a/packages/web-util/src/forms/InputLine.tsx b/packages/web-util/src/forms/InputLine.tsx
index a89d2e24e..4c0176195 100644
--- a/packages/web-util/src/forms/InputLine.tsx
+++ b/packages/web-util/src/forms/InputLine.tsx
@@ -59,9 +59,9 @@ export function LabelWithTooltipMaybeRequired({
);
if (required) {
return (
- <div class="flex justify-between">
+ <div class="flex justify-between w-fit">
{WithTooltip}
- <span class="text-sm leading-6 text-red-600">*</span>
+ <span class="text-sm leading-6 text-red-600 pl-2">*</span>
</div>
);
}
@@ -121,7 +121,7 @@ function InputWrapper<T extends object, K extends keyof T>({
children: ComponentChildren;
} & UIFormProps<T, K>): VNode {
return (
- <div class="sm:col-span-6">
+ <div class="sm:col-span-6 ">
<LabelWithTooltipMaybeRequired
label={label}
required={required}
diff --git a/packages/web-util/src/forms/forms.ts b/packages/web-util/src/forms/forms.ts
index 3b56081ef..2c789b9a3 100644
--- a/packages/web-util/src/forms/forms.ts
+++ b/packages/web-util/src/forms/forms.ts
@@ -297,7 +297,7 @@ export function convertUiField(
}
case "textArea": {
return {
- type: "text",
+ type: "textArea",
properties: {
...converBaseFieldsProps(i18n_, config),
...converInputFieldsProps(form, config, getConverterById),