diff --git a/packages/taler-util/src/talerTypes.ts b/packages/taler-util/src/talerTypes.ts index d9213ef5d..7fc3fcba0 100644 --- a/packages/taler-util/src/talerTypes.ts +++ b/packages/taler-util/src/talerTypes.ts @@ -362,6 +362,9 @@ export interface MerchantInfo { name: string; jurisdiction?: Location; address?: Location; + logo?: string; + website?: string; + email?: string; } export interface Tax { diff --git a/packages/taler-util/src/transactionsTypes.ts b/packages/taler-util/src/transactionsTypes.ts index 37c1c7ef1..dcaa56675 100644 --- a/packages/taler-util/src/transactionsTypes.ts +++ b/packages/taler-util/src/transactionsTypes.ts @@ -33,6 +33,7 @@ import { codecForInternationalizedString, codecForMerchantInfo, codecForProduct, + Location, } from "./talerTypes.js"; import { Codec, @@ -276,6 +277,17 @@ export interface OrderShortInfo { */ products: Product[] | undefined; + /** + * Time indicating when the order should be delivered. + * May be overwritten by individual products. + */ + delivery_date?: TalerProtocolTimestamp; + + /** + * Delivery location for (all!) products. + */ + delivery_location?: Location; + /** * URL of the fulfillment, given by the merchant */ diff --git a/packages/taler-wallet-webextension/build-fast-with-linaria.mjs b/packages/taler-wallet-webextension/build-fast-with-linaria.mjs index f6de67885..41747a745 100755 --- a/packages/taler-wallet-webextension/build-fast-with-linaria.mjs +++ b/packages/taler-wallet-webextension/build-fast-with-linaria.mjs @@ -54,6 +54,7 @@ export const buildConfig = { loader: { '.svg': 'text', '.png': 'dataurl', + '.jpeg': 'dataurl', }, target: [ 'es6' diff --git a/packages/taler-wallet-webextension/src/components/Amount.tsx b/packages/taler-wallet-webextension/src/components/Amount.tsx index c41f7faf6..b415a30cd 100644 --- a/packages/taler-wallet-webextension/src/components/Amount.tsx +++ b/packages/taler-wallet-webextension/src/components/Amount.tsx @@ -6,7 +6,7 @@ export function Amount({ value }: { value: AmountJson | AmountString }): VNode { const amount = Amounts.stringifyValue(aj, 2); return ( - {amount} {aj.currency} + {amount} {aj.currency} ); } diff --git a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx index e67fb6b4d..a2c91f4a1 100644 --- a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx +++ b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx @@ -44,7 +44,7 @@ export function BalanceTable({ width: "100%", }} > - {Amounts.stringifyValue(av)} + {Amounts.stringifyValue(av, 2)} ); diff --git a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx index 185021bc0..3a2a12c72 100644 --- a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx +++ b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx @@ -46,43 +46,47 @@ export function BankDetailsByPaytoType({ if (payto.isKnown && payto.targetType === "bitcoin") { const min = segwitMinAmount(amount.currency); return ( -
+
+

Bitcoin transfer details

- Bitcoin exchange need a transaction with 3 output, one output is the + The exchange need a transaction with 3 output, one output is the exchange account and the other two are segwit fake address for - metadata with an minimum amount. Reserve pub : {subject} + metadata with an minimum amount.

+ Reserve} + value={subject} + /> +

In bitcoincore wallet use 'Add Recipient' button to add two additional recipient and copy addresses and amounts -

    -
  • - {payto.targetPath} {Amounts.stringifyValue(amount)} BTC -
  • - {payto.segwitAddrs.map((addr, i) => ( -
  • - {addr} {Amounts.stringifyValue(min)} BTC -
  • - ))} -
- - In Electrum wallet paste the following three lines in 'Pay - to' field : - -
    -
  • - {payto.targetPath},{Amounts.stringifyValue(amount)} -
  • - {payto.segwitAddrs.map((addr, i) => ( -
  • - {addr} {Amounts.stringifyValue(min)} BTC -
  • - ))} -
+

+ + + + + + {payto.segwitAddrs.map((addr, i) => ( + + + + + ))} +
{payto.targetPath}{Amounts.stringifyValue(amount)} BTC
{addr}{Amounts.stringifyValue(min)} BTC
+

Make sure the amount show{" "} {Amounts.stringifyValue(Amounts.sum([amount, min, min]).amount)}{" "} @@ -93,7 +97,7 @@ export function BankDetailsByPaytoType({ ); } - const firstPart = !payto.isKnown ? ( + const accountPart = !payto.isKnown ? ( Account} value={payto.targetPath} @@ -113,10 +117,17 @@ export function BankDetailsByPaytoType({ IBAN} value={payto.iban} /> ) : undefined; return ( -

-

Bank transfer details

+
+

Bank transfer details

- {firstPart} + {accountPart} Exchange} value={exchangeBaseUrl} @@ -176,7 +187,7 @@ function Row({ )} - {literal ? ( diff --git a/packages/taler-wallet-webextension/src/components/Part.tsx b/packages/taler-wallet-webextension/src/components/Part.tsx index 21c0f65dc..58165a349 100644 --- a/packages/taler-wallet-webextension/src/components/Part.tsx +++ b/packages/taler-wallet-webextension/src/components/Part.tsx @@ -14,33 +14,122 @@ GNU Taler; see the file COPYING. If not, see */ import { PaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; +import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; -import { ExtraLargeText, LargeText, SmallLightText } from "./styled/index.js"; +import { useState } from "preact/hooks"; +import { + ExtraLargeText, + LargeText, + SmallBoldText, + SmallLightText, +} from "./styled/index.js"; export type Kind = "positive" | "negative" | "neutral"; interface Props { - title: VNode; + title: VNode | string; text: VNode | string; - kind: Kind; + kind?: Kind; big?: boolean; + showSign?: boolean; } -export function Part({ text, title, kind, big }: Props): VNode { +export function Part({ + text, + title, + kind = "neutral", + big, + showSign, +}: Props): VNode { const Text = big ? ExtraLargeText : LargeText; return (
- {title} + {title} + {!showSign || kind === "neutral" + ? undefined + : kind === "positive" + ? "+" + : "-"} {text}
); } +const CollasibleBox = styled.div` + border: 1px solid black; + border-radius: 0.25em; + display: flex; + vertical-align: middle; + justify-content: space-between; + flex-direction: column; + /* margin: 0.5em; */ + padding: 0.5em; + /* margin: 1em; */ + /* width: 100%; */ + /* color: #721c24; */ + /* background: #f8d7da; */ + + & > div { + display: flex; + justify-content: space-between; + div { + margin-top: auto; + margin-bottom: auto; + } + & > button { + align-self: center; + font-size: 100%; + padding: 0; + height: 28px; + width: 28px; + } + } +`; +import arrowDown from "../svg/chevron-down.svg"; + +export function PartCollapsible({ text, title, big, showSign }: Props): VNode { + const Text = big ? ExtraLargeText : LargeText; + const [collapsed, setCollapsed] = useState(true); + + return ( + +
+ {title} + +
+ {/* + + */} + {!collapsed &&
{text}
} +
+ ); +} + interface PropsPayto { payto: PaytoUri; kind: Kind; diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index 7517a1388..a531a15dc 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -87,7 +87,7 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>` justify-content: space-between; align-items: center; & > * { - width: 500px; + width: 600px; } & > section { padding: ${({ noPadding }) => (noPadding ? "0px" : "8px")}; @@ -660,6 +660,12 @@ export const WarningText = styled.div` export const SmallText = styled.div` font-size: small; `; + +export const SmallBoldText = styled.div` + font-size: small; + font-weight: bold; +`; + export const LargeText = styled.div` font-size: large; `; diff --git a/packages/taler-wallet-webextension/src/custom.d.ts b/packages/taler-wallet-webextension/src/custom.d.ts index 521b824c7..711112ad8 100644 --- a/packages/taler-wallet-webextension/src/custom.d.ts +++ b/packages/taler-wallet-webextension/src/custom.d.ts @@ -13,7 +13,11 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ -declare module "*.jpeg" { + declare module "*.jpeg" { + const content: any; + export default content; +} +declare module "*.jpg" { const content: any; export default content; } diff --git a/packages/taler-wallet-webextension/src/stories.tsx b/packages/taler-wallet-webextension/src/stories.tsx index 9c0f69ec4..fd5d3c590 100644 --- a/packages/taler-wallet-webextension/src/stories.tsx +++ b/packages/taler-wallet-webextension/src/stories.tsx @@ -330,9 +330,11 @@ function Application(): VNode { const hash = location.hash.substring(1); const found = document.getElementById(hash); if (found) { - found.scrollIntoView({ - block: "center", - }); + setTimeout(() => { + found.scrollIntoView({ + block: "center", + }); + }, 10); } } }, []); diff --git a/packages/taler-wallet-webextension/src/test-utils.ts b/packages/taler-wallet-webextension/src/test-utils.ts index eceda616f..9e219daa6 100644 --- a/packages/taler-wallet-webextension/src/test-utils.ts +++ b/packages/taler-wallet-webextension/src/test-utils.ts @@ -26,22 +26,27 @@ options.requestAnimationFrame = (fn: () => void) => { export function createExample( Component: FunctionalComponent, - props: Partial, + props: Partial | (() => Partial), ): ComponentChildren { + //FIXME: props are evaluated on build time + // in some cases we want to evaluated the props on render time so we can get some relative timestamp + // check how we can build evaluatedProps in render time + const evaluatedProps = typeof props === "function" ? props() : props const Render = (args: any): VNode => create(Component, args); - Render.args = props; + Render.args = evaluatedProps; return Render; } export function createExampleWithCustomContext( Component: FunctionalComponent, - props: Partial, + props: Partial | (() => Partial), ContextProvider: FunctionalComponent, contextProps: Partial, ): ComponentChildren { + const evaluatedProps = typeof props === "function" ? props() : props const Render = (args: any): VNode => create(Component, args); const WithContext = (args: any): VNode => create(ContextProvider, { ...contextProps, children: [Render(args)] } as any); - WithContext.args = props + WithContext.args = evaluatedProps return WithContext } diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx index f162543ae..493cdd1d7 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx @@ -30,6 +30,7 @@ import { TransactionTip, TransactionType, TransactionWithdrawal, + WithdrawalDetails, WithdrawalType, } from "@gnu-taler/taler-util"; import { DevContextProviderForTesting } from "../context/devContext.js"; @@ -57,6 +58,8 @@ const commonTransaction = { transactionId: "12", } as TransactionCommon; +import merchantIcon from "../../static-dev/merchant-icon-11.jpeg"; + const exampleData = { withdraw: { ...commonTransaction, @@ -65,27 +68,34 @@ const exampleData = { withdrawalDetails: { confirmed: false, reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", - exchangePaytoUris: ["payto://x-taler-bank/bank/account"], + exchangePaytoUris: ["payto://x-taler-bank/bank.demo.taler.net/Exchange"], type: WithdrawalType.ManualTransfer, }, } as TransactionWithdrawal, payment: { ...commonTransaction, - amountEffective: "KUDOS:11", + amountEffective: "KUDOS:12", type: TransactionType.Payment, info: { contractTermsHash: "ASDZXCASD", merchant: { name: "the merchant", + logo: merchantIcon, + website: "https://www.themerchant.taler", + email: "contact@merchant.taler", }, orderId: "2021.167-03NPY6MCYMVGT", products: [], summary: "Essay: Why the Devil's Advocate Doesn't Help Reach the Truth", fulfillmentMessage: "", + // delivery_date: { t_s: 1 }, + // delivery_location: { + // address_lines: [""], + // }, }, refundPending: undefined, - totalRefundEffective: "USD:0", - totalRefundRaw: "USD:0", + totalRefundEffective: "KUDOS:0", + totalRefundRaw: "KUDOS:0", proposalId: "1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0", status: PaymentStatus.Accepted, } as TransactionPayment, @@ -93,7 +103,7 @@ const exampleData = { ...commonTransaction, type: TransactionType.Deposit, depositGroupId: "#groupId", - targetPaytoUri: "payto://x-taler-bank/bank/account", + targetPaytoUri: "payto://x-taler-bank/bank.demo.taler.net/Exchange", } as TransactionDeposit, refresh: { ...commonTransaction, @@ -117,7 +127,7 @@ const exampleData = { }, orderId: "2021.167-03NPY6MCYMVGT", products: [], - summary: "the summary", + summary: "Essay: Why the Devil's Advocate Doesn't Help Reach the Truth", fulfillmentMessage: "", }, refundPending: undefined, @@ -143,20 +153,27 @@ export const Withdraw = createExample(TestedComponent, { transaction: exampleData.withdraw, }); -export const WithdrawOneMinuteAgo = createExample(TestedComponent, { +export const WithdrawFiveMinutesAgo = createExample(TestedComponent, () => ({ transaction: { ...exampleData.withdraw, - timestamp: TalerProtocolTimestamp.fromSeconds(new Date().getTime() - 60), + timestamp: TalerProtocolTimestamp.fromSeconds( + new Date().getTime() / 1000 - 60 * 5, + ), }, -}); +})); -export const WithdrawOneMinuteAgoAndPending = createExample(TestedComponent, { - transaction: { - ...exampleData.withdraw, - timestamp: TalerProtocolTimestamp.fromSeconds(new Date().getTime() - 60), - pending: true, - }, -}); +export const WithdrawFiveMinutesAgoAndPending = createExample( + TestedComponent, + () => ({ + transaction: { + ...exampleData.withdraw, + timestamp: TalerProtocolTimestamp.fromSeconds( + new Date().getTime() / 1000 - 60 * 5, + ), + pending: true, + }, + }), +); export const WithdrawError = createExample(TestedComponent, { transaction: { @@ -177,17 +194,17 @@ export const WithdrawErrorInDevMode = createExampleInCustomContext( { value: true }, ); -export const WithdrawPendingManual = createExample(TestedComponent, { +export const WithdrawPendingManual = createExample(TestedComponent, () => ({ transaction: { ...exampleData.withdraw, withdrawalDetails: { type: WithdrawalType.ManualTransfer, exchangePaytoUris: ["payto://iban/asdasdasd"], reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", - }, + } as WithdrawalDetails, pending: true, }, -}); +})); export const WithdrawPendingTalerBankUnconfirmed = createExample( TestedComponent, @@ -231,10 +248,95 @@ export const PaymentError = createExample(TestedComponent, { }, }); -export const PaymentWithoutFee = createExample(TestedComponent, { +export const PaymentWithRefund = createExample(TestedComponent, { + transaction: { + ...exampleData.payment, + amountRaw: "KUDOS:12", + totalRefundEffective: "KUDOS:1", + totalRefundRaw: "KUDOS:1", + }, +}); + +export const PaymentWithDeliveryDate = createExample(TestedComponent, { + transaction: { + ...exampleData.payment, + amountRaw: "KUDOS:12", + info: { + ...exampleData.payment.info, + delivery_date: { + t_s: new Date().getTime() / 1000, + }, + }, + }, +}); + +export const PaymentWithDeliveryAddr = createExample(TestedComponent, { + transaction: { + ...exampleData.payment, + amountRaw: "KUDOS:12", + info: { + ...exampleData.payment.info, + delivery_location: { + country: "Argentina", + street: "Elm Street", + district: "CABA", + post_code: "1101", + }, + }, + }, +}); + +export const PaymentWithDeliveryFull = createExample(TestedComponent, { + transaction: { + ...exampleData.payment, + amountRaw: "KUDOS:12", + info: { + ...exampleData.payment.info, + delivery_date: { + t_s: new Date().getTime() / 1000, + }, + delivery_location: { + country: "Argentina", + street: "Elm Street", + district: "CABA", + post_code: "1101", + }, + }, + }, +}); + +export const PaymentWithRefundPending = createExample(TestedComponent, { + transaction: { + ...exampleData.payment, + amountRaw: "KUDOS:12", + refundPending: "KUDOS:3", + totalRefundEffective: "KUDOS:1", + totalRefundRaw: "KUDOS:1", + }, +}); + +export const PaymentWithFeeAndRefund = createExample(TestedComponent, { transaction: { ...exampleData.payment, amountRaw: "KUDOS:11", + totalRefundEffective: "KUDOS:1", + totalRefundRaw: "KUDOS:1", + }, +}); + +export const PaymentWithFeeAndRefundFee = createExample(TestedComponent, { + transaction: { + ...exampleData.payment, + amountRaw: "KUDOS:11", + totalRefundEffective: "KUDOS:1", + totalRefundRaw: "KUDOS:2", + }, +}); + +export const PaymentWithoutFee = createExample(TestedComponent, { + transaction: { + ...exampleData.payment, + amountRaw: "KUDOS:12", }, }); @@ -249,7 +351,7 @@ export const PaymentWithProducts = createExample(TestedComponent, { ...exampleData.payment, info: { ...exampleData.payment.info, - summary: "this order has 5 products", + summary: "summary of 5 products", products: [ { description: "t-shirt", @@ -360,20 +462,3 @@ export const RefundError = createExample(TestedComponent, { export const RefundPending = createExample(TestedComponent, { transaction: { ...exampleData.refund, pending: true }, }); - -export const RefundWithProducts = createExample(TestedComponent, { - transaction: { - ...exampleData.refund, - info: { - ...exampleData.refund.info, - products: [ - { - description: "t-shirt", - }, - { - description: "beer", - }, - ], - }, - } as TransactionRefund, -}); 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({ )} -
-
{children}
-
+
{children}
@@ -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 ( {confirmBeforeForget ? ( @@ -219,205 +235,125 @@ export function TransactionView({ ) : undefined} - - Withdrawal - - ); } - 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 ( - - Payment - -
+ + + + )} + {location.address_lines && ( + + + + + )} + {location.building_number && ( + + + + + )} + {location.building_name && ( + + + + + )} + {location.street && ( + + + + + )} + {location.post_code && ( + + + + + )} + {location.town_location && ( + + + + + )} + {location.town && ( + + + + + )} + {location.district && ( + + + + + )} + {location.country_subdivision && ( + + + + + )} + + )} + + {!location || !date ? undefined : ( + + + + )} + {date && ( + + + + + + + )} + + ); +} + +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 ( + + + + + + + {Amounts.isNonZero(refundRaw) && ( + + + + + )} + {Amounts.isNonZero(fee) && ( + + + + + )} + + + + + + + + {hasProducts && ( + + + + )} + {hasShipping && ( + + + + )} + + ); +} + +function RefundDetails({ + transaction, +}: { + transaction: TransactionRefund; +}): VNode { + const { i18n } = useTranslationContext(); + + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountRaw), + Amounts.parseOrThrow(transaction.amountEffective), + ).amount; + + return ( + + + + + + + {Amounts.isNonZero(fee) && ( + + + + + )} + + + + + + + + + ); +} + +function DepositDetails({ + transaction, +}: { + transaction: TransactionDeposit; +}): VNode { + const { i18n } = useTranslationContext(); + + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountRaw), + Amounts.parseOrThrow(transaction.amountEffective), + ).amount; + + return ( + + + + + + + {Amounts.isNonZero(fee) && ( + + + + + )} + + + + + + + + + ); +} +function RefreshDetails({ + transaction, +}: { + transaction: TransactionRefresh; +}): VNode { + const { i18n } = useTranslationContext(); + + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountRaw), + Amounts.parseOrThrow(transaction.amountEffective), + ).amount; + + return ( + + + + + + + + + + + + + + ); +} + +function TipDetails({ transaction }: { transaction: TransactionTip }): VNode { + const { i18n } = useTranslationContext(); + + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountRaw), + Amounts.parseOrThrow(transaction.amountEffective), + ).amount; + + return ( + + + + + + + {Amounts.isNonZero(fee) && ( + + + + + )} + + + + + + + + + ); +} + +function WithdrawDetails({ + transaction, +}: { + transaction: TransactionWithdrawal; +}): VNode { + const { i18n } = useTranslationContext(); + + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountRaw), + Amounts.parseOrThrow(transaction.amountEffective), + ).amount; + + return ( + + + + + + + {Amounts.isNonZero(fee) && ( + + + + + )} + + + + + + + + + ); +} + +function Header({ + timestamp, + total, + children, + kind, + type, +}: { + timestamp: TalerProtocolTimestamp; + total: AmountJson; + children: ComponentChildren; + kind: Kind; + type: string; +}): VNode { + return ( +
+
+ {children} +
+
+ + } + kind={kind} + showSign + /> + +
+
+ ); +} diff --git a/packages/taler-wallet-webextension/static-dev/merchant-icon-11.jpeg b/packages/taler-wallet-webextension/static-dev/merchant-icon-11.jpeg new file mode 100644 index 000000000..1777936c8 Binary files /dev/null and b/packages/taler-wallet-webextension/static-dev/merchant-icon-11.jpeg differ
+ {name}
+ Country + {location.country}
+ Address lines + {location.address_lines}
+ Building number + {location.building_number}
+ Building name + {location.building_name}
+ Street + {location.street}
+ Post code + {location.post_code}
+ Town location + {location.town_location}
+ Town + {location.town}
+ District + {location.district}
+ Country subdivision + {location.country_subdivision}
+
+
Date +
Price + +
Refunded + +
Transaction fees + +
+
+
Total + +
+ Products} + text={ + + {transaction.info.products?.map((p, k) => ( + + + + +
+ {p.quantity && p.quantity > 0 && ( + + x {p.quantity} {p.unit} + + )} +
{p.description}
+
+
+ ))} +
+ } + /> +
+ Delivery} + text={ + + } + /> +
Amount + +
Transaction fees + +
+
+
Total + +
Amount + +
Transaction fees + +
+
+
Total transfer + +
Amount + +
+
+
Transaction fees + +
Amount + +
Transaction fees + +
+
+
Total + +
Withdraw + +
Transaction fees + +
+
+
Total + +