diff --git a/web/src/App.tsx b/web/src/App.tsx
index 1119da75..355c2523 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -33,6 +33,7 @@ const AdminPanel = lazy(() => import('@/pages/AdminPanel'));
const WalletDashboard = lazy(() => import('./pages/WalletDashboard'));
const ReservesDashboardPage = lazy(() => import('./pages/ReservesDashboardPage'));
const BeCitizen = lazy(() => import('./pages/BeCitizen'));
+const Identity = lazy(() => import('./pages/Identity'));
const Citizens = lazy(() => import('./pages/Citizens'));
const CitizensIssues = lazy(() => import('./pages/citizens/CitizensIssues'));
const GovernmentEntrance = lazy(() => import('./pages/citizens/GovernmentEntrance'));
@@ -145,6 +146,7 @@ function App() {
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/web/src/components/AppLayout.tsx b/web/src/components/AppLayout.tsx
index c0f7bb8f..397fb34e 100644
--- a/web/src/components/AppLayout.tsx
+++ b/web/src/components/AppLayout.tsx
@@ -31,6 +31,8 @@ import { PezkuwiWalletButton } from './PezkuwiWalletButton';
import { DEXDashboard } from './dex/DEXDashboard';
import { P2PDashboard } from './p2p/P2PDashboard';
import EducationPlatform from '../pages/EducationPlatform';
+import { useIsMobile } from '@/hooks/use-mobile';
+import MobileHomeLayout from './MobileHomeLayout';
const AppLayout: React.FC = () => {
const navigate = useNavigate();
@@ -52,6 +54,7 @@ const AppLayout: React.FC = () => {
const { t } = useTranslation();
const { isConnected } = useWebSocket();
useWallet();
+ const isMobile = useIsMobile();
const [, _setIsAdmin] = useState(false);
// Close dropdown on outside click
@@ -72,6 +75,13 @@ const AppLayout: React.FC = () => {
React.useEffect(() => {
_setIsAdmin(false); // Admin status managed by AuthContext
}, [user]);
+ // On mobile, when no feature panel is active, show the mobile home layout
+ const isFeaturePanelOpen = showDEX || showProposalWizard || showDelegation || showForum || showModeration || showTreasury || showStaking || showMultiSig || showEducation || showP2P;
+
+ if (isMobile && !isFeaturePanelOpen) {
+ return ;
+ }
+
return (
{/* Navigation */}
diff --git a/web/src/components/MobileHomeLayout.tsx b/web/src/components/MobileHomeLayout.tsx
new file mode 100644
index 00000000..e41a1e19
--- /dev/null
+++ b/web/src/components/MobileHomeLayout.tsx
@@ -0,0 +1,403 @@
+import React, { useState, useEffect, useCallback } from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { useAuth } from '@/contexts/AuthContext';
+import { usePezkuwi } from '@/contexts/PezkuwiContext';
+import { supabase } from '@/lib/supabase';
+import { LanguageSwitcher } from './LanguageSwitcher';
+import { PezkuwiWalletButton } from './PezkuwiWalletButton';
+import NotificationBell from './notifications/NotificationBell';
+import { fetchUserTikis, getPrimaryRole, getTikiDisplayName, getTikiEmoji } from '@pezkuwi/lib/tiki';
+import { getAllScores, type UserScores } from '@pezkuwi/lib/scores';
+import { getKycStatus } from '@pezkuwi/lib/kyc';
+
+// Avatar pool (same as mobile)
+const AVATAR_POOL = [
+ { id: 'avatar1', emoji: '👨🏻' }, { id: 'avatar2', emoji: '👨🏼' },
+ { id: 'avatar3', emoji: '👨🏽' }, { id: 'avatar4', emoji: '👨🏾' },
+ { id: 'avatar5', emoji: '👩🏻' }, { id: 'avatar6', emoji: '👩🏼' },
+ { id: 'avatar7', emoji: '👩🏽' }, { id: 'avatar8', emoji: '👩🏾' },
+ { id: 'avatar9', emoji: '🧔🏻' }, { id: 'avatar10', emoji: '🧔🏼' },
+ { id: 'avatar11', emoji: '🧔🏽' }, { id: 'avatar12', emoji: '🧔🏾' },
+ { id: 'avatar13', emoji: '👳🏻♂️' }, { id: 'avatar14', emoji: '👳🏼♂️' },
+ { id: 'avatar15', emoji: '👳🏽♂️' }, { id: 'avatar16', emoji: '🧕🏻' },
+ { id: 'avatar17', emoji: '🧕🏼' }, { id: 'avatar18', emoji: '🧕🏽' },
+];
+
+const getEmojiFromAvatarId = (avatarId: string): string => {
+ const avatar = AVATAR_POOL.find(a => a.id === avatarId);
+ return avatar ? avatar.emoji : '👤';
+};
+
+// App icon definition
+interface AppItem {
+ title: string;
+ icon: string;
+ route: string;
+ comingSoon?: boolean;
+ requiresAuth?: boolean;
+}
+
+// Section definition
+interface AppSection {
+ titleKey: string;
+ emoji: string;
+ borderColor: string;
+ apps: AppItem[];
+}
+
+const APP_SECTIONS: AppSection[] = [
+ {
+ titleKey: 'FINANCE',
+ emoji: '💰',
+ borderColor: 'border-l-green-500',
+ apps: [
+ { title: 'Wallet', icon: '👛', route: '/wallet' },
+ { title: 'Bank', icon: '🏦', route: '/wallet', comingSoon: true },
+ { title: 'Exchange', icon: '💱', route: '/dex', requiresAuth: true },
+ { title: 'P2P', icon: '🤝', route: '/p2p', requiresAuth: true },
+ { title: 'B2B', icon: '🤖', route: '/wallet', comingSoon: true },
+ { title: 'Bac/Zekat', icon: '💰', route: '/wallet', comingSoon: true },
+ { title: 'Launchpad', icon: '🚀', route: '/launchpad' },
+ ],
+ },
+ {
+ titleKey: 'GOVERNANCE',
+ emoji: '🏛️',
+ borderColor: 'border-l-red-500',
+ apps: [
+ { title: 'President', icon: '👑', route: '/elections', requiresAuth: true },
+ { title: 'Assembly', icon: '🏛️', route: '/citizens/government', comingSoon: true },
+ { title: 'Vote', icon: '🗳️', route: '/elections', requiresAuth: true },
+ { title: 'Validators', icon: '🛡️', route: '/wallet' },
+ { title: 'Justice', icon: '⚖️', route: '/citizens/government', comingSoon: true },
+ { title: 'Proposals', icon: '📜', route: '/citizens/government' },
+ { title: 'Polls', icon: '📊', route: '/citizens/government', comingSoon: true },
+ { title: 'Identity', icon: '🆔', route: '/identity' },
+ ],
+ },
+ {
+ titleKey: 'SOCIAL',
+ emoji: '💬',
+ borderColor: 'border-l-blue-500',
+ apps: [
+ { title: 'whatsKURD', icon: '💬', route: '/message', comingSoon: true },
+ { title: 'Forum', icon: '📰', route: '/forum' },
+ { title: 'KurdMedia', icon: '📺', route: '/forum', comingSoon: true },
+ { title: 'Events', icon: '📅', route: '/forum', comingSoon: true },
+ { title: 'Help', icon: '❓', route: '/docs' },
+ { title: 'Music', icon: '🎵', route: '/forum', comingSoon: true },
+ { title: 'VPN', icon: '🛡️', route: '/forum', comingSoon: true },
+ { title: 'Referral', icon: '👥', route: '/dashboard', requiresAuth: true },
+ ],
+ },
+ {
+ titleKey: 'EDUCATION',
+ emoji: '📚',
+ borderColor: 'border-l-yellow-500',
+ apps: [
+ { title: 'University', icon: '🎓', route: '/education', comingSoon: true },
+ { title: 'Perwerde', icon: '📖', route: '/education', requiresAuth: true },
+ { title: 'Certificates', icon: '🏆', route: '/education', comingSoon: true },
+ { title: 'Research', icon: '🔬', route: '/education', comingSoon: true },
+ ],
+ },
+];
+
+const MobileHomeLayout: React.FC = () => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const { t } = useTranslation();
+ const { user } = useAuth();
+ const { peopleApi, isPeopleReady, selectedAccount } = usePezkuwi();
+
+ // Profile state
+ const [profileData, setProfileData] = useState<{
+ full_name?: string | null;
+ avatar_url?: string | null;
+ created_at?: string;
+ } | null>(null);
+
+ // Blockchain state
+ const [tikis, setTikis] = useState
([]);
+ const [scores, setScores] = useState({
+ trustScore: 0, referralScore: 0, stakingScore: 0, tikiScore: 0, totalScore: 0,
+ });
+ const [kycStatus, setKycStatus] = useState('NotStarted');
+ const [loadingScores, setLoadingScores] = useState(false);
+
+ const fetchProfile = useCallback(async () => {
+ if (!user) return;
+ try {
+ const { data } = await supabase
+ .from('profiles')
+ .select('*')
+ .eq('id', user.id)
+ .maybeSingle();
+ if (data) setProfileData(data);
+ } catch { /* profile fetch is best-effort */ }
+ }, [user]);
+
+ const fetchBlockchainData = useCallback(async () => {
+ if (!selectedAccount || !peopleApi || !isPeopleReady) return;
+ setLoadingScores(true);
+ try {
+ const [userTikis, allScores, status] = await Promise.all([
+ fetchUserTikis(peopleApi, selectedAccount.address),
+ getAllScores(peopleApi, selectedAccount.address),
+ getKycStatus(peopleApi, selectedAccount.address),
+ ]);
+ setTikis(userTikis);
+ setScores(allScores);
+ setKycStatus(status);
+ } catch { /* blockchain fetch is best-effort */ }
+ finally { setLoadingScores(false); }
+ }, [selectedAccount, peopleApi, isPeopleReady]);
+
+ useEffect(() => { fetchProfile(); }, [fetchProfile]);
+ useEffect(() => {
+ if (selectedAccount && peopleApi && isPeopleReady) fetchBlockchainData();
+ }, [fetchBlockchainData, selectedAccount, peopleApi, isPeopleReady]);
+
+ const primaryRole = tikis.length > 0 ? getPrimaryRole(tikis) : 'Visitor';
+ const displayName = profileData?.full_name || user?.email?.split('@')[0] || 'Heval';
+ const memberSince = profileData?.created_at
+ ? new Date(profileData.created_at).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })
+ : user?.created_at
+ ? new Date(user.created_at).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })
+ : 'N/A';
+
+ const avatarEmoji = profileData?.avatar_url && !profileData.avatar_url.startsWith('http')
+ ? getEmojiFromAvatarId(profileData.avatar_url)
+ : '👤';
+
+ const currentTab = location.pathname === '/be-citizen' ? 'citizen'
+ : location.pathname === '/dashboard' ? 'referral'
+ : 'home';
+
+ return (
+
+ {/* ── HEADER ── */}
+
+
+ {/* Left: Avatar + Greeting */}
+
+
+ {profileData?.avatar_url?.startsWith('http') ? (
+

+ ) : (
+
+ {avatarEmoji}
+
+ )}
+ {/* Online dot */}
+
+
+
+
+ {t('mobile.greeting', 'Rojbaş')}, {displayName}
+
+
+ {getTikiEmoji(primaryRole)} {getTikiDisplayName(primaryRole)}
+
+
+
+
+ {/* Right: Actions */}
+
+
+
+
+ {/* ── SCROLLABLE CONTENT ── */}
+
+
+ {/* ── SCORE CARDS (horizontal scroll) ── */}
+
+
+ {/* Card 1: Member Since OR Login/Sign Up */}
+ {user ? (
+
+ ) : (
+ navigate('/login') }}
+ />
+ )}
+ {/* Role - always visible, shows Visitor for guests */}
+
+ {/* Total Score */}
+ navigate('/login') } : undefined} />
+ {/* Trust Score */}
+ navigate('/login') } : undefined} />
+ {/* Referral Score */}
+ navigate('/login') } : undefined} />
+ {/* Staking Score */}
+ navigate('/login') } : undefined} />
+ {/* Tiki Score */}
+ navigate('/login') } : undefined} />
+ {/* KYC Status */}
+ navigate('/login') }
+ : kycStatus === 'NotStarted'
+ ? { label: t('mobile.apply', 'Apply'), onClick: () => navigate('/be-citizen') }
+ : undefined}
+ />
+
+
+
+ {/* ── APP SECTIONS ── */}
+ {APP_SECTIONS.map((section) => (
+
+ {/* Section header */}
+
+
+ {section.titleKey} {section.emoji}
+
+
+ {/* App grid - 4 per row */}
+
+ {section.apps.map((app) => {
+ const needsLogin = app.requiresAuth && !user;
+ return (
+
+ );
+ })}
+
+
+ ))}
+
+ {/* Bottom spacing for tab bar */}
+
+
+
+ {/* ── BOTTOM TAB BAR ── */}
+
+
+ navigate('/')}
+ />
+ navigate('/be-citizen')}
+ accent
+ />
+ navigate(user ? '/dashboard' : '/login')}
+ />
+
+
+
+ );
+};
+
+// ── Sub-components ──
+
+function ScoreCard({ icon, label, value, sub, color, action }: {
+ icon: string; label: string; value: string; sub?: string; color: string;
+ action?: { label: string; onClick: () => void };
+}) {
+ return (
+
+
{icon}
+
{label}
+
{value}
+ {sub &&
{sub}
}
+ {action && (
+
+ )}
+
+ );
+}
+
+function TabButton({ icon, label, active, onClick, accent }: {
+ icon: string; label: string; active: boolean; onClick: () => void; accent?: boolean;
+}) {
+ return (
+
+ );
+}
+
+export default MobileHomeLayout;
diff --git a/web/src/components/MobileShell.tsx b/web/src/components/MobileShell.tsx
new file mode 100644
index 00000000..384ef509
--- /dev/null
+++ b/web/src/components/MobileShell.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { ArrowLeft } from 'lucide-react';
+import { useAuth } from '@/contexts/AuthContext';
+
+interface MobileShellProps {
+ title: string;
+ children: React.ReactNode;
+ /** Hide header gradient bar (e.g. when page has its own header) */
+ hideHeader?: boolean;
+}
+
+/**
+ * Shared mobile wrapper: compact header with back button + sticky bottom tab bar.
+ * Used by all mobile-specific pages (Citizen, Referral, etc.)
+ */
+const MobileShell: React.FC = ({ title, children, hideHeader }) => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const { t } = useTranslation();
+ const { user } = useAuth();
+
+ const currentTab = location.pathname === '/be-citizen' ? 'citizen'
+ : location.pathname === '/dashboard' ? 'referral'
+ : 'home';
+
+ return (
+
+ {/* ── HEADER ── */}
+ {!hideHeader && (
+
+
+
{title}
+
+ )}
+
+ {/* ── CONTENT ── */}
+
+ {children}
+
+
+ {/* ── BOTTOM TAB BAR ── */}
+
+
+ navigate('/')} />
+ navigate('/be-citizen')} accent />
+ navigate(user ? '/dashboard' : '/login')} />
+
+
+
+ );
+};
+
+function TabBtn({ icon, label, active, onClick, accent }: {
+ icon: string; label: string; active: boolean; onClick: () => void; accent?: boolean;
+}) {
+ return (
+
+ );
+}
+
+export default MobileShell;
diff --git a/web/src/i18n/locales/ar.ts b/web/src/i18n/locales/ar.ts
index eaad4cd5..e0c8608c 100644
--- a/web/src/i18n/locales/ar.ts
+++ b/web/src/i18n/locales/ar.ts
@@ -3757,4 +3757,23 @@ export default {
'messaging.checkingKey': 'جاري التحقق من مفتاح التشفير...',
'messaging.send': 'إرسال',
'messaging.sending': 'جاري الإرسال...',
+
+ // Mobile Home Layout
+ 'mobile.greeting': 'مرحباً',
+ 'mobile.home': 'الرئيسية',
+ 'mobile.citizen': 'مواطن',
+ 'mobile.referral': 'إحالة',
+ 'mobile.memberSince': 'عضو منذ',
+ 'mobile.role': 'الدور',
+ 'mobile.totalScore': 'النقاط الإجمالية',
+ 'mobile.trustScore': 'نقاط الثقة',
+ 'mobile.referralScore': 'نقاط الإحالة',
+ 'mobile.stakingScore': 'نقاط الستاكينغ',
+ 'mobile.tikiScore': 'نقاط التيكي',
+ 'mobile.kycStatus': 'حالة KYC',
+ 'mobile.connectWallet': 'ربط المحفظة',
+ 'mobile.apply': 'تقديم',
+ 'mobile.joinUs': 'انضم إلينا',
+ 'mobile.signInUp': 'تسجيل الدخول / التسجيل',
+ 'mobile.loginToSeeRoles': 'سجل الدخول لرؤية الأدوار',
};
diff --git a/web/src/i18n/locales/ckb.ts b/web/src/i18n/locales/ckb.ts
index 75c65ecb..4ea14fa0 100644
--- a/web/src/i18n/locales/ckb.ts
+++ b/web/src/i18n/locales/ckb.ts
@@ -3747,4 +3747,23 @@ export default {
'messaging.checkingKey': 'کلیلی شفرکردن پشکنین دەکرێت...',
'messaging.send': 'بنێرە',
'messaging.sending': 'دەنێردرێت...',
+
+ // Mobile Home Layout
+ 'mobile.greeting': 'ڕۆژباش',
+ 'mobile.home': 'ماڵەوە',
+ 'mobile.citizen': 'هاوڵاتی',
+ 'mobile.referral': 'ئاماژە',
+ 'mobile.memberSince': 'ئەندام لە',
+ 'mobile.role': 'ڕۆڵ',
+ 'mobile.totalScore': 'کۆی خاڵ',
+ 'mobile.trustScore': 'خاڵی متمانە',
+ 'mobile.referralScore': 'خاڵی ئاماژە',
+ 'mobile.stakingScore': 'خاڵی ستەیکینگ',
+ 'mobile.tikiScore': 'خاڵی تیکی',
+ 'mobile.kycStatus': 'بارودۆخی KYC',
+ 'mobile.connectWallet': 'جزدان ببەستە',
+ 'mobile.apply': 'داواکاری',
+ 'mobile.joinUs': 'پەیوەست ببە',
+ 'mobile.signInUp': 'چوونەژوورەوە / تۆمارکردن',
+ 'mobile.loginToSeeRoles': 'بۆ بینینی ڕۆڵەکان بچۆ ژوورەوە',
};
diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts
index 926d1d76..f1e54898 100644
--- a/web/src/i18n/locales/en.ts
+++ b/web/src/i18n/locales/en.ts
@@ -3795,4 +3795,23 @@ export default {
'messaging.checkingKey': 'Checking encryption key...',
'messaging.send': 'Send',
'messaging.sending': 'Sending...',
+
+ // Mobile Home Layout
+ 'mobile.greeting': 'Rojbaş',
+ 'mobile.home': 'Home',
+ 'mobile.citizen': 'Citizen',
+ 'mobile.referral': 'Referral',
+ 'mobile.memberSince': 'Member Since',
+ 'mobile.role': 'Role',
+ 'mobile.totalScore': 'Total Score',
+ 'mobile.trustScore': 'Trust Score',
+ 'mobile.referralScore': 'Referral Score',
+ 'mobile.stakingScore': 'Staking Score',
+ 'mobile.tikiScore': 'Tiki Score',
+ 'mobile.kycStatus': 'KYC Status',
+ 'mobile.connectWallet': 'Connect wallet',
+ 'mobile.apply': 'Apply',
+ 'mobile.joinUs': 'Join Us',
+ 'mobile.signInUp': 'Sign In / Up',
+ 'mobile.loginToSeeRoles': 'Login to see roles',
}
diff --git a/web/src/i18n/locales/fa.ts b/web/src/i18n/locales/fa.ts
index be5b3a74..11540126 100644
--- a/web/src/i18n/locales/fa.ts
+++ b/web/src/i18n/locales/fa.ts
@@ -3791,4 +3791,23 @@ export default {
'messaging.checkingKey': 'بررسی کلید رمزنگاری...',
'messaging.send': 'ارسال',
'messaging.sending': 'در حال ارسال...',
+
+ // Mobile Home Layout
+ 'mobile.greeting': 'سلام',
+ 'mobile.home': 'خانه',
+ 'mobile.citizen': 'شهروند',
+ 'mobile.referral': 'ارجاع',
+ 'mobile.memberSince': 'عضو از',
+ 'mobile.role': 'نقش',
+ 'mobile.totalScore': 'امتیاز کل',
+ 'mobile.trustScore': 'امتیاز اعتماد',
+ 'mobile.referralScore': 'امتیاز ارجاع',
+ 'mobile.stakingScore': 'امتیاز استیکینگ',
+ 'mobile.tikiScore': 'امتیاز تیکی',
+ 'mobile.kycStatus': 'وضعیت KYC',
+ 'mobile.connectWallet': 'اتصال کیف پول',
+ 'mobile.apply': 'درخواست',
+ 'mobile.joinUs': 'به ما بپیوندید',
+ 'mobile.signInUp': 'ورود / ثبت نام',
+ 'mobile.loginToSeeRoles': 'برای دیدن نقشها وارد شوید',
};
diff --git a/web/src/i18n/locales/kmr.ts b/web/src/i18n/locales/kmr.ts
index d299ebdc..2995d7ae 100644
--- a/web/src/i18n/locales/kmr.ts
+++ b/web/src/i18n/locales/kmr.ts
@@ -3774,4 +3774,23 @@ export default {
'messaging.checkingKey': 'Mifteya şîfrekirinê tê kontrol kirin...',
'messaging.send': 'Bişîne',
'messaging.sending': 'Tê şandin...',
+
+ // Mobile Home Layout
+ 'mobile.greeting': 'Rojbaş',
+ 'mobile.home': 'Mal',
+ 'mobile.citizen': 'Welatî',
+ 'mobile.referral': 'Referans',
+ 'mobile.memberSince': 'Endam ji',
+ 'mobile.role': 'Rol',
+ 'mobile.totalScore': 'Pûana Giştî',
+ 'mobile.trustScore': 'Pûana Pêbaweriyê',
+ 'mobile.referralScore': 'Pûana Referansê',
+ 'mobile.stakingScore': 'Pûana Stakingê',
+ 'mobile.tikiScore': 'Pûana Tikiyê',
+ 'mobile.kycStatus': 'Rewşa KYC',
+ 'mobile.connectWallet': 'Cîzdanê girêde',
+ 'mobile.apply': 'Serlêdan',
+ 'mobile.joinUs': 'Tevlî me bibe',
+ 'mobile.signInUp': 'Têkeve / Tomar bibe',
+ 'mobile.loginToSeeRoles': 'Ji bo rolan têkeve',
};
diff --git a/web/src/i18n/locales/tr.ts b/web/src/i18n/locales/tr.ts
index 30a015dd..9d509d71 100644
--- a/web/src/i18n/locales/tr.ts
+++ b/web/src/i18n/locales/tr.ts
@@ -3777,4 +3777,23 @@ export default {
'messaging.checkingKey': 'Şifreleme anahtarı kontrol ediliyor...',
'messaging.send': 'Gönder',
'messaging.sending': 'Gönderiliyor...',
+
+ // Mobile Home Layout
+ 'mobile.greeting': 'Rojbaş',
+ 'mobile.home': 'Ana Sayfa',
+ 'mobile.citizen': 'Vatandaş',
+ 'mobile.referral': 'Referans',
+ 'mobile.memberSince': 'Üyelik Tarihi',
+ 'mobile.role': 'Rol',
+ 'mobile.totalScore': 'Toplam Puan',
+ 'mobile.trustScore': 'Güven Puanı',
+ 'mobile.referralScore': 'Referans Puanı',
+ 'mobile.stakingScore': 'Staking Puanı',
+ 'mobile.tikiScore': 'Tiki Puanı',
+ 'mobile.kycStatus': 'KYC Durumu',
+ 'mobile.connectWallet': 'Cüzdan bağla',
+ 'mobile.apply': 'Başvur',
+ 'mobile.joinUs': 'Bize Katil',
+ 'mobile.signInUp': 'Giris / Kayit',
+ 'mobile.loginToSeeRoles': 'Rolleri gormek icin giris yap',
};
diff --git a/web/src/pages/BeCitizen.tsx b/web/src/pages/BeCitizen.tsx
index 81e9b979..b634d353 100644
--- a/web/src/pages/BeCitizen.tsx
+++ b/web/src/pages/BeCitizen.tsx
@@ -6,6 +6,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { CitizenshipModal } from '@/components/citizenship/CitizenshipModal';
import { InviteUserModal } from '@/components/referral/InviteUserModal';
import { Shield, Users, Award, Globe, ChevronRight, ArrowLeft, UserPlus } from 'lucide-react';
+import { useIsMobile } from '@/hooks/use-mobile';
+import MobileShell from '@/components/MobileShell';
const BeCitizen: React.FC = () => {
const navigate = useNavigate();
@@ -25,6 +27,209 @@ const BeCitizen: React.FC = () => {
}
}, [searchParams]);
+ const isMobile = useIsMobile();
+
+ // ── MOBILE VERSION ──
+ if (isMobile) {
+ return (
+
+ {/* HERO - fills viewport minus shell tab bar (64px) */}
+
+ {/* Back button */}
+
+
+
+
+ {/* Decorative circles */}
+
+
+
+ {/* Content centered */}
+
+ {/* Kurdistan flag stripe */}
+
+
+
🏛️
+
+
+ {t('beCitizen.heroTitle', 'Digital Kurdistan')}
+
+
+ {t('beCitizen.heroSubtitle', 'Be a Citizen')}
+
+
+
+ {t('beCitizen.heroDesc')}
+
+
+ {/* CTA Buttons */}
+
+
+
+
+
+ {/* Feature badges */}
+
+
+ {t('beCitizen.zkAuth', 'ZK Authentication')}
+
+
+ {t('beCitizen.soulboundNft', 'Soulbound NFT')}
+
+
+ {t('beCitizen.decentralizedId', 'Decentralized ID')}
+
+
+
+
+ {/* Scroll indicator */}
+
+ {t('mobile.scrollDown', 'Scroll')}
+
+
+
+
+ {/* SCROLLABLE CONTENT - Ready to Join and below */}
+
+ {/* Ready to Join CTA */}
+
+
+
+
{t('beCitizen.readyToJoin')}
+
{t('beCitizen.readyToJoinDesc')}
+
+
+
+
+ {/* Benefits Grid - 2 col */}
+
+
+
+
+ {t('beCitizen.privacyTitle')}
+ {t('beCitizen.privacyDesc')}
+
+
+
+
+
+ {t('beCitizen.nftTitle')}
+ {t('beCitizen.nftDesc')}
+
+
+
+
+
+ {t('beCitizen.trustTitle')}
+ {t('beCitizen.trustDesc')}
+
+
+
+
+
+ {t('beCitizen.govTitle')}
+ {t('beCitizen.govDesc')}
+
+
+
+
+ {/* How It Works */}
+
+
{t('beCitizen.howItWorks')}
+
+
+
+
+
+ 1
+
+
{t('beCitizen.existingTitle')}
+
+
+
+ ✓ {t('beCitizen.existing1')}
+ ✓ {t('beCitizen.existing2')}
+ ✓ {t('beCitizen.existing3')}
+ ✓ {t('beCitizen.existing4')}
+
+
+
+
+
+
+ 2
+
+
{t('beCitizen.newTitle')}
+
+
+
+ ✓ {t('beCitizen.new1')}
+ ✓ {t('beCitizen.new2')}
+ ✓ {t('beCitizen.new3')}
+ ✓ {t('beCitizen.new4')}
+
+
+
+
+
+
+ 3
+
+
{t('beCitizen.benefitsTitle')}
+
+
+
+ ✓ {t('beCitizen.benefit1')}
+ ✓ {t('beCitizen.benefit2')}
+ ✓ {t('beCitizen.benefit3')}
+ ✓ {t('beCitizen.benefit4')}
+
+
+
+
+
+ {/* Security Notice */}
+
+
+
+
+
+
{t('beCitizen.securityTitle')}
+
{t('beCitizen.securityDesc')}
+
+
+
+
+
+
+
+
+ {/* Modals */}
+ setIsModalOpen(false)} referrerAddress={referrerAddress} />
+ setIsInviteModalOpen(false)} />
+
+ );
+ }
+
+ // ── DESKTOP VERSION (unchanged) ──
return (
@@ -150,7 +355,6 @@ const BeCitizen: React.FC = () => {
{t('beCitizen.howItWorks')}
- {/* Existing Citizens */}
@@ -166,7 +370,6 @@ const BeCitizen: React.FC = () => {
- {/* New Citizens */}
@@ -182,7 +385,6 @@ const BeCitizen: React.FC = () => {
- {/* After Citizenship */}
@@ -218,14 +420,7 @@ const BeCitizen: React.FC = () => {
- {/* Citizenship Modal */}
- setIsModalOpen(false)}
- referrerAddress={referrerAddress}
- />
-
- {/* Invite Friend Modal */}
+ setIsModalOpen(false)} referrerAddress={referrerAddress} />
setIsInviteModalOpen(false)} />
);
diff --git a/web/src/pages/Identity.tsx b/web/src/pages/Identity.tsx
new file mode 100644
index 00000000..70bd0c3e
--- /dev/null
+++ b/web/src/pages/Identity.tsx
@@ -0,0 +1,483 @@
+import React, { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { useIsMobile } from '@/hooks/use-mobile';
+import MobileShell from '@/components/MobileShell';
+import { useAuth } from '@/contexts/AuthContext';
+import { usePezkuwi } from '@/contexts/PezkuwiContext';
+import { Save, CreditCard, BookOpen, Camera } from 'lucide-react';
+
+// ── Types ──
+interface IdentityData {
+ fullName: string;
+ fatherName: string;
+ motherName: string;
+ dateOfBirth: string;
+ placeOfBirth: string;
+ gender: 'M' | 'F' | '';
+ bloodType: string;
+ citizenNumber: string;
+ passportNumber: string;
+ photo: string; // base64 data URL
+}
+
+const DEFAULT_DATA: IdentityData = {
+ fullName: '',
+ fatherName: '',
+ motherName: '',
+ dateOfBirth: '',
+ placeOfBirth: '',
+ gender: '',
+ bloodType: '',
+ citizenNumber: '',
+ passportNumber: '',
+ photo: '',
+};
+
+const STORAGE_KEY = 'pezkuwi_identity_data';
+
+// ── Helpers ──
+function generatePassportNo(citizenNo: string): string {
+ if (!citizenNo) return 'KRD-000000';
+ return `KRD-${citizenNo.replace(/\D/g, '').slice(0, 6).padStart(6, '0')}`;
+}
+
+function formatMRZ(data: IdentityData): [string, string] {
+ const name = data.fullName.toUpperCase().replace(/[^A-Z ]/g, '').replace(/ /g, '<') || 'SURNAME< = {
+ avatar1: '👨🏻', avatar2: '👨🏼', avatar3: '👨🏽', avatar4: '👨🏾',
+ avatar5: '👩🏻', avatar6: '👩🏼', avatar7: '👩🏽', avatar8: '👩🏾',
+ avatar9: '🧔🏻', avatar10: '🧔🏼', avatar11: '🧔🏽', avatar12: '🧔🏾',
+ avatar13: '👳🏻♂️', avatar14: '👳🏼♂️', avatar15: '👳🏽♂️', avatar16: '🧕🏻',
+ avatar17: '🧕🏼', avatar18: '🧕🏽',
+};
+
+// ── Main Component ──
+export default function Identity() {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const isMobile = useIsMobile();
+ const { user } = useAuth();
+ const { selectedAccount } = usePezkuwi();
+ const [tab, setTab] = useState<'id' | 'passport'>('id');
+ const [data, setData] = useState(DEFAULT_DATA);
+ const [saved, setSaved] = useState(false);
+
+ // Load from localStorage
+ useEffect(() => {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ if (raw) setData(JSON.parse(raw));
+ } catch { /* ignore */ }
+ }, []);
+
+ // Auto-fill citizen number from wallet
+ useEffect(() => {
+ if (selectedAccount && !data.citizenNumber) {
+ const short = selectedAccount.address.slice(-8).toUpperCase();
+ setData(prev => ({
+ ...prev,
+ citizenNumber: short,
+ passportNumber: generatePassportNo(short),
+ }));
+ }
+ }, [selectedAccount, data.citizenNumber]);
+
+ const photoInputRef = React.useRef(null);
+
+ const handlePhotoSelect = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+ // Resize to max 300px and compress
+ const reader = new FileReader();
+ reader.onload = () => {
+ const img = new Image();
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ const maxSize = 300;
+ let w = img.width, h = img.height;
+ if (w > h) { h = (h / w) * maxSize; w = maxSize; }
+ else { w = (w / h) * maxSize; h = maxSize; }
+ canvas.width = w;
+ canvas.height = h;
+ const ctx = canvas.getContext('2d')!;
+ ctx.drawImage(img, 0, 0, w, h);
+ const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
+ setData(prev => ({ ...prev, photo: dataUrl }));
+ setSaved(false);
+ };
+ img.src = reader.result as string;
+ };
+ reader.readAsDataURL(file);
+ };
+
+ const handleChange = (field: keyof IdentityData, value: string) => {
+ setData(prev => {
+ const next = { ...prev, [field]: value };
+ if (field === 'citizenNumber') {
+ next.passportNumber = generatePassportNo(value);
+ }
+ return next;
+ });
+ setSaved(false);
+ };
+
+ const handleSave = () => {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
+ setSaved(true);
+ setTimeout(() => setSaved(false), 2000);
+ };
+
+ const [mrzLine1, mrzLine2] = formatMRZ(data);
+
+ const content = (
+
+ {/* Tab switcher */}
+
+
+
+
+
+
+ {/* Hidden file input for photo */}
+
+
+ {/* ── CARD PREVIEW ── */}
+ {tab === 'id' ? (
+
photoInputRef.current?.click()} />
+ ) : (
+ photoInputRef.current?.click()} />
+ )}
+
+ {/* ── FORM ── */}
+
+
+ {t('identity.personalInfo', 'Personal Information')}
+
+
+
handleChange('fullName', v)} placeholder="Azad Kurdistanî" />
+
+ handleChange('fatherName', v)} placeholder="Rêber" />
+
+ handleChange('motherName', v)} placeholder="Jîn" />
+
+
+
handleChange('dateOfBirth', v)} type="date" />
+
+
+
+
+
+
+
+
+
handleChange('placeOfBirth', v)} placeholder="Hewlêr" />
+
+
+
+
+
+
+
+ handleChange('citizenNumber', v)} placeholder="KRD-000000" />
+
+ {/* Save button */}
+
+
+
+ {t('identity.localOnly', 'Data is stored only on your device. Never uploaded.')}
+
+
+
+
+
+
+ );
+
+ if (isMobile) {
+ return (
+
+ {content}
+
+ );
+ }
+
+ // Desktop: simple centered layout
+ return (
+
+
+
+ {content}
+
+
+ );
+}
+
+// ── ID Card Preview ──
+function IDCardPreview({ data, onPhotoClick }: { data: IdentityData; onPhotoClick: () => void }) {
+ return (
+
+
+ {/* Top stripe - Kurdistan flag colors */}
+
+
+ {/* Header */}
+
+
+
KOMARA KURDISTANÊ
+
KURDISTAN REPUBLIC
+
+ {/* Sun emblem */}
+
+ ☀️
+
+
+
کۆماری کوردستان
+
NASNAMA / ID CARD
+
+
+
+ {/* Divider */}
+
+
+ {/* Body */}
+
+ {/* Photo area - clickable */}
+
+
+ {/* Info */}
+
+
+
+ {/* Bottom bar */}
+
+
+
JIM / NO
+
+ {data.citizenNumber || 'KRD-000000'}
+
+
+
+
XWÎNê / Blood
+
{data.bloodType || '—'}
+
+
+
DERBASDAR / Expiry
+
{expiryDate}
+
+
+
+ );
+}
+
+// ── Passport Preview ──
+function PassportPreview({ data, mrzLine1, mrzLine2, onPhotoClick }: {
+ data: IdentityData; mrzLine1: string; mrzLine2: string; onPhotoClick: () => void;
+}) {
+ return (
+
+
+ {/* Top ornament */}
+
+
+ {/* Header */}
+
+
KOMARA KURDISTANÊ
+
KURDISTAN REPUBLIC
+
+ {/* Emblem */}
+
+ ☀️
+
+
+
پاسپۆرت
+
PASSPORT
+
+
+ {/* Divider */}
+
+
+ {/* Data page */}
+
+
+
+ {/* Photo - clickable */}
+
+
+
+
+
+
+
+
+
+
+
+ {/* MRZ Zone */}
+
+
{mrzLine1}
+
{mrzLine2}
+
+
+ );
+}
+
+// ── Shared sub-components ──
+function IDField({ label, value, bold }: { label: string; value: string; bold?: boolean }) {
+ return (
+
+ );
+}
+
+function PassField({ label, value, bold, mono }: { label: string; value: string; bold?: boolean; mono?: boolean }) {
+ return (
+
+ );
+}
+
+function FormField({ label, value, onChange, placeholder, type = 'text' }: {
+ label: string; value: string; onChange: (v: string) => void; placeholder?: string; type?: string;
+}) {
+ return (
+
+
+ onChange(e.target.value)}
+ placeholder={placeholder}
+ className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-green-500 focus:outline-none transition-colors"
+ />
+
+ );
+}