diff options
Diffstat (limited to 'packages/auditor-backoffice-ui/src/components/form')
18 files changed, 0 insertions, 2482 deletions
diff --git a/packages/auditor-backoffice-ui/src/components/form/InputArray.tsx b/packages/auditor-backoffice-ui/src/components/form/InputArray.tsx deleted file mode 100644 index b0b9eaefc..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputArray.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { InputProps, useField } from "./useField.js"; - -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 } = useTranslationContext(); - - 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.str`The value ${v} is invalid for a payment url`, - ); - return; - } - setLocalError(null); - onChange([v, ...array] as any); - setCurrentValue(""); - }} - data-tooltip={i18n.str`add element to the list`} - > - <i18n.Translate>add</i18n.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/auditor-backoffice-ui/src/components/form/InputBoolean.tsx b/packages/auditor-backoffice-ui/src/components/form/InputBoolean.tsx deleted file mode 100644 index bdb2feb6b..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputBoolean.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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.js"; - -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/auditor-backoffice-ui/src/components/form/InputDate.tsx b/packages/auditor-backoffice-ui/src/components/form/InputDate.tsx deleted file mode 100644 index cbcc6af2d..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputDate.tsx +++ /dev/null @@ -1,164 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { format } from "date-fns"; -import { ComponentChildren, h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { DatePicker } from "../picker/DatePicker.js"; -import { InputProps, useField } from "./useField.js"; -import { dateFormatForSettings, useSettings } from "../../hooks/useSettings.js"; - -export interface Props<T> extends InputProps<T> { - readonly?: boolean; - expand?: boolean; - //FIXME: create separated components InputDate and InputTimestamp - withTimestampSupport?: boolean; - side?: ComponentChildren; -} - -export function InputDate<T>({ - name, - readonly, - label, - placeholder, - help, - tooltip, - expand, - withTimestampSupport, - side, -}: Props<keyof T>): VNode { - const [opened, setOpened] = useState(false); - const { i18n } = useTranslationContext(); - const [settings] = useSettings() - - const { error, required, value, onChange } = useField<T>(name); - - let strValue = ""; - if (!value) { - strValue = withTimestampSupport ? "unknown" : ""; - } else if (value instanceof Date) { - strValue = format(value, dateFormatForSettings(settings)); - } else if (value.t_s) { - strValue = - value.t_s === "never" - ? withTimestampSupport - ? "never" - : "" - : format(new Date(value.t_s * 1000), dateFormatForSettings(settings)); - } - - 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.str`change value to unknown date` - : i18n.str`change value to empty` - } - > - <button - class="button is-info mr-3" - onClick={() => onChange(undefined as any)} - > - <i18n.Translate>clear</i18n.Translate> - </button> - </span> - )} - {withTimestampSupport && ( - <span data-tooltip={i18n.str`change value to never`}> - <button - class="button is-info" - onClick={() => onChange({ t_s: "never" } as any)} - > - <i18n.Translate>never</i18n.Translate> - </button> - </span> - )} - {side} - </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/auditor-backoffice-ui/src/components/form/InputDuration.tsx b/packages/auditor-backoffice-ui/src/components/form/InputDuration.tsx deleted file mode 100644 index 0103a97c1..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputDuration.tsx +++ /dev/null @@ -1,186 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { formatDuration, intervalToDuration } from "date-fns"; -import { ComponentChildren, h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { SimpleModal } from "../modal/index.js"; -import { DurationPicker } from "../picker/DurationPicker.js"; -import { InputProps, useField } from "./useField.js"; -import { Duration } from "@gnu-taler/taler-util"; - -export interface Props<T> extends InputProps<T> { - expand?: boolean; - readonly?: boolean; - withForever?: boolean; - side?: ComponentChildren; - withoutClear?: boolean; -} - -export function InputDuration<T>({ - name, - expand, - placeholder, - tooltip, - label, - help, - readonly, - withForever, - withoutClear, - side, -}: Props<keyof T>): VNode { - const [opened, setOpened] = useState(false); - const { i18n } = useTranslationContext(); - - const { error, required, value: anyValue, onChange } = useField<T>(name); - let strValue = ""; - const value: Duration = anyValue - if (!value) { - strValue = ""; - } else if (value.d_ms === "forever") { - strValue = i18n.str`forever`; - } else { - strValue = formatDuration( - intervalToDuration({ start: 0, end: value.d_ms }), - { - locale: { - formatDistance: (name, value) => { - switch (name) { - case "xMonths": - return i18n.str`${value}M`; - case "xYears": - return i18n.str`${value}Y`; - case "xDays": - return i18n.str`${value}d`; - case "xHours": - return i18n.str`${value}h`; - case "xMinutes": - return i18n.str`${value}min`; - case "xSeconds": - return i18n.str`${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 is-flex-grow-3"> - <label class="label"> - {label} - {tooltip && ( - <span class="icon" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span> - )} - </label> - </div> - - <div class="is-flex-grow-3"> - <div class="field-body "> - <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> - )} - </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.str`change value to never`}> - <button - class="button is-info mr-3" - onClick={() => onChange({ d_ms: "forever" } as any)} - > - <i18n.Translate>forever</i18n.Translate> - </button> - </span> - )} - {!readonly && !withoutClear && ( - <span data-tooltip={i18n.str`change value to empty`}> - <button - class="button is-info " - onClick={() => onChange(undefined as any)} - > - <i18n.Translate>clear</i18n.Translate> - </button> - </span> - )} - {side} - </div> - <span> - {help} - </span> - </div> - - - {opened && ( - <SimpleModal onCancel={() => setOpened(false)}> - <DurationPicker - days - hours - minutes - value={!value || value.d_ms === "forever" ? 0 : value.d_ms} - onChange={(v) => { - onChange({ d_ms: v } as any); - }} - /> - </SimpleModal> - )} - </div> - ); -} diff --git a/packages/auditor-backoffice-ui/src/components/form/InputGroup.tsx b/packages/auditor-backoffice-ui/src/components/form/InputGroup.tsx deleted file mode 100644 index 92b9e8b16..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputGroup.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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.js"; - -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/auditor-backoffice-ui/src/components/form/InputImage.tsx b/packages/auditor-backoffice-ui/src/components/form/InputImage.tsx deleted file mode 100644 index d284b476f..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputImage.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { ComponentChildren, h, VNode } from "preact"; -import { useRef, useState } from "preact/hooks"; -import { MAX_IMAGE_SIZE as MAX_IMAGE_UPLOAD_SIZE } from "../../utils/constants.js"; -import { InputProps, useField } from "./useField.js"; - -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 { i18n } = useTranslationContext(); - 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 = window.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"> - <i18n.Translate>Image should be smaller than 1 MB</i18n.Translate> - </p> - )} - {!value && ( - <button class="button" onClick={() => image.current?.click()}> - <i18n.Translate>Add</i18n.Translate> - </button> - )} - {value && ( - <button class="button" onClick={() => onChange(undefined!)}> - <i18n.Translate>Remove</i18n.Translate> - </button> - )} - </div> - </div> - </div> - ); -} diff --git a/packages/auditor-backoffice-ui/src/components/form/InputLocation.tsx b/packages/auditor-backoffice-ui/src/components/form/InputLocation.tsx deleted file mode 100644 index d4b13d555..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputLocation.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { Input } from "./Input.js"; - -export function InputLocation({ name }: { name: string }) { - const { i18n } = useTranslationContext(); - return ( - <> - <Input name={`${name}.country`} label={i18n.str`Country`} /> - <Input - name={`${name}.address_lines`} - inputType="multiline" - label={i18n.str`Address`} - toStr={(v: string[] | undefined) => (!v ? "" : v.join("\n"))} - fromStr={(v: string) => v.split("\n")} - /> - <Input - name={`${name}.building_number`} - label={i18n.str`Building number`} - /> - <Input name={`${name}.building_name`} label={i18n.str`Building name`} /> - <Input name={`${name}.street`} label={i18n.str`Street`} /> - <Input name={`${name}.post_code`} label={i18n.str`Post code`} /> - <Input name={`${name}.town_location`} label={i18n.str`Town location`} /> - <Input name={`${name}.town`} label={i18n.str`Town`} /> - <Input name={`${name}.district`} label={i18n.str`District`} /> - <Input - name={`${name}.country_subdivision`} - label={i18n.str`Country subdivision`} - /> - </> - ); -} diff --git a/packages/auditor-backoffice-ui/src/components/form/InputPayto.tsx b/packages/auditor-backoffice-ui/src/components/form/InputPayto.tsx deleted file mode 100644 index fcecd8932..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputPayto.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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.js"; -import { PAYTO_REGEX } from "../../utils/constants.js"; -import { InputProps } from "./useField.js"; - -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/auditor-backoffice-ui/src/components/form/InputPaytoForm.stories.tsx b/packages/auditor-backoffice-ui/src/components/form/InputPaytoForm.stories.tsx deleted file mode 100644 index cc5326bbe..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputPaytoForm.stories.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 * as tests from "@gnu-taler/web-util/testing"; -import { InputPaytoForm } from "./InputPaytoForm.js"; -import { FormProvider } from "./FormProvider.js"; -import { useState } from "preact/hooks"; - -export default { - title: "Components/Form/PayTo", - component: InputPaytoForm, - argTypes: { - onUpdate: { action: "onUpdate" }, - onBack: { action: "onBack" }, - }, -}; - -export const Example = tests.createExample(() => { - const initial = { - accounts: [], - }; - const [form, updateForm] = useState<Partial<typeof initial>>(initial); - return ( - <FormProvider valueHandler={updateForm} object={form}> - <InputPaytoForm name="accounts" label="Accounts:" /> - </FormProvider> - ); -}, {}); diff --git a/packages/auditor-backoffice-ui/src/components/form/InputPaytoForm.tsx b/packages/auditor-backoffice-ui/src/components/form/InputPaytoForm.tsx deleted file mode 100644 index 7eba8b0b5..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputPaytoForm.tsx +++ /dev/null @@ -1,397 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 { parsePaytoUri, PaytoUriGeneric, stringifyPaytoUri } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { Fragment, h, VNode } from "preact"; -import { COUNTRY_TABLE } from "../../utils/constants.js"; -import { undefinedIfEmpty } from "../../utils/table.js"; -import { FormErrors, FormProvider } from "./FormProvider.js"; -import { Input } from "./Input.js"; -import { InputGroup } from "./InputGroup.js"; -import { InputSelector } from "./InputSelector.js"; -import { InputProps, useField } from "./useField.js"; -import { useEffect, useState } from "preact/hooks"; - -export interface Props<T> extends InputProps<T> { - isValid?: (e: any) => boolean; -} - -// type Entity = PaytoUriGeneric -// 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; - // params of the payto uri - params: { - "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: ReturnType<typeof useTranslationContext>["i18n"], -): 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.str`This is not a valid bitcoin address.`; -} - -function validateEthereum( - addr: string, - i18n: ReturnType<typeof useTranslationContext>["i18n"], -): string | undefined { - try { - const valid = isEthereumAddress(addr); - if (valid) return undefined; - } catch (e) { - console.log(e); - } - return i18n.str`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: ReturnType<typeof useTranslationContext>["i18n"], -): string | undefined { - // Check total length - if (iban.length < 4) - return i18n.str`IBAN numbers usually have more that 4 digits`; - if (iban.length > 34) - return i18n.str`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.str`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.str`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: Entity = { - target: noTargetValue, - params: {}, -}; - -export function InputPaytoForm<T>({ - name, - readonly, - label, - tooltip, -}: Props<keyof T>): VNode { - const { value: initialValueStr, onChange } = useField<T>(name); - - const initialPayto = parsePaytoUri(initialValueStr ?? "") - const paths = !initialPayto ? [] : initialPayto.targetPath.split("/") - const initialPath1 = paths.length >= 1 ? paths[0] : undefined; - const initialPath2 = paths.length >= 2 ? paths[1] : undefined; - const initial: Entity = initialPayto === undefined ? defaultTarget : { - target: initialPayto.targetType, - params: initialPayto.params, - path1: initialPath1, - path2: initialPath2, - } - const [value, setValue] = useState<Partial<Entity>>(initial) - - const { i18n } = useTranslationContext(); - - const errors: FormErrors<Entity> = { - target: - value.target === noTargetValue - ? i18n.str`required` - : undefined, - path1: !value.path1 - ? i18n.str`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.str`required` - : undefined - : undefined, - params: undefinedIfEmpty({ - "receiver-name": !value.params?.["receiver-name"] - ? i18n.str`required` - : undefined, - }), - }; - - const hasErrors = Object.keys(errors).some( - (k) => (errors as any)[k] !== undefined, - ); - const str = hasErrors || !value.target ? undefined : stringifyPaytoUri({ - targetType: value.target, - targetPath: value.path2 ? `${value.path1}/${value.path2}` : (value.path1 ?? ""), - params: value.params ?? {} as any, - isKnown: false, - }) - useEffect(() => { - onChange(str as any) - }, [str]) - - // const submit = useCallback((): void => { - // // const accounts: MerchantBackend.BankAccounts.AccountAddDetails[] = paytos; - // // const alreadyExists = - // // accounts.findIndex((x) => x.payto_uri === paytoURL) !== -1; - // // if (!alreadyExists) { - // const newValue: MerchantBackend.BankAccounts.AccountAddDetails = { - // payto_uri: paytoURL, - // }; - // if (value.auth) { - // if (value.auth.url) { - // newValue.credit_facade_url = value.auth.url; - // } - // if (value.auth.type === "none") { - // newValue.credit_facade_credentials = { - // type: "none", - // }; - // } - // if (value.auth.type === "basic") { - // newValue.credit_facade_credentials = { - // type: "basic", - // username: value.auth.username ?? "", - // password: value.auth.password ?? "", - // }; - // } - // } - // onChange(newValue 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={setValue} - > - <InputSelector<Entity> - name="target" - label={i18n.str`Account type`} - tooltip={i18n.str`Method to use for wire transfer`} - values={targets} - readonly={readonly} - toStr={(v) => (v === noTargetValue ? i18n.str`Choose one...` : v)} - /> - - {value.target === "ach" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n.str`Routing`} - readonly={readonly} - tooltip={i18n.str`Routing number.`} - /> - <Input<Entity> - name="path2" - label={i18n.str`Account`} - readonly={readonly} - tooltip={i18n.str`Account number.`} - /> - </Fragment> - )} - {value.target === "bic" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n.str`Code`} - readonly={readonly} - tooltip={i18n.str`Business Identifier Code.`} - /> - </Fragment> - )} - {value.target === "iban" && ( - <Fragment> - <Input<Entity> - name="path1" - label={i18n.str`IBAN`} - tooltip={i18n.str`International Bank Account Number.`} - readonly={readonly} - placeholder="DE1231231231" - inputExtra={{ style: { textTransform: "uppercase" } }} - /> - </Fragment> - )} - {value.target === "upi" && ( - <Fragment> - <Input<Entity> - name="path1" - readonly={readonly} - label={i18n.str`Account`} - tooltip={i18n.str`Unified Payment Interface.`} - /> - </Fragment> - )} - {value.target === "bitcoin" && ( - <Fragment> - <Input<Entity> - name="path1" - readonly={readonly} - label={i18n.str`Address`} - tooltip={i18n.str`Bitcoin protocol.`} - /> - </Fragment> - )} - {value.target === "ethereum" && ( - <Fragment> - <Input<Entity> - name="path1" - readonly={readonly} - label={i18n.str`Address`} - tooltip={i18n.str`Ethereum protocol.`} - /> - </Fragment> - )} - {value.target === "ilp" && ( - <Fragment> - <Input<Entity> - name="path1" - readonly={readonly} - label={i18n.str`Address`} - tooltip={i18n.str`Interledger protocol.`} - /> - </Fragment> - )} - {value.target === "void" && <Fragment />} - {value.target === "x-taler-bank" && ( - <Fragment> - <Input<Entity> - name="path1" - readonly={readonly} - label={i18n.str`Host`} - tooltip={i18n.str`Bank host.`} - /> - <Input<Entity> - name="path2" - readonly={readonly} - label={i18n.str`Account`} - tooltip={i18n.str`Bank account.`} - /> - </Fragment> - )} - - {/** - * Show additional fields apart from the payto - */} - {value.target !== noTargetValue && ( - <Fragment> - <Input - name="params.receiver-name" - readonly={readonly} - label={i18n.str`Owner's name`} - tooltip={i18n.str`Legal name of the person holding the account.`} - /> - </Fragment> - )} - - </FormProvider> - </InputGroup> - ); -} - diff --git a/packages/auditor-backoffice-ui/src/components/form/InputSearchOnList.tsx b/packages/auditor-backoffice-ui/src/components/form/InputSearchOnList.tsx deleted file mode 100644 index 9956a6427..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputSearchOnList.tsx +++ /dev/null @@ -1,204 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import emptyImage from "../../assets/empty.png"; -import { FormErrors, FormProvider } from "./FormProvider.js"; -import { InputWithAddon } from "./InputWithAddon.js"; -import { TranslatedString } from "@gnu-taler/taler-util"; - -type Entity = { - id: string, - description: string; - image?: string; - extra?: string; -}; - -export interface Props<T extends Entity> { - selected?: T; - onChange: (p?: T) => void; - label: TranslatedString; - list: T[]; - withImage?: boolean; -} - -interface Search { - name: string; -} - -export function InputSearchOnList<T extends Entity>({ - selected, - onChange, - label, - list, - withImage, -}: Props<T>): VNode { - const [nameForm, setNameForm] = useState<Partial<Search>>({ - name: "", - }); - - const errors: FormErrors<Search> = { - name: undefined, - }; - const { i18n } = useTranslationContext(); - - if (selected) { - return ( - <article class="media"> - {withImage && - <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"> - <i18n.Translate>ID</i18n.Translate>: <b>{selected.id}</b> - </p> - <p> - <i18n.Translate>Description</i18n.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<Search> - errors={errors} - object={nameForm} - valueHandler={setNameForm} - > - <InputWithAddon<Search> - name="name" - label={label} - tooltip={i18n.str`enter description or id`} - addonAfter={ - <span class="icon"> - <i class="mdi mdi-magnify" /> - </span> - } - > - <div> - <DropdownList - name={nameForm.name} - list={list} - onSelect={(p) => { - setNameForm({ name: "" }); - onChange(p); - }} - withImage={!!withImage} - /> - </div> - </InputWithAddon> - </FormProvider> - ); -} - -interface DropdownListProps<T extends Entity> { - name?: string; - onSelect: (p: T) => void; - list: T[]; - withImage: boolean; -} - -function DropdownList<T extends Entity>({ name, onSelect, list, withImage }: DropdownListProps<T>) { - const { i18n } = useTranslationContext(); - 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"> - <i18n.Translate> - no match found with that description or id - </i18n.Translate> - </div> - ) : ( - filtered.map((p) => ( - <div - key={p.id} - class="dropdown-item" - onClick={() => onSelect(p)} - style={{ cursor: "pointer" }} - > - <article class="media"> - {withImage && - <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> {p.extra !== undefined ? <small>{p.extra}</small> : undefined} - <br /> - {p.description} - </p> - </div> - </div> - </article> - </div> - )) - )} - </div> - </div> - </div> - ); -} diff --git a/packages/auditor-backoffice-ui/src/components/form/InputSecured.stories.tsx b/packages/auditor-backoffice-ui/src/components/form/InputSecured.stories.tsx deleted file mode 100644 index 4de84d984..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputSecured.stories.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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.js"; -import { InputSecured } from "./InputSecured.js"; - -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/auditor-backoffice-ui/src/components/form/InputSecured.tsx b/packages/auditor-backoffice-ui/src/components/form/InputSecured.tsx deleted file mode 100644 index 4a35ad96c..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputSecured.tsx +++ /dev/null @@ -1,186 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { Fragment, h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { InputProps, useField } from "./useField.js"; - -export type Props<T> = InputProps<T>; - -const TokenStatus = ({ prev, post }: any) => { - const { i18n } = useTranslationContext(); - 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"> - <i18n.Translate>Deleting</i18n.Translate> - </span> - ) : ( - <span class="tag is-warning is-align-self-center ml-2"> - <i18n.Translate>Changing</i18n.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 } = useTranslationContext(); - - 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> - <i18n.Translate>Manage access token</i18n.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> - <i18n.Translate>Update</i18n.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> - <i18n.Translate>Remove</i18n.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> - <i18n.Translate>Cancel</i18n.Translate> - </span> - </button> - </div> - </div> - </div> - </div> - </div> - )} - </Fragment> - ); -} diff --git a/packages/auditor-backoffice-ui/src/components/form/InputStock.stories.tsx b/packages/auditor-backoffice-ui/src/components/form/InputStock.stories.tsx deleted file mode 100644 index d7cf04553..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputStock.stories.tsx +++ /dev/null @@ -1,162 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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.js"; -import { InputStock, Stock } from "./InputStock.js"; - -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/auditor-backoffice-ui/src/components/form/InputStock.tsx b/packages/auditor-backoffice-ui/src/components/form/InputStock.tsx deleted file mode 100644 index 5c98f7311..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputStock.tsx +++ /dev/null @@ -1,224 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { Fragment, h } from "preact"; -import { useLayoutEffect, useState } from "preact/hooks"; -import { MerchantBackend, Timestamp } from "../../declaration.js"; -import { FormErrors, FormProvider } from "./FormProvider.js"; -import { InputDate } from "./InputDate.js"; -import { InputGroup } from "./InputGroup.js"; -import { InputLocation } from "./InputLocation.js"; -import { InputNumber } from "./InputNumber.js"; -import { InputProps, useField } from "./useField.js"; - -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 } = useTranslationContext(); - - 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.str`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> - <i18n.Translate>Manage stock</i18n.Translate> - </span> - </button> - ) : ( - <button - class="button" - data-tooltip={i18n.str`this product has been configured without stock control`} - disabled - > - <span> - <i18n.Translate>Infinite</i18n.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.str`lost cannot be greater than current and incoming (max ${ - currentStock + addedStock.incoming - })` - : undefined, - }; - - // const stockUpdateDescription = stockAddedErrors.lost ? '' : ( - // !!addedStock.incoming || !!addedStock.lost ? - // i18n.str`current stock will change from ${currentStock} to ${currentStock + addedStock.incoming - addedStock.lost}` : - // i18n.str`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.str`Incoming`} /> - <InputNumber name="lost" label={i18n.str`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.str`Current`} - side={ - <button - class="button is-danger" - data-tooltip={i18n.str`remove stock control for this product`} - onClick={(): void => { - valueHandler(undefined as any); - }} - > - <span> - <i18n.Translate>without stock</i18n.Translate> - </span> - </button> - } - /> - )} - - <InputDate<Entity> - name="nextRestock" - label={i18n.str`Next restock`} - withTimestampSupport - /> - - <InputGroup<Entity> name="address" label={i18n.str`Warehouse address`}> - <InputLocation name="address" /> - </InputGroup> - </FormProvider> - </div> - </div> - </Fragment> - ); -} -// ( diff --git a/packages/auditor-backoffice-ui/src/components/form/InputTab.tsx b/packages/auditor-backoffice-ui/src/components/form/InputTab.tsx deleted file mode 100644 index 1cd88d31a..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputTab.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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.js"; - -interface Props<T> extends InputProps<T> { - readonly?: boolean; - expand?: boolean; - values: any[]; - toStr?: (v?: any) => string; - fromStr?: (s: string) => any; -} - -const defaultToString = (f?: any): string => f || ""; -const defaultFromString = (v: string): any => v as any; - -export function InputTab<T>({ - name, - readonly, - expand, - placeholder, - tooltip, - label, - help, - values, - 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 has-icons-right"> - <p class={expand ? "control is-expanded " : "control "}> - <div class="tabs is-toggle is-fullwidth is-small"> - <ul> - {values.map((v, i) => { - return ( - <li key={i} class={value === v ? "is-active" : ""} - onClick={(e) => { onChange(v) }} - > - <a style={{ cursor: "initial" }}> - <span>{toStr(v)}</span> - </a> - </li> - ); - })} - </ul> - </div> - {help} - </p> - {required && ( - <span class="icon has-text-danger is-right" style={{ height: "2.5em" }}> - <i class="mdi mdi-alert" /> - </span> - )} - {error && <p class="help is-danger">{error}</p>} - </div> - </div> - </div> - ); -} diff --git a/packages/auditor-backoffice-ui/src/components/form/InputTaxes.tsx b/packages/auditor-backoffice-ui/src/components/form/InputTaxes.tsx deleted file mode 100644 index 984b496e7..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/InputTaxes.tsx +++ /dev/null @@ -1,147 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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 { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { h, VNode } from "preact"; -import { useCallback, useState } from "preact/hooks"; -import * as yup from "yup"; -import { MerchantBackend } from "../../declaration.js"; -import { TaxSchema as schema } from "../../schemas/index.js"; -import { FormErrors, FormProvider } from "./FormProvider.js"; -import { Input } from "./Input.js"; -import { InputGroup } from "./InputGroup.js"; -import { InputProps, useField } from "./useField.js"; - -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 } = useTranslationContext(); - - //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.str`No taxes configured for this product.`} - </div> - </div> - - <Input<Entity> - name="tax" - label={i18n.str`Amount`} - tooltip={i18n.str`Taxes can be in currencies that differ from the main currency used by the merchant.`} - > - <i18n.Translate> - Enter currency and value separated with a colon, e.g. - "USD:2.3". - </i18n.Translate> - </Input> - - <Input<Entity> - name="name" - label={i18n.str`Description`} - tooltip={i18n.str`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.str`add tax to the tax list`} - disabled={hasErrors} - onClick={submit} - > - <i18n.Translate>Add</i18n.Translate> - </button> - </div> - </FormProvider> - </InputGroup> - ); -} diff --git a/packages/auditor-backoffice-ui/src/components/form/TextField.tsx b/packages/auditor-backoffice-ui/src/components/form/TextField.tsx deleted file mode 100644 index 8f897c2d8..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/TextField.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 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.js"; - -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> - ); -} |