mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-22 04:27:56 +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:
+400
@@ -0,0 +1,400 @@
|
||||
// ========================================
|
||||
// Pallet-Tiki Integration
|
||||
// ========================================
|
||||
// This file handles all tiki-related blockchain interactions
|
||||
// Based on: /Pezkuwi-SDK/pezkuwi/pallets/tiki/src/lib.rs
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
|
||||
// ========================================
|
||||
// TIKI TYPES (from Rust enum)
|
||||
// ========================================
|
||||
export enum Tiki {
|
||||
// Otomatik - KYC sonrası
|
||||
Hemwelatî = 'Hemwelatî',
|
||||
|
||||
// Seçilen roller (Elected)
|
||||
Parlementer = 'Parlementer',
|
||||
SerokiMeclise = 'SerokiMeclise',
|
||||
Serok = 'Serok',
|
||||
|
||||
// Atanan roller (Appointed) - Yargı
|
||||
EndameDiwane = 'EndameDiwane',
|
||||
Dadger = 'Dadger',
|
||||
Dozger = 'Dozger',
|
||||
Hiquqnas = 'Hiquqnas',
|
||||
Noter = 'Noter',
|
||||
|
||||
// Atanan roller - Yürütme
|
||||
Wezir = 'Wezir',
|
||||
SerokWeziran = 'SerokWeziran',
|
||||
WezireDarayiye = 'WezireDarayiye',
|
||||
WezireParez = 'WezireParez',
|
||||
WezireDad = 'WezireDad',
|
||||
WezireBelaw = 'WezireBelaw',
|
||||
WezireTend = 'WezireTend',
|
||||
WezireAva = 'WezireAva',
|
||||
WezireCand = 'WezireCand',
|
||||
|
||||
// Atanan roller - İdari
|
||||
Xezinedar = 'Xezinedar',
|
||||
Bacgir = 'Bacgir',
|
||||
GerinendeyeCavkaniye = 'GerinendeyeCavkaniye',
|
||||
OperatorêTorê = 'OperatorêTorê',
|
||||
PisporêEwlehiyaSîber = 'PisporêEwlehiyaSîber',
|
||||
GerinendeyeDaneye = 'GerinendeyeDaneye',
|
||||
Berdevk = 'Berdevk',
|
||||
Qeydkar = 'Qeydkar',
|
||||
Balyoz = 'Balyoz',
|
||||
Navbeynkar = 'Navbeynkar',
|
||||
ParêzvaneÇandî = 'ParêzvaneÇandî',
|
||||
Mufetîs = 'Mufetîs',
|
||||
KalîteKontrolker = 'KalîteKontrolker',
|
||||
|
||||
// Atanan roller - Kültürel/Dini
|
||||
Mela = 'Mela',
|
||||
Feqî = 'Feqî',
|
||||
Perwerdekar = 'Perwerdekar',
|
||||
Rewsenbîr = 'Rewsenbîr',
|
||||
RêveberêProjeyê = 'RêveberêProjeyê',
|
||||
SerokêKomele = 'SerokêKomele',
|
||||
ModeratorêCivakê = 'ModeratorêCivakê',
|
||||
|
||||
// Kazanılan roller (Earned)
|
||||
Axa = 'Axa',
|
||||
Pêseng = 'Pêseng',
|
||||
Sêwirmend = 'Sêwirmend',
|
||||
Hekem = 'Hekem',
|
||||
Mamoste = 'Mamoste',
|
||||
|
||||
// Ekonomik rol
|
||||
Bazargan = 'Bazargan',
|
||||
}
|
||||
|
||||
// Role assignment types
|
||||
export enum RoleAssignmentType {
|
||||
Automatic = 'Automatic',
|
||||
Appointed = 'Appointed',
|
||||
Elected = 'Elected',
|
||||
Earned = 'Earned',
|
||||
}
|
||||
|
||||
// Tiki to Display Name mapping (English)
|
||||
export const TIKI_DISPLAY_NAMES: Record<string, string> = {
|
||||
Hemwelatî: 'Citizen',
|
||||
Parlementer: 'Parliament Member',
|
||||
SerokiMeclise: 'Speaker of Parliament',
|
||||
Serok: 'President',
|
||||
Wezir: 'Minister',
|
||||
SerokWeziran: 'Prime Minister',
|
||||
WezireDarayiye: 'Minister of Finance',
|
||||
WezireParez: 'Minister of Defense',
|
||||
WezireDad: 'Minister of Justice',
|
||||
WezireBelaw: 'Minister of Education',
|
||||
WezireTend: 'Minister of Health',
|
||||
WezireAva: 'Minister of Water',
|
||||
WezireCand: 'Minister of Culture',
|
||||
EndameDiwane: 'Supreme Court Member',
|
||||
Dadger: 'Judge',
|
||||
Dozger: 'Prosecutor',
|
||||
Hiquqnas: 'Lawyer',
|
||||
Noter: 'Notary',
|
||||
Xezinedar: 'Treasurer',
|
||||
Bacgir: 'Tax Collector',
|
||||
GerinendeyeCavkaniye: 'Resource Manager',
|
||||
OperatorêTorê: 'Network Operator',
|
||||
PisporêEwlehiyaSîber: 'Cybersecurity Expert',
|
||||
GerinendeyeDaneye: 'Data Manager',
|
||||
Berdevk: 'Representative',
|
||||
Qeydkar: 'Registrar',
|
||||
Balyoz: 'Ambassador',
|
||||
Navbeynkar: 'Mediator',
|
||||
ParêzvaneÇandî: 'Cultural Guardian',
|
||||
Mufetîs: 'Inspector',
|
||||
KalîteKontrolker: 'Quality Controller',
|
||||
Mela: 'Religious Scholar',
|
||||
Feqî: 'Religious Jurist',
|
||||
Perwerdekar: 'Educator',
|
||||
Rewsenbîr: 'Intellectual',
|
||||
RêveberêProjeyê: 'Project Manager',
|
||||
SerokêKomele: 'Community Leader',
|
||||
ModeratorêCivakê: 'Community Moderator',
|
||||
Axa: 'Elder',
|
||||
Pêseng: 'Pioneer',
|
||||
Sêwirmend: 'Advisor',
|
||||
Hekem: 'Expert',
|
||||
Mamoste: 'Teacher',
|
||||
Bazargan: 'Merchant',
|
||||
};
|
||||
|
||||
// Tiki scores (from get_bonus_for_tiki function)
|
||||
export const TIKI_SCORES: Record<string, number> = {
|
||||
Axa: 250,
|
||||
RêveberêProjeyê: 250,
|
||||
ModeratorêCivakê: 200,
|
||||
Serok: 200,
|
||||
EndameDiwane: 175,
|
||||
Dadger: 150,
|
||||
SerokiMeclise: 150,
|
||||
SerokWeziran: 125,
|
||||
Dozger: 120,
|
||||
Serok: 100,
|
||||
Wezir: 100,
|
||||
WezireDarayiye: 100,
|
||||
WezireParez: 100,
|
||||
WezireDad: 100,
|
||||
WezireBelaw: 100,
|
||||
WezireTend: 100,
|
||||
WezireAva: 100,
|
||||
WezireCand: 100,
|
||||
SerokêKomele: 100,
|
||||
Xezinedar: 100,
|
||||
PisporêEwlehiyaSîber: 100,
|
||||
Parlementer: 100,
|
||||
Mufetîs: 90,
|
||||
Balyoz: 80,
|
||||
Hiquqnas: 75,
|
||||
Berdevk: 70,
|
||||
Mamoste: 70,
|
||||
Bazargan: 60,
|
||||
OperatorêTorê: 60,
|
||||
Mela: 50,
|
||||
Feqî: 50,
|
||||
Noter: 50,
|
||||
Bacgir: 50,
|
||||
Perwerdekar: 40,
|
||||
Rewsenbîr: 40,
|
||||
GerinendeyeCavkaniye: 40,
|
||||
GerinendeyeDaneye: 40,
|
||||
KalîteKontrolker: 30,
|
||||
Navbeynkar: 30,
|
||||
Hekem: 30,
|
||||
Qeydkar: 25,
|
||||
ParêzvaneÇandî: 25,
|
||||
Sêwirmend: 20,
|
||||
Hemwelatî: 10,
|
||||
Pêseng: 5, // Default for unlisted
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// ROLE CATEGORIZATION
|
||||
// ========================================
|
||||
|
||||
export const ROLE_CATEGORIES: Record<string, string[]> = {
|
||||
Government: ['Serok', 'SerokWeziran', 'Wezir', 'WezireDarayiye', 'WezireParez', 'WezireDad', 'WezireBelaw', 'WezireTend', 'WezireAva', 'WezireCand'],
|
||||
Legislature: ['Parlementer', 'SerokiMeclise'],
|
||||
Judiciary: ['EndameDiwane', 'Dadger', 'Dozger', 'Hiquqnas', 'Noter'],
|
||||
Administration: ['Xezinedar', 'Bacgir', 'Berdevk', 'Qeydkar', 'Balyoz', 'Mufetîs'],
|
||||
Technical: ['OperatorêTorê', 'PisporêEwlehiyaSîber', 'GerinendeyeDaneye', 'GerinendeyeCavkaniye'],
|
||||
Cultural: ['Mela', 'Feqî', 'ParêzvaneÇandî'],
|
||||
Education: ['Mamoste', 'Perwerdekar', 'Rewsenbîr'],
|
||||
Community: ['SerokêKomele', 'ModeratorêCivakê', 'Axa', 'Navbeynkar', 'Sêwirmend', 'Hekem'],
|
||||
Economic: ['Bazargan'],
|
||||
Leadership: ['RêveberêProjeyê', 'Pêseng'],
|
||||
Quality: ['KalîteKontrolker'],
|
||||
Citizen: ['Hemwelatî'],
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// TIKI QUERY FUNCTIONS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Fetch user's tiki roles from blockchain
|
||||
* @param api - Polkadot API instance
|
||||
* @param address - User's substrate address
|
||||
* @returns Array of tiki role strings
|
||||
*/
|
||||
export const fetchUserTikis = async (
|
||||
api: ApiPromise,
|
||||
address: string
|
||||
): Promise<string[]> => {
|
||||
try {
|
||||
if (!api || !api.query.tiki) {
|
||||
console.warn('Tiki pallet not available on this chain');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Query UserTikis storage
|
||||
const tikis = await api.query.tiki.userTikis(address);
|
||||
const tikisArray = tikis.toJSON() as any[];
|
||||
|
||||
if (!tikisArray || tikisArray.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Convert from enum index to string names
|
||||
return tikisArray.map((tikiIndex: any) => {
|
||||
// Tikis are stored as enum variants
|
||||
if (typeof tikiIndex === 'string') {
|
||||
return tikiIndex;
|
||||
} else if (typeof tikiIndex === 'object' && tikiIndex !== null) {
|
||||
// Handle object variant format {variantName: null}
|
||||
return Object.keys(tikiIndex)[0];
|
||||
}
|
||||
return 'Unknown';
|
||||
}).filter((t: string) => t !== 'Unknown');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching user tikis:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if user is a citizen (has Hemwelatî tiki)
|
||||
* @param api - Polkadot API instance
|
||||
* @param address - User's substrate address
|
||||
* @returns boolean
|
||||
*/
|
||||
export const isCitizen = async (
|
||||
api: ApiPromise,
|
||||
address: string
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
if (!api || !api.query.tiki) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const citizenNft = await api.query.tiki.citizenNft(address);
|
||||
return !citizenNft.isEmpty;
|
||||
} catch (error) {
|
||||
console.error('Error checking citizenship:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate total tiki score for a user
|
||||
* @param tikis - Array of tiki strings
|
||||
* @returns Total score
|
||||
*/
|
||||
export const calculateTikiScore = (tikis: string[]): number => {
|
||||
return tikis.reduce((total, tiki) => {
|
||||
return total + (TIKI_SCORES[tiki] || 5);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get primary role (highest scoring) from tikis
|
||||
* @param tikis - Array of tiki strings
|
||||
* @returns Primary role string
|
||||
*/
|
||||
export const getPrimaryRole = (tikis: string[]): string => {
|
||||
if (!tikis || tikis.length === 0) {
|
||||
return 'Member';
|
||||
}
|
||||
|
||||
// Find highest scoring role
|
||||
let primaryRole = tikis[0];
|
||||
let highestScore = TIKI_SCORES[tikis[0]] || 5;
|
||||
|
||||
for (const tiki of tikis) {
|
||||
const score = TIKI_SCORES[tiki] || 5;
|
||||
if (score > highestScore) {
|
||||
highestScore = score;
|
||||
primaryRole = tiki;
|
||||
}
|
||||
}
|
||||
|
||||
return primaryRole;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get display name for a tiki
|
||||
* @param tiki - Tiki string
|
||||
* @returns Display name
|
||||
*/
|
||||
export const getTikiDisplayName = (tiki: string): string => {
|
||||
return TIKI_DISPLAY_NAMES[tiki] || tiki;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all role categories for user's tikis
|
||||
* @param tikis - Array of tiki strings
|
||||
* @returns Array of category names
|
||||
*/
|
||||
export const getUserRoleCategories = (tikis: string[]): string[] => {
|
||||
const categories = new Set<string>();
|
||||
|
||||
for (const tiki of tikis) {
|
||||
for (const [category, roles] of Object.entries(ROLE_CATEGORIES)) {
|
||||
if (roles.includes(tiki)) {
|
||||
categories.add(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(categories);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if user has a specific tiki
|
||||
* @param tikis - Array of tiki strings
|
||||
* @param tiki - Tiki to check
|
||||
* @returns boolean
|
||||
*/
|
||||
export const hasTiki = (tikis: string[], tiki: string): boolean => {
|
||||
return tikis.includes(tiki);
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// DISPLAY HELPERS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get color for a tiki role
|
||||
* @param tiki - Tiki string
|
||||
* @returns Tailwind color class
|
||||
*/
|
||||
export const getTikiColor = (tiki: string): string => {
|
||||
const score = TIKI_SCORES[tiki] || 5;
|
||||
|
||||
if (score >= 200) return 'text-purple-500';
|
||||
if (score >= 150) return 'text-pink-500';
|
||||
if (score >= 100) return 'text-blue-500';
|
||||
if (score >= 70) return 'text-cyan-500';
|
||||
if (score >= 40) return 'text-teal-500';
|
||||
if (score >= 20) return 'text-green-500';
|
||||
return 'text-gray-500';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get emoji icon for a tiki category
|
||||
* @param tiki - Tiki string
|
||||
* @returns Emoji string
|
||||
*/
|
||||
export const getTikiEmoji = (tiki: string): string => {
|
||||
for (const [category, roles] of Object.entries(ROLE_CATEGORIES)) {
|
||||
if (roles.includes(tiki)) {
|
||||
switch (category) {
|
||||
case 'Government': return '👑';
|
||||
case 'Legislature': return '🏛️';
|
||||
case 'Judiciary': return '⚖️';
|
||||
case 'Administration': return '📋';
|
||||
case 'Technical': return '💻';
|
||||
case 'Cultural': return '📿';
|
||||
case 'Education': return '👨🏫';
|
||||
case 'Community': return '🤝';
|
||||
case 'Economic': return '💰';
|
||||
case 'Leadership': return '⭐';
|
||||
case 'Quality': return '✅';
|
||||
case 'Citizen': return '👤';
|
||||
}
|
||||
}
|
||||
}
|
||||
return '👤';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get badge variant for a tiki
|
||||
* @param tiki - Tiki string
|
||||
* @returns Badge variant string
|
||||
*/
|
||||
export const getTikiBadgeVariant = (tiki: string): 'default' | 'secondary' | 'destructive' | 'outline' => {
|
||||
const score = TIKI_SCORES[tiki] || 5;
|
||||
|
||||
if (score >= 150) return 'default'; // Purple/blue for high ranks
|
||||
if (score >= 70) return 'secondary'; // Gray for mid ranks
|
||||
return 'outline'; // Outline for low ranks
|
||||
};
|
||||
+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