Compare commits

..

2 Commits

Author SHA1 Message Date
ThaMunsta 1cf7892c8b feat: enhance positions table layout for better responsiveness
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m22s
2026-03-19 19:06:33 -04:00
ThaMunsta e1e6790628 feat: add zombie warning for auto-liquidation risk based on inactivity 2026-03-19 19:05:57 -04:00
2 changed files with 42 additions and 13 deletions
+28 -1
View File
@@ -6,10 +6,13 @@ import { formatCurrency, formatNumber } from '@/lib/utils'
import { PriceChart } from '@/components/PriceChart'
import { TradePanel } from './TradePanel'
import { ResearchPanel } from './ResearchPanel'
import { Hash, Clock, Link as LinkIcon } from 'lucide-react'
import { Hash, Clock, Link as LinkIcon, AlertTriangle } from 'lucide-react'
import { formatDistanceToNow } from 'date-fns'
import Link from 'next/link'
const ZOMBIE_ZERO_COUNT = parseInt(process.env.ZOMBIE_ZERO_COUNT ?? '1000', 10)
const PRICE_UPDATE_INTERVAL_MINUTES = parseInt(process.env.PRICE_UPDATE_INTERVAL_MINUTES ?? '60', 10)
export const dynamic = 'force-dynamic'
interface Props {
@@ -150,6 +153,30 @@ export default async function HashtagPage({ params, searchParams }: Props) {
/>
</div>
{/* Zombie warning — only shown when signed in, holding a position, and threshold reached */}
{session && (activeLong || activeShort) && hashtag.zeroCount >= ZOMBIE_ZERO_COUNT * 0.9 && (() => {
const zombieSince = formatDistanceToNow(
new Date(Date.now() - hashtag.zeroCount * PRICE_UPDATE_INTERVAL_MINUTES * 60 * 1000),
)
const zombieEta = formatDistanceToNow(
new Date(Date.now() + (ZOMBIE_ZERO_COUNT - hashtag.zeroCount) * PRICE_UPDATE_INTERVAL_MINUTES * 60 * 1000),
{ addSuffix: true },
)
return (
<div className="bg-amber-500/5 border border-amber-500/25 rounded-xl p-4 flex items-start gap-3">
<AlertTriangle className="h-5 w-5 text-amber-400 shrink-0 mt-0.5" />
<div>
<p className="text-sm font-medium text-amber-400">Auto-liquidation risk</p>
<p className="text-sm text-slate-400 mt-0.5">
No activity detected for <span className="text-slate-200">{zombieSince}</span>.
If this continues, your position will be force-closed{' '}
<span className="text-slate-200">{zombieEta}</span>.
</p>
</div>
</div>
)
})()}
{/* Trade panel or sign-in prompt */}
{session ? (
<TradePanel
+14 -12
View File
@@ -78,13 +78,13 @@ export default async function PositionsPage() {
) : (
<div className="bg-surface-card border border-surface-border rounded-xl overflow-hidden">
{/* Header row */}
<div className="grid grid-cols-[1fr_auto_auto_auto_auto_auto_auto] gap-4 px-4 py-2 text-xs text-slate-500 uppercase tracking-wider border-b border-surface-border">
<div className="grid grid-cols-[1fr_auto_auto] sm:grid-cols-[1fr_auto_auto_auto_auto_auto_auto] gap-4 px-4 py-2 text-xs text-slate-500 uppercase tracking-wider border-b border-surface-border">
<span>Hashtag</span>
<span className="text-right">Shares</span>
<span className="text-right">Avg buy</span>
<span className="text-right">Current</span>
<span className="text-right">Cost basis</span>
<span className="text-right">Value</span>
<span className="hidden sm:block text-right">Avg buy</span>
<span className="hidden sm:block text-right">Current</span>
<span className="hidden sm:block text-right">Cost basis</span>
<span className="hidden sm:block text-right">Value</span>
<span className="text-right">P&amp;L</span>
</div>
@@ -102,11 +102,13 @@ export default async function PositionsPage() {
return (
<div
key={pos.id}
className="grid grid-cols-[1fr_auto_auto_auto_auto_auto_auto] gap-4 items-center px-4 py-3"
className="grid grid-cols-[1fr_auto_auto] sm:grid-cols-[1fr_auto_auto_auto_auto_auto_auto] gap-4 items-center px-4 py-3"
>
{/* Hashtag + type + sparkline */}
{/* Hashtag + type badge (+ sparkline on desktop) */}
<div className="flex items-center gap-3 min-w-0">
<Sparkline prices={sparkPrices} />
<div className="hidden sm:block shrink-0">
<Sparkline prices={sparkPrices} />
</div>
<div className="min-w-0">
<Link
href={`/hashtag/${pos.hashtag.tag}`}
@@ -127,10 +129,10 @@ export default async function PositionsPage() {
</div>
<span className="text-right text-sm">{formatNumber(pos.shares)}</span>
<span className="text-right text-sm">{formatCurrency(pos.avgBuyPrice)}</span>
<span className="text-right text-sm">{formatCurrency(pos.hashtag.currentPrice)}</span>
<span className="text-right text-sm text-slate-400">{formatCurrency(costBasis)}</span>
<span className="text-right text-sm">{formatCurrency(currentValue)}</span>
<span className="hidden sm:block text-right text-sm">{formatCurrency(pos.avgBuyPrice)}</span>
<span className="hidden sm:block text-right text-sm">{formatCurrency(pos.hashtag.currentPrice)}</span>
<span className="hidden sm:block text-right text-sm text-slate-400">{formatCurrency(costBasis)}</span>
<span className="hidden sm:block text-right text-sm">{formatCurrency(currentValue)}</span>
<div className="text-right">
<p className={`text-sm font-medium ${pnlColor(pnl)}`}>{formatPnl(pnl)}</p>