Transactions.tsx
1 import { useEffect, useState } from "react"; 2 import { Link } from "@tanstack/react-router"; 3 import { formatUnits } from "viem"; 4 5 import { Order } from "@massmarket/schema"; 6 import { CodecValue } from "@massmarket/utils/codec"; 7 8 import { OrderId, OrderState } from "../../types.ts"; 9 import { useStateManager } from "../../hooks/useStateManager.ts"; 10 import { useBaseToken } from "../../hooks/useBaseToken.ts"; 11 import { formatDate, OrderStateFromNumber } from "../../utils/helper.ts"; 12 13 export default function Transactions( 14 { displayFive }: { displayFive?: boolean }, 15 ) { 16 const { stateManager } = useStateManager(); 17 const { baseToken } = useBaseToken(); 18 19 const [filter, setFilter] = useState<string>("all"); 20 const [sort, setSort] = useState<string>("oldest"); 21 const [orders, setOrders] = useState<Map<OrderId, Order>>(new Map()); 22 23 function mapToOrderClass(orders: CodecValue) { 24 if (!(orders instanceof Map)) { 25 throw new Error("Orders is not a Map"); 26 } 27 const allOrders = new Map(); 28 for ( 29 const [id, o] of orders.entries() 30 ) { 31 allOrders.set(id, Order.fromCBOR(o)); 32 } 33 return allOrders; 34 } 35 36 useEffect(() => { 37 if (!stateManager) return; 38 39 function ordersEvent(res: CodecValue) { 40 const allOrders = mapToOrderClass(res); 41 setOrders(allOrders); 42 } 43 44 stateManager.get(["Orders"]).then( 45 (res: CodecValue | undefined) => { 46 if (!res) return; 47 const allOrders = mapToOrderClass(res); 48 setOrders(allOrders); 49 }, 50 ); 51 52 stateManager.events.on(ordersEvent, ["Orders"]); 53 54 return () => { 55 stateManager.events.off( 56 ordersEvent, 57 ["Orders"], 58 ); 59 }; 60 }, [stateManager]); 61 62 function renderTransactions() { 63 if (!orders.size) { 64 return ( 65 <div data-testid="no-transactions"> 66 <p>No transactions</p> 67 </div> 68 ); 69 } 70 const transactions = Array.from([...orders.entries()]) 71 .filter(([_, value]) => { 72 if (filter === "paid") { 73 return value.State === OrderState.Paid; 74 } 75 return true; 76 }).sort((a, b) => { 77 if (!a[1].PaymentDetails) return 1; 78 if (!b[1].PaymentDetails) return -1; 79 80 if (sort === "oldest") { 81 return a[1].PaymentDetails.TTL - b[1].PaymentDetails.TTL; 82 } 83 return b[1].PaymentDetails.TTL - a[1].PaymentDetails.TTL; 84 }); 85 86 const displayTransactions = displayFive 87 ? transactions.slice(0, 5) 88 : transactions; 89 90 return displayTransactions.map(([key, value]) => { 91 const ID = key; 92 let date = "-"; 93 let time = "-"; 94 let total = "-"; 95 if (value.PaymentDetails) { 96 const d = formatDate(value.PaymentDetails!.TTL).split(","); 97 date = d[0]; 98 time = d[1]; 99 100 total = `${ 101 formatUnits(BigInt(value.PaymentDetails.Total), baseToken.decimals) 102 } ${baseToken.symbol}`; 103 } 104 105 return ( 106 <Link 107 data-testid="transaction" 108 key={ID} 109 to="/order-details" 110 search={(prev: Record<string, string>) => ({ 111 shopId: prev.shopId, 112 orderId: ID, 113 })} 114 style={{ color: "black" }} 115 > 116 <div className=" p-3 grid grid-cols-5 text-center bg-white"> 117 <p data-testid={ID} className="truncate"> 118 {ID.toString().slice(0, 8)}... 119 </p> 120 <p className="truncate">{date}</p> 121 <p className="truncate">{time}</p> 122 <p className="truncate">{total}</p> 123 <p data-testid="status" className="truncate"> 124 {OrderStateFromNumber(value.State)} 125 </p> 126 </div> 127 </Link> 128 ); 129 }); 130 } 131 return ( 132 <section className="transactions-container"> 133 <section className="flex items-center gap-4"> 134 <div className="flex items-center gap-1"> 135 <p>Filter:</p> 136 <select 137 name="filter" 138 id="filter" 139 className="cursor-pointer" 140 value={filter} 141 onChange={(e) => setFilter(e.target.value)} 142 > 143 <option value="all">All</option> 144 <option value="paid">Paid</option> 145 </select> 146 </div> 147 <div className="flex items-center gap-1"> 148 <p>Sort:</p> 149 <select 150 name="date" 151 id="date" 152 className="cursor-pointer" 153 value={sort} 154 onChange={(e) => { 155 setSort(e.target.value); 156 }} 157 > 158 <option value="oldest">Oldest</option> 159 <option value="newest">Newest</option> 160 </select> 161 </div> 162 </section> 163 164 <div className="bg-primary-dark-green grid grid-cols-5 text-white text-sm p-4 rounded-t-xl mt-4 text-center"> 165 <p>Order ID</p> 166 <p>Date</p> 167 <p>Time</p> 168 <p>Value</p> 169 <p>Status</p> 170 </div> 171 {renderTransactions()} 172 </section> 173 ); 174 }