aboutsummaryrefslogtreecommitdiff
path: root/packages/demobank-ui
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-11-21 17:10:07 -0300
committerSebastian <sebasjm@gmail.com>2023-11-21 17:10:31 -0300
commit32182fb1b912e1136ba933c4a4f204e6e2f33de2 (patch)
tree9de9caf82632994c233fbbd4366b086818217c7d /packages/demobank-ui
parent6000a55d583832a71335310514688f1f6faed722 (diff)
downloadwallet-core-32182fb1b912e1136ba933c4a4f204e6e2f33de2.tar.xz
cashout creation
Diffstat (limited to 'packages/demobank-ui')
-rw-r--r--packages/demobank-ui/README.md4
-rwxr-xr-xpackages/demobank-ui/dev.mjs2
-rw-r--r--packages/demobank-ui/src/bank-ui-settings.js (renamed from packages/demobank-ui/src/demobank-ui-settings.js)4
-rw-r--r--packages/demobank-ui/src/hooks/circuit.ts99
-rw-r--r--packages/demobank-ui/src/hooks/settings.ts4
-rw-r--r--packages/demobank-ui/src/index.html6
-rw-r--r--packages/demobank-ui/src/pages/BankFrame.tsx1
-rw-r--r--packages/demobank-ui/src/pages/OperationState/views.tsx53
-rw-r--r--packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx16
-rw-r--r--packages/demobank-ui/src/pages/ProfileNavigation.tsx4
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx53
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountForm.tsx20
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountList.tsx2
-rw-r--r--packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx14
-rw-r--r--packages/demobank-ui/src/pages/business/CreateCashout.tsx485
-rw-r--r--packages/demobank-ui/src/settings.ts6
-rw-r--r--packages/demobank-ui/src/stories.test.ts4
17 files changed, 335 insertions, 442 deletions
diff --git a/packages/demobank-ui/README.md b/packages/demobank-ui/README.md
index 877799748..109b6196b 100644
--- a/packages/demobank-ui/README.md
+++ b/packages/demobank-ui/README.md
@@ -26,7 +26,7 @@ localStorage.setItem("bank-base-url", OTHER_URL);
## Customizing Per-Deployment Settings
To customize per-deployment settings, make sure that the
-`demobank-ui-settings.js` file is served alongside the UI.
+`bank-ui-settings.js` file is served alongside the UI.
This file is loaded before the SPA and can do customizations by
changing `globalThis.`.
@@ -35,7 +35,7 @@ For example, the following settings would correspond
to the default settings:
```
-globalThis.talerDemobankSettings = {
+globalThis.talerBankSettings = {
// location of libeufin server
backendBaseURL: "https://bank.demo.taler.net/",
allowRegistrations: true,
diff --git a/packages/demobank-ui/dev.mjs b/packages/demobank-ui/dev.mjs
index f29a05e49..8b04155f4 100755
--- a/packages/demobank-ui/dev.mjs
+++ b/packages/demobank-ui/dev.mjs
@@ -18,7 +18,7 @@
import { serve } from "@gnu-taler/web-util/node";
import { initializeDev } from "@gnu-taler/web-util/build";
-const devEntryPoints = ["src/stories.tsx", "src/index.tsx", "src/demobank-ui-settings.js"];
+const devEntryPoints = ["src/stories.tsx", "src/index.tsx", "src/bank-ui-settings.js"];
const build = initializeDev({
type: "development",
diff --git a/packages/demobank-ui/src/demobank-ui-settings.js b/packages/demobank-ui/src/bank-ui-settings.js
index 827f207f8..397fa28c0 100644
--- a/packages/demobank-ui/src/demobank-ui-settings.js
+++ b/packages/demobank-ui/src/bank-ui-settings.js
@@ -1,9 +1,9 @@
// Values for development environment
/**
- * Global settings for the demobank UI.
+ * Global settings for the bank UI.
*/
-globalThis.talerDemobankSettings = {
+globalThis.talerBankSettings = {
backendBaseURL: "http://bank.taler.test:1180/",
allowRegistrations: true,
showDemoNav: true,
diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts
index 44edb4f8a..d0d180a53 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -18,7 +18,7 @@ import { useState } from "preact/hooks";
import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
import { useBackendState } from "./backend.js";
-import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, TalerCoreBankErrorsByMethod, TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } from "@gnu-taler/taler-util";
+import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, TalerBankConversionResultByMethod, TalerCoreBankErrorsByMethod, TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } from "@gnu-taler/taler-util";
import _useSWR, { SWRHook } from "swr";
import { useBankCoreApiContext } from "../context/config.js";
import { assertUnreachable } from "../pages/WithdrawalOperationPage.js";
@@ -34,6 +34,7 @@ export type TransferCalculation = {
};
type EstimatorFunction = (
amount: AmountJson,
+ currency: string,
sellFee: AmountJson,
sellRate: number,
) => Promise<TransferCalculation>;
@@ -43,50 +44,74 @@ type CashoutEstimators = {
estimateByDebit: EstimatorFunction;
};
+export function useConversionInfo() {
+ const { api, config } = useBankCoreApiContext()
+
+ async function fetcher() {
+ return await api.getConversionInfoAPI().getConfig()
+ }
+ const { data, error } = useSWR<TalerBankConversionResultByMethod<"getConfig">, TalerHttpError>(
+ !config.allow_conversion ? undefined : ["getConversionInfoAPI"], fetcher, {
+ refreshInterval: 0,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ errorRetryCount: 0,
+ errorRetryInterval: 1,
+ shouldRetryOnError: false,
+ keepPreviousData: true,
+ });
+
+ if (data) return data
+ if (error) return error;
+ return undefined;
+
+}
+
export function useEstimator(): CashoutEstimators {
const { state } = useBackendState();
const { api } = useBankCoreApiContext();
return {
- estimateByCredit: async (amount, fee, rate) => {
- const resp = await api.getCashoutRate({
- credit: amount
- });
- if (resp.type === "fail") {
- // can't happen
- // not-supported: it should not be able to call this function
- // wrong-calculation: we are using just one parameter
- throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint)
- }
- const credit = amount;
- const _credit = { ...credit, currency: fee.currency };
- const beforeFee = Amounts.sub(_credit, fee).amount;
+ estimateByCredit: async (fiatAmount, regionalCurrency, fee, rate) => {
+ // const resp = await api.getConversionInfoAPI().getCashoutRate({
+ // credit: amount
+ // });
+ // if (resp.type === "fail") {
+ // // can't happen
+ // // not-supported: it should not be able to call this function
+ // // wrong-calculation: we are using just one parameter
+ // throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint)
+ // }
+ const credit = fiatAmount;
+ const beforeFee = Amounts.sub(credit, fee).amount;
+
+ // const debit = Amounts.parseOrThrow(resp.body.amount_debit);
+ //FIXME: remove this when endpoint works
+ const debit = Amounts.add(
+ Amounts.zeroOfCurrency(regionalCurrency),
+ beforeFee
+ ).amount;
- const debit = Amounts.parseOrThrow(resp.body.amount_debit);
return {
debit,
beforeFee,
credit,
};
},
- estimateByDebit: async (amount, fee, rate) => {
- const zeroBalance = Amounts.zeroOfCurrency(fee.currency);
- const zeroFiat = Amounts.zeroOfCurrency(fee.currency);
- const zeroCalc = {
- debit: zeroBalance,
- credit: zeroFiat,
- beforeFee: zeroBalance,
- };
- const resp = await api.getCashoutRate({ debit: amount });
- if (resp.type === "fail") {
- // can't happen
- // not-supported: it should not be able to call this function
- // wrong-calculation: we are using just one parameter
- throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint)
- }
- const credit = Amounts.parseOrThrow(resp.body.amount_credit);
- const _credit = { ...credit, currency: fee.currency };
- const debit = amount;
- const beforeFee = Amounts.sub(_credit, fee).amount;
+ estimateByDebit: async (regionalAmount, fiatCurrency, fee, rate) => {
+ // const resp = await api.getConversionInfoAPI().getCashoutRate({ debit: amount });
+ // if (resp.type === "fail") {
+ // // can't happen
+ // // not-supported: it should not be able to call this function
+ // // wrong-calculation: we are using just one parameter
+ // throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint)
+ // }
+ // const credit = Amounts.parseOrThrow(resp.body.amount_credit);
+ const debit = regionalAmount;
+ const _credit = Amounts.parseOrThrow(regionalAmount);
+ const beforeFee = { ..._credit, currency: fiatCurrency };
+ const credit = Amounts.sub(beforeFee, fee).amount;
return {
debit,
beforeFee,
@@ -178,7 +203,7 @@ export function useCashouts(account: string) {
}
const { data, error } = useSWR<OperationOk<{ cashouts: CashoutWithId[] }> | TalerCoreBankErrorsByMethod<"getAccountCashouts">, TalerHttpError>(
- !config.have_cashout ? false : [account, token, "getAccountCashouts"], fetcher, {
+ !config.allow_conversion ? false : [account, token, "getAccountCashouts"], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -280,7 +305,7 @@ export function useLastMonitorInfo(time: Date, timeframe: TalerCorebankApi.Monit
}
}
- const previous: TalerCoreBankResultByMethod<"getMonitor"> = {
+ const previous: TalerCoreBankResultByMethod<"getMonitor"> = {
type: "ok" as const,
body: {
type: "with-conversions" as const,
@@ -304,7 +329,7 @@ export function useLastMonitorInfo(time: Date, timeframe: TalerCorebankApi.Monit
}
const { data, error } = useSWR<LastMonitor, TalerHttpError>(
- config.have_cashout || true ? ["useLastMonitorInfo"] : false, fetcher, {
+ config.allow_conversion || true ? ["useLastMonitorInfo"] : false, fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index 1e656b3ba..bd48ca680 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -73,7 +73,7 @@ const defaultSettings: Settings = {
showDebugInfo: false,
};
-const DEMOBANK_SETTINGS_KEY = buildStorageKey(
+const BANK_SETTINGS_KEY = buildStorageKey(
"bank-settings",
codecForSettings(),
);
@@ -83,7 +83,7 @@ export function useSettings(): [
<T extends keyof Settings>(key: T, value: Settings[T]) => void,
] {
const { value, update } = useLocalStorage(
- DEMOBANK_SETTINGS_KEY,
+ BANK_SETTINGS_KEY,
defaultSettings,
);
diff --git a/packages/demobank-ui/src/index.html b/packages/demobank-ui/src/index.html
index 315985648..f702f30ea 100644
--- a/packages/demobank-ui/src/index.html
+++ b/packages/demobank-ui/src/index.html
@@ -28,10 +28,10 @@
<link rel="icon"
href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" />
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
- <title>Demobank</title>
+ <title>Bank</title>
<!-- Optional customization script. -->
- <script src="demobank-ui-settings.js"></script>
- <!-- Entry point for the demobank SPA. -->
+ <script src="bank-ui-settings.js"></script>
+ <!-- Entry point for the bank SPA. -->
<script type="module" src="index.js"></script>
<link rel="stylesheet" href="index.css" />
</head>
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index 70d8cb4f4..f0baae3a3 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -45,6 +45,7 @@ export function BankFrame({
if (error) {
const desc = (error instanceof Error ? error.stack : String(error)) as TranslatedString
if (error instanceof Error) {
+ console.log(error)
notifyException(i18n.str`Internal error, please report.`, error)
} else {
notifyError(i18n.str`Internal error, please report.`, String(error) as TranslatedString)
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx
index 916a2bd98..e7db566ea 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -147,59 +147,6 @@ export function NeedConfirmationView({ error, onAbort: doAbort, onConfirm: doCon
<h3 class="text-base font-semibold text-gray-900">
<i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
</h3>
- <div class="mt-2 max-w-xl text-sm text-gray-500">
- <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-4 sm:gap-x-3">
-
- <label class={"relative sm:col-span-2 flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-noneborder-indigo-600 ring-2 ring-indigo-600"}>
- <input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" />
- <span class="flex flex-1">
- <span class="flex flex-col">
- <span id="project-type-0-label" class="block text-sm font-medium text-gray-900 ">
- <i18n.Translate>challenge response test</i18n.Translate>
- </span>
- </span>
- </span>
- <svg class="h-5 w-5 text-indigo-600" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
- </svg>
- </label>
-
-
- <label class="relative flex cursor-pointer rounded-lg border bg-gray-100 p-4 shadow-sm focus:outline-none border-gray-300">
- <input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" />
- <span class="flex flex-1">
- <span class="flex flex-col">
- <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
- <i18n.Translate>using SMS</i18n.Translate>
- </span>
- <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
- <i18n.Translate>not available</i18n.Translate>
- </span>
- </span>
- </span>
- <svg class="h-5 w-5 text-indigo-600 hidden" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
- </svg>
- </label>
-
- <label class="relative flex cursor-pointer rounded-lg border bg-gray-100 p-4 shadow-sm focus:outline-none border-gray-300">
- <input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" />
- <span class="flex flex-1">
- <span class="flex flex-col">
- <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
- <i18n.Translate>one time password</i18n.Translate>
- </span>
- <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
- <i18n.Translate>not available</i18n.Translate>
- </span>
- </span>
- </span>
- <svg class="h-5 w-5 text-indigo-600 hidden" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
- </svg>
- </label>
- </div>
- </div>
<div class="mt-3 text-sm leading-6">
<form
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 4f7b25f6d..e9d254332 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -458,8 +458,8 @@ export function InputAmount(
if (!onChange) return;
const l = e.currentTarget.value.length
const sep_pos = e.currentTarget.value.indexOf(FRAC_SEPARATOR)
- if (sep_pos !== -1 && l - sep_pos - 1 > config.currency.num_fractional_input_digits) {
- e.currentTarget.value = e.currentTarget.value.substring(0, sep_pos + config.currency.num_fractional_input_digits + 1)
+ if (sep_pos !== -1 && l - sep_pos - 1 > config.currency_specification.num_fractional_input_digits) {
+ e.currentTarget.value = e.currentTarget.value.substring(0, sep_pos + config.currency_specification.num_fractional_input_digits + 1)
}
onChange(e.currentTarget.value);
}}
@@ -470,21 +470,21 @@ export function InputAmount(
);
}
-export function RenderAmount({ value, negative, noCurrency }: { value: AmountJson, negative?: boolean, noCurrency?: boolean }): VNode {
+export function RenderAmount({ value, negative }: { value: AmountJson, negative?: boolean }): VNode {
const { config } = useBankCoreApiContext()
const str = Amounts.stringifyValue(value)
const sep_pos = str.indexOf(FRAC_SEPARATOR)
- if (sep_pos !== -1 && str.length - sep_pos - 1 > config.currency.num_fractional_normal_digits) {
- const limit = sep_pos + config.currency.num_fractional_normal_digits + 1
+ if (sep_pos !== -1 && str.length - sep_pos - 1 > config.currency_specification.num_fractional_normal_digits) {
+ const limit = sep_pos + config.currency_specification.num_fractional_normal_digits + 1
const normal = str.substring(0, limit)
const small = str.substring(limit)
- return <span class="whitespace-nowrap">
+ return <span data-negative={negative} class="whitespace-nowrap data-[negative=true]:bg-red-400">
{negative ? "-" : undefined}
- {noCurrency ? undefined : value.currency} {normal} <sup class="-ml-2">{small}</sup>
+ {value.currency} {normal} <sup class="-ml-1">{small}</sup>
</span>
}
return <span class="whitespace-nowrap">
{negative ? "-" : undefined}
- {noCurrency ? undefined : value.currency} {str}
+ {value.currency} {str}
</span>
} \ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
index 20a1ececd..1a4b4b865 100644
--- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx
+++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
@@ -29,7 +29,7 @@ export function ProfileNavigation({ current }: { current: "details" | "credentia
}}>
<option value="details" selected={current == "details"}><i18n.Translate>Details</i18n.Translate></option>
<option value="credentials" selected={current == "credentials"}><i18n.Translate>Credentials</i18n.Translate></option>
- {config.have_cashout ?
+ {config.allow_conversion ?
<option value="cashouts" selected={current == "cashouts"}><i18n.Translate>Cashouts</i18n.Translate></option>
: undefined}
</select>
@@ -44,7 +44,7 @@ export function ProfileNavigation({ current }: { current: "details" | "credentia
<span><i18n.Translate>Credentials</i18n.Translate></span>
<span aria-hidden="true" data-selected={current == "credentials"} class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
</a>
- {config.have_cashout ?
+ {config.allow_conversion ?
<a href="#/my-cashouts" data-selected={current == "cashouts"} class="rounded-r-lg text-gray-500 hover:text-gray-700 data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 focus:z-10">
<span>Cashouts</span>
<span aria-hidden="true" data-selected={current == "cashouts"} class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 0b339030e..7fec76d2f 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -174,59 +174,6 @@ export function WithdrawalConfirmationQuestion({
<h3 class="text-base font-semibold text-gray-900">
<i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
</h3>
- <div class="mt-2 max-w-xl text-sm text-gray-500">
- <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-3 sm:gap-x-3">
-
- <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-noneborder-indigo-600 ring-2 ring-indigo-600"}>
- <input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" />
- <span class="flex flex-1">
- <span class="flex flex-col">
- <span id="project-type-0-label" class="block text-sm font-medium text-gray-900 ">
- <i18n.Translate>challenge response test</i18n.Translate>
- </span>
- </span>
- </span>
- <svg class="h-5 w-5 text-indigo-600" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
- </svg>
- </label>
-
-
- <label class="relative flex cursor-pointer rounded-lg border bg-gray-100 p-4 shadow-sm focus:outline-none border-gray-300">
- <input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" />
- <span class="flex flex-1">
- <span class="flex flex-col">
- <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
- <i18n.Translate>using SMS</i18n.Translate>
- </span>
- <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
- <i18n.Translate>not available</i18n.Translate>
- </span>
- </span>
- </span>
- <svg class="h-5 w-5 text-indigo-600 hidden" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
- </svg>
- </label>
-
- <label class="relative flex cursor-pointer rounded-lg border bg-gray-100 p-4 shadow-sm focus:outline-none border-gray-300">
- <input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" />
- <span class="flex flex-1">
- <span class="flex flex-col">
- <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
- <i18n.Translate>one time password</i18n.Translate>
- </span>
- <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
- <i18n.Translate>not available</i18n.Translate>
- </span>
- </span>
- </span>
- <svg class="h-5 w-5 text-indigo-600 hidden" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
- </svg>
- </label>
- </div>
- </div>
<div class="mt-3 text-sm leading-6">
<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index 7311d826e..4fcc32484 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -3,7 +3,7 @@ import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js";
import { useEffect, useRef, useState } from "preact/hooks";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { PaytoString, TalerCorebankApi, buildPayto, parsePaytoUri } from "@gnu-taler/taler-util";
+import { PaytoString, TalerCorebankApi, buildPayto, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { doAutoFocus } from "../PaytoWireTransferForm.js";
import { CopyButton } from "@gnu-taler/web-util/browser";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
@@ -80,7 +80,16 @@ export function AccountForm({
});
setErrors(errors);
setForm(newForm);
- onChange(errors === undefined ? (newForm as any) : undefined);
+ if (errors) {
+ onChange(undefined)
+ } else {
+ const cashout = buildPayto("iban", newForm.cashout_payto_uri!, undefined)
+ const account: AccountFormData = {
+ ...newForm as any,
+ cashout_payto_uri: stringifyPaytoUri(cashout)
+ }
+ onChange(account);
+ }
}
return (
@@ -296,6 +305,13 @@ function initializeFromTemplate(
if (typeof initial.contact_data === "undefined") {
initial.contact_data = emptyContact;
}
+ if (initial.cashout_payto_uri) {
+ const ac = parsePaytoUri(initial.cashout_payto_uri)
+ if (ac?.isKnown && ac.targetType === "iban") {
+ // we are using the cashout field for the iban number
+ initial.cashout_payto_uri = ac.targetPath as any
+ }
+ }
const result: WithIntermediate<AccountFormData> = initial as any // FIXME: check types
result.username = username
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx
index 2aefde715..8c018120d 100644
--- a/packages/demobank-ui/src/pages/admin/AccountList.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -119,7 +119,7 @@ export function AccountList({ onRemoveAccount, onShowAccountDetails, onUpdateAcc
change password
</a>
<br />
- {config.have_cashout ?
+ {config.allow_conversion ?
<Fragment>
<a href="#" class="text-indigo-600 hover:text-indigo-900" onClick={(e) => {
diff --git a/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx b/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx
index 466dc1a4b..3aefb32af 100644
--- a/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx
@@ -3,6 +3,8 @@ import { Fragment, VNode, h } from "preact";
import { Cashouts } from "../../components/Cashouts/index.js";
import { useBackendState } from "../../hooks/backend.js";
import { ProfileNavigation } from "../ProfileNavigation.js";
+import { CreateNewAccount } from "./CreateNewAccount.js";
+import { CreateCashout } from "../business/CreateCashout.js";
interface Props {
account: string,
@@ -27,20 +29,22 @@ export function CashoutListForAccount({ account, onSelected, onClose }: Props):
<i18n.Translate>Cashout for account {account}</i18n.Translate>
</h1>
}
+
+ <CreateCashout onCancel={() => {}} onComplete={() => {}} account={account} />
<Cashouts
account={account}
onSelected={onSelected}
/>
<p>
- <input
- class="pure-button"
- type="submit"
- value={i18n.str`Close`}
+ <button
+ class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8"
onClick={async (e) => {
e.preventDefault();
onClose();
}}
- />
+ >
+ {i18n.str`Close`}
+ </button>
</p>
</Fragment>
}
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 525a170bc..771004ec6 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -36,6 +36,7 @@ import { useBankCoreApiContext } from "../../context/config.js";
import { useAccountDetails } from "../../hooks/access.js";
import { useBackendState } from "../../hooks/backend.js";
import {
+ useConversionInfo,
useEstimator
} from "../../hooks/circuit.js";
import {
@@ -43,11 +44,12 @@ import {
undefinedIfEmpty
} from "../../utils.js";
import { LoginForm } from "../LoginForm.js";
-import { InputAmount } from "../PaytoWireTransferForm.js";
+import { InputAmount, RenderAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
interface Props {
account: string;
+ focus?: boolean,
onComplete: (id: string) => void;
onCancel: () => void;
}
@@ -66,6 +68,7 @@ type ErrorFrom<T> = {
export function CreateCashout({
account: accountName,
onComplete,
+ focus,
onCancel,
}: Props): VNode {
const { i18n } = useTranslationContext();
@@ -77,20 +80,15 @@ export function CreateCashout({
const { state } = useBackendState()
const creds = state.status !== "loggedIn" ? undefined : state
const { api, config } = useBankCoreApiContext()
- const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
+ const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, amount:"2" });
const [notification, notify, handleError] = useLocalNotification()
+ const info = useConversionInfo();
- if (!config.have_cashout) {
+ if (!config.allow_conversion) {
return <Attention type="warning" title={i18n.str`Unable to create a cashout`} onClose={onCancel}>
<i18n.Translate>The bank configuration does not support cashout operations.</i18n.Translate>
</Attention>
}
- if (!config.fiat_currency) {
- return <Attention type="warning" title={i18n.str`Unable to create a cashout`} onClose={onCancel}>
- <i18n.Translate>The bank configuration support cashout operations but there is no fiat currency.</i18n.Translate>
- </Attention>
- }
-
if (!resultAccount) {
return <Loading />
}
@@ -104,15 +102,13 @@ export function CreateCashout({
default: assertUnreachable(resultAccount)
}
}
+ if (!info) {
+ return <Loading />
+ }
- // if (resultRatios.type === "fail") {
- // switch (resultRatios.case) {
- // case "not-supported": return <div>cashout operations are not supported</div>
- // default: assertUnreachable(resultRatios.case)
- // }
- // }
-
- // const ratio = resultRatios.body
+ if (info instanceof TalerError) {
+ return <ErrorLoading error={info} />
+ }
const account = {
balance: Amounts.parseOrThrow(resultAccount.body.balance.amount),
@@ -120,37 +116,46 @@ export function CreateCashout({
debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold)
}
- const zero = Amounts.zeroOfCurrency(account.balance.currency);
+ const {fiat_currency, regional_currency, cashout_ratio, cashout_fee} = info.body
+ const regionalZero = Amounts.zeroOfCurrency(regional_currency);
+ const fiatZero = Amounts.zeroOfCurrency(fiat_currency);
const limit = account.balanceIsDebit
? Amounts.sub(account.debitThreshold, account.balance).amount
: Amounts.add(account.balance, account.debitThreshold).amount;
- const zeroCalc = { debit: zero, credit: zero, beforeFee: zero };
+ const zeroCalc = { debit: regionalZero, credit: fiatZero, beforeFee: regionalZero };
const [calc, setCalc] = useState(zeroCalc);
- const sellRate = config.conversion_info?.sell_at_ratio;
- const sellFee = !config.conversion_info?.sell_out_fee
- ? zero
- : Amounts.parseOrThrow(
- `${account.balance.currency}:${config.conversion_info.sell_out_fee}`,
- );
+ const sellRate = Number.parseFloat(cashout_ratio);
+ const sellFee = !cashout_fee
+ ? fiatZero
+ : Amounts.parseOrThrow(cashout_fee);
- if (sellRate === undefined || sellRate < 0) return <div>error rate</div>;
+ if (sellRate === undefined || sellRate < 0) return <div>error rate d
+ <pre>
+ {JSON.stringify(info.body, undefined, 2)}
+ </pre>
+ </div>;
const safeSellRate = sellRate
- const amount = Amounts.parseOrThrow(
- `${!form.isDebit ? config.fiat_currency.name : account.balance.currency}:${!form.amount ? "0" : form.amount
- }`,
+ /**
+ * can be in regional currency or fiat currency
+ * depending on the isDebit flag
+ */
+ const inputAmount = Amounts.parseOrThrow(
+ `${form.isDebit ? regional_currency : fiat_currency}:${!form.amount ? "0" : form.amount}`,
);
useEffect(() => {
async function doAsync() {
await handleError(async () => {
- const resp = await (form.isDebit ?
- calculateFromDebit(amount, sellFee, safeSellRate) :
- calculateFromCredit(amount, sellFee, safeSellRate));
- setCalc(resp)
+ if (Amounts.isNonZero(inputAmount)) {
+ const resp = await (form.isDebit ?
+ calculateFromDebit(inputAmount, fiat_currency, sellFee, safeSellRate) :
+ calculateFromCredit(inputAmount, regional_currency, sellFee, safeSellRate));
+ setCalc(resp)
+ }
})
}
doAsync()
@@ -164,256 +169,202 @@ export function CreateCashout({
const errors = undefinedIfEmpty<ErrorFrom<typeof form>>({
amount: !form.amount
? i18n.str`required`
- : !amount
+ : !inputAmount
? i18n.str`could not be parsed`
: Amounts.cmp(limit, calc.debit) === -1
? i18n.str`balance is not enough`
- : Amounts.cmp(calc.beforeFee, sellFee) === -1
+ : Amounts.cmp(calc.credit, sellFee) === -1
? i18n.str`the total amount to transfer does not cover the fees`
: Amounts.isZero(calc.credit)
? i18n.str`the total transfer at destination will be zero`
: undefined,
channel: !form.channel ? i18n.str`required` : undefined,
});
+ const trimmedAmountStr = form.amount?.trim();
return (
<div>
<LocalNotificationBanner notification={notification} />
- <h1>New cashout</h1>
- <form class="pure-form">
- <fieldset>
- <label>{i18n.str`Subject`}</label>
- <input
- value={form.subject ?? ""}
- onChange={(e) => {
- form.subject = e.currentTarget.value;
- updateForm(structuredClone(form));
- }}
- />
- <ShowInputErrorLabel
- message={errors?.subject}
- isDirty={form.subject !== undefined}
- />
- </fieldset>
- <fieldset>
- <label for="amount">
- {form.isDebit
- ? i18n.str`Amount to send`
- : i18n.str`Amount to receive`}
-
- </label>
- <div style={{ display: "flex" }}>
- <InputAmount
- name="amount"
- currency={amount.currency}
- value={form.amount}
- onChange={(v) => {
- form.amount = v;
- updateForm(structuredClone(form));
- }}
- error={errors?.amount}
- />
- <label class="toggle" style={{ marginLeft: 4, marginTop: 0 }}>
- <input
- class="toggle-checkbox"
- type="checkbox"
- name="asd"
- onChange={(e): void => {
- form.isDebit = !form.isDebit;
- updateForm(structuredClone(form));
- }}
- />
- <div class="toggle-switch"></div>
- </label>
+
+ <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
+
+ <section class="mt-4 rounded-sm px-4 py-6 p-8 ">
+ <h2 id="summary-heading" class="font-medium text-lg">Cashout</h2>
+
+ <dl class="mt-4 space-y-4">
+ <div class="justify-between items-center flex">
+ <dt class="text-sm text-gray-600">Convertion rate</dt>
+ <dd class="text-sm text-gray-900">{sellRate}</dd>
+ </div>
+
+
+ <div class="flex items-center justify-between border-t-2 afu pt-4">
+ <dt class="flex items-center text-sm text-gray-600">
+ <span>Current balance</span>
+ </dt>
+ <dd class="text-sm text-gray-900">
+ <RenderAmount value={account.balance} />
+ </dd>
+ </div>
+ <div class="flex items-center justify-between border-t-2 afu pt-4">
+ <dt class="flex items-center text-sm text-gray-600">
+ <span>Cashout fee</span>
+ </dt>
+ <dd class="text-sm text-gray-900">
+ <RenderAmount value={sellFee} />
+ </dd>
+ </div>
+ </dl>
+
+ </section>
+ <form
+ class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
+ autoCapitalize="none"
+ autoCorrect="off"
+ onSubmit={e => {
+ e.preventDefault()
+ }}
+ >
+ <div class="px-4 py-6 sm:p-8">
+ <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
+ {/* subject */}
+
+ <div class="sm:col-span-5">
+ <label
+ class="block text-sm font-medium leading-6 text-gray-900"
+ for="subject"
+ >
+ {i18n.str`Subject`}
+ </label>
+ <div class="mt-2">
+ <input
+ ref={focus ? doAutoFocus : undefined}
+ type="text"
+ class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+ name="subject"
+ id="subject"
+ data-error={!!errors?.subject && form.subject !== undefined}
+ value={form.subject ?? ""}
+ onChange={(e) => {
+ form.subject = e.currentTarget.value;
+ updateForm(structuredClone(form));
+ }}
+ autocomplete="off"
+ />
+ <ShowInputErrorLabel
+ message={errors?.subject}
+ isDirty={form.subject !== undefined}
+ />
+ </div>
+
+ </div>
+
+ {/* amount */}
+ <div class="sm:col-span-5">
+ <label
+ class="block text-sm font-medium leading-6 text-gray-900"
+ for="amount"
+ >
+ {form.isDebit
+ ? i18n.str`Amount to send`
+ : i18n.str`Amount to receive`}
+ </label>
+ <div class="mt-2">
+ <InputAmount
+ name="amount"
+ left
+ currency={limit.currency}
+ value={trimmedAmountStr}
+ onChange={(value) => {
+ form.amount = value;
+ updateForm(structuredClone(form));
+ }}
+ />
+ <ShowInputErrorLabel
+ message={errors?.amount}
+ isDirty={form.amount !== undefined}
+ />
+ </div>
+
+ </div>
+
+ {Amounts.isZero(calc.credit) ? undefined : (
+ <div class="sm:col-span-5">
+ <dl class="mt-4 space-y-4">
+
+ <div class="justify-between items-center flex">
+ <dt class="text-sm text-gray-600">Total cost</dt>
+ <dd class="text-sm text-gray-900">
+ <RenderAmount value={calc.debit} negative />
+ </dd>
+ </div>
+
+
+ <div class="flex items-center justify-between border-t-2 afu pt-4">
+ <dt class="flex items-center text-sm text-gray-600">
+ <span>Balance after</span>
+ {/* <a href="#" class="ml-2 shrink-0 text-gray-400 bkx">
+ <span class="sr-only">Learn more about how shipping is calculated</span>
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"
+ class="w-5 h-5"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM8.94 6.94a.75.75 0 11-1.061-1.061 3 3 0 112.871 5.026v.345a.75.75 0 01-1.5 0v-.5c0-.72.57-1.172 1.081-1.287A1.5 1.5 0 108.94 6.94zM10 15a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"></path></svg>
+ </a> */}
+ </dt>
+ <dd class="text-sm text-gray-900">
+ <RenderAmount value={balanceAfter} />
+ </dd>
+ </div>
+ {Amounts.isZero(sellFee) || Amounts.isZero(calc.beforeFee) ? undefined : (
+ <div class="flex items-center justify-between border-t-2 afu pt-4">
+ <dt class="flex items-center text-sm text-gray-600">
+ <span>Amount after conversion</span>
+ {/* <a href="#" class="ml-2 shrink-0 text-gray-400 bkx">
+ <span class="sr-only">Learn more about how shipping is calculated</span>
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"
+ class="w-5 h-5"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM8.94 6.94a.75.75 0 11-1.061-1.061 3 3 0 112.871 5.026v.345a.75.75 0 01-1.5 0v-.5c0-.72.57-1.172 1.081-1.287A1.5 1.5 0 108.94 6.94zM10 15a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"></path></svg>
+ </a> */}
+ </dt>
+ <dd class="text-sm text-gray-900">
+ <RenderAmount value={calc.beforeFee} />
+ </dd>
+ </div>
+ )}
+ <div class="flex justify-between items-center border-t-2 afu pt-4">
+ <dt class="text-lg text-gray-900 font-medium">Total cashout transfer</dt>
+ <dd class="text-lg text-gray-900 font-medium">
+ <RenderAmount value={calc.credit} />
+ </dd>
+ </div>
+ </dl>
+ </div>
+ )}
+
+ {/* channel */}
+ </div>
</div>
- </fieldset>
- <fieldset>
- <label>{i18n.str`Conversion rate`}</label>
- <input value={sellRate} disabled />
- </fieldset>
- <fieldset>
- <label for="balance-now">{i18n.str`Balance now`}</label>
- <InputAmount
- name="banace-now"
- currency={account.balance.currency}
- value={Amounts.stringifyValue(account.balance)}
- />
- </fieldset>
- <fieldset>
- <label for="total-cost"
- style={{ fontWeight: "bold", color: "red" }}
- >{i18n.str`Total cost`}</label>
- <InputAmount
- name="total-cost"
- currency={account.balance.currency}
- value={Amounts.stringifyValue(calc.debit)}
- />
- </fieldset>
- <fieldset>
- <label for="balance-after">{i18n.str`Balance after`}</label>
- <InputAmount
- name="balance-after"
- currency={account.balance.currency}
- value={balanceAfter ? Amounts.stringifyValue(balanceAfter) : ""}
- />
- </fieldset>{" "}
- {Amounts.isZero(sellFee) ? undefined : (
- <Fragment>
- <fieldset>
- <label for="amount-conversiojn">{i18n.str`Amount after conversion`}</label>
- <InputAmount
- name="amount-conversion"
- currency={config.fiat_currency.name}
- value={Amounts.stringifyValue(calc.beforeFee)}
- />
- </fieldset>
-
- <fieldset>
- <label form="cashout-fee">{i18n.str`Cashout fee`}</label>
- <InputAmount
- name="cashout-fee"
- currency={config.fiat_currency.name}
- value={Amounts.stringifyValue(sellFee)}
- />
- </fieldset>
- </Fragment>
- )}
- <fieldset>
- <label for="total"
- style={{ fontWeight: "bold", color: "green" }}
- >{i18n.str`Total cashout transfer`}</label>
- <InputAmount
- name="total"
- currency={config.fiat_currency.name}
- value={Amounts.stringifyValue(calc.credit)}
- />
- </fieldset>
- <fieldset>
- <label>{i18n.str`Confirmation channel`}</label>
-
- <div class="channel">
- <input
- class={
- "pure-button content " +
- (form.channel === TanChannel.EMAIL
- ? "pure-button-primary"
- : "pure-button-secondary")
- }
- type="submit"
- value={i18n.str`Email`}
- onClick={async (e) => {
- e.preventDefault();
- form.channel = TanChannel.EMAIL;
- updateForm(structuredClone(form));
- }}
- />
- <input
- class={
- "pure-button content " +
- (form.channel === TanChannel.SMS
- ? "pure-button-primary"
- : "pure-button-secondary")
- }
- type="submit"
- value={i18n.str`SMS`}
- onClick={async (e) => {
- e.preventDefault();
- form.channel = TanChannel.SMS;
- updateForm(structuredClone(form));
+
+ <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
+ {onCancel ?
+ <button type="button" class="text-sm font-semibold leading-6 text-gray-900"
+ onClick={onCancel}
+ >
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
+ : <div />
+ }
+ <button type="submit"
+ class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+ disabled={!!errors}
+ onClick={(e) => {
+ e.preventDefault()
+ // doChangePassword()
}}
- />
+ >
+ <i18n.Translate>Change</i18n.Translate>
+ </button>
</div>
- <ShowInputErrorLabel
- message={errors?.channel}
- isDirty={form.channel !== undefined}
- />
- </fieldset>
- <br />
- <div style={{ display: "flex", justifyContent: "space-between" }}>
- <button
- class="pure-button pure-button-secondary btn-cancel"
- onClick={(e) => {
- e.preventDefault();
- onCancel();
- }}
- >
- {i18n.str`Cancel`}
- </button>
-
- <button
- class="pure-button pure-button-primary btn-register"
- type="submit"
- disabled={!!errors}
- onClick={async (e) => {
- e.preventDefault();
-
- if (errors || !creds) return;
- await handleError(async () => {
- const request_uid = encodeCrock(getRandomBytes(16))
- const resp = await api.createCashout(creds, {
- request_uid,
- amount_credit: Amounts.stringify(calc.credit),
- amount_debit: Amounts.stringify(calc.debit),
- subject: form.subject,
- tan_channel: form.channel,
- });
- if (resp.type === "ok") {
- mutate(() => true)// clean cashout list
- onComplete(resp.body.cashout_id);
- } else {
- switch (resp.case) {
- case "incorrect-exchange-rate": return notify({
- type: "error",
- title: i18n.str`The exchange rate was incorrectly applied`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case "no-contact-info": return notify({
- type: "error",
- title: i18n.str`Need a contact data where to send the TAN`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case "no-enough-balance": return notify({
- type: "error",
- title: i18n.str`The account does not have sufficient funds`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case "account-not-found": return notify({
- type: "error",
- title: i18n.str`Account not found`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case "cashout-not-supported": return notify({
- type: "error",
- title: i18n.str`The bank does not support cashout`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case "request-already-used": return notify({
- type: "error",
- title: i18n.str`Duplicated request found, try again.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case "tan-failed": return notify({
- type: "error",
- title: i18n.str`Server couldn't send the confirmation request.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- default: assertUnreachable(resp)
- }
- }
- })
- }}
- >
- {i18n.str`Create`}
- </button>
- </div>
- </form>
+ </form>
+ </div>
+
</div>
);
}
diff --git a/packages/demobank-ui/src/settings.ts b/packages/demobank-ui/src/settings.ts
index 44a016de6..f17d1d511 100644
--- a/packages/demobank-ui/src/settings.ts
+++ b/packages/demobank-ui/src/settings.ts
@@ -26,7 +26,7 @@ export interface BankUiSettings {
}
/**
- * Global settings for the demobank UI.
+ * Global settings for the bank UI.
*/
const defaultSettings: BankUiSettings = {
backendBaseURL: "https://bank.demo.taler.net/demobanks/default/",
@@ -46,6 +46,6 @@ const defaultSettings: BankUiSettings = {
};
export const bankUiSettings: BankUiSettings =
- "talerDemobankSettings" in globalThis
- ? (globalThis as any).talerDemobankSettings
+ "talerBankSettings" in globalThis
+ ? (globalThis as any).talerBankSettings
: defaultSettings;
diff --git a/packages/demobank-ui/src/stories.test.ts b/packages/demobank-ui/src/stories.test.ts
index 09de227b8..fac363e5b 100644
--- a/packages/demobank-ui/src/stories.test.ts
+++ b/packages/demobank-ui/src/stories.test.ts
@@ -65,7 +65,9 @@ function DefaultTestingContext({
name: "libeufin-bank",
allow_deletions: true,
allow_registrations: true,
- currency: {
+ allow_conversion: true,
+ currency: "ASR",
+ currency_specification: {
name: "ARS",
alt_unit_names: {},
num_fractional_input_digits: 2,