remove unused
This commit is contained in:
parent
f2b319921c
commit
6b6f80466e
@ -1,3 +0,0 @@
|
||||
{
|
||||
"presets": ["preact-cli/babel"]
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 { useLayoutEffect, useRef } from "preact/hooks";
|
||||
import { useAsync } from "../hooks/async.js";
|
||||
|
||||
type Props = {
|
||||
children: ComponentChildren;
|
||||
disabled?: boolean;
|
||||
onClick?: () => Promise<void>;
|
||||
grabFocus?: boolean;
|
||||
[rest: string]: any;
|
||||
};
|
||||
|
||||
export function AsyncButton({
|
||||
onClick,
|
||||
grabFocus,
|
||||
disabled,
|
||||
children,
|
||||
...rest
|
||||
}: Props): VNode {
|
||||
const { isLoading, request } = useAsync(onClick);
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (grabFocus) buttonRef.current?.focus();
|
||||
}, [grabFocus]);
|
||||
|
||||
// if (isSlow) {
|
||||
// return <LoadingModal onCancel={cancel} />;
|
||||
// }
|
||||
if (isLoading) return <button class="button">Loading...</button>;
|
||||
|
||||
return (
|
||||
<span data-tooltip={rest["data-tooltip"]} style={{ marginLeft: 5 }}>
|
||||
<button {...rest} ref={buttonRef} onClick={request} disabled={disabled}>
|
||||
{children}
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 { useRef, useState } from "preact/hooks";
|
||||
|
||||
const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024;
|
||||
|
||||
export interface FileTypeContent {
|
||||
content: string;
|
||||
type: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
onChange: (v: FileTypeContent | undefined) => void;
|
||||
}
|
||||
export function FileButton(props: Props): VNode {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [sizeError, setSizeError] = useState(false);
|
||||
return (
|
||||
<div>
|
||||
<button class="button" onClick={(e) => fileInputRef.current?.click()}>
|
||||
<span>{props.label}</span>
|
||||
</button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
style={{ display: "none" }}
|
||||
type="file"
|
||||
onChange={(e) => {
|
||||
const f: FileList | null = e.currentTarget.files;
|
||||
if (!f || f.length != 1) return props.onChange(undefined);
|
||||
|
||||
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
|
||||
setSizeError(true);
|
||||
return props.onChange(undefined);
|
||||
}
|
||||
setSizeError(false);
|
||||
return f[0].arrayBuffer().then((b) => {
|
||||
const content = new Uint8Array(b).reduce(
|
||||
(data, byte) => data + String.fromCharCode(byte),
|
||||
"",
|
||||
);
|
||||
return props.onChange({
|
||||
content,
|
||||
name: f[0].name,
|
||||
type: f[0].type,
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{sizeError && (
|
||||
<p class="help is-danger">File should be smaller than 1 MB</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 interface Notification {
|
||||
message: string;
|
||||
description?: string | VNode;
|
||||
type: MessageType;
|
||||
}
|
||||
|
||||
export type MessageType = "INFO" | "WARN" | "ERROR" | "SUCCESS";
|
||||
|
||||
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="block">
|
||||
{notifications.map((n, i) => (
|
||||
<article key={i} class={messageStyle(n.type)}>
|
||||
<div class="message-header">
|
||||
<p>{n.message}</p>
|
||||
{removeNotification && (
|
||||
<button
|
||||
class="delete"
|
||||
onClick={() => removeNotification && removeNotification(n)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{n.description && <div class="message-body">{n.description}</div>}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 { format, subYears } from "date-fns";
|
||||
import { h, VNode } from "preact";
|
||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
import { DatePicker } from "../picker/DatePicker.js";
|
||||
|
||||
export interface DateInputProps {
|
||||
label: string;
|
||||
grabFocus?: boolean;
|
||||
tooltip?: string;
|
||||
error?: string;
|
||||
years?: Array<number>;
|
||||
onConfirm?: () => void;
|
||||
bind: [string, (x: string) => void];
|
||||
}
|
||||
|
||||
export function DateInput(props: DateInputProps): VNode {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (props.grabFocus) inputRef.current?.focus();
|
||||
}, [props.grabFocus]);
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
const value = props.bind[0] || "";
|
||||
const [dirty, setDirty] = useState(false);
|
||||
const showError = dirty && props.error;
|
||||
|
||||
const calendar = subYears(new Date(), 30);
|
||||
|
||||
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">
|
||||
<div class="field has-addons">
|
||||
<p class="control">
|
||||
<input
|
||||
type="text"
|
||||
class={showError ? "input is-danger" : "input"}
|
||||
value={value}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === "Enter" && props.onConfirm) props.onConfirm();
|
||||
}}
|
||||
onInput={(e) => {
|
||||
const text = e.currentTarget.value;
|
||||
setDirty(true);
|
||||
props.bind[1](text);
|
||||
}}
|
||||
ref={inputRef}
|
||||
/>
|
||||
</p>
|
||||
<p class="control">
|
||||
<a
|
||||
class="button"
|
||||
onClick={() => {
|
||||
setOpened(true);
|
||||
}}
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-calendar" />
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">Using the format yyyy-mm-dd</p>
|
||||
{showError && <p class="help is-danger">{props.error}</p>}
|
||||
<DatePicker
|
||||
opened={opened}
|
||||
initialDate={calendar}
|
||||
years={props.years}
|
||||
closeFunction={() => setOpened(false)}
|
||||
dateReceiver={(d) => {
|
||||
setDirty(true);
|
||||
const v = format(d, "yyyy-MM-dd");
|
||||
props.bind[1](v);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
export interface TextInputProps {
|
||||
label: string;
|
||||
grabFocus?: boolean;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
tooltip?: string;
|
||||
onConfirm?: () => void;
|
||||
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"}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === "Enter" && props.onConfirm) props.onConfirm();
|
||||
}}
|
||||
onInput={(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>
|
||||
);
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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";
|
||||
|
||||
const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024;
|
||||
|
||||
export interface FileTypeContent {
|
||||
content: string;
|
||||
type: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface FileInputProps {
|
||||
label: string;
|
||||
grabFocus?: boolean;
|
||||
disabled?: boolean;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
tooltip?: string;
|
||||
onChange: (v: FileTypeContent | undefined) => void;
|
||||
}
|
||||
|
||||
export function FileInput(props: FileInputProps): VNode {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (props.grabFocus) inputRef.current?.focus();
|
||||
}, [props.grabFocus]);
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [sizeError, setSizeError] = useState(false);
|
||||
return (
|
||||
<div class="field">
|
||||
<label class="label">
|
||||
<a class="button" onClick={(e) => fileInputRef.current?.click()}>
|
||||
<div class="icon is-small ">
|
||||
<i class="mdi mdi-folder" />
|
||||
</div>
|
||||
<span>{props.label}</span>
|
||||
</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={fileInputRef}
|
||||
style={{ display: "none" }}
|
||||
type="file"
|
||||
// name={String(name)}
|
||||
onChange={(e) => {
|
||||
const f: FileList | null = e.currentTarget.files;
|
||||
if (!f || f.length != 1) return props.onChange(undefined);
|
||||
|
||||
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
|
||||
setSizeError(true);
|
||||
return props.onChange(undefined);
|
||||
}
|
||||
setSizeError(false);
|
||||
return f[0].arrayBuffer().then((b) => {
|
||||
const b64 = btoa(
|
||||
new Uint8Array(b).reduce(
|
||||
(data, byte) => data + String.fromCharCode(byte),
|
||||
"",
|
||||
),
|
||||
);
|
||||
return props.onChange({
|
||||
content: `data:${f[0].type};base64,${b64}`,
|
||||
name: f[0].name,
|
||||
type: f[0].type,
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{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>
|
||||
);
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
import emptyImage from "../../assets/empty.png";
|
||||
import { TextInputProps } from "./TextInput.js";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
export interface TextInputProps {
|
||||
label: string;
|
||||
grabFocus?: boolean;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
tooltip?: string;
|
||||
onConfirm?: () => void;
|
||||
bind: [string, (x: string) => void];
|
||||
}
|
||||
|
||||
export function PhoneNumberInput(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}
|
||||
type="tel"
|
||||
placeholder={props.placeholder}
|
||||
class={showError ? "input is-danger" : "input"}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === "Enter" && props.onConfirm) props.onConfirm();
|
||||
}}
|
||||
onInput={(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>
|
||||
);
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
export interface TextInputProps {
|
||||
inputType?: "text" | "number" | "multiline" | "password";
|
||||
label: string;
|
||||
grabFocus?: boolean;
|
||||
disabled?: boolean;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
tooltip?: string;
|
||||
onConfirm?: () => void;
|
||||
bind: [string, (x: string) => void];
|
||||
}
|
||||
|
||||
const TextInputType = function ({ inputType, grabFocus, ...rest }: any): VNode {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (grabFocus) inputRef.current?.focus();
|
||||
}, [grabFocus]);
|
||||
|
||||
return inputType === "multiline" ? (
|
||||
<textarea {...rest} rows={5} ref={inputRef} style={{ height: "unset" }} />
|
||||
) : (
|
||||
<input {...rest} type={inputType} ref={inputRef} />
|
||||
);
|
||||
};
|
||||
|
||||
export function TextInput(props: TextInputProps): VNode {
|
||||
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">
|
||||
<TextInputType
|
||||
inputType={props.inputType}
|
||||
value={value}
|
||||
grabFocus={props.grabFocus}
|
||||
disabled={props.disabled}
|
||||
placeholder={props.placeholder}
|
||||
class={showError ? "input is-danger" : "input"}
|
||||
onKeyPress={(e: any) => {
|
||||
if (e.key === "Enter" && props.onConfirm) props.onConfirm();
|
||||
}}
|
||||
onInput={(e: any) => {
|
||||
setDirty(true);
|
||||
props.bind[1]((e.target as HTMLInputElement).value);
|
||||
}}
|
||||
style={{ display: "block" }}
|
||||
/>
|
||||
</div>
|
||||
{showError && <p class="help is-danger">{props.error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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";
|
||||
|
||||
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>
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu ">
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}>
|
||||
{/* <LangSelector /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
||||
|
||||
interface Props {
|
||||
mobile?: boolean;
|
||||
}
|
||||
|
||||
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 { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<aside class="aside is-placed-left is-expanded">
|
||||
<div class="aside-tools">
|
||||
<div class="aside-tools-label">
|
||||
<div>
|
||||
<b>euFin bank</b>
|
||||
</div>
|
||||
<div
|
||||
class="is-size-7 has-text-right"
|
||||
style={{ lineHeight: 0, marginTop: -10 }}
|
||||
>
|
||||
Version {process.env.__VERSION__} ({config.version})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu is-menu-main">
|
||||
<p class="menu-label">
|
||||
<i18n.Translate>Bank menu</i18n.Translate>
|
||||
</p>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<div class="ml-4">
|
||||
<span class="menu-item-label">
|
||||
<i18n.Translate>Select option1</i18n.Translate>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ml-4">
|
||||
<span class="menu-item-label">
|
||||
<i18n.Translate>Select option2</i18n.Translate>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 { NavigationBar } from "./NavigationBar.js";
|
||||
import { Sidebar } from "./SideBar.js";
|
||||
|
||||
interface MenuProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
function WithTitle({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
children: ComponentChildren;
|
||||
}): VNode {
|
||||
useEffect(() => {
|
||||
document.title = `${title}`;
|
||||
}, [title]);
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
|
||||
export function Menu({ title }: MenuProps): VNode {
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Match>
|
||||
{({ path }: { path: string }) => {
|
||||
const titleWithSubtitle = title; // title ? title : (!admin ? getInstanceTitle(path, instance) : getAdminTitle(path, instance))
|
||||
return (
|
||||
<WithTitle title={titleWithSubtitle}>
|
||||
<div
|
||||
class={mobileOpen ? "has-aside-mobile-expanded" : ""}
|
||||
onClick={() => setMobileOpen(false)}
|
||||
>
|
||||
<NavigationBar
|
||||
onMobileMenu={() => setMobileOpen(!mobileOpen)}
|
||||
title={titleWithSubtitle}
|
||||
/>
|
||||
|
||||
<Sidebar mobile={mobileOpen} />
|
||||
</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">{n.description}</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="has-aside-mobile-expanded"
|
||||
// class={mobileOpen ? "has-aside-mobile-expanded" : ""}
|
||||
onClick={() => setMobileOpen(false)}
|
||||
>
|
||||
<NavigationBar
|
||||
onMobileMenu={() => setMobileOpen(!mobileOpen)}
|
||||
title={title}
|
||||
/>
|
||||
{onLogout && <Sidebar mobile={mobileOpen} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
message: string;
|
||||
description?: string | VNode;
|
||||
type: MessageType;
|
||||
}
|
||||
|
||||
export type ValueOrFunction<T> = T | ((p: T) => T);
|
||||
export type MessageType = "INFO" | "WARN" | "ERROR" | "SUCCESS";
|
@ -1,347 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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;
|
||||
initialDate?: Date;
|
||||
years?: Array<number>;
|
||||
opened?: boolean;
|
||||
}
|
||||
interface State {
|
||||
displayedMonth: number;
|
||||
displayedYear: number;
|
||||
selectYearMode: boolean;
|
||||
currentDate: Date;
|
||||
}
|
||||
const now = new Date();
|
||||
|
||||
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 yearArr: number[] = [];
|
||||
|
||||
// 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(props: any) {
|
||||
super(props);
|
||||
|
||||
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);
|
||||
|
||||
const initial = props.initialDate || now;
|
||||
|
||||
this.state = {
|
||||
currentDate: initial,
|
||||
displayedMonth: initial.getMonth(),
|
||||
displayedYear: initial.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">
|
||||
{(this.props.years || 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 2010; i <= now.getFullYear() + 10; i++) yearArr.push(i);
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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.js";
|
||||
|
||||
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 />;
|
||||
};
|
@ -1,210 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
||||
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;
|
||||
const ms = ss * 60;
|
||||
const hs = ms * 60;
|
||||
const ds = hs * 24;
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return (
|
||||
<div class="rdp-picker">
|
||||
{days && (
|
||||
<DurationColumn
|
||||
unit={i18n.str`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.str`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.str`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.str`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}`;
|
||||
}
|
@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import App from "./components/app.js";
|
||||
export default App;
|
||||
import { render, h } from "preact";
|
||||
|
||||
import { h, render } from "preact";
|
||||
import "./scss/main.scss";
|
||||
|
||||
const app = document.getElementById("app");
|
||||
|
@ -42,7 +42,7 @@ export function QrCodeSection({
|
||||
<article>
|
||||
<div class="qr-div">
|
||||
<p>{i18n.str`Use this QR code to withdraw to your mobile wallet:`}</p>
|
||||
{QR({ text: talerWithdrawUri })}
|
||||
<QR text={talerWithdrawUri} />
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
Click{" "}
|
||||
|
Loading…
Reference in New Issue
Block a user