This commit is contained in:
Sebastian 2022-10-26 14:50:50 -03:00
parent c34e71cf3d
commit b4bad2deaf
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
31 changed files with 973 additions and 1257 deletions

View File

@ -19,10 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { ComponentChildren, h, VNode } from 'preact'; import { ComponentChildren, h, VNode } from "preact";
import { useLayoutEffect, useRef } from 'preact/hooks'; import { useLayoutEffect, useRef } from "preact/hooks";
// import { LoadingModal } from "../modal"; // import { LoadingModal } from "../modal";
import { useAsync } from '../hooks/async'; import { useAsync } from "../hooks/async";
// import { Translate } from "../../i18n"; // import { Translate } from "../../i18n";
type Props = { type Props = {
@ -44,20 +44,16 @@ export function AsyncButton({
const buttonRef = useRef<HTMLButtonElement>(null); const buttonRef = useRef<HTMLButtonElement>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
if (grabFocus) if (grabFocus) buttonRef.current?.focus();
buttonRef.current?.focus();
}, [grabFocus]); }, [grabFocus]);
// if (isSlow) { // if (isSlow) {
// return <LoadingModal onCancel={cancel} />; // return <LoadingModal onCancel={cancel} />;
// } // }
if (isLoading) if (isLoading) return <button class="button">Loading...</button>;
return <button class="button">Loading...</button>;
return ( return (
<span data-tooltip={rest['data-tooltip']} style={{ marginLeft: 5 }}> <span data-tooltip={rest["data-tooltip"]} style={{ marginLeft: 5 }}>
<button {...rest} ref={buttonRef} onClick={request} disabled={disabled}> <button {...rest} ref={buttonRef} onClick={request} disabled={disabled}>
{children} {children}
</button> </button>

View File

@ -1,5 +1,5 @@
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import { useRef, useState } from 'preact/hooks'; import { useRef, useState } from "preact/hooks";
const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024; const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024;
@ -23,12 +23,11 @@ export function FileButton(props: Props): VNode {
</button> </button>
<input <input
ref={fileInputRef} ref={fileInputRef}
style={{ display: 'none' }} style={{ display: "none" }}
type="file" type="file"
onChange={(e) => { onChange={(e) => {
const f: FileList | null = e.currentTarget.files; const f: FileList | null = e.currentTarget.files;
if (!f || f.length != 1) if (!f || f.length != 1) return props.onChange(undefined);
return props.onChange(undefined);
console.log(f); console.log(f);
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
@ -39,7 +38,7 @@ export function FileButton(props: Props): VNode {
return f[0].arrayBuffer().then((b) => { return f[0].arrayBuffer().then((b) => {
const content = new Uint8Array(b).reduce( const content = new Uint8Array(b).reduce(
(data, byte) => data + String.fromCharCode(byte), (data, byte) => data + String.fromCharCode(byte),
'', "",
); );
return props.onChange({ return props.onChange({
content, content,

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
export interface Notification { export interface Notification {
message: string; message: string;
@ -27,7 +27,7 @@ export interface Notification {
type: MessageType; type: MessageType;
} }
export type MessageType = 'INFO' | 'WARN' | 'ERROR' | 'SUCCESS'; export type MessageType = "INFO" | "WARN" | "ERROR" | "SUCCESS";
interface Props { interface Props {
notifications: Notification[]; notifications: Notification[];
@ -36,16 +36,16 @@ interface Props {
function messageStyle(type: MessageType): string { function messageStyle(type: MessageType): string {
switch (type) { switch (type) {
case 'INFO': case "INFO":
return 'message is-info'; return "message is-info";
case 'WARN': case "WARN":
return 'message is-warning'; return "message is-warning";
case 'ERROR': case "ERROR":
return 'message is-danger'; return "message is-danger";
case 'SUCCESS': case "SUCCESS":
return 'message is-success'; return "message is-success";
default: default:
return 'message'; return "message";
} }
} }

View File

@ -14,14 +14,14 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import { useEffect, useRef } from 'preact/hooks'; import { useEffect, useRef } from "preact/hooks";
import qrcode from 'qrcode-generator'; import qrcode from "qrcode-generator";
export function QR({ text }: { text: string }): VNode { export function QR({ text }: { text: string }): VNode {
const divRef = useRef<HTMLDivElement>(null); const divRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
const qr = qrcode(0, 'L'); const qr = qrcode(0, "L");
qr.addData(text); qr.addData(text);
qr.make(); qr.make();
if (divRef.current) if (divRef.current)
@ -33,14 +33,14 @@ export function QR({ text }: { text: string }): VNode {
return ( return (
<div <div
style={{ style={{
width: '100%', width: "100%",
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
alignItems: 'left', alignItems: "left",
}} }}
> >
<div <div
style={{ width: '50%', minWidth: 200, maxWidth: 300 }} style={{ width: "50%", minWidth: 200, maxWidth: 300 }}
ref={divRef} ref={divRef}
/> />
</div> </div>

View File

@ -1,7 +1,7 @@
import { format, subYears } from 'date-fns'; import { format, subYears } from "date-fns";
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import { useLayoutEffect, useRef, useState } from 'preact/hooks'; import { useLayoutEffect, useRef, useState } from "preact/hooks";
import { DatePicker } from '../picker/DatePicker'; import { DatePicker } from "../picker/DatePicker";
export interface DateInputProps { export interface DateInputProps {
label: string; label: string;
@ -16,13 +16,11 @@ export interface DateInputProps {
export function DateInput(props: DateInputProps): VNode { export function DateInput(props: DateInputProps): VNode {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
if (props.grabFocus) if (props.grabFocus) inputRef.current?.focus();
inputRef.current?.focus();
}, [props.grabFocus]); }, [props.grabFocus]);
const [opened, setOpened] = useState(false); const [opened, setOpened] = useState(false);
const value = props.bind[0] || ''; const value = props.bind[0] || "";
const [dirty, setDirty] = useState(false); const [dirty, setDirty] = useState(false);
const showError = dirty && props.error; const showError = dirty && props.error;
@ -43,12 +41,10 @@ export function DateInput(props: DateInputProps): VNode {
<p class="control"> <p class="control">
<input <input
type="text" type="text"
class={showError ? 'input is-danger' : 'input'} class={showError ? "input is-danger" : "input"}
value={value} value={value}
onKeyPress={(e) => { onKeyPress={(e) => {
if (e.key === 'Enter' && props.onConfirm) if (e.key === "Enter" && props.onConfirm) props.onConfirm();
props.onConfirm()
}} }}
onInput={(e) => { onInput={(e) => {
const text = e.currentTarget.value; const text = e.currentTarget.value;
@ -81,7 +77,7 @@ export function DateInput(props: DateInputProps): VNode {
closeFunction={() => setOpened(false)} closeFunction={() => setOpened(false)}
dateReceiver={(d) => { dateReceiver={(d) => {
setDirty(true); setDirty(true);
const v = format(d, 'yyyy-MM-dd'); const v = format(d, "yyyy-MM-dd");
props.bind[1](v); props.bind[1](v);
}} }}
/> />

View File

@ -1,5 +1,5 @@
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import { useLayoutEffect, useRef, useState } from 'preact/hooks'; import { useLayoutEffect, useRef, useState } from "preact/hooks";
export interface TextInputProps { export interface TextInputProps {
label: string; label: string;
@ -14,9 +14,7 @@ export interface TextInputProps {
export function EmailInput(props: TextInputProps): VNode { export function EmailInput(props: TextInputProps): VNode {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
if (props.grabFocus) if (props.grabFocus) inputRef.current?.focus();
inputRef.current?.focus();
}, [props.grabFocus]); }, [props.grabFocus]);
const value = props.bind[0]; const value = props.bind[0];
const [dirty, setDirty] = useState(false); const [dirty, setDirty] = useState(false);
@ -37,18 +35,16 @@ export function EmailInput(props: TextInputProps): VNode {
required required
placeholder={props.placeholder} placeholder={props.placeholder}
type="email" type="email"
class={showError ? 'input is-danger' : 'input'} class={showError ? "input is-danger" : "input"}
onKeyPress={(e) => { onKeyPress={(e) => {
if (e.key === 'Enter' && props.onConfirm) if (e.key === "Enter" && props.onConfirm) props.onConfirm();
props.onConfirm()
}} }}
onInput={(e) => { onInput={(e) => {
setDirty(true); setDirty(true);
props.bind[1]((e.target as HTMLInputElement).value); props.bind[1]((e.target as HTMLInputElement).value);
}} }}
ref={inputRef} ref={inputRef}
style={{ display: 'block' }} style={{ display: "block" }}
/> />
</div> </div>
{showError && <p class="help is-danger">{props.error}</p>} {showError && <p class="help is-danger">{props.error}</p>}

View File

@ -18,8 +18,8 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import { useLayoutEffect, useRef, useState } from 'preact/hooks'; import { useLayoutEffect, useRef, useState } from "preact/hooks";
const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024; const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024;
@ -42,9 +42,7 @@ export interface FileInputProps {
export function FileInput(props: FileInputProps): VNode { export function FileInput(props: FileInputProps): VNode {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
if (props.grabFocus) if (props.grabFocus) inputRef.current?.focus();
inputRef.current?.focus();
}, [props.grabFocus]); }, [props.grabFocus]);
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
@ -56,9 +54,7 @@ export function FileInput(props: FileInputProps): VNode {
<div class="icon is-small "> <div class="icon is-small ">
<i class="mdi mdi-folder" /> <i class="mdi mdi-folder" />
</div> </div>
<span> <span>{props.label}</span>
{props.label}
</span>
</a> </a>
{props.tooltip && ( {props.tooltip && (
<span class="icon has-tooltip-right" data-tooltip={props.tooltip}> <span class="icon has-tooltip-right" data-tooltip={props.tooltip}>
@ -69,15 +65,14 @@ export function FileInput(props: FileInputProps): VNode {
<div class="control"> <div class="control">
<input <input
ref={fileInputRef} ref={fileInputRef}
style={{ display: 'none' }} style={{ display: "none" }}
type="file" type="file"
// name={String(name)} // name={String(name)}
onChange={(e) => { onChange={(e) => {
const f: FileList | null = e.currentTarget.files; const f: FileList | null = e.currentTarget.files;
if (!f || f.length != 1) if (!f || f.length != 1) return props.onChange(undefined);
return props.onChange(undefined);
console.log(f) console.log(f);
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
setSizeError(true); setSizeError(true);
return props.onChange(undefined); return props.onChange(undefined);
@ -87,10 +82,14 @@ export function FileInput(props: FileInputProps): VNode {
const b64 = btoa( const b64 = btoa(
new Uint8Array(b).reduce( new Uint8Array(b).reduce(
(data, byte) => data + String.fromCharCode(byte), (data, byte) => data + String.fromCharCode(byte),
'', "",
), ),
); );
return props.onChange({content: `data:${f[0].type};base64,${b64}`, name: f[0].name, type: f[0].type}); return props.onChange({
content: `data:${f[0].type};base64,${b64}`,
name: f[0].name,
type: f[0].type,
});
}); });
}} }}
/> />

View File

@ -18,19 +18,17 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import { useLayoutEffect, useRef, useState } from 'preact/hooks'; import { useLayoutEffect, useRef, useState } from "preact/hooks";
import emptyImage from '../../assets/empty.png'; import emptyImage from "../../assets/empty.png";
import { TextInputProps } from './TextInput'; import { TextInputProps } from "./TextInput";
const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024; const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024;
export function ImageInput(props: TextInputProps): VNode { export function ImageInput(props: TextInputProps): VNode {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
if (props.grabFocus) if (props.grabFocus) inputRef.current?.focus();
inputRef.current?.focus();
}, [props.grabFocus]); }, [props.grabFocus]);
const value = props.bind[0]; const value = props.bind[0];
@ -59,13 +57,12 @@ export function ImageInput(props: TextInputProps): VNode {
/> />
<input <input
ref={image} ref={image}
style={{ display: 'none' }} style={{ display: "none" }}
type="file" type="file"
name={String(name)} name={String(name)}
onChange={(e) => { onChange={(e) => {
const f: FileList | null = e.currentTarget.files; const f: FileList | null = e.currentTarget.files;
if (!f || f.length != 1) if (!f || f.length != 1) return onChange(emptyImage);
return onChange(emptyImage);
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
setSizeError(true); setSizeError(true);
@ -76,7 +73,7 @@ export function ImageInput(props: TextInputProps): VNode {
const b64 = btoa( const b64 = btoa(
new Uint8Array(b).reduce( new Uint8Array(b).reduce(
(data, byte) => data + String.fromCharCode(byte), (data, byte) => data + String.fromCharCode(byte),
'', "",
), ),
); );
return onChange(`data:${f[0].type};base64,${b64}` as any); return onChange(`data:${f[0].type};base64,${b64}` as any);

View File

@ -1,5 +1,5 @@
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import { useLayoutEffect, useRef, useState } from 'preact/hooks'; import { useLayoutEffect, useRef, useState } from "preact/hooks";
export interface TextInputProps { export interface TextInputProps {
label: string; label: string;
@ -14,9 +14,7 @@ export interface TextInputProps {
export function PhoneNumberInput(props: TextInputProps): VNode { export function PhoneNumberInput(props: TextInputProps): VNode {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
if (props.grabFocus) if (props.grabFocus) inputRef.current?.focus();
inputRef.current?.focus();
}, [props.grabFocus]); }, [props.grabFocus]);
const value = props.bind[0]; const value = props.bind[0];
const [dirty, setDirty] = useState(false); const [dirty, setDirty] = useState(false);
@ -36,18 +34,16 @@ export function PhoneNumberInput(props: TextInputProps): VNode {
value={value} value={value}
type="tel" type="tel"
placeholder={props.placeholder} placeholder={props.placeholder}
class={showError ? 'input is-danger' : 'input'} class={showError ? "input is-danger" : "input"}
onKeyPress={(e) => { onKeyPress={(e) => {
if (e.key === 'Enter' && props.onConfirm) if (e.key === "Enter" && props.onConfirm) props.onConfirm();
props.onConfirm()
}} }}
onInput={(e) => { onInput={(e) => {
setDirty(true); setDirty(true);
props.bind[1]((e.target as HTMLInputElement).value); props.bind[1]((e.target as HTMLInputElement).value);
}} }}
ref={inputRef} ref={inputRef}
style={{ display: 'block' }} style={{ display: "block" }}
/> />
</div> </div>
{showError && <p class="help is-danger">{props.error}</p>} {showError && <p class="help is-danger">{props.error}</p>}

View File

@ -1,8 +1,8 @@
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import { useLayoutEffect, useRef, useState } from 'preact/hooks'; import { useLayoutEffect, useRef, useState } from "preact/hooks";
export interface TextInputProps { export interface TextInputProps {
inputType?: 'text' | 'number' | 'multiline' | 'password'; inputType?: "text" | "number" | "multiline" | "password";
label: string; label: string;
grabFocus?: boolean; grabFocus?: boolean;
disabled?: boolean; disabled?: boolean;
@ -16,13 +16,11 @@ export interface TextInputProps {
const TextInputType = function ({ inputType, grabFocus, ...rest }: any): VNode { const TextInputType = function ({ inputType, grabFocus, ...rest }: any): VNode {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
if (grabFocus) if (grabFocus) inputRef.current?.focus();
inputRef.current?.focus();
}, [grabFocus]); }, [grabFocus]);
return inputType === 'multiline' ? ( return inputType === "multiline" ? (
<textarea {...rest} rows={5} ref={inputRef} style={{ height: 'unset' }} /> <textarea {...rest} rows={5} ref={inputRef} style={{ height: "unset" }} />
) : ( ) : (
<input {...rest} type={inputType} ref={inputRef} /> <input {...rest} type={inputType} ref={inputRef} />
); );
@ -49,17 +47,15 @@ export function TextInput(props: TextInputProps): VNode {
grabFocus={props.grabFocus} grabFocus={props.grabFocus}
disabled={props.disabled} disabled={props.disabled}
placeholder={props.placeholder} placeholder={props.placeholder}
class={showError ? 'input is-danger' : 'input'} class={showError ? "input is-danger" : "input"}
onKeyPress={(e: any) => { onKeyPress={(e: any) => {
if (e.key === 'Enter' && props.onConfirm) if (e.key === "Enter" && props.onConfirm) props.onConfirm();
props.onConfirm();
}} }}
onInput={(e: any) => { onInput={(e: any) => {
setDirty(true); setDirty(true);
props.bind[1]((e.target as HTMLInputElement).value); props.bind[1]((e.target as HTMLInputElement).value);
}} }}
style={{ display: 'block' }} style={{ display: "block" }}
/> />
</div> </div>
{showError && <p class="help is-danger">{props.error}</p>} {showError && <p class="help is-danger">{props.error}</p>}

View File

@ -19,23 +19,23 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, VNode, Fragment } from 'preact'; import { h, VNode, Fragment } from "preact";
import { useCallback, useEffect, useState } from 'preact/hooks'; import { useCallback, useEffect, useState } from "preact/hooks";
import langIcon from '../../assets/icons/languageicon.svg'; import langIcon from "../../assets/icons/languageicon.svg";
import { useTranslationContext } from '../../context/translation'; import { useTranslationContext } from "../../context/translation";
import { strings as messages } from '../../i18n/strings'; import { strings as messages } from "../../i18n/strings";
type LangsNames = { type LangsNames = {
[P in keyof typeof messages]: string; [P in keyof typeof messages]: string;
}; };
const names: LangsNames = { const names: LangsNames = {
es: 'Español [es]', es: "Español [es]",
en: 'English [en]', en: "English [en]",
fr: 'Français [fr]', fr: "Français [fr]",
de: 'Deutsch [de]', de: "Deutsch [de]",
sv: 'Svenska [sv]', sv: "Svenska [sv]",
it: 'Italiano [it]', it: "Italiano [it]",
}; };
function getLangName(s: keyof LangsNames | string): string { function getLangName(s: keyof LangsNames | string): string {
@ -47,36 +47,38 @@ function getLangName(s: keyof LangsNames | string): string {
export function LangSelectorLikePy(): VNode { export function LangSelectorLikePy(): VNode {
const [updatingLang, setUpdatingLang] = useState(false); const [updatingLang, setUpdatingLang] = useState(false);
const { lang, changeLanguage } = useTranslationContext(); const { lang, changeLanguage } = useTranslationContext();
const [hidden, setHidden] = useState(true) const [hidden, setHidden] = useState(true);
useEffect(() => { useEffect(() => {
function bodyKeyPress(event:KeyboardEvent) { function bodyKeyPress(event: KeyboardEvent) {
if (event.code === 'Escape') if (event.code === "Escape") setHidden(true);
setHidden(true);
} }
function bodyOnClick(event:Event) { function bodyOnClick(event: Event) {
setHidden(true); setHidden(true);
} }
document.body.addEventListener('click', bodyOnClick) document.body.addEventListener("click", bodyOnClick);
document.body.addEventListener('keydown', bodyKeyPress as any) document.body.addEventListener("keydown", bodyKeyPress as any);
return () => { return () => {
document.body.removeEventListener('keydown', bodyKeyPress as any) document.body.removeEventListener("keydown", bodyKeyPress as any);
document.body.removeEventListener('click', bodyOnClick) document.body.removeEventListener("click", bodyOnClick);
} };
},[]) }, []);
return ( return (
<Fragment> <Fragment>
<button name="language" onClick={(ev) => { <button
setHidden(h => !h); name="language"
ev.stopPropagation(); onClick={(ev) => {
}}> setHidden((h) => !h);
ev.stopPropagation();
}}
>
{getLangName(lang)} {getLangName(lang)}
</button> </button>
<div id="lang" class={hidden ? 'hide' : ''}> <div id="lang" class={hidden ? "hide" : ""}>
<div style="position: relative; overflow: visible;"> <div style="position: relative; overflow: visible;">
<div <div
class="nav" class="nav"
style="position: absolute; max-height: 60vh; overflow-y: scroll"> style="position: absolute; max-height: 60vh; overflow-y: scroll"
>
{Object.keys(messages) {Object.keys(messages)
.filter((l) => l !== lang) .filter((l) => l !== lang)
.map((l) => ( .map((l) => (
@ -88,7 +90,8 @@ export function LangSelectorLikePy(): VNode {
onClick={() => { onClick={() => {
changeLanguage(l); changeLanguage(l);
setUpdatingLang(false); setUpdatingLang(false);
}}> }}
>
{getLangName(l)} {getLangName(l)}
</a> </a>
))} ))}

View File

@ -19,9 +19,9 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import logo from '../../assets/logo.jpeg'; import logo from "../../assets/logo.jpeg";
import { LangSelectorLikePy as LangSelector } from './LangSelector'; import { LangSelectorLikePy as LangSelector } from "./LangSelector";
interface Props { interface Props {
onMobileMenu: () => void; onMobileMenu: () => void;

View File

@ -19,8 +19,8 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import { Translate } from '../../i18n'; import { Translate } from "../../i18n";
interface Props { interface Props {
mobile?: boolean; mobile?: boolean;
@ -28,9 +28,9 @@ interface Props {
export function Sidebar({ mobile }: Props): VNode { export function Sidebar({ mobile }: Props): VNode {
// const config = useConfigContext(); // const config = useConfigContext();
const config = { version: 'none' }; const config = { version: "none" };
// FIXME: add replacement for __VERSION__ with the current version // FIXME: add replacement for __VERSION__ with the current version
const process = { env: { __VERSION__: '0.0.0' } }; const process = { env: { __VERSION__: "0.0.0" } };
return ( return (
<aside class="aside is-placed-left is-expanded"> <aside class="aside is-placed-left is-expanded">

View File

@ -14,11 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { ComponentChildren, Fragment, h, VNode } from 'preact'; import { ComponentChildren, Fragment, h, VNode } from "preact";
import Match from 'preact-router/match'; import Match from "preact-router/match";
import { useEffect, useState } from 'preact/hooks'; import { useEffect, useState } from "preact/hooks";
import { NavigationBar } from './NavigationBar'; import { NavigationBar } from "./NavigationBar";
import { Sidebar } from './SideBar'; import { Sidebar } from "./SideBar";
interface MenuProps { interface MenuProps {
title: string; title: string;
@ -47,7 +47,7 @@ export function Menu({ title }: MenuProps): VNode {
return ( return (
<WithTitle title={titleWithSubtitle}> <WithTitle title={titleWithSubtitle}>
<div <div
class={mobileOpen ? 'has-aside-mobile-expanded' : ''} class={mobileOpen ? "has-aside-mobile-expanded" : ""}
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
> >
<NavigationBar <NavigationBar
@ -82,11 +82,11 @@ export function NotificationCard({
<div class="column is-12"> <div class="column is-12">
<article <article
class={ class={
n.type === 'ERROR' n.type === "ERROR"
? 'message is-danger' ? "message is-danger"
: n.type === 'WARN' : n.type === "WARN"
? 'message is-warning' ? "message is-warning"
: 'message is-info' : "message is-info"
} }
> >
<div class="message-header"> <div class="message-header">
@ -132,4 +132,4 @@ export interface Notification {
} }
export type ValueOrFunction<T> = T | ((p: T) => T); export type ValueOrFunction<T> = T | ((p: T) => T);
export type MessageType = 'INFO' | 'WARN' | 'ERROR' | 'SUCCESS'; export type MessageType = "INFO" | "WARN" | "ERROR" | "SUCCESS";

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, Component } from 'preact'; import { h, Component } from "preact";
interface Props { interface Props {
closeFunction?: () => void; closeFunction?: () => void;
@ -37,36 +37,36 @@ interface State {
const now = new Date(); const now = new Date();
const monthArrShortFull = [ const monthArrShortFull = [
'January', "January",
'February', "February",
'March', "March",
'April', "April",
'May', "May",
'June', "June",
'July', "July",
'August', "August",
'September', "September",
'October', "October",
'November', "November",
'December', "December",
]; ];
const monthArrShort = [ const monthArrShort = [
'Jan', "Jan",
'Feb', "Feb",
'Mar', "Mar",
'Apr', "Apr",
'May', "May",
'Jun', "Jun",
'Jul', "Jul",
'Aug', "Aug",
'Sep', "Sep",
'Oct', "Oct",
'Nov', "Nov",
'Dec', "Dec",
]; ];
const dayArr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const dayArr = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const yearArr: number[] = []; const yearArr: number[] = [];
@ -83,10 +83,10 @@ export class DatePicker extends Component<Props, State> {
dayClicked(e: any) { dayClicked(e: any) {
const element = e.target; // the actual element clicked const element = e.target; // the actual element clicked
if (element.innerHTML === '') return false; // don't continue if <span /> empty if (element.innerHTML === "") return false; // don't continue if <span /> empty
// get date from clicked element (gets attached when rendered) // get date from clicked element (gets attached when rendered)
const date = new Date(element.getAttribute('data-value')); const date = new Date(element.getAttribute("data-value"));
// update the state // update the state
this.setState({ currentDate: date }); this.setState({ currentDate: date });
@ -140,7 +140,6 @@ export class DatePicker extends Component<Props, State> {
this.setState({ this.setState({
displayedMonth: this.state.displayedMonth - 1, displayedMonth: this.state.displayedMonth - 1,
}); });
} }
/** /**
@ -156,15 +155,13 @@ export class DatePicker extends Component<Props, State> {
this.setState({ this.setState({
displayedMonth: this.state.displayedMonth + 1, displayedMonth: this.state.displayedMonth + 1,
}); });
} }
/** /**
* Display the selected month (gets fired when clicking on the date string) * Display the selected month (gets fired when clicking on the date string)
*/ */
displaySelectedMonth() { displaySelectedMonth() {
if (this.state.selectYearMode) if (this.state.selectYearMode) this.toggleYearSelector();
this.toggleYearSelector();
else { else {
if (!this.state.currentDate) return false; if (!this.state.currentDate) return false;
this.setState({ this.setState({
@ -194,7 +191,7 @@ export class DatePicker extends Component<Props, State> {
this.passDateToParent(this.state.currentDate); this.passDateToParent(this.state.currentDate);
} }
passDateToParent(date: Date) { passDateToParent(date: Date) {
if (typeof this.props.dateReceiver === 'function') if (typeof this.props.dateReceiver === "function")
this.props.dateReceiver(date); this.props.dateReceiver(date);
this.closeDatePicker(); this.closeDatePicker();
} }
@ -229,22 +226,18 @@ export class DatePicker extends Component<Props, State> {
} }
render() { render() {
const { const { currentDate, displayedMonth, displayedYear, selectYearMode } =
currentDate, this.state;
displayedMonth,
displayedYear,
selectYearMode,
} = this.state;
return ( return (
<div> <div>
<div class={`datePicker ${this.props.opened && 'datePicker--opened'}`}> <div class={`datePicker ${this.props.opened && "datePicker--opened"}`}>
<div class="datePicker--titles"> <div class="datePicker--titles">
<h3 <h3
style={{ style={{
color: selectYearMode color: selectYearMode
? 'rgba(255,255,255,.87)' ? "rgba(255,255,255,.87)"
: 'rgba(255,255,255,.57)', : "rgba(255,255,255,.57)",
}} }}
onClick={this.toggleYearSelector} onClick={this.toggleYearSelector}
> >
@ -253,12 +246,12 @@ export class DatePicker extends Component<Props, State> {
<h2 <h2
style={{ style={{
color: !selectYearMode color: !selectYearMode
? 'rgba(255,255,255,.87)' ? "rgba(255,255,255,.87)"
: 'rgba(255,255,255,.57)', : "rgba(255,255,255,.57)",
}} }}
onClick={this.displaySelectedMonth} onClick={this.displaySelectedMonth}
> >
{dayArr[currentDate.getDay()]},{' '} {dayArr[currentDate.getDay()]},{" "}
{monthArrShort[currentDate.getMonth()]} {currentDate.getDate()} {monthArrShort[currentDate.getMonth()]} {currentDate.getDate()}
</h2> </h2>
</div> </div>
@ -267,7 +260,7 @@ export class DatePicker extends Component<Props, State> {
<nav> <nav>
<span onClick={this.displayPrevMonth} class="icon"> <span onClick={this.displayPrevMonth} class="icon">
<i <i
style={{ transform: 'rotate(180deg)' }} style={{ transform: "rotate(180deg)" }}
class="mdi mdi-forward" class="mdi mdi-forward"
/> />
</span> </span>
@ -284,7 +277,7 @@ export class DatePicker extends Component<Props, State> {
{!selectYearMode && ( {!selectYearMode && (
<div class="datePicker--calendar"> <div class="datePicker--calendar">
<div class="datePicker--dayNames"> <div class="datePicker--dayNames">
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day, i) => ( {["S", "M", "T", "W", "T", "F", "S"].map((day, i) => (
<span key={i}>{day}</span> <span key={i}>{day}</span>
))} ))}
</div> </div>
@ -309,8 +302,8 @@ export class DatePicker extends Component<Props, State> {
<span <span
key={day.day} key={day.day}
class={ class={
(day.today ? 'datePicker--today ' : '') + (day.today ? "datePicker--today " : "") +
(selected ? 'datePicker--selected' : '') (selected ? "datePicker--selected" : "")
} }
disabled={!day.date} disabled={!day.date}
data-value={day.date} data-value={day.date}
@ -328,7 +321,7 @@ export class DatePicker extends Component<Props, State> {
{(this.props.years || yearArr).map((year) => ( {(this.props.years || yearArr).map((year) => (
<span <span
key={year} key={year}
class={year === displayedYear ? 'selected' : ''} class={year === displayedYear ? "selected" : ""}
onClick={this.changeDisplayedYear} onClick={this.changeDisplayedYear}
> >
{year} {year}
@ -343,7 +336,7 @@ export class DatePicker extends Component<Props, State> {
class="datePicker--background" class="datePicker--background"
onClick={this.closeDatePicker} onClick={this.closeDatePicker}
style={{ style={{
display: this.props.opened ? 'block' : 'none', display: this.props.opened ? "block" : "none",
}} }}
/> />
</div> </div>
@ -351,6 +344,4 @@ export class DatePicker extends Component<Props, State> {
} }
} }
for (let i = 2010; i <= now.getFullYear() + 10; i++) for (let i = 2010; i <= now.getFullYear() + 10; i++) yearArr.push(i);
yearArr.push(i);

View File

@ -19,16 +19,16 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, FunctionalComponent } from 'preact'; import { h, FunctionalComponent } from "preact";
import { useState } from 'preact/hooks'; import { useState } from "preact/hooks";
import { DurationPicker as TestedComponent } from './DurationPicker'; import { DurationPicker as TestedComponent } from "./DurationPicker";
export default { export default {
title: 'Components/Picker/Duration', title: "Components/Picker/Duration",
component: TestedComponent, component: TestedComponent,
argTypes: { argTypes: {
onCreate: { action: 'onCreate' }, onCreate: { action: "onCreate" },
goBack: { action: 'goBack' }, goBack: { action: "goBack" },
}, },
}; };

View File

@ -19,10 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, VNode } from 'preact'; import { h, VNode } from "preact";
import { useState } from 'preact/hooks'; import { useState } from "preact/hooks";
import { useTranslator } from '../../i18n'; import { useTranslator } from "../../i18n";
import '../../scss/DurationPicker.scss'; import "../../scss/DurationPicker.scss";
export interface Props { export interface Props {
hours?: boolean; hours?: boolean;
@ -129,9 +129,9 @@ function InputNumber({
}} }}
style={{ style={{
width: 50, width: 50,
border: 'none', border: "none",
fontSize: 'inherit', fontSize: "inherit",
background: 'inherit', background: "inherit",
}} }}
/> />
); );
@ -157,7 +157,7 @@ function DurationColumn({
<div class="rdp-cell" key={value - 2}> <div class="rdp-cell" key={value - 2}>
{onDecrease && ( {onDecrease && (
<button <button
style={{ width: '100%', textAlign: 'center', margin: 5 }} style={{ width: "100%", textAlign: "center", margin: 5 }}
onClick={onDecrease} onClick={onDecrease}
> >
<span class="icon"> <span class="icon">
@ -167,7 +167,7 @@ function DurationColumn({
)} )}
</div> </div>
<div class="rdp-cell" key={value - 1}> <div class="rdp-cell" key={value - 1}>
{value > min ? toTwoDigitString(value - 1) : ''} {value > min ? toTwoDigitString(value - 1) : ""}
</div> </div>
<div class="rdp-cell rdp-center" key={value}> <div class="rdp-cell rdp-center" key={value}>
{onChange ? ( {onChange ? (
@ -182,13 +182,13 @@ function DurationColumn({
</div> </div>
<div class="rdp-cell" key={value + 1}> <div class="rdp-cell" key={value + 1}>
{value < max ? toTwoDigitString(value + 1) : ''} {value < max ? toTwoDigitString(value + 1) : ""}
</div> </div>
<div class="rdp-cell" key={value + 2}> <div class="rdp-cell" key={value + 2}>
{onIncrease && ( {onIncrease && (
<button <button
style={{ width: '100%', textAlign: 'center', margin: 5 }} style={{ width: "100%", textAlign: "center", margin: 5 }}
onClick={onIncrease} onClick={onIncrease}
> >
<span class="icon"> <span class="icon">
@ -204,8 +204,7 @@ function DurationColumn({
} }
function toTwoDigitString(n: number) { function toTwoDigitString(n: number) {
if (n < 10) if (n < 10) return `0${n}`;
return `0${n}`;
return `${n}`; return `${n}`;
} }

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createContext, h, VNode } from 'preact'; import { createContext, h, VNode } from "preact";
import { useContext, useEffect } from 'preact/hooks'; import { useContext, useEffect } from "preact/hooks";
import { useLang } from '../hooks/index.js'; import { useLang } from "../hooks/index.js";
import * as jedLib from 'jed'; import * as jedLib from "jed";
import { strings } from '../i18n/strings.js'; import { strings } from "../i18n/strings.js";
interface Type { interface Type {
lang: string; lang: string;
@ -31,7 +31,7 @@ interface Type {
changeLanguage: (l: string) => void; changeLanguage: (l: string) => void;
} }
const initial = { const initial = {
lang: 'en', lang: "en",
handler: null, handler: null,
changeLanguage: () => { changeLanguage: () => {
/** /**
@ -55,15 +55,12 @@ export const TranslationProvider = ({
children, children,
forceLang, forceLang,
}: Props): VNode => { }: Props): VNode => {
const [lang, changeLanguage] = useLang(initial); const [lang, changeLanguage] = useLang(initial);
useEffect(() => { useEffect(() => {
if (forceLang) if (forceLang) changeLanguage(forceLang);
changeLanguage(forceLang);
}); });
console.log('lang store', strings); console.log("lang store", strings);
const handler = new jedLib.Jed(strings[lang] || strings['en']); const handler = new jedLib.Jed(strings[lang] || strings["en"]);
return h(Context.Provider, { return h(Context.Provider, {
value: { lang, handler, changeLanguage }, value: { lang, handler, changeLanguage },
children, children,

View File

@ -1,20 +1,20 @@
declare module '*.css' { declare module "*.css" {
const mapping: Record<string, string>; const mapping: Record<string, string>;
export default mapping; export default mapping;
} }
declare module '*.svg' { declare module "*.svg" {
const content: any; const content: any;
export default content; export default content;
} }
declare module '*.jpeg' { declare module "*.jpeg" {
const content: any; const content: any;
export default content; export default content;
} }
declare module '*.png' { declare module "*.png" {
const content: any; const content: any;
export default content; export default content;
} }
declare module 'jed' { declare module "jed" {
const x: any; const x: any;
export = x; export = x;
} }

View File

@ -18,7 +18,7 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useState } from 'preact/hooks'; import { useState } from "preact/hooks";
// import { cancelPendingRequest } from "./backend"; // import { cancelPendingRequest } from "./backend";
export interface Options { export interface Options {
@ -51,9 +51,9 @@ export function useAsync<T>(
}, tooLong); }, tooLong);
try { try {
console.log('calling async', args); console.log("calling async", args);
const result = await fn(...args); const result = await fn(...args);
console.log('async back', result); console.log("async back", result);
setData(result); setData(result);
} catch (error) { } catch (error) {
setError(error); setError(error);

View File

@ -21,9 +21,9 @@
/** /**
* Imports * Imports
*/ */
import { ComponentChild, ComponentChildren, h, Fragment, VNode } from 'preact'; import { ComponentChild, ComponentChildren, h, Fragment, VNode } from "preact";
import { useTranslationContext } from '../context/translation'; import { useTranslationContext } from "../context/translation";
export function useTranslator() { export function useTranslator() {
const ctx = useTranslationContext(); const ctx = useTranslationContext();
@ -46,12 +46,10 @@ export function useTranslator() {
* Convert template strings to a msgid * Convert template strings to a msgid
*/ */
function toI18nString(stringSeq: ReadonlyArray<string>): string { function toI18nString(stringSeq: ReadonlyArray<string>): string {
let s = ''; let s = "";
for (let i = 0; i < stringSeq.length; i++) { for (let i = 0; i < stringSeq.length; i++) {
s += stringSeq[i]; s += stringSeq[i];
if (i < stringSeq.length - 1) if (i < stringSeq.length - 1) s += `%${i + 1}$s`;
s += `%${i + 1}$s`;
} }
return s; return s;
} }
@ -64,12 +62,11 @@ interface TranslateSwitchProps {
function stringifyChildren(children: ComponentChildren): string { function stringifyChildren(children: ComponentChildren): string {
let n = 1; let n = 1;
const ss = (children instanceof Array ? children : [children]).map((c) => { const ss = (children instanceof Array ? children : [children]).map((c) => {
if (typeof c === 'string') if (typeof c === "string") return c;
return c;
return `%${n++}$s`; return `%${n++}$s`;
}); });
const s = ss.join('').replace(/ +/g, ' ').trim(); const s = ss.join("").replace(/ +/g, " ").trim();
return s; return s;
} }
@ -97,13 +94,9 @@ function getTranslatedChildren(
const placeholderChildren = Array<ComponentChild>(); const placeholderChildren = Array<ComponentChild>();
for (let i = 0; i < childArray.length; i++) { for (let i = 0; i < childArray.length; i++) {
const x = childArray[i]; const x = childArray[i];
if (x === undefined) if (x === undefined) continue;
continue; else if (typeof x === "string") continue;
else if (typeof x === 'string') else placeholderChildren.push(x);
continue;
else
placeholderChildren.push(x);
} }
const result = Array<ComponentChild>(); const result = Array<ComponentChild>();
for (let i = 0; i < tr.length; i++) for (let i = 0; i < tr.length; i++)
@ -157,18 +150,15 @@ export function TranslateSwitch({ children, target }: TranslateSwitchProps) {
if (children) if (children)
(children instanceof Array ? children : [children]).forEach( (children instanceof Array ? children : [children]).forEach(
(child: any) => { (child: any) => {
if (child.type === TranslatePlural) if (child.type === TranslatePlural) plural = child;
plural = child;
if (child.type === TranslateSingular)
singular = child;
if (child.type === TranslateSingular) singular = child;
}, },
); );
if (!singular || !plural) { if (!singular || !plural) {
console.error('translation not found'); console.error("translation not found");
return h('span', {}, ['translation not found']); return h("span", {}, ["translation not found"]);
} }
singular.props.target = target; singular.props.target = target;
plural.props.target = target; plural.props.target = target;

View File

@ -15,458 +15,207 @@
*/ */
/*eslint quote-props: ["error", "consistent"]*/ /*eslint quote-props: ["error", "consistent"]*/
export const strings: {[s: string]: any} = {}; export const strings: { [s: string]: any } = {};
strings['de'] = { strings["de"] = {
'domain': 'messages', domain: "messages",
'locale_data': { locale_data: {
'messages': { messages: {
'days': [ days: [""],
'' hours: [""],
], minutes: [""],
'hours': [ seconds: [""],
'' Clear: [""],
], Logout: [""],
'minutes': [ "Demo Bank": [""],
'' "Go back": [""],
], "Wire transfer": [""],
'seconds': [ "Transfer money to another account of this bank:": [""],
'' "Want to try the raw payto://-format?": [""],
], "Transfer money via the Payto system:": [""],
'Clear': [ "payto address": [""],
'' Confirm: [""],
], "Confirm Withdrawal": [""],
'Logout': [ "Waiting the bank to create the operaion...": [""],
'' "This withdrawal was aborted!": [""],
], "Withdraw to a Taler Wallet": [""],
'Demo Bank': [ "You can use this QR code to withdraw to your mobile wallet:": [""],
'' "this link": [""],
], Abort: [""],
'Go back': [ "Start withdrawal": [""],
'' "Withdraw Money into a Taler wallet": [""],
], "Amount to withdraw": [""],
'Wire transfer': [ "Please login!": [""],
'' Login: [""],
], "Register to the euFin bank!": [""],
'Transfer money to another account of this bank:': [ "Registration form": [""],
'' Register: [""],
], Date: [""],
'Want to try the raw payto://-format?': [ Amount: [""],
'' Counterpart: [""],
], Subject: [""],
'Transfer money via the Payto system:': [ "Username or account label '%1$s' not found. Won't login.": [""],
'' "Wrong credentials given.": [""],
], "Account information could not be retrieved.": [""],
'payto address': [ "Close wire transfer": [""],
'' "Close Taler withdrawal": [""],
], "Bank account balance:": [""],
'Confirm': [ "Latest transactions:": [""],
'' "Transfer money manually": [""],
], "List of public accounts was not found.": [""],
'Confirm Withdrawal': [ "List of public accounts could not be retrieved.": [""],
'' "History of public accounts": [""],
], "Page has a problem: logged in but backend state is lost.": [""],
'Waiting the bank to create the operaion...': [ "Welcome to the euFin bank!": [""],
'' "": {
], domain: "messages",
'This withdrawal was aborted!': [ plural_forms: "nplurals=2; plural=(n != 1);",
'' lang: "de",
], },
'Withdraw to a Taler Wallet': [ },
'' },
],
'You can use this QR code to withdraw to your mobile wallet:': [
''
],
'this link': [
''
],
'Abort': [
''
],
'Start withdrawal': [
''
],
'Withdraw Money into a Taler wallet': [
''
],
'Amount to withdraw': [
''
],
'Please login!': [
''
],
'Login': [
''
],
'Register to the euFin bank!': [
''
],
'Registration form': [
''
],
'Register': [
''
],
'Date': [
''
],
'Amount': [
''
],
'Counterpart': [
''
],
'Subject': [
''
],
'Username or account label \'%1$s\' not found. Won\'t login.': [
''
],
'Wrong credentials given.': [
''
],
'Account information could not be retrieved.': [
''
],
'Close wire transfer': [
''
],
'Close Taler withdrawal': [
''
],
'Bank account balance:': [
''
],
'Latest transactions:': [
''
],
'Transfer money manually': [
''
],
'List of public accounts was not found.': [
''
],
'List of public accounts could not be retrieved.': [
''
],
'History of public accounts': [
''
],
'Page has a problem: logged in but backend state is lost.': [
''
],
'Welcome to the euFin bank!': [
''
],
'': {
'domain': 'messages',
'plural_forms': 'nplurals=2; plural=(n != 1);',
'lang': 'de'
}
}
}
}; };
strings['en'] = { strings["en"] = {
'domain': 'messages', domain: "messages",
'locale_data': { locale_data: {
'messages': { messages: {
'days': [ days: ["days"],
'days' hours: ["hours"],
minutes: ["minutes"],
seconds: ["seconds"],
Clear: [""],
Logout: [""],
"Demo Bank": [""],
"Go back": ["Go back"],
"Wire transfer": [""],
"Transfer money to another account of this bank:": [""],
"Want to try the raw payto://-format?": [""],
"Transfer money via the Payto system:": [""],
"payto address": [""],
Confirm: [""],
"Confirm Withdrawal": ["Confirm withdrawal"],
"Waiting the bank to create the operaion...": [""],
"This withdrawal was aborted!": [""],
"Withdraw to a Taler Wallet": ["Charge Taler wallet"],
"You can use this QR code to withdraw to your mobile wallet:": [""],
"this link": [""],
Abort: [""],
"Start withdrawal": ["Start withdrawal"],
"Withdraw Money into a Taler wallet": ["Charge Taler wallet"],
"Amount to withdraw": ["Amount to withdraw"],
"Please login!": [""],
Login: [""],
"Register to the euFin bank!": [""],
"Registration form": [""],
Register: [""],
Date: [""],
Amount: [""],
Counterpart: [""],
Subject: [""],
"Username or account label '%1$s' not found. Won't login.": [""],
"Wrong credentials given.": [""],
"Account information could not be retrieved.": [""],
"Close wire transfer": [""],
"Close Taler withdrawal": ["Close Taler withdrawal"],
"Bank account balance:": [""],
"Latest transactions:": [""],
"Transfer money manually": [""],
"List of public accounts was not found.": [""],
"List of public accounts could not be retrieved.": [""],
"History of public accounts": [""],
"Page has a problem: logged in but backend state is lost.": [
"Page has a problem: logged in but backend state is lost.",
], ],
'hours': [ "Welcome to the euFin bank!": [
'hours' "Welcome to euFin bank: Taler+IBAN now possible!",
], ],
'minutes': [ "": {
'minutes' domain: "messages",
], plural_forms: "nplurals=2; plural=(n != 1);",
'seconds': [ lang: "en",
'seconds' },
], },
'Clear': [ },
''
],
'Logout': [
''
],
'Demo Bank': [
''
],
'Go back': [
'Go back'
],
'Wire transfer': [
''
],
'Transfer money to another account of this bank:': [
''
],
'Want to try the raw payto://-format?': [
''
],
'Transfer money via the Payto system:': [
''
],
'payto address': [
''
],
'Confirm': [
''
],
'Confirm Withdrawal': [
'Confirm withdrawal'
],
'Waiting the bank to create the operaion...': [
''
],
'This withdrawal was aborted!': [
''
],
'Withdraw to a Taler Wallet': [
'Charge Taler wallet'
],
'You can use this QR code to withdraw to your mobile wallet:': [
''
],
'this link': [
''
],
'Abort': [
''
],
'Start withdrawal': [
'Start withdrawal'
],
'Withdraw Money into a Taler wallet': [
'Charge Taler wallet'
],
'Amount to withdraw': [
'Amount to withdraw'
],
'Please login!': [
''
],
'Login': [
''
],
'Register to the euFin bank!': [
''
],
'Registration form': [
''
],
'Register': [
''
],
'Date': [
''
],
'Amount': [
''
],
'Counterpart': [
''
],
'Subject': [
''
],
'Username or account label \'%1$s\' not found. Won\'t login.': [
''
],
'Wrong credentials given.': [
''
],
'Account information could not be retrieved.': [
''
],
'Close wire transfer': [
''
],
'Close Taler withdrawal': [
'Close Taler withdrawal'
],
'Bank account balance:': [
''
],
'Latest transactions:': [
''
],
'Transfer money manually': [
''
],
'List of public accounts was not found.': [
''
],
'List of public accounts could not be retrieved.': [
''
],
'History of public accounts': [
''
],
'Page has a problem: logged in but backend state is lost.': [
'Page has a problem: logged in but backend state is lost.'
],
'Welcome to the euFin bank!': [
'Welcome to euFin bank: Taler+IBAN now possible!'
],
'': {
'domain': 'messages',
'plural_forms': 'nplurals=2; plural=(n != 1);',
'lang': 'en'
}
}
}
}; };
strings['it'] = { strings["it"] = {
'domain': 'messages', domain: "messages",
'locale_data': { locale_data: {
'messages': { messages: {
'days': [ days: [""],
'' hours: [""],
minutes: [""],
seconds: [""],
Clear: ["Cancella"],
Logout: [""],
"Demo Bank": ["Banca 'demo'"],
"Go back": ["Indietro"],
"Wire transfer": ["Bonifico"],
"Transfer money to another account of this bank:": [
"Trasferisci fondi a un altro conto di questa banca:",
], ],
'hours': [ "Want to try the raw payto://-format?": [
'' "Prova il trasferimento tramite il formato Payto!",
], ],
'minutes': [ "Transfer money via the Payto system:": [
'' "Effettua un bonifico tramite il sistema Payto:",
], ],
'seconds': [ "payto address": ["indirizzo Payto"],
'' Confirm: ["Conferma"],
"Confirm Withdrawal": ["Conferma il ritiro"],
"Waiting the bank to create the operaion...": [
"La banca sta creando l'operazione...",
], ],
'Clear': [ "This withdrawal was aborted!": ["Questo ritiro è stato annullato!"],
'Cancella' "Withdraw to a Taler Wallet": ["Ritira contante nel portafoglio Taler"],
"You can use this QR code to withdraw to your mobile wallet:": [
"Usa questo codice QR per ritirare contante nel tuo wallet:",
], ],
'Logout': [ "this link": ["questo link"],
'' Abort: ["Annulla"],
"Start withdrawal": ["Ritira contante"],
"Withdraw Money into a Taler wallet": [
"Ritira contante nel portafoglio Taler",
], ],
'Demo Bank': [ "Amount to withdraw": ["Somma da ritirare"],
'Banca \'demo\'' "Please login!": ["Accedi!"],
Login: ["Accedi"],
"Register to the euFin bank!": ["Apri un conto in banca euFin!"],
"Registration form": ["Registrazione"],
Register: ["Registrati"],
Date: [""],
Amount: ["Somma"],
Counterpart: ["Controparte"],
Subject: ["Causale"],
"Username or account label '%1$s' not found. Won't login.": [
"L'utente '%1$s' non esiste. Login impossibile",
], ],
'Go back': [ "Wrong credentials given.": ["Credenziali invalide."],
'Indietro' "Account information could not be retrieved.": [
"Impossibile ricevere le informazioni relative al conto.",
], ],
'Wire transfer': [ "Close wire transfer": ["Chiudi il bonifico"],
'Bonifico' "Close Taler withdrawal": ["Chiudi il ritiro Taler"],
"Bank account balance:": ["Bilancio:"],
"Latest transactions:": ["Ultime transazioni:"],
"Transfer money manually": ["Effettua un bonifico"],
"List of public accounts was not found.": [
"Lista conti pubblici non trovata.",
], ],
'Transfer money to another account of this bank:': [ "List of public accounts could not be retrieved.": [
'Trasferisci fondi a un altro conto di questa banca:' "Lista conti pubblici non pervenuta.",
], ],
'Want to try the raw payto://-format?': [ "History of public accounts": ["Storico dei conti pubblici"],
'Prova il trasferimento tramite il formato Payto!' "Page has a problem: logged in but backend state is lost.": [
"Stato inconsistente: accesso utente effettuato ma stato con server perso.",
], ],
'Transfer money via the Payto system:': [ "Welcome to the euFin bank!": ["Benvenuti in banca euFin!"],
'Effettua un bonifico tramite il sistema Payto:' "": {
], domain: "messages",
'payto address': [ plural_forms: "nplurals=2; plural=(n != 1);",
'indirizzo Payto' lang: "it",
], },
'Confirm': [ },
'Conferma' },
],
'Confirm Withdrawal': [
'Conferma il ritiro'
],
'Waiting the bank to create the operaion...': [
'La banca sta creando l\'operazione...'
],
'This withdrawal was aborted!': [
'Questo ritiro è stato annullato!'
],
'Withdraw to a Taler Wallet': [
'Ritira contante nel portafoglio Taler'
],
'You can use this QR code to withdraw to your mobile wallet:': [
'Usa questo codice QR per ritirare contante nel tuo wallet:'
],
'this link': [
'questo link'
],
'Abort': [
'Annulla'
],
'Start withdrawal': [
'Ritira contante'
],
'Withdraw Money into a Taler wallet': [
'Ritira contante nel portafoglio Taler'
],
'Amount to withdraw': [
'Somma da ritirare'
],
'Please login!': [
'Accedi!'
],
'Login': [
'Accedi'
],
'Register to the euFin bank!': [
'Apri un conto in banca euFin!'
],
'Registration form': [
'Registrazione'
],
'Register': [
'Registrati'
],
'Date': [
''
],
'Amount': [
'Somma'
],
'Counterpart': [
'Controparte'
],
'Subject': [
'Causale'
],
'Username or account label \'%1$s\' not found. Won\'t login.': [
'L\'utente \'%1$s\' non esiste. Login impossibile'
],
'Wrong credentials given.': [
'Credenziali invalide.'
],
'Account information could not be retrieved.': [
'Impossibile ricevere le informazioni relative al conto.'
],
'Close wire transfer': [
'Chiudi il bonifico'
],
'Close Taler withdrawal': [
'Chiudi il ritiro Taler'
],
'Bank account balance:': [
'Bilancio:'
],
'Latest transactions:': [
'Ultime transazioni:'
],
'Transfer money manually': [
'Effettua un bonifico'
],
'List of public accounts was not found.': [
'Lista conti pubblici non trovata.'
],
'List of public accounts could not be retrieved.': [
'Lista conti pubblici non pervenuta.'
],
'History of public accounts': [
'Storico dei conti pubblici'
],
'Page has a problem: logged in but backend state is lost.': [
'Stato inconsistente: accesso utente effettuato ma stato con server perso.'
],
'Welcome to the euFin bank!': [
'Benvenuti in banca euFin!'
],
'': {
'domain': 'messages',
'plural_forms': 'nplurals=2; plural=(n != 1);',
'lang': 'it'
}
}
}
}; };

View File

@ -16,14 +16,20 @@
@author Sebastian Javier Marchano @author Sebastian Javier Marchano
--> -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"> <html
lang="en"
class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"
>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="icon" href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> <link
rel="icon"
href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
/>
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
</head> </head>

View File

@ -15,24 +15,22 @@
*/ */
/** /**
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h } from 'preact';
import Profile from './index';
import { h } from "preact";
import Profile from "./index";
export default { export default {
title: 'Profile/View', title: "Profile/View",
component: Profile, component: Profile,
argTypes: { argTypes: {
onSelect: { action: 'onSelect' }, onSelect: { action: "onSelect" },
}, },
}; };
export const Empty = (a: any) => <Profile {...a} />; export const Empty = (a: any) => <Profile {...a} />;
Empty.args = { Empty.args = {
instances: [] instances: [],
} };

View File

@ -135,9 +135,9 @@
text-align: center; text-align: center;
// there's probably a better way to do this, but wanted to try out CSS grid // there's probably a better way to do this, but wanted to try out CSS grid
grid-template-columns: calc(100% / 7) calc(100% / 7) calc(100% / 7) calc( grid-template-columns:
100% / 7 calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7)
) calc(100% / 7) calc(100% / 7) calc(100% / 7); calc(100% / 7) calc(100% / 7) calc(100% / 7);
span { span {
color: var(--secondary-text-color-dark); color: var(--secondary-text-color-dark);
@ -151,9 +151,9 @@
width: 100%; width: 100%;
display: grid; display: grid;
text-align: center; text-align: center;
grid-template-columns: calc(100% / 7) calc(100% / 7) calc(100% / 7) calc( grid-template-columns:
100% / 7 calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7)
) calc(100% / 7) calc(100% / 7) calc(100% / 7); calc(100% / 7) calc(100% / 7) calc(100% / 7);
span { span {
color: var(--primary-text-color-dark); color: var(--primary-text-color-dark);

View File

@ -1,5 +1,5 @@
.navcontainer:not(.default-navcontainer) { .navcontainer:not(.default-navcontainer) {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.abort-button { .abort-button {
@ -17,8 +17,8 @@ div.pages-list {
.login-div, .login-div,
.register-div { .register-div {
display: block; display: block;
text-align: center; text-align: center;
} }
a.page-number { a.page-number {
@ -42,55 +42,55 @@ input[type="number"]::-webkit-inner-spin-button {
/* This CSS code styles the tab */ /* This CSS code styles the tab */
.tab { .tab {
overflow: hidden; overflow: hidden;
} }
.logout { .logout {
float: right; float: right;
border: 20px; border: 20px;
margin-right: 15px; margin-right: 15px;
margin-top: 15px; margin-top: 15px;
} }
.tab button { .tab button {
background-color: lightgray; background-color: lightgray;
color: black; color: black;
float: left; float: left;
border: none; border: none;
outline: none; outline: none;
cursor: pointer; cursor: pointer;
padding: 18px 19px; padding: 18px 19px;
border: 2px solid #c1c1c1; border: 2px solid #c1c1c1;
transition: 0.5s; transition: 0.5s;
font-weight: bold; font-weight: bold;
} }
.tab button:hover { .tab button:hover {
background-color: yellow; background-color: yellow;
border: 2px solid #c1c1c1; border: 2px solid #c1c1c1;
color: black; color: black;
} }
.tab button.active { .tab button.active {
background-color: orange; background-color: orange;
border: 2px solid #c1c1c1; border: 2px solid #c1c1c1;
color: black; color: black;
font-weight: bold; font-weight: bold;
} }
.tabcontent { .tabcontent {
display: none; display: none;
padding: 8px 16px; padding: 8px 16px;
border: 2px solid #c1c1c1; border: 2px solid #c1c1c1;
width: max-content; width: max-content;
} }
.tabcontent.active { .tabcontent.active {
display: block; display: block;
} }
input[type="number"] { input[type="number"] {
-moz-appearance: textfield; -moz-appearance: textfield;
} }
#transfer-fields { #transfer-fields {
@ -115,16 +115,16 @@ input[type="number"] {
} }
input { input {
background-color: inherit; background-color: inherit;
} }
.large-amount { .large-amount {
font-weight: bold; font-weight: bold;
font-size: x-large; font-size: x-large;
} }
.currency { .currency {
font-style: oblique; font-style: oblique;
} }
/* /*
@ -154,111 +154,108 @@ input {
.register-form > .pure-form, .register-form > .pure-form,
.login-form > .pure-form { .login-form > .pure-form {
background: #4a4a4a; background: #4a4a4a;
color: #ffffff; color: #ffffff;
display: inline-block; display: inline-block;
text-align: left; text-align: left;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding: 16px 16px; padding: 16px 16px;
border-radius: 8px; border-radius: 8px;
width: max-content; width: max-content;
.formFieldLabel { .formFieldLabel {
margin: 2px 2px; margin: 2px 2px;
} }
input[type="text"], input[type="text"],
input[type="password"] { input[type="password"] {
border: none; border: none;
border-radius: 4px; border-radius: 4px;
background: #6a6a6a; background: #6a6a6a;
color: #fefefe; color: #fefefe;
box-shadow: none; box-shadow: none;
} }
input[placeholder="Password"][type="password"] { input[placeholder="Password"][type="password"] {
margin-bottom: 8px; margin-bottom: 8px;
} }
.btn-register, .btn-register,
.btn-login { .btn-login {
float: left; float: left;
} }
.btn-cancel { .btn-cancel {
float: right; float: right;
} }
h2 { h2 {
margin-top: 0; margin-top: 0;
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
.challenge-div { .challenge-div {
display: block; display: block;
text-align: center; text-align: center;
} }
.challenge-form > .pure-form { .challenge-form > .pure-form {
background: #4a4a4a; background: #4a4a4a;
color: #ffffff; color: #ffffff;
display: inline-block; display: inline-block;
text-align: left; text-align: left;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding: 16px 16px; padding: 16px 16px;
border-radius: 8px; border-radius: 8px;
width: max-content; width: max-content;
.formFieldLabel { .formFieldLabel {
margin: 2px 2px; margin: 2px 2px;
} }
input[type="text"] { input[type="text"] {
border: none; border: none;
border-radius: 4px; border-radius: 4px;
background: #6a6a6a; background: #6a6a6a;
color: #fefefe; color: #fefefe;
box-shadow: none; box-shadow: none;
} }
.btn-confirm { .btn-confirm {
float: left; float: left;
} }
.btn-cancel { .btn-cancel {
float: right; float: right;
} }
h2 { h2 {
margin-top: 0; margin-top: 0;
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
.wire-transfer-form > .pure-form, .wire-transfer-form > .pure-form,
.payto-form > .pure-form, .payto-form > .pure-form,
.reserve-form > .pure-form { .reserve-form > .pure-form {
background: #4a4a4a; background: #4a4a4a;
color: #ffffff; color: #ffffff;
display: inline-block; display: inline-block;
text-align: left; text-align: left;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding: 16px 16px; padding: 16px 16px;
border-radius: 8px; border-radius: 8px;
width: max-content; width: max-content;
.formFieldLabel { .formFieldLabel {
margin: 2px 2px; margin: 2px 2px;
} }
input[type="text"] { input[type="text"] {
border: none; border: none;
border-radius: 4px; border-radius: 4px;
background: #6a6a6a; background: #6a6a6a;
color: #fefefe; color: #fefefe;
box-shadow: none; box-shadow: none;
} }
} }
html { html {
background: #ffffff; background: #ffffff;
color: #2a2a2a; color: #2a2a2a;
} }
.hint { .hint {
scale: 0.7; scale: 0.7;
} }

View File

@ -15,8 +15,8 @@
*/ */
@font-face { @font-face {
font-family: 'Nunito'; font-family: "Nunito";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url(./XRXV3I6Li01BKofINeaE.ttf) format('truetype'); src: url(./XRXV3I6Li01BKofINeaE.ttf) format("truetype");
} }

View File

@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
/** /**
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */

File diff suppressed because it is too large Load Diff