/* 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, assertUnreachable, } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { 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 { InputSearchOnList } from "../../../../components/form/InputSearchOnList.js"; import { InputTab } from "../../../../components/form/InputTab.js"; import { InputWithAddon } from "../../../../components/form/InputWithAddon.js"; import { useSessionContext } from "../../../../context/session.js"; import { useInstanceOtpDevices } from "../../../../hooks/otp.js"; enum Steps { BOTH_FIXED, FIXED_PRICE, FIXED_SUMMARY, NON_FIXED, } // type Entity = TalerMerchantApi.TemplateAddDetails & { type: Steps }; type Entity = { id?: string; description?: string; otpId?: string; summary?: string; amount?: AmountString; minimum_age?: number; pay_duration?: Duration; type: Steps; }; interface Props { onCreate: (d: TalerMerchantApi.TemplateAddDetails) => Promise; onBack?: () => void; } export function CreatePage({ onCreate, onBack }: Props): VNode { const { i18n } = useTranslationContext(); const { state: { backendUrl }, } = useSessionContext(); const devices = useInstanceOtpDevices(); const [state, setState] = useState>({ minimum_age: 0, pay_duration: { d_ms: 1000 * 60 * 30, //30 min }, type: Steps.NON_FIXED, }); const parsedPrice = !state.amount ? undefined : Amounts.parse(state.amount); const errors: FormErrors = { id: !state.id ? i18n.str`should not be empty` : !/[a-zA-Z0-9]*/.test(state.id) ? i18n.str`no valid. only characters and numbers` : undefined, description: !state.description ? i18n.str`should not be empty` : undefined, amount: !( state.type === Steps.FIXED_PRICE || state.type === Steps.BOTH_FIXED ) ? undefined : !state.amount ? i18n.str`required` : !parsedPrice ? i18n.str`not valid` : Amounts.isZero(parsedPrice) ? i18n.str`must be greater than 0` : undefined, summary: !( state.type === Steps.FIXED_SUMMARY || state.type === Steps.BOTH_FIXED ) ? undefined : !state.summary ? i18n.str`required` : 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 hasErrors = Object.keys(errors).some( (k) => (errors as Record)[k] !== undefined, ); const submitForm = () => { if (hasErrors || state.type === undefined) return Promise.reject(); switch (state.type) { case Steps.FIXED_PRICE: return onCreate({ template_id: state.id!, template_description: state.description!, template_contract: { minimum_age: state.minimum_age!, pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!), amount: state.amount!, // summary: state.summary, }, otp_id: state.otpId!, }); case Steps.FIXED_SUMMARY: return onCreate({ template_id: state.id!, template_description: state.description!, template_contract: { minimum_age: state.minimum_age!, pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!), // amount: state.amount!, summary: state.summary, }, otp_id: state.otpId!, }); case Steps.NON_FIXED: return onCreate({ template_id: state.id!, template_description: state.description!, template_contract: { minimum_age: state.minimum_age!, pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!), // amount: state.amount!, // summary: state.summary, }, otp_id: state.otpId!, }); case Steps.BOTH_FIXED: return onCreate({ template_id: state.id!, template_description: state.description!, template_contract: { minimum_age: state.minimum_age!, pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!), amount: state.amount!, summary: state.summary, }, otp_id: state.otpId!, }); default: assertUnreachable(state.type); // return onCreate(state); } }; const deviceList = !devices || devices instanceof TalerError || devices.type === "fail" ? [] : devices.body; return (
name="id" help={new URL(`templates/${state.id ?? ""}`, backendUrl).href} label={i18n.str`Identifier`} tooltip={i18n.str`Name of the template in URLs.`} /> name="description" label={i18n.str`Description`} help="" tooltip={i18n.str`Describe what this template stands for`} /> name="type" label={i18n.str`Type`} help={(() => { if (state.type === undefined) return ""; switch (state.type) { case Steps.NON_FIXED: return i18n.str`User will be able to input price and summary before payment.`; case Steps.FIXED_PRICE: return i18n.str`User will be able to add a summary before payment.`; case Steps.FIXED_SUMMARY: return i18n.str`User will be able to set the price before payment.`; case Steps.BOTH_FIXED: return i18n.str`User will not be able to change the price or the summary.`; } })()} tooltip={i18n.str`Define what the user be allowed to modify`} values={[ Steps.NON_FIXED, Steps.FIXED_PRICE, Steps.FIXED_SUMMARY, Steps.BOTH_FIXED, ]} toStr={(v: Steps): string => { switch (v) { case Steps.NON_FIXED: return i18n.str`Simple`; case Steps.FIXED_PRICE: return i18n.str`With price`; case Steps.FIXED_SUMMARY: return i18n.str`With summary`; case Steps.BOTH_FIXED: return i18n.str`With price and summary`; } }} /> {state.type === Steps.BOTH_FIXED || state.type === Steps.FIXED_SUMMARY ? ( name="summary" inputType="multiline" label={i18n.str`Fixed summary`} tooltip={i18n.str`If specified, this template will create order with the same summary`} /> ) : undefined} {state.type === Steps.BOTH_FIXED || state.type === Steps.FIXED_PRICE ? ( name="amount" label={i18n.str`Fixed price`} tooltip={i18n.str`If specified, this template will create order with the same price`} /> ) : undefined} 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.`} /> name="otpId" label={i18n.str`OTP device`} readonly side={ } tooltip={i18n.str`Use to verify transaction in offline mode.`} /> setState((v) => ({ ...v, otpId: p?.id }))} list={deviceList.map((e) => ({ description: e.device_description, id: e.otp_device_id, }))} />
{onBack && ( )} Confirm
); }