aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2022-12-06 11:25:05 -0300
committerSebastian <sebasjm@gmail.com>2022-12-06 11:25:05 -0300
commit5969a44391f32d931d0b26416fb3e1528f4a32a2 (patch)
tree3d8e122200e30b4433ca7c3518e49d0d42734034
parentd3a6544bc5fd7b3d2d65494ba1c3155b024a436e (diff)
using web-utils in demobank
-rwxr-xr-xpackages/demobank-ui/build.mjs43
-rwxr-xr-xpackages/demobank-ui/dev.mjs30
-rw-r--r--packages/demobank-ui/package.json7
-rw-r--r--packages/demobank-ui/src/components/menu/SideBar.tsx9
-rw-r--r--packages/demobank-ui/src/components/picker/DurationPicker.tsx12
-rw-r--r--packages/demobank-ui/src/context/translation.ts52
-rw-r--r--packages/demobank-ui/src/hooks/index.ts79
-rw-r--r--packages/demobank-ui/src/hooks/useLang.ts30
-rw-r--r--packages/demobank-ui/src/hooks/useLocalStorage.ts80
-rw-r--r--packages/demobank-ui/src/i18n/index.tsx201
-rw-r--r--packages/demobank-ui/src/index.html15
-rw-r--r--packages/demobank-ui/src/index.tsx1
-rw-r--r--packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx33
-rw-r--r--packages/demobank-ui/src/pages/home/QrCodeSection.tsx55
-rw-r--r--packages/demobank-ui/src/pages/home/index.stories.tsx1
-rw-r--r--packages/demobank-ui/src/pages/home/index.tsx234
-rw-r--r--packages/demobank-ui/src/scss/main.scss8
-rw-r--r--packages/demobank-ui/src/stories.tsx46
-rw-r--r--packages/demobank-ui/static/index.html15
19 files changed, 477 insertions, 474 deletions
diff --git a/packages/demobank-ui/build.mjs b/packages/demobank-ui/build.mjs
index 63ddc1f25..c93b4eb67 100755
--- a/packages/demobank-ui/build.mjs
+++ b/packages/demobank-ui/build.mjs
@@ -18,9 +18,9 @@
import esbuild from "esbuild";
import path from "path";
import fs from "fs";
-import crypto from "crypto";
-import { sassPlugin } from "esbuild-sass-plugin";
+import sass from "sass";
+// eslint-disable-next-line no-undef
const BASE = process.cwd();
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;
while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") {
GIT_ROOT = path.join(GIT_ROOT, "../");
}
if (GIT_ROOT === "/") {
+ // eslint-disable-next-line no-undef
console.log("not found");
+ // eslint-disable-next-line no-undef
process.exit(1);
}
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 = {
entryPoints: [...entryPoints],
bundle: true,
@@ -95,6 +117,10 @@ export const buildConfig = {
".svg": "file",
".png": "dataurl",
".jpeg": "dataurl",
+ '.ttf': 'file',
+ '.woff': 'file',
+ '.woff2': 'file',
+ '.eot': 'file',
},
target: ["es6"],
format: "esm",
@@ -108,17 +134,14 @@ export const buildConfig = {
},
plugins: [
preactCompatPlugin,
- sassPlugin(),
copyFilesPlugin([
{
- src: "static/index.html",
- dest: "dist/index.html",
+ src: "./src/index.html",
+ dest: "./dist/index.html",
},
]),
+ buildSassPlugin
],
};
-esbuild.build(buildConfig).catch((e) => {
- console.log(e);
- process.exit(1);
-});
+await esbuild.build(buildConfig)
diff --git a/packages/demobank-ui/dev.mjs b/packages/demobank-ui/dev.mjs
new file mode 100755
index 000000000..35a9fa16c
--- /dev/null
+++ b/packages/demobank-ui/dev.mjs
@@ -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)
+})
diff --git a/packages/demobank-ui/package.json b/packages/demobank-ui/package.json
index cc8048a0f..41031977f 100644
--- a/packages/demobank-ui/package.json
+++ b/packages/demobank-ui/package.json
@@ -11,13 +11,13 @@
},
"dependencies": {
"@gnu-taler/taler-util": "workspace:*",
+ "@gnu-taler/web-util": "workspace:*",
"date-fns": "2.29.3",
"history": "4.10.1",
"jed": "1.1.1",
- "preact": "10.6.5",
+ "preact": "10.11.3",
"preact-router": "3.2.1",
"qrcode-generator": "^1.4.4",
- "react": "npm:@preact/compat@^17.1.2",
"swr": "1.3.0"
},
"devDependencies": {
@@ -30,11 +30,10 @@
"bulma-checkbox": "^1.1.1",
"bulma-radio": "^1.1.1",
"esbuild": "^0.15.12",
- "esbuild-sass-plugin": "^2.4.0",
"eslint": "^8.26.0",
"eslint-config-preact": "^1.2.0",
"po2json": "^0.4.5",
- "sass": "1.32.13",
+ "sass": "1.56.1",
"typescript": "^4.4.4"
}
}
diff --git a/packages/demobank-ui/src/components/menu/SideBar.tsx b/packages/demobank-ui/src/components/menu/SideBar.tsx
index d7833df5a..7bfba2a75 100644
--- a/packages/demobank-ui/src/components/menu/SideBar.tsx
+++ b/packages/demobank-ui/src/components/menu/SideBar.tsx
@@ -20,7 +20,7 @@
*/
import { h, VNode } from "preact";
-import { Translate } from "../../i18n";
+import { useTranslationContext } from "../../context/translation.js";
interface Props {
mobile?: boolean;
@@ -31,6 +31,7 @@ export function Sidebar({ mobile }: Props): VNode {
const config = { version: "none" };
// FIXME: add replacement for __VERSION__ with the current version
const process = { env: { __VERSION__: "0.0.0" } };
+ const { i18n } = useTranslationContext();
return (
<aside class="aside is-placed-left is-expanded">
@@ -49,20 +50,20 @@ export function Sidebar({ mobile }: Props): VNode {
</div>
<div class="menu is-menu-main">
<p class="menu-label">
- <Translate>Bank menu</Translate>
+ <i18n.Translate>Bank menu</i18n.Translate>
</p>
<ul class="menu-list">
<li>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Select option1</Translate>
+ <i18n.Translate>Select option1</i18n.Translate>
</span>
</div>
</li>
<li>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Select option2</Translate>
+ <i18n.Translate>Select option2</i18n.Translate>
</span>
</div>
</li>
diff --git a/packages/demobank-ui/src/components/picker/DurationPicker.tsx b/packages/demobank-ui/src/components/picker/DurationPicker.tsx
index 94f2326bc..b8a7671c3 100644
--- a/packages/demobank-ui/src/components/picker/DurationPicker.tsx
+++ b/packages/demobank-ui/src/components/picker/DurationPicker.tsx
@@ -21,7 +21,7 @@
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
-import { useTranslator } from "../../i18n";
+import { useTranslationContext } from "../../context/translation.js";
import "../../scss/DurationPicker.scss";
export interface Props {
@@ -46,13 +46,13 @@ export function DurationPicker({
const ms = ss * 60;
const hs = ms * 60;
const ds = hs * 24;
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
return (
<div class="rdp-picker">
{days && (
<DurationColumn
- unit={i18n`days`}
+ unit={i18n.str`days`}
max={99}
value={Math.floor(value / ds)}
onDecrease={value >= ds ? () => onChange(value - ds) : undefined}
@@ -62,7 +62,7 @@ export function DurationPicker({
)}
{hours && (
<DurationColumn
- unit={i18n`hours`}
+ unit={i18n.str`hours`}
max={23}
min={1}
value={Math.floor(value / hs) % 24}
@@ -73,7 +73,7 @@ export function DurationPicker({
)}
{minutes && (
<DurationColumn
- unit={i18n`minutes`}
+ unit={i18n.str`minutes`}
max={59}
min={1}
value={Math.floor(value / ms) % 60}
@@ -84,7 +84,7 @@ export function DurationPicker({
)}
{seconds && (
<DurationColumn
- unit={i18n`seconds`}
+ unit={i18n.str`seconds`}
max={59}
value={Math.floor(value / ss) % 60}
onDecrease={value >= ss ? () => onChange(value - ss) : undefined}
diff --git a/packages/demobank-ui/src/context/translation.ts b/packages/demobank-ui/src/context/translation.ts
index a411ecb16..a50f81b86 100644
--- a/packages/demobank-ui/src/context/translation.ts
+++ b/packages/demobank-ui/src/context/translation.ts
@@ -19,27 +19,42 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { i18n, setupI18n } from "@gnu-taler/taler-util";
import { createContext, h, VNode } from "preact";
import { useContext, useEffect } from "preact/hooks";
-import { useLang } from "../hooks/index.js";
-import * as jedLib from "jed";
+import { useLang } from "../hooks/useLang.js";
import { strings } from "../i18n/strings.js";
interface Type {
lang: string;
- handler: any;
+ supportedLang: { [id in keyof typeof supportedLang]: string };
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 = {
lang: "en",
- handler: null,
+ supportedLang,
changeLanguage: () => {
- /**
- * This function will be replaced by one with
- * the same signature _but_ coming from the state.
- * FIXME: clarify this design.
- */
+ // do not change anything
},
+ i18n,
+ isSaved: false,
};
const Context = createContext<Type>(initial);
@@ -55,14 +70,23 @@ export const TranslationProvider = ({
children,
forceLang,
}: Props): VNode => {
- const [lang, changeLanguage] = useLang(initial);
+ const [lang, changeLanguage, isSaved] = useLang(initial);
useEffect(() => {
- if (forceLang) changeLanguage(forceLang);
+ if (forceLang) {
+ changeLanguage(forceLang);
+ }
});
- console.log("lang store", strings);
- const handler = new jedLib.Jed(strings[lang] || strings["en"]);
+ useEffect(() => {
+ setupI18n(lang, strings);
+ }, [lang]);
+ if (forceLang) {
+ setupI18n(forceLang, strings);
+ } else {
+ setupI18n(lang, strings);
+ }
+
return h(Context.Provider, {
- value: { lang, handler, changeLanguage },
+ value: { lang, changeLanguage, supportedLang, i18n, isSaved },
children,
});
};
diff --git a/packages/demobank-ui/src/hooks/index.ts b/packages/demobank-ui/src/hooks/index.ts
index 94e66e5e3..b4191d182 100644
--- a/packages/demobank-ui/src/hooks/index.ts
+++ b/packages/demobank-ui/src/hooks/index.ts
@@ -19,7 +19,8 @@
* @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);
const calculateRootPath = () => {
@@ -68,79 +69,3 @@ export function useBackendInstanceToken(
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];
-}
diff --git a/packages/demobank-ui/src/hooks/useLang.ts b/packages/demobank-ui/src/hooks/useLang.ts
new file mode 100644
index 000000000..5b02c5255
--- /dev/null
+++ b/packages/demobank-ui/src/hooks/useLang.ts
@@ -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);
+}
diff --git a/packages/demobank-ui/src/hooks/useLocalStorage.ts b/packages/demobank-ui/src/hooks/useLocalStorage.ts
new file mode 100644
index 000000000..ed5b491f2
--- /dev/null
+++ b/packages/demobank-ui/src/hooks/useLocalStorage.ts
@@ -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];
+}
diff --git a/packages/demobank-ui/src/i18n/index.tsx b/packages/demobank-ui/src/i18n/index.tsx
deleted file mode 100644
index 2489184b2..000000000
--- a/packages/demobank-ui/src/i18n/index.tsx
+++ /dev/null
@@ -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>;
-}
diff --git a/packages/demobank-ui/src/index.html b/packages/demobank-ui/src/index.html
index a2154429b..4b3c89a66 100644
--- a/packages/demobank-ui/src/index.html
+++ b/packages/demobank-ui/src/index.html
@@ -16,25 +16,26 @@
@author Sebastian Javier Marchano
-->
<!DOCTYPE html>
-<html
- lang="en"
- class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"
->
+<html lang="en">
<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="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="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>
-
<body>
<div id="app"></div>
- <script type="module" src="index.tsx"></script>
</body>
</html>
diff --git a/packages/demobank-ui/src/index.tsx b/packages/demobank-ui/src/index.tsx
index 4302bb33b..0b88b0393 100644
--- a/packages/demobank-ui/src/index.tsx
+++ b/packages/demobank-ui/src/index.tsx
@@ -1,6 +1,7 @@
import App from "./components/app.js";
export default App;
import { render, h } from "preact";
+import "./scss/main.scss";
const app = document.getElementById("app");
diff --git a/packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx b/packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx
new file mode 100644
index 000000000..521d4255e
--- /dev/null
+++ b/packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx
@@ -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",
+ },
+};
diff --git a/packages/demobank-ui/src/pages/home/QrCodeSection.tsx b/packages/demobank-ui/src/pages/home/QrCodeSection.tsx
new file mode 100644
index 000000000..1d7b3db10
--- /dev/null
+++ b/packages/demobank-ui/src/pages/home/QrCodeSection.tsx
@@ -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>
+ );
+}
diff --git a/packages/demobank-ui/src/pages/home/index.stories.tsx b/packages/demobank-ui/src/pages/home/index.stories.tsx
new file mode 100644
index 000000000..e9ac00a76
--- /dev/null
+++ b/packages/demobank-ui/src/pages/home/index.stories.tsx
@@ -0,0 +1 @@
+export * as qr from "./QrCodeSection.stories.js";
diff --git a/packages/demobank-ui/src/pages/home/index.tsx b/packages/demobank-ui/src/pages/home/index.tsx
index 8f522c07c..8b2ffefac 100644
--- a/packages/demobank-ui/src/pages/home/index.tsx
+++ b/packages/demobank-ui/src/pages/home/index.tsx
@@ -27,13 +27,16 @@ import {
} from "preact/hooks";
import talerLogo from "../../assets/logo-white.svg";
import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
-import { QR } from "../../components/QR.js";
-import { useLocalStorage, useNotNullLocalStorage } from "../../hooks/index.js";
-import { Translate, useTranslator } from "../../i18n/index.js";
-import "../../scss/main.scss";
+import {
+ useLocalStorage,
+ useNotNullLocalStorage,
+} 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 { createHashHistory } from "history";
import Router, { Route, route } from "preact-router";
+import { QrCodeSection } from "./QrCodeSection.js";
interface BankUiSettings {
allowRegistrations: boolean;
@@ -987,7 +990,7 @@ async function registrationCall(
function ErrorBanner(Props: any): VNode | null {
const [pageState, pageStateSetter] = Props.pageState;
- // const i18n = useTranslator();
+ // const { i18n } = useTranslationContext();
if (!pageState.error) return null;
const rval = (
@@ -1041,7 +1044,7 @@ function StatusBanner(Props: any): VNode | null {
}
function BankFrame(Props: any): VNode {
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const [pageState, pageStateSetter] = useContext(PageContext);
console.log("BankFrame state", pageState);
const logOut = (
@@ -1062,7 +1065,7 @@ function BankFrame(Props: any): VNode {
};
});
}}
- >{i18n`Logout`}</a>
+ >{i18n.str`Logout`}</a>
</div>
);
@@ -1080,7 +1083,7 @@ function BankFrame(Props: any): VNode {
class="demobar"
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;">
<h1>
<span class="it">
@@ -1089,7 +1092,7 @@ function BankFrame(Props: any): VNode {
</h1>
{maybeDemoContent(
<p>
- <Translate>
+ <i18n.Translate>
This part of the demo shows how a bank that supports Taler
directly would work. In addition to using your own bank account,
you can also see the transaction history of some{" "}
@@ -1100,14 +1103,14 @@ function BankFrame(Props: any): VNode {
Public Accounts
</a>
.
- </Translate>
+ </i18n.Translate>
</p>,
)}
</div>
<a href="https://taler.net/">
<img
src={talerLogo}
- alt={i18n`Taler logo`}
+ alt={i18n.str`Taler logo`}
height="100"
width="224"
style="margin: 2em 2em"
@@ -1168,7 +1171,7 @@ function PaytoWireTransfer(Props: any): VNode {
const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
undefined,
);
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const { focus, backendState } = Props;
const amountRegex = "^[0-9]+(.[0-9]+)?$";
const ibanRegex = "^[A-Z][A-Z][0-9]+$";
@@ -1193,17 +1196,17 @@ function PaytoWireTransfer(Props: any): VNode {
? undefined
: undefinedIfEmpty({
iban: !submitData.iban
- ? i18n`Missing IBAN`
+ ? i18n.str`Missing 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,
- subject: !submitData.subject ? i18n`Missing subject` : undefined,
+ subject: !submitData.subject ? i18n.str`Missing subject` : undefined,
amount: !submitData.amount
- ? i18n`Missing amount`
+ ? i18n.str`Missing amount`
: !(parsedAmount = Amounts.parse(`${currency}:${submitData.amount}`))
- ? i18n`Amount is not valid`
+ ? i18n.str`Amount is not valid`
: Amounts.isZero(parsedAmount)
- ? i18n`Should be greater than 0`
+ ? i18n.str`Should be greater than 0`
: undefined,
});
@@ -1212,7 +1215,7 @@ function PaytoWireTransfer(Props: any): VNode {
<div>
<div class="pure-form" name="wire-transfer-form">
<p>
- <label for="iban">{i18n`Receiver IBAN:`}</label>&nbsp;
+ <label for="iban">{i18n.str`Receiver IBAN:`}</label>&nbsp;
<input
ref={ref}
type="text"
@@ -1235,7 +1238,7 @@ function PaytoWireTransfer(Props: any): VNode {
isDirty={submitData?.iban !== undefined}
/>
<br />
- <label for="subject">{i18n`Transfer subject:`}</label>&nbsp;
+ <label for="subject">{i18n.str`Transfer subject:`}</label>&nbsp;
<input
type="text"
name="subject"
@@ -1256,7 +1259,7 @@ function PaytoWireTransfer(Props: any): VNode {
isDirty={submitData?.subject !== undefined}
/>
<br />
- <label for="amount">{i18n`Amount:`}</label>&nbsp;
+ <label for="amount">{i18n.str`Amount:`}</label>&nbsp;
<input
type="number"
name="amount"
@@ -1309,7 +1312,7 @@ function PaytoWireTransfer(Props: any): VNode {
...prevState,
error: {
- title: i18n`Field(s) missing.`,
+ title: i18n.str`Field(s) missing.`,
},
}));
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>
</p>
</div>
@@ -1366,18 +1369,18 @@ function PaytoWireTransfer(Props: any): VNode {
const errorsPayto = undefinedIfEmpty({
rawPaytoInput: !rawPaytoInput
- ? i18n`Missing payto address`
+ ? i18n.str`Missing payto address`
: !parsePaytoUri(rawPaytoInput)
- ? i18n`Payto does not follow the pattern`
+ ? i18n.str`Payto does not follow the pattern`
: undefined,
});
return (
<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">
<p>
- <label for="address">{i18n`payto URI:`}</label>&nbsp;
+ <label for="address">{i18n.str`payto URI:`}</label>&nbsp;
<input
name="address"
type="text"
@@ -1386,7 +1389,7 @@ function PaytoWireTransfer(Props: any): VNode {
id="address"
value={rawPaytoInput ?? ""}
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]+)?`}
onInput={(e): void => {
rawPaytoInputSetter(e.currentTarget.value);
@@ -1410,7 +1413,7 @@ function PaytoWireTransfer(Props: any): VNode {
class="pure-button pure-button-primary"
type="submit"
disabled={!!errorsPayto}
- value={i18n`Send`}
+ value={i18n.str`Send`}
onClick={async () => {
// empty string evaluates to false.
if (!rawPaytoInput) {
@@ -1444,7 +1447,7 @@ function PaytoWireTransfer(Props: any): VNode {
}));
}}
>
- {i18n`Use wire-transfer form?`}
+ {i18n.str`Use wire-transfer form?`}
</a>
</p>
</div>
@@ -1459,7 +1462,7 @@ function PaytoWireTransfer(Props: any): VNode {
function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
const [pageState, pageStateSetter] = useContext(PageContext);
const { backendState } = Props;
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const captchaNumbers = {
a: Math.floor(Math.random() * 10),
b: Math.floor(Math.random() * 10),
@@ -1468,15 +1471,15 @@ function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
return (
<Fragment>
- <h1 class="nav">{i18n`Confirm Withdrawal`}</h1>
+ <h1 class="nav">{i18n.str`Confirm Withdrawal`}</h1>
<article>
<div class="challenge-div">
<form class="challenge-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>
<label for="answer">
- {i18n`What is`}&nbsp;
+ {i18n.str`What is`}&nbsp;
<em>
{captchaNumbers.a}&nbsp;+&nbsp;{captchaNumbers.b}
</em>
@@ -1514,12 +1517,12 @@ function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
...prevState,
error: {
- title: i18n`Answer is wrong.`,
+ title: i18n.str`Answer is wrong.`,
},
}));
}}
>
- {i18n`Confirm`}
+ {i18n.str`Confirm`}
</button>
&nbsp;
<button
@@ -1532,18 +1535,18 @@ function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
)
}
>
- {i18n`Cancel`}
+ {i18n.str`Cancel`}
</button>
</p>
</div>
</form>
<div class="hint">
<p>
- <Translate>
+ <i18n.Translate>
A this point, a <b>real</b> bank would ask for an additional
authentication proof (PIN/TAN, one time password, ..), instead
of a simple calculation.
- </Translate>
+ </i18n.Translate>
</p>
</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
* 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:
const [pageState, pageStateSetter] = useContext(PageContext);
const { withdrawalId, talerWithdrawUri, accountLabel, backendState } = Props;
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const abortButton = (
<a
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}`);
@@ -1629,7 +1598,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
...prevState,
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 (
@@ -1643,7 +1612,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
// data didn't arrive yet and wallet didn't communicate:
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:
@@ -1657,7 +1626,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
withdrawalInProgress: false,
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 {
const { backendState, pageStateSetter, focus } = Props;
const currency = useContext(CurrencyContext);
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
let submitAmount = "5.00";
const amountRegex = "^[0-9]+(.[0-9]+)?$";
@@ -1691,7 +1660,8 @@ function WalletWithdraw(Props: any): VNode {
return (
<div id="reserve-form" class="pure-form" name="tform">
<p>
- <label for="withdraw-amount">{i18n`Amount to withdraw:`}</label>&nbsp;
+ <label for="withdraw-amount">{i18n.str`Amount to withdraw:`}</label>
+ &nbsp;
<input
type="number"
ref={ref}
@@ -1724,7 +1694,7 @@ function WalletWithdraw(Props: any): VNode {
id="select-exchange"
class="pure-button pure-button-primary"
type="submit"
- value={i18n`Withdraw`}
+ value={i18n.str`Withdraw`}
onClick={() => {
submitAmount = validateAmount(submitAmount);
/**
@@ -1753,7 +1723,7 @@ function WalletWithdraw(Props: any): VNode {
function PaymentOptions(Props: any): VNode {
const { backendState, pageStateSetter, focus } = Props;
const currency = useContext(CurrencyContext);
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const [tab, setTab] = useState<"charge-wallet" | "wire-transfer">(
"charge-wallet",
@@ -1769,7 +1739,7 @@ function PaymentOptions(Props: any): VNode {
setTab("charge-wallet");
}}
>
- {i18n`Obtain digital cash`}
+ {i18n.str`Obtain digital cash`}
</button>
<button
class={tab === "wire-transfer" ? "tablinks active" : "tablinks"}
@@ -1777,12 +1747,12 @@ function PaymentOptions(Props: any): VNode {
setTab("wire-transfer");
}}
>
- {i18n`Transfer to bank account`}
+ {i18n.str`Transfer to bank account`}
</button>
</div>
{tab === "charge-wallet" && (
<div id="charge-wallet" class="tabcontent active">
- <h3>{i18n`Obtain digital cash`}</h3>
+ <h3>{i18n.str`Obtain digital cash`}</h3>
<WalletWithdraw
backendState={backendState}
focus
@@ -1792,7 +1762,7 @@ function PaymentOptions(Props: any): VNode {
)}
{tab === "wire-transfer" && (
<div id="wire-transfer" class="tabcontent active">
- <h3>{i18n`Transfer to bank account`}</h3>
+ <h3>{i18n.str`Transfer to bank account`}</h3>
<PaytoWireTransfer
backendState={backendState}
focus
@@ -1807,7 +1777,7 @@ function PaymentOptions(Props: any): VNode {
function RegistrationButton(Props: any): VNode {
const { backendStateSetter, pageStateSetter } = Props;
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
if (bankUiSettings.allowRegistrations)
return (
<button
@@ -1816,7 +1786,7 @@ function RegistrationButton(Props: any): VNode {
route("/register");
}}
>
- {i18n`Register`}
+ {i18n.str`Register`}
</button>
);
@@ -1834,7 +1804,7 @@ function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
function LoginForm(Props: any): VNode {
const { backendStateSetter, pageStateSetter } = Props;
const [submitData, submitDataSetter] = useCredentialsRequestType();
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
ref.current?.focus();
@@ -1843,17 +1813,17 @@ function LoginForm(Props: any): VNode {
const errors = !submitData
? undefined
: undefinedIfEmpty({
- username: !submitData.username ? i18n`Missing username` : undefined,
- password: !submitData.password ? i18n`Missing password` : undefined,
+ username: !submitData.username ? i18n.str`Missing username` : undefined,
+ password: !submitData.password ? i18n.str`Missing password` : undefined,
});
return (
<div class="login-div">
<form action="javascript:void(0);" class="login-form">
<div class="pure-form">
- <h2>{i18n`Please login!`}</h2>
+ <h2>{i18n.str`Please login!`}</h2>
<p class="unameFieldLabel loginFieldLabel formFieldLabel">
- <label for="username">{i18n`Username:`}</label>
+ <label for="username">{i18n.str`Username:`}</label>
</p>
<input
ref={ref}
@@ -1872,7 +1842,7 @@ function LoginForm(Props: any): VNode {
}}
/>
<p class="passFieldLabel loginFieldLabel formFieldLabel">
- <label for="password">{i18n`Password:`}</label>
+ <label for="password">{i18n.str`Password:`}</label>
</p>
<input
type="password"
@@ -1919,7 +1889,7 @@ function LoginForm(Props: any): VNode {
});
}}
>
- {i18n`Login`}
+ {i18n.str`Login`}
</button>
{RegistrationButton(Props)}
</div>
@@ -1935,30 +1905,30 @@ function RegistrationForm(Props: any): VNode {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [pageState, pageStateSetter] = useContext(PageContext);
const [submitData, submitDataSetter] = useCredentialsRequestType();
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const errors = !submitData
? undefined
: undefinedIfEmpty({
- username: !submitData.username ? i18n`Missing username` : undefined,
- password: !submitData.password ? i18n`Missing password` : undefined,
+ username: !submitData.username ? i18n.str`Missing username` : undefined,
+ password: !submitData.password ? i18n.str`Missing password` : undefined,
repeatPassword: !submitData.repeatPassword
- ? i18n`Missing password`
+ ? i18n.str`Missing password`
: submitData.repeatPassword !== submitData.password
- ? i18n`Password don't match`
+ ? i18n.str`Password don't match`
: undefined,
});
return (
<Fragment>
- <h1 class="nav">{i18n`Welcome to ${bankUiSettings.bankName}!`}</h1>
+ <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
<article>
<div class="register-div">
<form action="javascript:void(0);" class="register-form">
<div class="pure-form">
- <h2>{i18n`Please register!`}</h2>
+ <h2>{i18n.str`Please register!`}</h2>
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
- <label for="register-un">{i18n`Username:`}</label>
+ <label for="register-un">{i18n.str`Username:`}</label>
</p>
<input
id="register-un"
@@ -1976,7 +1946,7 @@ function RegistrationForm(Props: any): VNode {
/>
<br />
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
- <label for="register-pw">{i18n`Password:`}</label>
+ <label for="register-pw">{i18n.str`Password:`}</label>
</p>
<input
type="password"
@@ -1993,7 +1963,7 @@ function RegistrationForm(Props: any): VNode {
}}
/>
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
- <label for="register-repeat">{i18n`Repeat Password:`}</label>
+ <label for="register-repeat">{i18n.str`Repeat Password:`}</label>
</p>
<input
type="password"
@@ -2012,7 +1982,7 @@ function RegistrationForm(Props: any): VNode {
/>
<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)
<input
name="phone"
@@ -2054,7 +2024,7 @@ function RegistrationForm(Props: any): VNode {
});
}}
>
- {i18n`Register`}
+ {i18n.str`Register`}
</button>
{/* FIXME: should use a different color */}
<button
@@ -2068,7 +2038,7 @@ function RegistrationForm(Props: any): VNode {
route("/account");
}}
>
- {i18n`Cancel`}
+ {i18n.str`Cancel`}
</button>
</div>
</form>
@@ -2083,7 +2053,7 @@ function RegistrationForm(Props: any): VNode {
*/
function Transactions(Props: any): VNode {
const { pageNumber, accountLabel, balanceValue } = Props;
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const { data, error, mutate } = useSWR(
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
);
@@ -2114,10 +2084,10 @@ function Transactions(Props: any): VNode {
<table class="pure-table pure-table-striped">
<thead>
<tr>
- <th>{i18n`Date`}</th>
- <th>{i18n`Amount`}</th>
- <th>{i18n`Counterpart`}</th>
- <th>{i18n`Subject`}</th>
+ <th>{i18n.str`Date`}</th>
+ <th>{i18n.str`Amount`}</th>
+ <th>{i18n.str`Counterpart`}</th>
+ <th>{i18n.str`Subject`}</th>
</tr>
</thead>
<tbody>
@@ -2178,7 +2148,7 @@ function Account(Props: any): VNode {
talerWithdrawUri,
timestamp,
} = pageState;
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
useEffect(() => {
mutate();
}, [timestamp]);
@@ -2206,7 +2176,7 @@ function Account(Props: any): VNode {
isLoggedIn: false,
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,
error: {
- title: i18n`Wrong credentials given.`,
+ title: i18n.str`Wrong credentials given.`,
},
}));
return <p>Wrong credentials...</p>;
@@ -2244,7 +2214,7 @@ function Account(Props: any): VNode {
isLoggedIn: false,
error: {
- title: i18n`Account information could not be retrieved.`,
+ title: i18n.str`Account information could not be retrieved.`,
debug: JSON.stringify(error),
},
}));
@@ -2287,14 +2257,14 @@ function Account(Props: any): VNode {
<BankFrame>
<div>
<h1 class="nav welcome-text">
- <Translate>
+ <i18n.Translate>
Welcome, {accountLabel} ({getIbanFromPayto(data.paytoUri)})!
- </Translate>
+ </i18n.Translate>
</h1>
</div>
<section id="assets">
<div class="asset-summary">
- <h2>{i18n`Bank account balance`}</h2>
+ <h2>{i18n.str`Bank account balance`}</h2>
<div class="large-amount amount">
{data.balance.credit_debit_indicator == "debit" ? <b>-</b> : null}
<span class="value">{`${balanceValue}`}</span>&nbsp;
@@ -2304,7 +2274,7 @@ function Account(Props: any): VNode {
</section>
<section id="payments">
<div class="payments">
- <h2>{i18n`Payments`}</h2>
+ <h2>{i18n.str`Payments`}</h2>
{/* FIXME: turn into button! */}
<CurrencyContext.Provider value={balance.currency}>
{Props.children}
@@ -2317,7 +2287,7 @@ function Account(Props: any): VNode {
</section>
<section id="main">
<article>
- <h2>{i18n`Latest transactions:`}</h2>
+ <h2>{i18n.str`Latest transactions:`}</h2>
<Transactions
balanceValue={balanceValue}
pageNumber="0"
@@ -2379,7 +2349,7 @@ function SWRWithoutCredentials(Props: any): VNode {
function PublicHistories(Props: any): VNode {
const [showAccount, setShowAccount] = useShowPublicAccount();
const { data, error } = useSWR("access-api/public-accounts");
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
if (typeof error !== "undefined") {
console.log("account error", error);
@@ -2391,7 +2361,7 @@ function PublicHistories(Props: any): VNode {
showPublicHistories: false,
error: {
- title: i18n`List of public accounts was not found.`,
+ title: i18n.str`List of public accounts was not found.`,
debug: JSON.stringify(error),
},
}));
@@ -2403,7 +2373,7 @@ function PublicHistories(Props: any): VNode {
showPublicHistories: false,
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),
},
}));
@@ -2450,7 +2420,7 @@ function PublicHistories(Props: any): VNode {
return (
<Fragment>
- <h1 class="nav">{i18n`History of public accounts`}</h1>
+ <h1 class="nav">{i18n.str`History of public accounts`}</h1>
<section id="main">
<article>
<div class="pure-menu pure-menu-horizontal" name="accountMenu">
@@ -2471,7 +2441,7 @@ function PublicHistories(Props: any): VNode {
function PublicHistoriesPage(): VNode {
// const [backendState, backendStateSetter] = useBackendState();
const [pageState, pageStateSetter] = usePageState();
- // const i18n = useTranslator();
+ // const { i18n } = useTranslationContext();
return (
<SWRWithoutCredentials baseUrl={getBankBackendBaseUrl()}>
<PageContext.Provider value={[pageState, pageStateSetter]}>
@@ -2499,12 +2469,12 @@ function PublicHistoriesPage(): VNode {
function RegistrationPage(): VNode {
const [backendState, backendStateSetter] = useBackendState();
const [pageState, pageStateSetter] = usePageState();
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
if (!bankUiSettings.allowRegistrations) {
return (
<PageContext.Provider value={[pageState, pageStateSetter]}>
<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>
</PageContext.Provider>
);
@@ -2521,13 +2491,13 @@ function RegistrationPage(): VNode {
function AccountPage(): VNode {
const [backendState, backendStateSetter] = useBackendState();
const [pageState, pageStateSetter] = usePageState();
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
if (!pageState.isLoggedIn) {
return (
<PageContext.Provider value={[pageState, pageStateSetter]}>
<BankFrame>
- <h1 class="nav">{i18n`Welcome to ${bankUiSettings.bankName}!`}</h1>
+ <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
<LoginForm
pageStateSetter={pageStateSetter}
backendStateSetter={backendStateSetter}
@@ -2543,7 +2513,7 @@ function AccountPage(): VNode {
isLoggedIn: false,
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>;
diff --git a/packages/demobank-ui/src/scss/main.scss b/packages/demobank-ui/src/scss/main.scss
index ebe36b9b4..b92260af0 100644
--- a/packages/demobank-ui/src/scss/main.scss
+++ b/packages/demobank-ui/src/scss/main.scss
@@ -1,4 +1,4 @@
-@import "pure";
-@import "bank";
-@import "demo";
-@import "colors-bank";
+@use "pure";
+@use "bank";
+@use "demo";
+@use "colors-bank";
diff --git a/packages/demobank-ui/src/stories.tsx b/packages/demobank-ui/src/stories.tsx
new file mode 100644
index 000000000..52d42577d
--- /dev/null
+++ b/packages/demobank-ui/src/stories.tsx
@@ -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();
+}
diff --git a/packages/demobank-ui/static/index.html b/packages/demobank-ui/static/index.html
deleted file mode 100644
index 0fa5215d3..000000000
--- a/packages/demobank-ui/static/index.html
+++ /dev/null
@@ -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>