From 100f149c5376999529f492bceed3fd329d04ef5d Mon Sep 17 00:00:00 2001 From: Mike Johnston Date: Sun, 22 Mar 2026 01:20:34 -0400 Subject: [PATCH] easier fund management and navigations --- src/app/hashtag/[tag]/TradePanel.tsx | 55 ++++++++++++-- src/app/hashtag/[tag]/page.tsx | 11 +++ src/components/Navbar.tsx | 104 +++++++++++++++------------ 3 files changed, 120 insertions(+), 50 deletions(-) diff --git a/src/app/hashtag/[tag]/TradePanel.tsx b/src/app/hashtag/[tag]/TradePanel.tsx index ab54b7f..1dbbe9e 100644 --- a/src/app/hashtag/[tag]/TradePanel.tsx +++ b/src/app/hashtag/[tag]/TradePanel.tsx @@ -2,6 +2,7 @@ import { useState } from 'react' import { useRouter } from 'next/navigation' +import Link from 'next/link' import { formatCurrency, formatNumber } from '@/lib/utils' interface Props { @@ -11,18 +12,20 @@ interface Props { shortPosition: { shares: number; avgBuyPrice: number } | null fundId?: string fundName?: string + managedFunds?: { slug: string; name: string }[] maxPositionShares: number maxPositionValue: number } type Tab = 'BUY_LONG' | 'SELL_LONG' | 'BUY_SHORT' | 'SELL_SHORT' -export function TradePanel({ hashtag, balance, longPosition, shortPosition, fundId, fundName, maxPositionShares, maxPositionValue }: Props) { +export function TradePanel({ hashtag, balance, longPosition, shortPosition, fundId, fundName, managedFunds, maxPositionShares, maxPositionValue }: Props) { const router = useRouter() const [tab, setTab] = useState('BUY_LONG') const [shares, setShares] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState('') + const [showFundMenu, setShowFundMenu] = useState(false) const sharesNum = parseFloat(shares) || 0 const cost = sharesNum * hashtag.currentPrice @@ -65,13 +68,53 @@ export function TradePanel({ hashtag, balance, longPosition, shortPosition, fund return (
- {fundName && ( + {fundName ? (
- 🏦 - Trading as {fundName} - Fund mode + 🏦 + Trading as {fundName} + + Exit fund mode × +
- )} + ) : managedFunds && managedFunds.length === 1 ? ( + + 🏦 + Trade as {managedFunds[0].name} + + + ) : managedFunds && managedFunds.length > 1 ? ( +
+ + {showFundMenu && ( +
+ {managedFunds.map((f) => ( + + {f.name} + + + ))} +
+ )} +
+ ) : null}

Trade #{hashtag.displayTag}

diff --git a/src/app/hashtag/[tag]/page.tsx b/src/app/hashtag/[tag]/page.tsx index 11d946f..107ef5a 100644 --- a/src/app/hashtag/[tag]/page.tsx +++ b/src/app/hashtag/[tag]/page.tsx @@ -84,6 +84,16 @@ export default async function HashtagPage({ params, searchParams }: Props) { } } + // When not in fund mode, fetch funds this user manages for the fund-mode switcher + let managedFunds: { slug: string; name: string }[] = [] + if (session && !fundContext) { + const managerships = await prisma.fundManager.findMany({ + where: { userId: session.user.id }, + include: { fund: { select: { slug: true, name: true } } }, + }) + managedFunds = managerships.map((m) => ({ slug: m.fund.slug, name: m.fund.name })) + } + // Unknown hashtag — show research panel if (!hashtag || !hashtag.isActive) { return ( @@ -199,6 +209,7 @@ export default async function HashtagPage({ params, searchParams }: Props) { fundName={fundContext?.name} maxPositionShares={fundContext ? FUND_MAX_POSITION_SHARES : MAX_POSITION_SHARES} maxPositionValue={fundContext ? FUND_MAX_POSITION_VALUE : MAX_POSITION_VALUE} + managedFunds={managedFunds} /> ) : (
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 98c2cfe..3b3945a 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -3,30 +3,34 @@ import Link from 'next/link' import { useSession, signOut } from 'next-auth/react' import { TrendingUp, Search, User, LogOut, Shield, Trophy } from 'lucide-react' -import { useState, useRef, useEffect } from 'react' -import { useRouter } from 'next/navigation' +import { useState, useRef, useEffect, Suspense } from 'react' +import { useRouter, useSearchParams } from 'next/navigation' import { formatCurrency } from '@/lib/utils' import { normalizeTag } from '@/lib/utils' type Suggestion = { tag: string; displayTag: string; currentPrice: number } -export function Navbar() { - const { data: session } = useSession() +function NavSearchInner() { const router = useRouter() + const searchParams = useSearchParams() + const fundSlug = searchParams.get('fund') const [query, setQuery] = useState('') const [suggestions, setSuggestions] = useState([]) const [showSuggestions, setShowSuggestions] = useState(false) const debounceRef = useRef | null>(null) + function navigate(tag: string) { + const url = fundSlug ? `/hashtag/${tag}?fund=${encodeURIComponent(fundSlug)}` : `/hashtag/${tag}` + router.push(url) + setQuery('') + setSuggestions([]) + setShowSuggestions(false) + } + function handleSearch(e: React.FormEvent) { e.preventDefault() const tag = normalizeTag(query) - if (tag) { - router.push(`/hashtag/${tag}`) - setQuery('') - setSuggestions([]) - setShowSuggestions(false) - } + if (tag) navigate(tag) } function handleQueryChange(e: React.ChangeEvent) { @@ -51,6 +55,42 @@ export function Navbar() { }, 300) } + return ( +
+
+ + setTimeout(() => setShowSuggestions(false), 150)} + onFocus={() => suggestions.length > 0 && setShowSuggestions(true)} + placeholder={fundSlug ? `#hashtag (as ${fundSlug})` : '#hashtag'} + className="w-full bg-surface border border-surface-border rounded-lg pl-9 pr-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" + /> + {showSuggestions && suggestions.length > 0 && ( +
+ {suggestions.map((s) => ( + + ))} +
+ )} +
+
+ ) +} + +export function Navbar() { + const { data: session } = useSession() + return (