From 8f4b9087f3d300f50ed7dd87ee315e41b82a5783 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Sat, 14 Feb 2026 23:24:59 +0300 Subject: [PATCH] feat: simplify Be Citizen flow - remove wallet steps, add seed phrase input - Remove wallet setup/create/import/connect steps from CitizenPage - Add privacy notice banner with Shield icon to form - Add seed phrase textarea with mnemonic validation - CitizenProcessing creates keypair directly from seed phrase - CitizenSuccess shows 3-step next process info - Add /citizens path support alongside ?page=citizen - Update bot URL to /citizens - Add 10 new i18n keys in all 6 languages --- package.json | 2 +- src/App.tsx | 5 +- src/components/citizen/CitizenForm.tsx | 69 ++++++++++++++-- src/components/citizen/CitizenProcessing.tsx | 63 +++++++++------ src/components/citizen/CitizenSuccess.tsx | 32 ++++++-- src/i18n/translations/ar.ts | 10 +++ src/i18n/translations/ckb.ts | 10 +++ src/i18n/translations/en.ts | 10 +++ src/i18n/translations/fa.ts | 10 +++ src/i18n/translations/krd.ts | 11 +++ src/i18n/translations/tr.ts | 10 +++ src/i18n/types.ts | 12 +++ src/lib/citizenship.ts | 1 + src/pages/CitizenPage.tsx | 82 +++----------------- src/version.json | 6 +- supabase/functions/telegram-bot/index.ts | 2 +- 16 files changed, 221 insertions(+), 114 deletions(-) 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 */} +
+ +