on df0c310 help sign in
bridge/src/views/Transactions/TransactionHistory.tsx
186 lines 6.1 kB history raw
1import React, { useCallback, useEffect, useMemo, useState } from 'react';
3import * as need from 'lib/need';
4import * as ob from 'urbit-ob';
5import { useWallet } from 'store/wallet';
6import useRoller from 'lib/useRoller';
7import { useLocalRouter } from 'lib/LocalRouter';
9import View from 'components/View';
10import L2BackHeader from 'components/L2/Headers/L2BackHeader';
11import Window from 'components/L2/Window/Window';
12import HeaderPane from 'components/L2/Window/HeaderPane';
13import BodyPane from 'components/L2/Window/BodyPane';
14import { RollerTransaction } from '@urbit/roller-api';
16import './TransactionHistory.scss';
17import { TransactionRow } from './TransactionRow';
18import { Box, LoadingSpinner, Row } from '@tlon/indigo-react';
19import { PatpRow } from './PatpRow';
20import Dropdown from 'components/L2/Dropdowns/Dropdown';
21import Sigil from 'components/Sigil';
22import { AddressRow } from './AddressRow';
24interface GroupedTransactions {
25 [ship: string]: RollerTransaction[];
28const TransactionHistory = () => {
29 const { pop, data }: any = useLocalRouter();
30 const { api } = useRoller();
31 const { wallet }: any = useWallet();
32 const address = need.addressFromWallet(wallet);
33 const [loading, setLoading] = useState(true);
34 const [transactions, setTransactions] = useState<RollerTransaction[]>([]);
35 const [dropdownOpen, setDropdownOpen] = useState<boolean>(false);
36 const [selectedPatp, setSelectedPatp] = useState<string | null>();
38 // Pre-filter the results if navigating from Point view
39 const filterByPoint = useMemo(() => {
40 return data?.filterByPoint.matchWith({
41 Nothing: () => null,
42 Just: (p: any) => ob.patp(p.value),
43 });
44 }, [data]);
46 const shortAddress = useMemo(() => {
47 return address.slice(0, 6);
48 }, [address]);
50 const fetchTransactions = useCallback(async () => {
51 const txns = await api.getHistory(address);
52 setTransactions(txns);
53 if (filterByPoint) {
54 setSelectedPatp(filterByPoint.slice(1));
55 }
56 setLoading(false);
57 }, [address, api, filterByPoint]);
59 const selectPatp = useCallback(patp => {
60 setSelectedPatp(patp);
61 setDropdownOpen(false);
62 }, []);
64 const txnsByPatp = useMemo(() => {
65 return transactions.reduce((memo, tx) => {
66 const patp = tx.ship;
67 if (patp && Object.keys(memo).includes(patp as string)) {
68 memo[patp].push(tx);
69 } else {
70 memo[patp || '~'] = [tx];
71 }
72 return memo;
73 }, {} as GroupedTransactions);
74 }, [transactions]);
76 const txPatps = useMemo(() => {
77 return Object.keys(txnsByPatp).sort((a, b) => {
78 return ob.patp2dec(`~${a}`) - ob.patp2dec(`~${b}`);
79 });
80 }, [txnsByPatp]);
82 const filteredPatps = useMemo(() => {
83 if (!selectedPatp) {
84 return txPatps;
85 }
87 return txPatps.filter(p => p === selectedPatp);
88 }, [selectedPatp, txPatps]);
90 useEffect(() => {
91 fetchTransactions();
92 // Load the TX history only once
93 }, []); // eslint-disable-line react-hooks/exhaustive-deps
95 return (
96 <View
97 pop={pop}
98 hideBack
99 inset
100 className="transaction-history"
101 header={<L2BackHeader />}>
102 <Window>
103 <>
104 <HeaderPane>
105 <Box className="transaction-header">
106 <h5>Transactions</h5>
107 <Dropdown
108 className="transaction-dropdown"
109 open={dropdownOpen}
110 value={selectedPatp ? selectedPatp : shortAddress}
111 toggleOpen={() => setDropdownOpen(!dropdownOpen)}>
112 <Box className="divider" />
113 <Box className="points">
114 <Row className="entry mono" onClick={() => selectPatp(null)}>
115 <Box>{shortAddress}</Box>
116 <Row>
117 <Box
118 background="black"
119 borderRadius="2px"
120 width="24px"
121 height="24px"></Box>
122 </Row>
123 </Row>
124 {txPatps.map(patp => {
125 return (
126 <Row
127 className="entry mono"
128 onClick={() => selectPatp(patp)}
129 key={patp}>
130 <Box>~{patp}</Box>
131 <Row>
132 <Box className="sigil">
133 <Sigil
134 icon
135 patp={patp}
136 size={1}
137 colors={['#000', '#FFF']}
138 />
139 </Box>
140 </Row>
141 </Row>
142 );
143 })}
144 </Box>
145 </Dropdown>
146 </Box>
147 </HeaderPane>
148 <BodyPane>
149 {loading ? (
150 <Box className={'loading'}>
151 <LoadingSpinner
152 foreground="rgba(0,0,0,0.3)"
153 background="white"
154 />
155 </Box>
156 ) : (
157 <Box className="transaction-container">
158 {!selectedPatp && <AddressRow address={address} />}
159 {filteredPatps && filteredPatps.length > 0 ? (
160 filteredPatps.map(patp => {
161 return (
162 <Box key={patp}>
163 <PatpRow patp={patp} />
164 {txnsByPatp[patp]
165 .sort((a, b) => {
166 return (b?.time || 0) - (a?.time || 0);
167 })
168 .map(tx => (
169 <TransactionRow key={tx.time} {...tx} />
170 ))}
171 </Box>
172 );
173 })
174 ) : (
175 <Box className="no-results">No transactions yet</Box>
176 )}
177 </Box>
178 )}
179 </BodyPane>
180 </>
181 </Window>
182 </View>
183 );
184};
186export default TransactionHistory;