summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-06-15 14:30:43 -0300
committerSebastian <sebasjm@gmail.com>2021-06-15 14:33:24 -0300
commitd674e28bcd25e8e24697d2b871c1d8218686ec65 (patch)
tree0a93a2ea7583adacf7a745f470c1c71c14c2bc18
parentd4bce8447350148c24a6ac0717d4d979d6fc0cfe (diff)
downloadmerchant-backoffice-d674e28bcd25e8e24697d2b871c1d8218686ec65.tar.gz
merchant-backoffice-d674e28bcd25e8e24697d2b871c1d8218686ec65.tar.bz2
merchant-backoffice-d674e28bcd25e8e24697d2b871c1d8218686ec65.zip
fix product form on new order
-rw-r--r--CHANGELOG.md7
-rw-r--r--packages/frontend/src/components/form/InputImage.tsx7
-rw-r--r--packages/frontend/src/context/listener.ts35
-rw-r--r--packages/frontend/src/hooks/index.ts40
-rw-r--r--packages/frontend/src/hooks/listener.ts68
-rw-r--r--packages/frontend/src/hooks/product.ts8
-rw-r--r--packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx66
-rw-r--r--packages/frontend/src/paths/instance/products/create/CreatePage.tsx2
-rw-r--r--packages/frontend/src/paths/instance/products/update/UpdatePage.tsx4
-rw-r--r--packages/frontend/src/utils/constants.ts4
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