mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-30 12:47:56 +00:00
refactor(core): Apply various updates and fixes across components
This commit is contained in:
@@ -27,6 +27,10 @@ import {
|
||||
import { SessionMonitor } from '@/components/security/SessionMonitor';
|
||||
import { PermissionEditor } from '@/components/security/PermissionEditor';
|
||||
import { SecurityAudit } from '@/components/security/SecurityAudit';
|
||||
import { KycApprovalTab } from '@/components/admin/KycApprovalTab';
|
||||
import { CommissionVotingTab } from '@/components/admin/CommissionVotingTab';
|
||||
import { CommissionSetupTab } from '@/components/admin/CommissionSetupTab';
|
||||
|
||||
export default function AdminPanel() {
|
||||
const navigate = useNavigate();
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
@@ -149,38 +153,58 @@ export default function AdminPanel() {
|
||||
</button>
|
||||
<h1 className="text-3xl font-bold mb-8">Admin Panel</h1>
|
||||
|
||||
<Tabs defaultValue="users" className="space-y-4">
|
||||
<TabsList className="grid w-full grid-cols-7">
|
||||
<TabsTrigger value="users">
|
||||
<Users className="mr-2 h-4 w-4" />
|
||||
Users
|
||||
<Tabs defaultValue="setup" className="space-y-4">
|
||||
<TabsList className="grid w-full grid-cols-10 h-auto">
|
||||
<TabsTrigger value="setup" className="flex-col h-auto py-3">
|
||||
<Shield className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Commission<br/>Setup</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="roles">
|
||||
<Shield className="mr-2 h-4 w-4" />
|
||||
Roles
|
||||
<TabsTrigger value="kyc" className="flex-col h-auto py-3">
|
||||
<Users className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">KYC<br/>Approvals</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="sessions">
|
||||
<Monitor className="mr-2 h-4 w-4" />
|
||||
Sessions
|
||||
<TabsTrigger value="voting" className="flex-col h-auto py-3">
|
||||
<Activity className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Commission<br/>Voting</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="permissions">
|
||||
<Lock className="mr-2 h-4 w-4" />
|
||||
Permissions
|
||||
<TabsTrigger value="users" className="flex-col h-auto py-3">
|
||||
<Users className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Users</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="security">
|
||||
<AlertTriangle className="mr-2 h-4 w-4" />
|
||||
Security
|
||||
<TabsTrigger value="roles" className="flex-col h-auto py-3">
|
||||
<Shield className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Roles</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="activity">
|
||||
<Activity className="mr-2 h-4 w-4" />
|
||||
Activity
|
||||
<TabsTrigger value="sessions" className="flex-col h-auto py-3">
|
||||
<Monitor className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Sessions</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="settings">
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
Settings
|
||||
<TabsTrigger value="permissions" className="flex-col h-auto py-3">
|
||||
<Lock className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Permissions</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="security" className="flex-col h-auto py-3">
|
||||
<AlertTriangle className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Security</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="activity" className="flex-col h-auto py-3">
|
||||
<Activity className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Activity</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="settings" className="flex-col h-auto py-3">
|
||||
<Settings className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Settings</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="kyc">
|
||||
<KycApprovalTab />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="voting">
|
||||
<CommissionVotingTab />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="users">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -323,6 +347,9 @@ export default function AdminPanel() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="setup">
|
||||
<CommissionSetupTab />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
||||
+87
-58
@@ -1,79 +1,101 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { CitizenshipModal } from '@/components/citizenship/CitizenshipModal';
|
||||
import { Shield, Users, Award, Globe, ChevronRight, ArrowLeft, Home } from 'lucide-react';
|
||||
import { InviteUserModal } from '@/components/referral/InviteUserModal';
|
||||
import { Shield, Users, Award, Globe, ChevronRight, ArrowLeft, UserPlus } from 'lucide-react';
|
||||
|
||||
const BeCitizen: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isInviteModalOpen, setIsInviteModalOpen] = useState(false);
|
||||
const [referrerAddress, setReferrerAddress] = useState<string | null>(null);
|
||||
|
||||
// Check for referral parameter on mount
|
||||
useEffect(() => {
|
||||
const ref = searchParams.get('ref');
|
||||
if (ref) {
|
||||
setReferrerAddress(ref);
|
||||
// Auto-open modal if coming from referral link
|
||||
setIsModalOpen(true);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-purple-900 via-indigo-900 to-blue-900">
|
||||
<div className="min-h-screen bg-gradient-to-br from-green-700 via-white to-red-600">
|
||||
<div className="container mx-auto px-4 py-16">
|
||||
{/* Back to Home Button */}
|
||||
<div className="mb-8">
|
||||
{/* Back to Home Button and Invite Friend */}
|
||||
<div className="mb-8 flex justify-between items-center">
|
||||
<Button
|
||||
onClick={() => navigate('/')}
|
||||
variant="outline"
|
||||
className="bg-white/10 hover:bg-white/20 border-white/30 text-white"
|
||||
className="bg-red-600 hover:bg-red-700 border-yellow-400 border-2 text-white font-semibold shadow-lg"
|
||||
>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Back to Home
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => setIsInviteModalOpen(true)}
|
||||
className="bg-green-600 hover:bg-green-700 border-yellow-400 border-2 text-white font-semibold shadow-lg"
|
||||
>
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
Invite Friend
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="text-center mb-16">
|
||||
<h1 className="text-5xl md:text-6xl font-bold text-white mb-6">
|
||||
<h1 className="text-5xl md:text-6xl font-bold text-red-700 mb-6 drop-shadow-lg">
|
||||
🏛️ Digital Kurdistan
|
||||
</h1>
|
||||
<h2 className="text-3xl md:text-4xl font-semibold text-cyan-300 mb-4">
|
||||
<h2 className="text-3xl md:text-4xl font-semibold text-green-700 mb-4 drop-shadow-lg">
|
||||
Bibe Welati / Be a Citizen
|
||||
</h2>
|
||||
<p className="text-xl text-gray-200 max-w-3xl mx-auto">
|
||||
<p className="text-xl text-gray-800 font-semibold max-w-3xl mx-auto drop-shadow-md">
|
||||
Join the Digital Kurdistan State as a sovereign citizen. Receive your Welati Tiki NFT and unlock governance, trust scoring, and community benefits.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Benefits Grid */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
||||
<Card className="bg-white/10 backdrop-blur-md border-cyan-500/30 hover:border-cyan-500 transition-all">
|
||||
<Card className="bg-red-50/90 backdrop-blur-md border-red-600/50 hover:border-red-600 transition-all shadow-lg">
|
||||
<CardHeader>
|
||||
<Shield className="h-12 w-12 text-cyan-400 mb-3" />
|
||||
<CardTitle className="text-white">Privacy Protected</CardTitle>
|
||||
<CardDescription className="text-gray-300">
|
||||
<Shield className="h-12 w-12 text-red-600 mb-3" />
|
||||
<CardTitle className="text-red-700 font-bold">Privacy Protected</CardTitle>
|
||||
<CardDescription className="text-gray-700 font-medium">
|
||||
Your data is encrypted with ZK-proofs. Only hashes are stored on-chain.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur-md border-purple-500/30 hover:border-purple-500 transition-all">
|
||||
<Card className="bg-yellow-50/90 backdrop-blur-md border-yellow-600/50 hover:border-yellow-600 transition-all shadow-lg">
|
||||
<CardHeader>
|
||||
<Award className="h-12 w-12 text-purple-400 mb-3" />
|
||||
<CardTitle className="text-white">Welati Tiki NFT</CardTitle>
|
||||
<CardDescription className="text-gray-300">
|
||||
<Award className="h-12 w-12 text-yellow-700 mb-3" />
|
||||
<CardTitle className="text-yellow-800 font-bold">Welati Tiki NFT</CardTitle>
|
||||
<CardDescription className="text-gray-700 font-medium">
|
||||
Receive your unique soulbound citizenship NFT after KYC approval.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur-md border-green-500/30 hover:border-green-500 transition-all">
|
||||
<Card className="bg-green-50/90 backdrop-blur-md border-green-600/50 hover:border-green-600 transition-all shadow-lg">
|
||||
<CardHeader>
|
||||
<Users className="h-12 w-12 text-green-400 mb-3" />
|
||||
<CardTitle className="text-white">Trust Scoring</CardTitle>
|
||||
<CardDescription className="text-gray-300">
|
||||
<Users className="h-12 w-12 text-green-600 mb-3" />
|
||||
<CardTitle className="text-green-700 font-bold">Trust Scoring</CardTitle>
|
||||
<CardDescription className="text-gray-700 font-medium">
|
||||
Build trust through referrals, staking, and community contributions.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur-md border-yellow-500/30 hover:border-yellow-500 transition-all">
|
||||
<Card className="bg-red-50/90 backdrop-blur-md border-red-600/50 hover:border-red-600 transition-all shadow-lg">
|
||||
<CardHeader>
|
||||
<Globe className="h-12 w-12 text-yellow-400 mb-3" />
|
||||
<CardTitle className="text-white">Governance Access</CardTitle>
|
||||
<CardDescription className="text-gray-300">
|
||||
<Globe className="h-12 w-12 text-red-600 mb-3" />
|
||||
<CardTitle className="text-red-700 font-bold">Governance Access</CardTitle>
|
||||
<CardDescription className="text-gray-700 font-medium">
|
||||
Participate in on-chain governance and shape the future of Digital Kurdistan.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
@@ -82,12 +104,12 @@ const BeCitizen: React.FC = () => {
|
||||
|
||||
{/* CTA Section */}
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<Card className="bg-white/5 backdrop-blur-lg border-cyan-500/50">
|
||||
<Card className="bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-400 backdrop-blur-lg border-red-600 border-4 shadow-2xl">
|
||||
<CardContent className="pt-8 pb-8">
|
||||
<div className="text-center space-y-6">
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-white mb-3">Ready to Join?</h3>
|
||||
<p className="text-gray-300 mb-6">
|
||||
<h3 className="text-2xl font-bold text-red-700 mb-3">Ready to Join?</h3>
|
||||
<p className="text-gray-800 font-medium mb-6">
|
||||
Whether you're already a citizen or want to become one, start your journey here.
|
||||
</p>
|
||||
</div>
|
||||
@@ -95,25 +117,25 @@ const BeCitizen: React.FC = () => {
|
||||
<Button
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
size="lg"
|
||||
className="bg-gradient-to-r from-cyan-500 to-purple-600 hover:from-cyan-600 hover:to-purple-700 text-white font-semibold px-8 py-6 text-lg group"
|
||||
className="bg-gradient-to-r from-red-600 to-green-700 hover:from-red-700 hover:to-green-800 text-white font-bold px-8 py-6 text-lg group shadow-xl border-2 border-yellow-300"
|
||||
>
|
||||
<span>Start Citizenship Process</span>
|
||||
<ChevronRight className="ml-2 h-5 w-5 group-hover:translate-x-1 transition-transform" />
|
||||
</Button>
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-4 justify-center items-center text-sm text-gray-400 pt-4">
|
||||
<div className="flex flex-col md:flex-row gap-4 justify-center items-center text-sm text-gray-700 font-medium pt-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="h-4 w-4" />
|
||||
<Shield className="h-4 w-4 text-green-700" />
|
||||
<span>Secure ZK-Proof Authentication</span>
|
||||
</div>
|
||||
<div className="hidden md:block">•</div>
|
||||
<div className="hidden md:block text-red-600">•</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Award className="h-4 w-4" />
|
||||
<Award className="h-4 w-4 text-red-600" />
|
||||
<span>Soulbound NFT Citizenship</span>
|
||||
</div>
|
||||
<div className="hidden md:block">•</div>
|
||||
<div className="hidden md:block text-red-600">•</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="h-4 w-4" />
|
||||
<Globe className="h-4 w-4 text-green-700" />
|
||||
<span>Decentralized Identity</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,18 +146,18 @@ const BeCitizen: React.FC = () => {
|
||||
|
||||
{/* Process Overview */}
|
||||
<div className="mt-16 max-w-5xl mx-auto">
|
||||
<h3 className="text-3xl font-bold text-white text-center mb-8">How It Works</h3>
|
||||
<h3 className="text-3xl font-bold text-red-700 text-center mb-8 drop-shadow-lg">How It Works</h3>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{/* Existing Citizens */}
|
||||
<Card className="bg-white/5 backdrop-blur-md border-cyan-500/30">
|
||||
<Card className="bg-red-50/90 backdrop-blur-md border-red-600/50 shadow-lg">
|
||||
<CardHeader>
|
||||
<div className="bg-cyan-500/20 w-12 h-12 rounded-full flex items-center justify-center mb-4">
|
||||
<span className="text-2xl font-bold text-cyan-400">1</span>
|
||||
<div className="bg-red-600 w-12 h-12 rounded-full flex items-center justify-center mb-4">
|
||||
<span className="text-2xl font-bold text-white">1</span>
|
||||
</div>
|
||||
<CardTitle className="text-white">Already a Citizen?</CardTitle>
|
||||
<CardTitle className="text-red-700 font-bold">Already a Citizen?</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-300 space-y-2 text-sm">
|
||||
<CardContent className="text-gray-700 font-medium space-y-2 text-sm">
|
||||
<p>✓ Enter your Welati Tiki NFT number</p>
|
||||
<p>✓ Verify NFT ownership on-chain</p>
|
||||
<p>✓ Sign authentication challenge</p>
|
||||
@@ -144,14 +166,14 @@ const BeCitizen: React.FC = () => {
|
||||
</Card>
|
||||
|
||||
{/* New Citizens */}
|
||||
<Card className="bg-white/5 backdrop-blur-md border-purple-500/30">
|
||||
<Card className="bg-yellow-50/90 backdrop-blur-md border-yellow-600/50 shadow-lg">
|
||||
<CardHeader>
|
||||
<div className="bg-purple-500/20 w-12 h-12 rounded-full flex items-center justify-center mb-4">
|
||||
<span className="text-2xl font-bold text-purple-400">2</span>
|
||||
<div className="bg-yellow-600 w-12 h-12 rounded-full flex items-center justify-center mb-4">
|
||||
<span className="text-2xl font-bold text-white">2</span>
|
||||
</div>
|
||||
<CardTitle className="text-white">New to Citizenship?</CardTitle>
|
||||
<CardTitle className="text-yellow-800 font-bold">New to Citizenship?</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-300 space-y-2 text-sm">
|
||||
<CardContent className="text-gray-700 font-medium space-y-2 text-sm">
|
||||
<p>✓ Fill detailed KYC application</p>
|
||||
<p>✓ Data encrypted with ZK-proofs</p>
|
||||
<p>✓ Submit for admin approval</p>
|
||||
@@ -160,14 +182,14 @@ const BeCitizen: React.FC = () => {
|
||||
</Card>
|
||||
|
||||
{/* After Citizenship */}
|
||||
<Card className="bg-white/5 backdrop-blur-md border-green-500/30">
|
||||
<Card className="bg-green-50/90 backdrop-blur-md border-green-600/50 shadow-lg">
|
||||
<CardHeader>
|
||||
<div className="bg-green-500/20 w-12 h-12 rounded-full flex items-center justify-center mb-4">
|
||||
<span className="text-2xl font-bold text-green-400">3</span>
|
||||
<div className="bg-green-600 w-12 h-12 rounded-full flex items-center justify-center mb-4">
|
||||
<span className="text-2xl font-bold text-white">3</span>
|
||||
</div>
|
||||
<CardTitle className="text-white">Citizen Benefits</CardTitle>
|
||||
<CardTitle className="text-green-700 font-bold">Citizen Benefits</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-300 space-y-2 text-sm">
|
||||
<CardContent className="text-gray-700 font-medium space-y-2 text-sm">
|
||||
<p>✓ Trust score calculation enabled</p>
|
||||
<p>✓ Governance voting rights</p>
|
||||
<p>✓ Referral tree participation</p>
|
||||
@@ -179,13 +201,13 @@ const BeCitizen: React.FC = () => {
|
||||
|
||||
{/* Security Notice */}
|
||||
<div className="mt-12 max-w-3xl mx-auto">
|
||||
<Card className="bg-yellow-500/10 border-yellow-500/30">
|
||||
<Card className="bg-yellow-50/90 border-yellow-600/50 shadow-lg">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<Shield className="h-6 w-6 text-yellow-400 mt-1 flex-shrink-0" />
|
||||
<div className="text-sm text-gray-200">
|
||||
<p className="font-semibold text-yellow-400 mb-2">Privacy & Security</p>
|
||||
<p>
|
||||
<Shield className="h-6 w-6 text-yellow-700 mt-1 flex-shrink-0" />
|
||||
<div className="text-sm text-gray-700">
|
||||
<p className="font-bold text-yellow-800 mb-2">Privacy & Security</p>
|
||||
<p className="font-medium">
|
||||
Your personal data is encrypted using AES-GCM with your wallet-derived keys.
|
||||
Only commitment hashes are stored on the blockchain. Encrypted data is stored
|
||||
on IPFS and locally on your device. No personal information is ever publicly visible.
|
||||
@@ -198,7 +220,14 @@ const BeCitizen: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Citizenship Modal */}
|
||||
<CitizenshipModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
|
||||
<CitizenshipModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
referrerAddress={referrerAddress}
|
||||
/>
|
||||
|
||||
{/* Invite Friend Modal */}
|
||||
<InviteUserModal isOpen={isInviteModalOpen} onClose={() => setIsInviteModalOpen(false)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+254
-3
@@ -7,10 +7,14 @@ 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, Award, Users, TrendingUp } from 'lucide-react';
|
||||
import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowLeft, Award, Users, TrendingUp, UserMinus } from 'lucide-react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { fetchUserTikis, calculateTikiScore, getPrimaryRole, getTikiDisplayName, getTikiColor, getTikiEmoji, getUserRoleCategories } from '@pezkuwi/lib/tiki';
|
||||
import { fetchUserTikis, calculateTikiScore, getPrimaryRole, getTikiDisplayName, getTikiColor, getTikiEmoji, getUserRoleCategories, getAllTikiNFTDetails, generateCitizenNumber, type TikiNFTDetails } from '@pezkuwi/lib/tiki';
|
||||
import { getAllScores, type UserScores } from '@pezkuwi/lib/scores';
|
||||
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 { user } = useAuth();
|
||||
@@ -28,6 +32,13 @@ export default function Dashboard() {
|
||||
totalScore: 0
|
||||
});
|
||||
const [loadingScores, setLoadingScores] = useState(false);
|
||||
const [kycStatus, setKycStatus] = useState<string>('NotStarted');
|
||||
const [renouncingCitizenship, setRenouncingCitizenship] = useState(false);
|
||||
const [nftDetails, setNftDetails] = useState<{ citizenNFT: TikiNFTDetails | null; roleNFTs: TikiNFTDetails[]; totalNFTs: number }>({
|
||||
citizenNFT: null,
|
||||
roleNFTs: [],
|
||||
totalNFTs: 0
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchProfile();
|
||||
@@ -107,6 +118,14 @@ export default function Dashboard() {
|
||||
// Also fetch tikis separately for role display (needed for role details)
|
||||
const userTikis = await fetchUserTikis(api, selectedAccount.address);
|
||||
setTikis(userTikis);
|
||||
|
||||
// Fetch NFT details including collection/item IDs
|
||||
const details = await getAllTikiNFTDetails(api, selectedAccount.address);
|
||||
setNftDetails(details);
|
||||
|
||||
// Fetch KYC status to determine if user is a citizen
|
||||
const status = await getKycStatus(api, selectedAccount.address);
|
||||
setKycStatus(status);
|
||||
} catch (error) {
|
||||
console.error('Error fetching scores and tikis:', error);
|
||||
} finally {
|
||||
@@ -180,6 +199,98 @@ export default function Dashboard() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenounceCitizenship = async () => {
|
||||
if (!api || !selectedAccount) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Please connect your wallet first",
|
||||
variant: "destructive"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (kycStatus !== 'Approved') {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Only citizens can renounce citizenship",
|
||||
variant: "destructive"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm action
|
||||
const confirmed = window.confirm(
|
||||
'Are you sure you want to renounce your citizenship? This will:\n' +
|
||||
'• Burn your Citizen (Welati) NFT\n' +
|
||||
'• Reset your KYC status to NotStarted\n' +
|
||||
'• Remove all associated citizen privileges\n\n' +
|
||||
'You can always reapply later if you change your mind.'
|
||||
);
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
setRenouncingCitizenship(true);
|
||||
try {
|
||||
const { web3FromAddress } = await import('@polkadot/extension-dapp');
|
||||
const injector = await web3FromAddress(selectedAccount.address);
|
||||
|
||||
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();
|
||||
}
|
||||
console.error(errorMessage);
|
||||
toast({
|
||||
title: "Renunciation Failed",
|
||||
description: errorMessage,
|
||||
variant: "destructive"
|
||||
});
|
||||
setRenouncingCitizenship(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.isInBlock || status.isFinalized) {
|
||||
console.log('✅ Citizenship renounced successfully');
|
||||
|
||||
// Check for CitizenshipRenounced event
|
||||
events.forEach(({ event }) => {
|
||||
if (event.section === 'identityKyc' && event.method === 'CitizenshipRenounced') {
|
||||
console.log('📢 CitizenshipRenounced event detected');
|
||||
toast({
|
||||
title: "Citizenship Renounced",
|
||||
description: "Your citizenship has been successfully renounced. You can reapply anytime."
|
||||
});
|
||||
|
||||
// Refresh data after a short delay
|
||||
setTimeout(() => {
|
||||
fetchScoresAndTikis();
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
setRenouncingCitizenship(false);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err: any) {
|
||||
console.error('Renunciation error:', err);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: err.message || 'Failed to renounce citizenship',
|
||||
variant: "destructive"
|
||||
});
|
||||
setRenouncingCitizenship(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getRoleDisplay = (): string => {
|
||||
if (loadingScores) return 'Loading...';
|
||||
if (!selectedAccount) return 'Member';
|
||||
@@ -342,6 +453,7 @@ export default function Dashboard() {
|
||||
<TabsList>
|
||||
<TabsTrigger value="profile">Profile</TabsTrigger>
|
||||
<TabsTrigger value="roles">Roles & Tikis</TabsTrigger>
|
||||
<TabsTrigger value="referrals">Referrals</TabsTrigger>
|
||||
<TabsTrigger value="security">Security</TabsTrigger>
|
||||
<TabsTrigger value="activity">Activity</TabsTrigger>
|
||||
</TabsList>
|
||||
@@ -436,7 +548,7 @@ export default function Dashboard() {
|
||||
No roles assigned yet
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Complete KYC to become a Citizen (Hemwelatî)
|
||||
Complete KYC to become a Citizen (Welati)
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -475,18 +587,157 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{nftDetails.totalNFTs > 0 && (
|
||||
<div className="border-t pt-4">
|
||||
<h4 className="font-medium mb-3">NFT Details ({nftDetails.totalNFTs})</h4>
|
||||
<div className="space-y-3">
|
||||
{nftDetails.citizenNFT && (
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-medium text-blue-900 dark:text-blue-100">
|
||||
{nftDetails.citizenNFT.tikiEmoji} Citizen NFT
|
||||
</span>
|
||||
<Badge variant="outline" className="text-blue-700 border-blue-300">
|
||||
Primary
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* NFT Number and Citizen Number - Side by Side */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-3">
|
||||
{/* NFT Number */}
|
||||
<div className="p-2 bg-white dark:bg-blue-950 rounded border border-blue-300 dark:border-blue-700">
|
||||
<span className="text-xs text-blue-600 dark:text-blue-400 font-medium">NFT Number:</span>
|
||||
<div className="font-mono text-lg font-bold text-blue-900 dark:text-blue-100">
|
||||
#{nftDetails.citizenNFT.collectionId}-{nftDetails.citizenNFT.itemId}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Citizen Number = NFT Number + 6 digits */}
|
||||
<div className="p-2 bg-white dark:bg-green-950 rounded border border-green-300 dark:border-green-700">
|
||||
<span className="text-xs text-green-600 dark:text-green-400 font-medium">Citizen Number:</span>
|
||||
<div className="font-mono text-lg font-bold text-green-900 dark:text-green-100">
|
||||
#{nftDetails.citizenNFT.collectionId}-{nftDetails.citizenNFT.itemId}-{generateCitizenNumber(
|
||||
nftDetails.citizenNFT.owner,
|
||||
nftDetails.citizenNFT.collectionId,
|
||||
nftDetails.citizenNFT.itemId
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div>
|
||||
<span className="text-blue-700 dark:text-blue-300 font-medium">Collection ID:</span>
|
||||
<span className="ml-2 font-mono text-blue-900 dark:text-blue-100">
|
||||
{nftDetails.citizenNFT.collectionId}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-blue-700 dark:text-blue-300 font-medium">Item ID:</span>
|
||||
<span className="ml-2 font-mono text-blue-900 dark:text-blue-100">
|
||||
{nftDetails.citizenNFT.itemId}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<span className="text-blue-700 dark:text-blue-300 font-medium">Role:</span>
|
||||
<span className="ml-2 text-blue-900 dark:text-blue-100">
|
||||
{nftDetails.citizenNFT.tikiDisplayName}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<span className="text-blue-700 dark:text-blue-300 font-medium">Tiki Type:</span>
|
||||
<span className="ml-2 font-semibold text-purple-600 dark:text-purple-400">
|
||||
{nftDetails.citizenNFT.tikiRole}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nftDetails.roleNFTs.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground font-medium">Additional Role NFTs:</p>
|
||||
{nftDetails.roleNFTs.map((nft, index) => (
|
||||
<div
|
||||
key={`${nft.collectionId}-${nft.itemId}`}
|
||||
className="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-3"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-medium">
|
||||
{nft.tikiEmoji} {nft.tikiDisplayName}
|
||||
</span>
|
||||
<Badge variant="outline" className={nft.tikiColor}>
|
||||
Score: {nft.tikiScore}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm text-muted-foreground">
|
||||
<div>
|
||||
<span className="font-medium">Collection:</span>
|
||||
<span className="ml-2 font-mono">{nft.collectionId}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Item:</span>
|
||||
<span className="ml-2 font-mono">{nft.itemId}</span>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<span className="font-medium">Tiki Type:</span>
|
||||
<span className="ml-2 font-semibold text-purple-600 dark:text-purple-400">{nft.tikiRole}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</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>
|
||||
|
||||
{kycStatus === 'Approved' && (
|
||||
<div className="border-t pt-4">
|
||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
|
||||
<h4 className="font-medium mb-2 text-yellow-800 dark:text-yellow-200 flex items-center gap-2">
|
||||
<UserMinus className="h-4 w-4" />
|
||||
Renounce Citizenship
|
||||
</h4>
|
||||
<p className="text-sm text-yellow-700 dark:text-yellow-300 mb-3">
|
||||
You can voluntarily renounce your citizenship at any time. This will:
|
||||
</p>
|
||||
<ul className="text-sm text-yellow-700 dark:text-yellow-300 mb-3 list-disc list-inside space-y-1">
|
||||
<li>Burn your Citizen (Welati) NFT</li>
|
||||
<li>Reset your KYC status</li>
|
||||
<li>Remove citizen privileges</li>
|
||||
</ul>
|
||||
<p className="text-xs text-yellow-600 dark:text-yellow-400 mb-3">
|
||||
Note: You can always reapply for citizenship later if you change your mind.
|
||||
</p>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={handleRenounceCitizenship}
|
||||
disabled={renouncingCitizenship}
|
||||
>
|
||||
{renouncingCitizenship ? 'Renouncing...' : 'Renounce Citizenship'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="referrals" className="space-y-4">
|
||||
<ReferralDashboard />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="security" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
||||
@@ -320,8 +320,8 @@ const Login: React.FC = () => {
|
||||
<Input
|
||||
id="referral-code"
|
||||
type="text"
|
||||
placeholder={t('login.enterReferralCode', 'Enter referral code')}
|
||||
className="pl-10 bg-gray-800 border-gray-700 text-white"
|
||||
placeholder={t('login.enterReferralCode', 'Referral code (optional)')}
|
||||
className="pl-10 bg-gray-800 border-gray-700 text-white placeholder:text-gray-500 placeholder:opacity-50"
|
||||
value={signupData.referralCode}
|
||||
onChange={(e) => setSignupData({...signupData, referralCode: e.target.value})}
|
||||
/>
|
||||
|
||||
@@ -163,6 +163,13 @@ export default function CitizensIssues() {
|
||||
if (!api || !isApiReady) return;
|
||||
|
||||
try {
|
||||
// Check if welati pallet exists
|
||||
if (!api.query.welati) {
|
||||
console.log('Welati pallet not available yet');
|
||||
setIssues([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const issueCountResult = await api.query.welati.issueCount();
|
||||
const issueCount = issueCountResult.toNumber();
|
||||
|
||||
@@ -188,11 +195,7 @@ export default function CitizensIssues() {
|
||||
setIssues(fetchedIssues.reverse());
|
||||
} catch (error) {
|
||||
console.error('Error fetching issues:', error);
|
||||
toast({
|
||||
title: 'Xeletî (Error)',
|
||||
description: 'Pirsgirêk di barkirina pirsan de (Error loading issues)',
|
||||
variant: 'destructive'
|
||||
});
|
||||
setIssues([]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -200,6 +203,13 @@ export default function CitizensIssues() {
|
||||
if (!api || !isApiReady || !selectedAccount) return;
|
||||
|
||||
try {
|
||||
// Check if welati pallet exists
|
||||
if (!api.query.welati) {
|
||||
console.log('Welati pallet not available yet');
|
||||
setUserVotes(new Map());
|
||||
return;
|
||||
}
|
||||
|
||||
const votesEntries = await api.query.welati.issueVotes.entries(selectedAccount.address);
|
||||
const votes = new Map<number, boolean>();
|
||||
|
||||
@@ -212,6 +222,7 @@ export default function CitizensIssues() {
|
||||
setUserVotes(votes);
|
||||
} catch (error) {
|
||||
console.error('Error fetching user votes:', error);
|
||||
setUserVotes(new Map());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -304,6 +315,13 @@ export default function CitizensIssues() {
|
||||
if (!api || !isApiReady) return;
|
||||
|
||||
try {
|
||||
// Check if welati pallet exists
|
||||
if (!api.query.welati) {
|
||||
console.log('Welati pallet not available yet');
|
||||
setParliamentCandidates([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const candidatesEntries = await api.query.welati.parliamentCandidates.entries();
|
||||
const candidates: ParliamentCandidate[] = [];
|
||||
|
||||
@@ -333,6 +351,7 @@ export default function CitizensIssues() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching parliament candidates:', error);
|
||||
setParliamentCandidates([]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -450,6 +469,13 @@ export default function CitizensIssues() {
|
||||
if (!api || !isApiReady) return;
|
||||
|
||||
try {
|
||||
// Check if welati pallet exists
|
||||
if (!api.query.welati) {
|
||||
console.log('Welati pallet not available yet');
|
||||
setPresidentCandidates([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const candidatesEntries = await api.query.welati.presidentCandidates.entries();
|
||||
const candidates: PresidentCandidate[] = [];
|
||||
|
||||
@@ -479,6 +505,7 @@ export default function CitizensIssues() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching president candidates:', error);
|
||||
setPresidentCandidates([]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -596,6 +623,14 @@ export default function CitizensIssues() {
|
||||
if (!api || !isApiReady) return;
|
||||
|
||||
try {
|
||||
// Check if welati pallet exists
|
||||
if (!api.query.welati) {
|
||||
console.log('Welati pallet not available yet');
|
||||
setLegislationProposals([]);
|
||||
setUserLegislationVotes(new Map());
|
||||
return;
|
||||
}
|
||||
|
||||
const proposalsEntries = await api.query.welati.legislationProposals.entries();
|
||||
const proposals: LegislationProposal[] = [];
|
||||
|
||||
@@ -631,6 +666,8 @@ export default function CitizensIssues() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching legislation proposals:', error);
|
||||
setLegislationProposals([]);
|
||||
setUserLegislationVotes(new Map());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1085,9 +1122,10 @@ export default function CitizensIssues() {
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<Input
|
||||
placeholder="5Abc123... (Substrate Address)"
|
||||
placeholder="Candidate address"
|
||||
value={nominateParliamentAddress}
|
||||
onChange={(e) => setNominateParliamentAddress(e.target.value)}
|
||||
className="placeholder:text-gray-500 placeholder:opacity-50"
|
||||
/>
|
||||
<Button
|
||||
onClick={handleNominateParliament}
|
||||
@@ -1196,9 +1234,10 @@ export default function CitizensIssues() {
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<Input
|
||||
placeholder="5Abc123... (Substrate Address)"
|
||||
placeholder="Candidate address"
|
||||
value={nominatePresidentAddress}
|
||||
onChange={(e) => setNominatePresidentAddress(e.target.value)}
|
||||
className="placeholder:text-gray-500 placeholder:opacity-50"
|
||||
/>
|
||||
<Button
|
||||
onClick={handleNominatePresident}
|
||||
|
||||
Reference in New Issue
Block a user