import { useEffect, useState, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { useAuth } from '@/contexts/AuthContext'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; import { supabase } from '@/lib/supabase'; import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowLeft, Award, Users, TrendingUp, UserMinus, Play, Loader2, Coins } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; import { fetchUserTikis, getPrimaryRole, getTikiDisplayName, getTikiColor, getTikiEmoji, getUserRoleCategories, getAllTikiNFTDetails, generateCitizenNumber, type TikiNFTDetails } from '@pezkuwi/lib/tiki'; import { getAllScores, getStakingScoreStatus, startScoreTracking, getPezRewards, recordTrustScore, claimPezReward, type UserScores, type StakingScoreStatus, type PezRewardInfo, formatDuration } from '@pezkuwi/lib/scores'; import { getSigner } from '@/lib/get-signer'; import { getKycStatus } from '@pezkuwi/lib/kyc'; import { ReferralDashboard } from '@/components/referral/ReferralDashboard'; // Commission proposals card removed - no longer using notary system for KYC approval // import { CommissionProposalsCard } from '@/components/dashboard/CommissionProposalsCard'; export default function Dashboard() { const { t } = useTranslation(); const { user } = useAuth(); const { api, isApiReady, peopleApi, isPeopleReady, selectedAccount, walletSource } = usePezkuwi(); const navigate = useNavigate(); const { toast } = useToast(); const [profile, setProfile] = useState | null>(null); const [loading, setLoading] = useState(true); const [tikis, setTikis] = useState([]); const [scores, setScores] = useState({ trustScore: 0, referralScore: 0, stakingScore: 0, tikiScore: 0, totalScore: 0 }); const [loadingScores, setLoadingScores] = useState(false); const [stakingStatus, setStakingStatus] = useState(null); const [startingScoreTracking, setStartingScoreTracking] = useState(false); const [kycStatus, setKycStatus] = useState('NotStarted'); const [renouncingCitizenship, setRenouncingCitizenship] = useState(false); const [pezRewards, setPezRewards] = useState(null); const [isRecordingScore, setIsRecordingScore] = useState(false); const [isClaimingReward, setIsClaimingReward] = useState(false); const [nftDetails, setNftDetails] = useState<{ citizenNFT: TikiNFTDetails | null; roleNFTs: TikiNFTDetails[]; totalNFTs: number }>({ citizenNFT: null, roleNFTs: [], totalNFTs: 0 }); const fetchProfile = useCallback(async () => { if (!user) return; try { const { data, error } = await supabase .from('profiles') .select('*') .eq('id', user.id) .maybeSingle(); if (error) { if (import.meta.env.DEV) console.error('Profile fetch error:', error); return; } // Auto-sync user metadata from Auth to profiles if missing if (data) { const needsUpdate: Record = {}; // Sync full_name from Auth metadata if not set in profiles if (!data.full_name && user.user_metadata?.full_name) { needsUpdate.full_name = user.user_metadata.full_name; } // Sync phone from Auth metadata if not set in profiles if (!data.phone_number && user.user_metadata?.phone) { needsUpdate.phone_number = user.user_metadata.phone; } // Sync email if not set if (!data.email && user.email) { needsUpdate.email = user.email; } // If there are fields to update, update the profile if (Object.keys(needsUpdate).length > 0) { const { error: updateError } = await supabase .from('profiles') .update(needsUpdate) .eq('id', user.id); if (!updateError) { // Update local state Object.assign(data, needsUpdate); } } } // Note: Email verification is handled by Supabase Auth (user.email_confirmed_at) // We don't store it in profiles table to avoid duplication setProfile(data); } catch (error) { if (import.meta.env.DEV) console.error('Error fetching profile:', error); } finally { setLoading(false); } }, [user]); const fetchScoresAndTikis = useCallback(async () => { if (!selectedAccount || !api || !peopleApi) return; setLoadingScores(true); try { // Fetch all scores with frontend fallback (until runtime upgrade) // - Trust, referral, tiki: People Chain (on-chain) // - Staking: Relay Chain with frontend fallback const allScores = await getAllScores(peopleApi, selectedAccount.address); setScores(allScores); // Fetch staking score tracking status (People Chain - uses cached staking data from Asset Hub) const stakingStatusResult = await getStakingScoreStatus(peopleApi, selectedAccount.address); setStakingStatus(stakingStatusResult); // Fetch tikis from People Chain (tiki pallet is on People Chain) const userTikis = await fetchUserTikis(peopleApi, selectedAccount.address); setTikis(userTikis); // Fetch NFT details from People Chain const details = await getAllTikiNFTDetails(peopleApi, selectedAccount.address); setNftDetails(details); // Fetch KYC status from People Chain (identityKyc pallet is on People Chain) const kycStatusResult = await getKycStatus(peopleApi, selectedAccount.address); setKycStatus(kycStatusResult); // Fetch PEZ rewards from People Chain const rewards = await getPezRewards(peopleApi, selectedAccount.address); setPezRewards(rewards); } catch (error) { if (import.meta.env.DEV) console.error('Error fetching scores and tikis:', error); } finally { setLoadingScores(false); } }, [selectedAccount, api, peopleApi]); const handleStartScoreTracking = async () => { if (!peopleApi || !selectedAccount) { toast({ title: t('common.error'), description: t('dashboard.connectWalletError'), variant: "destructive" }); return; } setStartingScoreTracking(true); try { const injector = await getSigner(selectedAccount.address, walletSource, peopleApi); // startScoreTracking on People Chain - staking data comes from Asset Hub via XCM const result = await startScoreTracking(peopleApi, selectedAccount.address, injector.signer); if (result.success) { toast({ title: t('common.success'), description: t('dashboard.scoreTrackingStarted') }); // Refresh scores after starting tracking fetchScoresAndTikis(); } else { toast({ title: t('common.error'), description: result.error || t('dashboard.scoreTrackingFailed'), variant: "destructive" }); } } catch (error) { if (import.meta.env.DEV) console.error('Error starting score tracking:', error); toast({ title: t('common.error'), description: error instanceof Error ? error.message : t('dashboard.scoreTrackingFailed'), variant: "destructive" }); } finally { setStartingScoreTracking(false); } }; const handleRecordTrustScore = async () => { if (!peopleApi || !selectedAccount) return; setIsRecordingScore(true); try { const injector = await getSigner(selectedAccount.address, walletSource, peopleApi); const result = await recordTrustScore(peopleApi, selectedAccount.address, injector.signer); if (result.success) { toast({ title: t('common.success'), description: t('dashboard.trustScoreRecorded') }); fetchScoresAndTikis(); } else { toast({ title: t('common.error'), description: result.error || t('dashboard.trustScoreRecordFailed'), variant: "destructive" }); } } catch (error) { toast({ title: t('common.error'), description: error instanceof Error ? error.message : t('dashboard.trustScoreRecordFailed'), variant: "destructive" }); } finally { setIsRecordingScore(false); } }; const handleClaimReward = async (epochIndex: number) => { if (!peopleApi || !selectedAccount) return; setIsClaimingReward(true); try { const injector = await getSigner(selectedAccount.address, walletSource, peopleApi); const result = await claimPezReward(peopleApi, selectedAccount.address, epochIndex, injector.signer); if (result.success) { const rewardInfo = pezRewards?.claimableRewards.find(r => r.epoch === epochIndex); toast({ title: t('common.success'), description: t('dashboard.rewardClaimed', { amount: rewardInfo?.amount || '0' }) }); fetchScoresAndTikis(); } else { toast({ title: t('common.error'), description: result.error || t('dashboard.rewardClaimFailed'), variant: "destructive" }); } } catch (error) { toast({ title: t('common.error'), description: error instanceof Error ? error.message : t('dashboard.rewardClaimFailed'), variant: "destructive" }); } finally { setIsClaimingReward(false); } }; useEffect(() => { fetchProfile(); if (selectedAccount && api && isApiReady && peopleApi && isPeopleReady) { fetchScoresAndTikis(); } }, [user, selectedAccount, api, isApiReady, peopleApi, isPeopleReady, fetchProfile, fetchScoresAndTikis]); const sendVerificationEmail = async () => { if (!user?.email) { toast({ title: t('common.error'), description: t('dashboard.noEmailFound'), variant: "destructive" }); return; } if (import.meta.env.DEV) console.log('🔄 Attempting to send verification email to:', user.email); if (import.meta.env.DEV) console.log('🔐 User object:', user); try { // Method 1: Try resend API if (import.meta.env.DEV) console.log('📧 Trying Supabase auth.resend()...'); const { error: resendError } = await supabase.auth.resend({ type: 'signup', email: user.email, }); if (resendError) { if (import.meta.env.DEV) console.error('❌ Resend error:', resendError); } else { if (import.meta.env.DEV) console.log('✅ Resend successful'); } // If resend fails, try alternative method if (resendError) { if (import.meta.env.DEV) console.warn('Resend failed, trying alternative method:', resendError); // Method 2: Request password reset as verification alternative // This will send an email if the account exists const { error: resetError } = await supabase.auth.resetPasswordForEmail(user.email, { redirectTo: `${window.location.origin}/email-verification`, }); if (resetError) throw resetError; } toast({ title: t('dashboard.verificationEmailSent'), description: t('dashboard.checkInboxAndSpam'), }); } catch (error) { if (import.meta.env.DEV) console.error('Error sending verification email:', error); // Provide more detailed error message let errorMessage = t('dashboard.failedToSendEmail'); if (error.message?.includes('Email rate limit exceeded')) { errorMessage = t('dashboard.rateLimitExceeded'); } else if (error.message?.includes('User not found')) { errorMessage = t('dashboard.accountNotFound'); } else if (error.message) { errorMessage = error.message; } toast({ title: t('common.error'), description: errorMessage, variant: "destructive" }); } }; const handleRenounceCitizenship = async () => { if (!api || !selectedAccount) { toast({ title: t('common.error'), description: t('dashboard.connectWalletError'), variant: "destructive" }); return; } if (kycStatus !== 'Approved') { toast({ title: t('common.error'), description: t('dashboard.renounceOnlyCitizens'), variant: "destructive" }); return; } // Confirm action const confirmed = window.confirm(t('dashboard.renounceConfirmMsg')); if (!confirmed) return; setRenouncingCitizenship(true); try { const injector = await getSigner(selectedAccount.address, walletSource, peopleApi); if (import.meta.env.DEV) console.log('Renouncing citizenship...'); const tx = api.tx.identityKyc.renounceCitizenship(); await tx.signAndSend(selectedAccount.address, { signer: injector.signer }, ({ status, events, dispatchError }) => { if (dispatchError) { let errorMessage = 'Transaction failed'; if (dispatchError.isModule) { const decoded = api.registry.findMetaError(dispatchError.asModule); errorMessage = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`; } else { errorMessage = dispatchError.toString(); } if (import.meta.env.DEV) console.error(errorMessage); toast({ title: t('dashboard.renounceFailed'), description: errorMessage, variant: "destructive" }); setRenouncingCitizenship(false); return; } if (status.isInBlock || status.isFinalized) { if (import.meta.env.DEV) console.log('✅ Citizenship renounced successfully'); // Check for CitizenshipRenounced event events.forEach(({ event }) => { if (event.section === 'identityKyc' && event.method === 'CitizenshipRenounced') { if (import.meta.env.DEV) console.log('📢 CitizenshipRenounced event detected'); toast({ title: t('dashboard.citizenshipRenounced'), description: t('dashboard.renounceSuccess') }); // Refresh data after a short delay setTimeout(() => { fetchScoresAndTikis(); }, 2000); } }); setRenouncingCitizenship(false); } }); } catch (err) { if (import.meta.env.DEV) console.error('Renunciation error:', err); const errorMsg = err instanceof Error ? err.message : 'Failed to renounce citizenship'; toast({ title: t('common.error'), description: errorMsg, variant: "destructive" }); setRenouncingCitizenship(false); } }; const getRoleDisplay = (): string => { if (loadingScores) return t('dashboard.loading'); if (!selectedAccount) return 'Member'; if (tikis.length === 0) return 'Member'; const primaryRole = getPrimaryRole(tikis); return getTikiDisplayName(primaryRole); }; const getRoleCategories = (): string[] => { if (tikis.length === 0) return ['Member']; return getUserRoleCategories(tikis); }; if (loading) { return
{t('dashboard.loading')}
; } return (

{t('dashboard.title')}

{/* Account Status, Member Since, Role, Total Score - desktop only */}
{t('dashboard.accountStatus')}
{user?.email_confirmed_at || profile?.email_verified ? ( {t('dashboard.verified')} ) : ( {t('dashboard.unverified')} )}

{user?.email_confirmed_at ? t('dashboard.verifiedOn', { date: new Date(user.email_confirmed_at).toLocaleDateString() }) : t('dashboard.emailVerificationStatus')}

{t('dashboard.memberSince')}
{new Date(profile?.joined_at || user?.created_at).toLocaleDateString()}

{t('dashboard.registrationDate')}

{t('dashboard.role')}
{getRoleDisplay()}

{selectedAccount ? t('dashboard.fromTikiNfts') : t('dashboard.connectWalletForRoles')}

{t('dashboard.totalScore')}
{loadingScores ? '...' : scores.totalScore}

{t('dashboard.combinedScores')}

{/* Scores - compact 2x2 grid on mobile, full cards on desktop */}

{t('dashboard.trustScore')}

{loadingScores ? '...' : scores.trustScore}

{t('dashboard.referralScore')}

{loadingScores ? '...' : scores.referralScore}

{t('dashboard.stakingScore')}

{loadingScores ? '...' : scores.stakingScore}

{t('dashboard.tikiScore')}

{loadingScores ? '...' : scores.tikiScore}

{/* Staking Start Tracking - mobile only, separate card */} {!stakingStatus?.isTracking && selectedAccount && (
)} {/* Scores - full cards on desktop */}
{t('dashboard.trustScore')}
{loadingScores ? '...' : scores.trustScore}

{t('dashboard.frontendCalc')}

{t('dashboard.referralScore')}
{loadingScores ? '...' : scores.referralScore}

{t('dashboard.fromReferral')}

{t('dashboard.stakingScore')}
{loadingScores ? '...' : scores.stakingScore}
{stakingStatus?.isTracking ? (

{t('dashboard.tracking', { duration: formatDuration(stakingStatus.durationBlocks) })}

) : selectedAccount ? ( ) : (

{t('dashboard.connectToTrack')}

)}
{t('dashboard.tikiScore')}
{loadingScores ? '...' : scores.tikiScore}

{t('dashboard.rolesAssigned', { count: tikis.length })}

{/* PEZ Rewards Card - only show when pallet is available */} {selectedAccount && pezRewards && ( {t('dashboard.pezRewards')}
{pezRewards.epochStatus === 'Open' ? t('dashboard.epochOpen') : pezRewards.epochStatus === 'ClaimPeriod' ? t('dashboard.epochClaim') : t('dashboard.epochClosed')}

{t('dashboard.epoch', { number: pezRewards.currentEpoch })}

{/* Open epoch: Record score or show recorded score */} {pezRewards.epochStatus === 'Open' && ( pezRewards.hasRecordedThisEpoch ? (
Score: {pezRewards.userScoreCurrentEpoch}
{t('dashboard.recorded')}
) : ( ) )} {/* Claimable rewards */} {pezRewards.hasPendingClaim ? (
{parseFloat(pezRewards.totalClaimable).toFixed(2)} PEZ
{pezRewards.claimableRewards.map((reward) => (
{t('dashboard.epoch', { number: reward.epoch })}: {reward.amount} PEZ
))}
) : ( !pezRewards.hasRecordedThisEpoch && pezRewards.epochStatus !== 'Open' && (
0 PEZ
) )}
)} {t('dashboard.profileTab')} {t('dashboard.rolesTab')} {t('dashboard.referralsTab')} {t('dashboard.securityTab')} {t('dashboard.activityTab')} {t('dashboard.profileInfo')} {t('dashboard.personalDetails')}
{t('dashboard.fullName')} {profile?.full_name || t('dashboard.notSet')}
{t('dashboard.emailLabel')} {user?.email} {user?.email_confirmed_at || profile?.email_verified ? ( {t('dashboard.verified')} ) : ( )}
{t('dashboard.recoveryEmail')} {profile?.recovery_email || t('dashboard.notSet')} {profile?.recovery_email_verified && profile?.recovery_email && ( {t('dashboard.verified')} )}
{t('dashboard.phone')} {profile?.phone_number || t('dashboard.notSet')}
{t('dashboard.website')} {profile?.website || t('dashboard.notSet')}
{t('dashboard.location')} {profile?.location || t('dashboard.notSet')}
{t('dashboard.rolesTitle')} {selectedAccount ? t('dashboard.rolesFromBlockchain') : t('dashboard.connectWalletToView')} {!selectedAccount && (

{t('dashboard.connectWalletMsg')}

)} {selectedAccount && loadingScores && (

{t('dashboard.loadingRoles')}

)} {selectedAccount && !loadingScores && tikis.length === 0 && (

{t('dashboard.noRolesYet')}

{t('dashboard.completeKyc')}

)} {selectedAccount && !loadingScores && tikis.length > 0 && (
{t('dashboard.primaryRole')} {getTikiEmoji(getPrimaryRole(tikis))} {getTikiDisplayName(getPrimaryRole(tikis))}
{t('dashboard.totalScore')}: {scores.totalScore}
{t('dashboard.categories')} {getRoleCategories().join(', ')}

{t('dashboard.allRoles', { count: tikis.length })}

{tikis.map((tiki, index) => ( {getTikiEmoji(tiki)} {getTikiDisplayName(tiki)} ))}
{nftDetails.totalNFTs > 0 && (

{t('dashboard.nftDetails', { count: nftDetails.totalNFTs })}

{nftDetails.citizenNFT && (
{nftDetails.citizenNFT.tikiEmoji} {t('dashboard.citizenNft')} {t('dashboard.primary')}
{/* NFT Number and Citizen Number - Side by Side */}
{/* NFT Number */}
{t('dashboard.nftNumber')}
#{nftDetails.citizenNFT.collectionId}-{nftDetails.citizenNFT.itemId}
{/* Citizen Number = NFT Number + 6 digits */}
{t('dashboard.citizenNumberLabel')}
#{nftDetails.citizenNFT.collectionId}-{nftDetails.citizenNFT.itemId}-{generateCitizenNumber( nftDetails.citizenNFT.owner, nftDetails.citizenNFT.collectionId, nftDetails.citizenNFT.itemId )}
{t('dashboard.collectionId')} {nftDetails.citizenNFT.collectionId}
{t('dashboard.itemId')} {nftDetails.citizenNFT.itemId}
{t('dashboard.roleLabel')} {nftDetails.citizenNFT.tikiDisplayName}
{t('dashboard.tikiType')} {nftDetails.citizenNFT.tikiRole}
)} {nftDetails.roleNFTs.length > 0 && (

{t('dashboard.additionalRoleNfts')}

{nftDetails.roleNFTs.map((nft, /*index*/) => (
{nft.tikiEmoji} {nft.tikiDisplayName} Score: {nft.tikiScore}
{t('dashboard.collection')} {nft.collectionId}
{t('dashboard.item')} {nft.itemId}
{t('dashboard.tikiType')} {nft.tikiRole}
))}
)}
)}

{t('dashboard.blockchainAddress')}

{selectedAccount.address}

{kycStatus === 'Approved' && (

{t('dashboard.renounceCitizenship')}

{t('dashboard.renounceDesc')}

  • {t('dashboard.renounceBurn')}
  • {t('dashboard.renounceReset')}
  • {t('dashboard.renounceRemove')}

{t('dashboard.renounceNote')}

)}
)}
{t('dashboard.securitySettings')} {t('dashboard.manageAccountSecurity')}

{t('dashboard.password')}

{t('dashboard.lastChanged')}

{!user?.email_confirmed_at && !profile?.email_verified && (

{t('dashboard.verifyYourEmail')}

{t('dashboard.verifyEmailMsg')}

)}
{t('dashboard.recentActivity')} {t('dashboard.recentActivityDesc')}

{t('dashboard.noRecentActivity')}

); }