taler-typescript-core

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

commit 601614be93f061bb37c57c5d5fcbcdd65f7c4232
parent 22c2c530845aaebd88a56913397923cc05ea818a
Author: Iván Ávalos <avalos@disroot.org>
Date:   Wed, 30 Jul 2025 16:35:23 +0200

backoffice-ui: fix token family management

Diffstat:
Mpackages/merchant-backoffice-ui/src/components/form/InputDuration.tsx | 9++++++++-
Mpackages/merchant-backoffice-ui/src/components/tokenfamily/TokenFamilyForm.tsx | 11++++++++++-
Mpackages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/UpdatePage.tsx | 24++++--------------------
Mpackages/taler-util/src/http-client/merchant.ts | 8+++++++-
Mpackages/taler-util/src/types-taler-merchant.ts | 6+++---
5 files changed, 32 insertions(+), 26 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx b/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx @@ -33,6 +33,7 @@ export interface Props<T> extends InputProps<T> { withForever?: boolean; side?: ComponentChildren; withoutClear?: boolean; + useProtocolDuration?: boolean; } export function InputDuration<T>({ @@ -46,6 +47,7 @@ export function InputDuration<T>({ withForever, withoutClear, side, + useProtocolDuration, }: Props<keyof T>): VNode { const [opened, setOpened] = useState(false); const { i18n } = useTranslationContext(); @@ -188,7 +190,12 @@ export function InputDuration<T>({ minutes value={!value || value.d_ms === "forever" ? 0 : value.d_ms} onChange={(v) => { - onChange({ d_ms: v } as any); + let duration: any = { d_ms: v}; + if (useProtocolDuration === true) { + onChange(Duration.toTalerProtocolDuration(duration) as any); + } else { + onChange(duration); + } }} /> </SimpleModal> diff --git a/packages/merchant-backoffice-ui/src/components/tokenfamily/TokenFamilyForm.tsx b/packages/merchant-backoffice-ui/src/components/tokenfamily/TokenFamilyForm.tsx @@ -24,7 +24,7 @@ import { Duration, TalerMerchantApi } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { InputSelectOne, useTranslationContext } from "@gnu-taler/web-util/browser"; import { h } from "preact"; import { useCallback, useEffect, useState } from "preact/hooks"; import { useSessionContext } from "../../context/session.js"; @@ -53,6 +53,7 @@ export function TokenFamilyForm({ onSubscribe }: Props) { duration: Duration.toTalerProtocolDuration(Duration.getForever()), valid_after: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.never()), valid_before: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.never()), + validity_granularity: Duration.toTalerProtocolDuration(Duration.getForever()), }); const { i18n } = useTranslationContext(); @@ -63,6 +64,7 @@ export function TokenFamilyForm({ onSubscribe }: Props) { description: !value.description ? i18n.str`Required` : undefined, valid_after: !value.valid_after ? undefined : undefined, valid_before: !value.valid_before ? i18n.str`Required` : undefined, + validity_granularity: !value.validity_granularity ? i18n.str`Required` : undefined, duration: !value.duration ? i18n.str`Required` : undefined, kind: !value.kind ? i18n.str`Required` : undefined, }; @@ -127,10 +129,17 @@ export function TokenFamilyForm({ onSubscribe }: Props) { withTimestampSupport /> <InputDuration<Entity> + name="validity_granularity" + label={i18n.str`Validity Granularity`} + tooltip={i18n.str`Rounding granularity for the start validity of keys, must be 1 minute, 1 hour, 1 day, 1 week, 30 days or 90 days`} + useProtocolDuration + /> + <InputDuration<Entity> name="duration" label={i18n.str`Duration`} tooltip={i18n.str`Validity duration of a issued token`} withForever + useProtocolDuration /> </FormProvider> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/UpdatePage.tsx @@ -30,9 +30,7 @@ import { InputDate } from "../../../../components/form/InputDate.js"; import { InputDuration } from "../../../../components/form/InputDuration.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; -type Entity = Omit<TalerMerchantApi.TokenFamilyUpdateRequest, "duration"> & { - duration: Duration, -}; +type Entity = TalerMerchantApi.TokenFamilyUpdateRequest; interface Props { onUpdate: (d: TalerMerchantApi.TokenFamilyUpdateRequest) => Promise<void>; @@ -40,17 +38,8 @@ interface Props { tokenFamily: TalerMerchantApi.TokenFamilyUpdateRequest; } -function convert(from: TalerMerchantApi.TokenFamilyUpdateRequest) { - const { duration, ...rest } = from; - - const converted = { - duration: Duration.fromTalerProtocolDuration(duration), - }; - return { ...converted, ...rest }; -} - export function UpdatePage({ onUpdate, onBack, tokenFamily }: Props) { - const [value, valueHandler] = useState<Partial<Entity>>(convert(tokenFamily)); + const [value, valueHandler] = useState<Partial<Entity>>(tokenFamily); const { i18n } = useTranslationContext(); const errors = undefinedIfEmpty<FormErrors<Entity>>({ name: !value.name ? i18n.str`Required` : undefined, @@ -65,13 +54,7 @@ export function UpdatePage({ onUpdate, onBack, tokenFamily }: Props) { const submitForm = () => { if (hasErrors) return Promise.reject(); - const { duration, ...rest } = value as Required<Entity>; - const result: TalerMerchantApi.TokenFamilyUpdateRequest = { - ...rest, - duration: Duration.toTalerProtocolDuration(duration), - }; - - return onUpdate(result); + return onUpdate(value as Entity); } @@ -131,6 +114,7 @@ export function UpdatePage({ onUpdate, onBack, tokenFamily }: Props) { label={i18n.str`Duration`} tooltip={i18n.str`Validity duration of a issued token`} withForever + useProtocolDuration /> </FormProvider> diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -2341,11 +2341,17 @@ export class TalerMerchantInstanceHttpClient { headers.Authorization = makeBearerTokenAuthHeader(token); } const resp = await this.httpLib.fetch(url.href, { - method: "POST", + method: "PATCH", body, headers, }); switch (resp.status) { + case HttpStatusCode.NoContent: { + this.cacheEvictor.notifySuccess( + TalerMerchantInstanceCacheEviction.UPDATE_TOKENFAMILY, + ); + return opEmptySuccess(); + } case HttpStatusCode.Ok: { this.cacheEvictor.notifySuccess( TalerMerchantInstanceCacheEviction.UPDATE_TOKENFAMILY, diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts @@ -3136,8 +3136,8 @@ export interface TokenFamilyDetails { // How many tokens have been issued for this family. issued: Integer; - // How many tokens have been redeemed for this family. - redeemed: Integer; + // How many tokens have been used for this family. + used: Integer; } export interface OrderChoice { @@ -4260,7 +4260,7 @@ export const codecForTokenFamilyDetails = (): Codec<TokenFamilyDetails> => .property("duration", codecForDuration) .property("kind", codecForTokenFamilyKind) .property("issued", codecForNumber()) - .property("redeemed", codecForNumber()) + .property("used", codecForNumber()) .build("TalerMerchantApi.TokenFamilyDetails"); export const codecForTokenFamiliesList = (): Codec<TokenFamiliesList> =>