diff --git a/package.json b/package.json index eb265f3..7600814 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink", - "version": "1.3.0", + "version": "1.3.1", "private": true, "workspaces": [ "packages/*" diff --git a/src/components/airdrop/CampaignHero.tsx b/src/components/airdrop/CampaignHero.tsx index a84dd14..3f06ad0 100644 --- a/src/components/airdrop/CampaignHero.tsx +++ b/src/components/airdrop/CampaignHero.tsx @@ -1,9 +1,7 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { formatUsdValue } from "../../../lib/usd-price"; -import { AIRDROP_TEST_MODE } from "../../../lib/airdrop/config"; /* ─── Types ─── */ @@ -26,24 +24,6 @@ interface StatusData { lockerTx: string | null; } -/* ─── Constants ─── */ - -const MAX_SUPPLY = 1_000_000; - -const TIER_KEYS = ["bronze", "silver", "gold", "diamond"] as const; - -interface MilestoneRow { - fdv: number; - pct: number; - unlockPlot: number; - poolUsd: number; - burnPct: number; - cmcRank: string | null; - isFull: boolean; -} - -const CMC_RANKS = ["≈ CMC #1900", "≈ CMC #950", "≈ CMC #400", "≈ CMC #250"]; - /* ─── Helpers ─── */ function useAirdropStatus() { @@ -59,12 +39,6 @@ function useAirdropStatus() { }); } -function formatCompact(val: number): string { - if (val >= 1_000_000) return `$${(val / 1_000_000).toFixed(1)}M`; - if (val >= 1_000) return `$${(val / 1_000).toFixed(0)}K`; - return `$${val.toFixed(0)}`; -} - function useCountdown(endDateStr: string) { const [remaining, setRemaining] = useState({ d: 0, h: 0, m: 0, s: 0 }); @@ -88,131 +62,12 @@ function useCountdown(endDateStr: string) { return remaining; } -function buildMilestoneRows( - milestones: StatusData["milestones"], - poolAmount: number, -): MilestoneRow[] { - return TIER_KEYS.map((key, i) => { - const ms = milestones[key]; - const price = ms.mcap / MAX_SUPPLY; - const unlockPlot = poolAmount * (ms.pct / 100); - return { - fdv: ms.mcap, - pct: ms.pct, - unlockPlot, - poolUsd: unlockPlot * price, - burnPct: 100 - ms.pct, - cmcRank: AIRDROP_TEST_MODE ? null : (CMC_RANKS[i] ?? null), - isFull: ms.pct === 100, - }; - }); -} - -function getCurrentBurnState( - currentFdv: number, - milestones: StatusData["milestones"], - poolAmount: number, -): { burnPct: number; distributePct: number; poolUsd: number } { - const entries = TIER_KEYS.map((k) => milestones[k]); - let highestPct = 0; - for (let i = entries.length - 1; i >= 0; i--) { - if (currentFdv >= entries[i].mcap) { - highestPct = entries[i].pct; - break; - } - } - const price = currentFdv / MAX_SUPPLY; - const unlockPlot = poolAmount * (highestPct / 100); - return { - burnPct: 100 - highestPct, - distributePct: highestPct, - poolUsd: unlockPlot * price, - }; -} - -/* ─── Burn Bar ─── */ - -function BurnBar({ - burnPct, - distributePct, - currentFdv, - poolUsd, -}: { - burnPct: number; - distributePct: number; - currentFdv: number; - poolUsd: number; -}) { - const isFull = distributePct >= 100; - const isAllBurned = burnPct >= 100; - - return ( -
-
- If the campaign ended right now... -
- -
- {burnPct > 0 && ( -
- )} - {distributePct > 0 && ( -
- )} -
- -
- - ← {burnPct}% BURNED - - - {isFull ? "FULL DISTRIBUTION" : `${distributePct}% distributed →`} - -
- -
- - Current MCap: {currentFdv > 0 ? formatUsdValue(currentFdv) : "—"} - - - Pool value right now: {poolUsd > 0 ? formatUsdValue(poolUsd) : "$0"} - -
-
- ); -} - /* ─── Main component ─── */ export function CampaignHero() { const { data, isLoading } = useAirdropStatus(); const countdown = useCountdown(data?.campaignEnd ?? "2027-01-01"); - const milestoneRows = useMemo( - () => (data ? buildMilestoneRows(data.milestones, data.poolAmount) : []), - [data], - ); - - const burnState = useMemo( - () => - data - ? getCurrentBurnState(data.currentFdv, data.milestones, data.poolAmount) - : { burnPct: 100, distributePct: 0, poolUsd: 0 }, - [data], - ); - if (isLoading || !data) { return (
@@ -275,120 +130,6 @@ export function CampaignHero() { )}
- {/* ── Burn bar ── */} - - - {/* ── Section 1: Your airdrop grows with $PLOT ── */} -
-
- Your airdrop grows with $PLOT -
-

- The pool is {data.poolAmount.toLocaleString()} PLOT. Its value depends on the market. -

- -
- {/* "Now" row */} -
- Now - MCap {data.currentFdv > 0 ? formatCompact(data.currentFdv) : "—"} - Pool value: {data.currentFdv > 0 ? formatUsdValue(burnState.poolUsd) : "$0"} -
- {/* Milestone rows */} - {milestoneRows.map((row, i) => ( -
- - Step {i + 1} - - MCap {formatCompact(row.fdv)} - - Pool ~{formatCompact(row.poolUsd)} - -
- ))} -
- -

- The same {data.poolAmount.toLocaleString()} PLOT — worth{" "} - {data.currentFdv > 0 ? formatUsdValue(burnState.poolUsd) : "$0"} today, or{" "} - ~{formatCompact(milestoneRows[milestoneRows.length - 1]?.poolUsd ?? 0)} at full distribution. - Your airdrop size = market growth. -

-
- - {/* ── Section 2: Four steps — not unrealistic ── */} -
-
- Four steps — not unrealistic -
-

- Each step unlocks a bigger share of the pool. -

- -
- {milestoneRows.map((row) => { - const reached = data.currentFdv >= row.fdv; - return ( -
-
- {formatCompact(row.fdv)} -
- {row.cmcRank && ( -
{row.cmcRank}
- )} -
unlocks
-
- {row.pct}% -
-
- ); - })} -
- - {!AIRDROP_TEST_MODE && ( -

- These aren't moonshot numbers — #250 on CMC is a mid-tier project. - Thousands of tokens have done it. -

- )} -
- - {/* ── Section 3: Not reached? Burned forever. ── */} -
-
- Not reached? Burned forever. -
- -
-
- If MCap stays below {formatCompact(milestoneRows[0]?.fdv ?? 0)}: -
-
- {data.poolAmount.toLocaleString()} PLOT → burned permanently 🔥 -
-

- No team keeps it. No treasury recycles it.
- Burned = reduced supply = value for holders. -

-
- -

- Either way, PLOT holders benefit:
- reach milestones → earn airdrop. - Miss milestones → supply shrinks. -

-
- {/* ── Participant count ── */}
{data.totalParticipants > 0