taler-typescript-core

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

commit 8c142162591487ca02173c3b9c1ac62205b26e7e
parent 5dabf9f4103562b9315600667792459c0e7ed9c8
Author: Sebastian <sebasjm@gmail.com>
Date:   Sat,  5 Jul 2025 17:39:50 -0300

fix #10142

Diffstat:
Mpackages/merchant-backoffice-ui/src/hooks/instance.ts | 2+-
Mpackages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mpackages/merchant-backoffice-ui/src/paths/instance/templates/use/UsePage.tsx | 13+++++++++++--
3 files changed, 90 insertions(+), 17 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.ts b/packages/merchant-backoffice-ui/src/hooks/instance.ts @@ -220,7 +220,7 @@ function getLongPollingReasonSet( ): KycStatusLongPollingReason | undefined { const acc = data.find((k) => getLongPollingReason(k)); if (!acc) return undefined; - console.log("found reason", acc); + // console.log("found reason", acc); return getLongPollingReason(acc); } diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx @@ -20,6 +20,7 @@ */ import { + AmountLike, AmountString, Amounts, Duration, @@ -45,10 +46,13 @@ import { TextField } from "../../../../components/form/TextField.js"; import { useSessionContext } from "../../../../context/session.js"; import { useInstanceOtpDevices } from "../../../../hooks/otp.js"; import { WithId } from "../../../../declaration.js"; +import { InputWithAddon } from "../../../../components/form/InputWithAddon.js"; +import { NotificationCard } from "../../../../components/menu/index.js"; type Entity = { description?: string; otpId?: string | null; + currency: string; summary?: string; amount?: string; minimum_age?: number; @@ -64,10 +68,41 @@ interface Props { template: TalerMerchantApi.TemplateDetails & WithId; } +function changeToCurrency(am: string | undefined, currency: string | undefined): string | undefined { + if (!am || !currency) return undefined + const amount = Amounts.parse(am); + if (!amount) return undefined; + const newAmount = { + ...amount, + currency, + }; + return Amounts.stringify(newAmount); +} + export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { const { i18n } = useTranslationContext(); const { config, state: session } = useSessionContext(); + const cList = Object.values(config.currencies).map((d) => d.name); + const supportedCurrencies = Object.keys(config.currencies); + + const currentAmount = + template.editable_defaults?.amount ?? + (template.template_contract.amount as AmountString | undefined); + + const default_currency = supportedCurrencies[0]; + + const template_currency = currentAmount + ? Amounts.currencyOf(currentAmount) + : default_currency; + + const unsupportedCurrency = + supportedCurrencies.indexOf(template_currency) === -1; + + const startingAmount = unsupportedCurrency + ? changeToCurrency(currentAmount, default_currency) + : currentAmount + const [state, setState] = useState<Partial<Entity>>({ description: template.template_description, minimum_age: template.template_contract.minimum_age, @@ -79,9 +114,8 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { : undefined, summary: template.editable_defaults?.summary ?? template.template_contract.summary, - amount: - template.editable_defaults?.amount ?? - (template.template_contract.amount as AmountString | undefined), + currency: unsupportedCurrency ? default_currency : template_currency, + amount: startingAmount, currency_editable: !!template.editable_defaults?.currency, summary_editable: template.editable_defaults?.summary !== undefined, amount_editable: template.editable_defaults?.amount !== undefined, @@ -93,6 +127,7 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { if (!newState.amount_editable) { newState.currency_editable = false; } + newState.amount = changeToCurrency(newState.amount, newState.currency) return newState; }); } @@ -138,13 +173,13 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { : undefined, }; - const cList = Object.values(config.currencies).map((d) => d.name); - const hasErrors = Object.keys(errors).some( (k) => (errors as Record<string, unknown>)[k] !== undefined, ); - const zero = Amounts.stringify(Amounts.zeroOfCurrency(config.currency)); + const zero = Amounts.stringify( + Amounts.zeroOfCurrency(state.currency ?? default_currency), + ); const submitForm = () => { if (hasErrors) return Promise.reject(); @@ -160,7 +195,7 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { currency: cList.length > 1 && state.currency_editable ? undefined - : config.currency, + : state.currency, }; return onUpdate({ template_description: state.description!, @@ -171,7 +206,7 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { currency: cList.length === 1 || !state.currency_editable ? undefined - : config.currency, + : state.currency, }, otp_id: state.otpId!, }); @@ -199,6 +234,15 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { </div> </section> <hr /> + {unsupportedCurrency ? ( + <NotificationCard + notification={{ + message: i18n.str`The template configuration needs to be fixed.`, + description: i18n.str`The currency of the template is ${template_currency} and is not in the list of supported currencies.`, + type: "WARN", + }} + /> + ) : undefined} <section class="section is-main-section"> <div class="columns"> @@ -225,9 +269,31 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { label={i18n.str`Summary is editable`} tooltip={i18n.str`Allow the user to change the summary.`} /> - <InputCurrency<Entity> + {supportedCurrencies.length > 1 ? ( + <InputSelector + name="currency" + label={i18n.str`Supported currencies`} + values={supportedCurrencies} + // toStr={(str) => { + // if (str === "none") return i18n.str`Without authentication`; + // if (str === "basic") + // return i18n.str`With username and password`; + // if (str === "bearer") return i18n.str`With token`; + // return i18n.str`Do not change`; + // }} + /> + ) : undefined} + + <InputWithAddon<Entity> name="amount" label={i18n.str`Amount`} + addonBefore={state.currency} + inputType="number" + toStr={(v?: AmountString) => v?.split(":")[1] || ""} + fromStr={(v: string) => + !v ? undefined : `${state.currency}:${v}` + } + inputExtra={{ min: 0, step: 0.001 }} tooltip={i18n.str`If specified, this template will create orders with the same price`} /> <InputToggle<Entity> @@ -243,13 +309,11 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { label={i18n.str`Currency is editable`} tooltip={i18n.str`Allow the user to change currency.`} /> - <TextField name="sc" label={i18n.str`Supported currencies`}> - <i18n.Translate> - Supported currencies: {cList.join(", ")} - </i18n.Translate> - </TextField> </Fragment> )} + {/* <TextField name="sc" label={i18n.str`Supported currencies`}> + <i18n.Translate>{cList.join(", ")}</i18n.Translate> + </TextField> */} <InputNumber<Entity> name="minimum_age" label={i18n.str`Minimum age`} diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/use/UsePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/use/UsePage.tsx @@ -30,6 +30,7 @@ import { } from "../../../../components/form/FormProvider.js"; import { Input } from "../../../../components/form/Input.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js"; +import { InputWithAddon } from "../../../../components/form/InputWithAddon.js"; type Entity = TalerMerchantApi.TemplateContractDetails; @@ -103,12 +104,20 @@ export function UsePage({ id, template, onCreateOrder, onBack }: Props): VNode { valueHandler={setState} errors={errors} > - <InputCurrency<Entity> + <InputWithAddon<Entity> name="amount" label={i18n.str`Amount`} - readonly={!!template.template_contract.amount} + addonBefore={state.currency} + inputType="number" + toStr={(v?: AmountString) => v?.split(":")[1] || ""} + fromStr={(v: string) => + !v ? undefined : `${state.currency}:${v}` + } + inputExtra={{ min: 0, step: 0.001 }} tooltip={i18n.str`Amount of the order`} + readonly={!!template.template_contract.amount} /> + <Input<Entity> name="summary" inputType="multiline"