diff options
Diffstat (limited to 'packages/merchant-backoffice/src/components')
45 files changed, 0 insertions, 5022 deletions
diff --git a/packages/merchant-backoffice/src/components/exception/AsyncButton.tsx b/packages/merchant-backoffice/src/components/exception/AsyncButton.tsx deleted file mode 100644 index 92bab4b..0000000 --- a/packages/merchant-backoffice/src/components/exception/AsyncButton.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - 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 { ComponentChildren, h } from "preact"; -import { LoadingModal } from "../modal"; -import { useAsync } from "../../hooks/async"; -import { Translate } from "../../i18n"; - -type Props = { - children: ComponentChildren, - disabled: boolean; - onClick?: () => Promise<void>; - [rest:string]: any, -}; - -export function AsyncButton({ onClick, disabled, children, ...rest }: Props) { - const { isSlow, isLoading, request, cancel } = useAsync(onClick); - - if (isSlow) { - return <LoadingModal onCancel={cancel} />; - } - if (isLoading) { - return <button class="button"><Translate>Loading...</Translate></button>; - } - - return <span {...rest}> - <button class="button is-success" onClick={request} disabled={disabled}> - {children} - </button> - </span>; -} diff --git a/packages/merchant-backoffice/src/components/exception/QR.tsx b/packages/merchant-backoffice/src/components/exception/QR.tsx deleted file mode 100644 index bcb9964..0000000 --- a/packages/merchant-backoffice/src/components/exception/QR.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - 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/> - */ - -import { h, VNode } from "preact"; -import { useEffect, useRef } from "preact/hooks"; -import qrcode from "qrcode-generator"; - -export function QR({ text }: { text: string }): VNode { - const divRef = useRef<HTMLDivElement>(null); - useEffect(() => { - const qr = qrcode(0, "L"); - qr.addData(text); - qr.make(); - if (divRef.current) { - divRef.current.innerHTML = qr.createSvgTag({ - scalable: true, - }); - } - }); - - return ( - <div - style={{ - width: "100%", - display: "flex", - flexDirection: "column", - alignItems: "center", - }} - > - <div - style={{ width: "50%", minWidth: 200, maxWidth: 300 }} - ref={divRef} - /> - </div> - ); -} diff --git a/packages/merchant-backoffice/src/components/exception/loading.tsx b/packages/merchant-backoffice/src/components/exception/loading.tsx deleted file mode 100644 index f2139a1..0000000 --- a/packages/merchant-backoffice/src/components/exception/loading.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, VNode } from "preact"; - -export function Loading(): VNode { - return <div class="columns is-centered is-vcentered" style={{ height: 'calc(100% - 3rem)', position: 'absolute', width: '100%' }}> - <Spinner /> - </div> -} - -export function Spinner(): VNode { - return <div class="lds-ring"><div /><div /><div /><div /></div> -}
\ No newline at end of file diff --git a/packages/merchant-backoffice/src/components/exception/login.tsx b/packages/merchant-backoffice/src/components/exception/login.tsx deleted file mode 100644 index 498d994..0000000 --- a/packages/merchant-backoffice/src/components/exception/login.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { useBackendContext } from "../../context/backend"; -import { useInstanceContext } from "../../context/instance"; -import { Translate, useTranslator } from "../../i18n"; -import { Notification } from "../../utils/types"; - -interface Props { - withMessage?: Notification; - onConfirm: (backend: string, token?: string) => void; -} - -function getTokenValuePart(t?: string): string | undefined { - if (!t) return t; - const match = /secret-token:(.*)/.exec(t); - if (!match || !match[1]) return undefined; - return match[1]; -} - -function normalizeToken(r: string | undefined): string | undefined { - return r ? `secret-token:${encodeURIComponent(r)}` : undefined; -} - -export function LoginModal({ onConfirm, withMessage }: Props): VNode { - const { url: backendUrl, token: baseToken } = useBackendContext(); - const { admin, token: instanceToken } = useInstanceContext(); - const currentToken = getTokenValuePart( - !admin ? baseToken : instanceToken || "" - ); - const [token, setToken] = useState(currentToken); - - const [url, setURL] = useState(backendUrl); - const i18n = useTranslator(); - - return ( - <div class="columns is-centered"> - <div class="column is-two-thirds "> - <div class="modal-card" style={{ width: "100%", margin: 0 }}> - <header - class="modal-card-head" - style={{ border: "1px solid", borderBottom: 0 }} - > - <p class="modal-card-title">{i18n`Login required`}</p> - </header> - <section - class="modal-card-body" - style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }} - > - <Translate>Please enter your access token.</Translate> - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label">URL</label> - </div> - <div class="field-body"> - <div class="field"> - <p class="control is-expanded"> - <input - class="input" - type="text" - placeholder="set new url" - name="id" - value={url} - onKeyPress={(e) => - e.keyCode === 13 - ? onConfirm(url, normalizeToken(token)) - : null - } - onInput={(e): void => setURL(e?.currentTarget.value)} - /> - </p> - </div> - </div> - </div> - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - <Translate>Access Token</Translate> - </label> - </div> - <div class="field-body"> - <div class="field"> - <p class="control is-expanded"> - <input - class="input" - type="password" - placeholder={"set new access token"} - name="token" - onKeyPress={(e) => - e.keyCode === 13 - ? onConfirm(url, normalizeToken(token)) - : null - } - value={token} - onInput={(e): void => setToken(e?.currentTarget.value)} - /> - </p> - </div> - </div> - </div> - </section> - <footer - class="modal-card-foot " - style={{ - justifyContent: "flex-end", - border: "1px solid", - borderTop: 0, - }} - > - <button - class="button is-info" - onClick={(): void => { - onConfirm(url, normalizeToken(token)); - }} - > - <Translate>Confirm</Translate> - </button> - </footer> - </div> - </div> - </div> - ); -} diff --git a/packages/merchant-backoffice/src/components/form/FormProvider.tsx b/packages/merchant-backoffice/src/components/form/FormProvider.tsx deleted file mode 100644 index aef410c..0000000 --- a/packages/merchant-backoffice/src/components/form/FormProvider.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - 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 { ComponentChildren, createContext, h, VNode } from "preact"; -import { useContext, useMemo } from "preact/hooks"; - -type Updater<S> = (value: ((prevState: S) => S) ) => void; - -export interface Props<T> { - object?: Partial<T>; - errors?: FormErrors<T>; - name?: string; - valueHandler: Updater<Partial<T>> | null; - children: ComponentChildren -} - -const noUpdater: Updater<Partial<unknown>> = () => (s: unknown) => s - -export function FormProvider<T>({ object = {}, errors = {}, name = '', valueHandler, children }: Props<T>): VNode { - const initialObject = useMemo(() => object, []); - const value = useMemo<FormType<T>>(() => ({ errors, object, initialObject, valueHandler: valueHandler ? valueHandler : noUpdater, name, toStr: {}, fromStr: {} }), [errors, object, valueHandler]); - - return <FormContext.Provider value={value}> - <form class="field" onSubmit={(e) => { - e.preventDefault(); - // if (valueHandler) valueHandler(object); - }}> - {children} - </form> - </FormContext.Provider>; -} - -export interface FormType<T> { - object: Partial<T>; - initialObject: Partial<T>; - errors: FormErrors<T>; - toStr: FormtoStr<T>; - name: string; - fromStr: FormfromStr<T>; - valueHandler: Updater<Partial<T>>; -} - -const FormContext = createContext<FormType<unknown>>(null!) - -export function useFormContext<T>() { - return useContext<FormType<T>>(FormContext) -} - -export type FormErrors<T> = { - [P in keyof T]?: string | FormErrors<T[P]> -} - -export type FormtoStr<T> = { - [P in keyof T]?: ((f?: T[P]) => string) -} - -export type FormfromStr<T> = { - [P in keyof T]?: ((f: string) => T[P]) -} - -export type FormUpdater<T> = { - [P in keyof T]?: (f: keyof T) => (v: T[P]) => void -} diff --git a/packages/merchant-backoffice/src/components/form/Input.tsx b/packages/merchant-backoffice/src/components/form/Input.tsx deleted file mode 100644 index 9a9691e..0000000 --- a/packages/merchant-backoffice/src/components/form/Input.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - 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 { ComponentChildren, h, VNode } from "preact"; -import { useField, InputProps } from "./useField"; - -interface Props<T> extends InputProps<T> { - inputType?: 'text' | 'number' | 'multiline' | 'password'; - expand?: boolean; - toStr?: (v?: any) => string; - fromStr?: (s: string) => any; - inputExtra?: any, - side?: ComponentChildren; - children?: ComponentChildren; -} - -const defaultToString = (f?: any): string => f || '' -const defaultFromString = (v: string): any => v as any - -const TextInput = ({ inputType, error, ...rest }: any) => inputType === 'multiline' ? - <textarea {...rest} class={error ? "textarea is-danger" : "textarea"} rows="3" /> : - <input {...rest} class={error ? "input is-danger" : "input"} type={inputType} />; - -export function Input<T>({ name, readonly, placeholder, tooltip, label, expand, help, children, inputType, inputExtra, side, fromStr = defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode { - const { error, value, onChange, required } = useField<T>(name); - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <p class={expand ? "control is-expanded has-icons-right" : "control has-icons-right"}> - <TextInput error={error} {...inputExtra} - inputType={inputType} - placeholder={placeholder} readonly={readonly} - name={String(name)} value={toStr(value)} - onChange={(e: h.JSX.TargetedEvent<HTMLInputElement>): void => onChange(fromStr(e.currentTarget.value))} /> - {help} - {children} - { required && <span class="icon has-text-danger is-right"> - <i class="mdi mdi-alert" /> - </span> } - </p> - {error && <p class="help is-danger">{error}</p>} - </div> - {side} - </div> - </div>; -} diff --git a/packages/merchant-backoffice/src/components/form/InputArray.tsx b/packages/merchant-backoffice/src/components/form/InputArray.tsx deleted file mode 100644 index 984c6dc..0000000 --- a/packages/merchant-backoffice/src/components/form/InputArray.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { Translate, useTranslator } from "../../i18n"; -import { InputProps, useField } from "./useField"; - -export interface Props<T> extends InputProps<T> { - isValid?: (e: any) => boolean; - addonBefore?: string; - toStr?: (v?: any) => string; - fromStr?: (s: string) => any; -} - -const defaultToString = (f?: any): string => f || '' -const defaultFromString = (v: string): any => v as any - -export function InputArray<T>({ name, readonly, placeholder, tooltip, label, help, addonBefore, isValid = () => true, fromStr = defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode { - const { error: formError, value, onChange, required } = useField<T>(name); - const [localError, setLocalError] = useState<string | null>(null) - - const error = localError || formError - - const array: any[] = (value ? value! : []) as any; - const [currentValue, setCurrentValue] = useState(''); - const i18n = useTranslator(); - - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <div class="field has-addons"> - {addonBefore && <div class="control"> - <a class="button is-static">{addonBefore}</a> - </div>} - <p class="control is-expanded has-icons-right"> - <input class={error ? "input is-danger" : "input"} type="text" - placeholder={placeholder} readonly={readonly} disabled={readonly} - name={String(name)} value={currentValue} - onChange={(e): void => setCurrentValue(e.currentTarget.value)} /> - {required && <span class="icon has-text-danger is-right"> - <i class="mdi mdi-alert" /> - </span>} - </p> - <p class="control"> - <button class="button is-info has-tooltip-left" disabled={!currentValue} onClick={(): void => { - const v = fromStr(currentValue) - if (!isValid(v)) { - setLocalError(i18n`The value ${v} is invalid for a payment url`) - return; - } - setLocalError(null) - onChange([v, ...array] as any); - setCurrentValue(''); - }} data-tooltip={i18n`add element to the list`}><Translate>add</Translate></button> - </p> - </div> - {help} - {error && <p class="help is-danger"> {error} </p>} - {array.map((v, i) => <div key={i} class="tags has-addons mt-3 mb-0"> - <span class="tag is-medium is-info mb-0" style={{ maxWidth: '90%' }}>{v}</span> - <a class="tag is-medium is-danger is-delete mb-0" onClick={() => { - onChange(array.filter(f => f !== v) as any); - setCurrentValue(toStr(v)); - }} /> - </div> - )} - </div> - - </div> - </div>; -} diff --git a/packages/merchant-backoffice/src/components/form/InputBoolean.tsx b/packages/merchant-backoffice/src/components/form/InputBoolean.tsx deleted file mode 100644 index 2771fe4..0000000 --- a/packages/merchant-backoffice/src/components/form/InputBoolean.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ -import { h, VNode } from "preact"; -import { InputProps, useField } from "./useField"; - -interface Props<T> extends InputProps<T> { - name: T; - readonly?: boolean; - expand?: boolean; - threeState?: boolean; - toBoolean?: (v?: any) => boolean | undefined; - fromBoolean?: (s: boolean | undefined) => any; -} - -const defaultToBoolean = (f?: any): boolean | undefined => f || '' -const defaultFromBoolean = (v: boolean | undefined): any => v as any - - -export function InputBoolean<T>({ name, readonly, placeholder, tooltip, label, help, threeState, expand, fromBoolean = defaultFromBoolean, toBoolean = defaultToBoolean }: Props<keyof T>): VNode { - const { error, value, onChange } = useField<T>(name); - - const onCheckboxClick = (): void => { - const c = toBoolean(value) - if (c === false && threeState) return onChange(undefined as any) - return onChange(fromBoolean(!c)) - } - - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <p class={expand ? "control is-expanded" : "control"}> - <label class="b-checkbox checkbox"> - <input type="checkbox" class={toBoolean(value) === undefined ? "is-indeterminate" : ""} - checked={toBoolean(value)} - placeholder={placeholder} readonly={readonly} - name={String(name)} disabled={readonly} - onChange={onCheckboxClick} /> - <span class="check" /> - </label> - {help} - </p> - {error && <p class="help is-danger">{error}</p>} - </div> - </div> - </div>; -} diff --git a/packages/merchant-backoffice/src/components/form/InputCurrency.tsx b/packages/merchant-backoffice/src/components/form/InputCurrency.tsx deleted file mode 100644 index d3a46f4..0000000 --- a/packages/merchant-backoffice/src/components/form/InputCurrency.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - 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 { ComponentChildren, h } from "preact"; -import { useConfigContext } from "../../context/config"; -import { Amount } from "../../declaration"; -import { InputWithAddon } from "./InputWithAddon"; -import { InputProps } from "./useField"; - -export interface Props<T> extends InputProps<T> { - expand?: boolean; - addonAfter?: ComponentChildren; - children?: ComponentChildren; - side?: ComponentChildren; -} - -export function InputCurrency<T>({ name, readonly, label, placeholder, help, tooltip, expand, addonAfter, children, side }: Props<keyof T>) { - const config = useConfigContext() - return <InputWithAddon<T> name={name} readonly={readonly} addonBefore={config.currency} - side={side} - label={label} placeholder={placeholder} help={help} tooltip={tooltip} - addonAfter={addonAfter} - inputType='number' expand={expand} - toStr={(v?: Amount) => v?.split(':')[1] || ''} - fromStr={(v: string) => !v ? '' : `${config.currency}:${v}`} - inputExtra={{ min: 0 }} - children={children} - /> -} - diff --git a/packages/merchant-backoffice/src/components/form/InputDate.tsx b/packages/merchant-backoffice/src/components/form/InputDate.tsx deleted file mode 100644 index 7719952..0000000 --- a/packages/merchant-backoffice/src/components/form/InputDate.tsx +++ /dev/null @@ -1,159 +0,0 @@ -/* - 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 { format } from "date-fns"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { Translate, useTranslator } from "../../i18n"; -import { DatePicker } from "../picker/DatePicker"; -import { InputProps, useField } from "./useField"; - -export interface Props<T> extends InputProps<T> { - readonly?: boolean; - expand?: boolean; - //FIXME: create separated components InputDate and InputTimestamp - withTimestampSupport?: boolean; -} - -export function InputDate<T>({ - name, - readonly, - label, - placeholder, - help, - tooltip, - expand, - withTimestampSupport, -}: Props<keyof T>): VNode { - const [opened, setOpened] = useState(false); - const i18n = useTranslator(); - - const { error, required, value, onChange } = useField<T>(name); - - let strValue = ""; - if (!value) { - strValue = withTimestampSupport ? "unknown" : ""; - } else if (value instanceof Date) { - strValue = format(value, "yyyy/MM/dd"); - } else if (value.t_s) { - strValue = - value.t_s === "never" - ? withTimestampSupport - ? "never" - : "" - : format(new Date(value.t_s * 1000), "yyyy/MM/dd"); - } - - return ( - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && ( - <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span> - )} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <div class="field has-addons"> - <p - class={ - expand - ? "control is-expanded has-icons-right" - : "control has-icons-right" - } - > - <input - class="input" - type="text" - readonly - value={strValue} - placeholder={placeholder} - onClick={() => { - if (!readonly) setOpened(true); - }} - /> - {required && ( - <span class="icon has-text-danger is-right"> - <i class="mdi mdi-alert" /> - </span> - )} - {help} - </p> - <div - class="control" - onClick={() => { - if (!readonly) setOpened(true); - }} - > - <a class="button is-static"> - <span class="icon"> - <i class="mdi mdi-calendar" /> - </span> - </a> - </div> - </div> - {error && <p class="help is-danger">{error}</p>} - </div> - - {!readonly && ( - <span - data-tooltip={ - withTimestampSupport - ? i18n`change value to unknown date` - : i18n`change value to empty` - } - > - <button - class="button is-info mr-3" - onClick={() => onChange(undefined as any)} - > - <Translate>clear</Translate> - </button> - </span> - )} - {withTimestampSupport && ( - <span data-tooltip={i18n`change value to never`}> - <button - class="button is-info" - onClick={() => onChange({ t_s: "never" } as any)} - > - <Translate>never</Translate> - </button> - </span> - )} - </div> - <DatePicker - opened={opened} - closeFunction={() => setOpened(false)} - dateReceiver={(d) => { - if (withTimestampSupport) { - onChange({ t_s: d.getTime() / 1000 } as any); - } else { - onChange(d as any); - } - }} - /> - </div> - ); -} diff --git a/packages/merchant-backoffice/src/components/form/InputDuration.tsx b/packages/merchant-backoffice/src/components/form/InputDuration.tsx deleted file mode 100644 index d5c208e..0000000 --- a/packages/merchant-backoffice/src/components/form/InputDuration.tsx +++ /dev/null @@ -1,172 +0,0 @@ -/* - 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 { intervalToDuration, formatDuration } from "date-fns"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { Translate, useTranslator } from "../../i18n"; -import { SimpleModal } from "../modal"; -import { DurationPicker } from "../picker/DurationPicker"; -import { InputProps, useField } from "./useField"; - -export interface Props<T> extends InputProps<T> { - expand?: boolean; - readonly?: boolean; - withForever?: boolean; -} - -export function InputDuration<T>({ - name, - expand, - placeholder, - tooltip, - label, - help, - readonly, - withForever, -}: Props<keyof T>): VNode { - const [opened, setOpened] = useState(false); - const i18n = useTranslator(); - - const { error, required, value, onChange } = useField<T>(name); - let strValue = ""; - if (!value) { - strValue = ""; - } else if (value.d_us === "forever") { - strValue = i18n`forever`; - } else { - strValue = formatDuration( - intervalToDuration({ start: 0, end: value.d_us / 1000 }), - { - locale: { - formatDistance: (name, value) => { - switch (name) { - case "xMonths": - return i18n`${value}M`; - case "xYears": - return i18n`${value}Y`; - case "xDays": - return i18n`${value}d`; - case "xHours": - return i18n`${value}h`; - case "xMinutes": - return i18n`${value}min`; - case "xSeconds": - return i18n`${value}sec`; - } - }, - localize: { - day: () => "s", - month: () => "m", - ordinalNumber: () => "th", - dayPeriod: () => "p", - quarter: () => "w", - era: () => "e", - }, - }, - } - ); - } - - return ( - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && ( - <span class="icon" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span> - )} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <div class="field has-addons"> - <p class={expand ? "control is-expanded " : "control "}> - <input - class="input" - type="text" - readonly - value={strValue} - placeholder={placeholder} - onClick={() => { - if (!readonly) setOpened(true); - }} - /> - {required && ( - <span class="icon has-text-danger is-right"> - <i class="mdi mdi-alert" /> - </span> - )} - {help} - </p> - <div - class="control" - onClick={() => { - if (!readonly) setOpened(true); - }} - > - <a class="button is-static"> - <span class="icon"> - <i class="mdi mdi-clock" /> - </span> - </a> - </div> - </div> - {error && <p class="help is-danger">{error}</p>} - </div> - {withForever && ( - <span data-tooltip={i18n`change value to never`}> - <button - class="button is-info mr-3" - onClick={() => onChange({ d_us: "forever" } as any)} - > - <Translate>forever</Translate> - </button> - </span> - )} - {!readonly && ( - <span data-tooltip={i18n`change value to empty`}> - <button - class="button is-info " - onClick={() => onChange(undefined as any)} - > - <Translate>clear</Translate> - </button> - </span> - )} - </div> - {opened && ( - <SimpleModal onCancel={() => setOpened(false)}> - <DurationPicker - days - hours - minutes - value={!value || value.d_us === "forever" ? 0 : value.d_us} - onChange={(v) => { - onChange({ d_us: v } as any); - }} - /> - </SimpleModal> - )} - </div> - ); -} diff --git a/packages/merchant-backoffice/src/components/form/InputGroup.tsx b/packages/merchant-backoffice/src/components/form/InputGroup.tsx deleted file mode 100644 index 8af9c7d..0000000 --- a/packages/merchant-backoffice/src/components/form/InputGroup.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - 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 { ComponentChildren, h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { useGroupField } from "./useGroupField"; - -export interface Props<T> { - name: T; - children: ComponentChildren; - label: ComponentChildren; - tooltip?: ComponentChildren; - alternative?: ComponentChildren; - fixed?: boolean; - initialActive?: boolean; -} - -export function InputGroup<T>({ name, label, children, tooltip, alternative, fixed, initialActive }: Props<keyof T>): VNode { - const [active, setActive] = useState(initialActive || fixed); - const group = useGroupField<T>(name); - - return <div class="card"> - <header class="card-header"> - <p class="card-header-title"> - {label} - {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - {group?.hasError && <span class="icon has-text-danger" data-tooltip={tooltip}> - <i class="mdi mdi-alert" /> - </span>} - </p> - { !fixed && <button class="card-header-icon" aria-label="more options" onClick={(): void => setActive(!active)}> - <span class="icon"> - {active ? - <i class="mdi mdi-arrow-up" /> : - <i class="mdi mdi-arrow-down" />} - </span> - </button> } - </header> - {active ? <div class="card-content"> - {children} - </div> : ( - alternative ? <div class="card-content"> - {alternative} - </div> : undefined - )} - </div>; -} diff --git a/packages/merchant-backoffice/src/components/form/InputImage.tsx b/packages/merchant-backoffice/src/components/form/InputImage.tsx deleted file mode 100644 index 6cc9b9d..0000000 --- a/packages/merchant-backoffice/src/components/form/InputImage.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - 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 { ComponentChildren, h, VNode } 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> { - expand?: boolean; - addonAfter?: ComponentChildren; - children?: ComponentChildren; -} - -export function InputImage<T>({ name, readonly, placeholder, tooltip, label, help, children, expand }: Props<keyof T>): VNode { - const { error, value, onChange } = useField<T>(name); - - const image = useRef<HTMLInputElement>(null) - - const [sizeError, setSizeError] = useState(false) - - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <p class={expand ? "control is-expanded" : "control"}> - {value && - <img src={value} style={{ width: 200, height: 200 }} onClick={() => image.current?.click()} /> - } - <input - ref={image} style={{ display: 'none' }} - type="file" name={String(name)} - placeholder={placeholder} readonly={readonly} - onChange={e => { - const f: FileList | null = e.currentTarget.files - if (!f || f.length != 1) { - return onChange(undefined!) - } - if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { - setSizeError(true) - return onChange(undefined!) - } - setSizeError(false) - return f[0].arrayBuffer().then(b => { - const b64 = btoa( - new Uint8Array(b) - .reduce((data, byte) => data + String.fromCharCode(byte), '') - ) - return onChange(`data:${f[0].type};base64,${b64}` as any) - }) - }} /> - {help} - {children} - </p> - {error && <p class="help is-danger">{error}</p>} - {sizeError && <p class="help is-danger"> - <Translate>Image should be smaller than 1 MB</Translate> - </p>} - {!value && - <button class="button" onClick={() => image.current?.click()} ><Translate>Add</Translate></button> - } - {value && - <button class="button" onClick={() => onChange(undefined!)} ><Translate>Remove</Translate></button> - } - </div> - </div> - </div> -} - diff --git a/packages/merchant-backoffice/src/components/form/InputLocation.tsx b/packages/merchant-backoffice/src/components/form/InputLocation.tsx deleted file mode 100644 index 12755f4..0000000 --- a/packages/merchant-backoffice/src/components/form/InputLocation.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - 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 { Fragment, h } from "preact"; -import { useTranslator } from "../../i18n"; -import { Input } from "./Input"; - -export function InputLocation({name}:{name:string}) { - const i18n = useTranslator() - return <> - <Input name={`${name}.country`} label={i18n`Country`} /> - <Input name={`${name}.address_lines`} inputType="multiline" - label={i18n`Address`} - toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')} - fromStr={(v: string) => v.split('\n')} - /> - <Input name={`${name}.building_number`} label={i18n`Building number`} /> - <Input name={`${name}.building_name`} label={i18n`Building name`} /> - <Input name={`${name}.street`} label={i18n`Street`} /> - <Input name={`${name}.post_code`} label={i18n`Post code`} /> - <Input name={`${name}.town_location`} label={i18n`Town location`} /> - <Input name={`${name}.town`} label={i18n`Town`} /> - <Input name={`${name}.district`} label={i18n`District`} /> - <Input name={`${name}.country_subdivision`} label={i18n`Country subdivision`} /> - </> -}
\ No newline at end of file diff --git a/packages/merchant-backoffice/src/components/form/InputNumber.tsx b/packages/merchant-backoffice/src/components/form/InputNumber.tsx deleted file mode 100644 index 046cda5..0000000 --- a/packages/merchant-backoffice/src/components/form/InputNumber.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - 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 { ComponentChildren, h } from "preact"; -import { InputWithAddon } from "./InputWithAddon"; -import { InputProps } from "./useField"; - -export interface Props<T> extends InputProps<T> { - readonly?: boolean; - expand?: boolean; - side?: ComponentChildren; - children?: ComponentChildren; -} - -export function InputNumber<T>({ name, readonly, placeholder, tooltip, label, help, expand, children, side }: Props<keyof T>) { - return <InputWithAddon<T> name={name} readonly={readonly} - fromStr={(v) => !v ? undefined : parseInt(v, 10) } toStr={(v) => `${v}`} - inputType='number' expand={expand} - label={label} placeholder={placeholder} help={help} tooltip={tooltip} - inputExtra={{ min: 0 }} - children={children} - side={side} - /> -} - diff --git a/packages/merchant-backoffice/src/components/form/InputPayto.tsx b/packages/merchant-backoffice/src/components/form/InputPayto.tsx deleted file mode 100644 index 4425231..0000000 --- a/packages/merchant-backoffice/src/components/form/InputPayto.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ -import { h, VNode } from "preact"; -import { InputArray } from "./InputArray"; -import { PAYTO_REGEX } from "../../utils/constants"; -import { InputProps } from "./useField"; - -export type Props<T> = InputProps<T>; - -const PAYTO_START_REGEX = /^payto:\/\// - -export function InputPayto<T>({ name, readonly, placeholder, tooltip, label, help }: Props<keyof T>): VNode { - return <InputArray<T> name={name} readonly={readonly} - addonBefore="payto://" - label={label} placeholder={placeholder} help={help} tooltip={tooltip} - isValid={(v) => v && PAYTO_REGEX.test(v) } - toStr={(v?: string) => !v ? '': v.replace(PAYTO_START_REGEX, '')} - fromStr={(v: string) => `payto://${v}` } - /> -} - diff --git a/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx b/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx deleted file mode 100644 index 9cfef07..0000000 --- a/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx +++ /dev/null @@ -1,392 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ -import { h, VNode, Fragment } from "preact"; -import { useCallback, useState } from "preact/hooks"; -import { Translate, Translator, useTranslator } from "../../i18n"; -import { COUNTRY_TABLE } from "../../utils/constants"; -import { FormErrors, FormProvider } from "./FormProvider"; -import { Input } from "./Input"; -import { InputGroup } from "./InputGroup"; -import { InputSelector } from "./InputSelector"; -import { InputProps, useField } from "./useField"; - -export interface Props<T> extends InputProps<T> { - isValid?: (e: any) => boolean; -} - -// https://datatracker.ietf.org/doc/html/rfc8905 -type Entity = { - // iban, bitcoin, x-taler-bank. it defined the format - target: string; - // path1 if the first field to be used - path1: string; - // path2 if the second field to be used, optional - path2?: string; - // options of the payto uri - options: { - "receiver-name"?: string; - sender?: string; - message?: string; - amount?: string; - instruction?: string; - [name: string]: string | undefined; - }; -}; - -function isEthereumAddress(address: string) { - if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) { - return false; - } else if ( - /^(0x|0X)?[0-9a-f]{40}$/.test(address) || - /^(0x|0X)?[0-9A-F]{40}$/.test(address) - ) { - return true; - } - return checkAddressChecksum(address); -} - -function checkAddressChecksum(address: string) { - //TODO implement ethereum checksum - return true; -} - -function validateBitcoin(addr: string, i18n: Translator): string | undefined { - try { - const valid = /^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/.test(addr); - if (valid) return undefined; - } catch (e) { - console.log(e); - } - return i18n`This is not a valid bitcoin address.`; -} - -function validateEthereum(addr: string, i18n: Translator): string | undefined { - try { - const valid = isEthereumAddress(addr); - if (valid) return undefined; - } catch (e) { - console.log(e); - } - return i18n`This is not a valid Ethereum address.`; -} - -/** - * An IBAN is validated by converting it into an integer and performing a - * basic mod-97 operation (as described in ISO 7064) on it. - * If the IBAN is valid, the remainder equals 1. - * - * The algorithm of IBAN validation is as follows: - * 1.- Check that the total IBAN length is correct as per the country. If not, the IBAN is invalid - * 2.- Move the four initial characters to the end of the string - * 3.- Replace each letter in the string with two digits, thereby expanding the string, where A = 10, B = 11, ..., Z = 35 - * 4.- Interpret the string as a decimal integer and compute the remainder of that number on division by 97 - * - * If the remainder is 1, the check digit test is passed and the IBAN might be valid. - * - */ -function validateIBAN(iban: string, i18n: Translator): string | undefined { - // Check total length - if (iban.length < 4) - return i18n`IBAN numbers usually have more that 4 digits`; - if (iban.length > 34) - return i18n`IBAN numbers usually have less that 34 digits`; - - const A_code = "A".charCodeAt(0); - const Z_code = "Z".charCodeAt(0); - const IBAN = iban.toUpperCase(); - // check supported country - const code = IBAN.substr(0, 2); - const found = code in COUNTRY_TABLE; - if (!found) return i18n`IBAN country code not found`; - - // 2.- Move the four initial characters to the end of the string - const step2 = IBAN.substr(4) + iban.substr(0, 4); - const step3 = Array.from(step2) - .map((letter) => { - const code = letter.charCodeAt(0); - if (code < A_code || code > Z_code) return letter; - return `${letter.charCodeAt(0) - "A".charCodeAt(0) + 10}`; - }) - .join(""); - - function calculate_iban_checksum(str: string): number { - const numberStr = str.substr(0, 5); - const rest = str.substr(5); - const number = parseInt(numberStr, 10); - const result = number % 97; - if (rest.length > 0) { - return calculate_iban_checksum(`${result}${rest}`); - } - return result; - } - - const checksum = calculate_iban_checksum(step3); - if (checksum !== 1) return i18n`IBAN number is not valid, checksum is wrong`; - return undefined; -} - -// const targets = ['ach', 'bic', 'iban', 'upi', 'bitcoin', 'ilp', 'void', 'x-taler-bank'] -const targets = [ - "Choose one...", - "iban", - "x-taler-bank", - "bitcoin", - "ethereum", -]; -const noTargetValue = targets[0]; -const defaultTarget = { target: noTargetValue, options: {} }; - -function undefinedIfEmpty<T>(obj: T): T | undefined { - return Object.keys(obj).some((k) => (obj as any)[k] !== undefined) - ? obj - : undefined; -} - -export function InputPaytoForm<T>({ - name, - readonly, - label, - tooltip, -}: Props<keyof T>): VNode { - const { value: paytos, onChange } = useField<T>(name); - - const [value, valueHandler] = useState<Partial<Entity>>(defaultTarget); - - let payToPath; - if (value.target === "iban" && value.path1) { - payToPath = `/${value.path1.toUpperCase()}`; - } else if (value.path1) { - if (value.path2) { - payToPath = `/${value.path1}/${value.path2}`; - } else { - payToPath = `/${value.path1}`; - } - } - const i18n = useTranslator(); - - const ops = value.options!; - const url = tryUrl(`payto://${value.target}${payToPath}`); - if (url) { - Object.keys(ops).forEach((opt_key) => { - const opt_value = ops[opt_key]; - if (opt_value) url.searchParams.set(opt_key, opt_value); - }); - } - const paytoURL = !url ? "" : url.toString(); - - const errors: FormErrors<Entity> = { - target: value.target === noTargetValue ? i18n`required` : undefined, - path1: !value.path1 - ? i18n`required` - : value.target === "iban" - ? validateIBAN(value.path1, i18n) - : value.target === "bitcoin" - ? validateBitcoin(value.path1, i18n) - : value.target === "ethereum" - ? validateEthereum(value.path1, i18n) - : undefined, - path2: - value.target === "x-taler-bank" - ? !value.path2 - ? i18n`required` - : undefined - : undefined, - options: undefinedIfEmpty({ - "receiver-name": !value.options?.["receiver-name"] - ? i18n`required` - : undefined, - }), - }; - - const hasErrors = Object.keys(errors).some( - (k) => (errors as any)[k] !== undefined - ); - - const submit = useCallback((): void => { - const alreadyExists = - paytos.findIndex((x: string) => x === paytoURL) !== -1; - if (!alreadyExists) { - onChange([paytoURL, ...paytos] as any); - } - valueHandler(defaultTarget); - }, [value]); - - //FIXME: translating plural singular - return ( - <InputGroup name="payto" label={label} fixed tooltip={tooltip}> - <FormProvider<Entity> - name="tax" - errors={errors} - object={value} - valueHandler={valueHandler} - > - <InputSelector<Entity> - name="target" - label={i18n`Target type`} - tooltip={i18n`Method to use for wire transfer`} - values={targets} - toStr={(v) => (v === noTargetValue ? i18n`Choose one...` : v)} - /> - - {value.target === "ach" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n`Routing`} - tooltip={i18n`Routing number.`} - /> - <Input<Entity> - name="path2" - label={i18n`Account`} - tooltip={i18n`Account number.`} - /> - </Fragment> - )} - {value.target === "bic" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n`Code`} - tooltip={i18n`Business Identifier Code.`} - /> - </Fragment> - )} - {value.target === "iban" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n`Account`} - tooltip={i18n`Bank Account Number.`} - inputExtra={{ style: { textTransform: "uppercase" } }} - /> - </Fragment> - )} - {value.target === "upi" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n`Account`} - tooltip={i18n`Unified Payment Interface.`} - /> - </Fragment> - )} - {value.target === "bitcoin" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n`Address`} - tooltip={i18n`Bitcoin protocol.`} - /> - </Fragment> - )} - {value.target === "ethereum" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n`Address`} - tooltip={i18n`Ethereum protocol.`} - /> - </Fragment> - )} - {value.target === "ilp" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n`Address`} - tooltip={i18n`Interledger protocol.`} - /> - </Fragment> - )} - {value.target === "void" && <Fragment />} - {value.target === "x-taler-bank" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n`Host`} - tooltip={i18n`Bank host.`} - /> - <Input<Entity> - name="path2" - label={i18n`Account`} - tooltip={i18n`Bank account.`} - /> - </Fragment> - )} - - {value.target !== noTargetValue && ( - <Input - name="options.receiver-name" - label={i18n`Name`} - tooltip={i18n`Bank account owner's name.`} - /> - )} - - <div class="field is-horizontal"> - <div class="field-label is-normal" /> - <div class="field-body" style={{ display: "block" }}> - {paytos.map((v: any, i: number) => ( - <div - key={i} - class="tags has-addons mt-3 mb-0 mr-3" - style={{ flexWrap: "nowrap" }} - > - <span - class="tag is-medium is-info mb-0" - style={{ maxWidth: "90%" }} - > - {v} - </span> - <a - class="tag is-medium is-danger is-delete mb-0" - onClick={() => { - onChange(paytos.filter((f: any) => f !== v) as any); - }} - /> - </div> - ))} - {!paytos.length && i18n`No accounts yet.`} - </div> - </div> - - {value.target !== noTargetValue && ( - <div class="buttons is-right mt-5"> - <button - class="button is-info" - data-tooltip={i18n`add tax to the tax list`} - disabled={hasErrors} - onClick={submit} - > - <Translate>Add</Translate> - </button> - </div> - )} - </FormProvider> - </InputGroup> - ); -} - -function tryUrl(s: string): URL | undefined { - try { - return new URL(s); - } catch (e) { - return undefined; - } -} diff --git a/packages/merchant-backoffice/src/components/form/InputSearchProduct.tsx b/packages/merchant-backoffice/src/components/form/InputSearchProduct.tsx deleted file mode 100644 index 51f84fd..0000000 --- a/packages/merchant-backoffice/src/components/form/InputSearchProduct.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import emptyImage from "../../assets/empty.png"; -import { MerchantBackend, WithId } from "../../declaration"; -import { useInstanceProducts } from "../../hooks/product"; -import { Translate, useTranslator } from "../../i18n"; -import { FormErrors, FormProvider } from "./FormProvider"; -import { InputWithAddon } from "./InputWithAddon"; - -type Entity = MerchantBackend.Products.ProductDetail & WithId - -export interface Props { - selected?: Entity; - onChange: (p?: Entity) => void; - products: (MerchantBackend.Products.ProductDetail & WithId)[], -} - -interface ProductSearch { - name: string; -} - -export function InputSearchProduct({ selected, onChange, products }: Props): VNode { - const [prodForm, setProdName] = useState<Partial<ProductSearch>>({ name: '' }) - - const errors: FormErrors<ProductSearch> = { - name: undefined - } - const i18n = useTranslator() - - - if (selected) { - return <article class="media"> - <figure class="media-left"> - <p class="image is-128x128"> - <img src={selected.image ? selected.image : emptyImage} /> - </p> - </figure> - <div class="media-content"> - <div class="content"> - <p class="media-meta"><Translate>Product id</Translate>: <b>{selected.id}</b></p> - <p><Translate>Description</Translate>: {selected.description}</p> - <div class="buttons is-right mt-5"> - <button class="button is-info" onClick={() => onChange(undefined)}>clear</button> - </div> - </div> - </div> - </article> - } - - return <FormProvider<ProductSearch> errors={errors} object={prodForm} valueHandler={setProdName} > - - <InputWithAddon<ProductSearch> - name="name" - label={i18n`Product`} - tooltip={i18n`search products by it's description or id`} - addonAfter={<span class="icon" ><i class="mdi mdi-magnify" /></span>} - > - <div> - <ProductList - name={prodForm.name} - list={products} - onSelect={(p) => { - setProdName({ name: '' }) - onChange(p) - }} - /> - </div> - </InputWithAddon> - - </FormProvider> - -} - -interface ProductListProps { - name?: string; - onSelect: (p: MerchantBackend.Products.ProductDetail & WithId) => void; - list: (MerchantBackend.Products.ProductDetail & WithId)[] -} - -function ProductList({ name, onSelect, list }: ProductListProps) { - if (!name) { - /* FIXME - this BR is added to occupy the space that will be added when the - dropdown appears - */ - return <div ><br /></div> - } - const filtered = list.filter(p => p.id.includes(name) || p.description.includes(name)) - - return <div class="dropdown is-active"> - <div class="dropdown-menu" id="dropdown-menu" role="menu" style={{ minWidth: '20rem' }}> - <div class="dropdown-content"> - {!filtered.length ? - <div class="dropdown-item" > - <Translate>no products found with that description</Translate> - </div> : - filtered.map(p => ( - <div key={p.id} class="dropdown-item" onClick={() => onSelect(p)} style={{ cursor: 'pointer' }}> - <article class="media"> - <div class="media-left"> - <div class="image" style={{ minWidth: 64 }}><img src={p.image ? p.image : emptyImage} style={{ width: 64, height: 64 }} /></div> - </div> - <div class="media-content"> - <div class="content"> - <p> - <strong>{p.id}</strong> <small>{p.price}</small> - <br /> - {p.description} - </p> - </div> - </div> - </article> - </div> - )) - } - </div> - </div> - </div> -} diff --git a/packages/merchant-backoffice/src/components/form/InputSecured.stories.tsx b/packages/merchant-backoffice/src/components/form/InputSecured.stories.tsx deleted file mode 100644 index 1990eee..0000000 --- a/packages/merchant-backoffice/src/components/form/InputSecured.stories.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, VNode } from 'preact'; -import { useState } from 'preact/hooks'; -import { FormProvider } from "./FormProvider"; -import { InputSecured } from './InputSecured'; - -export default { - title: 'Components/Form/InputSecured', - component: InputSecured, -}; - -type T = { auth_token: string | null } - -export const InitialValueEmpty = (): VNode => { - const [state, setState] = useState<Partial<T>>({ auth_token: '' }) - return <FormProvider<T> object={state} errors={{}} valueHandler={setState}> - Initial value: '' - <InputSecured<T> name="auth_token" label="Access token" /> - </FormProvider> -} - -export const InitialValueToken = (): VNode => { - const [state, setState] = useState<Partial<T>>({ auth_token: 'token' }) - return <FormProvider<T> object={state} errors={{}} valueHandler={setState}> - <InputSecured<T> name="auth_token" label="Access token" /> - </FormProvider> -} - -export const InitialValueNull = (): VNode => { - const [state, setState] = useState<Partial<T>>({ auth_token: null }) - return <FormProvider<T> object={state} errors={{}} valueHandler={setState}> - Initial value: '' - <InputSecured<T> name="auth_token" label="Access token" /> - </FormProvider> -} diff --git a/packages/merchant-backoffice/src/components/form/InputSecured.tsx b/packages/merchant-backoffice/src/components/form/InputSecured.tsx deleted file mode 100644 index c9b0f43..0000000 --- a/packages/merchant-backoffice/src/components/form/InputSecured.tsx +++ /dev/null @@ -1,119 +0,0 @@ -/* - 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 { Fragment, h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { Translate, useTranslator } from "../../i18n"; -import { InputProps, useField } from "./useField"; - -export type Props<T> = InputProps<T>; - -const TokenStatus = ({ prev, post }: any) => { - if ((prev === undefined || prev === null) && (post === undefined || post === null)) - return null - return (prev === post) ? null : ( - post === null ? - <span class="tag is-danger is-align-self-center ml-2"><Translate>Deleting</Translate></span> : - <span class="tag is-warning is-align-self-center ml-2"><Translate>Changing</Translate></span> - ) -} - -export function InputSecured<T>({ name, readonly, placeholder, tooltip, label, help }: Props<keyof T>): VNode { - const { error, value, initial, onChange, toStr, fromStr } = useField<T>(name); - - const [active, setActive] = useState(false); - const [newValue, setNuewValue] = useState("") - - const i18n = useTranslator() - - return <Fragment> - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body is-flex-grow-3"> - {!active ? - <Fragment> - <div class="field has-addons"> - <button class="button" - onClick={(): void => { setActive(!active); }} > - <div class="icon is-left"><i class="mdi mdi-lock-reset" /></div> - <span><Translate>Manage access token</Translate></span> - </button> - <TokenStatus prev={initial} post={value} /> - </div> - </Fragment> : - <Fragment> - <div class="field has-addons"> - <div class="control"> - <a class="button is-static">secret-token:</a> - </div> - <div class="control is-expanded"> - <input class="input" type="text" - placeholder={placeholder} readonly={readonly || !active} - disabled={readonly || !active} - name={String(name)} value={newValue} - onInput={(e): void => { - setNuewValue(e.currentTarget.value) - }} /> - {help} - </div> - <div class="control"> - <button class="button is-info" disabled={fromStr(newValue) === value} onClick={(): void => { onChange(fromStr(newValue)); setActive(!active); setNuewValue(""); }} > - <div class="icon is-left"><i class="mdi mdi-lock-outline" /></div> - <span><Translate>Update</Translate></span> - </button> - </div> - </div> - </Fragment> - } - {error ? <p class="help is-danger">{error}</p> : null} - </div> - </div> - {active && - <div class="field is-horizontal"> - <div class="field-body is-flex-grow-3"> - <div class="level" style={{ width: '100%' }}> - <div class="level-right is-flex-grow-1"> - <div class="level-item"> - <button class="button is-danger" disabled={null === value || undefined === value} onClick={(): void => { onChange(null!); setActive(!active); setNuewValue(""); }} > - <div class="icon is-left"><i class="mdi mdi-lock-open-variant" /></div> - <span><Translate>Remove</Translate></span> - </button> - </div> - <div class="level-item"> - <button class="button " onClick={(): void => { onChange(initial!); setActive(!active); setNuewValue(""); }} > - <div class="icon is-left"><i class="mdi mdi-lock-open-variant" /></div> - <span><Translate>Cancel</Translate></span> - </button> - </div> - </div> - - </div> - </div> - </div> - } - </Fragment >; -} diff --git a/packages/merchant-backoffice/src/components/form/InputSelector.tsx b/packages/merchant-backoffice/src/components/form/InputSelector.tsx deleted file mode 100644 index 86f4de7..0000000 --- a/packages/merchant-backoffice/src/components/form/InputSelector.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ -import { h, VNode } from "preact"; -import { InputProps, useField } from "./useField"; - -interface Props<T> extends InputProps<T> { - readonly?: boolean; - expand?: boolean; - values: string[]; - toStr?: (v?: any) => string; - fromStr?: (s: string) => any; -} - -const defaultToString = (f?: any): string => f || ""; -const defaultFromString = (v: string): any => v as any; - -export function InputSelector<T>({ - name, - readonly, - expand, - placeholder, - tooltip, - label, - help, - values, - toStr = defaultToString, -}: Props<keyof T>): VNode { - const { error, value, onChange } = useField<T>(name); - - return ( - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && ( - <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span> - )} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <p class={expand ? "control is-expanded select" : "control select"}> - <select - class={error ? "select is-danger" : "select"} - name={String(name)} - disabled={readonly} - readonly={readonly} - onChange={(e) => { - onChange(e.currentTarget.value as any); - }} - > - {placeholder && <option>{placeholder}</option>} - {values.map((v, i) => ( - <option key={i} value={v} selected={value === v}> - {toStr(v)} - </option> - ))} - </select> - {help} - </p> - {error && <p class="help is-danger">{error}</p>} - </div> - </div> - </div> - ); -} diff --git a/packages/merchant-backoffice/src/components/form/InputStock.stories.tsx b/packages/merchant-backoffice/src/components/form/InputStock.stories.tsx deleted file mode 100644 index 63c7e41..0000000 --- a/packages/merchant-backoffice/src/components/form/InputStock.stories.tsx +++ /dev/null @@ -1,162 +0,0 @@ -/* - 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 { addDays } from "date-fns"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { FormProvider } from "./FormProvider"; -import { InputStock, Stock } from "./InputStock"; - -export default { - title: "Components/Form/InputStock", - component: InputStock, -}; - -type T = { stock?: Stock }; - -export const CreateStockEmpty = () => { - const [state, setState] = useState<Partial<T>>({}); - return ( - <FormProvider<T> - name="product" - object={state} - errors={{}} - valueHandler={setState} - > - <InputStock<T> name="stock" label="Stock" /> - <div> - <pre>{JSON.stringify(state, undefined, 2)}</pre> - </div> - </FormProvider> - ); -}; - -export const CreateStockUnknownRestock = () => { - const [state, setState] = useState<Partial<T>>({ - stock: { - current: 10, - lost: 0, - sold: 0, - }, - }); - return ( - <FormProvider<T> - name="product" - object={state} - errors={{}} - valueHandler={setState} - > - <InputStock<T> name="stock" label="Stock" /> - <div> - <pre>{JSON.stringify(state, undefined, 2)}</pre> - </div> - </FormProvider> - ); -}; - -export const CreateStockNoRestock = () => { - const [state, setState] = useState<Partial<T>>({ - stock: { - current: 10, - lost: 0, - sold: 0, - nextRestock: { t_s: "never" }, - }, - }); - return ( - <FormProvider<T> - name="product" - object={state} - errors={{}} - valueHandler={setState} - > - <InputStock<T> name="stock" label="Stock" /> - <div> - <pre>{JSON.stringify(state, undefined, 2)}</pre> - </div> - </FormProvider> - ); -}; - -export const CreateStockWithRestock = () => { - const [state, setState] = useState<Partial<T>>({ - stock: { - current: 15, - lost: 0, - sold: 0, - nextRestock: { t_s: addDays(new Date(), 1).getTime() / 1000 }, - }, - }); - return ( - <FormProvider<T> - name="product" - object={state} - errors={{}} - valueHandler={setState} - > - <InputStock<T> name="stock" label="Stock" /> - <div> - <pre>{JSON.stringify(state, undefined, 2)}</pre> - </div> - </FormProvider> - ); -}; - -export const UpdatingProductWithManagedStock = () => { - const [state, setState] = useState<Partial<T>>({ - stock: { - current: 100, - lost: 0, - sold: 0, - nextRestock: { t_s: addDays(new Date(), 1).getTime() / 1000 }, - }, - }); - return ( - <FormProvider<T> - name="product" - object={state} - errors={{}} - valueHandler={setState} - > - <InputStock<T> name="stock" label="Stock" alreadyExist /> - <div> - <pre>{JSON.stringify(state, undefined, 2)}</pre> - </div> - </FormProvider> - ); -}; - -export const UpdatingProductWithInfiniteStock = () => { - const [state, setState] = useState<Partial<T>>({}); - return ( - <FormProvider<T> - name="product" - object={state} - errors={{}} - valueHandler={setState} - > - <InputStock<T> name="stock" label="Stock" alreadyExist /> - <div> - <pre>{JSON.stringify(state, undefined, 2)}</pre> - </div> - </FormProvider> - ); -}; diff --git a/packages/merchant-backoffice/src/components/form/InputStock.tsx b/packages/merchant-backoffice/src/components/form/InputStock.tsx deleted file mode 100644 index 158f441..0000000 --- a/packages/merchant-backoffice/src/components/form/InputStock.tsx +++ /dev/null @@ -1,171 +0,0 @@ -/* - 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 { Fragment, h } from "preact"; -import { MerchantBackend, Timestamp } from "../../declaration"; -import { InputProps, useField } from "./useField"; -import { FormProvider, FormErrors } from "./FormProvider"; -import { useLayoutEffect, useState } from "preact/hooks"; -import { Input } from "./Input"; -import { InputGroup } from "./InputGroup"; -import { InputNumber } from "./InputNumber"; -import { InputDate } from "./InputDate"; -import { Translate, useTranslator } from "../../i18n"; -import { InputLocation } from "./InputLocation"; - -export interface Props<T> extends InputProps<T> { - alreadyExist?: boolean; -} - - -type Entity = Stock - -export interface Stock { - current: number; - lost: number; - sold: number; - address?: MerchantBackend.Location; - nextRestock?: Timestamp; -} - -interface StockDelta { - incoming: number; - lost: number; -} - - -export function InputStock<T>({ name, tooltip, label, alreadyExist }: Props<keyof T>) { - const { error, value, onChange } = useField<T>(name); - - const [errors, setErrors] = useState<FormErrors<Entity>>({}) - - const [formValue, valueHandler] = useState<Partial<Entity>>(value) - const [addedStock, setAddedStock] = useState<StockDelta>({ incoming: 0, lost: 0 }) - const i18n = useTranslator() - - - useLayoutEffect(() => { - 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) { - return <Fragment> - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field has-addons"> - {!alreadyExist ? - <button class="button" - data-tooltip={i18n`click here to configure the stock of the product, leave it as is and the backend will not control stock`} - onClick={(): void => { valueHandler({ current: 0, lost: 0, sold: 0 } as Stock as any); }} > - <span><Translate>Manage stock</Translate></span> - </button> : <button class="button" - data-tooltip={i18n`this product has been configured without stock control`} - disabled > - <span><Translate>Infinite</Translate></span> - </button> - } - </div> - </div> - </div> - </Fragment > - } - - const currentStock = (formValue.current || 0) - (formValue.lost || 0) - (formValue.sold || 0) - - const stockAddedErrors: FormErrors<typeof addedStock> = { - lost: currentStock + addedStock.incoming < addedStock.lost ? - i18n`lost cannot be greater than current and incoming (max ${currentStock + addedStock.incoming})` - : undefined - } - - // const stockUpdateDescription = stockAddedErrors.lost ? '' : ( - // !!addedStock.incoming || !!addedStock.lost ? - // i18n`current stock will change from ${currentStock} to ${currentStock + addedStock.incoming - addedStock.lost}` : - // i18n`current stock will stay at ${currentStock}` - // ) - - return <Fragment> - <div class="card"> - <header class="card-header"> - <p class="card-header-title"> - {label} - {tooltip && <span class="icon" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </p> - </header> - <div class="card-content"> - <FormProvider<Entity> name="stock" errors={errors} object={formValue} valueHandler={valueHandler}> - {alreadyExist ? <Fragment> - - <FormProvider name="added" errors={stockAddedErrors} object={addedStock} valueHandler={setAddedStock as any}> - <InputNumber name="incoming" label={i18n`Incoming`} /> - <InputNumber name="lost" label={i18n`Lost`} /> - </FormProvider> - - {/* <div class="field is-horizontal"> - <div class="field-label is-normal" /> - <div class="field-body is-flex-grow-3"> - <div class="field"> - {stockUpdateDescription} - </div> - </div> - </div> */} - - </Fragment> : <InputNumber<Entity> name="current" - label={i18n`Current`} - side={ - <button class="button is-danger" - data-tooltip={i18n`remove stock control for this product`} - onClick={(): void => { valueHandler(undefined as any) }} > - <span><Translate>without stock</Translate></span> - </button> - } - />} - - <InputDate<Entity> name="nextRestock" label={i18n`Next restock`} withTimestampSupport /> - - <InputGroup<Entity> name="address" label={i18n`Delivery address`}> - <InputLocation name="address" /> - </InputGroup> - </FormProvider> - </div> - </div> - </Fragment> -} - // ( - - diff --git a/packages/merchant-backoffice/src/components/form/InputTaxes.tsx b/packages/merchant-backoffice/src/components/form/InputTaxes.tsx deleted file mode 100644 index 507a612..0000000 --- a/packages/merchant-backoffice/src/components/form/InputTaxes.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ -import { h, VNode } from "preact"; -import { useCallback, useState } from "preact/hooks"; -import * as yup from 'yup'; -import { MerchantBackend } from "../../declaration"; -import { Translate, useTranslator } from "../../i18n"; -import { TaxSchema as schema } from '../../schemas'; -import { FormErrors, FormProvider } from "./FormProvider"; -import { Input } from "./Input"; -import { InputGroup } from "./InputGroup"; -import { InputProps, useField } from "./useField"; - -export interface Props<T> extends InputProps<T> { - isValid?: (e: any) => boolean; -} - -type Entity = MerchantBackend.Tax -export function InputTaxes<T>({ name, readonly, label }: Props<keyof T>): VNode { - const { value: taxes, onChange, } = useField<T>(name); - - const [value, valueHandler] = useState<Partial<Entity>>({}) - // const [errors, setErrors] = useState<FormErrors<Entity>>({}) - - let errors: FormErrors<Entity> = {} - - try { - schema.validateSync(value, { abortEarly: false }) - } catch (err) { - if (err instanceof yup.ValidationError) { - const yupErrors = err.inner as yup.ValidationError[] - errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: cur.message }), {}) - } - } - const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined) - - const submit = useCallback((): void => { - onChange([value as any, ...taxes] as any) - valueHandler({}) - }, [value]) - - const i18n = useTranslator() - - //FIXME: translating plural singular - return ( - <InputGroup name="tax" label={label} alternative={taxes.length > 0 && <p>This product has {taxes.length} applicable taxes configured.</p>}> - <FormProvider<Entity> name="tax" errors={errors} object={value} valueHandler={valueHandler} > - - <div class="field is-horizontal"> - <div class="field-label is-normal" /> - <div class="field-body" style={{ display: 'block' }}> - {taxes.map((v: any, i: number) => <div key={i} class="tags has-addons mt-3 mb-0 mr-3" style={{ flexWrap: 'nowrap' }}> - <span class="tag is-medium is-info mb-0" style={{ maxWidth: '90%' }}><b>{v.tax}</b>: {v.name}</span> - <a class="tag is-medium is-danger is-delete mb-0" onClick={() => { - onChange(taxes.filter((f: any) => f !== v) as any); - valueHandler(v); - }} /> - </div> - )} - {!taxes.length && i18n`No taxes configured for this product.`} - </div> - </div> - - <Input<Entity> name="tax" label={i18n`Amount`} tooltip={i18n`Taxes can be in currencies that differ from the main currency used by the merchant.`}> - <Translate>Enter currency and value separated with a colon, e.g. "USD:2.3".</Translate> - </Input> - - <Input<Entity> name="name" label={i18n`Description`} tooltip={i18n`Legal name of the tax, e.g. VAT or import duties.`} /> - - <div class="buttons is-right mt-5"> - <button class="button is-info" - data-tooltip={i18n`add tax to the tax list`} - disabled={hasErrors} - onClick={submit}><Translate>Add</Translate></button> - </div> - </FormProvider> - </InputGroup> - ) -} diff --git a/packages/merchant-backoffice/src/components/form/InputWithAddon.tsx b/packages/merchant-backoffice/src/components/form/InputWithAddon.tsx deleted file mode 100644 index a16ebc2..0000000 --- a/packages/merchant-backoffice/src/components/form/InputWithAddon.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - 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 { ComponentChildren, h, VNode } from "preact"; -import { InputProps, useField } from "./useField"; - -export interface Props<T> extends InputProps<T> { - expand?: boolean; - inputType?: 'text' | 'number'; - addonBefore?: ComponentChildren; - addonAfter?: ComponentChildren; - toStr?: (v?: any) => string; - fromStr?: (s: string) => any; - inputExtra?: any, - children?: ComponentChildren, - side?: ComponentChildren; -} - -const defaultToString = (f?: any): string => f || '' -const defaultFromString = (v: string): any => v as any - -export function InputWithAddon<T>({ name, readonly, addonBefore, children, expand, label, placeholder, help, tooltip, inputType, inputExtra, side, addonAfter, toStr = defaultToString, fromStr = defaultFromString }: Props<keyof T>): VNode { - const { error, value, onChange, required } = useField<T>(name); - - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <div class="field has-addons"> - {addonBefore && <div class="control"> - <a class="button is-static">{addonBefore}</a> - </div>} - <p class={`control${expand ? " is-expanded" :""}${required ? " has-icons-right" : ''}`}> - <input {...(inputExtra || {})} class={error ? "input is-danger" : "input"} type={inputType} - placeholder={placeholder} readonly={readonly} - name={String(name)} value={toStr(value)} - onChange={(e): void => onChange(fromStr(e.currentTarget.value))} /> - {required && <span class="icon has-text-danger is-right"> - <i class="mdi mdi-alert" /> - </span>} - {help} - {children} - </p> - {addonAfter && <div class="control"> - <a class="button is-static">{addonAfter}</a> - </div>} - </div> - {error && <p class="help is-danger">{error}</p>} - </div> - {side} - </div> - </div>; -} diff --git a/packages/merchant-backoffice/src/components/form/TextField.tsx b/packages/merchant-backoffice/src/components/form/TextField.tsx deleted file mode 100644 index 2579a27..0000000 --- a/packages/merchant-backoffice/src/components/form/TextField.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - 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 { ComponentChildren, h, VNode } from "preact"; -import { useField, InputProps } from "./useField"; - -interface Props<T> extends InputProps<T> { - inputType?: 'text' | 'number' | 'multiline' | 'password'; - expand?: boolean; - side?: ComponentChildren; - children: ComponentChildren; -} - -export function TextField<T>({ name, tooltip, label, expand, help, children, side}: Props<keyof T>): VNode { - const { error } = useField<T>(name); - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <p class={expand ? "control is-expanded has-icons-right" : "control has-icons-right"}> - {children} - {help} - </p> - {error && <p class="help is-danger">{error}</p>} - </div> - {side} - </div> - </div>; -} diff --git a/packages/merchant-backoffice/src/components/form/useField.tsx b/packages/merchant-backoffice/src/components/form/useField.tsx deleted file mode 100644 index 8479d7a..0000000 --- a/packages/merchant-backoffice/src/components/form/useField.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - 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 { ComponentChildren, VNode } from "preact"; -import { useFormContext } from "./FormProvider"; - -interface Use<V> { - error?: string; - required: boolean; - value: any; - initial: any; - onChange: (v: V) => void; - toStr: (f: V | undefined) => string; - fromStr: (v: string) => V -} - -export function useField<T>(name: keyof T): Use<T[typeof name]> { - const { errors, object, initialObject, toStr, fromStr, valueHandler } = useFormContext<T>() - type P = typeof name - type V = T[P] - - const updateField = (field: P) => (value: V): void => { - return valueHandler((prev) => { - return setValueDeeper(prev, String(field).split('.'), value) - }) - } - - const defaultToString = ((f?: V): string => String(!f ? '' : f)) - const defaultFromString = ((v: string): V => v as any) - const value = readField(object, String(name)) - const initial = readField(initialObject, String(name)) - const isDirty = value !== initial - const hasError = readField(errors, String(name)) - return { - error: isDirty ? hasError : undefined, - required: !isDirty && hasError, - value, - initial, - onChange: updateField(name) as any, - toStr: toStr[name] ? toStr[name]! : defaultToString, - fromStr: fromStr[name] ? fromStr[name]! : defaultFromString, - } -} -/** - * read the field of an object an support accessing it using '.' - * - * @param object - * @param name - * @returns - */ -const readField = (object: any, name: string) => { - return name.split('.').reduce((prev, current) => prev && prev[current], object) -} - -const setValueDeeper = (object: any, names: string[], value: any): any => { - if (names.length === 0) return value - const [head, ...rest] = names - return { ...object, [head]: setValueDeeper(object[head] || {}, rest, value) } -} - -export interface InputProps<T> { - name: T; - label: ComponentChildren; - placeholder?: string; - tooltip?: ComponentChildren; - readonly?: boolean; - help?: ComponentChildren; -}
\ No newline at end of file diff --git a/packages/merchant-backoffice/src/components/form/useGroupField.tsx b/packages/merchant-backoffice/src/components/form/useGroupField.tsx deleted file mode 100644 index a73f464..0000000 --- a/packages/merchant-backoffice/src/components/form/useGroupField.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - 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 { useFormContext } from "./FormProvider"; - -interface Use { - hasError?: boolean; -} - -export function useGroupField<T>(name: keyof T): Use { - const f = useFormContext<T>(); - if (!f) - return {}; - - return { - hasError: readField(f.errors, String(name)) - }; -} - -const readField = (object: any, name: string) => { - return name.split('.').reduce((prev, current) => prev && prev[current], object) -} diff --git a/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx b/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx deleted file mode 100644 index d80c65c..0000000 --- a/packages/merchant-backoffice/src/components/instance/DefaultInstanceFormFields.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - 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 { Fragment, h, VNode } from "preact"; -import { useBackendContext } from "../../context/backend"; -import { useTranslator } from "../../i18n"; -import { Entity } from "../../paths/admin/create/CreatePage"; -import { Input } from "../form/Input"; -import { InputCurrency } from "../form/InputCurrency"; -import { InputDuration } from "../form/InputDuration"; -import { InputGroup } from "../form/InputGroup"; -import { InputImage } from "../form/InputImage"; -import { InputLocation } from "../form/InputLocation"; -import { InputPaytoForm } from "../form/InputPaytoForm"; -import { InputWithAddon } from "../form/InputWithAddon"; - -export function DefaultInstanceFormFields({ - readonlyId, - showId, -}: { - readonlyId?: boolean; - showId: boolean; -}): VNode { - const i18n = useTranslator(); - const backend = useBackendContext(); - return ( - <Fragment> - {showId && ( - <InputWithAddon<Entity> - name="id" - addonBefore={`${backend.url}/instances/`} - readonly={readonlyId} - label={i18n`Identifier`} - tooltip={i18n`Name of the instance in URLs. The 'default' instance is special in that it is used to administer other instances.`} - /> - )} - - <Input<Entity> - name="name" - label={i18n`Business name`} - tooltip={i18n`Legal name of the business represented by this instance.`} - /> - - <Input<Entity> - name="email" - label={i18n`Email`} - tooltip={i18n`Contact email`} - /> - - <Input<Entity> - name="website" - label={i18n`Website URL`} - tooltip={i18n`URL.`} - /> - - <InputImage<Entity> - name="logo" - label={i18n`Logo`} - tooltip={i18n`Logo image.`} - /> - - <InputPaytoForm<Entity> - name="payto_uris" - label={i18n`Bank account`} - tooltip={i18n`URI specifying bank account for crediting revenue.`} - /> - - <InputCurrency<Entity> - name="default_max_deposit_fee" - label={i18n`Default max deposit fee`} - tooltip={i18n`Maximum deposit fees this merchant is willing to pay per order by default.`} - /> - - <InputCurrency<Entity> - name="default_max_wire_fee" - label={i18n`Default max wire fee`} - tooltip={i18n`Maximum wire fees this merchant is willing to pay per wire transfer by default.`} - /> - - <Input<Entity> - name="default_wire_fee_amortization" - label={i18n`Default wire fee amortization`} - tooltip={i18n`Number of orders excess wire transfer fees will be divided by to compute per order surcharge.`} - /> - - <InputGroup - name="address" - label={i18n`Address`} - tooltip={i18n`Physical location of the merchant.`} - > - <InputLocation name="address" /> - </InputGroup> - - <InputGroup - name="jurisdiction" - label={i18n`Jurisdiction`} - tooltip={i18n`Jurisdiction for legal disputes with the merchant.`} - > - <InputLocation name="jurisdiction" /> - </InputGroup> - - <InputDuration<Entity> - name="default_pay_delay" - label={i18n`Default payment delay`} - withForever - tooltip={i18n`Time customers have to pay an order before the offer expires by default.`} - /> - - <InputDuration<Entity> - name="default_wire_transfer_delay" - label={i18n`Default wire transfer delay`} - tooltip={i18n`Maximum time an exchange is allowed to delay wiring funds to the merchant, enabling it to aggregate smaller payments into larger wire transfers and reducing wire fees.`} - withForever - /> - </Fragment> - ); -} diff --git a/packages/merchant-backoffice/src/components/menu/LangSelector.tsx b/packages/merchant-backoffice/src/components/menu/LangSelector.tsx deleted file mode 100644 index 41d08a5..0000000 --- a/packages/merchant-backoffice/src/components/menu/LangSelector.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import langIcon from '../../assets/icons/languageicon.svg'; -import { useTranslationContext } from "../../context/translation"; -import { strings as messages } from '../../i18n/strings' - -type LangsNames = { - [P in keyof typeof messages]: string -} - -const names: LangsNames = { - es: 'Español [es]', - en: 'English [en]', - fr: 'Français [fr]', - de: 'Deutsch [de]', - sv: 'Svenska [sv]', - it: 'Italiano [it]', -} - -function getLangName(s: keyof LangsNames | string) { - if (names[s]) return names[s] - return s -} - -export function LangSelector(): VNode { - const [updatingLang, setUpdatingLang] = useState(false) - const { lang, changeLanguage } = useTranslationContext() - - return <div class="dropdown is-active "> - <div class="dropdown-trigger"> - <button class="button has-tooltip-left" - data-tooltip="change language selection" - aria-haspopup="true" - aria-controls="dropdown-menu" onClick={() => setUpdatingLang(!updatingLang)}> - <div class="icon is-small is-left"> - <img src={langIcon} /> - </div> - <span>{getLangName(lang)}</span> - <div class="icon is-right"> - <i class="mdi mdi-chevron-down" /> - </div> - </button> - </div> - {updatingLang && <div class="dropdown-menu" id="dropdown-menu" role="menu"> - <div class="dropdown-content"> - {Object.keys(messages) - .filter((l) => l !== lang) - .map(l => <a key={l} class="dropdown-item" value={l} onClick={() => { changeLanguage(l); setUpdatingLang(false) }}>{getLangName(l)}</a>)} - </div> - </div>} - </div> -}
\ No newline at end of file diff --git a/packages/merchant-backoffice/src/components/menu/NavigationBar.tsx b/packages/merchant-backoffice/src/components/menu/NavigationBar.tsx deleted file mode 100644 index e1bb4c7..0000000 --- a/packages/merchant-backoffice/src/components/menu/NavigationBar.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, VNode } from 'preact'; -import logo from '../../assets/logo.jpeg'; -import { LangSelector } from './LangSelector'; - -interface Props { - onMobileMenu: () => void; - title: string; -} - -export function NavigationBar({ onMobileMenu, title }: Props): VNode { - return (<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation"> - <div class="navbar-brand"> - <span class="navbar-item" style={{ fontSize: 24, fontWeight: 900 }}>{title}</span> - - <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" onClick={(e) => { - onMobileMenu() - e.stopPropagation() - }}> - <span aria-hidden="true" /> - <span aria-hidden="true" /> - <span aria-hidden="true" /> - </a> - </div> - - <div class="navbar-menu "> - <a class="navbar-start is-justify-content-center is-flex-grow-1" href="https://taler.net"> - <img src={logo} style={{ height: 50, maxHeight: 50 }} /> - </a> - <div class="navbar-end"> - <div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}> - <LangSelector /> - </div> - </div> - </div> - </nav> - ); -}
\ No newline at end of file diff --git a/packages/merchant-backoffice/src/components/menu/SideBar.tsx b/packages/merchant-backoffice/src/components/menu/SideBar.tsx deleted file mode 100644 index e9c5ef8..0000000 --- a/packages/merchant-backoffice/src/components/menu/SideBar.tsx +++ /dev/null @@ -1,227 +0,0 @@ -/* - 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 { Fragment, h, VNode } from "preact"; -import { useCallback } from "preact/hooks"; -import { useBackendContext } from "../../context/backend"; -import { useConfigContext } from "../../context/config"; -import { useInstanceContext } from "../../context/instance"; -import { useInstanceKYCDetails } from "../../hooks/instance"; -import { Translate } from "../../i18n"; -import { LangSelector } from "./LangSelector"; - -interface Props { - onLogout: () => void; - mobile?: boolean; - instance: string; - admin?: boolean; - mimic?: boolean; -} - -export function Sidebar({ - mobile, - instance, - onLogout, - admin, - mimic, -}: Props): VNode { - const config = useConfigContext(); - const backend = useBackendContext(); - - const kycStatus = useInstanceKYCDetails(); - const needKYC = kycStatus.ok && kycStatus.data.type === "redirect"; - // const withInstanceIdIfNeeded = useCallback(function (path: string) { - // if (mimic) { - // return path + '?instance=' + instance - // } - // return path - // },[instance]) - - return ( - <aside class="aside is-placed-left is-expanded"> - {mobile && ( - <div - class="footer" - onClick={(e) => { - return e.stopImmediatePropagation(); - }} - > - <LangSelector /> - </div> - )} - <div class="aside-tools"> - <div class="aside-tools-label"> - <div> - <b>Taler</b> Backoffice - </div> - <div - class="is-size-7 has-text-right" - style={{ lineHeight: 0, marginTop: -10 }} - > - {process.env.__VERSION__} ({config.version}) - </div> - </div> - </div> - <div class="menu is-menu-main"> - {instance ? ( - <Fragment> - <p class="menu-label"> - <Translate>Instance</Translate> - </p> - <ul class="menu-list"> - <li> - <a href={"/update"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-square-edit-outline" /> - </span> - <span class="menu-item-label"> - <Translate>Settings</Translate> - </span> - </a> - </li> - <li> - <a href={"/orders"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-cash-register" /> - </span> - <span class="menu-item-label"> - <Translate>Orders</Translate> - </span> - </a> - </li> - <li> - <a href={"/products"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-shopping" /> - </span> - <span class="menu-item-label"> - <Translate>Products</Translate> - </span> - </a> - </li> - <li> - <a href={"/transfers"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-bank" /> - </span> - <span class="menu-item-label"> - <Translate>Transfers</Translate> - </span> - </a> - </li> - <li> - <a href={"/reserves"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-cash" /> - </span> - <span class="menu-item-label">Reserves</span> - </a> - </li> - {needKYC && ( - <li> - <a href={"/kyc"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-account-check" /> - </span> - <span class="menu-item-label">KYC Status</span> - </a> - </li> - )} - </ul> - </Fragment> - ) : undefined} - <p class="menu-label"> - <Translate>Connection</Translate> - </p> - <ul class="menu-list"> - <li> - <div> - <span style={{ width: "3rem" }} class="icon"> - <i class="mdi mdi-currency-eur" /> - </span> - <span class="menu-item-label">{config.currency}</span> - </div> - </li> - <li> - <div> - <span style={{ width: "3rem" }} class="icon"> - <i class="mdi mdi-web" /> - </span> - <span class="menu-item-label"> - {new URL(backend.url).hostname} - </span> - </div> - </li> - <li> - <div> - <span style={{ width: "3rem" }} class="icon"> - ID - </span> - <span class="menu-item-label"> - {!instance ? "default" : instance} - </span> - </div> - </li> - {admin && !mimic && ( - <Fragment> - <p class="menu-label"> - <Translate>Instances</Translate> - </p> - <li> - <a href={"/instance/new"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-plus" /> - </span> - <span class="menu-item-label"> - <Translate>New</Translate> - </span> - </a> - </li> - <li> - <a href={"/instances"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-format-list-bulleted" /> - </span> - <span class="menu-item-label"> - <Translate>List</Translate> - </span> - </a> - </li> - </Fragment> - )} - <li> - <a - class="has-icon is-state-info is-hoverable" - onClick={(): void => onLogout()} - > - <span class="icon"> - <i class="mdi mdi-logout default" /> - </span> - <span class="menu-item-label"> - <Translate>Log out</Translate> - </span> - </a> - </li> - </ul> - </div> - </aside> - ); -} diff --git a/packages/merchant-backoffice/src/components/menu/index.tsx b/packages/merchant-backoffice/src/components/menu/index.tsx deleted file mode 100644 index 0a621af..0000000 --- a/packages/merchant-backoffice/src/components/menu/index.tsx +++ /dev/null @@ -1,210 +0,0 @@ -/* - 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/> - */ - -import { ComponentChildren, Fragment, h, VNode } from "preact"; -import Match from "preact-router/match"; -import { useEffect, useState } from "preact/hooks"; -import { AdminPaths } from "../../AdminRoutes"; -import { InstancePaths } from "../../InstanceRoutes"; -import { Notification } from "../../utils/types"; -import { NavigationBar } from "./NavigationBar"; -import { Sidebar } from "./SideBar"; - -function getInstanceTitle(path: string, id: string): string { - switch (path) { - case InstancePaths.update: - return `${id}: Settings`; - case InstancePaths.order_list: - return `${id}: Orders`; - case InstancePaths.order_new: - return `${id}: New order`; - case InstancePaths.product_list: - return `${id}: Products`; - case InstancePaths.product_new: - return `${id}: New product`; - case InstancePaths.product_update: - return `${id}: Update product`; - case InstancePaths.reserves_new: - return `${id}: New reserve`; - case InstancePaths.reserves_list: - return `${id}: Reserves`; - case InstancePaths.transfers_list: - return `${id}: Transfers`; - case InstancePaths.transfers_new: - return `${id}: New transfer`; - default: - return ""; - } -} - -function getAdminTitle(path: string, instance: string) { - if (path === AdminPaths.new_instance) return `New instance`; - if (path === AdminPaths.list_instances) return `Instances`; - return getInstanceTitle(path, instance); -} - -interface MenuProps { - title?: string; - instance: string; - admin?: boolean; - onLogout?: () => void; - setInstanceName: (s: string) => void; -} - -function WithTitle({ - title, - children, -}: { - title: string; - children: ComponentChildren; -}): VNode { - useEffect(() => { - document.title = `Taler Backoffice: ${title}`; - }, [title]); - return <Fragment>{children}</Fragment>; -} - -export function Menu({ - onLogout, - title, - instance, - admin, - setInstanceName, -}: MenuProps): VNode { - const [mobileOpen, setMobileOpen] = useState(false); - - return ( - <Match> - {({ path }: any) => { - const titleWithSubtitle = title - ? title - : !admin - ? getInstanceTitle(path, instance) - : getAdminTitle(path, instance); - const adminInstance = instance === "default"; - const mimic = admin && !adminInstance; - return ( - <WithTitle title={titleWithSubtitle}> - <div - class={mobileOpen ? "has-aside-mobile-expanded" : ""} - onClick={() => setMobileOpen(false)} - > - <NavigationBar - onMobileMenu={() => setMobileOpen(!mobileOpen)} - title={titleWithSubtitle} - /> - - {onLogout && ( - <Sidebar - onLogout={onLogout} - admin={admin} - mimic={mimic} - instance={instance} - mobile={mobileOpen} - /> - )} - - {mimic && ( - <nav class="level"> - <div class="level-item has-text-centered has-background-warning"> - <p class="is-size-5"> - You are viewing the instance <b>"{instance}"</b>.{" "} - <a - href="#/instances" - onClick={(e) => { - setInstanceName("default"); - }} - > - go back - </a> - </p> - </div> - </nav> - )} - </div> - </WithTitle> - ); - }} - </Match> - ); -} - -interface NotYetReadyAppMenuProps { - title: string; - onLogout?: () => void; -} - -interface NotifProps { - notification?: Notification; -} -export function NotificationCard({ - notification: n, -}: NotifProps): VNode | null { - if (!n) return null; - return ( - <div class="notification"> - <div class="columns is-vcentered"> - <div class="column is-12"> - <article - class={ - n.type === "ERROR" - ? "message is-danger" - : n.type === "WARN" - ? "message is-warning" - : "message is-info" - } - > - <div class="message-header"> - <p>{n.message}</p> - </div> - {n.description && ( - <div class="message-body"> - <div>{n.description}</div> - {n.details && <pre>{n.details}</pre>} - </div> - )} - </article> - </div> - </div> - </div> - ); -} - -export function NotYetReadyAppMenu({ - onLogout, - title, -}: NotYetReadyAppMenuProps): VNode { - const [mobileOpen, setMobileOpen] = useState(false); - - useEffect(() => { - document.title = `Taler Backoffice: ${title}`; - }, [title]); - - return ( - <div - class={mobileOpen ? "has-aside-mobile-expanded" : ""} - onClick={() => setMobileOpen(false)} - > - <NavigationBar - onMobileMenu={() => setMobileOpen(!mobileOpen)} - title={title} - /> - {onLogout && ( - <Sidebar onLogout={onLogout} instance="" mobile={mobileOpen} /> - )} - </div> - ); -} diff --git a/packages/merchant-backoffice/src/components/modal/index.tsx b/packages/merchant-backoffice/src/components/modal/index.tsx deleted file mode 100644 index a7edb9e..0000000 --- a/packages/merchant-backoffice/src/components/modal/index.tsx +++ /dev/null @@ -1,262 +0,0 @@ -/* - 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 { ComponentChildren, h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { useInstanceContext } from "../../context/instance"; -import { Translate, useTranslator } from "../../i18n"; -import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants"; -import { Loading, Spinner } from "../exception/loading"; -import { FormProvider } from "../form/FormProvider"; -import { Input } from "../form/Input"; - -interface Props { - active?: boolean; - description?: string; - onCancel?: () => void; - onConfirm?: () => void; - label?: string; - children?: ComponentChildren; - danger?: boolean; - disabled?: boolean; -} - -export function ConfirmModal({ active, description, onCancel, onConfirm, children, danger, disabled, label = 'Confirm' }: Props): VNode { - return <div class={active ? "modal is-active" : "modal"}> - <div class="modal-background " onClick={onCancel} /> - <div class="modal-card" style={{maxWidth: 700}}> - <header class="modal-card-head"> - {!description ? null : <p class="modal-card-title"><b>{description}</b></p>} - <button class="delete " aria-label="close" onClick={onCancel} /> - </header> - <section class="modal-card-body"> - {children} - </section> - <footer class="modal-card-foot"> - <div class="buttons is-right" style={{ width: '100%' }}> - <button class="button " onClick={onCancel} ><Translate>Cancel</Translate></button> - <button class={danger ? "button is-danger " : "button is-info "} disabled={disabled} onClick={onConfirm} ><Translate>{label}</Translate></button> - </div> - </footer> - </div> - <button class="modal-close is-large " aria-label="close" onClick={onCancel} /> - </div> -} - -export function ContinueModal({ active, description, onCancel, onConfirm, children, disabled }: Props): VNode { - return <div class={active ? "modal is-active" : "modal"}> - <div class="modal-background " onClick={onCancel} /> - <div class="modal-card"> - <header class="modal-card-head has-background-success"> - {!description ? null : <p class="modal-card-title">{description}</p>} - <button class="delete " aria-label="close" onClick={onCancel} /> - </header> - <section class="modal-card-body"> - {children} - </section> - <footer class="modal-card-foot"> - <div class="buttons is-right" style={{ width: '100%' }}> - <button class="button is-success " disabled={disabled} onClick={onConfirm} ><Translate>Continue</Translate></button> - </div> - </footer> - </div> - <button class="modal-close is-large " aria-label="close" onClick={onCancel} /> - </div> -} - -export function SimpleModal({ onCancel, children }: any): VNode { - return <div class="modal is-active"> - <div class="modal-background " onClick={onCancel} /> - <div class="modal-card"> - <section class="modal-card-body is-main-section"> - {children} - </section> - </div> - <button class="modal-close is-large " aria-label="close" onClick={onCancel} /> - </div> -} - -export function ClearConfirmModal({ description, onCancel, onClear, onConfirm, children }: Props & { onClear?: () => void }): VNode { - return <div class="modal is-active"> - <div class="modal-background " onClick={onCancel} /> - <div class="modal-card"> - <header class="modal-card-head"> - {!description ? null : <p class="modal-card-title">{description}</p>} - <button class="delete " aria-label="close" onClick={onCancel} /> - </header> - <section class="modal-card-body is-main-section"> - {children} - </section> - <footer class="modal-card-foot"> - {onClear && <button class="button is-danger" onClick={onClear} disabled={onClear === undefined} ><Translate>Clear</Translate></button>} - <div class="buttons is-right" style={{ width: '100%' }}> - <button class="button " onClick={onCancel} ><Translate>Cancel</Translate></button> - <button class="button is-info" onClick={onConfirm} disabled={onConfirm === undefined} ><Translate>Confirm</Translate></button> - </div> - </footer> - </div> - <button class="modal-close is-large " aria-label="close" onClick={onCancel} /> - </div> -} - -interface DeleteModalProps { - element: { id: string, name: string }; - onCancel: () => void; - onConfirm: (id: string) => void; -} - -export function DeleteModal({ element, onCancel, onConfirm }: DeleteModalProps): VNode { - return <ConfirmModal label={`Delete instance`} description={`Delete the instance "${element.name}"`} danger active onCancel={onCancel} onConfirm={() => onConfirm(element.id)}> - <p>If you delete the instance named <b>"{element.name}"</b> (ID: <b>{element.id}</b>), the merchant will no longer be able to process orders or refunds</p> - <p>This action deletes the instance private key, but preserves all transaction data. You can still access that data after deleting the instance.</p> - <p class="warning">Deleting an instance <b>cannot be undone</b>.</p> - </ConfirmModal> -} - -export function PurgeModal({ element, onCancel, onConfirm }: DeleteModalProps): VNode { - return <ConfirmModal label={`Purge the instance`} description={`Purge the instance "${element.name}"`} danger active onCancel={onCancel} onConfirm={() => onConfirm(element.id)}> - <p>If you purge the instance named <b>"{element.name}"</b> (ID: <b>{element.id}</b>), you will also delete all it's transaction data.</p> - <p>The instance will disappear from your list, and you will no longer be able to access it's data.</p> - <p class="warning">Purging an instance <b>cannot be undone</b>.</p> - </ConfirmModal> -} - -interface UpdateTokenModalProps { - oldToken?: string; - onCancel: () => void; - onConfirm: (value: string) => void; - onClear: () => void; -} - -//FIXME: merge UpdateTokenModal with SetTokenNewInstanceModal -export function UpdateTokenModal({ onCancel, onClear, onConfirm, oldToken }: UpdateTokenModalProps): VNode { - type State = { old_token: string, new_token: string, repeat_token: string } - const [form, setValue] = useState<Partial<State>>({ - old_token: '', new_token: '', repeat_token: '', - }) - const i18n = useTranslator() - - const hasInputTheCorrectOldToken = oldToken && oldToken !== form.old_token - const errors = { - old_token: hasInputTheCorrectOldToken ? i18n`is not the same as the current access token` : undefined, - new_token: !form.new_token ? i18n`cannot be empty` : (form.new_token === form.old_token ? i18n`cannot be the same as the old token` : undefined), - repeat_token: form.new_token !== form.repeat_token ? i18n`is not the same` : undefined - } - - const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined) - - const instance = useInstanceContext() - - const text = i18n`You are updating the access token from instance with id ${instance.id}` - - return <ClearConfirmModal description={text} - onCancel={onCancel} - onConfirm={!hasErrors ? () => onConfirm(form.new_token!) : undefined} - onClear={!hasInputTheCorrectOldToken && oldToken ? onClear : undefined} - > - <div class="columns"> - <div class="column" /> - <div class="column is-four-fifths" > - <FormProvider errors={errors} object={form} valueHandler={setValue}> - {oldToken && <Input<State> name="old_token" label={i18n`Old access token`} tooltip={i18n`access token currently in use`} inputType="password" />} - <Input<State> name="new_token" label={i18n`New access token`} tooltip={i18n`next access token to be used`} inputType="password" /> - <Input<State> name="repeat_token" label={i18n`Repeat access token`} tooltip={i18n`confirm the same access token`} inputType="password" /> - </FormProvider> - <p><Translate>Clearing the access token will mean public access to the instance</Translate></p> - </div> - <div class="column" /> - </div> - </ClearConfirmModal> -} - -export function SetTokenNewInstanceModal({ onCancel, onClear, onConfirm }: UpdateTokenModalProps): VNode { - type State = { old_token: string, new_token: string, repeat_token: string } - const [form, setValue] = useState<Partial<State>>({ - new_token: '', repeat_token: '', - }) - const i18n = useTranslator() - - const errors = { - new_token: !form.new_token ? i18n`cannot be empty` : (form.new_token === form.old_token ? i18n`cannot be the same as the old access token` : undefined), - repeat_token: form.new_token !== form.repeat_token ? i18n`is not the same` : undefined - } - - const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined) - - - return <div class="modal is-active"> - <div class="modal-background " onClick={onCancel} /> - <div class="modal-card"> - <header class="modal-card-head"> - <p class="modal-card-title">{i18n`You are setting the access token for the new instance`}</p> - <button class="delete " aria-label="close" onClick={onCancel} /> - </header> - <section class="modal-card-body is-main-section"> - <div class="columns"> - <div class="column" /> - <div class="column is-four-fifths" > - <FormProvider errors={errors} object={form} valueHandler={setValue}> - <Input<State> name="new_token" label={i18n`New access token`} tooltip={i18n`next access token to be used`} inputType="password" /> - <Input<State> name="repeat_token" label={i18n`Repeat access token`} tooltip={i18n`confirm the same access token`} inputType="password" /> - </FormProvider> - <p><Translate>With external authorization method no check will be done by the merchant backend</Translate></p> - </div> - <div class="column" /> - </div> - </section> - <footer class="modal-card-foot"> - {onClear && <button class="button is-danger" onClick={onClear} disabled={onClear === undefined} ><Translate>Set external authorization</Translate></button>} - <div class="buttons is-right" style={{ width: '100%' }}> - <button class="button " onClick={onCancel} ><Translate>Cancel</Translate></button> - <button class="button is-info" onClick={() => onConfirm(form.new_token!)} disabled={hasErrors} ><Translate>Set access token</Translate></button> - </div> - </footer> - </div> - <button class="modal-close is-large " aria-label="close" onClick={onCancel} /> - </div> -} - -export function LoadingModal({ onCancel }: { onCancel: () => void }): VNode { - const i18n = useTranslator() - return <div class="modal is-active"> - <div class="modal-background " onClick={onCancel} /> - <div class="modal-card"> - <header class="modal-card-head"> - <p class="modal-card-title"><Translate>Operation in progress...</Translate></p> - </header> - <section class="modal-card-body"> - <div class="columns"> - <div class="column" /> - <Spinner /> - <div class="column" /> - </div> - <p>{i18n`The operation will be automatically canceled after ${DEFAULT_REQUEST_TIMEOUT} seconds`}</p> - </section> - <footer class="modal-card-foot"> - <div class="buttons is-right" style={{ width: '100%' }}> - <button class="button " onClick={onCancel} ><Translate>Cancel</Translate></button> - </div> - </footer> - </div> - <button class="modal-close is-large " aria-label="close" onClick={onCancel} /> - </div> -} diff --git a/packages/merchant-backoffice/src/components/notifications/CreatedSuccessfully.tsx b/packages/merchant-backoffice/src/components/notifications/CreatedSuccessfully.tsx deleted file mode 100644 index e0b355c..0000000 --- a/packages/merchant-backoffice/src/components/notifications/CreatedSuccessfully.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - 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 { ComponentChildren, h, VNode } from "preact"; - -interface Props { - onCreateAnother?: () => void; - onConfirm: () => void; - children: ComponentChildren; -} - -export function CreatedSuccessfully({ children, onConfirm, onCreateAnother }: Props): VNode { - return <div class="columns is-fullwidth is-vcentered mt-3"> - <div class="column" /> - <div class="column is-four-fifths"> - <div class="card"> - <header class="card-header has-background-success"> - <p class="card-header-title has-text-white-ter"> - Success. - </p> - </header> - <div class="card-content"> - {children} - </div> - </div> - <div class="buttons is-right"> - {onCreateAnother && <button class="button is-info" onClick={onCreateAnother}>Create another</button>} - <button class="button is-info" onClick={onConfirm}>Continue</button> - </div> - </div> - <div class="column" /> - </div> -} diff --git a/packages/merchant-backoffice/src/components/notifications/Notifications.stories.tsx b/packages/merchant-backoffice/src/components/notifications/Notifications.stories.tsx deleted file mode 100644 index 3b95295..0000000 --- a/packages/merchant-backoffice/src/components/notifications/Notifications.stories.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h } from 'preact'; -import { Notifications } from './index'; - - -export default { - title: 'Components/Notification', - component: Notifications, - argTypes: { - removeNotification: { action: 'removeNotification' }, - }, -}; - -export const Info = (a: any) => <Notifications {...a} />; -Info.args = { - notifications: [{ - message: 'Title', - description: 'Some large description', - type: 'INFO', - }] -} -export const Warn = (a: any) => <Notifications {...a} />; -Warn.args = { - notifications: [{ - message: 'Title', - description: 'Some large description', - type: 'WARN', - }] -} -export const Error = (a: any) => <Notifications {...a} />; -Error.args = { - notifications: [{ - message: 'Title', - description: 'Some large description', - type: 'ERROR', - }] -} diff --git a/packages/merchant-backoffice/src/components/notifications/index.tsx b/packages/merchant-backoffice/src/components/notifications/index.tsx deleted file mode 100644 index 34bd40e..0000000 --- a/packages/merchant-backoffice/src/components/notifications/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, VNode } from "preact"; -import { MessageType, Notification } from "../../utils/types"; - -interface Props { - notifications: Notification[]; - removeNotification?: (n: Notification) => void; -} - -function messageStyle(type: MessageType): string { - switch (type) { - case "INFO": return "message is-info"; - case "WARN": return "message is-warning"; - case "ERROR": return "message is-danger"; - case "SUCCESS": return "message is-success"; - default: return "message" - } -} - -export function Notifications({ notifications, removeNotification }: Props): VNode { - return <div class="toast"> - {notifications.map((n,i) => <article key={i} class={messageStyle(n.type)}> - <div class="message-header"> - <p>{n.message}</p> - <button class="delete" onClick={() => removeNotification && removeNotification(n)} /> - </div> - {n.description && <div class="message-body"> - {n.description} - </div>} - </article>)} - </div> -}
\ No newline at end of file diff --git a/packages/merchant-backoffice/src/components/picker/DatePicker.tsx b/packages/merchant-backoffice/src/components/picker/DatePicker.tsx deleted file mode 100644 index 084b7b0..0000000 --- a/packages/merchant-backoffice/src/components/picker/DatePicker.tsx +++ /dev/null @@ -1,324 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, Component } from "preact"; - -interface Props { - closeFunction?: () => void; - dateReceiver?: (d: Date) => void; - opened?: boolean; -} -interface State { - displayedMonth: number; - displayedYear: number; - selectYearMode: boolean; - currentDate: Date; -} - -// inspired by https://codepen.io/m4r1vs/pen/MOOxyE -export class DatePicker extends Component<Props, State> { - - closeDatePicker() { - this.props.closeFunction && this.props.closeFunction(); // Function gets passed by parent - } - - /** - * Gets fired when a day gets clicked. - * @param {object} e The event thrown by the <span /> element clicked - */ - dayClicked(e: any) { - - const element = e.target; // the actual element clicked - - if (element.innerHTML === '') return false; // don't continue if <span /> empty - - // get date from clicked element (gets attached when rendered) - const date = new Date(element.getAttribute('data-value')); - - // update the state - this.setState({ currentDate: date }); - this.passDateToParent(date) - } - - /** - * returns days in month as array - * @param {number} month the month to display - * @param {number} year the year to display - */ - getDaysByMonth(month: number, year: number) { - - const calendar = []; - - const date = new Date(year, month, 1); // month to display - - const firstDay = new Date(year, month, 1).getDay(); // first weekday of month - const lastDate = new Date(year, month + 1, 0).getDate(); // last date of month - - let day: number | null = 0; - - // the calendar is 7*6 fields big, so 42 loops - for (let i = 0; i < 42; i++) { - - if (i >= firstDay && day !== null) day = day + 1; - if (day !== null && day > lastDate) day = null; - - // append the calendar Array - calendar.push({ - day: (day === 0 || day === null) ? null : day, // null or number - date: (day === 0 || day === null) ? null : new Date(year, month, day), // null or Date() - today: (day === now.getDate() && month === now.getMonth() && year === now.getFullYear()) // boolean - }); - } - - return calendar; - } - - /** - * Display previous month by updating state - */ - displayPrevMonth() { - if (this.state.displayedMonth <= 0) { - this.setState({ - displayedMonth: 11, - displayedYear: this.state.displayedYear - 1 - }); - } - else { - this.setState({ - displayedMonth: this.state.displayedMonth - 1 - }); - } - } - - /** - * Display next month by updating state - */ - displayNextMonth() { - if (this.state.displayedMonth >= 11) { - this.setState({ - displayedMonth: 0, - displayedYear: this.state.displayedYear + 1 - }); - } - else { - this.setState({ - displayedMonth: this.state.displayedMonth + 1 - }); - } - } - - /** - * Display the selected month (gets fired when clicking on the date string) - */ - displaySelectedMonth() { - if (this.state.selectYearMode) { - this.toggleYearSelector(); - } - else { - if (!this.state.currentDate) return false; - this.setState({ - displayedMonth: this.state.currentDate.getMonth(), - displayedYear: this.state.currentDate.getFullYear() - }); - } - } - - toggleYearSelector() { - this.setState({ selectYearMode: !this.state.selectYearMode }); - } - - changeDisplayedYear(e: any) { - const element = e.target; - this.toggleYearSelector(); - this.setState({ displayedYear: parseInt(element.innerHTML, 10), displayedMonth: 0 }); - } - - /** - * Pass the selected date to parent when 'OK' is clicked - */ - passSavedDateDateToParent() { - this.passDateToParent(this.state.currentDate) - } - passDateToParent(date: Date) { - if (typeof this.props.dateReceiver === 'function') this.props.dateReceiver(date); - this.closeDatePicker(); - } - - componentDidUpdate() { - if (this.state.selectYearMode) { - document.getElementsByClassName('selected')[0].scrollIntoView(); // works in every browser incl. IE, replace with scrollIntoViewIfNeeded when browsers support it - } - } - - constructor() { - super(); - - this.closeDatePicker = this.closeDatePicker.bind(this); - this.dayClicked = this.dayClicked.bind(this); - this.displayNextMonth = this.displayNextMonth.bind(this); - this.displayPrevMonth = this.displayPrevMonth.bind(this); - this.getDaysByMonth = this.getDaysByMonth.bind(this); - this.changeDisplayedYear = this.changeDisplayedYear.bind(this); - this.passDateToParent = this.passDateToParent.bind(this); - this.toggleYearSelector = this.toggleYearSelector.bind(this); - this.displaySelectedMonth = this.displaySelectedMonth.bind(this); - - - this.state = { - currentDate: now, - displayedMonth: now.getMonth(), - displayedYear: now.getFullYear(), - selectYearMode: false - } - } - - render() { - - const { currentDate, displayedMonth, displayedYear, selectYearMode } = this.state; - - return ( - <div> - <div class={`datePicker ${ this.props.opened && "datePicker--opened"}`} > - - <div class="datePicker--titles"> - <h3 style={{ - color: selectYearMode ? 'rgba(255,255,255,.87)' : 'rgba(255,255,255,.57)' - }} onClick={this.toggleYearSelector}>{currentDate.getFullYear()}</h3> - <h2 style={{ - color: !selectYearMode ? 'rgba(255,255,255,.87)' : 'rgba(255,255,255,.57)' - }} onClick={this.displaySelectedMonth}> - {dayArr[currentDate.getDay()]}, {monthArrShort[currentDate.getMonth()]} {currentDate.getDate()} - </h2> - </div> - - {!selectYearMode && <nav> - <span onClick={this.displayPrevMonth} class="icon"><i style={{ transform: 'rotate(180deg)' }} class="mdi mdi-forward" /></span> - <h4>{monthArrShortFull[displayedMonth]} {displayedYear}</h4> - <span onClick={this.displayNextMonth} class="icon"><i class="mdi mdi-forward" /></span> - </nav>} - - <div class="datePicker--scroll"> - - {!selectYearMode && <div class="datePicker--calendar" > - - <div class="datePicker--dayNames"> - {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day,i) => <span key={i}>{day}</span>)} - </div> - - <div onClick={this.dayClicked} class="datePicker--days"> - - {/* - Loop through the calendar object returned by getDaysByMonth(). - */} - - {this.getDaysByMonth(this.state.displayedMonth, this.state.displayedYear) - .map( - day => { - let selected = false; - - if (currentDate && day.date) selected = (currentDate.toLocaleDateString() === day.date.toLocaleDateString()); - - return (<span key={day.day} - class={(day.today ? 'datePicker--today ' : '') + (selected ? 'datePicker--selected' : '')} - disabled={!day.date} - data-value={day.date} - > - {day.day} - </span>) - } - ) - } - - </div> - - </div>} - - {selectYearMode && <div class="datePicker--selectYear"> - - {yearArr.map(year => ( - <span key={year} class={(year === displayedYear) ? 'selected' : ''} onClick={this.changeDisplayedYear}> - {year} - </span> - ))} - - </div>} - - </div> - </div> - - <div class="datePicker--background" onClick={this.closeDatePicker} style={{ - display: this.props.opened ? 'block' : 'none' - }} - /> - - </div> - ) - } -} - - -const monthArrShortFull = [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December' -] - -const monthArrShort = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' -] - -const dayArr = [ - 'Sun', - 'Mon', - 'Tue', - 'Wed', - 'Thu', - 'Fri', - 'Sat' -] - -const now = new Date() - -const yearArr: number[] = [] - -for (let i = 2010; i <= now.getFullYear() + 10; i++) { - yearArr.push(i); -} diff --git a/packages/merchant-backoffice/src/components/picker/DurationPicker.stories.tsx b/packages/merchant-backoffice/src/components/picker/DurationPicker.stories.tsx deleted file mode 100644 index 275c80f..0000000 --- a/packages/merchant-backoffice/src/components/picker/DurationPicker.stories.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, FunctionalComponent } from 'preact'; -import { useState } from 'preact/hooks'; -import { DurationPicker as TestedComponent } from './DurationPicker'; - - -export default { - title: 'Components/Picker/Duration', - component: TestedComponent, - argTypes: { - onCreate: { action: 'onCreate' }, - goBack: { action: 'goBack' }, - } -}; - -function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { - const r = (args: any) => <Component {...args} /> - r.args = props - return r -} - -export const Example = createExample(TestedComponent, { - days: true, minutes: true, hours: true, seconds: true, - value: 10000000 -}); - -export const WithState = () => { - const [v,s] = useState<number>(1000000) - return <TestedComponent value={v} onChange={s} days minutes hours seconds /> -} diff --git a/packages/merchant-backoffice/src/components/picker/DurationPicker.tsx b/packages/merchant-backoffice/src/components/picker/DurationPicker.tsx deleted file mode 100644 index f32a48f..0000000 --- a/packages/merchant-backoffice/src/components/picker/DurationPicker.tsx +++ /dev/null @@ -1,211 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { useTranslator } from "../../i18n"; -import "../../scss/DurationPicker.scss"; - -export interface Props { - hours?: boolean; - minutes?: boolean; - seconds?: boolean; - days?: boolean; - onChange: (value: number) => void; - value: number; -} - -// inspiration taken from https://github.com/flurmbo/react-duration-picker -export function DurationPicker({ - days, - hours, - minutes, - seconds, - onChange, - value, -}: Props): VNode { - const ss = 1000 * 1000; - const ms = ss * 60; - const hs = ms * 60; - const ds = hs * 24; - const i18n = useTranslator(); - - return ( - <div class="rdp-picker"> - {days && ( - <DurationColumn - unit={i18n`days`} - max={99} - value={Math.floor(value / ds)} - onDecrease={value >= ds ? () => onChange(value - ds) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + ds) : undefined} - onChange={(diff) => onChange(value + diff * ds)} - /> - )} - {hours && ( - <DurationColumn - unit={i18n`hours`} - max={23} - min={1} - value={Math.floor(value / hs) % 24} - onDecrease={value >= hs ? () => onChange(value - hs) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + hs) : undefined} - onChange={(diff) => onChange(value + diff * hs)} - /> - )} - {minutes && ( - <DurationColumn - unit={i18n`minutes`} - max={59} - min={1} - value={Math.floor(value / ms) % 60} - onDecrease={value >= ms ? () => onChange(value - ms) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + ms) : undefined} - onChange={(diff) => onChange(value + diff * ms)} - /> - )} - {seconds && ( - <DurationColumn - unit={i18n`seconds`} - max={59} - value={Math.floor(value / ss) % 60} - onDecrease={value >= ss ? () => onChange(value - ss) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + ss) : undefined} - onChange={(diff) => onChange(value + diff * ss)} - /> - )} - </div> - ); -} - -interface ColProps { - unit: string; - min?: number; - max: number; - value: number; - onIncrease?: () => void; - onDecrease?: () => void; - onChange?: (diff: number) => void; -} - -function InputNumber({ - initial, - onChange, -}: { - initial: number; - onChange: (n: number) => void; -}) { - const [value, handler] = useState<{ v: string }>({ - v: toTwoDigitString(initial), - }); - - return ( - <input - value={value.v} - onBlur={(e) => onChange(parseInt(value.v, 10))} - onInput={(e) => { - e.preventDefault(); - const n = Number.parseInt(e.currentTarget.value, 10); - if (isNaN(n)) return handler({ v: toTwoDigitString(initial) }); - return handler({ v: toTwoDigitString(n) }); - }} - style={{ - width: 50, - border: "none", - fontSize: "inherit", - background: "inherit", - }} - /> - ); -} - -function DurationColumn({ - unit, - min = 0, - max, - value, - onIncrease, - onDecrease, - onChange, -}: ColProps): VNode { - const cellHeight = 35; - return ( - <div class="rdp-column-container"> - <div class="rdp-masked-div"> - <hr class="rdp-reticule" style={{ top: cellHeight * 2 - 1 }} /> - <hr class="rdp-reticule" style={{ top: cellHeight * 3 - 1 }} /> - - <div class="rdp-column" style={{ top: 0 }}> - <div class="rdp-cell" key={value - 2}> - {onDecrease && ( - <button - style={{ width: "100%", textAlign: "center", margin: 5 }} - onClick={onDecrease} - > - <span class="icon"> - <i class="mdi mdi-chevron-up" /> - </span> - </button> - )} - </div> - <div class="rdp-cell" key={value - 1}> - {value > min ? toTwoDigitString(value - 1) : ""} - </div> - <div class="rdp-cell rdp-center" key={value}> - {onChange ? ( - <InputNumber - initial={value} - onChange={(n) => onChange(n - value)} - /> - ) : ( - toTwoDigitString(value) - )} - <div>{unit}</div> - </div> - - <div class="rdp-cell" key={value + 1}> - {value < max ? toTwoDigitString(value + 1) : ""} - </div> - - <div class="rdp-cell" key={value + 2}> - {onIncrease && ( - <button - style={{ width: "100%", textAlign: "center", margin: 5 }} - onClick={onIncrease} - > - <span class="icon"> - <i class="mdi mdi-chevron-down" /> - </span> - </button> - )} - </div> - </div> - </div> - </div> - ); -} - -function toTwoDigitString(n: number) { - if (n < 10) { - return `0${n}`; - } - return `${n}`; -} diff --git a/packages/merchant-backoffice/src/components/product/InventoryProductForm.stories.tsx b/packages/merchant-backoffice/src/components/product/InventoryProductForm.stories.tsx deleted file mode 100644 index 6504d85..0000000 --- a/packages/merchant-backoffice/src/components/product/InventoryProductForm.stories.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, VNode, FunctionalComponent } from 'preact'; -import { InventoryProductForm as TestedComponent } from './InventoryProductForm'; - - -export default { - title: 'Components/Product/Add', - component: TestedComponent, - argTypes: { - onAddProduct: { action: 'onAddProduct' }, - }, -}; - -function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { - const r = (args: any) => <Component {...args} /> - r.args = props - return r -} - -export const WithASimpleList = createExample(TestedComponent, { - inventory:[{ - id: 'this id', - description: 'this is the description', - } as any] -}); - -export const WithAProductSelected = createExample(TestedComponent, { - inventory:[], - currentProducts: { - thisid: { - quantity: 1, - product: { - id: 'asd', - description: 'asdsadsad', - } as any - } - } -}); diff --git a/packages/merchant-backoffice/src/components/product/InventoryProductForm.tsx b/packages/merchant-backoffice/src/components/product/InventoryProductForm.tsx deleted file mode 100644 index 8f05c97..0000000 --- a/packages/merchant-backoffice/src/components/product/InventoryProductForm.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - 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/> - */ -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { FormProvider, FormErrors } from "../form/FormProvider"; -import { InputNumber } from "../form/InputNumber"; -import { InputSearchProduct } from "../form/InputSearchProduct"; -import { MerchantBackend, WithId } from "../../declaration"; -import { Translate, useTranslator } from "../../i18n"; -import { ProductMap } from "../../paths/instance/orders/create/CreatePage"; - -type Form = { - product: MerchantBackend.Products.ProductDetail & WithId, - quantity: number; -} - -interface Props { - currentProducts: ProductMap, - onAddProduct: (product: MerchantBackend.Products.ProductDetail & WithId, quantity: number) => void, - inventory: (MerchantBackend.Products.ProductDetail & WithId)[], -} - -export function InventoryProductForm({ currentProducts, onAddProduct, inventory }: Props): VNode { - const initialState = { quantity: 1 } - const [state, setState] = useState<Partial<Form>>(initialState) - const [errors, setErrors] = useState<FormErrors<Form>>({}) - - const i18n = useTranslator() - - const productWithInfiniteStock = state.product && state.product.total_stock === -1 - - const submit = (): void => { - if (!state.product) { - setErrors({ product: i18n`You must enter a valid product identifier.` }); - return; - } - if (productWithInfiniteStock) { - onAddProduct(state.product, 1) - } else { - if (!state.quantity || state.quantity <= 0) { - setErrors({ quantity: i18n`Quantity must 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) { - const left = currentStock - p.quantity; - setErrors({ quantity: i18n`This quantity exceeds remaining stock. Currently, only ${left} units remain unreserved in stock.` }); - return; - } - onAddProduct(state.product, state.quantity + p.quantity) - } else { - if (state.quantity > currentStock) { - const left = currentStock; - setErrors({ quantity: i18n`This quantity exceeds remaining stock. Currently, only ${left} units remain unreserved in stock.` }); - return; - } - onAddProduct(state.product, state.quantity) - } - } - - setState(initialState) - } - - return <FormProvider<Form> errors={errors} object={state} valueHandler={setState}> - <InputSearchProduct selected={state.product} onChange={(p) => setState(v => ({ ...v, product: p }))} products={inventory} /> - { state.product && <div class="columns mt-5"> - <div class="column is-two-thirds"> - {!productWithInfiniteStock && - <InputNumber<Form> name="quantity" label={i18n`Quantity`} tooltip={i18n`how many products will be added`} /> - } - </div> - <div class="column"> - <div class="buttons is-right"> - <button class="button is-success" onClick={submit}><Translate>Add from inventory</Translate></button> - </div> - </div> - </div> } - - </FormProvider> -} diff --git a/packages/merchant-backoffice/src/components/product/NonInventoryProductForm.tsx b/packages/merchant-backoffice/src/components/product/NonInventoryProductForm.tsx deleted file mode 100644 index 397efe6..0000000 --- a/packages/merchant-backoffice/src/components/product/NonInventoryProductForm.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/* - 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/> - */ -import { Fragment, h, VNode } from "preact"; -import { useCallback, useEffect, useState } from "preact/hooks"; -import * as yup from 'yup'; -import { FormErrors, FormProvider } from "../form/FormProvider"; -import { Input } from "../form/Input"; -import { InputCurrency } from "../form/InputCurrency"; -import { InputImage } from "../form/InputImage"; -import { InputNumber } from "../form/InputNumber"; -import { InputTaxes } from "../form/InputTaxes"; -import { MerchantBackend } from "../../declaration"; -import { useListener } from "../../hooks/listener"; -import { Translate, useTranslator } from "../../i18n"; -import { - NonInventoryProductSchema as schema -} from '../../schemas'; - - -type Entity = MerchantBackend.Product - -interface Props { - onAddProduct: (p: Entity) => Promise<void>; - productToEdit?: Entity; -} -export function NonInventoryProductFrom({ productToEdit, onAddProduct }: Props): VNode { - const [showCreateProduct, setShowCreateProduct] = useState(false) - - const isEditing = !!productToEdit - - useEffect(() => { - setShowCreateProduct(isEditing) - }, [isEditing]) - - const [submitForm, addFormSubmitter] = useListener<Partial<MerchantBackend.Product> | undefined>((result) => { - if (result) { - setShowCreateProduct(false) - return onAddProduct({ - quantity: result.quantity || 0, - taxes: result.taxes || [], - description: result.description || '', - image: result.image || '', - price: result.price || '', - unit: result.unit || '' - }) - } - return Promise.resolve() - }) - - const i18n = useTranslator() - - return <Fragment> - <div class="buttons"> - <button class="button is-success" data-tooltip={i18n`describe and add a product that is not in the inventory list`} onClick={() => setShowCreateProduct(true)} ><Translate>Add custom product</Translate></button> - </div> - {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; - initial?: Partial<Entity>; -} - -interface NonInventoryProduct { - quantity: number; - description: string; - unit: string; - price: string; - image: string; - taxes: MerchantBackend.Tax[]; -} - -export function ProductForm({ onSubscribe, initial }: ProductProps): VNode { - const [value, valueHandler] = useState<Partial<NonInventoryProduct>>({ - taxes: [], - ...initial, - }) - let errors: FormErrors<Entity> = {} - try { - schema.validateSync(value, { abortEarly: false }) - } catch (err) { - if (err instanceof yup.ValidationError) { - 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 => { - return value as MerchantBackend.Product - }, [value]) - - const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined) - - useEffect(() => { - onSubscribe(hasErrors ? undefined : submit) - }, [submit, hasErrors]) - - const i18n = useTranslator() - - return <div> - <FormProvider<NonInventoryProduct> name="product" errors={errors} object={value} valueHandler={valueHandler} > - - <InputImage<NonInventoryProduct> name="image" label={i18n`Image`} tooltip={i18n`photo of the product`} /> - <Input<NonInventoryProduct> name="description" inputType="multiline" label={i18n`Description`} tooltip={i18n`full product description`} /> - <Input<NonInventoryProduct> name="unit" label={i18n`Unit`} tooltip={i18n`name of the product unit`} /> - <InputCurrency<NonInventoryProduct> name="price" label={i18n`Price`} tooltip={i18n`amount in the current currency`} /> - - <InputNumber<NonInventoryProduct> name="quantity" label={i18n`Quantity`} tooltip={i18n`how many products will be added`} /> - - <InputTaxes<NonInventoryProduct> name="taxes" label={i18n`Taxes`} /> - - </FormProvider> - </div> -} diff --git a/packages/merchant-backoffice/src/components/product/ProductForm.tsx b/packages/merchant-backoffice/src/components/product/ProductForm.tsx deleted file mode 100644 index 9434d3d..0000000 --- a/packages/merchant-backoffice/src/components/product/ProductForm.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { h } from "preact"; -import { useCallback, useEffect, useState } from "preact/hooks"; -import * as yup from "yup"; -import { useBackendContext } from "../../context/backend"; -import { MerchantBackend } from "../../declaration"; -import { useTranslator } from "../../i18n"; -import { - ProductCreateSchema as createSchema, - ProductUpdateSchema as updateSchema, -} from "../../schemas"; -import { FormProvider, FormErrors } from "../form/FormProvider"; -import { Input } from "../form/Input"; -import { InputCurrency } from "../form/InputCurrency"; -import { InputImage } from "../form/InputImage"; -import { InputNumber } from "../form/InputNumber"; -import { InputStock, Stock } from "../form/InputStock"; -import { InputTaxes } from "../form/InputTaxes"; -import { InputWithAddon } from "../form/InputWithAddon"; - -type Entity = MerchantBackend.Products.ProductDetail & { product_id: string }; - -interface Props { - onSubscribe: (c?: () => Entity | undefined) => void; - initial?: Partial<Entity>; - alreadyExist?: boolean; -} - -export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { - const [value, valueHandler] = useState<Partial<Entity & { stock: Stock }>>({ - address: {}, - description_i18n: {}, - taxes: [], - next_restock: { t_s: "never" }, - price: ":0", - ...initial, - stock: - !initial || initial.total_stock === -1 - ? undefined - : { - current: initial.total_stock || 0, - lost: initial.total_lost || 0, - sold: initial.total_sold || 0, - address: initial.address, - nextRestock: initial.next_restock, - }, - }); - let errors: FormErrors<Entity> = {}; - - try { - (alreadyExist ? updateSchema : createSchema).validateSync(value, { - abortEarly: false, - }); - } catch (err) { - if (err instanceof yup.ValidationError) { - const yupErrors = err.inner as yup.ValidationError[]; - errors = yupErrors.reduce( - (prev, cur) => - !cur.path ? prev : { ...prev, [cur.path]: cur.message }, - {} - ); - } - } - const hasErrors = Object.keys(errors).some( - (k) => (errors as any)[k] !== undefined - ); - - const submit = useCallback((): Entity | undefined => { - const stock: Stock = (value as any).stock; - - if (!stock) { - value.total_stock = -1; - } else { - value.total_stock = stock.current; - value.total_lost = stock.lost; - value.next_restock = - stock.nextRestock instanceof Date - ? { t_s: stock.nextRestock.getTime() / 1000 } - : stock.nextRestock; - value.address = stock.address; - } - delete (value as any).stock; - - if (typeof value.minimum_age !== "undefined" && value.minimum_age < 1) { - delete value.minimum_age; - } - - return value as MerchantBackend.Products.ProductDetail & { - product_id: string; - }; - }, [value]); - - useEffect(() => { - onSubscribe(hasErrors ? undefined : submit); - }, [submit, hasErrors]); - - const backend = useBackendContext(); - const i18n = useTranslator(); - - return ( - <div> - <FormProvider<Entity> - name="product" - errors={errors} - object={value} - valueHandler={valueHandler} - > - {alreadyExist ? undefined : ( - <InputWithAddon<Entity> - name="product_id" - addonBefore={`${backend.url}/product/`} - label={i18n`ID`} - tooltip={i18n`product identification to use in URLs (for internal use only)`} - /> - )} - <InputImage<Entity> - name="image" - label={i18n`Image`} - tooltip={i18n`illustration of the product for customers`} - /> - <Input<Entity> - name="description" - inputType="multiline" - label={i18n`Description`} - tooltip={i18n`product description for customers`} - /> - <InputNumber<Entity> - name="minimum_age" - label={i18n`Age restricted`} - tooltip={i18n`is this product restricted for customer below certain age?`} - /> - <Input<Entity> - name="unit" - label={i18n`Unit`} - tooltip={i18n`unit describing quantity of product sold (e.g. 2 kilograms, 5 liters, 3 items, 5 meters) for customers`} - /> - <InputCurrency<Entity> - name="price" - label={i18n`Price`} - tooltip={i18n`sale price for customers, including taxes, for above units of the product`} - /> - <InputStock - name="stock" - label={i18n`Stock`} - alreadyExist={alreadyExist} - tooltip={i18n`product inventory for products with finite supply (for internal use only)`} - /> - <InputTaxes<Entity> - name="taxes" - label={i18n`Taxes`} - tooltip={i18n`taxes included in the product price, exposed to customers`} - /> - </FormProvider> - </div> - ); -} diff --git a/packages/merchant-backoffice/src/components/product/ProductList.tsx b/packages/merchant-backoffice/src/components/product/ProductList.tsx deleted file mode 100644 index ff141bb..0000000 --- a/packages/merchant-backoffice/src/components/product/ProductList.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - 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/> - */ -import { Amounts } from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import emptyImage from "../../assets/empty.png"; -import { MerchantBackend } from "../../declaration"; -import { Translate } from "../../i18n"; - -interface Props { - list: MerchantBackend.Product[]; - actions?: { - name: string; - tooltip: string; - handler: (d: MerchantBackend.Product, index: number) => void; - }[]; -} -export function ProductList({ list, actions = [] }: Props): VNode { - return ( - <div class="table-container"> - <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> - <thead> - <tr> - <th> - <Translate>image</Translate> - </th> - <th> - <Translate>description</Translate> - </th> - <th> - <Translate>quantity</Translate> - </th> - <th> - <Translate>unit price</Translate> - </th> - <th> - <Translate>total price</Translate> - </th> - <th /> - </tr> - </thead> - <tbody> - {list.map((entry, index) => { - const unitPrice = !entry.price ? "0" : entry.price; - const totalPrice = !entry.price - ? "0" - : Amounts.stringify( - Amounts.mult( - Amounts.parseOrThrow(entry.price), - entry.quantity - ).amount - ); - - return ( - <tr key={index}> - <td> - <img - style={{ height: 32, width: 32 }} - src={entry.image ? entry.image : emptyImage} - /> - </td> - <td>{entry.description}</td> - <td> - {entry.quantity === 0 - ? "--" - : `${entry.quantity} ${entry.unit}`} - </td> - <td>{unitPrice}</td> - <td>{totalPrice}</td> - <td class="is-actions-cell right-sticky"> - {actions.map((a, i) => { - return ( - <div key={i} class="buttons is-right"> - <button - class="button is-small is-danger has-tooltip-left" - data-tooltip={a.tooltip} - type="button" - onClick={() => a.handler(entry, index)} - > - {a.name} - </button> - </div> - ); - })} - </td> - </tr> - ); - })} - </tbody> - </table> - </div> - ); -} |