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 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
|
||||
*/}
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
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
|
||||
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
|
||||
|
@ -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