mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-29 08:47:55 +00:00
4f683538d3
Add full internationalization across 127+ components and pages. 790+ translation keys in en, tr, kmr, ckb, ar, fa locales. Remove duplicate keys and delete unused .json locale files.
761 lines
31 KiB
TypeScript
761 lines
31 KiB
TypeScript
import { useEffect, useState, useRef } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
|
|
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
|
import { useDashboard } from '@/contexts/DashboardContext';
|
|
import { FileText, Building2, Home, Bell, ChevronLeft, ChevronRight, Upload, User, Sun, ShieldCheck, Pencil } from 'lucide-react';
|
|
import { useToast } from '@/hooks/use-toast';
|
|
import { getUserRoleCategories } from '@pezkuwi/lib/tiki';
|
|
|
|
// LocalStorage key prefix for citizen profile data
|
|
const CITIZEN_PROFILE_KEY = 'citizen_profile_';
|
|
|
|
interface CitizenProfileData {
|
|
fullName: string;
|
|
fatherName: string;
|
|
location: string;
|
|
photoUrl: string | null;
|
|
}
|
|
|
|
// Mock announcements data
|
|
const announcements = [
|
|
{
|
|
id: 1,
|
|
title: "Bijî Azadiya Kurdistanê! (Long Live Free Kurdistan!)",
|
|
description: "Bi serkeftina sîstema me ya dijîtal, em gaveke din li pêşiya azadiya xwe datînin. (With the success of our digital system, we take another step towards our freedom.)",
|
|
date: "2025-01-19"
|
|
},
|
|
{
|
|
id: 2,
|
|
title: "Daxuyaniya Nû ya Hikûmetê (New Government Announcement)",
|
|
description: "Pergalên nû yên xizmetguzariya dijîtal ji bo hemû welatiyan aktîv bûn. (New digital service systems have been activated for all citizens.)",
|
|
date: "2025-01-18"
|
|
},
|
|
{
|
|
id: 3,
|
|
title: "Civîna Giştî ya Welatiyên (Citizens General Assembly)",
|
|
description: "Civîna mehane ya welatiyên di 25ê vê mehê de pêk tê. Beşdarî bibin! (Monthly citizens assembly takes place on the 25th of this month. Participate!)",
|
|
date: "2025-01-17"
|
|
}
|
|
];
|
|
|
|
export default function Citizens() {
|
|
const { selectedAccount } = usePezkuwi();
|
|
const navigate = useNavigate();
|
|
const { toast } = useToast();
|
|
const { t } = useTranslation();
|
|
const { nftDetails, citizenNumber, loading } = useDashboard();
|
|
const [currentAnnouncementIndex, setCurrentAnnouncementIndex] = useState(0);
|
|
const [uploadingPhoto, setUploadingPhoto] = useState(false);
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
const [showGovDialog, setShowGovDialog] = useState(false);
|
|
const [showCitizensDialog, setShowCitizensDialog] = useState(false);
|
|
const [citizenNumberInput, setCitizenNumberInput] = useState('');
|
|
const [isVerifying, setIsVerifying] = useState(false);
|
|
const [dialogType, setDialogType] = useState<'gov' | 'citizens'>('gov');
|
|
|
|
// Citizen profile from localStorage
|
|
const [citizenProfile, setCitizenProfile] = useState<CitizenProfileData>({
|
|
fullName: '',
|
|
fatherName: '',
|
|
location: '',
|
|
photoUrl: null
|
|
});
|
|
const [showEditModal, setShowEditModal] = useState(false);
|
|
const [editForm, setEditForm] = useState<CitizenProfileData>({
|
|
fullName: '',
|
|
fatherName: '',
|
|
location: '',
|
|
photoUrl: null
|
|
});
|
|
|
|
// Load citizen profile from localStorage on mount
|
|
useEffect(() => {
|
|
if (selectedAccount?.address) {
|
|
const storageKey = CITIZEN_PROFILE_KEY + selectedAccount.address;
|
|
const savedProfile = localStorage.getItem(storageKey);
|
|
if (savedProfile) {
|
|
try {
|
|
const parsed = JSON.parse(savedProfile) as CitizenProfileData;
|
|
setCitizenProfile(parsed);
|
|
setEditForm(parsed);
|
|
} catch (e) {
|
|
console.error('Error parsing saved profile:', e);
|
|
}
|
|
} else {
|
|
// No saved profile - prompt user to fill in their info
|
|
setShowEditModal(true);
|
|
}
|
|
}
|
|
}, [selectedAccount?.address]);
|
|
|
|
// Save citizen profile to localStorage
|
|
const saveCitizenProfile = (data: CitizenProfileData) => {
|
|
if (selectedAccount?.address) {
|
|
const storageKey = CITIZEN_PROFILE_KEY + selectedAccount.address;
|
|
localStorage.setItem(storageKey, JSON.stringify(data));
|
|
setCitizenProfile(data);
|
|
toast({
|
|
title: t('citizens.profileSaved'),
|
|
description: t('citizens.profileSavedDesc')
|
|
});
|
|
}
|
|
};
|
|
|
|
const handlePhotoUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
if (!event.target.files || !event.target.files[0]) {
|
|
return;
|
|
}
|
|
|
|
if (!selectedAccount?.address) {
|
|
toast({
|
|
title: t('citizens.walletNotConnected'),
|
|
description: t('citizens.connectWallet'),
|
|
variant: "destructive"
|
|
});
|
|
return;
|
|
}
|
|
|
|
const file = event.target.files[0];
|
|
|
|
// Validate file type
|
|
if (!file.type.startsWith('image/')) {
|
|
toast({
|
|
title: t('citizens.fileError'),
|
|
description: t('citizens.uploadImageOnly'),
|
|
variant: "destructive"
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Validate file size (max 2MB for localStorage)
|
|
if (file.size > 2 * 1024 * 1024) {
|
|
toast({
|
|
title: t('citizens.fileTooLarge'),
|
|
description: t('citizens.maxFileSize'),
|
|
variant: "destructive"
|
|
});
|
|
return;
|
|
}
|
|
|
|
setUploadingPhoto(true);
|
|
|
|
try {
|
|
// Convert file to base64 data URL
|
|
const dataUrl = await new Promise<string>((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onloadend = () => {
|
|
if (reader.result) {
|
|
resolve(reader.result as string);
|
|
} else {
|
|
reject(new Error('Failed to read file'));
|
|
}
|
|
};
|
|
reader.onerror = () => reject(new Error('FileReader error'));
|
|
reader.readAsDataURL(file);
|
|
});
|
|
|
|
// Save to localStorage with profile
|
|
const updatedProfile = { ...citizenProfile, photoUrl: dataUrl };
|
|
saveCitizenProfile(updatedProfile);
|
|
|
|
toast({
|
|
title: t('citizens.photoUploaded'),
|
|
description: t('citizens.photoUploadedDesc')
|
|
});
|
|
} catch (error) {
|
|
console.error('Photo upload error:', error);
|
|
toast({
|
|
title: t('citizens.uploadError'),
|
|
description: error instanceof Error ? error.message : t('citizens.couldNotUpload'),
|
|
variant: "destructive"
|
|
});
|
|
} finally {
|
|
setUploadingPhoto(false);
|
|
}
|
|
};
|
|
|
|
// Handle case where no wallet is connected
|
|
if (!selectedAccount && !loading) {
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-green-700 via-white to-red-600 flex items-center justify-center">
|
|
<Card className="bg-white/90 backdrop-blur max-w-md">
|
|
<CardContent className="pt-6">
|
|
<div className="text-center space-y-4">
|
|
<p className="text-xl font-bold text-red-700">{t('citizens.connectWalletMsg')}</p>
|
|
<Button
|
|
onClick={() => navigate('/')}
|
|
className="bg-red-600 hover:bg-red-700 text-white"
|
|
>
|
|
<Home className="mr-2 h-4 w-4" />
|
|
{t('citizens.backToHome')}
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const handleCitizensIssue = () => {
|
|
// Check if user has Tiki NFT
|
|
if (!nftDetails.citizenNFT) {
|
|
toast({
|
|
title: t('citizens.noAccess'),
|
|
description: t('citizens.noAccessDesc'),
|
|
variant: "destructive"
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Show citizen number verification dialog for Citizens Issues
|
|
setDialogType('citizens');
|
|
setShowCitizensDialog(true);
|
|
};
|
|
|
|
const handleGovEntrance = () => {
|
|
// Check if user has Tiki NFT
|
|
if (!nftDetails.citizenNFT) {
|
|
toast({
|
|
title: t('citizens.noAccess'),
|
|
description: t('citizens.noAccessDesc'),
|
|
variant: "destructive"
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Navigate directly - GovernmentEntrance will handle verification
|
|
navigate('/citizens/government');
|
|
};
|
|
|
|
const handleVerifyCitizenNumber = () => {
|
|
setIsVerifying(true);
|
|
|
|
// Construct the full citizen number format: #42-0-123456
|
|
const actualCitizenNumber = nftDetails.citizenNFT
|
|
? `#${nftDetails.citizenNFT.collectionId}-${nftDetails.citizenNFT.itemId}-${citizenNumber}`
|
|
: '';
|
|
|
|
// Clean and compare
|
|
const inputCleaned = citizenNumberInput.trim().toUpperCase();
|
|
|
|
if (inputCleaned === actualCitizenNumber.toUpperCase()) {
|
|
setShowGovDialog(false);
|
|
setShowCitizensDialog(false);
|
|
setCitizenNumberInput('');
|
|
|
|
if (dialogType === 'gov') {
|
|
toast({
|
|
title: t('citizens.authSuccess'),
|
|
description: t('citizens.govAuthDesc'),
|
|
});
|
|
navigate('/citizens/government');
|
|
} else {
|
|
toast({
|
|
title: t('citizens.authSuccess'),
|
|
description: t('citizens.citizenAuthDesc'),
|
|
});
|
|
navigate('/citizens/issues');
|
|
}
|
|
} else {
|
|
toast({
|
|
title: t('citizens.wrongNumber'),
|
|
description: t('citizens.wrongNumberDesc'),
|
|
variant: "destructive"
|
|
});
|
|
}
|
|
|
|
setIsVerifying(false);
|
|
};
|
|
|
|
const nextAnnouncement = () => {
|
|
setCurrentAnnouncementIndex((prev) => (prev + 1) % announcements.length);
|
|
};
|
|
|
|
const prevAnnouncement = () => {
|
|
setCurrentAnnouncementIndex((prev) => (prev - 1 + announcements.length) % announcements.length);
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-green-700 via-white to-red-600 flex items-center justify-center">
|
|
<Card className="bg-white/90 backdrop-blur">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center space-x-4">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-red-600"></div>
|
|
<p className="text-gray-700 font-medium">{t('citizens.loading')}</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Get role categories for display
|
|
const roleCategories = getUserRoleCategories(nftDetails.roleNFTs) || {
|
|
government: [],
|
|
citizen: [],
|
|
business: [],
|
|
judicial: []
|
|
};
|
|
if (import.meta.env.DEV) console.log('Role categories:', roleCategories);
|
|
|
|
const currentAnnouncement = announcements[currentAnnouncementIndex];
|
|
|
|
return (
|
|
<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-8">
|
|
{/* Back Button */}
|
|
<div className="mb-6">
|
|
<Button
|
|
onClick={() => navigate('/')}
|
|
variant="outline"
|
|
className="bg-red-600 hover:bg-red-700 border-yellow-400 border-2 text-white font-semibold shadow-lg"
|
|
>
|
|
<Home className="mr-2 h-4 w-4" />
|
|
{t('citizens.backToHome')}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Title Section */}
|
|
<div className="text-center mb-8">
|
|
<h1 className="text-5xl md:text-6xl font-bold text-red-700 mb-3 drop-shadow-lg">
|
|
{t('citizens.portalTitle')}
|
|
</h1>
|
|
<p className="text-xl text-gray-800 font-semibold drop-shadow-md">
|
|
{t('citizens.digitalKurdistan')}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Announcements Widget - Modern Carousel */}
|
|
<div className="max-w-4xl mx-auto mb-8">
|
|
<div className="relative bg-gradient-to-r from-red-600 via-red-500 to-yellow-500 rounded-3xl shadow-xl overflow-hidden">
|
|
<div className="absolute inset-0 bg-black/10"></div>
|
|
<div className="relative p-8">
|
|
<div className="flex items-center justify-between">
|
|
<button
|
|
onClick={prevAnnouncement}
|
|
className="z-10 text-white/80 hover:text-white transition-all hover:scale-110"
|
|
>
|
|
<ChevronLeft className="h-8 w-8" />
|
|
</button>
|
|
|
|
<div className="flex-1 text-center px-6">
|
|
<div className="flex items-center justify-center mb-3">
|
|
<Bell className="h-7 w-7 text-white mr-3 animate-pulse" />
|
|
<h3 className="text-3xl font-bold text-white">{t('citizens.announcements')}</h3>
|
|
</div>
|
|
<h4 className="text-xl font-bold text-white mb-3">{currentAnnouncement.title}</h4>
|
|
<p className="text-white/90 text-base leading-relaxed max-w-2xl mx-auto">{currentAnnouncement.description}</p>
|
|
<p className="text-white/70 text-sm mt-3">{currentAnnouncement.date}</p>
|
|
|
|
{/* Modern dots indicator */}
|
|
<div className="flex justify-center gap-3 mt-5">
|
|
{announcements.map((_, idx) => (
|
|
<button
|
|
key={idx}
|
|
onClick={() => setCurrentAnnouncementIndex(idx)}
|
|
className={`h-2.5 rounded-full transition-all duration-300 ${
|
|
idx === currentAnnouncementIndex
|
|
? 'bg-white w-8'
|
|
: 'bg-white/40 w-2.5 hover:bg-white/60'
|
|
}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
onClick={nextAnnouncement}
|
|
className="z-10 text-white/80 hover:text-white transition-all hover:scale-110"
|
|
>
|
|
<ChevronRight className="h-8 w-8" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Entrance Cards - Grid with exactly 2 cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto mb-12">
|
|
{/* LEFT: Citizens Issues */}
|
|
<Card
|
|
className="bg-white/95 backdrop-blur border-4 border-purple-400 hover:border-purple-600 transition-all shadow-2xl cursor-pointer group hover:scale-105"
|
|
onClick={handleCitizensIssue}
|
|
>
|
|
<CardContent className="p-8 flex flex-col items-center justify-center min-h-[300px]">
|
|
<div className="mb-6">
|
|
<div className="bg-purple-500 w-24 h-24 rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform shadow-xl">
|
|
<FileText className="h-12 w-12 text-white" />
|
|
</div>
|
|
</div>
|
|
<h2 className="text-3xl font-bold text-purple-700 mb-2 text-center">
|
|
{t('citizens.citizenIssues')}
|
|
</h2>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* RIGHT: Gov Entrance */}
|
|
<Card
|
|
className="bg-white/95 backdrop-blur border-4 border-green-400 hover:border-green-600 transition-all shadow-2xl cursor-pointer group hover:scale-105"
|
|
onClick={handleGovEntrance}
|
|
>
|
|
<CardContent className="p-8 flex flex-col items-center justify-center min-h-[300px]">
|
|
<div className="mb-6">
|
|
<div className="bg-green-600 w-24 h-24 rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform shadow-xl">
|
|
<Building2 className="h-12 w-12 text-white" />
|
|
</div>
|
|
</div>
|
|
<h2 className="text-3xl font-bold text-green-700 mb-2 text-center">
|
|
{t('citizens.govEntrance')}
|
|
</h2>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Digital Citizen ID Card - Pure HTML/CSS Design */}
|
|
<div className="max-w-2xl mx-auto">
|
|
<div
|
|
className="relative rounded-2xl overflow-hidden shadow-2xl"
|
|
style={{
|
|
background: 'linear-gradient(135deg, #166534 0%, #16a34a 15%, #ffffff 35%, #ffffff 65%, #dc2626 85%, #991b1b 100%)',
|
|
aspectRatio: '1.586/1'
|
|
}}
|
|
>
|
|
{/* Security Pattern Overlay */}
|
|
<div
|
|
className="absolute inset-0 opacity-[0.03]"
|
|
style={{
|
|
backgroundImage: `repeating-linear-gradient(
|
|
45deg,
|
|
transparent,
|
|
transparent 10px,
|
|
#000 10px,
|
|
#000 11px
|
|
)`
|
|
}}
|
|
/>
|
|
|
|
{/* Top Header Band */}
|
|
<div className="absolute top-0 left-0 right-0 bg-gradient-to-r from-green-800 via-green-700 to-green-800 py-3 px-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
{/* Sun Symbol */}
|
|
<div className="relative">
|
|
<Sun className="h-8 w-8 text-yellow-400 drop-shadow-lg" />
|
|
<div className="absolute inset-0 animate-pulse">
|
|
<Sun className="h-8 w-8 text-yellow-300 opacity-50" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-white font-bold text-sm tracking-wider">KOMARÎ KURDISTAN</div>
|
|
<div className="text-green-200 text-[10px] tracking-wide">REPUBLIC OF KURDISTAN</div>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<div className="text-yellow-400 font-bold text-xs">NASNAMEYA DÎJÎTAL</div>
|
|
<div className="text-green-200 text-[10px]">DIGITAL IDENTITY CARD</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content Area */}
|
|
<div className="absolute top-16 bottom-10 left-0 right-0 px-5 flex flex-col sm:flex-row">
|
|
|
|
{/* Left Section - Photo */}
|
|
<div className="w-1/4 flex flex-col items-center justify-center">
|
|
<div className="relative w-full aspect-[3/4] max-w-[120px] group">
|
|
{/* Photo Frame */}
|
|
<div className="absolute inset-0 bg-gradient-to-br from-green-600 to-red-600 rounded-lg p-[2px]">
|
|
<div className="w-full h-full bg-white rounded-lg overflow-hidden flex items-center justify-center">
|
|
{citizenProfile.photoUrl ? (
|
|
<img src={citizenProfile.photoUrl} alt="Citizen Photo" className="w-full h-full object-cover" />
|
|
) : (
|
|
<div className="w-full h-full bg-gray-100 flex items-center justify-center">
|
|
<User className="w-12 h-12 text-gray-300" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{/* Upload Overlay */}
|
|
<div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity rounded-lg flex items-center justify-center">
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => fileInputRef.current?.click()}
|
|
disabled={uploadingPhoto}
|
|
className="text-white text-xs px-2 py-1 h-auto hover:bg-white/20"
|
|
>
|
|
{uploadingPhoto ? (
|
|
<div className="animate-spin h-4 w-4 border-2 border-white rounded-full border-t-transparent" />
|
|
) : (
|
|
<>
|
|
<Upload className="h-4 w-4 mr-1" />
|
|
{t('citizens.upload')}
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
className="hidden"
|
|
onChange={handlePhotoUpload}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Middle Section - Personal Info */}
|
|
<div className="flex-1 px-4 flex flex-col justify-center space-y-3 relative">
|
|
{/* Edit Button */}
|
|
<button
|
|
onClick={() => {
|
|
setEditForm(citizenProfile);
|
|
setShowEditModal(true);
|
|
}}
|
|
className="absolute -top-2 right-0 bg-white/90 hover:bg-white rounded-full p-1.5 shadow-md transition-colors"
|
|
title={t('citizens.editProfile')}
|
|
>
|
|
<Pencil className="h-3 w-3 text-gray-600" />
|
|
</button>
|
|
|
|
{/* Name */}
|
|
<div className="bg-white/80 backdrop-blur-sm rounded-lg px-3 py-2 border-l-4 border-green-600 shadow-sm">
|
|
<div className="text-[10px] text-gray-500 uppercase tracking-wider font-medium">{t('citizens.nameLabel')}</div>
|
|
<div className="text-sm font-bold text-gray-800 truncate">{citizenProfile.fullName || '...'}</div>
|
|
</div>
|
|
|
|
{/* Father's Name */}
|
|
<div className="bg-white/80 backdrop-blur-sm rounded-lg px-3 py-2 border-l-4 border-yellow-500 shadow-sm">
|
|
<div className="text-[10px] text-gray-500 uppercase tracking-wider font-medium">{t('citizens.fatherNameLabel')}</div>
|
|
<div className="text-sm font-bold text-gray-800 truncate">{citizenProfile.fatherName || '...'}</div>
|
|
</div>
|
|
|
|
{/* Location */}
|
|
<div className="bg-white/80 backdrop-blur-sm rounded-lg px-3 py-2 border-l-4 border-red-600 shadow-sm">
|
|
<div className="text-[10px] text-gray-500 uppercase tracking-wider font-medium">{t('citizens.locationLabel')}</div>
|
|
<div className="text-sm font-bold text-gray-800 truncate">{citizenProfile.location || '...'}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Section - NFT & ID Numbers */}
|
|
<div className="w-full sm:w-1/4 flex flex-row sm:flex-col justify-center items-center gap-2 sm:space-y-2 mt-2 sm:mt-0">
|
|
{/* NFT Badge */}
|
|
<div className="bg-gradient-to-br from-green-700 to-green-900 rounded-xl p-2 sm:p-3 text-center shadow-lg flex-1 sm:w-full sm:max-w-[110px]">
|
|
<div className="text-[8px] sm:text-[9px] text-green-200 font-medium uppercase tracking-wider">NFT ID</div>
|
|
<div className="text-white font-bold text-xs sm:text-sm mt-1">
|
|
{nftDetails.citizenNFT ? `#${nftDetails.citizenNFT.collectionId}-${nftDetails.citizenNFT.itemId}` : 'N/A'}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Citizen Number Badge */}
|
|
<div className="bg-gradient-to-br from-red-700 to-red-900 rounded-xl p-2 sm:p-3 text-center shadow-lg flex-1 sm:w-full sm:max-w-[110px]">
|
|
<div className="text-[8px] sm:text-[9px] text-red-200 font-medium uppercase tracking-wider">Hejmara Welatî</div>
|
|
<div className="text-[7px] sm:text-[8px] text-red-300 mb-1">Citizen No</div>
|
|
<div className="text-white font-bold text-[10px] sm:text-[11px]">
|
|
{nftDetails.citizenNFT ? `#${nftDetails.citizenNFT.collectionId}-${nftDetails.citizenNFT.itemId}-${citizenNumber}` : 'N/A'}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Verified Badge */}
|
|
<div className="flex items-center gap-1 bg-white/90 rounded-full px-2 py-1 shadow">
|
|
<ShieldCheck className="h-3 w-3 text-green-600" />
|
|
<span className="text-[8px] sm:text-[9px] font-semibold text-green-700">VERIFIED</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bottom Footer Band */}
|
|
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-r from-red-800 via-red-700 to-red-800 py-2 px-4">
|
|
<div className="flex items-center justify-between text-[9px]">
|
|
<div className="text-red-200">
|
|
<span className="font-medium">Blockchain:</span> Pezkuwichain People
|
|
</div>
|
|
<div className="text-yellow-400 font-bold tracking-wider">
|
|
BIJÎ KURDISTAN
|
|
</div>
|
|
<div className="text-red-200">
|
|
<span className="font-medium">Type:</span> Welati NFT
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Corner Decorations */}
|
|
<div className="absolute top-14 left-2 w-6 h-6 border-l-2 border-t-2 border-green-600/30 rounded-tl-lg" />
|
|
<div className="absolute top-14 right-2 w-6 h-6 border-r-2 border-t-2 border-red-600/30 rounded-tr-lg" />
|
|
<div className="absolute bottom-8 left-2 w-6 h-6 border-l-2 border-b-2 border-green-600/30 rounded-bl-lg" />
|
|
<div className="absolute bottom-8 right-2 w-6 h-6 border-r-2 border-b-2 border-red-600/30 rounded-br-lg" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Government Entrance - Citizen Number Verification Dialog */}
|
|
<Dialog open={showGovDialog} onOpenChange={setShowGovDialog}>
|
|
<DialogContent className="sm:max-w-md bg-gradient-to-br from-green-700 via-white to-red-600">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-center text-2xl font-bold text-red-700">
|
|
<div className="flex items-center justify-center mb-4">
|
|
<Sun className="h-16 w-16 text-yellow-500 animate-spin" style={{ animationDuration: '8s' }} />
|
|
</div>
|
|
{t('citizens.govDialogTitle')}
|
|
</DialogTitle>
|
|
<DialogDescription className="text-center text-gray-700 font-medium">
|
|
{t('citizens.enterCitizenNumber')}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-4 py-4">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-semibold text-gray-800">
|
|
{t('citizens.citizenNumberLabel')}
|
|
</label>
|
|
<Input
|
|
type="text"
|
|
placeholder="#42-0-123456"
|
|
value={citizenNumberInput}
|
|
onChange={(e) => setCitizenNumberInput(e.target.value)}
|
|
className="text-center font-mono text-lg border-2 border-green-400"
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' && !isVerifying) {
|
|
handleVerifyCitizenNumber();
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
<Button
|
|
onClick={handleVerifyCitizenNumber}
|
|
disabled={isVerifying || !citizenNumberInput.trim()}
|
|
className="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3"
|
|
>
|
|
<ShieldCheck className="mr-2 h-5 w-5" />
|
|
{isVerifying ? t('citizens.verifying') : t('citizens.enter')}
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Citizens Issues - Citizen Number Verification Dialog */}
|
|
<Dialog open={showCitizensDialog} onOpenChange={setShowCitizensDialog}>
|
|
<DialogContent className="sm:max-w-md bg-gradient-to-br from-green-700 via-white to-red-600">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-center text-2xl font-bold text-purple-700">
|
|
<div className="flex items-center justify-center mb-4">
|
|
<Sun className="h-16 w-16 text-yellow-500 animate-spin" style={{ animationDuration: '8s' }} />
|
|
</div>
|
|
{t('citizens.citizenDialogTitle')}
|
|
</DialogTitle>
|
|
<DialogDescription className="text-center text-gray-700 font-medium">
|
|
{t('citizens.enterCitizenNumber')}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-4 py-4">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-semibold text-gray-800">
|
|
{t('citizens.citizenNumberLabel')}
|
|
</label>
|
|
<Input
|
|
type="text"
|
|
placeholder="#42-0-123456"
|
|
value={citizenNumberInput}
|
|
onChange={(e) => setCitizenNumberInput(e.target.value)}
|
|
className="text-center font-mono text-lg border-2 border-purple-400"
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' && !isVerifying) {
|
|
handleVerifyCitizenNumber();
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
<Button
|
|
onClick={handleVerifyCitizenNumber}
|
|
disabled={isVerifying || !citizenNumberInput.trim()}
|
|
className="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-3"
|
|
>
|
|
<ShieldCheck className="mr-2 h-5 w-5" />
|
|
{isVerifying ? t('citizens.verifying') : t('citizens.enter')}
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Edit Profile Modal */}
|
|
<Dialog open={showEditModal} onOpenChange={setShowEditModal}>
|
|
<DialogContent className="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-center text-xl font-bold text-gray-800">
|
|
{t('citizens.editProfile')}
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-4 py-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="edit-name" className="text-sm font-medium">
|
|
{t('citizens.nameLabel')}
|
|
</Label>
|
|
<Input
|
|
id="edit-name"
|
|
type="text"
|
|
placeholder={t('citizens.namePlaceholder')}
|
|
value={editForm.fullName}
|
|
onChange={(e) => setEditForm({ ...editForm, fullName: e.target.value })}
|
|
className="border-2"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="edit-father" className="text-sm font-medium">
|
|
{t('citizens.fatherNameLabel')}
|
|
</Label>
|
|
<Input
|
|
id="edit-father"
|
|
type="text"
|
|
placeholder={t('citizens.fatherNamePlaceholder')}
|
|
value={editForm.fatherName}
|
|
onChange={(e) => setEditForm({ ...editForm, fatherName: e.target.value })}
|
|
className="border-2"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="edit-location" className="text-sm font-medium">
|
|
{t('citizens.locationLabel')}
|
|
</Label>
|
|
<Input
|
|
id="edit-location"
|
|
type="text"
|
|
placeholder={t('citizens.locationPlaceholder')}
|
|
value={editForm.location}
|
|
onChange={(e) => setEditForm({ ...editForm, location: e.target.value })}
|
|
className="border-2"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex gap-2 pt-4">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setShowEditModal(false)}
|
|
className="flex-1"
|
|
>
|
|
{t('common.cancel')}
|
|
</Button>
|
|
<Button
|
|
onClick={() => {
|
|
saveCitizenProfile(editForm);
|
|
setShowEditModal(false);
|
|
}}
|
|
className="flex-1 bg-green-600 hover:bg-green-700"
|
|
>
|
|
{t('citizens.save')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|