diff --git a/package.json b/package.json index 5b99d6c..0204785 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pezkuwi-telegram-miniapp", - "version": "1.0.194", + "version": "1.0.196", "type": "module", "description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards", "author": "Pezkuwichain Team", diff --git a/src/App.tsx b/src/App.tsx index b356ae6..52d66e4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -54,11 +54,12 @@ const NAV_ITEMS: NavItem[] = [ // P2P Web App URL - Mobile-optimized P2P const P2P_WEB_URL = 'https://telegram.pezkuwichain.io/p2p'; -// Check for standalone pages via URL query params (evaluated once at module level) +// Check for standalone pages via URL query params or path (evaluated once at module level) const PAGE_PARAM = new URLSearchParams(window.location.search).get('page'); +const IS_CITIZEN_PAGE = PAGE_PARAM === 'citizen' || window.location.pathname === '/citizens'; export default function App() { - if (PAGE_PARAM === 'citizen') { + if (IS_CITIZEN_PAGE) { return ( }> diff --git a/src/components/citizen/CitizenForm.tsx b/src/components/citizen/CitizenForm.tsx index 63b93af..ab73e91 100644 --- a/src/components/citizen/CitizenForm.tsx +++ b/src/components/citizen/CitizenForm.tsx @@ -1,16 +1,16 @@ /** * Citizen Application Form - * Collects citizenship data from the user + * Collects citizenship data and seed phrase from the user */ -import { useState } from 'react'; -import { Plus, Trash2 } from 'lucide-react'; +import { useState, useEffect, useMemo } from 'react'; +import { Plus, Trash2, Shield } from 'lucide-react'; import { useTranslation } from '@/i18n'; import { useTelegram } from '@/hooks/useTelegram'; +import { initWalletService, validateMnemonic, getAddressFromMnemonic } from '@/lib/wallet-service'; import type { CitizenshipData, Region, MaritalStatus, ChildInfo } from '@/lib/citizenship'; interface Props { - walletAddress: string; onSubmit: (data: CitizenshipData) => void; } @@ -23,7 +23,7 @@ const REGIONS: { value: Region; labelKey: string }[] = [ { value: 'diaspora', labelKey: 'citizen.regionDiaspora' }, ]; -export function CitizenForm({ walletAddress, onSubmit }: Props) { +export function CitizenForm({ onSubmit }: Props) { const { t } = useTranslation(); const { hapticImpact, hapticNotification } = useTelegram(); @@ -39,8 +39,33 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) { const [email, setEmail] = useState(''); const [profession, setProfession] = useState(''); const [referrerAddress, setReferrerAddress] = useState(''); + const [seedPhrase, setSeedPhrase] = useState(''); const [consent, setConsent] = useState(false); const [error, setError] = useState(''); + const [cryptoReady, setCryptoReady] = useState(false); + + // Initialize crypto libraries for mnemonic validation + useEffect(() => { + initWalletService().then(() => setCryptoReady(true)); + }, []); + + // Derive seed phrase validation error (no setState in effect) + const seedPhraseError = useMemo(() => { + const trimmed = seedPhrase.trim(); + if (!trimmed) return ''; + if (!cryptoReady) return ''; + + const words = trimmed.split(/\s+/); + if (words.length !== 12 && words.length !== 24) { + return t('citizen.invalidSeedPhrase'); + } + + if (!validateMnemonic(trimmed)) { + return t('citizen.invalidSeedPhrase'); + } + + return ''; + }, [seedPhrase, cryptoReady, t]); const handleMaritalChange = (status: MaritalStatus) => { hapticImpact('light'); @@ -100,6 +125,14 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) { return; } + // Validate seed phrase + const trimmedSeed = seedPhrase.trim(); + if (!trimmedSeed || !cryptoReady || !validateMnemonic(trimmedSeed)) { + setError(t('citizen.invalidSeedPhrase')); + hapticNotification('error'); + return; + } + if (!consent) { setError(t('citizen.acceptConsent')); hapticNotification('error'); @@ -108,6 +141,9 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) { hapticImpact('medium'); + // Derive wallet address from seed phrase + const walletAddress = getAddressFromMnemonic(trimmedSeed); + const data: CitizenshipData = { fullName, fatherName, @@ -122,17 +158,25 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) { profession, referrerAddress: referrerAddress || undefined, walletAddress, + seedPhrase: trimmedSeed, timestamp: Date.now(), }; onSubmit(data); }; + const isSeedValid = cryptoReady && seedPhrase.trim() && !seedPhraseError; const inputClass = 'w-full px-4 py-3 bg-muted rounded-xl text-sm'; const labelClass = 'text-sm text-muted-foreground mb-1 block'; return (
+ {/* Privacy Notice */} +
+ +

{t('citizen.privacyNotice')}

+
+ {/* Full Name */}
@@ -319,6 +363,19 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) { />
+ {/* Seed Phrase */} +
+ +