mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-06-13 19:11:01 +00:00
feat: Integrate real-time blockchain roles with Pallet-Tiki
Implemented comprehensive tiki (role) system integration with Dashboard: New Features: - Created src/lib/tiki.ts utility library based on real pallet-tiki implementation - Added fetchUserTikis() to query user roles from blockchain - Implemented role scoring, categorization, and display helpers - Added "Roles & Tikis" tab to Dashboard showing: * Primary role from highest-scoring tiki * Total tiki score (sum of all role scores) * All assigned roles with emoji indicators * Role categories (Government, Judiciary, Education, etc.) * Connected wallet address Role System Details: - 49 different tiki types from pallet-tiki (Serok, Wezir, Dadger, etc.) - Role assignment types: Automatic, Appointed, Elected, Earned - Score-based hierarchy (10-250 points per role) - Dynamic role display based on blockchain state - Fallback to "Member" when no wallet connected or no tikis assigned Dashboard Improvements: - Fixed email verification using Supabase auth.resend() - Fixed Edit Profile button to navigate to /profile/settings - Added Tiki Score card showing total score and role count - Expanded stats grid to 4 columns (Status, Join Date, Role, Tiki Score) - Real-time role updates when wallet connects/disconnects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+156
-11
@@ -5,20 +5,29 @@ 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 { usePolkadot } from '@/contexts/PolkadotContext';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowLeft } from 'lucide-react';
|
||||
import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowLeft, Award } from 'lucide-react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { fetchUserTikis, calculateTikiScore, getPrimaryRole, getTikiDisplayName, getTikiColor, getTikiEmoji, getUserRoleCategories } from '@/lib/tiki';
|
||||
|
||||
export default function Dashboard() {
|
||||
const { user } = useAuth();
|
||||
const { api, isApiReady, selectedAccount } = usePolkadot();
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
const [profile, setProfile] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [tikis, setTikis] = useState<string[]>([]);
|
||||
const [tikiScore, setTikiScore] = useState<number>(0);
|
||||
const [loadingTikis, setLoadingTikis] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchProfile();
|
||||
}, [user]);
|
||||
if (selectedAccount && api && isApiReady) {
|
||||
fetchTikiData();
|
||||
}
|
||||
}, [user, selectedAccount, api, isApiReady]);
|
||||
|
||||
const fetchProfile = async () => {
|
||||
if (!user) return;
|
||||
@@ -39,27 +48,60 @@ export default function Dashboard() {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchTikiData = async () => {
|
||||
if (!selectedAccount || !api) return;
|
||||
|
||||
setLoadingTikis(true);
|
||||
try {
|
||||
const userTikis = await fetchUserTikis(api, selectedAccount.address);
|
||||
setTikis(userTikis);
|
||||
setTikiScore(calculateTikiScore(userTikis));
|
||||
} catch (error) {
|
||||
console.error('Error fetching tiki data:', error);
|
||||
} finally {
|
||||
setLoadingTikis(false);
|
||||
}
|
||||
};
|
||||
|
||||
const sendVerificationEmail = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase.functions.invoke('email-verification', {
|
||||
body: { action: 'send', email: user?.email }
|
||||
// Supabase automatically sends verification email when updating email
|
||||
// or we can trigger resend with updateUser
|
||||
const { error } = await supabase.auth.resend({
|
||||
type: 'signup',
|
||||
email: user?.email || '',
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
|
||||
toast({
|
||||
title: "Verification Email Sent",
|
||||
description: "Please check your email for verification link",
|
||||
});
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Error sending verification email:', error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to send verification email",
|
||||
description: error.message || "Failed to send verification email",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getRoleDisplay = (): string => {
|
||||
if (loadingTikis) return '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 <div className="flex justify-center items-center h-screen">Loading...</div>;
|
||||
}
|
||||
@@ -74,7 +116,7 @@ export default function Dashboard() {
|
||||
</button>
|
||||
<h1 className="text-3xl font-bold mb-6">User Dashboard</h1>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-3 mb-6">
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4 mb-6">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Account Status</CardTitle>
|
||||
@@ -116,10 +158,25 @@ export default function Dashboard() {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">
|
||||
{profile?.role || 'Member'}
|
||||
{getRoleDisplay()}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Account type
|
||||
{selectedAccount ? 'From Tiki NFTs' : 'Connect wallet for roles'}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Tiki Score</CardTitle>
|
||||
<Award className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">
|
||||
{loadingTikis ? '...' : tikiScore}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{tikis.length} {tikis.length === 1 ? 'role' : 'roles'} assigned
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -128,6 +185,7 @@ export default function Dashboard() {
|
||||
<Tabs defaultValue="profile" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="profile">Profile</TabsTrigger>
|
||||
<TabsTrigger value="roles">Roles & Tikis</TabsTrigger>
|
||||
<TabsTrigger value="security">Security</TabsTrigger>
|
||||
<TabsTrigger value="activity">Activity</TabsTrigger>
|
||||
</TabsList>
|
||||
@@ -179,7 +237,94 @@ export default function Dashboard() {
|
||||
<span>{profile?.location || 'Not set'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={() => navigate('/settings')}>Edit Profile</Button>
|
||||
<Button onClick={() => navigate('/profile/settings')}>Edit Profile</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="roles" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Roles & Tikis</CardTitle>
|
||||
<CardDescription>
|
||||
{selectedAccount
|
||||
? 'Your roles from the blockchain (Pallet-Tiki)'
|
||||
: 'Connect your wallet to view your roles'}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{!selectedAccount && (
|
||||
<div className="text-center py-8">
|
||||
<Shield className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Connect your Polkadot wallet to view your on-chain roles
|
||||
</p>
|
||||
<Button onClick={() => navigate('/')}>
|
||||
Go to Home to Connect Wallet
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedAccount && loadingTikis && (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-muted-foreground">Loading roles from blockchain...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedAccount && !loadingTikis && tikis.length === 0 && (
|
||||
<div className="text-center py-8">
|
||||
<Award className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-muted-foreground mb-2">
|
||||
No roles assigned yet
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Complete KYC to become a Citizen (Hemwelatî)
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedAccount && !loadingTikis && tikis.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">Primary Role:</span>
|
||||
<Badge className="text-lg">
|
||||
{getTikiEmoji(getPrimaryRole(tikis))} {getTikiDisplayName(getPrimaryRole(tikis))}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">Total Score:</span>
|
||||
<span className="text-lg font-bold text-purple-600">{tikiScore}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">Categories:</span>
|
||||
<span className="text-muted-foreground">{getRoleCategories().join(', ')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-4">
|
||||
<h4 className="font-medium mb-3">All Roles ({tikis.length})</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{tikis.map((tiki, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
variant="outline"
|
||||
className={getTikiColor(tiki)}
|
||||
>
|
||||
{getTikiEmoji(tiki)} {getTikiDisplayName(tiki)}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-4 bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
|
||||
<h4 className="font-medium mb-2">Blockchain Address</h4>
|
||||
<p className="text-sm text-muted-foreground font-mono break-all">
|
||||
{selectedAccount.address}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
Reference in New Issue
Block a user