feat: add i18n support with 6 languages (en, tr, krd, ar, fa, ckb)

- Add translation system with useTranslation hook and LanguageProvider
- Auto-detect language from Telegram user settings
- Update all components and sections to use translation keys
- Support English, Turkish, Kurdish, Arabic, Persian, Sorani
This commit is contained in:
2026-02-14 11:06:14 +03:00
parent e5dd2b4b5b
commit 9da348bdf3
26 changed files with 2682 additions and 424 deletions
+6 -4
View File
@@ -10,17 +10,19 @@ import {
import { cn, formatDate, formatNumber } from '@/lib/utils';
import { useTelegram } from '@/hooks/useTelegram';
import { useAnnouncements, useAnnouncementReaction } from '@/hooks/useSupabase';
import { useTranslation } from '@/i18n';
export function AnnouncementsSection() {
const { hapticImpact, hapticNotification, openLink } = useTelegram();
const { data: announcements, isLoading, refetch, isRefetching } = useAnnouncements();
const reactionMutation = useAnnouncementReaction();
const { t } = useTranslation();
const handleReaction = (id: string, reaction: 'like' | 'dislike') => {
if (!window.Telegram?.WebApp?.initData) {
hapticNotification('error');
if (window.Telegram?.WebApp) {
window.Telegram.WebApp.showAlert('Ji bo dengdanê divê tu di Telegramê de bî');
window.Telegram.WebApp.showAlert(t('announcements.reactionAuthRequired'));
}
return;
}
@@ -31,7 +33,7 @@ export function AnnouncementsSection() {
onSuccess: () => hapticNotification('success'),
onError: (err) => {
hapticNotification('error');
window.Telegram?.WebApp?.showAlert(err.message || 'Çewtî');
window.Telegram?.WebApp?.showAlert(err.message || t('common.error'));
},
}
);
@@ -51,7 +53,7 @@ export function AnnouncementsSection() {
<div className="w-8 h-8 rounded-lg bg-primary/20 flex items-center justify-center">
<Megaphone className="w-4 h-4 text-primary" />
</div>
<h1 className="text-lg font-semibold">Ragihandin</h1>
<h1 className="text-lg font-semibold">{t('announcements.title')}</h1>
</div>
<button
onClick={handleRefresh}
@@ -113,7 +115,7 @@ export function AnnouncementsSection() {
className="flex items-center gap-1.5 text-sm text-primary mb-3 hover:underline"
>
<ExternalLink className="w-3.5 h-3.5" />
Zêdetir bixwîne
{t('announcements.readMore')}
</button>
)}
+47 -41
View File
@@ -31,12 +31,14 @@ import { useTelegram } from '@/hooks/useTelegram';
import { useAuth } from '@/contexts/AuthContext';
import { useForum, type ForumDiscussion, type ForumReply } from '@/hooks/useForum';
import { formatDistanceToNow } from 'date-fns';
import { useTranslation } from '@/i18n';
type SortBy = 'recent' | 'popular' | 'replies' | 'views';
export function ForumSection() {
const { hapticImpact, hapticNotification, showAlert } = useTelegram();
const { user: authUser } = useAuth();
const { t } = useTranslation();
// Use authenticated user ID from backend, not initDataUnsafe
const userId = authUser?.telegram_id?.toString() || '';
const userName = authUser?.first_name || 'Telegram User';
@@ -116,7 +118,7 @@ export function ForumSection() {
const handleVoteDiscussion = async (voteType: 'upvote' | 'downvote') => {
if (!selectedDiscussion || !userId) {
showAlert('Ji bo dengdanê têkeve');
showAlert(t('forum.loginToVote'));
return;
}
@@ -132,13 +134,13 @@ export function ForumSection() {
}
} catch {
hapticNotification('error');
showAlert('Çewtî di dengdanê de');
showAlert(t('forum.voteError'));
}
};
const handleVoteReply = async (replyId: string, voteType: 'upvote' | 'downvote') => {
if (!userId) {
showAlert('Ji bo dengdanê têkeve');
showAlert(t('forum.loginToVote'));
return;
}
@@ -159,12 +161,12 @@ export function ForumSection() {
const handleSubmitReply = async () => {
if (!selectedDiscussion || !replyText.trim() || !userId) {
showAlert('Ji kerema xwe bersiva xwe binivîse');
showAlert(t('forum.writeReply'));
return;
}
if (selectedDiscussion.is_locked) {
showAlert('Ev mijar kilîtkirî ye');
showAlert(t('forum.topicLocked'));
return;
}
@@ -187,7 +189,7 @@ export function ForumSection() {
setReplies(loadedReplies);
} catch {
hapticNotification('error');
showAlert('Çewtî di şandina bersivê de');
showAlert(t('forum.replyError'));
} finally {
setSubmittingReply(false);
}
@@ -195,7 +197,7 @@ export function ForumSection() {
const handleSubmitDiscussion = async () => {
if (!newTitle.trim() || !newContent.trim() || !newCategory || !userId) {
showAlert('Ji kerema xwe hemû qadan tije bike');
showAlert(t('forum.fillAllFields'));
return;
}
@@ -205,8 +207,8 @@ export function ForumSection() {
try {
const tags = newTags
.split(',')
.map((t) => t.trim())
.filter((t) => t.length > 0);
.map((tag) => tag.trim())
.filter((tag) => tag.length > 0);
await createDiscussion({
title: newTitle.trim(),
@@ -218,11 +220,11 @@ export function ForumSection() {
});
hapticNotification('success');
showAlert('Mijar hat afirandin!');
showAlert(t('forum.topicCreated'));
handleCloseCreate();
} catch {
hapticNotification('error');
showAlert('Çewtî di afirandina mijarê de');
showAlert(t('forum.topicCreateError'));
} finally {
setSubmittingDiscussion(false);
}
@@ -291,7 +293,7 @@ export function ForumSection() {
>
<X className="w-5 h-5" />
</button>
<h1 className="text-lg font-semibold">Mijara </h1>
<h1 className="text-lg font-semibold">{t('forum.newTopic')}</h1>
</div>
<button
onClick={handleSubmitDiscussion}
@@ -303,7 +305,7 @@ export function ForumSection() {
: 'bg-primary text-primary-foreground'
)}
>
{submittingDiscussion ? 'Tê şandin...' : 'Biweşîne'}
{submittingDiscussion ? t('forum.submitting') : t('forum.publish')}
</button>
</div>
</header>
@@ -311,7 +313,9 @@ export function ForumSection() {
<div className="flex-1 overflow-y-auto hide-scrollbar p-4 space-y-4">
{/* Category */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Kategorî</label>
<label className="text-sm text-muted-foreground mb-2 block">
{t('forum.category')}
</label>
<div className="flex gap-2 flex-wrap">
{categories.map((cat) => (
<button
@@ -336,12 +340,14 @@ export function ForumSection() {
{/* Title */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Sernav</label>
<label className="text-sm text-muted-foreground mb-2 block">
{t('forum.topicTitle')}
</label>
<input
type="text"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
placeholder="Navê mijarê..."
placeholder={t('forum.topicTitlePlaceholder')}
className="w-full px-4 py-3 bg-secondary rounded-lg text-foreground"
maxLength={200}
/>
@@ -349,11 +355,11 @@ export function ForumSection() {
{/* Content */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Naverok</label>
<label className="text-sm text-muted-foreground mb-2 block">{t('forum.content')}</label>
<textarea
value={newContent}
onChange={(e) => setNewContent(e.target.value)}
placeholder="Naveroka mijarê binivîse..."
placeholder={t('forum.contentPlaceholder')}
className="w-full px-4 py-3 bg-secondary rounded-lg text-foreground min-h-[200px] resize-none"
/>
</div>
@@ -361,13 +367,13 @@ export function ForumSection() {
{/* Tags */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">
Etîket (bi virgulê cuda bike)
{t('forum.tagsLabel')}
</label>
<input
type="text"
value={newTags}
onChange={(e) => setNewTags(e.target.value)}
placeholder="blockchain, kurd, pez..."
placeholder={t('forum.tagsPlaceholder')}
className="w-full px-4 py-3 bg-secondary rounded-lg text-foreground"
/>
</div>
@@ -395,13 +401,13 @@ export function ForumSection() {
{selectedDiscussion.is_pinned && (
<span className="inline-flex items-center gap-1 text-xs bg-yellow-500/20 text-yellow-400 px-2 py-1 rounded-full">
<Pin className="w-3 h-3" />
Pinned
{t('common.pinned')}
</span>
)}
{selectedDiscussion.is_locked && (
<span className="inline-flex items-center gap-1 text-xs bg-red-500/20 text-red-400 px-2 py-1 rounded-full">
<Lock className="w-3 h-3" />
Kilîtkirî
{t('common.locked')}
</span>
)}
{selectedDiscussion.category && (
@@ -419,7 +425,7 @@ export function ForumSection() {
</div>
<div>
<p className="text-sm font-medium">
{selectedDiscussion.author_name || 'Anonymous'}
{selectedDiscussion.author_name || t('common.anonymous')}
</p>
<p className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(selectedDiscussion.created_at), {
@@ -499,7 +505,7 @@ export function ForumSection() {
<div className="mb-4">
<h3 className="text-sm font-semibold text-muted-foreground mb-3 flex items-center gap-2">
<MessageSquare className="w-4 h-4" />
Bersiv ({replies.length})
{t('forum.replies')} ({replies.length})
</h3>
{loadingReplies ? (
@@ -514,8 +520,8 @@ export function ForumSection() {
) : replies.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
<MessageSquare className="w-8 h-8 mx-auto mb-2 opacity-50" />
<p className="text-sm">Hêj bersiv tune ye</p>
<p className="text-xs">Yekemîn bersivê tu bide!</p>
<p className="text-sm">{t('forum.noRepliesYet')}</p>
<p className="text-xs">{t('forum.beFirstToReply')}</p>
</div>
) : (
<div className="space-y-3">
@@ -529,7 +535,7 @@ export function ForumSection() {
{reply.author_name?.charAt(0) || 'A'}
</div>
<span className="text-sm font-medium">
{reply.author_name || 'Anonymous'}
{reply.author_name || t('common.anonymous')}
</span>
<span className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(reply.created_at), { addSuffix: true })}
@@ -579,7 +585,7 @@ export function ForumSection() {
type="text"
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
placeholder="Bersiva xwe binivîse..."
placeholder={t('forum.replyPlaceholder')}
className="flex-1 px-4 py-2.5 bg-secondary rounded-lg text-sm"
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
@@ -616,7 +622,7 @@ export function ForumSection() {
<div className="w-8 h-8 rounded-lg bg-blue-500/20 flex items-center justify-center">
<MessageCircle className="w-4 h-4 text-blue-400" />
</div>
<h1 className="text-lg font-semibold">Forum</h1>
<h1 className="text-lg font-semibold">{t('forum.title')}</h1>
<span className="text-xs text-muted-foreground">({discussions.length})</span>
</div>
<div className="flex items-center gap-1">
@@ -648,7 +654,7 @@ export function ForumSection() {
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Mijar bigere..."
placeholder={t('forum.searchPlaceholder')}
className="w-full pl-9 pr-4 py-2 bg-secondary rounded-lg text-sm"
/>
</div>
@@ -656,10 +662,10 @@ export function ForumSection() {
{/* Sort Tabs */}
<div className="flex gap-1 bg-secondary/50 rounded-lg p-1">
{[
{ id: 'recent' as SortBy, icon: Clock, label: 'Nû' },
{ id: 'popular' as SortBy, icon: TrendingUp, label: 'Populer' },
{ id: 'replies' as SortBy, icon: MessageSquare, label: 'Bersiv' },
{ id: 'views' as SortBy, icon: Eye, label: 'Dîtin' },
{ id: 'recent' as SortBy, icon: Clock, label: t('forum.sortRecent') },
{ id: 'popular' as SortBy, icon: TrendingUp, label: t('forum.sortPopular') },
{ id: 'replies' as SortBy, icon: MessageSquare, label: t('forum.sortReplies') },
{ id: 'views' as SortBy, icon: Eye, label: t('forum.sortViews') },
].map(({ id, icon: Icon, label }) => (
<button
key={id}
@@ -718,7 +724,7 @@ export function ForumSection() {
: 'bg-secondary text-muted-foreground'
)}
>
Hemû
{t('forum.all')}
</button>
{categories.map((category) => (
<button
@@ -755,14 +761,14 @@ export function ForumSection() {
) : filteredDiscussions.length === 0 ? (
<div className="flex flex-col items-center justify-center h-64 p-8 text-center">
<MessageCircle className="w-12 h-12 text-muted-foreground/50 mb-4" />
<p className="text-muted-foreground">Mijar nehat dîtin</p>
<p className="text-sm text-muted-foreground/70 mb-4">Filterên xwe biguhêre</p>
<p className="text-muted-foreground">{t('forum.noTopicsFound')}</p>
<p className="text-sm text-muted-foreground/70 mb-4">{t('forum.changeFilters')}</p>
<button
onClick={handleOpenCreate}
className="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm"
>
<Plus className="w-4 h-4" />
Mijara Biafirîne
{t('forum.createNewTopic')}
</button>
</div>
) : (
@@ -778,7 +784,7 @@ export function ForumSection() {
{discussion.is_pinned && (
<span className="inline-flex items-center gap-1 text-[10px] bg-yellow-500/20 text-yellow-400 px-1.5 py-0.5 rounded">
<Pin className="w-2.5 h-2.5" />
Pinned
{t('common.pinned')}
</span>
)}
{discussion.is_locked && (
@@ -794,7 +800,7 @@ export function ForumSection() {
{(discussion.upvotes || 0) > 10 && (
<span className="inline-flex items-center gap-1 text-[10px] bg-orange-500/20 text-orange-400 px-1.5 py-0.5 rounded">
<Flame className="w-2.5 h-2.5" />
Trending
{t('common.trending')}
</span>
)}
</div>
@@ -817,7 +823,7 @@ export function ForumSection() {
{/* Meta */}
<div className="flex items-center gap-3 text-xs text-muted-foreground flex-wrap">
<span>{discussion.author_name || 'Anonymous'}</span>
<span>{discussion.author_name || t('common.anonymous')}</span>
<span>
{formatDistanceToNow(new Date(discussion.last_activity_at), {
addSuffix: true,
+77 -70
View File
@@ -29,6 +29,7 @@ import { useAuth } from '@/contexts/AuthContext';
import { useReferral } from '@/contexts/ReferralContext';
import { useWallet } from '@/contexts/WalletContext';
import { SocialLinks } from '@/components/SocialLinks';
import { useTranslation } from '@/i18n';
import {
getAllScores,
getStakingScoreStatus,
@@ -54,6 +55,7 @@ export function RewardsSection() {
const { user: authUser } = useAuth();
const { stats, myReferrals, loading, refreshStats } = useReferral();
const { isConnected, address, peopleApi } = useWallet();
const { t } = useTranslation();
const [copied, setCopied] = useState(false);
const [activeTab, setActiveTab] = useState<'overview' | 'referrals' | 'scores'>('overview');
@@ -136,7 +138,7 @@ export function RewardsSection() {
localStorage.setItem(ACTIVITY_STORAGE_KEY, Date.now().toString());
setIsActive(true);
setTimeRemaining('24s 0d');
showAlert('Tu niha aktîv î! 24 saet paşê dîsa bikirtîne.');
showAlert(t('rewards.activatedAlert'));
};
// Telegram referral link (for sharing) - use authenticated user ID
@@ -151,16 +153,13 @@ export function RewardsSection() {
hapticNotification('success');
setTimeout(() => setCopied(false), 2000);
} catch {
showAlert('Kopî bû');
showAlert(t('rewards.copyAlert'));
}
};
const handleShare = () => {
hapticImpact('medium');
shareUrl(
referralLink,
'Pezkuwichain - Dewleta Dîjîtal a Kurd! Bi lînka min ve tev li me bibe:'
);
shareUrl(referralLink, t('rewards.shareText'));
};
const handleRefresh = () => {
@@ -180,10 +179,8 @@ export function RewardsSection() {
return (
<div className="flex flex-col h-full items-center justify-center p-8 text-center">
<Gift className="w-16 h-16 text-purple-400 mb-4" />
<h2 className="text-xl font-semibold mb-2">Xelat - Referral System</h2>
<p className="text-muted-foreground mb-4">
Ji bo dîtina referral û xelatên xwe, berî her tiştî cîzdanê xwe girêde.
</p>
<h2 className="text-xl font-semibold mb-2">{t('rewards.subtitle')}</h2>
<p className="text-muted-foreground mb-4">{t('rewards.connectWalletFirst')}</p>
</div>
);
}
@@ -197,7 +194,7 @@ export function RewardsSection() {
<div className="w-8 h-8 rounded-lg bg-purple-500/20 flex items-center justify-center">
<Gift className="w-4 h-4 text-purple-400" />
</div>
<h1 className="text-lg font-semibold">Xelat</h1>
<h1 className="text-lg font-semibold">{t('rewards.title')}</h1>
</div>
<button
onClick={handleRefresh}
@@ -213,9 +210,9 @@ export function RewardsSection() {
{/* Tabs */}
<div className="flex gap-1 bg-secondary/50 rounded-lg p-1">
{[
{ id: 'overview' as const, label: 'Geşbîn' },
{ id: 'referrals' as const, label: 'Referral' },
{ id: 'scores' as const, label: 'Xal' },
{ id: 'overview' as const, label: t('rewards.overview') },
{ id: 'referrals' as const, label: t('rewards.referral') },
{ id: 'scores' as const, label: t('rewards.scores') },
].map(({ id, label }) => (
<button
key={id}
@@ -255,7 +252,7 @@ export function RewardsSection() {
<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>
<p className="text-purple-100 text-sm">Pûana Referral</p>
<p className="text-purple-100 text-sm">{t('rewards.referralScore')}</p>
<p className="text-4xl font-bold">{stats?.referralScore ?? 0}</p>
</div>
<div className="w-16 h-16 rounded-full bg-white/20 flex items-center justify-center">
@@ -264,7 +261,7 @@ export function RewardsSection() {
</div>
<div className="flex items-center gap-2 text-sm text-purple-100">
<TrendingUp className="w-4 h-4" />
<span>Max pûan: 500</span>
<span>{t('rewards.maxScore')}</span>
</div>
</div>
@@ -276,10 +273,10 @@ export function RewardsSection() {
</div>
<div>
<h4 className="font-semibold text-amber-100 mb-1">
زیاتر ڕیفەر بکە، زیاتر قازانج بکە!
{t('rewards.referMoreTitle')}
</h4>
<p className="text-sm text-amber-200/80">
هەر کەسێک بهێنیت، HEZ و PEZ وەک خەڵات وەردەگریت. زیاتر ڕیفەر = زیاتر خەڵات!
{t('rewards.referMoreDescription')}
</p>
</div>
</div>
@@ -290,27 +287,27 @@ export function RewardsSection() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<Users className="w-4 h-4 text-green-400" />
<span className="text-xs text-muted-foreground">Referral</span>
<span className="text-xs text-muted-foreground">{t('rewards.referral')}</span>
</div>
<p className="text-2xl font-bold text-foreground">
{stats?.referralCount ?? 0}
</p>
<p className="text-xs text-muted-foreground">KYC pejirandî</p>
<p className="text-xs text-muted-foreground">{t('rewards.kycApproved')}</p>
</div>
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<Award className="w-4 h-4 text-blue-400" />
<span className="text-xs text-muted-foreground">Referrer</span>
<span className="text-xs text-muted-foreground">{t('rewards.referrer')}</span>
</div>
{stats?.whoInvitedMe ? (
<p className="text-sm font-mono text-foreground truncate">
{formatAddress(stats.whoInvitedMe, 6)}
</p>
) : (
<p className="text-sm text-muted-foreground">Tune</p>
<p className="text-sm text-muted-foreground">{t('rewards.none')}</p>
)}
<p className="text-xs text-muted-foreground">Min vexwand</p>
<p className="text-xs text-muted-foreground">{t('rewards.invitedMe')}</p>
</div>
</div>
@@ -322,9 +319,11 @@ export function RewardsSection() {
<Award className="w-5 h-5 text-blue-400" />
</div>
<div className="flex-1">
<div className="text-white font-semibold">Referral li bendê</div>
<div className="text-white font-semibold">
{t('rewards.pendingReferral')}
</div>
<div className="text-sm text-blue-300">
KYC temam bike ji bo pejirandina referral ji{' '}
{t('rewards.completeKyc')}{' '}
<span className="font-mono">
{formatAddress(stats.pendingReferral, 6)}
</span>
@@ -338,11 +337,11 @@ export function RewardsSection() {
<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">
<Share2 className="w-4 h-4 text-primary" />
Hevalên xwe vexwîne
{t('rewards.inviteFriends')}
</h3>
<div className="bg-background rounded-lg p-3 mb-3">
<p className="text-xs text-muted-foreground mb-1">Lînka te</p>
<p className="text-xs text-muted-foreground mb-1">{t('rewards.yourLink')}</p>
<code className="text-sm text-foreground break-all">{referralLink}</code>
</div>
@@ -357,14 +356,14 @@ export function RewardsSection() {
)}
>
{copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
{copied ? 'Kopî bû!' : 'Kopî bike'}
{copied ? t('rewards.copySuccess') : t('rewards.copyLink')}
</button>
<button
onClick={handleShare}
className="flex-1 flex items-center justify-center gap-2 py-2.5 bg-primary rounded-lg text-primary-foreground text-sm font-medium"
>
<Share2 className="w-4 h-4" />
Parve bike
{t('rewards.shareLink')}
</button>
</div>
</div>
@@ -373,7 +372,7 @@ export function RewardsSection() {
<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">
<Star className="w-4 h-4 text-yellow-400" />
Sîstema pûanan
{t('rewards.scoreSystem')}
</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between py-2 border-b border-border/30">
@@ -403,9 +402,11 @@ export function RewardsSection() {
className={cn('w-5 h-5', isActive ? 'text-green-400' : 'text-gray-400')}
/>
<div>
<h3 className="font-medium text-foreground">Rewşa Aktîvbûnê</h3>
<h3 className="font-medium text-foreground">{t('rewards.activeStatus')}</h3>
{isActive && timeRemaining && (
<p className="text-xs text-green-400">Dem: {timeRemaining} maye</p>
<p className="text-xs text-green-400">
{t('rewards.timeRemaining', { time: timeRemaining })}
</p>
)}
</div>
</div>
@@ -415,11 +416,11 @@ export function RewardsSection() {
isActive ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
)}
>
{isActive ? 'Aktîv' : 'Ne Aktîv'}
{isActive ? t('rewards.active') : t('rewards.inactive')}
</div>
</div>
<p className="text-sm text-muted-foreground mb-3">
Her 24 saet carekê bikirtîne da ku aktîv bimînî û xelatên zêdetir qezenc bikî!
{t('rewards.activeDescription')}
</p>
<button
onClick={handleActivate}
@@ -432,7 +433,7 @@ export function RewardsSection() {
)}
>
<Zap className="w-5 h-5" />
{isActive ? 'Tu Aktîv î!' : 'Ez Aktîv im!'}
{isActive ? t('rewards.youAreActive') : t('rewards.iAmActive')}
</button>
</div>
@@ -454,11 +455,9 @@ export function RewardsSection() {
</div>
<div>
<h4 className="font-semibold text-amber-100 mb-1">
زیاتر ڕیفەر بکە، زیاتر قازانج بکە!
{t('rewards.referMoreTitle')}
</h4>
<p className="text-sm text-amber-200/80">
هەر کەسێک بهێنیت، HEZ و PEZ وەک خەڵات وەردەگریت. زیاتر ڕیفەر = زیاتر خەڵات!
</p>
<p className="text-sm text-amber-200/80">{t('rewards.referMoreDescription')}</p>
</div>
</div>
</div>
@@ -469,9 +468,11 @@ export function RewardsSection() {
<div className="flex items-center gap-2">
<Zap className={cn('w-5 h-5', isActive ? 'text-green-400' : 'text-gray-400')} />
<div>
<h3 className="font-medium text-foreground">Rewşa Aktîvbûnê</h3>
<h3 className="font-medium text-foreground">{t('rewards.activeStatus')}</h3>
{isActive && timeRemaining && (
<p className="text-xs text-green-400">Dem: {timeRemaining} maye</p>
<p className="text-xs text-green-400">
{t('rewards.timeRemaining', { time: timeRemaining })}
</p>
)}
</div>
</div>
@@ -481,7 +482,7 @@ export function RewardsSection() {
isActive ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
)}
>
{isActive ? 'Aktîv' : 'Ne Aktîv'}
{isActive ? t('rewards.active') : t('rewards.inactive')}
</div>
</div>
<button
@@ -495,7 +496,7 @@ export function RewardsSection() {
)}
>
<Zap className="w-5 h-5" />
{isActive ? 'Tu Aktîv î!' : 'Ez Aktîv im!'}
{isActive ? t('rewards.youAreActive') : t('rewards.iAmActive')}
</button>
</div>
@@ -510,7 +511,7 @@ export function RewardsSection() {
) : myReferrals.length > 0 ? (
<div className="space-y-3">
<div className="text-sm text-muted-foreground mb-2">
{myReferrals.length} referral (KYC pejirandî)
{t('rewards.referralCount', { count: myReferrals.length })}
</div>
{myReferrals.map((referralAddress, index) => (
<div
@@ -524,11 +525,11 @@ export function RewardsSection() {
<code className="text-sm text-foreground">
{formatAddress(referralAddress, 8)}
</code>
<p className="text-xs text-green-400">KYC Pejirandî</p>
<p className="text-xs text-green-400">{t('rewards.kycApproved')}</p>
</div>
<div className="text-right">
<span className="text-green-400 text-sm font-medium">
+{getPointsForPosition(index + 1)} pûan
+{getPointsForPosition(index + 1)} {t('rewards.points')}
</span>
</div>
</div>
@@ -537,14 +538,14 @@ export function RewardsSection() {
) : (
<div className="flex flex-col items-center justify-center py-12 text-center">
<Users className="w-12 h-12 text-muted-foreground mb-4" />
<p className="text-muted-foreground">Hêj referralên te tune ne</p>
<p className="text-sm text-muted-foreground mt-1">Lînka xwe parve bike!</p>
<p className="text-muted-foreground">{t('rewards.noReferrals')}</p>
<p className="text-sm text-muted-foreground mt-1">{t('rewards.shareYourLink')}</p>
<button
onClick={handleShare}
className="mt-4 flex items-center gap-2 px-4 py-2 bg-primary rounded-lg text-primary-foreground text-sm font-medium"
>
<Share2 className="w-4 h-4" />
Parve bike
{t('rewards.shareLink')}
</button>
</div>
)}
@@ -571,7 +572,7 @@ export function RewardsSection() {
<div>
<p className="text-purple-100 text-sm flex items-center gap-2">
<Shield className="w-4 h-4" />
Pûana Pêbaweriyê (Trust)
{t('rewards.trustScore')}
</p>
<p
className={cn(
@@ -588,11 +589,11 @@ export function RewardsSection() {
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-purple-100">
Rêze: {getScoreRating(userScores?.trustScore || 0)}
{t('rewards.rank', { rating: getScoreRating(userScores?.trustScore || 0) })}
</span>
{userScores?.isCitizen && (
<span className="bg-green-500/30 text-green-200 px-2 py-1 rounded-full text-xs">
Welatî
{t('rewards.citizen')}
</span>
)}
</div>
@@ -604,7 +605,7 @@ export function RewardsSection() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<Target className="w-4 h-4 text-blue-400" />
<span className="text-xs text-muted-foreground">Staking</span>
<span className="text-xs text-muted-foreground">{t('rewards.staking')}</span>
</div>
<p className="text-2xl font-bold text-blue-400">
{stakingStatus?.isTracking ? (
@@ -613,23 +614,27 @@ export function RewardsSection() {
{formatDuration(stakingStatus.durationBlocks)}
</span>
) : (
<span className="text-sm text-muted-foreground">Nehatiye destpêkirin</span>
<span className="text-sm text-muted-foreground">
{t('rewards.stakingNotStarted')}
</span>
)}
</p>
<p className="text-xs text-muted-foreground mt-1">Di Trust de hesibandin</p>
<p className="text-xs text-muted-foreground mt-1">
{t('rewards.stakingCountedInTrust')}
</p>
</div>
{/* Referral Score */}
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<Users className="w-4 h-4 text-green-400" />
<span className="text-xs text-muted-foreground">Referral</span>
<span className="text-xs text-muted-foreground">{t('rewards.referral')}</span>
</div>
<p className="text-2xl font-bold text-green-400">
{userScores?.referralScore ?? 0}
</p>
<p className="text-xs text-muted-foreground mt-1">
{stats?.referralCount ?? 0} kes
{t('rewards.people', { count: stats?.referralCount ?? 0 })}
</p>
</div>
@@ -637,24 +642,26 @@ export function RewardsSection() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<Sparkles className="w-4 h-4 text-yellow-400" />
<span className="text-xs text-muted-foreground">Tiki</span>
<span className="text-xs text-muted-foreground">{t('rewards.tiki')}</span>
</div>
<p className="text-2xl font-bold text-yellow-400">
{userScores?.tikiScore ?? 0}
</p>
<p className="text-xs text-muted-foreground mt-1">Rola NFT</p>
<p className="text-xs text-muted-foreground mt-1">{t('rewards.nftRole')}</p>
</div>
{/* Perwerde Score */}
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<GraduationCap className="w-4 h-4 text-pink-400" />
<span className="text-xs text-muted-foreground">Perwerde</span>
<span className="text-xs text-muted-foreground">
{t('rewards.education')}
</span>
</div>
<p className="text-2xl font-bold text-pink-400">
{userScores?.perwerdeScore ?? 0}
</p>
<p className="text-xs text-muted-foreground mt-1">Xwendin</p>
<p className="text-xs text-muted-foreground mt-1">{t('rewards.reading')}</p>
</div>
</div>
@@ -662,12 +669,12 @@ export function RewardsSection() {
<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">
<Coins className="w-4 h-4 text-amber-400" />
Xelatên Staking
{t('rewards.stakingRewards')}
</h3>
{/* Total Accumulated */}
<div className="bg-amber-500/10 rounded-lg p-3 mb-3 border border-amber-500/20">
<p className="text-xs text-amber-300 mb-1">Tevahiya xelatên wergirtî</p>
<p className="text-xs text-amber-300 mb-1">{t('rewards.totalRewards')}</p>
<p className="text-2xl font-bold text-amber-400">
{stakingRewards && stakingRewards.totalAccumulatedHez > 0
? `${stakingRewards.totalAccumulatedHez.toFixed(4)} HEZ`
@@ -678,7 +685,9 @@ export function RewardsSection() {
{/* Recent Rewards List */}
{stakingRewards && stakingRewards.rewards.length > 0 ? (
<div className="space-y-2">
<p className="text-xs text-muted-foreground mb-2">Xelatên dawî</p>
<p className="text-xs text-muted-foreground mb-2">
{t('rewards.recentRewards')}
</p>
{stakingRewards.rewards.map((reward) => (
<div
key={reward.id}
@@ -709,7 +718,7 @@ export function RewardsSection() {
</div>
) : (
<p className="text-sm text-muted-foreground text-center py-3">
Hêj xelatek nehatiye tomarkirin
{t('rewards.noRewardsYet')}
</p>
)}
</div>
@@ -718,7 +727,7 @@ export function RewardsSection() {
<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">
<Star className="w-4 h-4 text-yellow-400" />
Formûla Pûanê
{t('rewards.scoreFormula')}
</h3>
<div className="text-sm text-muted-foreground space-y-2">
<p>Trust = (Staking × Weighted Sum) / 100</p>
@@ -726,9 +735,7 @@ export function RewardsSection() {
Weighted Sum = Staking×100 + Referral×300 + Perwerde×300 + Tiki×300
</p>
<div className="mt-3 p-2 bg-yellow-500/10 rounded-lg border border-yellow-500/20">
<p className="text-yellow-300 text-xs">
Staking 0 be, Trust pûan 0 dibe. Berî her tiştî stake bike!
</p>
<p className="text-yellow-300 text-xs">{t('rewards.stakingZeroWarning')}</p>
</div>
</div>
</div>
@@ -740,7 +747,7 @@ export function RewardsSection() {
className="w-full py-3 bg-primary rounded-lg text-primary-foreground font-medium flex items-center justify-center gap-2"
>
<RefreshCw className={cn('w-4 h-4', scoresLoading && 'animate-spin')} />
Pûanan Nûve Bike
{t('rewards.refreshScores')}
</button>
</>
)}
+7 -6
View File
@@ -16,6 +16,7 @@ import {
} from '@/components/wallet';
import { LoadingScreen } from '@/components/LoadingScreen';
import { VersionInfo } from '@/components/VersionInfo';
import { useTranslation } from '@/i18n';
type Screen = 'loading' | 'auth-error' | 'setup' | 'create' | 'import' | 'connect' | 'dashboard';
type UserScreen = 'create' | 'import' | null;
@@ -23,6 +24,7 @@ type UserScreen = 'create' | 'import' | null;
export function WalletSection() {
const { isInitialized, isConnected, hasWallet, deleteWalletData } = useWallet();
const { isAuthenticated, isLoading: authLoading, authError, signIn } = useAuth();
const { t } = useTranslation();
const [userScreen, setUserScreen] = useState<UserScreen>(null);
// Derive screen from wallet state and user navigation
@@ -80,10 +82,8 @@ export function WalletSection() {
<AlertTriangle className="w-8 h-8 text-red-400" />
</div>
<div>
<h2 className="text-xl font-semibold mb-2">Teketin Tek Cu</h2>
<p className="text-muted-foreground text-sm">
Ji kerema xwe pistrast bikin ku hun ve app-e di nav Telegram de vedikin
</p>
<h2 className="text-xl font-semibold mb-2">{t('wallet.authFailed')}</h2>
<p className="text-muted-foreground text-sm">{t('wallet.authDescription')}</p>
{authError && (
<p className="text-xs text-red-400 mt-2 font-mono break-all px-4">{authError}</p>
)}
@@ -93,7 +93,7 @@ export function WalletSection() {
className="px-6 py-3 bg-primary text-primary-foreground rounded-xl font-semibold flex items-center gap-2 mx-auto"
>
<RefreshCw className="w-4 h-4" />
Disa Biceribine
{t('wallet.retry')}
</button>
</div>
</div>
@@ -132,6 +132,7 @@ export function WalletSection() {
// Header component
function Header() {
const { t } = useTranslation();
return (
<header className="flex-shrink-0 px-4 py-3 border-b border-border bg-background/80 backdrop-blur-sm safe-area-top">
<div className="flex items-center justify-between">
@@ -139,7 +140,7 @@ function Header() {
<div className="w-8 h-8 rounded-lg bg-cyan-500/20 flex items-center justify-center">
<Wallet className="w-4 h-4 text-cyan-400" />
</div>
<h1 className="text-lg font-semibold">Berîk</h1>
<h1 className="text-lg font-semibold">{t('wallet.title')}</h1>
</div>
<VersionInfo />
</div>