feat: add managed funds section to user profile and enhance trade display for fund-related transactions
Build Images and Deploy / Update-PROD-Stack (push) Failing after 34s

This commit is contained in:
2026-03-21 01:48:45 -04:00
parent 9c3312ed75
commit 468b8b6677
3 changed files with 74 additions and 45 deletions
+6
View File
@@ -183,6 +183,12 @@ export default async function HomePage() {
<h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
<TrendingUp className="h-5 w-5 text-indigo-400" />
Your top positions
<Link
href="/positions"
className="ml-auto text-sm font-normal text-indigo-400 hover:text-indigo-300"
>
View all
</Link>
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{holdings.biggestGain && (
+31 -31
View File
@@ -187,6 +187,37 @@ export default async function ProfilePage({ params }: Props) {
</div>
)}
{/* Funds managed — only shown to the profile owner */}
{isOwn && user.managedFunds.length > 0 && (
<section>
<h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
<Building2 className="h-5 w-5 text-indigo-400" />
Funds you manage
</h2>
<div className="bg-surface-card border border-surface-border rounded-xl overflow-hidden">
<div className="divide-y divide-surface-border">
{user.managedFunds.map(({ fund }) => (
<div key={fund.id} className="flex items-center justify-between px-4 py-3">
<Link
href={`/fund/${fund.slug}`}
className="font-medium hover:text-indigo-300 transition-colors flex items-center gap-2"
>
<Building2 className="h-3.5 w-3.5 text-indigo-400 shrink-0" />
{fund.name}
</Link>
<Link
href={`/stocks?fund=${fund.slug}`}
className="text-xs text-indigo-400 hover:text-indigo-300 transition-colors"
>
Trade as this fund
</Link>
</div>
))}
</div>
</div>
</section>
)}
{/* Positions */}
{user.positions.length > 0 && (
<section>
@@ -240,37 +271,6 @@ export default async function ProfilePage({ params }: Props) {
</section>
)}
{/* Funds managed — only shown to the profile owner */}
{isOwn && user.managedFunds.length > 0 && (
<section>
<h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
<Building2 className="h-5 w-5 text-indigo-400" />
Funds you manage
</h2>
<div className="bg-surface-card border border-surface-border rounded-xl overflow-hidden">
<div className="divide-y divide-surface-border">
{user.managedFunds.map(({ fund }) => (
<div key={fund.id} className="flex items-center justify-between px-4 py-3">
<Link
href={`/fund/${fund.slug}`}
className="font-medium hover:text-indigo-300 transition-colors flex items-center gap-2"
>
<Building2 className="h-3.5 w-3.5 text-indigo-400 shrink-0" />
{fund.name}
</Link>
<Link
href={`/stocks?fund=${fund.slug}`}
className="text-xs text-indigo-400 hover:text-indigo-300 transition-colors"
>
Trade as this fund
</Link>
</div>
))}
</div>
</div>
</section>
)}
{/* Trade history */}
{user.trades.length > 0 && (
<section>
+37 -14
View File
@@ -15,15 +15,23 @@ interface PageProps {
export default async function GlobalTradesPage({ searchParams }: PageProps) {
const page = Math.max(1, parseInt(searchParams.page ?? '1', 10))
const tradeWhere = {
OR: [
{ hashtagId: { not: null }, type: { not: 'LOTTERY_WIN' } },
{ type: { in: ['FUND_INVEST', 'FUND_REDEEM'] } },
],
} as const
const [total, trades] = await Promise.all([
prisma.trade.count({ where: { hashtagId: { not: null }, type: { not: 'LOTTERY_WIN' } } }),
prisma.trade.count({ where: tradeWhere }),
prisma.trade.findMany({
where: { hashtagId: { not: null }, type: { not: 'LOTTERY_WIN' } },
where: tradeWhere,
orderBy: { createdAt: 'desc' },
take: PAGE_SIZE,
skip: (page - 1) * PAGE_SIZE,
include: {
hashtag: { select: { tag: true, displayTag: true } },
fund: { select: { name: true, slug: true } },
user: { select: { username: true, displayUsername: true, isFund: true } },
},
}),
@@ -49,19 +57,34 @@ export default async function GlobalTradesPage({ searchParams }: PageProps) {
className={`text-xs font-medium px-2 py-0.5 rounded shrink-0 ${
(t.type === 'LIQUIDATE_LONG' || t.type === 'LIQUIDATE_SHORT')
? 'bg-orange-500/15 text-orange-400'
: t.type.startsWith('BUY')
? 'bg-emerald-500/15 text-emerald-400'
: 'bg-red-500/15 text-red-400'
: (t.type === 'FUND_INVEST' || t.type === 'FUND_REDEEM')
? 'bg-indigo-500/15 text-indigo-400'
: t.type.startsWith('BUY')
? 'bg-emerald-500/15 text-emerald-400'
: 'bg-red-500/15 text-red-400'
}`}
>
{(t.type === 'LIQUIDATE_LONG' || t.type === 'LIQUIDATE_SHORT') ? 'LIQUIDATED' : t.type.replace('_', ' ')}
{(t.type === 'LIQUIDATE_LONG' || t.type === 'LIQUIDATE_SHORT') ? 'LIQUIDATED' : t.type.replace(/_/g, ' ')}
</span>
<Link
href={`/hashtag/${t.hashtag!.tag}`}
className="text-indigo-300 hover:text-indigo-200 font-medium truncate flex-1 min-w-0"
>
#{t.hashtag!.displayTag}
</Link>
{(t.type === 'FUND_INVEST' || t.type === 'FUND_REDEEM') ? (
t.fund ? (
<Link
href={`/fund/${t.fund.slug}`}
className="text-indigo-300 hover:text-indigo-200 font-medium truncate flex-1 min-w-0"
>
{t.fund.name}
</Link>
) : (
<span className="text-slate-500 font-medium flex-1 min-w-0">Deleted Fund</span>
)
) : (
<Link
href={`/hashtag/${t.hashtag!.tag}`}
className="text-indigo-300 hover:text-indigo-200 font-medium truncate flex-1 min-w-0"
>
#{t.hashtag!.displayTag}
</Link>
)}
<span className="shrink-0 font-medium tabular-nums">{formatCurrency(t.total)}</span>
</div>
{/* Secondary row: user · time (left) shares @ price (right) */}
@@ -79,8 +102,8 @@ export default async function GlobalTradesPage({ searchParams }: PageProps) {
</div>
<span className="shrink-0 tabular-nums ml-3">{formatNumber(t.shares)} sh @ {formatCurrency(t.price)}</span>
</div>
{/* PnL: sell and liquidation trades */}
{(t.type === 'SELL_LONG' || t.type === 'SELL_SHORT' || t.type === 'LIQUIDATE_LONG' || t.type === 'LIQUIDATE_SHORT') && (
{/* PnL: sell, liquidation, and fund redeem trades */}
{(t.type === 'SELL_LONG' || t.type === 'SELL_SHORT' || t.type === 'LIQUIDATE_LONG' || t.type === 'LIQUIDATE_SHORT' || t.type === 'FUND_REDEEM') && (
<div className={`text-xs text-right ${pnlColor(t.profit)}`}>{formatPnl(t.profit)}</div>
)}
</div>