Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1cf7892c8b | |||
| e1e6790628 |
@@ -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
@@ -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&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>
|
||||
|
||||
Reference in New Issue
Block a user