gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: show server measures


From: gnunet
Subject: [taler-wallet-core] branch master updated: show server measures
Date: Fri, 20 Dec 2024 19:03:33 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 6b291c6dc show server measures
6b291c6dc is described below

commit 6b291c6dcda98c9b7c3561b23d7af91c0376c84a
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Dec 20 15:03:26 2024 -0300

    show server measures
---
 .../aml-backoffice-ui/src/pages/CaseDetails.tsx    | 183 ++++++++++++++++++++-
 packages/aml-backoffice-ui/src/pages/Measures.tsx  |  57 ++++++-
 .../aml-backoffice-ui/src/pages/MeasuresTable.tsx  | 123 ++++++++++++++
 3 files changed, 353 insertions(+), 10 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx 
b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
index fd7e562c1..8a2dca1bf 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -61,12 +61,13 @@ import { useState } from "preact/hooks";
 import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
 import { useUiFormsContext } from "../context/ui-forms.js";
 import { preloadedForms } from "../forms/index.js";
-import { useAccountInformation } from "../hooks/account.js";
+import { useAccountInformation, useServerMeasures } from "../hooks/account.js";
 import { useAccountDecisions } from "../hooks/decisions.js";
 import { ShowConsolidated } from "./ShowConsolidated.js";
 import { useOfficer } from "../hooks/officer.js";
 import { getShapeFromFields, useFormState } from "../hooks/form.js";
 import { privatePages } from "../Routing.js";
+import { CurrentMeasureTable, MeasureInfo } from "./MeasuresTable.js";
 
 export type AmlEvent =
   | AmlFormEvent
@@ -201,6 +202,7 @@ export function CaseDetails({
   const [request, setDesicionRequest] = useState<NewDecision | undefined>(
     undefined,
   );
+  const [selectMeasure, setSelectMeasure] = useState<boolean>();
   const { config } = useExchangeApiContext();
 
   const { i18n } = useTranslationContext();
@@ -249,6 +251,37 @@ export function CaseDetails({
 
   const events = getEventsFromAmlHistory(accountDetails, i18n, allForms);
 
+  if (selectMeasure) {
+    return (
+      <ShowMeasuresToSelect
+        onSelect={(d) => {
+          setSelectMeasure(false);
+          setDesicionRequest({
+            request: {
+              payto_uri: paytoString,
+              decision_time: AbsoluteTime.toProtocolTimestamp(
+                AbsoluteTime.now(),
+              ),
+              h_payto: account,
+              keep_investigating: false,
+              properties: {},
+              // the custom measure with context
+              new_measures: d.name,
+              new_rules: {
+                // this value is going to be overridden
+                custom_measures: {},
+                expiration_time: AbsoluteTime.toProtocolTimestamp(
+                  AbsoluteTime.never(),
+                ),
+                rules: FREEZE_RULES(config.currency),
+              },
+            },
+            askInformation: false,
+          });
+        }}
+      />
+    );
+  }
   if (request) {
     return (
       <SubmitNewDecision
@@ -391,6 +424,15 @@ export function CaseDetails({
         >
           <i18n.Translate>Ask for more information</i18n.Translate>
         </button>
+
+        <button
+          onClick={async () => {
+            setSelectMeasure(true);
+          }}
+          class="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"
+        >
+          <i18n.Translate>Set predefined measure</i18n.Translate>
+        </button>
       </div>
 
       {!activeDecision ? (
@@ -575,7 +617,9 @@ function SubmitNewDecision({
       };
 
   const submitHandler =
-    decisionForm === undefined || !session || customForm === undefined
+    decisionForm === undefined ||
+    !session ||
+    (decision.askInformation && customForm === undefined)
       ? undefined
       : withErrorHandler(
           () => {
@@ -598,7 +642,7 @@ function SubmitNewDecision({
                     // check of type form, it will use the officer defined form
                     check_name: "askContext",
                     // after that, mark as investigate to read what the user 
sent
-                    prog_name: "markInvestigate",
+                    prog_name: "preserve-investigate",
                   },
                 },
               },
@@ -673,6 +717,11 @@ function SubmitNewDecision({
         <i18n.Translate>New rules to submit</i18n.Translate>
       </h1>
 
+      <ShowMesaureInfo
+        nextMeasures={separateMeasures(decision.request.new_measures)}
+        customMeasure={decision.request.new_rules.custom_measures}
+      />
+
       <ShowDecisionLimitInfo
         since={AbsoluteTime.fromProtocolTimestamp(
           decision.request.decision_time,
@@ -687,6 +736,99 @@ function SubmitNewDecision({
   );
 }
 
+function separateMeasures(measureList: string | undefined): string[][] {
+  if (!measureList) return new Array();
+  const orList = measureList.trim().split(" ");
+  const orAndList = orList.map((or) => or.split("+"));
+  return orAndList;
+}
+
+function ShowMesaureInfo({
+  nextMeasures,
+  customMeasure,
+}: {
+  nextMeasures: string[][];
+  customMeasure: { [d: string]: TalerExchangeApi.MeasureInformation };
+}): VNode {
+  const measures = useServerMeasures();
+  const { i18n } = useTranslationContext();
+  if (!measures) {
+    return <Loading />;
+  }
+  if (measures instanceof TalerError) {
+    return <ErrorLoadingWithDebug error={measures} />;
+  }
+  const summary: TalerExchangeApi.AvailableMeasureSummary = measures.body;
+
+  const map: { [d: string]: MeasureInfo } = {};
+
+  function addUpIntoMap([key, value]: [
+    string,
+    TalerExchangeApi.MeasureInformation,
+  ]): void {
+    if (value.check_name !== "SKIP") {
+      map[key] = {
+        name: key,
+        context: value.context,
+        program: summary.programs[value.prog_name],
+        check: summary.checks[value.check_name],
+      };
+    } else {
+      map[key] = {
+        name: key,
+        context: value.context,
+        program: summary.programs[value.prog_name],
+      };
+    }
+  }
+
+  Object.entries(measures.body.roots).forEach(addUpIntoMap);
+  Object.entries(customMeasure).forEach(addUpIntoMap);
+
+  const filteredMeasures = nextMeasures.filter((n) => !!n.length);
+
+  if (!filteredMeasures.length) {
+    return (
+      <div>
+        <i18n.Translate>no new measure</i18n.Translate>
+      </div>
+    );
+  }
+  if (filteredMeasures.length === 1) {
+    const measurePath = filteredMeasures[0];
+    if (measurePath.length === 1) {
+      const m = map[measurePath[0]];
+
+      return (
+        <div>
+          <i18n.Translate>
+            the user needs to complete this measure
+          </i18n.Translate>
+          <CurrentMeasureTable list={[m]} />
+        </div>
+      );
+    }
+    return (
+      <div>
+        <i18n.Translate>
+          the user needs to complete all of these measures
+        </i18n.Translate>
+        <CurrentMeasureTable list={measurePath.map((name) => map[name])} />
+      </div>
+    );
+  }
+
+  return (
+    <div>
+      <i18n.Translate>
+        the user has more than one option with different measures to complete.
+        For any options, if there is more than one measure then the user will
+        need to complete all the measures.
+      </i18n.Translate>
+    </div>
+  );
+}
+
 function ShowDecisionLimitInfo({
   ruleSet,
   since,
@@ -1381,3 +1523,38 @@ const FREEZE_RULES: (currency: string) => 
TalerExchangeApi.KycRule[] = (
     is_and_combinator: true,
   },
 ];
+
+function ShowMeasuresToSelect({
+  onSelect,
+}: {
+  onSelect?: (m: MeasureInfo) => void;
+}): VNode {
+  const measures = useServerMeasures();
+
+  if (!measures) {
+    return <Loading />;
+  }
+  if (measures instanceof TalerError) {
+    return <ErrorLoadingWithDebug error={measures} />;
+  }
+
+  const list = Object.entries(measures.body.roots).map(
+    ([key, value]): MeasureInfo => {
+      if (value.check_name !== "SKIP") {
+        return {
+          name: key,
+          context: value.context,
+          program: measures.body.programs[value.prog_name],
+          check: measures.body.checks[value.check_name],
+        };
+      }
+      return {
+        name: key,
+        context: value.context,
+        program: measures.body.programs[value.prog_name],
+      };
+    },
+  );
+
+  return <CurrentMeasureTable list={list} onSelect={onSelect} />;
+}
diff --git a/packages/aml-backoffice-ui/src/pages/Measures.tsx 
b/packages/aml-backoffice-ui/src/pages/Measures.tsx
index 1e6a633d5..911fae82e 100644
--- a/packages/aml-backoffice-ui/src/pages/Measures.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Measures.tsx
@@ -14,20 +14,21 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import {
-  TalerError
+  AmlProgramRequirement,
+  KycCheckInformation,
+  TalerError,
 } from "@gnu-taler/taler-util";
 import {
   Loading,
   useExchangeApiContext,
-  useTranslationContext
+  useTranslationContext,
 } from "@gnu-taler/web-util/browser";
-import { Fragment, h } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
 import { useServerMeasures } from "../hooks/account.js";
+import { CurrentMeasureTable, MeasureInfo } from "./MeasuresTable.js";
 
 export function Measures({}: {}) {
-  const { config } = useExchangeApiContext();
-
   const { i18n } = useTranslationContext();
 
   // const { forms } = useUiFormsContext();
@@ -35,16 +36,58 @@ export function Measures({}: {}) {
   // const allForms = [...forms, ...preloadedForms(i18n)];
   const measures = useServerMeasures();
 
-  if (!measures || !history) {
+  if (!measures) {
     return <Loading />;
   }
   if (measures instanceof TalerError) {
     return <ErrorLoadingWithDebug error={measures} />;
   }
 
+  const list = Object.entries(measures.body.roots).map(
+    ([key, value]): MeasureInfo => {
+      if (value.check_name !== "SKIP") {
+        return {
+          name: key,
+          context: value.context,
+          program: measures.body.programs[value.prog_name],
+          check: measures.body.checks[value.check_name],
+        };
+      }
+      return {
+        name: key,
+        context: value.context,
+        program: measures.body.programs[value.prog_name],
+      };
+    },
+  );
+
   return (
     <div>
-      <pre>{JSON.stringify(measures, undefined ,2)}</pre>
+      <div class="px-4 sm:px-6 lg:px-8">
+        <div class="sm:flex sm:items-center">
+          <div class="sm:flex-auto">
+            <h1 class="text-base font-semibold text-gray-900">
+              <i18n.Translate>Measures</i18n.Translate>
+            </h1>
+            <p class="mt-2 text-sm text-gray-700">
+              <i18n.Translate>
+                A list of all the pre-define measures in your that can used 
with
+                the user.
+              </i18n.Translate>
+            </p>
+          </div>
+          <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
+            {/* <button
+            type="button"
+            class="block rounded-md bg-indigo-600 px-3 py-2 text-center 
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"
+          >
+            Add user
+          </button> */}
+          </div>
+        </div>
+
+        <CurrentMeasureTable list={list} />
+      </div>
     </div>
   );
 }
diff --git a/packages/aml-backoffice-ui/src/pages/MeasuresTable.tsx 
b/packages/aml-backoffice-ui/src/pages/MeasuresTable.tsx
new file mode 100644
index 000000000..a9a02c2cb
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/pages/MeasuresTable.tsx
@@ -0,0 +1,123 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022-2024 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
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+import {
+  AmlProgramRequirement,
+  KycCheckInformation,
+} from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, h, VNode } from "preact";
+
+export type MeasureInfo = {
+  name: string;
+  program: AmlProgramRequirement;
+  check?: KycCheckInformation;
+  context?: object;
+};
+export function CurrentMeasureTable({
+  list,
+  onSelect,
+}: {
+  list: MeasureInfo[];
+  onSelect?: (m: MeasureInfo) => void;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  return (
+    <div class="mt-8 flow-root">
+      <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
+        <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
+          <div class="overflow-hidden shadow ring-1 ring-black/5 
sm:rounded-lg">
+            <table class="min-w-full divide-y divide-gray-300">
+              <thead class="bg-gray-50">
+                <tr>
+                  {onSelect ? (
+                    <th scope="col" class="relative p-2 ">
+                      <span class="sr-only">Select</span>
+                    </th>
+                  ) : (
+                    <Fragment />
+                  )}
+                  <th
+                    scope="col"
+                    class="p-2 text-left text-sm font-semibold text-gray-900 
sm:pl-6"
+                  >
+                    <i18n.Translate>Name</i18n.Translate>
+                  </th>
+                  <th
+                    scope="col"
+                    class="p-2 text-left text-sm font-semibold text-gray-900"
+                  >
+                    <i18n.Translate>Check</i18n.Translate>
+                  </th>
+                  <th
+                    scope="col"
+                    class="p-2 text-left text-sm font-semibold text-gray-900"
+                  >
+                    <i18n.Translate>Program</i18n.Translate>
+                  </th>
+                  <th
+                    scope="col"
+                    class="p-2 text-left text-sm font-semibold text-gray-900"
+                  >
+                    <i18n.Translate>Context</i18n.Translate>
+                  </th>
+                </tr>
+              </thead>
+              <tbody class="divide-y divide-gray-200 bg-white">
+                {list.map((m) => {
+                  if (
+                    m.context &&
+                    "internal" in m.context &&
+                    m.context.internal
+                  ) {
+                    return <Fragment />;
+                  }
+                  return (
+                    <tr>
+                      {onSelect ? (
+                        <td class="relative whitespace-nowrap p-2 text-right 
text-sm font-medium ">
+                          <button
+                            onClick={() => onSelect(m)}
+                            class="rounded-md w-fit border-0 p-2 text-center 
text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
+                          >
+                            <i18n.Translate>Select</i18n.Translate>
+                          </button>
+                        </td>
+                      ) : (
+                        <Fragment />
+                      )}
+                      <td class="whitespace-nowrap p-2 text-sm font-medium 
text-gray-900 sm:pl-6">
+                        {m.name}
+                      </td>
+                      <td class="whitespace-nowrap p-2 text-sm text-gray-500">
+                        {m.check?.description ?? ""}
+                      </td>
+                      <td class="whitespace-nowrap p-2 text-sm text-gray-500">
+                        {m.program.description}
+                      </td>
+                      <td class="whitespace-nowrap p-2 text-sm text-gray-500">
+                        {Object.keys(m.context ?? {}).join(", ")}
+                      </td>
+                    </tr>
+                  );
+                })}
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

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