use translation context from web-utils, don't use match react-router since is broken

This commit is contained in:
Sebastian 2022-12-20 17:45:24 -03:00
parent 382e66b179
commit c59f9a2556
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
81 changed files with 1165 additions and 1420 deletions

View File

@ -64,8 +64,7 @@ function getFilesInDirectory(startPath, regex) {
return result return result
} }
const allTestFiles = getFilesInDirectory(path.join(BASE, 'tests'), /.test.tsx?$/) const allTestFiles = getFilesInDirectory(path.join(BASE, 'src'), /.test.tsx?$/)
const entryPoints = ["src/index.tsx", "src/stories.tsx", ...allTestFiles]; const entryPoints = ["src/index.tsx", "src/stories.tsx", ...allTestFiles];
let GIT_ROOT = BASE; let GIT_ROOT = BASE;

View File

@ -3,12 +3,13 @@
"name": "@gnu-taler/merchant-backoffice", "name": "@gnu-taler/merchant-backoffice",
"version": "0.0.4", "version": "0.0.4",
"license": "MIT", "license": "MIT",
"type": "module",
"scripts": { "scripts": {
"build": "./build.mjs", "build": "./build.mjs",
"check": "tsc", "check": "tsc",
"compile": "tsc && ./build.mjs", "compile": "tsc && ./build.mjs",
"dev": "preact watch --port ${PORT:=8080} --no-sw --no-esm", "dev": "preact watch --port ${PORT:=8080} --no-sw --no-esm",
"test": "pnpm compile && mocha --require source-map-support/register '*.test.js' 'dist/**/test.js'", "test": "pnpm compile && mocha --require source-map-support/register 'dist/**/*.test.js' 'dist/**/test.js'",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'", "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
"i18n:extract": "pogen extract", "i18n:extract": "pogen extract",
"i18n:merge": "pogen merge", "i18n:merge": "pogen merge",
@ -38,7 +39,7 @@
"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",
"swr": "1.3.0", "swr": "1.3.0",
@ -79,22 +80,5 @@
"sass": "1.56.1", "sass": "1.56.1",
"typedoc": "^0.20.36", "typedoc": "^0.20.36",
"typescript": "4.8.4" "typescript": "4.8.4"
},
"jest": {
"preset": "jest-preset-preact",
"transformIgnorePatterns": [
"node_modules/.pnpm/(?!(@gnu-taler\\+taler-util))",
"\\.pnp\\.[^\\/]+$"
],
"setupFiles": [
"<rootDir>/tests/__mocks__/browserMocks.ts",
"<rootDir>/tests/__mocks__/setupTests.ts"
],
"moduleNameMapper": {
"\\.(css|less)$": "identity-obj-proxy"
},
"transform": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|po)$": "<rootDir>/tests/__mocks__/fileTransformer.js"
}
} }
} }

View File

@ -19,6 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import {
TranslationProvider,
useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { route } from "preact-router"; import { route } from "preact-router";
import { useMemo } from "preact/hooks"; import { useMemo } from "preact/hooks";
@ -33,16 +37,15 @@ import {
useBackendContext, useBackendContext,
} from "./context/backend.js"; } from "./context/backend.js";
import { ConfigContextProvider } from "./context/config.js"; import { ConfigContextProvider } from "./context/config.js";
import { TranslationProvider } from "./context/translation.js";
import { useBackendConfig } from "./hooks/backend.js"; import { useBackendConfig } from "./hooks/backend.js";
import { useTranslator } from "./i18n/index.js"; import { strings } from "./i18n/strings.js";
import LoginPage from "./paths/login/index.js"; import LoginPage from "./paths/login/index.js";
export function Application(): VNode { export function Application(): VNode {
return ( return (
// <FetchContextProvider> // <FetchContextProvider>
<BackendContextProvider> <BackendContextProvider>
<TranslationProvider> <TranslationProvider source={strings}>
<ApplicationStatusRoutes /> <ApplicationStatusRoutes />
</TranslationProvider> </TranslationProvider>
</BackendContextProvider> </BackendContextProvider>
@ -53,7 +56,7 @@ export function Application(): VNode {
function ApplicationStatusRoutes(): VNode { function ApplicationStatusRoutes(): VNode {
const { updateLoginStatus, triedToLog } = useBackendContext(); const { updateLoginStatus, triedToLog } = useBackendContext();
const result = useBackendConfig(); const result = useBackendConfig();
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const updateLoginInfoAndGoToRoot = (url: string, token?: string) => { const updateLoginInfoAndGoToRoot = (url: string, token?: string) => {
updateLoginStatus(url, token); updateLoginStatus(url, token);
@ -88,7 +91,7 @@ function ApplicationStatusRoutes(): VNode {
<NotYetReadyAppMenu title="Error" /> <NotYetReadyAppMenu title="Error" />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n`Server not found`, message: i18n.str`Server not found`,
type: "ERROR", type: "ERROR",
description: `Check your url`, description: `Check your url`,
}} }}
@ -103,9 +106,9 @@ function ApplicationStatusRoutes(): VNode {
<NotYetReadyAppMenu title="Error" /> <NotYetReadyAppMenu title="Error" />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n`Couldn't access the server`, message: i18n.str`Couldn't access the server`,
type: "ERROR", type: "ERROR",
description: i18n`Got message ${result.message} from ${result.info?.url}`, description: i18n.str`Got message ${result.message} from ${result.info?.url}`,
}} }}
/> />
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} /> <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
@ -120,9 +123,9 @@ function ApplicationStatusRoutes(): VNode {
<NotYetReadyAppMenu title="Error" /> <NotYetReadyAppMenu title="Error" />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n`Unexpected Error`, message: i18n.str`Unexpected Error`,
type: "ERROR", type: "ERROR",
description: i18n`Got message ${result.message} from ${result.info?.url}`, description: i18n.str`Got message ${result.message} from ${result.info?.url}`,
}} }}
/> />
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} /> <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
@ -130,7 +133,7 @@ function ApplicationStatusRoutes(): VNode {
); );
return ( return (
<div id="app" class="has-navbar-fixed-top"> <div class="has-navbar-fixed-top">
<ConfigContextProvider value={ctx}> <ConfigContextProvider value={ctx}>
<ApplicationReadyRoutes /> <ApplicationReadyRoutes />
</ConfigContextProvider> </ConfigContextProvider>

View File

@ -18,23 +18,23 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { createHashHistory } from "history";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import Router, { Route, route } from "preact-router"; import Router, { Route, route } from "preact-router";
import { useState } from "preact/hooks";
import {
NotificationCard,
NotYetReadyAppMenu,
} from "./components/menu/index.js";
import { useBackendContext } from "./context/backend.js"; import { useBackendContext } from "./context/backend.js";
import { useBackendInstancesTestForAdmin } from "./hooks/backend.js"; import { useBackendInstancesTestForAdmin } from "./hooks/backend.js";
import { InstanceRoutes } from "./InstanceRoutes.js"; import { InstanceRoutes } from "./InstanceRoutes.js";
import LoginPage from "./paths/login/index.js"; import LoginPage from "./paths/login/index.js";
import { INSTANCE_ID_LOOKUP } from "./utils/constants.js"; import { INSTANCE_ID_LOOKUP } from "./utils/constants.js";
import {
NotYetReadyAppMenu,
NotificationCard,
} from "./components/menu/index.js";
import { useTranslator } from "./i18n/index.js";
import { createHashHistory } from "history";
import { useState } from "preact/hooks";
export function ApplicationReadyRoutes(): VNode { export function ApplicationReadyRoutes(): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const { const {
url: backendURL, url: backendURL,
updateLoginStatus, updateLoginStatus,
@ -54,8 +54,8 @@ export function ApplicationReadyRoutes(): VNode {
<NotYetReadyAppMenu title="Login" onLogout={clearTokenAndGoToRoot} /> <NotYetReadyAppMenu title="Login" onLogout={clearTokenAndGoToRoot} />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n`Access denied`, message: i18n.str`Access denied`,
description: i18n`Check your token is valid`, description: i18n.str`Check your token is valid`,
type: "ERROR", type: "ERROR",
}} }}
/> />
@ -81,8 +81,8 @@ export function ApplicationReadyRoutes(): VNode {
<NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} /> <NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n`Couldn't access the server.`, message: i18n.str`Couldn't access the server.`,
description: i18n`Could not infer instance id from url ${backendURL}`, description: i18n.str`Could not infer instance id from url ${backendURL}`,
type: "ERROR", type: "ERROR",
}} }}
/> />
@ -108,7 +108,12 @@ export function ApplicationReadyRoutes(): VNode {
); );
} }
function DefaultMainRoute({ instance, admin, instanceNameByBackendURL }: any) { function DefaultMainRoute({
instance,
admin,
instanceNameByBackendURL,
url, //from preact-router
}: any): VNode {
const [instanceName, setInstanceName] = useState( const [instanceName, setInstanceName] = useState(
instanceNameByBackendURL || instance || "default", instanceNameByBackendURL || instance || "default",
); );
@ -116,6 +121,7 @@ function DefaultMainRoute({ instance, admin, instanceNameByBackendURL }: any) {
return ( return (
<InstanceRoutes <InstanceRoutes
admin={admin} admin={admin}
path={url}
id={instanceName} id={instanceName}
setInstanceName={setInstanceName} setInstanceName={setInstanceName}
/> />

View File

@ -19,6 +19,8 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { format } from "date-fns";
import { Fragment, FunctionComponent, h, VNode } from "preact"; import { Fragment, FunctionComponent, h, VNode } from "preact";
import { Route, route, Router } from "preact-router"; import { Route, route, Router } from "preact-router";
import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
@ -26,39 +28,37 @@ import { Loading } from "./components/exception/loading.js";
import { Menu, NotificationCard } from "./components/menu/index.js"; import { Menu, NotificationCard } from "./components/menu/index.js";
import { useBackendContext } from "./context/backend.js"; import { useBackendContext } from "./context/backend.js";
import { InstanceContextProvider } from "./context/instance.js"; import { InstanceContextProvider } from "./context/instance.js";
import { HttpError } from "./hooks/backend.js";
import { import {
useBackendDefaultToken, useBackendDefaultToken,
useBackendInstanceToken, useBackendInstanceToken,
useLocalStorage, useLocalStorage,
} from "./hooks/index.js"; } from "./hooks/index.js";
import { HttpError } from "./hooks/backend.js"; import { useInstanceKYCDetails } from "./hooks/instance.js";
import { Translate, useTranslator } from "./i18n/index.js";
import InstanceCreatePage from "./paths/admin/create/index.js"; import InstanceCreatePage from "./paths/admin/create/index.js";
import InstanceListPage from "./paths/admin/list/index.js"; import InstanceListPage from "./paths/admin/list/index.js";
import ListKYCPage from "./paths/instance/kyc/list/index.js";
import OrderCreatePage from "./paths/instance/orders/create/index.js"; import OrderCreatePage from "./paths/instance/orders/create/index.js";
import OrderDetailsPage from "./paths/instance/orders/details/index.js"; import OrderDetailsPage from "./paths/instance/orders/details/index.js";
import OrderListPage from "./paths/instance/orders/list/index.js"; import OrderListPage from "./paths/instance/orders/list/index.js";
import ProductCreatePage from "./paths/instance/products/create/index.js"; import ProductCreatePage from "./paths/instance/products/create/index.js";
import ProductListPage from "./paths/instance/products/list/index.js"; import ProductListPage from "./paths/instance/products/list/index.js";
import ProductUpdatePage from "./paths/instance/products/update/index.js"; import ProductUpdatePage from "./paths/instance/products/update/index.js";
import TransferListPage from "./paths/instance/transfers/list/index.js";
import TransferCreatePage from "./paths/instance/transfers/create/index.js";
import TemplateListPage from "./paths/instance/templates/list/index.js";
import TemplateUpdatePage from "./paths/instance/templates/update/index.js";
import TemplateCreatePage from "./paths/instance/templates/create/index.js";
import ReservesCreatePage from "./paths/instance/reserves/create/index.js"; import ReservesCreatePage from "./paths/instance/reserves/create/index.js";
import ReservesDetailsPage from "./paths/instance/reserves/details/index.js"; import ReservesDetailsPage from "./paths/instance/reserves/details/index.js";
import ReservesListPage from "./paths/instance/reserves/list/index.js"; import ReservesListPage from "./paths/instance/reserves/list/index.js";
import ListKYCPage from "./paths/instance/kyc/list/index.js"; import TemplateCreatePage from "./paths/instance/templates/create/index.js";
import TemplateListPage from "./paths/instance/templates/list/index.js";
import TemplateUpdatePage from "./paths/instance/templates/update/index.js";
import TransferCreatePage from "./paths/instance/transfers/create/index.js";
import TransferListPage from "./paths/instance/transfers/list/index.js";
import InstanceUpdatePage, { import InstanceUpdatePage, {
Props as InstanceUpdatePageProps,
AdminUpdate as InstanceAdminUpdatePage, AdminUpdate as InstanceAdminUpdatePage,
Props as InstanceUpdatePageProps,
} from "./paths/instance/update/index.js"; } from "./paths/instance/update/index.js";
import LoginPage from "./paths/login/index.js"; import LoginPage from "./paths/login/index.js";
import NotFoundPage from "./paths/notfound/index.js"; import NotFoundPage from "./paths/notfound/index.js";
import { Notification } from "./utils/types.js"; import { Notification } from "./utils/types.js";
import { useInstanceKYCDetails } from "./hooks/instance.js";
import { format } from "date-fns";
export enum InstancePaths { export enum InstancePaths {
// details = '/', // details = '/',
@ -99,10 +99,16 @@ export enum AdminPaths {
export interface Props { export interface Props {
id: string; id: string;
admin?: boolean; admin?: boolean;
path: string;
setInstanceName: (s: string) => void; setInstanceName: (s: string) => void;
} }
export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode { export function InstanceRoutes({
id,
admin,
path,
setInstanceName,
}: Props): VNode {
const [_, updateDefaultToken] = useBackendDefaultToken(); const [_, updateDefaultToken] = useBackendDefaultToken();
const [token, updateToken] = useBackendInstanceToken(id); const [token, updateToken] = useBackendInstanceToken(id);
const { const {
@ -113,7 +119,7 @@ export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode {
const cleaner = useCallback(() => { const cleaner = useCallback(() => {
updateToken(undefined); updateToken(undefined);
}, [id]); }, [id]);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
type GlobalNotifState = (Notification & { to: string }) | undefined; type GlobalNotifState = (Notification & { to: string }) | undefined;
const [globalNotification, setGlobalNotification] = const [globalNotification, setGlobalNotification] =
@ -144,8 +150,8 @@ export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode {
function ServerErrorRedirectTo(to: InstancePaths | AdminPaths) { function ServerErrorRedirectTo(to: InstancePaths | AdminPaths) {
return function ServerErrorRedirectToImpl(error: HttpError) { return function ServerErrorRedirectToImpl(error: HttpError) {
setGlobalNotification({ setGlobalNotification({
message: i18n`The backend reported a problem: HTTP status #${error.status}`, message: i18n.str`The backend reported a problem: HTTP status #${error.status}`,
description: i18n`Diagnostic from ${error.info?.url} is "${error.message}"`, description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
details: details:
error.clientError || error.serverError error.clientError || error.serverError
? error.error?.detail ? error.error?.detail
@ -161,8 +167,8 @@ export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode {
<Fragment> <Fragment>
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n`Access denied`, message: i18n.str`Access denied`,
description: i18n`The access token provided is invalid.`, description: i18n.str`The access token provided is invalid.`,
type: "ERROR", type: "ERROR",
}} }}
/> />
@ -177,8 +183,8 @@ export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode {
<Fragment> <Fragment>
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n`No 'default' instance configured yet.`, message: i18n.str`No 'default' instance configured yet.`,
description: i18n`Create a 'default' instance to begin using the merchant backoffice.`, description: i18n.str`Create a 'default' instance to begin using the merchant backoffice.`,
type: "INFO", type: "INFO",
}} }}
/> />
@ -208,6 +214,7 @@ export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode {
<Menu <Menu
instance={id} instance={id}
admin={admin} admin={admin}
path={path}
onLogout={clearTokenAndGoToRoot} onLogout={clearTokenAndGoToRoot}
setInstanceName={setInstanceName} setInstanceName={setInstanceName}
/> />
@ -488,7 +495,7 @@ function AdminInstanceUpdatePage({
() => ({ id, token, admin: true, changeToken }), () => ({ id, token, admin: true, changeToken }),
[id, token], [id, token],
); );
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<InstanceContextProvider value={value}> <InstanceContextProvider value={value}>
@ -500,8 +507,8 @@ function AdminInstanceUpdatePage({
<Fragment> <Fragment>
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n`The backend reported a problem: HTTP status #${error.status}`, message: i18n.str`The backend reported a problem: HTTP status #${error.status}`,
description: i18n`Diagnostic from ${error.info?.url} is "${error.message}"`, description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
details: details:
error.clientError || error.serverError error.clientError || error.serverError
? error.error?.detail ? error.error?.detail
@ -518,8 +525,8 @@ function AdminInstanceUpdatePage({
<Fragment> <Fragment>
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n`Access denied`, message: i18n.str`Access denied`,
description: i18n`The access token provided is invalid`, description: i18n.str`The access token provided is invalid`,
type: "ERROR", type: "ERROR",
}} }}
/> />
@ -534,6 +541,7 @@ function AdminInstanceUpdatePage({
function KycBanner(): VNode { function KycBanner(): VNode {
const kycStatus = useInstanceKYCDetails(); const kycStatus = useInstanceKYCDetails();
const { i18n } = useTranslationContext();
const today = format(new Date(), "yyyy-MM-dd"); const today = format(new Date(), "yyyy-MM-dd");
const [lastHide, setLastHide] = useLocalStorage("kyc-last-hide"); const [lastHide, setLastHide] = useLocalStorage("kyc-last-hide");
const hasBeenHidden = today === lastHide; const hasBeenHidden = today === lastHide;
@ -552,7 +560,7 @@ function KycBanner(): VNode {
</p> </p>
<div class="buttons is-right"> <div class="buttons is-right">
<button class="button" onClick={() => setLastHide(today)}> <button class="button" onClick={() => setLastHide(today)}>
<Translate>Hide for today</Translate> <i18n.Translate>Hide for today</i18n.Translate>
</button> </button>
</div> </div>
</div> </div>

View File

@ -22,7 +22,7 @@
import { ComponentChildren, h } from "preact"; import { ComponentChildren, h } from "preact";
import { LoadingModal } from "../modal/index.js"; import { LoadingModal } from "../modal/index.js";
import { useAsync } from "../../hooks/async.js"; import { useAsync } from "../../hooks/async.js";
import { Translate } from "../../i18n/index.js"; import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
type Props = { type Props = {
children: ComponentChildren; children: ComponentChildren;
@ -33,14 +33,14 @@ type Props = {
export function AsyncButton({ onClick, disabled, children, ...rest }: Props) { export function AsyncButton({ onClick, disabled, children, ...rest }: Props) {
const { isSlow, isLoading, request, cancel } = useAsync(onClick); const { isSlow, isLoading, request, cancel } = useAsync(onClick);
const { i18n } = useTranslationContext();
if (isSlow) { if (isSlow) {
return <LoadingModal onCancel={cancel} />; return <LoadingModal onCancel={cancel} />;
} }
if (isLoading) { if (isLoading) {
return ( return (
<button class="button"> <button class="button">
<Translate>Loading...</Translate> <i18n.Translate>Loading...</i18n.Translate>
</button> </button>
); );
} }

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useInstanceContext } from "../../context/instance.js"; import { useInstanceContext } from "../../context/instance.js";
import { Translate, useTranslator } from "../../i18n/index.js";
import { Notification } from "../../utils/types.js"; import { Notification } from "../../utils/types.js";
interface Props { interface Props {
@ -51,7 +51,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
const [token, setToken] = useState(currentToken); const [token, setToken] = useState(currentToken);
const [url, setURL] = useState(backendUrl); const [url, setURL] = useState(backendUrl);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="columns is-centered"> <div class="columns is-centered">
@ -61,13 +61,13 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
class="modal-card-head" class="modal-card-head"
style={{ border: "1px solid", borderBottom: 0 }} style={{ border: "1px solid", borderBottom: 0 }}
> >
<p class="modal-card-title">{i18n`Login required`}</p> <p class="modal-card-title">{i18n.str`Login required`}</p>
</header> </header>
<section <section
class="modal-card-body" class="modal-card-body"
style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }} style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }}
> >
<Translate>Please enter your access token.</Translate> <i18n.Translate>Please enter your access token.</i18n.Translate>
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label">URL</label> <label class="label">URL</label>
@ -95,7 +95,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label"> <label class="label">
<Translate>Access Token</Translate> <i18n.Translate>Access Token</i18n.Translate>
</label> </label>
</div> </div>
<div class="field-body"> <div class="field-body">
@ -133,7 +133,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
onConfirm(url, normalizeToken(token)); onConfirm(url, normalizeToken(token));
}} }}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</button> </button>
</footer> </footer>
</div> </div>

View File

@ -18,9 +18,9 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Translate, useTranslator } from "../../i18n/index.js";
import { InputProps, useField } from "./useField.js"; import { InputProps, useField } from "./useField.js";
export interface Props<T> extends InputProps<T> { export interface Props<T> extends InputProps<T> {
@ -52,7 +52,7 @@ export function InputArray<T>({
const array: any[] = (value ? value! : []) as any; const array: any[] = (value ? value! : []) as any;
const [currentValue, setCurrentValue] = useState(""); const [currentValue, setCurrentValue] = useState("");
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="field is-horizontal"> <div class="field is-horizontal">
@ -99,7 +99,7 @@ export function InputArray<T>({
const v = fromStr(currentValue); const v = fromStr(currentValue);
if (!isValid(v)) { if (!isValid(v)) {
setLocalError( setLocalError(
i18n`The value ${v} is invalid for a payment url`, i18n.str`The value ${v} is invalid for a payment url`,
); );
return; return;
} }
@ -107,9 +107,9 @@ export function InputArray<T>({
onChange([v, ...array] as any); onChange([v, ...array] as any);
setCurrentValue(""); setCurrentValue("");
}} }}
data-tooltip={i18n`add element to the list`} data-tooltip={i18n.str`add element to the list`}
> >
<Translate>add</Translate> <i18n.Translate>add</i18n.Translate>
</button> </button>
</p> </p>
</div> </div>

View File

@ -18,7 +18,7 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { ComponentChildren, h } from "preact"; import { ComponentChildren, h, VNode } from "preact";
import { useConfigContext } from "../../context/config.js"; import { useConfigContext } from "../../context/config.js";
import { Amount } from "../../declaration.js"; import { Amount } from "../../declaration.js";
import { InputWithAddon } from "./InputWithAddon.js"; import { InputWithAddon } from "./InputWithAddon.js";
@ -42,7 +42,7 @@ export function InputCurrency<T>({
addonAfter, addonAfter,
children, children,
side, side,
}: Props<keyof T>) { }: Props<keyof T>): VNode {
const config = useConfigContext(); const config = useConfigContext();
return ( return (
<InputWithAddon<T> <InputWithAddon<T>
@ -60,7 +60,8 @@ export function InputCurrency<T>({
toStr={(v?: Amount) => v?.split(":")[1] || ""} toStr={(v?: Amount) => v?.split(":")[1] || ""}
fromStr={(v: string) => (!v ? "" : `${config.currency}:${v}`)} fromStr={(v: string) => (!v ? "" : `${config.currency}:${v}`)}
inputExtra={{ min: 0 }} inputExtra={{ min: 0 }}
children={children} >
/> {children}
</InputWithAddon>
); );
} }

View File

@ -18,10 +18,10 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { format } from "date-fns"; import { format } from "date-fns";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Translate, useTranslator } from "../../i18n/index.js";
import { DatePicker } from "../picker/DatePicker.js"; import { DatePicker } from "../picker/DatePicker.js";
import { InputProps, useField } from "./useField.js"; import { InputProps, useField } from "./useField.js";
@ -43,7 +43,7 @@ export function InputDate<T>({
withTimestampSupport, withTimestampSupport,
}: Props<keyof T>): VNode { }: Props<keyof T>): VNode {
const [opened, setOpened] = useState(false); const [opened, setOpened] = useState(false);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const { error, required, value, onChange } = useField<T>(name); const { error, required, value, onChange } = useField<T>(name);
@ -120,25 +120,25 @@ export function InputDate<T>({
<span <span
data-tooltip={ data-tooltip={
withTimestampSupport withTimestampSupport
? i18n`change value to unknown date` ? i18n.str`change value to unknown date`
: i18n`change value to empty` : i18n.str`change value to empty`
} }
> >
<button <button
class="button is-info mr-3" class="button is-info mr-3"
onClick={() => onChange(undefined as any)} onClick={() => onChange(undefined as any)}
> >
<Translate>clear</Translate> <i18n.Translate>clear</i18n.Translate>
</button> </button>
</span> </span>
)} )}
{withTimestampSupport && ( {withTimestampSupport && (
<span data-tooltip={i18n`change value to never`}> <span data-tooltip={i18n.str`change value to never`}>
<button <button
class="button is-info" class="button is-info"
onClick={() => onChange({ t_s: "never" } as any)} onClick={() => onChange({ t_s: "never" } as any)}
> >
<Translate>never</Translate> <i18n.Translate>never</i18n.Translate>
</button> </button>
</span> </span>
)} )}

View File

@ -18,10 +18,10 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { intervalToDuration, formatDuration } from "date-fns"; import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { formatDuration, intervalToDuration } from "date-fns";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Translate, useTranslator } from "../../i18n/index.js";
import { SimpleModal } from "../modal/index.js"; import { SimpleModal } from "../modal/index.js";
import { DurationPicker } from "../picker/DurationPicker.js"; import { DurationPicker } from "../picker/DurationPicker.js";
import { InputProps, useField } from "./useField.js"; import { InputProps, useField } from "./useField.js";
@ -43,14 +43,14 @@ export function InputDuration<T>({
withForever, withForever,
}: Props<keyof T>): VNode { }: Props<keyof T>): VNode {
const [opened, setOpened] = useState(false); const [opened, setOpened] = useState(false);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const { error, required, value, onChange } = useField<T>(name); const { error, required, value, onChange } = useField<T>(name);
let strValue = ""; let strValue = "";
if (!value) { if (!value) {
strValue = ""; strValue = "";
} else if (value.d_us === "forever") { } else if (value.d_us === "forever") {
strValue = i18n`forever`; strValue = i18n.str`forever`;
} else { } else {
strValue = formatDuration( strValue = formatDuration(
intervalToDuration({ start: 0, end: value.d_us / 1000 }), intervalToDuration({ start: 0, end: value.d_us / 1000 }),
@ -59,17 +59,17 @@ export function InputDuration<T>({
formatDistance: (name, value) => { formatDistance: (name, value) => {
switch (name) { switch (name) {
case "xMonths": case "xMonths":
return i18n`${value}M`; return i18n.str`${value}M`;
case "xYears": case "xYears":
return i18n`${value}Y`; return i18n.str`${value}Y`;
case "xDays": case "xDays":
return i18n`${value}d`; return i18n.str`${value}d`;
case "xHours": case "xHours":
return i18n`${value}h`; return i18n.str`${value}h`;
case "xMinutes": case "xMinutes":
return i18n`${value}min`; return i18n.str`${value}min`;
case "xSeconds": case "xSeconds":
return i18n`${value}sec`; return i18n.str`${value}sec`;
} }
}, },
localize: { localize: {
@ -134,22 +134,22 @@ export function InputDuration<T>({
{error && <p class="help is-danger">{error}</p>} {error && <p class="help is-danger">{error}</p>}
</div> </div>
{withForever && ( {withForever && (
<span data-tooltip={i18n`change value to never`}> <span data-tooltip={i18n.str`change value to never`}>
<button <button
class="button is-info mr-3" class="button is-info mr-3"
onClick={() => onChange({ d_us: "forever" } as any)} onClick={() => onChange({ d_us: "forever" } as any)}
> >
<Translate>forever</Translate> <i18n.Translate>forever</i18n.Translate>
</button> </button>
</span> </span>
)} )}
{!readonly && ( {!readonly && (
<span data-tooltip={i18n`change value to empty`}> <span data-tooltip={i18n.str`change value to empty`}>
<button <button
class="button is-info " class="button is-info "
onClick={() => onChange(undefined as any)} onClick={() => onChange(undefined as any)}
> >
<Translate>clear</Translate> <i18n.Translate>clear</i18n.Translate>
</button> </button>
</span> </span>
)} )}

View File

@ -18,9 +18,9 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { ComponentChildren, h, VNode } from "preact"; import { ComponentChildren, h, VNode } from "preact";
import { useRef, useState } from "preact/hooks"; import { useRef, useState } from "preact/hooks";
import { Translate } from "../../i18n/index.js";
import { MAX_IMAGE_SIZE as MAX_IMAGE_UPLOAD_SIZE } from "../../utils/constants.js"; import { MAX_IMAGE_SIZE as MAX_IMAGE_UPLOAD_SIZE } from "../../utils/constants.js";
import { InputProps, useField } from "./useField.js"; import { InputProps, useField } from "./useField.js";
@ -43,7 +43,7 @@ export function InputImage<T>({
const { error, value, onChange } = useField<T>(name); const { error, value, onChange } = useField<T>(name);
const image = useRef<HTMLInputElement>(null); const image = useRef<HTMLInputElement>(null);
const { i18n } = useTranslationContext();
const [sizeError, setSizeError] = useState(false); const [sizeError, setSizeError] = useState(false);
return ( return (
@ -102,17 +102,17 @@ export function InputImage<T>({
{error && <p class="help is-danger">{error}</p>} {error && <p class="help is-danger">{error}</p>}
{sizeError && ( {sizeError && (
<p class="help is-danger"> <p class="help is-danger">
<Translate>Image should be smaller than 1 MB</Translate> <i18n.Translate>Image should be smaller than 1 MB</i18n.Translate>
</p> </p>
)} )}
{!value && ( {!value && (
<button class="button" onClick={() => image.current?.click()}> <button class="button" onClick={() => image.current?.click()}>
<Translate>Add</Translate> <i18n.Translate>Add</i18n.Translate>
</button> </button>
)} )}
{value && ( {value && (
<button class="button" onClick={() => onChange(undefined!)}> <button class="button" onClick={() => onChange(undefined!)}>
<Translate>Remove</Translate> <i18n.Translate>Remove</i18n.Translate>
</button> </button>
)} )}
</div> </div>

View File

@ -19,31 +19,34 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { Fragment, h } from "preact"; import { Fragment, h } from "preact";
import { useTranslator } from "../../i18n/index.js"; import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Input } from "./Input.js"; import { Input } from "./Input.js";
export function InputLocation({ name }: { name: string }) { export function InputLocation({ name }: { name: string }) {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<> <>
<Input name={`${name}.country`} label={i18n`Country`} /> <Input name={`${name}.country`} label={i18n.str`Country`} />
<Input <Input
name={`${name}.address_lines`} name={`${name}.address_lines`}
inputType="multiline" inputType="multiline"
label={i18n`Address`} label={i18n.str`Address`}
toStr={(v: string[] | undefined) => (!v ? "" : v.join("\n"))} toStr={(v: string[] | undefined) => (!v ? "" : v.join("\n"))}
fromStr={(v: string) => v.split("\n")} fromStr={(v: string) => v.split("\n")}
/> />
<Input name={`${name}.building_number`} label={i18n`Building number`} /> <Input
<Input name={`${name}.building_name`} label={i18n`Building name`} /> name={`${name}.building_number`}
<Input name={`${name}.street`} label={i18n`Street`} /> label={i18n.str`Building number`}
<Input name={`${name}.post_code`} label={i18n`Post code`} /> />
<Input name={`${name}.town_location`} label={i18n`Town location`} /> <Input name={`${name}.building_name`} label={i18n.str`Building name`} />
<Input name={`${name}.town`} label={i18n`Town`} /> <Input name={`${name}.street`} label={i18n.str`Street`} />
<Input name={`${name}.district`} label={i18n`District`} /> <Input name={`${name}.post_code`} label={i18n.str`Post code`} />
<Input name={`${name}.town_location`} label={i18n.str`Town location`} />
<Input name={`${name}.town`} label={i18n.str`Town`} />
<Input name={`${name}.district`} label={i18n.str`District`} />
<Input <Input
name={`${name}.country_subdivision`} name={`${name}.country_subdivision`}
label={i18n`Country subdivision`} label={i18n.str`Country subdivision`}
/> />
</> </>
); );

View File

@ -18,9 +18,9 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, VNode, Fragment } from "preact"; import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact";
import { useCallback, useState } from "preact/hooks"; import { useCallback, useState } from "preact/hooks";
import { Translate, Translator, useTranslator } from "../../i18n/index.js";
import { COUNTRY_TABLE } from "../../utils/constants.js"; import { COUNTRY_TABLE } from "../../utils/constants.js";
import { undefinedIfEmpty } from "../../utils/table.js"; import { undefinedIfEmpty } from "../../utils/table.js";
import { FormErrors, FormProvider } from "./FormProvider.js"; import { FormErrors, FormProvider } from "./FormProvider.js";
@ -69,24 +69,30 @@ function checkAddressChecksum(address: string) {
return true; return true;
} }
function validateBitcoin(addr: string, i18n: Translator): string | undefined { function validateBitcoin(
addr: string,
i18n: ReturnType<typeof useTranslationContext>["i18n"],
): string | undefined {
try { try {
const valid = /^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/.test(addr); const valid = /^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/.test(addr);
if (valid) return undefined; if (valid) return undefined;
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
return i18n`This is not a valid bitcoin address.`; return i18n.str`This is not a valid bitcoin address.`;
} }
function validateEthereum(addr: string, i18n: Translator): string | undefined { function validateEthereum(
addr: string,
i18n: ReturnType<typeof useTranslationContext>["i18n"],
): string | undefined {
try { try {
const valid = isEthereumAddress(addr); const valid = isEthereumAddress(addr);
if (valid) return undefined; if (valid) return undefined;
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
return i18n`This is not a valid Ethereum address.`; return i18n.str`This is not a valid Ethereum address.`;
} }
/** /**
@ -103,12 +109,15 @@ function validateEthereum(addr: string, i18n: Translator): string | undefined {
* If the remainder is 1, the check digit test is passed and the IBAN might be valid. * If the remainder is 1, the check digit test is passed and the IBAN might be valid.
* *
*/ */
function validateIBAN(iban: string, i18n: Translator): string | undefined { function validateIBAN(
iban: string,
i18n: ReturnType<typeof useTranslationContext>["i18n"],
): string | undefined {
// Check total length // Check total length
if (iban.length < 4) if (iban.length < 4)
return i18n`IBAN numbers usually have more that 4 digits`; return i18n.str`IBAN numbers usually have more that 4 digits`;
if (iban.length > 34) if (iban.length > 34)
return i18n`IBAN numbers usually have less that 34 digits`; return i18n.str`IBAN numbers usually have less that 34 digits`;
const A_code = "A".charCodeAt(0); const A_code = "A".charCodeAt(0);
const Z_code = "Z".charCodeAt(0); const Z_code = "Z".charCodeAt(0);
@ -116,7 +125,7 @@ function validateIBAN(iban: string, i18n: Translator): string | undefined {
// check supported country // check supported country
const code = IBAN.substr(0, 2); const code = IBAN.substr(0, 2);
const found = code in COUNTRY_TABLE; const found = code in COUNTRY_TABLE;
if (!found) return i18n`IBAN country code not found`; if (!found) return i18n.str`IBAN country code not found`;
// 2.- Move the four initial characters to the end of the string // 2.- Move the four initial characters to the end of the string
const step2 = IBAN.substr(4) + iban.substr(0, 4); const step2 = IBAN.substr(4) + iban.substr(0, 4);
@ -140,7 +149,8 @@ function validateIBAN(iban: string, i18n: Translator): string | undefined {
} }
const checksum = calculate_iban_checksum(step3); const checksum = calculate_iban_checksum(step3);
if (checksum !== 1) return i18n`IBAN number is not valid, checksum is wrong`; if (checksum !== 1)
return i18n.str`IBAN number is not valid, checksum is wrong`;
return undefined; return undefined;
} }
@ -175,7 +185,7 @@ export function InputPaytoForm<T>({
payToPath = `/${value.path1}`; payToPath = `/${value.path1}`;
} }
} }
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const ops = value.options!; const ops = value.options!;
const url = tryUrl(`payto://${value.target}${payToPath}`); const url = tryUrl(`payto://${value.target}${payToPath}`);
@ -188,9 +198,9 @@ export function InputPaytoForm<T>({
const paytoURL = !url ? "" : url.toString(); const paytoURL = !url ? "" : url.toString();
const errors: FormErrors<Entity> = { const errors: FormErrors<Entity> = {
target: value.target === noTargetValue ? i18n`required` : undefined, target: value.target === noTargetValue ? i18n.str`required` : undefined,
path1: !value.path1 path1: !value.path1
? i18n`required` ? i18n.str`required`
: value.target === "iban" : value.target === "iban"
? validateIBAN(value.path1, i18n) ? validateIBAN(value.path1, i18n)
: value.target === "bitcoin" : value.target === "bitcoin"
@ -201,12 +211,12 @@ export function InputPaytoForm<T>({
path2: path2:
value.target === "x-taler-bank" value.target === "x-taler-bank"
? !value.path2 ? !value.path2
? i18n`required` ? i18n.str`required`
: undefined : undefined
: undefined, : undefined,
options: undefinedIfEmpty({ options: undefinedIfEmpty({
"receiver-name": !value.options?.["receiver-name"] "receiver-name": !value.options?.["receiver-name"]
? i18n`required` ? i18n.str`required`
: undefined, : undefined,
}), }),
}; };
@ -235,23 +245,23 @@ export function InputPaytoForm<T>({
> >
<InputSelector<Entity> <InputSelector<Entity>
name="target" name="target"
label={i18n`Target type`} label={i18n.str`Target type`}
tooltip={i18n`Method to use for wire transfer`} tooltip={i18n.str`Method to use for wire transfer`}
values={targets} values={targets}
toStr={(v) => (v === noTargetValue ? i18n`Choose one...` : v)} toStr={(v) => (v === noTargetValue ? i18n.str`Choose one...` : v)}
/> />
{value.target === "ach" && ( {value.target === "ach" && (
<Fragment> <Fragment>
<Input<Entity> <Input<Entity>
name="path1" name="path1"
label={i18n`Routing`} label={i18n.str`Routing`}
tooltip={i18n`Routing number.`} tooltip={i18n.str`Routing number.`}
/> />
<Input<Entity> <Input<Entity>
name="path2" name="path2"
label={i18n`Account`} label={i18n.str`Account`}
tooltip={i18n`Account number.`} tooltip={i18n.str`Account number.`}
/> />
</Fragment> </Fragment>
)} )}
@ -259,8 +269,8 @@ export function InputPaytoForm<T>({
<Fragment> <Fragment>
<Input<Entity> <Input<Entity>
name="path1" name="path1"
label={i18n`Code`} label={i18n.str`Code`}
tooltip={i18n`Business Identifier Code.`} tooltip={i18n.str`Business Identifier Code.`}
/> />
</Fragment> </Fragment>
)} )}
@ -268,8 +278,8 @@ export function InputPaytoForm<T>({
<Fragment> <Fragment>
<Input<Entity> <Input<Entity>
name="path1" name="path1"
label={i18n`Account`} label={i18n.str`Account`}
tooltip={i18n`Bank Account Number.`} tooltip={i18n.str`Bank Account Number.`}
inputExtra={{ style: { textTransform: "uppercase" } }} inputExtra={{ style: { textTransform: "uppercase" } }}
/> />
</Fragment> </Fragment>
@ -278,8 +288,8 @@ export function InputPaytoForm<T>({
<Fragment> <Fragment>
<Input<Entity> <Input<Entity>
name="path1" name="path1"
label={i18n`Account`} label={i18n.str`Account`}
tooltip={i18n`Unified Payment Interface.`} tooltip={i18n.str`Unified Payment Interface.`}
/> />
</Fragment> </Fragment>
)} )}
@ -287,8 +297,8 @@ export function InputPaytoForm<T>({
<Fragment> <Fragment>
<Input<Entity> <Input<Entity>
name="path1" name="path1"
label={i18n`Address`} label={i18n.str`Address`}
tooltip={i18n`Bitcoin protocol.`} tooltip={i18n.str`Bitcoin protocol.`}
/> />
</Fragment> </Fragment>
)} )}
@ -296,8 +306,8 @@ export function InputPaytoForm<T>({
<Fragment> <Fragment>
<Input<Entity> <Input<Entity>
name="path1" name="path1"
label={i18n`Address`} label={i18n.str`Address`}
tooltip={i18n`Ethereum protocol.`} tooltip={i18n.str`Ethereum protocol.`}
/> />
</Fragment> </Fragment>
)} )}
@ -305,8 +315,8 @@ export function InputPaytoForm<T>({
<Fragment> <Fragment>
<Input<Entity> <Input<Entity>
name="path1" name="path1"
label={i18n`Address`} label={i18n.str`Address`}
tooltip={i18n`Interledger protocol.`} tooltip={i18n.str`Interledger protocol.`}
/> />
</Fragment> </Fragment>
)} )}
@ -315,13 +325,13 @@ export function InputPaytoForm<T>({
<Fragment> <Fragment>
<Input<Entity> <Input<Entity>
name="path1" name="path1"
label={i18n`Host`} label={i18n.str`Host`}
tooltip={i18n`Bank host.`} tooltip={i18n.str`Bank host.`}
/> />
<Input<Entity> <Input<Entity>
name="path2" name="path2"
label={i18n`Account`} label={i18n.str`Account`}
tooltip={i18n`Bank account.`} tooltip={i18n.str`Bank account.`}
/> />
</Fragment> </Fragment>
)} )}
@ -329,8 +339,8 @@ export function InputPaytoForm<T>({
{value.target !== noTargetValue && ( {value.target !== noTargetValue && (
<Input <Input
name="options.receiver-name" name="options.receiver-name"
label={i18n`Name`} label={i18n.str`Name`}
tooltip={i18n`Bank account owner's name.`} tooltip={i18n.str`Bank account owner's name.`}
/> />
)} )}
@ -357,7 +367,7 @@ export function InputPaytoForm<T>({
/> />
</div> </div>
))} ))}
{!paytos.length && i18n`No accounts yet.`} {!paytos.length && i18n.str`No accounts yet.`}
</div> </div>
</div> </div>
@ -365,11 +375,11 @@ export function InputPaytoForm<T>({
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
<button <button
class="button is-info" class="button is-info"
data-tooltip={i18n`add tax to the tax list`} data-tooltip={i18n.str`add tax to the tax list`}
disabled={hasErrors} disabled={hasErrors}
onClick={submit} onClick={submit}
> >
<Translate>Add</Translate> <i18n.Translate>Add</i18n.Translate>
</button> </button>
</div> </div>
)} )}

View File

@ -18,11 +18,11 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import emptyImage from "../../assets/empty.png"; import emptyImage from "../../assets/empty.png";
import { MerchantBackend, WithId } from "../../declaration.js"; import { MerchantBackend, WithId } from "../../declaration.js";
import { Translate, useTranslator } from "../../i18n/index.js";
import { FormErrors, FormProvider } from "./FormProvider.js"; import { FormErrors, FormProvider } from "./FormProvider.js";
import { InputWithAddon } from "./InputWithAddon.js"; import { InputWithAddon } from "./InputWithAddon.js";
@ -50,7 +50,7 @@ export function InputSearchProduct({
const errors: FormErrors<ProductSearch> = { const errors: FormErrors<ProductSearch> = {
name: undefined, name: undefined,
}; };
const i18n = useTranslator(); const { i18n } = useTranslationContext();
if (selected) { if (selected) {
return ( return (
@ -63,10 +63,11 @@ export function InputSearchProduct({
<div class="media-content"> <div class="media-content">
<div class="content"> <div class="content">
<p class="media-meta"> <p class="media-meta">
<Translate>Product id</Translate>: <b>{selected.id}</b> <i18n.Translate>Product id</i18n.Translate>: <b>{selected.id}</b>
</p> </p>
<p> <p>
<Translate>Description</Translate>: {selected.description} <i18n.Translate>Description</i18n.Translate>:{" "}
{selected.description}
</p> </p>
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
<button <button
@ -90,8 +91,8 @@ export function InputSearchProduct({
> >
<InputWithAddon<ProductSearch> <InputWithAddon<ProductSearch>
name="name" name="name"
label={i18n`Product`} label={i18n.str`Product`}
tooltip={i18n`search products by it's description or id`} tooltip={i18n.str`search products by it's description or id`}
addonAfter={ addonAfter={
<span class="icon"> <span class="icon">
<i class="mdi mdi-magnify" /> <i class="mdi mdi-magnify" />
@ -120,6 +121,7 @@ interface ProductListProps {
} }
function ProductList({ name, onSelect, list }: ProductListProps) { function ProductList({ name, onSelect, list }: ProductListProps) {
const { i18n } = useTranslationContext();
if (!name) { if (!name) {
/* FIXME /* FIXME
this BR is added to occupy the space that will be added when the this BR is added to occupy the space that will be added when the
@ -146,7 +148,9 @@ function ProductList({ name, onSelect, list }: ProductListProps) {
<div class="dropdown-content"> <div class="dropdown-content">
{!filtered.length ? ( {!filtered.length ? (
<div class="dropdown-item"> <div class="dropdown-item">
<Translate>no products found with that description</Translate> <i18n.Translate>
no products found with that description
</i18n.Translate>
</div> </div>
) : ( ) : (
filtered.map((p) => ( filtered.map((p) => (

View File

@ -18,14 +18,15 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Translate, useTranslator } from "../../i18n/index.js";
import { InputProps, useField } from "./useField.js"; import { InputProps, useField } from "./useField.js";
export type Props<T> = InputProps<T>; export type Props<T> = InputProps<T>;
const TokenStatus = ({ prev, post }: any) => { const TokenStatus = ({ prev, post }: any) => {
const { i18n } = useTranslationContext();
if ( if (
(prev === undefined || prev === null) && (prev === undefined || prev === null) &&
(post === undefined || post === null) (post === undefined || post === null)
@ -33,11 +34,11 @@ const TokenStatus = ({ prev, post }: any) => {
return null; return null;
return prev === post ? null : post === null ? ( return prev === post ? null : post === null ? (
<span class="tag is-danger is-align-self-center ml-2"> <span class="tag is-danger is-align-self-center ml-2">
<Translate>Deleting</Translate> <i18n.Translate>Deleting</i18n.Translate>
</span> </span>
) : ( ) : (
<span class="tag is-warning is-align-self-center ml-2"> <span class="tag is-warning is-align-self-center ml-2">
<Translate>Changing</Translate> <i18n.Translate>Changing</i18n.Translate>
</span> </span>
); );
}; };
@ -55,7 +56,7 @@ export function InputSecured<T>({
const [active, setActive] = useState(false); const [active, setActive] = useState(false);
const [newValue, setNuewValue] = useState(""); const [newValue, setNuewValue] = useState("");
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<Fragment> <Fragment>
@ -84,7 +85,7 @@ export function InputSecured<T>({
<i class="mdi mdi-lock-reset" /> <i class="mdi mdi-lock-reset" />
</div> </div>
<span> <span>
<Translate>Manage access token</Translate> <i18n.Translate>Manage access token</i18n.Translate>
</span> </span>
</button> </button>
<TokenStatus prev={initial} post={value} /> <TokenStatus prev={initial} post={value} />
@ -125,7 +126,7 @@ export function InputSecured<T>({
<i class="mdi mdi-lock-outline" /> <i class="mdi mdi-lock-outline" />
</div> </div>
<span> <span>
<Translate>Update</Translate> <i18n.Translate>Update</i18n.Translate>
</span> </span>
</button> </button>
</div> </div>
@ -154,7 +155,7 @@ export function InputSecured<T>({
<i class="mdi mdi-lock-open-variant" /> <i class="mdi mdi-lock-open-variant" />
</div> </div>
<span> <span>
<Translate>Remove</Translate> <i18n.Translate>Remove</i18n.Translate>
</span> </span>
</button> </button>
</div> </div>
@ -171,7 +172,7 @@ export function InputSecured<T>({
<i class="mdi mdi-lock-open-variant" /> <i class="mdi mdi-lock-open-variant" />
</div> </div>
<span> <span>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</span> </span>
</button> </button>
</div> </div>

View File

@ -18,17 +18,16 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h } from "preact"; import { Fragment, h } from "preact";
import { MerchantBackend, Timestamp } from "../../declaration.js";
import { InputProps, useField } from "./useField.js";
import { FormProvider, FormErrors } from "./FormProvider.js";
import { useLayoutEffect, useState } from "preact/hooks"; import { useLayoutEffect, useState } from "preact/hooks";
import { Input } from "./Input.js"; import { MerchantBackend, Timestamp } from "../../declaration.js";
import { InputGroup } from "./InputGroup.js"; import { FormErrors, FormProvider } from "./FormProvider.js";
import { InputNumber } from "./InputNumber.js";
import { InputDate } from "./InputDate.js"; import { InputDate } from "./InputDate.js";
import { Translate, useTranslator } from "../../i18n/index.js"; import { InputGroup } from "./InputGroup.js";
import { InputLocation } from "./InputLocation.js"; import { InputLocation } from "./InputLocation.js";
import { InputNumber } from "./InputNumber.js";
import { InputProps, useField } from "./useField.js";
export interface Props<T> extends InputProps<T> { export interface Props<T> extends InputProps<T> {
alreadyExist?: boolean; alreadyExist?: boolean;
@ -64,7 +63,7 @@ export function InputStock<T>({
incoming: 0, incoming: 0,
lost: 0, lost: 0,
}); });
const i18n = useTranslator(); const { i18n } = useTranslationContext();
useLayoutEffect(() => { useLayoutEffect(() => {
if (!formValue) { if (!formValue) {
@ -97,7 +96,7 @@ export function InputStock<T>({
{!alreadyExist ? ( {!alreadyExist ? (
<button <button
class="button" class="button"
data-tooltip={i18n`click here to configure the stock of the product, leave it as is and the backend will not control stock`} data-tooltip={i18n.str`click here to configure the stock of the product, leave it as is and the backend will not control stock`}
onClick={(): void => { onClick={(): void => {
valueHandler({ valueHandler({
current: 0, current: 0,
@ -107,17 +106,17 @@ export function InputStock<T>({
}} }}
> >
<span> <span>
<Translate>Manage stock</Translate> <i18n.Translate>Manage stock</i18n.Translate>
</span> </span>
</button> </button>
) : ( ) : (
<button <button
class="button" class="button"
data-tooltip={i18n`this product has been configured without stock control`} data-tooltip={i18n.str`this product has been configured without stock control`}
disabled disabled
> >
<span> <span>
<Translate>Infinite</Translate> <i18n.Translate>Infinite</i18n.Translate>
</span> </span>
</button> </button>
)} )}
@ -134,7 +133,7 @@ export function InputStock<T>({
const stockAddedErrors: FormErrors<typeof addedStock> = { const stockAddedErrors: FormErrors<typeof addedStock> = {
lost: lost:
currentStock + addedStock.incoming < addedStock.lost currentStock + addedStock.incoming < addedStock.lost
? i18n`lost cannot be greater than current and incoming (max ${ ? i18n.str`lost cannot be greater than current and incoming (max ${
currentStock + addedStock.incoming currentStock + addedStock.incoming
})` })`
: undefined, : undefined,
@ -142,8 +141,8 @@ export function InputStock<T>({
// const stockUpdateDescription = stockAddedErrors.lost ? '' : ( // const stockUpdateDescription = stockAddedErrors.lost ? '' : (
// !!addedStock.incoming || !!addedStock.lost ? // !!addedStock.incoming || !!addedStock.lost ?
// i18n`current stock will change from ${currentStock} to ${currentStock + addedStock.incoming - addedStock.lost}` : // i18n.str`current stock will change from ${currentStock} to ${currentStock + addedStock.incoming - addedStock.lost}` :
// i18n`current stock will stay at ${currentStock}` // i18n.str`current stock will stay at ${currentStock}`
// ) // )
return ( return (
@ -174,8 +173,8 @@ export function InputStock<T>({
object={addedStock} object={addedStock}
valueHandler={setAddedStock as any} valueHandler={setAddedStock as any}
> >
<InputNumber name="incoming" label={i18n`Incoming`} /> <InputNumber name="incoming" label={i18n.str`Incoming`} />
<InputNumber name="lost" label={i18n`Lost`} /> <InputNumber name="lost" label={i18n.str`Lost`} />
</FormProvider> </FormProvider>
{/* <div class="field is-horizontal"> {/* <div class="field is-horizontal">
@ -190,17 +189,17 @@ export function InputStock<T>({
) : ( ) : (
<InputNumber<Entity> <InputNumber<Entity>
name="current" name="current"
label={i18n`Current`} label={i18n.str`Current`}
side={ side={
<button <button
class="button is-danger" class="button is-danger"
data-tooltip={i18n`remove stock control for this product`} data-tooltip={i18n.str`remove stock control for this product`}
onClick={(): void => { onClick={(): void => {
valueHandler(undefined as any); valueHandler(undefined as any);
}} }}
> >
<span> <span>
<Translate>without stock</Translate> <i18n.Translate>without stock</i18n.Translate>
</span> </span>
</button> </button>
} }
@ -209,11 +208,14 @@ export function InputStock<T>({
<InputDate<Entity> <InputDate<Entity>
name="nextRestock" name="nextRestock"
label={i18n`Next restock`} label={i18n.str`Next restock`}
withTimestampSupport withTimestampSupport
/> />
<InputGroup<Entity> name="address" label={i18n`Delivery address`}> <InputGroup<Entity>
name="address"
label={i18n.str`Delivery address`}
>
<InputLocation name="address" /> <InputLocation name="address" />
</InputGroup> </InputGroup>
</FormProvider> </FormProvider>

View File

@ -18,11 +18,11 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useCallback, useState } from "preact/hooks"; import { useCallback, useState } from "preact/hooks";
import * as yup from "yup"; import * as yup from "yup";
import { MerchantBackend } from "../../declaration.js"; import { MerchantBackend } from "../../declaration.js";
import { Translate, useTranslator } from "../../i18n/index.js";
import { TaxSchema as schema } from "../../schemas/index.js"; import { TaxSchema as schema } from "../../schemas/index.js";
import { FormErrors, FormProvider } from "./FormProvider.js"; import { FormErrors, FormProvider } from "./FormProvider.js";
import { Input } from "./Input.js"; import { Input } from "./Input.js";
@ -67,7 +67,7 @@ export function InputTaxes<T>({
valueHandler({}); valueHandler({});
}, [value]); }, [value]);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
//FIXME: translating plural singular //FIXME: translating plural singular
return ( return (
@ -110,34 +110,35 @@ export function InputTaxes<T>({
/> />
</div> </div>
))} ))}
{!taxes.length && i18n`No taxes configured for this product.`} {!taxes.length && i18n.str`No taxes configured for this product.`}
</div> </div>
</div> </div>
<Input<Entity> <Input<Entity>
name="tax" name="tax"
label={i18n`Amount`} label={i18n.str`Amount`}
tooltip={i18n`Taxes can be in currencies that differ from the main currency used by the merchant.`} tooltip={i18n.str`Taxes can be in currencies that differ from the main currency used by the merchant.`}
> >
<Translate> <i18n.Translate>
Enter currency and value separated with a colon, e.g. "USD:2.3". Enter currency and value separated with a colon, e.g.
</Translate> &quot;USD:2.3&quot;.
</i18n.Translate>
</Input> </Input>
<Input<Entity> <Input<Entity>
name="name" name="name"
label={i18n`Description`} label={i18n.str`Description`}
tooltip={i18n`Legal name of the tax, e.g. VAT or import duties.`} tooltip={i18n.str`Legal name of the tax, e.g. VAT or import duties.`}
/> />
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
<button <button
class="button is-info" class="button is-info"
data-tooltip={i18n`add tax to the tax list`} data-tooltip={i18n.str`add tax to the tax list`}
disabled={hasErrors} disabled={hasErrors}
onClick={submit} onClick={submit}
> >
<Translate>Add</Translate> <i18n.Translate>Add</i18n.Translate>
</button> </button>
</div> </div>
</FormProvider> </FormProvider>

View File

@ -19,9 +19,9 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslator } from "../../i18n/index.js";
import { Entity } from "../../paths/admin/create/CreatePage.js"; import { Entity } from "../../paths/admin/create/CreatePage.js";
import { Input } from "../form/Input.js"; import { Input } from "../form/Input.js";
import { InputCurrency } from "../form/InputCurrency.js"; import { InputCurrency } from "../form/InputCurrency.js";
@ -39,7 +39,7 @@ export function DefaultInstanceFormFields({
readonlyId?: boolean; readonlyId?: boolean;
showId: boolean; showId: boolean;
}): VNode { }): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const backend = useBackendContext(); const backend = useBackendContext();
return ( return (
<Fragment> <Fragment>
@ -48,86 +48,86 @@ export function DefaultInstanceFormFields({
name="id" name="id"
addonBefore={`${backend.url}/instances/`} addonBefore={`${backend.url}/instances/`}
readonly={readonlyId} readonly={readonlyId}
label={i18n`Identifier`} label={i18n.str`Identifier`}
tooltip={i18n`Name of the instance in URLs. The 'default' instance is special in that it is used to administer other instances.`} tooltip={i18n.str`Name of the instance in URLs. The 'default' instance is special in that it is used to administer other instances.`}
/> />
)} )}
<Input<Entity> <Input<Entity>
name="name" name="name"
label={i18n`Business name`} label={i18n.str`Business name`}
tooltip={i18n`Legal name of the business represented by this instance.`} tooltip={i18n.str`Legal name of the business represented by this instance.`}
/> />
<Input<Entity> <Input<Entity>
name="email" name="email"
label={i18n`Email`} label={i18n.str`Email`}
tooltip={i18n`Contact email`} tooltip={i18n.str`Contact email`}
/> />
<Input<Entity> <Input<Entity>
name="website" name="website"
label={i18n`Website URL`} label={i18n.str`Website URL`}
tooltip={i18n`URL.`} tooltip={i18n.str`URL.`}
/> />
<InputImage<Entity> <InputImage<Entity>
name="logo" name="logo"
label={i18n`Logo`} label={i18n.str`Logo`}
tooltip={i18n`Logo image.`} tooltip={i18n.str`Logo image.`}
/> />
<InputPaytoForm<Entity> <InputPaytoForm<Entity>
name="payto_uris" name="payto_uris"
label={i18n`Bank account`} label={i18n.str`Bank account`}
tooltip={i18n`URI specifying bank account for crediting revenue.`} tooltip={i18n.str`URI specifying bank account for crediting revenue.`}
/> />
<InputCurrency<Entity> <InputCurrency<Entity>
name="default_max_deposit_fee" name="default_max_deposit_fee"
label={i18n`Default max deposit fee`} label={i18n.str`Default max deposit fee`}
tooltip={i18n`Maximum deposit fees this merchant is willing to pay per order by default.`} tooltip={i18n.str`Maximum deposit fees this merchant is willing to pay per order by default.`}
/> />
<InputCurrency<Entity> <InputCurrency<Entity>
name="default_max_wire_fee" name="default_max_wire_fee"
label={i18n`Default max wire fee`} label={i18n.str`Default max wire fee`}
tooltip={i18n`Maximum wire fees this merchant is willing to pay per wire transfer by default.`} tooltip={i18n.str`Maximum wire fees this merchant is willing to pay per wire transfer by default.`}
/> />
<Input<Entity> <Input<Entity>
name="default_wire_fee_amortization" name="default_wire_fee_amortization"
label={i18n`Default wire fee amortization`} label={i18n.str`Default wire fee amortization`}
tooltip={i18n`Number of orders excess wire transfer fees will be divided by to compute per order surcharge.`} tooltip={i18n.str`Number of orders excess wire transfer fees will be divided by to compute per order surcharge.`}
/> />
<InputGroup <InputGroup
name="address" name="address"
label={i18n`Address`} label={i18n.str`Address`}
tooltip={i18n`Physical location of the merchant.`} tooltip={i18n.str`Physical location of the merchant.`}
> >
<InputLocation name="address" /> <InputLocation name="address" />
</InputGroup> </InputGroup>
<InputGroup <InputGroup
name="jurisdiction" name="jurisdiction"
label={i18n`Jurisdiction`} label={i18n.str`Jurisdiction`}
tooltip={i18n`Jurisdiction for legal disputes with the merchant.`} tooltip={i18n.str`Jurisdiction for legal disputes with the merchant.`}
> >
<InputLocation name="jurisdiction" /> <InputLocation name="jurisdiction" />
</InputGroup> </InputGroup>
<InputDuration<Entity> <InputDuration<Entity>
name="default_pay_delay" name="default_pay_delay"
label={i18n`Default payment delay`} label={i18n.str`Default payment delay`}
withForever withForever
tooltip={i18n`Time customers have to pay an order before the offer expires by default.`} tooltip={i18n.str`Time customers have to pay an order before the offer expires by default.`}
/> />
<InputDuration<Entity> <InputDuration<Entity>
name="default_wire_transfer_delay" name="default_wire_transfer_delay"
label={i18n`Default wire transfer delay`} label={i18n.str`Default wire transfer delay`}
tooltip={i18n`Maximum time an exchange is allowed to delay wiring funds to the merchant, enabling it to aggregate smaller payments into larger wire transfers and reducing wire fees.`} tooltip={i18n.str`Maximum time an exchange is allowed to delay wiring funds to the merchant, enabling it to aggregate smaller payments into larger wire transfers and reducing wire fees.`}
withForever withForever
/> />
</Fragment> </Fragment>

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import langIcon from "../../assets/icons/languageicon.svg"; import langIcon from "../../assets/icons/languageicon.svg";
import { useTranslationContext } from "../../context/translation.js"; import { strings as messages } from "../../i18n/strings.js";
import { strings as messages } from "../../i18n/strings";
type LangsNames = { type LangsNames = {
[P in keyof typeof messages]: string; [P in keyof typeof messages]: string;

View File

@ -19,13 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useCallback } from "preact/hooks";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useConfigContext } from "../../context/config.js"; import { useConfigContext } from "../../context/config.js";
import { useInstanceContext } from "../../context/instance.js";
import { useInstanceKYCDetails } from "../../hooks/instance.js"; import { useInstanceKYCDetails } from "../../hooks/instance.js";
import { Translate } from "../../i18n/index.js";
import { LangSelector } from "./LangSelector.js"; import { LangSelector } from "./LangSelector.js";
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
@ -48,7 +46,7 @@ export function Sidebar({
}: Props): VNode { }: Props): VNode {
const config = useConfigContext(); const config = useConfigContext();
const backend = useBackendContext(); const backend = useBackendContext();
const { i18n } = useTranslationContext();
const kycStatus = useInstanceKYCDetails(); const kycStatus = useInstanceKYCDetails();
const needKYC = kycStatus.ok && kycStatus.data.type === "redirect"; const needKYC = kycStatus.ok && kycStatus.data.type === "redirect";
@ -81,7 +79,7 @@ export function Sidebar({
{instance ? ( {instance ? (
<Fragment> <Fragment>
<p class="menu-label"> <p class="menu-label">
<Translate>Instance</Translate> <i18n.Translate>Instance</i18n.Translate>
</p> </p>
<ul class="menu-list"> <ul class="menu-list">
<li> <li>
@ -90,7 +88,7 @@ export function Sidebar({
<i class="mdi mdi-square-edit-outline" /> <i class="mdi mdi-square-edit-outline" />
</span> </span>
<span class="menu-item-label"> <span class="menu-item-label">
<Translate>Settings</Translate> <i18n.Translate>Settings</i18n.Translate>
</span> </span>
</a> </a>
</li> </li>
@ -100,7 +98,7 @@ export function Sidebar({
<i class="mdi mdi-cash-register" /> <i class="mdi mdi-cash-register" />
</span> </span>
<span class="menu-item-label"> <span class="menu-item-label">
<Translate>Orders</Translate> <i18n.Translate>Orders</i18n.Translate>
</span> </span>
</a> </a>
</li> </li>
@ -110,7 +108,7 @@ export function Sidebar({
<i class="mdi mdi-shopping" /> <i class="mdi mdi-shopping" />
</span> </span>
<span class="menu-item-label"> <span class="menu-item-label">
<Translate>Products</Translate> <i18n.Translate>Products</i18n.Translate>
</span> </span>
</a> </a>
</li> </li>
@ -120,7 +118,7 @@ export function Sidebar({
<i class="mdi mdi-bank" /> <i class="mdi mdi-bank" />
</span> </span>
<span class="menu-item-label"> <span class="menu-item-label">
<Translate>Transfers</Translate> <i18n.Translate>Transfers</i18n.Translate>
</span> </span>
</a> </a>
</li> </li>
@ -130,7 +128,7 @@ export function Sidebar({
<i class="mdi mdi-newspaper" /> <i class="mdi mdi-newspaper" />
</span> </span>
<span class="menu-item-label"> <span class="menu-item-label">
<Translate>Templates</Translate> <i18n.Translate>Templates</i18n.Translate>
</span> </span>
</a> </a>
</li> </li>
@ -156,7 +154,7 @@ export function Sidebar({
</Fragment> </Fragment>
) : undefined} ) : undefined}
<p class="menu-label"> <p class="menu-label">
<Translate>Connection</Translate> <i18n.Translate>Connection</i18n.Translate>
</p> </p>
<ul class="menu-list"> <ul class="menu-list">
<li> <li>
@ -190,7 +188,7 @@ export function Sidebar({
{admin && !mimic && ( {admin && !mimic && (
<Fragment> <Fragment>
<p class="menu-label"> <p class="menu-label">
<Translate>Instances</Translate> <i18n.Translate>Instances</i18n.Translate>
</p> </p>
<li> <li>
<a href={"/instance/new"} class="has-icon"> <a href={"/instance/new"} class="has-icon">
@ -198,7 +196,7 @@ export function Sidebar({
<i class="mdi mdi-plus" /> <i class="mdi mdi-plus" />
</span> </span>
<span class="menu-item-label"> <span class="menu-item-label">
<Translate>New</Translate> <i18n.Translate>New</i18n.Translate>
</span> </span>
</a> </a>
</li> </li>
@ -208,7 +206,7 @@ export function Sidebar({
<i class="mdi mdi-format-list-bulleted" /> <i class="mdi mdi-format-list-bulleted" />
</span> </span>
<span class="menu-item-label"> <span class="menu-item-label">
<Translate>List</Translate> <i18n.Translate>List</i18n.Translate>
</span> </span>
</a> </a>
</li> </li>
@ -223,7 +221,7 @@ export function Sidebar({
<i class="mdi mdi-logout default" /> <i class="mdi mdi-logout default" />
</span> </span>
<span class="menu-item-label"> <span class="menu-item-label">
<Translate>Log out</Translate> <i18n.Translate>Log out</i18n.Translate>
</span> </span>
</a> </a>
</li> </li>

View File

@ -15,7 +15,6 @@
*/ */
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
import Match from "preact-router/match";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { AdminPaths } from "../../AdminRoutes.js"; import { AdminPaths } from "../../AdminRoutes.js";
import { InstancePaths } from "../../InstanceRoutes.js"; import { InstancePaths } from "../../InstanceRoutes.js";
@ -58,6 +57,7 @@ function getAdminTitle(path: string, instance: string) {
interface MenuProps { interface MenuProps {
title?: string; title?: string;
path: string;
instance: string; instance: string;
admin?: boolean; admin?: boolean;
onLogout?: () => void; onLogout?: () => void;
@ -81,64 +81,59 @@ export function Menu({
onLogout, onLogout,
title, title,
instance, instance,
path,
admin, admin,
setInstanceName, setInstanceName,
}: MenuProps): VNode { }: MenuProps): VNode {
const [mobileOpen, setMobileOpen] = useState(false); const [mobileOpen, setMobileOpen] = useState(false);
const titleWithSubtitle = title
? title
: !admin
? getInstanceTitle(path, instance)
: getAdminTitle(path, instance);
const adminInstance = instance === "default";
const mimic = admin && !adminInstance;
return ( return (
<Match> <WithTitle title={titleWithSubtitle}>
{({ path }: any) => { <div
const titleWithSubtitle = title class={mobileOpen ? "has-aside-mobile-expanded" : ""}
? title onClick={() => setMobileOpen(false)}
: !admin >
? getInstanceTitle(path, instance) <NavigationBar
: getAdminTitle(path, instance); onMobileMenu={() => setMobileOpen(!mobileOpen)}
const adminInstance = instance === "default"; title={titleWithSubtitle}
const mimic = admin && !adminInstance; />
return (
<WithTitle title={titleWithSubtitle}>
<div
class={mobileOpen ? "has-aside-mobile-expanded" : ""}
onClick={() => setMobileOpen(false)}
>
<NavigationBar
onMobileMenu={() => setMobileOpen(!mobileOpen)}
title={titleWithSubtitle}
/>
{onLogout && ( {onLogout && (
<Sidebar <Sidebar
onLogout={onLogout} onLogout={onLogout}
admin={admin} admin={admin}
mimic={mimic} mimic={mimic}
instance={instance} instance={instance}
mobile={mobileOpen} mobile={mobileOpen}
/> />
)} )}
{mimic && ( {mimic && (
<nav class="level"> <nav class="level">
<div class="level-item has-text-centered has-background-warning"> <div class="level-item has-text-centered has-background-warning">
<p class="is-size-5"> <p class="is-size-5">
You are viewing the instance <b>"{instance}"</b>.{" "} You are viewing the instance <b>&quot;{instance}&quot;</b>.{" "}
<a <a
href="#/instances" href="#/instances"
onClick={(e) => { onClick={(e) => {
setInstanceName("default"); setInstanceName("default");
}} }}
> >
go back go back
</a> </a>
</p> </p>
</div>
</nav>
)}
</div> </div>
</WithTitle> </nav>
); )}
}} </div>
</Match> </WithTitle>
); );
} }

View File

@ -19,12 +19,12 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { ComponentChildren, h, VNode } from "preact"; import { ComponentChildren, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useInstanceContext } from "../../context/instance.js"; import { useInstanceContext } from "../../context/instance.js";
import { Translate, useTranslator } from "../../i18n/index.js";
import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants.js"; import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants.js";
import { Loading, Spinner } from "../exception/loading.js"; import { Spinner } from "../exception/loading.js";
import { FormProvider } from "../form/FormProvider.js"; import { FormProvider } from "../form/FormProvider.js";
import { Input } from "../form/Input.js"; import { Input } from "../form/Input.js";
@ -49,6 +49,7 @@ export function ConfirmModal({
disabled, disabled,
label = "Confirm", label = "Confirm",
}: Props): VNode { }: Props): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class={active ? "modal is-active" : "modal"}> <div class={active ? "modal is-active" : "modal"}>
<div class="modal-background " onClick={onCancel} /> <div class="modal-background " onClick={onCancel} />
@ -65,14 +66,14 @@ export function ConfirmModal({
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<div class="buttons is-right" style={{ width: "100%" }}> <div class="buttons is-right" style={{ width: "100%" }}>
<button class="button " onClick={onCancel}> <button class="button " onClick={onCancel}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
<button <button
class={danger ? "button is-danger " : "button is-info "} class={danger ? "button is-danger " : "button is-info "}
disabled={disabled} disabled={disabled}
onClick={onConfirm} onClick={onConfirm}
> >
<Translate>{label}</Translate> <i18n.Translate>{label}</i18n.Translate>
</button> </button>
</div> </div>
</footer> </footer>
@ -94,6 +95,7 @@ export function ContinueModal({
children, children,
disabled, disabled,
}: Props): VNode { }: Props): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class={active ? "modal is-active" : "modal"}> <div class={active ? "modal is-active" : "modal"}>
<div class="modal-background " onClick={onCancel} /> <div class="modal-background " onClick={onCancel} />
@ -110,7 +112,7 @@ export function ContinueModal({
disabled={disabled} disabled={disabled}
onClick={onConfirm} onClick={onConfirm}
> >
<Translate>Continue</Translate> <i18n.Translate>Continue</i18n.Translate>
</button> </button>
</div> </div>
</footer> </footer>
@ -147,6 +149,7 @@ export function ClearConfirmModal({
onConfirm, onConfirm,
children, children,
}: Props & { onClear?: () => void }): VNode { }: Props & { onClear?: () => void }): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="modal is-active"> <div class="modal is-active">
<div class="modal-background " onClick={onCancel} /> <div class="modal-background " onClick={onCancel} />
@ -163,19 +166,19 @@ export function ClearConfirmModal({
onClick={onClear} onClick={onClear}
disabled={onClear === undefined} disabled={onClear === undefined}
> >
<Translate>Clear</Translate> <i18n.Translate>Clear</i18n.Translate>
</button> </button>
)} )}
<div class="buttons is-right" style={{ width: "100%" }}> <div class="buttons is-right" style={{ width: "100%" }}>
<button class="button " onClick={onCancel}> <button class="button " onClick={onCancel}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
<button <button
class="button is-info" class="button is-info"
onClick={onConfirm} onClick={onConfirm}
disabled={onConfirm === undefined} disabled={onConfirm === undefined}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</button> </button>
</div> </div>
</footer> </footer>
@ -210,7 +213,7 @@ export function DeleteModal({
onConfirm={() => onConfirm(element.id)} onConfirm={() => onConfirm(element.id)}
> >
<p> <p>
If you delete the instance named <b>"{element.name}"</b> (ID:{" "} If you delete the instance named <b>&quot;{element.name}&quot;</b> (ID:{" "}
<b>{element.id}</b>), the merchant will no longer be able to process <b>{element.id}</b>), the merchant will no longer be able to process
orders or refunds orders or refunds
</p> </p>
@ -241,12 +244,13 @@ export function PurgeModal({
onConfirm={() => onConfirm(element.id)} onConfirm={() => onConfirm(element.id)}
> >
<p> <p>
If you purge the instance named <b>"{element.name}"</b> (ID:{" "} If you purge the instance named <b>&quot;{element.name}&quot;</b> (ID:{" "}
<b>{element.id}</b>), you will also delete all it's transaction data. <b>{element.id}</b>), you will also delete all it&apos;s transaction
data.
</p> </p>
<p> <p>
The instance will disappear from your list, and you will no longer be The instance will disappear from your list, and you will no longer be
able to access it's data. able to access it&apos;s data.
</p> </p>
<p class="warning"> <p class="warning">
Purging an instance <b>cannot be undone</b>. Purging an instance <b>cannot be undone</b>.
@ -275,20 +279,22 @@ export function UpdateTokenModal({
new_token: "", new_token: "",
repeat_token: "", repeat_token: "",
}); });
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const hasInputTheCorrectOldToken = oldToken && oldToken !== form.old_token; const hasInputTheCorrectOldToken = oldToken && oldToken !== form.old_token;
const errors = { const errors = {
old_token: hasInputTheCorrectOldToken old_token: hasInputTheCorrectOldToken
? i18n`is not the same as the current access token` ? i18n.str`is not the same as the current access token`
: undefined, : undefined,
new_token: !form.new_token new_token: !form.new_token
? i18n`cannot be empty` ? i18n.str`cannot be empty`
: form.new_token === form.old_token : form.new_token === form.old_token
? i18n`cannot be the same as the old token` ? i18n.str`cannot be the same as the old token`
: undefined, : undefined,
repeat_token: repeat_token:
form.new_token !== form.repeat_token ? i18n`is not the same` : undefined, form.new_token !== form.repeat_token
? i18n.str`is not the same`
: undefined,
}; };
const hasErrors = Object.keys(errors).some( const hasErrors = Object.keys(errors).some(
@ -297,7 +303,7 @@ export function UpdateTokenModal({
const instance = useInstanceContext(); const instance = useInstanceContext();
const text = i18n`You are updating the access token from instance with id ${instance.id}`; const text = i18n.str`You are updating the access token from instance with id ${instance.id}`;
return ( return (
<ClearConfirmModal <ClearConfirmModal
@ -313,28 +319,28 @@ export function UpdateTokenModal({
{oldToken && ( {oldToken && (
<Input<State> <Input<State>
name="old_token" name="old_token"
label={i18n`Old access token`} label={i18n.str`Old access token`}
tooltip={i18n`access token currently in use`} tooltip={i18n.str`access token currently in use`}
inputType="password" inputType="password"
/> />
)} )}
<Input<State> <Input<State>
name="new_token" name="new_token"
label={i18n`New access token`} label={i18n.str`New access token`}
tooltip={i18n`next access token to be used`} tooltip={i18n.str`next access token to be used`}
inputType="password" inputType="password"
/> />
<Input<State> <Input<State>
name="repeat_token" name="repeat_token"
label={i18n`Repeat access token`} label={i18n.str`Repeat access token`}
tooltip={i18n`confirm the same access token`} tooltip={i18n.str`confirm the same access token`}
inputType="password" inputType="password"
/> />
</FormProvider> </FormProvider>
<p> <p>
<Translate> <i18n.Translate>
Clearing the access token will mean public access to the instance Clearing the access token will mean public access to the instance
</Translate> </i18n.Translate>
</p> </p>
</div> </div>
<div class="column" /> <div class="column" />
@ -353,16 +359,18 @@ export function SetTokenNewInstanceModal({
new_token: "", new_token: "",
repeat_token: "", repeat_token: "",
}); });
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const errors = { const errors = {
new_token: !form.new_token new_token: !form.new_token
? i18n`cannot be empty` ? i18n.str`cannot be empty`
: form.new_token === form.old_token : form.new_token === form.old_token
? i18n`cannot be the same as the old access token` ? i18n.str`cannot be the same as the old access token`
: undefined, : undefined,
repeat_token: repeat_token:
form.new_token !== form.repeat_token ? i18n`is not the same` : undefined, form.new_token !== form.repeat_token
? i18n.str`is not the same`
: undefined,
}; };
const hasErrors = Object.keys(errors).some( const hasErrors = Object.keys(errors).some(
@ -374,7 +382,7 @@ export function SetTokenNewInstanceModal({
<div class="modal-background " onClick={onCancel} /> <div class="modal-background " onClick={onCancel} />
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title">{i18n`You are setting the access token for the new instance`}</p> <p class="modal-card-title">{i18n.str`You are setting the access token for the new instance`}</p>
<button class="delete " aria-label="close" onClick={onCancel} /> <button class="delete " aria-label="close" onClick={onCancel} />
</header> </header>
<section class="modal-card-body is-main-section"> <section class="modal-card-body is-main-section">
@ -388,22 +396,22 @@ export function SetTokenNewInstanceModal({
> >
<Input<State> <Input<State>
name="new_token" name="new_token"
label={i18n`New access token`} label={i18n.str`New access token`}
tooltip={i18n`next access token to be used`} tooltip={i18n.str`next access token to be used`}
inputType="password" inputType="password"
/> />
<Input<State> <Input<State>
name="repeat_token" name="repeat_token"
label={i18n`Repeat access token`} label={i18n.str`Repeat access token`}
tooltip={i18n`confirm the same access token`} tooltip={i18n.str`confirm the same access token`}
inputType="password" inputType="password"
/> />
</FormProvider> </FormProvider>
<p> <p>
<Translate> <i18n.Translate>
With external authorization method no check will be done by With external authorization method no check will be done by
the merchant backend the merchant backend
</Translate> </i18n.Translate>
</p> </p>
</div> </div>
<div class="column" /> <div class="column" />
@ -416,19 +424,19 @@ export function SetTokenNewInstanceModal({
onClick={onClear} onClick={onClear}
disabled={onClear === undefined} disabled={onClear === undefined}
> >
<Translate>Set external authorization</Translate> <i18n.Translate>Set external authorization</i18n.Translate>
</button> </button>
)} )}
<div class="buttons is-right" style={{ width: "100%" }}> <div class="buttons is-right" style={{ width: "100%" }}>
<button class="button " onClick={onCancel}> <button class="button " onClick={onCancel}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
<button <button
class="button is-info" class="button is-info"
onClick={() => onConfirm(form.new_token!)} onClick={() => onConfirm(form.new_token!)}
disabled={hasErrors} disabled={hasErrors}
> >
<Translate>Set access token</Translate> <i18n.Translate>Set access token</i18n.Translate>
</button> </button>
</div> </div>
</footer> </footer>
@ -443,14 +451,14 @@ export function SetTokenNewInstanceModal({
} }
export function LoadingModal({ onCancel }: { onCancel: () => void }): VNode { export function LoadingModal({ onCancel }: { onCancel: () => void }): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="modal is-active"> <div class="modal is-active">
<div class="modal-background " onClick={onCancel} /> <div class="modal-background " onClick={onCancel} />
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title"> <p class="modal-card-title">
<Translate>Operation in progress...</Translate> <i18n.Translate>Operation in progress...</i18n.Translate>
</p> </p>
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
@ -459,12 +467,12 @@ export function LoadingModal({ onCancel }: { onCancel: () => void }): VNode {
<Spinner /> <Spinner />
<div class="column" /> <div class="column" />
</div> </div>
<p>{i18n`The operation will be automatically canceled after ${DEFAULT_REQUEST_TIMEOUT} seconds`}</p> <p>{i18n.str`The operation will be automatically canceled after ${DEFAULT_REQUEST_TIMEOUT} seconds`}</p>
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<div class="buttons is-right" style={{ width: "100%" }}> <div class="buttons is-right" style={{ width: "100%" }}>
<button class="button " onClick={onCancel}> <button class="button " onClick={onCancel}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
</div> </div>
</footer> </footer>

View File

@ -19,9 +19,9 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useTranslator } from "../../i18n/index.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}

View File

@ -13,14 +13,14 @@
You should have received a copy of the GNU General Public License along with 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/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { FormProvider, FormErrors } from "../form/FormProvider.js"; import { MerchantBackend, WithId } from "../../declaration.js";
import { ProductMap } from "../../paths/instance/orders/create/CreatePage.js";
import { FormErrors, FormProvider } from "../form/FormProvider.js";
import { InputNumber } from "../form/InputNumber.js"; import { InputNumber } from "../form/InputNumber.js";
import { InputSearchProduct } from "../form/InputSearchProduct.js"; import { InputSearchProduct } from "../form/InputSearchProduct.js";
import { MerchantBackend, WithId } from "../../declaration.js";
import { Translate, useTranslator } from "../../i18n/index.js";
import { ProductMap } from "../../paths/instance/orders/create/CreatePage.js";
type Form = { type Form = {
product: MerchantBackend.Products.ProductDetail & WithId; product: MerchantBackend.Products.ProductDetail & WithId;
@ -45,21 +45,23 @@ export function InventoryProductForm({
const [state, setState] = useState<Partial<Form>>(initialState); const [state, setState] = useState<Partial<Form>>(initialState);
const [errors, setErrors] = useState<FormErrors<Form>>({}); const [errors, setErrors] = useState<FormErrors<Form>>({});
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const productWithInfiniteStock = const productWithInfiniteStock =
state.product && state.product.total_stock === -1; state.product && state.product.total_stock === -1;
const submit = (): void => { const submit = (): void => {
if (!state.product) { if (!state.product) {
setErrors({ product: i18n`You must enter a valid product identifier.` }); setErrors({
product: i18n.str`You must enter a valid product identifier.`,
});
return; return;
} }
if (productWithInfiniteStock) { if (productWithInfiniteStock) {
onAddProduct(state.product, 1); onAddProduct(state.product, 1);
} else { } else {
if (!state.quantity || state.quantity <= 0) { if (!state.quantity || state.quantity <= 0) {
setErrors({ quantity: i18n`Quantity must be greater than 0!` }); setErrors({ quantity: i18n.str`Quantity must be greater than 0!` });
return; return;
} }
const currentStock = const currentStock =
@ -71,7 +73,7 @@ export function InventoryProductForm({
if (state.quantity + p.quantity > currentStock) { if (state.quantity + p.quantity > currentStock) {
const left = currentStock - p.quantity; const left = currentStock - p.quantity;
setErrors({ setErrors({
quantity: i18n`This quantity exceeds remaining stock. Currently, only ${left} units remain unreserved in stock.`, quantity: i18n.str`This quantity exceeds remaining stock. Currently, only ${left} units remain unreserved in stock.`,
}); });
return; return;
} }
@ -80,7 +82,7 @@ export function InventoryProductForm({
if (state.quantity > currentStock) { if (state.quantity > currentStock) {
const left = currentStock; const left = currentStock;
setErrors({ setErrors({
quantity: i18n`This quantity exceeds remaining stock. Currently, only ${left} units remain unreserved in stock.`, quantity: i18n.str`This quantity exceeds remaining stock. Currently, only ${left} units remain unreserved in stock.`,
}); });
return; return;
} }
@ -104,15 +106,15 @@ export function InventoryProductForm({
{!productWithInfiniteStock && ( {!productWithInfiniteStock && (
<InputNumber<Form> <InputNumber<Form>
name="quantity" name="quantity"
label={i18n`Quantity`} label={i18n.str`Quantity`}
tooltip={i18n`how many products will be added`} tooltip={i18n.str`how many products will be added`}
/> />
)} )}
</div> </div>
<div class="column"> <div class="column">
<div class="buttons is-right"> <div class="buttons is-right">
<button class="button is-success" onClick={submit}> <button class="button is-success" onClick={submit}>
<Translate>Add from inventory</Translate> <i18n.Translate>Add from inventory</i18n.Translate>
</button> </button>
</div> </div>
</div> </div>

View File

@ -13,19 +13,19 @@
You should have received a copy of the GNU General Public License along with 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/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useCallback, useEffect, useState } from "preact/hooks"; import { useCallback, useEffect, useState } from "preact/hooks";
import * as yup from "yup"; import * as yup from "yup";
import { MerchantBackend } from "../../declaration.js";
import { useListener } from "../../hooks/listener.js";
import { NonInventoryProductSchema as schema } from "../../schemas/index.js";
import { FormErrors, FormProvider } from "../form/FormProvider.js"; import { FormErrors, FormProvider } from "../form/FormProvider.js";
import { Input } from "../form/Input.js"; import { Input } from "../form/Input.js";
import { InputCurrency } from "../form/InputCurrency.js"; import { InputCurrency } from "../form/InputCurrency.js";
import { InputImage } from "../form/InputImage.js"; import { InputImage } from "../form/InputImage.js";
import { InputNumber } from "../form/InputNumber.js"; import { InputNumber } from "../form/InputNumber.js";
import { InputTaxes } from "../form/InputTaxes.js"; import { InputTaxes } from "../form/InputTaxes.js";
import { MerchantBackend } from "../../declaration.js";
import { useListener } from "../../hooks/listener.js";
import { Translate, useTranslator } from "../../i18n/index.js";
import { NonInventoryProductSchema as schema } from "../../schemas/index.js";
type Entity = MerchantBackend.Product; type Entity = MerchantBackend.Product;
@ -62,17 +62,17 @@ export function NonInventoryProductFrom({
return Promise.resolve(); return Promise.resolve();
}); });
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<Fragment> <Fragment>
<div class="buttons"> <div class="buttons">
<button <button
class="button is-success" class="button is-success"
data-tooltip={i18n`describe and add a product that is not in the inventory list`} data-tooltip={i18n.str`describe and add a product that is not in the inventory list`}
onClick={() => setShowCreateProduct(true)} onClick={() => setShowCreateProduct(true)}
> >
<Translate>Add custom product</Translate> <i18n.Translate>Add custom product</i18n.Translate>
</button> </button>
</div> </div>
{showCreateProduct && ( {showCreateProduct && (
@ -83,7 +83,7 @@ export function NonInventoryProductFrom({
/> />
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title">{i18n`Complete information of the product`}</p> <p class="modal-card-title">{i18n.str`Complete information of the product`}</p>
<button <button
class="delete " class="delete "
aria-label="close" aria-label="close"
@ -102,14 +102,14 @@ export function NonInventoryProductFrom({
class="button " class="button "
onClick={() => setShowCreateProduct(false)} onClick={() => setShowCreateProduct(false)}
> >
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
<button <button
class="button is-info " class="button is-info "
disabled={!submitForm} disabled={!submitForm}
onClick={submitForm} onClick={submitForm}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</button> </button>
</div> </div>
</footer> </footer>
@ -170,7 +170,7 @@ export function ProductForm({ onSubscribe, initial }: ProductProps): VNode {
onSubscribe(hasErrors ? undefined : submit); onSubscribe(hasErrors ? undefined : submit);
}, [submit, hasErrors]); }, [submit, hasErrors]);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div> <div>
@ -182,33 +182,33 @@ export function ProductForm({ onSubscribe, initial }: ProductProps): VNode {
> >
<InputImage<NonInventoryProduct> <InputImage<NonInventoryProduct>
name="image" name="image"
label={i18n`Image`} label={i18n.str`Image`}
tooltip={i18n`photo of the product`} tooltip={i18n.str`photo of the product`}
/> />
<Input<NonInventoryProduct> <Input<NonInventoryProduct>
name="description" name="description"
inputType="multiline" inputType="multiline"
label={i18n`Description`} label={i18n.str`Description`}
tooltip={i18n`full product description`} tooltip={i18n.str`full product description`}
/> />
<Input<NonInventoryProduct> <Input<NonInventoryProduct>
name="unit" name="unit"
label={i18n`Unit`} label={i18n.str`Unit`}
tooltip={i18n`name of the product unit`} tooltip={i18n.str`name of the product unit`}
/> />
<InputCurrency<NonInventoryProduct> <InputCurrency<NonInventoryProduct>
name="price" name="price"
label={i18n`Price`} label={i18n.str`Price`}
tooltip={i18n`amount in the current currency`} tooltip={i18n.str`amount in the current currency`}
/> />
<InputNumber<NonInventoryProduct> <InputNumber<NonInventoryProduct>
name="quantity" name="quantity"
label={i18n`Quantity`} label={i18n.str`Quantity`}
tooltip={i18n`how many products will be added`} tooltip={i18n.str`how many products will be added`}
/> />
<InputTaxes<NonInventoryProduct> name="taxes" label={i18n`Taxes`} /> <InputTaxes<NonInventoryProduct> name="taxes" label={i18n.str`Taxes`} />
</FormProvider> </FormProvider>
</div> </div>
); );

View File

@ -19,17 +19,17 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h } from "preact"; import { h } from "preact";
import { useCallback, useEffect, useState } from "preact/hooks"; import { useCallback, useEffect, useState } from "preact/hooks";
import * as yup from "yup"; import * as yup from "yup";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { MerchantBackend } from "../../declaration.js"; import { MerchantBackend } from "../../declaration.js";
import { useTranslator } from "../../i18n/index.js";
import { import {
ProductCreateSchema as createSchema, ProductCreateSchema as createSchema,
ProductUpdateSchema as updateSchema, ProductUpdateSchema as updateSchema,
} from "../../schemas/index.js"; } from "../../schemas/index.js";
import { FormProvider, FormErrors } from "../form/FormProvider.js"; import { FormErrors, FormProvider } from "../form/FormProvider.js";
import { Input } from "../form/Input.js"; import { Input } from "../form/Input.js";
import { InputCurrency } from "../form/InputCurrency.js"; import { InputCurrency } from "../form/InputCurrency.js";
import { InputImage } from "../form/InputImage.js"; import { InputImage } from "../form/InputImage.js";
@ -115,7 +115,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
}, [submit, hasErrors]); }, [submit, hasErrors]);
const backend = useBackendContext(); const backend = useBackendContext();
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div> <div>
@ -129,46 +129,46 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
<InputWithAddon<Entity> <InputWithAddon<Entity>
name="product_id" name="product_id"
addonBefore={`${backend.url}/product/`} addonBefore={`${backend.url}/product/`}
label={i18n`ID`} label={i18n.str`ID`}
tooltip={i18n`product identification to use in URLs (for internal use only)`} tooltip={i18n.str`product identification to use in URLs (for internal use only)`}
/> />
)} )}
<InputImage<Entity> <InputImage<Entity>
name="image" name="image"
label={i18n`Image`} label={i18n.str`Image`}
tooltip={i18n`illustration of the product for customers`} tooltip={i18n.str`illustration of the product for customers`}
/> />
<Input<Entity> <Input<Entity>
name="description" name="description"
inputType="multiline" inputType="multiline"
label={i18n`Description`} label={i18n.str`Description`}
tooltip={i18n`product description for customers`} tooltip={i18n.str`product description for customers`}
/> />
<InputNumber<Entity> <InputNumber<Entity>
name="minimum_age" name="minimum_age"
label={i18n`Age restricted`} label={i18n.str`Age restricted`}
tooltip={i18n`is this product restricted for customer below certain age?`} tooltip={i18n.str`is this product restricted for customer below certain age?`}
/> />
<Input<Entity> <Input<Entity>
name="unit" name="unit"
label={i18n`Unit`} label={i18n.str`Unit`}
tooltip={i18n`unit describing quantity of product sold (e.g. 2 kilograms, 5 liters, 3 items, 5 meters) for customers`} tooltip={i18n.str`unit describing quantity of product sold (e.g. 2 kilograms, 5 liters, 3 items, 5 meters) for customers`}
/> />
<InputCurrency<Entity> <InputCurrency<Entity>
name="price" name="price"
label={i18n`Price`} label={i18n.str`Price`}
tooltip={i18n`sale price for customers, including taxes, for above units of the product`} tooltip={i18n.str`sale price for customers, including taxes, for above units of the product`}
/> />
<InputStock <InputStock
name="stock" name="stock"
label={i18n`Stock`} label={i18n.str`Stock`}
alreadyExist={alreadyExist} alreadyExist={alreadyExist}
tooltip={i18n`product inventory for products with finite supply (for internal use only)`} tooltip={i18n.str`product inventory for products with finite supply (for internal use only)`}
/> />
<InputTaxes<Entity> <InputTaxes<Entity>
name="taxes" name="taxes"
label={i18n`Taxes`} label={i18n.str`Taxes`}
tooltip={i18n`taxes included in the product price, exposed to customers`} tooltip={i18n.str`taxes included in the product price, exposed to customers`}
/> />
</FormProvider> </FormProvider>
</div> </div>

View File

@ -16,8 +16,8 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import emptyImage from "../../assets/empty.png"; import emptyImage from "../../assets/empty.png";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { MerchantBackend } from "../../declaration.js"; import { MerchantBackend } from "../../declaration.js";
import { Translate } from "../../i18n/index.js";
interface Props { interface Props {
list: MerchantBackend.Product[]; list: MerchantBackend.Product[];
@ -28,25 +28,26 @@ interface Props {
}[]; }[];
} }
export function ProductList({ list, actions = [] }: Props): VNode { export function ProductList({ list, actions = [] }: Props): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th> <th>
<Translate>image</Translate> <i18n.Translate>image</i18n.Translate>
</th> </th>
<th> <th>
<Translate>description</Translate> <i18n.Translate>description</i18n.Translate>
</th> </th>
<th> <th>
<Translate>quantity</Translate> <i18n.Translate>quantity</i18n.Translate>
</th> </th>
<th> <th>
<Translate>unit price</Translate> <i18n.Translate>unit price</i18n.Translate>
</th> </th>
<th> <th>
<Translate>total price</Translate> <i18n.Translate>total price</i18n.Translate>
</th> </th>
<th /> <th />
</tr> </tr>

View File

@ -29,7 +29,7 @@ interface Type {
useSWRInfinite: typeof useSWRInfinite; useSWRInfinite: typeof useSWRInfinite;
} }
const Context = createContext<Type>({} as any); const Context = createContext<Type>({} as Type);
export const useFetchContext = (): Type => useContext(Context); export const useFetchContext = (): Type => useContext(Context);
export const FetchContextProvider = ({ export const FetchContextProvider = ({

View File

@ -1,35 +0,0 @@
/*
This file is part of GNU Taler
(C) 2021-2023 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 { createContext } from "preact";
import { useContext } from "preact/hooks";
interface Type {
id: string;
token?: string;
admin?: boolean;
changeToken: (t?: string) => void;
}
const Context = createContext<Type>({} as any);
export const ListenerContextProvider = Context.Provider;
export const useListenerContext = (): Type => useContext(Context);

View File

@ -1,66 +0,0 @@
/*
This file is part of GNU Taler
(C) 2021-2023 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 { createContext, h, VNode } from "preact";
import { useContext, useEffect } from "preact/hooks";
import { useLang } from "../hooks";
import * as jedLib from "jed";
import { strings } from "../i18n/strings";
interface Type {
lang: string;
handler: any;
changeLanguage: (l: string) => void;
}
const initial = {
lang: "en",
handler: null,
changeLanguage: () => {
// do not change anything
},
};
const Context = createContext<Type>(initial);
interface Props {
initial?: string;
children: any;
forceLang?: string;
}
export const TranslationProvider = ({
initial,
children,
forceLang,
}: Props): VNode => {
const [lang, changeLanguage] = useLang(initial);
useEffect(() => {
if (forceLang) {
changeLanguage(forceLang);
}
});
const handler = new jedLib.Jed(strings[lang]);
return h(Context.Provider, {
value: { lang, handler, changeLanguage },
children,
});
};
export const useTranslationContext = (): Type => useContext(Context);

View File

@ -1,215 +0,0 @@
/*
This file is part of GNU Taler
(C) 2021-2023 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.js";
export type Translator = (
stringSeq: TemplateStringsArray,
...values: any[]
) => string;
export function useTranslator(): Translator {
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>;
}

View File

@ -20,6 +20,7 @@
*/ */
import { h, VNode, FunctionalComponent } from "preact"; import { h, VNode, FunctionalComponent } from "preact";
import { ConfigContextProvider } from "../../../context/config.js";
import { CreatePage as TestedComponent } from "./CreatePage.js"; import { CreatePage as TestedComponent } from "./CreatePage.js";
export default { export default {
@ -35,7 +36,16 @@ function createExample<Props>(
Component: FunctionalComponent<Props>, Component: FunctionalComponent<Props>,
props: Partial<Props>, props: Partial<Props>,
) { ) {
const r = (args: any) => <Component {...args} />; const r = (args: any) => (
<ConfigContextProvider
value={{
currency: "ARS",
version: "1",
}}
>
<Component {...args} />
</ConfigContextProvider>
);
r.args = props; r.args = props;
return r; return r;
} }

View File

@ -19,20 +19,19 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { Amounts } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import * as yup from "yup";
import { AsyncButton } from "../../../components/exception/AsyncButton.js"; import { AsyncButton } from "../../../components/exception/AsyncButton.js";
import { import {
FormErrors, FormErrors,
FormProvider, FormProvider,
} from "../../../components/form/FormProvider.js"; } from "../../../components/form/FormProvider.js";
import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js";
import { SetTokenNewInstanceModal } from "../../../components/modal/index.js"; import { SetTokenNewInstanceModal } from "../../../components/modal/index.js";
import { MerchantBackend } from "../../../declaration.js"; import { MerchantBackend } from "../../../declaration.js";
import { Translate, useTranslator } from "../../../i18n/index.js";
import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js";
import { INSTANCE_ID_REGEX, PAYTO_REGEX } from "../../../utils/constants.js"; import { INSTANCE_ID_REGEX, PAYTO_REGEX } from "../../../utils/constants.js";
import { Amounts } from "@gnu-taler/taler-util";
import { undefinedIfEmpty } from "../../../utils/table.js"; import { undefinedIfEmpty } from "../../../utils/table.js";
export type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & { export type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & {
@ -61,55 +60,57 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
const [isTokenDialogActive, updateIsTokenDialogActive] = const [isTokenDialogActive, updateIsTokenDialogActive] =
useState<boolean>(false); useState<boolean>(false);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const errors: FormErrors<Entity> = { const errors: FormErrors<Entity> = {
id: !value.id id: !value.id
? i18n`required` ? i18n.str`required`
: !INSTANCE_ID_REGEX.test(value.id) : !INSTANCE_ID_REGEX.test(value.id)
? i18n`is not valid` ? i18n.str`is not valid`
: undefined, : undefined,
name: !value.name ? i18n`required` : undefined, name: !value.name ? i18n.str`required` : undefined,
payto_uris: payto_uris:
!value.payto_uris || !value.payto_uris.length !value.payto_uris || !value.payto_uris.length
? i18n`required` ? i18n.str`required`
: undefinedIfEmpty( : undefinedIfEmpty(
value.payto_uris.map((p) => { value.payto_uris.map((p) => {
return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined; return !PAYTO_REGEX.test(p) ? i18n.str`is not valid` : undefined;
}), }),
), ),
default_max_deposit_fee: !value.default_max_deposit_fee default_max_deposit_fee: !value.default_max_deposit_fee
? i18n`required` ? i18n.str`required`
: !Amounts.parse(value.default_max_deposit_fee) : !Amounts.parse(value.default_max_deposit_fee)
? i18n`invalid format` ? i18n.str`invalid format`
: undefined, : undefined,
default_max_wire_fee: !value.default_max_wire_fee default_max_wire_fee: !value.default_max_wire_fee
? i18n`required` ? i18n.str`required`
: !Amounts.parse(value.default_max_wire_fee) : !Amounts.parse(value.default_max_wire_fee)
? i18n`invalid format` ? i18n.str`invalid format`
: undefined, : undefined,
default_wire_fee_amortization: default_wire_fee_amortization:
value.default_wire_fee_amortization === undefined value.default_wire_fee_amortization === undefined
? i18n`required` ? i18n.str`required`
: isNaN(value.default_wire_fee_amortization) : isNaN(value.default_wire_fee_amortization)
? i18n`is not a number` ? i18n.str`is not a number`
: value.default_wire_fee_amortization < 1 : value.default_wire_fee_amortization < 1
? i18n`must be 1 or greater` ? i18n.str`must be 1 or greater`
: undefined, : undefined,
default_pay_delay: !value.default_pay_delay ? i18n`required` : undefined, default_pay_delay: !value.default_pay_delay
? i18n.str`required`
: undefined,
default_wire_transfer_delay: !value.default_wire_transfer_delay default_wire_transfer_delay: !value.default_wire_transfer_delay
? i18n`required` ? i18n.str`required`
: undefined, : undefined,
address: undefinedIfEmpty({ address: undefinedIfEmpty({
address_lines: address_lines:
value.address?.address_lines && value.address?.address_lines.length > 7 value.address?.address_lines && value.address?.address_lines.length > 7
? i18n`max 7 lines` ? i18n.str`max 7 lines`
: undefined, : undefined,
}), }),
jurisdiction: undefinedIfEmpty({ jurisdiction: undefinedIfEmpty({
address_lines: address_lines:
value.address?.address_lines && value.address?.address_lines.length > 7 value.address?.address_lines && value.address?.address_lines.length > 7
? i18n`max 7 lines` ? i18n.str`max 7 lines`
: undefined, : undefined,
}), }),
}; };
@ -174,14 +175,14 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
<h1 class="title"> <h1 class="title">
<button <button
class="button is-danger has-tooltip-bottom" class="button is-danger has-tooltip-bottom"
data-tooltip={i18n`change authorization configuration`} data-tooltip={i18n.str`change authorization configuration`}
onClick={() => updateIsTokenDialogActive(true)} onClick={() => updateIsTokenDialogActive(true)}
> >
<div class="icon is-centered"> <div class="icon is-centered">
<i class="mdi mdi-lock-reset" /> <i class="mdi mdi-lock-reset" />
</div> </div>
<span> <span>
<Translate>Set access token</Translate> <i18n.Translate>Set access token</i18n.Translate>
</span> </span>
</button> </button>
</h1> </h1>
@ -205,7 +206,7 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
{onBack && ( {onBack && (
<button class="button" onClick={onBack}> <button class="button" onClick={onBack}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
)} )}
<AsyncButton <AsyncButton
@ -213,11 +214,11 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
disabled={!isTokenSet || hasErrors} disabled={!isTokenSet || hasErrors}
data-tooltip={ data-tooltip={
hasErrors hasErrors
? i18n`Need to complete marked fields and choose authorization method` ? i18n.str`Need to complete marked fields and choose authorization method`
: "confirm operation" : "confirm operation"
} }
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</AsyncButton> </AsyncButton>
</div> </div>
</div> </div>

View File

@ -17,12 +17,12 @@
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { NotificationCard } from "../../../components/menu/index.js"; import { NotificationCard } from "../../../components/menu/index.js";
import { MerchantBackend } from "../../../declaration.js"; import { MerchantBackend } from "../../../declaration.js";
import { useAdminAPI } from "../../../hooks/instance.js"; import { useAdminAPI } from "../../../hooks/instance.js";
import { useTranslator } from "../../../i18n/index.js";
import { Notification } from "../../../utils/types.js"; import { Notification } from "../../../utils/types.js";
import { CreatePage } from "./CreatePage.js"; import { CreatePage } from "./CreatePage.js";
import { InstanceCreatedSuccessfully } from "./InstanceCreatedSuccessfully.js"; import { InstanceCreatedSuccessfully } from "./InstanceCreatedSuccessfully.js";
@ -38,7 +38,7 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode {
const { createInstance } = useAdminAPI(); const { createInstance } = useAdminAPI();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const [createdOk, setCreatedOk] = useState<Entity | undefined>(undefined); const [createdOk, setCreatedOk] = useState<Entity | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
if (createdOk) { if (createdOk) {
return ( return (
@ -62,7 +62,7 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode {
}) })
.catch((error) => { .catch((error) => {
setNotif({ setNotif({
message: i18n`Failed to create instance`, message: i18n.str`Failed to create instance`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}); });

View File

@ -20,6 +20,7 @@
*/ */
import { h, VNode, FunctionalComponent } from "preact"; import { h, VNode, FunctionalComponent } from "preact";
import { ConfigContextProvider } from "../../../context/config.js";
import { CreatePage as TestedComponent } from "./CreatePage.js"; import { CreatePage as TestedComponent } from "./CreatePage.js";
export default { export default {
@ -32,17 +33,20 @@ export default {
}; };
function createExample<Props>( function createExample<Props>(
Component: FunctionalComponent<Props>, Internal: FunctionalComponent<Props>,
props: Partial<Props>, props: Partial<Props>,
) { ) {
const r = (args: any) => h(Component, args); const component = (args: any) => (
// const r = (args: any) => <Component {...args} />; <ConfigContextProvider
r.args = props; value={{
return r; currency: "TESTKUDOS",
version: "1",
}}
>
<Internal {...(props as any)} />
</ConfigContextProvider>
);
return { component, props };
} }
export const Example = createExample(TestedComponent, {}); export const Example = createExample(TestedComponent, {});
// export const Example = (a: any): VNode => <CreatePage {...a} />;
// Example.args = {
// isLoading: false
// }

View File

@ -19,10 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { StateUpdater, useEffect, useState } from "preact/hooks"; import { StateUpdater, useEffect, useState } from "preact/hooks";
import { MerchantBackend } from "../../../declaration.js"; import { MerchantBackend } from "../../../declaration.js";
import { Translate, useTranslator } from "../../../i18n/index.js";
interface Props { interface Props {
instances: MerchantBackend.Instances.Instance[]; instances: MerchantBackend.Instances.Instance[];
@ -68,7 +68,7 @@ export function CardTable({
} }
}, [actionQueue, selected, onUpdate]); }, [actionQueue, selected, onUpdate]);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="card has-table"> <div class="card has-table">
@ -77,7 +77,7 @@ export function CardTable({
<span class="icon"> <span class="icon">
<i class="mdi mdi-desktop-mac" /> <i class="mdi mdi-desktop-mac" />
</span> </span>
<Translate>Instances</Translate> <i18n.Translate>Instances</i18n.Translate>
</p> </p>
<div class="card-header-icon" aria-label="more options"> <div class="card-header-icon" aria-label="more options">
@ -90,11 +90,14 @@ export function CardTable({
) )
} }
> >
<Translate>Delete</Translate> <i18n.Translate>Delete</i18n.Translate>
</button> </button>
</div> </div>
<div class="card-header-icon" aria-label="more options"> <div class="card-header-icon" aria-label="more options">
<span class="has-tooltip-left" data-tooltip={i18n`add new instance`}> <span
class="has-tooltip-left"
data-tooltip={i18n.str`add new instance`}
>
<button class="button is-info" type="button" onClick={onCreate}> <button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small"> <span class="icon is-small">
<i class="mdi mdi-plus mdi-36px" /> <i class="mdi mdi-plus mdi-36px" />
@ -149,6 +152,7 @@ function Table({
onDelete, onDelete,
onPurge, onPurge,
}: TableProps): VNode { }: TableProps): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@ -171,10 +175,10 @@ function Table({
</label> </label>
</th> </th>
<th> <th>
<Translate>ID</Translate> <i18n.Translate>ID</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Name</Translate> <i18n.Translate>Name</i18n.Translate>
</th> </th>
<th /> <th />
</tr> </tr>
@ -213,7 +217,7 @@ function Table({
type="button" type="button"
onClick={(): void => onUpdate(i.id)} onClick={(): void => onUpdate(i.id)}
> >
<Translate>Edit</Translate> <i18n.Translate>Edit</i18n.Translate>
</button> </button>
{!i.deleted && ( {!i.deleted && (
<button <button
@ -221,7 +225,7 @@ function Table({
type="button" type="button"
onClick={(): void => onDelete(i)} onClick={(): void => onDelete(i)}
> >
<Translate>Delete</Translate> <i18n.Translate>Delete</i18n.Translate>
</button> </button>
)} )}
{i.deleted && ( {i.deleted && (
@ -230,7 +234,7 @@ function Table({
type="button" type="button"
onClick={(): void => onPurge(i)} onClick={(): void => onPurge(i)}
> >
<Translate>Purge</Translate> <i18n.Translate>Purge</i18n.Translate>
</button> </button>
)} )}
</div> </div>
@ -245,6 +249,7 @@ function Table({
} }
function EmptyTable(): VNode { function EmptyTable(): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="content has-text-grey has-text-centered"> <div class="content has-text-grey has-text-centered">
<p> <p>
@ -253,9 +258,9 @@ function EmptyTable(): VNode {
</span> </span>
</p> </p>
<p> <p>
<Translate> <i18n.Translate>
There is no instances yet, add more pressing the + sign There is no instances yet, add more pressing the + sign
</Translate> </i18n.Translate>
</p> </p>
</div> </div>
); );

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks";
import { MerchantBackend } from "../../../declaration.js"; import { MerchantBackend } from "../../../declaration.js";
import { CardTable as CardTableActive } from "./TableActive.js"; import { CardTable as CardTableActive } from "./TableActive.js";
import { useState } from "preact/hooks";
import { Translate, useTranslator } from "../../../i18n/index.js";
interface Props { interface Props {
instances: MerchantBackend.Instances.Instance[]; instances: MerchantBackend.Instances.Instance[];
@ -48,7 +48,7 @@ export function View({
const showIsActive = show === "active" ? "is-active" : ""; const showIsActive = show === "active" ? "is-active" : "";
const showIsDeleted = show === "deleted" ? "is-active" : ""; const showIsDeleted = show === "deleted" ? "is-active" : "";
const showAll = show === null ? "is-active" : ""; const showAll = show === null ? "is-active" : "";
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const showingInstances = showIsDeleted const showingInstances = showIsDeleted
? instances.filter((i) => i.deleted) ? instances.filter((i) => i.deleted)
@ -66,30 +66,30 @@ export function View({
<li class={showIsActive}> <li class={showIsActive}>
<div <div
class="has-tooltip-right" class="has-tooltip-right"
data-tooltip={i18n`Only show active instances`} data-tooltip={i18n.str`Only show active instances`}
> >
<a onClick={() => setShow("active")}> <a onClick={() => setShow("active")}>
<Translate>Active</Translate> <i18n.Translate>Active</i18n.Translate>
</a> </a>
</div> </div>
</li> </li>
<li class={showIsDeleted}> <li class={showIsDeleted}>
<div <div
class="has-tooltip-right" class="has-tooltip-right"
data-tooltip={i18n`Only show deleted instances`} data-tooltip={i18n.str`Only show deleted instances`}
> >
<a onClick={() => setShow("deleted")}> <a onClick={() => setShow("deleted")}>
<Translate>Deleted</Translate> <i18n.Translate>Deleted</i18n.Translate>
</a> </a>
</div> </div>
</li> </li>
<li class={showAll}> <li class={showAll}>
<div <div
class="has-tooltip-right" class="has-tooltip-right"
data-tooltip={i18n`Show all instances`} data-tooltip={i18n.str`Show all instances`}
> >
<a onClick={() => setShow(null)}> <a onClick={() => setShow(null)}>
<Translate>All</Translate> <i18n.Translate>All</i18n.Translate>
</a> </a>
</div> </div>
</li> </li>

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Loading } from "../../../components/exception/loading.js"; import { Loading } from "../../../components/exception/loading.js";
@ -27,7 +28,6 @@ import { DeleteModal, PurgeModal } from "../../../components/modal/index.js";
import { MerchantBackend } from "../../../declaration.js"; import { MerchantBackend } from "../../../declaration.js";
import { HttpError } from "../../../hooks/backend.js"; import { HttpError } from "../../../hooks/backend.js";
import { useAdminAPI, useBackendInstances } from "../../../hooks/instance.js"; import { useAdminAPI, useBackendInstances } from "../../../hooks/instance.js";
import { useTranslator } from "../../../i18n/index.js";
import { Notification } from "../../../utils/types.js"; import { Notification } from "../../../utils/types.js";
import { View } from "./View.js"; import { View } from "./View.js";
@ -56,7 +56,7 @@ export default function Instances({
useState<MerchantBackend.Instances.Instance | null>(null); useState<MerchantBackend.Instances.Instance | null>(null);
const { deleteInstance, purgeInstance } = useAdminAPI(); const { deleteInstance, purgeInstance } = useAdminAPI();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isUnauthorized) return onUnauthorized();
if (result.clientError && result.isNotfound) return onNotFound(); if (result.clientError && result.isNotfound) return onNotFound();
@ -84,12 +84,12 @@ export default function Instances({
await deleteInstance(deleting.id); await deleteInstance(deleting.id);
// pushNotification({ message: 'delete_success', type: 'SUCCESS' }) // pushNotification({ message: 'delete_success', type: 'SUCCESS' })
setNotif({ setNotif({
message: i18n`Instance "${deleting.name}" (ID: ${deleting.id}) has been deleted`, message: i18n.str`Instance "${deleting.name}" (ID: ${deleting.id}) has been deleted`,
type: "SUCCESS", type: "SUCCESS",
}); });
} catch (error) { } catch (error) {
setNotif({ setNotif({
message: i18n`Failed to delete instance`, message: i18n.str`Failed to delete instance`,
type: "ERROR", type: "ERROR",
description: error instanceof Error ? error.message : undefined, description: error instanceof Error ? error.message : undefined,
}); });
@ -107,12 +107,12 @@ export default function Instances({
try { try {
await purgeInstance(purging.id); await purgeInstance(purging.id);
setNotif({ setNotif({
message: i18n`Instance "${purging.name}" (ID: ${purging.id}) has been disabled`, message: i18n.str`Instance "${purging.name}" (ID: ${purging.id}) has been disabled`,
type: "SUCCESS", type: "SUCCESS",
}); });
} catch (error) { } catch (error) {
setNotif({ setNotif({
message: i18n`Failed to purge instance`, message: i18n.str`Failed to purge instance`,
type: "ERROR", type: "ERROR",
description: error instanceof Error ? error.message : undefined, description: error instanceof Error ? error.message : undefined,
}); });

View File

@ -19,12 +19,12 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { FormProvider } from "../../../components/form/FormProvider.js"; import { FormProvider } from "../../../components/form/FormProvider.js";
import { Input } from "../../../components/form/Input.js"; import { Input } from "../../../components/form/Input.js";
import { MerchantBackend } from "../../../declaration.js"; import { MerchantBackend } from "../../../declaration.js";
import { useTranslator } from "../../../i18n/index.js";
type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage; type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage;
interface Props { interface Props {
@ -49,7 +49,7 @@ function convert(
export function DetailPage({ selected }: Props): VNode { export function DetailPage({ selected }: Props): VNode {
const [value, valueHandler] = useState<Partial<Entity>>(convert(selected)); const [value, valueHandler] = useState<Partial<Entity>>(convert(selected));
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div> <div>
@ -73,11 +73,11 @@ export function DetailPage({ selected }: Props): VNode {
<div class="column" /> <div class="column" />
<div class="column is-6"> <div class="column is-6">
<FormProvider<Entity> object={value} valueHandler={valueHandler}> <FormProvider<Entity> object={value} valueHandler={valueHandler}>
<Input<Entity> name="name" readonly label={i18n`Name`} /> <Input<Entity> name="name" readonly label={i18n.str`Name`} />
<Input<Entity> <Input<Entity>
name="payto_uris" name="payto_uris"
readonly readonly
label={i18n`Account address`} label={i18n.str`Account address`}
/> />
</FormProvider> </FormProvider>
</div> </div>

View File

@ -20,6 +20,7 @@
*/ */
import { h, VNode, FunctionalComponent } from "preact"; import { h, VNode, FunctionalComponent } from "preact";
import { ConfigContextProvider } from "../../../context/config.js";
import { DetailPage as TestedComponent } from "./DetailPage.js"; import { DetailPage as TestedComponent } from "./DetailPage.js";
export default { export default {
@ -32,12 +33,20 @@ export default {
}; };
function createExample<Props>( function createExample<Props>(
Component: FunctionalComponent<Props>, Internal: FunctionalComponent<Props>,
props: Partial<Props>, props: Partial<Props>,
) { ) {
const r = (args: any) => <Component {...args} />; const component = (args: any) => (
r.args = props; <ConfigContextProvider
return r; value={{
currency: "TESTKUDOS",
version: "1",
}}
>
<Internal {...(props as any)} />
</ConfigContextProvider>
);
return { component, props };
} }
export const Example = createExample(TestedComponent, { export const Example = createExample(TestedComponent, {

View File

@ -15,4 +15,3 @@
*/ */
export * as details from "./details/stories.js"; export * as details from "./details/stories.js";

View File

@ -19,16 +19,16 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
export interface Props { export interface Props {
status: MerchantBackend.Instances.AccountKycRedirects; status: MerchantBackend.Instances.AccountKycRedirects;
} }
export function ListPage({ status }: Props): VNode { export function ListPage({ status }: Props): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<section class="section is-main-section"> <section class="section is-main-section">
@ -38,7 +38,7 @@ export function ListPage({ status }: Props): VNode {
<span class="icon"> <span class="icon">
<i class="mdi mdi-clock" /> <i class="mdi mdi-clock" />
</span> </span>
<Translate>Pending KYC verification</Translate> <i18n.Translate>Pending KYC verification</i18n.Translate>
</p> </p>
<div class="card-header-icon" aria-label="more options" /> <div class="card-header-icon" aria-label="more options" />
@ -63,7 +63,7 @@ export function ListPage({ status }: Props): VNode {
<span class="icon"> <span class="icon">
<i class="mdi mdi-clock" /> <i class="mdi mdi-clock" />
</span> </span>
<Translate>Timed out</Translate> <i18n.Translate>Timed out</i18n.Translate>
</p> </p>
<div class="card-header-icon" aria-label="more options" /> <div class="card-header-icon" aria-label="more options" />
@ -93,19 +93,20 @@ interface TimedOutTableProps {
} }
function PendingTable({ entries }: PendingTableProps): VNode { function PendingTable({ entries }: PendingTableProps): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
<table class="table is-striped is-hoverable is-fullwidth"> <table class="table is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th> <th>
<Translate>Exchange</Translate> <i18n.Translate>Exchange</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Target account</Translate> <i18n.Translate>Target account</i18n.Translate>
</th> </th>
<th> <th>
<Translate>KYC URL</Translate> <i18n.Translate>KYC URL</i18n.Translate>
</th> </th>
</tr> </tr>
</thead> </thead>
@ -130,19 +131,20 @@ function PendingTable({ entries }: PendingTableProps): VNode {
} }
function TimedOutTable({ entries }: TimedOutTableProps): VNode { function TimedOutTable({ entries }: TimedOutTableProps): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
<table class="table is-striped is-hoverable is-fullwidth"> <table class="table is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th> <th>
<Translate>Exchange</Translate> <i18n.Translate>Exchange</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Code</Translate> <i18n.Translate>Code</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Http Status</Translate> <i18n.Translate>Http Status</i18n.Translate>
</th> </th>
</tr> </tr>
</thead> </thead>
@ -163,6 +165,7 @@ function TimedOutTable({ entries }: TimedOutTableProps): VNode {
} }
function EmptyTable(): VNode { function EmptyTable(): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="content has-text-grey has-text-centered"> <div class="content has-text-grey has-text-centered">
<p> <p>
@ -171,7 +174,7 @@ function EmptyTable(): VNode {
</span> </span>
</p> </p>
<p> <p>
<Translate>No pending kyc verification!</Translate> <i18n.Translate>No pending kyc verification!</i18n.Translate>
</p> </p>
</div> </div>
); );

View File

@ -19,29 +19,29 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { add, isAfter, isBefore, isFuture } from "date-fns";
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { add, isAfter, isBefore, isFuture } from "date-fns";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { import {
FormProvider,
FormErrors, FormErrors,
FormProvider,
} from "../../../../components/form/FormProvider.js"; } from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js"; import { Input } from "../../../../components/form/Input.js";
import { InputBoolean } from "../../../../components/form/InputBoolean.js";
import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputDate } from "../../../../components/form/InputDate.js"; import { InputDate } from "../../../../components/form/InputDate.js";
import { InputGroup } from "../../../../components/form/InputGroup.js"; import { InputGroup } from "../../../../components/form/InputGroup.js";
import { InputLocation } from "../../../../components/form/InputLocation.js"; import { InputLocation } from "../../../../components/form/InputLocation.js";
import { InputNumber } from "../../../../components/form/InputNumber.js";
import { InventoryProductForm } from "../../../../components/product/InventoryProductForm.js";
import { NonInventoryProductFrom } from "../../../../components/product/NonInventoryProductForm.js";
import { ProductList } from "../../../../components/product/ProductList.js"; import { ProductList } from "../../../../components/product/ProductList.js";
import { useConfigContext } from "../../../../context/config.js"; import { useConfigContext } from "../../../../context/config.js";
import { Duration, MerchantBackend, WithId } from "../../../../declaration.js"; import { Duration, MerchantBackend, WithId } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
import { OrderCreateSchema as schema } from "../../../../schemas/index.js"; import { OrderCreateSchema as schema } from "../../../../schemas/index.js";
import { rate } from "../../../../utils/amount.js"; import { rate } from "../../../../utils/amount.js";
import { InventoryProductForm } from "../../../../components/product/InventoryProductForm.js";
import { NonInventoryProductFrom } from "../../../../components/product/NonInventoryProductForm.js";
import { InputNumber } from "../../../../components/form/InputNumber.js";
import { InputBoolean } from "../../../../components/form/InputBoolean.js";
import { undefinedIfEmpty } from "../../../../utils/table.js"; import { undefinedIfEmpty } from "../../../../utils/table.js";
interface Props { interface Props {
@ -140,65 +140,65 @@ export function CreatePage({
const inventoryList = Object.values(value.inventoryProducts || {}); const inventoryList = Object.values(value.inventoryProducts || {});
const productList = Object.values(value.products || {}); const productList = Object.values(value.products || {});
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const errors: FormErrors<Entity> = { const errors: FormErrors<Entity> = {
pricing: undefinedIfEmpty({ pricing: undefinedIfEmpty({
summary: !value.pricing?.summary ? i18n`required` : undefined, summary: !value.pricing?.summary ? i18n.str`required` : undefined,
order_price: !value.pricing?.order_price order_price: !value.pricing?.order_price
? i18n`required` ? i18n.str`required`
: Amounts.isZero(value.pricing.order_price) : Amounts.isZero(value.pricing.order_price)
? i18n`must be greater than 0` ? i18n.str`must be greater than 0`
: undefined, : undefined,
}), }),
extra: extra:
value.extra && !stringIsValidJSON(value.extra) value.extra && !stringIsValidJSON(value.extra)
? i18n`not a valid json` ? i18n.str`not a valid json`
: undefined, : undefined,
payments: undefinedIfEmpty({ payments: undefinedIfEmpty({
refund_deadline: !value.payments?.refund_deadline refund_deadline: !value.payments?.refund_deadline
? undefined ? undefined
: !isFuture(value.payments.refund_deadline) : !isFuture(value.payments.refund_deadline)
? i18n`should be in the future` ? i18n.str`should be in the future`
: value.payments.pay_deadline && : value.payments.pay_deadline &&
isBefore(value.payments.refund_deadline, value.payments.pay_deadline) isBefore(value.payments.refund_deadline, value.payments.pay_deadline)
? i18n`refund deadline cannot be before pay deadline` ? i18n.str`refund deadline cannot be before pay deadline`
: value.payments.wire_transfer_deadline && : value.payments.wire_transfer_deadline &&
isBefore( isBefore(
value.payments.wire_transfer_deadline, value.payments.wire_transfer_deadline,
value.payments.refund_deadline, value.payments.refund_deadline,
) )
? i18n`wire transfer deadline cannot be before refund deadline` ? i18n.str`wire transfer deadline cannot be before refund deadline`
: undefined, : undefined,
pay_deadline: !value.payments?.pay_deadline pay_deadline: !value.payments?.pay_deadline
? undefined ? undefined
: !isFuture(value.payments.pay_deadline) : !isFuture(value.payments.pay_deadline)
? i18n`should be in the future` ? i18n.str`should be in the future`
: value.payments.wire_transfer_deadline && : value.payments.wire_transfer_deadline &&
isBefore( isBefore(
value.payments.wire_transfer_deadline, value.payments.wire_transfer_deadline,
value.payments.pay_deadline, value.payments.pay_deadline,
) )
? i18n`wire transfer deadline cannot be before pay deadline` ? i18n.str`wire transfer deadline cannot be before pay deadline`
: undefined, : undefined,
auto_refund_deadline: !value.payments?.auto_refund_deadline auto_refund_deadline: !value.payments?.auto_refund_deadline
? undefined ? undefined
: !isFuture(value.payments.auto_refund_deadline) : !isFuture(value.payments.auto_refund_deadline)
? i18n`should be in the future` ? i18n.str`should be in the future`
: !value.payments?.refund_deadline : !value.payments?.refund_deadline
? i18n`should have a refund deadline` ? i18n.str`should have a refund deadline`
: !isAfter( : !isAfter(
value.payments.refund_deadline, value.payments.refund_deadline,
value.payments.auto_refund_deadline, value.payments.auto_refund_deadline,
) )
? i18n`auto refund cannot be after refund deadline` ? i18n.str`auto refund cannot be after refund deadline`
: undefined, : undefined,
}), }),
shipping: undefinedIfEmpty({ shipping: undefinedIfEmpty({
delivery_date: !value.shipping?.delivery_date delivery_date: !value.shipping?.delivery_date
? undefined ? undefined
: !isFuture(value.shipping.delivery_date) : !isFuture(value.shipping.delivery_date)
? i18n`should be in the future` ? i18n.str`should be in the future`
: undefined, : undefined,
}), }),
}; };
@ -349,7 +349,7 @@ export function CreatePage({
{/* // FIXME: translating plural singular */} {/* // FIXME: translating plural singular */}
<InputGroup <InputGroup
name="inventory_products" name="inventory_products"
label={i18n`Manage products in order`} label={i18n.str`Manage products in order`}
alternative={ alternative={
allProducts.length > 0 && ( allProducts.length > 0 && (
<p> <p>
@ -358,7 +358,7 @@ export function CreatePage({
</p> </p>
) )
} }
tooltip={i18n`Manage list of products in the order.`} tooltip={i18n.str`Manage list of products in the order.`}
> >
<InventoryProductForm <InventoryProductForm
currentProducts={value.inventoryProducts || {}} currentProducts={value.inventoryProducts || {}}
@ -379,8 +379,8 @@ export function CreatePage({
list={allProducts} list={allProducts}
actions={[ actions={[
{ {
name: i18n`Remove`, name: i18n.str`Remove`,
tooltip: i18n`Remove this product from the order.`, tooltip: i18n.str`Remove this product from the order.`,
handler: (e, index) => { handler: (e, index) => {
if (e.product_id) { if (e.product_id) {
removeProductFromTheInventoryList(e.product_id); removeProductFromTheInventoryList(e.product_id);
@ -404,13 +404,13 @@ export function CreatePage({
<Fragment> <Fragment>
<InputCurrency <InputCurrency
name="pricing.products_price" name="pricing.products_price"
label={i18n`Total price`} label={i18n.str`Total price`}
readonly readonly
tooltip={i18n`total product price added up`} tooltip={i18n.str`total product price added up`}
/> />
<InputCurrency <InputCurrency
name="pricing.order_price" name="pricing.order_price"
label={i18n`Total price`} label={i18n.str`Total price`}
addonAfter={ addonAfter={
discountOrRise > 0 && discountOrRise > 0 &&
(discountOrRise < 1 (discountOrRise < 1
@ -419,103 +419,103 @@ export function CreatePage({
)}` )}`
: `rise of %${Math.round((discountOrRise - 1) * 100)}`) : `rise of %${Math.round((discountOrRise - 1) * 100)}`)
} }
tooltip={i18n`Amount to be paid by the customer`} tooltip={i18n.str`Amount to be paid by the customer`}
/> />
</Fragment> </Fragment>
) : ( ) : (
<InputCurrency <InputCurrency
name="pricing.order_price" name="pricing.order_price"
label={i18n`Order price`} label={i18n.str`Order price`}
tooltip={i18n`final order price`} tooltip={i18n.str`final order price`}
/> />
)} )}
<Input <Input
name="pricing.summary" name="pricing.summary"
inputType="multiline" inputType="multiline"
label={i18n`Summary`} label={i18n.str`Summary`}
tooltip={i18n`Title of the order to be shown to the customer`} tooltip={i18n.str`Title of the order to be shown to the customer`}
/> />
<InputGroup <InputGroup
name="shipping" name="shipping"
label={i18n`Shipping and Fulfillment`} label={i18n.str`Shipping and Fulfillment`}
initialActive initialActive
> >
<InputDate <InputDate
name="shipping.delivery_date" name="shipping.delivery_date"
label={i18n`Delivery date`} label={i18n.str`Delivery date`}
tooltip={i18n`Deadline for physical delivery assured by the merchant.`} tooltip={i18n.str`Deadline for physical delivery assured by the merchant.`}
/> />
{value.shipping?.delivery_date && ( {value.shipping?.delivery_date && (
<InputGroup <InputGroup
name="shipping.delivery_location" name="shipping.delivery_location"
label={i18n`Location`} label={i18n.str`Location`}
tooltip={i18n`address where the products will be delivered`} tooltip={i18n.str`address where the products will be delivered`}
> >
<InputLocation name="shipping.delivery_location" /> <InputLocation name="shipping.delivery_location" />
</InputGroup> </InputGroup>
)} )}
<Input <Input
name="shipping.fullfilment_url" name="shipping.fullfilment_url"
label={i18n`Fulfillment URL`} label={i18n.str`Fulfillment URL`}
tooltip={i18n`URL to which the user will be redirected after successful payment.`} tooltip={i18n.str`URL to which the user will be redirected after successful payment.`}
/> />
</InputGroup> </InputGroup>
<InputGroup <InputGroup
name="payments" name="payments"
label={i18n`Taler payment options`} label={i18n.str`Taler payment options`}
tooltip={i18n`Override default Taler payment settings for this order`} tooltip={i18n.str`Override default Taler payment settings for this order`}
> >
<InputDate <InputDate
name="payments.pay_deadline" name="payments.pay_deadline"
label={i18n`Payment deadline`} label={i18n.str`Payment deadline`}
tooltip={i18n`Deadline for the customer to pay for the offer before it expires. Inventory products will be reserved until this deadline.`} tooltip={i18n.str`Deadline for the customer to pay for the offer before it expires. Inventory products will be reserved until this deadline.`}
/> />
<InputDate <InputDate
name="payments.refund_deadline" name="payments.refund_deadline"
label={i18n`Refund deadline`} label={i18n.str`Refund deadline`}
tooltip={i18n`Time until which the order can be refunded by the merchant.`} tooltip={i18n.str`Time until which the order can be refunded by the merchant.`}
/> />
<InputDate <InputDate
name="payments.wire_transfer_deadline" name="payments.wire_transfer_deadline"
label={i18n`Wire transfer deadline`} label={i18n.str`Wire transfer deadline`}
tooltip={i18n`Deadline for the exchange to make the wire transfer.`} tooltip={i18n.str`Deadline for the exchange to make the wire transfer.`}
/> />
<InputDate <InputDate
name="payments.auto_refund_deadline" name="payments.auto_refund_deadline"
label={i18n`Auto-refund deadline`} label={i18n.str`Auto-refund deadline`}
tooltip={i18n`Time until which the wallet will automatically check for refunds without user interaction.`} tooltip={i18n.str`Time until which the wallet will automatically check for refunds without user interaction.`}
/> />
<InputCurrency <InputCurrency
name="payments.max_fee" name="payments.max_fee"
label={i18n`Maximum deposit fee`} label={i18n.str`Maximum deposit fee`}
tooltip={i18n`Maximum deposit fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`} tooltip={i18n.str`Maximum deposit fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`}
/> />
<InputCurrency <InputCurrency
name="payments.max_wire_fee" name="payments.max_wire_fee"
label={i18n`Maximum wire fee`} label={i18n.str`Maximum wire fee`}
tooltip={i18n`Maximum aggregate wire fees the merchant is willing to cover for this order. Wire fees exceeding this amount are to be covered by the customers.`} tooltip={i18n.str`Maximum aggregate wire fees the merchant is willing to cover for this order. Wire fees exceeding this amount are to be covered by the customers.`}
/> />
<InputNumber <InputNumber
name="payments.wire_fee_amortization" name="payments.wire_fee_amortization"
label={i18n`Wire fee amortization`} label={i18n.str`Wire fee amortization`}
tooltip={i18n`Factor by which wire fees exceeding the above threshold are divided to determine the share of excess wire fees to be paid explicitly by the consumer.`} tooltip={i18n.str`Factor by which wire fees exceeding the above threshold are divided to determine the share of excess wire fees to be paid explicitly by the consumer.`}
/> />
<InputBoolean <InputBoolean
name="payments.createToken" name="payments.createToken"
label={i18n`Create token`} label={i18n.str`Create token`}
tooltip={i18n`Uncheck this option if the merchant backend generated an order ID with enough entropy to prevent adversarial claims.`} tooltip={i18n.str`Uncheck this option if the merchant backend generated an order ID with enough entropy to prevent adversarial claims.`}
/> />
<InputNumber <InputNumber
name="payments.minimum_age" name="payments.minimum_age"
label={i18n`Minimum age required`} label={i18n.str`Minimum age required`}
tooltip={i18n`Any value greater than 0 will limit the coins able be used to pay this contract. If empty the age restriction will be defined by the products`} tooltip={i18n.str`Any value greater than 0 will limit the coins able be used to pay this contract. If empty the age restriction will be defined by the products`}
help={ help={
minAgeByProducts > 0 minAgeByProducts > 0
? i18n`Min age defined by the producs is ${minAgeByProducts}` ? i18n.str`Min age defined by the producs is ${minAgeByProducts}`
: undefined : undefined
} }
/> />
@ -523,14 +523,14 @@ export function CreatePage({
<InputGroup <InputGroup
name="extra" name="extra"
label={i18n`Additional information`} label={i18n.str`Additional information`}
tooltip={i18n`Custom information to be included in the contract for this order.`} tooltip={i18n.str`Custom information to be included in the contract for this order.`}
> >
<Input <Input
name="extra" name="extra"
inputType="multiline" inputType="multiline"
label={`Value`} label={`Value`}
tooltip={i18n`You must enter a value in JavaScript Object Notation (JSON).`} tooltip={i18n.str`You must enter a value in JavaScript Object Notation (JSON).`}
/> />
</InputGroup> </InputGroup>
</FormProvider> </FormProvider>
@ -538,7 +538,7 @@ export function CreatePage({
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
{onBack && ( {onBack && (
<button class="button" onClick={onBack}> <button class="button" onClick={onBack}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
)} )}
<button <button
@ -546,7 +546,7 @@ export function CreatePage({
onClick={submit} onClick={submit}
disabled={hasErrors} disabled={hasErrors}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</button> </button>
</div> </div>
</div> </div>

View File

@ -13,11 +13,11 @@
You should have received a copy of the GNU General Public License along with 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/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { CreatedSuccessfully } from "../../../../components/notifications/CreatedSuccessfully.js"; import { CreatedSuccessfully } from "../../../../components/notifications/CreatedSuccessfully.js";
import { useOrderAPI } from "../../../../hooks/order.js"; import { useOrderAPI } from "../../../../hooks/order.js";
import { Translate } from "../../../../i18n/index.js";
import { Entity } from "./index.js"; import { Entity } from "./index.js";
interface Props { interface Props {
@ -33,7 +33,7 @@ export function OrderCreatedSuccessfully({
}: Props): VNode { }: Props): VNode {
const { getPaymentURL } = useOrderAPI(); const { getPaymentURL } = useOrderAPI();
const [url, setURL] = useState<string | undefined>(undefined); const [url, setURL] = useState<string | undefined>(undefined);
const { i18n } = useTranslationContext();
useEffect(() => { useEffect(() => {
getPaymentURL(entity.response.order_id).then((response) => { getPaymentURL(entity.response.order_id).then((response) => {
setURL(response.data); setURL(response.data);
@ -48,7 +48,7 @@ export function OrderCreatedSuccessfully({
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label"> <label class="label">
<Translate>Amount</Translate> <i18n.Translate>Amount</i18n.Translate>
</label> </label>
</div> </div>
<div class="field-body is-flex-grow-3"> <div class="field-body is-flex-grow-3">
@ -66,7 +66,7 @@ export function OrderCreatedSuccessfully({
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label"> <label class="label">
<Translate>Summary</Translate> <i18n.Translate>Summary</i18n.Translate>
</label> </label>
</div> </div>
<div class="field-body is-flex-grow-3"> <div class="field-body is-flex-grow-3">
@ -84,7 +84,7 @@ export function OrderCreatedSuccessfully({
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label"> <label class="label">
<Translate>Order ID</Translate> <i18n.Translate>Order ID</i18n.Translate>
</label> </label>
</div> </div>
<div class="field-body is-flex-grow-3"> <div class="field-body is-flex-grow-3">
@ -98,7 +98,7 @@ export function OrderCreatedSuccessfully({
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label"> <label class="label">
<Translate>Payment URL</Translate> <i18n.Translate>Payment URL</i18n.Translate>
</label> </label>
</div> </div>
<div class="field-body is-flex-grow-3"> <div class="field-body is-flex-grow-3">

View File

@ -20,6 +20,7 @@
*/ */
import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { format } from "date-fns"; import { format } from "date-fns";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
@ -34,7 +35,6 @@ import { TextField } from "../../../../components/form/TextField.js";
import { ProductList } from "../../../../components/product/ProductList.js"; import { ProductList } from "../../../../components/product/ProductList.js";
import { useBackendContext } from "../../../../context/backend.js"; import { useBackendContext } from "../../../../context/backend.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
import { mergeRefunds } from "../../../../utils/amount.js"; import { mergeRefunds } from "../../../../utils/amount.js";
import { RefundModal } from "../list/Table.js"; import { RefundModal } from "../list/Table.js";
import { Event, Timeline } from "./Timeline.js"; import { Event, Timeline } from "./Timeline.js";
@ -56,84 +56,84 @@ type Unpaid = MerchantBackend.Orders.CheckPaymentUnpaidResponse;
type Claimed = MerchantBackend.Orders.CheckPaymentClaimedResponse; type Claimed = MerchantBackend.Orders.CheckPaymentClaimedResponse;
function ContractTerms({ value }: { value: CT }) { function ContractTerms({ value }: { value: CT }) {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<InputGroup name="contract_terms" label={i18n`Contract Terms`}> <InputGroup name="contract_terms" label={i18n.str`Contract Terms`}>
<FormProvider<CT> object={value} valueHandler={null}> <FormProvider<CT> object={value} valueHandler={null}>
<Input<CT> <Input<CT>
readonly readonly
name="summary" name="summary"
label={i18n`Summary`} label={i18n.str`Summary`}
tooltip={i18n`human-readable description of the whole purchase`} tooltip={i18n.str`human-readable description of the whole purchase`}
/> />
<InputCurrency<CT> <InputCurrency<CT>
readonly readonly
name="amount" name="amount"
label={i18n`Amount`} label={i18n.str`Amount`}
tooltip={i18n`total price for the transaction`} tooltip={i18n.str`total price for the transaction`}
/> />
{value.fulfillment_url && ( {value.fulfillment_url && (
<Input<CT> <Input<CT>
readonly readonly
name="fulfillment_url" name="fulfillment_url"
label={i18n`Fulfillment URL`} label={i18n.str`Fulfillment URL`}
tooltip={i18n`URL for this purchase`} tooltip={i18n.str`URL for this purchase`}
/> />
)} )}
<Input<CT> <Input<CT>
readonly readonly
name="max_fee" name="max_fee"
label={i18n`Max fee`} label={i18n.str`Max fee`}
tooltip={i18n`maximum total deposit fee accepted by the merchant for this contract`} tooltip={i18n.str`maximum total deposit fee accepted by the merchant for this contract`}
/> />
<Input<CT> <Input<CT>
readonly readonly
name="max_wire_fee" name="max_wire_fee"
label={i18n`Max wire fee`} label={i18n.str`Max wire fee`}
tooltip={i18n`maximum wire fee accepted by the merchant`} tooltip={i18n.str`maximum wire fee accepted by the merchant`}
/> />
<Input<CT> <Input<CT>
readonly readonly
name="wire_fee_amortization" name="wire_fee_amortization"
label={i18n`Wire fee amortization`} label={i18n.str`Wire fee amortization`}
tooltip={i18n`over how many customer transactions does the merchant expect to amortize wire fees on average`} tooltip={i18n.str`over how many customer transactions does the merchant expect to amortize wire fees on average`}
/> />
<InputDate<CT> <InputDate<CT>
readonly readonly
name="timestamp" name="timestamp"
label={i18n`Created at`} label={i18n.str`Created at`}
tooltip={i18n`time when this contract was generated`} tooltip={i18n.str`time when this contract was generated`}
/> />
<InputDate<CT> <InputDate<CT>
readonly readonly
name="refund_deadline" name="refund_deadline"
label={i18n`Refund deadline`} label={i18n.str`Refund deadline`}
tooltip={i18n`after this deadline has passed no refunds will be accepted`} tooltip={i18n.str`after this deadline has passed no refunds will be accepted`}
/> />
<InputDate<CT> <InputDate<CT>
readonly readonly
name="pay_deadline" name="pay_deadline"
label={i18n`Payment deadline`} label={i18n.str`Payment deadline`}
tooltip={i18n`after this deadline, the merchant won't accept payments for the contract`} tooltip={i18n.str`after this deadline, the merchant won't accept payments for the contract`}
/> />
<InputDate<CT> <InputDate<CT>
readonly readonly
name="wire_transfer_deadline" name="wire_transfer_deadline"
label={i18n`Wire transfer deadline`} label={i18n.str`Wire transfer deadline`}
tooltip={i18n`transfer deadline for the exchange`} tooltip={i18n.str`transfer deadline for the exchange`}
/> />
<InputDate<CT> <InputDate<CT>
readonly readonly
name="delivery_date" name="delivery_date"
label={i18n`Delivery date`} label={i18n.str`Delivery date`}
tooltip={i18n`time indicating when the order should be delivered`} tooltip={i18n.str`time indicating when the order should be delivered`}
/> />
{value.delivery_date && ( {value.delivery_date && (
<InputGroup <InputGroup
name="delivery_location" name="delivery_location"
label={i18n`Location`} label={i18n.str`Location`}
tooltip={i18n`where the order will be delivered`} tooltip={i18n.str`where the order will be delivered`}
> >
<InputLocation name="payments.delivery_location" /> <InputLocation name="payments.delivery_location" />
</InputGroup> </InputGroup>
@ -141,14 +141,14 @@ function ContractTerms({ value }: { value: CT }) {
<InputDuration<CT> <InputDuration<CT>
readonly readonly
name="auto_refund" name="auto_refund"
label={i18n`Auto-refund delay`} label={i18n.str`Auto-refund delay`}
tooltip={i18n`how long the wallet should try to get an automatic refund for the purchase`} tooltip={i18n.str`how long the wallet should try to get an automatic refund for the purchase`}
/> />
<Input<CT> <Input<CT>
readonly readonly
name="extra" name="extra"
label={i18n`Extra info`} label={i18n.str`Extra info`}
tooltip={i18n`extra data that is only interpreted by the merchant frontend`} tooltip={i18n.str`extra data that is only interpreted by the merchant frontend`}
/> />
</FormProvider> </FormProvider>
</InputGroup> </InputGroup>
@ -203,7 +203,7 @@ function ClaimedPage({
} }
const [value, valueHandler] = useState<Partial<Claimed>>(order); const [value, valueHandler] = useState<Partial<Claimed>>(order);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div> <div>
@ -216,9 +216,9 @@ function ClaimedPage({
<div class="level"> <div class="level">
<div class="level-left"> <div class="level-left">
<div class="level-item"> <div class="level-item">
<Translate>Order</Translate> #{id} <i18n.Translate>Order</i18n.Translate> #{id}
<div class="tag is-info ml-4"> <div class="tag is-info ml-4">
<Translate>claimed</Translate> <i18n.Translate>claimed</i18n.Translate>
</div> </div>
</div> </div>
</div> </div>
@ -244,7 +244,7 @@ function ClaimedPage({
> >
<p> <p>
<b> <b>
<Translate>claimed at</Translate>: <i18n.Translate>claimed at</i18n.Translate>:
</b>{" "} </b>{" "}
{format( {format(
new Date(order.contract_terms.timestamp.t_s * 1000), new Date(order.contract_terms.timestamp.t_s * 1000),
@ -262,13 +262,13 @@ function ClaimedPage({
<div class="columns"> <div class="columns">
<div class="column is-4"> <div class="column is-4">
<div class="title"> <div class="title">
<Translate>Timeline</Translate> <i18n.Translate>Timeline</i18n.Translate>
</div> </div>
<Timeline events={events} /> <Timeline events={events} />
</div> </div>
<div class="column is-8"> <div class="column is-8">
<div class="title"> <div class="title">
<Translate>Payment details</Translate> <i18n.Translate>Payment details</i18n.Translate>
</div> </div>
<FormProvider<Claimed> <FormProvider<Claimed>
object={value} object={value}
@ -278,17 +278,17 @@ function ClaimedPage({
name="contract_terms.summary" name="contract_terms.summary"
readonly readonly
inputType="multiline" inputType="multiline"
label={i18n`Summary`} label={i18n.str`Summary`}
/> />
<InputCurrency <InputCurrency
name="contract_terms.amount" name="contract_terms.amount"
readonly readonly
label={i18n`Amount`} label={i18n.str`Amount`}
/> />
<Input<Claimed> <Input<Claimed>
name="order_status" name="order_status"
readonly readonly
label={i18n`Order status`} label={i18n.str`Order status`}
/> />
</FormProvider> </FormProvider>
</div> </div>
@ -298,7 +298,7 @@ function ClaimedPage({
{order.contract_terms.products.length ? ( {order.contract_terms.products.length ? (
<Fragment> <Fragment>
<div class="title"> <div class="title">
<Translate>Product list</Translate> <i18n.Translate>Product list</i18n.Translate>
</div> </div>
<ProductList list={order.contract_terms.products} /> <ProductList list={order.contract_terms.products} />
</Fragment> </Fragment>
@ -426,7 +426,7 @@ function PaidPage({
const refundurl = `${proto}://refund/${refundHost}/${order.contract_terms.order_id}/`; const refundurl = `${proto}://refund/${refundHost}/${order.contract_terms.order_id}/`;
const refundable = const refundable =
new Date().getTime() < order.contract_terms.refund_deadline.t_s * 1000; new Date().getTime() < order.contract_terms.refund_deadline.t_s * 1000;
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const amount = Amounts.parseOrThrow(order.contract_terms.amount); const amount = Amounts.parseOrThrow(order.contract_terms.amount);
const refund_taken = order.refund_details.reduce((prev, cur) => { const refund_taken = order.refund_details.reduce((prev, cur) => {
@ -446,18 +446,18 @@ function PaidPage({
<div class="level"> <div class="level">
<div class="level-left"> <div class="level-left">
<div class="level-item"> <div class="level-item">
<Translate>Order</Translate> #{id} <i18n.Translate>Order</i18n.Translate> #{id}
<div class="tag is-success ml-4"> <div class="tag is-success ml-4">
<Translate>paid</Translate> <i18n.Translate>paid</i18n.Translate>
</div> </div>
{order.wired ? ( {order.wired ? (
<div class="tag is-success ml-4"> <div class="tag is-success ml-4">
<Translate>wired</Translate> <i18n.Translate>wired</i18n.Translate>
</div> </div>
) : null} ) : null}
{order.refunded ? ( {order.refunded ? (
<div class="tag is-danger ml-4"> <div class="tag is-danger ml-4">
<Translate>refunded</Translate> <i18n.Translate>refunded</i18n.Translate>
</div> </div>
) : null} ) : null}
</div> </div>
@ -477,8 +477,8 @@ function PaidPage({
class="has-tooltip-left" class="has-tooltip-left"
data-tooltip={ data-tooltip={
refundable refundable
? i18n`refund order` ? i18n.str`refund order`
: i18n`not refundable` : i18n.str`not refundable`
} }
> >
<button <button
@ -486,7 +486,7 @@ function PaidPage({
disabled={!refundable} disabled={!refundable}
onClick={() => onRefund(id)} onClick={() => onRefund(id)}
> >
<Translate>refund</Translate> <i18n.Translate>refund</i18n.Translate>
</button> </button>
</span> </span>
</div> </div>
@ -533,41 +533,41 @@ function PaidPage({
<div class="columns"> <div class="columns">
<div class="column is-4"> <div class="column is-4">
<div class="title"> <div class="title">
<Translate>Timeline</Translate> <i18n.Translate>Timeline</i18n.Translate>
</div> </div>
<Timeline events={events} /> <Timeline events={events} />
</div> </div>
<div class="column is-8"> <div class="column is-8">
<div class="title"> <div class="title">
<Translate>Payment details</Translate> <i18n.Translate>Payment details</i18n.Translate>
</div> </div>
<FormProvider<Paid> <FormProvider<Paid>
object={value} object={value}
valueHandler={valueHandler} valueHandler={valueHandler}
> >
{/* <InputCurrency<Paid> name="deposit_total" readonly label={i18n`Deposit total`} /> */} {/* <InputCurrency<Paid> name="deposit_total" readonly label={i18n.str`Deposit total`} /> */}
{order.refunded && ( {order.refunded && (
<InputCurrency<Paid> <InputCurrency<Paid>
name="refund_amount" name="refund_amount"
readonly readonly
label={i18n`Refunded amount`} label={i18n.str`Refunded amount`}
/> />
)} )}
{order.refunded && ( {order.refunded && (
<InputCurrency<Paid> <InputCurrency<Paid>
name="refund_taken" name="refund_taken"
readonly readonly
label={i18n`Refund taken`} label={i18n.str`Refund taken`}
/> />
)} )}
<Input<Paid> <Input<Paid>
name="order_status" name="order_status"
readonly readonly
label={i18n`Order status`} label={i18n.str`Order status`}
/> />
<TextField<Paid> <TextField<Paid>
name="order_status_url" name="order_status_url"
label={i18n`Status URL`} label={i18n.str`Status URL`}
> >
<a <a
target="_blank" target="_blank"
@ -580,7 +580,7 @@ function PaidPage({
{order.refunded && ( {order.refunded && (
<TextField<Paid> <TextField<Paid>
name="order_status_url" name="order_status_url"
label={i18n`Refund URI`} label={i18n.str`Refund URI`}
> >
<a target="_blank" rel="noreferrer" href={refundurl}> <a target="_blank" rel="noreferrer" href={refundurl}>
{refundurl} {refundurl}
@ -595,7 +595,7 @@ function PaidPage({
{order.contract_terms.products.length ? ( {order.contract_terms.products.length ? (
<Fragment> <Fragment>
<div class="title"> <div class="title">
<Translate>Product list</Translate> <i18n.Translate>Product list</i18n.Translate>
</div> </div>
<ProductList list={order.contract_terms.products} /> <ProductList list={order.contract_terms.products} />
</Fragment> </Fragment>
@ -620,7 +620,7 @@ function UnpaidPage({
order: MerchantBackend.Orders.CheckPaymentUnpaidResponse; order: MerchantBackend.Orders.CheckPaymentUnpaidResponse;
}) { }) {
const [value, valueHandler] = useState<Partial<Unpaid>>(order); const [value, valueHandler] = useState<Partial<Unpaid>>(order);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div> <div>
<section class="hero is-hero-bar"> <section class="hero is-hero-bar">
@ -629,11 +629,11 @@ function UnpaidPage({
<div class="level-left"> <div class="level-left">
<div class="level-item"> <div class="level-item">
<h1 class="title"> <h1 class="title">
<Translate>Order</Translate> #{id} <i18n.Translate>Order</i18n.Translate> #{id}
</h1> </h1>
</div> </div>
<div class="tag is-dark"> <div class="tag is-dark">
<Translate>unpaid</Translate> <i18n.Translate>unpaid</i18n.Translate>
</div> </div>
</div> </div>
</div> </div>
@ -651,7 +651,7 @@ function UnpaidPage({
> >
<p> <p>
<b> <b>
<Translate>pay at</Translate>: <i18n.Translate>pay at</i18n.Translate>:
</b>{" "} </b>{" "}
<a <a
href={order.order_status_url} href={order.order_status_url}
@ -663,7 +663,7 @@ function UnpaidPage({
</p> </p>
<p> <p>
<b> <b>
<Translate>created at</Translate>: <i18n.Translate>created at</i18n.Translate>:
</b>{" "} </b>{" "}
{order.creation_time.t_s === "never" {order.creation_time.t_s === "never"
? "never" ? "never"
@ -687,26 +687,29 @@ function UnpaidPage({
<Input<Unpaid> <Input<Unpaid>
readonly readonly
name="summary" name="summary"
label={i18n`Summary`} label={i18n.str`Summary`}
tooltip={i18n`human-readable description of the whole purchase`} tooltip={i18n.str`human-readable description of the whole purchase`}
/> />
<InputCurrency<Unpaid> <InputCurrency<Unpaid>
readonly readonly
name="total_amount" name="total_amount"
label={i18n`Amount`} label={i18n.str`Amount`}
tooltip={i18n`total price for the transaction`} tooltip={i18n.str`total price for the transaction`}
/> />
<Input<Unpaid> <Input<Unpaid>
name="order_status" name="order_status"
readonly readonly
label={i18n`Order status`} label={i18n.str`Order status`}
/> />
<Input<Unpaid> <Input<Unpaid>
name="order_status_url" name="order_status_url"
readonly readonly
label={i18n`Order status URL`} label={i18n.str`Order status URL`}
/> />
<TextField<Unpaid> name="taler_pay_uri" label={i18n`Payment URI`}> <TextField<Unpaid>
name="taler_pay_uri"
label={i18n.str`Payment URI`}
>
<a target="_blank" rel="noreferrer" href={value.taler_pay_uri}> <a target="_blank" rel="noreferrer" href={value.taler_pay_uri}>
{value.taler_pay_uri} {value.taler_pay_uri}
</a> </a>
@ -722,7 +725,7 @@ function UnpaidPage({
export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode { export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode {
const [showRefund, setShowRefund] = useState<string | undefined>(undefined); const [showRefund, setShowRefund] = useState<string | undefined>(undefined);
const { i18n } = useTranslationContext();
const DetailByStatus = function () { const DetailByStatus = function () {
switch (selected.order_status) { switch (selected.order_status) {
case "claimed": case "claimed":
@ -734,10 +737,10 @@ export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode {
default: default:
return ( return (
<div> <div>
<Translate> <i18n.Translate>
Unknown order status. This is an error, please contact the Unknown order status. This is an error, please contact the
administrator. administrator.
</Translate> </i18n.Translate>
</div> </div>
); );
} }
@ -761,7 +764,7 @@ export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode {
<div class="column is-four-fifths"> <div class="column is-four-fifths">
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
<button class="button" onClick={onBack}> <button class="button" onClick={onBack}>
<Translate>Back</Translate> <i18n.Translate>Back</i18n.Translate>
</button> </button>
</div> </div>
</div> </div>

View File

@ -13,13 +13,13 @@
You should have received a copy of the GNU General Public License along with 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/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js"; import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js"; import { NotificationCard } from "../../../../components/menu/index.js";
import { HttpError } from "../../../../hooks/backend.js"; import { HttpError } from "../../../../hooks/backend.js";
import { useOrderDetails, useOrderAPI } from "../../../../hooks/order.js"; import { useOrderAPI, useOrderDetails } from "../../../../hooks/order.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { DetailPage } from "./DetailPage.js"; import { DetailPage } from "./DetailPage.js";
@ -43,7 +43,7 @@ export default function Update({
const result = useOrderDetails(oid); const result = useOrderDetails(oid);
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isUnauthorized) return onUnauthorized();
if (result.clientError && result.isNotfound) return onNotFound(); if (result.clientError && result.isNotfound) return onNotFound();
@ -61,13 +61,13 @@ export default function Update({
refundOrder(id, value) refundOrder(id, value)
.then(() => .then(() =>
setNotif({ setNotif({
message: i18n`refund created successfully`, message: i18n.str`refund created successfully`,
type: "SUCCESS", type: "SUCCESS",
}), }),
) )
.catch((error) => .catch((error) =>
setNotif({ setNotif({
message: i18n`could not create the refund`, message: i18n.str`could not create the refund`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}), }),

View File

@ -19,12 +19,12 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { format } from "date-fns"; import { format } from "date-fns";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { DatePicker } from "../../../../components/picker/DatePicker.js"; import { DatePicker } from "../../../../components/picker/DatePicker.js";
import { MerchantBackend, WithId } from "../../../../declaration.js"; import { MerchantBackend, WithId } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
import { CardTable } from "./Table.js"; import { CardTable } from "./Table.js";
export interface ListPageProps { export interface ListPageProps {
@ -74,8 +74,8 @@ export function ListPage({
isNotWiredActive, isNotWiredActive,
onCreate, onCreate,
}: ListPageProps): VNode { }: ListPageProps): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const dateTooltip = i18n`select date to show nearby orders`; const dateTooltip = i18n.str`select date to show nearby orders`;
const [pickDate, setPickDate] = useState(false); const [pickDate, setPickDate] = useState(false);
const [orderId, setOrderId] = useState<string>(""); const [orderId, setOrderId] = useState<string>("");
@ -91,13 +91,13 @@ export function ListPage({
type="text" type="text"
value={orderId} value={orderId}
onChange={(e) => setOrderId(e.currentTarget.value)} onChange={(e) => setOrderId(e.currentTarget.value)}
placeholder={i18n`order id`} placeholder={i18n.str`order id`}
/> />
{errorOrderId && <p class="help is-danger">{errorOrderId}</p>} {errorOrderId && <p class="help is-danger">{errorOrderId}</p>}
</div> </div>
<span <span
class="has-tooltip-bottom" class="has-tooltip-bottom"
data-tooltip={i18n`jump to order with the given order ID`} data-tooltip={i18n.str`jump to order with the given order ID`}
> >
<button <button
class="button" class="button"
@ -119,40 +119,40 @@ export function ListPage({
<li class={isAllActive}> <li class={isAllActive}>
<div <div
class="has-tooltip-right" class="has-tooltip-right"
data-tooltip={i18n`remove all filters`} data-tooltip={i18n.str`remove all filters`}
> >
<a onClick={onShowAll}> <a onClick={onShowAll}>
<Translate>All</Translate> <i18n.Translate>All</i18n.Translate>
</a> </a>
</div> </div>
</li> </li>
<li class={isPaidActive}> <li class={isPaidActive}>
<div <div
class="has-tooltip-right" class="has-tooltip-right"
data-tooltip={i18n`only show paid orders`} data-tooltip={i18n.str`only show paid orders`}
> >
<a onClick={onShowPaid}> <a onClick={onShowPaid}>
<Translate>Paid</Translate> <i18n.Translate>Paid</i18n.Translate>
</a> </a>
</div> </div>
</li> </li>
<li class={isRefundedActive}> <li class={isRefundedActive}>
<div <div
class="has-tooltip-right" class="has-tooltip-right"
data-tooltip={i18n`only show orders with refunds`} data-tooltip={i18n.str`only show orders with refunds`}
> >
<a onClick={onShowRefunded}> <a onClick={onShowRefunded}>
<Translate>Refunded</Translate> <i18n.Translate>Refunded</i18n.Translate>
</a> </a>
</div> </div>
</li> </li>
<li class={isNotWiredActive}> <li class={isNotWiredActive}>
<div <div
class="has-tooltip-left" class="has-tooltip-left"
data-tooltip={i18n`only show orders where customers paid, but wire payments from payment provider are still pending`} data-tooltip={i18n.str`only show orders where customers paid, but wire payments from payment provider are still pending`}
> >
<a onClick={onShowNotWired}> <a onClick={onShowNotWired}>
<Translate>Not wired</Translate> <i18n.Translate>Not wired</i18n.Translate>
</a> </a>
</div> </div>
</li> </li>
@ -165,7 +165,10 @@ export function ListPage({
{jumpToDate && ( {jumpToDate && (
<div class="control"> <div class="control">
<a class="button" onClick={() => onSelectDate(undefined)}> <a class="button" onClick={() => onSelectDate(undefined)}>
<span class="icon" data-tooltip={i18n`clear date filter`}> <span
class="icon"
data-tooltip={i18n.str`clear date filter`}
>
<i class="mdi mdi-close" /> <i class="mdi mdi-close" />
</span> </span>
</a> </a>
@ -178,7 +181,7 @@ export function ListPage({
type="text" type="text"
readonly readonly
value={!jumpToDate ? "" : format(jumpToDate, "yyyy/MM/dd")} value={!jumpToDate ? "" : format(jumpToDate, "yyyy/MM/dd")}
placeholder={i18n`date (YYYY/MM/DD)`} placeholder={i18n.str`date (YYYY/MM/DD)`}
onClick={() => { onClick={() => {
setPickDate(true); setPickDate(true);
}} }}

View File

@ -20,6 +20,7 @@
*/ */
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { format } from "date-fns"; import { format } from "date-fns";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { StateUpdater, useState } from "preact/hooks"; import { StateUpdater, useState } from "preact/hooks";
@ -34,7 +35,6 @@ import { InputSelector } from "../../../../components/form/InputSelector.js";
import { ConfirmModal } from "../../../../components/modal/index.js"; import { ConfirmModal } from "../../../../components/modal/index.js";
import { useConfigContext } from "../../../../context/config.js"; import { useConfigContext } from "../../../../context/config.js";
import { MerchantBackend, WithId } from "../../../../declaration.js"; import { MerchantBackend, WithId } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
import { mergeRefunds } from "../../../../utils/amount.js"; import { mergeRefunds } from "../../../../utils/amount.js";
type Entity = MerchantBackend.Orders.OrderHistoryEntry & WithId; type Entity = MerchantBackend.Orders.OrderHistoryEntry & WithId;
@ -63,7 +63,7 @@ export function CardTable({
}: Props): VNode { }: Props): VNode {
const [rowSelection, rowSelectionHandler] = useState<string[]>([]); const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="card has-table"> <div class="card has-table">
@ -72,13 +72,13 @@ export function CardTable({
<span class="icon"> <span class="icon">
<i class="mdi mdi-cash-register" /> <i class="mdi mdi-cash-register" />
</span> </span>
<Translate>Orders</Translate> <i18n.Translate>Orders</i18n.Translate>
</p> </p>
<div class="card-header-icon" aria-label="more options" /> <div class="card-header-icon" aria-label="more options" />
<div class="card-header-icon" aria-label="more options"> <div class="card-header-icon" aria-label="more options">
<span class="has-tooltip-left" data-tooltip={i18n`create order`}> <span class="has-tooltip-left" data-tooltip={i18n.str`create order`}>
<button class="button is-info" type="button" onClick={onCreate}> <button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small"> <span class="icon is-small">
<i class="mdi mdi-plus mdi-36px" /> <i class="mdi mdi-plus mdi-36px" />
@ -135,6 +135,7 @@ function Table({
hasMoreAfter, hasMoreAfter,
hasMoreBefore, hasMoreBefore,
}: TableProps): VNode { }: TableProps): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
{onLoadMoreBefore && ( {onLoadMoreBefore && (
@ -143,20 +144,20 @@ function Table({
disabled={!hasMoreBefore} disabled={!hasMoreBefore}
onClick={onLoadMoreBefore} onClick={onLoadMoreBefore}
> >
<Translate>load newer orders</Translate> <i18n.Translate>load newer orders</i18n.Translate>
</button> </button>
)} )}
<table class="table is-striped is-hoverable is-fullwidth"> <table class="table is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th style={{ minWidth: 100 }}> <th style={{ minWidth: 100 }}>
<Translate>Date</Translate> <i18n.Translate>Date</i18n.Translate>
</th> </th>
<th style={{ minWidth: 100 }}> <th style={{ minWidth: 100 }}>
<Translate>Amount</Translate> <i18n.Translate>Amount</i18n.Translate>
</th> </th>
<th style={{ minWidth: 400 }}> <th style={{ minWidth: 400 }}>
<Translate>Summary</Translate> <i18n.Translate>Summary</i18n.Translate>
</th> </th>
<th style={{ minWidth: 50 }} /> <th style={{ minWidth: 50 }} />
</tr> </tr>
@ -196,7 +197,7 @@ function Table({
type="button" type="button"
onClick={(): void => onRefund(i)} onClick={(): void => onRefund(i)}
> >
<Translate>Refund</Translate> <i18n.Translate>Refund</i18n.Translate>
</button> </button>
)} )}
{!i.paid && ( {!i.paid && (
@ -205,7 +206,7 @@ function Table({
type="button" type="button"
onClick={(): void => onCopyURL(i)} onClick={(): void => onCopyURL(i)}
> >
<Translate>copy url</Translate> <i18n.Translate>copy url</i18n.Translate>
</button> </button>
)} )}
</div> </div>
@ -221,7 +222,7 @@ function Table({
disabled={!hasMoreAfter} disabled={!hasMoreAfter}
onClick={onLoadMoreAfter} onClick={onLoadMoreAfter}
> >
<Translate>load older orders</Translate> <i18n.Translate>load older orders</i18n.Translate>
</button> </button>
)} )}
</div> </div>
@ -229,6 +230,7 @@ function Table({
} }
function EmptyTable(): VNode { function EmptyTable(): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="content has-text-grey has-text-centered"> <div class="content has-text-grey has-text-centered">
<p> <p>
@ -237,7 +239,9 @@ function EmptyTable(): VNode {
</span> </span>
</p> </p>
<p> <p>
<Translate>No orders have been found matching your query!</Translate> <i18n.Translate>
No orders have been found matching your query!
</i18n.Translate>
</p> </p>
</div> </div>
); );
@ -256,7 +260,7 @@ export function RefundModal({
}: RefundModalProps): VNode { }: RefundModalProps): VNode {
type State = { mainReason?: string; description?: string; refund?: string }; type State = { mainReason?: string; description?: string; refund?: string };
const [form, setValue] = useState<State>({}); const [form, setValue] = useState<State>({});
const i18n = useTranslator(); const { i18n } = useTranslationContext();
// const [errors, setErrors] = useState<FormErrors<State>>({}); // const [errors, setErrors] = useState<FormErrors<State>>({});
const refunds = ( const refunds = (
@ -281,20 +285,20 @@ export function RefundModal({
: orderPrice; : orderPrice;
const isRefundable = Amounts.isNonZero(totalRefundable); const isRefundable = Amounts.isNonZero(totalRefundable);
const duplicatedText = i18n`duplicated`; const duplicatedText = i18n.str`duplicated`;
const errors: FormErrors<State> = { const errors: FormErrors<State> = {
mainReason: !form.mainReason ? i18n`required` : undefined, mainReason: !form.mainReason ? i18n.str`required` : undefined,
description: description:
!form.description && form.mainReason !== duplicatedText !form.description && form.mainReason !== duplicatedText
? i18n`required` ? i18n.str`required`
: undefined, : undefined,
refund: !form.refund refund: !form.refund
? i18n`required` ? i18n.str`required`
: !Amounts.parse(form.refund) : !Amounts.parse(form.refund)
? i18n`invalid format` ? i18n.str`invalid format`
: Amounts.cmp(totalRefundable, Amounts.parse(form.refund)!) === -1 : Amounts.cmp(totalRefundable, Amounts.parse(form.refund)!) === -1
? i18n`this value exceed the refundable amount` ? i18n.str`this value exceed the refundable amount`
: undefined, : undefined,
}; };
const hasErrors = Object.keys(errors).some( const hasErrors = Object.keys(errors).some(
@ -339,13 +343,13 @@ export function RefundModal({
<thead> <thead>
<tr> <tr>
<th> <th>
<Translate>date</Translate> <i18n.Translate>date</i18n.Translate>
</th> </th>
<th> <th>
<Translate>amount</Translate> <i18n.Translate>amount</i18n.Translate>
</th> </th>
<th> <th>
<Translate>reason</Translate> <i18n.Translate>reason</i18n.Translate>
</th> </th>
</tr> </tr>
</thead> </thead>
@ -381,28 +385,28 @@ export function RefundModal({
> >
<InputCurrency<State> <InputCurrency<State>
name="refund" name="refund"
label={i18n`Refund`} label={i18n.str`Refund`}
tooltip={i18n`amount to be refunded`} tooltip={i18n.str`amount to be refunded`}
> >
<Translate>Max refundable:</Translate>{" "} <i18n.Translate>Max refundable:</i18n.Translate>{" "}
{Amounts.stringify(totalRefundable)} {Amounts.stringify(totalRefundable)}
</InputCurrency> </InputCurrency>
<InputSelector <InputSelector
name="mainReason" name="mainReason"
label={i18n`Reason`} label={i18n.str`Reason`}
values={[ values={[
i18n`Choose one...`, i18n.str`Choose one...`,
duplicatedText, duplicatedText,
i18n`requested by the customer`, i18n.str`requested by the customer`,
i18n`other`, i18n.str`other`,
]} ]}
tooltip={i18n`why this order is being refunded`} tooltip={i18n.str`why this order is being refunded`}
/> />
{form.mainReason && form.mainReason !== duplicatedText ? ( {form.mainReason && form.mainReason !== duplicatedText ? (
<Input<State> <Input<State>
label={i18n`Description`} label={i18n.str`Description`}
name="description" name="description"
tooltip={i18n`more information to give context`} tooltip={i18n.str`more information to give context`}
/> />
) : undefined} ) : undefined}
</FormProvider> </FormProvider>

View File

@ -19,7 +19,8 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h, VNode, Fragment } from "preact"; import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js"; import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js"; import { NotificationCard } from "../../../../components/menu/index.js";
@ -31,10 +32,9 @@ import {
useOrderAPI, useOrderAPI,
useOrderDetails, useOrderDetails,
} from "../../../../hooks/order.js"; } from "../../../../hooks/order.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { RefundModal } from "./Table.js";
import { ListPage } from "./ListPage.js"; import { ListPage } from "./ListPage.js";
import { RefundModal } from "./Table.js";
interface Props { interface Props {
onUnauthorized: () => VNode; onUnauthorized: () => VNode;
@ -64,7 +64,7 @@ export default function OrderList({
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const [errorOrderId, setErrorOrderId] = useState<string | undefined>( const [errorOrderId, setErrorOrderId] = useState<string | undefined>(
undefined, undefined,
); );
@ -86,7 +86,7 @@ export default function OrderList({
async function testIfOrderExistAndSelect(orderId: string): Promise<void> { async function testIfOrderExistAndSelect(orderId: string): Promise<void> {
if (!orderId) { if (!orderId) {
setErrorOrderId(i18n`Enter an order id`); setErrorOrderId(i18n.str`Enter an order id`);
return; return;
} }
try { try {
@ -94,7 +94,7 @@ export default function OrderList({
onSelect(orderId); onSelect(orderId);
setErrorOrderId(undefined); setErrorOrderId(undefined);
} catch { } catch {
setErrorOrderId(i18n`order not found`); setErrorOrderId(i18n.str`order not found`);
} }
} }
@ -136,13 +136,13 @@ export default function OrderList({
refundOrder(orderToBeRefunded.order_id, value) refundOrder(orderToBeRefunded.order_id, value)
.then(() => .then(() =>
setNotif({ setNotif({
message: i18n`refund created successfully`, message: i18n.str`refund created successfully`,
type: "SUCCESS", type: "SUCCESS",
}), }),
) )
.catch((error) => .catch((error) =>
setNotif({ setNotif({
message: i18n`could not create the refund`, message: i18n.str`could not create the refund`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}), }),
@ -151,7 +151,7 @@ export default function OrderList({
} }
onLoadError={(error) => { onLoadError={(error) => {
setNotif({ setNotif({
message: i18n`could not create the refund`, message: i18n.str`could not create the refund`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}); });
@ -161,7 +161,7 @@ export default function OrderList({
onUnauthorized={onUnauthorized} onUnauthorized={onUnauthorized}
onNotFound={() => { onNotFound={() => {
setNotif({ setNotif({
message: i18n`could not get the order to refund`, message: i18n.str`could not get the order to refund`,
type: "ERROR", type: "ERROR",
// description: error.message // description: error.message
}); });

View File

@ -19,12 +19,12 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
import { ProductForm } from "../../../../components/product/ProductForm.js"; import { ProductForm } from "../../../../components/product/ProductForm.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { useListener } from "../../../../hooks/listener.js"; import { useListener } from "../../../../hooks/listener.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
type Entity = MerchantBackend.Products.ProductAddDetail & { type Entity = MerchantBackend.Products.ProductAddDetail & {
product_id: string; product_id: string;
@ -43,7 +43,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
}, },
); );
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div> <div>
@ -56,19 +56,19 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
{onBack && ( {onBack && (
<button class="button" onClick={onBack}> <button class="button" onClick={onBack}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
)} )}
<AsyncButton <AsyncButton
onClick={submitForm} onClick={submitForm}
data-tooltip={ data-tooltip={
!submitForm !submitForm
? i18n`Need to complete marked fields` ? i18n.str`Need to complete marked fields`
: "confirm operation" : "confirm operation"
} }
disabled={!submitForm} disabled={!submitForm}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</AsyncButton> </AsyncButton>
</div> </div>
</div> </div>

View File

@ -19,12 +19,12 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { NotificationCard } from "../../../../components/menu/index.js"; import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { useProductAPI } from "../../../../hooks/product.js"; import { useProductAPI } from "../../../../hooks/product.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { CreatePage } from "./CreatePage.js"; import { CreatePage } from "./CreatePage.js";
@ -36,7 +36,7 @@ interface Props {
export default function CreateProduct({ onConfirm, onBack }: Props): VNode { export default function CreateProduct({ onConfirm, onBack }: Props): VNode {
const { createProduct } = useProductAPI(); const { createProduct } = useProductAPI();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<Fragment> <Fragment>
@ -48,7 +48,7 @@ export default function CreateProduct({ onConfirm, onBack }: Props): VNode {
.then(() => onConfirm()) .then(() => onConfirm())
.catch((error) => { .catch((error) => {
setNotif({ setNotif({
message: i18n`could not create product`, message: i18n.str`could not create product`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}); });

View File

@ -19,19 +19,19 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { Amounts } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { format } from "date-fns"; import { format } from "date-fns";
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
import { StateUpdater, useState } from "preact/hooks"; import { StateUpdater, useState } from "preact/hooks";
import emptyImage from "../../../../assets/empty.png";
import { import {
FormProvider,
FormErrors, FormErrors,
FormProvider,
} from "../../../../components/form/FormProvider.js"; } from "../../../../components/form/FormProvider.js";
import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputNumber } from "../../../../components/form/InputNumber.js"; import { InputNumber } from "../../../../components/form/InputNumber.js";
import { MerchantBackend, WithId } from "../../../../declaration.js"; import { MerchantBackend, WithId } from "../../../../declaration.js";
import emptyImage from "../../../../assets/empty.png";
import { Translate, useTranslator } from "../../../../i18n/index.js";
import { Amounts } from "@gnu-taler/taler-util";
type Entity = MerchantBackend.Products.ProductDetail & WithId; type Entity = MerchantBackend.Products.ProductDetail & WithId;
@ -57,7 +57,7 @@ export function CardTable({
const [rowSelection, rowSelectionHandler] = useState<string | undefined>( const [rowSelection, rowSelectionHandler] = useState<string | undefined>(
undefined, undefined,
); );
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="card has-table"> <div class="card has-table">
<header class="card-header"> <header class="card-header">
@ -65,12 +65,12 @@ export function CardTable({
<span class="icon"> <span class="icon">
<i class="mdi mdi-shopping" /> <i class="mdi mdi-shopping" />
</span> </span>
<Translate>Products</Translate> <i18n.Translate>Products</i18n.Translate>
</p> </p>
<div class="card-header-icon" aria-label="more options"> <div class="card-header-icon" aria-label="more options">
<span <span
class="has-tooltip-left" class="has-tooltip-left"
data-tooltip={i18n`add product to inventory`} data-tooltip={i18n.str`add product to inventory`}
> >
<button class="button is-info" type="button" onClick={onCreate}> <button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small"> <span class="icon is-small">
@ -121,32 +121,32 @@ function Table({
onUpdate, onUpdate,
onDelete, onDelete,
}: TableProps): VNode { }: TableProps): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th> <th>
<Translate>Image</Translate> <i18n.Translate>Image</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Description</Translate> <i18n.Translate>Description</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Sell</Translate> <i18n.Translate>Sell</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Taxes</Translate> <i18n.Translate>Taxes</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Profit</Translate> <i18n.Translate>Profit</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Stock</Translate> <i18n.Translate>Stock</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Sold</Translate> <i18n.Translate>Sold</i18n.Translate>
</th> </th>
<th /> <th />
</tr> </tr>
@ -207,7 +207,7 @@ function Table({
} }
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
> >
{isFree ? i18n`free` : `${i.price} / ${i.unit}`} {isFree ? i18n.str`free` : `${i.price} / ${i.unit}`}
</td> </td>
<td <td
onClick={() => onClick={() =>
@ -245,26 +245,26 @@ function Table({
<div class="buttons is-right"> <div class="buttons is-right">
<span <span
class="has-tooltip-bottom" class="has-tooltip-bottom"
data-tooltip={i18n`go to product update page`} data-tooltip={i18n.str`go to product update page`}
> >
<button <button
class="button is-small is-success " class="button is-small is-success "
type="button" type="button"
onClick={(): void => onSelect(i)} onClick={(): void => onSelect(i)}
> >
<Translate>Update</Translate> <i18n.Translate>Update</i18n.Translate>
</button> </button>
</span> </span>
<span <span
class="has-tooltip-left" class="has-tooltip-left"
data-tooltip={i18n`remove this product from the database`} data-tooltip={i18n.str`remove this product from the database`}
> >
<button <button
class="button is-small is-danger" class="button is-small is-danger"
type="button" type="button"
onClick={(): void => onDelete(i)} onClick={(): void => onDelete(i)}
> >
<Translate>Delete</Translate> <i18n.Translate>Delete</i18n.Translate>
</button> </button>
</span> </span>
</div> </div>
@ -316,7 +316,7 @@ function FastProductWithInfiniteStockUpdateForm({
onCancel, onCancel,
}: FastProductUpdateFormProps) { }: FastProductUpdateFormProps) {
const [value, valueHandler] = useState<UpdatePrice>({ price: product.price }); const [value, valueHandler] = useState<UpdatePrice>({ price: product.price });
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<Fragment> <Fragment>
@ -327,18 +327,18 @@ function FastProductWithInfiniteStockUpdateForm({
> >
<InputCurrency<FastProductUpdate> <InputCurrency<FastProductUpdate>
name="price" name="price"
label={i18n`Price`} label={i18n.str`Price`}
tooltip={i18n`update the product with new price`} tooltip={i18n.str`update the product with new price`}
/> />
</FormProvider> </FormProvider>
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
<button class="button" onClick={onCancel}> <button class="button" onClick={onCancel}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
<span <span
class="has-tooltip-left" class="has-tooltip-left"
data-tooltip={i18n`update product with new price`} data-tooltip={i18n.str`update product with new price`}
> >
<button <button
class="button is-info" class="button is-info"
@ -349,7 +349,7 @@ function FastProductWithInfiniteStockUpdateForm({
}) })
} }
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</button> </button>
</span> </span>
</div> </div>
@ -383,7 +383,7 @@ function FastProductWithManagedStockUpdateForm({
const hasErrors = Object.keys(errors).some( const hasErrors = Object.keys(errors).some(
(k) => (errors as any)[k] !== undefined, (k) => (errors as any)[k] !== undefined,
); );
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<Fragment> <Fragment>
@ -395,31 +395,31 @@ function FastProductWithManagedStockUpdateForm({
> >
<InputNumber<FastProductUpdate> <InputNumber<FastProductUpdate>
name="incoming" name="incoming"
label={i18n`Incoming`} label={i18n.str`Incoming`}
tooltip={i18n`add more elements to the inventory`} tooltip={i18n.str`add more elements to the inventory`}
/> />
<InputNumber<FastProductUpdate> <InputNumber<FastProductUpdate>
name="lost" name="lost"
label={i18n`Lost`} label={i18n.str`Lost`}
tooltip={i18n`report elements lost in the inventory`} tooltip={i18n.str`report elements lost in the inventory`}
/> />
<InputCurrency<FastProductUpdate> <InputCurrency<FastProductUpdate>
name="price" name="price"
label={i18n`Price`} label={i18n.str`Price`}
tooltip={i18n`new price for the product`} tooltip={i18n.str`new price for the product`}
/> />
</FormProvider> </FormProvider>
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
<button class="button" onClick={onCancel}> <button class="button" onClick={onCancel}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
<span <span
class="has-tooltip-left" class="has-tooltip-left"
data-tooltip={ data-tooltip={
hasErrors hasErrors
? i18n`the are value with errors` ? i18n.str`the are value with errors`
: i18n`update product with new stock and price` : i18n.str`update product with new stock and price`
} }
> >
<button <button
@ -434,7 +434,7 @@ function FastProductWithManagedStockUpdateForm({
}) })
} }
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</button> </button>
</span> </span>
</div> </div>
@ -451,6 +451,7 @@ function FastProductUpdateForm(props: FastProductUpdateFormProps) {
} }
function EmptyTable(): VNode { function EmptyTable(): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="content has-text-grey has-text-centered"> <div class="content has-text-grey has-text-centered">
<p> <p>
@ -459,9 +460,9 @@ function EmptyTable(): VNode {
</span> </span>
</p> </p>
<p> <p>
<Translate> <i18n.Translate>
There is no products yet, add more pressing the + sign There is no products yet, add more pressing the + sign
</Translate> </i18n.Translate>
</p> </p>
</div> </div>
); );

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js"; import { Loading } from "../../../../components/exception/loading.js";
@ -29,7 +30,6 @@ import {
useInstanceProducts, useInstanceProducts,
useProductAPI, useProductAPI,
} from "../../../../hooks/product.js"; } from "../../../../hooks/product.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { CardTable } from "./Table.js"; import { CardTable } from "./Table.js";
@ -51,7 +51,7 @@ export default function ProductList({
const { deleteProduct, updateProduct } = useProductAPI(); const { deleteProduct, updateProduct } = useProductAPI();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isUnauthorized) return onUnauthorized();
if (result.clientError && result.isNotfound) return onNotFound(); if (result.clientError && result.isNotfound) return onNotFound();
@ -69,13 +69,13 @@ export default function ProductList({
updateProduct(id, prod) updateProduct(id, prod)
.then(() => .then(() =>
setNotif({ setNotif({
message: i18n`product updated successfully`, message: i18n.str`product updated successfully`,
type: "SUCCESS", type: "SUCCESS",
}), }),
) )
.catch((error) => .catch((error) =>
setNotif({ setNotif({
message: i18n`could not update the product`, message: i18n.str`could not update the product`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}), }),
@ -86,13 +86,13 @@ export default function ProductList({
deleteProduct(prod.id) deleteProduct(prod.id)
.then(() => .then(() =>
setNotif({ setNotif({
message: i18n`product delete successfully`, message: i18n.str`product delete successfully`,
type: "SUCCESS", type: "SUCCESS",
}), }),
) )
.catch((error) => .catch((error) =>
setNotif({ setNotif({
message: i18n`could not delete the product`, message: i18n.str`could not delete the product`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}), }),

View File

@ -19,12 +19,12 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
import { ProductForm } from "../../../../components/product/ProductForm.js"; import { ProductForm } from "../../../../components/product/ProductForm.js";
import { MerchantBackend, WithId } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { useListener } from "../../../../hooks/listener.js"; import { useListener } from "../../../../hooks/listener.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
type Entity = MerchantBackend.Products.ProductDetail & { product_id: string }; type Entity = MerchantBackend.Products.ProductDetail & { product_id: string };
@ -42,7 +42,7 @@ export function UpdatePage({ product, onUpdate, onBack }: Props): VNode {
}, },
); );
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div> <div>
@ -53,7 +53,7 @@ export function UpdatePage({ product, onUpdate, onBack }: Props): VNode {
<div class="level-left"> <div class="level-left">
<div class="level-item"> <div class="level-item">
<span class="is-size-4"> <span class="is-size-4">
<Translate>Product id:</Translate> <i18n.Translate>Product id:</i18n.Translate>
<b>{product.product_id}</b> <b>{product.product_id}</b>
</span> </span>
</div> </div>
@ -75,19 +75,19 @@ export function UpdatePage({ product, onUpdate, onBack }: Props): VNode {
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
{onBack && ( {onBack && (
<button class="button" onClick={onBack}> <button class="button" onClick={onBack}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
)} )}
<AsyncButton <AsyncButton
onClick={submitForm} onClick={submitForm}
data-tooltip={ data-tooltip={
!submitForm !submitForm
? i18n`Need to complete marked fields` ? i18n.str`Need to complete marked fields`
: "confirm operation" : "confirm operation"
} }
disabled={!submitForm} disabled={!submitForm}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</AsyncButton> </AsyncButton>
</div> </div>
</div> </div>

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js"; import { Loading } from "../../../../components/exception/loading.js";
@ -26,7 +27,6 @@ import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { HttpError } from "../../../../hooks/backend.js"; import { HttpError } from "../../../../hooks/backend.js";
import { useProductAPI, useProductDetails } from "../../../../hooks/product.js"; import { useProductAPI, useProductDetails } from "../../../../hooks/product.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { UpdatePage } from "./UpdatePage.js"; import { UpdatePage } from "./UpdatePage.js";
@ -51,7 +51,7 @@ export default function UpdateProduct({
const result = useProductDetails(pid); const result = useProductDetails(pid);
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isUnauthorized) return onUnauthorized();
if (result.clientError && result.isNotfound) return onNotFound(); if (result.clientError && result.isNotfound) return onNotFound();
@ -69,7 +69,7 @@ export default function UpdateProduct({
.then(onConfirm) .then(onConfirm)
.catch((error) => { .catch((error) => {
setNotif({ setNotif({
message: i18n`could not create product`, message: i18n.str`could not create product`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}); });

View File

@ -19,24 +19,23 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { StateUpdater, useEffect, useState } from "preact/hooks"; import { StateUpdater, useEffect, useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
import { import {
FormErrors, FormErrors,
FormProvider, FormProvider,
} from "../../../../components/form/FormProvider.js"; } from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js"; import { Input } from "../../../../components/form/Input.js";
import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { ExchangeBackend, MerchantBackend } from "../../../../declaration.js"; import { ExchangeBackend, MerchantBackend } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js"; import { request } from "../../../../hooks/backend.js";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
import { canonicalizeBaseUrl, ExchangeKeysJson } from "@gnu-taler/taler-util";
import { import {
PAYTO_WIRE_METHOD_LOOKUP, PAYTO_WIRE_METHOD_LOOKUP,
URL_REGEX, URL_REGEX,
} from "../../../../utils/constants.js"; } from "../../../../utils/constants.js";
import { request } from "../../../../hooks/backend.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
type Entity = MerchantBackend.Tips.ReserveCreateRequest; type Entity = MerchantBackend.Tips.ReserveCreateRequest;
@ -66,7 +65,7 @@ function ViewStep({
submitForm, submitForm,
setReserve, setReserve,
}: ViewProps): VNode { }: ViewProps): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const [wireMethods, setWireMethods] = useState<Array<string>>([]); const [wireMethods, setWireMethods] = useState<Array<string>>([]);
const [exchangeQueryError, setExchangeQueryError] = useState< const [exchangeQueryError, setExchangeQueryError] = useState<
string | undefined string | undefined
@ -82,12 +81,12 @@ function ViewStep({
initial_balance: !reserve.initial_balance initial_balance: !reserve.initial_balance
? "cannot be empty" ? "cannot be empty"
: !(parseInt(reserve.initial_balance.split(":")[1], 10) > 0) : !(parseInt(reserve.initial_balance.split(":")[1], 10) > 0)
? i18n`it should be greater than 0` ? i18n.str`it should be greater than 0`
: undefined, : undefined,
exchange_url: !reserve.exchange_url exchange_url: !reserve.exchange_url
? i18n`cannot be empty` ? i18n.str`cannot be empty`
: !URL_REGEX.test(reserve.exchange_url) : !URL_REGEX.test(reserve.exchange_url)
? i18n`must be a valid URL` ? i18n.str`must be a valid URL`
: !exchangeQueryError : !exchangeQueryError
? undefined ? undefined
: exchangeQueryError, : exchangeQueryError,
@ -106,20 +105,20 @@ function ViewStep({
> >
<InputCurrency<Entity> <InputCurrency<Entity>
name="initial_balance" name="initial_balance"
label={i18n`Initial balance`} label={i18n.str`Initial balance`}
tooltip={i18n`balance prior to deposit`} tooltip={i18n.str`balance prior to deposit`}
/> />
<Input<Entity> <Input<Entity>
name="exchange_url" name="exchange_url"
label={i18n`Exchange URL`} label={i18n.str`Exchange URL`}
tooltip={i18n`URL of exchange`} tooltip={i18n.str`URL of exchange`}
/> />
</FormProvider> </FormProvider>
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
{onBack && ( {onBack && (
<button class="button" onClick={onBack}> <button class="button" onClick={onBack}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
)} )}
<AsyncButton <AsyncButton
@ -143,12 +142,12 @@ function ViewStep({
}} }}
data-tooltip={ data-tooltip={
hasErrors hasErrors
? i18n`Need to complete marked fields` ? i18n.str`Need to complete marked fields`
: "confirm operation" : "confirm operation"
} }
disabled={hasErrors} disabled={hasErrors}
> >
<Translate>Next</Translate> <i18n.Translate>Next</i18n.Translate>
</AsyncButton> </AsyncButton>
</div> </div>
</Fragment> </Fragment>
@ -157,7 +156,9 @@ function ViewStep({
case Steps.WIRE_METHOD: { case Steps.WIRE_METHOD: {
const errors: FormErrors<Entity> = { const errors: FormErrors<Entity> = {
wire_method: !reserve.wire_method ? i18n`cannot be empty` : undefined, wire_method: !reserve.wire_method
? i18n.str`cannot be empty`
: undefined,
}; };
const hasErrors = Object.keys(errors).some( const hasErrors = Object.keys(errors).some(
@ -172,22 +173,22 @@ function ViewStep({
> >
<InputCurrency<Entity> <InputCurrency<Entity>
name="initial_balance" name="initial_balance"
label={i18n`Initial balance`} label={i18n.str`Initial balance`}
tooltip={i18n`balance prior to deposit`} tooltip={i18n.str`balance prior to deposit`}
readonly readonly
/> />
<Input<Entity> <Input<Entity>
name="exchange_url" name="exchange_url"
label={i18n`Exchange URL`} label={i18n.str`Exchange URL`}
tooltip={i18n`URL of exchange`} tooltip={i18n.str`URL of exchange`}
readonly readonly
/> />
<InputSelector<Entity> <InputSelector<Entity>
name="wire_method" name="wire_method"
label={i18n`Wire method`} label={i18n.str`Wire method`}
tooltip={i18n`method to use for wire transfer`} tooltip={i18n.str`method to use for wire transfer`}
values={wireMethods} values={wireMethods}
placeholder={i18n`Select one wire method`} placeholder={i18n.str`Select one wire method`}
/> />
</FormProvider> </FormProvider>
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
@ -196,19 +197,19 @@ function ViewStep({
class="button" class="button"
onClick={() => setCurrentStep(Steps.EXCHANGE)} onClick={() => setCurrentStep(Steps.EXCHANGE)}
> >
<Translate>Back</Translate> <i18n.Translate>Back</i18n.Translate>
</button> </button>
)} )}
<AsyncButton <AsyncButton
onClick={submitForm} onClick={submitForm}
data-tooltip={ data-tooltip={
hasErrors hasErrors
? i18n`Need to complete marked fields` ? i18n.str`Need to complete marked fields`
: "confirm operation" : "confirm operation"
} }
disabled={hasErrors} disabled={hasErrors}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</AsyncButton> </AsyncButton>
</div> </div>
</Fragment> </Fragment>

View File

@ -14,11 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { QR } from "../../../../components/exception/QR.js";
import { CreatedSuccessfully as Template } from "../../../../components/notifications/CreatedSuccessfully.js"; import { CreatedSuccessfully as Template } from "../../../../components/notifications/CreatedSuccessfully.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { Translate } from "../../../../i18n/index.js";
import { QR } from "../../../../components/exception/QR.js";
type Entity = { type Entity = {
request: MerchantBackend.Tips.ReserveCreateRequest; request: MerchantBackend.Tips.ReserveCreateRequest;
@ -37,7 +37,7 @@ export function CreatedSuccessfully({
onCreateAnother, onCreateAnother,
}: Props): VNode { }: Props): VNode {
const link = `${entity.response.payto_uri}?message=${entity.response.reserve_pub}&amount=${entity.request.initial_balance}`; const link = `${entity.response.payto_uri}?message=${entity.response.reserve_pub}&amount=${entity.request.initial_balance}`;
const { i18n } = useTranslationContext();
return ( return (
<Template onConfirm={onConfirm} onCreateAnother={onCreateAnother}> <Template onConfirm={onConfirm} onCreateAnother={onCreateAnother}>
<div class="field is-horizontal"> <div class="field is-horizontal">
@ -85,16 +85,16 @@ export function CreatedSuccessfully({
</div> </div>
</div> </div>
<p class="is-size-5"> <p class="is-size-5">
<Translate> <i18n.Translate>
To complete the setup of the reserve, you must now initiate a wire To complete the setup of the reserve, you must now initiate a wire
transfer using the given wire transfer subject and crediting the transfer using the given wire transfer subject and crediting the
specified amount to the indicated account of the exchange. specified amount to the indicated account of the exchange.
</Translate> </i18n.Translate>
</p> </p>
<p class="is-size-5"> <p class="is-size-5">
<Translate> <i18n.Translate>
If your system supports RFC 8905, you can do this by opening this URI: If your system supports RFC 8905, you can do this by opening this URI:
</Translate> </i18n.Translate>
</p> </p>
<pre> <pre>
<a target="_blank" rel="noreferrer" href={link}> <a target="_blank" rel="noreferrer" href={link}>

View File

@ -19,12 +19,12 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { NotificationCard } from "../../../../components/menu/index.js"; import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { useReservesAPI } from "../../../../hooks/reserves.js"; import { useReservesAPI } from "../../../../hooks/reserves.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { CreatedSuccessfully } from "./CreatedSuccessfully.js"; import { CreatedSuccessfully } from "./CreatedSuccessfully.js";
import { CreatePage } from "./CreatePage.js"; import { CreatePage } from "./CreatePage.js";
@ -35,7 +35,7 @@ interface Props {
export default function CreateReserve({ onBack, onConfirm }: Props): VNode { export default function CreateReserve({ onBack, onConfirm }: Props): VNode {
const { createReserve } = useReservesAPI(); const { createReserve } = useReservesAPI();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const [createdOk, setCreatedOk] = useState< const [createdOk, setCreatedOk] = useState<
| { | {
@ -59,7 +59,7 @@ export default function CreateReserve({ onBack, onConfirm }: Props): VNode {
.then((r) => setCreatedOk({ request, response: r.data })) .then((r) => setCreatedOk({ request, response: r.data }))
.catch((error) => { .catch((error) => {
setNotif({ setNotif({
message: i18n`could not create reserve`, message: i18n.str`could not create reserve`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}); });

View File

@ -20,6 +20,7 @@
*/ */
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { format } from "date-fns"; import { format } from "date-fns";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
@ -29,13 +30,9 @@ import { Input } from "../../../../components/form/Input.js";
import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputDate } from "../../../../components/form/InputDate.js"; import { InputDate } from "../../../../components/form/InputDate.js";
import { TextField } from "../../../../components/form/TextField.js"; import { TextField } from "../../../../components/form/TextField.js";
import { import { SimpleModal } from "../../../../components/modal/index.js";
ContinueModal,
SimpleModal,
} from "../../../../components/modal/index.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { useTipDetails } from "../../../../hooks/reserves.js"; import { useTipDetails } from "../../../../hooks/reserves.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
import { TipInfo } from "./TipInfo.js"; import { TipInfo } from "./TipInfo.js";
type Entity = MerchantBackend.Tips.ReserveDetail; type Entity = MerchantBackend.Tips.ReserveDetail;
@ -48,7 +45,7 @@ interface Props {
} }
export function DetailPage({ id, selected, onBack }: Props): VNode { export function DetailPage({ id, selected, onBack }: Props): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const didExchangeAckTransfer = Amounts.isNonZero( const didExchangeAckTransfer = Amounts.isNonZero(
Amounts.parseOrThrow(selected.exchange_initial_amount), Amounts.parseOrThrow(selected.exchange_initial_amount),
); );
@ -62,22 +59,22 @@ export function DetailPage({ id, selected, onBack }: Props): VNode {
<FormProvider object={{ ...selected, id }} valueHandler={null}> <FormProvider object={{ ...selected, id }} valueHandler={null}>
<InputDate<Entity> <InputDate<Entity>
name="creation_time" name="creation_time"
label={i18n`Created at`} label={i18n.str`Created at`}
readonly readonly
/> />
<InputDate<Entity> <InputDate<Entity>
name="expiration_time" name="expiration_time"
label={i18n`Valid until`} label={i18n.str`Valid until`}
readonly readonly
/> />
<InputCurrency<Entity> <InputCurrency<Entity>
name="merchant_initial_amount" name="merchant_initial_amount"
label={i18n`Created balance`} label={i18n.str`Created balance`}
readonly readonly
/> />
<TextField<Entity> <TextField<Entity>
name="exchange_url" name="exchange_url"
label={i18n`Exchange URL`} label={i18n.str`Exchange URL`}
readonly readonly
> >
<a target="_blank" rel="noreferrer" href={selected.exchange_url}> <a target="_blank" rel="noreferrer" href={selected.exchange_url}>
@ -89,27 +86,27 @@ export function DetailPage({ id, selected, onBack }: Props): VNode {
<Fragment> <Fragment>
<InputCurrency<Entity> <InputCurrency<Entity>
name="exchange_initial_amount" name="exchange_initial_amount"
label={i18n`Exchange balance`} label={i18n.str`Exchange balance`}
readonly readonly
/> />
<InputCurrency<Entity> <InputCurrency<Entity>
name="pickup_amount" name="pickup_amount"
label={i18n`Picked up`} label={i18n.str`Picked up`}
readonly readonly
/> />
<InputCurrency<Entity> <InputCurrency<Entity>
name="committed_amount" name="committed_amount"
label={i18n`Committed`} label={i18n.str`Committed`}
readonly readonly
/> />
</Fragment> </Fragment>
)} )}
<Input<Entity> <Input<Entity>
name="payto_uri" name="payto_uri"
label={i18n`Account address`} label={i18n.str`Account address`}
readonly readonly
/> />
<Input name="id" label={i18n`Subject`} readonly /> <Input name="id" label={i18n.str`Subject`} readonly />
</FormProvider> </FormProvider>
{didExchangeAckTransfer ? ( {didExchangeAckTransfer ? (
@ -120,7 +117,7 @@ export function DetailPage({ id, selected, onBack }: Props): VNode {
<span class="icon"> <span class="icon">
<i class="mdi mdi-cash-register" /> <i class="mdi mdi-cash-register" />
</span> </span>
<Translate>Tips</Translate> <i18n.Translate>Tips</i18n.Translate>
</p> </p>
</header> </header>
<div class="card-content"> <div class="card-content">
@ -139,18 +136,18 @@ export function DetailPage({ id, selected, onBack }: Props): VNode {
) : ( ) : (
<Fragment> <Fragment>
<p class="is-size-5"> <p class="is-size-5">
<Translate> <i18n.Translate>
To complete the setup of the reserve, you must now initiate a To complete the setup of the reserve, you must now initiate a
wire transfer using the given wire transfer subject and wire transfer using the given wire transfer subject and
crediting the specified amount to the indicated account of the crediting the specified amount to the indicated account of the
exchange. exchange.
</Translate> </i18n.Translate>
</p> </p>
<p class="is-size-5"> <p class="is-size-5">
<Translate> <i18n.Translate>
If your system supports RFC 8905, you can do this by opening If your system supports RFC 8905, you can do this by opening
this URI: this URI:
</Translate> </i18n.Translate>
</p> </p>
<pre> <pre>
<a target="_blank" rel="noreferrer" href={link}> <a target="_blank" rel="noreferrer" href={link}>
@ -163,7 +160,7 @@ export function DetailPage({ id, selected, onBack }: Props): VNode {
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
<button class="button" onClick={onBack}> <button class="button" onClick={onBack}>
<Translate>Back</Translate> <i18n.Translate>Back</i18n.Translate>
</button> </button>
</div> </div>
</div> </div>
@ -174,6 +171,7 @@ export function DetailPage({ id, selected, onBack }: Props): VNode {
} }
function EmptyTable(): VNode { function EmptyTable(): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="content has-text-grey has-text-centered"> <div class="content has-text-grey has-text-centered">
<p> <p>
@ -182,7 +180,9 @@ function EmptyTable(): VNode {
</span> </span>
</p> </p>
<p> <p>
<Translate>No tips has been authorized from this reserve</Translate> <i18n.Translate>
No tips has been authorized from this reserve
</i18n.Translate>
</p> </p>
</div> </div>
); );
@ -193,22 +193,23 @@ interface TableProps {
} }
function Table({ tips }: TableProps): VNode { function Table({ tips }: TableProps): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th> <th>
<Translate>Authorized</Translate> <i18n.Translate>Authorized</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Picked up</Translate> <i18n.Translate>Picked up</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Reason</Translate> <i18n.Translate>Reason</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Expiration</Translate> <i18n.Translate>Expiration</i18n.Translate>
</th> </th>
</tr> </tr>
</thead> </thead>

View File

@ -19,8 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import * as yup from "yup";
import { import {
FormErrors, FormErrors,
FormProvider, FormProvider,
@ -32,10 +34,8 @@ import {
ContinueModal, ContinueModal,
} from "../../../../components/modal/index.js"; } from "../../../../components/modal/index.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { useTranslator } from "../../../../i18n/index.js";
import { AuthorizeTipSchema } from "../../../../schemas/index.js"; import { AuthorizeTipSchema } from "../../../../schemas/index.js";
import { CreatedSuccessfully } from "./CreatedSuccessfully.js"; import { CreatedSuccessfully } from "./CreatedSuccessfully.js";
import * as yup from "yup";
interface AuthorizeTipModalProps { interface AuthorizeTipModalProps {
onCancel: () => void; onCancel: () => void;
@ -54,7 +54,7 @@ export function AuthorizeTipModal({
// const result = useOrderDetails(id) // const result = useOrderDetails(id)
type State = MerchantBackend.Tips.TipCreateRequest; type State = MerchantBackend.Tips.TipCreateRequest;
const [form, setValue] = useState<Partial<State>>({}); const [form, setValue] = useState<Partial<State>>({});
const i18n = useTranslator(); const { i18n } = useTranslationContext();
// const [errors, setErrors] = useState<FormErrors<State>>({}) // const [errors, setErrors] = useState<FormErrors<State>>({})
let errors: FormErrors<State> = {}; let errors: FormErrors<State> = {};
@ -104,19 +104,19 @@ export function AuthorizeTipModal({
> >
<InputCurrency<State> <InputCurrency<State>
name="amount" name="amount"
label={i18n`Amount`} label={i18n.str`Amount`}
tooltip={i18n`amount of tip`} tooltip={i18n.str`amount of tip`}
/> />
<Input<State> <Input<State>
name="justification" name="justification"
label={i18n`Justification`} label={i18n.str`Justification`}
inputType="multiline" inputType="multiline"
tooltip={i18n`reason for the tip`} tooltip={i18n.str`reason for the tip`}
/> />
<Input<State> <Input<State>
name="next_url" name="next_url"
label={i18n`URL after tip`} label={i18n.str`URL after tip`}
tooltip={i18n`URL to visit after tip payment`} tooltip={i18n.str`URL to visit after tip payment`}
/> />
</FormProvider> </FormProvider>
</ConfirmModal> </ConfirmModal>

View File

@ -19,10 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { format } from "date-fns"; import { format } from "date-fns";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { MerchantBackend, WithId } from "../../../../declaration.js"; import { MerchantBackend, WithId } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
type Entity = MerchantBackend.Tips.ReserveStatusEntry & WithId; type Entity = MerchantBackend.Tips.ReserveStatusEntry & WithId;
@ -51,7 +51,7 @@ export function CardTable({
return prev; return prev;
}, new Array<Array<Entity>>([], [])); }, new Array<Array<Entity>>([], []));
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<Fragment> <Fragment>
@ -62,7 +62,7 @@ export function CardTable({
<span class="icon"> <span class="icon">
<i class="mdi mdi-cash" /> <i class="mdi mdi-cash" />
</span> </span>
<Translate>Reserves not yet funded</Translate> <i18n.Translate>Reserves not yet funded</i18n.Translate>
</p> </p>
</header> </header>
<div class="card-content"> <div class="card-content">
@ -86,11 +86,14 @@ export function CardTable({
<span class="icon"> <span class="icon">
<i class="mdi mdi-cash" /> <i class="mdi mdi-cash" />
</span> </span>
<Translate>Reserves ready</Translate> <i18n.Translate>Reserves ready</i18n.Translate>
</p> </p>
<div class="card-header-icon" aria-label="more options" /> <div class="card-header-icon" aria-label="more options" />
<div class="card-header-icon" aria-label="more options"> <div class="card-header-icon" aria-label="more options">
<span class="has-tooltip-left" data-tooltip={i18n`add new reserve`}> <span
class="has-tooltip-left"
data-tooltip={i18n.str`add new reserve`}
>
<button class="button is-info" type="button" onClick={onCreate}> <button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small"> <span class="icon is-small">
<i class="mdi mdi-plus mdi-36px" /> <i class="mdi mdi-plus mdi-36px" />
@ -127,26 +130,26 @@ interface TableProps {
} }
function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode { function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th> <th>
<Translate>Created at</Translate> <i18n.Translate>Created at</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Expires at</Translate> <i18n.Translate>Expires at</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Initial</Translate> <i18n.Translate>Initial</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Picked up</Translate> <i18n.Translate>Picked up</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Committed</Translate> <i18n.Translate>Committed</i18n.Translate>
</th> </th>
<th /> <th />
</tr> </tr>
@ -196,7 +199,7 @@ function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode {
<div class="buttons is-right"> <div class="buttons is-right">
<button <button
class="button is-small is-danger has-tooltip-left" class="button is-small is-danger has-tooltip-left"
data-tooltip={i18n`delete selected reserve from the database`} data-tooltip={i18n.str`delete selected reserve from the database`}
type="button" type="button"
onClick={(): void => onDelete(i)} onClick={(): void => onDelete(i)}
> >
@ -204,7 +207,7 @@ function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode {
</button> </button>
<button <button
class="button is-small is-info has-tooltip-left" class="button is-small is-info has-tooltip-left"
data-tooltip={i18n`authorize new tip from selected reserve`} data-tooltip={i18n.str`authorize new tip from selected reserve`}
type="button" type="button"
onClick={(): void => onNewTip(i)} onClick={(): void => onNewTip(i)}
> >
@ -222,6 +225,7 @@ function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode {
} }
function EmptyTable(): VNode { function EmptyTable(): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="content has-text-grey has-text-centered"> <div class="content has-text-grey has-text-centered">
<p> <p>
@ -230,10 +234,10 @@ function EmptyTable(): VNode {
</span> </span>
</p> </p>
<p> <p>
<Translate> <i18n.Translate>
There is no ready reserves yet, add more pressing the + sign or fund There is no ready reserves yet, add more pressing the + sign or fund
them them
</Translate> </i18n.Translate>
</p> </p>
</div> </div>
); );
@ -244,20 +248,20 @@ function TableWithoutFund({
onSelect, onSelect,
onDelete, onDelete,
}: TableProps): VNode { }: TableProps): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th> <th>
<Translate>Created at</Translate> <i18n.Translate>Created at</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Expires at</Translate> <i18n.Translate>Expires at</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Expected Balance</Translate> <i18n.Translate>Expected Balance</i18n.Translate>
</th> </th>
<th /> <th />
</tr> </tr>
@ -296,7 +300,7 @@ function TableWithoutFund({
<button <button
class="button is-small is-danger jb-modal has-tooltip-left" class="button is-small is-danger jb-modal has-tooltip-left"
type="button" type="button"
data-tooltip={i18n`delete selected reserve from the database`} data-tooltip={i18n.str`delete selected reserve from the database`}
onClick={(): void => onDelete(i)} onClick={(): void => onDelete(i)}
> >
Delete Delete

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js"; import { Loading } from "../../../../components/exception/loading.js";
@ -29,10 +30,9 @@ import {
useInstanceReserves, useInstanceReserves,
useReservesAPI, useReservesAPI,
} from "../../../../hooks/reserves.js"; } from "../../../../hooks/reserves.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { CardTable } from "./Table.js";
import { AuthorizeTipModal } from "./AutorizeTipModal.js"; import { AuthorizeTipModal } from "./AutorizeTipModal.js";
import { CardTable } from "./Table.js";
interface Props { interface Props {
onUnauthorized: () => VNode; onUnauthorized: () => VNode;
@ -57,7 +57,7 @@ export default function ListTips({
const result = useInstanceReserves(); const result = useInstanceReserves();
const { deleteReserve, authorizeTipReserve } = useReservesAPI(); const { deleteReserve, authorizeTipReserve } = useReservesAPI();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const [reserveForTip, setReserveForTip] = useState<string | undefined>( const [reserveForTip, setReserveForTip] = useState<string | undefined>(
undefined, undefined,
); );
@ -93,7 +93,7 @@ export default function ListTips({
}); });
} catch (error) { } catch (error) {
setNotif({ setNotif({
message: i18n`could not create the tip`, message: i18n.str`could not create the tip`,
type: "ERROR", type: "ERROR",
description: error instanceof Error ? error.message : undefined, description: error instanceof Error ? error.message : undefined,
}); });

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
@ -33,7 +34,6 @@ import { InputNumber } from "../../../../components/form/InputNumber.js";
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js"; import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
import { useBackendContext } from "../../../../context/backend.js"; import { useBackendContext } from "../../../../context/backend.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
import { undefinedIfEmpty } from "../../../../utils/table.js"; import { undefinedIfEmpty } from "../../../../utils/table.js";
type Entity = MerchantBackend.Template.TemplateAddDetails; type Entity = MerchantBackend.Template.TemplateAddDetails;
@ -44,7 +44,7 @@ interface Props {
} }
export function CreatePage({ onCreate, onBack }: Props): VNode { export function CreatePage({ onCreate, onBack }: Props): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const backend = useBackendContext(); const backend = useBackendContext();
const [state, setState] = useState<Partial<Entity>>({ const [state, setState] = useState<Partial<Entity>>({
@ -57,23 +57,23 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
}); });
const errors: FormErrors<Entity> = { const errors: FormErrors<Entity> = {
template_id: !state.template_id ? i18n`should not be empty` : undefined, template_id: !state.template_id ? i18n.str`should not be empty` : undefined,
template_description: !state.template_description template_description: !state.template_description
? i18n`should not be empty` ? i18n.str`should not be empty`
: undefined, : undefined,
template_contract: !state.template_contract template_contract: !state.template_contract
? undefined ? undefined
: undefinedIfEmpty({ : undefinedIfEmpty({
minimum_age: minimum_age:
state.template_contract.minimum_age < 0 state.template_contract.minimum_age < 0
? i18n`should be greater that 0` ? i18n.str`should be greater that 0`
: undefined, : undefined,
pay_duration: !state.template_contract.pay_duration pay_duration: !state.template_contract.pay_duration
? i18n`can't be empty` ? i18n.str`can't be empty`
: state.template_contract.pay_duration.d_us === "forever" : state.template_contract.pay_duration.d_us === "forever"
? undefined ? undefined
: state.template_contract.pay_duration.d_us < 1000 : state.template_contract.pay_duration.d_us < 1000
? i18n`to short` ? i18n.str`to short`
: undefined, : undefined,
}), }),
}; };
@ -101,57 +101,57 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<InputWithAddon<Entity> <InputWithAddon<Entity>
name="template_id" name="template_id"
addonBefore={`${backend.url}/instances/templates/`} addonBefore={`${backend.url}/instances/templates/`}
label={i18n`Identifier`} label={i18n.str`Identifier`}
tooltip={i18n`Name of the template in URLs.`} tooltip={i18n.str`Name of the template in URLs.`}
/> />
<Input<Entity> <Input<Entity>
name="template_description" name="template_description"
label={i18n`Description`} label={i18n.str`Description`}
help="" help=""
tooltip={i18n`Describe what this template stands for`} tooltip={i18n.str`Describe what this template stands for`}
/> />
<Input <Input
name="template_contract.summary" name="template_contract.summary"
inputType="multiline" inputType="multiline"
label={i18n`Order summary`} label={i18n.str`Order summary`}
tooltip={i18n`Title of the order to be shown to the customer`} tooltip={i18n.str`Title of the order to be shown to the customer`}
/> />
<InputCurrency <InputCurrency
name="template_contract.amount" name="template_contract.amount"
label={i18n`Order price`} label={i18n.str`Order price`}
tooltip={i18n`Order price`} tooltip={i18n.str`Order price`}
/> />
<InputNumber <InputNumber
name="template_contract.minimum_age" name="template_contract.minimum_age"
label={i18n`Minimum age`} label={i18n.str`Minimum age`}
help="" help=""
tooltip={i18n`Is this contract restricted to some age?`} tooltip={i18n.str`Is this contract restricted to some age?`}
/> />
<InputDuration <InputDuration
name="template_contract.pay_duration" name="template_contract.pay_duration"
label={i18n`Payment timeout`} label={i18n.str`Payment timeout`}
help="" help=""
tooltip={i18n`How much time has the customer to complete the payment once the order was created.`} tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`}
/> />
</FormProvider> </FormProvider>
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
{onBack && ( {onBack && (
<button class="button" onClick={onBack}> <button class="button" onClick={onBack}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
)} )}
<AsyncButton <AsyncButton
disabled={hasErrors} disabled={hasErrors}
data-tooltip={ data-tooltip={
hasErrors hasErrors
? i18n`Need to complete marked fields` ? i18n.str`Need to complete marked fields`
: "confirm operation" : "confirm operation"
} }
onClick={submitForm} onClick={submitForm}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</AsyncButton> </AsyncButton>
</div> </div>
</div> </div>

View File

@ -19,14 +19,12 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { NotificationCard } from "../../../../components/menu/index.js"; import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { useInstanceDetails } from "../../../../hooks/instance.js";
import { useTemplateAPI } from "../../../../hooks/templates.js"; import { useTemplateAPI } from "../../../../hooks/templates.js";
import { useTransferAPI } from "../../../../hooks/transfer.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { CreatePage } from "./CreatePage.js"; import { CreatePage } from "./CreatePage.js";
@ -39,7 +37,7 @@ interface Props {
export default function CreateTransfer({ onConfirm, onBack }: Props): VNode { export default function CreateTransfer({ onConfirm, onBack }: Props): VNode {
const { createTemplate } = useTemplateAPI(); const { createTemplate } = useTemplateAPI();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<> <>
@ -51,7 +49,7 @@ export default function CreateTransfer({ onConfirm, onBack }: Props): VNode {
.then(() => onConfirm()) .then(() => onConfirm())
.catch((error) => { .catch((error) => {
setNotif({ setNotif({
message: i18n`could not inform template`, message: i18n.str`could not inform template`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}); });

View File

@ -21,7 +21,7 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { useTranslator } from "../../../../i18n/index.js"; import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { CardTable } from "./Table.js"; import { CardTable } from "./Table.js";
export interface Props { export interface Props {
@ -43,7 +43,7 @@ export function ListPage({
}: Props): VNode { }: Props): VNode {
const form = { payto_uri: "" }; const form = { payto_uri: "" };
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<section class="section is-main-section"> <section class="section is-main-section">
<CardTable <CardTable

View File

@ -19,10 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { StateUpdater, useState } from "preact/hooks"; import { StateUpdater, useState } from "preact/hooks";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
type Entity = MerchantBackend.Template.TemplateEntry; type Entity = MerchantBackend.Template.TemplateEntry;
@ -49,7 +49,7 @@ export function CardTable({
}: Props): VNode { }: Props): VNode {
const [rowSelection, rowSelectionHandler] = useState<string[]>([]); const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="card has-table"> <div class="card has-table">
@ -58,10 +58,13 @@ export function CardTable({
<span class="icon"> <span class="icon">
<i class="mdi mdi-newspaper" /> <i class="mdi mdi-newspaper" />
</span> </span>
<Translate>Templates</Translate> <i18n.Translate>Templates</i18n.Translate>
</p> </p>
<div class="card-header-icon" aria-label="more options"> <div class="card-header-icon" aria-label="more options">
<span class="has-tooltip-left" data-tooltip={i18n`add new templates`}> <span
class="has-tooltip-left"
data-tooltip={i18n.str`add new templates`}
>
<button class="button is-info" type="button" onClick={onCreate}> <button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small"> <span class="icon is-small">
<i class="mdi mdi-plus mdi-36px" /> <i class="mdi mdi-plus mdi-36px" />
@ -120,27 +123,27 @@ function Table({
hasMoreAfter, hasMoreAfter,
hasMoreBefore, hasMoreBefore,
}: TableProps): VNode { }: TableProps): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
{onLoadMoreBefore && ( {onLoadMoreBefore && (
<button <button
class="button is-fullwidth" class="button is-fullwidth"
data-tooltip={i18n`load more templates before the first one`} data-tooltip={i18n.str`load more templates before the first one`}
disabled={!hasMoreBefore} disabled={!hasMoreBefore}
onClick={onLoadMoreBefore} onClick={onLoadMoreBefore}
> >
<Translate>load newer templates</Translate> <i18n.Translate>load newer templates</i18n.Translate>
</button> </button>
)} )}
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th> <th>
<Translate>ID</Translate> <i18n.Translate>ID</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Description</Translate> <i18n.Translate>Description</i18n.Translate>
</th> </th>
<th /> <th />
</tr> </tr>
@ -164,7 +167,7 @@ function Table({
<td> <td>
<button <button
class="button is-danger is-small has-tooltip-left" class="button is-danger is-small has-tooltip-left"
data-tooltip={i18n`delete selected templates from the database`} data-tooltip={i18n.str`delete selected templates from the database`}
onClick={() => onDelete(i)} onClick={() => onDelete(i)}
> >
Delete Delete
@ -178,11 +181,11 @@ function Table({
{onLoadMoreAfter && ( {onLoadMoreAfter && (
<button <button
class="button is-fullwidth" class="button is-fullwidth"
data-tooltip={i18n`load more templates after the last one`} data-tooltip={i18n.str`load more templates after the last one`}
disabled={!hasMoreAfter} disabled={!hasMoreAfter}
onClick={onLoadMoreAfter} onClick={onLoadMoreAfter}
> >
<Translate>load older templates</Translate> <i18n.Translate>load older templates</i18n.Translate>
</button> </button>
)} )}
</div> </div>
@ -190,6 +193,7 @@ function Table({
} }
function EmptyTable(): VNode { function EmptyTable(): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="content has-text-grey has-text-centered"> <div class="content has-text-grey has-text-centered">
<p> <p>
@ -198,9 +202,9 @@ function EmptyTable(): VNode {
</span> </span>
</p> </p>
<p> <p>
<Translate> <i18n.Translate>
There is no templates yet, add more pressing the + sign There is no templates yet, add more pressing the + sign
</Translate> </i18n.Translate>
</p> </p>
</div> </div>
); );

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js"; import { Loading } from "../../../../components/exception/loading.js";
@ -29,7 +30,6 @@ import {
useInstanceTemplates, useInstanceTemplates,
useTemplateAPI, useTemplateAPI,
} from "../../../../hooks/templates.js"; } from "../../../../hooks/templates.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { ListPage } from "./ListPage.js"; import { ListPage } from "./ListPage.js";
@ -49,7 +49,7 @@ export default function ListTemplates({
onNotFound, onNotFound,
}: Props): VNode { }: Props): VNode {
const [position, setPosition] = useState<string | undefined>(undefined); const [position, setPosition] = useState<string | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { deleteTemplate } = useTemplateAPI(); const { deleteTemplate } = useTemplateAPI();
const result = useInstanceTemplates({ position }, (id) => setPosition(id)); const result = useInstanceTemplates({ position }, (id) => setPosition(id));
@ -77,13 +77,13 @@ export default function ListTemplates({
deleteTemplate(e.template_id) deleteTemplate(e.template_id)
.then(() => .then(() =>
setNotif({ setNotif({
message: i18n`template delete successfully`, message: i18n.str`template delete successfully`,
type: "SUCCESS", type: "SUCCESS",
}), }),
) )
.catch((error) => .catch((error) =>
setNotif({ setNotif({
message: i18n`could not delete the template`, message: i18n.str`could not delete the template`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}), }),

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
@ -31,11 +32,8 @@ import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputDuration } from "../../../../components/form/InputDuration.js"; import { InputDuration } from "../../../../components/form/InputDuration.js";
import { InputNumber } from "../../../../components/form/InputNumber.js"; import { InputNumber } from "../../../../components/form/InputNumber.js";
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js"; import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
import { ProductForm } from "../../../../components/product/ProductForm.js";
import { useBackendContext } from "../../../../context/backend.js"; import { useBackendContext } from "../../../../context/backend.js";
import { MerchantBackend, WithId } from "../../../../declaration.js"; import { MerchantBackend, WithId } from "../../../../declaration.js";
import { useListener } from "../../../../hooks/listener.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
import { undefinedIfEmpty } from "../../../../utils/table.js"; import { undefinedIfEmpty } from "../../../../utils/table.js";
type Entity = MerchantBackend.Template.TemplatePatchDetails & WithId; type Entity = MerchantBackend.Template.TemplatePatchDetails & WithId;
@ -47,28 +45,28 @@ interface Props {
} }
export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const backend = useBackendContext(); const backend = useBackendContext();
const [state, setState] = useState<Partial<Entity>>(template); const [state, setState] = useState<Partial<Entity>>(template);
const errors: FormErrors<Entity> = { const errors: FormErrors<Entity> = {
template_description: !state.template_description template_description: !state.template_description
? i18n`should not be empty` ? i18n.str`should not be empty`
: undefined, : undefined,
template_contract: !state.template_contract template_contract: !state.template_contract
? undefined ? undefined
: undefinedIfEmpty({ : undefinedIfEmpty({
minimum_age: minimum_age:
state.template_contract.minimum_age < 0 state.template_contract.minimum_age < 0
? i18n`should be greater that 0` ? i18n.str`should be greater that 0`
: undefined, : undefined,
pay_duration: !state.template_contract.pay_duration pay_duration: !state.template_contract.pay_duration
? i18n`can't be empty` ? i18n.str`can't be empty`
: state.template_contract.pay_duration.d_us === "forever" : state.template_contract.pay_duration.d_us === "forever"
? undefined ? undefined
: state.template_contract.pay_duration.d_us < 1000 : state.template_contract.pay_duration.d_us < 1000
? i18n`to short` ? i18n.str`to short`
: undefined, : undefined,
}), }),
}; };
@ -112,57 +110,57 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
name="id" name="id"
addonBefore={`templates/`} addonBefore={`templates/`}
readonly readonly
label={i18n`Identifier`} label={i18n.str`Identifier`}
tooltip={i18n`Name of the template in URLs.`} tooltip={i18n.str`Name of the template in URLs.`}
/> />
<Input<Entity> <Input<Entity>
name="template_description" name="template_description"
label={i18n`Description`} label={i18n.str`Description`}
help="" help=""
tooltip={i18n`Describe what this template stands for`} tooltip={i18n.str`Describe what this template stands for`}
/> />
<Input <Input
name="template_contract.summary" name="template_contract.summary"
inputType="multiline" inputType="multiline"
label={i18n`Order summary`} label={i18n.str`Order summary`}
tooltip={i18n`Title of the order to be shown to the customer`} tooltip={i18n.str`Title of the order to be shown to the customer`}
/> />
<InputCurrency <InputCurrency
name="template_contract.amount" name="template_contract.amount"
label={i18n`Order price`} label={i18n.str`Order price`}
tooltip={i18n`total product price added up`} tooltip={i18n.str`total product price added up`}
/> />
<InputNumber <InputNumber
name="template_contract.minimum_age" name="template_contract.minimum_age"
label={i18n`Minimum age`} label={i18n.str`Minimum age`}
help="" help=""
tooltip={i18n`Is this contract restricted to some age?`} tooltip={i18n.str`Is this contract restricted to some age?`}
/> />
<InputDuration <InputDuration
name="template_contract.pay_duration" name="template_contract.pay_duration"
label={i18n`Payment timeout`} label={i18n.str`Payment timeout`}
help="" help=""
tooltip={i18n`How much time has the customer to complete the payment once the order was created.`} tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`}
/> />
</FormProvider> </FormProvider>
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
{onBack && ( {onBack && (
<button class="button" onClick={onBack}> <button class="button" onClick={onBack}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
)} )}
<AsyncButton <AsyncButton
disabled={hasErrors} disabled={hasErrors}
data-tooltip={ data-tooltip={
hasErrors hasErrors
? i18n`Need to complete marked fields` ? i18n.str`Need to complete marked fields`
: "confirm operation" : "confirm operation"
} }
onClick={submitForm} onClick={submitForm}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</AsyncButton> </AsyncButton>
</div> </div>
</div> </div>

View File

@ -19,18 +19,17 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Loading } from "../../../../components/exception/loading.js"; import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js"; import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend, WithId } from "../../../../declaration.js"; import { MerchantBackend, WithId } from "../../../../declaration.js";
import { HttpError } from "../../../../hooks/backend.js"; import { HttpError } from "../../../../hooks/backend.js";
import { useProductAPI, useProductDetails } from "../../../../hooks/product.js";
import { import {
useTemplateAPI, useTemplateAPI,
useTemplateDetails, useTemplateDetails,
} from "../../../../hooks/templates.js"; } from "../../../../hooks/templates.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { UpdatePage } from "./UpdatePage.js"; import { UpdatePage } from "./UpdatePage.js";
@ -56,7 +55,7 @@ export default function UpdateTemplate({
const result = useTemplateDetails(tid); const result = useTemplateDetails(tid);
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isUnauthorized) return onUnauthorized();
if (result.clientError && result.isNotfound) return onNotFound(); if (result.clientError && result.isNotfound) return onNotFound();
@ -74,7 +73,7 @@ export default function UpdateTemplate({
.then(onConfirm) .then(onConfirm)
.catch((error) => { .catch((error) => {
setNotif({ setNotif({
message: i18n`could not update template`, message: i18n.str`could not update template`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}); });

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
@ -31,7 +32,6 @@ import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputSelector } from "../../../../components/form/InputSelector.js"; import { InputSelector } from "../../../../components/form/InputSelector.js";
import { useConfigContext } from "../../../../context/config.js"; import { useConfigContext } from "../../../../context/config.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
import { import {
CROCKFORD_BASE32_REGEX, CROCKFORD_BASE32_REGEX,
URL_REGEX, URL_REGEX,
@ -46,7 +46,7 @@ interface Props {
} }
export function CreatePage({ accounts, onCreate, onBack }: Props): VNode { export function CreatePage({ accounts, onCreate, onBack }: Props): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const { currency } = useConfigContext(); const { currency } = useConfigContext();
const [state, setState] = useState<Partial<Entity>>({ const [state, setState] = useState<Partial<Entity>>({
@ -58,18 +58,18 @@ export function CreatePage({ accounts, onCreate, onBack }: Props): VNode {
const errors: FormErrors<Entity> = { const errors: FormErrors<Entity> = {
wtid: !state.wtid wtid: !state.wtid
? i18n`cannot be empty` ? i18n.str`cannot be empty`
: !CROCKFORD_BASE32_REGEX.test(state.wtid) : !CROCKFORD_BASE32_REGEX.test(state.wtid)
? i18n`check the id, does not look valid` ? i18n.str`check the id, does not look valid`
: state.wtid.length !== 52 : state.wtid.length !== 52
? i18n`should have 52 characters, current ${state.wtid.length}` ? i18n.str`should have 52 characters, current ${state.wtid.length}`
: undefined, : undefined,
payto_uri: !state.payto_uri ? i18n`cannot be empty` : undefined, payto_uri: !state.payto_uri ? i18n.str`cannot be empty` : undefined,
credit_amount: !state.credit_amount ? i18n`cannot be empty` : undefined, credit_amount: !state.credit_amount ? i18n.str`cannot be empty` : undefined,
exchange_url: !state.exchange_url exchange_url: !state.exchange_url
? i18n`cannot be empty` ? i18n.str`cannot be empty`
: !URL_REGEX.test(state.exchange_url) : !URL_REGEX.test(state.exchange_url)
? i18n`URL doesn't have the right format` ? i18n.str`URL doesn't have the right format`
: undefined, : undefined,
}; };
@ -95,46 +95,46 @@ export function CreatePage({ accounts, onCreate, onBack }: Props): VNode {
> >
<InputSelector <InputSelector
name="payto_uri" name="payto_uri"
label={i18n`Credited bank account`} label={i18n.str`Credited bank account`}
values={accounts} values={accounts}
placeholder={i18n`Select one account`} placeholder={i18n.str`Select one account`}
tooltip={i18n`Bank account of the merchant where the payment was received`} tooltip={i18n.str`Bank account of the merchant where the payment was received`}
/> />
<Input<Entity> <Input<Entity>
name="wtid" name="wtid"
label={i18n`Wire transfer ID`} label={i18n.str`Wire transfer ID`}
help="" help=""
tooltip={i18n`unique identifier of the wire transfer used by the exchange, must be 52 characters long`} tooltip={i18n.str`unique identifier of the wire transfer used by the exchange, must be 52 characters long`}
/> />
<Input<Entity> <Input<Entity>
name="exchange_url" name="exchange_url"
label={i18n`Exchange URL`} label={i18n.str`Exchange URL`}
tooltip={i18n`Base URL of the exchange that made the transfer, should have been in the wire transfer subject`} tooltip={i18n.str`Base URL of the exchange that made the transfer, should have been in the wire transfer subject`}
help="http://exchange.taler:8081/" help="http://exchange.taler:8081/"
/> />
<InputCurrency<Entity> <InputCurrency<Entity>
name="credit_amount" name="credit_amount"
label={i18n`Amount credited`} label={i18n.str`Amount credited`}
tooltip={i18n`Actual amount that was wired to the merchant's bank account`} tooltip={i18n.str`Actual amount that was wired to the merchant's bank account`}
/> />
</FormProvider> </FormProvider>
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
{onBack && ( {onBack && (
<button class="button" onClick={onBack}> <button class="button" onClick={onBack}>
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
)} )}
<AsyncButton <AsyncButton
disabled={hasErrors} disabled={hasErrors}
data-tooltip={ data-tooltip={
hasErrors hasErrors
? i18n`Need to complete marked fields` ? i18n.str`Need to complete marked fields`
: "confirm operation" : "confirm operation"
} }
onClick={submitForm} onClick={submitForm}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</AsyncButton> </AsyncButton>
</div> </div>
</div> </div>

View File

@ -19,13 +19,13 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { NotificationCard } from "../../../../components/menu/index.js"; import { NotificationCard } from "../../../../components/menu/index.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { useInstanceDetails } from "../../../../hooks/instance.js"; import { useInstanceDetails } from "../../../../hooks/instance.js";
import { useTransferAPI } from "../../../../hooks/transfer.js"; import { useTransferAPI } from "../../../../hooks/transfer.js";
import { useTranslator } from "../../../../i18n/index.js";
import { Notification } from "../../../../utils/types.js"; import { Notification } from "../../../../utils/types.js";
import { CreatePage } from "./CreatePage.js"; import { CreatePage } from "./CreatePage.js";
@ -38,7 +38,7 @@ interface Props {
export default function CreateTransfer({ onConfirm, onBack }: Props): VNode { export default function CreateTransfer({ onConfirm, onBack }: Props): VNode {
const { informTransfer } = useTransferAPI(); const { informTransfer } = useTransferAPI();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const instance = useInstanceDetails(); const instance = useInstanceDetails();
const accounts = !instance.ok const accounts = !instance.ok
? [] ? []
@ -55,7 +55,7 @@ export default function CreateTransfer({ onConfirm, onBack }: Props): VNode {
.then(() => onConfirm()) .then(() => onConfirm())
.catch((error) => { .catch((error) => {
setNotif({ setNotif({
message: i18n`could not inform transfer`, message: i18n.str`could not inform transfer`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}); });

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { FormProvider } from "../../../../components/form/FormProvider.js"; import { FormProvider } from "../../../../components/form/FormProvider.js";
import { InputSelector } from "../../../../components/form/InputSelector.js"; import { InputSelector } from "../../../../components/form/InputSelector.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
import { CardTable } from "./Table.js"; import { CardTable } from "./Table.js";
export interface Props { export interface Props {
@ -61,7 +61,7 @@ export function ListPage({
}: Props): VNode { }: Props): VNode {
const form = { payto_uri: payTo }; const form = { payto_uri: payTo };
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<section class="section is-main-section"> <section class="section is-main-section">
<div class="columns"> <div class="columns">
@ -73,10 +73,10 @@ export function ListPage({
> >
<InputSelector <InputSelector
name="payto_uri" name="payto_uri"
label={i18n`Address`} label={i18n.str`Address`}
values={accounts} values={accounts}
placeholder={i18n`Select one account`} placeholder={i18n.str`Select one account`}
tooltip={i18n`filter by account address`} tooltip={i18n.str`filter by account address`}
/> />
</FormProvider> </FormProvider>
</div> </div>
@ -87,30 +87,30 @@ export function ListPage({
<li class={isAllTransfers ? "is-active" : ""}> <li class={isAllTransfers ? "is-active" : ""}>
<div <div
class="has-tooltip-right" class="has-tooltip-right"
data-tooltip={i18n`remove all filters`} data-tooltip={i18n.str`remove all filters`}
> >
<a onClick={onShowAll}> <a onClick={onShowAll}>
<Translate>All</Translate> <i18n.Translate>All</i18n.Translate>
</a> </a>
</div> </div>
</li> </li>
<li class={isVerifiedTransfers ? "is-active" : ""}> <li class={isVerifiedTransfers ? "is-active" : ""}>
<div <div
class="has-tooltip-right" class="has-tooltip-right"
data-tooltip={i18n`only show wire transfers confirmed by the merchant`} data-tooltip={i18n.str`only show wire transfers confirmed by the merchant`}
> >
<a onClick={onShowVerified}> <a onClick={onShowVerified}>
<Translate>Verified</Translate> <i18n.Translate>Verified</i18n.Translate>
</a> </a>
</div> </div>
</li> </li>
<li class={isNonVerifiedTransfers ? "is-active" : ""}> <li class={isNonVerifiedTransfers ? "is-active" : ""}>
<div <div
class="has-tooltip-right" class="has-tooltip-right"
data-tooltip={i18n`only show wire transfers claimed by the exchange`} data-tooltip={i18n.str`only show wire transfers claimed by the exchange`}
> >
<a onClick={onShowUnverified}> <a onClick={onShowUnverified}>
<Translate>Unverified</Translate> <i18n.Translate>Unverified</i18n.Translate>
</a> </a>
</div> </div>
</li> </li>

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { format } from "date-fns"; import { format } from "date-fns";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { StateUpdater, useState } from "preact/hooks"; import { StateUpdater, useState } from "preact/hooks";
import { MerchantBackend, WithId } from "../../../../declaration.js"; import { MerchantBackend, WithId } from "../../../../declaration.js";
import { Translate, useTranslator } from "../../../../i18n/index.js";
type Entity = MerchantBackend.Transfers.TransferDetails & WithId; type Entity = MerchantBackend.Transfers.TransferDetails & WithId;
@ -49,7 +49,7 @@ export function CardTable({
}: Props): VNode { }: Props): VNode {
const [rowSelection, rowSelectionHandler] = useState<string[]>([]); const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="card has-table"> <div class="card has-table">
@ -58,10 +58,13 @@ export function CardTable({
<span class="icon"> <span class="icon">
<i class="mdi mdi-bank" /> <i class="mdi mdi-bank" />
</span> </span>
<Translate>Transfers</Translate> <i18n.Translate>Transfers</i18n.Translate>
</p> </p>
<div class="card-header-icon" aria-label="more options"> <div class="card-header-icon" aria-label="more options">
<span class="has-tooltip-left" data-tooltip={i18n`add new transfer`}> <span
class="has-tooltip-left"
data-tooltip={i18n.str`add new transfer`}
>
<button class="button is-info" type="button" onClick={onCreate}> <button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small"> <span class="icon is-small">
<i class="mdi mdi-plus mdi-36px" /> <i class="mdi mdi-plus mdi-36px" />
@ -117,42 +120,42 @@ function Table({
hasMoreAfter, hasMoreAfter,
hasMoreBefore, hasMoreBefore,
}: TableProps): VNode { }: TableProps): VNode {
const i18n = useTranslator(); const { i18n } = useTranslationContext();
return ( return (
<div class="table-container"> <div class="table-container">
{onLoadMoreBefore && ( {onLoadMoreBefore && (
<button <button
class="button is-fullwidth" class="button is-fullwidth"
data-tooltip={i18n`load more transfers before the first one`} data-tooltip={i18n.str`load more transfers before the first one`}
disabled={!hasMoreBefore} disabled={!hasMoreBefore}
onClick={onLoadMoreBefore} onClick={onLoadMoreBefore}
> >
<Translate>load newer transfers</Translate> <i18n.Translate>load newer transfers</i18n.Translate>
</button> </button>
)} )}
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th> <th>
<Translate>ID</Translate> <i18n.Translate>ID</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Credit</Translate> <i18n.Translate>Credit</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Address</Translate> <i18n.Translate>Address</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Exchange URL</Translate> <i18n.Translate>Exchange URL</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Confirmed</Translate> <i18n.Translate>Confirmed</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Verified</Translate> <i18n.Translate>Verified</i18n.Translate>
</th> </th>
<th> <th>
<Translate>Executed at</Translate> <i18n.Translate>Executed at</i18n.Translate>
</th> </th>
<th /> <th />
</tr> </tr>
@ -165,23 +168,23 @@ function Table({
<td>{i.credit_amount}</td> <td>{i.credit_amount}</td>
<td>{i.payto_uri}</td> <td>{i.payto_uri}</td>
<td>{i.exchange_url}</td> <td>{i.exchange_url}</td>
<td>{i.confirmed ? i18n`yes` : i18n`no`}</td> <td>{i.confirmed ? i18n.str`yes` : i18n.str`no`}</td>
<td>{i.verified ? i18n`yes` : i18n`no`}</td> <td>{i.verified ? i18n.str`yes` : i18n.str`no`}</td>
<td> <td>
{i.execution_time {i.execution_time
? i.execution_time.t_s == "never" ? i.execution_time.t_s == "never"
? i18n`never` ? i18n.str`never`
: format( : format(
i.execution_time.t_s * 1000, i.execution_time.t_s * 1000,
"yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss",
) )
: i18n`unknown`} : i18n.str`unknown`}
</td> </td>
<td> <td>
{i.verified === undefined ? ( {i.verified === undefined ? (
<button <button
class="button is-danger is-small has-tooltip-left" class="button is-danger is-small has-tooltip-left"
data-tooltip={i18n`delete selected transfer from the database`} data-tooltip={i18n.str`delete selected transfer from the database`}
onClick={() => onDelete(i)} onClick={() => onDelete(i)}
> >
Delete Delete
@ -196,11 +199,11 @@ function Table({
{onLoadMoreAfter && ( {onLoadMoreAfter && (
<button <button
class="button is-fullwidth" class="button is-fullwidth"
data-tooltip={i18n`load more transfer after the last one`} data-tooltip={i18n.str`load more transfer after the last one`}
disabled={!hasMoreAfter} disabled={!hasMoreAfter}
onClick={onLoadMoreAfter} onClick={onLoadMoreAfter}
> >
<Translate>load older transfers</Translate> <i18n.Translate>load older transfers</i18n.Translate>
</button> </button>
)} )}
</div> </div>
@ -208,6 +211,7 @@ function Table({
} }
function EmptyTable(): VNode { function EmptyTable(): VNode {
const { i18n } = useTranslationContext();
return ( return (
<div class="content has-text-grey has-text-centered"> <div class="content has-text-grey has-text-centered">
<p> <p>
@ -216,9 +220,9 @@ function EmptyTable(): VNode {
</span> </span>
</p> </p>
<p> <p>
<Translate> <i18n.Translate>
There is no transfer yet, add more pressing the + sign There is no transfer yet, add more pressing the + sign
</Translate> </i18n.Translate>
</p> </p>
</div> </div>
); );

View File

@ -19,21 +19,20 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { Amounts } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import * as yup from "yup";
import { AsyncButton } from "../../../components/exception/AsyncButton.js"; import { AsyncButton } from "../../../components/exception/AsyncButton.js";
import { import {
FormProvider,
FormErrors, FormErrors,
FormProvider,
} from "../../../components/form/FormProvider.js"; } from "../../../components/form/FormProvider.js";
import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js";
import { UpdateTokenModal } from "../../../components/modal/index.js"; import { UpdateTokenModal } from "../../../components/modal/index.js";
import { useInstanceContext } from "../../../context/instance.js"; import { useInstanceContext } from "../../../context/instance.js";
import { MerchantBackend } from "../../../declaration.js"; import { MerchantBackend } from "../../../declaration.js";
import { Translate, useTranslator } from "../../../i18n/index.js";
import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js";
import { PAYTO_REGEX } from "../../../utils/constants.js"; import { PAYTO_REGEX } from "../../../utils/constants.js";
import { Amounts } from "@gnu-taler/taler-util";
import { undefinedIfEmpty } from "../../../utils/table.js"; import { undefinedIfEmpty } from "../../../utils/table.js";
type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & { type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & {
@ -95,50 +94,52 @@ export function UpdatePage({
const [value, valueHandler] = useState<Partial<Entity>>(convert(selected)); const [value, valueHandler] = useState<Partial<Entity>>(convert(selected));
const i18n = useTranslator(); const { i18n } = useTranslationContext();
const errors: FormErrors<Entity> = { const errors: FormErrors<Entity> = {
name: !value.name ? i18n`required` : undefined, name: !value.name ? i18n.str`required` : undefined,
payto_uris: payto_uris:
!value.payto_uris || !value.payto_uris.length !value.payto_uris || !value.payto_uris.length
? i18n`required` ? i18n.str`required`
: undefinedIfEmpty( : undefinedIfEmpty(
value.payto_uris.map((p) => { value.payto_uris.map((p) => {
return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined; return !PAYTO_REGEX.test(p) ? i18n.str`is not valid` : undefined;
}), }),
), ),
default_max_deposit_fee: !value.default_max_deposit_fee default_max_deposit_fee: !value.default_max_deposit_fee
? i18n`required` ? i18n.str`required`
: !Amounts.parse(value.default_max_deposit_fee) : !Amounts.parse(value.default_max_deposit_fee)
? i18n`invalid format` ? i18n.str`invalid format`
: undefined, : undefined,
default_max_wire_fee: !value.default_max_wire_fee default_max_wire_fee: !value.default_max_wire_fee
? i18n`required` ? i18n.str`required`
: !Amounts.parse(value.default_max_wire_fee) : !Amounts.parse(value.default_max_wire_fee)
? i18n`invalid format` ? i18n.str`invalid format`
: undefined, : undefined,
default_wire_fee_amortization: default_wire_fee_amortization:
value.default_wire_fee_amortization === undefined value.default_wire_fee_amortization === undefined
? i18n`required` ? i18n.str`required`
: isNaN(value.default_wire_fee_amortization) : isNaN(value.default_wire_fee_amortization)
? i18n`is not a number` ? i18n.str`is not a number`
: value.default_wire_fee_amortization < 1 : value.default_wire_fee_amortization < 1
? i18n`must be 1 or greater` ? i18n.str`must be 1 or greater`
: undefined, : undefined,
default_pay_delay: !value.default_pay_delay ? i18n`required` : undefined, default_pay_delay: !value.default_pay_delay
? i18n.str`required`
: undefined,
default_wire_transfer_delay: !value.default_wire_transfer_delay default_wire_transfer_delay: !value.default_wire_transfer_delay
? i18n`required` ? i18n.str`required`
: undefined, : undefined,
address: undefinedIfEmpty({ address: undefinedIfEmpty({
address_lines: address_lines:
value.address?.address_lines && value.address?.address_lines.length > 7 value.address?.address_lines && value.address?.address_lines.length > 7
? i18n`max 7 lines` ? i18n.str`max 7 lines`
: undefined, : undefined,
}), }),
jurisdiction: undefinedIfEmpty({ jurisdiction: undefinedIfEmpty({
address_lines: address_lines:
value.address?.address_lines && value.address?.address_lines.length > 7 value.address?.address_lines && value.address?.address_lines.length > 7
? i18n`max 7 lines` ? i18n.str`max 7 lines`
: undefined, : undefined,
}), }),
}; };
@ -160,7 +161,7 @@ export function UpdatePage({
<div class="level-left"> <div class="level-left">
<div class="level-item"> <div class="level-item">
<span class="is-size-4"> <span class="is-size-4">
<Translate>Instance id</Translate>: <b>{id}</b> <i18n.Translate>Instance id</i18n.Translate>: <b>{id}</b>
</span> </span>
</div> </div>
</div> </div>
@ -169,7 +170,7 @@ export function UpdatePage({
<h1 class="title"> <h1 class="title">
<button <button
class="button is-danger" class="button is-danger"
data-tooltip={i18n`Change the authorization method use for this instance.`} data-tooltip={i18n.str`Change the authorization method use for this instance.`}
onClick={(): void => { onClick={(): void => {
setActive(!active); setActive(!active);
}} }}
@ -178,7 +179,7 @@ export function UpdatePage({
<i class="mdi mdi-lock-reset" /> <i class="mdi mdi-lock-reset" />
</div> </div>
<span> <span>
<Translate>Manage access token</Translate> <i18n.Translate>Manage access token</i18n.Translate>
</span> </span>
</button> </button>
</h1> </h1>
@ -229,19 +230,19 @@ export function UpdatePage({
onClick={onBack} onClick={onBack}
data-tooltip="cancel operation" data-tooltip="cancel operation"
> >
<Translate>Cancel</Translate> <i18n.Translate>Cancel</i18n.Translate>
</button> </button>
<AsyncButton <AsyncButton
onClick={submit} onClick={submit}
data-tooltip={ data-tooltip={
hasErrors hasErrors
? i18n`Need to complete marked fields` ? i18n.str`Need to complete marked fields`
: "confirm operation" : "confirm operation"
} }
disabled={hasErrors} disabled={hasErrors}
> >
<Translate>Confirm</Translate> <i18n.Translate>Confirm</i18n.Translate>
</AsyncButton> </AsyncButton>
</div> </div>
</div> </div>

View File

@ -13,6 +13,7 @@
You should have received a copy of the GNU General Public License along with 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/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Loading } from "../../../components/exception/loading.js"; import { Loading } from "../../../components/exception/loading.js";
@ -26,7 +27,6 @@ import {
useManagedInstanceDetails, useManagedInstanceDetails,
useManagementAPI, useManagementAPI,
} from "../../../hooks/instance.js"; } from "../../../hooks/instance.js";
import { useTranslator } from "../../../i18n/index.js";
import { Notification } from "../../../utils/types.js"; import { Notification } from "../../../utils/types.js";
import { UpdatePage } from "./UpdatePage.js"; import { UpdatePage } from "./UpdatePage.js";
@ -70,7 +70,7 @@ function CommonUpdate(
): VNode { ): VNode {
const { changeToken } = useInstanceContext(); const { changeToken } = useInstanceContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const i18n = useTranslator(); const { i18n } = useTranslationContext();
if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isUnauthorized) return onUnauthorized();
if (result.clientError && result.isNotfound) return onNotFound(); if (result.clientError && result.isNotfound) return onNotFound();
@ -91,7 +91,7 @@ function CommonUpdate(
.then(onConfirm) .then(onConfirm)
.catch((error: Error) => .catch((error: Error) =>
setNotif({ setNotif({
message: i18n`Failed to create instance`, message: i18n.str`Failed to create instance`,
type: "ERROR", type: "ERROR",
description: error.message, description: error.message,
}), }),

View File

@ -20,15 +20,13 @@
*/ */
import { setupI18n } from "@gnu-taler/taler-util"; import { setupI18n } from "@gnu-taler/taler-util";
import { parseGroupImport } from "@gnu-taler/web-util/lib/index.browser"; import { parseGroupImport } from "@gnu-taler/web-util/lib/index.browser";
import { h as create } from "preact";
import { render as renderToString } from "preact-render-to-string";
import * as admin from "./paths/admin/index.stories.js"; import * as admin from "./paths/admin/index.stories.js";
import * as instance from "./paths/instance/index.stories.js"; import * as instance from "./paths/instance/index.stories.js";
setupI18n("en", { en: {} }); setupI18n("en", { en: {} });
import { h as create } from "preact"
import { render as renderToString } from "preact-render-to-string";
describe("All the examples:", () => { describe("All the examples:", () => {
const cms = parseGroupImport({ admin, instance }); const cms = parseGroupImport({ admin, instance });
cms.forEach((group) => { cms.forEach((group) => {
@ -37,8 +35,11 @@ describe("All the examples:", () => {
describe(`Component: ${component.name}`, () => { describe(`Component: ${component.name}`, () => {
component.examples.forEach((example) => { component.examples.forEach((example) => {
it(`should render example: ${example.name}`, () => { it(`should render example: ${example.name}`, () => {
const vdom = create(example.render.component, example.render.props) const vdom = create(
const html = renderToString(vdom) example.render.component,
example.render.props,
);
const html = renderToString(vdom);
// console.log(html) // console.log(html)
}); });
}); });

View File

@ -14,10 +14,10 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
/** /**
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { AMOUNT_REGEX, PAYTO_REGEX } from "../../src/utils/constants.js"; import { AMOUNT_REGEX, PAYTO_REGEX } from "../../src/utils/constants.js";
@ -30,7 +30,7 @@ describe('payto uri format', () => {
'payto://ilp/g.acme.bob' 'payto://ilp/g.acme.bob'
] ]
test('should be valid', () => { it('should be valid', () => {
valids.forEach(v => expect(v).toMatch(PAYTO_REGEX)) valids.forEach(v => expect(v).toMatch(PAYTO_REGEX))
}); });
@ -47,7 +47,7 @@ describe('payto uri format', () => {
'payto: //ilp/g.acme.bob' 'payto: //ilp/g.acme.bob'
] ]
test('should not be valid', () => { it('should not be valid', () => {
invalids.forEach(v => expect(v).not.toMatch(PAYTO_REGEX)) invalids.forEach(v => expect(v).not.toMatch(PAYTO_REGEX))
}); });
}) })
@ -63,7 +63,7 @@ describe('amount format', () => {
'THISISTHEMOTHERCOIN:1,000,000.123,123', 'THISISTHEMOTHERCOIN:1,000,000.123,123',
] ]
test('should be valid', () => { it('should be valid', () => {
valids.forEach(v => expect(v).toMatch(AMOUNT_REGEX)) valids.forEach(v => expect(v).toMatch(AMOUNT_REGEX))
}); });
@ -80,7 +80,7 @@ describe('amount format', () => {
'USD:', 'USD:',
] ]
test('should not be valid', () => { it('should not be valid', () => {
invalids.forEach(v => expect(v).not.toMatch(AMOUNT_REGEX)) invalids.forEach(v => expect(v).not.toMatch(AMOUNT_REGEX))
}); });

View File

@ -23,23 +23,23 @@ import { h } from "preact";
import { ProductList } from "../src/components/product/ProductList.js"; import { ProductList } from "../src/components/product/ProductList.js";
// See: https://github.com/preactjs/enzyme-adapter-preact-pure // See: https://github.com/preactjs/enzyme-adapter-preact-pure
// import { shallow } from 'enzyme'; // import { shallow } from 'enzyme';
import { render } from "@testing-library/preact";
import * as backend from "../src/context/config.js"; import * as backend from "../src/context/config.js";
import { render, findAllByText } from "@testing-library/preact"; // import * as i18n from "../src/context/translation.js";
import * as i18n from "../src/context/translation.js";
import * as jedLib from "jed"; // import * as jedLib from "jed";
const handler = new jedLib.Jed("en"); // const handler = new jedLib.Jed("en");
describe("Initial Test of the Sidebar", () => { describe("Initial Test of the Sidebar", () => {
beforeEach(() => { beforeEach(() => {
jest jest
.spyOn(backend, "useConfigContext") .spyOn(backend, "useConfigContext")
.mockImplementation(() => ({ version: "", currency: "" })); .mockImplementation(() => ({ version: "", currency: "" }));
jest.spyOn(i18n, "useTranslationContext").mockImplementation(() => ({ // jest.spyOn(i18n, "useTranslationContext").mockImplementation(() => ({
changeLanguage: () => null, // changeLanguage: () => null,
handler, // handler,
lang: "en", // lang: "en",
})); // }));
}); });
test("Product list renders a table", () => { test("Product list renders a table", () => {
const context = render( const context = render(

View File

@ -120,6 +120,7 @@ interface Mounted<T> {
* *
* @returns testing API * @returns testing API
*/ */
// eslint-disable-next-line @typescript-eslint/ban-types
export function mountHook<T extends object>( export function mountHook<T extends object>(
hookToBeTested: () => RecursiveState<T>, hookToBeTested: () => RecursiveState<T>,
Context?: ({ children }: { children: any }) => VNode | null, Context?: ({ children }: { children: any }) => VNode | null,
@ -242,6 +243,7 @@ interface HookTestResultError {
* *
* @returns testing result, should also be checked to be "ok" * @returns testing result, should also be checked to be "ok"
*/ */
// eslint-disable-next-line @typescript-eslint/ban-types
export async function hookBehaveLikeThis<T extends object, PropsType>( export async function hookBehaveLikeThis<T extends object, PropsType>(
hookFunction: (p: PropsType) => RecursiveState<T>, hookFunction: (p: PropsType) => RecursiveState<T>,
props: PropsType, props: PropsType,

View File

@ -374,7 +374,7 @@ importers:
jest: ^26.6.3 jest: ^26.6.3
jest-preset-preact: ^4.0.2 jest-preset-preact: ^4.0.2
mocha: ^9.2.0 mocha: ^9.2.0
preact: 10.6.5 preact: 10.11.3
preact-render-to-string: ^5.2.6 preact-render-to-string: ^5.2.6
preact-router: 3.2.1 preact-router: 3.2.1
qrcode-generator: 1.4.4 qrcode-generator: 1.4.4
@ -391,16 +391,16 @@ importers:
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@10.6.5 preact-router: 3.2.1_preact@10.11.3
qrcode-generator: 1.4.4 qrcode-generator: 1.4.4
swr: 1.3.0 swr: 1.3.0
yup: 0.32.11 yup: 0.32.11
devDependencies: devDependencies:
'@creativebulma/bulma-tooltip': 1.2.0 '@creativebulma/bulma-tooltip': 1.2.0
'@gnu-taler/pogen': link:../pogen '@gnu-taler/pogen': link:../pogen
'@testing-library/preact': 2.0.1_preact@10.6.5 '@testing-library/preact': 2.0.1_preact@10.11.3
'@testing-library/preact-hooks': 1.1.0_vfcmu6iy7nffpurikpgxo6gwxi '@testing-library/preact-hooks': 1.1.0_eng4adldpgibddgycwaukopxga
'@types/history': 4.7.11 '@types/history': 4.7.11
'@types/jest': 26.0.24 '@types/jest': 26.0.24
'@types/mocha': 8.2.3 '@types/mocha': 8.2.3
@ -424,9 +424,9 @@ importers:
html-webpack-skip-assets-plugin: 1.0.3 html-webpack-skip-assets-plugin: 1.0.3
inline-chunk-html-plugin: 1.1.1 inline-chunk-html-plugin: 1.1.1
jest: 26.6.3 jest: 26.6.3
jest-preset-preact: 4.0.5_moqeqtbsr7edkxzj3jgnhqkxsm jest-preset-preact: 4.0.5_w5bq6jgm3cbfmbu2zwqko4iate
mocha: 9.2.2 mocha: 9.2.2
preact-render-to-string: 5.2.6_preact@10.6.5 preact-render-to-string: 5.2.6_preact@10.11.3
rimraf: 3.0.2 rimraf: 3.0.2
sass: 1.56.1 sass: 1.56.1
typedoc: 0.20.37_typescript@4.8.4 typedoc: 0.20.37_typescript@4.8.4
@ -5795,14 +5795,14 @@ packages:
preact: 10.11.2 preact: 10.11.2
dev: true dev: true
/@testing-library/preact-hooks/1.1.0_vfcmu6iy7nffpurikpgxo6gwxi: /@testing-library/preact-hooks/1.1.0_eng4adldpgibddgycwaukopxga:
resolution: {integrity: sha512-+JIor+NsOHkK3oIrwMDGKGHXTN0JJi462dBJlj4FNbGaDPTlctE6eu2ranWQirh7/FJMkWfzQCP+tk7jmY8ZrQ==} resolution: {integrity: sha512-+JIor+NsOHkK3oIrwMDGKGHXTN0JJi462dBJlj4FNbGaDPTlctE6eu2ranWQirh7/FJMkWfzQCP+tk7jmY8ZrQ==}
peerDependencies: peerDependencies:
'@testing-library/preact': ^2.0.0 '@testing-library/preact': ^2.0.0
preact: ^10.4.8 preact: ^10.4.8
dependencies: dependencies:
'@testing-library/preact': 2.0.1_preact@10.6.5 '@testing-library/preact': 2.0.1_preact@10.11.3
preact: 10.6.5 preact: 10.11.3
dev: true dev: true
/@testing-library/preact/2.0.1_preact@10.11.2: /@testing-library/preact/2.0.1_preact@10.11.2:
@ -5815,14 +5815,14 @@ packages:
preact: 10.11.2 preact: 10.11.2
dev: true dev: true
/@testing-library/preact/2.0.1_preact@10.6.5: /@testing-library/preact/2.0.1_preact@10.11.3:
resolution: {integrity: sha512-79kwVOY+3caoLgaPbiPzikjgY0Aya7Fc7TvGtR1upCnz2wrtmPDnN2t9vO7I7vDP2zoA+feSwOH5Q0BFErhaaQ==} resolution: {integrity: sha512-79kwVOY+3caoLgaPbiPzikjgY0Aya7Fc7TvGtR1upCnz2wrtmPDnN2t9vO7I7vDP2zoA+feSwOH5Q0BFErhaaQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
peerDependencies: peerDependencies:
preact: '>=10 || ^10.0.0-alpha.0 || ^10.0.0-beta.0' preact: '>=10 || ^10.0.0-alpha.0 || ^10.0.0-beta.0'
dependencies: dependencies:
'@testing-library/dom': 7.31.2 '@testing-library/dom': 7.31.2
preact: 10.6.5 preact: 10.11.3
dev: true dev: true
/@tootallnate/once/1.1.2: /@tootallnate/once/1.1.2:
@ -14567,7 +14567,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/jest-preset-preact/4.0.5_moqeqtbsr7edkxzj3jgnhqkxsm: /jest-preset-preact/4.0.5_w5bq6jgm3cbfmbu2zwqko4iate:
resolution: {integrity: sha512-MnU7mfpnwopJkdx0WoEyRmrNDIvRN+w6sOur0zEhaRYYMo0gJM7UdZHWTV8k6uo0+ypY+m0kQW6kMukUx4v8JQ==} resolution: {integrity: sha512-MnU7mfpnwopJkdx0WoEyRmrNDIvRN+w6sOur0zEhaRYYMo0gJM7UdZHWTV8k6uo0+ypY+m0kQW6kMukUx4v8JQ==}
peerDependencies: peerDependencies:
jest: 26.x || 27.x jest: 26.x || 27.x
@ -14584,8 +14584,8 @@ packages:
isomorphic-unfetch: 3.1.0 isomorphic-unfetch: 3.1.0
jest: 26.6.3 jest: 26.6.3
jest-watch-typeahead: 0.6.5_jest@26.6.3 jest-watch-typeahead: 0.6.5_jest@26.6.3
preact: 10.6.5 preact: 10.11.3
preact-render-to-string: 5.2.6_preact@10.6.5 preact-render-to-string: 5.2.6_preact@10.11.3
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- supports-color - supports-color
@ -17955,15 +17955,6 @@ packages:
preact: 10.11.3 preact: 10.11.3
pretty-format: 3.8.0 pretty-format: 3.8.0
/preact-render-to-string/5.2.6_preact@10.6.5:
resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==}
peerDependencies:
preact: '>=10'
dependencies:
preact: 10.6.5
pretty-format: 3.8.0
dev: true
/preact-router/3.2.1_preact@10.11.2: /preact-router/3.2.1_preact@10.11.2:
resolution: {integrity: sha512-KEN2VN1DxUlTwzW5IFkF13YIA2OdQ2OvgJTkQREF+AA2NrHRLaGbB68EjS4IeZOa1shvQ1FvEm3bSLta4sXBhg==} resolution: {integrity: sha512-KEN2VN1DxUlTwzW5IFkF13YIA2OdQ2OvgJTkQREF+AA2NrHRLaGbB68EjS4IeZOa1shvQ1FvEm3bSLta4sXBhg==}
peerDependencies: peerDependencies:
@ -17980,23 +17971,12 @@ packages:
preact: 10.11.3 preact: 10.11.3
dev: false dev: false
/preact-router/3.2.1_preact@10.6.5:
resolution: {integrity: sha512-KEN2VN1DxUlTwzW5IFkF13YIA2OdQ2OvgJTkQREF+AA2NrHRLaGbB68EjS4IeZOa1shvQ1FvEm3bSLta4sXBhg==}
peerDependencies:
preact: '>=10'
dependencies:
preact: 10.6.5
dev: false
/preact/10.11.2: /preact/10.11.2:
resolution: {integrity: sha512-skAwGDFmgxhq1DCBHke/9e12ewkhc7WYwjuhHB8HHS8zkdtITXLRmUMTeol2ldxvLwYtwbFeifZ9uDDWuyL4Iw==} resolution: {integrity: sha512-skAwGDFmgxhq1DCBHke/9e12ewkhc7WYwjuhHB8HHS8zkdtITXLRmUMTeol2ldxvLwYtwbFeifZ9uDDWuyL4Iw==}
/preact/10.11.3: /preact/10.11.3:
resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==}
/preact/10.6.5:
resolution: {integrity: sha512-i+LXM6JiVjQXSt2jG2vZZFapGpCuk1fl8o6ii3G84MA3xgj686FKjs4JFDkmUVhtxyq21+4ay74zqPykz9hU6w==}
/prelude-ls/1.1.2: /prelude-ls/1.1.2:
resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}