gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]