commit 1458e6673a83e82dfae65d71b4fdf863f5967df2
parent 67a835bf2ad101349281bb9d18d1b333f9067ff5
Author: Sebastian <sebasjm@gmail.com>
Date: Mon, 17 Nov 2025 09:59:00 -0300
use default persona from server
Diffstat:
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()))