aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÖzgür Kesim <oec-taler@kesim.org>2023-08-15 13:47:29 +0200
committerÖzgür Kesim <oec-taler@kesim.org>2023-08-15 13:47:29 +0200
commit819949d7f2cfcce8d334cbfdb701255daac64951 (patch)
tree592043554f59ec209f9afc1c31c20c4fb585c3ca
parent77ea209ddb6d457db0ce113f91247207cc29fbd8 (diff)
parentadb0e70f151e63d103f27852312319520f4d0a84 (diff)
Merge branch 'master' into age-withdraw
-rw-r--r--packages/aml-backoffice-ui/src/account.ts9
-rw-r--r--packages/aml-backoffice-ui/src/hooks/useCases.ts1
-rw-r--r--packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx2
-rw-r--r--packages/anastasis-cli/src/index.ts101
-rw-r--r--packages/anastasis-core/src/crypto.ts4
-rw-r--r--packages/demobank-ui/src/components/Loading.tsx8
-rw-r--r--packages/demobank-ui/src/i18n/it.po14
-rw-r--r--packages/demobank-ui/src/pages/HomePage.tsx15
-rw-r--r--packages/demobank-ui/src/pages/Routing.tsx2
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx3
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalQRCode.tsx95
-rw-r--r--packages/demobank-ui/src/scss/bank.scss38
-rw-r--r--packages/demobank-ui/src/utils.ts6
-rw-r--r--packages/merchant-backoffice-ui/src/Application.tsx24
-rw-r--r--packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx14
-rw-r--r--packages/merchant-backoffice-ui/src/InstanceRoutes.tsx7
-rw-r--r--packages/merchant-backoffice-ui/src/components/exception/login.tsx97
-rw-r--r--packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx91
-rw-r--r--packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx64
-rw-r--r--packages/merchant-backoffice-ui/src/components/menu/index.tsx14
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/backend.ts8
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/index.ts27
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/useSettings.ts59
-rw-r--r--packages/merchant-backoffice-ui/src/i18n/de.po15
-rw-r--r--packages/merchant-backoffice-ui/src/i18n/es.po6
-rw-r--r--packages/merchant-backoffice-ui/src/i18n/it.po21
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx309
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx31
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx20
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx4
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx38
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx40
-rw-r--r--packages/merchant-backoffice-ui/src/paths/notfound/index.tsx1
-rw-r--r--packages/merchant-backoffice-ui/src/paths/settings/index.tsx77
-rw-r--r--packages/merchant-backoffice-ui/src/scss/main.scss14
-rw-r--r--packages/merchant-backoffice-ui/src/scss/toggle.scss51
-rw-r--r--packages/taler-harness/src/integrationtests/test-age-restrictions-peer.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/testrunner.ts13
-rw-r--r--packages/taler-util/src/taleruri.ts2
-rw-r--r--packages/taler-util/src/wallet-types.ts65
-rw-r--r--packages/taler-wallet-core/src/db.ts5
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts10
-rw-r--r--packages/taler-wallet-core/src/wallet.ts13
-rw-r--r--packages/taler-wallet-embedded/src/wallet-qjs.ts44
-rw-r--r--packages/taler-wallet-webextension/src/i18n/es.po6
-rw-r--r--packages/taler-wallet-webextension/src/i18n/it.po18
-rw-r--r--packages/taler-wallet-webextension/src/i18n/nl.po1950
48 files changed, 3030 insertions, 432 deletions
diff --git a/packages/aml-backoffice-ui/src/account.ts b/packages/aml-backoffice-ui/src/account.ts
index 2225bf2ff..615d843c4 100644
--- a/packages/aml-backoffice-ui/src/account.ts
+++ b/packages/aml-backoffice-ui/src/account.ts
@@ -61,17 +61,20 @@ export function buildQuerySignature(key: SigningKey): string {
return encodeCrock(eddsaSign(sigBlob, key));
}
+
export function buildDecisionSignature(
key: SigningKey,
decision: AmlExchangeBackend.AmlDecision,
): string {
+ const zero = new Uint8Array(new ArrayBuffer(64))
const sigBlob = buildSigPS(TalerSignaturePurpose.TALER_SIGNATURE_AML_DECISION)
- .put(hash(stringToBytes(decision.justification)))
- // .put(timestampRoundedToBuffer(decision.decision_time))
+ //TODO: new need the null terminator, also in the exchange
+ .put(hash(stringToBytes(decision.justification)))//check null
+ .put(timestampRoundedToBuffer(decision.decision_time))
.put(amountToBuffer(decision.new_threshold))
.put(decodeCrock(decision.h_payto))
- // .put(hash(stringToBytes(decision.kyc_requirements)))
+ .put(zero) //kyc_requirement
.put(bufferForUint32(decision.new_state))
.build();
diff --git a/packages/aml-backoffice-ui/src/hooks/useCases.ts b/packages/aml-backoffice-ui/src/hooks/useCases.ts
index c5a0fc489..c07bd5f18 100644
--- a/packages/aml-backoffice-ui/src/hooks/useCases.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useCases.ts
@@ -85,7 +85,6 @@ export function useCases(
const records = !afterData
? []
: ((afterData ?? lastAfter).data ?? { records: [] }).records;
- console.log("afterdata", afterData, lastAfter, records)
if (loadingAfter) return { loading: true, data: { records } };
if (afterData) {
return { ok: true, data: { records }, ...pagination };
diff --git a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx b/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
index 13e78b169..429cfb9ca 100644
--- a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
+++ b/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
@@ -38,7 +38,7 @@ export function NewFormEntry({
fullName: "loggedIn_user_fullname",
when: AbsoluteTime.now(),
state: AmlExchangeBackend.AmlState.pending,
- threshold: Amounts.parseOrThrow("ARS:1000"),
+ threshold: Amounts.parseOrThrow("KUDOS:1000"),
};
const api = useAmlCasesAPI()
diff --git a/packages/anastasis-cli/src/index.ts b/packages/anastasis-cli/src/index.ts
index 560574276..7c011569f 100644
--- a/packages/anastasis-cli/src/index.ts
+++ b/packages/anastasis-cli/src/index.ts
@@ -1,20 +1,78 @@
import { clk } from "@gnu-taler/taler-util/clk";
import {
+ discoverPolicies,
getBackupStartState,
getRecoveryStartState,
reduceAction,
} from "@gnu-taler/anastasis-core";
import fs from "fs";
+import { j2s } from "@gnu-taler/taler-util";
-export const reducerCli = clk
- .program("reducer", {
- help: "Command line interface for Anastasis.",
+export const reducerCli = clk.program("anastasis-cli", {
+ help: "Command line interface for Anastasis.",
+});
+
+reducerCli
+ .subcommand("reducer", "reduce", {
+ help: "Run the anastasis reducer",
})
.flag("initBackup", ["-b", "--backup"])
.flag("initRecovery", ["-r", "--restore"])
.maybeOption("argumentsJson", ["-a", "--arguments"], clk.STRING)
.maybeArgument("action", clk.STRING)
- .maybeArgument("stateFile", clk.STRING);
+ .maybeArgument("stateFile", clk.STRING)
+ .action(async (x) => {
+ if (x.reducer.initBackup) {
+ console.log(JSON.stringify(await getBackupStartState()));
+ return;
+ } else if (x.reducer.initRecovery) {
+ console.log(JSON.stringify(await getRecoveryStartState()));
+ return;
+ }
+
+ const action = x.reducer.action;
+ if (!action) {
+ console.log("action required");
+ return;
+ }
+
+ let lastState: any;
+ if (x.reducer.stateFile) {
+ const s = fs.readFileSync(x.reducer.stateFile, { encoding: "utf-8" });
+ lastState = JSON.parse(s);
+ } else {
+ const s = await read(process.stdin);
+ lastState = JSON.parse(s);
+ }
+
+ let args: any;
+ if (x.reducer.argumentsJson) {
+ args = JSON.parse(x.reducer.argumentsJson);
+ } else {
+ args = {};
+ }
+
+ const nextState = await reduceAction(lastState, action, args);
+ console.log(JSON.stringify(nextState));
+ });
+
+reducerCli
+ .subcommand("discover", "discover", {
+ help: "Run the anastasis reducer",
+ })
+ .maybeArgument("stateFile", clk.STRING)
+ .action(async (args) => {
+ let lastState: any;
+ if (args.discover.stateFile) {
+ const s = fs.readFileSync(args.discover.stateFile, { encoding: "utf-8" });
+ lastState = JSON.parse(s);
+ } else {
+ const s = await read(process.stdin);
+ lastState = JSON.parse(s);
+ }
+ const res = await discoverPolicies(lastState);
+ console.log(j2s(res));
+ });
async function read(stream: NodeJS.ReadStream): Promise<string> {
const chunks = [];
@@ -24,41 +82,6 @@ async function read(stream: NodeJS.ReadStream): Promise<string> {
return Buffer.concat(chunks).toString("utf8");
}
-reducerCli.action(async (x) => {
- if (x.reducer.initBackup) {
- console.log(JSON.stringify(await getBackupStartState()));
- return;
- } else if (x.reducer.initRecovery) {
- console.log(JSON.stringify(await getRecoveryStartState()));
- return;
- }
-
- const action = x.reducer.action;
- if (!action) {
- console.log("action required");
- return;
- }
-
- let lastState: any;
- if (x.reducer.stateFile) {
- const s = fs.readFileSync(x.reducer.stateFile, { encoding: "utf-8" });
- lastState = JSON.parse(s);
- } else {
- const s = await read(process.stdin);
- lastState = JSON.parse(s);
- }
-
- let args: any;
- if (x.reducer.argumentsJson) {
- args = JSON.parse(x.reducer.argumentsJson);
- } else {
- args = {};
- }
-
- const nextState = await reduceAction(lastState, action, args);
- console.log(JSON.stringify(nextState));
-});
-
export function reducerCliMain() {
reducerCli.run();
}
diff --git a/packages/anastasis-core/src/crypto.ts b/packages/anastasis-core/src/crypto.ts
index 3a9483aa1..8bc004e95 100644
--- a/packages/anastasis-core/src/crypto.ts
+++ b/packages/anastasis-core/src/crypto.ts
@@ -151,7 +151,11 @@ export async function decryptPolicyMetadata(
userId: UserIdentifier,
metadataEnc: OpaqueData,
): Promise<PolicyMetadata> {
+ // @ts-ignore
+ console.log("metadataEnc", metadataEnc);
const plain = await anastasisDecrypt(asOpaque(userId), metadataEnc, "rmd");
+ // @ts-ignore
+ console.log("plain:", plain);
const metadataBytes = decodeCrock(plain);
const policyHash = encodeCrock(metadataBytes.slice(0, 64));
const secretName = bytesToString(metadataBytes.slice(64));
diff --git a/packages/demobank-ui/src/components/Loading.tsx b/packages/demobank-ui/src/components/Loading.tsx
index 7cbdad681..b567e9056 100644
--- a/packages/demobank-ui/src/components/Loading.tsx
+++ b/packages/demobank-ui/src/components/Loading.tsx
@@ -21,9 +21,11 @@ export function Loading(): VNode {
<div
class="columns is-centered is-vcentered"
style={{
- height: "calc(100% - 3rem)",
- position: "absolute",
width: "100%",
+ height: "200px",
+ display: "flex",
+ margin: "auto",
+ justifyContent: "center",
}}
>
<Spinner />
@@ -33,7 +35,7 @@ export function Loading(): VNode {
export function Spinner(): VNode {
return (
- <div class="lds-ring">
+ <div class="lds-ring" style={{margin:"auto"}}>
<div />
<div />
<div />
diff --git a/packages/demobank-ui/src/i18n/it.po b/packages/demobank-ui/src/i18n/it.po
index a3a599376..5dfebedae 100644
--- a/packages/demobank-ui/src/i18n/it.po
+++ b/packages/demobank-ui/src/i18n/it.po
@@ -14,8 +14,8 @@ msgstr ""
"Project-Id-Version: Taler Wallet\n"
"Report-Msgid-Bugs-To: taler@gnu.org\n"
"POT-Creation-Date: 2016-11-23 00:00+0100\n"
-"PO-Revision-Date: 2022-12-26 23:30+0000\n"
-"Last-Translator: Stefan Kügel <skuegel@web.de>\n"
+"PO-Revision-Date: 2023-08-15 07:28+0000\n"
+"Last-Translator: Krystian Baran <kiszkot@murena.io>\n"
"Language-Team: Italian <https://weblate.taler.net/projects/gnu-taler/"
"taler-bank-spa/it/>\n"
"Language: it\n"
@@ -199,9 +199,9 @@ msgid "Amount to withdraw:"
msgstr "Somma da ritirare"
#: src/pages/home/WalletWithdrawForm.tsx:84
-#, fuzzy, c-format
+#, c-format
msgid "Withdraw"
-msgstr "Conferma il ritiro"
+msgstr "Prelevare"
#: src/pages/home/WalletWithdrawForm.tsx:128
#, fuzzy, c-format
@@ -231,12 +231,12 @@ msgstr "Trasferisci fondi a un altro conto di questa banca:"
#: src/pages/home/Transactions.tsx:69
#, c-format
msgid "Date"
-msgstr ""
+msgstr "Data"
#: src/pages/home/Transactions.tsx:70
#, c-format
msgid "Amount"
-msgstr "Somma"
+msgstr "Importo"
#: src/pages/home/Transactions.tsx:71
#, c-format
@@ -246,7 +246,7 @@ msgstr "Controparte"
#: src/pages/home/Transactions.tsx:72
#, c-format
msgid "Subject"
-msgstr "Causale"
+msgstr "Soggetto"
#: src/pages/home/QrCodeSection.tsx:41
#, fuzzy, c-format
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index 20fcef39a..93a9bdfae 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -84,11 +84,11 @@ export function HomePage({
export function WithdrawalOperationPage({
operationId,
onLoadNotOk,
- onAbort,
+ onContinue,
}: {
operationId: string;
onLoadNotOk: () => void;
- onAbort: () => void;
+ onContinue: () => void;
}): VNode {
//FIXME: libeufin sandbox should return show to create the integration api endpoint
//or return withdrawal uri from response
@@ -99,12 +99,6 @@ export function WithdrawalOperationPage({
const parsedUri = parseWithdrawUri(uri);
const { i18n } = useTranslationContext();
- const [settings, updateSettings] = useSettings();
- function clearCurrentWithdrawal(): void {
- updateSettings("currentWithdrawalOperationId", undefined);
- onAbort();
- }
-
if (!parsedUri) {
notifyError({
title: i18n.str`The Withdrawal URI is not valid: "${uri}"`,
@@ -115,10 +109,7 @@ export function WithdrawalOperationPage({
return (
<WithdrawalQRCode
withdrawUri={parsedUri}
- onConfirmed={() => {
- notifyInfo(i18n.str`Withdrawal confirmed!`);
- }}
- onAborted={clearCurrentWithdrawal}
+ onContinue={onContinue}
onLoadNotOk={onLoadNotOk}
/>
);
diff --git a/packages/demobank-ui/src/pages/Routing.tsx b/packages/demobank-ui/src/pages/Routing.tsx
index 64f9b1208..f176c73db 100644
--- a/packages/demobank-ui/src/pages/Routing.tsx
+++ b/packages/demobank-ui/src/pages/Routing.tsx
@@ -40,7 +40,7 @@ export function Routing(): VNode {
component={({ wopid }: { wopid: string }) => (
<WithdrawalOperationPage
operationId={wopid}
- onAbort={() => {
+ onContinue={() => {
route("/account");
}}
onLoadNotOk={() => {
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index da245b75d..cdb612155 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -33,7 +33,6 @@ import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
const logger = new Logger("WithdrawalConfirmationQuestion");
interface Props {
- onConfirmed: () => void;
onAborted: () => void;
withdrawUri: WithdrawUriResult;
}
@@ -42,7 +41,6 @@ interface Props {
* Not providing a back button, only abort.
*/
export function WithdrawalConfirmationQuestion({
- onConfirmed,
onAborted,
withdrawUri,
}: Props): VNode {
@@ -119,7 +117,6 @@ export function WithdrawalConfirmationQuestion({
await confirmWithdrawal(
withdrawUri.withdrawalOperationId,
);
- onConfirmed();
} catch (error) {
if (error instanceof RequestError) {
notifyError(
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 9f9c9925e..80fdac3c8 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -24,6 +24,7 @@ import { Fragment, VNode, h } from "preact";
import { Loading } from "../components/Loading.js";
import { useWithdrawalDetails } from "../hooks/access.js";
import { notifyInfo } from "../hooks/notification.js";
+import { useSettings } from "../hooks/settings.js";
import { handleNotOkResult } from "./HomePage.js";
import { QrCodeSection } from "./QrCodeSection.js";
import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
@@ -32,8 +33,7 @@ const logger = new Logger("WithdrawalQRCode");
interface Props {
withdrawUri: WithdrawUriResult;
- onAborted: () => void;
- onConfirmed: () => void;
+ onContinue: () => void;
onLoadNotOk: () => void;
}
/**
@@ -43,10 +43,14 @@ interface Props {
*/
export function WithdrawalQRCode({
withdrawUri,
- onConfirmed,
- onAborted,
+ onContinue,
onLoadNotOk,
}: Props): VNode {
+ const [settings, updateSettings] = useSettings();
+ function clearCurrentWithdrawal(): void {
+ updateSettings("currentWithdrawalOperationId", undefined);
+ onContinue();
+ }
const { i18n } = useTranslationContext();
const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
if (!result.ok) {
@@ -64,13 +68,64 @@ export function WithdrawalQRCode({
}
const { data } = result;
- logger.trace("withdrawal status", data);
- if (data.aborted || data.confirmation_done) {
- // signal that this withdrawal is aborted
- // will redirect to account info
- notifyInfo(i18n.str`Operation completed`);
- onAborted();
- return <Loading />;
+ if (data.aborted) {
+ return <section id="main" class="content">
+ <h1 class="nav">{i18n.str`Operation aborted`}</h1>
+ <section>
+ <p>
+ <i18n.Translate>
+ The wire transfer to the GNU Taler Exchange bank's account was aborted, your balance
+ was not affected.
+ </i18n.Translate>
+ </p>
+ <p>
+ <i18n.Translate>
+ You can close this page now or continue to the account page.
+ </i18n.Translate>
+ </p>
+ <a class="pure-button pure-button-primary"
+ style={{float:"right"}}
+ onClick={async (e) => {
+ e.preventDefault();
+ clearCurrentWithdrawal()
+ onContinue()
+ }}>
+ {i18n.str`Continue`}
+ </a>
+
+ </section>
+ </section>
+ }
+
+ if (data.confirmation_done) {
+ return <section id="main" class="content">
+ <h1 class="nav">{i18n.str`Operation completed`}</h1>
+
+ <section id="assets" style={{maxWidth: 400, marginLeft: "auto", marginRight:"auto"}}>
+ <p>
+ <i18n.Translate>
+ The wire transfer to the GNU Taler Exchange bank's account is completed, now the
+ exchange will send the requested amount into your GNU Taler wallet.
+ </i18n.Translate>
+ </p>
+ <p>
+ <i18n.Translate>
+ You can close this page now or continue to the account page.
+ </i18n.Translate>
+ </p>
+ <div style={{textAlign:"center"}}>
+
+ <a class="pure-button pure-button-primary"
+ onClick={async (e) => {
+ e.preventDefault();
+ clearCurrentWithdrawal()
+ onContinue()
+ }}>
+ {i18n.str`Continue`}
+ </a>
+ </div>
+ </section>
+ </section>
}
if (!data.selection_done) {
@@ -79,25 +134,21 @@ export function WithdrawalQRCode({
withdrawUri={withdrawUri}
onAborted={() => {
notifyInfo(i18n.str`Operation canceled`);
- onAborted();
- }}
+ clearCurrentWithdrawal()
+ onContinue()
+ }}
/>
);
}
- // Wallet POSTed the withdrawal details! Ask the
- // user to authorize the operation (here CAPTCHA).
return (
<WithdrawalConfirmationQuestion
withdrawUri={withdrawUri}
- onConfirmed={() => {
- notifyInfo(i18n.str`Operation confirmed`);
- onConfirmed();
- }}
onAborted={() => {
notifyInfo(i18n.str`Operation canceled`);
- onAborted();
- }}
+ clearCurrentWithdrawal()
+ onContinue()
+ }}
/>
);
-}
+} \ No newline at end of file
diff --git a/packages/demobank-ui/src/scss/bank.scss b/packages/demobank-ui/src/scss/bank.scss
index 0089b9734..f8de0a984 100644
--- a/packages/demobank-ui/src/scss/bank.scss
+++ b/packages/demobank-ui/src/scss/bank.scss
@@ -314,4 +314,40 @@ h1.nav {
[name=wire-transfer-form] > input {
margin-bottom: 1em;
-} \ No newline at end of file
+}
+
+.lds-ring {
+ display: inline-block;
+ position: relative;
+ width: 80px;
+ height: 80px;
+}
+.lds-ring div {
+ box-sizing: border-box;
+ display: block;
+ position: absolute;
+ width: 64px;
+ height: 64px;
+ margin: 8px;
+ border: 8px solid black;
+ border-radius: 50%;
+ animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
+ border-color: black transparent transparent transparent;
+}
+.lds-ring div:nth-child(1) {
+ animation-delay: -0.45s;
+}
+.lds-ring div:nth-child(2) {
+ animation-delay: -0.3s;
+}
+.lds-ring div:nth-child(3) {
+ animation-delay: -0.15s;
+}
+@keyframes lds-ring {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/packages/demobank-ui/src/utils.ts b/packages/demobank-ui/src/utils.ts
index e60ba7f3b..4ce0f140e 100644
--- a/packages/demobank-ui/src/utils.ts
+++ b/packages/demobank-ui/src/utils.ts
@@ -134,7 +134,7 @@ export function buildRequestErrorMessage(
specialCases.onClientError && specialCases.onClientError(cause.status);
result = {
title: title ? title : i18n.str`The server didn't accept the request`,
- description: cause.payload.error.description,
+ description: cause?.payload?.error?.description,
debug: JSON.stringify(cause),
};
break;
@@ -146,7 +146,7 @@ export function buildRequestErrorMessage(
title: title
? title
: i18n.str`The server had problems processing the request`,
- description: cause.payload.error.description,
+ description: cause?.payload?.error?.description,
debug: JSON.stringify(cause),
};
break;
@@ -154,7 +154,7 @@ export function buildRequestErrorMessage(
case ErrorType.UNREADABLE: {
result = {
title: i18n.str`Unexpected error`,
- description: `Response from ${cause.info?.url} is unreadable, status: ${cause.status}`,
+ description: `Response from ${cause?.info?.url} is unreadable, status: ${cause?.status}`,
debug: JSON.stringify(cause),
};
break;
diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx
index 23510c456..f6a81ff8d 100644
--- a/packages/merchant-backoffice-ui/src/Application.tsx
+++ b/packages/merchant-backoffice-ui/src/Application.tsx
@@ -26,7 +26,7 @@ import {
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { route } from "preact-router";
-import { useMemo } from "preact/hooks";
+import { useMemo, useState } from "preact/hooks";
import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes.js";
import { Loading } from "./components/exception/loading.js";
import {
@@ -42,6 +42,7 @@ import { useBackendConfig } from "./hooks/backend.js";
import { strings } from "./i18n/strings.js";
import LoginPage from "./paths/login/index.js";
import { HttpStatusCode } from "@gnu-taler/taler-util";
+import { Settings } from "./paths/settings/index.js";
export function Application(): VNode {
return (
@@ -70,10 +71,19 @@ function ApplicationStatusRoutes(): VNode {
: { currency: "unknown", version: "unknown" };
const ctx = useMemo(() => ({ currency, version }), [currency, version]);
+ const [showSettings, setShowSettings] = useState(false)
+
+ if (showSettings) {
+ return <Fragment>
+ <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="UI Settings" />
+ <Settings />
+ </Fragment>
+ }
+
if (!triedToLog) {
return (
<Fragment>
- <NotYetReadyAppMenu title="Welcome!" />
+ <NotYetReadyAppMenu title="Welcome!" onShowSettings={() => setShowSettings(true)} />
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
</Fragment>
);
@@ -87,7 +97,7 @@ function ApplicationStatusRoutes(): VNode {
) {
return (
<Fragment>
- <NotYetReadyAppMenu title="Login" />
+ <NotYetReadyAppMenu title="Login" onShowSettings={() => setShowSettings(true)} />
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
</Fragment>
);
@@ -98,7 +108,7 @@ function ApplicationStatusRoutes(): VNode {
) {
return (
<Fragment>
- <NotYetReadyAppMenu title="Error" />
+ <NotYetReadyAppMenu title="Error" onShowSettings={() => setShowSettings(true)} />
<NotificationCard
notification={{
message: i18n.str`Server not found`,
@@ -112,7 +122,7 @@ function ApplicationStatusRoutes(): VNode {
}
if (result.type === ErrorType.SERVER) {
<Fragment>
- <NotYetReadyAppMenu title="Error" />
+ <NotYetReadyAppMenu title="Error" onShowSettings={() => setShowSettings(true)} />
<NotificationCard
notification={{
message: i18n.str`Server response with an error code`,
@@ -125,7 +135,7 @@ function ApplicationStatusRoutes(): VNode {
}
if (result.type === ErrorType.UNREADABLE) {
<Fragment>
- <NotYetReadyAppMenu title="Error" />
+ <NotYetReadyAppMenu title="Error" onShowSettings={() => setShowSettings(true)} />
<NotificationCard
notification={{
message: i18n.str`Response from server is unreadable, http status: ${result.status}`,
@@ -138,7 +148,7 @@ function ApplicationStatusRoutes(): VNode {
}
return (
<Fragment>
- <NotYetReadyAppMenu title="Error" />
+ <NotYetReadyAppMenu title="Error" onShowSettings={() => setShowSettings(true)} />
<NotificationCard
notification={{
message: i18n.str`Unexpected Error`,
diff --git a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
index 7731dac88..277c2b176 100644
--- a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
@@ -33,6 +33,7 @@ import { InstanceRoutes } from "./InstanceRoutes.js";
import LoginPage from "./paths/login/index.js";
import { INSTANCE_ID_LOOKUP } from "./utils/constants.js";
import { HttpStatusCode } from "@gnu-taler/taler-util";
+import { Settings } from "./paths/settings/index.js";
export function ApplicationReadyRoutes(): VNode {
const { i18n } = useTranslationContext();
@@ -48,8 +49,15 @@ export function ApplicationReadyRoutes(): VNode {
clearAllTokens();
route("/");
};
+ const [showSettings, setShowSettings] = useState(false)
- if (result.loading) return <NotYetReadyAppMenu title="Loading..." />;
+ if (showSettings) {
+ return <Fragment>
+ <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="UI Settings" onLogout={clearTokenAndGoToRoot} />
+ <Settings/>
+ </Fragment>
+ }
+ if (result.loading) return <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Loading..." />;
let admin = true;
let instanceNameByBackendURL;
@@ -61,7 +69,7 @@ export function ApplicationReadyRoutes(): VNode {
) {
return (
<Fragment>
- <NotYetReadyAppMenu title="Login" onLogout={clearTokenAndGoToRoot} />
+ <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Login" onLogout={clearTokenAndGoToRoot} />
<NotificationCard
notification={{
message: i18n.str`Access denied`,
@@ -81,7 +89,7 @@ export function ApplicationReadyRoutes(): VNode {
// does not match our pattern
return (
<Fragment>
- <NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} />
+ <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Error" onLogout={clearTokenAndGoToRoot} />
<NotificationCard
notification={{
message: i18n.str`Couldn't access the server.`,
diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
index cb4abdd40..1547442ea 100644
--- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
@@ -68,6 +68,7 @@ import LoginPage from "./paths/login/index.js";
import NotFoundPage from "./paths/notfound/index.js";
import { Notification } from "./utils/types.js";
import { MerchantBackend } from "./declaration.js";
+import { Settings } from "./paths/settings/index.js";
export enum InstancePaths {
// details = '/',
@@ -100,6 +101,8 @@ export enum InstancePaths {
webhooks_list = "/webhooks",
webhooks_update = "/webhooks/:tid/update",
webhooks_new = "/webhooks/new",
+
+ settings = "/settings",
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -240,6 +243,9 @@ export function InstanceRoutes({
<Menu
instance={id}
admin={admin}
+ onShowSettings={() => {
+ route("/settings")
+ }}
path={path}
onLogout={clearTokenAndGoToRoot}
setInstanceName={setInstanceName}
@@ -558,6 +564,7 @@ export function InstanceRoutes({
}}
/>
<Route path={InstancePaths.kyc} component={ListKYCPage} />
+ <Route path={InstancePaths.settings} component={Settings} />
{/**
* Example pages
*/}
diff --git a/packages/merchant-backoffice-ui/src/components/exception/login.tsx b/packages/merchant-backoffice-ui/src/components/exception/login.tsx
index 42c5e89d0..f2f94a7c5 100644
--- a/packages/merchant-backoffice-ui/src/components/exception/login.tsx
+++ b/packages/merchant-backoffice-ui/src/components/exception/login.tsx
@@ -20,7 +20,7 @@
*/
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { h, VNode } from "preact";
+import { ComponentChildren, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { useBackendContext } from "../../context/backend.js";
import { useInstanceContext } from "../../context/instance.js";
@@ -40,7 +40,7 @@ function getTokenValuePart(t: string): string {
}
function normalizeToken(r: string): string {
- return `secret-token:${encodeURIComponent(r)}`;
+ return `secret-token:${r}`;
}
function cleanUp(s: string): string {
@@ -53,7 +53,7 @@ function cleanUp(s: string): string {
export function LoginModal({ onConfirm, withMessage }: Props): VNode {
const { url: backendUrl, token: baseToken } = useBackendContext();
- const { admin, token: instanceToken } = useInstanceContext();
+ const { admin, token: instanceToken, id } = useInstanceContext();
const testLogin = useCredentialsChecker();
const currentToken = getTokenValuePart(
(!admin ? baseToken : instanceToken) ?? "",
@@ -63,6 +63,78 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
const [url, setURL] = useState(cleanUp(backendUrl));
const { i18n } = useTranslationContext();
+ if (admin && id !== "default") {
+ //admin trying to access another instance
+ return (<div class="columns is-centered" style={{ margin: "auto" }}>
+ <div class="column is-two-thirds ">
+ <div class="modal-card" style={{ width: "100%", margin: 0 }}>
+ <header
+ class="modal-card-head"
+ style={{ border: "1px solid", borderBottom: 0 }}
+ >
+ <p class="modal-card-title">{i18n.str`Login required`}</p>
+ </header>
+ <section
+ class="modal-card-body"
+ style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }}
+ >
+ <p>
+ <i18n.Translate>Need the access token for the instance.</i18n.Translate>
+ </p>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">
+ <i18n.Translate>Access Token</i18n.Translate>
+ </label>
+ </div>
+ <div class="field-body">
+ <div class="field">
+ <p class="control is-expanded">
+ <input
+ class="input"
+ type="password"
+ placeholder={"set new access token"}
+ name="token"
+ onKeyPress={(e) =>
+ e.keyCode === 13
+ ? onConfirm(url, normalizeToken(token))
+ : null
+ }
+ value={token}
+ onInput={(e): void => setToken(e?.currentTarget.value)}
+ />
+ </p>
+ </div>
+ </div>
+ </div>
+ </section>
+ <footer
+ class="modal-card-foot "
+ style={{
+ justifyContent: "flex-end",
+ border: "1px solid",
+ borderTop: 0,
+ }}
+ >
+ <AsyncButton
+ onClick={async () => {
+ const secretToken = normalizeToken(token);
+ const { valid, cause } = await testLogin(`${url}/instances/${id}`, secretToken);
+ if (valid) {
+ onConfirm(url, secretToken);
+ } else {
+ onConfirm(url);
+ }
+ }}
+ >
+ <i18n.Translate>Confirm</i18n.Translate>
+ </AsyncButton>
+ </footer>
+ </div>
+ </div>
+ </div>)
+ }
+
return (
<div class="columns is-centered" style={{ margin: "auto" }}>
<div class="column is-two-thirds ">
@@ -137,8 +209,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
borderTop: 0,
}}
>
- <button
- class="button is-info"
+ <AsyncButton
onClick={async () => {
const secretToken = normalizeToken(token);
const { valid, cause } = await testLogin(url, secretToken);
@@ -150,10 +221,24 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
}}
>
<i18n.Translate>Confirm</i18n.Translate>
- </button>
+ </AsyncButton>
</footer>
</div>
</div>
</div>
);
}
+
+function AsyncButton({ onClick, children }: { onClick: () => Promise<void>, children: ComponentChildren }): VNode {
+ const [running, setRunning] = useState(false)
+ return <button class="button is-info" disabled={running} onClick={() => {
+ setRunning(true)
+ onClick().then(() => {
+ setRunning(false)
+ }).catch(() => {
+ setRunning(false)
+ })
+ }}>
+ {children}
+ </button>
+}
diff --git a/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx b/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx
new file mode 100644
index 000000000..61ddf3c84
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx
@@ -0,0 +1,91 @@
+/*
+ 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 { h, VNode } from "preact";
+import { InputProps, useField } from "./useField.js";
+
+interface Props<T> extends InputProps<T> {
+ name: T;
+ readonly?: boolean;
+ expand?: boolean;
+ threeState?: boolean;
+ toBoolean?: (v?: any) => boolean | undefined;
+ fromBoolean?: (s: boolean | undefined) => any;
+}
+
+const defaultToBoolean = (f?: any): boolean | undefined => f || "";
+const defaultFromBoolean = (v: boolean | undefined): any => v as any;
+
+export function InputToggle<T>({
+ name,
+ readonly,
+ placeholder,
+ tooltip,
+ label,
+ help,
+ threeState,
+ expand,
+ fromBoolean = defaultFromBoolean,
+ toBoolean = defaultToBoolean,
+}: Props<keyof T>): VNode {
+ const { error, value, onChange } = useField<T>(name);
+
+ const onCheckboxClick = (): void => {
+ const c = toBoolean(value);
+ if (c === false && threeState) return onChange(undefined as any);
+ return onChange(fromBoolean(!c));
+ };
+
+ return (
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label" style={{ width: 200 }}>
+ {label}
+ {tooltip && (
+ <span class="icon has-tooltip-right" data-tooltip={tooltip}>
+ <i class="mdi mdi-information" />
+ </span>
+ )}
+ </label>
+ </div>
+ <div class="field-body is-flex-grow-1">
+ <div class="field">
+ <p class={expand ? "control is-expanded" : "control"}>
+ <label class="toggle" style={{ marginLeft: 4, marginTop: 0 }}>
+ <input
+ type="checkbox"
+ class={toBoolean(value) === undefined ? "is-indeterminate" : "toggle-checkbox"}
+ checked={toBoolean(value)}
+ placeholder={placeholder}
+ readonly={readonly}
+ name={String(name)}
+ disabled={readonly}
+ onChange={onCheckboxClick}
+ />
+ <div class="toggle-switch"></div>
+ </label>
+ {help}
+ </p>
+ {error && <p class="help is-danger">{error}</p>}
+ </div>
+ </div>
+ </div>
+ );
+}
diff --git a/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx
index 9624a2c38..9f1b33893 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx
@@ -20,7 +20,6 @@
*/
import { h, VNode } from "preact";
-import { LangSelector } from "./LangSelector.js";
import logo from "../../assets/logo-2021.svg";
interface Props {
@@ -65,7 +64,6 @@ export function NavigationBar({ onMobileMenu, title }: Props): VNode {
</a>
<div class="navbar-end">
<div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}>
- <LangSelector />
</div>
</div>
</div>
diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
index 6fee600eb..f3cf80b92 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
@@ -31,6 +31,7 @@ const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
interface Props {
onLogout: () => void;
+ onShowSettings: () => void;
mobile?: boolean;
instance: string;
admin?: boolean;
@@ -40,6 +41,7 @@ interface Props {
export function Sidebar({
mobile,
instance,
+ onShowSettings,
onLogout,
admin,
mimic,
@@ -78,21 +80,8 @@ export function Sidebar({
<div class="menu is-menu-main">
{instance ? (
<Fragment>
- <p class="menu-label">
- <i18n.Translate>Instance</i18n.Translate>
- </p>
<ul class="menu-list">
- <li>
- <a href={"/update"} class="has-icon">
- <span class="icon">
- <i class="mdi mdi-square-edit-outline" />
- </span>
- <span class="menu-item-label">
- <i18n.Translate>Settings</i18n.Translate>
- </span>
- </a>
- </li>
- <li>
+ <li>
<a href={"/orders"} class="has-icon">
<span class="icon">
<i class="mdi mdi-cash-register" />
@@ -132,6 +121,31 @@ export function Sidebar({
</span>
</a>
</li>
+ {needKYC && (
+ <li>
+ <a href={"/kyc"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-account-check" />
+ </span>
+ <span class="menu-item-label">KYC Status</span>
+ </a>
+ </li>
+ )}
+ </ul>
+ <p class="menu-label">
+ <i18n.Translate>Configuration</i18n.Translate>
+ </p>
+ <ul class="menu-list">
+ <li>
+ <a href={"/update"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Account</i18n.Translate>
+ </span>
+ </a>
+ </li>
<li>
<a href={"/reserves"} class="has-icon">
<span class="icon">
@@ -150,16 +164,6 @@ export function Sidebar({
</span>
</a>
</li>
- {needKYC && (
- <li>
- <a href={"/kyc"} class="has-icon">
- <span class="icon">
- <i class="mdi mdi-account-check" />
- </span>
- <span class="menu-item-label">KYC Status</span>
- </a>
- </li>
- )}
</ul>
</Fragment>
) : undefined}
@@ -168,6 +172,18 @@ export function Sidebar({
</p>
<ul class="menu-list">
<li>
+ <a class="has-icon is-state-info is-hoverable"
+ onClick={(): void => onShowSettings()}
+ >
+ <span class="icon">
+ <i class="mdi mdi-newspaper" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Settings</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
<div>
<span style={{ width: "3rem" }} class="icon">
<i class="mdi mdi-currency-eur" />
diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx b/packages/merchant-backoffice-ui/src/components/menu/index.tsx
index 56573b8ca..cdbae4ae0 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/index.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/index.tsx
@@ -75,6 +75,7 @@ interface MenuProps {
instance: string;
admin?: boolean;
onLogout?: () => void;
+ onShowSettings: () => void;
setInstanceName: (s: string) => void;
}
@@ -93,6 +94,7 @@ function WithTitle({
export function Menu({
onLogout,
+ onShowSettings,
title,
instance,
path,
@@ -121,6 +123,7 @@ export function Menu({
{onLogout && (
<Sidebar
+ onShowSettings={onShowSettings}
onLogout={onLogout}
admin={admin}
mimic={mimic}
@@ -130,7 +133,12 @@ export function Menu({
)}
{mimic && (
- <nav class="level">
+ <nav class="level" style={{
+ zIndex: 100,
+ position:"fixed",
+ width:"50%",
+ marginLeft: "20%"
+ }}>
<div class="level-item has-text-centered has-background-warning">
<p class="is-size-5">
You are viewing the instance <b>&quot;{instance}&quot;</b>.{" "}
@@ -154,6 +162,7 @@ export function Menu({
interface NotYetReadyAppMenuProps {
title: string;
onLogout?: () => void;
+ onShowSettings: () => void;
}
interface NotifProps {
@@ -194,6 +203,7 @@ export function NotificationCard({
export function NotYetReadyAppMenu({
onLogout,
+ onShowSettings,
title,
}: NotYetReadyAppMenuProps): VNode {
const [mobileOpen, setMobileOpen] = useState(false);
@@ -212,7 +222,7 @@ export function NotYetReadyAppMenu({
title={title}
/>
{onLogout && (
- <Sidebar onLogout={onLogout} instance="" mobile={mobileOpen} />
+ <Sidebar onShowSettings={onShowSettings} onLogout={onLogout} instance="" mobile={mobileOpen} />
)}
</div>
);
diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts b/packages/merchant-backoffice-ui/src/hooks/backend.ts
index 90fd320a9..145a366f6 100644
--- a/packages/merchant-backoffice-ui/src/hooks/backend.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts
@@ -239,16 +239,16 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType {
searchDate?: Date,
delta?: number,
): Promise<HttpResponseOk<T>> {
- const date_ms =
+ const date_s =
delta && delta < 0 && searchDate
- ? searchDate.getTime() + 1
- : searchDate?.getTime();
+ ? (searchDate.getTime() / 1000) + 1
+ : searchDate !== undefined ? (searchDate.getTime() / 1000) : undefined;
const params: any = {};
if (paid !== undefined) params.paid = paid;
if (delta !== undefined) params.delta = delta;
if (refunded !== undefined) params.refunded = refunded;
if (wired !== undefined) params.wired = wired;
- if (date_ms !== undefined) params.date_ms = date_ms;
+ if (date_s !== undefined) params.date_s = date_s;
return requestHandler<T>(baseUrl, endpoint, { params, token });
},
[baseUrl, token],
diff --git a/packages/merchant-backoffice-ui/src/hooks/index.ts b/packages/merchant-backoffice-ui/src/hooks/index.ts
index 316620cf7..b77b9dea8 100644
--- a/packages/merchant-backoffice-ui/src/hooks/index.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/index.ts
@@ -21,6 +21,7 @@
import { StateUpdater, useCallback, useState } from "preact/hooks";
import { ValueOrFunction } from "../utils/types.js";
+import { useMemoryStorage } from "@gnu-taler/web-util/browser";
const calculateRootPath = () => {
const rootPath =
@@ -52,14 +53,17 @@ export function useBackendURL(
export function useBackendDefaultToken(
initialValue?: string,
-): [string | undefined, StateUpdater<string | undefined>] {
- return useLocalStorage("backend-token", initialValue);
+): [string | undefined, ((d: string | undefined) => void)] {
+ // uncomment for testing
+ initialValue = "secret-token:secret" as string | undefined
+ const { update, value } = useMemoryStorage(`backend-token`, initialValue)
+ return [value, update];
}
export function useBackendInstanceToken(
id: string,
-): [string | undefined, StateUpdater<string | undefined>] {
- const [token, setToken] = useLocalStorage(`backend-token-${id}`);
+): [string | undefined, ((d: string | undefined) => void)] {
+ const { update: setToken, value: token, reset } = useMemoryStorage(`backend-token-${id}`)
const [defaultToken, defaultSetToken] = useBackendDefaultToken();
// instance named 'default' use the default token
@@ -67,15 +71,16 @@ export function useBackendInstanceToken(
return [defaultToken, defaultSetToken];
}
function updateToken(
- value:
- | (string | undefined)
- | ((s: string | undefined) => string | undefined),
+ value: (string | undefined)
): void {
- setToken((p) => {
- const toStore = value instanceof Function ? value(p) : value;
- return toStore;
- });
+ console.log("seeting token", value)
+ if (value === undefined) {
+ reset()
+ } else {
+ setToken(value)
+ }
}
+ console.log("token", token)
return [token, updateToken];
}
diff --git a/packages/merchant-backoffice-ui/src/hooks/useSettings.ts b/packages/merchant-backoffice-ui/src/hooks/useSettings.ts
new file mode 100644
index 000000000..5c0932f27
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/useSettings.ts
@@ -0,0 +1,59 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
+import {
+ Codec,
+ buildCodecForObject,
+ codecForBoolean,
+} from "@gnu-taler/taler-util";
+
+function parse_json_or_undefined<T>(str: string | undefined): T | undefined {
+ if (str === undefined) return undefined;
+ try {
+ return JSON.parse(str);
+ } catch {
+ return undefined;
+ }
+}
+
+export interface Settings {
+ advanceOrderMode: boolean
+}
+
+const defaultSettings: Settings = {
+ advanceOrderMode: false,
+}
+
+export const codecForSettings = (): Codec<Settings> =>
+ buildCodecForObject<Settings>()
+ .property("advanceOrderMode", codecForBoolean())
+ .build("Settings");
+
+const SETTINGS_KEY = buildStorageKey("merchant-settings", codecForSettings());
+
+export function useSettings(): [
+ Readonly<Settings>,
+ <T extends keyof Settings>(key: T, value: Settings[T]) => void,
+] {
+ const { value, update } = useLocalStorage(SETTINGS_KEY);
+
+ const parsed: Settings = value ?? defaultSettings;
+ function updateField<T extends keyof Settings>(k: T, v: Settings[T]) {
+ update({ ...parsed, [k]: v });
+ }
+ return [parsed, updateField];
+}
diff --git a/packages/merchant-backoffice-ui/src/i18n/de.po b/packages/merchant-backoffice-ui/src/i18n/de.po
index d8d0bae29..19f8f283b 100644
--- a/packages/merchant-backoffice-ui/src/i18n/de.po
+++ b/packages/merchant-backoffice-ui/src/i18n/de.po
@@ -12,20 +12,21 @@
# You should have received a copy of the GNU General Public License along with
# TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
#
-#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Taler Wallet\n"
"Report-Msgid-Bugs-To: taler@gnu.org\n"
"POT-Creation-Date: 2016-11-23 00:00+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"Language: \n"
+"PO-Revision-Date: 2023-08-15 07:28+0000\n"
+"Last-Translator: Stefan Kügel <skuegel@web.de>\n"
+"Language-Team: German <https://weblate.taler.net/projects/gnu-taler/"
+"merchant-backoffice/de/>\n"
+"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.13.1\n"
#: src/components/modal/index.tsx:71
#, c-format
@@ -1252,7 +1253,7 @@ msgstr ""
#: src/paths/instance/orders/list/ListPage.tsx:145
#, c-format
msgid "Refunded"
-msgstr ""
+msgstr "Rückerstattet"
#: src/paths/instance/orders/list/ListPage.tsx:152
#, c-format
diff --git a/packages/merchant-backoffice-ui/src/i18n/es.po b/packages/merchant-backoffice-ui/src/i18n/es.po
index 8571ef17b..10ec0cf3b 100644
--- a/packages/merchant-backoffice-ui/src/i18n/es.po
+++ b/packages/merchant-backoffice-ui/src/i18n/es.po
@@ -17,8 +17,8 @@ msgstr ""
"Project-Id-Version: Taler Wallet\n"
"Report-Msgid-Bugs-To: taler@gnu.org\n"
"POT-Creation-Date: 2016-11-23 00:00+0100\n"
-"PO-Revision-Date: 2023-04-24 06:43+0000\n"
-"Last-Translator: Stefan Kügel <skuegel@web.de>\n"
+"PO-Revision-Date: 2023-08-13 10:14+0000\n"
+"Last-Translator: Javier Sepulveda <javier.sepulveda@uv.es>\n"
"Language-Team: Spanish <https://weblate.taler.net/projects/gnu-taler/"
"merchant-backoffice/es/>\n"
"Language: es\n"
@@ -1273,7 +1273,7 @@ msgstr "No se pudo create el reembolso"
#: src/paths/instance/orders/list/ListPage.tsx:145
#, c-format
msgid "Refunded"
-msgstr "Reembolzado"
+msgstr "Reembolsado"
#: src/paths/instance/orders/list/ListPage.tsx:152
#, c-format
diff --git a/packages/merchant-backoffice-ui/src/i18n/it.po b/packages/merchant-backoffice-ui/src/i18n/it.po
index d8d0bae29..05f1e7002 100644
--- a/packages/merchant-backoffice-ui/src/i18n/it.po
+++ b/packages/merchant-backoffice-ui/src/i18n/it.po
@@ -12,20 +12,21 @@
# You should have received a copy of the GNU General Public License along with
# TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
#
-#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Taler Wallet\n"
"Report-Msgid-Bugs-To: taler@gnu.org\n"
"POT-Creation-Date: 2016-11-23 00:00+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"Language: \n"
+"PO-Revision-Date: 2023-08-15 07:28+0000\n"
+"Last-Translator: Krystian Baran <kiszkot@murena.io>\n"
+"Language-Team: Italian <https://weblate.taler.net/projects/gnu-taler/"
+"merchant-backoffice/it/>\n"
+"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.13.1\n"
#: src/components/modal/index.tsx:71
#, c-format
@@ -439,7 +440,7 @@ msgstr ""
#: src/components/form/InputTaxes.tsx:119
#, c-format
msgid "Amount"
-msgstr ""
+msgstr "Importo"
#: src/components/form/InputTaxes.tsx:120
#, c-format
@@ -887,7 +888,7 @@ msgstr ""
#: src/paths/instance/orders/list/Table.tsx:154
#, c-format
msgid "Date"
-msgstr ""
+msgstr "Data"
#: src/paths/instance/orders/list/Table.tsx:200
#, c-format
@@ -1252,7 +1253,7 @@ msgstr ""
#: src/paths/instance/orders/list/ListPage.tsx:145
#, c-format
msgid "Refunded"
-msgstr ""
+msgstr "Rimborsato"
#: src/paths/instance/orders/list/ListPage.tsx:152
#, c-format
@@ -1633,7 +1634,7 @@ msgstr ""
#: src/paths/instance/reserves/details/DetailPage.tsx:119
#, c-format
msgid "Subject"
-msgstr ""
+msgstr "Soggetto"
#: src/paths/instance/reserves/details/DetailPage.tsx:130
#, c-format
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
index c8cc20ae0..fa9347c6e 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
@@ -43,6 +43,7 @@ import { Duration, MerchantBackend, WithId } from "../../../../declaration.js";
import { OrderCreateSchema as schema } from "../../../../schemas/index.js";
import { rate } from "../../../../utils/amount.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
+import { useSettings } from "../../../../hooks/useSettings.js";
interface Props {
onCreate: (d: MerchantBackend.Orders.PostOrderRequest) => void;
@@ -62,8 +63,8 @@ function with_defaults(config: InstanceConfig): Partial<Entity> {
!config.default_pay_delay || config.default_pay_delay.d_us === "forever"
? undefined
: add(new Date(), {
- seconds: config.default_pay_delay.d_us / (1000 * 1000),
- });
+ seconds: config.default_pay_delay.d_us / (1000 * 1000),
+ });
return {
inventoryProducts: {},
@@ -138,7 +139,7 @@ export function CreatePage({
const [value, valueHandler] = useState(with_defaults(instanceConfig));
const config = useConfigContext();
const zero = Amounts.zeroOfCurrency(config.currency);
-
+ const [settings] = useSettings()
const inventoryList = Object.values(value.inventoryProducts || {});
const productList = Object.values(value.products || {});
@@ -154,10 +155,10 @@ export function CreatePage({
order_price: !value.pricing?.order_price
? i18n.str`required`
: !parsedPrice
- ? i18n.str`not valid`
- : Amounts.isZero(parsedPrice)
- ? i18n.str`must be greater than 0`
- : undefined,
+ ? i18n.str`not valid`
+ : Amounts.isZero(parsedPrice)
+ ? i18n.str`must be greater than 0`
+ : undefined,
}),
extra:
value.extra && !stringIsValidJSON(value.extra)
@@ -167,47 +168,47 @@ export function CreatePage({
refund_deadline: !value.payments?.refund_deadline
? undefined
: !isFuture(value.payments.refund_deadline)
- ? i18n.str`should be in the future`
- : value.payments.pay_deadline &&
- isBefore(value.payments.refund_deadline, value.payments.pay_deadline)
- ? i18n.str`refund deadline cannot be before pay deadline`
- : value.payments.wire_transfer_deadline &&
- isBefore(
- value.payments.wire_transfer_deadline,
- value.payments.refund_deadline,
- )
- ? i18n.str`wire transfer deadline cannot be before refund deadline`
- : undefined,
+ ? i18n.str`should be in the future`
+ : value.payments.pay_deadline &&
+ isBefore(value.payments.refund_deadline, value.payments.pay_deadline)
+ ? i18n.str`refund deadline cannot be before pay deadline`
+ : value.payments.wire_transfer_deadline &&
+ isBefore(
+ value.payments.wire_transfer_deadline,
+ value.payments.refund_deadline,
+ )
+ ? i18n.str`wire transfer deadline cannot be before refund deadline`
+ : undefined,
pay_deadline: !value.payments?.pay_deadline
? undefined
: !isFuture(value.payments.pay_deadline)
- ? i18n.str`should be in the future`
- : value.payments.wire_transfer_deadline &&
- isBefore(
- value.payments.wire_transfer_deadline,
- value.payments.pay_deadline,
- )
- ? i18n.str`wire transfer deadline cannot be before pay deadline`
- : undefined,
+ ? i18n.str`should be in the future`
+ : value.payments.wire_transfer_deadline &&
+ isBefore(
+ value.payments.wire_transfer_deadline,
+ value.payments.pay_deadline,
+ )
+ ? i18n.str`wire transfer deadline cannot be before pay deadline`
+ : undefined,
auto_refund_deadline: !value.payments?.auto_refund_deadline
? undefined
: !isFuture(value.payments.auto_refund_deadline)
- ? i18n.str`should be in the future`
- : !value.payments?.refund_deadline
- ? i18n.str`should have a refund deadline`
- : !isAfter(
- value.payments.refund_deadline,
- value.payments.auto_refund_deadline,
- )
- ? i18n.str`auto refund cannot be after refund deadline`
- : undefined,
+ ? i18n.str`should be in the future`
+ : !value.payments?.refund_deadline
+ ? i18n.str`should have a refund deadline`
+ : !isAfter(
+ value.payments.refund_deadline,
+ value.payments.auto_refund_deadline,
+ )
+ ? i18n.str`auto refund cannot be after refund deadline`
+ : undefined,
}),
shipping: undefinedIfEmpty({
delivery_date: !value.shipping?.delivery_date
? undefined
: !isFuture(value.shipping.delivery_date)
- ? i18n.str`should be in the future`
- : undefined,
+ ? i18n.str`should be in the future`
+ : undefined,
}),
};
const hasErrors = Object.keys(errors).some(
@@ -227,27 +228,27 @@ export function CreatePage({
extra: value.extra,
pay_deadline: value.payments.pay_deadline
? {
- t_s: Math.floor(value.payments.pay_deadline.getTime() / 1000),
- }
+ t_s: Math.floor(value.payments.pay_deadline.getTime() / 1000),
+ }
: undefined,
wire_transfer_deadline: value.payments.wire_transfer_deadline
? {
- t_s: Math.floor(
- value.payments.wire_transfer_deadline.getTime() / 1000,
- ),
- }
+ t_s: Math.floor(
+ value.payments.wire_transfer_deadline.getTime() / 1000,
+ ),
+ }
: undefined,
refund_deadline: value.payments.refund_deadline
? {
- t_s: Math.floor(value.payments.refund_deadline.getTime() / 1000),
- }
+ t_s: Math.floor(value.payments.refund_deadline.getTime() / 1000),
+ }
: undefined,
auto_refund: value.payments.auto_refund_deadline
? {
- d_us: Math.floor(
- value.payments.auto_refund_deadline.getTime() * 1000,
- ),
- }
+ d_us: Math.floor(
+ value.payments.auto_refund_deadline.getTime() * 1000,
+ ),
+ }
: undefined,
wire_fee_amortization: value.payments.wire_fee_amortization as number,
max_fee: value.payments.max_fee as string,
@@ -374,13 +375,15 @@ export function CreatePage({
inventory={instanceInventory}
/>
- <NonInventoryProductFrom
- productToEdit={editingProduct}
- onAddProduct={(p) => {
- setEditingProduct(undefined);
- return addNewProduct(p);
- }}
- />
+ {settings.advanceOrderMode &&
+ <NonInventoryProductFrom
+ productToEdit={editingProduct}
+ onAddProduct={(p) => {
+ setEditingProduct(undefined);
+ return addNewProduct(p);
+ }}
+ />
+ }
{allProducts.length > 0 && (
<ProductList
@@ -423,8 +426,8 @@ export function CreatePage({
discountOrRise > 0 &&
(discountOrRise < 1
? `discount of %${Math.round(
- (1 - discountOrRise) * 100,
- )}`
+ (1 - discountOrRise) * 100,
+ )}`
: `rise of %${Math.round((discountOrRise - 1) * 100)}`)
}
tooltip={i18n.str`Amount to be paid by the customer`}
@@ -445,102 +448,108 @@ export function CreatePage({
tooltip={i18n.str`Title of the order to be shown to the customer`}
/>
- <InputGroup
- name="shipping"
- label={i18n.str`Shipping and Fulfillment`}
- initialActive
- >
- <InputDate
- name="shipping.delivery_date"
- label={i18n.str`Delivery date`}
- tooltip={i18n.str`Deadline for physical delivery assured by the merchant.`}
- />
- {value.shipping?.delivery_date && (
- <InputGroup
- name="shipping.delivery_location"
- label={i18n.str`Location`}
- tooltip={i18n.str`address where the products will be delivered`}
- >
- <InputLocation name="shipping.delivery_location" />
- </InputGroup>
- )}
- <Input
- name="shipping.fullfilment_url"
- label={i18n.str`Fulfillment URL`}
- tooltip={i18n.str`URL to which the user will be redirected after successful payment.`}
- />
- </InputGroup>
+ {settings.advanceOrderMode &&
+ <InputGroup
+ name="shipping"
+ label={i18n.str`Shipping and Fulfillment`}
+ initialActive
+ >
+ <InputDate
+ name="shipping.delivery_date"
+ label={i18n.str`Delivery date`}
+ tooltip={i18n.str`Deadline for physical delivery assured by the merchant.`}
+ />
+ {value.shipping?.delivery_date && (
+ <InputGroup
+ name="shipping.delivery_location"
+ label={i18n.str`Location`}
+ tooltip={i18n.str`address where the products will be delivered`}
+ >
+ <InputLocation name="shipping.delivery_location" />
+ </InputGroup>
+ )}
+ <Input
+ name="shipping.fullfilment_url"
+ label={i18n.str`Fulfillment URL`}
+ tooltip={i18n.str`URL to which the user will be redirected after successful payment.`}
+ />
+ </InputGroup>
+ }
- <InputGroup
- name="payments"
- label={i18n.str`Taler payment options`}
- tooltip={i18n.str`Override default Taler payment settings for this order`}
- >
- <InputDate
- name="payments.pay_deadline"
- label={i18n.str`Payment 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
- name="payments.refund_deadline"
- label={i18n.str`Refund deadline`}
- tooltip={i18n.str`Time until which the order can be refunded by the merchant.`}
- />
- <InputDate
- name="payments.wire_transfer_deadline"
- label={i18n.str`Wire transfer deadline`}
- tooltip={i18n.str`Deadline for the exchange to make the wire transfer.`}
- />
- <InputDate
- name="payments.auto_refund_deadline"
- label={i18n.str`Auto-refund deadline`}
- tooltip={i18n.str`Time until which the wallet will automatically check for refunds without user interaction.`}
- />
+ {settings.advanceOrderMode &&
+ <InputGroup
+ name="payments"
+ label={i18n.str`Taler payment options`}
+ tooltip={i18n.str`Override default Taler payment settings for this order`}
+ >
+ <InputDate
+ name="payments.pay_deadline"
+ label={i18n.str`Payment 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
+ name="payments.refund_deadline"
+ label={i18n.str`Refund deadline`}
+ tooltip={i18n.str`Time until which the order can be refunded by the merchant.`}
+ />
+ <InputDate
+ name="payments.wire_transfer_deadline"
+ label={i18n.str`Wire transfer deadline`}
+ tooltip={i18n.str`Deadline for the exchange to make the wire transfer.`}
+ />
+ <InputDate
+ name="payments.auto_refund_deadline"
+ label={i18n.str`Auto-refund deadline`}
+ tooltip={i18n.str`Time until which the wallet will automatically check for refunds without user interaction.`}
+ />
- <InputCurrency
- name="payments.max_fee"
- label={i18n.str`Maximum deposit fee`}
- 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
- name="payments.max_wire_fee"
- label={i18n.str`Maximum wire fee`}
- 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
- name="payments.wire_fee_amortization"
- label={i18n.str`Wire fee amortization`}
- 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
- name="payments.createToken"
- label={i18n.str`Create token`}
- tooltip={i18n.str`Uncheck this option if the merchant backend generated an order ID with enough entropy to prevent adversarial claims.`}
- />
- <InputNumber
- name="payments.minimum_age"
- label={i18n.str`Minimum age required`}
- 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={
- minAgeByProducts > 0
- ? i18n.str`Min age defined by the producs is ${minAgeByProducts}`
- : undefined
- }
- />
- </InputGroup>
+ <InputCurrency
+ name="payments.max_fee"
+ label={i18n.str`Maximum deposit fee`}
+ 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
+ name="payments.max_wire_fee"
+ label={i18n.str`Maximum wire fee`}
+ 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
+ name="payments.wire_fee_amortization"
+ label={i18n.str`Wire fee amortization`}
+ 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
+ name="payments.createToken"
+ label={i18n.str`Create token`}
+ tooltip={i18n.str`Uncheck this option if the merchant backend generated an order ID with enough entropy to prevent adversarial claims.`}
+ />
+ <InputNumber
+ name="payments.minimum_age"
+ label={i18n.str`Minimum age required`}
+ 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={
+ minAgeByProducts > 0
+ ? i18n.str`Min age defined by the producs is ${minAgeByProducts}`
+ : undefined
+ }
+ />
+ </InputGroup>
+ }
- <InputGroup
- name="extra"
- label={i18n.str`Additional information`}
- tooltip={i18n.str`Custom information to be included in the contract for this order.`}
- >
- <Input
+ {settings.advanceOrderMode &&
+ <InputGroup
name="extra"
- inputType="multiline"
- label={`Value`}
- tooltip={i18n.str`You must enter a value in JavaScript Object Notation (JSON).`}
- />
- </InputGroup>
+ label={i18n.str`Additional information`}
+ tooltip={i18n.str`Custom information to be included in the contract for this order.`}
+ >
+ <Input
+ name="extra"
+ inputType="multiline"
+ label={`Value`}
+ tooltip={i18n.str`You must enter a value in JavaScript Object Notation (JSON).`}
+ />
+ </InputGroup>
+ }
</FormProvider>
<div class="buttons is-right mt-5">
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
index 8dabfbe12..8965d41c9 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
@@ -21,7 +21,7 @@
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { format } from "date-fns";
+import { format, formatDistance } from "date-fns";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { FormProvider } from "../../../../components/form/FormProvider.js";
@@ -223,6 +223,7 @@ function ClaimedPage({
</div>
</div>
</div>
+
<div class="level">
<div class="level-left">
<div class="level-item">
@@ -419,6 +420,11 @@ function PaidPage({
}
}
+ const now = new Date()
+ const nextEvent = events.find((e) => {
+ return e.when.getTime() > now.getTime()
+ })
+
const [value, valueHandler] = useState<Partial<Paid>>(order);
const { url } = useBackendContext();
const refundHost = url.replace(/.*:\/\//, ""); // remove protocol part
@@ -504,22 +510,13 @@ function PaidPage({
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
- // maxWidth: '100%',
}}
>
<p>
- <a
- href={order.contract_terms.fulfillment_url}
- rel="nofollow"
- target="new"
- >
- {order.contract_terms.fulfillment_url}
- </a>
- </p>
- <p>
- {format(
- new Date(order.contract_terms.timestamp.t_s * 1000),
- "yyyy/MM/dd HH:mm:ss",
+ <i18n.Translate>Next event in </i18n.Translate> {formatDistance(
+ nextEvent!.when,
+ new Date(),
+ // "yyyy/MM/dd HH:mm:ss",
)}
</p>
</div>
@@ -668,9 +665,9 @@ function UnpaidPage({
{order.creation_time.t_s === "never"
? "never"
: format(
- new Date(order.creation_time.t_s * 1000),
- "yyyy-MM-dd HH:mm:ss",
- )}
+ new Date(order.creation_time.t_s * 1000),
+ "yyyy-MM-dd HH:mm:ss",
+ )}
</p>
</div>
</div>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx
index d73ba3acc..e68889a92 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx
@@ -67,7 +67,7 @@ export function Timeline({ events: e }: Props) {
);
case "start":
return (
- <div class="timeline-marker is-icon is-success">
+ <div class="timeline-marker is-icon">
<i class="mdi mdi-flag " />
</div>
);
@@ -104,7 +104,7 @@ export function Timeline({ events: e }: Props) {
}
})()}
<div class="timeline-content">
- <p class="heading">{format(e.when, "yyyy/MM/dd HH:mm:ss")}</p>
+ {e.description !== "now" && <p class="heading">{format(e.when, "yyyy/MM/dd HH:mm:ss")}</p>}
<p>{e.description}</p>
</div>
</div>
@@ -117,12 +117,12 @@ export interface Event {
when: Date;
description: string;
type:
- | "start"
- | "refund"
- | "refund-taken"
- | "wired"
- | "wired-range"
- | "deadline"
- | "delivery"
- | "now";
+ | "start"
+ | "refund"
+ | "refund-taken"
+ | "wired"
+ | "wired-range"
+ | "deadline"
+ | "delivery"
+ | "now";
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx
index 56d9dda74..37770d273 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx
@@ -164,7 +164,7 @@ export function ListPage({
<div class="field has-addons">
{jumpToDate && (
<div class="control">
- <a class="button" onClick={() => onSelectDate(undefined)}>
+ <a class="button is-fullwidth" onClick={() => onSelectDate(undefined)}>
<span
class="icon"
data-tooltip={i18n.str`clear date filter`}
@@ -191,7 +191,7 @@ export function ListPage({
<div class="control">
<span class="has-tooltip-left" data-tooltip={dateTooltip}>
<a
- class="button"
+ class="button is-fullwidth"
onClick={() => {
setPickDate(true);
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
index 4dde202c4..e20b9bc27 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
@@ -85,34 +85,34 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
template_contract: !state.template_contract
? undefined
: undefinedIfEmpty({
- amount: !state.template_contract?.amount
- ? undefined
- : !parsedPrice
+ amount: !state.template_contract?.amount
+ ? undefined
+ : !parsedPrice
? i18n.str`not valid`
: Amounts.isZero(parsedPrice)
- ? i18n.str`must be greater than 0`
- : undefined,
- minimum_age:
- state.template_contract.minimum_age < 0
- ? i18n.str`should be greater that 0`
+ ? i18n.str`must be greater than 0`
: undefined,
- pay_duration: !state.template_contract.pay_duration
- ? i18n.str`can't be empty`
- : state.template_contract.pay_duration.d_us === "forever"
+ minimum_age:
+ state.template_contract.minimum_age < 0
+ ? i18n.str`should be greater that 0`
+ : undefined,
+ pay_duration: !state.template_contract.pay_duration
+ ? i18n.str`can't be empty`
+ : state.template_contract.pay_duration.d_us === "forever"
? undefined
: state.template_contract.pay_duration.d_us < 1000 * 1000 //less than one second
- ? i18n.str`to short`
- : undefined,
- } as Partial<MerchantTemplateContractDetails>),
+ ? i18n.str`to short`
+ : undefined,
+ } as Partial<MerchantTemplateContractDetails>),
pos_key: !state.pos_key
? !state.pos_algorithm
? undefined
: i18n.str`required`
: !isBase32RFC3548Charset(state.pos_key)
- ? i18n.str`just letters and numbers from 2 to 7`
- : state.pos_key.length !== 32
- ? i18n.str`size of the key should be 32`
- : undefined,
+ ? i18n.str`just letters and numbers from 2 to 7`
+ : state.pos_key.length !== 32
+ ? i18n.str`size of the key should be 32`
+ : undefined,
};
const hasErrors = Object.keys(errors).some(
@@ -139,7 +139,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
>
<InputWithAddon<Entity>
name="template_id"
- addonBefore={`${backend.url}/instances/templates/`}
+ help={`${backend.url}/instances/templates/${state.template_id ?? ""}`}
label={i18n.str`Identifier`}
tooltip={i18n.str`Name of the template in URLs.`}
/>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
index 90084f113..0f30efafd 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
@@ -34,6 +34,7 @@ import { useBackendContext } from "../../../../context/backend.js";
import { useConfigContext } from "../../../../context/config.js";
import { useInstanceContext } from "../../../../context/instance.js";
import { MerchantBackend } from "../../../../declaration.js";
+import { stringifyPayTemplateUri } from "@gnu-taler/taler-util";
type Entity = MerchantBackend.Template.UsingTemplateDetails;
@@ -64,46 +65,47 @@ export function QrPage({ template, id: templateId, onBack }: Props): VNode {
const fixedAmount = !!template.template_contract.amount;
const fixedSummary = !!template.template_contract.summary;
- const params = new URLSearchParams();
+ const templateParams: Record<string, string> = {}
if (!fixedAmount) {
if (state.amount) {
- params.append("amount", state.amount);
+ templateParams.amount = state.amount
} else {
- params.append("amount", config.currency);
+ templateParams.amount = config.currency
}
}
+
if (!fixedSummary) {
- params.append("summary", state.summary ?? "");
+ templateParams.summary = state.summary ?? ""
}
- const paramsStr = fixedAmount && fixedSummary ? "" : "?" + params.toString();
- const merchantURL = new URL(backendUrl);
-
- const talerProto =
- merchantURL.protocol === "http:" ? "taler+http:" : "taler:";
+ const merchantBaseUrl = new URL(backendUrl).href;
- const payTemplateUri = `${talerProto}//pay-template/${merchantURL.hostname}/${templateId}${paramsStr}`;
+ const payTemplateUri = stringifyPayTemplateUri({
+ merchantBaseUrl,
+ templateId,
+ templateParams
+ })
const issuer = encodeURIComponent(
- `${new URL(backendUrl).hostname}/${instanceId}`,
+ `${new URL(backendUrl).host}/${instanceId}`,
);
const oauthUri = !template.pos_algorithm
? undefined
: template.pos_algorithm === 1
- ? `otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
- : template.pos_algorithm === 2
- ? `otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
- : undefined;
+ ? `otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
+ : template.pos_algorithm === 2
+ ? `otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
+ : undefined;
const keySlice = template.pos_key?.substring(0, 4);
const oauthUriWithoutSecret = !template.pos_algorithm
? undefined
: template.pos_algorithm === 1
- ? `otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
- : template.pos_algorithm === 2
- ? `otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
- : undefined;
+ ? `otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
+ : template.pos_algorithm === 2
+ ? `otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
+ : undefined;
return (
<div>
{oauthUri && (
diff --git a/packages/merchant-backoffice-ui/src/paths/notfound/index.tsx b/packages/merchant-backoffice-ui/src/paths/notfound/index.tsx
index b58948dbd..061a67025 100644
--- a/packages/merchant-backoffice-ui/src/paths/notfound/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/notfound/index.tsx
@@ -25,7 +25,6 @@ import { Link } from "preact-router";
export default function NotFoundPage(): VNode {
return (
<div>
- <h1>Error 404</h1>
<p>That page doesn&apos;t exist.</p>
<Link href="/">
<h4>Back to Home</h4>
diff --git a/packages/merchant-backoffice-ui/src/paths/settings/index.tsx b/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
new file mode 100644
index 000000000..128450553
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
@@ -0,0 +1,77 @@
+import { VNode, h } from "preact";
+import { LangSelector } from "../../components/menu/LangSelector.js";
+import { useLang, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { InputToggle } from "../../components/form/InputToggle.js";
+import { Settings, useSettings } from "../../hooks/useSettings.js";
+import { FormErrors, FormProvider } from "../../components/form/FormProvider.js";
+import { useState } from "preact/hooks";
+
+function getBrowserLang(): string | undefined {
+ if (typeof window === "undefined") return undefined;
+ if (window.navigator.languages) return window.navigator.languages[0];
+ if (window.navigator.language) return window.navigator.language;
+ return undefined;
+}
+
+export function Settings(): VNode {
+ const { i18n } = useTranslationContext()
+ const borwserLang = getBrowserLang()
+ const { update } = useLang()
+
+ const [value, updateValue] = useSettings()
+ const errors: FormErrors<Settings> = {
+ }
+
+ function valueHandler(s: (d: Partial<Settings>) => Partial<Settings>): void {
+ const next = s(value)
+ updateValue("advanceOrderMode", next.advanceOrderMode ?? false)
+ }
+
+ return <div>
+ <section class="section is-main-section">
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-four-fifths">
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label" style={{ width: 200 }}>
+ <i18n.Translate>Language</i18n.Translate>
+ <span class="icon has-tooltip-right" data-tooltip={"Force language setting instance of taking the browser"}>
+ <i class="mdi mdi-information" />
+ </span>
+ </label>
+ </div>
+ <div class="field has-addons">
+ <LangSelector />
+ &nbsp;
+ {borwserLang !== undefined && <button
+ data-tooltip={i18n.str`generate random secret key`}
+ class="button is-info mr-3"
+ onClick={(e) => {
+ update(borwserLang.substring(0, 2))
+ }}
+ >
+ <i18n.Translate>Set default</i18n.Translate>
+ </button>}
+ </div>
+ </div>
+ <FormProvider<Settings>
+ name="settings"
+ errors={errors}
+ object={value}
+ valueHandler={valueHandler}
+ >
+ <InputToggle<Settings>
+ label={i18n.str`Advance order creation`}
+ tooltip={i18n.str`Shows more options in the order creation form`}
+ name="advanceOrderMode"
+ />
+ </FormProvider>
+
+
+ </div>
+ <div class="column" />
+ </div>
+ </section>
+ </div>
+} \ No newline at end of file
diff --git a/packages/merchant-backoffice-ui/src/scss/main.scss b/packages/merchant-backoffice-ui/src/scss/main.scss
index ad698eb26..c4be8aa73 100644
--- a/packages/merchant-backoffice-ui/src/scss/main.scss
+++ b/packages/merchant-backoffice-ui/src/scss/main.scss
@@ -52,6 +52,8 @@ $tooltip-color: red;
@import "../../node_modules/@creativebulma/bulma-tooltip/dist/bulma-tooltip.min.css";
@import "../../node_modules/bulma-timeline/dist/css/bulma-timeline.min.css";
+@import "toggle";
+
.notification {
background-color: transparent;
}
@@ -82,7 +84,7 @@ $tooltip-color: red;
pointer-events: none;
}
-.toast > .message {
+.toast>.message {
white-space: pre-wrap;
opacity: 80%;
}
@@ -92,6 +94,7 @@ div {
position: relative;
pointer-events: none;
opacity: 0.5;
+
&:after {
// @include loader;
position: absolute;
@@ -104,7 +107,7 @@ div {
}
}
-input[type="checkbox"]:indeterminate + .check {
+input[type="checkbox"]:indeterminate+.check {
background: red !important;
}
@@ -125,6 +128,7 @@ input[type="checkbox"]:indeterminate + .check {
tr:hover .right-sticky {
background-color: hsl(0, 0%, 80%);
}
+
.table.is-striped tbody tr:nth-child(even):hover .right-sticky {
background-color: hsl(0, 0%, 95%);
}
@@ -181,11 +185,11 @@ div[data-tooltip]::before {
position: absolute;
}
-.modal-card-body > p {
+.modal-card-body>p {
padding: 1em;
}
-.modal-card-body > p.warning {
+.modal-card-body>p.warning {
background-color: #fffbdd;
border: solid 1px #f2e9bf;
-}
+} \ No newline at end of file
diff --git a/packages/merchant-backoffice-ui/src/scss/toggle.scss b/packages/merchant-backoffice-ui/src/scss/toggle.scss
new file mode 100644
index 000000000..24636da2f
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/scss/toggle.scss
@@ -0,0 +1,51 @@
+$green: #56c080;
+
+.toggle {
+ cursor: pointer;
+ display: inline-block;
+}
+.toggle-switch {
+ display: inline-block;
+ background: #ccc;
+ border-radius: 16px;
+ width: 58px;
+ height: 32px;
+ position: relative;
+ vertical-align: middle;
+ transition: background 0.25s;
+ &:before,
+ &:after {
+ content: "";
+ }
+ &:before {
+ display: block;
+ background: linear-gradient(to bottom, #fff 0%, #eee 100%);
+ border-radius: 50%;
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
+ width: 24px;
+ height: 24px;
+ position: absolute;
+ top: 4px;
+ left: 4px;
+ transition: left 0.25s;
+ }
+ .toggle:hover &:before {
+ background: linear-gradient(to bottom, #fff 0%, #fff 100%);
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
+ }
+ .toggle-checkbox:checked + & {
+ background: $green;
+ &:before {
+ left: 30px;
+ }
+ }
+}
+.toggle-checkbox {
+ position: absolute;
+ visibility: hidden;
+}
+.toggle-label {
+ margin-left: 5px;
+ position: relative;
+ top: 2px;
+}
diff --git a/packages/taler-harness/src/integrationtests/test-age-restrictions-peer.ts b/packages/taler-harness/src/integrationtests/test-age-restrictions-peer.ts
index d15858322..45a4391cb 100644
--- a/packages/taler-harness/src/integrationtests/test-age-restrictions-peer.ts
+++ b/packages/taler-harness/src/integrationtests/test-age-restrictions-peer.ts
@@ -26,7 +26,7 @@ import {
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { defaultCoinConfig } from "../harness/denomStructures.js";
-import { GlobalTestState, WalletCli } from "../harness/harness.js";
+import { GlobalTestState } from "../harness/harness.js";
import {
createSimpleTestkudosEnvironmentV2,
createWalletDaemonWithClient,
@@ -106,7 +106,7 @@ export async function runAgeRestrictionsPeerTest(t: GlobalTestState) {
);
await wallet2.call(WalletApiOperation.ConfirmPeerPushCredit, {
- peerPushPaymentIncomingId: checkResp.peerPushPaymentIncomingId,
+ transactionId: checkResp.transactionId,
});
const peerPullCreditDoneCond = wallet2.waitForNotificationCond(
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
index e35264d13..67572f0f7 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -338,8 +338,17 @@ export async function runTests(spec: TestRunSpec) {
currentChild.stdout?.pipe(harnessLogStream);
currentChild.stderr?.pipe(harnessLogStream);
- const defaultTimeout = 60000;
- const testTimeoutMs = testCase.timeoutMs ?? defaultTimeout;
+ // Default timeout when the test doesn't override it.
+ let defaultTimeout = 60000;
+ const overrideDefaultTimeout = process.env.TALER_TEST_TIMEOUT;
+ if (overrideDefaultTimeout) {
+ defaultTimeout = Number.parseInt(overrideDefaultTimeout, 10) * 1000;
+ }
+
+ // Set the timeout to at least be the default timeout.
+ const testTimeoutMs = testCase.timeoutMs
+ ? Math.max(testCase.timeoutMs, defaultTimeout)
+ : defaultTimeout;
if (spec.noTimeout) {
console.log(`running ${testName}, no timeout`);
diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts
index 777cb5245..fff1ca833 100644
--- a/packages/taler-util/src/taleruri.ts
+++ b/packages/taler-util/src/taleruri.ts
@@ -767,7 +767,7 @@ function getUrlInfo(
const qp = new URLSearchParams();
let withParams = false;
Object.entries(params).forEach(([name, value]) => {
- if (value) {
+ if (value !== undefined) {
withParams = true;
qp.append(name, value);
}
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index 38e5787ba..3179cd6f3 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -379,6 +379,54 @@ export interface Balance {
requiresUserInput: boolean;
}
+export const codecForScopeInfoGlobal = (): Codec<ScopeInfoGlobal> =>
+ buildCodecForObject<ScopeInfoGlobal>()
+ .property("currency", codecForString())
+ .property("type", codecForConstString(ScopeType.Global))
+ .build("ScopeInfoGlobal");
+
+export const codecForScopeInfoExchange = (): Codec<ScopeInfoExchange> =>
+ buildCodecForObject<ScopeInfoExchange>()
+ .property("currency", codecForString())
+ .property("type", codecForConstString(ScopeType.Exchange))
+ .property("url", codecForString())
+ .build("ScopeInfoExchange");
+
+export const codecForScopeInfoAuditor = (): Codec<ScopeInfoAuditor> =>
+ buildCodecForObject<ScopeInfoAuditor>()
+ .property("currency", codecForString())
+ .property("type", codecForConstString(ScopeType.Auditor))
+ .property("url", codecForString())
+ .build("ScopeInfoAuditor");
+
+export const codecForScopeInfo = (): Codec<ScopeInfo> =>
+ buildCodecForUnion<ScopeInfo>()
+ .discriminateOn("type")
+ .alternative(ScopeType.Global, codecForScopeInfoGlobal())
+ .alternative(ScopeType.Exchange, codecForScopeInfoExchange())
+ .alternative(ScopeType.Auditor, codecForScopeInfoAuditor())
+ .build("ScopeInfo");
+
+export interface GetCurrencyInfoRequest {
+ scope: ScopeInfo;
+}
+
+export const codecForGetCurrencyInfoRequest =
+ (): Codec<GetCurrencyInfoRequest> =>
+ buildCodecForObject<GetCurrencyInfoRequest>()
+ .property("scope", codecForScopeInfo())
+ .build("GetCurrencyInfoRequest");
+
+export interface GetCurrencyInfoResponse {
+ decimalSeparator: string;
+ numFractionalDigits: number;
+ numTinyDigits: number;
+ /**
+ * Is the currency name leading or trailing?
+ */
+ isCurrencyNameLeading: boolean;
+}
+
export interface InitRequest {
skipDefaults?: boolean;
}
@@ -393,10 +441,19 @@ export enum ScopeType {
Auditor = "auditor",
}
-export type ScopeInfo =
- | { type: ScopeType.Global; currency: string }
- | { type: ScopeType.Exchange; currency: string; url: string }
- | { type: ScopeType.Auditor; currency: string; url: string };
+export type ScopeInfoGlobal = { type: ScopeType.Global; currency: string };
+export type ScopeInfoExchange = {
+ type: ScopeType.Exchange;
+ currency: string;
+ url: string;
+};
+export type ScopeInfoAuditor = {
+ type: ScopeType.Auditor;
+ currency: string;
+ url: string;
+};
+
+export type ScopeInfo = ScopeInfoGlobal | ScopeInfoExchange | ScopeInfoAuditor;
export interface BalancesResponse {
balances: Balance[];
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 3d2878d93..c7d0b0bda 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -711,7 +711,10 @@ export interface RewardCoinSource {
coinIndex: number;
}
-export type CoinSource = WithdrawCoinSource | RefreshCoinSource | RewardCoinSource;
+export type CoinSource =
+ | WithdrawCoinSource
+ | RefreshCoinSource
+ | RewardCoinSource;
/**
* CoinRecord as stored in the "coins" data store
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index eaa99a6c3..36c4809af 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -114,6 +114,8 @@ import {
WithdrawUriInfoResponse,
SharePaymentRequest,
SharePaymentResult,
+ GetCurrencyInfoRequest,
+ GetCurrencyInfoResponse,
} from "@gnu-taler/taler-util";
import { AuditorTrustRecord, WalletContractData } from "./db.js";
import {
@@ -210,6 +212,7 @@ export enum WalletApiOperation {
ApplyDevExperiment = "applyDevExperiment",
ValidateIban = "validateIban",
TestingWaitTransactionsFinal = "testingWaitTransactionsFinal",
+ GetScopedCurrencyInfo = "getScopedCurrencyInfo",
}
// group: Initialization
@@ -601,6 +604,12 @@ export type ListCurrenciesOp = {
response: WalletCurrencyInfo;
};
+export type GetScopedCurrencyInfoOp = {
+ op: WalletApiOperation.GetScopedCurrencyInfo;
+ request: GetCurrencyInfoRequest;
+ response: GetCurrencyInfoResponse;
+};
+
// group: Deposits
/**
@@ -1072,6 +1081,7 @@ export type WalletOperations = {
[WalletApiOperation.ApplyDevExperiment]: ApplyDevExperimentOp;
[WalletApiOperation.ValidateIban]: ValidateIbanOp;
[WalletApiOperation.TestingWaitTransactionsFinal]: TestingWaitTransactionsFinal;
+ [WalletApiOperation.GetScopedCurrencyInfo]: GetScopedCurrencyInfoOp;
};
export type WalletCoreRequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index aab414e94..796a96f14 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -118,6 +118,8 @@ import {
sampleWalletCoreTransactions,
validateIban,
codecForSharePaymentRequest,
+ GetCurrencyInfoResponse,
+ codecForGetCurrencyInfoRequest,
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
@@ -1395,6 +1397,17 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
const resp = await getBackupRecovery(ws);
return resp;
}
+ case WalletApiOperation.GetScopedCurrencyInfo: {
+ // Ignore result, just validate in this mock implementation
+ codecForGetCurrencyInfoRequest().decode(payload);
+ const resp: GetCurrencyInfoResponse = {
+ decimalSeparator: ",",
+ isCurrencyNameLeading: false,
+ numFractionalDigits: 2,
+ numTinyDigits: 1,
+ };
+ return resp;
+ }
case WalletApiOperation.ImportBackupRecovery: {
const req = codecForAny().decode(payload);
await loadBackupRecovery(ws, req);
diff --git a/packages/taler-wallet-embedded/src/wallet-qjs.ts b/packages/taler-wallet-embedded/src/wallet-qjs.ts
index 04efb458a..e475f9542 100644
--- a/packages/taler-wallet-embedded/src/wallet-qjs.ts
+++ b/packages/taler-wallet-embedded/src/wallet-qjs.ts
@@ -45,11 +45,11 @@ import {
reduceAction,
getBackupStartState,
getRecoveryStartState,
+ discoverPolicies,
+ mergeDiscoveryAggregate,
ReducerState,
} from "@gnu-taler/anastasis-core";
-import {
- userIdentifierDerive,
-} from "@gnu-taler/anastasis-core/lib/crypto.js";
+import { userIdentifierDerive } from "@gnu-taler/anastasis-core/lib/crypto.js";
setGlobalLogLevelFromString("trace");
@@ -195,18 +195,33 @@ async function handleAnastasisRequest(
};
};
+ let req = args ?? {};
+
switch (operation) {
case "anastasisReduce":
// TODO: do some input validation here
- let req = args ?? {};
- let res = await reduceAction(req.state, req.action, req.args ?? {});
+ let reduceRes = await reduceAction(req.state, req.action, req.args ?? {});
// For now, this will return "success" even if the wrapped Anastasis
// response is a ReducerStateError.
- return wrapSuccessResponse(res);
+ return wrapSuccessResponse(reduceRes);
case "anastasisStartBackup":
return wrapSuccessResponse(await getBackupStartState());
case "anastasisStartRecovery":
return wrapSuccessResponse(await getRecoveryStartState());
+ case "anastasisDiscoverPolicies":
+ let discoverRes = await discoverPolicies(req.state, req.cursor);
+ let aggregatedPolicies = mergeDiscoveryAggregate(
+ discoverRes.policies ?? [],
+ req.state.discoveryState?.aggregatedPolicies ?? [],
+ );
+ return wrapSuccessResponse({
+ ...req.state,
+ discoveryState: {
+ state: "finished",
+ aggregatedPolicies,
+ cursor: discoverRes.cursor,
+ },
+ });
}
}
@@ -318,13 +333,15 @@ export async function testArgon2id() {
},
input_server_salt: "FZ48EFS7WS3R2ZR4V53A3GFFY4",
output_id:
- "YS45R6CGJV84K1NN7T14ZBCPVTZ6H15XJSM1FV0R748MHPV82SM0126EBZKBAAGCR34Q9AFKPEW1HRT2Q9GQ5JRA3642AB571DKZS18",
+ "YS45R6CGJV84K1NN7T14ZBCPVTZ6H15XJSM1FV0R748MHPV82SM0126EBZKBAAGCR34Q9AFKPEW1HRT2Q9GQ5JRA3642AB571DKZS18",
};
- if (await userIdentifierDerive(
- userIdVector.input_id_data,
- userIdVector.input_server_salt,
- ) != userIdVector.output_id) {
+ if (
+ (await userIdentifierDerive(
+ userIdVector.input_id_data,
+ userIdVector.input_server_salt,
+ )) != userIdVector.output_id
+ ) {
throw Error("argon2id is not working!");
}
@@ -337,4 +354,7 @@ globalThis.testWithGv = testWithGv;
globalThis.testWithLocal = testWithLocal;
// @ts-ignore
globalThis.testArgon2id = testArgon2id;
-
+// @ts-ignore
+globalThis.testReduceAction = reduceAction;
+// @ts-ignore
+globalThis.testDiscoverPolicies = discoverPolicies; \ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/i18n/es.po b/packages/taler-wallet-webextension/src/i18n/es.po
index ecea5db58..a482b9550 100644
--- a/packages/taler-wallet-webextension/src/i18n/es.po
+++ b/packages/taler-wallet-webextension/src/i18n/es.po
@@ -17,8 +17,8 @@ msgstr ""
"Project-Id-Version: Taler Wallet\n"
"Report-Msgid-Bugs-To: languages@taler.net\n"
"POT-Creation-Date: 2016-11-23 00:00+0100\n"
-"PO-Revision-Date: 2023-04-24 06:43+0000\n"
-"Last-Translator: José Huamán <princetomato@firemail.cc>\n"
+"PO-Revision-Date: 2023-08-13 10:14+0000\n"
+"Last-Translator: Javier Sepulveda <javier.sepulveda@uv.es>\n"
"Language-Team: Spanish <https://weblate.taler.net/projects/gnu-taler/"
"webextensions/es/>\n"
"Language: es\n"
@@ -828,7 +828,7 @@ msgstr "Precio"
#: src/wallet/Transaction.tsx:1156
#, c-format
msgid "Refunded"
-msgstr "Reembolzado"
+msgstr "Reembolsado"
#: src/wallet/Transaction.tsx:1220
#, c-format
diff --git a/packages/taler-wallet-webextension/src/i18n/it.po b/packages/taler-wallet-webextension/src/i18n/it.po
index 909d6390c..a11542c83 100644
--- a/packages/taler-wallet-webextension/src/i18n/it.po
+++ b/packages/taler-wallet-webextension/src/i18n/it.po
@@ -17,8 +17,8 @@ msgstr ""
"Project-Id-Version: Taler Wallet\n"
"Report-Msgid-Bugs-To: languages@taler.net\n"
"POT-Creation-Date: 2016-11-23 00:00+0100\n"
-"PO-Revision-Date: 2023-03-06 22:06+0000\n"
-"Last-Translator: Stefan Kügel <skuegel@web.de>\n"
+"PO-Revision-Date: 2023-08-15 07:28+0000\n"
+"Last-Translator: Krystian Baran <kiszkot@murena.io>\n"
"Language-Team: Italian <https://weblate.taler.net/projects/gnu-taler/"
"webextensions/it/>\n"
"Language: it\n"
@@ -328,7 +328,7 @@ msgstr ""
#: src/components/ShowFullContractTermPopup.tsx:195
#, c-format
msgid "Amount"
-msgstr ""
+msgstr "Importo"
#: src/components/ShowFullContractTermPopup.tsx:203
#, c-format
@@ -530,7 +530,7 @@ msgstr ""
#: src/components/BankDetailsByPaytoType.tsx:148
#, c-format
msgid "Subject"
-msgstr ""
+msgstr "Soggetto"
#: src/components/BankDetailsByPaytoType.tsx:154
#, c-format
@@ -784,7 +784,7 @@ msgstr ""
#: src/wallet/Transaction.tsx:935
#, c-format
msgid "Date"
-msgstr ""
+msgstr "Data"
#: src/wallet/Transaction.tsx:990
#, c-format
@@ -799,7 +799,7 @@ msgstr ""
#: src/wallet/Transaction.tsx:1074
#, c-format
msgid "Withdraw"
-msgstr ""
+msgstr "Prelevare"
#: src/wallet/Transaction.tsx:1146
#, c-format
@@ -809,7 +809,7 @@ msgstr ""
#: src/wallet/Transaction.tsx:1156
#, c-format
msgid "Refunded"
-msgstr ""
+msgstr "Rimborsato"
#: src/wallet/Transaction.tsx:1220
#, c-format
@@ -1270,7 +1270,7 @@ msgstr ""
#: src/wallet/CreateManualWithdraw.tsx:277
#, c-format
msgid "Start withdrawal"
-msgstr ""
+msgstr "Inizia a prelevare"
#: src/wallet/DepositPage/views.tsx:38
#, c-format
@@ -1576,7 +1576,7 @@ msgstr ""
#: src/wallet/Settings.tsx:191
#, c-format
msgid "ok"
-msgstr ""
+msgstr "ok"
#: src/wallet/Settings.tsx:197
#, c-format
diff --git a/packages/taler-wallet-webextension/src/i18n/nl.po b/packages/taler-wallet-webextension/src/i18n/nl.po
new file mode 100644
index 000000000..26f543b52
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/i18n/nl.po
@@ -0,0 +1,1950 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-11-23 00:00+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/NavigationBar.tsx:139
+#, c-format
+msgid "Balance"
+msgstr ""
+
+#: src/NavigationBar.tsx:142
+#, c-format
+msgid "Backup"
+msgstr ""
+
+#: src/NavigationBar.tsx:147
+#, c-format
+msgid "QR Reader and Taler URI"
+msgstr ""
+
+#: src/NavigationBar.tsx:154
+#, c-format
+msgid "Settings"
+msgstr ""
+
+#: src/NavigationBar.tsx:184
+#, c-format
+msgid "Dev"
+msgstr ""
+
+#: src/mui/Typography.tsx:122
+#, c-format
+msgid "%1$s"
+msgstr ""
+
+#: src/components/PendingTransactions.tsx:74
+#, c-format
+msgid "PENDING OPERATIONS"
+msgstr ""
+
+#: src/components/Loading.tsx:36
+#, c-format
+msgid "Loading"
+msgstr ""
+
+#: src/wallet/BackupPage.tsx:123
+#, c-format
+msgid "Could not load backup providers"
+msgstr ""
+
+#: src/wallet/BackupPage.tsx:202
+#, c-format
+msgid "No backup providers configured"
+msgstr ""
+
+#: src/wallet/BackupPage.tsx:205
+#, c-format
+msgid "Add provider"
+msgstr ""
+
+#: src/wallet/BackupPage.tsx:219
+#, c-format
+msgid "Sync all backups"
+msgstr ""
+
+#: src/wallet/BackupPage.tsx:221
+#, c-format
+msgid "Sync now"
+msgstr ""
+
+#: src/wallet/BackupPage.tsx:264
+#, c-format
+msgid "Last synced"
+msgstr ""
+
+#: src/wallet/BackupPage.tsx:269
+#, c-format
+msgid "Not synced"
+msgstr ""
+
+#: src/wallet/BackupPage.tsx:289
+#, c-format
+msgid "Expires in"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:60
+#, c-format
+msgid "There was an error loading the provider detail for &quot; %1$s&quot;"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:108
+#, c-format
+msgid "There is not known provider with url &quot;%1$s&quot;."
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:115
+#, c-format
+msgid "See providers"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:143
+#, c-format
+msgid "Last backup"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:148
+#, c-format
+msgid "Back up"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:154
+#, c-format
+msgid "Provider fee"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:157
+#, c-format
+msgid "per year"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:163
+#, c-format
+msgid "Extend"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:169
+#, c-format
+msgid ""
+"terms has changed, extending the service will imply accepting the new terms of "
+"service"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:179
+#, c-format
+msgid "old"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:183
+#, c-format
+msgid "new"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:190
+#, c-format
+msgid "fee"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:198
+#, c-format
+msgid "storage"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:215
+#, c-format
+msgid "Remove provider"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:228
+#, c-format
+msgid "This provider has reported an error"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:242
+#, c-format
+msgid "There is conflict with another backup from %1$s"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:253
+#, c-format
+msgid "Backup is not readable"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:261
+#, c-format
+msgid "Unknown backup problem: %1$s"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:283
+#, c-format
+msgid "service paid"
+msgstr ""
+
+#: src/wallet/ProviderDetailPage.tsx:290
+#, c-format
+msgid "Backup valid until"
+msgstr ""
+
+#: src/wallet/AddNewActionView.tsx:57
+#, c-format
+msgid "Cancel"
+msgstr ""
+
+#: src/wallet/AddNewActionView.tsx:68
+#, c-format
+msgid "Open reserve page"
+msgstr ""
+
+#: src/wallet/AddNewActionView.tsx:70
+#, c-format
+msgid "Open pay page"
+msgstr ""
+
+#: src/wallet/AddNewActionView.tsx:72
+#, c-format
+msgid "Open refund page"
+msgstr ""
+
+#: src/wallet/AddNewActionView.tsx:74
+#, c-format
+msgid "Open tip page"
+msgstr ""
+
+#: src/wallet/AddNewActionView.tsx:76
+#, c-format
+msgid "Open withdraw page"
+msgstr ""
+
+#: src/popup/NoBalanceHelp.tsx:43
+#, c-format
+msgid "Get digital cash"
+msgstr ""
+
+#: src/popup/BalancePage.tsx:138
+#, c-format
+msgid "Could not load balance page"
+msgstr ""
+
+#: src/popup/BalancePage.tsx:175
+#, c-format
+msgid "Add"
+msgstr ""
+
+#: src/popup/BalancePage.tsx:179
+#, c-format
+msgid "Send %1$s"
+msgstr ""
+
+#: src/popup/TalerActionFound.tsx:44
+#, c-format
+msgid "Taler Action"
+msgstr ""
+
+#: src/popup/TalerActionFound.tsx:49
+#, c-format
+msgid "This page has pay action."
+msgstr ""
+
+#: src/popup/TalerActionFound.tsx:63
+#, c-format
+msgid "This page has a withdrawal action."
+msgstr ""
+
+#: src/popup/TalerActionFound.tsx:79
+#, c-format
+msgid "This page has a tip action."
+msgstr ""
+
+#: src/popup/TalerActionFound.tsx:93
+#, c-format
+msgid "This page has a notify reserve action."
+msgstr ""
+
+#: src/popup/TalerActionFound.tsx:102
+#, c-format
+msgid "Notify"
+msgstr ""
+
+#: src/popup/TalerActionFound.tsx:109
+#, c-format
+msgid "This page has a refund action."
+msgstr ""
+
+#: src/popup/TalerActionFound.tsx:123
+#, c-format
+msgid "This page has a malformed taler uri."
+msgstr ""
+
+#: src/popup/TalerActionFound.tsx:134
+#, c-format
+msgid "Dismiss"
+msgstr ""
+
+#: src/popup/Application.tsx:177
+#, c-format
+msgid "this popup is being closed and you are being redirected to %1$s"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:158
+#, c-format
+msgid "Could not load purchase proposal details"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:183
+#, c-format
+msgid "Order Id"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:189
+#, c-format
+msgid "Summary"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:195
+#, c-format
+msgid "Amount"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:203
+#, c-format
+msgid "Merchant name"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:209
+#, c-format
+msgid "Merchant jurisdiction"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:215
+#, c-format
+msgid "Merchant address"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:221
+#, c-format
+msgid "Merchant logo"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:234
+#, c-format
+msgid "Merchant website"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:240
+#, c-format
+msgid "Merchant email"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:246
+#, c-format
+msgid "Merchant public key"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:256
+#, c-format
+msgid "Delivery date"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:271
+#, c-format
+msgid "Delivery location"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:277
+#, c-format
+msgid "Products"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:289
+#, c-format
+msgid "Created at"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:304
+#, c-format
+msgid "Refund deadline"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:319
+#, c-format
+msgid "Auto refund"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:339
+#, c-format
+msgid "Pay deadline"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:354
+#, c-format
+msgid "Fulfillment URL"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:360
+#, c-format
+msgid "Fulfillment message"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:370
+#, c-format
+msgid "Max deposit fee"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:378
+#, c-format
+msgid "Max fee"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:386
+#, c-format
+msgid "Minimum age"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:398
+#, c-format
+msgid "Wire fee amortization"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:404
+#, c-format
+msgid "Auditors"
+msgstr ""
+
+#: src/components/ShowFullContractTermPopup.tsx:419
+#, c-format
+msgid "Exchanges"
+msgstr ""
+
+#: src/components/Part.tsx:148
+#, c-format
+msgid "Bank account"
+msgstr ""
+
+#: src/components/Part.tsx:160
+#, c-format
+msgid "Bitcoin address"
+msgstr ""
+
+#: src/components/Part.tsx:163
+#, c-format
+msgid "IBAN"
+msgstr ""
+
+#: src/cta/Deposit/views.tsx:38
+#, c-format
+msgid "Could not load deposit status"
+msgstr ""
+
+#: src/cta/Deposit/views.tsx:52
+#, c-format
+msgid "Digital cash deposit"
+msgstr ""
+
+#: src/cta/Deposit/views.tsx:58
+#, c-format
+msgid "Cost"
+msgstr ""
+
+#: src/cta/Deposit/views.tsx:66
+#, c-format
+msgid "Fee"
+msgstr ""
+
+#: src/cta/Deposit/views.tsx:73
+#, c-format
+msgid "To be received"
+msgstr ""
+
+#: src/cta/Deposit/views.tsx:84
+#, c-format
+msgid "Send &nbsp; %1$s"
+msgstr ""
+
+#: src/components/BankDetailsByPaytoType.tsx:63
+#, c-format
+msgid "Bitcoin transfer details"
+msgstr ""
+
+#: src/components/BankDetailsByPaytoType.tsx:66
+#, c-format
+msgid ""
+"The exchange need a transaction with 3 output, one output is the exchange "
+"account and the other two are segwit fake address for metadata with an minimum "
+"amount."
+msgstr ""
+
+#: src/components/BankDetailsByPaytoType.tsx:74
+#, c-format
+msgid ""
+"In bitcoincore wallet use &apos;Add Recipient&apos; button to add two additional "
+"recipient and copy addresses and amounts"
+msgstr ""
+
+#: src/components/BankDetailsByPaytoType.tsx:98
+#, c-format
+msgid "Make sure the amount show %1$s BTC, else you have to change the base unit to BTC"
+msgstr ""
+
+#: src/components/BankDetailsByPaytoType.tsx:110
+#, c-format
+msgid "Account"
+msgstr ""
+
+#: src/components/BankDetailsByPaytoType.tsx:116
+#, c-format
+msgid "Bank host"
+msgstr ""
+
+#: src/components/BankDetailsByPaytoType.tsx:139
+#, c-format
+msgid "Bank transfer details"
+msgstr ""
+
+#: src/components/BankDetailsByPaytoType.tsx:148
+#, c-format
+msgid "Subject"
+msgstr ""
+
+#: src/components/BankDetailsByPaytoType.tsx:154
+#, c-format
+msgid "Receiver name"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:98
+#, c-format
+msgid "Could not load the transaction information"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:191
+#, c-format
+msgid "There was an error trying to complete the transaction"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:200
+#, c-format
+msgid "This transaction is not completed"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:209
+#, c-format
+msgid "Send"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:216
+#, c-format
+msgid "Retry"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:224
+#, c-format
+msgid "Forget"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:241
+#, c-format
+msgid "Caution!"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:244
+#, c-format
+msgid ""
+"If you have already wired money to the exchange you will loose the chance to get "
+"the coins form it."
+msgstr ""
+
+#: src/wallet/Transaction.tsx:259
+#, c-format
+msgid "Confirm"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:267
+#, c-format
+msgid "Withdrawal"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:286
+#, c-format
+msgid ""
+"Make sure to use the correct subject, otherwise the money will not arrive in "
+"this wallet."
+msgstr ""
+
+#: src/wallet/Transaction.tsx:298
+#, c-format
+msgid ""
+"The bank did not yet confirmed the wire transfer. Go to the %1$s %2$s and check "
+"there is no pending step."
+msgstr ""
+
+#: src/wallet/Transaction.tsx:316
+#, c-format
+msgid "Bank has confirmed the wire transfer. Waiting for the exchange to send the coins"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:325
+#, c-format
+msgid "Details"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:360
+#, c-format
+msgid "Payment"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:378
+#, c-format
+msgid "Refunds"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:385
+#, c-format
+msgid "%1$s %2$s on %3$s"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:415
+#, c-format
+msgid "Merchant created a refund for this order but was not automatically picked up."
+msgstr ""
+
+#: src/wallet/Transaction.tsx:420
+#, c-format
+msgid "Offer"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:431
+#, c-format
+msgid "Accept"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:438
+#, c-format
+msgid "Merchant"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:443
+#, c-format
+msgid "Invoice ID"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:470
+#, c-format
+msgid "Deposit"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:496
+#, c-format
+msgid "Refresh"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:517
+#, c-format
+msgid "Tip"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:542
+#, c-format
+msgid "Refund"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:555
+#, c-format
+msgid "Original order ID"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:568
+#, c-format
+msgid "Purchase summary"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:593
+#, c-format
+msgid "copy"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:596
+#, c-format
+msgid "hide qr"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:608
+#, c-format
+msgid "show qr"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:620
+#, c-format
+msgid "Credit"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:624
+#, c-format
+msgid "Invoice"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:635
+#, c-format
+msgid "Exchange"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:641
+#, c-format
+msgid "URI"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:667
+#, c-format
+msgid "Debit"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:710
+#, c-format
+msgid "Transfer"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:844
+#, c-format
+msgid "Country"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:852
+#, c-format
+msgid "Address lines"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:860
+#, c-format
+msgid "Building number"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:868
+#, c-format
+msgid "Building name"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:876
+#, c-format
+msgid "Street"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:884
+#, c-format
+msgid "Post code"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:892
+#, c-format
+msgid "Town location"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:900
+#, c-format
+msgid "Town"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:908
+#, c-format
+msgid "District"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:916
+#, c-format
+msgid "Country subdivision"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:935
+#, c-format
+msgid "Date"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:990
+#, c-format
+msgid "Transaction fees"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:1004
+#, c-format
+msgid "Total"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:1074
+#, c-format
+msgid "Withdraw"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:1146
+#, c-format
+msgid "Price"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:1156
+#, c-format
+msgid "Refunded"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:1220
+#, c-format
+msgid "Delivery"
+msgstr ""
+
+#: src/wallet/Transaction.tsx:1335
+#, c-format
+msgid "Total transfer"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:57
+#, c-format
+msgid "Could not load pay status"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:87
+#, c-format
+msgid "Digital cash payment"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:119
+#, c-format
+msgid "Purchase"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:149
+#, c-format
+msgid "Receipt"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:156
+#, c-format
+msgid "Valid until"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:191
+#, c-format
+msgid "List of products"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:242
+#, c-format
+msgid "free"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:263
+#, c-format
+msgid "Already paid, you are going to be redirected to %1$s"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:274
+#, c-format
+msgid "Already paid"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:280
+#, c-format
+msgid "Already claimed"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:296
+#, c-format
+msgid "Pay with a mobile phone"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:298
+#, c-format
+msgid "Hide QR"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:305
+#, c-format
+msgid "Scan the QR code or &nbsp; %1$s"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:346
+#, c-format
+msgid "Pay &nbsp; %1$s"
+msgstr ""
+
+#: src/cta/Payment/views.tsx:360
+#, c-format
+msgid "You have no balance for this currency. Withdraw digital cash first."
+msgstr ""
+
+#: src/cta/Payment/views.tsx:364
+#, c-format
+msgid ""
+"Could not find enough coins to pay. Even if you have enough %1$s some "
+"restriction may apply."
+msgstr ""
+
+#: src/cta/Payment/views.tsx:366
+#, c-format
+msgid "Your current balance is not enough."
+msgstr ""
+
+#: src/cta/Payment/views.tsx:395
+#, c-format
+msgid "Merchant message"
+msgstr ""
+
+#: src/cta/Refund/views.tsx:34
+#, c-format
+msgid "Could not load refund status"
+msgstr ""
+
+#: src/cta/Refund/views.tsx:48
+#, c-format
+msgid "Digital cash refund"
+msgstr ""
+
+#: src/cta/Refund/views.tsx:52
+#, c-format
+msgid "You&apos;ve ignored the tip."
+msgstr ""
+
+#: src/cta/Refund/views.tsx:70
+#, c-format
+msgid "The refund is in progress."
+msgstr ""
+
+#: src/cta/Refund/views.tsx:76
+#, c-format
+msgid "Total to refund"
+msgstr ""
+
+#: src/cta/Refund/views.tsx:106
+#, c-format
+msgid "The merchant &quot;%1$s&quot; is offering you a refund."
+msgstr ""
+
+#: src/cta/Refund/views.tsx:115
+#, c-format
+msgid "Order amount"
+msgstr ""
+
+#: src/cta/Refund/views.tsx:122
+#, c-format
+msgid "Already refunded"
+msgstr ""
+
+#: src/cta/Refund/views.tsx:129
+#, c-format
+msgid "Refund offered"
+msgstr ""
+
+#: src/cta/Refund/views.tsx:145
+#, c-format
+msgid "Accept &nbsp; %1$s"
+msgstr ""
+
+#: src/cta/Tip/views.tsx:32
+#, c-format
+msgid "Could not load tip status"
+msgstr ""
+
+#: src/cta/Tip/views.tsx:45
+#, c-format
+msgid "Digital cash tip"
+msgstr ""
+
+#: src/cta/Tip/views.tsx:66
+#, c-format
+msgid "The merchant is offering you a tip"
+msgstr ""
+
+#: src/cta/Tip/views.tsx:74
+#, c-format
+msgid "Merchant URL"
+msgstr ""
+
+#: src/cta/Tip/views.tsx:90
+#, c-format
+msgid "Receive &nbsp; %1$s"
+msgstr ""
+
+#: src/cta/Tip/views.tsx:114
+#, c-format
+msgid "Tip from %1$s accepted. Check your transactions list for more details."
+msgstr ""
+
+#: src/components/SelectList.tsx:66
+#, c-format
+msgid "Select one option"
+msgstr ""
+
+#: src/components/TermsOfService/views.tsx:39
+#, c-format
+msgid "Could not load"
+msgstr ""
+
+#: src/components/TermsOfService/views.tsx:73
+#, c-format
+msgid "Show terms of service"
+msgstr ""
+
+#: src/components/TermsOfService/views.tsx:81
+#, c-format
+msgid "I accept the exchange terms of service"
+msgstr ""
+
+#: src/components/TermsOfService/views.tsx:107
+#, c-format
+msgid "Exchange doesn&apos;t have terms of service"
+msgstr ""
+
+#: src/components/TermsOfService/views.tsx:135
+#, c-format
+msgid "Review exchange terms of service"
+msgstr ""
+
+#: src/components/TermsOfService/views.tsx:146
+#, c-format
+msgid "Review new version of terms of service"
+msgstr ""
+
+#: src/components/TermsOfService/views.tsx:170
+#, c-format
+msgid "The exchange reply with a empty terms of service"
+msgstr ""
+
+#: src/components/TermsOfService/views.tsx:193
+#, c-format
+msgid "Download Terms of Service"
+msgstr ""
+
+#: src/components/TermsOfService/views.tsx:204
+#, c-format
+msgid "Hide terms of service"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:117
+#, c-format
+msgid "Could not load exchange fees"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:131
+#, c-format
+msgid "Close"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:160
+#, c-format
+msgid "could not find any exchange"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:166
+#, c-format
+msgid "could not find any exchange for the currency %1$s"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:186
+#, c-format
+msgid "Service fee description"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:201
+#, c-format
+msgid "Select %1$s exchange"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:215
+#, c-format
+msgid "Reset"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:218
+#, c-format
+msgid "Use this exchange"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:230
+#, c-format
+msgid "Doesn&apos;t have auditors"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:241
+#, c-format
+msgid "currency"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:249
+#, c-format
+msgid "Operations"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:252
+#, c-format
+msgid "Deposits"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:259
+#, c-format
+msgid "Denomination"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:265
+#, c-format
+msgid "Until"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:274
+#, c-format
+msgid "Withdrawals"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:423
+#, c-format
+msgid "Currency"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:433
+#, c-format
+msgid "Coin operations"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:436
+#, c-format
+msgid ""
+"Every operation in this section may be different by denomination value and is "
+"valid for a period of time. The exchange will charge the indicated amount every "
+"time a coin is used in such operation."
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:545
+#, c-format
+msgid "Transfer operations"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:548
+#, c-format
+msgid ""
+"Every operation in this section may be different by transfer type and is valid "
+"for a period of time. The exchange will charge the indicated amount every time a "
+"transfer is made."
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:563
+#, c-format
+msgid "Operation"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:583
+#, c-format
+msgid "Wallet operations"
+msgstr ""
+
+#: src/wallet/ExchangeSelection/views.tsx:597
+#, c-format
+msgid "Feature"
+msgstr ""
+
+#: src/cta/Withdraw/views.tsx:47
+#, c-format
+msgid "Could not get the info from the URI"
+msgstr ""
+
+#: src/cta/Withdraw/views.tsx:60
+#, c-format
+msgid "Could not get info of withdrawal"
+msgstr ""
+
+#: src/cta/Withdraw/views.tsx:74
+#, c-format
+msgid "Digital cash withdrawal"
+msgstr ""
+
+#: src/cta/Withdraw/views.tsx:79
+#, c-format
+msgid "Could not finish the withdrawal operation"
+msgstr ""
+
+#: src/cta/Withdraw/views.tsx:127
+#, c-format
+msgid "Age restriction"
+msgstr ""
+
+#: src/cta/Withdraw/views.tsx:145
+#, c-format
+msgid "Withdraw &nbsp; %1$s"
+msgstr ""
+
+#: src/cta/Withdraw/views.tsx:179
+#, c-format
+msgid "Withdraw to a mobile phone"
+msgstr ""
+
+#: src/cta/InvoiceCreate/views.tsx:65
+#, c-format
+msgid "Digital invoice"
+msgstr ""
+
+#: src/cta/InvoiceCreate/views.tsx:69
+#, c-format
+msgid "Could not finish the invoice creation"
+msgstr ""
+
+#: src/cta/InvoiceCreate/views.tsx:130
+#, c-format
+msgid "Create"
+msgstr ""
+
+#: src/cta/InvoicePay/views.tsx:63
+#, c-format
+msgid "Could not finish the payment operation"
+msgstr ""
+
+#: src/cta/TransferCreate/views.tsx:55
+#, c-format
+msgid "Digital cash transfer"
+msgstr ""
+
+#: src/cta/TransferCreate/views.tsx:59
+#, c-format
+msgid "Could not finish the transfer creation"
+msgstr ""
+
+#: src/cta/TransferPickup/views.tsx:57
+#, c-format
+msgid "Could not finish the pickup operation"
+msgstr ""
+
+#: src/wallet/CreateManualWithdraw.tsx:149
+#, c-format
+msgid "Manual Withdrawal for %1$s"
+msgstr ""
+
+#: src/wallet/CreateManualWithdraw.tsx:154
+#, c-format
+msgid ""
+"Choose a exchange from where the coins will be withdrawn. The exchange will send "
+"the coins to this wallet after receiving a wire transfer with the correct "
+"subject."
+msgstr ""
+
+#: src/wallet/CreateManualWithdraw.tsx:162
+#, c-format
+msgid "No exchange found for %1$s"
+msgstr ""
+
+#: src/wallet/CreateManualWithdraw.tsx:170
+#, c-format
+msgid "Add Exchange"
+msgstr ""
+
+#: src/wallet/CreateManualWithdraw.tsx:192
+#, c-format
+msgid "No exchange configured"
+msgstr ""
+
+#: src/wallet/CreateManualWithdraw.tsx:210
+#, c-format
+msgid "Can&apos;t create the reserve"
+msgstr ""
+
+#: src/wallet/CreateManualWithdraw.tsx:277
+#, c-format
+msgid "Start withdrawal"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:38
+#, c-format
+msgid "Could not load deposit balance"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:51
+#, c-format
+msgid "A currency or an amount should be indicated"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:67
+#, c-format
+msgid "There is no enough balance to make a deposit for currency %1$s"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:117
+#, c-format
+msgid "Send %1$s to your account"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:121
+#, c-format
+msgid "There is no account to make a deposit for currency %1$s"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:127
+#, c-format
+msgid "Add account"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:151
+#, c-format
+msgid "Select account"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:163
+#, c-format
+msgid "Add another account"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:191
+#, c-format
+msgid "Deposit fee"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:205
+#, c-format
+msgid "Total deposit"
+msgstr ""
+
+#: src/wallet/DepositPage/views.tsx:233
+#, c-format
+msgid "Deposit&nbsp;%1$s %2$s"
+msgstr ""
+
+#: src/wallet/AddAccount/views.tsx:56
+#, c-format
+msgid "Add bank account for %1$s"
+msgstr ""
+
+#: src/wallet/AddAccount/views.tsx:59
+#, c-format
+msgid "Enter the URL of an exchange you trust."
+msgstr ""
+
+#: src/wallet/AddAccount/views.tsx:66
+#, c-format
+msgid "Unable add this account"
+msgstr ""
+
+#: src/wallet/AddAccount/views.tsx:73
+#, c-format
+msgid "Select account type"
+msgstr ""
+
+#: src/wallet/ExchangeAddConfirm.tsx:42
+#, c-format
+msgid "Review terms of service"
+msgstr ""
+
+#: src/wallet/ExchangeAddConfirm.tsx:45
+#, c-format
+msgid "Exchange URL"
+msgstr ""
+
+#: src/wallet/ExchangeAddConfirm.tsx:70
+#, c-format
+msgid "Add exchange"
+msgstr ""
+
+#: src/wallet/ExchangeSetUrl.tsx:112
+#, c-format
+msgid "Add new exchange"
+msgstr ""
+
+#: src/wallet/ExchangeSetUrl.tsx:116
+#, c-format
+msgid "Add exchange for %1$s"
+msgstr ""
+
+#: src/wallet/ExchangeSetUrl.tsx:128
+#, c-format
+msgid "An exchange has been found! Review the information and click next"
+msgstr ""
+
+#: src/wallet/ExchangeSetUrl.tsx:135
+#, c-format
+msgid "This exchange doesn&apos;t match the expected currency %1$s"
+msgstr ""
+
+#: src/wallet/ExchangeSetUrl.tsx:143
+#, c-format
+msgid "Unable to verify this exchange"
+msgstr ""
+
+#: src/wallet/ExchangeSetUrl.tsx:151
+#, c-format
+msgid "Unable to add this exchange"
+msgstr ""
+
+#: src/wallet/ExchangeSetUrl.tsx:167
+#, c-format
+msgid "loading"
+msgstr ""
+
+#: src/wallet/ExchangeSetUrl.tsx:174
+#, c-format
+msgid "Version"
+msgstr ""
+
+#: src/wallet/ExchangeSetUrl.tsx:206
+#, c-format
+msgid "Next"
+msgstr ""
+
+#: src/components/TransactionItem.tsx:201
+#, c-format
+msgid "Waiting for confirmation"
+msgstr ""
+
+#: src/components/TransactionItem.tsx:266
+#, c-format
+msgid "PENDING"
+msgstr ""
+
+#: src/wallet/History.tsx:75
+#, c-format
+msgid "Could not load the list of transactions"
+msgstr ""
+
+#: src/wallet/History.tsx:233
+#, c-format
+msgid "Your transaction history is empty for this currency."
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:127
+#, c-format
+msgid "Add backup provider"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:131
+#, c-format
+msgid "Could not get provider information"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:140
+#, c-format
+msgid "Backup providers may charge for their service"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:147
+#, c-format
+msgid "URL"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:158
+#, c-format
+msgid "Name"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:212
+#, c-format
+msgid "Provider URL"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:218
+#, c-format
+msgid "Please review and accept this provider&apos;s terms of service"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:223
+#, c-format
+msgid "Pricing"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:226
+#, c-format
+msgid "free of charge"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:228
+#, c-format
+msgid "%1$s per year of service"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:235
+#, c-format
+msgid "Storage"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:238
+#, c-format
+msgid "%1$s megabytes of storage per year of service"
+msgstr ""
+
+#: src/wallet/ProviderAddPage.tsx:244
+#, c-format
+msgid "Accept terms of service"
+msgstr ""
+
+#: src/wallet/ReserveCreated.tsx:44
+#, c-format
+msgid "Could not parse the payto URI"
+msgstr ""
+
+#: src/wallet/ReserveCreated.tsx:45
+#, c-format
+msgid "Please check the uri"
+msgstr ""
+
+#: src/wallet/ReserveCreated.tsx:75
+#, c-format
+msgid "Exchange is ready for withdrawal"
+msgstr ""
+
+#: src/wallet/ReserveCreated.tsx:78
+#, c-format
+msgid "To complete the process you need to wire%1$s %2$s to the exchange bank account"
+msgstr ""
+
+#: src/wallet/ReserveCreated.tsx:87
+#, c-format
+msgid ""
+"Alternative, you can also scan this QR code or open %1$s if you have a banking "
+"app installed that supports RFC 8905"
+msgstr ""
+
+#: src/wallet/ReserveCreated.tsx:98
+#, c-format
+msgid "Cancel withdrawal"
+msgstr ""
+
+#: src/wallet/Settings.tsx:115
+#, c-format
+msgid "Could not toggle auto-open"
+msgstr ""
+
+#: src/wallet/Settings.tsx:121
+#, c-format
+msgid "Could not toggle clipboard"
+msgstr ""
+
+#: src/wallet/Settings.tsx:126
+#, c-format
+msgid "Navigator"
+msgstr ""
+
+#: src/wallet/Settings.tsx:129
+#, c-format
+msgid "Automatically open wallet based on page content"
+msgstr ""
+
+#: src/wallet/Settings.tsx:135
+#, c-format
+msgid ""
+"Enabling this option below will make using the wallet faster, but requires more "
+"permissions from your browser."
+msgstr ""
+
+#: src/wallet/Settings.tsx:145
+#, c-format
+msgid "Automatically check clipboard for Taler URI"
+msgstr ""
+
+#: src/wallet/Settings.tsx:162
+#, c-format
+msgid "Trust"
+msgstr ""
+
+#: src/wallet/Settings.tsx:166
+#, c-format
+msgid "No exchange yet"
+msgstr ""
+
+#: src/wallet/Settings.tsx:180
+#, c-format
+msgid "Term of Service"
+msgstr ""
+
+#: src/wallet/Settings.tsx:191
+#, c-format
+msgid "ok"
+msgstr ""
+
+#: src/wallet/Settings.tsx:197
+#, c-format
+msgid "changed"
+msgstr ""
+
+#: src/wallet/Settings.tsx:204
+#, c-format
+msgid "not accepted"
+msgstr ""
+
+#: src/wallet/Settings.tsx:210
+#, c-format
+msgid "unknown (exchange status should be updated)"
+msgstr ""
+
+#: src/wallet/Settings.tsx:236
+#, c-format
+msgid "Add an exchange"
+msgstr ""
+
+#: src/wallet/Settings.tsx:241
+#, c-format
+msgid "Troubleshooting"
+msgstr ""
+
+#: src/wallet/Settings.tsx:244
+#, c-format
+msgid "Developer mode"
+msgstr ""
+
+#: src/wallet/Settings.tsx:246
+#, c-format
+msgid "More options and information useful for debugging"
+msgstr ""
+
+#: src/wallet/Settings.tsx:257
+#, c-format
+msgid "Display"
+msgstr ""
+
+#: src/wallet/Settings.tsx:261
+#, c-format
+msgid "Current Language"
+msgstr ""
+
+#: src/wallet/Settings.tsx:274
+#, c-format
+msgid "Wallet Core"
+msgstr ""
+
+#: src/wallet/Settings.tsx:284
+#, c-format
+msgid "Web Extension"
+msgstr ""
+
+#: src/wallet/Settings.tsx:295
+#, c-format
+msgid "Exchange compatibility"
+msgstr ""
+
+#: src/wallet/Settings.tsx:299
+#, c-format
+msgid "Merchant compatibility"
+msgstr ""
+
+#: src/wallet/Settings.tsx:303
+#, c-format
+msgid "Bank compatibility"
+msgstr ""
+
+#: src/wallet/Welcome.tsx:59
+#, c-format
+msgid "Browser Extension Installed!"
+msgstr ""
+
+#: src/wallet/Welcome.tsx:63
+#, c-format
+msgid "You can open the GNU Taler Wallet using the combination %1$s ."
+msgstr ""
+
+#: src/wallet/Welcome.tsx:72
+#, c-format
+msgid ""
+"Also pinning the GNU Taler Wallet to your Chrome browser allows you to quick "
+"access without keyboard:"
+msgstr ""
+
+#: src/wallet/Welcome.tsx:79
+#, c-format
+msgid "Click the puzzle icon"
+msgstr ""
+
+#: src/wallet/Welcome.tsx:82
+#, c-format
+msgid "Search for GNU Taler Wallet"
+msgstr ""
+
+#: src/wallet/Welcome.tsx:85
+#, c-format
+msgid "Click the pin icon"
+msgstr ""
+
+#: src/wallet/Welcome.tsx:91
+#, c-format
+msgid "Permissions"
+msgstr ""
+
+#: src/wallet/Welcome.tsx:100
+#, c-format
+msgid ""
+"(Enabling this option below will make using the wallet faster, but requires more "
+"permissions from your browser.)"
+msgstr ""
+
+#: src/wallet/Welcome.tsx:110
+#, c-format
+msgid "Next Steps"
+msgstr ""
+
+#: src/wallet/Welcome.tsx:113
+#, c-format
+msgid "Try the demo"
+msgstr ""
+
+#: src/wallet/Welcome.tsx:116
+#, c-format
+msgid "Learn how to top up your wallet balance"
+msgstr ""
+
+#: src/components/Diagnostics.tsx:31
+#, c-format
+msgid "Diagnostics timed out. Could not talk to the wallet backend."
+msgstr ""
+
+#: src/components/Diagnostics.tsx:52
+#, c-format
+msgid "Problems detected:"
+msgstr ""
+
+#: src/components/Diagnostics.tsx:61
+#, c-format
+msgid ""
+"Please check in your %1$s settings that you have IndexedDB enabled (check the "
+"preference name %2$s)."
+msgstr ""
+
+#: src/components/Diagnostics.tsx:70
+#, c-format
+msgid ""
+"Your wallet database is outdated. Currently automatic migration is not "
+"supported. Please go %1$s to reset the wallet database."
+msgstr ""
+
+#: src/components/Diagnostics.tsx:83
+#, c-format
+msgid "Running diagnostics"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:163
+#, c-format
+msgid "Debug tools"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:170
+#, c-format
+msgid ""
+"Do you want to IRREVOCABLY DESTROY everything inside your wallet and LOSE ALL "
+"YOUR COINS?"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:176
+#, c-format
+msgid "reset"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:183
+#, c-format
+msgid "TESTING: This may delete all your coin, proceed with caution"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:189
+#, c-format
+msgid "run gc"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:197
+#, c-format
+msgid "import database"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:219
+#, c-format
+msgid "export database"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:225
+#, c-format
+msgid "Database exported at %1$s %2$s to download"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:248
+#, c-format
+msgid "Coins"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:282
+#, c-format
+msgid "Pending operations"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:328
+#, c-format
+msgid "usable coins"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:337
+#, c-format
+msgid "id"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:340
+#, c-format
+msgid "denom"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:343
+#, c-format
+msgid "value"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:346
+#, c-format
+msgid "status"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:349
+#, c-format
+msgid "from refresh?"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:352
+#, c-format
+msgid "age key count"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:369
+#, c-format
+msgid "spent coins"
+msgstr ""
+
+#: src/wallet/DeveloperPage.tsx:373
+#, c-format
+msgid "click to show"
+msgstr ""
+
+#: src/wallet/QrReader.tsx:108
+#, c-format
+msgid "Scan a QR code or enter taler:// URI below"
+msgstr ""
+
+#: src/wallet/QrReader.tsx:122
+#, c-format
+msgid "Open"
+msgstr ""
+
+#: src/wallet/QrReader.tsx:128
+#, c-format
+msgid "URI is not valid. Taler URI should start with `taler://`"
+msgstr ""
+
+#: src/wallet/QrReader.tsx:133
+#, c-format
+msgid "Try another"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:183
+#, c-format
+msgid "Could not load list of exchange"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:209
+#, c-format
+msgid "Choose a currency to proceed or add another exchange"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:217
+#, c-format
+msgid "Known currencies"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:318
+#, c-format
+msgid "Specify the amount and the origin"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:336
+#, c-format
+msgid "Change currency"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:344
+#, c-format
+msgid "Use previous origins:"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:364
+#, c-format
+msgid "Or specify the origin of the money"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:372
+#, c-format
+msgid "Specify the origin of the money"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:380
+#, c-format
+msgid "From my bank account"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:395
+#, c-format
+msgid "From another wallet"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:449
+#, c-format
+msgid "currency not provided"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:459
+#, c-format
+msgid "Specify the amount and the destination"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:483
+#, c-format
+msgid "Use previous destinations:"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:503
+#, c-format
+msgid "Or specify the destination of the money"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:511
+#, c-format
+msgid "Specify the destination of the money"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:521
+#, c-format
+msgid "To my bank account"
+msgstr ""
+
+#: src/wallet/DestinationSelection.tsx:534
+#, c-format
+msgid "To another wallet"
+msgstr ""
+
+#: src/cta/Recovery/views.tsx:30
+#, c-format
+msgid "Could not load backup recovery information"
+msgstr ""
+
+#: src/cta/Recovery/views.tsx:47
+#, c-format
+msgid "Digital wallet recovery"
+msgstr ""
+
+#: src/cta/Recovery/views.tsx:52
+#, c-format
+msgid "Import backup, show info"
+msgstr ""
+
+#: src/wallet/Application.tsx:189
+#, c-format
+msgid "All done, your transaction is in progress"
+msgstr ""
+
+#: src/components/EditableText.tsx:45
+#, c-format
+msgid "Edit"
+msgstr ""
+
+#: src/wallet/ManualWithdrawPage.tsx:102
+#, c-format
+msgid "Could not load the list of known exchanges"
+msgstr ""