fix: citizen portal mobile responsive layout

- Shrink title/banner fonts and padding on mobile
- Move digital ID card above entrance buttons
- Auto-resize uploaded photos via canvas instead of 2MB limit
- Make entrance cards compact 2-column grid on all screens
This commit is contained in:
2026-02-24 10:26:25 +03:00
parent 6c4960c32a
commit 570e426333
+83 -69
View File
@@ -133,31 +133,45 @@ export default function Citizens() {
return; 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); setUploadingPhoto(true);
try { try {
// Convert file to base64 data URL // Load image, resize to fit ID card photo area, compress to JPEG
const dataUrl = await new Promise<string>((resolve, reject) => { const dataUrl = await new Promise<string>((resolve, reject) => {
const reader = new FileReader(); const img = new Image();
reader.onloadend = () => { img.onload = () => {
if (reader.result) { const MAX_DIM = 400; // max width or height in px
resolve(reader.result as string); let { width, height } = img;
} else {
reject(new Error('Failed to read file')); // Scale down if larger than MAX_DIM
if (width > MAX_DIM || height > MAX_DIM) {
const ratio = Math.min(MAX_DIM / width, MAX_DIM / height);
width = Math.round(width * ratio);
height = Math.round(height * ratio);
} }
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) { reject(new Error('Canvas not supported')); return; }
ctx.drawImage(img, 0, 0, width, height);
// Compress as JPEG, start at 0.85 quality, reduce if still too large
let quality = 0.85;
let result = canvas.toDataURL('image/jpeg', quality);
// Ensure it fits in localStorage (target < 500KB base64)
while (result.length > 500_000 && quality > 0.3) {
quality -= 0.1;
result = canvas.toDataURL('image/jpeg', quality);
}
resolve(result);
}; };
reader.onerror = () => reject(new Error('FileReader error')); img.onerror = () => reject(new Error('Failed to load image'));
reader.readAsDataURL(file); img.src = URL.createObjectURL(file);
}); });
// Save to localStorage with profile // Save to localStorage with profile
@@ -323,35 +337,35 @@ export default function Citizens() {
</div> </div>
{/* Title Section */} {/* Title Section */}
<div className="text-center mb-8"> <div className="text-center mb-4 sm:mb-8">
<h1 className="text-5xl md:text-6xl font-bold text-red-700 mb-3 drop-shadow-lg"> <h1 className="text-3xl sm:text-5xl md:text-6xl font-bold text-red-700 mb-3 drop-shadow-lg">
{t('citizens.portalTitle')} {t('citizens.portalTitle')}
</h1> </h1>
<p className="text-xl text-gray-800 font-semibold drop-shadow-md"> <p className="text-base sm:text-xl text-gray-800 font-semibold drop-shadow-md">
{t('citizens.digitalKurdistan')} {t('citizens.digitalKurdistan')}
</p> </p>
</div> </div>
{/* Announcements Widget - Modern Carousel */} {/* Announcements Widget - Modern Carousel */}
<div className="max-w-4xl mx-auto mb-8"> <div className="max-w-4xl mx-auto mb-6 sm: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="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="absolute inset-0 bg-black/10"></div>
<div className="relative p-8"> <div className="relative p-4 sm:p-8">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<button <button
onClick={prevAnnouncement} onClick={prevAnnouncement}
className="z-10 text-white/80 hover:text-white transition-all hover:scale-110" className="z-10 text-white/80 hover:text-white transition-all hover:scale-110"
> >
<ChevronLeft className="h-8 w-8" /> <ChevronLeft className="h-6 w-6 sm:h-8 sm:w-8" />
</button> </button>
<div className="flex-1 text-center px-6"> <div className="flex-1 text-center px-3 sm:px-6">
<div className="flex items-center justify-center mb-3"> <div className="flex items-center justify-center mb-3">
<Bell className="h-7 w-7 text-white mr-3 animate-pulse" /> <Bell className="h-5 w-5 sm:h-7 sm:w-7 text-white mr-2 sm:mr-3 animate-pulse" />
<h3 className="text-3xl font-bold text-white">{t('citizens.announcements')}</h3> <h3 className="text-xl sm:text-3xl font-bold text-white">{t('citizens.announcements')}</h3>
</div> </div>
<h4 className="text-xl font-bold text-white mb-3">{currentAnnouncement.title}</h4> <h4 className="text-base sm:text-xl font-bold text-white mb-2 sm: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/90 text-sm sm:text-base leading-relaxed max-w-2xl mx-auto">{currentAnnouncement.description}</p>
<p className="text-white/70 text-sm mt-3">{currentAnnouncement.date}</p> <p className="text-white/70 text-sm mt-3">{currentAnnouncement.date}</p>
{/* Modern dots indicator */} {/* Modern dots indicator */}
@@ -374,52 +388,15 @@ export default function Citizens() {
onClick={nextAnnouncement} onClick={nextAnnouncement}
className="z-10 text-white/80 hover:text-white transition-all hover:scale-110" className="z-10 text-white/80 hover:text-white transition-all hover:scale-110"
> >
<ChevronRight className="h-8 w-8" /> <ChevronRight className="h-6 w-6 sm:h-8 sm:w-8" />
</button> </button>
</div> </div>
</div> </div>
</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 */} {/* Digital Citizen ID Card - Pure HTML/CSS Design */}
<div className="max-w-2xl mx-auto"> <div className="max-w-lg sm:max-w-2xl mx-auto mb-6 sm:mb-8">
<div <div
className="relative rounded-2xl overflow-hidden shadow-2xl" className="relative rounded-2xl overflow-hidden shadow-2xl"
style={{ style={{
@@ -594,6 +571,43 @@ export default function Citizens() {
</div> </div>
</div> </div>
{/* Main Entrance Cards - Compact 2-column buttons */}
<div className="grid grid-cols-2 gap-3 sm:gap-4 max-w-5xl mx-auto mb-6 sm:mb-8">
{/* LEFT: Citizens Issues */}
<Card
className="bg-white/95 backdrop-blur border-2 border-purple-400 hover:border-purple-600 transition-all shadow-lg cursor-pointer group sm:hover:scale-105"
onClick={handleCitizensIssue}
>
<CardContent className="p-3 sm:p-4 flex flex-col items-center justify-center">
<div className="mb-2 sm:mb-3">
<div className="bg-purple-500 w-10 h-10 sm:w-12 sm:h-12 rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform shadow-lg">
<FileText className="h-5 w-5 sm:h-6 sm:w-6 text-white" />
</div>
</div>
<h2 className="text-sm sm:text-lg font-bold text-purple-700 text-center">
{t('citizens.citizenIssues')}
</h2>
</CardContent>
</Card>
{/* RIGHT: Gov Entrance */}
<Card
className="bg-white/95 backdrop-blur border-2 border-green-400 hover:border-green-600 transition-all shadow-lg cursor-pointer group sm:hover:scale-105"
onClick={handleGovEntrance}
>
<CardContent className="p-3 sm:p-4 flex flex-col items-center justify-center">
<div className="mb-2 sm:mb-3">
<div className="bg-green-600 w-10 h-10 sm:w-12 sm:h-12 rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform shadow-lg">
<Building2 className="h-5 w-5 sm:h-6 sm:w-6 text-white" />
</div>
</div>
<h2 className="text-sm sm:text-lg font-bold text-green-700 text-center">
{t('citizens.govEntrance')}
</h2>
</CardContent>
</Card>
</div>
{/* Government Entrance - Citizen Number Verification Dialog */} {/* Government Entrance - Citizen Number Verification Dialog */}
<Dialog open={showGovDialog} onOpenChange={setShowGovDialog}> <Dialog open={showGovDialog} onOpenChange={setShowGovDialog}>
<DialogContent className="sm:max-w-md bg-gradient-to-br from-green-700 via-white to-red-600"> <DialogContent className="sm:max-w-md bg-gradient-to-br from-green-700 via-white to-red-600">