taler-typescript-core

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

commit 82992808c2a277aa7f64147dd3560e9a5bbe3337
parent c86546a4e2fbdf9494115766a1278bb8954fb451
Author: Sebastian <sebasjm@gmail.com>
Date:   Sun, 16 Nov 2025 16:41:57 -0300

fix #10581

Diffstat:
Mpackages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx | 124+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mpackages/merchant-backoffice-ui/src/components/menu/SideBar.tsx | 35++++++++++++++++++++++++++---------
Mpackages/merchant-backoffice-ui/src/hooks/preference.ts | 8++------
Mpackages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx | 29-----------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx | 134++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mpackages/merchant-backoffice-ui/src/paths/settings/index.tsx | 16++--------------
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":