/* This file is part of GNU Taler (C) 2021-2024 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ /** * * @author Sebastian Javier Marchano (sebasjm) */ import { AmountString, Amounts, Duration, TalerError, TalerMerchantApi, TranslatedString, } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { FormErrors, FormProvider, } from "../../../../components/form/FormProvider.js"; import { Input } from "../../../../components/form/Input.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputDuration } from "../../../../components/form/InputDuration.js"; import { InputNumber } from "../../../../components/form/InputNumber.js"; import { InputSelector } from "../../../../components/form/InputSelector.js"; import { InputToggle } from "../../../../components/form/InputToggle.js"; import { TextField } from "../../../../components/form/TextField.js"; import { useSessionContext } from "../../../../context/session.js"; import { useInstanceOtpDevices } from "../../../../hooks/otp.js"; type Entity = { description?: string; otpId?: string | null; summary?: string; amount?: AmountString; minimum_age?: number; pay_duration?: Duration; summary_editable?: boolean; amount_editable?: boolean; currency_editable?: boolean; }; interface Props { onUpdate: (d: TalerMerchantApi.TemplatePatchDetails) => Promise; onBack?: () => void; template: TalerMerchantApi.TemplateDetails & WithId; } export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { const { i18n } = useTranslationContext(); const { config } = useSessionContext(); const {state:session} = useSessionContext(); const [state, setState] = useState>({ description: template.template_description, minimum_age: template.template_contract.minimum_age, otpId: template.otp_id, pay_duration: template.template_contract.pay_duration ? Duration.fromTalerProtocolDuration( template.template_contract.pay_duration, ) : undefined, summary: template.editable_defaults?.summary ?? template.template_contract.summary, amount: template.editable_defaults?.amount ?? (template.template_contract.amount as AmountString | undefined), currency_editable: !!template.editable_defaults?.currency, summary_editable: !!template.editable_defaults?.summary, amount_editable: !!template.editable_defaults?.amount, }); function updateState(up: (s: Partial) => Partial) { setState((old) => { const newState = up(old); if (!newState.amount_editable) { newState.currency_editable = false; } return newState; }); } const devices = useInstanceOtpDevices(); const deviceList = !devices || devices instanceof TalerError || devices.type === "fail" ? [] : devices.body.otp_devices; const deviceMap = deviceList.reduce( (prev, cur) => { prev[cur.otp_device_id] = cur.device_description as TranslatedString; return prev; }, {} as Record, ); const parsedPrice = !state.amount ? undefined : Amounts.parse(state.amount); const errors: FormErrors = { description: !state.description ? i18n.str`should not be empty` : undefined, amount: !state.amount ? undefined : !parsedPrice ? i18n.str`not valid` : Amounts.isZero(parsedPrice) ? i18n.str`must be greater than 0` : undefined, minimum_age: state.minimum_age && state.minimum_age < 0 ? i18n.str`should be greater that 0` : undefined, pay_duration: !state.pay_duration ? i18n.str`can't be empty` : state.pay_duration.d_ms === "forever" ? undefined : state.pay_duration.d_ms < 1000 // less than one second ? i18n.str`to short` : undefined, }; const cList = Object.values(config.currencies).map((d) => d.name); const hasErrors = Object.keys(errors).some( (k) => (errors as Record)[k] !== undefined, ); const submitForm = () => { if (hasErrors) return Promise.reject(); return onUpdate({ template_description: state.description!, template_contract: { minimum_age: state.minimum_age!, pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!), amount: state.amount_editable ? undefined : state.amount, summary: state.summary_editable ? undefined : state.summary, currency: cList.length > 1 && state.currency_editable ? undefined : config.currency, }, editable_defaults: { amount: !state.amount_editable ? undefined : state.amount, summary: !state.summary_editable ? undefined : state.summary, currency: cList.length === 1 || !state.currency_editable ? undefined : config.currency, }, otp_id: state.otpId!, }); }; return (
{new URL(`templates/${template.id}`, session.backendUrl.href).href}

name="description" label={i18n.str`Description`} help="" tooltip={i18n.str`Describe what this template stands for`} /> name="summary" inputType="multiline" label={i18n.str`Summary`} tooltip={i18n.str`If specified, this template will create order with the same summary`} /> name="summary_editable" label={i18n.str`Summary is editable`} tooltip={i18n.str`Allow the user to change the summary.`} /> name="amount" label={i18n.str`Amount`} tooltip={i18n.str`If specified, this template will create order with the same price`} /> name="amount_editable" label={i18n.str`Amount is editable`} tooltip={i18n.str`Allow the user to select the amount to pay.`} /> {cList.length > 1 && ( name="currency_editable" readonly={!state.amount_editable} label={i18n.str`Currency is editable`} tooltip={i18n.str`Allow the user to change currency.`} /> supported currencies: {cList.join(", ")} )} name="minimum_age" label={i18n.str`Minimum age`} help="" tooltip={i18n.str`Is this contract restricted to some age?`} /> name="pay_duration" label={i18n.str`Payment timeout`} help="" tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`} /> {!deviceList.length ? ( No OTP device.  Add one first ) : ( name="otpId" label={i18n.str`OTP device`} values={[ undefined, ...deviceList.map((e) => e.otp_device_id), ]} toStr={(v?: string) => { if (!v) { return i18n.str`No device`; } return deviceMap[v]; }} tooltip={i18n.str`Use to verify transaction in offline mode.`} /> )}
{onBack && ( )} Confirm
); }