Skip to content

Commit 7cf7653

Browse files
authored
Merge pull request #7 from MakerXStudio/payment-transaction
chore: visualise payment transaction + tests
2 parents c8ae8ca + 00ea744 commit 7cf7653

27 files changed

+843
-84
lines changed

.vscode/extensions.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
{
2-
"recommendations": ["editorconfig.editorconfig", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "bradlc.vscode-tailwindcss"]
2+
"recommendations": [
3+
"editorconfig.editorconfig",
4+
"esbenp.prettier-vscode",
5+
"dbaeumer.vscode-eslint",
6+
"bradlc.vscode-tailwindcss",
7+
"vitest.explorer"
8+
]
39
}

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@makerx/eslint-config": "^3.1.0",
5050
"@makerx/prettier-config": "^2.0.0",
5151
"@makerx/ts-config": "^1.0.1",
52+
"@makerx/ts-dossier": "^3.0.0",
5253
"@tauri-apps/cli": "^1.5.11",
5354
"@testing-library/react": "^14.2.2",
5455
"@testing-library/user-event": "^14.5.2",
@@ -138,4 +139,4 @@
138139
"semantic-release-export-data"
139140
]
140141
}
141-
}
142+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { cn } from '../utils'
2+
3+
type Props = {
4+
items: { dt: string; dd: string | number | JSX.Element[] | JSX.Element }[]
5+
}
6+
7+
export function DescriptionList({ items }: Props) {
8+
return (
9+
<div className={cn('grid grid-cols-[minmax(min-content,auto)_1fr] gap-x-4')}>
10+
{items.map((item, index) => (
11+
<dl key={index} className={cn('grid grid-cols-subgrid col-span-2')}>
12+
<dt>{item.dt}</dt>
13+
<dd>{item.dd}</dd>
14+
</dl>
15+
))}
16+
</div>
17+
)
18+
}

src/features/common/components/display-algo.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { cn } from '../utils'
33
import { AlgoAmount } from '@algorandfoundation/algokit-utils/types/amount'
44

55
export type Props = {
6+
className?: string
67
amount: AlgoAmount
78
}
89

9-
export function DisplayAlgo({ amount }: Props) {
10+
export function DisplayAlgo({ className, amount }: Props) {
1011
return (
11-
<div className={cn('flex items-center')}>
12+
<div className={cn(className, 'flex items-center')}>
1213
{amount.algos}
1314
<SvgAlgorand />
1415
</div>

src/features/theme/constant.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,23 @@ export const themeConstants = {
55
export const transactionPageConstants = {
66
transactionNotFound: 'Transaction not found',
77
genericError: 'Error loading transaction',
8+
labels: {
9+
transactionId: 'Transaction ID',
10+
type: 'Type',
11+
timestamp: 'Timestamp',
12+
block: 'Block',
13+
group: 'Group',
14+
fee: 'Fee',
15+
sender: 'Sender',
16+
receiver: 'Receiver',
17+
amount: 'Amount',
18+
viewTransaction: 'View Transaction',
19+
visual: 'Visual',
20+
table: 'Table',
21+
multisig: {
22+
version: 'Version',
23+
threshold: 'Threshold',
24+
subsigners: 'Subsigners',
25+
},
26+
},
827
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { MultisigModel } from '../models'
2+
import { Card, CardContent } from '@/features/common/components/card'
3+
import { cn } from '@/features/common/utils'
4+
import { useMemo } from 'react'
5+
import { DescriptionList } from '@/features/common/components/description-list'
6+
import { transactionPageConstants } from '@/features/theme/constant'
7+
8+
export type MultisigProps = {
9+
multisig: MultisigModel
10+
}
11+
12+
export function Multisig({ multisig: multisig }: MultisigProps) {
13+
const multisigItems = useMemo(
14+
() => [
15+
{
16+
dt: transactionPageConstants.labels.multisig.version,
17+
dd: multisig.version,
18+
},
19+
{
20+
dt: transactionPageConstants.labels.multisig.threshold,
21+
dd: multisig.threshold,
22+
},
23+
{
24+
dt: transactionPageConstants.labels.multisig.subsigners,
25+
dd: multisig.subsigners.map((address, index) => <div key={index}>{address}</div>),
26+
},
27+
],
28+
[multisig.subsigners, multisig.version, multisig.threshold]
29+
)
30+
31+
return (
32+
<Card className={cn('p-4')}>
33+
<CardContent className={cn('text-sm space-y-4')}>
34+
<div className={cn('space-y-2')}>
35+
<h1 className={cn('text-2xl text-primary font-bold')}>Multisig</h1>
36+
<DescriptionList items={multisigItems} />
37+
</div>
38+
</CardContent>
39+
</Card>
40+
)
41+
}

src/features/transactions/components/payment-transaction.tsx

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@ import { TransactionJson } from './transaction-json'
88
import { useMemo } from 'react'
99
import { PaymentTransactionModel } from '../models'
1010
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
11+
import { DescriptionList } from '@/features/common/components/description-list'
12+
import { TransactionViewVisual } from './transaction-view-visual'
13+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/features/common/components/tabs'
14+
import { TransactionViewTable } from './transaction-view-table'
15+
import { Multisig } from './multisig'
1116

1217
export type Props = {
1318
transaction: PaymentTransactionModel
1419
rawTransaction: TransactionResult
1520
}
1621

1722
export function PaymentTransaction({ transaction, rawTransaction }: Props) {
18-
const transactionCardItems = useMemo(
23+
const paymentTransactionItems = useMemo(
1924
() => [
2025
{
2126
dt: 'Sender',
@@ -51,15 +56,27 @@ export function PaymentTransaction({ transaction, rawTransaction }: Props) {
5156
<h1 className={cn('text-2xl text-primary font-bold')}>Payment</h1>
5257
<Button>Replay</Button>
5358
</div>
54-
{transactionCardItems.map((item, index) => (
55-
<dl className={cn('grid grid-cols-8')} key={index}>
56-
<dt>{item.dt}</dt>
57-
<dd className={cn('col-span-7')}>{item.dd}</dd>
58-
</dl>
59-
))}
59+
<DescriptionList items={paymentTransactionItems} />
6060
</div>
61+
<Tabs defaultValue="visual">
62+
<TabsList aria-label="View Transaction">
63+
<TabsTrigger className={cn('data-[state=active]:border-primary data-[state=active]:border-b-2 w-32')} value="visual">
64+
Visual
65+
</TabsTrigger>
66+
<TabsTrigger className={cn('data-[state=active]:border-primary data-[state=active]:border-b-2 w-32')} value="table">
67+
Table
68+
</TabsTrigger>
69+
</TabsList>
70+
<TabsContent value="visual" className={cn('border-solid border-2 border-border p-4')}>
71+
<TransactionViewVisual transaction={transaction} />
72+
</TabsContent>
73+
<TabsContent value="table" className={cn('border-solid border-2 border-border p-4')}>
74+
<TransactionViewTable transaction={transaction} />
75+
</TabsContent>
76+
</Tabs>
6177
<TransactionNote transaction={transaction} />
6278
<TransactionJson transaction={rawTransaction} />
79+
{transaction.multisig && <Multisig multisig={transaction.multisig} />}
6380
</CardContent>
6481
</Card>
6582
</div>

src/features/transactions/components/transaction-info.tsx

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,60 @@ import { dateFormatter } from '@/utils/format'
44
import { DisplayAlgo } from '@/features/common/components/display-algo'
55
import { useMemo } from 'react'
66
import { PaymentTransactionModel } from '../models'
7+
import { DescriptionList } from '@/features/common/components/description-list'
8+
import { isDefined } from '@/utils/is-defined'
9+
import { transactionPageConstants } from '@/features/theme/constant'
710

811
export type Props = {
912
transaction: PaymentTransactionModel
1013
}
1114

1215
export function TransactionInfo({ transaction }: Props) {
13-
const transactionCardItems = useMemo(
14-
() => [
15-
{
16-
dt: 'Transaction ID',
17-
dd: transaction.id,
18-
},
19-
{
20-
dt: 'Type',
21-
dd: transaction.type,
22-
},
23-
{
24-
dt: 'Timestamp',
25-
// TODO: check timezone
26-
dd: dateFormatter.asLongDateTime(transaction.roundTime),
27-
},
28-
{
29-
dt: 'Block',
30-
dd: (
31-
<a href="#" className={cn('text-primary underline')}>
32-
{transaction.confirmedRound}
33-
</a>
34-
),
35-
},
36-
{
37-
dt: 'Group',
38-
dd: (
39-
<a href="#" className={cn('text-primary underline')}>
40-
{transaction.group}
41-
</a>
42-
),
43-
},
44-
{
45-
dt: 'Fee',
46-
dd: <DisplayAlgo amount={transaction.fee} />,
47-
},
48-
],
16+
const transactionInfoItems = useMemo(
17+
() =>
18+
[
19+
{
20+
dt: transactionPageConstants.labels.transactionId,
21+
dd: transaction.id,
22+
},
23+
{
24+
dt: transactionPageConstants.labels.type,
25+
dd: transaction.type,
26+
},
27+
{
28+
dt: transactionPageConstants.labels.timestamp,
29+
dd: dateFormatter.asLongDateTime(new Date(transaction.roundTime)),
30+
},
31+
{
32+
dt: transactionPageConstants.labels.block,
33+
dd: (
34+
<a href="#" className={cn('text-primary underline')}>
35+
{transaction.confirmedRound}
36+
</a>
37+
),
38+
},
39+
transaction.group
40+
? {
41+
dt: transactionPageConstants.labels.group,
42+
dd: (
43+
<a href="#" className={cn('text-primary underline')}>
44+
{transaction.group}
45+
</a>
46+
),
47+
}
48+
: undefined,
49+
{
50+
dt: transactionPageConstants.labels.fee,
51+
dd: transaction.fee ? <DisplayAlgo amount={transaction.fee} /> : 'N/A',
52+
},
53+
].filter(isDefined),
4954
[transaction.confirmedRound, transaction.fee, transaction.group, transaction.id, transaction.roundTime, transaction.type]
5055
)
5156

5257
return (
5358
<Card className={cn('p-4')}>
5459
<CardContent className={cn('text-sm space-y-2')}>
55-
{transactionCardItems.map((item, index) => (
56-
<dl className={cn('grid grid-cols-8')} key={index}>
57-
<dt>{item.dt}</dt>
58-
<dd className={cn('col-span-7')}>{item.dd}</dd>
59-
</dl>
60-
))}
60+
<DescriptionList items={transactionInfoItems} />
6161
</CardContent>
6262
</Card>
6363
)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { cn } from '@/features/common/utils'
2+
import { TransactionModel, TransactionType } from '../models'
3+
import { DisplayAlgo } from '@/features/common/components/display-algo'
4+
import { ellipseAddress } from '@/utils/ellipse-address'
5+
import { flattenInnerTransactions } from '@/utils/flatten-inner-transactions'
6+
import { useMemo } from 'react'
7+
8+
const graphConfig = {
9+
indentationWidth: 20,
10+
}
11+
12+
type Props = {
13+
transaction: TransactionModel
14+
}
15+
16+
export function TransactionViewTable({ transaction }: Props) {
17+
const flattenedTransactions = useMemo(() => flattenInnerTransactions(transaction), [transaction])
18+
19+
return (
20+
<table className={cn('w-full')}>
21+
<thead>
22+
<tr>
23+
<th className={cn('border-2')}>Transaction ID</th>
24+
<th className={cn('p-2 border-2')}>From</th>
25+
<th className={cn('p-2 border-2')}>To</th>
26+
<th className={cn('p-2 border-2')}>Type</th>
27+
<th className={cn('p-2 border-2')}>Amount</th>
28+
</tr>
29+
</thead>
30+
<tbody>
31+
{flattenedTransactions.map(({ transaction, nestingLevel }) => (
32+
<tr key={transaction.id}>
33+
<td className={cn('p-2 border-2')}>
34+
<div
35+
style={{
36+
marginLeft: `${graphConfig.indentationWidth * nestingLevel}px`,
37+
}}
38+
>
39+
{ellipseAddress(transaction.id)}
40+
</div>
41+
</td>
42+
<td className={cn('p-2 border-2 text-center')}>{ellipseAddress(transaction.sender)}</td>
43+
<td className={cn('p-2 border-2 text-center')}>{ellipseAddress(transaction.receiver)}</td>
44+
<td className={cn('p-2 border-2 text-center')}>{transaction.type}</td>
45+
<td className={cn('p-2 border-2')}>
46+
{transaction.type === TransactionType.Payment ? (
47+
<DisplayAlgo className={cn('justify-center')} amount={transaction.amount} />
48+
) : (
49+
'N/A'
50+
)}
51+
</td>
52+
</tr>
53+
))}
54+
</tbody>
55+
</table>
56+
)
57+
}

0 commit comments

Comments
 (0)