taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit a945c2fe7934fd258f7aa96fe8c623def8f4ca75
parent 1e15e48de352bd10c1e07e169d35e8d7d12974e2
Author: Sebastian <sebasjm@gmail.com>
Date:   Tue, 11 Feb 2025 17:46:41 -0300

fix #9509

Diffstat:
Mpackages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx | 53++++++++++++++++++++++++++++++++++++-----------------
Mpackages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx | 46+++++++++++++++++++++++++++++++++++++++++-----
Mpackages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx | 24+++++++++++++-----------
Mpackages/taler-util/src/http-client/merchant.ts | 2+-
4 files changed, 91 insertions(+), 34 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx @@ -39,6 +39,7 @@ interface Props { onBack?: () => void; } +const validType = ["pay", "refund"]; const validMethod = ["GET", "POST", "PUT", "PATCH", "HEAD"]; export function CreatePage({ onCreate, onBack }: Props): VNode { @@ -50,15 +51,19 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { webhook_id: !state.webhook_id ? i18n.str`Required` : undefined, event_type: !state.event_type ? i18n.str`Required` - : state.event_type !== "pay" && state.event_type !== "refund" - ? i18n.str`Must be "pay" or "refund"` + : validType.indexOf(state.event_type) === -1 + ? i18n.str`Must be one of '${validType.join(", ")}'` : undefined, http_method: !state.http_method ? i18n.str`Required` - : !validMethod.includes(state.http_method) + : validMethod.indexOf(state.http_method) === -1 ? i18n.str`Must be one of '${validMethod.join(", ")}'` : undefined, - url: !state.url ? i18n.str`Required` : undefined, + url: !state.url + ? i18n.str`Required` + : isInvalidUrl(state.url) + ? i18n.str`URL is invalid` + : undefined, }); const hasErrors = errors !== undefined; @@ -87,24 +92,29 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { <InputSelector name="event_type" label={i18n.str`Event`} - values={[ - i18n.str`Choose one...`, - i18n.str`Pay`, - i18n.str`Refund`, - ]} + values={["choose", ...validType]} + toStr={(v) => { + const idx = validType.indexOf(v); + if (idx === -1) return i18n.str`Choose one...`; + return [i18n.str`Pay`, i18n.str`Refund`][idx]; + }} tooltip={i18n.str`The event of the webhook: why the webhook is used`} /> <InputSelector name="http_method" label={i18n.str`Method`} - values={[ - i18n.str`Choose one...`, - i18n.str`GET`, - i18n.str`POST`, - i18n.str`PUT`, - i18n.str`PATCH`, - i18n.str`HEAD`, - ]} + toStr={(v) => { + const idx = validMethod.indexOf(v); + if (idx === -1) return i18n.str`Choose one...`; + return [ + i18n.str`GET`, + i18n.str`POST`, + i18n.str`PUT`, + i18n.str`PATCH`, + i18n.str`HEAD`, + ][idx]; + }} + values={["choose", ...validMethod]} tooltip={i18n.str`Method used by the webhook`} /> @@ -228,3 +238,12 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { </div> ); } + +function isInvalidUrl(url: string) { + try { + const asd = new URL("./", url); + return false; + } catch (e) { + return true; + } +} diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx @@ -31,6 +31,7 @@ import { import { Input } from "../../../../components/form/Input.js"; import { WithId } from "../../../../declaration.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; +import { InputSelector } from "../../../../components/form/InputSelector.js"; type Entity = TalerMerchantApi.WebhookPatchDetails & WithId; @@ -39,6 +40,7 @@ interface Props { onBack?: () => void; webhook: Entity; } +const validType = ["pay", "refund"]; const validMethod = ["GET", "POST", "PUT", "PATCH", "HEAD"]; export function UpdatePage({ webhook, onUpdate, onBack }: Props): VNode { @@ -47,13 +49,21 @@ export function UpdatePage({ webhook, onUpdate, onBack }: Props): VNode { const [state, setState] = useState<Partial<Entity>>(webhook); const errors = undefinedIfEmpty<FormErrors<Entity>>({ - event_type: !state.event_type ? i18n.str`Required` : undefined, + event_type: !state.event_type + ? i18n.str`Required` + : validType.indexOf(state.event_type) === -1 + ? i18n.str`Must be one of '${validType.join(", ")}'` + : undefined, http_method: !state.http_method ? i18n.str`Required` - : !validMethod.includes(state.http_method) + : validMethod.indexOf(state.http_method) === -1 ? i18n.str`Must be one of '${validMethod.join(", ")}'` : undefined, - url: !state.url ? i18n.str`Required` : undefined, + url: !state.url + ? i18n.str`Required` + : isInvalidUrl(state.url) + ? i18n.str`URL is invalid` + : undefined, }); const hasErrors = errors !== undefined; @@ -89,14 +99,32 @@ export function UpdatePage({ webhook, onUpdate, onBack }: Props): VNode { valueHandler={setState} errors={errors} > - <Input<Entity> + <InputSelector<Entity> name="event_type" label={i18n.str`Event`} + values={["choose", ...validType]} + toStr={(v) => { + const idx = validType.indexOf(v); + if (idx === -1) return i18n.str`Choose one...`; + return [i18n.str`Pay`, i18n.str`Refund`][idx]; + }} tooltip={i18n.str`The event of the webhook: why the webhook is used`} /> - <Input<Entity> + <InputSelector<Entity> name="http_method" label={i18n.str`Method`} + toStr={(v) => { + const idx = validMethod.indexOf(v); + if (idx === -1) return i18n.str`Choose one...`; + return [ + i18n.str`GET`, + i18n.str`POST`, + i18n.str`PUT`, + i18n.str`PATCH`, + i18n.str`HEAD`, + ][idx]; + }} + values={["choose", ...validMethod]} tooltip={i18n.str`Method used by the webhook`} /> <Input<Entity> @@ -143,3 +171,11 @@ export function UpdatePage({ webhook, onUpdate, onBack }: Props): VNode { </div> ); } +function isInvalidUrl(url: string) { + try { + const asd = new URL("./", url); + return false; + } catch (e) { + return true; + } +} diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx @@ -19,10 +19,13 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { HttpStatusCode, TalerError, TalerMerchantApi, assertUnreachable } from "@gnu-taler/taler-util"; import { - useTranslationContext -} from "@gnu-taler/web-util/browser"; + HttpStatusCode, + TalerError, + TalerMerchantApi, + assertUnreachable, +} from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js"; @@ -30,9 +33,7 @@ import { Loading } from "../../../../components/exception/loading.js"; import { NotificationCard } from "../../../../components/menu/index.js"; import { useSessionContext } from "../../../../context/session.js"; import { WithId } from "../../../../declaration.js"; -import { - useWebhookDetails, -} from "../../../../hooks/webhooks.js"; +import { useWebhookDetails } from "../../../../hooks/webhooks.js"; import { Notification } from "../../../../utils/types.js"; import { LoginPage } from "../../../login/index.js"; import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js"; @@ -66,7 +67,7 @@ export default function UpdateWebhook({ return <NotFoundPageOrAdminCreate />; } case HttpStatusCode.Unauthorized: { - return <LoginPage /> + return <LoginPage />; } default: { assertUnreachable(result); @@ -81,28 +82,29 @@ export default function UpdateWebhook({ webhook={{ ...result.body, id: tid }} onBack={onBack} onUpdate={async (data) => { - return lib.instance.updateWebhook(state.token, tid, data) + return lib.instance + .updateWebhook(state.token, tid, data) .then((resp) => { if (resp.type === "ok") { setNotif({ message: i18n.str`Webhook updated`, type: "SUCCESS", }); - onConfirm() + onConfirm(); } else { setNotif({ message: i18n.str`Could not update webhook`, type: "ERROR", description: resp.detail?.hint, }); - } }) .catch((error) => { setNotif({ message: i18n.str`Could not update webhook`, type: "ERROR", - description: error instanceof Error ? error.message : String(error), + description: + error instanceof Error ? error.message : String(error), }); }); }} diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -2115,7 +2115,7 @@ export class TalerMerchantInstanceHttpClient { headers, }); switch (resp.status) { - case HttpStatusCode.NoContent: + case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForWebhookDetails()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp);