[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-wallet-core] branch master updated (9ba6a59d1 -> 6f7512be7)
From: |
gnunet |
Subject: |
[taler-wallet-core] branch master updated (9ba6a59d1 -> 6f7512be7) |
Date: |
Fri, 24 Nov 2023 18:43:24 +0100 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a change to branch master
in repository wallet-core.
from 9ba6a59d1 using monitor api
new 3df64dd45 update to the latest gana
new 6f7512be7 show cashout details
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
packages/demobank-ui/src/Routing.tsx | 2 +-
.../demobank-ui/src/components/Cashouts/views.tsx | 57 ++-
packages/demobank-ui/src/hooks/circuit.ts | 41 +-
.../demobank-ui/src/pages/AccountPage/views.tsx | 17 +
packages/demobank-ui/src/pages/BankFrame.tsx | 1 -
packages/demobank-ui/src/pages/PaymentOptions.tsx | 2 +-
.../src/pages/PaytoWireTransferForm.tsx | 2 +-
.../demobank-ui/src/pages/ProfileNavigation.tsx | 4 +-
.../src/pages/account/ShowAccountDetails.tsx | 3 +-
.../src/pages/account/UpdateAccountPassword.tsx | 2 +-
.../demobank-ui/src/pages/admin/AccountForm.tsx | 93 +++--
.../src/pages/business/CreateCashout.tsx | 52 ++-
.../src/pages/business/ShowCashoutDetails.tsx | 431 ++++++++++++---------
packages/taler-util/src/http-client/bank-core.ts | 2 +-
packages/taler-util/src/taler-error-codes.ts | 80 ++++
15 files changed, 503 insertions(+), 286 deletions(-)
diff --git a/packages/demobank-ui/src/Routing.tsx
b/packages/demobank-ui/src/Routing.tsx
index 733d55a0f..8ed66d4cf 100644
--- a/packages/demobank-ui/src/Routing.tsx
+++ b/packages/demobank-ui/src/Routing.tsx
@@ -258,7 +258,7 @@ export function Routing(): VNode {
<ShowCashoutDetails
id={cid}
onCancel={() => {
- route("/account");
+ route("/my-cashouts");
}}
/>
)}
diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx
b/packages/demobank-ui/src/components/Cashouts/views.tsx
index 651a7a034..59bb4a16b 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -93,7 +93,7 @@ export function ReadyView({ cashouts, onSelected }:
State.Ready): VNode {
{Object.entries(txByDate).map(([date, txs], idx) => {
return <Fragment key={idx}>
<tr class="border-t border-gray-200">
- <th colSpan={4} scope="colgroup" class="bg-gray-50 py-2 pl-4
pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-3">
+ <th colSpan={6} scope="colgroup" class="bg-gray-50 py-2 pl-4
pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-3">
{date}
</th>
</tr>
@@ -102,9 +102,12 @@ export function ReadyView({ cashouts, onSelected }:
State.Ready): VNode {
const confirmationTime = item.confirmation_time
? item.confirmation_time.t_s === "never" ? i18n.str`never`
: format(item.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss")
: "-"
- return (<tr key={idx} class="border-b border-gray-200
last:border-none">
+ return (<tr key={idx} class="border-b border-gray-200
hover:bg-gray-200 last:border-none">
- <td class="relative py-2 pl-2 pr-2 text-sm ">
+ <td onClick={(e) => {
+ e.preventDefault();
+ onSelected(item.id);
+ }} class="relative py-2 pl-2 pr-2 text-sm ">
<div class="font-medium
text-gray-900">{creationTime}</div>
{/* <dl class="font-normal sm:hidden">
<dt class="sr-only
sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt>
@@ -128,18 +131,28 @@ export function ReadyView({ cashouts, onSelected }:
State.Ready): VNode {
</dd>
</dl> */}
</td>
- <td class="hidden sm:table-cell px-3 py-3.5 text-sm
text-gray-500">{confirmationTime}</td>
- <td class="hidden sm:table-cell px-3 py-3.5 text-sm
text-red-600"><RenderAmount value={Amounts.parseOrThrow(item.amount_debit)}
spec={resp.body.regional_currency_specification} /></td>
- <td class="hidden sm:table-cell px-3 py-3.5 text-sm
text-green-600"><RenderAmount value={Amounts.parseOrThrow(item.amount_credit)}
spec={resp.body.fiat_currency_specification} /></td>
+ <td onClick={(e) => {
+ e.preventDefault();
+ onSelected(item.id);
+ }}class="hidden sm:table-cell px-3 py-3.5 text-sm
text-gray-500 cursor-pointer">{confirmationTime}</td>
+ <td onClick={(e) => {
+ e.preventDefault();
+ onSelected(item.id);
+ }}class="hidden sm:table-cell px-3 py-3.5 text-sm
text-red-600 cursor-pointer"><RenderAmount
value={Amounts.parseOrThrow(item.amount_debit)}
spec={resp.body.regional_currency_specification} /></td>
+ <td onClick={(e) => {
+ e.preventDefault();
+ onSelected(item.id);
+ }}class="hidden sm:table-cell px-3 py-3.5 text-sm
text-green-600 cursor-pointer"><RenderAmount
value={Amounts.parseOrThrow(item.amount_credit)}
spec={resp.body.fiat_currency_specification} /></td>
- <td class="hidden sm:table-cell px-3 py-3.5 text-sm
text-gray-500">{item.status}</td>
- <td class="hidden sm:table-cell px-3 py-3.5 text-sm
text-gray-500 break-all min-w-md">
- <a href="#" onClick={(e) => {
+ <td onClick={(e) => {
e.preventDefault();
onSelected(item.id);
- }}>
- {item.subject}
- </a>
+ }}class="hidden sm:table-cell px-3 py-3.5 text-sm
text-gray-500 cursor-pointer">{item.status}</td>
+ <td onClick={(e) => {
+ e.preventDefault();
+ onSelected(item.id);
+ }} class="hidden sm:table-cell px-3 py-3.5 text-sm
text-gray-500 break-all min-w-md">
+ {item.subject}
</td>
</tr>)
})}
@@ -150,27 +163,9 @@ export function ReadyView({ cashouts, onSelected }:
State.Ready): VNode {
</table>
- {/* <nav class="flex items-center justify-between border-t
border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination">
- <div class="flex flex-1 justify-between sm:justify-end">
- <button
- class="relative disabled:bg-gray-100 disabled:text-gray-500
inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold
text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50
focus-visible:outline-offset-0"
- disabled={!onPrev}
- onClick={onPrev}
- >
- <i18n.Translate>First page</i18n.Translate>
- </button>
- <button
- class="relative disabled:bg-gray-100 disabled:text-gray-500 ml-3
inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold
text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50
focus-visible:outline-offset-0"
- disabled={!onNext}
- onClick={onNext}
- >
- <i18n.Translate>Next</i18n.Translate>
- </button>
- </div>
- </nav> */}
+
</div>
</div>
);
- // }
}
diff --git a/packages/demobank-ui/src/hooks/circuit.ts
b/packages/demobank-ui/src/hooks/circuit.ts
index b483a5420..bb9f5801e 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,
TalerBankConversionResultByMethod, TalerCoreBankErrorsByMethod,
TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError }
from "@gnu-taler/taler-util";
+import { AccessToken, AmountJson, AmountString, Amounts, OperationOk,
TalerBankConversionResultByMethod, TalerCoreBankErrorsByMethod,
TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError,
opFixedSuccess } from "@gnu-taler/taler-util";
import _useSWR, { SWRHook } from "swr";
import { useBankCoreApiContext } from "../context/config.js";
import { assertUnreachable } from "../pages/WithdrawalOperationPage.js";
@@ -174,6 +174,43 @@ type CashoutWithId =
TalerCorebankApi.CashoutStatusResponse & { id: number }
function notUndefined(c: CashoutWithId | undefined): c is CashoutWithId {
return c !== undefined
}
+export function useOnePendingCashouts(account: string) {
+ const { state: credentials } = useBackendState();
+ const { api, config } = useBankCoreApiContext();
+ const token = credentials.status !== "loggedIn" ? undefined :
credentials.token
+
+ async function fetcher([username, token]: [string, AccessToken]) {
+ const list = await api.getAccountCashouts({ username, token })
+ if (list.type !== "ok") {
+ return list;
+ }
+ const pendingCashout = list.body.cashouts.find(c => c.status === "pending")
+ if (!pendingCashout) return opFixedSuccess(undefined)
+ const cashoutInfo = await api.getCashoutById({ username, token },
pendingCashout?.cashout_id)
+ if (cashoutInfo.type !== "ok") {
+ return cashoutInfo;
+ }
+ return opFixedSuccess({ ...cashoutInfo.body, id: pendingCashout.cashout_id
})
+ }
+
+ const { data, error } = useSWR<OperationOk<CashoutWithId | undefined> |
TalerCoreBankErrorsByMethod<"getAccountCashouts"> |
TalerCoreBankErrorsByMethod<"getCashoutById">, TalerHttpError>(
+ !config.allow_conversion ? undefined : [account, token,
"getAccountCashouts"], 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 useCashouts(account: string) {
const { state: credentials } = useBackendState();
const { api, config } = useBankCoreApiContext();
@@ -182,13 +219,11 @@ export function useCashouts(account: string) {
async function fetcher([username, token]: [string, AccessToken]) {
const list = await api.getAccountCashouts({ username, token })
if (list.type !== "ok") {
- console.error(list)
return list;
}
const all: Array<CashoutWithId | undefined> = await
Promise.all(list.body.cashouts.map(c => {
return api.getCashoutById({ username, token }, c.cashout_id).then(r => {
if (r.type === "fail") {
- console.error("failed", r)
return undefined
}
return { ...r.body, id: c.cashout_id }
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx
b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index cfee684fa..d760543c6 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -21,6 +21,8 @@ import { Transactions } from
"../../components/Transactions/index.js";
import { usePreferences } from "../../hooks/preferences.js";
import { PaymentOptions } from "../PaymentOptions.js";
import { State } from "./index.js";
+import { useCashouts, useOnePendingCashouts } from "../../hooks/circuit.js";
+import { TalerError } from "@gnu-taler/taler-util";
export function InvalidIbanView({ error }: State.InvalidIban) {
return (
@@ -57,8 +59,23 @@ export function ReadyView({ account, limit,
goToConfirmOperation }: State.Ready)
return <Fragment>
<ShowDemoInfo />
+ <PendingCashouts account={account}/>
<PaymentOptions limit={limit} goToConfirmOperation={goToConfirmOperation}
/>
<Transactions account={account} />
</Fragment>;
}
+
+function PendingCashouts({account}: {account: string}):VNode {
+ const { i18n } = useTranslationContext();
+ const result = useOnePendingCashouts(account)
+ if (!result || result instanceof TalerError || result.type !== "ok" ||
!result.body) {
+ return <Fragment />
+ }
+
+ return <Attention title={i18n.str`You have pending cashout operation to
complete`} >
+ <i18n.Translate>
+ Cashout with subject "{result.body.subject}", look for the code and
complete the operation <a target="_blank" rel="noreferrer noopener"
class="font-semibold text-blue-700 hover:text-blue-600"
href={`#/cashout/${result.body.id}`}>here</a>.
+ </i18n.Translate>
+ </Attention>
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx
b/packages/demobank-ui/src/pages/BankFrame.tsx
index 34c39e9d3..24012cd2b 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -46,7 +46,6 @@ export function BankFrame({
useEffect(() => {
if (error) {
const desc = (error instanceof Error ? error.stack : String(error)) as
TranslatedString
- console.log(error)
if (error instanceof Error) {
notifyException(i18n.str`Internal error, please report.`, error)
} else {
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx
b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 76d20867e..bbe33eb57 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -33,7 +33,7 @@ export function PaymentOptions({ limit, goToConfirmOperation
}: { limit: AmountJ
const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" |
undefined>();
return (
- <div class="mt-2">
+ <div class="mt-4">
<fieldset>
<legend class="px-4 text-base font-semibold leading-6 text-gray-900">
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index e035c7fed..33bf18abc 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -447,7 +447,7 @@ export function InputAmount(
<input
type="number"
data-left={left}
- class="text-right rounded-md rounded-l-none
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900
placeholder:text-gray-400 sm:text-sm sm:leading-6"
+ class="disabled:bg-gray-200 text-right rounded-md rounded-l-none
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900
placeholder:text-gray-400 sm:text-sm sm:leading-6"
placeholder="0.00" aria-describedby="price-currency"
ref={ref}
name={name}
diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx
b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
index 1a4b4b865..61a55fe16 100644
--- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx
+++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
@@ -3,7 +3,7 @@ import { Fragment, VNode, h } from "preact";
import { useBankCoreApiContext } from "../context/config.js";
import { assertUnreachable } from "./WithdrawalOperationPage.js";
-export function ProfileNavigation({ current }: { current: "details" |
"credentials" | "cashouts" }): VNode {
+export function ProfileNavigation({ current, noCashout }: { noCashout?:
boolean, current: "details" | "credentials" | "cashouts" }): VNode {
const { i18n } = useTranslationContext()
const { config } = useBankCoreApiContext()
return <div>
@@ -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.allow_conversion ?
+ {config.allow_conversion && !noCashout ?
<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/account/ShowAccountDetails.tsx
b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
index 3ef3f568c..4332284e8 100644
--- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
@@ -99,7 +99,7 @@ export function ShowAccountDetails({
<Fragment>
<LocalNotificationBanner notification={notification} />
{accountIsTheCurrentUser ?
- <ProfileNavigation current="details" />
+ <ProfileNavigation current="details" noCashout={credentials.status ===
"loggedIn" ? credentials.isUserAdministrator : undefined} />
:
<h1 class="text-base font-semibold leading-6 text-gray-900">
<i18n.Translate>Account "{account}"</i18n.Translate>
@@ -128,6 +128,7 @@ export function ShowAccountDetails({
<AccountForm
focus={update}
+ noCashout={credentials.status === "loggedIn" ?
credentials.isUserAdministrator : undefined}
username={account}
template={result.body}
purpose={update ? "update" : "show"}
diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
index d7f5155c9..3c00ad1b8 100644
--- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
@@ -81,7 +81,7 @@ export function UpdateAccountPassword({
<Fragment>
<LocalNotificationBanner notification={notification} />
{accountIsTheCurrentUser ?
- <ProfileNavigation current="credentials" /> :
+ <ProfileNavigation current="credentials" noCashout={credentials.status
=== "loggedIn" ? credentials.isUserAdministrator : undefined} /> :
<h1 class="text-base font-semibold leading-6 text-gray-900">
<i18n.Translate>Account "{accountName}"</i18n.Translate>
</h1>
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index b38d40012..526deeeab 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -27,11 +27,13 @@ export function AccountForm({
purpose,
onChange,
focus,
+ noCashout,
children,
}: {
focus?: boolean,
children: ComponentChildren,
username?: string,
+ noCashout?: boolean,
template: TalerCorebankApi.AccountData | undefined;
onChange: (a: AccountFormData | undefined) => void;
purpose: "create" | "update" | "show";
@@ -44,14 +46,14 @@ export function AccountForm({
const { i18n } = useTranslationContext();
function updateForm(newForm: typeof initial): void {
-
+ console.log(newForm)
const parsed = !newForm.cashout_payto_uri
? undefined
: buildPayto("iban", newForm.cashout_payto_uri, undefined);;
const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({
cashout_payto_uri: (!newForm.cashout_payto_uri
- ? i18n.str`required`
+ ? undefined
: !parsed
? i18n.str`does not follow the pattern`
: !parsed.isKnown || parsed.targetType !== "iban"
@@ -81,10 +83,10 @@ export function AccountForm({
if (errors) {
onChange(undefined)
} else {
- const cashout = buildPayto("iban", newForm.cashout_payto_uri!, undefined)
+ const cashout = !newForm.cashout_payto_uri? undefined
:buildPayto("iban", newForm.cashout_payto_uri, undefined)
const account: AccountFormData = {
...newForm as any,
- cashout_payto_uri: stringifyPaytoUri(cashout)
+ cashout_payto_uri: !cashout ? undefined : stringifyPaytoUri(cashout)
}
onChange(account);
}
@@ -194,6 +196,9 @@ export function AccountForm({
onChange={(e) => {
if (form.contact_data) {
form.contact_data.email = e.currentTarget.value;
+ if (!form.contact_data.email) {
+ form.contact_data.email = undefined
+ }
updateForm(structuredClone(form));
}
}}
@@ -220,12 +225,15 @@ export function AccountForm({
class="block w-full disabled:bg-gray-100 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="phone"
id="phone"
- disabled={purpose !== "create"}
+ disabled={purpose === "show"}
value={form.contact_data?.phone ?? ""}
data-error={!!errors?.contact_data?.phone &&
form.contact_data?.phone !== undefined}
onChange={(e) => {
if (form.contact_data) {
form.contact_data.phone = e.currentTarget.value;
+ if (!form.contact_data.email) {
+ form.contact_data.email = undefined
+ }
updateForm(structuredClone(form));
}
}}
@@ -240,44 +248,45 @@ export function AccountForm({
</div>
- <div class="sm:col-span-5">
- <label
- class="block text-sm font-medium leading-6 text-gray-900"
- for="cashout"
- >
- {i18n.str`Cashout IBAN`}
- {purpose !== "show" && <b style={{ color: "red" }}> *</b>}
- </label>
- <div class="mt-2">
- <input
- type="text"
- ref={focus && purpose === "update" ? doAutoFocus : undefined}
- data-error={!!errors?.cashout_payto_uri &&
form.cashout_payto_uri !== undefined}
- class="block w-full disabled:bg-gray-100 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="cashout"
- id="cashout"
- disabled={purpose === "show"}
- value={form.cashout_payto_uri ?? ""}
- onChange={(e) => {
- form.cashout_payto_uri = e.currentTarget.value as
PaytoString;
- updateForm(structuredClone(form));
- }}
- autocomplete="off"
- />
- <ShowInputErrorLabel
- message={errors?.cashout_payto_uri}
- isDirty={form.cashout_payto_uri !== undefined}
- />
+ {!noCashout &&
+ <div class="sm:col-span-5">
+ <label
+ class="block text-sm font-medium leading-6 text-gray-900"
+ for="cashout"
+ >
+ {i18n.str`Cashout IBAN`}
+ {purpose !== "show" && <b style={{ color: "red" }}> *</b>}
+ </label>
+ <div class="mt-2">
+ <input
+ type="text"
+ ref={focus && purpose === "update" ? doAutoFocus : undefined}
+ data-error={!!errors?.cashout_payto_uri &&
form.cashout_payto_uri !== undefined}
+ class="block w-full disabled:bg-gray-100 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="cashout"
+ id="cashout"
+ disabled={purpose === "show"}
+ value={form.cashout_payto_uri ?? ""}
+ onChange={(e) => {
+ form.cashout_payto_uri = e.currentTarget.value as
PaytoString;
+ if (!form.cashout_payto_uri) {
+ form.cashout_payto_uri= undefined
+ }
+ updateForm(structuredClone(form));
+ }}
+ autocomplete="off"
+ />
+ <ShowInputErrorLabel
+ message={errors?.cashout_payto_uri}
+ isDirty={form.cashout_payto_uri !== undefined}
+ />
+ </div>
+ <p class="mt-2 text-sm text-gray-500" >
+ <i18n.Translate>account number where the money is going to be
sent when doing cashouts</i18n.Translate>
+ </p>
</div>
- <p class="mt-2 text-sm text-gray-500" >
- <i18n.Translate>account number where the money is going to be
sent when doing cashouts</i18n.Translate>
- </p>
- </div>
- <div class="sm:col-span-5">
- <pre>
- {JSON.stringify(errors, undefined, 2)}
- </pre>
- </div>
+ }
+
</div>
</div>
{children}
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 2f77f3960..3d3f30250 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -18,7 +18,8 @@ import {
TalerError,
TranslatedString,
encodeCrock,
- getRandomBytes
+ getRandomBytes,
+ parsePaytoUri
} from "@gnu-taler/taler-util";
import {
Attention,
@@ -83,7 +84,7 @@ export function CreateCashout({
const creds = credentials.status !== "loggedIn" ? undefined : credentials
const { api, config } = useBankCoreApiContext()
- const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, amount:
"2" });
+ const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, });
const [notification, notify, handleError] = useLocalNotification()
const info = useConversionInfo();
@@ -171,7 +172,7 @@ export function CreateCashout({
: Amounts.cmp(limit, calc.debit) === -1
? i18n.str`balance is not enough`
: Amounts.cmp(calc.credit, sellFee) === -1
- ? i18n.str`the total amount to transfer does not cover the fees`
+ ? i18n.str`need to be higher due to fees`
: Amounts.isZero(calc.credit)
? i18n.str`the total transfer at destination will be zero`
: undefined,
@@ -242,7 +243,9 @@ export function CreateCashout({
}
})
}
-
+ const cashoutAccount = !resultAccount.body.cashout_payto_uri ? undefined :
+ parsePaytoUri(resultAccount.body.cashout_payto_uri);
+ const cashoutAccountName = !cashoutAccount ? undefined :
cashoutAccount.targetPath
return (
<div>
<LocalNotificationBanner notification={notification} />
@@ -275,6 +278,24 @@ export function CreateCashout({
<RenderAmount value={sellFee}
spec={fiat_currency_specification} />
</dd>
</div>
+ {cashoutAccountName ?
+ <div class="flex items-center justify-between border-t-2 afu
pt-4">
+ <dt class="flex items-center text-sm text-gray-600">
+ <span><i18n.Translate>To account</i18n.Translate></span>
+ </dt>
+ <dd class="text-sm text-gray-900">
+ {cashoutAccountName }
+ </dd>
+ </div> :
+ <div class="flex items-center justify-between border-t-2 afu
pt-4">
+ <Attention type="warning" title={i18n.str`No cashout account`}>
+ <i18n.Translate>
+ Before doing a cashout you need to complete your profile
+ </i18n.Translate>
+ </Attention>
+ </div>
+ }
+
</dl>
</section>
@@ -301,9 +322,10 @@ export function CreateCashout({
<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"
+ class="block w-full rounded-md disabled:bg-gray-200
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"
+ disabled={!resultAccount.body.cashout_payto_uri}
data-error={!!errors?.subject && form.subject !==
undefined}
value={form.subject ?? ""}
onChange={(e) => {
@@ -346,7 +368,7 @@ export function CreateCashout({
left
currency={limit.currency}
value={trimmedAmountStr}
- onChange={(value) => {
+ onChange={!resultAccount.body.cashout_payto_uri ?
undefined : (value) => {
form.amount = value;
updateForm(structuredClone(form));
}}
@@ -411,7 +433,7 @@ export function CreateCashout({
{/* channel */}
<div class="sm:col-span-5">
- <label
+ <label
class="block text-sm font-medium leading-6 text-gray-900"
for="channel"
>
@@ -421,16 +443,19 @@ export function CreateCashout({
<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">
- <label onClick={()=>{
+ <label onClick={() => {
+ if (!resultAccount.body.contact_data?.email) return;
form.channel = TanChannel.EMAIL
updateForm(structuredClone(form))
- }} data-selected={form.channel === TanChannel.EMAIL}
class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm
focus:outline-none border-gray-300 data-[selected=true]:ring-2
data-[selected=true]:ring-indigo-600">
+ }} data-disabled={!resultAccount.body.contact_data?.email}
data-selected={form.channel === TanChannel.EMAIL}
+ class="relative flex
data-[disabled=false]:cursor-pointer rounded-lg border bg-white
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none
border-gray-300 data-[selected=true]:ring-2
data-[selected=true]:ring-indigo-600">
<input type="radio" name="channel" value="Newsletter"
class="sr-only" />
<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>Email</i18n.Translate>
</span>
+ {!resultAccount.body.contact_data?.email &&
i18n.str`add a email in your profile to enable this option`}
</span>
</span>
<svg data-selected={form.channel === TanChannel.EMAIL}
class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" viewBox="0 0 20
20" fill="currentColor" aria-hidden="true">
@@ -438,16 +463,19 @@ export function CreateCashout({
</svg>
</label>
- <label onClick={()=>{
+ <label onClick={() => {
+ if (!resultAccount.body.contact_data?.phone) return;
form.channel = TanChannel.SMS
updateForm(structuredClone(form))
- }} data-selected={form.channel === TanChannel.SMS}
class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm
focus:outline-none border-gray-300 data-[selected=true]:ring-2
data-[selected=true]:ring-indigo-600">
- <input type="radio" name="channel" value="Existing
Customers" class="sr-only" />
+ }} data-disabled={!resultAccount.body.contact_data?.phone}
data-selected={form.channel === TanChannel.SMS}
+ class="relative flex
data-[disabled=false]:cursor-pointer rounded-lg border
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none
border-gray-300 data-[selected=true]:ring-2
data-[selected=true]:ring-indigo-600">
+ <input type="radio" name="channel" value="Existing
Customers" class="sr-only" />
<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>SMS</i18n.Translate>
</span>
+ {!resultAccount.body.contact_data?.phone &&
i18n.str`add a phone number in your profile to enable this option`}
</span>
</span>
<svg data-selected={form.channel === TanChannel.SMS}
class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" viewBox="0 0 20
20" fill="currentColor" aria-hidden="true">
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index 52ff713e2..6fd9eb18c 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -14,6 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import {
+ Amounts,
TalerError,
TranslatedString
} from "@gnu-taler/taler-util";
@@ -32,7 +33,7 @@ import { ShowInputErrorLabel } from
"@gnu-taler/web-util/browser";
import { useBankCoreApiContext } from "../../context/config.js";
import { useBackendState } from "../../hooks/backend.js";
import {
- useCashoutDetails
+ useCashoutDetails, useConversionInfo
} from "../../hooks/circuit.js";
import {
undefinedIfEmpty,
@@ -40,6 +41,7 @@ import {
} from "../../utils.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
+import { RenderAmount } from "../PaytoWireTransferForm.js";
interface Props {
id: string;
@@ -58,7 +60,12 @@ export function ShowCashoutDetails({
const result = useCashoutDetails(Number.isNaN(cid) ? undefined : cid);
const [code, setCode] = useState<string | undefined>(undefined);
const [notification, notify, handleError] = useLocalNotification()
+ const info = useConversionInfo();
+ if (Number.isNaN(cid)) {
+ //TODO: better error message
+ return <div>cashout id should be a number</div>
+ }
if (!result) {
return <Loading />
}
@@ -74,206 +81,252 @@ export function ShowCashoutDetails({
default: assertUnreachable(result)
}
}
- if (Number.isNaN(cid)) {
- //TODO: better error message
- return <div>cashout id should be a number</div>
+ if (!info) {
+ return <Loading />
+ }
+
+ if (info instanceof TalerError) {
+ return <ErrorLoading error={info} />
}
+
const errors = undefinedIfEmpty({
code: !code ? i18n.str`required` : undefined,
});
const isPending = String(result.body.status).toUpperCase() === "PENDING";
+ const { fiat_currency_specification, regional_currency_specification } =
info.body
+ async function doAbortCashout() {
+ if (!creds) return;
+ await handleError(async () => {
+ const resp = await api.abortCashoutById(creds, cid);
+ if (resp.type === "ok") {
+ onCancel();
+ } else {
+ switch (resp.case) {
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Cashout not found. It may be also mean that it was
already aborted.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "already-confirmed": return notify({
+ type: "error",
+ title: i18n.str`Cashout was already confimed.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "cashout-not-supported": return notify({
+ type: "error",
+ title: i18n.str`Cashout operation is not supported.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: {
+ assertUnreachable(resp)
+ }
+ }
+ }
+ })
+ }
+ async function doConfirmCashout() {
+ if (!creds || !code) return;
+ await handleError(async () => {
+ const resp = await api.confirmCashoutById(creds, cid, {
+ tan: code,
+ });
+ if (resp.type === "ok") {
+ mutate(() => true)//clean cashout state
+ } else {
+ switch (resp.case) {
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Cashout not found. It may be also mean that it was
already aborted.`,
+ 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 "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 "already-aborted": return notify({
+ type: "error",
+ title: i18n.str`The cashout operation is already aborted.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "no-cashout-payto": return notify({
+ type: "error",
+ title: i18n.str`Missing destination account.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "too-many-attempts": return notify({
+ type: "error",
+ title: i18n.str`Too many failed attempts.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "cashout-not-supported": return notify({
+ type: "error",
+ title: i18n.str`Cashout operation is not supported.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "invalid-code": return notify({
+ type: "error",
+ title: i18n.str`The code for this cashout is invalid.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
+ }
+ })
+ }
+
return (
<div>
<LocalNotificationBanner notification={notification} />
- <h1>Cashout details {id}</h1>
- <form class="pure-form">
- <fieldset>
- <label>
- <i18n.Translate>Subject</i18n.Translate>
- </label>
- <input readOnly value={result.body.subject} />
- </fieldset>
- <fieldset>
- <label>
- <i18n.Translate>Created</i18n.Translate>
- </label>
- <input readOnly value={result.body.creation_time.t_s === "never" ?
i18n.str`never` : format(result.body.creation_time.t_s, "dd/MM/yyyy HH:mm:ss")}
/>
- </fieldset>
- <fieldset>
- <label>
- <i18n.Translate>Confirmed</i18n.Translate>
- </label>
- <input readOnly value={result.body.confirmation_time === undefined ?
"-" :
- (result.body.confirmation_time.t_s === "never" ?
- i18n.str`never` :
- format(result.body.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss"))
- } />
- </fieldset>
- <fieldset>
- <label>
- <i18n.Translate>Debited</i18n.Translate>
- </label>
- <input readOnly value={result.body.amount_debit} />
- </fieldset>
- <fieldset>
- <label>
- <i18n.Translate>Credit</i18n.Translate>
- </label>
- <input readOnly value={result.body.amount_credit} />
- </fieldset>
- <fieldset>
- <label>
- <i18n.Translate>Status</i18n.Translate>
- </label>
- <input readOnly value={result.body.status} />
- </fieldset>
- {/* <fieldset>
- <label>
- <i18n.Translate>Destination</i18n.Translate>
- </label>
- <input readOnly value={result.body.credit_payto_uri} />
- </fieldset> */}
- {isPending ? (
- <fieldset>
- <label>
- <i18n.Translate>Code</i18n.Translate>
- </label>
- <input
- value={code ?? ""}
- onChange={(e) => {
- setCode(e.currentTarget.value);
+ <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="rounded-sm px-4">
+ <h2 id="summary-heading" class="font-medium
text-lg"><i18n.Translate>Cashout detail</i18n.Translate></h2>
+ <dl class="mt-8 space-y-4">
+ <div class="justify-between items-center flex">
+ <dt class="text-sm
text-gray-600"><i18n.Translate>Subject</i18n.Translate></dt>
+ <dd class="text-sm ">{result.body.subject}</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><i18n.Translate>Status</i18n.Translate></span>
+ </dt>
+ <dd data-status={result.body.status} class="text-sm uppercase
data-[status=pending]:text-yellow-600 data-[status=aborted]:text-red-600
data-[status=confirmed]:text-green-600" >
+ {result.body.status}
+ </dd>
+ </div>
+ </dl>
+ </section>
+ <div class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl
md:col-span-2">
+ <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">
+ <div class="sm:col-span-5">
+ <dl class="space-y-4">
+
+ {result.body.creation_time.t_s !== "never" ?
+ <div class="justify-between items-center flex ">
+ <dt class="
text-gray-600"><i18n.Translate>Created</i18n.Translate></dt>
+ <dd class="text-sm ">
+ {format(result.body.creation_time.t_s * 1000,
"dd/MM/yyyy HH:mm:ss")}
+ </dd>
+ </div>
+ : undefined}
+
+ <div class="flex justify-between items-center border-t-2 afu
pt-4">
+ <dt
class="text-gray-600"><i18n.Translate>Debited</i18n.Translate></dt>
+ <dd class=" font-medium">
+ <RenderAmount
value={Amounts.parseOrThrow(result.body.amount_debit)} negative withColor
spec={regional_currency_specification} />
+ </dd>
+ </div>
+
+ <div class="flex items-center justify-between border-t-2 afu
pt-4">
+ <dt class="flex items-center text-gray-600">
+ <span><i18n.Translate>Credited</i18n.Translate></span>
+
+ </dt>
+ <dd class="text-sm ">
+ <RenderAmount
value={Amounts.parseOrThrow(result.body.amount_credit)} withColor
spec={fiat_currency_specification} />
+ </dd>
+ </div>
+
+ {result.body.confirmation_time &&
result.body.confirmation_time.t_s !== "never" ?
+ <div class="flex justify-between items-center border-t-2
afu pt-4">
+ <dt class="
font-medium"><i18n.Translate>Confirmed</i18n.Translate></dt>
+ <dd class=" font-medium">
+ {format(result.body.confirmation_time.t_s * 1000,
"dd/MM/yyyy HH:mm:ss")}
+ </dd>
+ </div>
+ : undefined}
+ </dl>
+ </div>
+ </div>
+ </div>
+
+ </div>
+
+ {!isPending ? undefined :
+ <Fragment>
+
+ <div />
+ <form
+ class="bg-white shadow-sm ring-1 ring-gray-900/5"
+ autoCapitalize="none"
+ autoCorrect="off"
+ onSubmit={e => {
+ e.preventDefault()
}}
- />
- <ShowInputErrorLabel
- message={errors?.code}
- isDirty={code !== undefined}
- />
- </fieldset>
- ) : undefined}
- </form>
+ >
+ <div class="px-4 py-6 sm:p-8">
+ <label for="withdraw-amount">
+ Enter the confirmation code
+ </label>
+ <div class="mt-2">
+ <div class="relative rounded-md shadow-sm">
+ <input
+ type="text"
+ // class="block w-full rounded-md border-0 py-1.5 pl-16
text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400
focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+ aria-describedby="answer"
+ autoFocus
+ class="block w-full rounded-md border-0 py-1.5
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600
sm:text-sm sm:leading-6"
+ value={code ?? ""}
+ required
+
+ name="answer"
+ id="answer"
+ autocomplete="off"
+ onChange={(e): void => {
+ setCode(e.currentTarget.value)
+ }}
+ />
+ </div>
+ <ShowInputErrorLabel message={errors?.code} isDirty={code
!== undefined} />
+ </div>
+ </div>
+ <div class="flex items-center justify-between gap-x-6 border-t
border-gray-900/10 px-4 py-4 sm:px-8">
+ <button type="button"
+ class="inline-flex items-center rounded-md bg-red-600 px-3
py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2
focus-visible:outline-red-500"
+ onClick={doAbortCashout}
+ >
+ <i18n.Translate>Abort</i18n.Translate></button>
+ <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) => {
+ doConfirmCashout()
+ }}
+ >
+ <i18n.Translate>Confirm</i18n.Translate>
+ </button>
+ </div>
+
+ </form>
+ </Fragment>}
+ </div>
+
<br />
<div style={{ display: "flex", justifyContent: "space-between" }}>
- <button
- class="pure-button pure-button-secondary btn-cancel"
- onClick={(e) => {
- e.preventDefault();
- onCancel();
- }}
+ <button type="button" class="text-sm font-semibold leading-6
text-gray-900"
+ onClick={onCancel}
>
- {i18n.str`Back`}
- </button>
- {isPending ? (
- <div>
- <button
- type="submit"
- class="pure-button pure-button-primary button-error"
- onClick={async (e) => {
- e.preventDefault();
- if (!creds) return;
- await handleError(async () => {
- const resp = await api.abortCashoutById(creds, cid);
- if (resp.type === "ok") {
- onCancel();
- } else {
- switch (resp.case) {
- case "not-found": return notify({
- type: "error",
- title: i18n.str`Cashout not found. It may be also mean
that it was already aborted.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "already-confirmed": return notify({
- type: "error",
- title: i18n.str`Cashout was already confimed.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "cashout-not-supported": return notify({
- type: "error",
- title: i18n.str`Cashout operation is not supported.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- default: {
- assertUnreachable(resp)
- }
- }
- }
- })
- }}
- >
- {i18n.str`Abort`}
- </button>
-
- <button
- type="submit"
- disabled={!code}
- class="pure-button pure-button-primary "
- onClick={async (e) => {
- e.preventDefault();
- if (!creds || !code) return;
- await handleError(async () => {
- const resp = await api.confirmCashoutById(creds, cid, {
- tan: code,
- });
- if (resp.type === "ok") {
- mutate(() => true)//clean cashout state
- } else {
- switch (resp.case) {
- case "not-found": return notify({
- type: "error",
- title: i18n.str`Cashout not found. It may be also mean
that it was already aborted.`,
- 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 "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 "already-aborted": return notify({
- type: "error",
- title: i18n.str`The cashout operation is already
aborted.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case "no-cashout-payto": return notify({
- type: "error",
- title: i18n.str`Missing destination account.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "too-many-attempts": return notify({
- type: "error",
- title: i18n.str`Too many failed attempts.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "cashout-not-supported": return notify({
- type: "error",
- title: i18n.str`Cashout operation is not supported.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- default: assertUnreachable(resp)
- }
- }
- })
- }}
- >
- {i18n.str`Confirm`}
- </button>
- </div>
- ) : (
- <div />
- )}
+ <i18n.Translate>Cancel</i18n.Translate></button>
</div>
</div>
);
diff --git a/packages/taler-util/src/http-client/bank-core.ts
b/packages/taler-util/src/http-client/bank-core.ts
index 273fb97c6..9d1a18e04 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -508,7 +508,7 @@ export class TalerCoreBankHttpClient {
case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return
opKnownFailure("no-cashout-payto", resp);
case TalerErrorCode.BANK_UNALLOWED_DEBIT: return
opKnownFailure("no-enough-balance", resp);
case TalerErrorCode.BANK_BAD_CONVERSION: return
opKnownFailure("incorrect-exchange-rate", resp);
- // case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: return
opKnownFailure("no-enough-balance", resp);
+ case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: return
opKnownFailure("invalid-code", resp);
default: return opUnknownFailure(resp, body)
}
}
diff --git a/packages/taler-util/src/taler-error-codes.ts
b/packages/taler-util/src/taler-error-codes.ts
index 979fb332e..694adff8f 100644
--- a/packages/taler-util/src/taler-error-codes.ts
+++ b/packages/taler-util/src/taler-error-codes.ts
@@ -664,6 +664,14 @@ export enum TalerErrorCode {
EXCHANGE_GENERIC_AML_FROZEN = 1036,
+ /**
+ * The exchange failed to start a KYC attribute conversion helper process.
It is likely configured incorrectly.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR
(500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ EXCHANGE_GENERIC_KYC_CONVERTER_FAILED = 1037,
+
+
/**
* The exchange did not find information about the specified transaction in
the database.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
@@ -3392,6 +3400,78 @@ export enum TalerErrorCode {
BANK_CONFIRM_INCOMPLETE = 5130,
+ /**
+ * The request rate is too high. The server is refusing requests to guard
against brute-force attacks.
+ * Returned with an HTTP status code of #MHD_HTTP_TOO_MANY_REQUESTS (429).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ BANK_TAN_RATE_LIMITED = 5131,
+
+
+ /**
+ * This TAN channel is not supported.
+ * Returned with an HTTP status code of #MHD_HTTP_NOT_IMPLEMENTED (501).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ BANK_TAN_CHANNEL_NOT_SUPPORTED = 5132,
+
+
+ /**
+ * Failed to send TAN using the helper script. Either script is not found,
or script timeout, or script terminated with a non-successful result.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR
(500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ BANK_TAN_CHANNEL_SCRIPT_FAILED = 5133,
+
+
+ /**
+ * The client's response to the challenge was invalid.
+ * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ BANK_TAN_CHALLENGE_FAILED = 5134,
+
+
+ /**
+ * A non-admin user has tried to change their legal name.
+ * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ BANK_NON_ADMIN_PATCH_LEGAL_NAME = 5135,
+
+
+ /**
+ * A non-admin user has tried to change their debt limit.
+ * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ BANK_NON_ADMIN_PATCH_DEBT_LIMIT = 5136,
+
+
+ /**
+ * A non-admin user has tried to change their password whihout providing the
current one.
+ * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD = 5137,
+
+
+ /**
+ * Provided old password does not match current password.
+ * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ BANK_PATCH_BAD_OLD_PASSWORD = 5138,
+
+
+ /**
+ * An admin user has tried to become an exchange.
+ * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ BANK_PATCH_ADMIN_EXCHANGE = 5139,
+
+
/**
* The sync service failed find the account in its database.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-wallet-core] branch master updated (9ba6a59d1 -> 6f7512be7),
gnunet <=