[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-wallet-core] 03/04: several changes related UI
From: |
gnunet |
Subject: |
[taler-wallet-core] 03/04: several changes related UI |
Date: |
Mon, 06 Jan 2025 23:57:00 +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 318d7ba24624b5840bb9a838ede520cca0e0aa27
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Mon Jan 6 19:45:28 2025 -0300
several changes related UI
---
packages/aml-backoffice-ui/src/Routing.tsx | 6 +-
packages/aml-backoffice-ui/src/forms/ganaForms.ts | 5 +-
.../aml-backoffice-ui/src/pages/CaseDetails.tsx | 148 ++++++++++-----
.../aml-backoffice-ui/src/pages/CaseUpdate.tsx | 2 +-
packages/aml-backoffice-ui/src/pages/Cases.tsx | 89 +++++++--
packages/aml-backoffice-ui/src/pages/Search.tsx | 198 +++++++++++++++++++--
.../src/pages/ShowConsolidated.tsx | 47 +++--
packages/aml-backoffice-ui/src/stories.test.ts | 4 +-
packages/aml-backoffice-ui/src/stories.tsx | 3 +-
9 files changed, 399 insertions(+), 103 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/Routing.tsx
b/packages/aml-backoffice-ui/src/Routing.tsx
index d5669fc2e..a48cc373a 100644
--- a/packages/aml-backoffice-ui/src/Routing.tsx
+++ b/packages/aml-backoffice-ui/src/Routing.tsx
@@ -163,10 +163,12 @@ function PrivateRouting(): VNode {
return <SelectForm account={location.values.cid} />;
}
case "investigation": {
- return <CasesUnderInvestigation />;
+ return (
+ <CasesUnderInvestigation caseByIdRoute={privatePages.caseDetails} />
+ );
}
case "active": {
- return <Cases />;
+ return <Cases caseByIdRoute={privatePages.caseDetails} />;
}
case "search": {
return <Search />;
diff --git a/packages/aml-backoffice-ui/src/forms/ganaForms.ts
b/packages/aml-backoffice-ui/src/forms/ganaForms.ts
index 8bce4d9fe..4d43f55e7 100644
--- a/packages/aml-backoffice-ui/src/forms/ganaForms.ts
+++ b/packages/aml-backoffice-ui/src/forms/ganaForms.ts
@@ -230,10 +230,7 @@ function convertGanaJsonToDoubleColumnFormSection(
}, {} as FieldsBySection);
Object.values(sections).forEach((sec) => {
- sec.sort((a, b) => {
- console.log("a", a.order, b.order);
- return (a.order ?? 0) - (b.order ?? 0);
- });
+ sec.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
});
return sections;
}
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
index d9b007d1e..5cf090816 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -39,6 +39,7 @@ import {
Attention,
Button,
convertUiField,
+ CopyButton,
DefaultForm,
FormConfiguration,
FormMetadata,
@@ -178,7 +179,7 @@ export function getEventsFromAmlHistory(
const ke = events.map((event) => {
return {
type: "kyc-collection",
- title: i18n.str`collection`,
+ title: i18n.str`User filled a form`,
when: AbsoluteTime.fromProtocolTimestamp(event.collection_time),
values: !event.attributes ? {} : event.attributes,
provider: event.provider_name,
@@ -274,7 +275,7 @@ export function CaseDetails({
expiration_time: AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.never(),
),
- rules: FREEZE_RULES(config.currency),
+ rules: FREEZE_RULES(config.config.currency),
},
},
askInformation: false,
@@ -298,11 +299,10 @@ export function CaseDetails({
<div class="min-w-60">
<header class="flex items-center justify-between border-b border-white/5
px-4 py-4 sm:px-6 sm:py-6 lg:px-8">
<h1 class="text-base font-semibold leading-7 text-black">
- <i18n.Translate>
- Case history for account{" "}
- <span title={account}>{account.substring(0, 16)}...</span>
- </i18n.Translate>
+ <i18n.Translate>Case history for account: </i18n.Translate>
</h1>
+ <div>{account}</div>
+ <CopyButton class="" getContent={() => account} />
</header>
{!activeDecision || !activeDecision.to_investigate ? undefined : (
@@ -331,7 +331,7 @@ export function CaseDetails({
expiration_time: AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.never(),
),
- rules: FREEZE_RULES(config.currency),
+ rules: FREEZE_RULES(config.config.currency),
successor_measure: "verboten",
},
},
@@ -358,7 +358,7 @@ export function CaseDetails({
expiration_time: AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.never(),
),
- rules: THRESHOLD_100_HOUR(config.currency),
+ rules: THRESHOLD_100_HOUR(config.config.currency),
successor_measure: "verboten",
},
},
@@ -385,7 +385,7 @@ export function CaseDetails({
expiration_time: AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.never(),
),
- rules: THRESHOLD_2000_WEEK(config.currency),
+ rules: THRESHOLD_2000_WEEK(config.config.currency),
successor_measure: "verboten",
},
},
@@ -415,7 +415,7 @@ export function CaseDetails({
expiration_time: AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.never(),
),
- rules: FREEZE_RULES(config.currency),
+ rules: FREEZE_RULES(config.config.currency),
},
},
askInformation: true,
@@ -471,20 +471,8 @@ export function CaseDetails({
) : (
<ShowTimeline
history={events}
- onSelect={(e) => {
- switch (e.type) {
- case "aml-form": {
- // const { justification, metadata } = e;
- // setShowForm({ justification, metadata });
- break;
- }
- case "kyc-collection":
- case "kyc-expiration": {
- setSelected(e.when);
- break;
- }
- case "aml-form-error":
- }
+ onSelect={(time) => {
+ setSelected(time);
}}
/>
)}
@@ -724,6 +712,7 @@ function SubmitNewDecision({
/>
<ShowDecisionLimitInfo
+ fixed
since={AbsoluteTime.fromProtocolTimestamp(
decision.request.decision_time,
)}
@@ -855,12 +844,14 @@ function ShowDecisionLimitInfo({
until,
startOpen,
justification,
+ fixed,
}: {
since: AbsoluteTime;
until: AbsoluteTime;
justification?: string;
ruleSet: LegitimizationRuleSet;
startOpen?: boolean;
+ fixed?: boolean;
}): VNode {
const { i18n } = useTranslationContext();
const { config } = useExchangeApiContext();
@@ -869,32 +860,74 @@ function ShowDecisionLimitInfo({
function Header() {
return (
<div
- class="p-4 relative bg-gray-50 flex justify-between cursor-pointer"
- onClick={() => setOpened((o) => !o)}
+ data-fixed={!!fixed}
+ class="p-4 relative bg-gray-200 flex justify-between
data-[fixed=false]:cursor-pointer"
+ onClick={() => {
+ if (!fixed) {
+ setOpened((o) => !o);
+ }
+ }}
>
<div class="flex min-w-0 gap-x-4">
<div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset
ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
- <div class="pointer-events-none bg-gray-200 inset-y-0 flex
items-center px-3">
+ <div class="pointer-events-none bg-gray-300 inset-y-0 flex
items-center px-3">
<i18n.Translate>Since</i18n.Translate>
</div>
- <div class="p-2 disabled:bg-gray-200 text-right rounded-md
rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900
placeholder:text-gray-400 sm:text-sm sm:leading-6">
+ <div class="p-2 bg-gray-50 text-right rounded-md rounded-l-none
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900
placeholder:text-gray-50 sm:text-sm sm:leading-6">
<Time format="dd/MM/yyyy HH:mm:ss" timestamp={since} />
</div>
</div>
</div>
<div class="flex shrink-0 items-center gap-x-4">
<div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset
ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
- <div class="pointer-events-none bg-gray-200 inset-y-0 flex
items-center px-3">
+ <div class="pointer-events-none bg-gray-300 inset-y-0 flex
items-center px-3">
{AbsoluteTime.isExpired(until) ? (
<i18n.Translate>Expired</i18n.Translate>
) : (
<i18n.Translate>Expires</i18n.Translate>
)}
</div>
- <div class="p-2 disabled:bg-gray-200 text-right rounded-md
rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900
placeholder:text-gray-400 sm:text-sm sm:leading-6">
+ <div class="p-2 bg-gray-50 text-right rounded-md rounded-l-none
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900
placeholder:text-gray-50 sm:text-sm sm:leading-6">
<Time format="dd/MM/yyyy HH:mm:ss" timestamp={until} />
</div>
</div>
+ {fixed ? (
+ <Fragment />
+ ) : (
+ <div class="rounded-full bg-gray-50 p-2">
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke-width="1.5"
+ stroke="currentColor"
+ class="size-6 w-6 h-6"
+ >
+ {opened ? (
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="m19.5 8.25-7.5 7.5-7.5-7.5"
+ />
+ ) : (
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="m4.5 15.75 7.5-7.5 7.5 7.5"
+ />
+ )}
+ </svg>
+ {/* <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke-width="1.5"
+ stroke="currentColor"
+ class="size-6 w-6 h-6"
+ >
+ </svg> */}
+ </div>
+ )}
</div>
</div>
);
@@ -902,7 +935,7 @@ function ShowDecisionLimitInfo({
if (!opened) {
return (
- <div class="overflow-hidden ring-1 ring-gray-900/5 rounded-xl">
+ <div class="overflow-hidden border border-gray-800 rounded-xl">
<Header />
</div>
);
@@ -912,7 +945,7 @@ function ShowDecisionLimitInfo({
);
return (
- <div class="overflow-hidden ring-1 ring-gray-900/5 rounded-xl">
+ <div class="overflow-hidden border border-gray-800 rounded-xl">
<Header />
<div class="p-4 grid gap-y-4">
{!justification ? undefined : (
@@ -948,7 +981,7 @@ function ShowDecisionLimitInfo({
) : (
<RenderAmount
value={Amounts.parseOrThrow(balanceLimit.threshold)}
- spec={config.currency_specification}
+ spec={config.config.currency_specification}
/>
)}
</div>
@@ -1008,7 +1041,7 @@ function ShowDecisionLimitInfo({
<td class=" relative whitespace-nowrap py-4 pl-3 pr-4
text-right text-sm font-medium sm:pr-6 text-right">
<RenderAmount
value={Amounts.parseOrThrow(r.threshold)}
- spec={config.currency_specification}
+ spec={config.config.currency_specification}
/>
</td>
</tr>
@@ -1086,30 +1119,26 @@ function ShowTimeline({
history,
onSelect,
}: {
- onSelect: (e: AmlEvent) => void;
+ onSelect: (e: AbsoluteTime) => void;
history: AmlEvent[];
}): VNode {
+ const { i18n } = useTranslationContext();
return (
<div class="flow-root">
<ul role="list">
{history.map((e, idx) => {
- const isLast = history.length - 1 === idx;
+ // const isLast = history.length - 1 === idx;
return (
<li
key={idx}
data-ok={e.type !== "aml-form-error"}
class="hover:bg-gray-200 p-2 rounded
data-[ok=true]:cursor-pointer"
onClick={() => {
- onSelect(e);
+ onSelect(e.when);
}}
>
- <div class="relative pb-6">
- {!isLast ? (
- <span
- class="absolute left-4 top-4 -ml-px h-full w-1 bg-gray-200"
- aria-hidden="true"
- ></span>
- ) : undefined}
+ <div class="relative pb-3">
+ <span class="absolute left-3 top-5 -ml-px h-full w-1
bg-gray-200"></span>
<div class="relative flex space-x-3">
{(() => {
switch (e.type) {
@@ -1192,6 +1221,32 @@ function ShowTimeline({
</li>
);
})}
+ <li
+ class="hover:bg-gray-200 p-2 rounded data-[ok=true]:cursor-pointer"
+ onClick={() => {
+ onSelect(AbsoluteTime.now());
+ }}
+ >
+ <div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke-width="1.5"
+ stroke="currentColor"
+ class="size-6 w-6 h-6"
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
+ />
+ </svg>
+ <p class="text-sm text-gray-900">
+ <i18n.Translate>Now </i18n.Translate>
+ </p>
+ </div>
+ </li>
</ul>
</div>
);
@@ -1240,12 +1295,13 @@ function InputAmount(
if (
sep_pos !== -1 &&
l - sep_pos - 1 >
- config.currency_specification.num_fractional_input_digits
+
config.config.currency_specification.num_fractional_input_digits
) {
e.currentTarget.value = e.currentTarget.value.substring(
0,
sep_pos +
- config.currency_specification.num_fractional_input_digits +
+ config.config.currency_specification
+ .num_fractional_input_digits +
1,
);
}
diff --git a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
index a268aaa89..1bd50141f 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
@@ -94,7 +94,7 @@ export function CaseUpdate({
const initial: FormType = {
when: AbsoluteTime.now(),
state: TalerExchangeApi.AmlState.pending,
- threshold: Amounts.zeroOfCurrency(config.currency),
+ threshold: Amounts.zeroOfCurrency(config.config.currency),
comment: "",
};
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx
b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index 0429d397d..07185e476 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -22,6 +22,7 @@ import {
import {
Attention,
Loading,
+ RouteDefinition,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
@@ -33,18 +34,66 @@ import {
import { privatePages } from "../Routing.js";
import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
import { Officer } from "./Officer.js";
+import { useState } from "preact/hooks";
type FormType = {
// state: TalerExchangeApi.AmlState;
};
+function JumpByIdForm({
+ caseByIdRoute,
+}: {
+ caseByIdRoute: RouteDefinition<{ cid: string }>;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ const [account, setAccount] = useState<string>("");
+ return (
+ <form class="mt-5 sm:flex sm:items-center">
+ <div class="w-full sm:max-w-xs">
+ <input
+ type="email"
+ name="email"
+ id="email"
+ onChange={(e) => {
+ setAccount(e.currentTarget.value);
+ }}
+ aria-label="Email"
+ class="block w-full rounded-md bg-white px-3 py-1.5 text-base
text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300
placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2
focus:outline-indigo-600 sm:text-sm/6"
+ placeholder={i18n.str`Search by ID`}
+ />
+ </div>
+ <a
+ href={caseByIdRoute.url({ cid: account })}
+ class="mt-3 inline-flex w-full items-center justify-center rounded-md
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2
focus-visible:outline-offset-2 focus-visible:outline-indigo-600 sm:ml-3 sm:mt-0
sm:w-auto"
+ >
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke-width="1.5"
+ stroke="currentColor"
+ class="size-6 w-6 h-6"
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
+ />
+ </svg>
+ </a>
+ </form>
+ );
+}
+
export function CasesUI({
records,
onFirstPage,
onNext,
filtered,
+ caseByIdRoute,
}: {
filtered: boolean;
+ caseByIdRoute: RouteDefinition<{ cid: string }>;
onFirstPage?: () => void;
onNext?: () => void;
records: TalerExchangeApi.AmlDecision[];
@@ -113,6 +162,8 @@ export function CasesUI({
</p>
</div>
)}
+
+ <JumpByIdForm caseByIdRoute={caseByIdRoute} />
</div>
<div class="mt-8 flow-root">
<div class="overflow-x-auto">
@@ -147,9 +198,9 @@ export function CasesUI({
href={privatePages.caseDetails.url({
cid: r.h_payto,
})}
- class="text-indigo-600 hover:text-indigo-900"
+ class="text-indigo-600 hover:text-indigo-900
font-mono"
>
- {r.h_payto.substring(0, 16)}...
+ {r.h_payto}
</a>
</div>
</td>
@@ -174,7 +225,11 @@ export function CasesUI({
);
}
-export function Cases() {
+export function Cases({
+ caseByIdRoute,
+}: {
+ caseByIdRoute: RouteDefinition<{ cid: string }>;
+}) {
const list = useCurrentDecisions();
const { i18n } = useTranslationContext();
@@ -234,6 +289,7 @@ export function Cases() {
<CasesUI
filtered={false}
records={list.body}
+ caseByIdRoute={caseByIdRoute}
onFirstPage={list.isFirstPage ? undefined : list.loadFirst}
onNext={list.isLastPage ? undefined : list.loadNext}
// filter={stateFilter}
@@ -243,7 +299,11 @@ export function Cases() {
/>
);
}
-export function CasesUnderInvestigation() {
+export function CasesUnderInvestigation({
+ caseByIdRoute,
+}: {
+ caseByIdRoute: RouteDefinition<{ cid: string }>;
+}) {
const list = useCurrentDecisionsUnderInvestigation();
const { i18n } = useTranslationContext();
@@ -303,6 +363,7 @@ export function CasesUnderInvestigation() {
<CasesUI
filtered={true}
records={list.body}
+ caseByIdRoute={caseByIdRoute}
onFirstPage={list.isFirstPage ? undefined : list.loadFirst}
onNext={list.isLastPage ? undefined : list.loadNext}
// filter={stateFilter}
@@ -370,14 +431,18 @@ export const HomeIcon = () => (
</svg>
);
export const FormIcon = () => (
-<svg
-xmlns="http://www.w3.org/2000/svg"
-viewBox="0 0 24 24"
-fill="currentColor"
-class="w-6 h-6"
->
- <path fillRule="evenodd" d="M1.5 5.625c0-1.036.84-1.875
1.875-1.875h17.25c1.035 0 1.875.84 1.875 1.875v12.75c0 1.035-.84 1.875-1.875
1.875H3.375A1.875 1.875 0 0 1 1.5 18.375V5.625ZM21 9.375A.375.375 0 0 0 20.625
9h-7.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h7.5a.375.375 0 0 0
.375-.375v-1.5Zm0 3.75a.375.375 0 0 0-.375-.375h-7.5a.375.375 0 0
0-.375.375v1.5c0 .207.168.375.375.375h7.5a.375.375 0 0 0 .375-.375v-1.5Zm0
3.75a.375.375 0 0 0-.375-.375h-7.5a.375.375 0 0 0-.375.375v1 [...]
-</svg>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ fill="currentColor"
+ class="w-6 h-6"
+ >
+ <path
+ fillRule="evenodd"
+ d="M1.5 5.625c0-1.036.84-1.875 1.875-1.875h17.25c1.035 0 1.875.84 1.875
1.875v12.75c0 1.035-.84 1.875-1.875 1.875H3.375A1.875 1.875 0 0 1 1.5
18.375V5.625ZM21 9.375A.375.375 0 0 0 20.625 9h-7.5a.375.375 0 0
0-.375.375v1.5c0 .207.168.375.375.375h7.5a.375.375 0 0 0 .375-.375v-1.5Zm0
3.75a.375.375 0 0 0-.375-.375h-7.5a.375.375 0 0 0-.375.375v1.5c0
.207.168.375.375.375h7.5a.375.375 0 0 0 .375-.375v-1.5Zm0 3.75a.375.375 0 0
0-.375-.375h-7.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375 [...]
+ clipRule="evenodd"
+ />
+ </svg>
);
export const SearchIcon = () => (
diff --git a/packages/aml-backoffice-ui/src/pages/Search.tsx
b/packages/aml-backoffice-ui/src/pages/Search.tsx
index 635eb6582..9cb17255c 100644
--- a/packages/aml-backoffice-ui/src/pages/Search.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Search.tsx
@@ -18,6 +18,7 @@ import {
assertUnreachable,
buildPayto,
encodeCrock,
+ getURLHostnamePortPath,
hashNormalizedPaytoUri,
HttpStatusCode,
parsePaytoUri,
@@ -37,6 +38,7 @@ import {
Time,
UIFormElementConfig,
UIHandlerId,
+ useExchangeApiContext,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
@@ -99,14 +101,26 @@ export function Search() {
</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} />
- )}
+ {(function () {
+ if (paytoForm.status.status !== "ok") return undefined;
+ switch (paytoForm.status.result.paytoType) {
+ case "iban": {
+ return <IbanForm onSearch={setPayto} />;
+ }
+ case "generic": {
+ return <GenericForm onSearch={setPayto} />;
+ }
+ case "x-taler-bank": {
+ return <XTalerBankForm onSearch={setPayto} />;
+ }
+ case "wallet": {
+ return <WalletForm onSearch={setPayto} />;
+ }
+ default: {
+ assertUnreachable(paytoForm.status.result.paytoType);
+ }
+ }
+ })()}
{!paytoUri ? undefined : <ShowResult payto={paytoUri} />}
</div>
);
@@ -378,6 +392,56 @@ function IbanForm({
</form>
);
}
+function WalletForm({
+ onSearch,
+}: {
+ onSearch: (p: PaytoUri | undefined) => void;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ const { config } = useExchangeApiContext();
+ const fields = walletFields(i18n);
+ const form = useFormState(
+ getShapeFromFields(fields),
+ {
+ exchange: getURLHostnamePortPath(config.keys.base_url),
+ },
+ createTalerPaytoValidator(i18n),
+ );
+ const paytoUri =
+ form.status.status === "fail"
+ ? undefined
+ : buildPayto(
+ "taler",
+ form.status.result.exchange,
+ form.status.result.reservePub,
+ );
+
+ 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)}
+ >
+ <i18n.Translate>Search</i18n.Translate>
+ </button>
+ </form>
+ );
+}
+
function GenericForm({
onSearch,
}: {
@@ -421,7 +485,7 @@ function GenericForm({
}
interface FormPayto {
- paytoType: "generic" | "iban" | "x-taler-bank";
+ paytoType: "generic" | "iban" | "x-taler-bank" | "wallet";
}
function createFormValidator(i18n: InternationalizationAPI) {
@@ -558,6 +622,46 @@ function createTalerBankPaytoValidator(i18n:
InternationalizationAPI) {
};
}
+interface PaytoUriTalerForm {
+ exchange: string;
+ reservePub: string;
+}
+function createTalerPaytoValidator(i18n: InternationalizationAPI) {
+ return function check(
+ state: RecursivePartial<FormValues<PaytoUriTalerForm>>,
+ ): FormStatus<PaytoUriTalerForm> {
+ const errors = undefinedIfEmpty<FormErrors<PaytoUriTalerForm>>({
+ exchange: !state.exchange ? i18n.str`required` : undefined,
+ reservePub: !state.reservePub
+ ? i18n.str`required`
+ : state.reservePub.length !== 16
+ ? i18n.str`Should be 16 charaters`
+ : undefined,
+ });
+
+ if (errors === undefined) {
+ const result: PaytoUriTalerForm = {
+ exchange: state.exchange!,
+ reservePub: state.reservePub!,
+ };
+ return {
+ status: "ok",
+ result,
+ errors,
+ };
+ }
+ const result: RecursivePartial<PaytoUriTalerForm> = {
+ exchange: state.exchange,
+ reservePub: state.reservePub,
+ };
+ return {
+ status: "fail",
+ result,
+ errors,
+ };
+ };
+}
+
const paytoTypeField: (
i18n: InternationalizationAPI,
) => UIFormElementConfig[] = (i18n) => [
@@ -574,6 +678,10 @@ const paytoTypeField: (
value: "x-taler-bank",
label: i18n.str`Taler Bank`,
},
+ {
+ value: "wallet",
+ label: i18n.str`Wallet`,
+ },
{
value: "generic",
label: i18n.str`Generic Payto:// URI`,
@@ -613,20 +721,74 @@ const ibanFields: (i18n: InternationalizationAPI) =>
UIFormElementConfig[] = (
id: "account" as UIHandlerId,
type: "text",
required: true,
- label: i18n.str`Account`,
+ label: i18n.str`IBAN`,
help: i18n.str`International Bank Account Number`,
placeholder: i18n.str`DE1231231231`,
// validator: (value) => validateIBAN(value, i18n),
},
- receiverName(i18n),
+ // receiverName(i18n),
+ // {
+ // id: "bic" as UIHandlerId,
+ // type: "text",
+ // label: i18n.str`Bank`,
+ // help: i18n.str`Business Identifier Code`,
+ // placeholder: i18n.str`GENODEM1GLS`,
+ // // validator: (value) => validateIBAN(value, i18n),
+ // },
+];
+const paytoHashFields: (
+ i18n: InternationalizationAPI,
+) => UIFormElementConfig[] = (i18n) => [
{
- id: "bic" as UIHandlerId,
+ id: "account" as UIHandlerId,
type: "text",
- label: i18n.str`Bank`,
- help: i18n.str`Business Identifier Code`,
- placeholder: i18n.str`GENODEM1GLS`,
+ required: true,
+ label: i18n.str`ID`,
+ help: i18n.str`Normalized payto:// hash`,
+ placeholder: i18n.str`ABC123`,
// validator: (value) => validateIBAN(value, i18n),
},
+ // receiverName(i18n),
+ // {
+ // id: "bic" as UIHandlerId,
+ // type: "text",
+ // label: i18n.str`Bank`,
+ // help: i18n.str`Business Identifier Code`,
+ // placeholder: i18n.str`GENODEM1GLS`,
+ // // validator: (value) => validateIBAN(value, i18n),
+ // },
+];
+
+const walletFields: (i18n: InternationalizationAPI) => UIFormElementConfig[] =
(
+ i18n,
+) => [
+ {
+ id: "exchange" as UIHandlerId,
+ type: "text",
+ required: true,
+ label: i18n.str`Host`,
+ help: i18n.str`Exchange hostname`,
+ placeholder: i18n.str`exchange.taler.net`,
+ // validator: (value) => validateIBAN(value, i18n),
+ },
+ {
+ id: "reservePub" as UIHandlerId,
+ type: "text",
+ required: true,
+ label: i18n.str`ID`,
+ help: i18n.str`Wallet reserve public key`,
+ placeholder: i18n.str`abcdef1235`,
+ // validator: (value) => validateIBAN(value, i18n),
+ },
+ // receiverName(i18n),
+ // {
+ // id: "bic" as UIHandlerId,
+ // type: "text",
+ // label: i18n.str`Bank`,
+ // help: i18n.str`Business Identifier Code`,
+ // placeholder: i18n.str`GENODEM1GLS`,
+ // // validator: (value) => validateIBAN(value, i18n),
+ // },
];
const talerBankFields: (
@@ -636,8 +798,8 @@ const talerBankFields: (
id: "account" as UIHandlerId,
type: "text",
required: true,
- label: i18n.str`Bank account`,
- help: i18n.str`Bank account id`,
+ label: i18n.str`Account`,
+ help: i18n.str`Bank account identification`,
placeholder: i18n.str`DE123123123`,
},
{
@@ -649,7 +811,7 @@ const talerBankFields: (
placeholder: i18n.str`bank.demo.taler.net`,
// validator: (value) => validateTalerBank(value, i18n),
},
- receiverName(i18n),
+ // receiverName(i18n),
];
function validateIBAN(
diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
index 6ec6b787a..4088ed823 100644
--- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
+++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
@@ -68,24 +68,35 @@ export function ShowConsolidated({
const formConfig: FormConfiguration = {
type: "double-column",
- design: Object.entries(fixed).length > 0 ? [
- {
- title: i18n.str`KYC collected info`,
- fields: Object.entries(fixed).map(([key, field]) => {
- const result: UIFormElementConfig = {
- type: "text",
- label: key as TranslatedString,
- id: `${key}.value` as UIHandlerId,
- disabled: true,
- help: `At ${field.since.t_ms === "never"
- ? "never"
- : format(field.since.t_ms, "dd/MM/yyyy HH:mm:ss")
- }` as TranslatedString,
- };
- return result;
- }),
- }
- ] : [],
+ design:
+ Object.entries(fixed).length > 0
+ ? [
+ {
+ title: i18n.str`KYC collected info`,
+ description:
+ until.t_ms === "never"
+ ? undefined
+ : i18n.str`All information known until ${format(
+ until.t_ms,
+ "dd/MM/yyyy HH:mm:ss",
+ )}`,
+ fields: Object.entries(fixed).map(([key, field]) => {
+ const result: UIFormElementConfig = {
+ type: "text",
+ label: key as TranslatedString,
+ id: `${key}.value` as UIHandlerId,
+ disabled: true,
+ help: `At ${
+ field.since.t_ms === "never"
+ ? "never"
+ : format(field.since.t_ms, "dd/MM/yyyy HH:mm:ss")
+ }` as TranslatedString,
+ };
+ return result;
+ }),
+ },
+ ]
+ : [],
};
const shape: Array<UIHandlerId> = formConfig.design.flatMap((field) =>
getShapeFromFields(field.fields),
diff --git a/packages/aml-backoffice-ui/src/stories.test.ts
b/packages/aml-backoffice-ui/src/stories.test.ts
index 265a2165b..7af904eda 100644
--- a/packages/aml-backoffice-ui/src/stories.test.ts
+++ b/packages/aml-backoffice-ui/src/stories.test.ts
@@ -68,9 +68,11 @@ function DefaultTestingContext({
supported_kyc_requirements: [],
version: "asd",
};
+ const keys: TalerExchangeApi.ExchangeKeysResponse = {} as any;
+
const value: ExchangeContextType = {
cancelRequest: () => null,
- config,
+ config: { config, keys },
url: new URL("/", "http://localhost"),
hints: [],
lib: {
diff --git a/packages/aml-backoffice-ui/src/stories.tsx
b/packages/aml-backoffice-ui/src/stories.tsx
index 9a23d82fa..ed1fecc8b 100644
--- a/packages/aml-backoffice-ui/src/stories.tsx
+++ b/packages/aml-backoffice-ui/src/stories.tsx
@@ -57,9 +57,10 @@ function getWrapperForGroup(): FunctionComponent {
supported_kyc_requirements: [],
version: "asd",
};
+ const keys: TalerExchangeApi.ExchangeKeysResponse = {} as any;
const value: ExchangeContextType = {
cancelRequest: () => null,
- config,
+ config: { config, keys },
url: new URL("/", "http://localhost"),
hints: [],
lib: {
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.