diff options
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 |
commit | 819949d7f2cfcce8d334cbfdb701255daac64951 (patch) | |
tree | 592043554f59ec209f9afc1c31c20c4fb585c3ca | |
parent | 77ea209ddb6d457db0ce113f91247207cc29fbd8 (diff) | |
parent | adb0e70f151e63d103f27852312319520f4d0a84 (diff) |
Merge branch 'master' into age-withdraw
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>"{instance}"</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'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 /> + + {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 " %1$s"" +msgstr "" + +#: src/wallet/ProviderDetailPage.tsx:108 +#, c-format +msgid "There is not known provider with url "%1$s"." +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 %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 'Add Recipient' 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 %1$s" +msgstr "" + +#: src/cta/Payment/views.tsx:346 +#, c-format +msgid "Pay %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'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 "%1$s" 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 %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 %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'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'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 %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'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 %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'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'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 "" |