mirror of
https://github.com/pezkuwichain/pezkuwi-telegram-miniapp.git
synced 2026-04-22 03:07:55 +00:00
feat: migrate staking to Asset Hub and add citizen count card
- HEZStakingModal: switch all staking queries/tx from RC api to assetHubApi - Add citizen count card to Rewards overview (Hejmara Kurd Le Cihane) - Add getCitizenCount() to fetch total citizens from People Chain - Add translations for citizen count card (6 languages)
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pezkuwi-telegram-miniapp",
|
"name": "pezkuwi-telegram-miniapp",
|
||||||
"version": "1.0.213",
|
"version": "1.0.214",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards",
|
"description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards",
|
||||||
"author": "Pezkuwichain Team",
|
"author": "Pezkuwichain Team",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* HEZ Staking Modal for Telegram Mini App
|
* HEZ Staking Modal for Telegram Mini App
|
||||||
* Allows users to stake HEZ on Relay Chain for Trust Score
|
* Allows users to stake HEZ on Asset Hub for Trust Score
|
||||||
|
* (Staking moved from Relay Chain to Asset Hub)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
@@ -34,7 +35,7 @@ interface HEZStakingModalProps {
|
|||||||
const UNITS = 1_000_000_000_000; // 10^12
|
const UNITS = 1_000_000_000_000; // 10^12
|
||||||
|
|
||||||
export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||||
const { api, keypair, address, balance } = useWallet();
|
const { assetHubApi, keypair, address } = useWallet();
|
||||||
const { hapticImpact, hapticNotification, showAlert } = useTelegram();
|
const { hapticImpact, hapticNotification, showAlert } = useTelegram();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -47,21 +48,33 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
const [bondAmount, setBondAmount] = useState('');
|
const [bondAmount, setBondAmount] = useState('');
|
||||||
const [unbondAmount, setUnbondAmount] = useState('');
|
const [unbondAmount, setUnbondAmount] = useState('');
|
||||||
const [selectedValidators, setSelectedValidators] = useState<string[]>([]);
|
const [selectedValidators, setSelectedValidators] = useState<string[]>([]);
|
||||||
|
const [ahBalance, setAhBalance] = useState<string | null>(null);
|
||||||
|
|
||||||
// Fetch staking info
|
// Fetch staking info from Asset Hub
|
||||||
const fetchStakingInfo = useCallback(async () => {
|
const fetchStakingInfo = useCallback(async () => {
|
||||||
if (!api || !address) return;
|
if (!assetHubApi || !address) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const stakingPallet = api.query.staking as any;
|
const stakingPallet = assetHubApi.query.staking as any;
|
||||||
if (!stakingPallet) {
|
if (!stakingPallet) {
|
||||||
setError(t('staking.palletNotFound'));
|
setError(t('staking.palletNotFound'));
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch AH native HEZ balance
|
||||||
|
try {
|
||||||
|
const accountInfo = await assetHubApi.query.system.account(address);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const free = (accountInfo as any).data.free.toString();
|
||||||
|
const balanceNum = Number(free) / UNITS;
|
||||||
|
setAhBalance(balanceNum.toFixed(4));
|
||||||
|
} catch {
|
||||||
|
setAhBalance(null);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if user has bonded
|
// Check if user has bonded
|
||||||
const ledger = await stakingPallet.ledger(address);
|
const ledger = await stakingPallet.ledger(address);
|
||||||
|
|
||||||
@@ -93,7 +106,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
setStakingInfo(null);
|
setStakingInfo(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch validators
|
// Fetch validators from AH staking pallet
|
||||||
const validatorEntries = await stakingPallet.validators.entries();
|
const validatorEntries = await stakingPallet.validators.entries();
|
||||||
const validatorList: ValidatorInfo[] = validatorEntries
|
const validatorList: ValidatorInfo[] = validatorEntries
|
||||||
.slice(0, 20) // Limit to 20 validators
|
.slice(0, 20) // Limit to 20 validators
|
||||||
@@ -120,13 +133,13 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [api, address]);
|
}, [assetHubApi, address]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && api && address) {
|
if (isOpen && assetHubApi && address) {
|
||||||
fetchStakingInfo();
|
fetchStakingInfo();
|
||||||
}
|
}
|
||||||
}, [isOpen, api, address, fetchStakingInfo]);
|
}, [isOpen, assetHubApi, address, fetchStakingInfo]);
|
||||||
|
|
||||||
const formatHEZ = (amount: bigint): string => {
|
const formatHEZ = (amount: bigint): string => {
|
||||||
const hez = Number(amount) / UNITS;
|
const hez = Number(amount) / UNITS;
|
||||||
@@ -134,7 +147,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleBond = async () => {
|
const handleBond = async () => {
|
||||||
if (!api || !keypair || !bondAmount) return;
|
if (!assetHubApi || !keypair || !bondAmount) return;
|
||||||
|
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -143,7 +156,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
const amountBN = BigInt(Math.floor(parseFloat(bondAmount) * UNITS));
|
const amountBN = BigInt(Math.floor(parseFloat(bondAmount) * UNITS));
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const stakingPallet = api.tx.staking as any;
|
const stakingPallet = assetHubApi.tx.staking as any;
|
||||||
|
|
||||||
let tx;
|
let tx;
|
||||||
if (stakingInfo) {
|
if (stakingInfo) {
|
||||||
@@ -169,7 +182,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
if (dispatchError) {
|
if (dispatchError) {
|
||||||
let errorMsg = t('staking.bondFailed');
|
let errorMsg = t('staking.bondFailed');
|
||||||
if (dispatchError.isModule) {
|
if (dispatchError.isModule) {
|
||||||
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
|
||||||
errorMsg = `${decoded.section}.${decoded.name}`;
|
errorMsg = `${decoded.section}.${decoded.name}`;
|
||||||
}
|
}
|
||||||
reject(new Error(errorMsg));
|
reject(new Error(errorMsg));
|
||||||
@@ -196,14 +209,14 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleNominate = async () => {
|
const handleNominate = async () => {
|
||||||
if (!api || !keypair || selectedValidators.length === 0) return;
|
if (!assetHubApi || !keypair || selectedValidators.length === 0) return;
|
||||||
|
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const tx = (api.tx.staking as any).nominate(selectedValidators);
|
const tx = (assetHubApi.tx.staking as any).nominate(selectedValidators);
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
tx.signAndSend(
|
tx.signAndSend(
|
||||||
@@ -220,7 +233,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
if (dispatchError) {
|
if (dispatchError) {
|
||||||
let errorMsg = t('staking.nominateFailed');
|
let errorMsg = t('staking.nominateFailed');
|
||||||
if (dispatchError.isModule) {
|
if (dispatchError.isModule) {
|
||||||
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
|
||||||
errorMsg = `${decoded.section}.${decoded.name}`;
|
errorMsg = `${decoded.section}.${decoded.name}`;
|
||||||
}
|
}
|
||||||
reject(new Error(errorMsg));
|
reject(new Error(errorMsg));
|
||||||
@@ -246,7 +259,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUnbond = async () => {
|
const handleUnbond = async () => {
|
||||||
if (!api || !keypair || !unbondAmount) return;
|
if (!assetHubApi || !keypair || !unbondAmount) return;
|
||||||
|
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -255,7 +268,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
const amountBN = BigInt(Math.floor(parseFloat(unbondAmount) * UNITS));
|
const amountBN = BigInt(Math.floor(parseFloat(unbondAmount) * UNITS));
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const tx = (api.tx.staking as any).unbond(amountBN.toString());
|
const tx = (assetHubApi.tx.staking as any).unbond(amountBN.toString());
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
tx.signAndSend(
|
tx.signAndSend(
|
||||||
@@ -272,7 +285,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
if (dispatchError) {
|
if (dispatchError) {
|
||||||
let errorMsg = t('staking.unbondFailed');
|
let errorMsg = t('staking.unbondFailed');
|
||||||
if (dispatchError.isModule) {
|
if (dispatchError.isModule) {
|
||||||
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
|
||||||
errorMsg = `${decoded.section}.${decoded.name}`;
|
errorMsg = `${decoded.section}.${decoded.name}`;
|
||||||
}
|
}
|
||||||
reject(new Error(errorMsg));
|
reject(new Error(errorMsg));
|
||||||
@@ -448,7 +461,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
<div className="bg-secondary/50 rounded-xl p-4">
|
<div className="bg-secondary/50 rounded-xl p-4">
|
||||||
<div className="flex justify-between text-sm mb-2">
|
<div className="flex justify-between text-sm mb-2">
|
||||||
<span className="text-muted-foreground">{t('staking.yourBalance')}</span>
|
<span className="text-muted-foreground">{t('staking.yourBalance')}</span>
|
||||||
<span>{balance || '0'} HEZ</span>
|
<span>{ahBalance || '0'} HEZ</span>
|
||||||
</div>
|
</div>
|
||||||
{stakingInfo && (
|
{stakingInfo && (
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
@@ -473,7 +486,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
|||||||
className="w-full bg-secondary border border-border rounded-xl p-4 pr-20 text-lg"
|
className="w-full bg-secondary border border-border rounded-xl p-4 pr-20 text-lg"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={() => setBondAmount(balance || '0')}
|
onClick={() => setBondAmount(ahBalance || '0')}
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-primary"
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-primary"
|
||||||
>
|
>
|
||||||
MAX
|
MAX
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ const ar: Translations = {
|
|||||||
recordingTrustScore: 'جاري تسجيل نقاط الثقة...',
|
recordingTrustScore: 'جاري تسجيل نقاط الثقة...',
|
||||||
trustScoreRecorded: 'تم تسجيل نقاط الثقة!',
|
trustScoreRecorded: 'تم تسجيل نقاط الثقة!',
|
||||||
trustScoreRecordFailed: 'فشل تسجيل النقاط',
|
trustScoreRecordFailed: 'فشل تسجيل النقاط',
|
||||||
|
citizenCountTitle: 'الأكراد في العالم',
|
||||||
|
citizenCountDesc: 'المواطنون المسجلون على PezkuwiChain',
|
||||||
|
beCitizen: 'كن مواطناً',
|
||||||
},
|
},
|
||||||
|
|
||||||
wallet: {
|
wallet: {
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ const ckb: Translations = {
|
|||||||
recordingTrustScore: 'خاڵ تۆمار دەکرێت...',
|
recordingTrustScore: 'خاڵ تۆمار دەکرێت...',
|
||||||
trustScoreRecorded: 'خاڵی متمانە تۆمار کرا!',
|
trustScoreRecorded: 'خاڵی متمانە تۆمار کرا!',
|
||||||
trustScoreRecordFailed: 'تۆمارکردنی خاڵ سەرنەکەوت',
|
trustScoreRecordFailed: 'تۆمارکردنی خاڵ سەرنەکەوت',
|
||||||
|
citizenCountTitle: 'ژمارەی کورد لە جیهان',
|
||||||
|
citizenCountDesc: 'هاوڵاتییانی تۆمارکراو لە PezkuwiChain',
|
||||||
|
beCitizen: 'ببە هاوڵاتی',
|
||||||
},
|
},
|
||||||
|
|
||||||
wallet: {
|
wallet: {
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ const en: Translations = {
|
|||||||
recordingTrustScore: 'Recording trust score...',
|
recordingTrustScore: 'Recording trust score...',
|
||||||
trustScoreRecorded: 'Trust score recorded!',
|
trustScoreRecorded: 'Trust score recorded!',
|
||||||
trustScoreRecordFailed: 'Failed to record score',
|
trustScoreRecordFailed: 'Failed to record score',
|
||||||
|
citizenCountTitle: 'Kurds in the World',
|
||||||
|
citizenCountDesc: 'Citizens registered on PezkuwiChain',
|
||||||
|
beCitizen: 'Be Citizen',
|
||||||
},
|
},
|
||||||
|
|
||||||
wallet: {
|
wallet: {
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ const fa: Translations = {
|
|||||||
recordingTrustScore: 'در حال ثبت امتیاز اعتماد...',
|
recordingTrustScore: 'در حال ثبت امتیاز اعتماد...',
|
||||||
trustScoreRecorded: 'امتیاز اعتماد ثبت شد!',
|
trustScoreRecorded: 'امتیاز اعتماد ثبت شد!',
|
||||||
trustScoreRecordFailed: 'ثبت امتیاز ناموفق بود',
|
trustScoreRecordFailed: 'ثبت امتیاز ناموفق بود',
|
||||||
|
citizenCountTitle: 'کوردها در جهان',
|
||||||
|
citizenCountDesc: 'شهروندان ثبتشده در PezkuwiChain',
|
||||||
|
beCitizen: 'شهروند شوید',
|
||||||
},
|
},
|
||||||
|
|
||||||
wallet: {
|
wallet: {
|
||||||
|
|||||||
@@ -149,6 +149,9 @@ const krd: Translations = {
|
|||||||
recordingTrustScore: 'Pûan tê tomarkirin...',
|
recordingTrustScore: 'Pûan tê tomarkirin...',
|
||||||
trustScoreRecorded: 'Pûana pêbaweriyê hat tomarkirin!',
|
trustScoreRecorded: 'Pûana pêbaweriyê hat tomarkirin!',
|
||||||
trustScoreRecordFailed: 'Tomarkirina pûanê biserneket',
|
trustScoreRecordFailed: 'Tomarkirina pûanê biserneket',
|
||||||
|
citizenCountTitle: 'Hejmara Kurd Le Cîhanê',
|
||||||
|
citizenCountDesc: 'Welatiyên ku li ser PezkuwiChain qeyd bûne',
|
||||||
|
beCitizen: 'Bibe Welatî',
|
||||||
},
|
},
|
||||||
|
|
||||||
wallet: {
|
wallet: {
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ const tr: Translations = {
|
|||||||
recordingTrustScore: 'Güven puanı kaydediliyor...',
|
recordingTrustScore: 'Güven puanı kaydediliyor...',
|
||||||
trustScoreRecorded: 'Güven puanı kaydedildi!',
|
trustScoreRecorded: 'Güven puanı kaydedildi!',
|
||||||
trustScoreRecordFailed: 'Puan kaydedilemedi',
|
trustScoreRecordFailed: 'Puan kaydedilemedi',
|
||||||
|
citizenCountTitle: 'Dünyadaki Kürt Sayısı',
|
||||||
|
citizenCountDesc: "PezkuwiChain'de kayıtlı vatandaşlar",
|
||||||
|
beCitizen: 'Vatandaş Ol',
|
||||||
},
|
},
|
||||||
|
|
||||||
wallet: {
|
wallet: {
|
||||||
|
|||||||
@@ -146,6 +146,9 @@ export interface Translations {
|
|||||||
recordingTrustScore: string;
|
recordingTrustScore: string;
|
||||||
trustScoreRecorded: string;
|
trustScoreRecorded: string;
|
||||||
trustScoreRecordFailed: string;
|
trustScoreRecordFailed: string;
|
||||||
|
citizenCountTitle: string;
|
||||||
|
citizenCountDesc: string;
|
||||||
|
beCitizen: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wallet section
|
// Wallet section
|
||||||
|
|||||||
@@ -91,6 +91,27 @@ export async function uploadToIPFS(_data: CitizenshipData): Promise<string> {
|
|||||||
return mockCID;
|
return mockCID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Citizen Count ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total number of citizens by counting citizenNft entries on People Chain.
|
||||||
|
* Citizen NFTs are in collection 42.
|
||||||
|
*/
|
||||||
|
export async function getCitizenCount(api: ApiPromise): Promise<number> {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
if (!(api?.query as any)?.tiki?.citizenNft) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const entries = await (api.query as any).tiki.citizenNft.entries();
|
||||||
|
return entries.length;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Citizenship] Error fetching citizen count:', error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Citizenship Status ──────────────────────────────────────────────
|
// ── Citizenship Status ──────────────────────────────────────────────
|
||||||
|
|
||||||
export async function getCitizenshipStatus(
|
export async function getCitizenshipStatus(
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
GraduationCap,
|
GraduationCap,
|
||||||
Play,
|
Play,
|
||||||
PenTool,
|
PenTool,
|
||||||
|
Globe2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn, formatAddress } from '@/lib/utils';
|
import { cn, formatAddress } from '@/lib/utils';
|
||||||
import { useTelegram } from '@/hooks/useTelegram';
|
import { useTelegram } from '@/hooks/useTelegram';
|
||||||
@@ -50,6 +51,7 @@ import {
|
|||||||
} from '@/lib/subquery';
|
} from '@/lib/subquery';
|
||||||
import {
|
import {
|
||||||
getCitizenshipStatus,
|
getCitizenshipStatus,
|
||||||
|
getCitizenCount,
|
||||||
confirmCitizenship,
|
confirmCitizenship,
|
||||||
type CitizenshipStatus,
|
type CitizenshipStatus,
|
||||||
} from '@/lib/citizenship';
|
} from '@/lib/citizenship';
|
||||||
@@ -75,6 +77,7 @@ export function RewardsSection() {
|
|||||||
const [stakingRewards, setStakingRewards] = useState<StakingRewardsResult | null>(null);
|
const [stakingRewards, setStakingRewards] = useState<StakingRewardsResult | null>(null);
|
||||||
const [scoresLoading, setScoresLoading] = useState(false);
|
const [scoresLoading, setScoresLoading] = useState(false);
|
||||||
const [citizenshipStatus, setCitizenshipStatus] = useState<CitizenshipStatus>('NotStarted');
|
const [citizenshipStatus, setCitizenshipStatus] = useState<CitizenshipStatus>('NotStarted');
|
||||||
|
const [citizenCount, setCitizenCount] = useState<number | null>(null);
|
||||||
const [showConfirmAnimation, setShowConfirmAnimation] = useState(false);
|
const [showConfirmAnimation, setShowConfirmAnimation] = useState(false);
|
||||||
const [showTrackingAnimation, setShowTrackingAnimation] = useState(false);
|
const [showTrackingAnimation, setShowTrackingAnimation] = useState(false);
|
||||||
const [trackingAnimationText, setTrackingAnimationText] = useState('');
|
const [trackingAnimationText, setTrackingAnimationText] = useState('');
|
||||||
@@ -146,10 +149,13 @@ export function RewardsSection() {
|
|||||||
}
|
}
|
||||||
}, [activeTab, address, fetchUserScores]);
|
}, [activeTab, address, fetchUserScores]);
|
||||||
|
|
||||||
// Fetch citizenship status
|
// Fetch citizenship status and citizen count
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!peopleApi || !address) return;
|
if (!peopleApi) return;
|
||||||
getCitizenshipStatus(peopleApi, address).then(setCitizenshipStatus);
|
if (address) {
|
||||||
|
getCitizenshipStatus(peopleApi, address).then(setCitizenshipStatus);
|
||||||
|
}
|
||||||
|
getCitizenCount(peopleApi).then(setCitizenCount);
|
||||||
}, [peopleApi, address]);
|
}, [peopleApi, address]);
|
||||||
|
|
||||||
const handleConfirmCitizenship = async () => {
|
const handleConfirmCitizenship = async () => {
|
||||||
@@ -335,6 +341,36 @@ export function RewardsSection() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
{/* Citizen Count Card */}
|
||||||
|
<div className="bg-gradient-to-br from-emerald-600 to-teal-700 rounded-2xl p-4 text-white">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-emerald-100 text-sm font-medium">
|
||||||
|
{t('rewards.citizenCountTitle')}
|
||||||
|
</p>
|
||||||
|
<p className="text-4xl font-bold mt-1">
|
||||||
|
{citizenCount !== null ? citizenCount.toLocaleString() : '...'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-16 h-16 rounded-full bg-white/20 flex items-center justify-center">
|
||||||
|
<KurdistanSun size={40} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-emerald-200/80 text-xs mb-3">
|
||||||
|
{t('rewards.citizenCountDesc')}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
hapticImpact('medium');
|
||||||
|
window.location.href = `${window.location.origin}/citizens${window.location.hash}`;
|
||||||
|
}}
|
||||||
|
className="w-full py-2.5 bg-white/20 hover:bg-white/30 rounded-xl text-sm font-medium flex items-center justify-center gap-2 transition-colors"
|
||||||
|
>
|
||||||
|
<Globe2 className="w-4 h-4" />
|
||||||
|
{t('rewards.beCitizen')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Score Card */}
|
{/* Score Card */}
|
||||||
<div className="bg-gradient-to-br from-purple-600 to-pink-600 rounded-2xl p-4 text-white">
|
<div className="bg-gradient-to-br from-purple-600 to-pink-600 rounded-2xl p-4 text-white">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.213",
|
"version": "1.0.214",
|
||||||
"buildTime": "2026-02-19T23:27:53.391Z",
|
"buildTime": "2026-02-20T23:55:07.518Z",
|
||||||
"buildNumber": 1771543673392
|
"buildNumber": 1771631707519
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user