diff options
Diffstat (limited to 'packages/demobank-ui/src/components/Transactions')
3 files changed, 194 insertions, 97 deletions
diff --git a/packages/demobank-ui/src/components/Transactions/state.ts b/packages/demobank-ui/src/components/Transactions/state.ts index 721fede45..56eaefb8d 100644 --- a/packages/demobank-ui/src/components/Transactions/state.ts +++ b/packages/demobank-ui/src/components/Transactions/state.ts @@ -14,7 +14,12 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AbsoluteTime, Amounts, TalerError, parsePaytoUri } from "@gnu-taler/taler-util"; +import { + AbsoluteTime, + Amounts, + TalerError, + parsePaytoUri, +} from "@gnu-taler/taler-util"; import { useTransactions } from "../../hooks/access.js"; import { Props, State, Transaction } from "./index.js"; @@ -33,29 +38,38 @@ export function useComponentState({ account }: Props): State { }; } - const transactions = result.data.type === "fail" ? [] : result.data.body.transactions - .map((tx) => { + const transactions = + result.data.type === "fail" + ? [] + : result.data.body.transactions + .map((tx) => { + const negative = tx.direction === "debit"; + const cp = parsePaytoUri( + negative ? tx.creditor_payto_uri : tx.debtor_payto_uri, + ); + const counterpart = + (cp === undefined || !cp.isKnown + ? undefined + : cp.targetType === "iban" + ? cp.iban + : cp.targetType === "x-taler-bank" + ? cp.account + : cp.targetType === "bitcoin" + ? `${cp.targetPath.substring(0, 6)}...` + : undefined) ?? "unkown"; - const negative = tx.direction === "debit"; - const cp = parsePaytoUri(negative ? tx.creditor_payto_uri : tx.debtor_payto_uri); - const counterpart = (cp === undefined || !cp.isKnown ? undefined : - cp.targetType === "iban" ? cp.iban : - cp.targetType === "x-taler-bank" ? cp.account : - cp.targetType === "bitcoin" ? `${cp.targetPath.substring(0, 6)}...` : undefined) ?? - "unkown"; - - const when = AbsoluteTime.fromProtocolTimestamp(tx.date); - const amount = Amounts.parse(tx.amount); - const subject = tx.subject; - return { - negative, - counterpart, - when, - amount, - subject, - }; - }) - .filter((x): x is Transaction => x !== undefined); + const when = AbsoluteTime.fromProtocolTimestamp(tx.date); + const amount = Amounts.parse(tx.amount); + const subject = tx.subject; + return { + negative, + counterpart, + when, + amount, + subject, + }; + }) + .filter((x): x is Transaction => x !== undefined); return { status: "ready", diff --git a/packages/demobank-ui/src/components/Transactions/test.ts b/packages/demobank-ui/src/components/Transactions/test.ts index 0b5e2bfbf..cf33a0b1c 100644 --- a/packages/demobank-ui/src/components/Transactions/test.ts +++ b/packages/demobank-ui/src/components/Transactions/test.ts @@ -19,14 +19,13 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { ErrorType } from "@gnu-taler/web-util/browser"; +import { TalerErrorCode } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { SwrMockEnvironment } from "@gnu-taler/web-util/testing"; import { expect } from "chai"; import { TRANSACTION_API_EXAMPLE } from "../../endpoints.js"; import { Props } from "./index.js"; import { useComponentState } from "./state.js"; -import { HttpStatusCode, TalerError, TalerErrorCode } from "@gnu-taler/taler-util"; describe("Transaction states", () => { it.skip("should query backend and render transactions", async () => { @@ -36,7 +35,6 @@ describe("Transaction states", () => { account: "myAccount", }; - //@ts-ignore env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_FIRST_PAGE, { response: { data: { @@ -183,10 +181,15 @@ describe("Transaction states", () => { }, ({ status, error }) => { expect(status).equals("loading-error"); - if (error === undefined || !error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED)) { + if ( + error === undefined || + !error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED) + ) { throw Error("not the expected error"); } - expect(error.errorDetail.code).deep.equal(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED); + expect(error.errorDetail.code).deep.equal( + TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, + ); }, ], env.buildTestingContext(), diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx index 7b3c77fa2..1d63cc2cb 100644 --- a/packages/demobank-ui/src/components/Transactions/views.tsx +++ b/packages/demobank-ui/src/components/Transactions/views.tsx @@ -20,99 +20,179 @@ import { Fragment, h, VNode } from "preact"; import { useBankCoreApiContext } from "../../context/config.js"; import { RenderAmount } from "../../pages/PaytoWireTransferForm.js"; import { State } from "./index.js"; +import { privatePages } from "../../Routing.js"; - -export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode { +export function ReadyView({ + transactions, + onNext, + onPrev, +}: State.Ready): VNode { const { i18n, dateLocale } = useTranslationContext(); const { config } = useBankCoreApiContext(); - if (!transactions.length) return <div /> - const txByDate = transactions.reduce((prev, cur) => { - const d = cur.when.t_ms === "never" - ? "" - : format(cur.when.t_ms, "dd/MM/yyyy", { locale: dateLocale }) - if (!prev[d]) { - prev[d] = [] - } - prev[d].push(cur) - return prev - }, {} as Record<string, typeof transactions>) + if (!transactions.length) return <div />; + const txByDate = transactions.reduce( + (prev, cur) => { + const d = + cur.when.t_ms === "never" + ? "" + : format(cur.when.t_ms, "dd/MM/yyyy", { locale: dateLocale }); + if (!prev[d]) { + prev[d] = []; + } + prev[d].push(cur); + return prev; + }, + {} as Record<string, typeof transactions>, + ); return ( <div class="px-4 mt-4"> <div class="sm:flex sm:items-center"> <div class="sm:flex-auto"> - <h1 class="text-base font-semibold leading-6 text-gray-900"><i18n.Translate>Latest transactions</i18n.Translate></h1> + <h1 class="text-base font-semibold leading-6 text-gray-900"> + <i18n.Translate>Latest transactions</i18n.Translate> + </h1> </div> </div> <div class="-mx-4 mt-5 ring-1 ring-gray-300 sm:mx-0 rounded-lg min-w-fit bg-white"> <table class="min-w-full divide-y divide-gray-300"> <thead> <tr> - <th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Date`}</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`Amount`}</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`Counterpart`}</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> + <th + scope="col" + class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 " + >{i18n.str`Date`}</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`Amount`}</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`Counterpart`}</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> <tbody> {Object.entries(txByDate).map(([date, txs], idx) => { - return <Fragment> - <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"> - {date} - </th> - </tr> - {txs.map(item => { - const time = item.when.t_ms === "never" ? "" : format(item.when.t_ms, "HH:mm:ss", { locale: dateLocale }) - return (<tr key={idx} class="border-b border-gray-200 last:border-none"> - <td class="relative py-2 pl-2 pr-2 text-sm "> - <div class="font-medium text-gray-900">{time}</div> - <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"> - {item.negative ? i18n.str`sent` : i18n.str`received`} {item.amount ? ( - <span data-negative={item.negative ? "true" : "false"} class="data-[negative=false]:text-green-600 data-[negative=true]:text-red-600"> - <RenderAmount value={item.amount} spec={config.currency_specification} /> - </span> - ) : ( - <span style={{ color: "grey" }}><{i18n.str`invalid value`}></span> - )}</dd> + 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" + > + {date} + </th> + </tr> + {txs.map((item) => { + const time = + item.when.t_ms === "never" + ? "" + : format(item.when.t_ms, "HH:mm:ss", { + locale: dateLocale, + }); + return ( + <tr + key={idx} + class="border-b border-gray-200 last:border-none" + > + <td class="relative py-2 pl-2 pr-2 text-sm "> + <div class="font-medium text-gray-900">{time}</div> + <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"> + {item.negative + ? i18n.str`sent` + : i18n.str`received`}{" "} + {item.amount ? ( + <span + data-negative={ + item.negative ? "true" : "false" + } + class="data-[negative=false]:text-green-600 data-[negative=true]:text-red-600" + > + <RenderAmount + value={item.amount} + spec={config.currency_specification} + /> + </span> + ) : ( + <span style={{ color: "grey" }}> + <{i18n.str`invalid value`}> + </span> + )} + </dd> - <dt class="sr-only sm:hidden"><i18n.Translate>Counterpart</i18n.Translate></dt> - <dd class="mt-1 truncate text-gray-500 sm:hidden"> - {item.negative ? i18n.str`to` : i18n.str`from`} <a href={`#/wire-transfer/${item.counterpart}`} class="text-indigo-600 hover:text-indigo-900"> + <dt class="sr-only sm:hidden"> + <i18n.Translate>Counterpart</i18n.Translate> + </dt> + <dd class="mt-1 truncate text-gray-500 sm:hidden"> + {item.negative ? i18n.str`to` : i18n.str`from`}{" "} + <a + href={privatePages.wireTranserCreate.url({ + destination: item.counterpart, + })} + class="text-indigo-600 hover:text-indigo-900" + > + {item.counterpart} + </a> + </dd> + <dd class="mt-1 text-gray-500 sm:hidden"> + <pre class="break-words w-56 whitespace-break-spaces p-2 rounded-md mx-auto my-2 bg-gray-100"> + {item.subject} + </pre> + </dd> + </dl> + </td> + <td + data-negative={item.negative ? "true" : "false"} + class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 " + > + {item.amount ? ( + <RenderAmount + value={item.amount} + negative={item.negative} + withColor + spec={config.currency_specification} + /> + ) : ( + <span style={{ color: "grey" }}> + <{i18n.str`invalid value`}> + </span> + )} + </td> + <td class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500"> + <a + href={privatePages.wireTranserCreate.url({ + destination: item.counterpart, + })} + class="text-indigo-600 hover:text-indigo-900" + > {item.counterpart} </a> - </dd> - <dd class="mt-1 text-gray-500 sm:hidden" > - <pre class="break-words w-56 whitespace-break-spaces p-2 rounded-md mx-auto my-2 bg-gray-100"> - {item.subject} - </pre> - </dd> - </dl> - </td> - <td data-negative={item.negative ? "true" : "false"} - class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 "> - {item.amount ? (<RenderAmount value={item.amount} negative={item.negative} withColor spec={config.currency_specification} /> - ) : ( - <span style={{ color: "grey" }}><{i18n.str`invalid value`}></span> - )} - </td> - <td class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500"> - <a href={`#/wire-transfer/${item.counterpart}`} class="text-indigo-600 hover:text-indigo-900"> - {item.counterpart} - </a> - </td> - <td class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 break-all min-w-md">{item.subject}</td> - </tr>) - })} - </Fragment> - + </td> + <td class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 break-all min-w-md"> + {item.subject} + </td> + </tr> + ); + })} + </Fragment> + ); })} </tbody> - </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"> + <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" |