taler-typescript-core

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

commit 1458e6673a83e82dfae65d71b4fdf863f5967df2
parent 67a835bf2ad101349281bb9d18d1b333f9067ff5
Author: Sebastian <sebasjm@gmail.com>
Date:   Mon, 17 Nov 2025 09:59:00 -0300

use default persona from server

Diffstat:
Mpackages/merchant-backoffice-ui/src/components/menu/SideBar.tsx | 27++++++++++++++++++---------
Mpackages/merchant-backoffice-ui/src/hooks/preference.ts | 28++++++++++++----------------
Mpackages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx | 9+++++----
Mpackages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx | 1+
Mpackages/merchant-backoffice-ui/src/paths/settings/index.tsx | 40++++++++++++++++++++++------------------
Mpackages/taler-util/src/types-taler-merchant.ts | 28++++++++++++++++++++++++++++
6 files changed, 86 insertions(+), 47 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx @@ -22,17 +22,15 @@ import { getMerchantAccountKycStatusSimplified, MerchantAccountKycStatusSimplified, + MerchantPersona, TalerError, } from "@gnu-taler/taler-util"; -import { - useCommonPreferences, - useTranslationContext, -} from "@gnu-taler/web-util/browser"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { useSessionContext } from "../../context/session.js"; import { useInstanceKYCDetailsLongPolling } from "../../hooks/instance.js"; +import { UIElement, usePreference } from "../../hooks/preference.js"; import { LangSelector } from "./LangSelector.js"; -import { Personas, UIElement, usePreference } from "../../hooks/preference.js"; // const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; @@ -402,10 +400,12 @@ const ALL_ELEMENTS = Object.values(UIElement).reduce((prev, ui) => { prev[ui as UIElement] = true; return prev; }, {} as ElementMap); -function getAvailableForPersona(p: Personas): ElementMap { +function getAvailableForPersona(p: MerchantPersona): ElementMap { switch (p) { case "expert": return ALL_ELEMENTS; + case "developer": + return ALL_ELEMENTS; case "offline-vending-machine": return { [UIElement.sidebar_orders]: true, @@ -463,7 +463,10 @@ export function HtmlPersonaFlag<T extends keyof h.JSX.IntrinsicElements>( ): VNode | null { const { htmlElement: el, children, point, ...rest } = props; const [{ persona }] = usePreference(); - const isEnabled = getAvailableForPersona(persona)[point]; + const { config } = useSessionContext(); + const isEnabled = getAvailableForPersona(persona ?? config.default_persona)[ + point + ]; if (isEnabled) return h(el as any, rest as any, children); return null; } @@ -477,7 +480,10 @@ export function ComponentPersonaFlag<FN extends (props: P) => VNode, P>( ): VNode | null { const { children, point, Comp, ...rest } = props; const [{ persona }] = usePreference(); - const isEnabled = getAvailableForPersona(persona)[point]; + const { config } = useSessionContext(); + const isEnabled = getAvailableForPersona(persona ?? config.default_persona)[ + point + ]; const d = rest as any; if (isEnabled) return <Comp {...d}>{children}</Comp>; return null; @@ -490,7 +496,10 @@ export function FragmentPersonaFlag(props: { }): VNode | null { const { children, point } = props; const [{ persona }] = usePreference(); - const isEnabled = getAvailableForPersona(persona)[point]; + const { config } = useSessionContext(); + const isEnabled = getAvailableForPersona(persona ?? config.default_persona)[ + point + ]; if (props.showAnywayIf === true || isEnabled) return <Fragment>{children}</Fragment>; return null; diff --git a/packages/merchant-backoffice-ui/src/hooks/preference.ts b/packages/merchant-backoffice-ui/src/hooks/preference.ts @@ -17,21 +17,15 @@ import { AbsoluteTime, Codec, + MerchantPersona, buildCodecForObject, codecForAbsoluteTime, - codecForBoolean, codecForConstString, codecForEither, + codecOptional, } from "@gnu-taler/taler-util"; import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; -export type Personas = - | "expert" - | "offline-vending-machine" - | "point-of-sale" - | "digital-publishing" - | "e-commerce"; - export enum UIElement { sidebar_orders, sidebar_inventory, @@ -61,14 +55,14 @@ export interface Preferences { hideKycUntil: AbsoluteTime; hideMissingAccountUntil: AbsoluteTime; dateFormat: "ymd" | "dmy" | "mdy"; - persona: Personas; + persona?: MerchantPersona; } const defaultSettings: Preferences = { hideKycUntil: AbsoluteTime.never(), hideMissingAccountUntil: AbsoluteTime.never(), dateFormat: "ymd", - persona: "expert", + persona: undefined, }; export const codecForPreferences = (): Codec<Preferences> => @@ -85,12 +79,14 @@ export const codecForPreferences = (): Codec<Preferences> => ) .property( "persona", - codecForEither( - codecForConstString("expert"), - codecForConstString("offline-vending-machine"), - codecForConstString("point-of-sale"), - codecForConstString("digital-publishing"), - codecForConstString("e-commerce"), + codecOptional( + codecForEither( + codecForConstString("expert"), + codecForConstString("offline-vending-machine"), + codecForConstString("point-of-sale"), + codecForConstString("digital-publishing"), + codecForConstString("e-commerce"), + ), ), ) .build("Preferences"); diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx @@ -45,23 +45,24 @@ function createExample<Props>( config: { currency: "ARS", version: "1", + default_persona: "developer", currencies: { - "ASD": { + ASD: { name: "testkudos", alt_unit_names: {}, num_fractional_input_digits: 1, num_fractional_normal_digits: 1, num_fractional_trailing_zero_digits: 1, - } + }, }, default_wire_transfer_rounding_interval: RoundingInterval.NONE, - default_pay_delay: {d_us:"forever"}, + default_pay_delay: { d_us: "forever" }, have_donau: false, mandatory_tan_channels: [], payment_target_regex: "*", payment_target_types: "*", exchanges: [], - name: "taler-merchant" + name: "taler-merchant", }, hints: [], lib: {} as any, diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx @@ -45,6 +45,7 @@ function createExample<Props>( config: { currency: "ARS", version: "1", + default_persona: "developer", currencies: { ASD: { name: "testkudos", diff --git a/packages/merchant-backoffice-ui/src/paths/settings/index.tsx b/packages/merchant-backoffice-ui/src/paths/settings/index.tsx @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AbsoluteTime } from "@gnu-taler/taler-util"; +import { AbsoluteTime, MerchantPersona } from "@gnu-taler/taler-util"; import { useCommonPreferences, useTranslationContext, @@ -25,32 +25,33 @@ import { FormProvider, } from "../../components/form/FormProvider.js"; import { InputSelector } from "../../components/form/InputSelector.js"; -import { InputToggle } from "../../components/form/InputToggle.js"; import { LangSelector } from "../../components/menu/LangSelector.js"; -import { - Preferences, - Personas, - usePreference, -} from "../../hooks/preference.js"; +import { Preferences, usePreference } from "../../hooks/preference.js"; +import { useSessionContext } from "../../context/session.js"; type FormType = Preferences; export function Settings({ onClose }: { onClose?: () => void }): VNode { const { i18n } = useTranslationContext(); - + const { config } = useSessionContext(); const [value, , updateValue] = usePreference(); const [{ showDebugInfo }, updateCommonPref] = useCommonPreferences(); const errors: FormErrors<FormType> = {}; + const formValue: typeof value = { + ...value, + persona : value.persona ?? config.default_persona + } + function valueHandler(s: (d: Partial<FormType>) => Partial<FormType>): void { - const next = s(value); + const next = s(formValue); const v: Preferences = { hideMissingAccountUntil: next.hideMissingAccountUntil ?? AbsoluteTime.never(), hideKycUntil: next.hideKycUntil ?? AbsoluteTime.never(), dateFormat: next.dateFormat ?? "ymd", - persona: next.persona ?? "expert", + persona: next.persona ?? config.default_persona, }; - const isDeveloper = next.persona === "expert"; + const isDeveloper = next.persona === "developer"; if (isDeveloper !== showDebugInfo) { updateCommonPref("showDebugInfo", isDeveloper); } @@ -68,7 +69,7 @@ export function Settings({ onClose }: { onClose?: () => void }): VNode { <FormProvider<FormType> name="settings" errors={errors} - object={value} + object={formValue} valueHandler={valueHandler} > <div class="field is-horizontal"> @@ -94,11 +95,11 @@ export function Settings({ onClose }: { onClose?: () => void }): VNode { label={i18n.str`Date format`} expand={true} help={ - value.dateFormat === "dmy" + formValue.dateFormat === "dmy" ? "31/12/2001" - : value.dateFormat === "mdy" + : formValue.dateFormat === "mdy" ? "12/31/2001" - : value.dateFormat === "ymd" + : formValue.dateFormat === "ymd" ? "2001/12/31" : "" } @@ -117,17 +118,20 @@ export function Settings({ onClose }: { onClose?: () => void }): VNode { name="persona" values={ [ - "expert", "digital-publishing", "e-commerce", "offline-vending-machine", "point-of-sale", - ] as Personas[] + "expert", + "developer", + ] as MerchantPersona[] } - toStr={(e: Personas) => { + toStr={(e: MerchantPersona) => { switch (e) { case "expert": return i18n.str`Expert user`; + case "developer": + return i18n.str`Taler developer`; case "offline-vending-machine": return i18n.str`Unattended in-person offline vending`; case "point-of-sale": diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts @@ -1188,6 +1188,14 @@ export const codecForMerchantContractDiscountTokenDetails = .property("expected_domains", codecForList(codecForString())) .build("TalerMerchantApi.ContractDiscountTokenDetails"); +export type MerchantPersona = + | "developer" + | "expert" + | "offline-vending-machine" + | "point-of-sale" + | "digital-publishing" + | "e-commerce"; + export interface MerchantVersionResponse { // libtool-style representation of the Merchant protocol version, see // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning @@ -1208,6 +1216,13 @@ export interface MerchantVersionResponse { // supported currencies and how to render them. currency: string; + // Which Persona should be used by default by new clients in the SPA. + // Can be changed locally per browers under "Personalization". + // Possible values include "expert", "offline-vending-machine", + // "point-of-sale", "digital-publishing", "e-commerce" and "developer". + // @since **v23**. + default_persona: MerchantPersona; + // How services should render currencies supported // by this backend. Maps // currency codes (e.g. "EUR" or "KUDOS") to @@ -3769,6 +3784,19 @@ export const codecForTalerMerchantConfigResponse = buildCodecForObject<MerchantVersionResponse>() .property("name", codecForConstString("taler-merchant")) .property("currency", codecForString()) + .property( + "default_persona", + codecOptionalDefault( + codecForEither( + codecForConstString("expert"), + codecForConstString("offline-vending-machine"), + codecForConstString("point-of-sale"), + codecForConstString("digital-publishing"), + codecForConstString("e-commerce"), + ), + "expert", + ), + ) .property("version", codecForString()) .property("currencies", codecForMap(codecForCurrencySpecificiation())) .property("exchanges", codecForList(codecForExchangeConfigInfo()))