From d2969c1062f8fbed06cef421850a641e49d545a8 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Fri, 20 Feb 2026 01:20:57 +0300 Subject: [PATCH] feat: add confirmCitizenship step to Rewards section --- package.json | 2 +- src/i18n/translations/ar.ts | 9 +++ src/i18n/translations/ckb.ts | 9 +++ src/i18n/translations/en.ts | 9 +++ src/i18n/translations/fa.ts | 9 +++ src/i18n/translations/krd.ts | 9 +++ src/i18n/translations/tr.ts | 9 +++ src/i18n/types.ts | 9 +++ src/lib/citizenship.ts | 60 +++++++++++++++++++ src/sections/Rewards.tsx | 110 ++++++++++++++++++++++++++++++++++- src/version.json | 6 +- 11 files changed, 236 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index bd2200d..55a57f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pezkuwi-telegram-miniapp", - "version": "1.0.207", + "version": "1.0.209", "type": "module", "description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards", "author": "Pezkuwichain Team", diff --git a/src/i18n/translations/ar.ts b/src/i18n/translations/ar.ts index 1984e45..15cf287 100644 --- a/src/i18n/translations/ar.ts +++ b/src/i18n/translations/ar.ts @@ -125,6 +125,15 @@ const ar: Translations = { stakingZeroWarning: 'إذا كان التخزين 0، فنقاط الثقة أيضًا 0. قم بالتخزين أولاً!', refreshScores: 'تحديث النقاط', points: 'نقاط', + citizenshipStatus: 'حالة المواطنة', + pendingApproval: 'بانتظار الموافقة', + approved: 'تمت الموافقة', + waitingReferrer: 'بانتظار موافقة المُحيل...', + confirmCitizenship: 'تأكيد المواطنة', + confirmingCitizenship: 'جاري تأكيد المواطنة...', + signingBlockchain: 'جاري التوقيع على البلوكتشين', + citizenshipConfirmed: 'مبروك! أنت الآن مواطن!', + citizenshipFailed: 'فشل التأكيد', }, wallet: { diff --git a/src/i18n/translations/ckb.ts b/src/i18n/translations/ckb.ts index 5f31498..c08cb08 100644 --- a/src/i18n/translations/ckb.ts +++ b/src/i18n/translations/ckb.ts @@ -126,6 +126,15 @@ const ckb: Translations = { stakingZeroWarning: 'ئەگەر ستەیکینگ ٠ بێت، خاڵی متمانەش ٠ دەبێت. سەرەتا ستەیک بکە!', refreshScores: 'نوێکردنەوەی خاڵەکان', points: 'خاڵ', + citizenshipStatus: 'بارودۆخی هاوڵاتیبوون', + pendingApproval: 'چاوەڕوانی پەسەندکردن', + approved: 'پەسەندکراو', + waitingReferrer: 'چاوەڕوانی پەسەندکردنی ناساندنکەر...', + confirmCitizenship: 'هاوڵاتیبوون پشتڕاست بکەرەوە', + confirmingCitizenship: 'هاوڵاتیبوون پشتڕاست دەکرێتەوە...', + signingBlockchain: 'لەسەر بلۆکچەین واژوو دەکرێت', + citizenshipConfirmed: 'پیرۆزبێت! تۆ ئێستا هاوڵاتیت!', + citizenshipFailed: 'پشتڕاستکردنەوە سەرنەکەوت', }, wallet: { diff --git a/src/i18n/translations/en.ts b/src/i18n/translations/en.ts index 9324d24..792aaca 100644 --- a/src/i18n/translations/en.ts +++ b/src/i18n/translations/en.ts @@ -125,6 +125,15 @@ const en: Translations = { stakingZeroWarning: 'If Staking is 0, Trust score is also 0. Stake first!', refreshScores: 'Refresh Scores', points: 'points', + citizenshipStatus: 'Citizenship Status', + pendingApproval: 'Pending', + approved: 'Approved', + waitingReferrer: 'Waiting for referrer approval...', + confirmCitizenship: 'Confirm Citizenship', + confirmingCitizenship: 'Confirming citizenship...', + signingBlockchain: 'Signing on blockchain', + citizenshipConfirmed: 'Congratulations! You are now a citizen!', + citizenshipFailed: 'Confirmation failed', }, wallet: { diff --git a/src/i18n/translations/fa.ts b/src/i18n/translations/fa.ts index 4ec56af..9fdadb9 100644 --- a/src/i18n/translations/fa.ts +++ b/src/i18n/translations/fa.ts @@ -125,6 +125,15 @@ const fa: Translations = { stakingZeroWarning: 'اگر سهام‌گذاری ۰ باشد، امتیاز اعتماد نیز ۰ است. ابتدا سهام‌گذاری کنید!', refreshScores: 'بازنشانی امتیازها', points: 'امتیاز', + citizenshipStatus: 'وضعیت شهروندی', + pendingApproval: 'در انتظار تایید', + approved: 'تایید شده', + waitingReferrer: 'در انتظار تایید معرف...', + confirmCitizenship: 'تایید شهروندی', + confirmingCitizenship: 'در حال تایید شهروندی...', + signingBlockchain: 'در حال امضا روی بلاکچین', + citizenshipConfirmed: 'تبریک! شما اکنون شهروند هستید!', + citizenshipFailed: 'تایید ناموفق بود', }, wallet: { diff --git a/src/i18n/translations/krd.ts b/src/i18n/translations/krd.ts index 04d5c0d..0635228 100644 --- a/src/i18n/translations/krd.ts +++ b/src/i18n/translations/krd.ts @@ -130,6 +130,15 @@ const krd: Translations = { 'Staking 0 be, Trust p\u00fban j\u00ee 0 dibe. Ber\u00ee her ti\u015ft\u00ee stake bike!', refreshScores: 'P\u00fbanan N\u00fbve Bike', points: 'p\u00fban', + citizenshipStatus: 'Rew\u015fa Welat\u00eeb\u00fbn\u00ea', + pendingApproval: 'Li benda pejrandin\u00ea', + approved: 'Pejrand\u00ee', + waitingReferrer: 'Li benda pejirandina referrer...', + confirmCitizenship: 'Welat\u00eeb\u00fbn\u00ea Pi\u015ftrast Bike', + confirmingCitizenship: 'Welat\u00eeb\u00fbn t\u00ea pi\u015ftrastkirin...', + signingBlockchain: 'Li blockchain t\u00ea \u00eemzekirin', + citizenshipConfirmed: 'P\u00eeroz be! Tu b\u00fby\u00ee welat\u00ee!', + citizenshipFailed: 'Pi\u015ftrastkirin biserneket', }, wallet: { diff --git a/src/i18n/translations/tr.ts b/src/i18n/translations/tr.ts index 7b4ccd8..ad58e79 100644 --- a/src/i18n/translations/tr.ts +++ b/src/i18n/translations/tr.ts @@ -125,6 +125,15 @@ const tr: Translations = { stakingZeroWarning: 'Staking 0 ise Güven puanı da 0 olur. Önce stake yapın!', refreshScores: 'Puanları Yenile', points: 'puan', + citizenshipStatus: 'Vatanda\u015fl\u0131k Durumu', + pendingApproval: 'Onay Bekliyor', + approved: 'Onaylandı', + waitingReferrer: 'Referrer onay\u0131 bekleniyor...', + confirmCitizenship: 'Vatanda\u015fl\u0131\u011f\u0131 Onayla', + confirmingCitizenship: 'Vatanda\u015fl\u0131k onaylan\u0131yor...', + signingBlockchain: "Blockchain'de imzalan\u0131yor", + citizenshipConfirmed: 'Tebrikler! Art\u0131k vatanda\u015fs\u0131n\u0131z!', + citizenshipFailed: 'Onay ba\u015far\u0131s\u0131z oldu', }, wallet: { diff --git a/src/i18n/types.ts b/src/i18n/types.ts index eb6e3a9..adb650f 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -127,6 +127,15 @@ export interface Translations { stakingZeroWarning: string; refreshScores: string; points: string; + citizenshipStatus: string; + pendingApproval: string; + approved: string; + waitingReferrer: string; + confirmCitizenship: string; + confirmingCitizenship: string; + signingBlockchain: string; + citizenshipConfirmed: string; + citizenshipFailed: string; }; // Wallet section diff --git a/src/lib/citizenship.ts b/src/lib/citizenship.ts index 42af7c7..3a68dfc 100644 --- a/src/lib/citizenship.ts +++ b/src/lib/citizenship.ts @@ -183,3 +183,63 @@ export async function applyCitizenship( }; } } + +// ── Confirm Citizenship ───────────────────────────────────────────── + +export async function confirmCitizenship( + api: ApiPromise, + keypair: KeyringPair +): Promise { + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const tx = api.tx as any; + if (!tx?.identityKyc?.confirmCitizenship) { + return { success: false, error: 'Identity KYC pallet not available' }; + } + + const result = await new Promise((resolve) => { + tx.identityKyc + .confirmCitizenship() + .signAndSend( + keypair, + { nonce: -1 }, + ({ + status, + dispatchError, + }: { + status: { + isInBlock: boolean; + isFinalized: boolean; + asInBlock?: { toString: () => string }; + asFinalized?: { toString: () => string }; + }; + dispatchError?: { isModule: boolean; asModule: unknown; toString: () => string }; + }) => { + if (status.isInBlock || status.isFinalized) { + if (dispatchError) { + let errorMessage = 'Citizenship confirmation failed'; + if (dispatchError.isModule) { + const decoded = api.registry.findMetaError( + dispatchError.asModule as Parameters[0] + ); + errorMessage = `${decoded.section}.${decoded.name}`; + } + resolve({ success: false, error: errorMessage }); + return; + } + const blockHash = status.asFinalized?.toString() || status.asInBlock?.toString(); + resolve({ success: true, blockHash }); + } + } + ) + .catch((error: Error) => resolve({ success: false, error: error.message })); + }); + + return result; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }; + } +} diff --git a/src/sections/Rewards.tsx b/src/sections/Rewards.tsx index 0b3717c..a55f716 100644 --- a/src/sections/Rewards.tsx +++ b/src/sections/Rewards.tsx @@ -44,6 +44,12 @@ import { formatRewardDate, type StakingRewardsResult, } from '@/lib/subquery'; +import { + getCitizenshipStatus, + confirmCitizenship, + type CitizenshipStatus, +} from '@/lib/citizenship'; +import { KurdistanSun } from '@/components/KurdistanSun'; // Activity tracking constants const ACTIVITY_STORAGE_KEY = 'pezkuwi_last_active'; @@ -53,7 +59,7 @@ export function RewardsSection() { const { hapticImpact, hapticNotification, shareUrl, showAlert } = useTelegram(); const { user: authUser } = useAuth(); const { stats, myReferrals, loading, refreshStats } = useReferral(); - const { isConnected, address, peopleApi } = useWallet(); + const { isConnected, address, peopleApi, keypair } = useWallet(); const { t } = useTranslation(); const [copied, setCopied] = useState(false); @@ -64,6 +70,8 @@ export function RewardsSection() { const [stakingStatus, setStakingStatus] = useState(null); const [stakingRewards, setStakingRewards] = useState(null); const [scoresLoading, setScoresLoading] = useState(false); + const [citizenshipStatus, setCitizenshipStatus] = useState('NotStarted'); + const [showConfirmAnimation, setShowConfirmAnimation] = useState(false); // Check activity status const checkActivityStatus = useCallback(() => { @@ -132,6 +140,34 @@ export function RewardsSection() { } }, [activeTab, address, fetchUserScores]); + // Fetch citizenship status + useEffect(() => { + if (!peopleApi || !address) return; + getCitizenshipStatus(peopleApi, address).then(setCitizenshipStatus); + }, [peopleApi, address]); + + const handleConfirmCitizenship = async () => { + if (!peopleApi || !keypair) return; + setShowConfirmAnimation(true); + hapticImpact('medium'); + try { + const result = await confirmCitizenship(peopleApi, keypair); + if (result.success) { + hapticNotification('success'); + setCitizenshipStatus('Approved'); + showAlert(t('rewards.citizenshipConfirmed')); + } else { + hapticNotification('error'); + showAlert(result.error || t('rewards.citizenshipFailed')); + } + } catch (err) { + hapticNotification('error'); + showAlert(err instanceof Error ? err.message : t('rewards.citizenshipFailed')); + } finally { + setShowConfirmAnimation(false); + } + }; + const handleActivate = () => { hapticNotification('success'); localStorage.setItem(ACTIVITY_STORAGE_KEY, Date.now().toString()); @@ -332,6 +368,69 @@ export function RewardsSection() { )} + {/* Citizenship Status Card */} + {(citizenshipStatus === 'PendingReferral' || + citizenshipStatus === 'ReferrerApproved') && ( +
+
+ +

+ {t('rewards.citizenshipStatus')} +

+
+
+
+
+ {t('rewards.pendingApproval')} +
+
+ {citizenshipStatus === 'ReferrerApproved' ? ( + + ) : ( +
+ )} + {t('rewards.approved')} +
+
+ {citizenshipStatus === 'PendingReferral' && ( +

+ {t('rewards.waitingReferrer')} +

+ )} + {citizenshipStatus === 'ReferrerApproved' && ( + + )} +
+ )} + {/* Invite Card */}

@@ -748,6 +847,15 @@ export function RewardsSection() {

)}
+ + {/* Confirm Citizenship Overlay */} + {showConfirmAnimation && ( +
+ +

{t('rewards.confirmingCitizenship')}

+

{t('rewards.signingBlockchain')}

+
+ )}
); } diff --git a/src/version.json b/src/version.json index 6b5451c..b8dfecb 100644 --- a/src/version.json +++ b/src/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.207", - "buildTime": "2026-02-16T23:33:56.480Z", - "buildNumber": 1771284836480 + "version": "1.0.209", + "buildTime": "2026-02-19T22:20:57.906Z", + "buildNumber": 1771539657907 }