using web-utils in demobank
This commit is contained in:
parent
d3a6544bc5
commit
5969a44391
@ -18,9 +18,9 @@
|
|||||||
import esbuild from "esbuild";
|
import esbuild from "esbuild";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import crypto from "crypto";
|
import sass from "sass";
|
||||||
import { sassPlugin } from "esbuild-sass-plugin";
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
const BASE = process.cwd();
|
const BASE = process.cwd();
|
||||||
|
|
||||||
const preact = path.join(
|
const preact = path.join(
|
||||||
@ -44,14 +44,16 @@ const preactCompatPlugin = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const entryPoints = ["src/index.tsx"];
|
const entryPoints = ["src/index.tsx", "src/stories.tsx"];
|
||||||
|
|
||||||
let GIT_ROOT = BASE;
|
let GIT_ROOT = BASE;
|
||||||
while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") {
|
while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") {
|
||||||
GIT_ROOT = path.join(GIT_ROOT, "../");
|
GIT_ROOT = path.join(GIT_ROOT, "../");
|
||||||
}
|
}
|
||||||
if (GIT_ROOT === "/") {
|
if (GIT_ROOT === "/") {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
console.log("not found");
|
console.log("not found");
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const GIT_HASH = GIT_ROOT === "/" ? undefined : git_hash();
|
const GIT_HASH = GIT_ROOT === "/" ? undefined : git_hash();
|
||||||
@ -86,6 +88,26 @@ function copyFilesPlugin(options) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_SASS_FILTER = /\.(s[ac]ss|css)$/
|
||||||
|
|
||||||
|
const buildSassPlugin = {
|
||||||
|
name: "custom-build-sass",
|
||||||
|
setup(build) {
|
||||||
|
|
||||||
|
build.onLoad({ filter: DEFAULT_SASS_FILTER }, ({ path: file }) => {
|
||||||
|
const resolveDir = path.dirname(file)
|
||||||
|
const { css: contents } = sass.compile(file, { loadPaths: ["./"] })
|
||||||
|
|
||||||
|
return {
|
||||||
|
resolveDir,
|
||||||
|
loader: 'css',
|
||||||
|
contents
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const buildConfig = {
|
export const buildConfig = {
|
||||||
entryPoints: [...entryPoints],
|
entryPoints: [...entryPoints],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
@ -95,6 +117,10 @@ export const buildConfig = {
|
|||||||
".svg": "file",
|
".svg": "file",
|
||||||
".png": "dataurl",
|
".png": "dataurl",
|
||||||
".jpeg": "dataurl",
|
".jpeg": "dataurl",
|
||||||
|
'.ttf': 'file',
|
||||||
|
'.woff': 'file',
|
||||||
|
'.woff2': 'file',
|
||||||
|
'.eot': 'file',
|
||||||
},
|
},
|
||||||
target: ["es6"],
|
target: ["es6"],
|
||||||
format: "esm",
|
format: "esm",
|
||||||
@ -108,17 +134,14 @@ export const buildConfig = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
preactCompatPlugin,
|
preactCompatPlugin,
|
||||||
sassPlugin(),
|
|
||||||
copyFilesPlugin([
|
copyFilesPlugin([
|
||||||
{
|
{
|
||||||
src: "static/index.html",
|
src: "./src/index.html",
|
||||||
dest: "dist/index.html",
|
dest: "./dist/index.html",
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
|
buildSassPlugin
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
esbuild.build(buildConfig).catch((e) => {
|
await esbuild.build(buildConfig)
|
||||||
console.log(e);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
30
packages/demobank-ui/dev.mjs
Executable file
30
packages/demobank-ui/dev.mjs
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/*
|
||||||
|
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 { serve } from "@gnu-taler/web-util/lib/index.node";
|
||||||
|
import esbuild from "esbuild";
|
||||||
|
import { buildConfig } from "./build.mjs";
|
||||||
|
|
||||||
|
buildConfig.inject = ['./node_modules/@gnu-taler/web-util/lib/live-reload.mjs']
|
||||||
|
|
||||||
|
serve({
|
||||||
|
folder: './dist',
|
||||||
|
port: 8080,
|
||||||
|
source: './src',
|
||||||
|
development: true,
|
||||||
|
onUpdate: async () => esbuild.build(buildConfig)
|
||||||
|
})
|
@ -11,13 +11,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gnu-taler/taler-util": "workspace:*",
|
"@gnu-taler/taler-util": "workspace:*",
|
||||||
|
"@gnu-taler/web-util": "workspace:*",
|
||||||
"date-fns": "2.29.3",
|
"date-fns": "2.29.3",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"jed": "1.1.1",
|
"jed": "1.1.1",
|
||||||
"preact": "10.6.5",
|
"preact": "10.11.3",
|
||||||
"preact-router": "3.2.1",
|
"preact-router": "3.2.1",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"react": "npm:@preact/compat@^17.1.2",
|
|
||||||
"swr": "1.3.0"
|
"swr": "1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -30,11 +30,10 @@
|
|||||||
"bulma-checkbox": "^1.1.1",
|
"bulma-checkbox": "^1.1.1",
|
||||||
"bulma-radio": "^1.1.1",
|
"bulma-radio": "^1.1.1",
|
||||||
"esbuild": "^0.15.12",
|
"esbuild": "^0.15.12",
|
||||||
"esbuild-sass-plugin": "^2.4.0",
|
|
||||||
"eslint": "^8.26.0",
|
"eslint": "^8.26.0",
|
||||||
"eslint-config-preact": "^1.2.0",
|
"eslint-config-preact": "^1.2.0",
|
||||||
"po2json": "^0.4.5",
|
"po2json": "^0.4.5",
|
||||||
"sass": "1.32.13",
|
"sass": "1.56.1",
|
||||||
"typescript": "^4.4.4"
|
"typescript": "^4.4.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { Translate } from "../../i18n";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mobile?: boolean;
|
mobile?: boolean;
|
||||||
@ -31,6 +31,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
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" } };
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside class="aside is-placed-left is-expanded">
|
<aside class="aside is-placed-left is-expanded">
|
||||||
@ -49,20 +50,20 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
</div>
|
</div>
|
||||||
<div class="menu is-menu-main">
|
<div class="menu is-menu-main">
|
||||||
<p class="menu-label">
|
<p class="menu-label">
|
||||||
<Translate>Bank menu</Translate>
|
<i18n.Translate>Bank menu</i18n.Translate>
|
||||||
</p>
|
</p>
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li>
|
<li>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Select option1</Translate>
|
<i18n.Translate>Select option1</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Select option2</Translate>
|
<i18n.Translate>Select option2</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
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 { useTranslationContext } from "../../context/translation.js";
|
||||||
import "../../scss/DurationPicker.scss";
|
import "../../scss/DurationPicker.scss";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -46,13 +46,13 @@ export function DurationPicker({
|
|||||||
const ms = ss * 60;
|
const ms = ss * 60;
|
||||||
const hs = ms * 60;
|
const hs = ms * 60;
|
||||||
const ds = hs * 24;
|
const ds = hs * 24;
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="rdp-picker">
|
<div class="rdp-picker">
|
||||||
{days && (
|
{days && (
|
||||||
<DurationColumn
|
<DurationColumn
|
||||||
unit={i18n`days`}
|
unit={i18n.str`days`}
|
||||||
max={99}
|
max={99}
|
||||||
value={Math.floor(value / ds)}
|
value={Math.floor(value / ds)}
|
||||||
onDecrease={value >= ds ? () => onChange(value - ds) : undefined}
|
onDecrease={value >= ds ? () => onChange(value - ds) : undefined}
|
||||||
@ -62,7 +62,7 @@ export function DurationPicker({
|
|||||||
)}
|
)}
|
||||||
{hours && (
|
{hours && (
|
||||||
<DurationColumn
|
<DurationColumn
|
||||||
unit={i18n`hours`}
|
unit={i18n.str`hours`}
|
||||||
max={23}
|
max={23}
|
||||||
min={1}
|
min={1}
|
||||||
value={Math.floor(value / hs) % 24}
|
value={Math.floor(value / hs) % 24}
|
||||||
@ -73,7 +73,7 @@ export function DurationPicker({
|
|||||||
)}
|
)}
|
||||||
{minutes && (
|
{minutes && (
|
||||||
<DurationColumn
|
<DurationColumn
|
||||||
unit={i18n`minutes`}
|
unit={i18n.str`minutes`}
|
||||||
max={59}
|
max={59}
|
||||||
min={1}
|
min={1}
|
||||||
value={Math.floor(value / ms) % 60}
|
value={Math.floor(value / ms) % 60}
|
||||||
@ -84,7 +84,7 @@ export function DurationPicker({
|
|||||||
)}
|
)}
|
||||||
{seconds && (
|
{seconds && (
|
||||||
<DurationColumn
|
<DurationColumn
|
||||||
unit={i18n`seconds`}
|
unit={i18n.str`seconds`}
|
||||||
max={59}
|
max={59}
|
||||||
value={Math.floor(value / ss) % 60}
|
value={Math.floor(value / ss) % 60}
|
||||||
onDecrease={value >= ss ? () => onChange(value - ss) : undefined}
|
onDecrease={value >= ss ? () => onChange(value - ss) : undefined}
|
||||||
|
@ -19,27 +19,42 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { i18n, setupI18n } from "@gnu-taler/taler-util";
|
||||||
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/useLang.js";
|
||||||
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;
|
||||||
handler: any;
|
supportedLang: { [id in keyof typeof supportedLang]: string };
|
||||||
changeLanguage: (l: string) => void;
|
changeLanguage: (l: string) => void;
|
||||||
|
i18n: typeof i18n;
|
||||||
|
isSaved: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const supportedLang = {
|
||||||
|
es: "Español [es]",
|
||||||
|
ja: "日本語 [ja]",
|
||||||
|
en: "English [en]",
|
||||||
|
fr: "Français [fr]",
|
||||||
|
de: "Deutsch [de]",
|
||||||
|
sv: "Svenska [sv]",
|
||||||
|
it: "Italiano [it]",
|
||||||
|
// ko: "한국어 [ko]",
|
||||||
|
// ru: "Ру́сский язы́к [ru]",
|
||||||
|
tr: "Türk [tr]",
|
||||||
|
navigator: "Defined by navigator",
|
||||||
|
};
|
||||||
|
|
||||||
const initial = {
|
const initial = {
|
||||||
lang: "en",
|
lang: "en",
|
||||||
handler: null,
|
supportedLang,
|
||||||
changeLanguage: () => {
|
changeLanguage: () => {
|
||||||
/**
|
// do not change anything
|
||||||
* This function will be replaced by one with
|
|
||||||
* the same signature _but_ coming from the state.
|
|
||||||
* FIXME: clarify this design.
|
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
|
i18n,
|
||||||
|
isSaved: false,
|
||||||
};
|
};
|
||||||
const Context = createContext<Type>(initial);
|
const Context = createContext<Type>(initial);
|
||||||
|
|
||||||
@ -55,14 +70,23 @@ export const TranslationProvider = ({
|
|||||||
children,
|
children,
|
||||||
forceLang,
|
forceLang,
|
||||||
}: Props): VNode => {
|
}: Props): VNode => {
|
||||||
const [lang, changeLanguage] = useLang(initial);
|
const [lang, changeLanguage, isSaved] = useLang(initial);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (forceLang) changeLanguage(forceLang);
|
if (forceLang) {
|
||||||
|
changeLanguage(forceLang);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
console.log("lang store", strings);
|
useEffect(() => {
|
||||||
const handler = new jedLib.Jed(strings[lang] || strings["en"]);
|
setupI18n(lang, strings);
|
||||||
|
}, [lang]);
|
||||||
|
if (forceLang) {
|
||||||
|
setupI18n(forceLang, strings);
|
||||||
|
} else {
|
||||||
|
setupI18n(lang, strings);
|
||||||
|
}
|
||||||
|
|
||||||
return h(Context.Provider, {
|
return h(Context.Provider, {
|
||||||
value: { lang, handler, changeLanguage },
|
value: { lang, changeLanguage, supportedLang, i18n, isSaved },
|
||||||
children,
|
children,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { StateUpdater, useState } from "preact/hooks";
|
import { StateUpdater } from "preact/hooks";
|
||||||
|
import { useLocalStorage, useNotNullLocalStorage } from "./useLocalStorage.js";
|
||||||
export type ValueOrFunction<T> = T | ((p: T) => T);
|
export type ValueOrFunction<T> = T | ((p: T) => T);
|
||||||
|
|
||||||
const calculateRootPath = () => {
|
const calculateRootPath = () => {
|
||||||
@ -68,79 +69,3 @@ export function useBackendInstanceToken(
|
|||||||
|
|
||||||
return [token, setToken];
|
return [token, setToken];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLang(initial?: string): [string, StateUpdater<string>] {
|
|
||||||
const browserLang =
|
|
||||||
typeof window !== "undefined"
|
|
||||||
? navigator.language || (navigator as any).userLanguage
|
|
||||||
: undefined;
|
|
||||||
const defaultLang = (browserLang || initial || "en").substring(0, 2);
|
|
||||||
const [value, setValue] = useNotNullLocalStorage(
|
|
||||||
"lang-preference",
|
|
||||||
defaultLang,
|
|
||||||
);
|
|
||||||
function updateValue(newValue: string | ((v: string) => string)) {
|
|
||||||
if (document.body.parentElement) {
|
|
||||||
const htmlElement = document.body.parentElement;
|
|
||||||
if (typeof newValue === "string") {
|
|
||||||
htmlElement.lang = newValue;
|
|
||||||
setValue(newValue);
|
|
||||||
} else if (typeof newValue === "function")
|
|
||||||
setValue((old) => {
|
|
||||||
const nv = newValue(old);
|
|
||||||
htmlElement.lang = nv;
|
|
||||||
return nv;
|
|
||||||
});
|
|
||||||
} else setValue(newValue);
|
|
||||||
}
|
|
||||||
return [value, updateValue];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useLocalStorage(
|
|
||||||
key: string,
|
|
||||||
initialValue?: string,
|
|
||||||
): [string | undefined, StateUpdater<string | undefined>] {
|
|
||||||
const [storedValue, setStoredValue] = useState<string | undefined>(
|
|
||||||
(): string | undefined => {
|
|
||||||
return typeof window !== "undefined"
|
|
||||||
? window.localStorage.getItem(key) || initialValue
|
|
||||||
: initialValue;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const setValue = (
|
|
||||||
value?: string | ((val?: string) => string | undefined),
|
|
||||||
) => {
|
|
||||||
setStoredValue((p) => {
|
|
||||||
const toStore = value instanceof Function ? value(p) : value;
|
|
||||||
if (typeof window !== "undefined")
|
|
||||||
if (!toStore) window.localStorage.removeItem(key);
|
|
||||||
else window.localStorage.setItem(key, toStore);
|
|
||||||
|
|
||||||
return toStore;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return [storedValue, setValue];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useNotNullLocalStorage(
|
|
||||||
key: string,
|
|
||||||
initialValue: string,
|
|
||||||
): [string, StateUpdater<string>] {
|
|
||||||
const [storedValue, setStoredValue] = useState<string>((): string => {
|
|
||||||
return typeof window !== "undefined"
|
|
||||||
? window.localStorage.getItem(key) || initialValue
|
|
||||||
: initialValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
const setValue = (value: string | ((val: string) => string)) => {
|
|
||||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
||||||
setStoredValue(valueToStore);
|
|
||||||
if (typeof window !== "undefined")
|
|
||||||
if (!valueToStore) window.localStorage.removeItem(key);
|
|
||||||
else window.localStorage.setItem(key, valueToStore);
|
|
||||||
};
|
|
||||||
|
|
||||||
return [storedValue, setValue];
|
|
||||||
}
|
|
||||||
|
30
packages/demobank-ui/src/hooks/useLang.ts
Normal file
30
packages/demobank-ui/src/hooks/useLang.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Anastasis
|
||||||
|
(C) 2021-2022 Anastasis SARL
|
||||||
|
|
||||||
|
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU Affero General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Anastasis 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License along with
|
||||||
|
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useNotNullLocalStorage } from "./useLocalStorage.js";
|
||||||
|
|
||||||
|
function getBrowserLang(): string | undefined {
|
||||||
|
if (window.navigator.languages) return window.navigator.languages[0];
|
||||||
|
if (window.navigator.language) return window.navigator.language;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLang(
|
||||||
|
initial?: string,
|
||||||
|
): [string, (s: string) => void, boolean] {
|
||||||
|
const defaultLang = (getBrowserLang() || initial || "en").substring(0, 2);
|
||||||
|
return useNotNullLocalStorage("lang-preference", defaultLang);
|
||||||
|
}
|
80
packages/demobank-ui/src/hooks/useLocalStorage.ts
Normal file
80
packages/demobank-ui/src/hooks/useLocalStorage.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Anastasis
|
||||||
|
(C) 2021-2022 Anastasis SARL
|
||||||
|
|
||||||
|
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU Affero General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Anastasis 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License along with
|
||||||
|
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { StateUpdater, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
export function useLocalStorage(
|
||||||
|
key: string,
|
||||||
|
initialValue?: string,
|
||||||
|
): [string | undefined, StateUpdater<string | undefined>] {
|
||||||
|
const [storedValue, setStoredValue] = useState<string | undefined>(
|
||||||
|
(): string | undefined => {
|
||||||
|
return typeof window !== "undefined"
|
||||||
|
? window.localStorage.getItem(key) || initialValue
|
||||||
|
: initialValue;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const setValue = (
|
||||||
|
value?: string | ((val?: string) => string | undefined),
|
||||||
|
): void => {
|
||||||
|
setStoredValue((p) => {
|
||||||
|
const toStore = value instanceof Function ? value(p) : value;
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
if (!toStore) {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
} else {
|
||||||
|
window.localStorage.setItem(key, toStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toStore;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return [storedValue, setValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: merge with the above function
|
||||||
|
export function useNotNullLocalStorage(
|
||||||
|
key: string,
|
||||||
|
initialValue: string,
|
||||||
|
): [string, StateUpdater<string>, boolean] {
|
||||||
|
const [storedValue, setStoredValue] = useState<string>((): string => {
|
||||||
|
return typeof window !== "undefined"
|
||||||
|
? window.localStorage.getItem(key) || initialValue
|
||||||
|
: initialValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
const setValue = (value: string | ((val: string) => string)): void => {
|
||||||
|
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||||
|
setStoredValue(valueToStore);
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
if (!valueToStore) {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
} else {
|
||||||
|
window.localStorage.setItem(key, valueToStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSaved = window.localStorage.getItem(key) !== null;
|
||||||
|
return [storedValue, setValue, isSaved];
|
||||||
|
}
|
@ -1,201 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of GNU Taler
|
|
||||||
(C) 2021 Taler Systems S.A.
|
|
||||||
|
|
||||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
|
||||||
terms of the GNU General Public License as published by the Free Software
|
|
||||||
Foundation; either version 3, or (at your option) any later version.
|
|
||||||
|
|
||||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with
|
|
||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translation helpers for React components and template literals.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imports
|
|
||||||
*/
|
|
||||||
import { ComponentChild, ComponentChildren, h, Fragment, VNode } from "preact";
|
|
||||||
|
|
||||||
import { useTranslationContext } from "../context/translation";
|
|
||||||
|
|
||||||
export function useTranslator() {
|
|
||||||
const ctx = useTranslationContext();
|
|
||||||
const jed = ctx.handler;
|
|
||||||
return function str(
|
|
||||||
stringSeq: TemplateStringsArray,
|
|
||||||
...values: any[]
|
|
||||||
): string {
|
|
||||||
const s = toI18nString(stringSeq);
|
|
||||||
if (!s) return s;
|
|
||||||
const tr = jed
|
|
||||||
.translate(s)
|
|
||||||
.ifPlural(1, s)
|
|
||||||
.fetch(...values);
|
|
||||||
return tr;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert template strings to a msgid
|
|
||||||
*/
|
|
||||||
function toI18nString(stringSeq: ReadonlyArray<string>): string {
|
|
||||||
let s = "";
|
|
||||||
for (let i = 0; i < stringSeq.length; i++) {
|
|
||||||
s += stringSeq[i];
|
|
||||||
if (i < stringSeq.length - 1) s += `%${i + 1}$s`;
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TranslateSwitchProps {
|
|
||||||
target: number;
|
|
||||||
children: ComponentChildren;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringifyChildren(children: ComponentChildren): string {
|
|
||||||
let n = 1;
|
|
||||||
const ss = (children instanceof Array ? children : [children]).map((c) => {
|
|
||||||
if (typeof c === "string") return c;
|
|
||||||
|
|
||||||
return `%${n++}$s`;
|
|
||||||
});
|
|
||||||
const s = ss.join("").replace(/ +/g, " ").trim();
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TranslateProps {
|
|
||||||
children: ComponentChildren;
|
|
||||||
/**
|
|
||||||
* Component that the translated element should be wrapped in.
|
|
||||||
* Defaults to "div".
|
|
||||||
*/
|
|
||||||
wrap?: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Props to give to the wrapped component.
|
|
||||||
*/
|
|
||||||
wrapProps?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTranslatedChildren(
|
|
||||||
translation: string,
|
|
||||||
children: ComponentChildren,
|
|
||||||
): ComponentChild[] {
|
|
||||||
const tr = translation.split(/%(\d+)\$s/);
|
|
||||||
const childArray = children instanceof Array ? children : [children];
|
|
||||||
// Merge consecutive string children.
|
|
||||||
const placeholderChildren = Array<ComponentChild>();
|
|
||||||
for (let i = 0; i < childArray.length; i++) {
|
|
||||||
const x = childArray[i];
|
|
||||||
if (x === undefined) continue;
|
|
||||||
else if (typeof x === "string") continue;
|
|
||||||
else placeholderChildren.push(x);
|
|
||||||
}
|
|
||||||
const result = Array<ComponentChild>();
|
|
||||||
for (let i = 0; i < tr.length; i++)
|
|
||||||
if (i % 2 == 0)
|
|
||||||
// Text
|
|
||||||
result.push(tr[i]);
|
|
||||||
else {
|
|
||||||
const childIdx = Number.parseInt(tr[i], 10) - 1;
|
|
||||||
result.push(placeholderChildren[childIdx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translate text node children of this component.
|
|
||||||
* If a child component might produce a text node, it must be wrapped
|
|
||||||
* in a another non-text element.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* ```
|
|
||||||
* <Translate>
|
|
||||||
* Hello. Your score is <span><PlayerScore player={player} /></span>
|
|
||||||
* </Translate>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function Translate({ children }: TranslateProps): VNode {
|
|
||||||
const s = stringifyChildren(children);
|
|
||||||
const ctx = useTranslationContext();
|
|
||||||
const translation: string = ctx.handler.ngettext(s, s, 1);
|
|
||||||
const result = getTranslatedChildren(translation, children);
|
|
||||||
return <Fragment>{result}</Fragment>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch translation based on singular or plural based on the target prop.
|
|
||||||
* Should only contain TranslateSingular and TransplatePlural as children.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* ```
|
|
||||||
* <TranslateSwitch target={n}>
|
|
||||||
* <TranslateSingular>I have {n} apple.</TranslateSingular>
|
|
||||||
* <TranslatePlural>I have {n} apples.</TranslatePlural>
|
|
||||||
* </TranslateSwitch>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function TranslateSwitch({ children, target }: TranslateSwitchProps) {
|
|
||||||
let singular: VNode<TranslationPluralProps> | undefined;
|
|
||||||
let plural: VNode<TranslationPluralProps> | undefined;
|
|
||||||
// const children = this.props.children;
|
|
||||||
if (children)
|
|
||||||
(children instanceof Array ? children : [children]).forEach(
|
|
||||||
(child: any) => {
|
|
||||||
if (child.type === TranslatePlural) plural = child;
|
|
||||||
|
|
||||||
if (child.type === TranslateSingular) singular = child;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!singular || !plural) {
|
|
||||||
console.error("translation not found");
|
|
||||||
return h("span", {}, ["translation not found"]);
|
|
||||||
}
|
|
||||||
singular.props.target = target;
|
|
||||||
plural.props.target = target;
|
|
||||||
// We're looking up the translation based on the
|
|
||||||
// singular, even if we must use the plural form.
|
|
||||||
return singular;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TranslationPluralProps {
|
|
||||||
children: ComponentChildren;
|
|
||||||
target: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See [[TranslateSwitch]].
|
|
||||||
*/
|
|
||||||
export function TranslatePlural({
|
|
||||||
children,
|
|
||||||
target,
|
|
||||||
}: TranslationPluralProps): VNode {
|
|
||||||
const s = stringifyChildren(children);
|
|
||||||
const ctx = useTranslationContext();
|
|
||||||
const translation = ctx.handler.ngettext(s, s, 1);
|
|
||||||
const result = getTranslatedChildren(translation, children);
|
|
||||||
return <Fragment>{result}</Fragment>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See [[TranslateSwitch]].
|
|
||||||
*/
|
|
||||||
export function TranslateSingular({
|
|
||||||
children,
|
|
||||||
target,
|
|
||||||
}: TranslationPluralProps): VNode {
|
|
||||||
const s = stringifyChildren(children);
|
|
||||||
const ctx = useTranslationContext();
|
|
||||||
const translation = ctx.handler.ngettext(s, s, target);
|
|
||||||
const result = getTranslatedChildren(translation, children);
|
|
||||||
return <Fragment>{result}</Fragment>;
|
|
||||||
}
|
|
@ -16,25 +16,26 @@
|
|||||||
@author Sebastian Javier Marchano
|
@author Sebastian Javier Marchano
|
||||||
-->
|
-->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html
|
<html lang="en">
|
||||||
lang="en"
|
|
||||||
class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"
|
|
||||||
>
|
|
||||||
<head>
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
<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
|
<link
|
||||||
rel="icon"
|
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=="
|
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" />
|
||||||
|
<title>Demobank</title>
|
||||||
|
<!-- Optional customization script. -->
|
||||||
|
<script src="demobank-ui-settings.js"></script>
|
||||||
|
<!-- Entry point for the demobank SPA. -->
|
||||||
|
<script type="module" src="index.js"></script>
|
||||||
|
<link rel="stylesheet" href="index.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="index.tsx"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import App from "./components/app.js";
|
import App from "./components/app.js";
|
||||||
export default App;
|
export default App;
|
||||||
import { render, h } from "preact";
|
import { render, h } from "preact";
|
||||||
|
import "./scss/main.scss";
|
||||||
|
|
||||||
const app = document.getElementById("app");
|
const app = document.getElementById("app");
|
||||||
|
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
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 { QrCodeSection } from "./QrCodeSection.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Qr Code Selection",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SimpleExample = {
|
||||||
|
component: QrCodeSection,
|
||||||
|
props: {
|
||||||
|
talerWithdrawUri: "taler://withdraw/asdasdasd",
|
||||||
|
},
|
||||||
|
};
|
55
packages/demobank-ui/src/pages/home/QrCodeSection.tsx
Normal file
55
packages/demobank-ui/src/pages/home/QrCodeSection.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
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 { useEffect } from "preact/hooks";
|
||||||
|
import { QR } from "../../components/QR.js";
|
||||||
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
|
|
||||||
|
export function QrCodeSection({
|
||||||
|
talerWithdrawUri,
|
||||||
|
abortButton,
|
||||||
|
}: {
|
||||||
|
talerWithdrawUri: string;
|
||||||
|
abortButton: h.JSX.Element;
|
||||||
|
}): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
useEffect(() => {
|
||||||
|
//Taler Wallet WebExtension is listening to headers response and tab updates.
|
||||||
|
//In the SPA there is no header response with the Taler URI so
|
||||||
|
//this hack manually triggers the tab update after the QR is in the DOM.
|
||||||
|
window.location.hash = `/account/${new Date().getTime()}`;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="main" class="content">
|
||||||
|
<h1 class="nav">{i18n.str`Transfer to Taler Wallet`}</h1>
|
||||||
|
<article>
|
||||||
|
<div class="qr-div">
|
||||||
|
<p>{i18n.str`Use this QR code to withdraw to your mobile wallet:`}</p>
|
||||||
|
{QR({ text: talerWithdrawUri })}
|
||||||
|
<p>
|
||||||
|
Click{" "}
|
||||||
|
<a id="linkqr" href={talerWithdrawUri}>{i18n.str`this link`}</a> to
|
||||||
|
open your Taler wallet!
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
{abortButton}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
1
packages/demobank-ui/src/pages/home/index.stories.tsx
Normal file
1
packages/demobank-ui/src/pages/home/index.stories.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * as qr from "./QrCodeSection.stories.js";
|
@ -27,13 +27,16 @@ import {
|
|||||||
} from "preact/hooks";
|
} from "preact/hooks";
|
||||||
import talerLogo from "../../assets/logo-white.svg";
|
import talerLogo from "../../assets/logo-white.svg";
|
||||||
import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
|
import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
|
||||||
import { QR } from "../../components/QR.js";
|
import {
|
||||||
import { useLocalStorage, useNotNullLocalStorage } from "../../hooks/index.js";
|
useLocalStorage,
|
||||||
import { Translate, useTranslator } from "../../i18n/index.js";
|
useNotNullLocalStorage,
|
||||||
import "../../scss/main.scss";
|
} from "../../hooks/useLocalStorage.js";
|
||||||
|
// import { Translate, useTranslator } from "../../i18n/index.js";
|
||||||
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
|
import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
|
||||||
import { createHashHistory } from "history";
|
import { createHashHistory } from "history";
|
||||||
import Router, { Route, route } from "preact-router";
|
import Router, { Route, route } from "preact-router";
|
||||||
|
import { QrCodeSection } from "./QrCodeSection.js";
|
||||||
|
|
||||||
interface BankUiSettings {
|
interface BankUiSettings {
|
||||||
allowRegistrations: boolean;
|
allowRegistrations: boolean;
|
||||||
@ -987,7 +990,7 @@ async function registrationCall(
|
|||||||
|
|
||||||
function ErrorBanner(Props: any): VNode | null {
|
function ErrorBanner(Props: any): VNode | null {
|
||||||
const [pageState, pageStateSetter] = Props.pageState;
|
const [pageState, pageStateSetter] = Props.pageState;
|
||||||
// const i18n = useTranslator();
|
// const { i18n } = useTranslationContext();
|
||||||
if (!pageState.error) return null;
|
if (!pageState.error) return null;
|
||||||
|
|
||||||
const rval = (
|
const rval = (
|
||||||
@ -1041,7 +1044,7 @@ function StatusBanner(Props: any): VNode | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BankFrame(Props: any): VNode {
|
function BankFrame(Props: any): VNode {
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
const [pageState, pageStateSetter] = useContext(PageContext);
|
const [pageState, pageStateSetter] = useContext(PageContext);
|
||||||
console.log("BankFrame state", pageState);
|
console.log("BankFrame state", pageState);
|
||||||
const logOut = (
|
const logOut = (
|
||||||
@ -1062,7 +1065,7 @@ function BankFrame(Props: any): VNode {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>{i18n`Logout`}</a>
|
>{i18n.str`Logout`}</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1080,7 +1083,7 @@ function BankFrame(Props: any): VNode {
|
|||||||
class="demobar"
|
class="demobar"
|
||||||
style="display: flex; flex-direction: row; justify-content: space-between;"
|
style="display: flex; flex-direction: row; justify-content: space-between;"
|
||||||
>
|
>
|
||||||
<a href="#main" class="skip">{i18n`Skip to main content`}</a>
|
<a href="#main" class="skip">{i18n.str`Skip to main content`}</a>
|
||||||
<div style="max-width: 50em; margin-left: 2em;">
|
<div style="max-width: 50em; margin-left: 2em;">
|
||||||
<h1>
|
<h1>
|
||||||
<span class="it">
|
<span class="it">
|
||||||
@ -1089,7 +1092,7 @@ function BankFrame(Props: any): VNode {
|
|||||||
</h1>
|
</h1>
|
||||||
{maybeDemoContent(
|
{maybeDemoContent(
|
||||||
<p>
|
<p>
|
||||||
<Translate>
|
<i18n.Translate>
|
||||||
This part of the demo shows how a bank that supports Taler
|
This part of the demo shows how a bank that supports Taler
|
||||||
directly would work. In addition to using your own bank account,
|
directly would work. In addition to using your own bank account,
|
||||||
you can also see the transaction history of some{" "}
|
you can also see the transaction history of some{" "}
|
||||||
@ -1100,14 +1103,14 @@ function BankFrame(Props: any): VNode {
|
|||||||
Public Accounts
|
Public Accounts
|
||||||
</a>
|
</a>
|
||||||
.
|
.
|
||||||
</Translate>
|
</i18n.Translate>
|
||||||
</p>,
|
</p>,
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<a href="https://taler.net/">
|
<a href="https://taler.net/">
|
||||||
<img
|
<img
|
||||||
src={talerLogo}
|
src={talerLogo}
|
||||||
alt={i18n`Taler logo`}
|
alt={i18n.str`Taler logo`}
|
||||||
height="100"
|
height="100"
|
||||||
width="224"
|
width="224"
|
||||||
style="margin: 2em 2em"
|
style="margin: 2em 2em"
|
||||||
@ -1168,7 +1171,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
|
const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
const { focus, backendState } = Props;
|
const { focus, backendState } = Props;
|
||||||
const amountRegex = "^[0-9]+(.[0-9]+)?$";
|
const amountRegex = "^[0-9]+(.[0-9]+)?$";
|
||||||
const ibanRegex = "^[A-Z][A-Z][0-9]+$";
|
const ibanRegex = "^[A-Z][A-Z][0-9]+$";
|
||||||
@ -1193,17 +1196,17 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
? undefined
|
? undefined
|
||||||
: undefinedIfEmpty({
|
: undefinedIfEmpty({
|
||||||
iban: !submitData.iban
|
iban: !submitData.iban
|
||||||
? i18n`Missing IBAN`
|
? i18n.str`Missing IBAN`
|
||||||
: !/^[A-Z0-9]*$/.test(submitData.iban)
|
: !/^[A-Z0-9]*$/.test(submitData.iban)
|
||||||
? i18n`IBAN should have just uppercased letters and numbers`
|
? i18n.str`IBAN should have just uppercased letters and numbers`
|
||||||
: undefined,
|
: undefined,
|
||||||
subject: !submitData.subject ? i18n`Missing subject` : undefined,
|
subject: !submitData.subject ? i18n.str`Missing subject` : undefined,
|
||||||
amount: !submitData.amount
|
amount: !submitData.amount
|
||||||
? i18n`Missing amount`
|
? i18n.str`Missing amount`
|
||||||
: !(parsedAmount = Amounts.parse(`${currency}:${submitData.amount}`))
|
: !(parsedAmount = Amounts.parse(`${currency}:${submitData.amount}`))
|
||||||
? i18n`Amount is not valid`
|
? i18n.str`Amount is not valid`
|
||||||
: Amounts.isZero(parsedAmount)
|
: Amounts.isZero(parsedAmount)
|
||||||
? i18n`Should be greater than 0`
|
? i18n.str`Should be greater than 0`
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1212,7 +1215,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
<div>
|
<div>
|
||||||
<div class="pure-form" name="wire-transfer-form">
|
<div class="pure-form" name="wire-transfer-form">
|
||||||
<p>
|
<p>
|
||||||
<label for="iban">{i18n`Receiver IBAN:`}</label>
|
<label for="iban">{i18n.str`Receiver IBAN:`}</label>
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
type="text"
|
type="text"
|
||||||
@ -1235,7 +1238,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
isDirty={submitData?.iban !== undefined}
|
isDirty={submitData?.iban !== undefined}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<label for="subject">{i18n`Transfer subject:`}</label>
|
<label for="subject">{i18n.str`Transfer subject:`}</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="subject"
|
name="subject"
|
||||||
@ -1256,7 +1259,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
isDirty={submitData?.subject !== undefined}
|
isDirty={submitData?.subject !== undefined}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<label for="amount">{i18n`Amount:`}</label>
|
<label for="amount">{i18n.str`Amount:`}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="amount"
|
name="amount"
|
||||||
@ -1309,7 +1312,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
...prevState,
|
...prevState,
|
||||||
|
|
||||||
error: {
|
error: {
|
||||||
title: i18n`Field(s) missing.`,
|
title: i18n.str`Field(s) missing.`,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
return;
|
return;
|
||||||
@ -1358,7 +1361,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n`Want to try the raw payto://-format?`}
|
{i18n.str`Want to try the raw payto://-format?`}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -1366,18 +1369,18 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
|
|
||||||
const errorsPayto = undefinedIfEmpty({
|
const errorsPayto = undefinedIfEmpty({
|
||||||
rawPaytoInput: !rawPaytoInput
|
rawPaytoInput: !rawPaytoInput
|
||||||
? i18n`Missing payto address`
|
? i18n.str`Missing payto address`
|
||||||
: !parsePaytoUri(rawPaytoInput)
|
: !parsePaytoUri(rawPaytoInput)
|
||||||
? i18n`Payto does not follow the pattern`
|
? i18n.str`Payto does not follow the pattern`
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{i18n`Transfer money to account identified by payto:// URI:`}</p>
|
<p>{i18n.str`Transfer money to account identified by payto:// URI:`}</p>
|
||||||
<div class="pure-form" name="payto-form">
|
<div class="pure-form" name="payto-form">
|
||||||
<p>
|
<p>
|
||||||
<label for="address">{i18n`payto URI:`}</label>
|
<label for="address">{i18n.str`payto URI:`}</label>
|
||||||
<input
|
<input
|
||||||
name="address"
|
name="address"
|
||||||
type="text"
|
type="text"
|
||||||
@ -1386,7 +1389,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
id="address"
|
id="address"
|
||||||
value={rawPaytoInput ?? ""}
|
value={rawPaytoInput ?? ""}
|
||||||
required
|
required
|
||||||
placeholder={i18n`payto address`}
|
placeholder={i18n.str`payto address`}
|
||||||
// pattern={`payto://iban/[A-Z][A-Z][0-9]+?message=[a-zA-Z0-9 ]+&amount=${currency}:[0-9]+(.[0-9]+)?`}
|
// pattern={`payto://iban/[A-Z][A-Z][0-9]+?message=[a-zA-Z0-9 ]+&amount=${currency}:[0-9]+(.[0-9]+)?`}
|
||||||
onInput={(e): void => {
|
onInput={(e): void => {
|
||||||
rawPaytoInputSetter(e.currentTarget.value);
|
rawPaytoInputSetter(e.currentTarget.value);
|
||||||
@ -1410,7 +1413,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
class="pure-button pure-button-primary"
|
class="pure-button pure-button-primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!!errorsPayto}
|
disabled={!!errorsPayto}
|
||||||
value={i18n`Send`}
|
value={i18n.str`Send`}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
// empty string evaluates to false.
|
// empty string evaluates to false.
|
||||||
if (!rawPaytoInput) {
|
if (!rawPaytoInput) {
|
||||||
@ -1444,7 +1447,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n`Use wire-transfer form?`}
|
{i18n.str`Use wire-transfer form?`}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -1459,7 +1462,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
|
function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
|
||||||
const [pageState, pageStateSetter] = useContext(PageContext);
|
const [pageState, pageStateSetter] = useContext(PageContext);
|
||||||
const { backendState } = Props;
|
const { backendState } = Props;
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
const captchaNumbers = {
|
const captchaNumbers = {
|
||||||
a: Math.floor(Math.random() * 10),
|
a: Math.floor(Math.random() * 10),
|
||||||
b: Math.floor(Math.random() * 10),
|
b: Math.floor(Math.random() * 10),
|
||||||
@ -1468,15 +1471,15 @@ function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<h1 class="nav">{i18n`Confirm Withdrawal`}</h1>
|
<h1 class="nav">{i18n.str`Confirm Withdrawal`}</h1>
|
||||||
<article>
|
<article>
|
||||||
<div class="challenge-div">
|
<div class="challenge-div">
|
||||||
<form class="challenge-form">
|
<form class="challenge-form">
|
||||||
<div class="pure-form" id="captcha" name="capcha-form">
|
<div class="pure-form" id="captcha" name="capcha-form">
|
||||||
<h2>{i18n`Authorize withdrawal by solving challenge`}</h2>
|
<h2>{i18n.str`Authorize withdrawal by solving challenge`}</h2>
|
||||||
<p>
|
<p>
|
||||||
<label for="answer">
|
<label for="answer">
|
||||||
{i18n`What is`}
|
{i18n.str`What is`}
|
||||||
<em>
|
<em>
|
||||||
{captchaNumbers.a} + {captchaNumbers.b}
|
{captchaNumbers.a} + {captchaNumbers.b}
|
||||||
</em>
|
</em>
|
||||||
@ -1514,12 +1517,12 @@ function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
|
|||||||
...prevState,
|
...prevState,
|
||||||
|
|
||||||
error: {
|
error: {
|
||||||
title: i18n`Answer is wrong.`,
|
title: i18n.str`Answer is wrong.`,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n`Confirm`}
|
{i18n.str`Confirm`}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -1532,18 +1535,18 @@ function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{i18n`Cancel`}
|
{i18n.str`Cancel`}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
<p>
|
<p>
|
||||||
<Translate>
|
<i18n.Translate>
|
||||||
A this point, a <b>real</b> bank would ask for an additional
|
A this point, a <b>real</b> bank would ask for an additional
|
||||||
authentication proof (PIN/TAN, one time password, ..), instead
|
authentication proof (PIN/TAN, one time password, ..), instead
|
||||||
of a simple calculation.
|
of a simple calculation.
|
||||||
</Translate>
|
</i18n.Translate>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1552,40 +1555,6 @@ function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function QrCodeSection({
|
|
||||||
talerWithdrawUri,
|
|
||||||
abortButton,
|
|
||||||
}: {
|
|
||||||
talerWithdrawUri: string;
|
|
||||||
abortButton: h.JSX.Element;
|
|
||||||
}): VNode {
|
|
||||||
const i18n = useTranslator();
|
|
||||||
useEffect(() => {
|
|
||||||
//Taler Wallet WebExtension is listening to headers response and tab updates.
|
|
||||||
//In the SPA there is no header response with the Taler URI so
|
|
||||||
//this hack manually triggers the tab update after the QR is in the DOM.
|
|
||||||
window.location.hash = `/account/${new Date().getTime()}`;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="main" class="content">
|
|
||||||
<h1 class="nav">{i18n`Transfer to Taler Wallet`}</h1>
|
|
||||||
<article>
|
|
||||||
<div class="qr-div">
|
|
||||||
<p>{i18n`Use this QR code to withdraw to your mobile wallet:`}</p>
|
|
||||||
{QR({ text: talerWithdrawUri })}
|
|
||||||
<p>
|
|
||||||
Click <a id="linkqr" href={talerWithdrawUri}>{i18n`this link`}</a>{" "}
|
|
||||||
to open your Taler wallet!
|
|
||||||
</p>
|
|
||||||
<br />
|
|
||||||
{abortButton}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Offer the QR code (and a clickable taler://-link) to
|
* Offer the QR code (and a clickable taler://-link) to
|
||||||
* permit the passing of exchange and reserve details to
|
* permit the passing of exchange and reserve details to
|
||||||
@ -1595,7 +1564,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
|
|||||||
// turns true when the wallet POSTed the reserve details:
|
// turns true when the wallet POSTed the reserve details:
|
||||||
const [pageState, pageStateSetter] = useContext(PageContext);
|
const [pageState, pageStateSetter] = useContext(PageContext);
|
||||||
const { withdrawalId, talerWithdrawUri, accountLabel, backendState } = Props;
|
const { withdrawalId, talerWithdrawUri, accountLabel, backendState } = Props;
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
const abortButton = (
|
const abortButton = (
|
||||||
<a
|
<a
|
||||||
class="pure-button btn-cancel"
|
class="pure-button btn-cancel"
|
||||||
@ -1609,7 +1578,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>{i18n`Abort`}</a>
|
>{i18n.str`Abort`}</a>
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Showing withdraw URI: ${talerWithdrawUri}`);
|
console.log(`Showing withdraw URI: ${talerWithdrawUri}`);
|
||||||
@ -1629,7 +1598,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
|
|||||||
...prevState,
|
...prevState,
|
||||||
|
|
||||||
error: {
|
error: {
|
||||||
title: i18n`withdrawal (${withdrawalId}) was never (correctly) created at the bank...`,
|
title: i18n.str`withdrawal (${withdrawalId}) was never (correctly) created at the bank...`,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
return (
|
return (
|
||||||
@ -1643,7 +1612,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
|
|||||||
|
|
||||||
// data didn't arrive yet and wallet didn't communicate:
|
// data didn't arrive yet and wallet didn't communicate:
|
||||||
if (typeof data === "undefined")
|
if (typeof data === "undefined")
|
||||||
return <p>{i18n`Waiting the bank to create the operation...`}</p>;
|
return <p>{i18n.str`Waiting the bank to create the operation...`}</p>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wallet didn't communicate withdrawal details yet:
|
* Wallet didn't communicate withdrawal details yet:
|
||||||
@ -1657,7 +1626,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
|
|||||||
withdrawalInProgress: false,
|
withdrawalInProgress: false,
|
||||||
|
|
||||||
error: {
|
error: {
|
||||||
title: i18n`This withdrawal was aborted!`,
|
title: i18n.str`This withdrawal was aborted!`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -1680,7 +1649,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
|
|||||||
function WalletWithdraw(Props: any): VNode {
|
function WalletWithdraw(Props: any): VNode {
|
||||||
const { backendState, pageStateSetter, focus } = Props;
|
const { backendState, pageStateSetter, focus } = Props;
|
||||||
const currency = useContext(CurrencyContext);
|
const currency = useContext(CurrencyContext);
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
let submitAmount = "5.00";
|
let submitAmount = "5.00";
|
||||||
const amountRegex = "^[0-9]+(.[0-9]+)?$";
|
const amountRegex = "^[0-9]+(.[0-9]+)?$";
|
||||||
|
|
||||||
@ -1691,7 +1660,8 @@ function WalletWithdraw(Props: any): VNode {
|
|||||||
return (
|
return (
|
||||||
<div id="reserve-form" class="pure-form" name="tform">
|
<div id="reserve-form" class="pure-form" name="tform">
|
||||||
<p>
|
<p>
|
||||||
<label for="withdraw-amount">{i18n`Amount to withdraw:`}</label>
|
<label for="withdraw-amount">{i18n.str`Amount to withdraw:`}</label>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -1724,7 +1694,7 @@ function WalletWithdraw(Props: any): VNode {
|
|||||||
id="select-exchange"
|
id="select-exchange"
|
||||||
class="pure-button pure-button-primary"
|
class="pure-button pure-button-primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
value={i18n`Withdraw`}
|
value={i18n.str`Withdraw`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
submitAmount = validateAmount(submitAmount);
|
submitAmount = validateAmount(submitAmount);
|
||||||
/**
|
/**
|
||||||
@ -1753,7 +1723,7 @@ function WalletWithdraw(Props: any): VNode {
|
|||||||
function PaymentOptions(Props: any): VNode {
|
function PaymentOptions(Props: any): VNode {
|
||||||
const { backendState, pageStateSetter, focus } = Props;
|
const { backendState, pageStateSetter, focus } = Props;
|
||||||
const currency = useContext(CurrencyContext);
|
const currency = useContext(CurrencyContext);
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
const [tab, setTab] = useState<"charge-wallet" | "wire-transfer">(
|
const [tab, setTab] = useState<"charge-wallet" | "wire-transfer">(
|
||||||
"charge-wallet",
|
"charge-wallet",
|
||||||
@ -1769,7 +1739,7 @@ function PaymentOptions(Props: any): VNode {
|
|||||||
setTab("charge-wallet");
|
setTab("charge-wallet");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n`Obtain digital cash`}
|
{i18n.str`Obtain digital cash`}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class={tab === "wire-transfer" ? "tablinks active" : "tablinks"}
|
class={tab === "wire-transfer" ? "tablinks active" : "tablinks"}
|
||||||
@ -1777,12 +1747,12 @@ function PaymentOptions(Props: any): VNode {
|
|||||||
setTab("wire-transfer");
|
setTab("wire-transfer");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n`Transfer to bank account`}
|
{i18n.str`Transfer to bank account`}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{tab === "charge-wallet" && (
|
{tab === "charge-wallet" && (
|
||||||
<div id="charge-wallet" class="tabcontent active">
|
<div id="charge-wallet" class="tabcontent active">
|
||||||
<h3>{i18n`Obtain digital cash`}</h3>
|
<h3>{i18n.str`Obtain digital cash`}</h3>
|
||||||
<WalletWithdraw
|
<WalletWithdraw
|
||||||
backendState={backendState}
|
backendState={backendState}
|
||||||
focus
|
focus
|
||||||
@ -1792,7 +1762,7 @@ function PaymentOptions(Props: any): VNode {
|
|||||||
)}
|
)}
|
||||||
{tab === "wire-transfer" && (
|
{tab === "wire-transfer" && (
|
||||||
<div id="wire-transfer" class="tabcontent active">
|
<div id="wire-transfer" class="tabcontent active">
|
||||||
<h3>{i18n`Transfer to bank account`}</h3>
|
<h3>{i18n.str`Transfer to bank account`}</h3>
|
||||||
<PaytoWireTransfer
|
<PaytoWireTransfer
|
||||||
backendState={backendState}
|
backendState={backendState}
|
||||||
focus
|
focus
|
||||||
@ -1807,7 +1777,7 @@ function PaymentOptions(Props: any): VNode {
|
|||||||
|
|
||||||
function RegistrationButton(Props: any): VNode {
|
function RegistrationButton(Props: any): VNode {
|
||||||
const { backendStateSetter, pageStateSetter } = Props;
|
const { backendStateSetter, pageStateSetter } = Props;
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
if (bankUiSettings.allowRegistrations)
|
if (bankUiSettings.allowRegistrations)
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@ -1816,7 +1786,7 @@ function RegistrationButton(Props: any): VNode {
|
|||||||
route("/register");
|
route("/register");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n`Register`}
|
{i18n.str`Register`}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1834,7 +1804,7 @@ function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
|
|||||||
function LoginForm(Props: any): VNode {
|
function LoginForm(Props: any): VNode {
|
||||||
const { backendStateSetter, pageStateSetter } = Props;
|
const { backendStateSetter, pageStateSetter } = Props;
|
||||||
const [submitData, submitDataSetter] = useCredentialsRequestType();
|
const [submitData, submitDataSetter] = useCredentialsRequestType();
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ref.current?.focus();
|
ref.current?.focus();
|
||||||
@ -1843,17 +1813,17 @@ function LoginForm(Props: any): VNode {
|
|||||||
const errors = !submitData
|
const errors = !submitData
|
||||||
? undefined
|
? undefined
|
||||||
: undefinedIfEmpty({
|
: undefinedIfEmpty({
|
||||||
username: !submitData.username ? i18n`Missing username` : undefined,
|
username: !submitData.username ? i18n.str`Missing username` : undefined,
|
||||||
password: !submitData.password ? i18n`Missing password` : undefined,
|
password: !submitData.password ? i18n.str`Missing password` : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="login-div">
|
<div class="login-div">
|
||||||
<form action="javascript:void(0);" class="login-form">
|
<form action="javascript:void(0);" class="login-form">
|
||||||
<div class="pure-form">
|
<div class="pure-form">
|
||||||
<h2>{i18n`Please login!`}</h2>
|
<h2>{i18n.str`Please login!`}</h2>
|
||||||
<p class="unameFieldLabel loginFieldLabel formFieldLabel">
|
<p class="unameFieldLabel loginFieldLabel formFieldLabel">
|
||||||
<label for="username">{i18n`Username:`}</label>
|
<label for="username">{i18n.str`Username:`}</label>
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -1872,7 +1842,7 @@ function LoginForm(Props: any): VNode {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<p class="passFieldLabel loginFieldLabel formFieldLabel">
|
<p class="passFieldLabel loginFieldLabel formFieldLabel">
|
||||||
<label for="password">{i18n`Password:`}</label>
|
<label for="password">{i18n.str`Password:`}</label>
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
@ -1919,7 +1889,7 @@ function LoginForm(Props: any): VNode {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n`Login`}
|
{i18n.str`Login`}
|
||||||
</button>
|
</button>
|
||||||
{RegistrationButton(Props)}
|
{RegistrationButton(Props)}
|
||||||
</div>
|
</div>
|
||||||
@ -1935,30 +1905,30 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [pageState, pageStateSetter] = useContext(PageContext);
|
const [pageState, pageStateSetter] = useContext(PageContext);
|
||||||
const [submitData, submitDataSetter] = useCredentialsRequestType();
|
const [submitData, submitDataSetter] = useCredentialsRequestType();
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
const errors = !submitData
|
const errors = !submitData
|
||||||
? undefined
|
? undefined
|
||||||
: undefinedIfEmpty({
|
: undefinedIfEmpty({
|
||||||
username: !submitData.username ? i18n`Missing username` : undefined,
|
username: !submitData.username ? i18n.str`Missing username` : undefined,
|
||||||
password: !submitData.password ? i18n`Missing password` : undefined,
|
password: !submitData.password ? i18n.str`Missing password` : undefined,
|
||||||
repeatPassword: !submitData.repeatPassword
|
repeatPassword: !submitData.repeatPassword
|
||||||
? i18n`Missing password`
|
? i18n.str`Missing password`
|
||||||
: submitData.repeatPassword !== submitData.password
|
: submitData.repeatPassword !== submitData.password
|
||||||
? i18n`Password don't match`
|
? i18n.str`Password don't match`
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<h1 class="nav">{i18n`Welcome to ${bankUiSettings.bankName}!`}</h1>
|
<h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
|
||||||
<article>
|
<article>
|
||||||
<div class="register-div">
|
<div class="register-div">
|
||||||
<form action="javascript:void(0);" class="register-form">
|
<form action="javascript:void(0);" class="register-form">
|
||||||
<div class="pure-form">
|
<div class="pure-form">
|
||||||
<h2>{i18n`Please register!`}</h2>
|
<h2>{i18n.str`Please register!`}</h2>
|
||||||
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
|
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
|
||||||
<label for="register-un">{i18n`Username:`}</label>
|
<label for="register-un">{i18n.str`Username:`}</label>
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
id="register-un"
|
id="register-un"
|
||||||
@ -1976,7 +1946,7 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
|
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
|
||||||
<label for="register-pw">{i18n`Password:`}</label>
|
<label for="register-pw">{i18n.str`Password:`}</label>
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
@ -1993,7 +1963,7 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
|
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
|
||||||
<label for="register-repeat">{i18n`Repeat Password:`}</label>
|
<label for="register-repeat">{i18n.str`Repeat Password:`}</label>
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
@ -2012,7 +1982,7 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
{/*
|
{/*
|
||||||
<label for="phone">{i18n`Phone number:`}</label>
|
<label for="phone">{i18n.str`Phone number:`}</label>
|
||||||
// FIXME: add input validation (must start with +, otherwise only numbers)
|
// FIXME: add input validation (must start with +, otherwise only numbers)
|
||||||
<input
|
<input
|
||||||
name="phone"
|
name="phone"
|
||||||
@ -2054,7 +2024,7 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n`Register`}
|
{i18n.str`Register`}
|
||||||
</button>
|
</button>
|
||||||
{/* FIXME: should use a different color */}
|
{/* FIXME: should use a different color */}
|
||||||
<button
|
<button
|
||||||
@ -2068,7 +2038,7 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
route("/account");
|
route("/account");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n`Cancel`}
|
{i18n.str`Cancel`}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -2083,7 +2053,7 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
*/
|
*/
|
||||||
function Transactions(Props: any): VNode {
|
function Transactions(Props: any): VNode {
|
||||||
const { pageNumber, accountLabel, balanceValue } = Props;
|
const { pageNumber, accountLabel, balanceValue } = Props;
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
const { data, error, mutate } = useSWR(
|
const { data, error, mutate } = useSWR(
|
||||||
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
|
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
|
||||||
);
|
);
|
||||||
@ -2114,10 +2084,10 @@ function Transactions(Props: any): VNode {
|
|||||||
<table class="pure-table pure-table-striped">
|
<table class="pure-table pure-table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{i18n`Date`}</th>
|
<th>{i18n.str`Date`}</th>
|
||||||
<th>{i18n`Amount`}</th>
|
<th>{i18n.str`Amount`}</th>
|
||||||
<th>{i18n`Counterpart`}</th>
|
<th>{i18n.str`Counterpart`}</th>
|
||||||
<th>{i18n`Subject`}</th>
|
<th>{i18n.str`Subject`}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -2178,7 +2148,7 @@ function Account(Props: any): VNode {
|
|||||||
talerWithdrawUri,
|
talerWithdrawUri,
|
||||||
timestamp,
|
timestamp,
|
||||||
} = pageState;
|
} = pageState;
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mutate();
|
mutate();
|
||||||
}, [timestamp]);
|
}, [timestamp]);
|
||||||
@ -2206,7 +2176,7 @@ function Account(Props: any): VNode {
|
|||||||
|
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
error: {
|
error: {
|
||||||
title: i18n`Username or account label '${accountLabel}' not found. Won't login.`,
|
title: i18n.str`Username or account label '${accountLabel}' not found. Won't login.`,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -2233,7 +2203,7 @@ function Account(Props: any): VNode {
|
|||||||
|
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
error: {
|
error: {
|
||||||
title: i18n`Wrong credentials given.`,
|
title: i18n.str`Wrong credentials given.`,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
return <p>Wrong credentials...</p>;
|
return <p>Wrong credentials...</p>;
|
||||||
@ -2244,7 +2214,7 @@ function Account(Props: any): VNode {
|
|||||||
|
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
error: {
|
error: {
|
||||||
title: i18n`Account information could not be retrieved.`,
|
title: i18n.str`Account information could not be retrieved.`,
|
||||||
debug: JSON.stringify(error),
|
debug: JSON.stringify(error),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@ -2287,14 +2257,14 @@ function Account(Props: any): VNode {
|
|||||||
<BankFrame>
|
<BankFrame>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="nav welcome-text">
|
<h1 class="nav welcome-text">
|
||||||
<Translate>
|
<i18n.Translate>
|
||||||
Welcome, {accountLabel} ({getIbanFromPayto(data.paytoUri)})!
|
Welcome, {accountLabel} ({getIbanFromPayto(data.paytoUri)})!
|
||||||
</Translate>
|
</i18n.Translate>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<section id="assets">
|
<section id="assets">
|
||||||
<div class="asset-summary">
|
<div class="asset-summary">
|
||||||
<h2>{i18n`Bank account balance`}</h2>
|
<h2>{i18n.str`Bank account balance`}</h2>
|
||||||
<div class="large-amount amount">
|
<div class="large-amount amount">
|
||||||
{data.balance.credit_debit_indicator == "debit" ? <b>-</b> : null}
|
{data.balance.credit_debit_indicator == "debit" ? <b>-</b> : null}
|
||||||
<span class="value">{`${balanceValue}`}</span>
|
<span class="value">{`${balanceValue}`}</span>
|
||||||
@ -2304,7 +2274,7 @@ function Account(Props: any): VNode {
|
|||||||
</section>
|
</section>
|
||||||
<section id="payments">
|
<section id="payments">
|
||||||
<div class="payments">
|
<div class="payments">
|
||||||
<h2>{i18n`Payments`}</h2>
|
<h2>{i18n.str`Payments`}</h2>
|
||||||
{/* FIXME: turn into button! */}
|
{/* FIXME: turn into button! */}
|
||||||
<CurrencyContext.Provider value={balance.currency}>
|
<CurrencyContext.Provider value={balance.currency}>
|
||||||
{Props.children}
|
{Props.children}
|
||||||
@ -2317,7 +2287,7 @@ function Account(Props: any): VNode {
|
|||||||
</section>
|
</section>
|
||||||
<section id="main">
|
<section id="main">
|
||||||
<article>
|
<article>
|
||||||
<h2>{i18n`Latest transactions:`}</h2>
|
<h2>{i18n.str`Latest transactions:`}</h2>
|
||||||
<Transactions
|
<Transactions
|
||||||
balanceValue={balanceValue}
|
balanceValue={balanceValue}
|
||||||
pageNumber="0"
|
pageNumber="0"
|
||||||
@ -2379,7 +2349,7 @@ function SWRWithoutCredentials(Props: any): VNode {
|
|||||||
function PublicHistories(Props: any): VNode {
|
function PublicHistories(Props: any): VNode {
|
||||||
const [showAccount, setShowAccount] = useShowPublicAccount();
|
const [showAccount, setShowAccount] = useShowPublicAccount();
|
||||||
const { data, error } = useSWR("access-api/public-accounts");
|
const { data, error } = useSWR("access-api/public-accounts");
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
if (typeof error !== "undefined") {
|
if (typeof error !== "undefined") {
|
||||||
console.log("account error", error);
|
console.log("account error", error);
|
||||||
@ -2391,7 +2361,7 @@ function PublicHistories(Props: any): VNode {
|
|||||||
|
|
||||||
showPublicHistories: false,
|
showPublicHistories: false,
|
||||||
error: {
|
error: {
|
||||||
title: i18n`List of public accounts was not found.`,
|
title: i18n.str`List of public accounts was not found.`,
|
||||||
debug: JSON.stringify(error),
|
debug: JSON.stringify(error),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@ -2403,7 +2373,7 @@ function PublicHistories(Props: any): VNode {
|
|||||||
|
|
||||||
showPublicHistories: false,
|
showPublicHistories: false,
|
||||||
error: {
|
error: {
|
||||||
title: i18n`List of public accounts could not be retrieved.`,
|
title: i18n.str`List of public accounts could not be retrieved.`,
|
||||||
debug: JSON.stringify(error),
|
debug: JSON.stringify(error),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@ -2450,7 +2420,7 @@ function PublicHistories(Props: any): VNode {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<h1 class="nav">{i18n`History of public accounts`}</h1>
|
<h1 class="nav">{i18n.str`History of public accounts`}</h1>
|
||||||
<section id="main">
|
<section id="main">
|
||||||
<article>
|
<article>
|
||||||
<div class="pure-menu pure-menu-horizontal" name="accountMenu">
|
<div class="pure-menu pure-menu-horizontal" name="accountMenu">
|
||||||
@ -2471,7 +2441,7 @@ function PublicHistories(Props: any): VNode {
|
|||||||
function PublicHistoriesPage(): VNode {
|
function PublicHistoriesPage(): VNode {
|
||||||
// const [backendState, backendStateSetter] = useBackendState();
|
// const [backendState, backendStateSetter] = useBackendState();
|
||||||
const [pageState, pageStateSetter] = usePageState();
|
const [pageState, pageStateSetter] = usePageState();
|
||||||
// const i18n = useTranslator();
|
// const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<SWRWithoutCredentials baseUrl={getBankBackendBaseUrl()}>
|
<SWRWithoutCredentials baseUrl={getBankBackendBaseUrl()}>
|
||||||
<PageContext.Provider value={[pageState, pageStateSetter]}>
|
<PageContext.Provider value={[pageState, pageStateSetter]}>
|
||||||
@ -2499,12 +2469,12 @@ function PublicHistoriesPage(): VNode {
|
|||||||
function RegistrationPage(): VNode {
|
function RegistrationPage(): VNode {
|
||||||
const [backendState, backendStateSetter] = useBackendState();
|
const [backendState, backendStateSetter] = useBackendState();
|
||||||
const [pageState, pageStateSetter] = usePageState();
|
const [pageState, pageStateSetter] = usePageState();
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
if (!bankUiSettings.allowRegistrations) {
|
if (!bankUiSettings.allowRegistrations) {
|
||||||
return (
|
return (
|
||||||
<PageContext.Provider value={[pageState, pageStateSetter]}>
|
<PageContext.Provider value={[pageState, pageStateSetter]}>
|
||||||
<BankFrame>
|
<BankFrame>
|
||||||
<p>{i18n`Currently, the bank is not accepting new registrations!`}</p>
|
<p>{i18n.str`Currently, the bank is not accepting new registrations!`}</p>
|
||||||
</BankFrame>
|
</BankFrame>
|
||||||
</PageContext.Provider>
|
</PageContext.Provider>
|
||||||
);
|
);
|
||||||
@ -2521,13 +2491,13 @@ function RegistrationPage(): VNode {
|
|||||||
function AccountPage(): VNode {
|
function AccountPage(): VNode {
|
||||||
const [backendState, backendStateSetter] = useBackendState();
|
const [backendState, backendStateSetter] = useBackendState();
|
||||||
const [pageState, pageStateSetter] = usePageState();
|
const [pageState, pageStateSetter] = usePageState();
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
if (!pageState.isLoggedIn) {
|
if (!pageState.isLoggedIn) {
|
||||||
return (
|
return (
|
||||||
<PageContext.Provider value={[pageState, pageStateSetter]}>
|
<PageContext.Provider value={[pageState, pageStateSetter]}>
|
||||||
<BankFrame>
|
<BankFrame>
|
||||||
<h1 class="nav">{i18n`Welcome to ${bankUiSettings.bankName}!`}</h1>
|
<h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
|
||||||
<LoginForm
|
<LoginForm
|
||||||
pageStateSetter={pageStateSetter}
|
pageStateSetter={pageStateSetter}
|
||||||
backendStateSetter={backendStateSetter}
|
backendStateSetter={backendStateSetter}
|
||||||
@ -2543,7 +2513,7 @@ function AccountPage(): VNode {
|
|||||||
|
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
error: {
|
error: {
|
||||||
title: i18n`Page has a problem: logged in but backend state is lost.`,
|
title: i18n.str`Page has a problem: logged in but backend state is lost.`,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
return <p>Error: waiting for details...</p>;
|
return <p>Error: waiting for details...</p>;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@import "pure";
|
@use "pure";
|
||||||
@import "bank";
|
@use "bank";
|
||||||
@import "demo";
|
@use "demo";
|
||||||
@import "colors-bank";
|
@use "colors-bank";
|
||||||
|
46
packages/demobank-ui/src/stories.tsx
Normal file
46
packages/demobank-ui/src/stories.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Anastasis
|
||||||
|
(C) 2021-2022 Anastasis SARL
|
||||||
|
|
||||||
|
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU Affero General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Anastasis 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License along with
|
||||||
|
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
|
*/
|
||||||
|
import { strings } from "./i18n/strings.js";
|
||||||
|
|
||||||
|
import * as pages from "./pages/home/index.stories.js";
|
||||||
|
|
||||||
|
import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
|
|
||||||
|
import "./scss/main.scss";
|
||||||
|
|
||||||
|
function SortStories(a: any, b: any): number {
|
||||||
|
return (a?.order ?? 0) - (b?.order ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main(): void {
|
||||||
|
renderStories(
|
||||||
|
{ pages },
|
||||||
|
{
|
||||||
|
strings,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", main);
|
||||||
|
} else {
|
||||||
|
main();
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
|
||||||
<title>Demobank</title>
|
|
||||||
<!-- Optional customization script. -->
|
|
||||||
<script src="demobank-ui-settings.js"></script>
|
|
||||||
<!-- Entry point for the demobank SPA. -->
|
|
||||||
<script type="module" src="index.js"></script>
|
|
||||||
<link rel="stylesheet" href="index.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in New Issue
Block a user