gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 03/03: webhook api


From: gnunet
Subject: [taler-wallet-core] 03/03: webhook api
Date: Fri, 27 Jan 2023 19:09:27 +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 eebb85bef4bb6bba41533fa0ff343cf2f1995761
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Jan 27 15:08:03 2023 -0300

    webhook api
---
 .../merchant-backoffice-ui/src/InstanceRoutes.tsx  |  46 ++++++
 .../src/components/menu/SideBar.tsx                |  10 ++
 .../merchant-backoffice-ui/src/declaration.d.ts    |  76 ++++++++++
 .../merchant-backoffice-ui/src/hooks/backend.ts    |  23 +++
 .../merchant-backoffice-ui/src/hooks/webhooks.ts   | 165 +++++++++++++++++++++
 .../src/paths/instance/templates/list/Table.tsx    |   2 +-
 .../instance/webhooks/create/Create.stories.tsx    |  28 ++++
 .../paths/instance/webhooks/create/CreatePage.tsx  | 140 +++++++++++++++++
 .../src/paths/instance/webhooks/create/index.tsx   |  61 ++++++++
 .../paths/instance/webhooks/list/List.stories.tsx  |  28 ++++
 .../src/paths/instance/webhooks/list/ListPage.tsx  |  64 ++++++++
 .../{templates => webhooks}/list/Table.tsx         |  48 +++---
 .../src/paths/instance/webhooks/list/index.tsx     |  95 ++++++++++++
 .../instance/webhooks/update/Update.stories.tsx    |  32 ++++
 .../paths/instance/webhooks/update/UpdatePage.tsx  | 146 ++++++++++++++++++
 .../src/paths/instance/webhooks/update/index.tsx   |  85 +++++++++++
 16 files changed, 1024 insertions(+), 25 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx 
b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
index 3be793ada..56f223620 100644
--- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
@@ -51,6 +51,9 @@ import TemplateCreatePage from 
"./paths/instance/templates/create/index.js";
 import TemplateUsePage from "./paths/instance/templates/use/index.js";
 import TemplateListPage from "./paths/instance/templates/list/index.js";
 import TemplateUpdatePage from "./paths/instance/templates/update/index.js";
+import WebhookCreatePage from "./paths/instance/webhooks/create/index.js";
+import WebhookListPage from "./paths/instance/webhooks/list/index.js";
+import WebhookUpdatePage from "./paths/instance/webhooks/update/index.js";
 import TransferCreatePage from "./paths/instance/transfers/create/index.js";
 import TransferListPage from "./paths/instance/transfers/list/index.js";
 import InstanceUpdatePage, {
@@ -87,6 +90,10 @@ export enum InstancePaths {
   templates_update = "/templates/:tid/update",
   templates_new = "/templates/new",
   templates_use = "/templates/:tid/use",
+
+  webhooks_list = "/webhooks",
+  webhooks_update = "/webhooks/:tid/update",
+  webhooks_new = "/webhooks/new",
 }
 
 // eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -389,6 +396,45 @@ export function InstanceRoutes({
             route(InstancePaths.transfers_list);
           }}
         />
+        {/**
+         * Webhooks pages
+         */}
+        <Route
+          path={InstancePaths.webhooks_list}
+          component={WebhookListPage}
+          onUnauthorized={LoginPageAccessDenied}
+          onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
+          onLoadError={ServerErrorRedirectTo(InstancePaths.update)}
+          onCreate={() => {
+            route(InstancePaths.webhooks_new);
+          }}
+          onSelect={(id: string) => {
+            route(InstancePaths.webhooks_update.replace(":tid", id));
+          }}
+        />
+        <Route
+          path={InstancePaths.webhooks_update}
+          component={WebhookUpdatePage}
+          onConfirm={() => {
+            route(InstancePaths.webhooks_list);
+          }}
+          onUnauthorized={LoginPageAccessDenied}
+          onLoadError={ServerErrorRedirectTo(InstancePaths.webhooks_list)}
+          onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
+          onBack={() => {
+            route(InstancePaths.webhooks_list);
+          }}
+        />
+        <Route
+          path={InstancePaths.webhooks_new}
+          component={WebhookCreatePage}
+          onConfirm={() => {
+            route(InstancePaths.webhooks_list);
+          }}
+          onBack={() => {
+            route(InstancePaths.webhooks_list);
+          }}
+        />
         {/**
          * Templates pages
          */}
diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx 
b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
index 92d144b1a..c7ece9ca2 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
@@ -140,6 +140,16 @@ export function Sidebar({
                   <span class="menu-item-label">Reserves</span>
                 </a>
               </li>
+              <li>
+                <a href={"/webhooks"} class="has-icon">
+                  <span class="icon">
+                    <i class="mdi mdi-newspaper" />
+                  </span>
+                  <span class="menu-item-label">
+                    <i18n.Translate>Webhooks</i18n.Translate>
+                  </span>
+                </a>
+              </li>
               {needKYC && (
                 <li>
                   <a href={"/kyc"} class="has-icon">
diff --git a/packages/merchant-backoffice-ui/src/declaration.d.ts 
b/packages/merchant-backoffice-ui/src/declaration.d.ts
index b0621c13c..32e6b44ea 100644
--- a/packages/merchant-backoffice-ui/src/declaration.d.ts
+++ b/packages/merchant-backoffice-ui/src/declaration.d.ts
@@ -1360,6 +1360,82 @@ export namespace MerchantBackend {
     }
   }
 
+  namespace Webhooks {
+    interface WebhookAddDetails {
+
+      // Webhook ID to use.
+      webhook_id: string;
+
+      // The event of the webhook: why the webhook is used.
+      event_type: string;
+
+      // URL of the webhook where the customer will be redirected.
+      url: string;
+
+      // Method used by the webhook
+      http_method: string;
+
+      // Header template of the webhook
+      header_template?: string;
+
+      // Body template by the webhook
+      body_template?: string;
+
+    }
+    interface WebhookPatchDetails {
+
+      // The event of the webhook: why the webhook is used.
+      event_type: string;
+
+      // URL of the webhook where the customer will be redirected.
+      url: string;
+
+      // Method used by the webhook
+      http_method: string;
+
+      // Header template of the webhook
+      header_template?: string;
+
+      // Body template by the webhook
+      body_template?: string;
+
+    }
+    interface WebhookSummaryResponse {
+
+      // List of webhooks that are present in our backend.
+      webhooks: WebhookEntry[];
+
+    }
+    interface WebhookEntry {
+
+      // Webhook identifier, as found in the webhook.
+      webhook_id: string;
+
+      // The event of the webhook: why the webhook is used.
+      event_type: string;
+
+    }
+    interface WebhookDetails {
+
+      // The event of the webhook: why the webhook is used.
+      event_type: string;
+
+      // URL of the webhook where the customer will be redirected.
+      url: string;
+
+      // Method used by the webhook
+      http_method: string;
+
+      // Header template of the webhook
+      header_template?: string;
+
+      // Body template by the webhook
+      body_template?: string;
+
+    }
+
+  }
+
   interface ContractTerms {
     // Human-readable description of the whole purchase
     summary: string;
diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts 
b/packages/merchant-backoffice-ui/src/hooks/backend.ts
index a0639a4a0..3f3db2fa1 100644
--- a/packages/merchant-backoffice-ui/src/hooks/backend.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts
@@ -115,6 +115,11 @@ interface useBackendInstanceRequestType {
     position?: string,
     delta?: number,
   ) => Promise<HttpResponseOk<T>>;
+  webhookFetcher: <T>(
+    path: string,
+    position?: string,
+    delta?: number,
+  ) => Promise<HttpResponseOk<T>>;
 }
 interface useBackendBaseRequestType {
   request: <T>(
@@ -274,6 +279,23 @@ export function useBackendInstanceRequest(): 
useBackendInstanceRequestType {
     [backend, token],
   );
 
+  const webhookFetcher = useCallback(
+    function webhookFetcherImpl<T>(
+      path: string,
+      position?: string,
+      delta?: number,
+    ): Promise<HttpResponseOk<T>> {
+      const params: any = {};
+      if (delta !== undefined) {
+        params.limit = delta;
+      }
+      if (position !== undefined) params.offset = position;
+
+      return requestHandler<T>(backend, path, { params, token });
+    },
+    [backend, token],
+  );
+
   return {
     request,
     fetcher,
@@ -283,5 +305,6 @@ export function useBackendInstanceRequest(): 
useBackendInstanceRequestType {
     tipsDetailFetcher,
     transferFetcher,
     templateFetcher,
+    webhookFetcher,
   };
 }
diff --git a/packages/merchant-backoffice-ui/src/hooks/webhooks.ts 
b/packages/merchant-backoffice-ui/src/hooks/webhooks.ts
new file mode 100644
index 000000000..9f196cefa
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/webhooks.ts
@@ -0,0 +1,165 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { MerchantBackend } from "../declaration.js";
+import { useMatchMutate, useBackendInstanceRequest } from "./backend.js";
+import useSWR from "swr";
+import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
+import { useEffect, useState } from "preact/hooks";
+import {
+  HttpError,
+  HttpResponse,
+  HttpResponseOk,
+  HttpResponsePaginated,
+} from "../utils/request.js";
+
+export function useWebhookAPI(): WebhookAPI {
+  const mutateAll = useMatchMutate();
+  const { request } = useBackendInstanceRequest();
+
+  const createWebhook = async (
+    data: MerchantBackend.Webhooks.WebhookAddDetails,
+  ): Promise<HttpResponseOk<void>> => {
+    const res = await request<void>(`/private/webhooks`, {
+      method: "POST",
+      data,
+    });
+    await mutateAll(/.*private\/webhooks.*/);
+    return res;
+  };
+
+  const updateWebhook = async (
+    webhookId: string,
+    data: MerchantBackend.Webhooks.WebhookPatchDetails,
+  ): Promise<HttpResponseOk<void>> => {
+    const res = await request<void>(`/private/webhooks/${webhookId}`, {
+      method: "PATCH",
+      data,
+    });
+    await mutateAll(/.*private\/webhooks.*/);
+    return res;
+  };
+
+  const deleteWebhook = async (
+    webhookId: string,
+  ): Promise<HttpResponseOk<void>> => {
+    const res = await request<void>(`/private/webhooks/${webhookId}`, {
+      method: "DELETE",
+    });
+    await mutateAll(/.*private\/webhooks.*/);
+    return res;
+  };
+
+  return { createWebhook, updateWebhook, deleteWebhook };
+}
+
+export interface WebhookAPI {
+  createWebhook: (
+    data: MerchantBackend.Webhooks.WebhookAddDetails,
+  ) => Promise<HttpResponseOk<void>>;
+  updateWebhook: (
+    id: string,
+    data: MerchantBackend.Webhooks.WebhookPatchDetails,
+  ) => Promise<HttpResponseOk<void>>;
+  deleteWebhook: (id: string) => Promise<HttpResponseOk<void>>;
+}
+
+export interface InstanceWebhookFilter {
+  //FIXME: add filter to the webhook list
+  position?: string;
+}
+
+export function useInstanceWebhooks(
+  args?: InstanceWebhookFilter,
+  updatePosition?: (id: string) => void,
+): HttpResponsePaginated<MerchantBackend.Webhooks.WebhookSummaryResponse> {
+  const { webhookFetcher } = useBackendInstanceRequest();
+
+  const [pageAfter, setPageAfter] = useState(1);
+
+  const totalAfter = pageAfter * PAGE_SIZE;
+
+  const {
+    data: afterData,
+    error: afterError,
+    isValidating: loadingAfter,
+  } = useSWR<
+    HttpResponseOk<MerchantBackend.Webhooks.WebhookSummaryResponse>,
+    HttpError
+  >([`/private/webhooks`, args?.position, -totalAfter], webhookFetcher);
+
+  const [lastAfter, setLastAfter] = useState<
+    HttpResponse<MerchantBackend.Webhooks.WebhookSummaryResponse>
+  >({ loading: true });
+  useEffect(() => {
+    if (afterData) setLastAfter(afterData);
+  }, [afterData]);
+
+  if (afterError) return afterError;
+
+  const isReachingEnd =
+    afterData && afterData.data.webhooks.length < totalAfter;
+  const isReachingStart = false;
+
+  const pagination = {
+    isReachingEnd,
+    isReachingStart,
+    loadMore: () => {
+      if (!afterData || isReachingEnd) return;
+      if (afterData.data.webhooks.length < MAX_RESULT_SIZE) {
+        setPageAfter(pageAfter + 1);
+      } else {
+        const from = `${afterData.data.webhooks[afterData.data.webhooks.length 
- 1]
+          .webhook_id
+          }`;
+        if (from && updatePosition) updatePosition(from);
+      }
+    },
+    loadMorePrev: () => {
+      return
+    },
+  };
+
+  const webhooks = !afterData ? [] : (afterData || lastAfter).data.webhooks;
+
+  if (loadingAfter)
+    return { loading: true, data: { webhooks } };
+  if (afterData) {
+    return { ok: true, data: { webhooks }, ...pagination };
+  }
+  return { loading: true };
+}
+
+export function useWebhookDetails(
+  webhookId: string,
+): HttpResponse<MerchantBackend.Webhooks.WebhookDetails> {
+  const { webhookFetcher } = useBackendInstanceRequest();
+
+  const { data, error, isValidating } = useSWR<
+    HttpResponseOk<MerchantBackend.Webhooks.WebhookDetails>,
+    HttpError
+  >([`/private/webhooks/${webhookId}`], webhookFetcher, {
+    refreshInterval: 0,
+    refreshWhenHidden: false,
+    revalidateOnFocus: false,
+    revalidateOnReconnect: false,
+    refreshWhenOffline: false,
+  });
+
+  if (isValidating) return { loading: true, data: data?.data };
+  if (data) return data;
+  if (error) return error;
+  return { loading: true };
+}
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
index 6635d6c55..57d328d39 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
@@ -180,7 +180,7 @@ function Table({
                     </button>
                     <button
                       class="button is-info is-small has-tooltip-left"
-                      data-tooltip={i18n.str`delete selected templates from 
the database`}
+                      data-tooltip={i18n.str`use template to create new order`}
                       onClick={() => onNewOrder(i)}
                     >
                       New order
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/Create.stories.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/Create.stories.tsx
new file mode 100644
index 000000000..4857ede97
--- /dev/null
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/Create.stories.tsx
@@ -0,0 +1,28 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { h, VNode, FunctionalComponent } from "preact";
+import { CreatePage as TestedComponent } from "./CreatePage.js";
+
+export default {
+  title: "Pages/Webhooks/Create",
+  component: TestedComponent,
+};
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
new file mode 100644
index 000000000..1d049149b
--- /dev/null
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
@@ -0,0 +1,140 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
+import {
+  FormErrors,
+  FormProvider,
+} from "../../../../components/form/FormProvider.js";
+import { Input } from "../../../../components/form/Input.js";
+import { InputCurrency } from "../../../../components/form/InputCurrency.js";
+import { InputDuration } from "../../../../components/form/InputDuration.js";
+import { InputNumber } from "../../../../components/form/InputNumber.js";
+import { useBackendContext } from "../../../../context/backend.js";
+import { MerchantBackend } from "../../../../declaration.js";
+
+type Entity = MerchantBackend.Webhooks.WebhookAddDetails;
+
+interface Props {
+  onCreate: (d: Entity) => Promise<void>;
+  onBack?: () => void;
+}
+
+const validMethod = ["GET", "POST", "PUT", "PATCH", "HEAD"];
+
+export function CreatePage({ onCreate, onBack }: Props): VNode {
+  const { i18n } = useTranslationContext();
+
+  const [state, setState] = useState<Partial<Entity>>({});
+
+  const errors: FormErrors<Entity> = {
+    webhook_id: !state.webhook_id ? i18n.str`required` : undefined,
+    event_type: !state.event_type ? i18n.str`required` : undefined,
+    http_method: !state.http_method
+      ? i18n.str`required`
+      : !validMethod.includes(state.http_method)
+      ? i18n.str`should be one of "${validMethod.join(", ")}"`
+      : undefined,
+    url: !state.url ? i18n.str`required` : undefined,
+  };
+
+  const hasErrors = Object.keys(errors).some(
+    (k) => (errors as any)[k] !== undefined,
+  );
+
+  const submitForm = () => {
+    if (hasErrors) return Promise.reject();
+    return onCreate(state as any);
+  };
+
+  return (
+    <div>
+      <section class="section is-main-section">
+        <div class="columns">
+          <div class="column" />
+          <div class="column is-four-fifths">
+            <FormProvider
+              object={state}
+              valueHandler={setState}
+              errors={errors}
+            >
+              <Input<Entity>
+                name="webhook_id"
+                label={i18n.str`ID`}
+                tooltip={i18n.str`Webhook ID to use`}
+              />
+              <Input<Entity>
+                name="event_type"
+                label={i18n.str`Event`}
+                tooltip={i18n.str`The event of the webhook: why the webhook is 
used`}
+              />
+              <Input<Entity>
+                name="http_method"
+                label={i18n.str`Method`}
+                tooltip={i18n.str`Method used by the webhook`}
+              />
+              <Input<Entity>
+                name="url"
+                label={i18n.str`URL`}
+                tooltip={i18n.str`URL of the webhook where the customer will 
be redirected`}
+              />
+              <Input<Entity>
+                name="header_template"
+                label={i18n.str`Header`}
+                inputType="multiline"
+                tooltip={i18n.str`Header template of the webhook`}
+              />
+              <Input<Entity>
+                name="body_template"
+                inputType="multiline"
+                label={i18n.str`Body`}
+                tooltip={i18n.str`Body template by the webhook`}
+              />
+            </FormProvider>
+
+            <div class="buttons is-right mt-5">
+              {onBack && (
+                <button class="button" onClick={onBack}>
+                  <i18n.Translate>Cancel</i18n.Translate>
+                </button>
+              )}
+              <AsyncButton
+                disabled={hasErrors}
+                data-tooltip={
+                  hasErrors
+                    ? i18n.str`Need to complete marked fields`
+                    : "confirm operation"
+                }
+                onClick={submitForm}
+              >
+                <i18n.Translate>Confirm</i18n.Translate>
+              </AsyncButton>
+            </div>
+          </div>
+          <div class="column" />
+        </div>
+      </section>
+    </div>
+  );
+}
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx
new file mode 100644
index 000000000..9f1c5e905
--- /dev/null
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx
@@ -0,0 +1,61 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { NotificationCard } from "../../../../components/menu/index.js";
+import { MerchantBackend } from "../../../../declaration.js";
+import { useWebhookAPI } from "../../../../hooks/webhooks.js";
+import { Notification } from "../../../../utils/types.js";
+import { CreatePage } from "./CreatePage.js";
+
+export type Entity = MerchantBackend.Webhooks.WebhookAddDetails;
+interface Props {
+  onBack?: () => void;
+  onConfirm: () => void;
+}
+
+export default function CreateWebhook({ onConfirm, onBack }: Props): VNode {
+  const { createWebhook } = useWebhookAPI();
+  const [notif, setNotif] = useState<Notification | undefined>(undefined);
+  const { i18n } = useTranslationContext();
+
+  return (
+    <>
+      <NotificationCard notification={notif} />
+      <CreatePage
+        onBack={onBack}
+        onCreate={(request: MerchantBackend.Webhooks.WebhookAddDetails) => {
+          return createWebhook(request)
+            .then(() => onConfirm())
+            .catch((error) => {
+              setNotif({
+                message: i18n.str`could not inform template`,
+                type: "ERROR",
+                description: error.message,
+              });
+            });
+        }}
+      />
+    </>
+  );
+}
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/List.stories.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/List.stories.tsx
new file mode 100644
index 000000000..702e9ba4a
--- /dev/null
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/List.stories.tsx
@@ -0,0 +1,28 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { FunctionalComponent, h } from "preact";
+import { ListPage as TestedComponent } from "./ListPage.js";
+
+export default {
+  title: "Pages/Templates/List",
+  component: TestedComponent,
+};
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/ListPage.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/ListPage.tsx
new file mode 100644
index 000000000..942a8a63e
--- /dev/null
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/ListPage.tsx
@@ -0,0 +1,64 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { h, VNode } from "preact";
+import { MerchantBackend } from "../../../../declaration.js";
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { CardTable } from "./Table.js";
+
+export interface Props {
+  webhooks: MerchantBackend.Webhooks.WebhookEntry[];
+  onLoadMoreBefore?: () => void;
+  onLoadMoreAfter?: () => void;
+  onCreate: () => void;
+  onDelete: (e: MerchantBackend.Webhooks.WebhookEntry) => void;
+  onSelect: (e: MerchantBackend.Webhooks.WebhookEntry) => void;
+}
+
+export function ListPage({
+  webhooks,
+  onCreate,
+  onDelete,
+  onSelect,
+  onLoadMoreBefore,
+  onLoadMoreAfter,
+}: Props): VNode {
+  const form = { payto_uri: "" };
+
+  const { i18n } = useTranslationContext();
+  return (
+    <section class="section is-main-section">
+      <CardTable
+        webhooks={webhooks.map((o) => ({
+          ...o,
+          id: String(o.webhook_id),
+        }))}
+        onCreate={onCreate}
+        onDelete={onDelete}
+        onSelect={onSelect}
+        onLoadMoreBefore={onLoadMoreBefore}
+        hasMoreBefore={!onLoadMoreBefore}
+        onLoadMoreAfter={onLoadMoreAfter}
+        hasMoreAfter={!onLoadMoreAfter}
+      />
+    </section>
+  );
+}
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx
similarity index 82%
copy from 
packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
copy to 
packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx
index 6635d6c55..1981cabdd 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx
@@ -24,13 +24,12 @@ import { h, VNode } from "preact";
 import { StateUpdater, useState } from "preact/hooks";
 import { MerchantBackend } from "../../../../declaration.js";
 
-type Entity = MerchantBackend.Template.TemplateEntry;
+type Entity = MerchantBackend.Webhooks.WebhookEntry;
 
 interface Props {
-  templates: Entity[];
+  webhooks: Entity[];
   onDelete: (e: Entity) => void;
   onSelect: (e: Entity) => void;
-  onNewOrder: (e: Entity) => void;
   onCreate: () => void;
   onLoadMoreBefore?: () => void;
   hasMoreBefore?: boolean;
@@ -39,11 +38,10 @@ interface Props {
 }
 
 export function CardTable({
-  templates,
+  webhooks,
   onCreate,
   onDelete,
   onSelect,
-  onNewOrder,
   onLoadMoreAfter,
   onLoadMoreBefore,
   hasMoreAfter,
@@ -60,12 +58,12 @@ export function CardTable({
           <span class="icon">
             <i class="mdi mdi-newspaper" />
           </span>
-          <i18n.Translate>Templates</i18n.Translate>
+          <i18n.Translate>Webhooks</i18n.Translate>
         </p>
         <div class="card-header-icon" aria-label="more options">
           <span
             class="has-tooltip-left"
-            data-tooltip={i18n.str`add new templates`}
+            data-tooltip={i18n.str`add new webhooks`}
           >
             <button class="button is-info" type="button" onClick={onCreate}>
               <span class="icon is-small">
@@ -78,12 +76,14 @@ export function CardTable({
       <div class="card-content">
         <div class="b-table has-pagination">
           <div class="table-wrapper has-mobile-cards">
-            {templates.length > 0 ? (
+            {webhooks.length > 0 ? (
               <Table
-                instances={templates}
+                instances={webhooks}
                 onDelete={onDelete}
                 onSelect={onSelect}
-                onNewOrder={onNewOrder}
+                onNewOrder={(d) => {
+                  console.log("test", d);
+                }}
                 rowSelection={rowSelection}
                 rowSelectionHandler={rowSelectionHandler}
                 onLoadMoreAfter={onLoadMoreAfter}
@@ -134,11 +134,11 @@ function Table({
       {onLoadMoreBefore && (
         <button
           class="button is-fullwidth"
-          data-tooltip={i18n.str`load more templates before the first one`}
+          data-tooltip={i18n.str`load more webhooks before the first one`}
           disabled={!hasMoreBefore}
           onClick={onLoadMoreBefore}
         >
-          <i18n.Translate>load newer templates</i18n.Translate>
+          <i18n.Translate>load newer webhooks</i18n.Translate>
         </button>
       )}
       <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -148,7 +148,7 @@ function Table({
               <i18n.Translate>ID</i18n.Translate>
             </th>
             <th>
-              <i18n.Translate>Description</i18n.Translate>
+              <i18n.Translate>Event type</i18n.Translate>
             </th>
             <th />
           </tr>
@@ -156,35 +156,35 @@ function Table({
         <tbody>
           {instances.map((i) => {
             return (
-              <tr key={i.template_id}>
+              <tr key={i.webhook_id}>
                 <td
                   onClick={(): void => onSelect(i)}
                   style={{ cursor: "pointer" }}
                 >
-                  {i.template_id}
+                  {i.webhook_id}
                 </td>
                 <td
                   onClick={(): void => onSelect(i)}
                   style={{ cursor: "pointer" }}
                 >
-                  {i.template_description}
+                  {i.event_type}
                 </td>
                 <td class="is-actions-cell right-sticky">
                   <div class="buttons is-right">
                     <button
                       class="button is-danger is-small has-tooltip-left"
-                      data-tooltip={i18n.str`delete selected templates from 
the database`}
+                      data-tooltip={i18n.str`delete selected webhook from the 
database`}
                       onClick={() => onDelete(i)}
                     >
                       Delete
                     </button>
-                    <button
+                    {/* <button
                       class="button is-info is-small has-tooltip-left"
-                      data-tooltip={i18n.str`delete selected templates from 
the database`}
+                      data-tooltip={i18n.str`test webhook`}
                       onClick={() => onNewOrder(i)}
                     >
-                      New order
-                    </button>
+                      Test
+                    </button> */}
                   </div>
                 </td>
               </tr>
@@ -195,11 +195,11 @@ function Table({
       {onLoadMoreAfter && (
         <button
           class="button is-fullwidth"
-          data-tooltip={i18n.str`load more templates after the last one`}
+          data-tooltip={i18n.str`load more webhooks after the last one`}
           disabled={!hasMoreAfter}
           onClick={onLoadMoreAfter}
         >
-          <i18n.Translate>load older templates</i18n.Translate>
+          <i18n.Translate>load older webhooks</i18n.Translate>
         </button>
       )}
     </div>
@@ -217,7 +217,7 @@ function EmptyTable(): VNode {
       </p>
       <p>
         <i18n.Translate>
-          There is no templates yet, add more pressing the + sign
+          There is no webhooks yet, add more pressing the + sign
         </i18n.Translate>
       </p>
     </div>
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx
new file mode 100644
index 000000000..c5846e4db
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx
@@ -0,0 +1,95 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Loading } from "../../../../components/exception/loading.js";
+import { NotificationCard } from "../../../../components/menu/index.js";
+import { MerchantBackend } from "../../../../declaration.js";
+import {
+  useInstanceWebhooks,
+  useWebhookAPI,
+} from "../../../../hooks/webhooks.js";
+import { HttpError } from "../../../../utils/request.js";
+import { Notification } from "../../../../utils/types.js";
+import { ListPage } from "./ListPage.js";
+
+interface Props {
+  onUnauthorized: () => VNode;
+  onLoadError: (error: HttpError) => VNode;
+  onNotFound: () => VNode;
+  onCreate: () => void;
+  onSelect: (id: string) => void;
+}
+
+export default function ListWebhooks({
+  onUnauthorized,
+  onLoadError,
+  onCreate,
+  onSelect,
+  onNotFound,
+}: Props): VNode {
+  const [position, setPosition] = useState<string | undefined>(undefined);
+  const { i18n } = useTranslationContext();
+  const [notif, setNotif] = useState<Notification | undefined>(undefined);
+  const { deleteWebhook } = useWebhookAPI();
+  const result = useInstanceWebhooks({ position }, (id) => setPosition(id));
+
+  if (result.clientError && result.isUnauthorized) return onUnauthorized();
+  if (result.clientError && result.isNotfound) return onNotFound();
+  if (result.loading) return <Loading />;
+  if (!result.ok) return onLoadError(result);
+
+  return (
+    <Fragment>
+      <NotificationCard notification={notif} />
+
+      <ListPage
+        webhooks={result.data.webhooks}
+        onLoadMoreBefore={
+          result.isReachingStart ? result.loadMorePrev : undefined
+        }
+        onLoadMoreAfter={result.isReachingEnd ? result.loadMore : undefined}
+        onCreate={onCreate}
+        onSelect={(e) => {
+          onSelect(e.webhook_id);
+        }}
+        onDelete={(e: MerchantBackend.Webhooks.WebhookEntry) =>
+          deleteWebhook(e.webhook_id)
+            .then(() =>
+              setNotif({
+                message: i18n.str`webhook delete successfully`,
+                type: "SUCCESS",
+              }),
+            )
+            .catch((error) =>
+              setNotif({
+                message: i18n.str`could not delete the webhook`,
+                type: "ERROR",
+                description: error.message,
+              }),
+            )
+        }
+      />
+    </Fragment>
+  );
+}
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/Update.stories.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/Update.stories.tsx
new file mode 100644
index 000000000..8d07cb31f
--- /dev/null
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/Update.stories.tsx
@@ -0,0 +1,32 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { h, VNode, FunctionalComponent } from "preact";
+import { UpdatePage as TestedComponent } from "./UpdatePage.js";
+
+export default {
+  title: "Pages/Templates/Update",
+  component: TestedComponent,
+  argTypes: {
+    onUpdate: { action: "onUpdate" },
+    onBack: { action: "onBack" },
+  },
+};
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
new file mode 100644
index 000000000..4e3674dca
--- /dev/null
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
@@ -0,0 +1,146 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
+import {
+  FormErrors,
+  FormProvider,
+} from "../../../../components/form/FormProvider.js";
+import { Input } from "../../../../components/form/Input.js";
+import { useBackendContext } from "../../../../context/backend.js";
+import { MerchantBackend, WithId } from "../../../../declaration.js";
+
+type Entity = MerchantBackend.Webhooks.WebhookPatchDetails & WithId;
+
+interface Props {
+  onUpdate: (d: Entity) => Promise<void>;
+  onBack?: () => void;
+  webhook: Entity;
+}
+const validMethod = ["GET", "POST", "PUT", "PATCH", "HEAD"];
+
+export function UpdatePage({ webhook, onUpdate, onBack }: Props): VNode {
+  const { i18n } = useTranslationContext();
+
+  const [state, setState] = useState<Partial<Entity>>(webhook);
+
+  const errors: FormErrors<Entity> = {
+    event_type: !state.event_type ? i18n.str`required` : undefined,
+    http_method: !state.http_method
+      ? i18n.str`required`
+      : !validMethod.includes(state.http_method)
+      ? i18n.str`should be one of "${validMethod.join(", ")}"`
+      : undefined,
+    url: !state.url ? i18n.str`required` : undefined,
+  };
+
+  const hasErrors = Object.keys(errors).some(
+    (k) => (errors as any)[k] !== undefined,
+  );
+
+  const submitForm = () => {
+    if (hasErrors) return Promise.reject();
+    return onUpdate(state as any);
+  };
+
+  return (
+    <div>
+      <section class="section">
+        <section class="hero is-hero-bar">
+          <div class="hero-body">
+            <div class="level">
+              <div class="level-left">
+                <div class="level-item">
+                  <span class="is-size-4">
+                    Webhook: <b>{webhook.id}</b>
+                  </span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </section>
+        <hr />
+
+        <section class="section is-main-section">
+          <div class="columns">
+            <div class="column is-four-fifths">
+              <FormProvider
+                object={state}
+                valueHandler={setState}
+                errors={errors}
+              >
+                <Input<Entity>
+                  name="event_type"
+                  label={i18n.str`Event`}
+                  tooltip={i18n.str`The event of the webhook: why the webhook 
is used`}
+                />
+                <Input<Entity>
+                  name="http_method"
+                  label={i18n.str`Method`}
+                  tooltip={i18n.str`Method used by the webhook`}
+                />
+                <Input<Entity>
+                  name="url"
+                  label={i18n.str`URL`}
+                  tooltip={i18n.str`URL of the webhook where the customer will 
be redirected`}
+                />
+                <Input<Entity>
+                  name="header_template"
+                  label={i18n.str`Header`}
+                  inputType="multiline"
+                  tooltip={i18n.str`Header template of the webhook`}
+                />
+                <Input<Entity>
+                  name="body_template"
+                  inputType="multiline"
+                  label={i18n.str`Body`}
+                  tooltip={i18n.str`Body template by the webhook`}
+                />
+              </FormProvider>
+
+              <div class="buttons is-right mt-5">
+                {onBack && (
+                  <button class="button" onClick={onBack}>
+                    <i18n.Translate>Cancel</i18n.Translate>
+                  </button>
+                )}
+                <AsyncButton
+                  disabled={hasErrors}
+                  data-tooltip={
+                    hasErrors
+                      ? i18n.str`Need to complete marked fields`
+                      : "confirm operation"
+                  }
+                  onClick={submitForm}
+                >
+                  <i18n.Translate>Confirm</i18n.Translate>
+                </AsyncButton>
+              </div>
+            </div>
+          </div>
+        </section>
+      </section>
+    </div>
+  );
+}
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx
new file mode 100644
index 000000000..3597fb849
--- /dev/null
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx
@@ -0,0 +1,85 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Loading } from "../../../../components/exception/loading.js";
+import { NotificationCard } from "../../../../components/menu/index.js";
+import { MerchantBackend, WithId } from "../../../../declaration.js";
+import {
+  useWebhookAPI,
+  useWebhookDetails,
+} from "../../../../hooks/webhooks.js";
+import { HttpError } from "../../../../utils/request.js";
+import { Notification } from "../../../../utils/types.js";
+import { UpdatePage } from "./UpdatePage.js";
+
+export type Entity = MerchantBackend.Webhooks.WebhookPatchDetails & WithId;
+
+interface Props {
+  onBack?: () => void;
+  onConfirm: () => void;
+  onUnauthorized: () => VNode;
+  onNotFound: () => VNode;
+  onLoadError: (e: HttpError) => VNode;
+  tid: string;
+}
+export default function UpdateWebhook({
+  tid,
+  onConfirm,
+  onBack,
+  onUnauthorized,
+  onNotFound,
+  onLoadError,
+}: Props): VNode {
+  const { updateWebhook } = useWebhookAPI();
+  const result = useWebhookDetails(tid);
+  const [notif, setNotif] = useState<Notification | undefined>(undefined);
+
+  const { i18n } = useTranslationContext();
+
+  if (result.clientError && result.isUnauthorized) return onUnauthorized();
+  if (result.clientError && result.isNotfound) return onNotFound();
+  if (result.loading) return <Loading />;
+  if (!result.ok) return onLoadError(result);
+
+  return (
+    <Fragment>
+      <NotificationCard notification={notif} />
+      <UpdatePage
+        webhook={{ ...result.data, id: tid }}
+        onBack={onBack}
+        onUpdate={(data) => {
+          return updateWebhook(tid, data)
+            .then(onConfirm)
+            .catch((error) => {
+              setNotif({
+                message: i18n.str`could not update template`,
+                type: "ERROR",
+                description: error.message,
+              });
+            });
+        }}
+      />
+    </Fragment>
+  );
+}

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