/*
This file is part of GNU Taler
(C) 2021-2023 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 { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h } from "preact";
import { useCallback, useEffect, useState } from "preact/hooks";
import * as yup from "yup";
import { useBackendContext } from "../../context/backend.js";
import { MerchantBackend } from "../../declaration.js";
import {
ProductCreateSchema as createSchema,
ProductUpdateSchema as updateSchema,
} from "../../schemas/index.js";
import { FormErrors, FormProvider } from "../form/FormProvider.js";
import { Input } from "../form/Input.js";
import { InputCurrency } from "../form/InputCurrency.js";
import { InputImage } from "../form/InputImage.js";
import { InputNumber } from "../form/InputNumber.js";
import { InputStock, Stock } from "../form/InputStock.js";
import { InputTaxes } from "../form/InputTaxes.js";
import { InputWithAddon } from "../form/InputWithAddon.js";
type Entity = MerchantBackend.Products.ProductDetail & { product_id: string };
interface Props {
onSubscribe: (c?: () => Entity | undefined) => void;
initial?: Partial;
alreadyExist?: boolean;
}
export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
const [value, valueHandler] = useState>({
address: {},
description_i18n: {},
taxes: [],
next_restock: { t_s: "never" },
price: ":0",
...initial,
stock:
!initial || initial.total_stock === -1
? undefined
: {
current: initial.total_stock || 0,
lost: initial.total_lost || 0,
sold: initial.total_sold || 0,
address: initial.address,
nextRestock: initial.next_restock,
},
});
let errors: FormErrors = {};
try {
(alreadyExist ? updateSchema : createSchema).validateSync(value, {
abortEarly: false,
});
} catch (err) {
if (err instanceof yup.ValidationError) {
const yupErrors = err.inner as yup.ValidationError[];
errors = yupErrors.reduce(
(prev, cur) =>
!cur.path ? prev : { ...prev, [cur.path]: cur.message },
{},
);
}
}
const hasErrors = Object.keys(errors).some(
(k) => (errors as any)[k] !== undefined,
);
const submit = useCallback((): Entity | undefined => {
const stock: Stock = (value as any).stock;
if (!stock) {
value.total_stock = -1;
} else {
value.total_stock = stock.current;
value.total_lost = stock.lost;
value.next_restock =
stock.nextRestock instanceof Date
? { t_s: stock.nextRestock.getTime() / 1000 }
: stock.nextRestock;
value.address = stock.address;
}
delete (value as any).stock;
if (typeof value.minimum_age !== "undefined" && value.minimum_age < 1) {
delete value.minimum_age;
}
return value as MerchantBackend.Products.ProductDetail & {
product_id: string;
};
}, [value]);
useEffect(() => {
onSubscribe(hasErrors ? undefined : submit);
}, [submit, hasErrors]);
const backend = useBackendContext();
const { i18n } = useTranslationContext();
return (
name="product"
errors={errors}
object={value}
valueHandler={valueHandler}
>
{alreadyExist ? undefined : (
name="product_id"
addonBefore={`${backend.url}/product/`}
label={i18n.str`ID`}
tooltip={i18n.str`product identification to use in URLs (for internal use only)`}
/>
)}
name="image"
label={i18n.str`Image`}
tooltip={i18n.str`illustration of the product for customers`}
/>
name="description"
inputType="multiline"
label={i18n.str`Description`}
tooltip={i18n.str`product description for customers`}
/>
name="minimum_age"
label={i18n.str`Age restricted`}
tooltip={i18n.str`is this product restricted for customer below certain age?`}
/>
name="unit"
label={i18n.str`Unit`}
tooltip={i18n.str`unit describing quantity of product sold (e.g. 2 kilograms, 5 liters, 3 items, 5 meters) for customers`}
/>
name="price"
label={i18n.str`Price`}
tooltip={i18n.str`sale price for customers, including taxes, for above units of the product`}
/>
name="taxes"
label={i18n.str`Taxes`}
tooltip={i18n.str`taxes included in the product price, exposed to customers`}
/>