feat: add Start Tracking and Record Trust Score buttons to Scores tab

This commit is contained in:
2026-02-20 02:27:53 +03:00
parent 714b234c76
commit ef8132c82a
11 changed files with 295 additions and 8 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pezkuwi-telegram-miniapp",
"version": "1.0.211",
"version": "1.0.213",
"type": "module",
"description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards",
"author": "Pezkuwichain Team",
+10
View File
@@ -134,6 +134,16 @@ const ar: Translations = {
signingBlockchain: 'جاري التوقيع على البلوكتشين',
citizenshipConfirmed: 'مبروك! أنت الآن مواطن!',
citizenshipFailed: 'فشل التأكيد',
startTracking: 'بدء التتبع',
startTrackingDesc: 'تفعيل تتبع نقاط التخزين',
startingTracking: 'جاري بدء التتبع...',
trackingStarted: 'بدأ التتبع!',
trackingFailed: 'فشل بدء التتبع',
recordTrustScore: 'تسجيل نقاط الثقة',
recordTrustDesc: 'سجل نقاطك لمكافآت هذه الفترة',
recordingTrustScore: 'جاري تسجيل نقاط الثقة...',
trustScoreRecorded: 'تم تسجيل نقاط الثقة!',
trustScoreRecordFailed: 'فشل تسجيل النقاط',
},
wallet: {
+10
View File
@@ -135,6 +135,16 @@ const ckb: Translations = {
signingBlockchain: 'لەسەر بلۆکچەین واژوو دەکرێت',
citizenshipConfirmed: 'پیرۆزبێت! تۆ ئێستا هاوڵاتیت!',
citizenshipFailed: 'پشتڕاستکردنەوە سەرنەکەوت',
startTracking: 'شوێنکەوتن دەست پێ بکە',
startTrackingDesc: 'شوێنکەوتنی خاڵی ستەیکینگ چالاک بکە',
startingTracking: 'شوێنکەوتن دەست پێ دەکات...',
trackingStarted: 'شوێنکەوتن دەست پێ کرد!',
trackingFailed: 'دەست پێکردنی شوێنکەوتن سەرنەکەوت',
recordTrustScore: 'خاڵی متمانە تۆمار بکە',
recordTrustDesc: 'خاڵەکەت بۆ خەڵاتەکانی ئەم سەردەمە تۆمار بکە',
recordingTrustScore: 'خاڵ تۆمار دەکرێت...',
trustScoreRecorded: 'خاڵی متمانە تۆمار کرا!',
trustScoreRecordFailed: 'تۆمارکردنی خاڵ سەرنەکەوت',
},
wallet: {
+10
View File
@@ -134,6 +134,16 @@ const en: Translations = {
signingBlockchain: 'Signing on blockchain',
citizenshipConfirmed: 'Congratulations! You are now a citizen!',
citizenshipFailed: 'Confirmation failed',
startTracking: 'Start Tracking',
startTrackingDesc: 'Enable staking score tracking',
startingTracking: 'Starting tracking...',
trackingStarted: 'Tracking started!',
trackingFailed: 'Failed to start tracking',
recordTrustScore: 'Record Trust Score',
recordTrustDesc: "Record your score for this epoch's rewards",
recordingTrustScore: 'Recording trust score...',
trustScoreRecorded: 'Trust score recorded!',
trustScoreRecordFailed: 'Failed to record score',
},
wallet: {
+10
View File
@@ -134,6 +134,16 @@ const fa: Translations = {
signingBlockchain: 'در حال امضا روی بلاکچین',
citizenshipConfirmed: 'تبریک! شما اکنون شهروند هستید!',
citizenshipFailed: 'تایید ناموفق بود',
startTracking: 'شروع ردیابی',
startTrackingDesc: 'ردیابی امتیاز سهام‌گذاری را فعال کنید',
startingTracking: 'در حال شروع ردیابی...',
trackingStarted: 'ردیابی شروع شد!',
trackingFailed: 'شروع ردیابی ناموفق بود',
recordTrustScore: 'ثبت امتیاز اعتماد',
recordTrustDesc: 'امتیاز خود را برای پاداش‌های این دوره ثبت کنید',
recordingTrustScore: 'در حال ثبت امتیاز اعتماد...',
trustScoreRecorded: 'امتیاز اعتماد ثبت شد!',
trustScoreRecordFailed: 'ثبت امتیاز ناموفق بود',
},
wallet: {
+12 -2
View File
@@ -137,8 +137,18 @@ const krd: Translations = {
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',
citizenshipConfirmed: 'Pîroz be! Tu bûyî welatî!',
citizenshipFailed: 'Piştrastkirin biserneket',
startTracking: 'Şopandinê Dest Pê Bike',
startTrackingDesc: 'Şopandina pûana stakingê çalak bike',
startingTracking: 'Şopandin tê destpêkirin...',
trackingStarted: 'Şopandin dest pê kir!',
trackingFailed: 'Destpêkirina şopandinê biserneket',
recordTrustScore: 'Pûana Pêbaweriyê Tomar Bike',
recordTrustDesc: 'Pûana xwe ji bo xelatên vê serdemê tomar bike',
recordingTrustScore: 'Pûan tê tomarkirin...',
trustScoreRecorded: 'Pûana pêbaweriyê hat tomarkirin!',
trustScoreRecordFailed: 'Tomarkirina pûanê biserneket',
},
wallet: {
+12 -2
View File
@@ -132,8 +132,18 @@ const tr: Translations = {
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',
citizenshipConfirmed: 'Tebrikler! Artık vatandaşsınız!',
citizenshipFailed: 'Onay başarısız oldu',
startTracking: 'Takibi Başlat',
startTrackingDesc: 'Staking puan takibini etkinleştirin',
startingTracking: 'Takip başlatılıyor...',
trackingStarted: 'Takip başlatıldı!',
trackingFailed: 'Takip başlatılamadı',
recordTrustScore: 'Güven Puanını Kaydet',
recordTrustDesc: 'Bu dönemin ödülleri için puanınızı kaydedin',
recordingTrustScore: 'Güven puanı kaydediliyor...',
trustScoreRecorded: 'Güven puanı kaydedildi!',
trustScoreRecordFailed: 'Puan kaydedilemedi',
},
wallet: {
+10
View File
@@ -136,6 +136,16 @@ export interface Translations {
signingBlockchain: string;
citizenshipConfirmed: string;
citizenshipFailed: string;
startTracking: string;
startTrackingDesc: string;
startingTracking: string;
trackingStarted: string;
trackingFailed: string;
recordTrustScore: string;
recordTrustDesc: string;
recordingTrustScore: string;
trustScoreRecorded: string;
trustScoreRecordFailed: string;
};
// Wallet section
+125
View File
@@ -11,6 +11,7 @@
*/
import type { ApiPromise } from '@pezkuwi/api';
import type { KeyringPair } from '@pezkuwi/keyring/types';
// ========================================
// TYPE DEFINITIONS
@@ -425,6 +426,130 @@ export async function getPerwerdeScore(
}
}
// ========================================
// SCORE TRACKING & RECORDING (People Chain Extrinsics)
// ========================================
/**
* Start staking score tracking on People Chain.
* Calls stakingScore.startScoreTracking() - no arguments.
* This enables the noter to cache staking details.
*/
export async function startScoreTracking(
peopleApi: ApiPromise,
keypair: KeyringPair
): Promise<{ success: boolean; error?: string }> {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tx = peopleApi.tx as any;
if (!tx?.stakingScore?.startScoreTracking) {
return { success: false, error: 'stakingScore pallet not available' };
}
const result = await new Promise<{ success: boolean; error?: string }>((resolve) => {
tx.stakingScore
.startScoreTracking()
.signAndSend(
keypair,
{ nonce: -1 },
({
status,
dispatchError,
}: {
status: {
isInBlock: boolean;
isFinalized: boolean;
};
dispatchError?: { isModule: boolean; asModule: unknown; toString: () => string };
}) => {
if (status.isInBlock || status.isFinalized) {
if (dispatchError) {
let errorMessage = 'startScoreTracking failed';
if (dispatchError.isModule) {
const decoded = peopleApi.registry.findMetaError(
dispatchError.asModule as Parameters<typeof peopleApi.registry.findMetaError>[0]
);
errorMessage = `${decoded.section}.${decoded.name}`;
}
resolve({ success: false, error: errorMessage });
return;
}
resolve({ success: true });
}
}
)
.catch((error: Error) => resolve({ success: false, error: error.message }));
});
return result;
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
/**
* Record trust score for current epoch on People Chain.
* Calls pezRewards.recordTrustScore() - no arguments.
* Required for PEZ epoch rewards.
*/
export async function recordTrustScore(
peopleApi: ApiPromise,
keypair: KeyringPair
): Promise<{ success: boolean; error?: string }> {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tx = peopleApi.tx as any;
if (!tx?.pezRewards?.recordTrustScore) {
return { success: false, error: 'pezRewards pallet not available' };
}
const result = await new Promise<{ success: boolean; error?: string }>((resolve) => {
tx.pezRewards
.recordTrustScore()
.signAndSend(
keypair,
{ nonce: -1 },
({
status,
dispatchError,
}: {
status: {
isInBlock: boolean;
isFinalized: boolean;
};
dispatchError?: { isModule: boolean; asModule: unknown; toString: () => string };
}) => {
if (status.isInBlock || status.isFinalized) {
if (dispatchError) {
let errorMessage = 'recordTrustScore failed';
if (dispatchError.isModule) {
const decoded = peopleApi.registry.findMetaError(
dispatchError.asModule as Parameters<typeof peopleApi.registry.findMetaError>[0]
);
errorMessage = `${decoded.section}.${decoded.name}`;
}
resolve({ success: false, error: errorMessage });
return;
}
resolve({ success: true });
}
}
)
.catch((error: Error) => resolve({ success: false, error: error.message }));
});
return result;
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
// ========================================
// COMPREHENSIVE SCORE FETCHING
// ========================================
+92
View File
@@ -21,6 +21,8 @@ import {
Target,
Sparkles,
GraduationCap,
Play,
PenTool,
} from 'lucide-react';
import { cn, formatAddress } from '@/lib/utils';
import { useTelegram } from '@/hooks/useTelegram';
@@ -32,6 +34,8 @@ import { useTranslation } from '@/i18n';
import {
getAllScores,
getStakingScoreStatus,
startScoreTracking,
recordTrustScore,
formatDuration,
getScoreColor,
getScoreRating,
@@ -72,6 +76,8 @@ export function RewardsSection() {
const [scoresLoading, setScoresLoading] = useState(false);
const [citizenshipStatus, setCitizenshipStatus] = useState<CitizenshipStatus>('NotStarted');
const [showConfirmAnimation, setShowConfirmAnimation] = useState(false);
const [showTrackingAnimation, setShowTrackingAnimation] = useState(false);
const [trackingAnimationText, setTrackingAnimationText] = useState('');
// Check activity status
const checkActivityStatus = useCallback(() => {
@@ -168,6 +174,52 @@ export function RewardsSection() {
}
};
const handleStartTracking = async () => {
if (!peopleApi || !keypair) return;
setTrackingAnimationText(t('rewards.startingTracking'));
setShowTrackingAnimation(true);
hapticImpact('medium');
try {
const result = await startScoreTracking(peopleApi, keypair);
if (result.success) {
hapticNotification('success');
showAlert(t('rewards.trackingStarted'));
fetchUserScores();
} else {
hapticNotification('error');
showAlert(result.error || t('rewards.trackingFailed'));
}
} catch (err) {
hapticNotification('error');
showAlert(err instanceof Error ? err.message : t('rewards.trackingFailed'));
} finally {
setShowTrackingAnimation(false);
}
};
const handleRecordTrustScore = async () => {
if (!peopleApi || !keypair) return;
setTrackingAnimationText(t('rewards.recordingTrustScore'));
setShowTrackingAnimation(true);
hapticImpact('medium');
try {
const result = await recordTrustScore(peopleApi, keypair);
if (result.success) {
hapticNotification('success');
showAlert(t('rewards.trustScoreRecorded'));
fetchUserScores();
} else {
hapticNotification('error');
showAlert(result.error || t('rewards.trustScoreRecordFailed'));
}
} catch (err) {
hapticNotification('error');
showAlert(err instanceof Error ? err.message : t('rewards.trustScoreRecordFailed'));
} finally {
setShowTrackingAnimation(false);
}
};
const handleActivate = () => {
hapticNotification('success');
localStorage.setItem(ACTIVITY_STORAGE_KEY, Date.now().toString());
@@ -758,6 +810,37 @@ export function RewardsSection() {
</div>
</div>
{/* Start Tracking / Record Trust Score Button */}
{!stakingStatus?.isTracking ? (
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<button
onClick={handleStartTracking}
disabled={!keypair || !peopleApi}
className="w-full py-3 rounded-lg font-medium flex items-center justify-center gap-2 bg-gradient-to-r from-blue-500 to-cyan-600 text-white hover:opacity-90 transition-all disabled:opacity-50"
>
<Play className="w-5 h-5" />
{t('rewards.startTracking')}
</button>
<p className="text-xs text-muted-foreground text-center mt-2">
{t('rewards.startTrackingDesc')}
</p>
</div>
) : (
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<button
onClick={handleRecordTrustScore}
disabled={!keypair || !peopleApi}
className="w-full py-3 rounded-lg font-medium flex items-center justify-center gap-2 bg-gradient-to-r from-green-500 to-emerald-600 text-white hover:opacity-90 transition-all disabled:opacity-50"
>
<PenTool className="w-5 h-5" />
{t('rewards.recordTrustScore')}
</button>
<p className="text-xs text-muted-foreground text-center mt-2">
{t('rewards.recordTrustDesc')}
</p>
</div>
)}
{/* Staking Rewards from SubQuery */}
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<h3 className="font-medium text-foreground mb-3 flex items-center gap-2">
@@ -856,6 +939,15 @@ export function RewardsSection() {
<p className="text-white/60 text-sm mt-2">{t('rewards.signingBlockchain')}</p>
</div>
)}
{/* Score Tracking Overlay */}
{showTrackingAnimation && (
<div className="fixed inset-0 z-50 bg-black/90 flex flex-col items-center justify-center">
<KurdistanSun size={120} />
<p className="text-white mt-6 text-lg">{trackingAnimationText}</p>
<p className="text-white/60 text-sm mt-2">{t('rewards.signingBlockchain')}</p>
</div>
)}
</div>
);
}
+3 -3
View File
@@ -1,5 +1,5 @@
{
"version": "1.0.211",
"buildTime": "2026-02-19T22:35:04.294Z",
"buildNumber": 1771540504295
"version": "1.0.213",
"buildTime": "2026-02-19T23:27:53.391Z",
"buildNumber": 1771543673392
}