diff options
author | Sebastian <sebasjm@gmail.com> | 2021-06-15 14:30:43 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-06-15 14:33:24 -0300 |
commit | d674e28bcd25e8e24697d2b871c1d8218686ec65 (patch) | |
tree | 0a93a2ea7583adacf7a745f470c1c71c14c2bc18 | |
parent | d4bce8447350148c24a6ac0717d4d979d6fc0cfe (diff) | |
download | merchant-backoffice-d674e28bcd25e8e24697d2b871c1d8218686ec65.tar.gz merchant-backoffice-d674e28bcd25e8e24697d2b871c1d8218686ec65.tar.bz2 merchant-backoffice-d674e28bcd25e8e24697d2b871c1d8218686ec65.zip |
fix product form on new order
-rw-r--r-- | CHANGELOG.md | 7 | ||||
-rw-r--r-- | packages/frontend/src/components/form/InputImage.tsx | 7 | ||||
-rw-r--r-- | packages/frontend/src/context/listener.ts | 35 | ||||
-rw-r--r-- | packages/frontend/src/hooks/index.ts | 40 | ||||
-rw-r--r-- | packages/frontend/src/hooks/listener.ts | 68 | ||||
-rw-r--r-- | packages/frontend/src/hooks/product.ts | 8 | ||||
-rw-r--r-- | packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx | 66 | ||||
-rw-r--r-- | packages/frontend/src/paths/instance/products/create/CreatePage.tsx | 2 | ||||
-rw-r--r-- | packages/frontend/src/paths/instance/products/update/UpdatePage.tsx | 4 | ||||
-rw-r--r-- | packages/frontend/src/utils/constants.ts | 4 |
10 files changed, 166 insertions, 75 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 852efd5..6b78731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,13 @@ wallet - show transaction with error state - add developer mode in settings, this will show debug tab - add transaction details, and delete button + onDetails + - add examples for error and pending + - retry button when there is an error + - starting the backup UI + + + ## [Unreleased] diff --git a/packages/frontend/src/components/form/InputImage.tsx b/packages/frontend/src/components/form/InputImage.tsx index 8f10c47..2f598b3 100644 --- a/packages/frontend/src/components/form/InputImage.tsx +++ b/packages/frontend/src/components/form/InputImage.tsx @@ -22,6 +22,7 @@ import { ComponentChildren, h } from "preact"; import { useRef, useState } from "preact/hooks"; import emptyImage from "../../assets/empty.png"; import { Translate } from "../../i18n"; +import { MAX_IMAGE_SIZE as MAX_IMAGE_UPLOAD_SIZE } from "../../utils/constants"; import { InputProps, useField } from "./useField"; export interface Props<T> extends InputProps<T> { @@ -59,17 +60,17 @@ export function InputImage<T>({ name, readonly, placeholder, tooltip, label, hel if (!f || f.length != 1) { return onChange(emptyImage) } - if (f[0].size > 1024 * 1024) { + if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { setSizeError(true) return onChange(emptyImage) } setSizeError(false) - f[0].arrayBuffer().then(b => { + return 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) + return onChange(`data:${f[0].type};base64,${b64}` as any) }) }} /> {help} diff --git a/packages/frontend/src/context/listener.ts b/packages/frontend/src/context/listener.ts new file mode 100644 index 0000000..659db0a --- /dev/null +++ b/packages/frontend/src/context/listener.ts @@ -0,0 +1,35 @@ +/* + 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 { createContext } from 'preact' +import { useContext } from 'preact/hooks' + +interface Type { + id: string; + token?: string; + admin?: boolean; + changeToken: (t?:string) => void; +} + +const Context = createContext<Type>({} as any) + +export const ListenerContextProvider = Context.Provider +export const useListenerContext = (): Type => useContext(Context); diff --git a/packages/frontend/src/hooks/index.ts b/packages/frontend/src/hooks/index.ts index 3a16de7..19d672a 100644 --- a/packages/frontend/src/hooks/index.ts +++ b/packages/frontend/src/hooks/index.ts @@ -107,44 +107,4 @@ export function useNotNullLocalStorage(key: string, initialValue: string): [stri return [storedValue, setValue]; } -/** - * returns subscriber and activator - * subscriber will receive a method (listener) that will be call when the activator runs. - * the result of calling the listener will be sent to @action - * - * @param action from <T> to <R> - * @returns activator and subscriber, undefined activator means that there is not subscriber - */ -export function useListener<T, R = any>(action: (r: T) => Promise<R>): [undefined | (() => Promise<R>), (listener?: () => T) => void] { - type RunnerHandler = { toBeRan?: () => Promise<R> } - const [state, setState] = useState<RunnerHandler>({}) - - /** - * subscriber will receive a method that will be call when the activator runs - * - * @param listener function to be run when the activator runs - */ - const subscriber = (listener?: () => T) => { - if (listener) { - setState({ - toBeRan: () => { - const whatWeGetFromTheListener = listener() - return action(whatWeGetFromTheListener) - } - }) - } - } - - /** - * activator will call runner if there is someone subscribed - */ - const activator = state.toBeRan ? async () => { - if (state.toBeRan) { - return state.toBeRan() - } - return Promise.reject() - } : undefined - - return [activator, subscriber] -} diff --git a/packages/frontend/src/hooks/listener.ts b/packages/frontend/src/hooks/listener.ts new file mode 100644 index 0000000..231ed6c --- /dev/null +++ b/packages/frontend/src/hooks/listener.ts @@ -0,0 +1,68 @@ +/* + 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 { useState } from "preact/hooks"; + +/** + * returns subscriber and activator + * subscriber will receive a method (listener) that will be call when the activator runs. + * the result of calling the listener will be sent to @action + * + * @param action from <T> to <R> + * @returns activator and subscriber, undefined activator means that there is not subscriber + */ + +export function useListener<T, R = any>(action: (r: T) => Promise<R>): [undefined | (() => Promise<R>), (listener?: () => T) => void] { + type RunnerHandler = { toBeRan?: () => Promise<R>; }; + const [state, setState] = useState<RunnerHandler>({}); + + /** + * subscriber will receive a method that will be call when the activator runs + * + * @param listener function to be run when the activator runs + */ + const subscriber = (listener?: () => T) => { + if (listener) { + setState({ + toBeRan: () => { + const whatWeGetFromTheListener = listener(); + return action(whatWeGetFromTheListener); + } + }); + } else { + setState({ + toBeRan: undefined + }) + } + }; + + /** + * activator will call runner if there is someone subscribed + */ + const activator = state.toBeRan ? async () => { + if (state.toBeRan) { + return state.toBeRan(); + } + return Promise.reject(); + } : undefined; + + return [activator, subscriber]; +} diff --git a/packages/frontend/src/hooks/product.ts b/packages/frontend/src/hooks/product.ts index a8b33ba..6bc131d 100644 --- a/packages/frontend/src/hooks/product.ts +++ b/packages/frontend/src/hooks/product.ts @@ -134,7 +134,13 @@ export function useInstanceProducts(): HttpResponse<(MerchantBackend.Products.Pr url: `${baseUrl}/instances/${id}`, token: instanceToken }; - const { data: list, error: listError, isValidating: listLoading } = useSWR<HttpResponseOk<MerchantBackend.Products.InventorySummaryResponse>, HttpError>([`/private/products`, token, url], fetcher); + const { data: list, error: listError, isValidating: listLoading } = useSWR<HttpResponseOk<MerchantBackend.Products.InventorySummaryResponse>, HttpError>([`/private/products`, token, url], fetcher, { + refreshInterval: 0, + refreshWhenHidden: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + refreshWhenOffline: false, + }); const { data: products, error: productError, setSize, size } = useSWRInfinite<HttpResponseOk<MerchantBackend.Products.ProductDetail>, HttpError>((pageIndex: number) => { if (!list?.data || !list.data.products.length || listError || listLoading) return null diff --git a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx b/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx index 31cf33b..47764ec 100644 --- a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx +++ b/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx @@ -23,7 +23,8 @@ import { InputNumber } from "../../../../components/form/InputNumber"; import { InputTaxes } from "../../../../components/form/InputTaxes"; import { ConfirmModal } from "../../../../components/modal"; import { MerchantBackend } from "../../../../declaration"; -import { useListener } from "../../../../hooks"; +import { useListener } from "../../../../hooks/listener"; + import { NonInventoryProductSchema as schema } from '../../../../schemas'; @@ -57,24 +58,41 @@ export function NonInventoryProductFrom({ productToEdit, onAddProduct }: Props): unit: result.unit || '' }) } - return Promise.reject() + return Promise.resolve() }) const i18n = useTranslator() + + console.log('submit form', submitForm) + return <Fragment> <div class="buttons"> <button class="button is-success" onClick={() => setShowCreateProduct(true)} ><Translate>add new product</Translate></button> </div> - {showCreateProduct && <ConfirmModal active - description={i18n`Complete information of the product`} - onCancel={() => setShowCreateProduct(false)} onConfirm={submitForm}> - <ProductForm initial={productToEdit} onSubscribe={addFormSubmitter} /> - </ConfirmModal>} + {showCreateProduct && <div class="modal is-active"> + <div class="modal-background " onClick={() => setShowCreateProduct(false)} /> + <div class="modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title">{i18n`Complete information of the product`}</p> + <button class="delete " aria-label="close" onClick={() => setShowCreateProduct(false)} /> + </header> + <section class="modal-card-body"> + <ProductForm initial={productToEdit} onSubscribe={addFormSubmitter} /> + </section> + <footer class="modal-card-foot"> + <div class="buttons is-right" style={{ width: '100%' }}> + <button class="button " onClick={() => setShowCreateProduct(false)} ><Translate>Cancel</Translate></button> + <button class="button is-info " disabled={!submitForm} onClick={submitForm} ><Translate>Confirm</Translate></button> + </div> + </footer> + </div> + <button class="modal-close is-large " aria-label="close" onClick={() => setShowCreateProduct(false)} /> + </div>} </Fragment> } interface ProductProps { - onSubscribe: (c: () => Entity | undefined) => void; + onSubscribe: (c?: () => Entity | undefined) => void; initial?: Partial<Entity>; } @@ -92,30 +110,24 @@ export function ProductForm({ onSubscribe, initial }: ProductProps) { taxes: [], ...initial, }) - const [errors, setErrors] = useState<FormErrors<NonInventoryProduct>>({}) + let errors: FormErrors<Entity> = {} + try { + schema.validateSync(value, { abortEarly: false }) + } catch (err) { + const yupErrors = err.inner as yup.ValidationError[] + errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: cur.message }), {}) + } const submit = useCallback((): Entity | undefined => { - try { - const validated = schema.validateSync(value, { abortEarly: false }) - const result: MerchantBackend.Product = { - description: validated.description, - image: validated.image, - price: validated.price, - quantity: validated.quantity, - taxes: validated.taxes, - unit: validated.unit, - } - return result - } catch (err) { - const errors = err.inner as yup.ValidationError[] - const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: cur.message }), {}) - setErrors(pathMessages) - } + return value as MerchantBackend.Product }, [value]) + const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined) + useEffect(() => { - onSubscribe(submit) - }, [submit]) + console.log('has errors', hasErrors) + onSubscribe(hasErrors ? undefined : submit) + }, [submit, hasErrors]) const i18n = useTranslator() diff --git a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx index 2498d6a..0f2411b 100644 --- a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx +++ b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx @@ -23,7 +23,7 @@ import { h, VNode } from "preact"; import { AsyncButton } from "../../../../components/exception/AsyncButton"; import { ProductForm } from "../../../../components/product/ProductForm"; import { MerchantBackend } from "../../../../declaration"; -import { useListener } from "../../../../hooks"; +import { useListener } from "../../../../hooks/listener"; import { Translate, useTranslator } from "../../../../i18n"; type Entity = MerchantBackend.Products.ProductAddDetail & { product_id: string} diff --git a/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx b/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx index 0c665be..32d67c0 100644 --- a/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx +++ b/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx @@ -23,7 +23,7 @@ import { h, VNode } from "preact"; import { AsyncButton } from "../../../../components/exception/AsyncButton"; import { ProductForm } from "../../../../components/product/ProductForm"; import { MerchantBackend, WithId } from "../../../../declaration"; -import { useListener } from "../../../../hooks"; +import { useListener } from "../../../../hooks/listener"; import { Translate, useTranslator } from "../../../../i18n"; type Entity = MerchantBackend.Products.ProductDetail & { product_id: string } @@ -41,7 +41,7 @@ export function UpdatePage({ product, onUpdate, onBack }: Props): VNode { }) const i18n = useTranslator() - + return <div> <section class="section"> <section class="hero is-hero-bar"> diff --git a/packages/frontend/src/utils/constants.ts b/packages/frontend/src/utils/constants.ts index 1f654c0..403adb9 100644 --- a/packages/frontend/src/utils/constants.ts +++ b/packages/frontend/src/utils/constants.ts @@ -40,4 +40,6 @@ export const PAGE_SIZE = 20 export const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1; // how much we will wait for all request, in seconds -export const DEFAULT_REQUEST_TIMEOUT = 10;
\ No newline at end of file +export const DEFAULT_REQUEST_TIMEOUT = 10; + +export const MAX_IMAGE_SIZE = 1024 * 1024;
\ No newline at end of file |