173 lines
5.5 KiB
TypeScript
173 lines
5.5 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { useState, useEffect } from "react";
|
||
|
|
import { toast } from "sonner";
|
||
|
|
import { useAccount } from "wagmi";
|
||
|
|
import EarnOpportunityCard from "./EarnOpportunityCard";
|
||
|
|
import ActivityHistory from "./ActivityHistory";
|
||
|
|
import TopPerformers from "./TopPerformers";
|
||
|
|
import ReferralCodeCard from "./ReferralCodeCard";
|
||
|
|
import BindInviteCard from "./BindInviteCard";
|
||
|
|
import TeamTVLCard from "./TeamTVLCard";
|
||
|
|
import DepositCard from "./DepositCard";
|
||
|
|
import { fetchTeamTVL, bindInviteCode, registerWallet, type TeamTVLData } from "@/lib/api/points";
|
||
|
|
import { useApp } from "@/contexts/AppContext";
|
||
|
|
|
||
|
|
export default function PointsCards() {
|
||
|
|
const { t } = useApp();
|
||
|
|
const { address } = useAccount();
|
||
|
|
const [mounted, setMounted] = useState(false);
|
||
|
|
const [teamTVL, setTeamTVL] = useState<TeamTVLData | null>(null);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const [inviteCode, setInviteCode] = useState('');
|
||
|
|
const [inviteUsedCount, setInviteUsedCount] = useState(0);
|
||
|
|
const [inviteLoading, setInviteLoading] = useState(false);
|
||
|
|
|
||
|
|
useEffect(() => { setMounted(true); }, []);
|
||
|
|
|
||
|
|
// Register wallet and load user data when address connects
|
||
|
|
useEffect(() => {
|
||
|
|
if (address) {
|
||
|
|
registerAndLoad(address);
|
||
|
|
}
|
||
|
|
}, [address]);
|
||
|
|
|
||
|
|
async function registerAndLoad(walletAddress: string) {
|
||
|
|
setInviteLoading(true);
|
||
|
|
// Register wallet (creates user if not exists), get invite code
|
||
|
|
const userData = await registerWallet(walletAddress);
|
||
|
|
if (userData) {
|
||
|
|
setInviteCode(userData.inviteCode);
|
||
|
|
setInviteUsedCount(userData.usedCount);
|
||
|
|
}
|
||
|
|
setInviteLoading(false);
|
||
|
|
|
||
|
|
// Load team TVL for this wallet
|
||
|
|
setLoading(true);
|
||
|
|
const teamData = await fetchTeamTVL(walletAddress);
|
||
|
|
setTeamTVL(teamData);
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
|
||
|
|
const handleCopy = () => {
|
||
|
|
if (inviteCode) {
|
||
|
|
navigator.clipboard.writeText(inviteCode);
|
||
|
|
toast.success(t("points.copiedToClipboard"));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleShare = async () => {
|
||
|
|
if (!inviteCode) return;
|
||
|
|
const shareUrl = `${window.location.origin}?ref=${inviteCode}`;
|
||
|
|
if (navigator.share) {
|
||
|
|
try {
|
||
|
|
await navigator.share({ url: shareUrl });
|
||
|
|
} catch {
|
||
|
|
// user cancelled, do nothing
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
navigator.clipboard.writeText(shareUrl);
|
||
|
|
toast.success(t("points.shareLinkCopied"));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleApply = async (code: string) => {
|
||
|
|
if (!code) {
|
||
|
|
toast.error(t("points.pleaseEnterInviteCode"));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: Get signature from wallet
|
||
|
|
const signature = "0x..."; // Placeholder
|
||
|
|
|
||
|
|
const result = await bindInviteCode(code, signature, address);
|
||
|
|
|
||
|
|
if (result.success) {
|
||
|
|
toast.success(result.message || t("points.inviteCodeBoundSuccess"));
|
||
|
|
if (address) registerAndLoad(address); // Reload data
|
||
|
|
} else {
|
||
|
|
toast.error(result.error || t("points.failedToBindInviteCode"));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="flex flex-col gap-6">
|
||
|
|
{/* First Row - Cards 1, 2, 3 */}
|
||
|
|
<div className="flex flex-col md:flex-row gap-6 animate-fade-in" style={{ animationDelay: '0.1s' }}>
|
||
|
|
<ReferralCodeCard
|
||
|
|
code={inviteCode || t("points.loading")}
|
||
|
|
usedCount={inviteUsedCount}
|
||
|
|
loading={!mounted || inviteLoading || !address}
|
||
|
|
onCopy={handleCopy}
|
||
|
|
onShare={handleShare}
|
||
|
|
/>
|
||
|
|
<BindInviteCard
|
||
|
|
onApply={handleApply}
|
||
|
|
/>
|
||
|
|
<TeamTVLCard
|
||
|
|
currentTVL={teamTVL?.currentTVL || "$0"}
|
||
|
|
targetTVL={teamTVL?.targetTVL || "$10M"}
|
||
|
|
progressPercent={teamTVL?.progressPercent || 0}
|
||
|
|
members={teamTVL?.totalMembers || 0}
|
||
|
|
roles={teamTVL?.roles || []}
|
||
|
|
loading={loading}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Second Row - Card 4 */}
|
||
|
|
<div className="animate-fade-in" style={{ animationDelay: '0.2s' }}>
|
||
|
|
<DepositCard
|
||
|
|
title="Deposit USDC to ALP"
|
||
|
|
subtitle="Native Staking"
|
||
|
|
badge="UP TO 3X"
|
||
|
|
lockPeriod="30 DAYS"
|
||
|
|
progressPercent={65}
|
||
|
|
pointsBoost="+10% POINTS"
|
||
|
|
onDeposit={() => {}}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Third Row - Earn Opportunities */}
|
||
|
|
<div className="flex flex-col md:flex-row gap-6 animate-fade-in" style={{ animationDelay: '0.3s' }}>
|
||
|
|
<EarnOpportunityCard
|
||
|
|
pointsLabel="7X Points"
|
||
|
|
title="Pendle YT"
|
||
|
|
subtitle="Yield Trading optimization"
|
||
|
|
metricLabel="EST. APY"
|
||
|
|
metricValue="50%-300%"
|
||
|
|
buttonText="ZAP IN"
|
||
|
|
onButtonClick={() => {}}
|
||
|
|
/>
|
||
|
|
<EarnOpportunityCard
|
||
|
|
pointsLabel="4X Points"
|
||
|
|
title="Curve LP"
|
||
|
|
subtitle="Liquidity Pool provision"
|
||
|
|
metricLabel="CURRENT APY"
|
||
|
|
metricValue="15%-35%"
|
||
|
|
buttonText="Add Liquidity"
|
||
|
|
onButtonClick={() => {}}
|
||
|
|
/>
|
||
|
|
<EarnOpportunityCard
|
||
|
|
pointsLabel="2X-8X Points"
|
||
|
|
title="Morpho"
|
||
|
|
subtitle="Lending Loop strategy"
|
||
|
|
metricLabel="MAX LTV"
|
||
|
|
metricValue="85%"
|
||
|
|
buttonText="Start LOOP"
|
||
|
|
onButtonClick={() => {}}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Fourth Row - Activity History and Top Performers */}
|
||
|
|
<div className="flex flex-col md:flex-row gap-6 animate-fade-in" style={{ animationDelay: '0.4s' }}>
|
||
|
|
<div className="flex-[2]">
|
||
|
|
<ActivityHistory />
|
||
|
|
</div>
|
||
|
|
<div className="flex-1">
|
||
|
|
<TopPerformers />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|