[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] branch master updated: create and update pro
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] branch master updated: create and update product form |
Date: |
Wed, 14 Apr 2021 22:13:24 +0200 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository merchant-backoffice.
The following commit(s) were added to refs/heads/master by this push:
new 14b76f2 create and update product form
14b76f2 is described below
commit 14b76f2c318bf483bd7534c8761aec720d067532
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Apr 14 17:10:48 2021 -0300
create and update product form
---
packages/frontend/src/InstanceRoutes.tsx | 5 ++
packages/frontend/src/assets/empty.png | Bin 0 -> 2785 bytes
packages/frontend/src/components/form/Field.tsx | 27 -------
packages/frontend/src/components/form/Input.tsx | 8 +-
.../components/form/{Input.tsx => InputImage.tsx} | 51 +++++++------
.../src/components/product/ProductForm.tsx | 82 +++++++++++++++++++++
packages/frontend/src/custom.d.ts | 4 +
packages/frontend/src/hooks/product.ts | 20 ++++-
packages/frontend/src/messages/en.po | 20 ++++-
.../orders/create/NonInventoryProductForm.tsx | 2 +-
.../paths/instance/products/create/CreatePage.tsx | 29 +-------
.../products/create/CreatedSuccessfully.tsx | 14 ++--
.../paths/instance/products/create/ProductForm.tsx | 48 ------------
.../src/paths/instance/products/create/index.tsx | 1 +
.../src/paths/instance/products/list/index.tsx | 23 +-----
.../paths/instance/products/update/UpdatePage.tsx | 63 ++++++++++++++++
.../src/paths/instance/products/update/index.tsx | 49 +++++++++++-
packages/frontend/src/schemas/index.ts | 10 +++
18 files changed, 292 insertions(+), 164 deletions(-)
diff --git a/packages/frontend/src/InstanceRoutes.tsx
b/packages/frontend/src/InstanceRoutes.tsx
index 18f97e8..e4da3b0 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -195,6 +195,11 @@ export function InstanceRoutes({ id, admin }: Props):
VNode {
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
/>
<Route path={InstancePaths.product_update} component={ProductUpdatePage}
+ onUnauthorized={LoginPageAccessDenied}
+ onLoadError={LoginPageServerError}
+ onConfirm={() => { route(InstancePaths.product_list); }}
+ onBack={() => { route(InstancePaths.product_list); }}
+ onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
/>
<Route path={InstancePaths.product_new} component={ProductCreatePage}
/>
diff --git a/packages/frontend/src/assets/empty.png
b/packages/frontend/src/assets/empty.png
new file mode 100644
index 0000000..5120d31
Binary files /dev/null and b/packages/frontend/src/assets/empty.png differ
diff --git a/packages/frontend/src/components/form/Field.tsx
b/packages/frontend/src/components/form/Field.tsx
index ed50eb7..53ef1e6 100644
--- a/packages/frontend/src/components/form/Field.tsx
+++ b/packages/frontend/src/components/form/Field.tsx
@@ -125,30 +125,3 @@ export function useGroupField<T>(name: keyof T) {
}
}
-// export function Field<T>({ name, info, readonly }: Props<T>): VNode {
-// const {errors, object, valueHandler, updateField} = useForm<T>()
-
-// const backend = useContext(BackendContext)
-// const config = useContext(ConfigContext)
-
-// switch (info.meta?.type) {
- // case 'group': {
- // return <InputObject name={name} readonly={readonly}
-
- // onChange={(updater: any): void => valueHandler((prev: any) => ({
...prev, [name]: updater(prev[name]) }))}
- // />
- // }
- // case 'array': return <InputArray name={name} readonly={readonly} />;
- // case 'amount': {
- // if (config.currency) {
- // return <InputWithAddon name={name} readonly={readonly}
addon={config.currency} onChange={(v: string): void =>
values.onChange(`${config.currency}:${v}`)} value={values.value?.split(':')[1]}
/>
- // }
- // return <Input name={name} readonly={readonly} />;
- // }
- // case 'url': return <InputWithAddon name={name} readonly={readonly}
addon={`${backend.url}/private/instances/`} />;
- // case 'secured': return <InputSecured name={name} readonly={readonly} />;
- // case 'duration': return <InputWithAddon name={name} readonly={readonly}
addon={readableDuration(values.value?.d_ms)} atTheEnd
value={`${values.value?.d_ms / 1000 || ''}`} onChange={(v: string): void =>
values.onChange({ d_ms: (parseInt(v, 10) * 1000) || undefined } as any)} />;
- // default: return <Input name={name} readonly={readonly} />;
-
- // }
-// }
diff --git a/packages/frontend/src/components/form/Input.tsx
b/packages/frontend/src/components/form/Input.tsx
index a3201e9..e7af633 100644
--- a/packages/frontend/src/components/form/Input.tsx
+++ b/packages/frontend/src/components/form/Input.tsx
@@ -18,7 +18,7 @@
*
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { h, VNode } from "preact";
+import { ComponentChildren, h, VNode } from "preact";
import { Message, useMessage } from "preact-messages";
import { useField } from "./Field";
@@ -30,6 +30,7 @@ interface Props<T> {
toStr?: (v?: any) => string;
fromStr?: (s: string) => any;
inputExtra?: any,
+ children?: ComponentChildren;
}
const defaultToString = (f?: any): string => f || ''
@@ -39,7 +40,7 @@ const TextInput = ({inputType, error, ...rest}:any) =>
inputType === 'multiline'
<textarea {...rest} class={error ? "textarea is-danger" : "textarea"}
rows="3" /> :
<input {...rest} class={error ? "input is-danger" : "input"}
type={inputType} />;
-export function Input<T>({ name, readonly, expand, inputType, inputExtra,
fromStr = defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode {
+export function Input<T>({ name, readonly, expand, children, inputType,
inputExtra, fromStr = defaultFromString, toStr = defaultToString }: Props<keyof
T>): VNode {
const { error, value, onChange } = useField<T>(name);
const placeholder = useMessage(`fields.instance.${name}.placeholder`);
@@ -56,13 +57,14 @@ export function Input<T>({ name, readonly, expand,
inputType, inputExtra, fromSt
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
- <p class={ expand ? "control is-expanded" : "control" }>
+ <p class={expand ? "control is-expanded" : "control"}>
<TextInput error={error} {...inputExtra}
inputType={inputType}
placeholder={placeholder} readonly={readonly}
name={String(name)} value={toStr(value)}
onChange={(e:h.JSX.TargetedEvent<HTMLInputElement>): void =>
onChange(fromStr(e.currentTarget.value))} />
<Message id={`fields.instance.${name}.help`}> </Message>
+ {children}
</p>
{error ? <p class="help is-danger">
<Message id={`validation.${error.type}`}
fields={error.params}>{error.message} </Message>
diff --git a/packages/frontend/src/components/form/Input.tsx
b/packages/frontend/src/components/form/InputImage.tsx
similarity index 59%
copy from packages/frontend/src/components/form/Input.tsx
copy to packages/frontend/src/components/form/InputImage.tsx
index a3201e9..8227f3b 100644
--- a/packages/frontend/src/components/form/Input.tsx
+++ b/packages/frontend/src/components/form/InputImage.tsx
@@ -18,32 +18,26 @@
*
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { h, VNode } from "preact";
-import { Message, useMessage } from "preact-messages";
+import { ComponentChildren, Fragment, h } from "preact";
import { useField } from "./Field";
+import emptyImage from "../../assets/empty.png";
+import { Message, useMessage } from "preact-messages";
+import { useRef } from "preact/hooks";
-interface Props<T> {
- name: T;
+export interface Props<T> {
+ name: keyof T;
readonly?: boolean;
- inputType?: 'text' | 'number' | 'multiline';
expand?: boolean;
- toStr?: (v?: any) => string;
- fromStr?: (s: string) => any;
- inputExtra?: any,
+ addonAfter?: ComponentChildren;
+ children?: ComponentChildren;
}
-const defaultToString = (f?: any): string => f || ''
-const defaultFromString = (v: string): any => v as any
-
-const TextInput = ({inputType, error, ...rest}:any) => inputType ===
'multiline' ?
- <textarea {...rest} class={error ? "textarea is-danger" : "textarea"}
rows="3" /> :
- <input {...rest} class={error ? "input is-danger" : "input"}
type={inputType} />;
-
-export function Input<T>({ name, readonly, expand, inputType, inputExtra,
fromStr = defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode {
+export function InputImage<T>({ name, readonly, children, expand }: Props<T>) {
const { error, value, onChange } = useField<T>(name);
const placeholder = useMessage(`fields.instance.${name}.placeholder`);
const tooltip = useMessage(`fields.instance.${name}.tooltip`);
+ const image = useRef<HTMLInputElement>(null)
return <div class="field is-horizontal">
<div class="field-label is-normal">
@@ -56,18 +50,31 @@ export function Input<T>({ name, readonly, expand,
inputType, inputExtra, fromSt
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
- <p class={ expand ? "control is-expanded" : "control" }>
- <TextInput error={error} {...inputExtra}
- inputType={inputType}
+ <p class={expand ? "control is-expanded" : "control"}>
+ <img src={!value ? emptyImage : value} style={{ width: 200, height:
200 }} onClick={() => image.current?.click()} />
+ <input
+ ref={image} style={{ display: 'none' }}
+ type="file" name={String(name)}
placeholder={placeholder} readonly={readonly}
- name={String(name)} value={toStr(value)}
- onChange={(e:h.JSX.TargetedEvent<HTMLInputElement>): void =>
onChange(fromStr(e.currentTarget.value))} />
+ onChange={e => {
+ const f: FileList | null = e.currentTarget.files
+ if (!f || f.length != 1 || f[0].size > 10000000) return
onChange(emptyImage)
+ f[0].arrayBuffer().then(b => {
+ const b64 = btoa(
+ new Uint8Array(b)
+ .reduce((data, byte) => data + String.fromCharCode(byte),
'')
+ )
+ onChange(`data:${f[0].type};base64,${b64}` as any)
+ })
+ }} />
<Message id={`fields.instance.${name}.help`}> </Message>
+ {children}
</p>
{error ? <p class="help is-danger">
<Message id={`validation.${error.type}`}
fields={error.params}>{error.message} </Message>
</p> : null}
</div>
</div>
- </div>;
+ </div>
}
+
diff --git a/packages/frontend/src/components/product/ProductForm.tsx
b/packages/frontend/src/components/product/ProductForm.tsx
new file mode 100644
index 0000000..94bb2b3
--- /dev/null
+++ b/packages/frontend/src/components/product/ProductForm.tsx
@@ -0,0 +1,82 @@
+import { h } from "preact";
+import { useCallback, useEffect, useState } from "preact/hooks";
+import { FormErrors, FormProvider } from "../form/Field";
+import { Input } from "../form/Input";
+import { InputCurrency } from "../form/InputCurrency";
+import { useBackendContext, useConfigContext } from "../../context/backend";
+import { MerchantBackend } from "../../declaration";
+import {
+ ProductUpdateSchema as updateSchema,
+ ProductCreateSchema as createSchema,
+ } from '../../schemas'
+import * as yup from 'yup';
+import { InputGroup } from "../form/InputGroup";
+import { useBackendURL } from "../../hooks";
+import { InputWithAddon } from "../form/InputWithAddon";
+import { InputImage } from "../form/InputImage";
+
+type Entity = MerchantBackend.Products.ProductAddDetail
+
+interface Props {
+ onSubscribe: (c: () => Entity | undefined) => void;
+ initial?: Partial<Entity>;
+ showId?: boolean;
+}
+
+export function ProductForm({ onSubscribe, initial, showId }: Props) {
+ const [value, valueHandler] = useState<Partial<Entity>>(initial || {
+ address: {},
+ description_i18n: {},
+ taxes: [],
+ next_restock: { t_ms: 'never' }
+ })
+ const [errors, setErrors] = useState<FormErrors<Entity>>({})
+
+ const submit = useCallback((): Entity | undefined => {
+ try {
+ (showId ? createSchema : updateSchema).validateSync(value, { abortEarly:
false })
+ return value as MerchantBackend.Products.ProductAddDetail
+ } catch (err) {
+ const errors = err.inner as yup.ValidationError[]
+ const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({
...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message
} }), {})
+ setErrors(pathMessages)
+ }
+ }, [value])
+
+ const config = useConfigContext()
+
+ useEffect(() => {
+ onSubscribe(submit)
+ }, [submit])
+
+ const backend = useBackendContext();
+ return <div>
+ <FormProvider<Entity> errors={errors} object={value}
valueHandler={valueHandler} >
+
+ { showId ? <InputWithAddon<Entity> name="product_id"
addonBefore={`${backend.url}/product/`} /> : undefined }
+ <InputImage<Entity> name="image" />
+ <Input<Entity> name="description" inputType="multiline" />
+ <Input<Entity> name="unit" />
+ <InputCurrency<Entity> name="price" currency={config.currency} />
+
+ <Input<Entity> name="total_stock" inputType="number" fromStr={(v) =>
parseInt(v, 10)} toStr={(v) => "" + v} inputExtra={{ min: 0 }} />
+
+ <InputGroup<Entity> name="address">
+ <Input name="address.country" />
+ <Input name="address.address_lines" inputType="multiline"
+ toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
+ fromStr={(v: string) => v.split('\n')}
+ />
+ <Input name="address.building_number" />
+ <Input name="address.building_name" />
+ <Input name="address.street" />
+ <Input name="address.post_code" />
+ <Input name="address.town_location" />
+ <Input name="address.town" />
+ <Input name="address.district" />
+ <Input name="address.country_subdivision" />
+ </InputGroup>
+
+ </FormProvider>
+ </div>
+}
diff --git a/packages/frontend/src/custom.d.ts
b/packages/frontend/src/custom.d.ts
index e9defe8..ae71b5f 100644
--- a/packages/frontend/src/custom.d.ts
+++ b/packages/frontend/src/custom.d.ts
@@ -21,6 +21,10 @@ declare module "*.jpeg" {
const content: any;
export default content;
}
+declare module "*.png" {
+ const content: any;
+ export default content;
+}
declare module '*.svg' {
const content: any;
export default content;
diff --git a/packages/frontend/src/hooks/product.ts
b/packages/frontend/src/hooks/product.ts
index 2208d53..c74496d 100644
--- a/packages/frontend/src/hooks/product.ts
+++ b/packages/frontend/src/hooks/product.ts
@@ -28,7 +28,10 @@ export function useProductAPI(): ProductAPI {
await request(`${url}/private/products`, {
method: 'post',
token,
- data
+ data: {
+ ...data,
+ image: {}
+ }
});
mutateAll(/@"\/private\/products"@/);
@@ -108,3 +111,18 @@ export function useInstanceProducts():
HttpResponse<(MerchantBackend.Products.Pr
return { loading: true }
}
+export function useProductDetails(productId: string):
HttpResponse<MerchantBackend.Products.ProductDetail> {
+ const { url: baseUrl } = useBackendContext();
+ const { token, id: instanceId, admin } = useInstanceContext();
+
+ const url = !admin ? baseUrl : `${baseUrl}/instances/${instanceId}`
+
+ const { data, error, isValidating } =
useSWR<HttpResponseOk<MerchantBackend.Products.ProductDetail>, HttpError>(
+ [`/private/products/${productId}`, token, url], fetcher
+ )
+
+ if (isValidating) return { loading: true, data: data?.data }
+ if (data) return data
+ if (error) return error
+ return { loading: true }
+}
diff --git a/packages/frontend/src/messages/en.po
b/packages/frontend/src/messages/en.po
index 73b9408..cabbc68 100644
--- a/packages/frontend/src/messages/en.po
+++ b/packages/frontend/src/messages/en.po
@@ -254,9 +254,6 @@ msgstr "Exchange Initial Amount"
msgid "fields.tips.merchant_initial_amount.label"
msgstr "Merchant Initial Amount"
-msgid "fields.instance.paid.placeholder"
-msgstr ""
-
msgid "fields.instance.paid.tooltip"
msgstr "three state boolean"
@@ -507,3 +504,20 @@ msgstr "Extra information"
msgid "fields.instance.extra.tooltip"
msgstr "Must be a JSON formatted string"
+
+
+msgid "fields.instance.product_id.label"
+msgstr "ID"
+
+
+msgid "fields.instance.image.label"
+msgstr "Image"
+
+
+msgid "fields.instance.unit.label"
+msgstr "Unit"
+
+
+
+msgid "fields.instance.total_stock.label"
+msgstr "Total Stock"
\ No newline at end of file
diff --git
a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
b/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
index 8426264..92b7413 100644
---
a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
+++
b/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
@@ -3,7 +3,7 @@ import { useEffect, useState } from "preact/hooks";
import { ConfirmModal } from "../../../../components/modal";
import { MerchantBackend } from "../../../../declaration";
import { useListener } from "../../../../hooks";
-import { ProductForm } from "../../products/create/ProductForm";
+import { ProductForm } from "../../../../components/product/ProductForm";
type Entity = MerchantBackend.Product
diff --git
a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
index b19e5a5..9df2c31 100644
--- a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
@@ -20,21 +20,9 @@
*/
import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
import { MerchantBackend } from "../../../../declaration";
-import * as yup from 'yup';
-import { FormErrors, FormProvider } from "../../../../components/form/Field"
-import { ProductCreateSchema as schema } from '../../../../schemas'
import { Message } from "preact-messages";
-import { Input } from "../../../../components/form/Input";
-import { InputSecured } from "../../../../components/form/InputSecured";
-import { InputWithAddon } from "../../../../components/form/InputWithAddon";
-import { InputGroup } from "../../../../components/form/InputGroup";
-import { useConfigContext, useBackendContext } from
"../../../../context/backend";
-import { InputDuration } from "../../../../components/form/InputDuration";
-import { InputCurrency } from "../../../../components/form/InputCurrency";
-import { InputPayto } from "../../../../components/form/InputPayto";
-import { ProductForm } from "./ProductForm";
+import { ProductForm } from "../../../../components/product/ProductForm";
import { useListener } from "../../../../hooks";
type Entity = MerchantBackend.Products.ProductAddDetail
@@ -45,12 +33,6 @@ interface Props {
}
-function with_defaults(id?: string): Partial<Entity> {
- return {
-
- };
-}
-
export function CreatePage({ onCreate, onBack }: Props): VNode {
const [submitForm, addFormSubmitter] = useListener<Entity |
undefined>((result) => {
@@ -62,14 +44,7 @@ export function CreatePage({ onCreate, onBack }: Props):
VNode {
<div class="columns">
<div class="column" />
<div class="column is-two-thirds">
- {/* <FormProvider<Entity> errors={errors} object={value}
valueHandler={valueHandler} >
-
- <Input<Entity> name="description" />
- <InputCurrency<Entity> name="price" currency={config.currency} />
- <Input<Entity> name="total_stock" inputType="number" />
-
- </FormProvider> */}
- <ProductForm onSubscribe={addFormSubmitter} />
+ <ProductForm onSubscribe={addFormSubmitter} showId />
<div class="buttons is-right mt-5">
{onBack && <button class="button" onClick={onBack} ><Message
id="Cancel" /></button>}
diff --git
a/packages/frontend/src/paths/instance/products/create/CreatedSuccessfully.tsx
b/packages/frontend/src/paths/instance/products/create/CreatedSuccessfully.tsx
index 17d716f..002c695 100644
---
a/packages/frontend/src/paths/instance/products/create/CreatedSuccessfully.tsx
+++
b/packages/frontend/src/paths/instance/products/create/CreatedSuccessfully.tsx
@@ -14,9 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { h } from "preact";
-import { useEffect, useState } from "preact/hooks";
import { CreatedSuccessfully as Template } from
"../../../../components/notifications/CreatedSuccessfully";
-import { useOrderAPI } from "../../../../hooks/order";
import { Entity } from "./index";
interface Props {
@@ -30,36 +28,36 @@ export function CreatedSuccessfully({ entity, onConfirm,
onCreateAnother }: Prop
return <Template onConfirm={onConfirm} onCreateAnother={onCreateAnother}>
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Amount</label>
+ <label class="label">Image</label>
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
<p class="control">
- <input class="input" readonly value={entity.price} />
+ <img src={entity.image} />
</p>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Summary</label>
+ <label class="label">Description</label>
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
<p class="control">
- <input class="input" readonly value={entity.description} />
+ <textarea class="input" readonly value={entity.description} />
</p>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Order ID</label>
+ <label class="label">Price</label>
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
<p class="control">
- <input class="input" readonly value={entity.total_stock} />
+ <input class="input" readonly value={entity.price} />
</p>
</div>
</div>
diff --git
a/packages/frontend/src/paths/instance/products/create/ProductForm.tsx
b/packages/frontend/src/paths/instance/products/create/ProductForm.tsx
deleted file mode 100644
index ac059b7..0000000
--- a/packages/frontend/src/paths/instance/products/create/ProductForm.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { h } from "preact";
-import { useCallback, useEffect, useState } from "preact/hooks";
-import { FormErrors, FormProvider } from "../../../../components/form/Field";
-import { Input } from "../../../../components/form/Input";
-import { InputCurrency } from "../../../../components/form/InputCurrency";
-import { useConfigContext } from "../../../../context/backend";
-import { MerchantBackend } from "../../../../declaration";
-import { ProductCreateSchema as schema } from '../../../../schemas'
-import * as yup from 'yup';
-
-type Entity = MerchantBackend.Products.ProductAddDetail
-
-interface Props {
- onSubscribe: (c:() => Entity|undefined) => void;
- initial?: Partial<Entity>;
-}
-
-export function ProductForm({onSubscribe, initial}:Props) {
- const [value, valueHandler] = useState<Partial<Entity>>(initial||{})
- const [errors, setErrors] = useState<FormErrors<Entity>>({})
-
- const submit = useCallback((): Entity|undefined => {
- try {
- schema.validateSync(value, { abortEarly: false })
- return value as MerchantBackend.Products.ProductAddDetail
- } catch (err) {
- const errors = err.inner as yup.ValidationError[]
- const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({
...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message
} }), {})
- setErrors(pathMessages)
- }
- },[value])
-
- const config = useConfigContext()
-
- useEffect(()=> {
- onSubscribe(submit)
- },[submit])
-
- return <div>
- <FormProvider<Entity> errors={errors} object={value}
valueHandler={valueHandler} >
-
- <Input<Entity> name="description" />
- <InputCurrency<Entity> name="price" currency={config.currency} />
- <Input<Entity> name="total_stock" inputType="number" fromStr={(v) =>
parseInt(v, 10)} toStr={(v) => ""+v} inputExtra={{min:0}} />
-
- </FormProvider>
- </div>
-}
\ No newline at end of file
diff --git a/packages/frontend/src/paths/instance/products/create/index.tsx
b/packages/frontend/src/paths/instance/products/create/index.tsx
index 1ac80a7..81faeeb 100644
--- a/packages/frontend/src/paths/instance/products/create/index.tsx
+++ b/packages/frontend/src/paths/instance/products/create/index.tsx
@@ -44,6 +44,7 @@ export default function ({ onConfirm, onBack }: Props): VNode
{
}
return <Fragment>
+ <NotificationCard notification={notif} />
<CreatePage
onBack={onBack}
onCreate={(request: MerchantBackend.Products.ProductAddDetail) => {
diff --git a/packages/frontend/src/paths/instance/products/list/index.tsx
b/packages/frontend/src/paths/instance/products/list/index.tsx
index 8bda1f2..c5f5564 100644
--- a/packages/frontend/src/paths/instance/products/list/index.tsx
+++ b/packages/frontend/src/paths/instance/products/list/index.tsx
@@ -40,8 +40,7 @@ interface Props {
}
export default function ({ onUnauthorized, onLoadError, onCreate, onSelect,
onNotFound }: Props): VNode {
const result = useInstanceProducts()
- const { createProduct, deleteProduct, updateProduct } = useProductAPI()
- const { currency } = useConfigContext()
+ const { deleteProduct, updateProduct } = useProductAPI()
const [notif, setNotif] = useState<Notification | undefined>(undefined)
if (result.clientError && result.isUnauthorized) return onUnauthorized()
@@ -60,25 +59,7 @@ export default function ({ onUnauthorized, onLoadError,
onCreate, onSelect, onNo
<NotificationCard notification={notif} />
<CardTable instances={result.data}
- // onCreate={onCreate}
- onCreate={() => {
- const product_id = `${Math.floor(Math.random() * 999999 + 1)}`
- const price = `${currency}:${Math.floor(Math.random() * 20 + 1)}`
- return createProduct({
- product_id,
- address: {},
- description: `product with id ${product_id} and price ${price}`,
- description_i18n: {
- en: '', es: ''
- },
- image: {} as string, //WTF?
- price,
- taxes: [],
- total_stock: Math.floor(Math.random() * 20 + 10),
- unit: 'units',
- next_restock: { t_ms: 'never' }, //WTF? should not be required
- })
- }}
+ onCreate={onCreate}
onUpdate={(id, prod) => updateProduct(id, prod)
.then(() => setNotif({
message: 'product updated successfully',
diff --git
a/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
b/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
new file mode 100644
index 0000000..40319a8
--- /dev/null
+++ b/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
@@ -0,0 +1,63 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { h, VNode } from "preact";
+import { MerchantBackend, WithId } from "../../../../declaration";
+import { Message } from "preact-messages";
+import { ProductForm } from "../../../../components/product/ProductForm";
+import { useListener } from "../../../../hooks";
+
+type Entity = MerchantBackend.Products.ProductPatchDetail & WithId
+
+interface Props {
+ onUpdate: (d: Entity) => void;
+ onBack?: () => void;
+ product: Entity;
+}
+
+export function UpdatePage({ product, onUpdate, onBack }: Props): VNode {
+ const [submitForm, addFormSubmitter] = useListener<Entity |
undefined>((result) => {
+ if (result) onUpdate(result)
+ })
+
+ return <div>
+ <section class="section is-main-section">
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-two-thirds">
+ <ProductForm initial={product} onSubscribe={(a) => {
+ addFormSubmitter(() => {
+ const p = a()
+ return p as any
+ })
+ }} />
+
+ <div class="buttons is-right mt-5">
+ {onBack && <button class="button" onClick={onBack} ><Message
id="Cancel" /></button>}
+ <button class="button is-success" onClick={submitForm} ><Message
id="Confirm" /></button>
+ </div>
+
+ </div>
+ <div class="column" />
+ </div>
+ </section>
+ </div>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/paths/instance/products/update/index.tsx
b/packages/frontend/src/paths/instance/products/update/index.tsx
index dbd9b1c..335c36c 100644
--- a/packages/frontend/src/paths/instance/products/update/index.tsx
+++ b/packages/frontend/src/paths/instance/products/update/index.tsx
@@ -19,8 +19,51 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { h, VNode } from 'preact';
+import { Fragment, h, VNode } from 'preact';
+import { useState } from 'preact/hooks';
+import { NotificationCard } from '../../../../components/menu';
+import { MerchantBackend } from '../../../../declaration';
+import { useOrderAPI } from '../../../../hooks/order';
+import { Notification } from '../../../../utils/types';
+import { UpdatePage } from './UpdatePage';
+import { useProductAPI, useProductDetails } from '../../../../hooks/product';
+import { HttpError } from '../../../../hooks/backend';
+import { Loading } from '../../../../components/exception/loading';
-export default function ():VNode {
- return <div>product update page</div>
+export type Entity = MerchantBackend.Products.ProductAddDetail
+interface Props {
+ onBack?: () => void;
+ onConfirm: () => void;
+ onUnauthorized: () => VNode;
+ onNotFound: () => VNode;
+ onLoadError: (e: HttpError) => VNode;
+ pid: string;
+}
+export default function ({ pid, onConfirm, onBack, onUnauthorized, onNotFound,
onLoadError }: Props): VNode {
+ const { updateProduct } = useProductAPI()
+ const result = useProductDetails(pid)
+ const [notif, setNotif] = useState<Notification | undefined>(undefined)
+
+ if (result.clientError && result.isUnauthorized) return onUnauthorized()
+ if (result.clientError && result.isNotfound) return onNotFound()
+ if (result.loading) return <Loading />
+ if (!result.ok) return onLoadError(result)
+
+ return <Fragment>
+ <NotificationCard notification={notif} />
+ <UpdatePage
+ product={{ ...result.data, id: pid }}
+ onBack={onBack}
+ onUpdate={(data) => {
+ updateProduct(pid, data)
+ .then(onConfirm)
+ .catch((error) => {
+ setNotif({
+ message: 'could not create product',
+ type: "ERROR",
+ description: error.message
+ })
+ })
+ }} />
+ </Fragment>
}
\ No newline at end of file
diff --git a/packages/frontend/src/schemas/index.ts
b/packages/frontend/src/schemas/index.ts
index 4cf766a..01209df 100644
--- a/packages/frontend/src/schemas/index.ts
+++ b/packages/frontend/src/schemas/index.ts
@@ -167,6 +167,16 @@ export const OrderCreateSchema = yup.object().shape({
})
export const ProductCreateSchema = yup.object().shape({
+ product_id: yup.string().ensure().required(),
+ description: yup.string().required(),
+ unit: yup.string().ensure().required(),
+ price: yup.string()
+ .required()
+ .test('amount', 'the amount is not valid', currencyWithAmountIsValid),
+ total_stock: yup.number().required(),
+})
+
+export const ProductUpdateSchema = yup.object().shape({
description: yup.string().required(),
price: yup.string()
.required()
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-merchant-backoffice] branch master updated: create and update product form,
gnunet <=