commit 82992808c2a277aa7f64147dd3560e9a5bbe3337
parent c86546a4e2fbdf9494115766a1278bb8954fb451
Author: Sebastian <sebasjm@gmail.com>
Date: Sun, 16 Nov 2025 16:41:57 -0300
fix #10581
Diffstat:
6 files changed, 162 insertions(+), 184 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx b/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx
@@ -30,14 +30,14 @@ import { InputImage } from "../form/InputImage.js";
import { InputLocation } from "../form/InputLocation.js";
import { InputToggle } from "../form/InputToggle.js";
import { InputWithAddon } from "../form/InputWithAddon.js";
+import { ComponentPersonaFlag, FragmentPersonaFlag } from "../menu/SideBar.js";
+import { UIElement } from "../../hooks/preference.js";
export function DefaultInstanceFormFields({
readonlyId,
showId,
- showLessFields,
}: {
readonlyId?: boolean;
- showLessFields?: boolean;
showId: boolean;
}): VNode {
const { i18n } = useTranslationContext();
@@ -60,67 +60,65 @@ export function DefaultInstanceFormFields({
tooltip={i18n.str`Legal name of the business represented by this instance.`}
/>
- {showLessFields ? undefined : (
- <Fragment>
- <Input<Entity>
- name="email"
- label={i18n.str`Email`}
- tooltip={i18n.str`Contact email`}
- />
-
- <Input<Entity>
- name="phone_number"
- label={i18n.str`Phone number`}
- tooltip={i18n.str`Contact phone`}
- />
-
- <Input<Entity>
- name="website"
- label={i18n.str`Website URL`}
- tooltip={i18n.str`URL.`}
- />
-
- <InputImage<Entity>
- name="logo"
- label={i18n.str`Logo`}
- tooltip={i18n.str`Logo image.`}
- />
-
- <InputGroup
- name="address"
- label={i18n.str`Address`}
- tooltip={i18n.str`Physical location of the merchant.`}
- >
- <InputLocation name="address" />
- </InputGroup>
-
- <InputGroup
- name="jurisdiction"
- label={i18n.str`Jurisdiction`}
- tooltip={i18n.str`Jurisdiction for legal disputes with the merchant.`}
- >
- <InputLocation name="jurisdiction" />
- </InputGroup>
-
- <InputToggle<Entity>
- name="use_stefan"
- label={i18n.str`Pay transaction fee`}
- tooltip={i18n.str`Cover the transaction cost or pass it on to the user.`}
- />
-
- <InputDuration<Entity>
- name="default_pay_delay"
- label={i18n.str`Default payment delay`}
- tooltip={i18n.str`Time customers have to pay an order before the offer expires by default.`}
- />
-
- <InputDuration<Entity>
- name="default_wire_transfer_delay"
- label={i18n.str`Default wire transfer delay`}
- tooltip={i18n.str`Maximum time an exchange is allowed to delay wiring funds to the merchant, enabling it to aggregate smaller payments into larger wire transfers and reducing wire fees.`}
- />
- </Fragment>
- )}
+ <FragmentPersonaFlag point={UIElement.option_advanceInstanceSettings}>
+ <Input<Entity>
+ name="email"
+ label={i18n.str`Email`}
+ tooltip={i18n.str`Contact email`}
+ />
+
+ <Input<Entity>
+ name="phone_number"
+ label={i18n.str`Phone number`}
+ tooltip={i18n.str`Contact phone`}
+ />
+
+ <Input<Entity>
+ name="website"
+ label={i18n.str`Website URL`}
+ tooltip={i18n.str`URL.`}
+ />
+
+ <InputImage<Entity>
+ name="logo"
+ label={i18n.str`Logo`}
+ tooltip={i18n.str`Logo image.`}
+ />
+
+ <InputGroup
+ name="address"
+ label={i18n.str`Address`}
+ tooltip={i18n.str`Physical location of the merchant.`}
+ >
+ <InputLocation name="address" />
+ </InputGroup>
+
+ <InputGroup
+ name="jurisdiction"
+ label={i18n.str`Jurisdiction`}
+ tooltip={i18n.str`Jurisdiction for legal disputes with the merchant.`}
+ >
+ <InputLocation name="jurisdiction" />
+ </InputGroup>
+
+ <InputToggle<Entity>
+ name="use_stefan"
+ label={i18n.str`Pay transaction fee`}
+ tooltip={i18n.str`Cover the transaction cost or pass it on to the user.`}
+ />
+
+ <InputDuration<Entity>
+ name="default_pay_delay"
+ label={i18n.str`Default payment delay`}
+ tooltip={i18n.str`Time customers have to pay an order before the offer expires by default.`}
+ />
+
+ <InputDuration<Entity>
+ name="default_wire_transfer_delay"
+ label={i18n.str`Default wire transfer delay`}
+ tooltip={i18n.str`Maximum time an exchange is allowed to delay wiring funds to the merchant, enabling it to aggregate smaller payments into larger wire transfers and reducing wire fees.`}
+ />
+ </FragmentPersonaFlag>
</Fragment>
);
}
diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
@@ -61,7 +61,9 @@ export function Sidebar({ mobile }: Props): VNode {
}, MerchantAccountKycStatusSimplified.OK);
const [{ persona }] = usePreference();
- const hideKycMenuItem = simplifiedKycStatus === MerchantAccountKycStatusSimplified.OK && persona !== "expert"
+ const hideKycMenuItem =
+ simplifiedKycStatus === MerchantAccountKycStatusSimplified.OK &&
+ persona !== "expert";
const isLoggedIn = state.status === "loggedIn";
const hasToken = isLoggedIn && state.token !== undefined;
@@ -172,7 +174,7 @@ export function Sidebar({ mobile }: Props): VNode {
<i class="mdi mdi-clock" />
</span>
<span class="menu-item-label">
- <i18n.Translate>Token Families</i18n.Translate>
+ <i18n.Translate>Subscriptions and Discounts</i18n.Translate>
</span>
</a>
</HtmlPersonaFlag>
@@ -322,7 +324,7 @@ export function Sidebar({ mobile }: Props): VNode {
<i class="mdi mdi-newspaper" />
</span>
<span class="menu-item-label">
- <i18n.Translate>Interface</i18n.Translate>
+ <i18n.Translate>Personalization</i18n.Translate>
</span>
</a>
</li>
@@ -466,15 +468,30 @@ export function HtmlPersonaFlag<T extends keyof h.JSX.IntrinsicElements>(
return null;
}
-export function ComponentPersonaFlag<FN extends (props:P) => VNode, P>(props: {
- Comp: FN,
- point: UIElement;
- children: ComponentChildren;
-} & P): VNode | null {
+export function ComponentPersonaFlag<FN extends (props: P) => VNode, P>(
+ props: {
+ Comp: FN;
+ point: UIElement;
+ children: ComponentChildren;
+ } & P,
+): VNode | null {
const { children, point, Comp, ...rest } = props;
const [{ persona }] = usePreference();
const isEnabled = getAvailableForPersona(persona)[point];
- const d = rest as any
+ const d = rest as any;
if (isEnabled) return <Comp {...d}>{children}</Comp>;
return null;
}
+
+export function FragmentPersonaFlag(props: {
+ point: UIElement;
+ showAnywayIf?: boolean;
+ children: ComponentChildren;
+}): VNode | null {
+ const { children, point } = props;
+ const [{ persona }] = usePreference();
+ const isEnabled = getAvailableForPersona(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
@@ -53,11 +53,11 @@ export enum UIElement {
// sidebar_instanceList,
action_manuallyCreatingOrders,
option_otpDevicesOnTemplate,
+ option_advanceOrderCreation,
+ option_advanceInstanceSettings,
}
export interface Preferences {
- advanceOrderMode: boolean;
- advanceInstanceMode: boolean;
hideKycUntil: AbsoluteTime;
hideMissingAccountUntil: AbsoluteTime;
dateFormat: "ymd" | "dmy" | "mdy";
@@ -65,8 +65,6 @@ export interface Preferences {
}
const defaultSettings: Preferences = {
- advanceOrderMode: false,
- advanceInstanceMode: false,
hideKycUntil: AbsoluteTime.never(),
hideMissingAccountUntil: AbsoluteTime.never(),
dateFormat: "ymd",
@@ -75,8 +73,6 @@ const defaultSettings: Preferences = {
export const codecForPreferences = (): Codec<Preferences> =>
buildCodecForObject<Preferences>()
- .property("advanceOrderMode", codecForBoolean())
- .property("advanceInstanceMode", codecForBoolean())
.property("hideKycUntil", codecForAbsoluteTime)
.property("hideMissingAccountUntil", codecForAbsoluteTime)
.property(
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx
@@ -217,34 +217,6 @@ export function CreatePage({ onConfirm, onBack, forceId }: Props): VNode {
<div>
<LocalNotificationBannerBulma notification={notification} />
<section class="section is-main-section">
- <div class="tabs is-toggle is-fullwidth is-small">
- <ul>
- <li
- class={!pref.advanceInstanceMode ? "is-active" : ""}
- onClick={() => {
- updatePref("advanceInstanceMode", false);
- }}
- >
- <a>
- <span>
- <i18n.Translate>Simple</i18n.Translate>
- </span>
- </a>
- </li>
- <li
- class={pref.advanceInstanceMode ? "is-active" : ""}
- onClick={() => {
- updatePref("advanceInstanceMode", true);
- }}
- >
- <a>
- <span>
- <i18n.Translate>Advanced</i18n.Translate>
- </span>
- </a>
- </li>
- </ul>
- </div>{" "}
<div class="columns">
<div class="column" />
<div class="column is-four-fifths">
@@ -256,7 +228,6 @@ export function CreatePage({ onConfirm, onBack, forceId }: Props): VNode {
<DefaultInstanceFormFields
readonlyId={!!forceId}
showId={!forceId}
- showLessFields={!pref.advanceInstanceMode}
/>
<Input<Entity>
name="password"
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
@@ -55,9 +55,10 @@ import { NonInventoryProductFrom } from "../../../../components/product/NonInven
import { ProductList } from "../../../../components/product/ProductList.js";
import { useSessionContext } from "../../../../context/session.js";
import { WithId } from "../../../../declaration.js";
-import { usePreference } from "../../../../hooks/preference.js";
+import { UIElement, usePreference } from "../../../../hooks/preference.js";
import { rate } from "../../../../utils/amount.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
+import { FragmentPersonaFlag } from "../../../../components/menu/SideBar.js";
export interface Props {
onCreated: (id: string) => void;
@@ -414,34 +415,6 @@ export function CreatePage({
<div>
<LocalNotificationBannerBulma notification={notification} />
<section class="section is-main-section">
- <div class="tabs is-toggle is-fullwidth is-small">
- <ul>
- <li
- class={!pref.advanceOrderMode ? "is-active" : ""}
- onClick={() => {
- updatePrefs("advanceOrderMode", false);
- }}
- >
- <a>
- <span>
- <i18n.Translate>Simple</i18n.Translate>
- </span>
- </a>
- </li>
- <li
- class={pref.advanceOrderMode ? "is-active" : ""}
- onClick={() => {
- updatePrefs("advanceOrderMode", true);
- }}
- >
- <a>
- <span>
- <i18n.Translate>Advanced</i18n.Translate>
- </span>
- </a>
- </li>
- </ul>
- </div>
<div class="columns">
<div class="column" />
<div class="column is-four-fifths">
@@ -467,7 +440,9 @@ export function CreatePage({
inventory={instanceInventory}
/>
- {pref.advanceOrderMode && (
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ >
<NonInventoryProductFrom
productToEdit={editingProduct}
onAddProduct={(p) => {
@@ -475,7 +450,7 @@ export function CreatePage({
return addNewProduct(p);
}}
/>
- )}
+ </FragmentPersonaFlag>
{allProducts.length > 0 && (
<ProductList
@@ -538,7 +513,10 @@ export function CreatePage({
tooltip={i18n.str`Title of the order to be shown to the customer`}
/>
- {(pref.advanceOrderMode || errors?.shipping) && (
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ showAnywayIf={errors?.shipping !== undefined}
+ >
<InputGroup
name="shipping"
label={i18n.str`Shipping and fulfillment`}
@@ -567,19 +545,26 @@ export function CreatePage({
tooltip={i18n.str`Message shown to the customer after paying for the order.`}
/>
</InputGroup>
- )}
+ </FragmentPersonaFlag>
- {(pref.advanceOrderMode ||
- requiresSomeTalerOptions ||
- errors?.payments) && (
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ showAnywayIf={
+ requiresSomeTalerOptions || errors?.payments !== undefined
+ }
+ >
<InputGroup
name="payments"
label={i18n.str`Taler payment options`}
tooltip={i18n.str`Override default Taler payment settings for this order`}
>
- {(pref.advanceOrderMode ||
- noDefault_payDeadline ||
- errors?.payments?.pay_deadline !== undefined) && (
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ showAnywayIf={
+ noDefault_payDeadline ||
+ errors?.payments?.pay_deadline !== undefined
+ }
+ >
<InputDuration
name="payments.pay_deadline"
label={i18n.str`Payment time`}
@@ -611,9 +596,13 @@ export function CreatePage({
</span>
}
/>
- )}
- {(pref.advanceOrderMode ||
- errors?.payments?.refund_deadline !== undefined) && (
+ </FragmentPersonaFlag>
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ showAnywayIf={
+ errors?.payments?.refund_deadline !== undefined
+ }
+ >
<InputDuration
name="payments.refund_deadline"
label={i18n.str`Refund time`}
@@ -646,10 +635,15 @@ export function CreatePage({
</span>
}
/>
- )}
- {(pref.advanceOrderMode ||
- noDefault_wireDeadline ||
- errors?.payments?.wire_transfer_deadline !== undefined) && (
+ </FragmentPersonaFlag>
+
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ showAnywayIf={
+ noDefault_wireDeadline ||
+ errors?.payments?.wire_transfer_deadline !== undefined
+ }
+ >
<InputDuration
name="payments.wire_transfer_deadline"
label={i18n.str`Wire transfer time`}
@@ -683,9 +677,13 @@ export function CreatePage({
</span>
}
/>
- )}
- {(pref.advanceOrderMode ||
- errors?.payments?.auto_refund_deadline !== undefined) && (
+ </FragmentPersonaFlag>
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ showAnywayIf={
+ errors?.payments?.auto_refund_deadline !== undefined
+ }
+ >
<InputDuration
name="payments.auto_refund_deadline"
label={i18n.str`Auto-refund time`}
@@ -697,26 +695,31 @@ export function CreatePage({
tooltip={i18n.str`Time until which the wallet will automatically check for refunds without user interaction.`}
withForever
/>
- )}
-
- {(pref.advanceOrderMode ||
- errors?.payments?.max_fee !== undefined) && (
+ </FragmentPersonaFlag>
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ showAnywayIf={errors?.payments?.max_fee !== undefined}
+ >
<InputCurrency
name="payments.max_fee"
label={i18n.str`Maximum fee`}
tooltip={i18n.str`Maximum fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`}
/>
- )}
- {(pref.advanceOrderMode ||
- errors?.payments?.createToken !== undefined) && (
+ </FragmentPersonaFlag>
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ showAnywayIf={errors?.payments?.createToken !== undefined}
+ >
<InputToggle
name="payments.createToken"
label={i18n.str`Create token`}
tooltip={i18n.str`If the order ID is easy to guess, the token will prevent other users from claiming the order.`}
/>
- )}
- {(pref.advanceOrderMode ||
- errors?.payments?.minimum_age !== undefined) && (
+ </FragmentPersonaFlag>
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ showAnywayIf={errors?.payments?.minimum_age !== undefined}
+ >
<InputNumber
name="payments.minimum_age"
label={i18n.str`Minimum age required`}
@@ -727,11 +730,16 @@ export function CreatePage({
: i18n.str`No product with age restriction in this order`
}
/>
- )}
+ </FragmentPersonaFlag>
</InputGroup>
- )}
+ </FragmentPersonaFlag>
- {(pref.advanceOrderMode || errors?.extra !== undefined) && (
+ <FragmentPersonaFlag
+ point={UIElement.option_advanceOrderCreation}
+ showAnywayIf={
+ requiresSomeTalerOptions || errors?.extra !== undefined
+ }
+ >
<InputGroup
name="extra"
label={i18n.str`Additional information`}
@@ -810,7 +818,7 @@ export function CreatePage({
</button>
</div>
</InputGroup>
- )}
+ </FragmentPersonaFlag>
</FormProvider>
<div class="buttons is-right mt-5">
diff --git a/packages/merchant-backoffice-ui/src/paths/settings/index.tsx b/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
@@ -44,8 +44,6 @@ export function Settings({ onClose }: { onClose?: () => void }): VNode {
function valueHandler(s: (d: Partial<FormType>) => Partial<FormType>): void {
const next = s(value);
const v: Preferences = {
- advanceOrderMode: next.advanceOrderMode ?? false,
- advanceInstanceMode: next.advanceInstanceMode ?? false,
hideMissingAccountUntil:
next.hideMissingAccountUntil ?? AbsoluteTime.never(),
hideKycUntil: next.hideKycUntil ?? AbsoluteTime.never(),
@@ -91,16 +89,6 @@ export function Settings({ onClose }: { onClose?: () => void }): VNode {
<LangSelector />
</div>
</div>
- <InputToggle<Preferences>
- label={i18n.str`Advance order creation`}
- tooltip={i18n.str`Shows more options in the order creation form`}
- name="advanceOrderMode"
- />
- <InputToggle<Preferences>
- label={i18n.str`Advance instance settings`}
- tooltip={i18n.str`Shows more options in the instance settings form`}
- name="advanceInstanceMode"
- />
<InputSelector<Preferences>
name="dateFormat"
label={i18n.str`Date format`}
@@ -141,9 +129,9 @@ export function Settings({ onClose }: { onClose?: () => void }): VNode {
case "expert":
return i18n.str`Expert user`;
case "offline-vending-machine":
- return i18n.str`Offline venfing machine`;
+ return i18n.str`Unattended in-person offline vending`;
case "point-of-sale":
- return i18n.str`In-person point of sale.`;
+ return i18n.str`In-person online point-of-sale with inventory`;
case "digital-publishing":
return i18n.str`Digital publishing`;
case "e-commerce":