diff --git a/package-lock.json b/package-lock.json
index 0411ca6..c8b864e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "plotlink",
- "version": "1.36.0",
+ "version": "1.37.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "plotlink",
- "version": "1.36.0",
+ "version": "1.37.0",
"workspaces": [
"packages/*"
],
diff --git a/package.json b/package.json
index fe9fa37..b8b2820 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "plotlink",
- "version": "1.36.0",
+ "version": "1.37.0",
"private": true,
"workspaces": [
"packages/*"
diff --git a/src/components/airdrop/ContributionPanel.tsx b/src/components/airdrop/ContributionPanel.tsx
index dc3ab6d..7a0402c 100644
--- a/src/components/airdrop/ContributionPanel.tsx
+++ b/src/components/airdrop/ContributionPanel.tsx
@@ -1,10 +1,131 @@
"use client";
+import { useEffect, useState } from "react";
+import { useAccount } from "wagmi";
+
+interface ProjectionData {
+ address: string;
+ buy_volume: number;
+ qualified_refs: number;
+ has_fc_bonus: boolean;
+ multiplier: number;
+ weighted_spend: number;
+ community_total: number;
+ projected_share: {
+ bronze: number;
+ silver: number;
+ gold: number;
+ diamond: number;
+ };
+}
+
+function Stat({ label, value, accent }: { label: string; value: string; accent?: boolean }) {
+ return (
+
+ );
+}
+
export function ContributionPanel() {
+ const { address, isConnected } = useAccount();
+ const [fetchState, setFetchState] = useState<{
+ data: ProjectionData | null;
+ error: string | null;
+ done: boolean;
+ }>({ data: null, error: null, done: !isConnected });
+
+ useEffect(() => {
+ if (!isConnected || !address) return;
+
+ let cancelled = false;
+ fetch(`/api/airdrop/projection?address=${address.toLowerCase()}`)
+ .then(res => {
+ if (res.status === 404) return null;
+ if (!res.ok) throw new Error("Failed to load");
+ return res.json();
+ })
+ .then(d => { if (!cancelled) setFetchState({ data: d, error: null, done: true }); })
+ .catch(() => { if (!cancelled) setFetchState({ data: null, error: "Failed to load contribution data.", done: true }); });
+
+ return () => { cancelled = true; setFetchState({ data: null, error: null, done: false }); };
+ }, [isConnected, address]);
+
+ const { data, error } = fetchState;
+ const loading = isConnected && !fetchState.done;
+
+ if (loading) {
+ return (
+
+
Loading contribution data...
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ if (!data) {
+ return (
+
+
Your Contribution
+
Buy PLOT tokens to start earning your share of the airdrop pool.
+
+ );
+ }
+
+ const currentShare = data.community_total > 0
+ ? (data.weighted_spend / data.community_total * 100).toFixed(2)
+ : "0.00";
+
return (
-
-
Your Contribution
-
Buy PLOT tokens to earn your share of the airdrop pool.
+
+
Your Contribution
+
+
+
+
+
+
+
+
+
+
+
+
Projected Share by Milestone
+
+
+
Bronze
+
{Math.round(data.projected_share.bronze).toLocaleString()}
+
PLOT
+
+
+
Silver
+
{Math.round(data.projected_share.silver).toLocaleString()}
+
PLOT
+
+
+
Gold
+
{Math.round(data.projected_share.gold).toLocaleString()}
+
PLOT
+
+
+
Diamond
+
{Math.round(data.projected_share.diamond).toLocaleString()}
+
PLOT
+
+
+
);
}