From d11e2e7b9adffaa97205cb2f6ec27433b20b09a8 Mon Sep 17 00:00:00 2001 From: Mike Johnston Date: Wed, 18 Mar 2026 19:05:09 -0400 Subject: [PATCH] feat: add trade history page and update profile links --- src/app/history/page.tsx | 141 ++++++++++++++++++++++++++++ src/app/page.tsx | 18 ++-- src/app/profile/[username]/page.tsx | 8 ++ src/middleware.ts | 1 + 4 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 src/app/history/page.tsx diff --git a/src/app/history/page.tsx b/src/app/history/page.tsx new file mode 100644 index 0000000..586ca81 --- /dev/null +++ b/src/app/history/page.tsx @@ -0,0 +1,141 @@ +import { prisma } from '@/lib/prisma' +import { getServerSession } from 'next-auth' +import { authOptions } from '@/lib/auth' +import { redirect } from 'next/navigation' +import { formatCurrency, formatNumber, formatPnl, pnlColor } from '@/lib/utils' +import Link from 'next/link' +import { TrendingUp } from 'lucide-react' +import { formatDistanceToNow } from 'date-fns' + +export const dynamic = 'force-dynamic' + +const PAGE_SIZE = 50 + +interface PageProps { + searchParams: { page?: string } +} + +export default async function TradeHistoryPage({ searchParams }: PageProps) { + const session = await getServerSession(authOptions) + if (!session) redirect('/auth/signin') + + const page = Math.max(1, parseInt(searchParams.page ?? '1', 10)) + + const [total, trades] = await Promise.all([ + prisma.trade.count({ where: { userId: session.user.id } }), + prisma.trade.findMany({ + where: { userId: session.user.id }, + orderBy: { createdAt: 'desc' }, + take: PAGE_SIZE, + skip: (page - 1) * PAGE_SIZE, + include: { + hashtag: { select: { tag: true, displayTag: true } }, + }, + }), + ]) + + const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)) + + return ( +
+ {/* Header */} +
+ +

Trade History

+ ({total} total) +
+ + {trades.length === 0 ? ( +
+ +

No trades yet.

+ + Browse trending hashtags → + +
+ ) : ( + <> +
+
+ {trades.map((t) => { + const isLottery = t.type === 'LOTTERY_WIN' + return ( +
+
+ + {t.type.replace(/_/g, ' ')} + +
+ {isLottery ? ( + Lucky Dip + ) : ( + + #{t.hashtag!.displayTag} + + )} +

+ {formatDistanceToNow(t.createdAt, { addSuffix: true })} +

+
+
+
+ {isLottery ? ( +

{formatCurrency(t.profit)}

+ ) : ( + <> +

{formatNumber(t.shares)} sh @ {formatCurrency(t.price)}

+

{formatCurrency(t.total)}

+ {(t.type === 'SELL_LONG' || t.type === 'SELL_SHORT') && ( +

+ {formatPnl(t.profit)} +

+ )} + + )} +
+
+ ) + })} +
+
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ {page > 1 && ( + + ← Prev + + )} + + Page {page} of {totalPages} + + {page < totalPages && ( + + Next → + + )} +
+ )} + + )} +
+ ) +} diff --git a/src/app/page.tsx b/src/app/page.tsx index b6ec65f..28e8839 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -91,18 +91,6 @@ export default async function HomePage() { > Lucky Dip - - Markets - - - My Positions - ) : ( <> @@ -172,6 +160,12 @@ export default async function HomePage() {

Trending now + + Full market → +

{topHashtags.map((h) => { diff --git a/src/app/profile/[username]/page.tsx b/src/app/profile/[username]/page.tsx index e7d2354..c55a7eb 100644 --- a/src/app/profile/[username]/page.tsx +++ b/src/app/profile/[username]/page.tsx @@ -164,6 +164,14 @@ export default async function ProfilePage({ params }: Props) { )} Trade history + {isOwn && ( + + View all → + + )}
diff --git a/src/middleware.ts b/src/middleware.ts index 809f8ef..ecd9e98 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -4,6 +4,7 @@ export const config = { matcher: [ '/profile/:path*', '/positions', + '/history', '/admin/:path*', '/api/trade/:path*', '/api/research/:path*',