merchant-backoffice

ZZZ: Inactive/Deprecated
Log | Files | Refs | Submodules | README

commit c9b0e41116d3347c844ac00bda401e84d67ffd4d
parent 497e6bea531efe25a8e3f4ea086cc7827a31d3e2
Author: Sebastian <sebasjm@gmail.com>
Date:   Tue, 27 Apr 2021 09:57:09 -0300

some fixes

Diffstat:
MCHANGELOG.md | 1+
Mpackages/frontend/.gitignore | 1+
Mpackages/frontend/.storybook/main.js | 9++++-----
Mpackages/frontend/.storybook/preview.js | 1+
Mpackages/frontend/package.json | 13+++++++++++--
Mpackages/frontend/src/InstanceRoutes.tsx | 60++++++++++++++++++++++++++++++++++++++++--------------------
Mpackages/frontend/src/components/form/InputSearchProduct.tsx | 34++++++++++++++++++++++++----------
Mpackages/frontend/src/components/form/InputSecured.stories.tsx | 10++++------
Mpackages/frontend/src/components/form/InputStock.tsx | 16+++++++++-------
Mpackages/frontend/src/components/product/ProductForm.tsx | 9++++-----
Mpackages/frontend/src/components/product/ProductList.tsx | 9++++++---
Mpackages/frontend/src/hooks/backend.ts | 36+++++++++++++++++++++++++-----------
Mpackages/frontend/src/messages/en.po | 3+++
Mpackages/frontend/src/paths/instance/orders/create/CreatePage.tsx | 6+-----
Mpackages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx | 39++++++++++++++++++++++-----------------
Mpackages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mpackages/frontend/src/paths/instance/orders/create/index.tsx | 8+-------
Mpackages/frontend/src/paths/instance/orders/details/DetailPage.tsx | 30++++++++++--------------------
Mpackages/frontend/src/paths/instance/orders/details/Timeline.tsx | 27++++++++++++++++++++++++++-
Mpackages/frontend/src/paths/instance/products/create/CreatePage.tsx | 2+-
Mpackages/frontend/src/paths/instance/products/create/index.tsx | 12++----------
Mpackages/frontend/src/paths/instance/products/list/Table.tsx | 40+++++++++++++++++++++++++++++++++++-----
Mpackages/frontend/src/paths/instance/update/index.tsx | 2+-
Mpackages/frontend/src/schemas/index.ts | 12++++++++++--
Apackages/frontend/tests/__mocks__/fileTransformer.js | 9+++++++++
Apackages/frontend/tests/stories.test.tsx | 37+++++++++++++++++++++++++++++++++++++
Mpnpm-lock.yaml | 323++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
27 files changed, 643 insertions(+), 195 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - product detail: we could have some button that brings us to the detailed screen for the product - navigation to another instance should not do full refresh - cleanup instance and token management, because code is a mess and can be refactored + ## [Unreleased] - fixed bug when updating token and not admin - showing a yellow bar on non-default instance navigation (admin) diff --git a/packages/frontend/.gitignore b/packages/frontend/.gitignore @@ -3,3 +3,4 @@ /storybook-static /docs /single +/coverage diff --git a/packages/frontend/.storybook/main.js b/packages/frontend/.storybook/main.js @@ -14,10 +14,10 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - /** - * - * @author Sebastian Javier Marchano (sebasjm) - */ +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ module.exports = { @@ -34,7 +34,6 @@ module.exports = { // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' // You can change the configuration based on that. // 'PRODUCTION' is used when building the static version of storybook. - // Make whatever fine-grained changes you need config.module.rules.push({ test: [/\.pot?$/, /\.mo$/], diff --git a/packages/frontend/.storybook/preview.js b/packages/frontend/.storybook/preview.js @@ -47,6 +47,7 @@ export const globalTypes = { export const decorators = [ (Story, { globals }) => { + return <MessageProvider locale={globals.locale} onError="warn" messages={messages[globals.locale]} pathSep={null} onError={() => null}> <Story /> </MessageProvider> diff --git a/packages/frontend/package.json b/packages/frontend/package.json @@ -94,6 +94,7 @@ "messageformat-po-loader": "^0.3.0", "node-sass": "^5.0.0", "preact-cli": "^3.0.5", + "preact-render-to-json": "^3.6.6", "preact-render-to-string": "^5.1.16", "rimraf": "^3.0.2", "sass-loader": "10.1.1", @@ -107,6 +108,13 @@ "setupFiles": [ "<rootDir>/tests/__mocks__/browserMocks.ts", "<rootDir>/tests/__mocks__/setupTests.ts" - ] + ], + "collectCoverage": true, + "moduleNameMapper": { + "\\.(css|less)$": "identity-obj-proxy" + }, + "transform": { + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|po)$": "<rootDir>/tests/__mocks__/fileTransformer.js" + } } -} +} +\ No newline at end of file diff --git a/packages/frontend/src/InstanceRoutes.tsx b/packages/frontend/src/InstanceRoutes.tsx @@ -23,7 +23,7 @@ import { createHashHistory } from 'history'; import { Fragment, FunctionComponent, h, VNode } from 'preact'; import { useMessageTemplate } from 'preact-messages'; import { Route, route, Router } from 'preact-router'; -import { useCallback, useEffect, useMemo } from "preact/hooks"; +import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; import { Loading } from './components/exception/loading'; import { NotificationCard } from './components/menu'; import { InstanceContextProvider, useBackendContext } from './context/backend'; @@ -41,10 +41,11 @@ import TransferListPage from './paths/instance/transfers/list'; import InstanceUpdatePage, { Props as InstanceUpdatePageProps } from "./paths/instance/update"; import LoginPage from './paths/login'; import NotFoundPage from './paths/notfound'; - +import { Notification } from './utils/types'; export enum InstancePaths { // details = '/', + error = '/error', update = '/update', product_list = '/products', @@ -64,7 +65,7 @@ export enum InstancePaths { } // eslint-disable-next-line @typescript-eslint/no-empty-function -const noop = () => {} +const noop = () => { } export enum AdminPaths { list_instances = '/instances', @@ -83,6 +84,7 @@ export function InstanceRoutes({ id, admin }: Props): VNode { const { changeBackend, addTokenCleaner } = useBackendContext(); const cleaner = useCallback(() => { updateToken(undefined); }, [id]); const i18n = useMessageTemplate(''); + const [globalNotification, setGlobalNotification] = useState<Notification & {to:string} | undefined>(undefined) useEffect(() => { addTokenCleaner(cleaner); @@ -100,10 +102,15 @@ export function InstanceRoutes({ id, admin }: Props): VNode { const value = useMemo(() => ({ id, token, admin }), [id, token, admin]) - const LoginPageServerError = (error: HttpError) => <Fragment> - <NotificationCard notification={{ message: `Server reported a problem: HTTP status #${error.status}`, description: `Got message: ${error.message} from: ${error.info?.url}`, type: 'ERROR' }} /> - <LoginPage onConfirm={updateLoginStatus} /> - </Fragment> + const ServerErrorRedirectTo = (to: InstancePaths | AdminPaths) => (error: HttpError) => { + setGlobalNotification({ + message: `HTTP status #${error.status}: Server reported a problem`, + description: `Got message: "${error.message}" from: ${error.info?.url}`, + type: 'ERROR', + to + }) + return <Redirect to={to} /> + } const LoginPageAccessDenied = () => <Fragment> <NotificationCard notification={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} /> @@ -126,14 +133,23 @@ export function InstanceRoutes({ id, admin }: Props): VNode { } if (props) { return <Next {...props} /> - } - return <Next /> - + } + return <Next /> + } } return <InstanceContextProvider value={value}> - <Router history={createHashHistory()}> + + <NotificationCard notification={globalNotification} /> + + <Router history={createHashHistory()} onChange={(e) => { + const movingOutFromNotification = globalNotification && e.url !== globalNotification.to + if (movingOutFromNotification) { + setGlobalNotification(undefined) + } + }} > + <Route path="/" component={Redirect} to={InstancePaths.order_list} /> {/** @@ -144,7 +160,7 @@ export function InstanceRoutes({ id, admin }: Props): VNode { onCreate={() => { route(AdminPaths.new_instance) }} onUpdate={(id: string): void => { route(`/instance/${id}/update`); }} onUnauthorized={LoginPageAccessDenied} - onLoadError={LoginPageServerError} + onLoadError={ServerErrorRedirectTo(InstancePaths.error)} /> } @@ -159,7 +175,8 @@ export function InstanceRoutes({ id, admin }: Props): VNode { <Route path={AdminPaths.update_instance} component={AdminInstanceUpdatePage} onBack={() => route(AdminPaths.list_instances)} onConfirm={() => { route(AdminPaths.list_instances); }} - onUpdateError={noop} + onUpdateError={ServerErrorRedirectTo(AdminPaths.list_instances)} + onLoadError={ServerErrorRedirectTo(AdminPaths.list_instances)} onNotFound={NotFoundPage} /> } @@ -173,7 +190,7 @@ export function InstanceRoutes({ id, admin }: Props): VNode { onUpdateError={noop} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} onUnauthorized={LoginPageAccessDenied} - onLoadError={LoginPageServerError} + onLoadError={ServerErrorRedirectTo(InstancePaths.error)} /> {/** @@ -181,19 +198,22 @@ export function InstanceRoutes({ id, admin }: Props): VNode { */} <Route path={InstancePaths.product_list} component={ProductListPage} onUnauthorized={LoginPageAccessDenied} - onLoadError={LoginPageServerError} + onLoadError={ServerErrorRedirectTo(InstancePaths.update)} onCreate={() => { route(InstancePaths.product_new) }} onSelect={(id: string) => { route(InstancePaths.product_update.replace(':pid', id)) }} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} /> <Route path={InstancePaths.product_update} component={ProductUpdatePage} onUnauthorized={LoginPageAccessDenied} - onLoadError={LoginPageServerError} + onLoadError={ServerErrorRedirectTo(InstancePaths.product_list)} onConfirm={() => { route(InstancePaths.product_list); }} onBack={() => { route(InstancePaths.product_list); }} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} /> - <Route path={InstancePaths.product_new} component={ProductCreatePage} + <Route path={InstancePaths.product_new} + component={ProductCreatePage} + onConfirm={() => { route(InstancePaths.product_list); }} + onBack={() => { route(InstancePaths.product_list); }} /> {/** @@ -203,12 +223,12 @@ export function InstanceRoutes({ id, admin }: Props): VNode { onCreate={() => { route(InstancePaths.order_new) }} onSelect={(id: string) => { route(InstancePaths.order_details.replace(':oid', id)) }} onUnauthorized={LoginPageAccessDenied} - onLoadError={LoginPageServerError} + onLoadError={ServerErrorRedirectTo(InstancePaths.update)} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} /> <Route path={InstancePaths.order_details} component={OrderDetailsPage} onUnauthorized={LoginPageAccessDenied} - onLoadError={LoginPageServerError} + onLoadError={ServerErrorRedirectTo(InstancePaths.order_list)} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} onBack={() => { route(InstancePaths.order_list) }} /> @@ -222,7 +242,7 @@ export function InstanceRoutes({ id, admin }: Props): VNode { */} <Route path={InstancePaths.transfers_list} component={TransferListPage} onUnauthorized={LoginPageAccessDenied} - onLoadError={LoginPageServerError} + onLoadError={ServerErrorRedirectTo(InstancePaths.update)} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} /> diff --git a/packages/frontend/src/components/form/InputSearchProduct.tsx b/packages/frontend/src/components/form/InputSearchProduct.tsx @@ -24,6 +24,7 @@ import { InputWithAddon } from "./InputWithAddon"; import { FormErrors, FormProvider, useField } from "./Field"; import { useInstanceProducts } from "../../hooks/product"; import { useState } from "preact/hooks"; +import emptyImage from "../../assets/empty.png"; type Entity = MerchantBackend.Products.ProductDetail & WithId @@ -47,13 +48,13 @@ export function InputSearchProduct<T>({ selected, onChange }: Props) { return <article class="media"> <figure class="media-left"> <p class="image is-128x128"> - <img src="https://avatars.dicebear.com/v2/gridy/Ms.-Lora-Kiehn.svg" /> + <img src={selected.image ? selected.image : emptyImage} /> </p> </figure> <div class="media-content"> <div class="content"> - <p class="media-meta">Product #{selected.id}</p> - <p>{selected.description}</p> + <p class="media-meta">Product id: <b>{selected.id}</b></p> + <p>Description: {selected.description}</p> <div class="buttons is-right mt-5"> <button class="button" onClick={() => onChange(undefined)}>clear</button> </div> @@ -64,7 +65,7 @@ export function InputSearchProduct<T>({ selected, onChange }: Props) { return <FormProvider<ProductSearch> errors={errors} object={prodForm} valueHandler={setProdName} > - <InputWithAddon<ProductSearch> + <InputWithAddon<ProductSearch> name="name" addonBefore={<span class="icon" ><i class="mdi mdi-magnify" /></span>} > @@ -102,17 +103,30 @@ function ProductList({ name, onSelect }: ProductListProps) { </div> </div> } else { + const filtered = result.data.filter(p => re.test(p.id) || re.test(p.description)) products = <div class="dropdown-content"> - {result.data.filter(p => re.test(p.description)).map(p => ( - <div class="dropdown-item" onClick={() => onSelect(p)}> - {p.description} - </div> - ))} + {!filtered.length ? + <div class="dropdown-item" > + no results + </div> : + result.data.filter(p => re.test(p.id) || re.test(p.description)).map(p => ( + <div class="dropdown-item" onClick={() => onSelect(p)} style={{ cursor: 'pointer' }}> + <table> + <tr> + <td style={{width:32}}> + <div class="image" style={{minWidth:32}}><img src={p.image} style={{ width: 32, height: 32 }} /></div> + </td> + <td><b>{p.id}</b>: {p.description}</td> + </tr> + </table> + </div> + )) + } </div> } } return <div class="dropdown is-active"> - <div class="dropdown-menu" id="dropdown-menu" role="menu"> + <div class="dropdown-menu" id="dropdown-menu" role="menu" style={{minWidth: '20rem'}}> {products} </div> </div> diff --git a/packages/frontend/src/components/form/InputSecured.stories.tsx b/packages/frontend/src/components/form/InputSecured.stories.tsx @@ -28,13 +28,11 @@ export default { title: 'Fields/InputSecured', component: InputSecured, }; -{/* <FormProvider<Entity> errors={errors} object={value} valueHandler={valueHandler} > */ } -{/* <InputSecured<Entity> name="auth_token" /> */ } -type T = {auth_token: string | null} +type T = { auth_token: string | null } export const InitialValueEmpty = () => { - const [state, setState] = useState<Partial<T>>({auth_token: ''}) + const [state, setState] = useState<Partial<T>>({ auth_token: '' }) return <FormProvider<T> object={state} errors={{}} valueHandler={setState}> Initial value: '' <InputSecured<T> name="auth_token" /> @@ -42,14 +40,14 @@ export const InitialValueEmpty = () => { } export const InitialValueToken = () => { - const [state, setState] = useState<Partial<T>>({auth_token: 'token'}) + const [state, setState] = useState<Partial<T>>({ auth_token: 'token' }) return <FormProvider<T> object={state} errors={{}} valueHandler={setState}> <InputSecured<T> name="auth_token" /> </FormProvider> } export const InitialValueNull = () => { - const [state, setState] = useState<Partial<T>>({auth_token: null}) + const [state, setState] = useState<Partial<T>>({ auth_token: null }) return <FormProvider<T> object={state} errors={{}} valueHandler={setState}> Initial value: '' <InputSecured<T> name="auth_token" /> diff --git a/packages/frontend/src/components/form/InputStock.tsx b/packages/frontend/src/components/form/InputStock.tsx @@ -62,13 +62,15 @@ export function InputStock<T>({ name, readonly, alreadyExist }: Props<T>) { const [addedStock, setAddedStock] = useState<StockDelta>({ incoming: 0, lost: 0 }) useLayoutEffect(() => { - console.log(formValue) - - onChange({ - ...formValue, - current: (formValue?.current || 0) + addedStock.incoming, - lost: (formValue?.lost || 0) + addedStock.lost - } as any) + if (!formValue) { + onChange(undefined as any) + } else { + onChange({ + ...formValue, + current: (formValue?.current || 0) + addedStock.incoming, + lost: (formValue?.lost || 0) + addedStock.lost + } as any) + } }, [formValue, addedStock]) if (!formValue) { diff --git a/packages/frontend/src/components/product/ProductForm.tsx b/packages/frontend/src/components/product/ProductForm.tsx @@ -56,8 +56,8 @@ export function ProductForm({ onSubscribe, initial, alreadyExist, }: Props) { const submit = useCallback((): Entity | undefined => { try { - (alreadyExist ? updateSchema : createSchema).validateSync(value, { abortEarly: false }) - const stock:Stock = (value as any).stock; + (alreadyExist ? updateSchema : createSchema).validateSync(value, { abortEarly: false }) + const stock: Stock = (value as any).stock; delete (value as any).stock; if (!stock) { @@ -68,7 +68,6 @@ export function ProductForm({ onSubscribe, initial, alreadyExist, }: Props) { value.next_restock = stock.nextRestock instanceof Date ? { t_ms: stock.nextRestock.getTime() } : stock.nextRestock; value.address = stock.address; } - console.log(value) return value as MerchantBackend.Products.ProductDetail & { product_id: string } } catch (err) { const errors = err.inner as yup.ValidationError[] @@ -86,8 +85,8 @@ export function ProductForm({ onSubscribe, initial, alreadyExist, }: Props) { return <div> <FormProvider<Entity> name="product" errors={errors} object={value} valueHandler={valueHandler} > - {alreadyExist ? undefined : <InputWithAddon<Entity> name="product_id" addonBefore={`${backend.url}/product/`} /> } - + {alreadyExist ? undefined : <InputWithAddon<Entity> name="product_id" addonBefore={`${backend.url}/product/`} />} + <InputImage<Entity> name="image" /> <Input<Entity> name="description" inputType="multiline" /> <Input<Entity> name="unit" /> diff --git a/packages/frontend/src/components/product/ProductList.tsx b/packages/frontend/src/components/product/ProductList.tsx @@ -16,6 +16,7 @@ import { h, VNode } from "preact" import { MerchantBackend } from "../../declaration" import { multiplyPrice } from "../../utils/amount" +import emptyImage from "../../assets/empty.png"; interface Props { list: MerchantBackend.Product[], @@ -40,16 +41,18 @@ export function ProductList({ list, actions = [] }: Props): VNode { <tbody> {list.map((entry, index) => { return <tr> - <td>image</td> + <td> + <img style={{ height: 32, width: 32 }} src={entry.image ? entry.image : emptyImage} /> + </td> <td >{entry.description}</td> <td > - {entry.quantity} {entry.unit} + {entry.quantity === 0 ? '--' : `${entry.quantity} ${entry.unit}`} </td> <td >{entry.price}</td> <td >{multiplyPrice(entry.price, entry.quantity)}</td> <td class="is-actions-cell right-sticky"> {actions.map(a => { - <div class="buttons is-right"> + return <div class="buttons is-right"> <button class="button is-small is-danger jb-modal" type="button" onClick={() => a.handler(entry, index)}> {a.name} </button> diff --git a/packages/frontend/src/hooks/backend.ts b/packages/frontend/src/hooks/backend.ts @@ -27,7 +27,6 @@ import { useEffect, useState } from 'preact/hooks'; export function mutateAll(re: RegExp) { cache.keys().filter(key => { - // console.log(key, re.test(key)) return re.test(key) }).forEach(key => mutate(key, null)) } @@ -140,7 +139,7 @@ function buildRequestOk<T>(res: any, url: string, hasToken: boolean): HttpRespon function buildRequestFailed(ex: AxiosError<MerchantBackend.ErrorDetail>, url: string, hasToken: boolean): HttpResponseClientError | HttpResponseServerError | HttpResponseUnexpectedError { const status = ex.response?.status - + const info = { data: ex.request?.data, params: ex.request?.params, @@ -165,7 +164,7 @@ function buildRequestFailed(ex: AxiosError<MerchantBackend.ErrorDetail>, url: st serverError: true, status, info, - message: ex.response?.data?.hint || ex.message, + message: `${ex.response?.data?.hint} (code ${ex.response?.data?.code})` || ex.message, error: ex.response?.data } return error; @@ -181,19 +180,34 @@ function buildRequestFailed(ex: AxiosError<MerchantBackend.ErrorDetail>, url: st return error } + export async function request<T>(url: string, options: RequestOptions = {}): Promise<HttpResponseOk<T>> { const headers = options.token ? { Authorization: `Bearer ${options.token}` } : undefined + // use this when simulating an error message from the server + // if (url.match(/\/orders\/.*/)) { + // const pepe: any = { + // message: 'qweqwe', + // request: { + // data: { + + // }, + // params: { + + // }, + // }, + // response: { + // data: { + // hint: "This part is the hint", + // code: 2008, + // }, + // status: 500, + // } + // }; + // throw buildRequestFailed(pepe, url, !!options.token); + // } try { - // // http://localhost:9966/instances/blog/private/instances - // // Hack, endpoint should respond 404 - // if (/^\/instances\/[^/]*\/private\/instances$/.test(new URL(url).pathname)) { - // console.warn(`HACK: Not going to query ${url}, instead return 404`) - // throw ({ response: { status: 404 }, message: 'not found' }) - // } - - const res = await axios({ url, responseType: 'json', diff --git a/packages/frontend/src/messages/en.po b/packages/frontend/src/messages/en.po @@ -376,6 +376,9 @@ msgstr "Profit" msgid "fields.product.stock.label" msgstr "Stock" +msgid "fields.product.quantity.label" +msgstr "Quantity" + msgid "fields.product.sold.label" msgstr "Sold" diff --git a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx @@ -267,13 +267,9 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { {productList.length > 0 && <ProductList list={productList} actions={[{ - name: 'Update', handler: (e, index) => { - removeFromNewProduct(index); - setEditingProduct(e); - } - }, { name: 'Remove', handler: (e, index) => { removeFromNewProduct(index); + setEditingProduct(e); } }]} /> diff --git a/packages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx b/packages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx @@ -32,7 +32,8 @@ interface Props { } export function InventoryProductForm({ currentProducts, onAddProduct }: Props): VNode { - const [state, setState] = useState<Partial<Form>>({}) + const initialState = { quantity: 1 } + const [state, setState] = useState<Partial<Form>>(initialState) const [errors, setErrors] = useState<FormErrors<Form>>({}) const submit = (): void => { @@ -40,27 +41,31 @@ export function InventoryProductForm({ currentProducts, onAddProduct }: Props): setErrors({ product: { message: 'select a product first' } }); return; } - if (!state.quantity || state.quantity <= 0) { - setErrors({ quantity: { message: 'should be greater than 0' } }); - return; - } - const currentStock = state.product.total_stock - state.product.total_lost - state.product.total_sold - const p = currentProducts[state.product.id] - if (p) { - if (state.quantity + p.quantity > currentStock) { - setErrors({ quantity: { message: `cannot be greater than current stock and quantity previously added. max: ${currentStock - p.quantity}` } }); - return; - } - onAddProduct(state.product, state.quantity + p.quantity) + if (state.product.total_stock === -1) { + onAddProduct(state.product, 1) } else { - if (state.quantity > currentStock) { - setErrors({ quantity: { message: `cannot be greater than current stock ${currentStock}` } }); + if (!state.quantity || state.quantity <= 0) { + setErrors({ quantity: { message: 'should be greater than 0' } }); return; } - onAddProduct(state.product, state.quantity) + const currentStock = state.product.total_stock - state.product.total_lost - state.product.total_sold + const p = currentProducts[state.product.id] + if (p) { + if (state.quantity + p.quantity > currentStock) { + setErrors({ quantity: { message: `cannot be greater than current stock and quantity previously added. max: ${currentStock - p.quantity}` } }); + return; + } + onAddProduct(state.product, state.quantity + p.quantity) + } else { + if (state.quantity > currentStock) { + setErrors({ quantity: { message: `cannot be greater than current stock ${currentStock}` } }); + return; + } + onAddProduct(state.product, state.quantity) + } } - setState({}) + setState(initialState) } return <FormProvider<Form> errors={errors} object={state} valueHandler={setState}> diff --git a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx b/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx @@ -14,11 +14,21 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ import { Fragment, h, VNode } from "preact"; -import { useEffect, useState } from "preact/hooks"; +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 { InputImage } from "../../../../components/form/InputImage"; +import { InputNumber } from "../../../../components/form/InputNumber"; +import { Stock } from "../../../../components/form/InputStock"; +import { InputTaxes } from "../../../../components/form/InputTaxes"; import { ConfirmModal } from "../../../../components/modal"; -import { ProductForm } from "../../../../components/product/ProductForm"; import { MerchantBackend } from "../../../../declaration"; import { useListener } from "../../../../hooks"; +import { + NonInventoryProductSchema as schema +} from '../../../../schemas'; +import * as yup from 'yup'; type Entity = MerchantBackend.Product @@ -35,11 +45,11 @@ export function NonInventoryProductFrom({ value, onAddProduct }: Props): VNode { setShowCreateProduct(editing) }, [editing]) - const [submitForm, addFormSubmitter] = useListener<Partial<MerchantBackend.Products.ProductAddDetail> | undefined>((result) => { + const [submitForm, addFormSubmitter] = useListener<Partial<MerchantBackend.Product> | undefined>((result) => { if (result) { setShowCreateProduct(false) onAddProduct({ - quantity: result.total_stock || 0, + quantity: result.quantity || 0, taxes: result.taxes || [], description: result.description || '', image: result.image || '', @@ -49,18 +59,72 @@ export function NonInventoryProductFrom({ value, onAddProduct }: Props): VNode { } }) - const initial: Partial<MerchantBackend.Products.ProductAddDetail> = { - ...value, - total_stock: value?.quantity || 0, - taxes: [] - } - return <Fragment> <div class="buttons"> <button class="button is-success" onClick={() => setShowCreateProduct(true)} >add new product</button> </div> {showCreateProduct && <ConfirmModal active onCancel={() => setShowCreateProduct(false)} onConfirm={submitForm}> - <ProductForm initial={initial} onSubscribe={addFormSubmitter} /> + <ProductForm initial={value} onSubscribe={addFormSubmitter} /> </ConfirmModal>} </Fragment> -} -\ No newline at end of file +} + +interface ProductProps { + onSubscribe: (c: () => Entity | undefined) => void; + initial?: Partial<Entity>; +} + +interface NonInventoryProduct { + quantity: number; + description: string; + unit: string; + price: string; + image: string; + taxes: MerchantBackend.Tax[]; +} + +export function ProductForm({ onSubscribe, initial }: ProductProps) { + const [value, valueHandler] = useState<Partial<NonInventoryProduct>>({ + taxes: [], + ...initial, + }) + const [errors, setErrors] = useState<FormErrors<NonInventoryProduct>>({}) + + 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]: { type: cur.type, params: cur.params, message: cur.message } }), {}) + setErrors(pathMessages) + } + }, [value]) + + useEffect(() => { + onSubscribe(submit) + }, [submit]) + + return <div> + <FormProvider<NonInventoryProduct> name="product" errors={errors} object={value} valueHandler={valueHandler} > + + <InputImage<NonInventoryProduct> name="image" /> + <Input<NonInventoryProduct> name="description" inputType="multiline" /> + <Input<NonInventoryProduct> name="unit" /> + <InputCurrency<NonInventoryProduct> name="price" /> + + <InputNumber<NonInventoryProduct> name="quantity" /> + + <InputTaxes<NonInventoryProduct> name="taxes" /> + + </FormProvider> + </div> +} diff --git a/packages/frontend/src/paths/instance/orders/create/index.tsx b/packages/frontend/src/paths/instance/orders/create/index.tsx @@ -39,11 +39,7 @@ interface Props { export default function OrderCreate({ onConfirm, onBack }: Props): VNode { const { createOrder } = useOrderAPI() const [notif, setNotif] = useState<Notification | undefined>(undefined) - const [createdOk, setCreatedOk] = useState<Entity | undefined>(undefined); - if (createdOk) { - return <OrderCreatedSuccessfully entity={createdOk} onConfirm={onConfirm} onCreateAnother={() => setCreatedOk(undefined)} /> - } return <Fragment> <NotificationCard notification={{ @@ -57,9 +53,7 @@ export default function OrderCreate({ onConfirm, onBack }: Props): VNode { <CreatePage onBack={onBack} onCreate={(request: MerchantBackend.Orders.PostOrderRequest) => { - createOrder(request).then((response) => { - setCreatedOk({ request, response: response.data }) - }).catch((error) => { + createOrder(request).then(onConfirm).catch((error) => { setNotif({ message: 'could not create order', type: "ERROR", diff --git a/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx b/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx @@ -49,11 +49,6 @@ type Claimed = MerchantBackend.Orders.CheckPaymentClaimedResponse function ClaimedPage({ id, order }: { id: string; order: MerchantBackend.Orders.CheckPaymentClaimedResponse }) { const events: Event[] = [] events.push({ - when: new Date(), - description: 'now', - type: 'now' - }) - events.push({ when: new Date(order.contract_terms.timestamp.t_ms), description: 'order created', type: 'start' @@ -79,7 +74,6 @@ function ClaimedPage({ id, order }: { id: string; order: MerchantBackend.Orders. type: 'delivery' }) - events.sort((a, b) => a.when.getTime() - b.when.getTime()) const [value, valueHandler] = useState<Partial<Claimed>>(order) return <div> @@ -154,15 +148,17 @@ function ClaimedPage({ id, order }: { id: string; order: MerchantBackend.Orders. </div> </section> - <section class="section"> - <div class="columns"> - <div class="column is-12" > - <div class="title">Product list</div> - <ProductList list={order.contract_terms.products} /> + {order.contract_terms.products.length > 0 && + <section class="section"> + <div class="columns"> + <div class="column is-12" > + <div class="title">Product list</div> + <ProductList list={order.contract_terms.products} /> + </div> + <div class="column" /> </div> - <div class="column" /> - </div> - </section> + </section> + } </div> <div class="column" /> @@ -173,11 +169,6 @@ function ClaimedPage({ id, order }: { id: string; order: MerchantBackend.Orders. function PaidPage({ id, order, onRefund }: { id: string; order: MerchantBackend.Orders.CheckPaymentPaidResponse, onRefund: (id: string) => void }) { const events: Event[] = [] events.push({ - when: new Date(), - description: 'now', - type: 'now' - }) - events.push({ when: new Date(order.contract_terms.timestamp.t_ms), description: 'order created', type: 'start' @@ -223,7 +214,6 @@ function PaidPage({ id, order, onRefund }: { id: string; order: MerchantBackend. type: 'wired', }) - events.sort((a, b) => a.when.getTime() - b.when.getTime()) const [value, valueHandler] = useState<Partial<Paid>>(order) const refundable = new Date().getTime() < order.contract_terms.refund_deadline.t_ms diff --git a/packages/frontend/src/paths/instance/orders/details/Timeline.tsx b/packages/frontend/src/paths/instance/orders/details/Timeline.tsx @@ -15,12 +15,37 @@ */ import { format } from "date-fns"; import { h } from "preact"; +import { useEffect, useState } from "preact/hooks"; interface Props { events: Event[] } -export function Timeline({ events }: Props) { +export function Timeline({ events:e }: Props) { + const events = [...e] + events.push({ + when: new Date(), + description: 'now', + type: 'now' + }) + + events.sort((a, b) => a.when.getTime() - b.when.getTime()) + + const [state, setState] = useState(events) + useEffect(() => { + const handle = setTimeout(() => { + const eventsWithoutNow = state.filter(e => e.type !== 'now') + eventsWithoutNow.push({ + when: new Date(), + description: 'now', + type: 'now' + }) + setState(eventsWithoutNow) + },1000) + return () => { + clearTimeout(handle) + } + }) return <div class="timeline"> {events.map(e => { return <div class="timeline-item"> diff --git a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx @@ -25,7 +25,7 @@ import { ProductForm } from "../../../../components/product/ProductForm"; import { MerchantBackend } from "../../../../declaration"; import { useListener } from "../../../../hooks"; -type Entity = MerchantBackend.Products.ProductDetail & { product_id: string} +type Entity = MerchantBackend.Products.ProductAddDetail & { product_id: string} interface Props { onCreate: (d: Entity) => void; diff --git a/packages/frontend/src/paths/instance/products/create/index.tsx b/packages/frontend/src/paths/instance/products/create/index.tsx @@ -25,7 +25,6 @@ import { NotificationCard } from '../../../../components/menu'; import { MerchantBackend } from '../../../../declaration'; import { useProductAPI } from '../../../../hooks/product'; import { Notification } from '../../../../utils/types'; -import { CreatedSuccessfully } from './CreatedSuccessfully'; import { CreatePage } from './CreatePage'; export type Entity = MerchantBackend.Products.ProductAddDetail @@ -36,20 +35,13 @@ interface Props { export default function CreateProduct({ onConfirm, onBack }: Props): VNode { const { createProduct } = useProductAPI() const [notif, setNotif] = useState<Notification | undefined>(undefined) - const [createdOk, setCreatedOk] = useState<Entity | undefined>(undefined); - - if (createdOk) { - return <CreatedSuccessfully entity={createdOk} onConfirm={onConfirm} onCreateAnother={() => setCreatedOk(undefined)} /> - } return <Fragment> <NotificationCard notification={notif} /> <CreatePage onBack={onBack} - onCreate={(request: MerchantBackend.Products.ProductDetail & { product_id: string}) => { - createProduct(request).then(() => { - setCreatedOk(request) - }).catch((error) => { + onCreate={(request: MerchantBackend.Products.ProductAddDetail) => { + createProduct(request).then(() => onConfirm()).catch((error) => { setNotif({ message: 'could not create product', type: "ERROR", diff --git a/packages/frontend/src/paths/instance/products/list/Table.tsx b/packages/frontend/src/paths/instance/products/list/Table.tsx @@ -27,6 +27,7 @@ import { FormErrors, FormProvider } from "../../../../components/form/Field" import { InputCurrency } from "../../../../components/form/InputCurrency" import { InputNumber } from "../../../../components/form/InputNumber" import { MerchantBackend, WithId } from "../../../../declaration" +import emptyImage from "../../../../assets/empty.png"; type Entity = MerchantBackend.Products.ProductDetail & WithId @@ -108,7 +109,9 @@ function Table({ rowSelection, rowSelectionHandler, instances, onSelect, onUpdat } return <Fragment><tr> - <td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} ><img src={i.image} style={{ border: 'solid black 1px', width: 100, height: 100 }} /></td> + <td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} > + <img src={i.image ? i.image : emptyImage} style={{ border: 'solid black 1px', width: 100, height: 100 }} /> + </td> <td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >{i.description}</td> <td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >{i.price} / {i.unit}</td> <td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >{sum(i.taxes)}</td> @@ -150,7 +153,30 @@ interface FastProductUpdate { price: string; } -function FastProductUpdateForm({ product, onUpdate, onCancel }: FastProductUpdateFormProps) { +function FastProductWithInfiniteStockUpdateForm({ product, onUpdate, onCancel }: FastProductUpdateFormProps) { + const [value, valueHandler] = useState<{price:string}>({price: product.price}) + + return <Fragment> + <FormProvider<FastProductUpdate> name="added" object={value} valueHandler={valueHandler as any} > + <InputCurrency<FastProductUpdate> name="price" /> + </FormProvider> + + <div class="buttons is-right mt-5"> + <button class="button" onClick={onCancel} ><Message id="Cancel" /></button> + <button class="button is-info" onClick={() => { + + return onUpdate({ + ...product, + price: value.price, + }) + + }}><Message id="Confirm" /></button> + </div> + + </Fragment> +} + +function FastProductWithManagedStockUpdateForm({ product, onUpdate, onCancel }: FastProductUpdateFormProps) { const [value, valueHandler] = useState<FastProductUpdate>({ incoming: 0, lost: 0, price: product.price }) @@ -170,7 +196,6 @@ function FastProductUpdateForm({ product, onUpdate, onCancel }: FastProductUpdat ) const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined) - const isDirty = Object.keys(value).some(k => !!(value as any)[k]) return <Fragment> <FormProvider<FastProductUpdate> name="added" errors={errors} object={value} valueHandler={valueHandler as any} > @@ -189,12 +214,12 @@ function FastProductUpdateForm({ product, onUpdate, onCancel }: FastProductUpdat <div class="buttons is-right mt-5"> <button class="button" onClick={onCancel} ><Message id="Cancel" /></button> - <button class="button is-info" disabled={hasErrors || !isDirty} onClick={() => { + <button class="button is-info" disabled={hasErrors} onClick={() => { return onUpdate({ ...product, total_stock: product.total_stock + value.incoming, - total_lost: product.total_lost+ value.lost, + total_lost: product.total_lost + value.lost, price: value.price, }) @@ -202,7 +227,12 @@ function FastProductUpdateForm({ product, onUpdate, onCancel }: FastProductUpdat </div> </Fragment> +} +function FastProductUpdateForm(props: FastProductUpdateFormProps) { + return props.product.total_stock === -1 ? + <FastProductWithInfiniteStockUpdateForm {...props} /> : + <FastProductWithManagedStockUpdateForm {...props} /> } function EmptyTable(): VNode { diff --git a/packages/frontend/src/paths/instance/update/index.tsx b/packages/frontend/src/paths/instance/update/index.tsx @@ -27,7 +27,7 @@ export interface Props { onUnauthorized: () => VNode; onNotFound: () => VNode; onLoadError: (e: HttpError) => VNode; - onUpdateError: (e: Error) => void; + onUpdateError: (e: HttpError) => void; } diff --git a/packages/frontend/src/schemas/index.ts b/packages/frontend/src/schemas/index.ts @@ -188,4 +188,13 @@ export const TaxSchema = yup.object().shape({ tax: yup.string() .required() .test('amount', 'the amount is not valid', currencyWithAmountIsValid), -}) -\ No newline at end of file +}) + +export const NonInventoryProductSchema = yup.object().shape({ + quantity: yup.number().required().positive(), + description: yup.string().required(), + unit: yup.string().ensure().required(), + price: yup.string() + .required() + .test('amount', 'the amount is not valid', currencyWithAmountIsValid), +}) diff --git a/packages/frontend/tests/__mocks__/fileTransformer.js b/packages/frontend/tests/__mocks__/fileTransformer.js @@ -0,0 +1,9 @@ +// fileTransformer.js +const path = require('path'); + +module.exports = { + process(src, filename, config, options) { + return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; + }, +}; + diff --git a/packages/frontend/tests/stories.test.tsx b/packages/frontend/tests/stories.test.tsx @@ -0,0 +1,37 @@ +import { mount } from 'enzyme'; +import { h } from 'preact'; +import * as ctx from '../src/context/backend'; + +const fs = require('fs'); + +function getFiles (dir: string, files_: string[] = []){ + var files = fs.readdirSync(dir); + for (var i in files){ + var name = dir + '/' + files[i]; + if (fs.statSync(name).isDirectory()){ + getFiles(name, files_); + } else { + files_.push(name); + } + } + return files_; +} + +const re = RegExp('.*\.stories.tsx') + +it('render every story', () => { + jest.spyOn(ctx, 'useConfigContext').mockImplementation(() => ({ version: '1.0.0', currency: 'EUR' })); + + getFiles('./src').filter(f => re.test(f)).map(f => { + const s = require('../'+f) + delete s.default + Object.keys(s).forEach(k => { + const Component = s[k]; + try { + mount(<Component {...Component.args}/>); + } catch (error) { + console.error(`problem rendering ${f} example ${k}`, error) + } + }) + }) +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml @@ -54,6 +54,7 @@ importers: messageformat-po-loader: 0.3.0_messageformat@2.3.0 node-sass: 5.0.0 preact-cli: 3.0.5_5ba117d350dffefc029551565eae09b5 + preact-render-to-json: 3.6.6_preact@10.5.13 preact-render-to-string: 5.1.16_preact@10.5.13 rimraf: 3.0.2 sass-loader: 10.1.1_node-sass@5.0.0 @@ -109,6 +110,7 @@ importers: preact: ^10.5.13 preact-cli: ^3.0.5 preact-messages: workspace:* + preact-render-to-json: ^3.6.6 preact-render-to-string: ^5.1.16 preact-router: ^3.2.1 rimraf: ^3.0.2 @@ -153,6 +155,10 @@ packages: dev: true resolution: integrity: sha512-BwKEkO+2a67DcFeS3RLl0Z3Gs2OvdXewuWjc1Hfokhb5eQWP9YRYH1/+VrVZvql2CfjOiNGqSAFOYt4lsqTHzg== + /@babel/compat-data/7.13.15: + dev: true + resolution: + integrity: sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA== /@babel/core/7.12.9: dependencies: '@babel/code-frame': 7.12.13 @@ -199,6 +205,36 @@ packages: node: '>=6.9.0' resolution: integrity: sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw== + /@babel/core/7.13.16: + dependencies: + '@babel/code-frame': 7.12.13 + '@babel/generator': 7.13.16 + '@babel/helper-compilation-targets': 7.13.16_@babel+core@7.13.16 + '@babel/helper-module-transforms': 7.13.14 + '@babel/helpers': 7.13.17 + '@babel/parser': 7.13.16 + '@babel/template': 7.12.13 + '@babel/traverse': 7.13.17 + '@babel/types': 7.13.17 + convert-source-map: 1.7.0 + debug: 4.3.1 + gensync: 1.0.0-beta.2 + json5: 2.2.0 + semver: 6.3.0 + source-map: 0.5.7 + dev: true + engines: + node: '>=6.9.0' + resolution: + integrity: sha512-sXHpixBiWWFti0AV2Zq7avpTasr6sIAu7Y396c608541qAU2ui4a193m0KSQmfPSKFZLnQ3cvlKDOm3XkuXm3Q== + /@babel/generator/7.13.16: + dependencies: + '@babel/types': 7.13.17 + jsesc: 2.5.2 + source-map: 0.5.7 + dev: true + resolution: + integrity: sha512-grBBR75UnKOcUWMp8WoDxNsWCFl//XCK6HWTrBQKTr5SV9f5g0pNOjdyzi/DTBv12S9GnYPInIXQBTky7OXEMg== /@babel/generator/7.13.9: dependencies: '@babel/types': 7.13.0 @@ -232,6 +268,18 @@ packages: '@babel/core': ^7.0.0 resolution: integrity: sha512-/Xju7Qg1GQO4mHZ/Kcs6Au7gfafgZnwm+a7sy/ow/tV1sHeraRUHbjdat8/UvDor4Tez+siGKDk6zIKtCPKVJA== + /@babel/helper-compilation-targets/7.13.16_@babel+core@7.13.16: + dependencies: + '@babel/compat-data': 7.13.15 + '@babel/core': 7.13.16 + '@babel/helper-validator-option': 7.12.17 + browserslist: 4.16.5 + semver: 6.3.0 + dev: true + peerDependencies: + '@babel/core': ^7.0.0 + resolution: + integrity: sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA== /@babel/helper-create-class-features-plugin/7.13.11_@babel+core@7.13.10: dependencies: '@babel/core': 7.13.10 @@ -304,12 +352,24 @@ packages: dev: true resolution: integrity: sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ== + /@babel/helper-member-expression-to-functions/7.13.12: + dependencies: + '@babel/types': 7.13.17 + dev: true + resolution: + integrity: sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw== /@babel/helper-module-imports/7.12.13: dependencies: '@babel/types': 7.13.0 dev: true resolution: integrity: sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g== + /@babel/helper-module-imports/7.13.12: + dependencies: + '@babel/types': 7.13.17 + dev: true + resolution: + integrity: sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== /@babel/helper-module-transforms/7.13.0: dependencies: '@babel/helper-module-imports': 7.12.13 @@ -324,6 +384,19 @@ packages: dev: true resolution: integrity: sha512-Ls8/VBwH577+pw7Ku1QkUWIyRRNHpYlts7+qSqBBFCW3I8QteB9DxfcZ5YJpOwH6Ihe/wn8ch7fMGOP1OhEIvw== + /@babel/helper-module-transforms/7.13.14: + dependencies: + '@babel/helper-module-imports': 7.13.12 + '@babel/helper-replace-supers': 7.13.12 + '@babel/helper-simple-access': 7.13.12 + '@babel/helper-split-export-declaration': 7.12.13 + '@babel/helper-validator-identifier': 7.12.11 + '@babel/template': 7.12.13 + '@babel/traverse': 7.13.17 + '@babel/types': 7.13.17 + dev: true + resolution: + integrity: sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g== /@babel/helper-optimise-call-expression/7.12.13: dependencies: '@babel/types': 7.13.0 @@ -355,12 +428,27 @@ packages: dev: true resolution: integrity: sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw== + /@babel/helper-replace-supers/7.13.12: + dependencies: + '@babel/helper-member-expression-to-functions': 7.13.12 + '@babel/helper-optimise-call-expression': 7.12.13 + '@babel/traverse': 7.13.17 + '@babel/types': 7.13.17 + dev: true + resolution: + integrity: sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw== /@babel/helper-simple-access/7.12.13: dependencies: '@babel/types': 7.13.0 dev: true resolution: integrity: sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA== + /@babel/helper-simple-access/7.13.12: + dependencies: + '@babel/types': 7.13.17 + dev: true + resolution: + integrity: sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA== /@babel/helper-skip-transparent-expression-wrappers/7.12.1: dependencies: '@babel/types': 7.13.0 @@ -398,6 +486,14 @@ packages: dev: true resolution: integrity: sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ== + /@babel/helpers/7.13.17: + dependencies: + '@babel/template': 7.12.13 + '@babel/traverse': 7.13.17 + '@babel/types': 7.13.17 + dev: true + resolution: + integrity: sha512-Eal4Gce4kGijo1/TGJdqp3WuhllaMLSrW6XcL0ulyUAQOuxHcCafZE8KHg9857gcTehsm/v7RcOx2+jp0Ryjsg== /@babel/highlight/7.13.10: dependencies: '@babel/helper-validator-identifier': 7.12.11 @@ -413,6 +509,13 @@ packages: hasBin: true resolution: integrity: sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q== + /@babel/parser/7.13.16: + dev: true + engines: + node: '>=6.0.0' + hasBin: true + resolution: + integrity: sha512-6bAg36mCwuqLO0hbR+z7PHuqWiCeP7Dzg73OpQwsAB1Eb8HnGEz5xYBzCfbu+YjoaJsJs+qheDxVAuqbt3ILEw== /@babel/plugin-proposal-async-generator-functions/7.13.8_@babel+core@7.13.10: dependencies: '@babel/core': 7.13.10 @@ -1279,18 +1382,24 @@ packages: '@babel/core': ^7.0.0-0 resolution: integrity: sha512-yCVtABcmvQjRsX2elcZFUV5Q5kDDpHdtXKKku22hNDma60lYuhKmtp1ykZ/okRCPLT2bR5S+cA1kvtBdAFlDTQ== - /@babel/runtime-corejs3/7.13.10: + /@babel/runtime-corejs3/7.13.17: dependencies: - core-js-pure: 3.9.1 + core-js-pure: 3.11.0 regenerator-runtime: 0.13.7 dev: true resolution: - integrity: sha512-x/XYVQ1h684pp1mJwOV4CyvqZXqbc8CMsMGUnAbuc82ZCdv1U63w5RSUzgDSXQHG5Rps/kiksH6g2D5BuaKyXg== + integrity: sha512-RGXINY1YvduBlGrP+vHjJqd/nK7JVpfM4rmZLGMx77WoL3sMrhheA0qxii9VNn1VHnxJLEyxmvCB+Wqc+x/FMw== /@babel/runtime/7.13.10: dependencies: regenerator-runtime: 0.13.7 resolution: integrity: sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== + /@babel/runtime/7.13.17: + dependencies: + regenerator-runtime: 0.13.7 + dev: true + resolution: + integrity: sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA== /@babel/template/7.12.13: dependencies: '@babel/code-frame': 7.12.13 @@ -1313,6 +1422,19 @@ packages: dev: true resolution: integrity: sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ== + /@babel/traverse/7.13.17: + dependencies: + '@babel/code-frame': 7.12.13 + '@babel/generator': 7.13.16 + '@babel/helper-function-name': 7.12.13 + '@babel/helper-split-export-declaration': 7.12.13 + '@babel/parser': 7.13.16 + '@babel/types': 7.13.17 + debug: 4.3.1 + globals: 11.12.0 + dev: true + resolution: + integrity: sha512-BMnZn0R+X6ayqm3C3To7o1j7Q020gWdqdyP50KEoVqaCO2c/Im7sYZSmVgvefp8TTMQ+9CtwuBp0Z1CZ8V3Pvg== /@babel/types/7.13.0: dependencies: '@babel/helper-validator-identifier': 7.12.11 @@ -1321,6 +1443,13 @@ packages: dev: true resolution: integrity: sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA== + /@babel/types/7.13.17: + dependencies: + '@babel/helper-validator-identifier': 7.12.11 + to-fast-properties: 2.0.0 + dev: true + resolution: + integrity: sha512-RawydLgxbOPDlTLJNtoIypwdmAy//uQIzlKt2+iBiJaRlVuI6QLUxVAyWGNfOzp8Yu4L4lLIacoCyTNtpb4wiA== /@base2/pretty-print-object/1.0.0: dev: true resolution: @@ -1331,7 +1460,7 @@ packages: integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== /@cnakazawa/watch/1.0.4: dependencies: - exec-sh: 0.3.4 + exec-sh: 0.3.6 minimist: 1.2.5 dev: true engines: @@ -1719,17 +1848,17 @@ packages: integrity: sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== /@jest/transform/26.6.2: dependencies: - '@babel/core': 7.13.10 + '@babel/core': 7.13.16 '@jest/types': 26.6.2 babel-plugin-istanbul: 6.0.0 - chalk: 4.1.0 + chalk: 4.1.1 convert-source-map: 1.7.0 fast-json-stable-stringify: 2.1.0 graceful-fs: 4.2.6 jest-haste-map: 26.6.2 jest-regex-util: 26.0.0 jest-util: 26.6.2 - micromatch: 4.0.2 + micromatch: 4.0.4 pirates: 4.0.1 slash: 3.0.0 source-map: 0.6.1 @@ -1743,9 +1872,9 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.3 '@types/istanbul-reports': 3.0.0 - '@types/node': 14.14.35 + '@types/node': 15.0.0 '@types/yargs': 15.0.13 - chalk: 4.1.0 + chalk: 4.1.1 dev: true engines: node: '>= 10.14.2' @@ -2962,13 +3091,13 @@ packages: node: '>=6' resolution: integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - /@testing-library/dom/7.30.0: + /@testing-library/dom/7.30.4: dependencies: '@babel/code-frame': 7.12.13 - '@babel/runtime': 7.13.10 + '@babel/runtime': 7.13.17 '@types/aria-query': 4.2.1 aria-query: 4.2.2 - chalk: 4.1.0 + chalk: 4.1.1 dom-accessibility-api: 0.5.4 lz-string: 1.4.4 pretty-format: 26.6.2 @@ -2976,7 +3105,7 @@ packages: engines: node: '>=10' resolution: - integrity: sha512-v4GzWtltaiDE0yRikLlcLAfEiiK8+ptu6OuuIebm9GdC2XlZTNDPGEfM2UkEtnH7hr9TRq2sivT5EA9P1Oy7bw== + integrity: sha512-GObDVMaI4ARrZEXaRy4moolNAxWPKvEYNV/fa6Uc2eAzR/t4otS6A7EhrntPBIQLeehL9DbVhscvvv7gd6hWqA== /@testing-library/preact-hooks/1.1.0_8a3b8354086a0a31d950b2aa8b26d524: dependencies: '@testing-library/preact': 2.0.1_preact@10.5.13 @@ -2989,7 +3118,7 @@ packages: integrity: sha512-+JIor+NsOHkK3oIrwMDGKGHXTN0JJi462dBJlj4FNbGaDPTlctE6eu2ranWQirh7/FJMkWfzQCP+tk7jmY8ZrQ== /@testing-library/preact/2.0.1_preact@10.5.13: dependencies: - '@testing-library/dom': 7.30.0 + '@testing-library/dom': 7.30.4 preact: 10.5.13 dev: true engines: @@ -3031,7 +3160,7 @@ packages: integrity: sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A== /@types/babel__traverse/7.11.1: dependencies: - '@babel/types': 7.13.0 + '@babel/types': 7.13.17 dev: true resolution: integrity: sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw== @@ -3066,14 +3195,14 @@ packages: integrity: sha1-pYHWiDR+EOUN18F9byiAoQNUMZ0= /@types/glob/7.1.3: dependencies: - '@types/minimatch': 3.0.3 - '@types/node': 14.14.35 + '@types/minimatch': 3.0.4 + '@types/node': 15.0.0 dev: true resolution: integrity: sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== /@types/graceful-fs/4.1.5: dependencies: - '@types/node': 14.14.35 + '@types/node': 15.0.0 dev: true resolution: integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== @@ -3144,10 +3273,10 @@ packages: dev: true resolution: integrity: sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw== - /@types/minimatch/3.0.3: + /@types/minimatch/3.0.4: dev: true resolution: - integrity: sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + integrity: sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== /@types/node-fetch/2.5.8: dependencies: '@types/node': 14.14.35 @@ -3159,6 +3288,14 @@ packages: dev: true resolution: integrity: sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag== + /@types/node/14.14.42: + dev: true + resolution: + integrity: sha512-88QoObqn9WYIUMRzOx92GmSHmU3JCyukC2ulEv8tFjUG9VeV2FQ/cA7VQ1gi+rB/+gBMVvzVFcTnz8RdMDVIWw== + /@types/node/15.0.0: + dev: true + resolution: + integrity: sha512-YN1d+ae2MCb4U0mMa+Zlb5lWTdpFShbAj5nmte6lel27waMMBfivrm0prC16p/Di3DyTrmerrYUT8/145HXxVw== /@types/normalize-package-data/2.4.0: dev: true resolution: @@ -3266,7 +3403,7 @@ packages: integrity: sha512-Fx+NpfOO0CpeYX2g9bkvX8O5qh9wrU1sOF4g8sft4Mu7z+qfe387YlyY8w8daDyDsKY5vUxM0yxkAYnbkRbZEw== /@types/webpack-sources/2.1.0: dependencies: - '@types/node': 14.14.35 + '@types/node': 14.14.42 '@types/source-list-map': 0.1.2 source-map: 0.7.3 dev: true @@ -3840,6 +3977,15 @@ packages: node: '>= 8' resolution: integrity: sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + /anymatch/3.1.2: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.2.3 + dev: true + engines: + node: '>= 8' + resolution: + integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== /app-root-dir/1.0.2: dev: true resolution: @@ -3863,8 +4009,8 @@ packages: integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== /aria-query/4.2.2: dependencies: - '@babel/runtime': 7.13.10 - '@babel/runtime-corejs3': 7.13.10 + '@babel/runtime': 7.13.17 + '@babel/runtime-corejs3': 7.13.17 dev: true engines: node: '>=6.0' @@ -4897,6 +5043,19 @@ packages: hasBin: true resolution: integrity: sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== + /browserslist/4.16.5: + dependencies: + caniuse-lite: 1.0.30001216 + colorette: 1.2.2 + electron-to-chromium: 1.3.720 + escalade: 3.1.1 + node-releases: 1.1.71 + dev: true + engines: + node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 + hasBin: true + resolution: + integrity: sha512-C2HAjrM1AI/djrpAUU/tr4pml1DqLIzJKSLDBXBrNErl9ZCCTXdhwxdJjYc16953+mBWf7Lw+uUJgpgb8cN71A== /bser/2.1.1: dependencies: node-int64: 0.4.0 @@ -5167,6 +5326,10 @@ packages: dev: true resolution: integrity: sha512-ic/jXfa6tgiPBAISWk16jRI2q8YfjxHnSG7ddSL1ptrIP8Uy11SayFrjXRAk3NumHpDb21fdTkbTxb/hOrFrnQ== + /caniuse-lite/1.0.30001216: + dev: true + resolution: + integrity: sha512-1uU+ww/n5WCJRwUcc9UH/W6925Se5aNnem/G5QaSDga2HzvjYMs8vRbekGUN/PnTZ7ezTHcxxTEb9fgiMYwH6Q== /capture-exit/2.0.0: dependencies: rsvp: 4.8.5 @@ -5239,6 +5402,15 @@ packages: node: '>=10' resolution: integrity: sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + /chalk/4.1.1: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + engines: + node: '>=10' + resolution: + integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== /char-regex/1.0.2: dev: true engines: @@ -5840,6 +6012,11 @@ packages: dev: true resolution: integrity: sha512-jXAirMQxrkbiiLsCx9bQPJFA6llDadKMpYrBJQJ3/c4/vsPP/fAf29h24tviRlvwUL6AmY5CHLu2GvjuYviQqA== + /core-js-pure/3.11.0: + dev: true + requiresBuild: true + resolution: + integrity: sha512-PxEiQGjzC+5qbvE7ZIs5Zn6BynNeZO9zHhrrWmkRff2SZLq0CE/H5LuZOJHhmOQ8L38+eMzEHAmPYWrUtDfuDQ== /core-js-pure/3.9.1: dev: true requiresBuild: true @@ -6843,6 +7020,10 @@ packages: dev: true resolution: integrity: sha512-WCn+ZaU3V8WttlLNSOGOAlR2XpxibGre7slwGrYBB6oTjYPgP29LNDGG6wLvLTMseLdE+G1vno7PfY7JyDV48g== + /electron-to-chromium/1.3.720: + dev: true + resolution: + integrity: sha512-B6zLTxxaOFP4WZm6DrvgRk8kLFYWNhQ5TrHMC0l5WtkMXhU5UbnvWoTfeEwqOruUSlNMhVLfYak7REX6oC5Yfw== /element-resize-detector/1.2.2: dependencies: batch-processor: 1.0.0 @@ -7431,10 +7612,10 @@ packages: dev: true resolution: integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - /exec-sh/0.3.4: + /exec-sh/0.3.6: dev: true resolution: - integrity: sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== + integrity: sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== /execa/1.0.0: dependencies: cross-spawn: 6.0.5 @@ -7907,7 +8088,7 @@ packages: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 - mime-types: 2.1.29 + mime-types: 2.1.30 dev: true engines: node: '>= 6' @@ -9954,7 +10135,7 @@ packages: integrity: sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== /jest-diff/26.6.2: dependencies: - chalk: 4.1.0 + chalk: 4.1.1 diff-sequences: 26.6.2 jest-get-type: 26.3.0 pretty-format: 26.6.2 @@ -10020,15 +10201,15 @@ packages: dependencies: '@jest/types': 26.6.2 '@types/graceful-fs': 4.1.5 - '@types/node': 14.14.35 - anymatch: 3.1.1 + '@types/node': 15.0.0 + anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.6 jest-regex-util: 26.0.0 jest-serializer: 26.6.2 jest-util: 26.6.2 jest-worker: 26.6.2 - micromatch: 4.0.2 + micromatch: 4.0.4 sane: 4.1.0 walker: 1.0.7 dev: true @@ -10074,7 +10255,7 @@ packages: integrity: sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== /jest-matcher-utils/26.6.2: dependencies: - chalk: 4.1.0 + chalk: 4.1.1 jest-diff: 26.6.2 jest-get-type: 26.3.0 pretty-format: 26.6.2 @@ -10088,9 +10269,9 @@ packages: '@babel/code-frame': 7.12.13 '@jest/types': 26.6.2 '@types/stack-utils': 2.0.0 - chalk: 4.1.0 + chalk: 4.1.1 graceful-fs: 4.2.6 - micromatch: 4.0.2 + micromatch: 4.0.4 pretty-format: 26.6.2 slash: 3.0.0 stack-utils: 2.0.3 @@ -10161,7 +10342,7 @@ packages: /jest-resolve/26.6.2: dependencies: '@jest/types': 26.6.2 - chalk: 4.1.0 + chalk: 4.1.1 graceful-fs: 4.2.6 jest-pnp-resolver: 1.2.2_jest-resolve@26.6.2 jest-util: 26.6.2 @@ -10237,7 +10418,7 @@ packages: integrity: sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== /jest-serializer/26.6.2: dependencies: - '@types/node': 14.14.35 + '@types/node': 15.0.0 graceful-fs: 4.2.6 dev: true engines: @@ -10246,11 +10427,11 @@ packages: integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== /jest-snapshot/26.6.2: dependencies: - '@babel/types': 7.13.0 + '@babel/types': 7.13.17 '@jest/types': 26.6.2 '@types/babel__traverse': 7.11.1 '@types/prettier': 2.2.3 - chalk: 4.1.0 + chalk: 4.1.1 expect: 26.6.2 graceful-fs: 4.2.6 jest-diff: 26.6.2 @@ -10261,7 +10442,7 @@ packages: jest-resolve: 26.6.2 natural-compare: 1.4.0 pretty-format: 26.6.2 - semver: 7.3.4 + semver: 7.3.5 dev: true engines: node: '>= 10.14.2' @@ -10270,11 +10451,11 @@ packages: /jest-util/26.6.2: dependencies: '@jest/types': 26.6.2 - '@types/node': 14.14.35 - chalk: 4.1.0 + '@types/node': 15.0.0 + chalk: 4.1.1 graceful-fs: 4.2.6 is-ci: 2.0.0 - micromatch: 4.0.2 + micromatch: 4.0.4 dev: true engines: node: '>= 10.14.2' @@ -11224,6 +11405,15 @@ packages: node: '>=8' resolution: integrity: sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + /micromatch/4.0.4: + dependencies: + braces: 3.0.2 + picomatch: 2.2.3 + dev: true + engines: + node: '>=8.6' + resolution: + integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== /miller-rabin/4.0.1: dependencies: bn.js: 4.12.0 @@ -11238,6 +11428,12 @@ packages: node: '>= 0.6' resolution: integrity: sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== + /mime-db/1.47.0: + dev: true + engines: + node: '>= 0.6' + resolution: + integrity: sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== /mime-types/2.1.29: dependencies: mime-db: 1.46.0 @@ -11246,6 +11442,14 @@ packages: node: '>= 0.6' resolution: integrity: sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== + /mime-types/2.1.30: + dependencies: + mime-db: 1.47.0 + dev: true + engines: + node: '>= 0.6' + resolution: + integrity: sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== /mime/1.6.0: dev: true engines: @@ -11798,6 +12002,10 @@ packages: node: '>=0.10.0' resolution: integrity: sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + /object-inspect/1.10.2: + dev: true + resolution: + integrity: sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA== /object-inspect/1.9.0: dev: true resolution: @@ -12397,6 +12605,12 @@ packages: node: '>=8.6' resolution: integrity: sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + /picomatch/2.2.3: + dev: true + engines: + node: '>=8.6' + resolution: + integrity: sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== /pify/2.3.0: dev: true engines: @@ -12977,6 +13191,14 @@ packages: preact-render-to-string: '*' resolution: integrity: sha512-Oc9HOjwX/3Zk1eXkmP7TMmtqbaROl7F0RWZ2Ni5Q/grmx3yBLJmarkUcOSKabkI/Usw2dU3RVju32Q3Pvy5qIw== + /preact-render-to-json/3.6.6_preact@10.5.13: + dependencies: + preact: 10.5.13 + dev: true + peerDependencies: + preact: '*' + resolution: + integrity: sha1-9n9IWBkSrFP8n0hzvG1840L3HCA= /preact-render-to-string/5.1.16_preact@10.5.13: dependencies: preact: 10.5.13 @@ -13053,7 +13275,7 @@ packages: '@jest/types': 26.6.2 ansi-regex: 5.0.0 ansi-styles: 4.3.0 - react-is: 17.0.1 + react-is: 17.0.2 dev: true engines: node: '>= 10' @@ -13538,6 +13760,10 @@ packages: dev: true resolution: integrity: sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== + /react-is/17.0.2: + dev: true + resolution: + integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== /react-lifecycles-compat/3.0.4: dev: true resolution: @@ -14296,7 +14522,7 @@ packages: '@cnakazawa/watch': 1.0.4 anymatch: 2.0.0 capture-exit: 2.0.0 - exec-sh: 0.3.4 + exec-sh: 0.3.6 execa: 1.0.0 fb-watchman: 2.0.1 micromatch: 3.1.10 @@ -14487,6 +14713,15 @@ packages: hasBin: true resolution: integrity: sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + /semver/7.3.5: + dependencies: + lru-cache: 6.0.0 + dev: true + engines: + node: '>=10' + hasBin: true + resolution: + integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== /send/0.17.1: dependencies: debug: 2.6.9 @@ -14656,7 +14891,7 @@ packages: dependencies: call-bind: 1.0.2 get-intrinsic: 1.1.1 - object-inspect: 1.9.0 + object-inspect: 1.10.2 dev: true resolution: integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==