mirror of
https://github.com/pezkuwichain/pezkuwi-telegram-miniapp.git
synced 2026-04-21 23:37:55 +00:00
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
This commit is contained in:
+1
-1
@@ -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",
|
||||
|
||||
+3
-2
@@ -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 (
|
||||
<Suspense fallback={<SectionLoader />}>
|
||||
<CitizenPage />
|
||||
|
||||
@@ -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 (
|
||||
<div className="p-4 space-y-4 pb-24">
|
||||
{/* Privacy Notice */}
|
||||
<div className="flex gap-3 p-3 bg-blue-500/10 border border-blue-500/30 rounded-xl">
|
||||
<Shield className="w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-blue-300">{t('citizen.privacyNotice')}</p>
|
||||
</div>
|
||||
|
||||
{/* Full Name */}
|
||||
<div>
|
||||
<label className={labelClass}>{t('citizen.fullName')}</label>
|
||||
@@ -319,6 +363,19 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Seed Phrase */}
|
||||
<div>
|
||||
<label className={labelClass}>{t('citizen.seedPhrase')}</label>
|
||||
<textarea
|
||||
value={seedPhrase}
|
||||
onChange={(e) => setSeedPhrase(e.target.value)}
|
||||
className={`${inputClass} min-h-[80px] resize-none`}
|
||||
placeholder={t('citizen.seedPhrasePlaceholder')}
|
||||
rows={3}
|
||||
/>
|
||||
{seedPhraseError && <p className="text-xs text-red-400 mt-1">{seedPhraseError}</p>}
|
||||
</div>
|
||||
|
||||
{/* Referrer Address */}
|
||||
<div>
|
||||
<label className={labelClass}>{t('citizen.referrerAddress')}</label>
|
||||
@@ -352,7 +409,7 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) {
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={!consent || !fullName || !region || !email}
|
||||
disabled={!consent || !fullName || !region || !email || !isSeedValid}
|
||||
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50"
|
||||
>
|
||||
{t('citizen.submit')}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/**
|
||||
* Citizen Processing Component
|
||||
* Shows KurdistanSun animation while preparing data,
|
||||
* then enables sign button when ready
|
||||
* connects to People Chain and signs with seed phrase keypair
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { KurdistanSun } from '@/components/KurdistanSun';
|
||||
import { useTranslation } from '@/i18n';
|
||||
import { useTelegram } from '@/hooks/useTelegram';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { initWalletService, createKeypair } from '@/lib/wallet-service';
|
||||
import { initPeopleConnection } from '@/lib/rpc-manager';
|
||||
import type { CitizenshipData } from '@/lib/citizenship';
|
||||
import {
|
||||
calculateIdentityHash,
|
||||
@@ -19,24 +20,31 @@ import {
|
||||
|
||||
interface Props {
|
||||
citizenshipData: CitizenshipData;
|
||||
onSuccess: (identityHash: string, blockHash?: string) => void;
|
||||
onSuccess: (identityHash: string, walletAddress: string) => void;
|
||||
onError: (error: string) => void;
|
||||
}
|
||||
|
||||
type ProcessingState = 'preparing' | 'ready' | 'signing';
|
||||
type ProcessingState = 'preparing' | 'connecting' | 'ready' | 'signing';
|
||||
|
||||
export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { hapticImpact, hapticNotification } = useTelegram();
|
||||
const { peopleApi, keypair } = useWallet();
|
||||
|
||||
const [state, setState] = useState<ProcessingState>('preparing');
|
||||
const [identityHash, setIdentityHash] = useState<string>('');
|
||||
const seedPhraseRef = useRef<string>(citizenshipData.seedPhrase);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const peopleApiRef = useRef<any>(null);
|
||||
|
||||
// Prepare data on mount
|
||||
// Prepare data and connect to chain on mount
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const prepare = async () => {
|
||||
try {
|
||||
// Init crypto
|
||||
await initWalletService();
|
||||
|
||||
// Mock IPFS upload
|
||||
const ipfsCid = await uploadToIPFS(citizenshipData);
|
||||
|
||||
@@ -44,26 +52,35 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
|
||||
const hash = calculateIdentityHash(citizenshipData.fullName, citizenshipData.email, [
|
||||
ipfsCid,
|
||||
]);
|
||||
if (cancelled) return;
|
||||
setIdentityHash(hash);
|
||||
|
||||
// Save encrypted data locally
|
||||
saveCitizenshipLocally(citizenshipData);
|
||||
|
||||
// Small delay to show animation
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
// Connect to People Chain
|
||||
setState('connecting');
|
||||
const peopleApi = await initPeopleConnection();
|
||||
if (cancelled) return;
|
||||
peopleApiRef.current = peopleApi;
|
||||
|
||||
setState('ready');
|
||||
hapticNotification('success');
|
||||
} catch (err) {
|
||||
onError(err instanceof Error ? err.message : 'Preparation failed');
|
||||
if (!cancelled) {
|
||||
onError(err instanceof Error ? err.message : 'Preparation failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
prepare();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [citizenshipData, hapticNotification, onError]);
|
||||
|
||||
const handleSign = useCallback(async () => {
|
||||
if (!peopleApi || !keypair) {
|
||||
if (!peopleApiRef.current || !seedPhraseRef.current) {
|
||||
onError(t('citizen.walletNotConnected'));
|
||||
return;
|
||||
}
|
||||
@@ -72,35 +89,32 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
|
||||
hapticImpact('medium');
|
||||
|
||||
try {
|
||||
const keypair = createKeypair(seedPhraseRef.current);
|
||||
|
||||
const result = await applyCitizenship(
|
||||
peopleApi,
|
||||
peopleApiRef.current,
|
||||
keypair,
|
||||
identityHash,
|
||||
citizenshipData.referrerAddress || null
|
||||
);
|
||||
|
||||
// Clear seed phrase from memory
|
||||
seedPhraseRef.current = '';
|
||||
|
||||
if (result.success) {
|
||||
hapticNotification('success');
|
||||
onSuccess(identityHash, result.blockHash);
|
||||
onSuccess(identityHash, citizenshipData.walletAddress);
|
||||
} else {
|
||||
hapticNotification('error');
|
||||
onError(result.error || t('citizen.submissionFailed'));
|
||||
}
|
||||
} catch (err) {
|
||||
// Clear seed phrase from memory
|
||||
seedPhraseRef.current = '';
|
||||
hapticNotification('error');
|
||||
onError(err instanceof Error ? err.message : t('citizen.submissionFailed'));
|
||||
}
|
||||
}, [
|
||||
peopleApi,
|
||||
keypair,
|
||||
citizenshipData,
|
||||
identityHash,
|
||||
hapticImpact,
|
||||
hapticNotification,
|
||||
onSuccess,
|
||||
onError,
|
||||
t,
|
||||
]);
|
||||
}, [citizenshipData, identityHash, hapticImpact, hapticNotification, onSuccess, onError, t]);
|
||||
|
||||
const isReady = state === 'ready';
|
||||
const isSigning = state === 'signing';
|
||||
@@ -116,6 +130,7 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
|
||||
<div className="text-center space-y-2">
|
||||
<p className="text-lg font-medium">
|
||||
{state === 'preparing' && t('citizen.preparingData')}
|
||||
{state === 'connecting' && t('citizen.connectingChain')}
|
||||
{state === 'ready' && t('citizen.readyToSign')}
|
||||
{state === 'signing' && t('citizen.signingTx')}
|
||||
</p>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Citizen Success Screen
|
||||
* Shows after successful citizenship application submission
|
||||
* Displays pending referral status instead of final approval
|
||||
* Displays 3-step process info for next steps
|
||||
*/
|
||||
|
||||
import { CheckCircle, Clock } from 'lucide-react';
|
||||
import { CheckCircle, Clock, ArrowRight } from 'lucide-react';
|
||||
import { useTranslation } from '@/i18n';
|
||||
import { useTelegram } from '@/hooks/useTelegram';
|
||||
import { formatAddress } from '@/lib/wallet-service';
|
||||
@@ -12,6 +12,7 @@ import { formatAddress } from '@/lib/wallet-service';
|
||||
interface Props {
|
||||
address: string;
|
||||
identityHash: string;
|
||||
hasReferrer: boolean;
|
||||
onOpenApp: () => void;
|
||||
}
|
||||
|
||||
@@ -34,9 +35,26 @@ export function CitizenSuccess({ address, identityHash, onOpenApp }: Props) {
|
||||
{/* Title */}
|
||||
<div className="text-center space-y-2">
|
||||
<h1 className="text-2xl font-bold">{t('citizen.applicationSubmitted')}</h1>
|
||||
<div className="flex items-center justify-center gap-2 text-yellow-500">
|
||||
<Clock className="w-4 h-4" />
|
||||
<p className="text-sm">{t('citizen.pendingReferral')}</p>
|
||||
</div>
|
||||
|
||||
{/* 3-Step Process */}
|
||||
<div className="w-full max-w-sm space-y-3">
|
||||
{/* Step 1 - Completed */}
|
||||
<div className="flex items-start gap-3 p-3 bg-green-500/10 border border-green-500/30 rounded-xl">
|
||||
<CheckCircle className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-green-400">{t('citizen.stepApplicationSent')}</p>
|
||||
</div>
|
||||
|
||||
{/* Step 2 - Pending */}
|
||||
<div className="flex items-start gap-3 p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-xl">
|
||||
<Clock className="w-5 h-5 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-yellow-400">{t('citizen.stepReferrerApproval')}</p>
|
||||
</div>
|
||||
|
||||
{/* Step 3 - Future */}
|
||||
<div className="flex items-start gap-3 p-3 bg-muted/50 border border-border rounded-xl">
|
||||
<ArrowRight className="w-5 h-5 text-muted-foreground flex-shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-muted-foreground">{t('citizen.stepConfirm')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -52,9 +70,9 @@ export function CitizenSuccess({ address, identityHash, onOpenApp }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info Note */}
|
||||
{/* Next Steps Info */}
|
||||
<p className="text-xs text-muted-foreground text-center max-w-sm">
|
||||
{t('citizen.applicationInfo')}
|
||||
{t('citizen.nextStepsInfo')}
|
||||
</p>
|
||||
|
||||
{/* Open App Button */}
|
||||
|
||||
@@ -623,6 +623,16 @@ const ar: Translations = {
|
||||
applicationInfo: 'بعد موافقة المُحيل، يمكنك التأكيد',
|
||||
depositRequired: '١ HEZ وديعة مطلوبة',
|
||||
walletAddress: 'عنوان المحفظة',
|
||||
privacyNotice:
|
||||
'لا يتم إرسال معلوماتك الشخصية إلى أي مكان. يتم فقط إنشاء رمز هاش وتسجيله على البلوكشين.',
|
||||
seedPhrase: 'Seed Phrase',
|
||||
seedPhrasePlaceholder: 'الصق seed phrase المكون من ١٢ أو ٢٤ كلمة هنا...',
|
||||
invalidSeedPhrase: 'Seed phrase غير صالح. يرجى إدخال ١٢ أو ٢٤ كلمة صالحة.',
|
||||
connectingChain: 'جاري الاتصال بـ People Chain...',
|
||||
stepApplicationSent: 'تم إرسال الطلب (مكتمل)',
|
||||
stepReferrerApproval: 'في انتظار موافقة المُحيل',
|
||||
stepConfirm: 'بعد الموافقة، قم بالتأكيد من محفظتك',
|
||||
nextStepsInfo: 'تابع الخطوات التالية من محفظتك',
|
||||
fillAllFields: 'يرجى ملء جميع الحقول',
|
||||
acceptConsent: 'يرجى تحديد مربع الموافقة',
|
||||
walletNotConnected: 'المحفظة غير متصلة',
|
||||
|
||||
@@ -625,6 +625,16 @@ const ckb: Translations = {
|
||||
applicationInfo: 'کاتێک ڕیفێڕەر پەسەند بکات، دەتوانیت confirm بکەیت',
|
||||
depositRequired: '١ HEZ ئەمانەت پێویستە',
|
||||
walletAddress: 'ناونیشانی جزدان',
|
||||
privacyNotice:
|
||||
'زانیارییە کەسییەکانت بۆ هیچ شوێنێک نانێردرێت. تەنها کۆدێکی هاش دروست دەکرێت و لە بلۆکچەین تۆمار دەکرێت.',
|
||||
seedPhrase: 'Seed Phrase',
|
||||
seedPhrasePlaceholder: 'Seed phrase ی ١٢ یان ٢٤ وشەییت لێرە بلکێنە...',
|
||||
invalidSeedPhrase: 'Seed phrase نادروستە. تکایە ١٢ یان ٢٤ وشەی دروست بنووسە.',
|
||||
connectingChain: 'پەیوەندیکردن بە People Chain...',
|
||||
stepApplicationSent: 'داواکاری نێردرا (تەواو)',
|
||||
stepReferrerApproval: 'چاوەڕوانی پەسەندکردنی ڕیفێڕەر',
|
||||
stepConfirm: 'دوای پەسەندکردن، لە جزدانەکەت confirm بکە',
|
||||
nextStepsInfo: 'هەنگاوە داهاتووەکان لە جزدانەکەت بەدواداگرە',
|
||||
fillAllFields: 'تکایە هەموو خانەکان پڕ بکەرەوە',
|
||||
acceptConsent: 'تکایە خانەی ڕەزامەندی نیشان بدە',
|
||||
walletNotConnected: 'جزدان پەیوەندی نییە',
|
||||
|
||||
@@ -624,6 +624,16 @@ const en: Translations = {
|
||||
applicationInfo: 'Once your referrer approves, you can confirm',
|
||||
depositRequired: '1 HEZ deposit required',
|
||||
walletAddress: 'Wallet Address',
|
||||
privacyNotice:
|
||||
'Your personal information is never sent anywhere. Only a hash code is generated and recorded on the blockchain.',
|
||||
seedPhrase: 'Seed Phrase',
|
||||
seedPhrasePlaceholder: 'Paste your 12 or 24 word seed phrase here...',
|
||||
invalidSeedPhrase: 'Invalid seed phrase. Please enter 12 or 24 valid words.',
|
||||
connectingChain: 'Connecting to People Chain...',
|
||||
stepApplicationSent: 'Application submitted (completed)',
|
||||
stepReferrerApproval: 'Waiting for referrer approval',
|
||||
stepConfirm: 'After approval, confirm from your wallet',
|
||||
nextStepsInfo: 'Follow the next steps from your wallet',
|
||||
fillAllFields: 'Please fill in all fields',
|
||||
acceptConsent: 'Please accept the consent checkbox',
|
||||
walletNotConnected: 'Wallet not connected',
|
||||
|
||||
@@ -624,6 +624,16 @@ const fa: Translations = {
|
||||
applicationInfo: 'پس از تأیید معرف، میتوانید تأیید نهایی کنید',
|
||||
depositRequired: '۱ HEZ سپرده لازم است',
|
||||
walletAddress: 'آدرس کیف پول',
|
||||
privacyNotice:
|
||||
'اطلاعات شخصی شما به هیچ جایی ارسال نمیشود. فقط یک کد هش تولید و در بلاکچین ثبت میشود.',
|
||||
seedPhrase: 'Seed Phrase',
|
||||
seedPhrasePlaceholder: 'seed phrase ۱۲ یا ۲۴ کلمهای خود را اینجا بچسبانید...',
|
||||
invalidSeedPhrase: 'Seed phrase نامعتبر. لطفاً ۱۲ یا ۲۴ کلمه معتبر وارد کنید.',
|
||||
connectingChain: 'در حال اتصال به People Chain...',
|
||||
stepApplicationSent: 'درخواست ارسال شد (تکمیل شده)',
|
||||
stepReferrerApproval: 'در انتظار تأیید معرف',
|
||||
stepConfirm: 'پس از تأیید، از کیف پول خود confirm کنید',
|
||||
nextStepsInfo: 'مراحل بعدی را از کیف پول خود پیگیری کنید',
|
||||
fillAllFields: 'لطفاً همه فیلدها را پر کنید',
|
||||
acceptConsent: 'لطفاً کادر رضایت را علامت بزنید',
|
||||
walletNotConnected: 'کیف پول متصل نیست',
|
||||
|
||||
@@ -649,6 +649,17 @@ const krd: Translations = {
|
||||
applicationInfo: 'Dema referrer pejirîne, hûn dikarin confirm bikin',
|
||||
depositRequired: '1 HEZ depozîto pêwîst e',
|
||||
walletAddress: 'Navnîşana Cûzdan',
|
||||
privacyNotice:
|
||||
'Agahiyên te yên kesane tu cih nayên şandin. Tenê kodek hash tê çêkirin û li ser blockchain tê tomarkirin.',
|
||||
seedPhrase: 'Seed Phrase',
|
||||
seedPhrasePlaceholder: 'Seed phrase ya xwe ya 12 an 24 peyvan li vir bileqîne...',
|
||||
invalidSeedPhrase:
|
||||
'Seed phrase ne derbasdar e. Ji kerema xwe 12 an 24 peyvên derbasdar binivîse.',
|
||||
connectingChain: 'People Chain tê girêdan...',
|
||||
stepApplicationSent: 'Serlêdan hat şandin (temam)',
|
||||
stepReferrerApproval: 'Li benda pejirandina referrer',
|
||||
stepConfirm: 'Piştî pejirandinê, ji cûzdana xwe confirm bike',
|
||||
nextStepsInfo: 'Gavên pêş ji cûzdana xwe bişopîne',
|
||||
fillAllFields: 'Ji kerema xwe hemû qadan tije bike',
|
||||
acceptConsent: 'Ji kerema xwe qutiya pejirandinê nîşan bide',
|
||||
walletNotConnected: 'Cûzdan girêdayî nîne',
|
||||
|
||||
@@ -625,6 +625,16 @@ const tr: Translations = {
|
||||
applicationInfo: 'Referrer onayladıktan sonra confirm edebilirsiniz',
|
||||
depositRequired: '1 HEZ depozito gerekli',
|
||||
walletAddress: 'Cüzdan Adresi',
|
||||
privacyNotice:
|
||||
"Kişisel bilgileriniz hiçbir yere gönderilmez. Yalnızca bir hash kodu oluşturulup blockchain'e kaydedilir.",
|
||||
seedPhrase: 'Seed Phrase',
|
||||
seedPhrasePlaceholder: "12 veya 24 kelimelik seed phrase'inizi buraya yapıştırın...",
|
||||
invalidSeedPhrase: 'Geçersiz seed phrase. Lütfen 12 veya 24 geçerli kelime girin.',
|
||||
connectingChain: "People Chain'e bağlanılıyor...",
|
||||
stepApplicationSent: 'Başvuru gönderildi (tamamlandı)',
|
||||
stepReferrerApproval: 'Referrer onayı bekleniyor',
|
||||
stepConfirm: 'Onaydan sonra cüzdanınızdan confirm yapılacak',
|
||||
nextStepsInfo: 'Sonraki adımları cüzdanınızdan takip edin',
|
||||
fillAllFields: 'Lütfen tüm alanları doldurun',
|
||||
acceptConsent: 'Lütfen onay kutusunu işaretleyin',
|
||||
walletNotConnected: 'Cüzdan bağlı değil',
|
||||
|
||||
@@ -635,6 +635,18 @@ export interface Translations {
|
||||
walletAddress: string;
|
||||
applicationInfo: string;
|
||||
depositRequired: string;
|
||||
// Privacy & Seed phrase
|
||||
privacyNotice: string;
|
||||
seedPhrase: string;
|
||||
seedPhrasePlaceholder: string;
|
||||
invalidSeedPhrase: string;
|
||||
// Processing
|
||||
connectingChain: string;
|
||||
// Success - next steps
|
||||
stepApplicationSent: string;
|
||||
stepReferrerApproval: string;
|
||||
stepConfirm: string;
|
||||
nextStepsInfo: string;
|
||||
// Errors
|
||||
fillAllFields: string;
|
||||
acceptConsent: string;
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface CitizenshipData {
|
||||
profession: string;
|
||||
referrerAddress?: string;
|
||||
walletAddress: string;
|
||||
seedPhrase: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
|
||||
+12
-70
@@ -1,30 +1,17 @@
|
||||
/**
|
||||
* Citizen Page
|
||||
* Standalone page for citizenship application flow
|
||||
* Accessed via ?page=citizen URL parameter
|
||||
* No bottom navigation bar
|
||||
* Accessed via ?page=citizen or /citizens URL
|
||||
* No bottom navigation bar, no wallet steps - direct form
|
||||
*/
|
||||
|
||||
import { useState, useCallback, lazy, Suspense } from 'react';
|
||||
import { Globe, Loader2 } from 'lucide-react';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { useTranslation, LANGUAGE_NAMES, VALID_LANGS } from '@/i18n';
|
||||
import type { LanguageCode } from '@/i18n';
|
||||
import type { CitizenshipData } from '@/lib/citizenship';
|
||||
|
||||
// Lazy load sub-components
|
||||
const WalletSetup = lazy(() =>
|
||||
import('@/components/wallet/WalletSetup').then((m) => ({ default: m.WalletSetup }))
|
||||
);
|
||||
const WalletCreate = lazy(() =>
|
||||
import('@/components/wallet/WalletCreate').then((m) => ({ default: m.WalletCreate }))
|
||||
);
|
||||
const WalletImport = lazy(() =>
|
||||
import('@/components/wallet/WalletImport').then((m) => ({ default: m.WalletImport }))
|
||||
);
|
||||
const WalletConnect = lazy(() =>
|
||||
import('@/components/wallet/WalletConnect').then((m) => ({ default: m.WalletConnect }))
|
||||
);
|
||||
const CitizenForm = lazy(() =>
|
||||
import('@/components/citizen/CitizenForm').then((m) => ({ default: m.CitizenForm }))
|
||||
);
|
||||
@@ -35,14 +22,7 @@ const CitizenSuccess = lazy(() =>
|
||||
import('@/components/citizen/CitizenSuccess').then((m) => ({ default: m.CitizenSuccess }))
|
||||
);
|
||||
|
||||
type Step =
|
||||
| 'wallet-setup'
|
||||
| 'wallet-create'
|
||||
| 'wallet-import'
|
||||
| 'wallet-connect'
|
||||
| 'form'
|
||||
| 'processing'
|
||||
| 'success';
|
||||
type Step = 'form' | 'processing' | 'success';
|
||||
|
||||
function SectionLoader() {
|
||||
return (
|
||||
@@ -53,32 +33,13 @@ function SectionLoader() {
|
||||
}
|
||||
|
||||
export function CitizenPage() {
|
||||
const { hasWallet, isConnected, address, deleteWalletData } = useWallet();
|
||||
const { t, lang, setLang } = useTranslation();
|
||||
const [showLangMenu, setShowLangMenu] = useState(false);
|
||||
const [citizenshipData, setCitizenshipData] = useState<CitizenshipData | null>(null);
|
||||
const [identityHash, setIdentityHash] = useState<string>('');
|
||||
const [walletAddress, setWalletAddress] = useState<string>('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Determine initial step based on wallet state
|
||||
const getInitialStep = (): Step => {
|
||||
if (!hasWallet) return 'wallet-setup';
|
||||
if (!isConnected) return 'wallet-connect';
|
||||
return 'form';
|
||||
};
|
||||
|
||||
const [step, setStep] = useState<Step>(getInitialStep);
|
||||
|
||||
// Wallet setup handlers
|
||||
const handleWalletCreate = useCallback(() => setStep('wallet-create'), []);
|
||||
const handleWalletImport = useCallback(() => setStep('wallet-import'), []);
|
||||
const handleWalletCreated = useCallback(() => setStep('wallet-connect'), []);
|
||||
const handleWalletImported = useCallback(() => setStep('wallet-connect'), []);
|
||||
const handleWalletConnected = useCallback(() => setStep('form'), []);
|
||||
const handleWalletDelete = useCallback(() => {
|
||||
deleteWalletData();
|
||||
setStep('wallet-setup');
|
||||
}, [deleteWalletData]);
|
||||
const [step, setStep] = useState<Step>('form');
|
||||
|
||||
// Form submission
|
||||
const handleFormSubmit = useCallback((data: CitizenshipData) => {
|
||||
@@ -88,8 +49,9 @@ export function CitizenPage() {
|
||||
}, []);
|
||||
|
||||
// Processing result
|
||||
const handleSuccess = useCallback((hash: string) => {
|
||||
const handleSuccess = useCallback((hash: string, address: string) => {
|
||||
setIdentityHash(hash);
|
||||
setWalletAddress(address);
|
||||
setStep('success');
|
||||
}, []);
|
||||
|
||||
@@ -100,9 +62,9 @@ export function CitizenPage() {
|
||||
|
||||
// Open main app
|
||||
const handleOpenApp = useCallback(() => {
|
||||
// Remove ?page=citizen and navigate to main app
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('page');
|
||||
url.pathname = '/';
|
||||
window.location.href = url.toString();
|
||||
}, []);
|
||||
|
||||
@@ -156,28 +118,7 @@ export function CitizenPage() {
|
||||
{/* Content */}
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<Suspense fallback={<SectionLoader />}>
|
||||
{step === 'wallet-setup' && (
|
||||
<WalletSetup onCreate={handleWalletCreate} onImport={handleWalletImport} />
|
||||
)}
|
||||
|
||||
{step === 'wallet-create' && (
|
||||
<WalletCreate onComplete={handleWalletCreated} onBack={() => setStep('wallet-setup')} />
|
||||
)}
|
||||
|
||||
{step === 'wallet-import' && (
|
||||
<WalletImport
|
||||
onComplete={handleWalletImported}
|
||||
onBack={() => setStep('wallet-setup')}
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === 'wallet-connect' && (
|
||||
<WalletConnect onConnected={handleWalletConnected} onDelete={handleWalletDelete} />
|
||||
)}
|
||||
|
||||
{step === 'form' && address && (
|
||||
<CitizenForm walletAddress={address} onSubmit={handleFormSubmit} />
|
||||
)}
|
||||
{step === 'form' && <CitizenForm onSubmit={handleFormSubmit} />}
|
||||
|
||||
{step === 'processing' && citizenshipData && (
|
||||
<CitizenProcessing
|
||||
@@ -187,10 +128,11 @@ export function CitizenPage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === 'success' && address && (
|
||||
{step === 'success' && (
|
||||
<CitizenSuccess
|
||||
address={address}
|
||||
address={walletAddress}
|
||||
identityHash={identityHash}
|
||||
hasReferrer={!!citizenshipData?.referrerAddress}
|
||||
onOpenApp={handleOpenApp}
|
||||
/>
|
||||
)}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.0.194",
|
||||
"buildTime": "2026-02-14T19:00:32.936Z",
|
||||
"buildNumber": 1771095632937
|
||||
"version": "1.0.196",
|
||||
"buildTime": "2026-02-14T20:24:59.270Z",
|
||||
"buildNumber": 1771100699271
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ async function sendKrdWelcome(token: string, chatId: number) {
|
||||
[
|
||||
{
|
||||
text: '🏛️ Be Citizen / Bibe Welatî',
|
||||
web_app: { url: `${appUrl}?page=citizen` },
|
||||
web_app: { url: `${appUrl}/citizens` },
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user