gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

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