taler-typescript-core

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

commit b8d685c7fbbdacab592fa414d89f56dd7507bf51
parent 0412802078c5e547c4121985c228924f37f53434
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Thu, 16 Apr 2026 14:59:56 -0300

fix #10959

Diffstat:
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx | 2+-
Mpackages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx | 11++++-------
Mpackages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx | 316+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx | 8++------
4 files changed, 203 insertions(+), 134 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx @@ -118,7 +118,7 @@ export function Timeline({ events: e }: Props) { <Tooltip text={i18n.str`This wire transfer has been notified by the payment service provider.`} > - <i class="timeline-marker is-icon is-danger timeline-marker is-icon icon mdi mdi-cash" /> + <i class="timeline-marker is-icon timeline-marker is-icon icon mdi mdi-cash" /> </Tooltip> ); case "refund-created": diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx @@ -29,14 +29,10 @@ const TALER_SCREEN_ID = 71; export interface Props { transfers: PaginatedResult<TalerMerchantApi.TransferDetails[]>; incomings: PaginatedResult<TalerMerchantApi.ExpectedTransferEntry[]>; - onSelectedToConfirm: (wid: TalerMerchantApi.ExpectedTransferEntry) => void; + onDetails: (wid: number) => void; } -export function ListPage({ - transfers, - incomings, - onSelectedToConfirm, -}: Props): VNode { +export function ListPage({ transfers, incomings, onDetails }: Props): VNode { return ( <section class="section is-main-section"> {!incomings.body.length ? undefined : ( @@ -47,7 +43,7 @@ export function ListPage({ }))} onLoadMoreBefore={incomings.loadFirst} onLoadMoreAfter={incomings.loadNext} - onSelectedToConfirm={onSelectedToConfirm} + onDetails={onDetails} /> )} {/* // ) : ( */} @@ -58,6 +54,7 @@ export function ListPage({ }))} onLoadMoreBefore={transfers.loadFirst} onLoadMoreAfter={transfers.loadNext} + onDetails={onDetails} /> {/* // )} */} </section> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx @@ -19,13 +19,22 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { Amounts, TalerMerchantApi } from "@gnu-taler/taler-util"; import { + AccessToken, + Amounts, + assertUnreachable, + HttpStatusCode, + TalerMerchantApi, +} from "@gnu-taler/taler-util"; +import { + ButtonBetterBulma, + LocalNotificationBannerBulma, RenderAmountBulma, + useLocalNotificationBetter, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { format } from "date-fns"; -import { h, VNode } from "preact"; +import { Fragment, h, VNode } from "preact"; import { useSessionContext } from "../../../../context/session.js"; import { WithId } from "../../../../declaration.js"; import { @@ -33,6 +42,7 @@ import { usePreference, } from "../../../../hooks/preference.js"; import { Tooltip } from "../../../../components/Tooltip.js"; +import { revalidateInstanceIncomingTransfers } from "../../../../hooks/transfer.js"; const TALER_SCREEN_ID = 72; @@ -40,132 +50,188 @@ interface TablePropsIncoming { transfers: (TalerMerchantApi.ExpectedTransferEntry & WithId)[]; onLoadMoreBefore?: () => void; onLoadMoreAfter?: () => void; - onSelectedToConfirm: (d: TalerMerchantApi.ExpectedTransferEntry) => void; + onDetails: (d: number) => void; } export function CardTableIncoming({ transfers, onLoadMoreAfter, onLoadMoreBefore, - onSelectedToConfirm, + onDetails, }: TablePropsIncoming): VNode { const { i18n } = useTranslationContext(); const [preferences] = usePreference(); const { config } = useSessionContext(); + const { state: session, lib } = useSessionContext(); + + const [notification, safeFunctionHandler] = useLocalNotificationBetter(); + + const confirm = safeFunctionHandler( + i18n.str`inform wire transfer`, + (at: AccessToken, wt: TalerMerchantApi.ExpectedTransferEntry) => + lib.instance.informWireTransfer(at, { + credit_amount: wt.expected_credit_amount!, + exchange_url: wt.exchange_url, + payto_uri: wt.payto_uri, + wtid: wt.wtid, + }), + ); + confirm.onSuccess = () => { + revalidateInstanceIncomingTransfers(); + }; + confirm.onFail = (fail) => { + switch (fail.case) { + case HttpStatusCode.Unauthorized: + return i18n.str`Unauthorized.`; + case HttpStatusCode.NotFound: + return i18n.str`Not found.`; + case HttpStatusCode.Conflict: + return i18n.str`Wire transfer already confirmed.`; + default: + assertUnreachable(fail); + } + }; return ( - <div class="card has-table"> - <header class="card-header"> - <p class="card-header-title"> - <i class="icon mdi mdi-star" /> - <i18n.Translate>New wire transfers</i18n.Translate> - </p> - </header> - <div class="card-content"> - <div class=" has-pagination"> - <div class="table-wrapper has-mobile-cards"> - {transfers.length > 0 ? ( - <div class=""> - {onLoadMoreBefore && ( - <Tooltip - text={i18n.str`Load more wire transfers preceding the first one`} - > - <button - type="button" - class="button is-fullwidth" - onClick={onLoadMoreBefore} + <Fragment> + <LocalNotificationBannerBulma notification={notification} /> + + <div class="card has-table"> + <header class="card-header"> + <p class="card-header-title"> + <i class="icon mdi mdi-star" /> + <i18n.Translate>New wire transfers</i18n.Translate> + </p> + </header> + <div class="card-content"> + <div class=" has-pagination"> + <div class="table-wrapper has-mobile-cards"> + {transfers.length > 0 ? ( + <div class=""> + {onLoadMoreBefore && ( + <Tooltip + text={i18n.str`Load more wire transfers preceding the first one`} > - <i18n.Translate>Load first page</i18n.Translate> - </button> - </Tooltip> - )} - <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> - <thead> - <tr> - <th> - <i18n.Translate>Date</i18n.Translate> - </th> - <th> - <i18n.Translate>Amount</i18n.Translate> - </th> - <th> - <i18n.Translate>ID</i18n.Translate> - </th> - <th></th> - </tr> - </thead> - <tbody> - {transfers.map((i, idx) => { - return ( - <tr key={i.id}> - <td> - {i.execution_time - ? i.execution_time.t_s == "never" - ? i18n.str`never` - : format( - i.execution_time.t_s * 1000, - datetimeFormatForPreferences(preferences), - ) - : i18n.str`unknown`} - </td> - <td> - {i.expected_credit_amount ? ( - <RenderAmountBulma - value={Amounts.parseOrThrow( - i.expected_credit_amount, - )} - specMap={config.currencies} - /> - ) : ( - <span> - <i18n.Translate> - To be determined. - </i18n.Translate> - </span> - )} - </td> - <td title={i.wtid}>{i.wtid}</td> - <td class="is-actions-cell right-sticky"> - {!i.expected_credit_amount ? undefined : ( - <div class="buttons is-right"> - <Tooltip - text={i18n.str`Show details about the incoming wire transfer.`} - > - <a - class="button is-info is-small" - onClick={() => onSelectedToConfirm(i)} + <button + type="button" + class="button is-fullwidth" + onClick={onLoadMoreBefore} + > + <i18n.Translate>Load first page</i18n.Translate> + </button> + </Tooltip> + )} + <table class="table is-fullwidth is-striped is-hoverable"> + <thead> + <tr> + <th> + <i18n.Translate>Date</i18n.Translate> + </th> + <th> + <i18n.Translate>Amount</i18n.Translate> + </th> + <th> + <i18n.Translate>ID</i18n.Translate> + </th> + <th></th> + </tr> + </thead> + <tbody> + {transfers.map((i, idx) => { + return ( + <tr key={i.id}> + <td + onClick={(): void => + onDetails(i.expected_transfer_serial_id!) + } + style={{ cursor: "pointer" }} + > + {i.execution_time + ? i.execution_time.t_s == "never" + ? i18n.str`never` + : format( + i.execution_time.t_s * 1000, + datetimeFormatForPreferences(preferences), + ) + : i18n.str`unknown`} + </td> + <td + onClick={(): void => + onDetails(i.expected_transfer_serial_id!) + } + style={{ cursor: "pointer" }} + > + {i.expected_credit_amount ? ( + <RenderAmountBulma + value={Amounts.parseOrThrow( + i.expected_credit_amount, + )} + specMap={config.currencies} + /> + ) : ( + <span> + <i18n.Translate> + To be determined. + </i18n.Translate> + </span> + )} + </td> + <td + title={i.wtid} + onClick={(): void => + onDetails(i.expected_transfer_serial_id!) + } + style={{ cursor: "pointer" }} + > + {i.wtid} + </td> + <td class="is-actions-cell right-sticky"> + {!i.expected_credit_amount ? undefined : ( + <div class="buttons is-right"> + <Tooltip + text={i18n.str`You confirm that the incoming wire transfer has arrived into your bank account.`} > - <i18n.Translate>Details</i18n.Translate> - </a> - </Tooltip> - </div> - )} - </td> - </tr> - ); - })} - </tbody> - </table> - {onLoadMoreAfter && ( - <Tooltip - text={i18n.str`Load more transfers after the last one`} - > - <button - type="button" - class="button is-fullwidth" - onClick={onLoadMoreAfter} + <ButtonBetterBulma + class="button is-success is-small" + onClick={ + !session.token + ? undefined + : confirm.withArgs(session.token, i) + } + type="button" + > + Confirm + </ButtonBetterBulma> + </Tooltip> + </div> + )} + </td> + </tr> + ); + })} + </tbody> + </table> + {onLoadMoreAfter && ( + <Tooltip + text={i18n.str`Load more transfers after the last one`} > - <i18n.Translate>Load next page</i18n.Translate> - </button> - </Tooltip> - )} - </div> - ) : ( - <EmptyTable /> - )} + <button + type="button" + class="button is-fullwidth" + onClick={onLoadMoreAfter} + > + <i18n.Translate>Load next page</i18n.Translate> + </button> + </Tooltip> + )} + </div> + ) : ( + <EmptyTable /> + )} + </div> </div> </div> </div> - </div> + </Fragment> ); } @@ -173,12 +239,14 @@ interface TablePropsVerified { transfers: (TalerMerchantApi.TransferDetails & WithId)[]; onLoadMoreBefore?: () => void; onLoadMoreAfter?: () => void; + onDetails: (d: number) => void; } export function CardTableVerified({ transfers, onLoadMoreAfter, onLoadMoreBefore, + onDetails, }: TablePropsVerified): VNode { const { i18n } = useTranslationContext(); const { config } = useSessionContext(); @@ -211,7 +279,7 @@ export function CardTableVerified({ </button> </Tooltip> )} - <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> + <table class="table is-fullwidth is-striped is-hoverable"> <thead> <tr> <th> @@ -223,9 +291,7 @@ export function CardTableVerified({ <th> <i18n.Translate>ID</i18n.Translate> </th> - <th> - <i18n.Translate>Expected</i18n.Translate> - </th> + <th></th> </tr> </thead> <tbody> @@ -255,11 +321,21 @@ export function CardTableVerified({ {i.wtid} </td> <td> - {i.expected ? ( - <i class="icon mdi mdi-check" /> - ) : ( - <i class="icon mdi mdi-close" /> - )} + <div class="buttons is-right"> + <Tooltip + text={i18n.str`Show details about the incoming wire transfer.`} + > + <button + class="button is-info is-small" + onClick={() => { + onDetails(i.transfer_serial_id); + }} + type="button" + > + <i18n.Translate>Details</i18n.Translate> + </button> + </Tooltip> + </div> </td> </tr> ); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx @@ -46,6 +46,7 @@ import { import { LoginPage } from "../../../login/index.js"; import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js"; import { ListPage } from "./ListPage.js"; +import { useSessionContext } from "../../../../context/session.js"; interface Props { onTransferDetails: (id: number) => void; @@ -163,7 +164,6 @@ function ListTransferWithBank({ setForm={setForm} incomings={incoming} transfers={confirmed} - onSelect={() => undefined} /> ); } @@ -174,7 +174,6 @@ function ListTransferInternal({ accounts, form, setForm, - onSelect, onTransferDetails, }: { onTransferDetails: (id: number) => void; @@ -183,7 +182,6 @@ function ListTransferInternal({ accounts: string[]; setForm: StateUpdater<Form>; form: Form; - onSelect: () => void; }): VNode { const { i18n } = useTranslationContext(); @@ -221,9 +219,7 @@ function ListTransferInternal({ <ListPage transfers={transfers} incomings={incomings} - onSelectedToConfirm={(e) => - onTransferDetails(e.expected_transfer_serial_id!) - } + onDetails={(id) => onTransferDetails(id)} /> </Fragment> );