diff options
Diffstat (limited to 'packages/anastasis-webui/src/components')
8 files changed, 251 insertions, 4 deletions
diff --git a/packages/anastasis-webui/src/components/QR.tsx b/packages/anastasis-webui/src/components/QR.tsx new file mode 100644 index 000000000..48f1a7c12 --- /dev/null +++ b/packages/anastasis-webui/src/components/QR.tsx @@ -0,0 +1,35 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +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/anastasis-webui/src/components/fields/DateInput.tsx b/packages/anastasis-webui/src/components/fields/DateInput.tsx index e1c354f7b..69a05fcf3 100644 --- a/packages/anastasis-webui/src/components/fields/DateInput.tsx +++ b/packages/anastasis-webui/src/components/fields/DateInput.tsx @@ -25,7 +25,7 @@ export function DateInput(props: DateInputProps): VNode { setOpened2(v) } - const value = props.bind[0]; + const value = props.bind[0] || ""; const [dirty, setDirty] = useState(false) const showError = dirty && props.error @@ -40,7 +40,8 @@ export function DateInput(props: DateInputProps): VNode { <input type="text" class={showError ? 'input is-danger' : 'input'} - onClick={() => { setOpened(true) }} + readonly + onFocus={() => { setOpened(true) } } value={value} ref={inputRef} /> diff --git a/packages/anastasis-webui/src/components/fields/EmailInput.tsx b/packages/anastasis-webui/src/components/fields/EmailInput.tsx new file mode 100644 index 000000000..e0fca0f46 --- /dev/null +++ b/packages/anastasis-webui/src/components/fields/EmailInput.tsx @@ -0,0 +1,44 @@ +import { h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; + +export interface TextInputProps { + label: string; + grabFocus?: boolean; + error?: string; + placeholder?: string; + tooltip?: string; + bind: [string, (x: string) => void]; +} + +export function EmailInput(props: TextInputProps): VNode { + const inputRef = useRef<HTMLInputElement>(null); + useLayoutEffect(() => { + if (props.grabFocus) { + inputRef.current?.focus(); + } + }, [props.grabFocus]); + const value = props.bind[0]; + const [dirty, setDirty] = useState(false) + const showError = dirty && props.error + return (<div class="field"> + <label class="label"> + {props.label} + {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span>} + </label> + <div class="control has-icons-right"> + <input + value={value} + required + placeholder={props.placeholder} + type="email" + class={showError ? 'input is-danger' : 'input'} + onChange={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} + ref={inputRef} + style={{ display: "block" }} /> + </div> + {showError && <p class="help is-danger">{props.error}</p>} + </div> + ); +} diff --git a/packages/anastasis-webui/src/components/fields/FileInput.tsx b/packages/anastasis-webui/src/components/fields/FileInput.tsx new file mode 100644 index 000000000..8b144ea43 --- /dev/null +++ b/packages/anastasis-webui/src/components/fields/FileInput.tsx @@ -0,0 +1,81 @@ +/* + 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 { useLayoutEffect, useRef, useState } from "preact/hooks"; +import { TextInputProps } from "./TextInput"; + +const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024 + +export function FileInput(props: TextInputProps): VNode { + const inputRef = useRef<HTMLInputElement>(null); + useLayoutEffect(() => { + if (props.grabFocus) { + inputRef.current?.focus(); + } + }, [props.grabFocus]); + + const value = props.bind[0]; + // const [dirty, setDirty] = useState(false) + const image = useRef<HTMLInputElement>(null) + const [sizeError, setSizeError] = useState(false) + function onChange(v: string): void { + // setDirty(true); + props.bind[1](v); + } + return <div class="field"> + <label class="label"> + <a onClick={() => image.current?.click()}> + {props.label} + </a> + {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span>} + </label> + <div class="control"> + <input + ref={image} style={{ display: 'none' }} + type="file" name={String(name)} + onChange={e => { + const f: FileList | null = e.currentTarget.files + if (!f || f.length != 1) { + return onChange("") + } + if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { + setSizeError(true) + return onChange("") + } + 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) + }) + }} /> + {props.error && <p class="help is-danger">{props.error}</p>} + {sizeError && <p class="help is-danger"> + File should be smaller than 1 MB + </p>} + </div> + </div> +} + diff --git a/packages/anastasis-webui/src/components/fields/ImageInput.tsx b/packages/anastasis-webui/src/components/fields/ImageInput.tsx new file mode 100644 index 000000000..d5bf643d4 --- /dev/null +++ b/packages/anastasis-webui/src/components/fields/ImageInput.tsx @@ -0,0 +1,81 @@ +/* + 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 { useLayoutEffect, useRef, useState } from "preact/hooks"; +import emptyImage from "../../assets/empty.png"; +import { TextInputProps } from "./TextInput"; + +const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024 + +export function ImageInput(props: TextInputProps): VNode { + const inputRef = useRef<HTMLInputElement>(null); + useLayoutEffect(() => { + if (props.grabFocus) { + inputRef.current?.focus(); + } + }, [props.grabFocus]); + + const value = props.bind[0]; + // const [dirty, setDirty] = useState(false) + const image = useRef<HTMLInputElement>(null) + const [sizeError, setSizeError] = useState(false) + function onChange(v: string): void { + // setDirty(true); + props.bind[1](v); + } + return <div class="field"> + <label class="label"> + {props.label} + {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span>} + </label> + <div class="control"> + <img src={!value ? emptyImage : value} style={{ width: 200, height: 200 }} onClick={() => image.current?.click()} /> + <input + ref={image} style={{ display: 'none' }} + type="file" name={String(name)} + onChange={e => { + const f: FileList | null = e.currentTarget.files + if (!f || f.length != 1) { + return onChange(emptyImage) + } + if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { + setSizeError(true) + return onChange(emptyImage) + } + 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) + }) + }} /> + {props.error && <p class="help is-danger">{props.error}</p>} + {sizeError && <p class="help is-danger"> + Image should be smaller than 1 MB + </p>} + </div> + </div> +} + diff --git a/packages/anastasis-webui/src/components/fields/NumberInput.tsx b/packages/anastasis-webui/src/components/fields/NumberInput.tsx index af9bbe66b..2b6cdcd2c 100644 --- a/packages/anastasis-webui/src/components/fields/NumberInput.tsx +++ b/packages/anastasis-webui/src/components/fields/NumberInput.tsx @@ -5,6 +5,7 @@ export interface TextInputProps { label: string; grabFocus?: boolean; error?: string; + placeholder?: string; tooltip?: string; bind: [string, (x: string) => void]; } @@ -30,6 +31,7 @@ export function NumberInput(props: TextInputProps): VNode { <input value={value} type="number" + placeholder={props.placeholder} class={showError ? 'input is-danger' : 'input'} onChange={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} ref={inputRef} diff --git a/packages/anastasis-webui/src/components/fields/TextInput.tsx b/packages/anastasis-webui/src/components/fields/TextInput.tsx index fa6fd9792..4bb785cd3 100644 --- a/packages/anastasis-webui/src/components/fields/TextInput.tsx +++ b/packages/anastasis-webui/src/components/fields/TextInput.tsx @@ -5,6 +5,7 @@ export interface TextInputProps { label: string; grabFocus?: boolean; error?: string; + placeholder?: string; tooltip?: string; bind: [string, (x: string) => void]; } @@ -29,6 +30,7 @@ export function TextInput(props: TextInputProps): VNode { <div class="control has-icons-right"> <input value={value} + placeholder={props.placeholder} class={showError ? 'input is-danger' : 'input'} onChange={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} ref={inputRef} diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx index 87e771009..35720e0f1 100644 --- a/packages/anastasis-webui/src/components/menu/SideBar.tsx +++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx @@ -33,6 +33,7 @@ interface Props { export function Sidebar({ mobile }: Props): VNode { // const config = useConfigContext(); const config = { version: 'none' } + // FIXME: add replacement for __VERSION__ with the current version const process = { env: { __VERSION__: '0.0.0' } } const reducer = useAnastasisContext()! @@ -105,12 +106,12 @@ export function Sidebar({ mobile }: Props): VNode { <span class="menu-item-label"><Translate>Backup completed</Translate></span> </div> </li> - <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}> + {/* <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}> <div class="ml-4"> <span class="menu-item-label"><Translate>Truth Paying</Translate></span> </div> - </li> + </li> */} </Fragment> : (reducer.currentReducerState && reducer.currentReducerState?.recovery_state && <Fragment> <li class={reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting || reducer.currentReducerState.recovery_state === RecoveryStates.CountrySelecting ? 'is-active' : ''}> |