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", "name": "pezkuwi-telegram-miniapp",
"version": "1.0.211", "version": "1.0.213",
"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",
+10
View File
@@ -134,6 +134,16 @@ const ar: Translations = {
signingBlockchain: 'جاري التوقيع على البلوكتشين', signingBlockchain: 'جاري التوقيع على البلوكتشين',
citizenshipConfirmed: 'مبروك! أنت الآن مواطن!', citizenshipConfirmed: 'مبروك! أنت الآن مواطن!',
citizenshipFailed: 'فشل التأكيد', citizenshipFailed: 'فشل التأكيد',
startTracking: 'بدء التتبع',
startTrackingDesc: 'تفعيل تتبع نقاط التخزين',
startingTracking: 'جاري بدء التتبع...',
trackingStarted: 'بدأ التتبع!',
trackingFailed: 'فشل بدء التتبع',
recordTrustScore: 'تسجيل نقاط الثقة',
recordTrustDesc: 'سجل نقاطك لمكافآت هذه الفترة',
recordingTrustScore: 'جاري تسجيل نقاط الثقة...',
trustScoreRecorded: 'تم تسجيل نقاط الثقة!',
trustScoreRecordFailed: 'فشل تسجيل النقاط',
}, },
wallet: { wallet: {
+10
View File
@@ -135,6 +135,16 @@ const ckb: Translations = {
signingBlockchain: 'لەسەر بلۆکچەین واژوو دەکرێت', signingBlockchain: 'لەسەر بلۆکچەین واژوو دەکرێت',
citizenshipConfirmed: 'پیرۆزبێت! تۆ ئێستا هاوڵاتیت!', citizenshipConfirmed: 'پیرۆزبێت! تۆ ئێستا هاوڵاتیت!',
citizenshipFailed: 'پشتڕاستکردنەوە سەرنەکەوت', citizenshipFailed: 'پشتڕاستکردنەوە سەرنەکەوت',
startTracking: 'شوێنکەوتن دەست پێ بکە',
startTrackingDesc: 'شوێنکەوتنی خاڵی ستەیکینگ چالاک بکە',
startingTracking: 'شوێنکەوتن دەست پێ دەکات...',
trackingStarted: 'شوێنکەوتن دەست پێ کرد!',
trackingFailed: 'دەست پێکردنی شوێنکەوتن سەرنەکەوت',
recordTrustScore: 'خاڵی متمانە تۆمار بکە',
recordTrustDesc: 'خاڵەکەت بۆ خەڵاتەکانی ئەم سەردەمە تۆمار بکە',
recordingTrustScore: 'خاڵ تۆمار دەکرێت...',
trustScoreRecorded: 'خاڵی متمانە تۆمار کرا!',
trustScoreRecordFailed: 'تۆمارکردنی خاڵ سەرنەکەوت',
}, },
wallet: { wallet: {
+10
View File
@@ -134,6 +134,16 @@ const en: Translations = {
signingBlockchain: 'Signing on blockchain', signingBlockchain: 'Signing on blockchain',
citizenshipConfirmed: 'Congratulations! You are now a citizen!', citizenshipConfirmed: 'Congratulations! You are now a citizen!',
citizenshipFailed: 'Confirmation failed', 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: { wallet: {
+10
View File
@@ -134,6 +134,16 @@ const fa: Translations = {
signingBlockchain: 'در حال امضا روی بلاکچین', signingBlockchain: 'در حال امضا روی بلاکچین',
citizenshipConfirmed: 'تبریک! شما اکنون شهروند هستید!', citizenshipConfirmed: 'تبریک! شما اکنون شهروند هستید!',
citizenshipFailed: 'تایید ناموفق بود', citizenshipFailed: 'تایید ناموفق بود',
startTracking: 'شروع ردیابی',
startTrackingDesc: 'ردیابی امتیاز سهام‌گذاری را فعال کنید',
startingTracking: 'در حال شروع ردیابی...',
trackingStarted: 'ردیابی شروع شد!',
trackingFailed: 'شروع ردیابی ناموفق بود',
recordTrustScore: 'ثبت امتیاز اعتماد',
recordTrustDesc: 'امتیاز خود را برای پاداش‌های این دوره ثبت کنید',
recordingTrustScore: 'در حال ثبت امتیاز اعتماد...',
trustScoreRecorded: 'امتیاز اعتماد ثبت شد!',
trustScoreRecordFailed: 'ثبت امتیاز ناموفق بود',
}, },
wallet: { wallet: {
+12 -2
View File
@@ -137,8 +137,18 @@ const krd: Translations = {
confirmCitizenship: 'Welat\u00eeb\u00fbn\u00ea Pi\u015ftrast Bike', confirmCitizenship: 'Welat\u00eeb\u00fbn\u00ea Pi\u015ftrast Bike',
confirmingCitizenship: 'Welat\u00eeb\u00fbn t\u00ea pi\u015ftrastkirin...', confirmingCitizenship: 'Welat\u00eeb\u00fbn t\u00ea pi\u015ftrastkirin...',
signingBlockchain: 'Li blockchain t\u00ea \u00eemzekirin', signingBlockchain: 'Li blockchain t\u00ea \u00eemzekirin',
citizenshipConfirmed: 'P\u00eeroz be! Tu b\u00fby\u00ee welat\u00ee!', citizenshipConfirmed: 'Pîroz be! Tu bûyî welatî!',
citizenshipFailed: 'Pi\u015ftrastkirin biserneket', 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: { wallet: {
+12 -2
View File
@@ -132,8 +132,18 @@ const tr: Translations = {
confirmCitizenship: 'Vatanda\u015fl\u0131\u011f\u0131 Onayla', confirmCitizenship: 'Vatanda\u015fl\u0131\u011f\u0131 Onayla',
confirmingCitizenship: 'Vatanda\u015fl\u0131k onaylan\u0131yor...', confirmingCitizenship: 'Vatanda\u015fl\u0131k onaylan\u0131yor...',
signingBlockchain: "Blockchain'de imzalan\u0131yor", signingBlockchain: "Blockchain'de imzalan\u0131yor",
citizenshipConfirmed: 'Tebrikler! Art\u0131k vatanda\u015fs\u0131n\u0131z!', citizenshipConfirmed: 'Tebrikler! Artık vatandaşsınız!',
citizenshipFailed: 'Onay ba\u015far\u0131s\u0131z oldu', 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: { wallet: {
+10
View File
@@ -136,6 +136,16 @@ export interface Translations {
signingBlockchain: string; signingBlockchain: string;
citizenshipConfirmed: string; citizenshipConfirmed: string;
citizenshipFailed: 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 // Wallet section
+125
View File
@@ -11,6 +11,7 @@
*/ */
import type { ApiPromise } from '@pezkuwi/api'; import type { ApiPromise } from '@pezkuwi/api';
import type { KeyringPair } from '@pezkuwi/keyring/types';
// ======================================== // ========================================
// TYPE DEFINITIONS // 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 // COMPREHENSIVE SCORE FETCHING
// ======================================== // ========================================
+92
View File
@@ -21,6 +21,8 @@ import {
Target, Target,
Sparkles, Sparkles,
GraduationCap, GraduationCap,
Play,
PenTool,
} 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';
@@ -32,6 +34,8 @@ import { useTranslation } from '@/i18n';
import { import {
getAllScores, getAllScores,
getStakingScoreStatus, getStakingScoreStatus,
startScoreTracking,
recordTrustScore,
formatDuration, formatDuration,
getScoreColor, getScoreColor,
getScoreRating, getScoreRating,
@@ -72,6 +76,8 @@ export function RewardsSection() {
const [scoresLoading, setScoresLoading] = useState(false); const [scoresLoading, setScoresLoading] = useState(false);
const [citizenshipStatus, setCitizenshipStatus] = useState<CitizenshipStatus>('NotStarted'); const [citizenshipStatus, setCitizenshipStatus] = useState<CitizenshipStatus>('NotStarted');
const [showConfirmAnimation, setShowConfirmAnimation] = useState(false); const [showConfirmAnimation, setShowConfirmAnimation] = useState(false);
const [showTrackingAnimation, setShowTrackingAnimation] = useState(false);
const [trackingAnimationText, setTrackingAnimationText] = useState('');
// Check activity status // Check activity status
const checkActivityStatus = useCallback(() => { 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 = () => { const handleActivate = () => {
hapticNotification('success'); hapticNotification('success');
localStorage.setItem(ACTIVITY_STORAGE_KEY, Date.now().toString()); localStorage.setItem(ACTIVITY_STORAGE_KEY, Date.now().toString());
@@ -758,6 +810,37 @@ export function RewardsSection() {
</div> </div>
</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 */} {/* Staking Rewards from SubQuery */}
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50"> <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"> <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> <p className="text-white/60 text-sm mt-2">{t('rewards.signingBlockchain')}</p>
</div> </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> </div>
); );
} }
+3 -3
View File
@@ -1,5 +1,5 @@
{ {
"version": "1.0.211", "version": "1.0.213",
"buildTime": "2026-02-19T22:35:04.294Z", "buildTime": "2026-02-19T23:27:53.391Z",
"buildNumber": 1771540504295 "buildNumber": 1771543673392
} }