transaction details new ui
This commit is contained in:
parent
0bc235c64b
commit
e22bdd52f7
@ -402,6 +402,12 @@ export class Amounts {
|
||||
*/
|
||||
static stringify(a: AmountLike): string {
|
||||
a = Amounts.jsonifyAmount(a);
|
||||
const s = this.stringifyValue(a)
|
||||
|
||||
return `${a.currency}:${s}`;
|
||||
}
|
||||
|
||||
static stringifyValue(a: AmountJson): string {
|
||||
const av = a.value + Math.floor(a.fraction / amountFractionalBase);
|
||||
const af = a.fraction % amountFractionalBase;
|
||||
let s = av.toString();
|
||||
@ -417,7 +423,6 @@ export class Amounts {
|
||||
n = (n * 10) % amountFractionalBase;
|
||||
}
|
||||
}
|
||||
|
||||
return `${a.currency}:${s}`;
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
@ -158,8 +158,6 @@ export const decorators = [
|
||||
</style>
|
||||
<LogoHeader />
|
||||
<NavBar path={path} devMode={path === '/dev'} />
|
||||
<link key="1" rel="stylesheet" type="text/css" href="/static/style/pure.css" />
|
||||
<link key="2" rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
|
||||
<Story />
|
||||
</div>
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>`
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
background-color: #f7f7f7;
|
||||
& button {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
@ -199,6 +200,33 @@ export const Button = styled.button`
|
||||
}
|
||||
`;
|
||||
|
||||
export const FontIcon = styled.div`
|
||||
font-family: monospace;
|
||||
font-size: x-large;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
/* vertical-align: text-top; */
|
||||
`
|
||||
export const ButtonBox = styled(Button)`
|
||||
padding: .5em;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
|
||||
& > ${FontIcon} {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
display: inline;
|
||||
line-height: 0px;
|
||||
}
|
||||
background-color: transparent;
|
||||
|
||||
border: 1px solid;
|
||||
border-radius: 4px;
|
||||
border-color: black;
|
||||
color: black;
|
||||
`
|
||||
|
||||
|
||||
const ButtonVariant = styled(Button)`
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
@ -208,18 +236,35 @@ const ButtonVariant = styled(Button)`
|
||||
export const ButtonPrimary = styled(ButtonVariant)`
|
||||
background-color: rgb(66, 184, 221);
|
||||
`
|
||||
export const ButtonBoxPrimary = styled(ButtonBox)`
|
||||
color: rgb(66, 184, 221);
|
||||
border-color: rgb(66, 184, 221);
|
||||
`
|
||||
|
||||
export const ButtonSuccess = styled(ButtonVariant)`
|
||||
background-color: rgb(28, 184, 65);
|
||||
`
|
||||
export const ButtonBoxSuccess = styled(ButtonBox)`
|
||||
color: rgb(28, 184, 65);
|
||||
border-color: rgb(28, 184, 65);
|
||||
`
|
||||
|
||||
export const ButtonWarning = styled(ButtonVariant)`
|
||||
background-color: rgb(223, 117, 20);
|
||||
`
|
||||
export const ButtonBoxWarning = styled(ButtonBox)`
|
||||
color: rgb(223, 117, 20);
|
||||
border-color: rgb(223, 117, 20);
|
||||
`
|
||||
|
||||
export const ButtonDestructive = styled(ButtonVariant)`
|
||||
background-color: rgb(202, 60, 60);
|
||||
`
|
||||
export const ButtonBoxDestructive = styled(ButtonBox)`
|
||||
color: rgb(202, 60, 60);
|
||||
border-color: rgb(202, 60, 60);
|
||||
`
|
||||
|
||||
|
||||
export const BoldLight = styled.div`
|
||||
color: gray;
|
||||
@ -336,6 +381,7 @@ export const CenteredTextBold = styled(CenteredText)`
|
||||
font-weight: bold;
|
||||
color: ${((props: any): any => String(props.color) as any) as any};
|
||||
`
|
||||
|
||||
export const Input = styled.div<{ invalid?: boolean }>`
|
||||
& label {
|
||||
display: block;
|
||||
@ -359,7 +405,7 @@ export const ErrorBox = styled.div`
|
||||
/* margin: 0.5em; */
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
width: 100%;
|
||||
/* width: 100%; */
|
||||
color: #721c24;
|
||||
background: #f8d7da;
|
||||
|
||||
|
@ -58,9 +58,7 @@ export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewP
|
||||
const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged
|
||||
return (
|
||||
<PopupBox>
|
||||
{info.backupProblem || info.lastError ? <header>
|
||||
<Error info={info} />
|
||||
</header> : undefined }
|
||||
<Error info={info} />
|
||||
<header>
|
||||
<h3>{info.name} <SmallTextLight>{info.syncProviderBaseUrl}</SmallTextLight></h3>
|
||||
<PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 60)'}>{isPaid ? 'Paid' : 'Unpaid'}</PaymentStatus>
|
||||
|
@ -58,9 +58,7 @@ export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewP
|
||||
const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged
|
||||
return (
|
||||
<WalletBox>
|
||||
{info.backupProblem || info.lastError ? <header>
|
||||
<Error info={info} />
|
||||
</header> : undefined }
|
||||
<Error info={info} />
|
||||
<header>
|
||||
<h3>{info.name} <SmallTextLight>{info.syncProviderBaseUrl}</SmallTextLight></h3>
|
||||
<PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 60)'}>{isPaid ? 'Paid' : 'Unpaid'}</PaymentStatus>
|
||||
|
@ -40,8 +40,8 @@ export default {
|
||||
};
|
||||
|
||||
const commonTransaction = {
|
||||
amountRaw: 'USD:10',
|
||||
amountEffective: 'USD:9',
|
||||
amountRaw: 'KUDOS:11',
|
||||
amountEffective: 'KUDOS:9.2',
|
||||
pending: false,
|
||||
timestamp: {
|
||||
t_ms: new Date().getTime()
|
||||
@ -62,7 +62,7 @@ const exampleData = {
|
||||
} as TransactionWithdrawal,
|
||||
payment: {
|
||||
...commonTransaction,
|
||||
amountEffective: 'USD:11',
|
||||
amountEffective: 'KUDOS:11',
|
||||
type: TransactionType.Payment,
|
||||
info: {
|
||||
contractTermsHash: 'ASDZXCASD',
|
||||
@ -147,7 +147,7 @@ export const PaymentError = createExample(TestedComponent, {
|
||||
export const PaymentWithoutFee = createExample(TestedComponent, {
|
||||
transaction: {
|
||||
...exampleData.payment,
|
||||
amountRaw: 'USD:11',
|
||||
amountRaw: 'KUDOS:11',
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -14,7 +14,7 @@
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { AmountJson, Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util";
|
||||
import { AmountJson, AmountLike, Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util";
|
||||
import { format } from "date-fns";
|
||||
import { Fragment, JSX, VNode, h } from "preact";
|
||||
import { route } from 'preact-router';
|
||||
@ -22,7 +22,7 @@ import { useEffect, useState } from "preact/hooks";
|
||||
import * as wxApi from "../wxApi";
|
||||
import { Pages } from "../NavigationBar";
|
||||
import emptyImg from "../../static/img/empty.png"
|
||||
import { Button, ButtonDestructive, ButtonPrimary, ListOfProducts, PopupBox, Row, RowBorderGray, SmallTextLight, WalletBox } from "../components/styled";
|
||||
import { Button, ButtonBox, ButtonBoxDestructive, ButtonDestructive, ButtonPrimary, ExtraLargeText, FontIcon, LargeText, ListOfProducts, PopupBox, Row, RowBorderGray, SmallTextLight, WalletBox } from "../components/styled";
|
||||
import { ErrorMessage } from "../components/ErrorMessage";
|
||||
|
||||
export function TransactionPage({ tid }: { tid: string; }): JSX.Element {
|
||||
@ -73,43 +73,54 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall
|
||||
return null
|
||||
}
|
||||
|
||||
function Fee({ value }: { value: AmountJson }) {
|
||||
if (Amounts.isZero(value)) return null
|
||||
return <span style="font-size: 16px;font-weight: normal;color: gray;">(fee {Amounts.stringify(value)})</span>
|
||||
}
|
||||
|
||||
function TransactionTemplate({ upperRight, children }: { upperRight: VNode, children: VNode[] }) {
|
||||
function TransactionTemplate({ children }: { children: VNode[] }) {
|
||||
return <WalletBox>
|
||||
<header>
|
||||
<SmallTextLight>
|
||||
{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}
|
||||
</SmallTextLight>
|
||||
<SmallTextLight>
|
||||
{upperRight}
|
||||
</SmallTextLight>
|
||||
</header>
|
||||
<section>
|
||||
<ErrorMessage title={transaction?.error?.hint} />
|
||||
{children}
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
<footer>
|
||||
<Button onClick={onBack}><i18n.Translate> < back</i18n.Translate></Button>
|
||||
<ButtonBox onClick={onBack}><i18n.Translate> <FontIcon>←</FontIcon> </i18n.Translate></ButtonBox>
|
||||
<div>
|
||||
{transaction?.error ? <ButtonPrimary onClick={onRetry}><i18n.Translate>retry</i18n.Translate></ButtonPrimary> : null}
|
||||
<ButtonDestructive onClick={onDelete}><i18n.Translate>delete</i18n.Translate></ButtonDestructive>
|
||||
<ButtonBoxDestructive onClick={onDelete}><i18n.Translate>🗑</i18n.Translate></ButtonBoxDestructive>
|
||||
</div>
|
||||
</footer>
|
||||
</WalletBox>
|
||||
}
|
||||
type Kind = 'positive' | 'negative' | 'neutral';
|
||||
function Part({ text, title, kind, big }: { title: string, text: AmountLike, kind: Kind, big?: boolean }) {
|
||||
const Text = big ? ExtraLargeText : LargeText;
|
||||
return <div style={{ margin: '1em' }}>
|
||||
<SmallTextLight style={{ margin: '.5em' }}>{title}</SmallTextLight>
|
||||
<Text style={{ color: kind == 'positive' ? 'green' : (kind == 'negative' ? 'red' : 'black') }}>
|
||||
{text}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
|
||||
function amountToString(text: AmountLike) {
|
||||
const aj = Amounts.jsonifyAmount(text)
|
||||
const amount = Amounts.stringifyValue(aj)
|
||||
return `${amount} ${aj.currency}`
|
||||
}
|
||||
|
||||
|
||||
if (transaction.type === TransactionType.Withdrawal) {
|
||||
const fee = Amounts.sub(
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
).amount
|
||||
return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.exchangeBaseUrl}</b></Fragment>}>
|
||||
<h3>Withdraw <Status /></h3>
|
||||
<h1>{transaction.amountEffective} <Fee value={fee} /></h1>
|
||||
return <TransactionTemplate>
|
||||
<h2>Withdrawal <Status /></h2>
|
||||
<div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div>
|
||||
<br />
|
||||
<Part title="Total withdrawn" text={amountToString(transaction.amountEffective)} kind='positive' />
|
||||
<Part title="Chosen amount" text={amountToString(transaction.amountRaw)} kind='neutral' />
|
||||
<Part title="Exchange fee" text={amountToString(fee)} kind='negative' />
|
||||
<Part title="Exchange" text={new URL(transaction.exchangeBaseUrl).hostname} kind='neutral' />
|
||||
</TransactionTemplate>
|
||||
}
|
||||
|
||||
@ -123,13 +134,17 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
).amount
|
||||
|
||||
return <TransactionTemplate upperRight={<Fragment>To <b>{transaction.info.merchant.name}</b></Fragment>}>
|
||||
<h3>Payment <Status /></h3>
|
||||
<h1>{transaction.amountEffective} <Fee value={fee} /></h1>
|
||||
<span style="font-size:small; color:gray">#{transaction.info.orderId}</span>
|
||||
<p>
|
||||
{transaction.info.summary}
|
||||
</p>
|
||||
return <TransactionTemplate>
|
||||
<h2>Payment <Status /></h2>
|
||||
<div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div>
|
||||
<br />
|
||||
<Part big title="Total paid" text={amountToString(transaction.amountEffective)} kind='negative' />
|
||||
<Part big title="Purchase amount" text={amountToString(transaction.amountRaw)} kind='neutral' />
|
||||
<Part big title="Fee" text={amountToString(fee)} kind='negative' />
|
||||
<Part title="Merchant" text={transaction.info.merchant.name} kind='neutral' />
|
||||
<Part title="Purchase" text={transaction.info.summary} kind='neutral' />
|
||||
<Part title="Receipt" text={`#${transaction.info.orderId}`} kind='neutral' />
|
||||
|
||||
<div>
|
||||
{transaction.info.products && transaction.info.products.length > 0 &&
|
||||
<ListOfProducts>
|
||||
@ -153,9 +168,13 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
).amount
|
||||
return <TransactionTemplate upperRight={<Fragment>To <b>{transaction.targetPaytoUri}</b></Fragment>}>
|
||||
<h3>Deposit <Status /></h3>
|
||||
<h1>{transaction.amountEffective} <Fee value={fee} /></h1>
|
||||
return <TransactionTemplate>
|
||||
<h2>Deposit <Status /></h2>
|
||||
<div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div>
|
||||
<br />
|
||||
<Part big title="Total deposit" text={amountToString(transaction.amountEffective)} kind='negative' />
|
||||
<Part big title="Purchase amount" text={amountToString(transaction.amountRaw)} kind='neutral' />
|
||||
<Part big title="Fee" text={amountToString(fee)} kind='negative' />
|
||||
</TransactionTemplate>
|
||||
}
|
||||
|
||||
@ -164,9 +183,13 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
).amount
|
||||
return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.exchangeBaseUrl}</b></Fragment>}>
|
||||
<h3>Refresh <Status /></h3>
|
||||
<h1>{transaction.amountEffective} <Fee value={fee} /></h1>
|
||||
return <TransactionTemplate>
|
||||
<h2>Refresh <Status /></h2>
|
||||
<div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div>
|
||||
<br />
|
||||
<Part big title="Total refresh" text={amountToString(transaction.amountEffective)} kind='negative' />
|
||||
<Part big title="Refresh amount" text={amountToString(transaction.amountRaw)} kind='neutral' />
|
||||
<Part big title="Fee" text={amountToString(fee)} kind='negative' />
|
||||
</TransactionTemplate>
|
||||
}
|
||||
|
||||
@ -175,9 +198,13 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
).amount
|
||||
return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.merchantBaseUrl}</b></Fragment>}>
|
||||
<h3>Tip <Status /></h3>
|
||||
<h1>{transaction.amountEffective} <Fee value={fee} /></h1>
|
||||
return <TransactionTemplate>
|
||||
<h2>Tip <Status /></h2>
|
||||
<div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div>
|
||||
<br />
|
||||
<Part big title="Total tip" text={amountToString(transaction.amountEffective)} kind='positive' />
|
||||
<Part big title="Received amount" text={amountToString(transaction.amountRaw)} kind='neutral' />
|
||||
<Part big title="Fee" text={amountToString(fee)} kind='negative' />
|
||||
</TransactionTemplate>
|
||||
}
|
||||
|
||||
@ -186,11 +213,17 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
).amount
|
||||
return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.info.merchant.name}</b></Fragment>}>
|
||||
<h3>Refund <Status /></h3>
|
||||
<h1>{transaction.amountEffective} <Fee value={fee} /></h1>
|
||||
return <TransactionTemplate>
|
||||
<h2>Refund <Status /></h2>
|
||||
<div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div>
|
||||
<br />
|
||||
<Part big title="Total refund" text={amountToString(transaction.amountEffective)} kind='positive' />
|
||||
<Part big title="Refund amount" text={amountToString(transaction.amountRaw)} kind='neutral' />
|
||||
<Part big title="Fee" text={amountToString(fee)} kind='negative' />
|
||||
<Part title="Merchant" text={transaction.info.merchant.name} kind='neutral' />
|
||||
<Part title="Purchase" text={transaction.info.summary} kind='neutral' />
|
||||
<Part title="Receipt" text={`#${transaction.info.orderId}`} kind='neutral' />
|
||||
|
||||
<span style="font-size:small; color:gray">#{transaction.info.orderId}</span>
|
||||
<p>
|
||||
{transaction.info.summary}
|
||||
</p>
|
||||
|
Loading…
Reference in New Issue
Block a user