@@ -4,9 +4,10 @@ import { useMemo, useState, useEffect } from "react";
44import { usePathname , useRouter , useSearchParams } from "next/navigation" ;
55import { Order , OrderSide , OrderStatus } from "@/types" ;
66import { Badge , Button , Modal } from "@/components/ui" ;
7- import { ArrowUpDown , Filter , Search , Zap , Eye , X } from "lucide-react" ;
7+ import { Search , Zap , X } from "lucide-react" ;
88import { clsx } from "clsx" ;
99import { useUnifiedWallet } from "@/components/wallet/UnifiedWalletProvider" ;
10+ import { OrderListTable , OrderSortKey } from "@/components/marketplace/OrderListTable" ;
1011
1112interface OrderBookListProps {
1213 orders : Order [ ] ;
@@ -95,7 +96,7 @@ export function OrderBookList({ orders, onTakeOrder }: OrderBookListProps) {
9596 const [ assetFilter , setAssetFilter ] = useState ( ( ) => searchParams . get ( "asset" ) ?? "all" ) ;
9697 const [ detailsOrder , setDetailsOrder ] = useState < Order | null > ( null ) ;
9798 const [ sortConfig , setSortConfig ] = useState < {
98- key : "price" | "amount" | "timestamp" ;
99+ key : OrderSortKey ;
99100 direction : "asc" | "desc" ;
100101 } > ( { key : "timestamp" , direction : "desc" } ) ;
101102
@@ -140,10 +141,6 @@ export function OrderBookList({ orders, onTakeOrder }: OrderBookListProps) {
140141 setAssetFilter ( searchParams . get ( "asset" ) ?? "all" ) ;
141142 } , [ searchParams ] ) ;
142143
143- function parseNumericValue ( value : string ) {
144- return Number ( value . replace ( / , / g, "" ) ) || 0 ;
145- }
146-
147144 const filteredOrders = useMemo ( ( ) => {
148145 return orders
149146 . filter ( ( o ) => {
@@ -166,32 +163,14 @@ export function OrderBookList({ orders, onTakeOrder }: OrderBookListProps) {
166163 o . status === OrderStatus . OPEN &&
167164 notExpired
168165 ) ;
169- } )
170- . sort ( ( a , b ) => {
171- const direction = sortConfig . direction === "asc" ? 1 : - 1 ;
172-
173- if ( sortConfig . key === "timestamp" ) {
174- return (
175- ( new Date ( a . timestamp ) . getTime ( ) - new Date ( b . timestamp ) . getTime ( ) ) * direction
176- ) ;
177- }
178-
179- if ( sortConfig . key === "price" ) {
180- return ( parseNumericValue ( a . price ) - parseNumericValue ( b . price ) ) * direction ;
181- }
182-
183- return ( parseNumericValue ( a . amount ) - parseNumericValue ( b . amount ) ) * direction ;
184166 } ) ;
185- } , [ assetFilter , chainPairFilter , orders , search , sideFilter , sortConfig ] ) ;
186-
187- const handleSort = ( key : "price" | "amount" | "timestamp" ) => {
188- let direction : "asc" | "desc" = "desc" ;
189- if ( sortConfig . key === key && sortConfig . direction === "desc" ) {
190- direction = "asc" ;
191- } else if ( sortConfig . key === key && sortConfig . direction === "asc" ) {
192- direction = "desc" ;
193- }
194- setSortConfig ( { key, direction } ) ;
167+ } , [ assetFilter , chainPairFilter , orders , search , sideFilter ] ) ;
168+
169+ const handleSort = ( key : OrderSortKey ) => {
170+ setSortConfig ( ( current ) => {
171+ if ( current . key !== key ) return { key, direction : "desc" } ;
172+ return { key, direction : current . direction === "desc" ? "asc" : "desc" } ;
173+ } ) ;
195174 } ;
196175
197176 const resetFilters = ( ) => {
@@ -241,6 +220,7 @@ export function OrderBookList({ orders, onTakeOrder }: OrderBookListProps) {
241220 Sells
242221 </ Button >
243222 < select
223+ aria-label = "Filter by chain route"
244224 value = { chainPairFilter }
245225 onChange = { ( event ) => setChainPairFilter ( event . target . value ) }
246226 className = "h-8 rounded-xl border border-border bg-surface-raised px-3 text-xs text-text-primary"
@@ -253,6 +233,7 @@ export function OrderBookList({ orders, onTakeOrder }: OrderBookListProps) {
253233 ) ) }
254234 </ select >
255235 < select
236+ aria-label = "Filter by asset"
256237 value = { assetFilter }
257238 onChange = { ( event ) => setAssetFilter ( event . target . value ) }
258239 className = "h-8 rounded-xl border border-border bg-surface-raised px-3 text-xs text-text-primary"
@@ -278,130 +259,18 @@ export function OrderBookList({ orders, onTakeOrder }: OrderBookListProps) {
278259 </ div >
279260 </ div >
280261
281- < div className = "rounded-2xl border border-border bg-background/50 overflow-hidden backdrop-blur-sm shadow-xl" >
282- < div className = "overflow-x-auto" >
283- < table className = "w-full text-left border-collapse" >
284- < thead >
285- < tr className = "bg-surface-overlay/50 border-b border-border" >
286- < th className = "px-6 py-4 text-xs font-bold uppercase tracking-wider text-text-muted" >
287- Pair
288- </ th >
289- < th className = "px-6 py-4 text-xs font-bold uppercase tracking-wider text-text-muted" >
290- Side
291- </ th >
292- < th
293- className = "px-6 py-4 text-xs font-bold uppercase tracking-wider text-text-muted cursor-pointer hover:text-text-primary"
294- onClick = { ( ) => handleSort ( "amount" ) }
295- >
296- < div className = "flex items-center gap-2" >
297- Amount < ArrowUpDown size = { 12 } />
298- </ div >
299- </ th >
300- < th
301- className = "px-6 py-4 text-xs font-bold uppercase tracking-wider text-text-muted cursor-pointer hover:text-text-primary"
302- onClick = { ( ) => handleSort ( "price" ) }
303- >
304- < div className = "flex items-center gap-2" >
305- Price < ArrowUpDown size = { 12 } />
306- </ div >
307- </ th >
308- < th
309- className = "px-6 py-4 text-xs font-bold uppercase tracking-wider text-text-muted cursor-pointer hover:text-text-primary"
310- onClick = { ( ) => handleSort ( "timestamp" ) }
311- >
312- < div className = "flex items-center gap-2" >
313- Created < ArrowUpDown size = { 12 } />
314- </ div >
315- </ th >
316- < th className = "px-6 py-4 text-xs font-bold uppercase tracking-wider text-text-muted" >
317- Total
318- </ th >
319- < th className = "px-4 py-4" > </ th >
320- </ tr >
321- </ thead >
322- < tbody className = "divide-y divide-border/50" >
323- { filteredOrders . length > 0 ? (
324- filteredOrders . map ( ( order ) => (
325- < tr
326- key = { order . id }
327- className = "group hover:bg-surface-overlay/30 transition-colors"
328- >
329- < td className = "px-6 py-4 whitespace-nowrap" >
330- < div className = "flex flex-col" >
331- < span className = "font-bold text-text-primary" > { order . pair } </ span >
332- < span className = "text-[10px] text-text-muted uppercase tracking-tighter" >
333- { order . chainIn } ↔ { order . chainOut }
334- </ span >
335- </ div >
336- </ td >
337- < td className = "px-6 py-4 whitespace-nowrap" >
338- < Badge variant = { order . side === OrderSide . BUY ? "success" : "error" } >
339- { order . side . toUpperCase ( ) }
340- </ Badge >
341- </ td >
342- < td className = "px-6 py-4 whitespace-nowrap font-mono text-sm text-text-primary" >
343- { order . amount } { order . tokenIn }
344- </ td >
345- < td className = "px-6 py-4 whitespace-nowrap font-mono text-sm text-text-secondary" >
346- { order . price }
347- </ td >
348- < td className = "px-6 py-4 whitespace-nowrap text-xs text-text-secondary" >
349- { new Date ( order . timestamp ) . toLocaleString ( [ ] , {
350- dateStyle : "short" ,
351- timeStyle : "short" ,
352- } ) }
353- </ td >
354- < td className = "px-6 py-4 whitespace-nowrap font-mono text-sm font-bold text-text-primary" >
355- { order . total } { order . tokenOut }
356- </ td >
357- < td className = "px-4 py-4 text-right" >
358- < div className = "flex justify-end gap-2" >
359- < Button
360- variant = "secondary"
361- size = "sm"
362- onClick = { ( ) => setDetailsOrder ( order ) }
363- icon = { < Eye size = { 14 } /> }
364- >
365- Details
366- </ Button >
367- < Button
368- variant = "primary"
369- size = "sm"
370- className = "shadow-glow-sm hover:shadow-glow-md"
371- onClick = { ( ) => onTakeOrder ( order ) }
372- icon = { < Zap size = { 14 } /> }
373- >
374- Take
375- </ Button >
376- </ div >
377- </ td >
378- </ tr >
379- ) )
380- ) : (
381- < tr >
382- < td colSpan = { 7 } className = "px-6 py-20 text-center" >
383- < div className = "flex flex-col items-center gap-3" >
384- < div className = "h-12 w-12 rounded-full bg-surface-overlay flex items-center justify-center border border-border" >
385- < Filter className = "text-text-muted" />
386- </ div >
387- < p className = "text-text-secondary font-medium" >
388- No active orders matching filters.
389- </ p >
390- < Button
391- variant = "ghost"
392- size = "sm"
393- onClick = { resetFilters }
394- >
395- Clear Filters
396- </ Button >
397- </ div >
398- </ td >
399- </ tr >
400- ) }
401- </ tbody >
402- </ table >
403- </ div >
404- </ div >
262+ < OrderListTable
263+ orders = { filteredOrders }
264+ sortKey = { sortConfig . key }
265+ sortDirection = { sortConfig . direction }
266+ onSort = { handleSort }
267+ onTakeOrder = { onTakeOrder }
268+ onViewDetails = { setDetailsOrder }
269+ onClearFilters = { hasActiveFilters ? resetFilters : undefined }
270+ emptyTitle = "No active orders matching filters."
271+ emptyDescription = "Adjust filters or clear them to browse all open orders."
272+ pageSize = { 8 }
273+ />
405274
406275 < OrderDetailsModal
407276 order = { detailsOrder }
0 commit comments