diff options
author | Sebastian <sebasjm@gmail.com> | 2024-01-19 17:09:43 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2024-01-19 17:09:52 -0300 |
commit | d845c0cf225a0f05214f368f4a98257238f53d07 (patch) | |
tree | 756d754f3c1c85460f01b4b5068aa02e4c0760a6 /packages/demobank-ui | |
parent | a04e822af063951d39f4ddc597cd163037cb5010 (diff) | |
download | wallet-core-d845c0cf225a0f05214f368f4a98257238f53d07.tar.xz |
fixes #8147
Diffstat (limited to 'packages/demobank-ui')
8 files changed, 74 insertions, 217 deletions
diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx b/packages/demobank-ui/src/components/Cashouts/views.tsx index c3fdec796..cb1c24aa7 100644 --- a/packages/demobank-ui/src/components/Cashouts/views.tsx +++ b/packages/demobank-ui/src/components/Cashouts/views.tsx @@ -32,13 +32,11 @@ export function FailedView({ error }: State.Failed) { {error.detail.hint} </div> </Attention> - case HttpStatusCode.NotFound: return <Attention type="danger" - title={i18n.str`Account not found.`}> - <div class="mt-2 text-sm text-red-700"> - {error.detail.hint} - </div> - </Attention> - default: assertUnreachable(error) + case HttpStatusCode.NotImplemented: { + return <Attention type="danger" title={i18n.str`Cashout not implemented`}> + </Attention>; + } + default: assertUnreachable(error.case) } } @@ -51,6 +49,16 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode { if (resp instanceof TalerError) { return <ErrorLoadingWithDebug error={resp} /> } + if (resp.type === "fail") { + switch (resp.case) { + case HttpStatusCode.NotImplemented: { + return <Attention type="danger" title={i18n.str`Cashout not implemented`}> + </Attention>; + } + default: assertUnreachable(resp.case) + } + } + if (!cashouts.length) return <div /> const txByDate = cashouts.reduce((prev, cur) => { const d = cur.creation_time.t_s === "never" @@ -74,10 +82,8 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode { <thead> <tr> <th scope="col" class=" pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Created`}</th> - <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Confirmed`}</th> <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Total debit`}</th> <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Total credit`}</th> - <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Status`}</th> <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Subject`}</th> </tr> </thead> @@ -91,9 +97,6 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode { </tr> {txs.map(item => { const creationTime = item.creation_time.t_s === "never" ? "" : format(item.creation_time.t_s * 1000, "HH:mm:ss", { locale: dateLocale }) - 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", { locale: dateLocale }) - : "-" return (<tr key={idx} class="border-b border-gray-200 hover:bg-gray-200 last:border-none"> <td onClick={(e) => { @@ -101,6 +104,8 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode { onSelected(item.id); }} class="relative py-2 pl-2 pr-2 text-sm "> <div class="font-medium text-gray-900">{creationTime}</div> + {//FIXME: implement responsive view + } {/* <dl class="font-normal sm:hidden"> <dt class="sr-only sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt> <dd class="mt-1 truncate text-gray-700"> @@ -126,10 +131,6 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode { <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(); @@ -139,10 +140,6 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode { <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">{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> diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index 627252682..7ec490c19 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { TranslatedString } from "@gnu-taler/taler-util"; +import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util"; import { LocalNotificationBanner, ShowInputErrorLabel, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; @@ -76,13 +76,13 @@ export function LoginForm({ currentUser, fixedUser, onRegister }: { fixedUser?: backend.logIn({ username, token: resp.body.access_token }); } else { switch (resp.case) { - case "wrong-credentials": return notify({ + case HttpStatusCode.Unauthorized: return notify({ type: "error", title: i18n.str`Wrong credentials for "${username}"`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) - case "not-found": return notify({ + case HttpStatusCode.NotFound: return notify({ type: "error", title: i18n.str`Account not found`, description: resp.detail.hint as TranslatedString, diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx index 005a0bc2c..e948a5dad 100644 --- a/packages/demobank-ui/src/pages/RegistrationPage.tsx +++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx @@ -177,13 +177,13 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on backend.logIn({ username, token: resp.body.access_token }); } else { switch (resp.case) { - case "wrong-credentials": return notify({ + case HttpStatusCode.Unauthorized: return notify({ type: "error", title: i18n.str`Wrong credentials for "${username}"`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) - case "not-found": return notify({ + case HttpStatusCode.NotFound: return notify({ type: "error", title: i18n.str`Account not found`, description: resp.detail.hint as TranslatedString, diff --git a/packages/demobank-ui/src/pages/SolveChallengePage.tsx b/packages/demobank-ui/src/pages/SolveChallengePage.tsx index 3647f2b65..d0ac5ab75 100644 --- a/packages/demobank-ui/src/pages/SolveChallengePage.tsx +++ b/packages/demobank-ui/src/pages/SolveChallengePage.tsx @@ -27,6 +27,7 @@ import { parsePaytoUri } from "@gnu-taler/taler-util"; import { + Attention, Loading, LocalNotificationBanner, ShowInputErrorLabel, @@ -528,6 +529,17 @@ function ShowCashoutDetails({ request }: { request: TalerCorebankApi.CashoutRequ if (info instanceof TalerError) { return <ErrorLoadingWithDebug error={info} /> } + if (info.type === "fail") { + switch (info.case) { + case HttpStatusCode.NotImplemented: { + return <Attention type="danger" title={i18n.str`Cashout not implemented`}> + </Attention>; + } + default: assertUnreachable(info.case) + } + } + + return <Fragment> {request.subject !== undefined && <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx index ca3e2fbdf..0dfdb39f3 100644 --- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx +++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx @@ -93,9 +93,9 @@ export function ShowAccountDetails({ description: resp.detail.hint as TranslatedString, debug: resp.detail, }) - case TalerErrorCode.BANK_NON_ADMIN_PATCH_CONTACT: return notify({ + case TalerErrorCode.BANK_MISSING_TAN_INFO: return notify({ type: "error", - title: i18n.str`You can't change the contact data, please contact the your account administrator.`, + title: i18n.str`No information for the selected authentication channel.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx b/packages/demobank-ui/src/pages/admin/AdminHome.tsx index c03f228cd..b7ef3aa00 100644 --- a/packages/demobank-ui/src/pages/admin/AdminHome.tsx +++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx @@ -1,5 +1,5 @@ -import { AmountString, Amounts, CurrencySpecification, TalerCorebankApi, TalerError, assertUnreachable } from "@gnu-taler/taler-util"; -import { useLang, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { AmountString, Amounts, CurrencySpecification, HttpStatusCode, TalerCorebankApi, TalerError, assertUnreachable } from "@gnu-taler/taler-util"; +import { Attention, useLang, useTranslationContext } from "@gnu-taler/web-util/browser"; import { format, getDate, getHours, getMonth, getYear, setDate, setHours, setMonth, setYear, sub } from "date-fns"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; @@ -90,10 +90,23 @@ function Metrics(): VNode { if (resp instanceof TalerError) { return <ErrorLoadingWithDebug error={resp} /> } + if (!respInfo) return <Fragment />; + if (respInfo instanceof TalerError) { + return <ErrorLoadingWithDebug error={respInfo} /> + } + if (respInfo.type === "fail") { + switch (respInfo.case) { + case HttpStatusCode.NotImplemented: { + return <Attention type="danger" title={i18n.str`Cashout not implemented`}> + </Attention>; + } + default: assertUnreachable(respInfo.case) + } + } + if (resp.current.type !== "ok" || resp.previous.type !== "ok") { return <Fragment /> } - const fiatSpec = respInfo && (!(respInfo instanceof TalerError)) ? respInfo.body.fiat_currency_specification : undefined return <Fragment> <div class="sm:hidden"> <label for="tabs" class="sr-only"><i18n.Translate>Select a section</i18n.Translate></label> @@ -135,7 +148,7 @@ function Metrics(): VNode { </div> <dl class="mt-5 grid grid-cols-1 md:grid-cols-2 divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow-lg md:divide-x md:divide-y-0"> - {!fiatSpec || resp.current.body.type !== "with-conversions" || resp.previous.body.type !== "with-conversions" ? undefined : + {resp.current.body.type !== "with-conversions" || resp.previous.body.type !== "with-conversions" ? undefined : <Fragment> <div class="px-4 py-5 sm:p-6"> <dt class="text-base font-normal text-gray-900"> @@ -144,7 +157,7 @@ function Metrics(): VNode { <MetricValue current={resp.current.body.cashinFiatVolume} previous={resp.previous.body.cashinFiatVolume} - spec={fiatSpec} + spec={respInfo.body.fiat_currency_specification} /> </div> <div class="px-4 py-5 sm:p-6"> @@ -154,7 +167,7 @@ function Metrics(): VNode { <MetricValue current={resp.current.body.cashoutFiatVolume} previous={resp.previous.body.cashoutFiatVolume} - spec={fiatSpec} + spec={respInfo.body.fiat_currency_specification} /> </div> </Fragment> diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx index f9962fbde..a39a379de 100644 --- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx +++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx @@ -120,6 +120,16 @@ export function CreateCashout({ if (info instanceof TalerError) { return <ErrorLoadingWithDebug error={info} /> } + if (info.type === "fail") { + switch (info.case) { + case HttpStatusCode.NotImplemented: { + return <Attention type="danger" title={i18n.str`Cashout not implemented`}> + </Attention>; + } + default: assertUnreachable(info.case) + } + } + const conversionInfo = info.body.conversion_rate if (!conversionInfo) { diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx index a55bf3ab6..b4d5b6584 100644 --- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx +++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx @@ -54,18 +54,13 @@ export function ShowCashoutDetails({ }: Props): VNode { const { i18n, dateLocale } = useTranslationContext(); const { state } = useBackendState(); - const creds = state.status !== "loggedIn" ? undefined : state - const { api } = useBankCoreApiContext() const cid = Number.parseInt(id, 10) 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> + return <Attention type="danger" title={i18n.str`cashout id should be a number`} /> } if (!result) { return <Loading /> @@ -89,116 +84,19 @@ export function ShowCashoutDetails({ if (info instanceof TalerError) { return <ErrorLoadingWithDebug error={info} /> } - - const errors = undefinedIfEmpty({ - code: !code ? i18n.str`required` : undefined, - }); - /** - * @deprecated - */ - const isPending = String(result.body.status).toUpperCase() === "PENDING"; - const { fiat_currency_specification, regional_currency_specification } = info.body - // won't implement in retry in old API 3:0:3 since request_uid is missing - 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 HttpStatusCode.NotFound: 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 HttpStatusCode.Conflict: return notify({ - type: "error", - title: i18n.str`Cashout was already confimed.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }) - case HttpStatusCode.NotImplemented: return notify({ - type: "error", - title: i18n.str`Cashout operation is not supported.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }) - default: { - assertUnreachable(resp) - } - } + if (info.type === "fail") { + switch (info.case) { + case HttpStatusCode.NotImplemented: { + return <Attention type="danger" title={i18n.str`Cashout not implemented`} /> } - }) - } - 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 HttpStatusCode.NotFound: 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 TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({ - type: "error", - title: i18n.str`The account does not have sufficient funds`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_BAD_CONVERSION: return notify({ - type: "error", - title: i18n.str`The conversion rate was incorrectly applied`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return notify({ - type: "error", - title: i18n.str`The cashout operation is already aborted.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }); - case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return notify({ - type: "error", - title: i18n.str`Missing destination account.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }) - case HttpStatusCode.TooManyRequests: return notify({ - type: "error", - title: i18n.str`Too many failed attempts.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }) - case HttpStatusCode.NotImplemented: return notify({ - type: "error", - title: i18n.str`Cashout operation is not supported.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - }) - case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: 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) - } - } - }) + default: assertUnreachable(info.case) + } } + const { fiat_currency_specification, regional_currency_specification } = info.body + return ( <div> - <LocalNotificationBanner notification={notification} /> <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"> @@ -208,16 +106,6 @@ export function ShowCashoutDetails({ <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"> @@ -252,14 +140,6 @@ export function ShowCashoutDetails({ </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", { locale: dateLocale })} - </dd> - </div> - : undefined} </dl> </div> </div> @@ -267,61 +147,6 @@ export function ShowCashoutDetails({ </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() - }} - > - <div class="px-4 py-6 sm:p-8"> - <label for="withdraw-amount"> - <i18n.Translate>Enter the confirmation code</i18n.Translate> - </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 /> |