diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/components')
8 files changed, 1072 insertions, 678 deletions
diff --git a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx index b8bcaa391..6dd577b88 100644 --- a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx +++ b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx @@ -15,8 +15,10 @@ */ import { Amounts, ScopeType, WalletBalance } from "@gnu-taler/taler-util"; -import { VNode, h } from "preact"; -import { TableWithRoundRows as TableWithRoundedRows } from "./styled/index.js"; +import { Fragment, VNode, h } from "preact"; +import { + TableWithRoundRows as TableWithRoundedRows +} from "./styled/index.js"; export function BalanceTable({ balances, @@ -26,32 +28,37 @@ export function BalanceTable({ goToWalletHistory: (currency: string) => void; }): VNode { return ( - <TableWithRoundedRows> - {balances.map((entry, idx) => { - const av = Amounts.parseOrThrow(entry.available); + <Fragment> + <TableWithRoundedRows> + {balances.map((entry, idx) => { + const av = Amounts.parseOrThrow(entry.available); - return ( - <tr - key={idx} - onClick={() => goToWalletHistory(av.currency)} - style={{ cursor: "pointer" }} - > - <td>{av.currency}</td> - <td - style={{ - fontSize: "2em", - textAlign: "right", - width: "100%", - }} + return ( + <tr + key={idx} + onClick={() => goToWalletHistory(av.currency)} + style={{ cursor: "pointer" }} > - {Amounts.stringifyValue(av, 2)} - <div style={{ fontSize: "small", color: "grey" }}> - {entry.scopeInfo.type === ScopeType.Exchange || entry.scopeInfo.type === ScopeType.Auditor ? entry.scopeInfo.url : undefined} - </div> - </td> - </tr> - ); - })} - </TableWithRoundedRows> + <td>{av.currency}</td> + <td + style={{ + fontSize: "2em", + textAlign: "right", + width: "100%", + }} + > + {Amounts.stringifyValue(av, 2)} + <div style={{ fontSize: "small", color: "grey" }}> + {entry.scopeInfo.type === ScopeType.Exchange || + entry.scopeInfo.type === ScopeType.Auditor + ? entry.scopeInfo.url + : undefined} + </div> + </td> + </tr> + ); + })} + </TableWithRoundedRows> + </Fragment> ); } diff --git a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx index bf77174df..8b6377fc5 100644 --- a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx +++ b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx @@ -21,11 +21,9 @@ import { segwitMinAmount, stringifyPaytoUri, TranslatedString, - WithdrawalExchangeAccountDetails + WithdrawalExchangeAccountDetails, } from "@gnu-taler/taler-util"; -import { - useTranslationContext -} from "@gnu-taler/web-util/browser"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; import { CopiedIcon, CopyIcon } from "../svg/index.js"; @@ -36,72 +34,94 @@ import { Button } from "../mui/Button.js"; export interface BankDetailsProps { subject: string; amount: AmountJson; - accounts: WithdrawalExchangeAccountDetails[], + accounts: WithdrawalExchangeAccountDetails[]; } export function BankDetailsByPaytoType({ subject, amount, - accounts, + accounts: unsortedAccounts, }: BankDetailsProps): VNode { const { i18n } = useTranslationContext(); - const [index, setIndex] = useState(0) - // const [currency, setCurrency] = useState(amount.currency) - if (!accounts.length) { - return <div>the exchange account list is empty</div> + const [index, setIndex] = useState(0); + + if (!unsortedAccounts.length) { + return <div>the exchange account list is empty</div>; } + + const accounts = unsortedAccounts.sort((a, b) => { + return (b.priority ?? 0) - (a.priority ?? 0); + }); + const selectedAccount = accounts[index]; - const altCurrency = selectedAccount.currencySpecification?.name + const altCurrency = selectedAccount.currencySpecification?.name; const payto = parsePaytoUri(selectedAccount.paytoUri); if (!payto) return <Fragment />; - payto.params["amount"] = altCurrency ? selectedAccount.transferAmount! : Amounts.stringify(amount); + payto.params["amount"] = altCurrency + ? selectedAccount.transferAmount! + : Amounts.stringify(amount); payto.params["message"] = subject; - - function Frame({ title, children }: { title: TranslatedString, children: ComponentChildren }): VNode { - return <section - style={{ - textAlign: "left", - border: "solid 1px black", - padding: 8, - borderRadius: 4, - }} - > - <div style={{ display: "flex", width: "100%", justifyContent: "space-between" }}> - <p style={{ marginTop: 0 }}> - {title} - </p> - <div> - + function Frame({ + title, + children, + }: { + title: TranslatedString; + children: ComponentChildren; + }): VNode { + return ( + <section + style={{ + textAlign: "left", + border: "solid 1px black", + padding: 8, + borderRadius: 4, + }} + > + <div + style={{ + display: "flex", + width: "100%", + justifyContent: "space-between", + }} + > + <p style={{ marginTop: 0 }}>{title}</p> + <div></div> </div> - </div> - {children} + {children} - {accounts.length > 1 ? - <Fragment> - {accounts.map((ac, acIdx) => { - return <Button variant={acIdx === index ? "contained" : "outlined"} - onClick={async () => { - setIndex(acIdx) - }} - > - <i18n.Translate>Account #{acIdx+1} ({ac.currencySpecification?.name ?? amount.currency})</i18n.Translate> - </Button> - })} + {accounts.length > 1 ? ( + <Fragment> + {accounts.map((ac, acIdx) => { + const accountLabel = ac.bankLabel ?? `Account #${acIdx + 1}`; + return ( + <Button + key={acIdx} + variant={acIdx === index ? "contained" : "outlined"} + onClick={async () => { + setIndex(acIdx); + }} + > + {accountLabel} ( + {ac.currencySpecification?.name ?? amount.currency}) + </Button> + ); + })} - {/* <Button variant={currency === altCurrency ? "contained" : "outlined"} + {/* <Button variant={currency === altCurrency ? "contained" : "outlined"} onClick={async () => { setCurrency(altCurrency) }} > <i18n.Translate>{altCurrency}</i18n.Translate> </Button> */} - </Fragment> - : undefined} - </section> + </Fragment> + ) : undefined} + </section> + ); } if (payto.isKnown && payto.targetType === "bitcoin") { @@ -157,7 +177,9 @@ export function BankDetailsByPaytoType({ } const accountPart = !payto.isKnown ? ( - <Row name={i18n.str`Account`} value={payto.targetPath} /> + <Fragment> + <Row name={i18n.str`Account`} value={payto.targetPath} /> + </Fragment> ) : payto.targetType === "x-taler-bank" ? ( <Fragment> <Row name={i18n.str`Bank host`} value={payto.host} /> @@ -172,30 +194,68 @@ export function BankDetailsByPaytoType({ </Fragment> ) : undefined; - const receiver = payto.params["receiver-name"] || payto.params["receiver"] || undefined; + const receiver = + payto.params["receiver-name"] || payto.params["receiver"] || undefined; return ( <Frame title={i18n.str`Bank transfer details`}> <table> <tbody> - {accountPart} + <tr> + <td colSpan={3}> + <i18n.Translate>Step 1:</i18n.Translate> + + <i18n.Translate> + Copy this code and paste it into the subject/purpose field in + your banking app or bank website + </i18n.Translate> + </td> + </tr> <Row name={i18n.str`Subject`} value={subject} literal /> - <Row - name={i18n.str`Amount`} - value={<Amount value={altCurrency ? selectedAccount.transferAmount! : amount} hideCurrency />} - /> - + <tr> + <td colSpan={3}> + <i18n.Translate>Step 2:</i18n.Translate> + + <i18n.Translate> + If you don't already have it in your banking favourites list, + then copy and paste this IBAN and the name into the receiver + fields in your banking app or website + </i18n.Translate> + </td> + </tr> + {accountPart} {receiver ? ( <Row name={i18n.str`Receiver name`} value={receiver} /> ) : undefined} <tr> <td colSpan={3}> + <i18n.Translate>Step 3:</i18n.Translate> + + <i18n.Translate> + Finish the wire transfer setting the amount in your banking app + or website, then this withdrawal will proceed automatically. + </i18n.Translate> + </td> + </tr> + <Row + name={i18n.str`Amount`} + value={ + <Amount + value={altCurrency ? selectedAccount.transferAmount! : amount} + hideCurrency + /> + } + /> + + <tr> + <td colSpan={3}> <WarningBox style={{ margin: 0 }}> <span> <i18n.Translate> - Make sure ALL data is correct, including the subject; otherwise, the money will not - arrive in this wallet. You can use the copy buttons (<CopyIcon />) to prevent typing errors + Make sure ALL data is correct, including the subject; + otherwise, the money will not arrive in this wallet. You can + use the copy buttons (<CopyIcon />) to prevent typing errors or the "payto://" URI below to copy just one value. </i18n.Translate> </span> @@ -204,22 +264,20 @@ export function BankDetailsByPaytoType({ </tr> <tr> - <td> - <pre> - <b> - <a - target="_bank" - rel="noreferrer" - title="RFC 8905 for designating targets for payments" - href="https://tools.ietf.org/html/rfc8905" - > - Payto URI - </a> - </b> - </pre> - </td> - <td width="100%" style={{ wordBreak: "break-all" }}> - {stringifyPaytoUri(payto)} + <td colSpan={2} width="100%" style={{ wordBreak: "break-all" }}> + <i18n.Translate> + Alternative if your bank already supports PayTo URI, you can use + this{" "} + <a + target="_bank" + rel="noreferrer" + title="RFC 8905 for designating targets for payments" + href="https://tools.ietf.org/html/rfc8905" + > + PayTo URI + </a>{" "} + link instead + </i18n.Translate> </td> <td> <CopyButton getContent={() => stringifyPaytoUri(payto)} /> diff --git a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx index 4b44365ea..9be9326b2 100644 --- a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx +++ b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx @@ -23,6 +23,8 @@ import { TransactionType, WithdrawalType, TransactionMajorState, + DenomLossEventType, + parsePaytoUri, } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -134,10 +136,6 @@ export function HistoryItem(props: { tx: Transaction }): VNode { } /> ); - case TransactionType.Reward: - return ( - <div>not supported</div> - ); case TransactionType.Refresh: return ( <Layout @@ -155,13 +153,16 @@ export function HistoryItem(props: { tx: Transaction }): VNode { } /> ); - case TransactionType.Deposit: + case TransactionType.Deposit:{ + const payto = parsePaytoUri(tx.targetPaytoUri); + const title = payto === undefined || !payto.isKnown ? tx.targetPaytoUri : + payto.params["receiver-name"] ; return ( <Layout id={tx.transactionId} amount={tx.amountEffective} debitCreditIndicator={"debit"} - title={tx.targetPaytoUri} + title={title} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} iconPath={"D"} currentState={tx.txState.major} @@ -172,6 +173,7 @@ export function HistoryItem(props: { tx: Transaction }): VNode { } /> ); + } case TransactionType.PeerPullCredit: return ( <Layout @@ -240,6 +242,56 @@ export function HistoryItem(props: { tx: Transaction }): VNode { } /> ); + case TransactionType.DenomLoss: { + switch (tx.lossEventType) { + case DenomLossEventType.DenomExpired: { + return ( + <Layout + id={tx.transactionId} + amount={tx.amountEffective} + debitCreditIndicator={"debit"} + title={i18n.str`Denomination expired`} + timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} + iconPath={"L"} + currentState={tx.txState.major} + description={undefined} + /> + ); + } + case DenomLossEventType.DenomVanished: { + return ( + <Layout + id={tx.transactionId} + amount={tx.amountEffective} + debitCreditIndicator={"debit"} + title={i18n.str`Denomination vanished`} + timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} + iconPath={"L"} + currentState={tx.txState.major} + description={undefined} + /> + ); + } + case DenomLossEventType.DenomUnoffered: { + return ( + <Layout + id={tx.transactionId} + amount={tx.amountEffective} + debitCreditIndicator={"debit"} + title={i18n.str`Denomination unoffered`} + timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} + iconPath={"L"} + currentState={tx.txState.major} + description={undefined} + /> + ); + } + default: { + assertUnreachable(tx.lossEventType); + } + } + break; + } case TransactionType.Recoup: throw Error("recoup transaction not implemented"); default: { @@ -256,12 +308,12 @@ function Layout(props: LayoutProps): VNode { style={{ backgroundColor: props.currentState === TransactionMajorState.Pending || - props.currentState === TransactionMajorState.Dialog + props.currentState === TransactionMajorState.Dialog ? "lightcyan" : props.currentState === TransactionMajorState.Failed ? "#ff000040" : props.currentState === TransactionMajorState.Aborted || - props.currentState === TransactionMajorState.Aborting + props.currentState === TransactionMajorState.Aborting ? "#00000010" : "inherit", alignItems: "center", diff --git a/packages/taler-wallet-webextension/src/components/Modal.tsx b/packages/taler-wallet-webextension/src/components/Modal.tsx index 5553c72df..f8c0f1651 100644 --- a/packages/taler-wallet-webextension/src/components/Modal.tsx +++ b/packages/taler-wallet-webextension/src/components/Modal.tsx @@ -52,7 +52,7 @@ const Body = styled.div` export function Modal({ title, children, onClose }: Props): VNode { return ( - <div style={{ position: "fixed", top: 0, width: "100%", height: "100%" }}> + <div style={{ top: 0, width: "100%", height: "100%" }}> <FullSize onClick={onClose?.onClick}> <div @@ -64,6 +64,7 @@ export function Modal({ title, children, onClose }: Props): VNode { margin: "auto", borderRadius: 8, padding: 8, + zIndex: 100, // overflow: "scroll", }} > diff --git a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx index 372ca7cb7..c94010ede 100644 --- a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx +++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx @@ -42,7 +42,10 @@ interface Props extends JSX.HTMLAttributes { */ const cache = { tx: [] as Transaction[] }; -export function PendingTransactions({ goToTransaction, goToURL }: Props): VNode { +export function PendingTransactions({ + goToTransaction, + goToURL, +}: Props): VNode { const api = useBackendContext(); const state = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.GetTransactions, {}), @@ -59,8 +62,8 @@ export function PendingTransactions({ goToTransaction, goToURL }: Props): VNode !state || state.hasError ? cache.tx : state.response.transactions.filter( - (t) => t.txState.major === TransactionMajorState.Pending, - ); + (t) => t.txState.major === TransactionMajorState.Pending, + ); if (state && !state.hasError) { cache.tx = transactions; @@ -87,50 +90,52 @@ export function PendingTransactionsView({ transactions: Transaction[]; }): VNode { const { i18n } = useTranslationContext(); - const kycTransaction = transactions.find(tx => tx.kycUrl) + const kycTransaction = transactions.find((tx) => tx.kycUrl); if (kycTransaction) { - return <div - style={{ - backgroundColor: "lightcyan", - display: "flex", - justifyContent: "center", - }} - > - <Banner - titleHead={i18n.str`KYC requirement`} + return ( + <div style={{ - backgroundColor: "lightred", - maxHeight: 150, - padding: 8, - flexGrow: 1, - maxWidth: 500, - overflowY: transactions.length > 3 ? "scroll" : "hidden", + backgroundColor: "#fff3cd", + color: "#664d03", + display: "flex", + justifyContent: "center", }} > - <Grid - container - item - xs={1} - wrap="nowrap" - role="button" - spacing={1} - alignItems="center" - onClick={() => { - goToURL(kycTransaction.kycUrl ?? "#") + <Banner + titleHead={i18n.str`KYC requirement`} + style={{ + backgroundColor: "lightred", + maxHeight: 150, + padding: 8, + flexGrow: 1, //#fff3cd //#ffecb5 + maxWidth: 500, + overflowY: transactions.length > 3 ? "scroll" : "hidden", }} > - <Grid item> - <Typography inline bold> - One or more transaction require a KYC step to complete - </Typography> + <Grid + container + item + xs={1} + wrap="nowrap" + role="button" + spacing={1} + alignItems="center" + onClick={() => { + goToURL(kycTransaction.kycUrl ?? "#"); + }} + > + <Grid item> + <Typography inline bold> + One or more transaction require a KYC step to complete + </Typography> + </Grid> </Grid> - - </Grid> - </Banner> - </div> + </Banner> + </div> + ); } - if (!goToTransaction) return <Fragment /> + if (!goToTransaction) return <Fragment />; return ( <div diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx index 99e2d0a76..0e23d5850 100644 --- a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx +++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx @@ -81,7 +81,7 @@ export const ShowingSimpleOrder = tests.createExample(ShowView, { contractTerms: cd, }); export const Error = tests.createExample(ErrorView, { - proposalId: "asd", + transactionId: "asd", error: { hasError: true, message: "message", diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx index b0f43d0d9..e655def39 100644 --- a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx +++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx @@ -17,6 +17,7 @@ import { AbsoluteTime, Duration, Location, + TransactionIdStr, WalletContractData, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -83,7 +84,7 @@ export namespace States { } export interface Error { status: "error"; - proposalId: string; + transactionId: string; error: HookError; hideHandler: ButtonHandler; } @@ -99,17 +100,17 @@ export namespace States { } interface Props { - proposalId: string; + transactionId: TransactionIdStr; } -function useComponentState({ proposalId }: Props): State { +function useComponentState({ transactionId }: Props): State { const api = useBackendContext(); const [show, setShow] = useState(false); const { pushAlertOnError } = useAlertContext(); const hook = useAsyncAsHook(async () => { if (!show) return undefined; return await api.wallet.call(WalletApiOperation.GetContractTermsDetails, { - proposalId, + transactionId, }); }, [show]); @@ -127,7 +128,7 @@ function useComponentState({ proposalId }: Props): State { } if (!hook) return { status: "loading", hideHandler }; if (hook.hasError) - return { status: "error", proposalId, error: hook, hideHandler }; + return { status: "error", transactionId, error: hook, hideHandler }; if (!hook.response) return { status: "loading", hideHandler }; return { status: "show", @@ -160,7 +161,7 @@ export function LoadingView({ hideHandler }: States.Loading): VNode { export function ErrorView({ hideHandler, error, - proposalId, + transactionId, }: States.Error): VNode { const { i18n } = useTranslationContext(); return ( @@ -170,7 +171,7 @@ export function ErrorView({ i18n, i18n.str`Could not load purchase proposal details`, error, - { proposalId }, + { transactionId }, )} /> </Modal> diff --git a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx index 60839e1f0..69a2c0675 100644 --- a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx +++ b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx @@ -22,7 +22,7 @@ import { TalerErrorDetail, TaskProgressNotification, WalletNotification, - assertUnreachable + assertUnreachable, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -37,61 +37,80 @@ import { WxApiType } from "../wxApi.js"; import { Modal } from "./Modal.js"; import { Time } from "./Time.js"; -interface Props extends JSX.HTMLAttributes { -} +interface Props extends JSX.HTMLAttributes {} -export function WalletActivity({ }: Props): VNode { - const { i18n } = useTranslationContext() - const [settings, updateSettings] = useSettings() +export function WalletActivity({}: Props): VNode { + const { i18n } = useTranslationContext(); + const [settings, updateSettings] = useSettings(); const api = useBackendContext(); useEffect(() => { - document.body.style.marginBottom = "250px" + document.body.style.marginBottom = "250px"; return () => { - document.body.style.marginBottom = "0px" - } - }) - const [table, setTable] = useState<"tasks" | "events">("tasks") + document.body.style.marginBottom = "0px"; + }; + }); + const [table, setTable] = useState<"tasks" | "events">("tasks"); return ( - <div style={{ position: "fixed", bottom: 0, background: "white", zIndex: 1, height: 250, overflowY: "scroll", width: "100%" }}> - <div style={{ display: "flex", justifyContent: "space-between", float: "right" }}> + <div + style={{ + position: "fixed", + bottom: 0, + background: "white", + zIndex: 1, + height: 250, + overflowY: "scroll", + width: "100%", + }} + > + <div + style={{ + display: "flex", + justifyContent: "space-between", + float: "right", + }} + > <div /> <div> - <div style={{ padding: 4, margin: 2, border: "solid 1px black" }} onClick={() => { - updateSettings("showWalletActivity", false) - }}> + <div + style={{ padding: 4, margin: 2, border: "solid 1px black" }} + onClick={() => { + updateSettings("showWalletActivity", false); + }} + > close </div> </div> </div> <div style={{ display: "flex", justifyContent: "space-around" }}> - <Button variant={table === "tasks" ? "contained" : "outlined"} + <Button + variant={table === "tasks" ? "contained" : "outlined"} style={{ margin: 4 }} onClick={async () => { - setTable("tasks") + setTable("tasks"); }} > <i18n.Translate>Tasks</i18n.Translate> </Button> - <Button variant={table === "events" ? "contained" : "outlined"} + <Button + variant={table === "events" ? "contained" : "outlined"} style={{ margin: 4 }} onClick={async () => { - setTable("events") + setTable("events"); }} > <i18n.Translate>Events</i18n.Translate> </Button> - </div> {(function (): VNode { switch (table) { case "events": { - return <ObservabilityEventsTable /> + return <ObservabilityEventsTable />; } case "tasks": { - return <ActiveTasksTable /> + return <ActiveTasksTable />; } default: { - assertUnreachable(table) + assertUnreachable(table); } } })()} @@ -99,406 +118,571 @@ export function WalletActivity({ }: Props): VNode { ); } -interface MoreInfoPRops { events: (WalletNotification & { when: AbsoluteTime })[], onClick: (content: VNode) => void } +interface MoreInfoPRops { + events: (WalletNotification & { when: AbsoluteTime })[]; + onClick: (content: VNode) => void; +} type Notif = { id: string; events: (WalletNotification & { when: AbsoluteTime })[]; description: string; start: AbsoluteTime; end: AbsoluteTime; - reference: { - eventType: NotificationType, - referenceType: "task" | "transaction" | "operation" | "exchange", - id: string; - } | undefined, + reference: + | { + eventType: NotificationType; + referenceType: "task" | "transaction" | "operation" | "exchange"; + id: string; + } + | undefined; MoreInfo: (p: MoreInfoPRops) => VNode; -} +}; function ShowBalanceChange({ events }: MoreInfoPRops): VNode { if (!events.length) return <Fragment />; const not = events[0]; if (not.type !== NotificationType.BalanceChange) return <Fragment />; - return <Fragment> - <dt>Transaction</dt> - <dd> - <a title={not.hintTransactionId} href={Pages.balanceTransaction({ tid: not.hintTransactionId })}>{not.hintTransactionId.substring(0, 10)}</a> - </dd> - </Fragment> + return ( + <Fragment> + <dt>Transaction</dt> + <dd> + <a + title={not.hintTransactionId} + href={Pages.balanceTransaction({ tid: not.hintTransactionId })} + > + {not.hintTransactionId.substring(0, 10)} + </a> + </dd> + </Fragment> + ); } function ShowBackupOperationError({ events, onClick }: MoreInfoPRops): VNode { if (!events.length) return <Fragment />; const not = events[0]; if (not.type !== NotificationType.BackupOperationError) return <Fragment />; - return <Fragment> - <dt>Error</dt> - <dd> - <a href="#" onClick={(e) => { - e.preventDefault(); - const error = not.error - onClick(<Fragment> - <dl> - <dt>Code</dt> - <dd>{TalerErrorCode[error.code]} ({error.code})</dd> - <dt>Hint</dt> - <dd>{error.hint ?? "--"}</dd> - <dt>Time</dt> - <dd><Time - timestamp={error.when} - format="yyyy/MM/dd HH:mm:ss" - /></dd> - </dl> - <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> - {JSON.stringify(error, undefined, 2)} - </pre> - </Fragment>) - }}>{TalerErrorCode[not.error.code]}</a> - </dd> - </Fragment> -} - -function ShowTransactionStateTransition({ events, onClick }: MoreInfoPRops): VNode { - if (!events.length) return <Fragment />; - const not = events[0]; - if (not.type !== NotificationType.TransactionStateTransition) return <Fragment />; - return <Fragment> - <dt>Old state</dt> - <dd> - {not.oldTxState.major} - {not.oldTxState.minor ?? ""} - </dd> - <dt>New state</dt> - <dd> - {not.newTxState.major} - {not.newTxState.minor ?? ""} - </dd> - <dt>Transaction</dt> - <dd> - <a title={not.transactionId} href={Pages.balanceTransaction({ tid: not.transactionId })}>{not.transactionId.substring(0, 10)}</a> - </dd> - {not.errorInfo ? <Fragment> + return ( + <Fragment> <dt>Error</dt> <dd> - <a href="#" onClick={(e) => { - if (!not.errorInfo) return; - e.preventDefault(); - const error = not.errorInfo; - onClick(<Fragment> - <dl> - <dt>Code</dt> - <dd>{TalerErrorCode[error.code]} ({error.code})</dd> - <dt>Hint</dt> - <dd>{error.hint ?? "--"}</dd> - <dt>Message</dt> - <dd>{error.message ?? "--"}</dd> - </dl> - </Fragment>) - - }}>{TalerErrorCode[not.errorInfo.code]}</a> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + const error = not.error; + onClick( + <Fragment> + <dl> + <dt>Code</dt> + <dd> + {TalerErrorCode[error.code]} ({error.code}) + </dd> + <dt>Hint</dt> + <dd>{error.hint ?? "--"}</dd> + <dt>Time</dt> + <dd> + <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss" /> + </dd> + </dl> + <pre + style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + > + {JSON.stringify(error, undefined, 2)} + </pre> + </Fragment>, + ); + }} + > + {TalerErrorCode[not.error.code]} + </a> </dd> - </Fragment> : undefined} - <dt>Experimental</dt> - <dd> - <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> - {JSON.stringify(not.experimentalUserData, undefined, 2)} - </pre> - </dd> - - - </Fragment> + </Fragment> + ); } -function ShowExchangeStateTransition({ events, onClick }: MoreInfoPRops): VNode { + +function ShowTransactionStateTransition({ + events, + onClick, +}: MoreInfoPRops): VNode { if (!events.length) return <Fragment />; const not = events[0]; - if (not.type !== NotificationType.ExchangeStateTransition) return <Fragment />; - return <Fragment> - <dt>Exchange</dt> - <dd> - {not.exchangeBaseUrl} - </dd> - {not.oldExchangeState && not.newExchangeState.exchangeEntryStatus !== not.oldExchangeState?.exchangeEntryStatus && <Fragment> - <dt>Entry status</dt> + if (not.type !== NotificationType.TransactionStateTransition) + return <Fragment />; + return ( + <Fragment> + <dt>Old state</dt> <dd> - from {not.oldExchangeState.exchangeEntryStatus} to {not.newExchangeState.exchangeEntryStatus} + {not.oldTxState.major} - {not.oldTxState.minor ?? ""} </dd> - </Fragment>} - {not.oldExchangeState && not.newExchangeState.exchangeUpdateStatus !== not.oldExchangeState?.exchangeUpdateStatus && <Fragment> - <dt>Update status</dt> + <dt>New state</dt> <dd> - from {not.oldExchangeState.exchangeUpdateStatus} to {not.newExchangeState.exchangeUpdateStatus} + {not.newTxState.major} - {not.newTxState.minor ?? ""} </dd> - </Fragment>} - {not.oldExchangeState && not.newExchangeState.tosStatus !== not.oldExchangeState?.tosStatus && <Fragment> - <dt>Tos status</dt> + <dt>Transaction</dt> <dd> - from {not.oldExchangeState.tosStatus} to {not.newExchangeState.tosStatus} + <a + title={not.transactionId} + href={Pages.balanceTransaction({ tid: not.transactionId })} + > + {not.transactionId.substring(0, 10)} + </a> </dd> - </Fragment>} - </Fragment> + {not.errorInfo ? ( + <Fragment> + <dt>Error</dt> + <dd> + <a + href="#" + onClick={(e) => { + if (!not.errorInfo) return; + e.preventDefault(); + const error = not.errorInfo; + onClick( + <Fragment> + <dl> + <dt>Code</dt> + <dd> + {TalerErrorCode[error.code]} ({error.code}) + </dd> + <dt>Hint</dt> + <dd>{error.hint ?? "--"}</dd> + <dt>Message</dt> + <dd>{error.message ?? "--"}</dd> + </dl> + </Fragment>, + ); + }} + > + {TalerErrorCode[not.errorInfo.code]} + </a> + </dd> + </Fragment> + ) : undefined} + <dt>Experimental</dt> + <dd> + <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> + {JSON.stringify(not.experimentalUserData, undefined, 2)} + </pre> + </dd> + </Fragment> + ); +} +function ShowExchangeStateTransition({ + events, + onClick, +}: MoreInfoPRops): VNode { + if (!events.length) return <Fragment />; + const not = events[0]; + if (not.type !== NotificationType.ExchangeStateTransition) + return <Fragment />; + return ( + <Fragment> + <dt>Exchange</dt> + <dd>{not.exchangeBaseUrl}</dd> + {not.oldExchangeState && + not.newExchangeState.exchangeEntryStatus !== + not.oldExchangeState?.exchangeEntryStatus && ( + <Fragment> + <dt>Entry status</dt> + <dd> + from {not.oldExchangeState.exchangeEntryStatus} to{" "} + {not.newExchangeState.exchangeEntryStatus} + </dd> + </Fragment> + )} + {not.oldExchangeState && + not.newExchangeState.exchangeUpdateStatus !== + not.oldExchangeState?.exchangeUpdateStatus && ( + <Fragment> + <dt>Update status</dt> + <dd> + from {not.oldExchangeState.exchangeUpdateStatus} to{" "} + {not.newExchangeState.exchangeUpdateStatus} + </dd> + </Fragment> + )} + {not.oldExchangeState && + not.newExchangeState.tosStatus !== not.oldExchangeState?.tosStatus && ( + <Fragment> + <dt>Tos status</dt> + <dd> + from {not.oldExchangeState.tosStatus} to{" "} + {not.newExchangeState.tosStatus} + </dd> + </Fragment> + )} + </Fragment> + ); } -type ObservaNotifWithTime = ((TaskProgressNotification | RequestProgressNotification) & { +type ObservaNotifWithTime = ( + | TaskProgressNotification + | RequestProgressNotification +) & { when: AbsoluteTime; -}) +}; function ShowObservabilityEvent({ events, onClick }: MoreInfoPRops): VNode { // let prev: ObservaNotifWithTime; - const asd = events.map(not => { - if (not.type !== NotificationType.RequestObservabilityEvent && not.type !== NotificationType.TaskObservabilityEvent) return <Fragment />; + const asd = events.map((not) => { + if ( + not.type !== NotificationType.RequestObservabilityEvent && + not.type !== NotificationType.TaskObservabilityEvent + ) + return <Fragment />; const title = (function () { switch (not.event.type) { case ObservabilityEventType.HttpFetchFinishError: case ObservabilityEventType.HttpFetchFinishSuccess: - case ObservabilityEventType.HttpFetchStart: return "HTTP Request" + case ObservabilityEventType.HttpFetchStart: + return "HTTP Request"; case ObservabilityEventType.DbQueryFinishSuccess: case ObservabilityEventType.DbQueryFinishError: - case ObservabilityEventType.DbQueryStart: return "Database" + case ObservabilityEventType.DbQueryStart: + return "Database"; case ObservabilityEventType.RequestFinishSuccess: case ObservabilityEventType.RequestFinishError: - case ObservabilityEventType.RequestStart: return "Wallet" + case ObservabilityEventType.RequestStart: + return "Wallet"; case ObservabilityEventType.CryptoFinishSuccess: case ObservabilityEventType.CryptoFinishError: - case ObservabilityEventType.CryptoStart: return "Crypto" - case ObservabilityEventType.TaskStart: return "Task start" - case ObservabilityEventType.TaskStop: return "Task stop" - case ObservabilityEventType.TaskReset: return "Task reset" - case ObservabilityEventType.ShepherdTaskResult: return "Schedule" - case ObservabilityEventType.DeclareTaskDependency: return "Task dependency" + case ObservabilityEventType.CryptoStart: + return "Crypto"; + case ObservabilityEventType.TaskStart: + return "Task start"; + case ObservabilityEventType.TaskStop: + return "Task stop"; + case ObservabilityEventType.TaskReset: + return "Task reset"; + case ObservabilityEventType.ShepherdTaskResult: + return "Schedule"; + case ObservabilityEventType.DeclareTaskDependency: + return "Task dependency"; + case ObservabilityEventType.Message: + return "Message"; } })(); - return <ShowObervavilityDetails title={title} notif={not} onClick={onClick} /> - - }) - return <table> - <thead> - <td>Event</td> - <td>Info</td> - <td>Start</td> - <td>End</td> - </thead> - <tbody> - {asd} - </tbody> - </table> + return ( + <ShowObervavilityDetails title={title} notif={not} onClick={onClick} /> + ); + }); + return ( + <table> + <thead> + <td>Event</td> + <td>Info</td> + <td>Start</td> + <td>End</td> + </thead> + <tbody>{asd}</tbody> + </table> + ); } -function ShowObervavilityDetails({ title, notif, onClick, prev }: { title: string, notif: ObservaNotifWithTime, prev?: ObservaNotifWithTime, onClick: (content: VNode) => void }): VNode { +function ShowObervavilityDetails({ + title, + notif, + onClick, + prev, +}: { + title: string; + notif: ObservaNotifWithTime; + prev?: ObservaNotifWithTime; + onClick: (content: VNode) => void; +}): VNode { switch (notif.event.type) { case ObservabilityEventType.HttpFetchStart: case ObservabilityEventType.HttpFetchFinishError: case ObservabilityEventType.HttpFetchFinishSuccess: { - return <tr> - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} - > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></td> - <td> - {notif.event.url} { - prev?.event.type === ObservabilityEventType.HttpFetchFinishSuccess ? `(${prev.event.status})` - : prev?.event.type === ObservabilityEventType.HttpFetchFinishError ? <a href="#" onClick={(e) => { + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { e.preventDefault(); - if (prev.event.type !== ObservabilityEventType.HttpFetchFinishError) return; - const error = prev.event.error - onClick(<Fragment> - <dl> - <dt>Code</dt> - <dd>{TalerErrorCode[error.code]} ({error.code})</dd> - <dt>Hint</dt> - <dd>{error.hint ?? "--"}</dd> - <dt>Time</dt> - <dd><Time - timestamp={error.when} - format="yyyy/MM/dd HH:mm:ss" - /></dd> - </dl> - <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> - {JSON.stringify(error, undefined, 2)} - </pre> - - </Fragment>) - }}>fail</a> : undefined - } - </td> - <td> <Time - timestamp={notif.when} - format="yyyy/MM/dd HH:mm:ss" - /></td> - <td> <Time - timestamp={prev?.when} - format="yyyy/MM/dd HH:mm:ss" - /></td> - </tr> - + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} + > + {title} + </a> + </td> + <td> + {notif.event.url}{" "} + {prev?.event.type === + ObservabilityEventType.HttpFetchFinishSuccess ? ( + `(${prev.event.status})` + ) : prev?.event.type === + ObservabilityEventType.HttpFetchFinishError ? ( + <a + href="#" + onClick={(e) => { + e.preventDefault(); + if ( + prev.event.type !== + ObservabilityEventType.HttpFetchFinishError + ) + return; + const error = prev.event.error; + onClick( + <Fragment> + <dl> + <dt>Code</dt> + <dd> + {TalerErrorCode[error.code]} ({error.code}) + </dd> + <dt>Hint</dt> + <dd>{error.hint ?? "--"}</dd> + <dt>Time</dt> + <dd> + <Time + timestamp={error.when} + format="yyyy/MM/dd HH:mm:ss" + /> + </dd> + </dl> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify(error, undefined, 2)} + </pre> + </Fragment>, + ); + }} + > + fail + </a> + ) : undefined} + </td> + <td> + {" "} + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + <td> + {" "} + <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + </tr> + ); } case ObservabilityEventType.DbQueryStart: case ObservabilityEventType.DbQueryFinishSuccess: case ObservabilityEventType.DbQueryFinishError: { - return <tr> - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></td> - <td> - {notif.event.location} {notif.event.name} - </td> - <td> - <Time - timestamp={notif.when} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - <td> - <Time - timestamp={prev?.when} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - </tr> + {title} + </a> + </td> + <td> + {notif.event.location} {notif.event.name} + </td> + <td> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + <td> + <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + </tr> + ); } case ObservabilityEventType.TaskStart: case ObservabilityEventType.TaskStop: case ObservabilityEventType.DeclareTaskDependency: case ObservabilityEventType.TaskReset: { - return <tr> - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></td> - <td> - {notif.event.taskId} - </td> - <td> - <Time - timestamp={notif.when} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - <td> - <Time - timestamp={prev?.when} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - </tr> + {title} + </a> + </td> + <td>{notif.event.taskId}</td> + <td> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + <td> + <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + </tr> + ); } case ObservabilityEventType.ShepherdTaskResult: { - return <tr> - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></td> - <td> - {notif.event.resultType} - </td> - <td> - <Time - timestamp={notif.when} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - <td> - <Time - timestamp={prev?.when} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - </tr> - + {title} + </a> + </td> + <td>{notif.event.resultType}</td> + <td> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + <td> + <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + </tr> + ); } case ObservabilityEventType.CryptoStart: case ObservabilityEventType.CryptoFinishSuccess: case ObservabilityEventType.CryptoFinishError: { - return <tr> - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></td> - <td> - {notif.event.operation} - </td> - <td> - <Time - timestamp={notif.when} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - <td> - <Time - timestamp={prev?.when} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - </tr> + {title} + </a> + </td> + <td>{notif.event.operation}</td> + <td> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + <td> + <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + </tr> + ); } case ObservabilityEventType.RequestStart: case ObservabilityEventType.RequestFinishSuccess: case ObservabilityEventType.RequestFinishError: { - return <tr > - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></td> - <td> - {notif.event.type} - </td> - <td> - <Time - timestamp={notif.when} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - <td> - <Time - timestamp={prev?.when} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - </tr> + {title} + </a> + </td> + <td>{notif.event.type}</td> + <td> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + <td> + <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + </td> + </tr> + ); } + case ObservabilityEventType.Message: + // FIXME + return <></>; } } -function getNotificationFor(id: string, event: WalletNotification, start: AbsoluteTime, list: Notif[]): Notif | undefined { - const eventWithTime = { ...event, when: start } +function getNotificationFor( + id: string, + event: WalletNotification, + start: AbsoluteTime, + list: Notif[], +): Notif | undefined { + const eventWithTime = { ...event, when: start }; switch (event.type) { case NotificationType.BalanceChange: { - return ({ + return { id, events: [eventWithTime], reference: { @@ -509,28 +693,32 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu description: "Balance change", start, end: AbsoluteTime.never(), - MoreInfo: ShowBalanceChange - }) + MoreInfo: ShowBalanceChange, + }; } case NotificationType.BackupOperationError: { - return ({ + return { id, events: [eventWithTime], reference: undefined, description: "Backup error", start, end: AbsoluteTime.never(), - MoreInfo: ShowBackupOperationError - }) + MoreInfo: ShowBackupOperationError, + }; } case NotificationType.TransactionStateTransition: { - const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.transactionId) + const found = list.find( + (a) => + a.reference?.eventType === event.type && + a.reference.id === event.transactionId, + ); if (found) { found.end = start; - found.events.unshift(eventWithTime) - return undefined + found.events.unshift(eventWithTime); + return undefined; } - return ({ + return { id, events: [eventWithTime], reference: { @@ -541,17 +729,21 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu description: event.type, start, end: AbsoluteTime.never(), - MoreInfo: ShowTransactionStateTransition - }) + MoreInfo: ShowTransactionStateTransition, + }; } case NotificationType.ExchangeStateTransition: { - const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.exchangeBaseUrl) + const found = list.find( + (a) => + a.reference?.eventType === event.type && + a.reference.id === event.exchangeBaseUrl, + ); if (found) { found.end = start; - found.events.unshift(eventWithTime) - return undefined + found.events.unshift(eventWithTime); + return undefined; } - return ({ + return { id, events: [eventWithTime], description: "Exchange update", @@ -562,17 +754,21 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu }, start, end: AbsoluteTime.never(), - MoreInfo: ShowExchangeStateTransition - }) + MoreInfo: ShowExchangeStateTransition, + }; } case NotificationType.TaskObservabilityEvent: { - const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.taskId) + const found = list.find( + (a) => + a.reference?.eventType === event.type && + a.reference.id === event.taskId, + ); if (found) { found.end = start; - found.events.unshift(eventWithTime) - return undefined + found.events.unshift(eventWithTime); + return undefined; } - return ({ + return { id, events: [eventWithTime], reference: { @@ -583,17 +779,20 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu description: `Task update ${event.taskId}`, start, end: AbsoluteTime.never(), - MoreInfo: ShowObservabilityEvent - }) + MoreInfo: ShowObservabilityEvent, + }; } case NotificationType.WithdrawalOperationTransition: { - const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.uri) + const found = list.find( + (a) => + a.reference?.eventType === event.type && a.reference.id === event.uri, + ); if (found) { found.end = start; - found.events.unshift(eventWithTime) - return undefined + found.events.unshift(eventWithTime); + return undefined; } - return ({ + return { id, events: [eventWithTime], reference: { @@ -604,17 +803,21 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu description: `Withdrawal operation updated`, start, end: AbsoluteTime.never(), - MoreInfo: ShowObservabilityEvent - }) + MoreInfo: ShowObservabilityEvent, + }; } case NotificationType.RequestObservabilityEvent: { - const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.requestId) + const found = list.find( + (a) => + a.reference?.eventType === event.type && + a.reference.id === event.requestId, + ); if (found) { found.end = start; - found.events.unshift(eventWithTime) - return undefined + found.events.unshift(eventWithTime); + return undefined; } - return ({ + return { id, events: [eventWithTime], reference: { @@ -625,145 +828,183 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu description: `wallet.${event.operation}(${event.requestId})`, start, end: AbsoluteTime.never(), - MoreInfo: ShowObservabilityEvent - }) + MoreInfo: ShowObservabilityEvent, + }; } + case NotificationType.Idle: + return undefined; default: { - assertUnreachable(event) + assertUnreachable(event); } } } - function refresh(api: WxApiType, onUpdate: (list: Notif[]) => void) { - api.background.call("getNotifications", undefined).then(notif => { - - const list: Notif[] = [] - for (const n of notif) { - if (n.notification.type === NotificationType.RequestObservabilityEvent && - n.notification.operation === "getActiveTasks") { - //ignore monitor request - continue; - } - const event = getNotificationFor(String(list.length), n.notification, n.when, list) - // pepe. - if (event) { - list.unshift(event) + api.background + .call("getNotifications", undefined) + .then((notif) => { + const list: Notif[] = []; + for (const n of notif) { + if ( + n.notification.type === NotificationType.RequestObservabilityEvent && + n.notification.operation === "getActiveTasks" + ) { + //ignore monitor request + continue; + } + const event = getNotificationFor( + String(list.length), + n.notification, + n.when, + list, + ); + // pepe. + if (event) { + list.unshift(event); + } } - } - onUpdate(list); - }).catch(error => { - console.log(error) - }) + onUpdate(list); + }) + .catch((error) => { + console.log(error); + }); } -export function ObservabilityEventsTable({ }: {}): VNode { - const { i18n } = useTranslationContext() +export function ObservabilityEventsTable({}: {}): VNode { + const { i18n } = useTranslationContext(); const api = useBackendContext(); - const [notifications, setNotifications] = useState<Notif[]>([]) - const [showDetails, setShowDetails] = useState<VNode>() + const [notifications, setNotifications] = useState<Notif[]>([]); + const [showDetails, setShowDetails] = useState<VNode>(); useEffect(() => { let lastTimeout: ReturnType<typeof setTimeout>; function periodicRefresh() { - - refresh(api, setNotifications) + refresh(api, setNotifications); lastTimeout = setTimeout(() => { periodicRefresh(); - }, 1000) + }, 1000); - //clear on unload - return () => { clearTimeout(lastTimeout) } + //clear on unload + return () => { + clearTimeout(lastTimeout); + }; } - return periodicRefresh() + return periodicRefresh(); }, [1]); - return <div> - <div style={{ display: "flex", justifyContent: "space-between" }}> - - <div style={{ padding: 4, margin: 2, border: "solid 1px black" }} onClick={() => { - api.background.call("clearNotifications", undefined).then(d => { - refresh(api, setNotifications) - }) - }}> - clear + return ( + <div> + <div style={{ display: "flex", justifyContent: "space-between" }}> + <div + style={{ padding: 4, margin: 2, border: "solid 1px black" }} + onClick={() => { + api.background.call("clearNotifications", undefined).then((d) => { + refresh(api, setNotifications); + }); + }} + > + clear + </div> </div> - - - </div> - {showDetails && <Modal title="event details" onClose={{ onClick: (async () => { setShowDetails(undefined) }) as any }} > - {showDetails} - </Modal>} - {notifications.map((not) => { - return ( - <details key={not.id}> - <summary> - <div style={{ width: "90%", display: "inline-flex", justifyContent: "space-between", padding: 4 }}> - <div style={{ padding: 4 }}> - {not.description} - </div> - <div style={{ padding: 4 }}> - <Time - timestamp={not.start} - format="yyyy/MM/dd HH:mm:ss" - /> + {showDetails && ( + <Modal + title="event details" + onClose={{ + onClick: (async () => { + setShowDetails(undefined); + }) as any, + }} + > + {showDetails} + </Modal> + )} + {notifications.map((not) => { + return ( + <details key={not.id}> + <summary> + <div + style={{ + width: "90%", + display: "inline-flex", + justifyContent: "space-between", + padding: 4, + }} + > + <div style={{ padding: 4 }}>{not.description}</div> + <div style={{ padding: 4 }}> + <Time timestamp={not.start} format="yyyy/MM/dd HH:mm:ss" /> + </div> + <div style={{ padding: 4 }}> + <Time timestamp={not.end} format="yyyy/MM/dd HH:mm:ss" /> + </div> </div> - <div style={{ padding: 4 }}><Time - timestamp={not.end} - format="yyyy/MM/dd HH:mm:ss" - /></div> - </div> - </summary> - <not.MoreInfo events={not.events} onClick={(details) => { - setShowDetails(details) - }} /> - </details> - ); - })} - </div > + </summary> + <not.MoreInfo + events={not.events} + onClick={(details) => { + setShowDetails(details); + }} + /> + </details> + ); + })} + </div> + ); } -function ErroDetailModal({ error, onClose }: { error: TalerErrorDetail, onClose: () => void }): VNode { - return <Modal title="Full detail" onClose={{ - onClick: onClose as any - }}> - <dl> - <dt>Code</dt> - <dd>{TalerErrorCode[error.code]} ({error.code})</dd> - <dt>Hint</dt> - <dd>{error.hint ?? "--"}</dd> - <dt>Time</dt> - <dd><Time - timestamp={error.when} - format="yyyy/MM/dd HH:mm:ss" - /></dd> - </dl> - <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> - {JSON.stringify(error, undefined, 2)} - </pre> - </Modal> +function ErroDetailModal({ + error, + onClose, +}: { + error: TalerErrorDetail; + onClose: () => void; +}): VNode { + return ( + <Modal + title="Full detail" + onClose={{ + onClick: onClose as any, + }} + > + <dl> + <dt>Code</dt> + <dd> + {TalerErrorCode[error.code]} ({error.code}) + </dd> + <dt>Hint</dt> + <dd>{error.hint ?? "--"}</dd> + <dt>Time</dt> + <dd> + <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss" /> + </dd> + </dl> + <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> + {JSON.stringify(error, undefined, 2)} + </pre> + </Modal> + ); } -export function ActiveTasksTable({ }: {}): VNode { - const { i18n } = useTranslationContext() +export function ActiveTasksTable({}: {}): VNode { + const { i18n } = useTranslationContext(); const api = useBackendContext(); const state = useAsyncAsHook(() => { return api.wallet.call(WalletApiOperation.GetActiveTasks, {}); }); - const [showError, setShowError] = useState<TalerErrorDetail>() + const [showError, setShowError] = useState<TalerErrorDetail>(); const tasks = state && !state.hasError ? state.response.tasks : []; useEffect(() => { - if (!state || state.hasError) return + if (!state || state.hasError) return; const lastTimeout = setTimeout(() => { state.retry(); - }, 1000) + }, 1000); return () => { - clearTimeout(lastTimeout) - } - }, [tasks]) + clearTimeout(lastTimeout); + }; + }, [tasks]); // const listenAllEvents = Array.from<NotificationType>({ length: 1 }); // listenAllEvents.includes = () => true @@ -772,59 +1013,88 @@ export function ActiveTasksTable({ }: {}): VNode { // state?.retry() // }); // }); - return <Fragment> - {showError && <ErroDetailModal error={showError} onClose={(async () => { setShowError(undefined) })} />} + return ( + <Fragment> + {showError && ( + <ErroDetailModal + error={showError} + onClose={async () => { + setShowError(undefined); + }} + /> + )} - <table style={{ width: "100%" }}> - <thead> - <tr> - <th> - <i18n.Translate>Type</i18n.Translate> - </th> - <th> - <i18n.Translate>Id</i18n.Translate> - </th> - <th> - <i18n.Translate>Since</i18n.Translate> - </th> - <th> - <i18n.Translate>Next try</i18n.Translate> - </th> - <th> - <i18n.Translate>Error</i18n.Translate> - </th> - <th> - <i18n.Translate>Transaction</i18n.Translate> - </th> - </tr> - </thead> - <tbody> - {tasks.map((task) => { - const [type, id] = task.id.split(":") - return ( - <tr> - <td>{type}</td> - <td title={id}>{id.substring(0, 10)}</td> - <td> - <Time - timestamp={task.firstTry} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - <td> - <Time - timestamp={task.nextTry} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - <td>{!task.lastError?.code ? "" : <a href="#" onClick={(e) => { e.preventDefault(); setShowError(task.lastError) }}>{TalerErrorCode[task.lastError.code]}</a>}</td> - <td> - {task.transaction ? <a title={task.transaction} href={Pages.balanceTransaction({ tid: task.transaction })}>{task.transaction.substring(0, 10)}</a> : "--"} - </td> - </tr> - ); - })} - </tbody> - </table> - </Fragment> -}
\ No newline at end of file + <table style={{ width: "100%" }}> + <thead> + <tr> + <th> + <i18n.Translate>Type</i18n.Translate> + </th> + <th> + <i18n.Translate>Id</i18n.Translate> + </th> + <th> + <i18n.Translate>Since</i18n.Translate> + </th> + <th> + <i18n.Translate>Next try</i18n.Translate> + </th> + <th> + <i18n.Translate>Error</i18n.Translate> + </th> + <th> + <i18n.Translate>Transaction</i18n.Translate> + </th> + </tr> + </thead> + <tbody> + {tasks.map((task) => { + const [type, id] = task.taskId.split(":"); + return ( + <tr> + <td>{type}</td> + <td title={id}>{id.substring(0, 10)}</td> + <td> + <Time + timestamp={task.firstTry} + format="yyyy/MM/dd HH:mm:ss" + /> + </td> + <td> + <Time timestamp={task.nextTry} format="yyyy/MM/dd HH:mm:ss" /> + </td> + <td> + {!task.lastError?.code ? ( + "" + ) : ( + <a + href="#" + onClick={(e) => { + e.preventDefault(); + setShowError(task.lastError); + }} + > + {TalerErrorCode[task.lastError.code]} + </a> + )} + </td> + <td> + {task.transaction ? ( + <a + title={task.transaction} + href={Pages.balanceTransaction({ tid: task.transaction })} + > + {task.transaction.substring(0, 10)} + </a> + ) : ( + "--" + )} + </td> + </tr> + ); + })} + </tbody> + </table> + </Fragment> + ); +} |