webhook api
This commit is contained in:
parent
1b2b5d62de
commit
eebb85bef4
@ -51,6 +51,9 @@ import TemplateCreatePage from "./paths/instance/templates/create/index.js";
|
|||||||
import TemplateUsePage from "./paths/instance/templates/use/index.js";
|
import TemplateUsePage from "./paths/instance/templates/use/index.js";
|
||||||
import TemplateListPage from "./paths/instance/templates/list/index.js";
|
import TemplateListPage from "./paths/instance/templates/list/index.js";
|
||||||
import TemplateUpdatePage from "./paths/instance/templates/update/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 TransferCreatePage from "./paths/instance/transfers/create/index.js";
|
||||||
import TransferListPage from "./paths/instance/transfers/list/index.js";
|
import TransferListPage from "./paths/instance/transfers/list/index.js";
|
||||||
import InstanceUpdatePage, {
|
import InstanceUpdatePage, {
|
||||||
@ -87,6 +90,10 @@ export enum InstancePaths {
|
|||||||
templates_update = "/templates/:tid/update",
|
templates_update = "/templates/:tid/update",
|
||||||
templates_new = "/templates/new",
|
templates_new = "/templates/new",
|
||||||
templates_use = "/templates/:tid/use",
|
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
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
@ -389,6 +396,45 @@ export function InstanceRoutes({
|
|||||||
route(InstancePaths.transfers_list);
|
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
|
* Templates pages
|
||||||
*/}
|
*/}
|
||||||
|
@ -140,6 +140,16 @@ export function Sidebar({
|
|||||||
<span class="menu-item-label">Reserves</span>
|
<span class="menu-item-label">Reserves</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</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 && (
|
{needKYC && (
|
||||||
<li>
|
<li>
|
||||||
<a href={"/kyc"} class="has-icon">
|
<a href={"/kyc"} class="has-icon">
|
||||||
|
@ -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 {
|
interface ContractTerms {
|
||||||
// Human-readable description of the whole purchase
|
// Human-readable description of the whole purchase
|
||||||
summary: string;
|
summary: string;
|
||||||
|
@ -115,6 +115,11 @@ interface useBackendInstanceRequestType {
|
|||||||
position?: string,
|
position?: string,
|
||||||
delta?: number,
|
delta?: number,
|
||||||
) => Promise<HttpResponseOk<T>>;
|
) => Promise<HttpResponseOk<T>>;
|
||||||
|
webhookFetcher: <T>(
|
||||||
|
path: string,
|
||||||
|
position?: string,
|
||||||
|
delta?: number,
|
||||||
|
) => Promise<HttpResponseOk<T>>;
|
||||||
}
|
}
|
||||||
interface useBackendBaseRequestType {
|
interface useBackendBaseRequestType {
|
||||||
request: <T>(
|
request: <T>(
|
||||||
@ -274,6 +279,23 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType {
|
|||||||
[backend, token],
|
[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 {
|
return {
|
||||||
request,
|
request,
|
||||||
fetcher,
|
fetcher,
|
||||||
@ -283,5 +305,6 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType {
|
|||||||
tipsDetailFetcher,
|
tipsDetailFetcher,
|
||||||
transferFetcher,
|
transferFetcher,
|
||||||
templateFetcher,
|
templateFetcher,
|
||||||
|
webhookFetcher,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
165
packages/merchant-backoffice-ui/src/hooks/webhooks.ts
Normal file
165
packages/merchant-backoffice-ui/src/hooks/webhooks.ts
Normal file
@ -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 };
|
||||||
|
}
|
@ -180,7 +180,7 @@ function Table({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button is-info is-small has-tooltip-left"
|
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)}
|
onClick={() => onNewOrder(i)}
|
||||||
>
|
>
|
||||||
New order
|
New order
|
||||||
|
@ -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,
|
||||||
|
};
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -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,
|
||||||
|
};
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
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 { StateUpdater, useState } from "preact/hooks";
|
||||||
|
import { MerchantBackend } from "../../../../declaration.js";
|
||||||
|
|
||||||
|
type Entity = MerchantBackend.Webhooks.WebhookEntry;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
webhooks: Entity[];
|
||||||
|
onDelete: (e: Entity) => void;
|
||||||
|
onSelect: (e: Entity) => void;
|
||||||
|
onCreate: () => void;
|
||||||
|
onLoadMoreBefore?: () => void;
|
||||||
|
hasMoreBefore?: boolean;
|
||||||
|
hasMoreAfter?: boolean;
|
||||||
|
onLoadMoreAfter?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CardTable({
|
||||||
|
webhooks,
|
||||||
|
onCreate,
|
||||||
|
onDelete,
|
||||||
|
onSelect,
|
||||||
|
onLoadMoreAfter,
|
||||||
|
onLoadMoreBefore,
|
||||||
|
hasMoreAfter,
|
||||||
|
hasMoreBefore,
|
||||||
|
}: Props): VNode {
|
||||||
|
const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="card has-table">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="mdi mdi-newspaper" />
|
||||||
|
</span>
|
||||||
|
<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 webhooks`}
|
||||||
|
>
|
||||||
|
<button class="button is-info" type="button" onClick={onCreate}>
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="mdi mdi-plus mdi-36px" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="b-table has-pagination">
|
||||||
|
<div class="table-wrapper has-mobile-cards">
|
||||||
|
{webhooks.length > 0 ? (
|
||||||
|
<Table
|
||||||
|
instances={webhooks}
|
||||||
|
onDelete={onDelete}
|
||||||
|
onSelect={onSelect}
|
||||||
|
onNewOrder={(d) => {
|
||||||
|
console.log("test", d);
|
||||||
|
}}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
rowSelectionHandler={rowSelectionHandler}
|
||||||
|
onLoadMoreAfter={onLoadMoreAfter}
|
||||||
|
onLoadMoreBefore={onLoadMoreBefore}
|
||||||
|
hasMoreAfter={hasMoreAfter}
|
||||||
|
hasMoreBefore={hasMoreBefore}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EmptyTable />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
interface TableProps {
|
||||||
|
rowSelection: string[];
|
||||||
|
instances: Entity[];
|
||||||
|
onDelete: (e: Entity) => void;
|
||||||
|
onNewOrder: (e: Entity) => void;
|
||||||
|
onSelect: (e: Entity) => void;
|
||||||
|
rowSelectionHandler: StateUpdater<string[]>;
|
||||||
|
onLoadMoreBefore?: () => void;
|
||||||
|
hasMoreBefore?: boolean;
|
||||||
|
hasMoreAfter?: boolean;
|
||||||
|
onLoadMoreAfter?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelected<T>(id: T): (prev: T[]) => T[] {
|
||||||
|
return (prev: T[]): T[] =>
|
||||||
|
prev.indexOf(id) == -1 ? [...prev, id] : prev.filter((e) => e != id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Table({
|
||||||
|
instances,
|
||||||
|
onLoadMoreAfter,
|
||||||
|
onDelete,
|
||||||
|
onNewOrder,
|
||||||
|
onSelect,
|
||||||
|
onLoadMoreBefore,
|
||||||
|
hasMoreAfter,
|
||||||
|
hasMoreBefore,
|
||||||
|
}: TableProps): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
return (
|
||||||
|
<div class="table-container">
|
||||||
|
{onLoadMoreBefore && (
|
||||||
|
<button
|
||||||
|
class="button is-fullwidth"
|
||||||
|
data-tooltip={i18n.str`load more webhooks before the first one`}
|
||||||
|
disabled={!hasMoreBefore}
|
||||||
|
onClick={onLoadMoreBefore}
|
||||||
|
>
|
||||||
|
<i18n.Translate>load newer webhooks</i18n.Translate>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>ID</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Event type</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{instances.map((i) => {
|
||||||
|
return (
|
||||||
|
<tr key={i.webhook_id}>
|
||||||
|
<td
|
||||||
|
onClick={(): void => onSelect(i)}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
{i.webhook_id}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
onClick={(): void => onSelect(i)}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
{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 webhook from the database`}
|
||||||
|
onClick={() => onDelete(i)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
{/* <button
|
||||||
|
class="button is-info is-small has-tooltip-left"
|
||||||
|
data-tooltip={i18n.str`test webhook`}
|
||||||
|
onClick={() => onNewOrder(i)}
|
||||||
|
>
|
||||||
|
Test
|
||||||
|
</button> */}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{onLoadMoreAfter && (
|
||||||
|
<button
|
||||||
|
class="button is-fullwidth"
|
||||||
|
data-tooltip={i18n.str`load more webhooks after the last one`}
|
||||||
|
disabled={!hasMoreAfter}
|
||||||
|
onClick={onLoadMoreAfter}
|
||||||
|
>
|
||||||
|
<i18n.Translate>load older webhooks</i18n.Translate>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmptyTable(): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
return (
|
||||||
|
<div class="content has-text-grey has-text-centered">
|
||||||
|
<p>
|
||||||
|
<span class="icon is-large">
|
||||||
|
<i class="mdi mdi-emoticon-sad mdi-48px" />
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<i18n.Translate>
|
||||||
|
There is no webhooks yet, add more pressing the + sign
|
||||||
|
</i18n.Translate>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -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" },
|
||||||
|
},
|
||||||
|
};
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user