diff --git a/app/src/app/dashboard/page.tsx b/app/src/app/dashboard/page.tsx new file mode 100644 index 0000000..9028383 --- /dev/null +++ b/app/src/app/dashboard/page.tsx @@ -0,0 +1,5 @@ +import { Dashboard } from '@/components/Dashboard' + +export default function DashboardPage() { + return +} diff --git a/app/src/components/Dashboard.tsx b/app/src/components/Dashboard.tsx new file mode 100644 index 0000000..f1f1000 --- /dev/null +++ b/app/src/components/Dashboard.tsx @@ -0,0 +1,280 @@ +'use client' + +import { useState } from 'react' +import { useRouter } from 'next/navigation' +import { useWallet } from '@solana/connector/react' +import { useMyListings } from '@/hooks/useMyListings' +import { useMyOrders } from '@/hooks/useMyOrders' +import { Badge, STATUS_COLORS } from '@/components/ui/Badge' +import { Button } from '@/components/ui/Button' +import { abbrev, fmtSol } from '@/lib/format' + +export function Dashboard() { + const router = useRouter() + const { account } = useWallet() + const [tab, setTab] = useState<'listings' | 'orders'>('listings') + const { data: myListings = [], isLoading: loadingL } = useMyListings() + const { data: myOrders = [], isLoading: loadingO } = useMyOrders() + + if (!account) { + return ( +
+
+
+
+

Wallet required

+

+ Connect a wallet to view your listings and orders. +

+
+ ) + } + + return ( +
+
+
+

+ My Dashboard +

+

+ {abbrev(account)} +

+
+ +
+ +
+ {( + [ + { key: 'listings' as const, label: `My Listings · ${myListings.length}` }, + { key: 'orders' as const, label: `My Orders · ${myOrders.length}` }, + ] as const + ).map((t) => ( + + ))} +
+ + {tab === 'listings' && ( +
+ {loadingL && ( +
Loading…
+ )} + {!loadingL && myListings.length === 0 && ( +
+ You have no listings yet. +
+ )} +
+ {myListings.map((l) => { + const sc = STATUS_COLORS[l.data.isActive ? 'active' : 'inactive'] + const avail = l.data.quantity - l.data.quantityReserved + return ( +
+
router.push(`/listing/${l.address}`)} + style={{ flex: 1, minWidth: 0, cursor: 'pointer' }} + > +
+ + {l.data.metadataUri || abbrev(l.address)} + + + {l.data.isActive ? 'Active' : 'Inactive'} + +
+
+ #{String(l.data.listingId)} · {fmtSol(l.data.price)} · {avail}/ + {String(l.data.quantity)} avail · {String(l.data.quantityReserved)} pending +
+
+
+ + +
+
+ ) + })} +
+
+ )} + + {tab === 'orders' && ( +
+ {loadingO && ( +
Loading…
+ )} + {!loadingO && myOrders.length === 0 && ( +
+ You have no orders as a buyer. +
+ )} +
+ {myOrders.map((o) => ( +
router.push(`/order/${o.address}`)} + style={{ + background: 'var(--bg2)', + border: '1px solid var(--bd)', + borderRadius: 'var(--radius)', + padding: '18px 20px', + display: 'flex', + alignItems: 'center', + gap: 18, + cursor: 'pointer', + }} + > +
+
{abbrev(o.data.listing)}
+
+ #{String(o.data.orderId)} · {fmtSol(o.data.amount)} +
+
+ + #{String(o.data.escrowId)} + +
+ ))} +
+
+ )} +
+ ) +} diff --git a/app/src/hooks/useMyListings.ts b/app/src/hooks/useMyListings.ts new file mode 100644 index 0000000..48ff261 --- /dev/null +++ b/app/src/hooks/useMyListings.ts @@ -0,0 +1,8 @@ +import { useWallet } from '@solana/connector/react' +import { useListings } from './useListings' + +export function useMyListings() { + const { account } = useWallet() + const { data: all = [], ...rest } = useListings() + return { data: all.filter((l) => l.data.seller === account), ...rest } +} diff --git a/app/src/hooks/useMyOrders.ts b/app/src/hooks/useMyOrders.ts new file mode 100644 index 0000000..232636f --- /dev/null +++ b/app/src/hooks/useMyOrders.ts @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query' +import { useCluster, useWallet } from '@solana/connector/react' +import { createSolanaRpc } from '@solana/kit' +import { fetchOrdersByBuyer } from '@solisting/sdk' +import type { Address } from '@solisting/sdk' + +export function useMyOrders() { + const { account } = useWallet() + const { cluster } = useCluster() + return useQuery({ + queryKey: ['myOrders', account, cluster?.id], + queryFn: () => { + const rpc = createSolanaRpc(cluster!.url) + return fetchOrdersByBuyer(rpc, account as Address) + }, + enabled: !!account && !!cluster, + refetchInterval: 30_000, + }) +}