diff --git a/package.json b/package.json index 5ab6f664..9eec94c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink", - "version": "0.1.40", + "version": "0.1.41", "private": true, "workspaces": [ "packages/*" diff --git a/src/app/api/airdrop/status/route.ts b/src/app/api/airdrop/status/route.ts index b2bcb994..94825e85 100644 --- a/src/app/api/airdrop/status/route.ts +++ b/src/app/api/airdrop/status/route.ts @@ -6,6 +6,7 @@ import { NextResponse } from "next/server"; import { createServerClient } from "../../../../../lib/supabase"; import { AIRDROP_CONFIG } from "../../../../../lib/airdrop/config"; +import { getPlotUsdPrice } from "../../../../../lib/usd-price"; export async function GET() { const supabase = createServerClient(); @@ -20,7 +21,7 @@ export async function GET() { const elapsedMs = Math.max(0, now.getTime() - start.getTime()); const remainingMs = Math.max(0, end.getTime() - now.getTime()); - // Latest price from pl_daily_prices + // Latest price: try pl_daily_prices first, fall back to live price const { data: latestPrice } = await supabase .from("pl_daily_prices") .select("price_usd, mcap_usd") @@ -28,6 +29,9 @@ export async function GET() { .limit(1) .single(); + // Live price fallback when daily snapshots haven't been recorded yet + const livePriceUsd = latestPrice?.price_usd ?? (await getPlotUsdPrice()); + // Total points earned + unique participants const { data: allPoints } = await supabase .from("pl_points") @@ -41,8 +45,13 @@ export async function GET() { } const totalParticipants = uniqueAddresses.size; - // Milestone status — mcap_usd column now stores FDV (price × max supply) - const currentFdv = latestPrice?.mcap_usd ?? 0; + // Milestone status — use stored FDV or compute from live price + const MAX_SUPPLY = 1_000_000; + const currentFdv = latestPrice?.mcap_usd + ? Number(latestPrice.mcap_usd) + : livePriceUsd + ? livePriceUsd * MAX_SUPPLY + : 0; const milestones = { bronze: { mcap: AIRDROP_CONFIG.MILESTONES.BRONZE.mcap, @@ -73,7 +82,7 @@ export async function GET() { timeElapsedPercent: totalMs > 0 ? Math.min(100, Math.round((elapsedMs / totalMs) * 100)) : 0, poolAmount: AIRDROP_CONFIG.POOL_AMOUNT, currentFdv, - latestPriceUsd: latestPrice?.price_usd ?? null, + latestPriceUsd: livePriceUsd ?? null, milestones, totalPointsEarned, totalParticipants, diff --git a/src/components/airdrop/CampaignHero.tsx b/src/components/airdrop/CampaignHero.tsx index d2175380..1d5de3f9 100644 --- a/src/components/airdrop/CampaignHero.tsx +++ b/src/components/airdrop/CampaignHero.tsx @@ -55,7 +55,7 @@ function buildTiers(milestones: StatusData["milestones"]): Tier[] { const SVG_W = 700; const SVG_H = 340; -const PAD = { top: 30, right: 70, bottom: 40, left: 70 }; +const PAD = { top: 30, right: 80, bottom: 40, left: 70 }; const CW = SVG_W - PAD.left - PAD.right; const CH = SVG_H - PAD.top - PAD.bottom; @@ -322,7 +322,7 @@ function TimelineChart({ x={PAD.left + CW + 4} y={m.y + 3} fill="#8B7355" - fontSize={8} + fontSize={11} fontFamily="Inter, system-ui, sans-serif" > {m.emoji} {formatCompact(m.fdv)} @@ -339,7 +339,7 @@ function TimelineChart({ y={poolToY(val, yLeftMax) + 3} textAnchor="end" fill="#8B7355" - fontSize={8} + fontSize={11} fontFamily="Inter, system-ui, sans-serif" > {formatCompact(val)} @@ -363,7 +363,7 @@ function TimelineChart({ y={PAD.top + CH + 14} textAnchor="middle" fill="#8B7355" - fontSize={9} + fontSize={12} fontFamily="Inter, system-ui, sans-serif" > {m.label} @@ -377,7 +377,7 @@ function TimelineChart({ y={PAD.top + CH / 2} textAnchor="middle" fill="#8B7355" - fontSize={9} + fontSize={11} fontFamily="Inter, system-ui, sans-serif" transform={`rotate(-90, 12, ${PAD.top + CH / 2})`} className="hidden sm:block" @@ -389,7 +389,7 @@ function TimelineChart({ y={PAD.top + CH / 2} textAnchor="middle" fill="#8B7355" - fontSize={9} + fontSize={11} fontFamily="Inter, system-ui, sans-serif" transform={`rotate(90, ${SVG_W - 8}, ${PAD.top + CH / 2})`} className="hidden sm:block"