summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2022-05-26 15:55:14 -0300
committerSebastian <sebasjm@gmail.com>2022-05-26 15:57:12 -0300
commit24162c1086c017305253c78280a82bfa9a572b1e (patch)
tree6842f44dad3fc029d44349527df8d0b09b92852d /packages/taler-wallet-webextension/src/wallet/Transaction.tsx
parent72d936eaf99ad1d5ee156ba8f156a983f4ec613c (diff)
downloadwallet-core-24162c1086c017305253c78280a82bfa9a572b1e.tar.gz
wallet-core-24162c1086c017305253c78280a82bfa9a572b1e.tar.bz2
wallet-core-24162c1086c017305253c78280a82bfa9a572b1e.zip
transaction details template
mayor change in the template of the transaction details for every transaction more work needs to be done in wallet core for tip and refund to show more information about the merchant like logo and website
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/Transaction.tsx')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Transaction.tsx1044
1 files changed, 671 insertions, 373 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 3377f98c7..9ccb353a9 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -16,14 +16,24 @@
import {
AbsoluteTime,
+ AmountJson,
Amounts,
+ Location,
NotificationType,
parsePaytoUri,
parsePayUri,
+ TalerProtocolTimestamp,
Transaction,
+ TransactionDeposit,
+ TransactionPayment,
+ TransactionRefresh,
+ TransactionRefund,
+ TransactionTip,
TransactionType,
+ TransactionWithdrawal,
WithdrawalType,
} from "@gnu-taler/taler-util";
+import { styled } from "@linaria/react";
import { differenceInSeconds } from "date-fns";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
@@ -33,15 +43,17 @@ import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js"
import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js";
import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js";
-import { Part, PartPayto } from "../components/Part.js";
+import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
import {
Button,
+ ButtonBox,
ButtonDestructive,
ButtonPrimary,
CenteredDialog,
InfoBox,
ListOfProducts,
Overlay,
+ Row,
RowBorderGray,
SmallLightText,
SubTitle,
@@ -119,6 +131,14 @@ export interface WalletTransactionProps {
onBack: () => void;
}
+const PurchaseDetailsTable = styled.table`
+ width: 100%;
+
+ & > tr > td:nth-child(2n) {
+ text-align: right;
+ }
+`;
+
export function TransactionView({
transaction,
onDelete,
@@ -168,9 +188,7 @@ export function TransactionView({
</WarningBox>
)}
</section>
- <section>
- <div style={{ textAlign: "center" }}>{children}</div>
- </section>
+ <section>{children}</section>
<footer>
<div />
<div>
@@ -189,10 +207,8 @@ export function TransactionView({
}
if (transaction.type === TransactionType.Withdrawal) {
- const fee = Amounts.sub(
- Amounts.parseOrThrow(transaction.amountRaw),
- Amounts.parseOrThrow(transaction.amountEffective),
- ).amount;
+ const total = Amounts.parseOrThrow(transaction.amountEffective);
+ const chosen = Amounts.parseOrThrow(transaction.amountRaw);
return (
<TransactionTemplate>
{confirmBeforeForget ? (
@@ -219,205 +235,125 @@ export function TransactionView({
</CenteredDialog>
</Overlay>
) : undefined}
- <SubTitle>
- <i18n.Translate>Withdrawal</i18n.Translate>
- </SubTitle>
- <Time
- timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
- format="dd MMMM yyyy, HH:mm"
- />
- {transaction.pending ? (
- transaction.withdrawalDetails.type ===
- WithdrawalType.ManualTransfer ? (
- <Fragment>
- <BankDetailsByPaytoType
- amount={Amounts.parseOrThrow(transaction.amountRaw)}
- exchangeBaseUrl={transaction.exchangeBaseUrl}
- payto={parsePaytoUri(
- transaction.withdrawalDetails.exchangePaytoUris[0],
- )}
- subject={transaction.withdrawalDetails.reservePub}
- />
- <p>
- <WarningBox>
- <i18n.Translate>
- Make sure to use the correct subject, otherwise the money
- will not arrive in this wallet.
- </i18n.Translate>
- </WarningBox>
- </p>
- <Part
- big
- title={<i18n.Translate>Total withdrawn</i18n.Translate>}
- text={<Amount value={transaction.amountEffective} />}
- kind="positive"
- />
- <Part
- big
- title={<i18n.Translate>Exchange fee</i18n.Translate>}
- text={<Amount value={fee} />}
- kind="negative"
- />
- </Fragment>
- ) : (
- <Fragment>
- {!transaction.withdrawalDetails.confirmed &&
- transaction.withdrawalDetails.bankConfirmationUrl ? (
- <InfoBox>
+ <Header
+ timestamp={transaction.timestamp}
+ type={i18n.str`Withdrawal`}
+ total={total}
+ kind="positive"
+ >
+ {transaction.exchangeBaseUrl}
+ </Header>
+
+ {!transaction.pending ? undefined : transaction.withdrawalDetails
+ .type === WithdrawalType.ManualTransfer ? (
+ <Fragment>
+ <BankDetailsByPaytoType
+ amount={chosen}
+ exchangeBaseUrl={transaction.exchangeBaseUrl}
+ payto={parsePaytoUri(
+ transaction.withdrawalDetails.exchangePaytoUris[0],
+ )}
+ subject={transaction.withdrawalDetails.reservePub}
+ />
+ <WarningBox>
+ <i18n.Translate>
+ Make sure to use the correct subject, otherwise the money will
+ not arrive in this wallet.
+ </i18n.Translate>
+ </WarningBox>
+ </Fragment>
+ ) : (
+ <Fragment>
+ {!transaction.withdrawalDetails.confirmed &&
+ transaction.withdrawalDetails.bankConfirmationUrl ? (
+ <InfoBox>
+ <div style={{ display: "block" }}>
<i18n.Translate>
- The bank is waiting for confirmation. Go to the
+ The bank did not yet confirmed the wire transfer. Go to the
+ {` `}
<a
href={transaction.withdrawalDetails.bankConfirmationUrl}
target="_blank"
rel="noreferrer"
+ style={{ display: "inline" }}
>
<i18n.Translate>bank site</i18n.Translate>
- </a>
- </i18n.Translate>
- </InfoBox>
- ) : undefined}
- {transaction.withdrawalDetails.confirmed && (
- <InfoBox>
- <i18n.Translate>
- Waiting for the coins to arrive
+ </a>{" "}
+ and check there is no pending step.
</i18n.Translate>
- </InfoBox>
- )}
- <Part
- big
- title={<i18n.Translate>Total withdrawn</i18n.Translate>}
- text={<Amount value={transaction.amountEffective} />}
- kind="positive"
- />
- <Part
- big
- title={<i18n.Translate>Chosen amount</i18n.Translate>}
- text={<Amount value={transaction.amountRaw} />}
- kind="neutral"
- />
- <Part
- big
- title={<i18n.Translate>Exchange fee</i18n.Translate>}
- text={<Amount value={fee} />}
- kind="negative"
- />
- </Fragment>
- )
- ) : (
- <Fragment>
- <Part
- big
- title={<i18n.Translate>Total withdrawn</i18n.Translate>}
- text={<Amount value={transaction.amountEffective} />}
- kind="positive"
- />
- <Part
- big
- title={<i18n.Translate>Chosen amount</i18n.Translate>}
- text={<Amount value={transaction.amountRaw} />}
- kind="neutral"
- />
- <Part
- big
- title={<i18n.Translate>Exchange fee</i18n.Translate>}
- text={<Amount value={fee} />}
- kind="negative"
- />
+ </div>
+ </InfoBox>
+ ) : undefined}
+ {transaction.withdrawalDetails.confirmed && (
+ <InfoBox>
+ <i18n.Translate>
+ Bank has confirmed the wire transfer. Waiting for the exchange
+ to send the coins
+ </i18n.Translate>
+ </InfoBox>
+ )}
</Fragment>
)}
<Part
- title={<i18n.Translate>Exchange</i18n.Translate>}
- text={new URL(transaction.exchangeBaseUrl).hostname}
- kind="neutral"
+ title={<i18n.Translate>Details</i18n.Translate>}
+ text={<WithdrawDetails transaction={transaction} />}
/>
</TransactionTemplate>
);
}
- const showLargePic = (): void => {
- return;
- };
-
if (transaction.type === TransactionType.Payment) {
- const fee = Amounts.sub(
- Amounts.parseOrThrow(transaction.amountEffective),
- Amounts.parseOrThrow(transaction.amountRaw),
- ).amount;
-
- const refundFee = Amounts.sub(
- Amounts.parseOrThrow(transaction.totalRefundRaw),
- Amounts.parseOrThrow(transaction.totalRefundEffective),
- ).amount;
- const refunded = Amounts.isNonZero(
- Amounts.parseOrThrow(transaction.totalRefundRaw),
- );
const pendingRefund =
transaction.refundPending === undefined
? undefined
: Amounts.parseOrThrow(transaction.refundPending);
+
+ const total = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountEffective),
+ Amounts.parseOrThrow(transaction.totalRefundEffective),
+ ).amount;
+
return (
<TransactionTemplate>
- <SubTitle>
- <i18n.Translate>Payment</i18n.Translate>
- </SubTitle>
- <Time
- timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
- format="dd MMMM yyyy, HH:mm"
- />
- <br />
- <Part
- big
- title={<i18n.Translate>Total paid</i18n.Translate>}
- text={<Amount value={transaction.amountEffective} />}
+ <Header
+ timestamp={transaction.timestamp}
+ total={total}
+ type={i18n.str`Payment`}
kind="negative"
- />
- {Amounts.isNonZero(fee) && (
- <Fragment>
- <Part
- big
- title={<i18n.Translate>Purchase amount</i18n.Translate>}
- text={<Amount value={transaction.amountRaw} />}
- kind="neutral"
- />
- <Part
- title={<i18n.Translate>Purchase Fee</i18n.Translate>}
- text={<Amount value={fee} />}
- kind="negative"
- />
- </Fragment>
- )}
- {refunded && (
- <Fragment>
+ >
+ {transaction.info.fulfillmentUrl ? (
+ <a
+ href={transaction.info.fulfillmentUrl}
+ target="_bank"
+ rel="noreferrer"
+ >
+ {transaction.info.summary}
+ </a>
+ ) : (
+ transaction.info.summary
+ )}
+ </Header>
+ <br />
+ {pendingRefund !== undefined && Amounts.isNonZero(pendingRefund) && (
+ <InfoBox>
+ <i18n.Translate>
+ Merchant created a refund for this order but was not automatically
+ picked up.
+ </i18n.Translate>
<Part
- big
- title={<i18n.Translate>Total refunded</i18n.Translate>}
- text={<Amount value={transaction.totalRefundEffective} />}
+ title={<i18n.Translate>Offer</i18n.Translate>}
+ text={<Amount value={pendingRefund} />}
kind="positive"
/>
- {Amounts.isNonZero(refundFee) && (
- <Fragment>
- <Part
- big
- title={<i18n.Translate>Refund amount</i18n.Translate>}
- text={<Amount value={transaction.totalRefundRaw} />}
- kind="neutral"
- />
- <Part
- title={<i18n.Translate>Refund fee</i18n.Translate>}
- text={<Amount value={refundFee} />}
- kind="negative"
- />
- </Fragment>
- )}
- </Fragment>
- )}
- {pendingRefund !== undefined && Amounts.isNonZero(pendingRefund) && (
- <Part
- big
- title={<i18n.Translate>Refund pending</i18n.Translate>}
- text={<Amount value={pendingRefund} />}
- kind="positive"
- />
+ <div>
+ <div />
+ <div>
+ <ButtonPrimary>
+ <i18n.Translate>Accept</i18n.Translate>
+ </ButtonPrimary>
+ </div>
+ </div>
+ </InfoBox>
)}
<Part
title={<i18n.Translate>Merchant</i18n.Translate>}
@@ -425,268 +361,630 @@ export function TransactionView({
kind="neutral"
/>
<Part
- title={<i18n.Translate>Purchase</i18n.Translate>}
- text={
- transaction.info.fulfillmentUrl ? (
- <a
- href={transaction.info.fulfillmentUrl}
- target="_bank"
- rel="noreferrer"
- >
- {transaction.info.summary}
- </a>
- ) : (
- transaction.info.summary
- )
- }
+ title={<i18n.Translate>Invoice ID</i18n.Translate>}
+ text={transaction.info.orderId}
kind="neutral"
/>
<Part
- title={<i18n.Translate>Receipt</i18n.Translate>}
- text={`#${transaction.info.orderId}`}
+ title={<i18n.Translate>Details</i18n.Translate>}
+ text={<PurchaseDetails transaction={transaction} />}
kind="neutral"
/>
-
- <div>
- {transaction.info.products && transaction.info.products.length > 0 && (
- <ListOfProducts>
- {transaction.info.products.map((p, k) => (
- <RowBorderGray key={k}>
- <a href="#" onClick={showLargePic}>
- <img src={p.image ? p.image : emptyImg} />
- </a>
- <div>
- {p.quantity && p.quantity > 0 && (
- <SmallLightText>
- x {p.quantity} {p.unit}
- </SmallLightText>
- )}
- <div>{p.description}</div>
- </div>
- </RowBorderGray>
- ))}
- </ListOfProducts>
- )}
- </div>
</TransactionTemplate>
);
}
if (transaction.type === TransactionType.Deposit) {
- const fee = Amounts.sub(
- Amounts.parseOrThrow(transaction.amountEffective),
- Amounts.parseOrThrow(transaction.amountRaw),
- ).amount;
+ const total = Amounts.parseOrThrow(transaction.amountRaw);
const payto = parsePaytoUri(transaction.targetPaytoUri);
return (
<TransactionTemplate>
- <SubTitle>
- <i18n.Translate>Deposit</i18n.Translate>
- </SubTitle>
- <Time
- timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
- format="dd MMMM yyyy, HH:mm"
- />
- <br />
+ <Header
+ timestamp={transaction.timestamp}
+ type={i18n.str`Deposit`}
+ total={total}
+ kind="negative"
+ >
+ {transaction.targetPaytoUri}
+ </Header>
+ {payto && <PartPayto big payto={payto} kind="neutral" />}
<Part
- big
- title={<i18n.Translate>Total send</i18n.Translate>}
- text={<Amount value={transaction.amountEffective} />}
+ title={<i18n.Translate>Details</i18n.Translate>}
+ text={<DepositDetails transaction={transaction} />}
kind="neutral"
/>
- {Amounts.isNonZero(fee) && (
- <Fragment>
- <Part
- big
- title={<i18n.Translate>Deposit amount</i18n.Translate>}
- text={<Amount value={transaction.amountRaw} />}
- kind="positive"
- />
- <Part
- big
- title={<i18n.Translate>Fee</i18n.Translate>}
- text={<Amount value={fee} />}
- kind="negative"
- />
- </Fragment>
- )}
- {payto && <PartPayto big payto={payto} kind="neutral" />}
</TransactionTemplate>
);
}
if (transaction.type === TransactionType.Refresh) {
- const fee = Amounts.sub(
+ const total = Amounts.sub(
Amounts.parseOrThrow(transaction.amountRaw),
Amounts.parseOrThrow(transaction.amountEffective),
).amount;
+
return (
<TransactionTemplate>
- <SubTitle>
- <i18n.Translate>Refresh</i18n.Translate>
- </SubTitle>
- <Time
- timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
- format="dd MMMM yyyy, HH:mm"
- />
- <br />
- <Part
- big
- title={<i18n.Translate>Total refresh</i18n.Translate>}
- text={<Amount value={transaction.amountEffective} />}
+ <Header
+ timestamp={transaction.timestamp}
+ type={i18n.str`Refresh`}
+ total={total}
kind="negative"
+ >
+ {transaction.exchangeBaseUrl}
+ </Header>
+ <Part
+ title={<i18n.Translate>Details</i18n.Translate>}
+ text={<RefreshDetails transaction={transaction} />}
/>
- {Amounts.isNonZero(fee) && (
- <Fragment>
- <Part
- big
- title={<i18n.Translate>Refresh amount</i18n.Translate>}
- text={<Amount value={transaction.amountRaw} />}
- kind="neutral"
- />
- <Part
- big
- title={<i18n.Translate>Fee</i18n.Translate>}
- text={<Amount value={fee} />}
- kind="negative"
- />
- </Fragment>
- )}
</TransactionTemplate>
);
}
if (transaction.type === TransactionType.Tip) {
- const fee = Amounts.sub(
- Amounts.parseOrThrow(transaction.amountRaw),
- Amounts.parseOrThrow(transaction.amountEffective),
- ).amount;
+ const total = Amounts.parseOrThrow(transaction.amountEffective);
+
return (
<TransactionTemplate>
- <SubTitle>
- <i18n.Translate>Tip</i18n.Translate>
- </SubTitle>
- <Time
- timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
- format="dd MMMM yyyy, HH:mm"
+ <Header
+ timestamp={transaction.timestamp}
+ type={i18n.str`Tip`}
+ total={total}
+ kind="positive"
+ >
+ {transaction.merchantBaseUrl}
+ </Header>
+ {/* <Part
+ title={<i18n.Translate>Merchant</i18n.Translate>}
+ text={transaction.info.merchant.name}
+ kind="neutral"
/>
- <br />
<Part
- big
- title={<i18n.Translate>Total tip</i18n.Translate>}
- text={<Amount value={transaction.amountRaw} />}
- kind="positive"
+ title={<i18n.Translate>Invoice ID</i18n.Translate>}
+ text={transaction.info.orderId}
+ kind="neutral"
+ /> */}
+ <Part
+ title={<i18n.Translate>Details</i18n.Translate>}
+ text={<TipDetails transaction={transaction} />}
/>
- {Amounts.isNonZero(fee) && (
- <Fragment>
- <Part
- big
- title={<i18n.Translate>Received amount</i18n.Translate>}
- text={<Amount value={transaction.amountEffective} />}
- kind="neutral"
- />
- <Part
- big
- title={<i18n.Translate>Fee</i18n.Translate>}
- text={<Amount value={fee} />}
- kind="negative"
- />
- </Fragment>
- )}
</TransactionTemplate>
);
}
if (transaction.type === TransactionType.Refund) {
- const fee = Amounts.sub(
- Amounts.parseOrThrow(transaction.amountRaw),
- Amounts.parseOrThrow(transaction.amountEffective),
- ).amount;
+ const total = Amounts.parseOrThrow(transaction.amountEffective);
return (
<TransactionTemplate>
- <SubTitle>
- <i18n.Translate>Refund</i18n.Translate>
- </SubTitle>
- <Time
- timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
- format="dd MMMM yyyy, HH:mm"
- />
- <br />
- <Part
- big
- title={<i18n.Translate>Total refund</i18n.Translate>}
- text={<Amount value={transaction.amountEffective} />}
+ <Header
+ timestamp={transaction.timestamp}
+ type={i18n.str`Refund`}
+ total={total}
kind="positive"
- />
- {Amounts.isNonZero(fee) && (
- <Fragment>
- <Part
- big
- title={<i18n.Translate>Refund amount</i18n.Translate>}
- text={<Amount value={transaction.amountRaw} />}
- kind="neutral"
- />
- <Part
- big
- title={<i18n.Translate>Fee</i18n.Translate>}
- text={<Amount value={fee} />}
- kind="negative"
- />
- </Fragment>
- )}
+ >
+ {transaction.info.summary}
+ </Header>
+
<Part
title={<i18n.Translate>Merchant</i18n.Translate>}
text={transaction.info.merchant.name}
kind="neutral"
/>
-
<Part
- title={<i18n.Translate>Purchase</i18n.Translate>}
+ title={<i18n.Translate>Original order ID</i18n.Translate>}
text={
<a
href={Pages.balance_transaction.replace(
":tid",
transaction.refundedTransactionId,
)}
- // href={transaction.info.fulfillmentUrl}
- // target="_bank"
- // rel="noreferrer"
>
- {transaction.info.summary}
+ {transaction.info.orderId}
</a>
}
kind="neutral"
/>
<Part
- title={<i18n.Translate>Receipt</i18n.Translate>}
- text={`#${transaction.info.orderId}`}
+ title={<i18n.Translate>Purchase summary</i18n.Translate>}
+ text={transaction.info.summary}
kind="neutral"
/>
-
- <div>
- {transaction.info.products && transaction.info.products.length > 0 && (
- <ListOfProducts>
- {transaction.info.products.map((p, k) => (
- <RowBorderGray key={k}>
- <a href="#" onClick={showLargePic}>
- <img src={p.image ? p.image : emptyImg} />
- </a>
- <div>
- {p.quantity && p.quantity > 0 && (
- <SmallLightText>
- x {p.quantity} {p.unit}
- </SmallLightText>
- )}
- <div>{p.description}</div>
- </div>
- </RowBorderGray>
- ))}
- </ListOfProducts>
- )}
- </div>
+ <Part
+ title={<i18n.Translate>Details</i18n.Translate>}
+ text={<RefundDetails transaction={transaction} />}
+ />
</TransactionTemplate>
);
}
return <div />;
}
+
+function DeliveryDetails({
+ date,
+ location,
+}: {
+ date: TalerProtocolTimestamp | undefined;
+ location: Location | undefined;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <PurchaseDetailsTable>
+ {location && (
+ <Fragment>
+ {location.country && (
+ <tr>
+ <td>
+ <i18n.Translate>Country</i18n.Translate>
+ </td>
+ <td>{location.country}</td>
+ </tr>
+ )}
+ {location.address_lines && (
+ <tr>
+ <td>
+ <i18n.Translate>Address lines</i18n.Translate>
+ </td>
+ <td>{location.address_lines}</td>
+ </tr>
+ )}
+ {location.building_number && (
+ <tr>
+ <td>
+ <i18n.Translate>Building number</i18n.Translate>
+ </td>
+ <td>{location.building_number}</td>
+ </tr>
+ )}
+ {location.building_name && (
+ <tr>
+ <td>
+ <i18n.Translate>Building name</i18n.Translate>
+ </td>
+ <td>{location.building_name}</td>
+ </tr>
+ )}
+ {location.street && (
+ <tr>
+ <td>
+ <i18n.Translate>Street</i18n.Translate>
+ </td>
+ <td>{location.street}</td>
+ </tr>
+ )}
+ {location.post_code && (
+ <tr>
+ <td>
+ <i18n.Translate>Post code</i18n.Translate>
+ </td>
+ <td>{location.post_code}</td>
+ </tr>
+ )}
+ {location.town_location && (
+ <tr>
+ <td>
+ <i18n.Translate>Town location</i18n.Translate>
+ </td>
+ <td>{location.town_location}</td>
+ </tr>
+ )}
+ {location.town && (
+ <tr>
+ <td>
+ <i18n.Translate>Town</i18n.Translate>
+ </td>
+ <td>{location.town}</td>
+ </tr>
+ )}
+ {location.district && (
+ <tr>
+ <td>
+ <i18n.Translate>District</i18n.Translate>
+ </td>
+ <td>{location.district}</td>
+ </tr>
+ )}
+ {location.country_subdivision && (
+ <tr>
+ <td>
+ <i18n.Translate>Country subdivision</i18n.Translate>
+ </td>
+ <td>{location.country_subdivision}</td>
+ </tr>
+ )}
+ </Fragment>
+ )}
+
+ {!location || !date ? undefined : (
+ <tr>
+ <td colSpan={2}>
+ <hr />
+ </td>
+ </tr>
+ )}
+ {date && (
+ <Fragment>
+ <tr>
+ <td>Date</td>
+ <td>
+ <Time
+ timestamp={AbsoluteTime.fromTimestamp(date)}
+ format="dd MMMM yyyy, HH:mm"
+ />
+ </td>
+ </tr>
+ </Fragment>
+ )}
+ </PurchaseDetailsTable>
+ );
+}
+
+function PurchaseDetails({
+ transaction,
+}: {
+ transaction: TransactionPayment;
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const partialFee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountEffective),
+ Amounts.parseOrThrow(transaction.amountRaw),
+ ).amount;
+
+ const refundRaw = Amounts.parseOrThrow(transaction.totalRefundRaw);
+
+ const refundFee = Amounts.sub(
+ refundRaw,
+ Amounts.parseOrThrow(transaction.totalRefundEffective),
+ ).amount;
+
+ const fee = Amounts.sum([partialFee, refundFee]).amount;
+
+ const hasProducts =
+ transaction.info.products && transaction.info.products.length > 0;
+
+ const hasShipping =
+ transaction.info.delivery_date !== undefined ||
+ transaction.info.delivery_location !== undefined;
+
+ const showLargePic = (): void => {
+ return;
+ };
+
+ const total = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountEffective),
+ Amounts.parseOrThrow(transaction.totalRefundEffective),
+ ).amount;
+
+ return (
+ <PurchaseDetailsTable>
+ <tr>
+ <td>Price</td>
+ <td>
+ <Amount value={transaction.amountRaw} />
+ </td>
+ </tr>
+
+ {Amounts.isNonZero(refundRaw) && (
+ <tr>
+ <td>Refunded</td>
+ <td>
+ <Amount value={transaction.totalRefundEffective} />
+ </td>
+ </tr>
+ )}
+ {Amounts.isNonZero(fee) && (
+ <tr>
+ <td>Transaction fees</td>
+ <td>
+ <Amount value={fee} />
+ </td>
+ </tr>
+ )}
+ <tr>
+ <td colSpan={2}>
+ <hr />
+ </td>
+ </tr>
+ <tr>
+ <td>Total</td>
+ <td>
+ <Amount value={total} />
+ </td>
+ </tr>
+ {hasProducts && (
+ <tr>
+ <td colSpan={2}>
+ <PartCollapsible
+ big
+ title={<i18n.Translate>Products</i18n.Translate>}
+ text={
+ <ListOfProducts>
+ {transaction.info.products?.map((p, k) => (
+ <Row key={k}>
+ <a href="#" onClick={showLargePic}>
+ <img src={p.image ? p.image : emptyImg} />
+ </a>
+ <div>
+ {p.quantity && p.quantity > 0 && (
+ <SmallLightText>
+ x {p.quantity} {p.unit}
+ </SmallLightText>
+ )}
+ <div>{p.description}</div>
+ </div>
+ </Row>
+ ))}
+ </ListOfProducts>
+ }
+ />
+ </td>
+ </tr>
+ )}
+ {hasShipping && (
+ <tr>
+ <td colSpan={2}>
+ <PartCollapsible
+ big
+ title={<i18n.Translate>Delivery</i18n.Translate>}
+ text={
+ <DeliveryDetails
+ date={transaction.info.delivery_date}
+ location={transaction.info.delivery_location}
+ />
+ }
+ />
+ </td>
+ </tr>
+ )}
+ </PurchaseDetailsTable>
+ );
+}
+
+function RefundDetails({
+ transaction,
+}: {
+ transaction: TransactionRefund;
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const fee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount;
+
+ return (
+ <PurchaseDetailsTable>
+ <tr>
+ <td>Amount</td>
+ <td>
+ <Amount value={transaction.amountRaw} />
+ </td>
+ </tr>
+
+ {Amounts.isNonZero(fee) && (
+ <tr>
+ <td>Transaction fees</td>
+ <td>
+ <Amount value={fee} />
+ </td>
+ </tr>
+ )}
+ <tr>
+ <td colSpan={2}>
+ <hr />
+ </td>
+ </tr>
+ <tr>
+ <td>Total</td>
+ <td>
+ <Amount value={transaction.amountEffective} />
+ </td>
+ </tr>
+ </PurchaseDetailsTable>
+ );
+}
+
+function DepositDetails({
+ transaction,
+}: {
+ transaction: TransactionDeposit;
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const fee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount;
+
+ return (
+ <PurchaseDetailsTable>
+ <tr>
+ <td>Amount</td>
+ <td>
+ <Amount value={transaction.amountRaw} />
+ </td>
+ </tr>
+
+ {Amounts.isNonZero(fee) && (
+ <tr>
+ <td>Transaction fees</td>
+ <td>
+ <Amount value={fee} />
+ </td>
+ </tr>
+ )}
+ <tr>
+ <td colSpan={2}>
+ <hr />
+ </td>
+ </tr>
+ <tr>
+ <td>Total transfer</td>
+ <td>
+ <Amount value={transaction.amountEffective} />
+ </td>
+ </tr>
+ </PurchaseDetailsTable>
+ );
+}
+function RefreshDetails({
+ transaction,
+}: {
+ transaction: TransactionRefresh;
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const fee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount;
+
+ return (
+ <PurchaseDetailsTable>
+ <tr>
+ <td>Amount</td>
+ <td>
+ <Amount value={transaction.amountRaw} />
+ </td>
+ </tr>
+ <tr>
+ <td colSpan={2}>
+ <hr />
+ </td>
+ </tr>
+ <tr>
+ <td>Transaction fees</td>
+ <td>
+ <Amount value={fee} />
+ </td>
+ </tr>
+ </PurchaseDetailsTable>
+ );
+}
+
+function TipDetails({ transaction }: { transaction: TransactionTip }): VNode {
+ const { i18n } = useTranslationContext();
+
+ const fee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount;
+
+ return (
+ <PurchaseDetailsTable>
+ <tr>
+ <td>Amount</td>
+ <td>
+ <Amount value={transaction.amountRaw} />
+ </td>
+ </tr>
+
+ {Amounts.isNonZero(fee) && (
+ <tr>
+ <td>Transaction fees</td>
+ <td>
+ <Amount value={fee} />
+ </td>
+ </tr>
+ )}
+ <tr>
+ <td colSpan={2}>
+ <hr />
+ </td>
+ </tr>
+ <tr>
+ <td>Total</td>
+ <td>
+ <Amount value={transaction.amountEffective} />
+ </td>
+ </tr>
+ </PurchaseDetailsTable>
+ );
+}
+
+function WithdrawDetails({
+ transaction,
+}: {
+ transaction: TransactionWithdrawal;
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const fee = Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount;
+
+ return (
+ <PurchaseDetailsTable>
+ <tr>
+ <td>Withdraw</td>
+ <td>
+ <Amount value={transaction.amountRaw} />
+ </td>
+ </tr>
+
+ {Amounts.isNonZero(fee) && (
+ <tr>
+ <td>Transaction fees</td>
+ <td>
+ <Amount value={fee} />
+ </td>
+ </tr>
+ )}
+ <tr>
+ <td colSpan={2}>
+ <hr />
+ </td>
+ </tr>
+ <tr>
+ <td>Total</td>
+ <td>
+ <Amount value={transaction.amountEffective} />
+ </td>
+ </tr>
+ </PurchaseDetailsTable>
+ );
+}
+
+function Header({
+ timestamp,
+ total,
+ children,
+ kind,
+ type,
+}: {
+ timestamp: TalerProtocolTimestamp;
+ total: AmountJson;
+ children: ComponentChildren;
+ kind: Kind;
+ type: string;
+}): VNode {
+ return (
+ <div
+ style={{
+ display: "flex",
+ justifyContent: "space-between",
+ flexDirection: "row",
+ }}
+ >
+ <div>
+ <SubTitle>{children}</SubTitle>
+ <Time
+ timestamp={AbsoluteTime.fromTimestamp(timestamp)}
+ format="dd MMMM yyyy, HH:mm"
+ />
+ </div>
+ <div>
+ <SubTitle>
+ <Part
+ title={type}
+ text={<Amount value={total} />}
+ kind={kind}
+ showSign
+ />
+ </SubTitle>
+ </div>
+ </div>
+ );
+}