taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 7b78c1350cfe49e06ce31ed076f69aa9dfac3490
parent b84fc8b580a7dbb00d2ec4bd463966e546b9b47f
Author: Sebastian <sebasjm@gmail.com>
Date:   Tue,  7 May 2024 12:09:48 -0300

fix #8781: min cashout ui

Diffstat:
Mpackages/bank-ui/src/Routing.tsx | 10+++++++++-
Mpackages/bank-ui/src/pages/LoginForm.tsx | 4++--
Mpackages/bank-ui/src/pages/account/CashoutListForAccount.tsx | 3+++
Mpackages/bank-ui/src/pages/admin/AccountForm.tsx | 1+
Mpackages/bank-ui/src/pages/admin/AdminHome.tsx | 5++---
Mpackages/bank-ui/src/pages/regional/CreateCashout.tsx | 53++++++++++++++++++++++++++++++++++-------------------
6 files changed, 51 insertions(+), 25 deletions(-)

diff --git a/packages/bank-ui/src/Routing.tsx b/packages/bank-ui/src/Routing.tsx @@ -31,6 +31,7 @@ import { HttpStatusCode, TranslatedString, assertUnreachable, + createRFC8959AccessTokenEncoded } from "@gnu-taler/taler-util"; import { useEffect } from "preact/hooks"; import { useSessionState } from "./hooks/session.js"; @@ -121,7 +122,7 @@ function PublicRounting({ refreshable: true, }); if (resp.type === "ok") { - onLoggedUser(username, resp.body.access_token); + onLoggedUser(username, createRFC8959AccessTokenEncoded(resp.body.access_token)); } else { switch (resp.case) { case HttpStatusCode.Unauthorized: @@ -394,6 +395,9 @@ function PrivateRouting({ routeMyAccountDetails={privatePages.myAccountDetails} routeMyAccountPassword={privatePages.myAccountPassword} routeConversionConfig={privatePages.conversionConfig} + onCashout={() => + navigateTo(privatePages.home.url({})) + } onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) } @@ -461,6 +465,9 @@ function PrivateRouting({ routeMyAccountDetails={privatePages.myAccountDetails} routeMyAccountPassword={privatePages.myAccountPassword} routeConversionConfig={privatePages.conversionConfig} + onCashout={() => + navigateTo(privatePages.home.url({})) + } onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) } @@ -515,6 +522,7 @@ function PrivateRouting({ onAuthorizationRequired={() => navigateTo(privatePages.solveSecondFactor.url({})) } + onCashout={() => navigateTo(privatePages.home.url({}))} routeClose={privatePages.home} /> ); diff --git a/packages/bank-ui/src/pages/LoginForm.tsx b/packages/bank-ui/src/pages/LoginForm.tsx @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { HttpStatusCode } from "@gnu-taler/taler-util"; +import { HttpStatusCode, createRFC8959AccessTokenEncoded, createRFC8959AccessTokenPlain } from "@gnu-taler/taler-util"; import { Button, LocalNotificationBanner, @@ -87,7 +87,7 @@ export function LoginForm({ refreshable: true, }), (result) => { - session.logIn({ username, token: result.body.access_token }); + session.logIn({ username, token: createRFC8959AccessTokenEncoded(result.body.access_token) }); }, (fail) => { switch (fail.case) { diff --git a/packages/bank-ui/src/pages/account/CashoutListForAccount.tsx b/packages/bank-ui/src/pages/account/CashoutListForAccount.tsx @@ -25,6 +25,7 @@ interface Props { account: string; routeClose: RouteDefinition; onAuthorizationRequired: () => void; + onCashout: () => void; routeCashoutDetails: RouteDefinition<{ cid: string }>; routeMyAccountDetails: RouteDefinition; routeMyAccountDelete: RouteDefinition; @@ -37,6 +38,7 @@ interface Props { export function CashoutListForAccount({ account, onAuthorizationRequired, + onCashout, routeCreateCashout, routeCashoutDetails, routeMyAccountCashout, @@ -76,6 +78,7 @@ export function CashoutListForAccount({ focus routeHere={routeCreateCashout} routeClose={routeClose} + onCashout={onCashout} onAuthorizationRequired={onAuthorizationRequired} account={account} /> diff --git a/packages/bank-ui/src/pages/admin/AccountForm.tsx b/packages/bank-ui/src/pages/admin/AccountForm.tsx @@ -213,6 +213,7 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({ : undefined, name: !editableName ? undefined // disabled + : purpose === "update" && newForm.name === undefined ? undefined // the field hasn't been changed : !newForm.name ? i18n.str`Required` : undefined, diff --git a/packages/bank-ui/src/pages/admin/AdminHome.tsx b/packages/bank-ui/src/pages/admin/AdminHome.tsx @@ -276,10 +276,9 @@ function Metrics({ name="tabs" class="block w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500" onChange={(e) => { - // const op = e.currentTarget.value as typeof metricType setMetricType( - e.currentTarget - .value as unknown as TalerCorebankApi.MonitorTimeframeParam, + parseInt(e.currentTarget + .value, 10) as TalerCorebankApi.MonitorTimeframeParam, ); }} > diff --git a/packages/bank-ui/src/pages/regional/CreateCashout.tsx b/packages/bank-ui/src/pages/regional/CreateCashout.tsx @@ -59,6 +59,7 @@ interface Props { account: string; focus?: boolean; onAuthorizationRequired: () => void; + onCashout: () => void; routeClose: RouteDefinition; routeHere: RouteDefinition; } @@ -76,6 +77,7 @@ type ErrorFrom<T> = { export function CreateCashout({ account: accountName, onAuthorizationRequired, + onCashout, focus, routeHere, routeClose, @@ -93,7 +95,6 @@ export function CreateCashout({ const { lib: { bank: api }, config, - hints, } = useBankCoreApiContext(); const [form, setForm] = useState<Partial<FormType>>({ isDebit: true }); const [notification, notify, handleError] = useLocalNotification(); @@ -167,13 +168,6 @@ export function CreateCashout({ ); } - const account = { - balance: Amounts.parseOrThrow(resultAccount.body.balance.amount), - balanceIsDebit: - resultAccount.body.balance.credit_debit_indicator == "debit", - debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold), - }; - const { fiat_currency, regional_currency, @@ -182,6 +176,15 @@ export function CreateCashout({ } = info.body; const regionalZero = Amounts.zeroOfCurrency(regional_currency); const fiatZero = Amounts.zeroOfCurrency(fiat_currency); + + const account = { + balance: Amounts.parseOrThrow(resultAccount.body.balance.amount), + balanceIsDebit: + resultAccount.body.balance.credit_debit_indicator == "debit", + debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold), + minCashout: resultAccount.body.min_cashout === undefined ? regionalZero : Amounts.parseOrThrow(resultAccount.body.min_cashout) + }; + const limit = account.balanceIsDebit ? Amounts.sub(account.debitThreshold, account.balance).amount : Amounts.add(account.balance, account.debitThreshold).amount; @@ -241,16 +244,23 @@ export function CreateCashout({ ? i18n.str`Invalid` : Amounts.cmp(limit, calc.debit) === -1 ? i18n.str`Balance is not enough` - : form.isDebit && - Amounts.cmp(inputAmount, conversionInfo.cashout_min_amount) < 1 - ? i18n.str`Needs to be higher than ${ + : calculationResult === "amount-is-too-small" + ? i18n.str`Amount needs to be higher` + : Amounts.cmp(calc.debit, conversionInfo.cashout_min_amount) < 0 + ? i18n.str`No account can't cashout less than ${ Amounts.stringifyValueWithSpec( Amounts.parseOrThrow(conversionInfo.cashout_min_amount), regional_currency_specification, ).normal }` - : calculationResult === "amount-is-too-small" - ? i18n.str`Amount needs to be higher` + : Amounts.cmp(calc.debit, account.minCashout) < 0 + ? i18n.str`Your account can't cashout less than ${ + Amounts.stringifyValueWithSpec( + Amounts.parseOrThrow(account.minCashout), + regional_currency_specification, + ).normal + }` + : Amounts.isZero(calc.credit) ? i18n.str`The total transfer at destination will be zero` : undefined, @@ -260,21 +270,17 @@ export function CreateCashout({ async function createCashout() { const request_uid = encodeCrock(getRandomBytes(32)); await handleError(async () => { - // new cashout api doesn't require channel - const validChannel = - config.supported_tan_channels.length === 0 || form.channel; - - if (!creds || !form.subject || !validChannel) return; + if (!creds || !form.subject) return; const request = { request_uid, amount_credit: Amounts.stringify(calc.credit), amount_debit: Amounts.stringify(calc.debit), subject: form.subject, - tan_channel: form.channel, }; const resp = await api.createCashout(creds, request); if (resp.type === "ok") { notifyInfo(i18n.str`Cashout created`); + onCashout(); } else { switch (resp.case) { case HttpStatusCode.Accepted: { @@ -335,6 +341,15 @@ export function CreateCashout({ debug: resp.detail, when: AbsoluteTime.now(), }); + case TalerErrorCode.BANK_CONVERSION_AMOUNT_TO_SMALL: + return notify({ + type: "error", + title: i18n.str`The amount is less than the minimum allowed.`, + description: resp.detail.hint as TranslatedString, + debug: resp.detail, + when: AbsoluteTime.now(), + }); + case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: return notify({ type: "error",