/** * Rewards Section - Referral System * Uses real blockchain data from ReferralContext */ import { useState, useEffect, useCallback } from 'react'; import { Gift, Users, Trophy, Copy, Check, Share2, RefreshCw, Star, TrendingUp, Award, Zap, Coins, Shield, Target, Sparkles, GraduationCap, Play, Globe2, Clock, Loader2, } from 'lucide-react'; import { cn, formatAddress } from '@/lib/utils'; import { useTelegram } from '@/hooks/useTelegram'; import { useAuth } from '@/contexts/AuthContext'; import { useReferral } from '@/contexts/ReferralContext'; import { useWallet } from '@/contexts/WalletContext'; import { SocialLinks } from '@/components/SocialLinks'; import { useTranslation } from '@/i18n'; import { getAllScores, getStakingScoreStatus, startScoreTracking, getPezRewards, claimPezReward, formatDuration, getScoreColor, getScoreRating, type UserScores, type StakingScoreStatus, type PezRewardInfo, } from '@/lib/scores'; import { getStakingRewards, formatRewardAmount, formatRewardDate, type StakingRewardsResult, } from '@/lib/subquery'; import { getUnclaimedRewards, payoutStakingReward, payoutAllRewards, type UnclaimedRewardsResult, } from '@/lib/staking-rewards'; import { getCitizenshipStatus, getCitizenCount, confirmCitizenship, approveReferral, type CitizenshipStatus, } from '@/lib/citizenship'; import { KurdistanSun } from '@/components/KurdistanSun'; import { CitizenshipModal } from '@/components/CitizenshipModal'; // Activity tracking constants const ACTIVITY_STORAGE_KEY = 'pezkuwi_last_active'; const ACTIVITY_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours export function RewardsSection() { const { hapticImpact, hapticNotification, shareUrl, showAlert } = useTelegram(); const { user: authUser } = useAuth(); const { stats, myReferrals, pendingApprovals, loading, refreshStats } = useReferral(); const { isConnected, address, peopleApi, assetHubApi, keypair } = useWallet(); const { t } = useTranslation(); const [copied, setCopied] = useState(false); const [activeTab, setActiveTab] = useState<'overview' | 'referrals' | 'scores'>('overview'); const [isActive, setIsActive] = useState(false); const [timeRemaining, setTimeRemaining] = useState(null); const [userScores, setUserScores] = useState(null); const [stakingStatus, setStakingStatus] = useState(null); const [stakingRewards, setStakingRewards] = useState(null); const [scoresLoading, setScoresLoading] = useState(false); const [citizenshipStatus, setCitizenshipStatus] = useState('NotStarted'); const [citizenCount, setCitizenCount] = useState(null); const [pezRewards, setPezRewards] = useState(null); const [claimingEpoch, setClaimingEpoch] = useState(null); const [showConfirmAnimation, setShowConfirmAnimation] = useState(false); const [showTrackingAnimation, setShowTrackingAnimation] = useState(false); const [trackingAnimationText, setTrackingAnimationText] = useState(''); const [unclaimedRewards, setUnclaimedRewards] = useState(null); const [claimingStaking, setClaimingStaking] = useState(false); const [claimingStakingEra, setClaimingStakingEra] = useState(null); const [approvingAddress, setApprovingAddress] = useState(null); const [showCitizenshipModal, setShowCitizenshipModal] = useState(false); // Check activity status const checkActivityStatus = useCallback(() => { const lastActive = localStorage.getItem(ACTIVITY_STORAGE_KEY); if (lastActive) { const lastActiveTime = parseInt(lastActive, 10); const now = Date.now(); const elapsed = now - lastActiveTime; if (elapsed < ACTIVITY_DURATION_MS) { setIsActive(true); const remaining = ACTIVITY_DURATION_MS - elapsed; const hours = Math.floor(remaining / (60 * 60 * 1000)); const minutes = Math.floor((remaining % (60 * 60 * 1000)) / (60 * 1000)); setTimeRemaining(`${hours}s ${minutes}d`); } else { setIsActive(false); setTimeRemaining(null); } } else { setIsActive(false); setTimeRemaining(null); } }, []); // Check activity status on mount and every minute useEffect(() => { const timeoutId = setTimeout(checkActivityStatus, 0); const interval = setInterval(checkActivityStatus, 60000); return () => { clearTimeout(timeoutId); clearInterval(interval); }; }, [checkActivityStatus]); // Fetch user scores when on scores tab const fetchUserScores = useCallback(async () => { if (!address) { setUserScores(null); setStakingStatus(null); setStakingRewards(null); setPezRewards(null); setUnclaimedRewards(null); return; } setScoresLoading(true); try { const [scores, staking, rewards, pezRewardsData, unclaimed] = await Promise.all([ getAllScores(peopleApi, address), peopleApi ? getStakingScoreStatus(peopleApi, address) : Promise.resolve(null), getStakingRewards(address), peopleApi ? getPezRewards(peopleApi, address) : Promise.resolve(null), assetHubApi ? getUnclaimedRewards(assetHubApi, address) : Promise.resolve(null), ]); setUserScores(scores); setStakingStatus(staking); setStakingRewards(rewards); setPezRewards(pezRewardsData); setUnclaimedRewards(unclaimed); } catch (err) { console.error('Error fetching scores:', err); } finally { setScoresLoading(false); } }, [peopleApi, assetHubApi, address]); // Fetch scores when tab changes to scores or on initial load useEffect(() => { if (activeTab === 'scores' && address) { fetchUserScores(); } }, [activeTab, address, fetchUserScores]); // Fetch citizenship status and citizen count useEffect(() => { if (!peopleApi) return; if (address) { getCitizenshipStatus(peopleApi, address).then(setCitizenshipStatus); } getCitizenCount(peopleApi).then(setCitizenCount); }, [peopleApi, address]); const handleConfirmCitizenship = async () => { if (!peopleApi || !keypair) return; setShowConfirmAnimation(true); hapticImpact('medium'); try { const result = await confirmCitizenship(peopleApi, keypair); if (result.success) { hapticNotification('success'); setCitizenshipStatus('Approved'); showAlert(t('rewards.citizenshipConfirmed')); } else { hapticNotification('error'); showAlert(result.error || t('rewards.citizenshipFailed')); } } catch (err) { hapticNotification('error'); showAlert(err instanceof Error ? err.message : t('rewards.citizenshipFailed')); } finally { setShowConfirmAnimation(false); } }; const handleStartTracking = async () => { if (!peopleApi || !keypair) return; setTrackingAnimationText(t('rewards.startingTracking')); setShowTrackingAnimation(true); hapticImpact('medium'); try { const result = await startScoreTracking(peopleApi, keypair); if (result.success) { hapticNotification('success'); showAlert(t('rewards.trackingStarted')); fetchUserScores(); } else { hapticNotification('error'); showAlert(result.error || t('rewards.trackingFailed')); } } catch (err) { hapticNotification('error'); showAlert(err instanceof Error ? err.message : t('rewards.trackingFailed')); } finally { setShowTrackingAnimation(false); } }; const handleClaimReward = async (epochIndex: number) => { if (!peopleApi || !keypair) return; setClaimingEpoch(epochIndex); setTrackingAnimationText(t('rewards.claimingReward')); setShowTrackingAnimation(true); hapticImpact('medium'); try { const result = await claimPezReward(peopleApi, keypair, epochIndex); if (result.success) { hapticNotification('success'); showAlert(t('rewards.claimSuccess')); fetchUserScores(); } else { hapticNotification('error'); showAlert(result.error || t('rewards.claimFailed')); } } catch (err) { hapticNotification('error'); showAlert(err instanceof Error ? err.message : t('rewards.claimFailed')); } finally { setShowTrackingAnimation(false); setClaimingEpoch(null); } }; const handleClaimAll = async () => { if (!peopleApi || !keypair || !pezRewards?.claimableRewards.length) return; setTrackingAnimationText(t('rewards.claimingReward')); setShowTrackingAnimation(true); hapticImpact('medium'); try { for (const reward of pezRewards.claimableRewards) { setClaimingEpoch(reward.epoch); const result = await claimPezReward(peopleApi, keypair, reward.epoch); if (!result.success) { hapticNotification('error'); showAlert(result.error || t('rewards.claimFailed')); setShowTrackingAnimation(false); setClaimingEpoch(null); return; } } hapticNotification('success'); showAlert(t('rewards.claimSuccess')); fetchUserScores(); } catch (err) { hapticNotification('error'); showAlert(err instanceof Error ? err.message : t('rewards.claimFailed')); } finally { setShowTrackingAnimation(false); setClaimingEpoch(null); } }; const handleClaimStakingReward = async (validator: string, era: number, page?: number) => { if (!assetHubApi || !keypair) return; setClaimingStakingEra(era); setTrackingAnimationText(t('rewards.claimingStakingReward')); setShowTrackingAnimation(true); hapticImpact('medium'); try { const result = await payoutStakingReward(assetHubApi, keypair, validator, era, page); if (result.success) { hapticNotification('success'); showAlert(t('rewards.stakingClaimSuccess')); fetchUserScores(); } else { hapticNotification('error'); showAlert(result.error || t('rewards.stakingClaimFailed')); } } catch (err) { hapticNotification('error'); showAlert(err instanceof Error ? err.message : t('rewards.stakingClaimFailed')); } finally { setShowTrackingAnimation(false); setClaimingStakingEra(null); } }; const handleClaimAllStaking = async () => { if (!assetHubApi || !keypair || !unclaimedRewards?.unclaimed.length) return; setClaimingStaking(true); setTrackingAnimationText(t('rewards.claimingStakingReward')); setShowTrackingAnimation(true); hapticImpact('medium'); try { const result = await payoutAllRewards(assetHubApi, keypair, unclaimedRewards.unclaimed); if (result.success) { hapticNotification('success'); showAlert(t('rewards.stakingClaimSuccess')); fetchUserScores(); } else { hapticNotification('error'); showAlert(result.error || t('rewards.stakingClaimFailed')); } } catch (err) { hapticNotification('error'); showAlert(err instanceof Error ? err.message : t('rewards.stakingClaimFailed')); } finally { setShowTrackingAnimation(false); setClaimingStaking(false); } }; const handleActivate = () => { hapticNotification('success'); localStorage.setItem(ACTIVITY_STORAGE_KEY, Date.now().toString()); setIsActive(true); setTimeRemaining('24s 0d'); showAlert(t('rewards.activatedAlert')); }; const handleApproveReferral = async (applicantAddress: string) => { if (!peopleApi || !keypair) return; setApprovingAddress(applicantAddress); hapticImpact('medium'); try { const result = await approveReferral(peopleApi, keypair, applicantAddress); if (result.success) { hapticNotification('success'); showAlert(t('rewards.referralApprovalSuccess')); refreshStats(); } else { hapticNotification('error'); showAlert(result.error || t('rewards.referralApprovalFailed')); } } catch (err) { hapticNotification('error'); showAlert(err instanceof Error ? err.message : t('rewards.referralApprovalFailed')); } finally { setApprovingAddress(null); } }; // Telegram referral link (for sharing) - use authenticated user ID const referralLink = authUser?.telegram_id ? `https://t.me/pezkuwichainBot?start=ref_${authUser.telegram_id}` : 'https://t.me/pezkuwichainBot'; // Full share message: invitation text + link + wallet address for manual paste const shareMessage = address ? `${t('rewards.shareText')}\n${referralLink}\n\n${t('rewards.referralInstruction')}\n${address}` : `${t('rewards.shareText')}\n${referralLink}`; const handleCopy = async () => { try { await window.navigator.clipboard.writeText(shareMessage); setCopied(true); hapticNotification('success'); setTimeout(() => setCopied(false), 2000); } catch { showAlert(t('rewards.copyAlert')); } }; const handleShare = () => { hapticImpact('medium'); shareUrl( referralLink, address ? `${t('rewards.shareText')}\n\n${t('rewards.referralInstruction')}\n${address}` : t('rewards.shareText') ); }; const handleRefresh = () => { hapticImpact('medium'); refreshStats(); }; // Calculate points per referral based on position const getPointsForPosition = (position: number): number => { if (position <= 10) return 10; if (position <= 50) return 5; if (position <= 100) return 4; return 0; }; if (!isConnected) { return (

{t('rewards.subtitle')}

{t('rewards.connectWalletFirst')}

); } return (
{/* Header */}

{t('rewards.title')}

{/* Tabs */}
{[ { id: 'overview' as const, label: t('rewards.overview') }, { id: 'referrals' as const, label: t('rewards.referral') }, { id: 'scores' as const, label: t('rewards.scores') }, ].map(({ id, label }) => ( ))}
{/* Overview Tab */} {activeTab === 'overview' && (
{loading ? (
{[1, 2, 3, 4].map((i) => (
))}
) : ( <> {/* Citizen Count Card */}

{t('rewards.citizenCountTitle')}

{citizenCount !== null ? citizenCount.toLocaleString() : '...'}

{t('rewards.citizenCountDesc')}

{/* Score Card */}

{t('rewards.referralScore')}

{stats?.referralScore ?? 0}

{t('rewards.maxScore')}
{/* Refer More Card */}

{t('rewards.referMoreTitle')}

{t('rewards.referMoreDescription')}

{/* Stats Grid */}
{t('rewards.referral')}

{stats?.referralCount ?? 0}

{t('rewards.kycApproved')}

{t('rewards.referrer')}
{stats?.whoInvitedMe ? (

{formatAddress(stats.whoInvitedMe, 6)}

) : (

{t('rewards.none')}

)}

{t('rewards.invitedMe')}

{/* Pending Referral Notification */} {stats?.pendingReferral && (
{t('rewards.pendingReferral')}
{t('rewards.completeKyc')}{' '} {formatAddress(stats.pendingReferral, 6)}
)} {/* Citizenship Status Card */} {(citizenshipStatus === 'PendingReferral' || citizenshipStatus === 'ReferrerApproved') && (

{t('rewards.citizenshipStatus')}

{t('rewards.pendingApproval')}
{citizenshipStatus === 'ReferrerApproved' ? ( ) : (
)} {t('rewards.approved')}
{citizenshipStatus === 'PendingReferral' && (

{t('rewards.waitingReferrer')}

)} {citizenshipStatus === 'ReferrerApproved' && ( )}
)} {/* Invite Card */}

{t('rewards.inviteFriends')}

{t('rewards.yourLink')}

{referralLink}
{/* Score System */}

{t('rewards.scoreSystem')}

1-10 referral ×10 pûan
11-50 referral 100 + ×5
51-100 referral 300 + ×4
101+ referral 500 (Max)
{/* I am Active Button */}

{t('rewards.activeStatus')}

{isActive && timeRemaining && (

{t('rewards.timeRemaining', { time: timeRemaining })}

)}
{isActive ? t('rewards.active') : t('rewards.inactive')}

{t('rewards.activeDescription')}

{/* Social Links */} )}
)} {/* Referrals Tab */} {activeTab === 'referrals' && (
{/* Refer More Card */}

{t('rewards.referMoreTitle')}

{t('rewards.referMoreDescription')}

{/* I am Active Button */}

{t('rewards.activeStatus')}

{isActive && timeRemaining && (

{t('rewards.timeRemaining', { time: timeRemaining })}

)}
{isActive ? t('rewards.active') : t('rewards.inactive')}
{/* Pending Approvals Section */} {pendingApprovals.length > 0 && (

{t('rewards.pendingApprovals')}

{pendingApprovals.length}
{pendingApprovals.map((approval) => (
{formatAddress(approval.applicantAddress, 8)}

{t('rewards.pendingReferralStatus')}

))}
)} {loading ? (
{[1, 2, 3, 4, 5].map((i) => (
))}
) : myReferrals.length > 0 ? (
{t('rewards.referralCount', { count: myReferrals.length })}
{myReferrals.map((referralAddress, index) => (
{index + 1}
{formatAddress(referralAddress, 8)}

{t('rewards.kycApproved')}

+{getPointsForPosition(index + 1)} {t('rewards.points')}
))}
) : (

{t('rewards.noReferrals')}

{t('rewards.shareYourLink')}

)}
)} {/* Scores Tab */} {activeTab === 'scores' && (
{scoresLoading ? (
{[1, 2, 3, 4].map((i) => (
))}
) : ( <> {/* Trust Score - Main Card */}

{t('rewards.trustScore')}

{userScores?.trustScore ?? 0}

{t('rewards.rank', { rating: getScoreRating(userScores?.trustScore || 0) })} {userScores?.isCitizen && ( {t('rewards.citizen')} )}
{/* Score Components Grid */}
{/* Staking Score */}
{t('rewards.staking')}

{userScores?.stakingScore ?? 0}

{stakingStatus?.isTracking ? stakingStatus.hasCachedData ? formatDuration(stakingStatus.durationBlocks) : t('rewards.stakingWaitingData') : t('rewards.stakingNotStarted')}

{/* Referral Score */}
{t('rewards.referral')}

{userScores?.referralScore ?? 0}

{t('rewards.people', { count: stats?.referralCount ?? 0 })}

{/* Tiki Score */}
{t('rewards.tiki')}

{userScores?.tikiScore ?? 0}

{t('rewards.nftRole')}

{/* Perwerde Score */}
{t('rewards.education')}

{userScores?.perwerdeScore ?? 0}

{t('rewards.reading')}

{/* Start Tracking Button. Once tracking, there is nothing to do each month: the chain holds every score still while an epoch is claimed. */} {!stakingStatus?.isTracking ? (

{t('rewards.startTrackingDesc')}

) : null} {/* PEZ Epoch Rewards */}

{t('rewards.pezRewardsTitle')}

{/* Epoch Info */}
{t('rewards.epoch')}: {pezRewards?.currentEpoch ?? '...'} {pezRewards?.epochStatus === 'ClaimPeriod' ? t('rewards.claimPeriod') : pezRewards?.epochStatus === 'Closed' ? t('rewards.epochClosed') : t('rewards.epochOpen')}
{/* Claimable PEZ Rewards */}

{t('rewards.claimablePez')}

{pezRewards && parseFloat(pezRewards.totalClaimable) > 0 ? `${pezRewards.totalClaimable} PEZ` : '0 PEZ'}

{/* Claimable Epochs List */} {pezRewards && pezRewards.claimableRewards.length > 0 ? (
{pezRewards.claimableRewards.map((reward) => (

{t('rewards.epoch')} #{reward.epoch}

{reward.amount} PEZ

))} {/* Claim All Button */} {pezRewards.claimableRewards.length > 1 && ( )}
) : (

{t('rewards.noPezRewards')}

)} {/* Single Claim Button when only 1 reward */} {pezRewards && pezRewards.claimableRewards.length === 1 && ( )}
{/* HEZ Staking Rewards */}

{t('rewards.stakingRewards')}

{/* Unclaimed Rewards */}

{t('rewards.unclaimedRewards')}

{unclaimedRewards && unclaimedRewards.totalUnclaimedRaw > 0n ? `${unclaimedRewards.totalUnclaimedHez} HEZ` : '0 HEZ'}

{/* Unclaimed Era List */} {unclaimedRewards && unclaimedRewards.unclaimed.length > 0 ? (
{unclaimedRewards.unclaimed.map((reward) => (

{t('rewards.era')} #{reward.era}

{reward.validator.slice(0, 8)}...{reward.validator.slice(-6)}

{reward.estimatedReward} HEZ

))} {/* Claim All Button */} {unclaimedRewards.unclaimed.length > 1 && ( )} {/* Single Claim All when only 1 reward */} {unclaimedRewards.unclaimed.length === 1 && ( )}
) : (

{t('rewards.noUnclaimedRewards')}

)} {/* Reward History (SubQuery) */} {stakingRewards && stakingRewards.rewards.length > 0 && (

{t('rewards.rewardHistory')}

{t('rewards.totalRewards')}

{stakingRewards.totalAccumulatedHez.toFixed(4)} HEZ

{stakingRewards.rewards.map((reward) => (

{reward.type === 'REWARD' ? '+' : '-'} {formatRewardAmount(reward.amount)} HEZ

Block #{reward.blockNumber}

{formatRewardDate(reward.timestamp)}
))}
)}
{/* Score Formula Info */}

{t('rewards.scoreFormula')}

Trust = (Staking × Weighted Sum) / 100

Weighted Sum = Staking×100 + Referral×300 + Perwerde×300 + Tiki×300

{t('rewards.stakingZeroWarning')}

{/* Refresh Button */} )}
)}
{/* Confirm Citizenship Overlay */} {showConfirmAnimation && (

{t('rewards.confirmingCitizenship')}

{t('rewards.signingBlockchain')}

)} {/* Score Tracking Overlay */} {showTrackingAnimation && (

{trackingAnimationText}

{t('rewards.signingBlockchain')}

)} {/* Citizenship Application Modal */} { setShowCitizenshipModal(false); if (peopleApi && address) { getCitizenshipStatus(peopleApi, address).then(setCitizenshipStatus); } }} />
); }