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
+20 -3
View File
@@ -1,6 +1,21 @@
import { Component, ErrorInfo, ReactNode } from 'react';
import { AlertTriangle, RefreshCw } from 'lucide-react';
import { trackError } from '@/lib/error-tracking';
import krd from '@/i18n/translations/krd';
import en from '@/i18n/translations/en';
import tr from '@/i18n/translations/tr';
import ckb from '@/i18n/translations/ckb';
import fa from '@/i18n/translations/fa';
import ar from '@/i18n/translations/ar';
import type { Translations, LanguageCode } from '@/i18n/types';
const translations: Record<LanguageCode, Translations> = { krd, en, tr, ckb, fa, ar };
function detectLang(): LanguageCode {
const seg = window.location.pathname.split('/').filter(Boolean)[0];
if (seg && seg in translations) return seg as LanguageCode;
return 'krd';
}
interface Props {
children: ReactNode;
@@ -52,16 +67,18 @@ export class ErrorBoundary extends Component<Props, State> {
<div className="w-16 h-16 rounded-full bg-red-500/20 flex items-center justify-center mb-4">
<AlertTriangle className="w-8 h-8 text-red-400" />
</div>
<h1 className="text-xl font-semibold mb-2">Tiştek çewt çê</h1>
<h1 className="text-xl font-semibold mb-2">
{translations[detectLang()].errorBoundary.title}
</h1>
<p className="text-sm text-muted-foreground text-center mb-6 max-w-xs">
Bibore, pirsgirêkek teknîkî derket. Ji kerema xwe dîsa biceribîne.
{translations[detectLang()].errorBoundary.description}
</p>
<button
onClick={this.handleRetry}
className="flex items-center gap-2 px-4 py-2 bg-primary rounded-lg text-primary-foreground font-medium"
>
<RefreshCw className="w-4 h-4" />
Dîsa biceribîne
{translations[detectLang()].errorBoundary.retry}
</button>
{import.meta.env.DEV && this.state.error && (
<div className="mt-6 w-full max-w-lg">
+5 -2
View File
@@ -1,10 +1,13 @@
import { Loader2 } from 'lucide-react';
import { useTranslation } from '@/i18n';
interface LoadingScreenProps {
message?: string;
}
export function LoadingScreen({ message = 'Tê barkirin...' }: LoadingScreenProps) {
export function LoadingScreen({ message }: LoadingScreenProps) {
const { t } = useTranslation();
const displayMessage = message ?? t('loadingScreen.loading');
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-background">
<div className="relative">
@@ -12,7 +15,7 @@ export function LoadingScreen({ message = 'Tê barkirin...' }: LoadingScreenProp
<Loader2 className="w-8 h-8 text-primary animate-spin" />
</div>
</div>
<p className="mt-4 text-sm text-muted-foreground">{message}</p>
<p className="mt-4 text-sm text-muted-foreground">{displayMessage}</p>
</div>
);
}
+16 -102
View File
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { X, ExternalLink, Info } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useTranslation } from '@/i18n';
interface P2PModalProps {
isOpen: boolean;
@@ -8,84 +8,8 @@ interface P2PModalProps {
onOpenP2P: () => void;
}
type Language = 'en' | 'ckb' | 'ku' | 'tr';
const LANGUAGES: { code: Language; label: string }[] = [
{ code: 'en', label: 'EN' },
{ code: 'ckb', label: 'سۆرانی' },
{ code: 'ku', label: 'Kurmancî' },
{ code: 'tr', label: 'TR' },
];
const CONTENT: Record<
Language,
{
title: string;
subtitle: string;
firstTime: string;
steps: string[];
note: string;
button: string;
}
> = {
en: {
title: 'P2P Exchange',
subtitle: 'Trade crypto peer-to-peer',
firstTime: 'First time using P2P?',
steps: [
'Click the button below to open the web app',
'Create an account or log in',
'Complete the P2P setup process',
'After setup, you can access P2P directly',
],
note: 'The web app will open in a new window. Complete the registration process there.',
button: 'Open P2P Platform',
},
ckb: {
title: 'P2P ئاڵۆگۆڕ',
subtitle: 'ئاڵۆگۆڕی کریپتۆ لە نێوان کەسەکاندا',
firstTime: 'یەکەم جار P2P بەکاردەهێنیت؟',
steps: [
'کلیک لە دوگمەی خوارەوە بکە بۆ کردنەوەی ماڵپەڕ',
'هەژمارێک دروست بکە یان بچۆ ژوورەوە',
'پرۆسەی دامەزراندنی P2P تەواو بکە',
'دوای دامەزراندن، دەتوانیت ڕاستەوخۆ بچیتە P2P',
],
note: 'ماڵپەڕ لە پەنجەرەیەکی نوێ دەکرێتەوە. پرۆسەی تۆمارکردن لەوێ تەواو بکە.',
button: 'کردنەوەی P2P',
},
ku: {
title: 'P2P Danûstandin',
subtitle: 'Danûstandina krîpto di navbera kesan de',
firstTime: 'Cara yekem P2P bikar tînin?',
steps: [
'Li bişkoja jêrîn bikirtînin da ku malpera webê vebike',
'Hesabek çêbikin an têkevin',
'Pêvajoya sazkirina P2P temam bikin',
'Piştî sazkirinê, hûn dikarin rasterast bigihîjin P2P',
],
note: 'Malpera webê di pencereyek nû de vedibe. Pêvajoya qeydkirinê li wir temam bikin.',
button: 'P2P Veke',
},
tr: {
title: 'P2P Borsa',
subtitle: 'Kullanıcılar arası kripto alım satım',
firstTime: "P2P'yi ilk kez mi kullanıyorsunuz?",
steps: [
'Web uygulamasını açmak için aşağıdaki butona tıklayın',
'Hesap oluşturun veya giriş yapın',
'P2P kurulum sürecini tamamlayın',
"Kurulumdan sonra P2P'ye doğrudan erişebilirsiniz",
],
note: 'Web uygulaması yeni bir pencerede açılacak. Kayıt işlemini orada tamamlayın.',
button: 'P2P Platformunu Aç',
},
};
export function P2PModal({ isOpen, onClose, onOpenP2P }: P2PModalProps) {
const [lang, setLang] = useState<Language>('en');
const content = CONTENT[lang];
const isRTL = lang === 'ckb';
const { t, isRTL } = useTranslation();
if (!isOpen) return null;
@@ -94,6 +18,14 @@ export function P2PModal({ isOpen, onClose, onOpenP2P }: P2PModalProps) {
onClose();
};
// Access steps array directly - t() only works for string values
// We need to get the steps from the translation as individual indexed items
const steps: string[] = [];
for (let i = 0; i < 4; i++) {
const step = t(`p2p.steps.${i}`);
if (step !== `p2p.steps.${i}`) steps.push(step);
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm">
<div
@@ -105,42 +37,24 @@ export function P2PModal({ isOpen, onClose, onOpenP2P }: P2PModalProps) {
>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-border">
<h2 className="text-lg font-semibold text-foreground">{content.title}</h2>
<h2 className="text-lg font-semibold text-foreground">{t('p2p.title')}</h2>
<button onClick={onClose} className="p-2 rounded-full hover:bg-muted transition-colors">
<X className="w-5 h-5 text-muted-foreground" />
</button>
</div>
{/* Language Selector */}
<div className="flex gap-2 p-4 pb-0">
{LANGUAGES.map((l) => (
<button
key={l.code}
onClick={() => setLang(l.code)}
className={cn(
'px-3 py-1.5 rounded-full text-xs font-medium transition-colors',
lang === l.code
? 'bg-primary text-primary-foreground'
: 'bg-muted text-muted-foreground hover:bg-muted/80'
)}
>
{l.label}
</button>
))}
</div>
{/* Content */}
<div className="p-4 space-y-4">
<p className="text-sm text-muted-foreground">{content.subtitle}</p>
<p className="text-sm text-muted-foreground">{t('p2p.subtitle')}</p>
{/* First Time Info */}
<div className="bg-amber-500/10 border border-amber-500/30 rounded-xl p-4">
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-amber-400 flex-shrink-0 mt-0.5" />
<div className="space-y-2">
<p className="text-sm font-medium text-amber-400">{content.firstTime}</p>
<p className="text-sm font-medium text-amber-400">{t('p2p.firstTime')}</p>
<ol className="space-y-1.5">
{content.steps.map((step, i) => (
{steps.map((step, i) => (
<li key={i} className="text-xs text-amber-200/80 flex gap-2">
<span className="font-semibold text-amber-400">{i + 1}.</span>
<span>{step}</span>
@@ -152,7 +66,7 @@ export function P2PModal({ isOpen, onClose, onOpenP2P }: P2PModalProps) {
</div>
{/* Note */}
<p className="text-xs text-muted-foreground">{content.note}</p>
<p className="text-xs text-muted-foreground">{t('p2p.note')}</p>
</div>
{/* Action Button */}
@@ -162,7 +76,7 @@ export function P2PModal({ isOpen, onClose, onOpenP2P }: P2PModalProps) {
className="w-full flex items-center justify-center gap-2 py-3 px-4 bg-cyan-500 hover:bg-cyan-600 text-white font-medium rounded-xl transition-colors"
>
<ExternalLink className="w-5 h-5" />
{content.button}
{t('p2p.button')}
</button>
</div>
</div>
+14 -14
View File
@@ -1,12 +1,13 @@
import { ExternalLink } from 'lucide-react';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
interface SocialLink {
name: string;
url: string;
icon: string;
color: string;
description: string;
descriptionKey: string;
}
const SOCIAL_LINKS: SocialLink[] = [
@@ -15,61 +16,62 @@ const SOCIAL_LINKS: SocialLink[] = [
url: 'https://www.instagram.com/pezkuwichain',
icon: '📸',
color: 'from-pink-500 to-purple-600',
description: 'Wêne û Story',
descriptionKey: 'social.instagram',
},
{
name: 'TikTok',
url: 'https://www.tiktok.com/@pezkuwi.chain',
icon: '🎵',
color: 'from-gray-800 to-gray-900',
description: 'Vîdyoyên kurt',
descriptionKey: 'social.tiktok',
},
{
name: 'Snapchat',
url: 'https://www.snapchat.com/add/pezkuwichain',
icon: '👻',
color: 'from-yellow-400 to-yellow-500',
description: 'Snap bike!',
descriptionKey: 'social.snapchat',
},
{
name: 'Telegram',
url: 'https://t.me/pezkuwichain',
icon: '📢',
color: 'from-blue-400 to-blue-600',
description: 'Kanala fermî',
descriptionKey: 'social.telegram',
},
{
name: 'X (Twitter)',
url: 'https://x.com/pezkuwichain',
icon: '𝕏',
color: 'from-gray-700 to-gray-900',
description: 'Nûçeyên rojane',
descriptionKey: 'social.twitter',
},
{
name: 'YouTube',
url: 'https://www.youtube.com/@SatoshiQazi',
icon: '▶️',
color: 'from-red-500 to-red-700',
description: 'Vîdyoyên me',
descriptionKey: 'social.youtube',
},
{
name: 'Facebook',
url: 'https://www.facebook.com/people/Pezkuwi-Chain/61587122224932/',
icon: '📘',
color: 'from-blue-600 to-blue-800',
description: 'Rûpela fermî',
descriptionKey: 'social.facebook',
},
{
name: 'Discord',
url: 'https://discord.gg/Y3VyEC6h8W',
icon: '💬',
color: 'from-indigo-500 to-purple-600',
description: 'Civaka me',
descriptionKey: 'social.discord',
},
];
export function SocialLinks() {
const { openLink, hapticImpact } = useTelegram();
const { t } = useTranslation();
const handleClick = (url: string) => {
hapticImpact('light');
@@ -80,11 +82,9 @@ export function SocialLinks() {
<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">
<ExternalLink className="w-4 h-4 text-primary" />
Me bişopîne
{t('social.followUs')}
</h3>
<p className="text-sm text-muted-foreground mb-4">
Bi me re têkiliyê ragire û nûçeyên herî dawî bistîne!
</p>
<p className="text-sm text-muted-foreground mb-4">{t('social.stayConnected')}</p>
<div className="grid grid-cols-2 gap-2">
{SOCIAL_LINKS.map((link) => (
<button
@@ -100,7 +100,7 @@ export function SocialLinks() {
<span className="text-2xl">{link.icon}</span>
<div className="text-left">
<p className="text-sm font-medium text-white">{link.name}</p>
<p className="text-xs text-white/70">{link.description}</p>
<p className="text-xs text-white/70">{t(link.descriptionKey)}</p>
</div>
</button>
))}
+6 -6
View File
@@ -6,10 +6,12 @@
import { RefreshCw, X } from 'lucide-react';
import { useVersion } from '@/hooks/useVersion';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
export function UpdateNotification() {
const { hasUpdate, forceUpdate, dismissUpdate, currentVersion } = useVersion();
const { hapticImpact } = useTelegram();
const { t } = useTranslation();
if (!hasUpdate) return null;
@@ -31,10 +33,8 @@ export function UpdateNotification() {
<RefreshCw className="w-5 h-5" />
</div>
<div className="flex-1 min-w-0">
<h4 className="font-semibold text-sm">Guhertoya heye!</h4>
<p className="text-xs opacity-90 mt-0.5">
Ji bo taybetmendiyên û rastkirinên ewlehiyê nûve bike.
</p>
<h4 className="font-semibold text-sm">{t('update.newVersion')}</h4>
<p className="text-xs opacity-90 mt-0.5">{t('update.description')}</p>
<p className="text-[10px] opacity-70 mt-1">v{currentVersion}</p>
</div>
<button
@@ -50,14 +50,14 @@ export function UpdateNotification() {
onClick={handleDismiss}
className="flex-1 py-2 px-3 rounded-lg bg-white/10 hover:bg-white/20 text-sm font-medium transition-colors"
>
Paşê
{t('update.later')}
</button>
<button
onClick={handleUpdate}
className="flex-1 py-2 px-3 rounded-lg bg-white text-primary text-sm font-medium hover:bg-white/90 transition-colors flex items-center justify-center gap-2"
>
<RefreshCw className="w-4 h-4" />
Nûve bike
{t('update.updateNow')}
</button>
</div>
</div>
+50 -50
View File
@@ -18,6 +18,7 @@ import {
import { useTelegram } from '@/hooks/useTelegram';
import { useWallet } from '@/contexts/WalletContext';
import { supabase } from '@/lib/supabase';
import { useTranslation } from '@/i18n';
type Network = 'ton' | 'polkadot' | 'trc20';
@@ -28,7 +29,7 @@ interface NetworkInfo {
icon: string;
recommended: boolean;
fee: number;
feeWarning?: string;
feeWarning?: boolean;
explorer: string;
minDeposit: number;
}
@@ -61,7 +62,7 @@ const NETWORKS: NetworkInfo[] = [
icon: '🔴',
recommended: false,
fee: 3,
feeWarning: 'Mesrefa tora TRC20 bi qasî $3 ye. Em tora TON an Polkadot pêşniyar dikin.',
feeWarning: true,
explorer: 'https://tronscan.org/#/transaction/',
minDeposit: 10,
},
@@ -84,6 +85,7 @@ interface Props {
export function DepositUSDTModal({ isOpen, onClose }: Props) {
const { hapticImpact, showAlert } = useTelegram();
const { address: localWalletAddress } = useWallet();
const { t } = useTranslation();
const [selectedNetwork, setSelectedNetwork] = useState<Network>('ton');
const [depositCode, setDepositCode] = useState<string>('');
@@ -184,7 +186,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
hapticImpact('light');
setTimeout(() => setCopied(null), 2000);
} catch {
showAlert('Kopî nekir');
showAlert(t('deposit.copyFailed'));
}
};
@@ -204,15 +206,15 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
const getStatusText = (status: string) => {
switch (status) {
case 'pending':
return 'Li benda';
return t('deposit.statusPending');
case 'confirming':
return 'Tê pejirandin';
return t('deposit.statusConfirming');
case 'completed':
return 'Qediya';
return t('deposit.statusCompleted');
case 'failed':
return 'Neserketî';
return t('deposit.statusFailed');
case 'expired':
return 'Dema wê derbas bû';
return t('deposit.statusExpired');
default:
return status;
}
@@ -233,8 +235,8 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
<Plus className="w-5 h-5 text-green-400" />
</div>
<div>
<h2 className="text-lg font-semibold">USDT Depo Bike</h2>
<p className="text-xs text-muted-foreground">Bo wUSDT li Asset Hub</p>
<h2 className="text-lg font-semibold">{t('deposit.title')}</h2>
<p className="text-xs text-muted-foreground">{t('deposit.subtitle')}</p>
</div>
</div>
<div className="flex gap-2">
@@ -256,9 +258,11 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
{showHistory ? (
/* Deposit History */
<div className="space-y-3">
<h3 className="text-sm font-medium text-muted-foreground">Dîroka Depoyan</h3>
<h3 className="text-sm font-medium text-muted-foreground">
{t('deposit.depositHistory')}
</h3>
{deposits.length === 0 ? (
<p className="text-center text-muted-foreground py-8">Hîn depo tune</p>
<p className="text-center text-muted-foreground py-8">{t('deposit.noDeposits')}</p>
) : (
deposits.map((deposit) => (
<div key={deposit.id} className="bg-muted/50 rounded-xl p-3">
@@ -290,7 +294,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
className="text-xs text-blue-400 flex items-center gap-1 mt-2"
>
<ExternalLink className="w-3 h-3" />
TX bibîne
{t('deposit.viewTx')}
</a>
)}
</div>
@@ -300,14 +304,16 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
onClick={() => setShowHistory(false)}
className="w-full py-3 bg-muted rounded-xl text-center"
>
Vegere
{t('deposit.goBack')}
</button>
</div>
) : (
<div className="space-y-4">
{/* Network Selection */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Torê Hilbijêre</label>
<label className="text-sm text-muted-foreground mb-2 block">
{t('deposit.selectNetwork')}
</label>
<div className="grid grid-cols-3 gap-2">
{NETWORKS.map((net) => (
<button
@@ -327,7 +333,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
>
{net.recommended && (
<span className="absolute -top-2 left-1/2 -translate-x-1/2 text-[10px] bg-green-500 text-white px-1.5 py-0.5 rounded">
Pêşniyar
{t('deposit.recommended')}
</span>
)}
<div className="text-xl mb-1">{net.icon}</div>
@@ -349,11 +355,13 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
<div className="flex gap-3">
<AlertTriangle className="w-6 h-6 text-yellow-400 flex-shrink-0" />
<div>
<p className="text-sm text-yellow-400 font-medium mb-2">Dikkkat!</p>
<p className="text-xs text-yellow-400/80 mb-3">{network.feeWarning}</p>
<p className="text-xs text-yellow-400/80 mb-3">
Mînak: 10 USDT bişîne 7 wUSDT werbigire ($3 masraf)
<p className="text-sm text-yellow-400 font-medium mb-2">
{t('deposit.warning')}
</p>
<p className="text-xs text-yellow-400/80 mb-3">
{t('deposit.trc20FeeWarning')}
</p>
<p className="text-xs text-yellow-400/80 mb-3">{t('deposit.example')}</p>
<button
onClick={() => {
setTrc20Accepted(true);
@@ -361,7 +369,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
}}
className="w-full py-2 bg-yellow-500/20 border border-yellow-500/50 rounded-lg text-yellow-400 text-sm font-medium"
>
Qebûl dikim, bi TRC20 bişîne
{t('deposit.acceptTrc20')}
</button>
</div>
</div>
@@ -376,19 +384,13 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
<div className="flex gap-2">
<AlertCircle className="w-5 h-5 text-blue-400 flex-shrink-0" />
<div className="text-sm text-blue-400">
<p className="font-medium">Girîng!</p>
<p className="font-medium">{t('deposit.important')}</p>
<ul className="list-disc list-inside text-xs mt-1 space-y-1">
<li>
Kêmtirîn: <strong>{network.minDeposit} USDT</strong>
</li>
{selectedNetwork !== 'trc20' && (
<li>Memo/Comment qada de koda xwe binivîse</li>
)}
<li>Tenê USDT bişîne, tokenên din winda dibin</li>
<li>{t('deposit.minimum', { amount: network.minDeposit })}</li>
{selectedNetwork !== 'trc20' && <li>{t('deposit.memoRequired')}</li>}
<li>{t('deposit.onlyUsdt')}</li>
{selectedNetwork === 'trc20' && (
<li className="text-yellow-400">
$3 masraf ji mîqdara we kêmkirin
</li>
<li className="text-yellow-400">{t('deposit.trc20Fee')}</li>
)}
</ul>
</div>
@@ -399,7 +401,9 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
<div className="bg-muted/50 rounded-xl p-4 space-y-4">
{/* Address */}
<div>
<label className="text-xs text-muted-foreground">Navnîşana Depoyê</label>
<label className="text-xs text-muted-foreground">
{t('deposit.depositAddress')}
</label>
<div className="flex items-center gap-2 mt-1">
{isLoading && selectedNetwork === 'trc20' ? (
<div className="flex-1 p-2 bg-background rounded-lg flex justify-center">
@@ -407,7 +411,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
</div>
) : (
<code className="flex-1 text-xs font-mono bg-background p-2 rounded-lg break-all">
{currentAddress || 'Amade nîne'}
{currentAddress || t('deposit.notAvailable')}
</code>
)}
<button
@@ -428,7 +432,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
{selectedNetwork !== 'trc20' && (
<div>
<label className="text-xs text-muted-foreground">
Memo / Comment (PÊWÎST)
{t('deposit.memoLabel')}
</label>
<div className="flex items-center gap-2 mt-1">
{isLoading ? (
@@ -452,31 +456,27 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
)}
</button>
</div>
<p className="text-xs text-red-400 mt-1">
kodê di memo de binivîse, wekî din depoya te nayê nas kirin!
</p>
<p className="text-xs text-red-400 mt-1">{t('deposit.memoWarning')}</p>
</div>
)}
{selectedNetwork === 'trc20' && (
<p className="text-xs text-green-400">
Ev navnîşan tenê ya te ye. Memo ne pêwîst e.
</p>
<p className="text-xs text-green-400">{t('deposit.uniqueAddress')}</p>
)}
</div>
{/* Steps */}
<div className="bg-muted/30 rounded-xl p-4">
<h4 className="text-sm font-medium mb-3">Çawa Depo Bikim?</h4>
<h4 className="text-sm font-medium mb-3">{t('deposit.howToDeposit')}</h4>
<ol className="text-xs text-muted-foreground space-y-2">
<li className="flex gap-2">
<span className="text-green-400 font-bold">1.</span>
<span>Navnîşan kopî bike</span>
<span>{t('deposit.stepCopyAddress')}</span>
</li>
{selectedNetwork !== 'trc20' && (
<li className="flex gap-2">
<span className="text-green-400 font-bold">2.</span>
<span>Memo kodê kopî bike</span>
<span>{t('deposit.stepCopyMemo')}</span>
</li>
)}
<li className="flex gap-2">
@@ -485,30 +485,30 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
</span>
<span>
{selectedNetwork === 'ton'
? 'Telegram Wallet an cîzdana xwe veke'
? t('deposit.stepOpenTon')
: selectedNetwork === 'polkadot'
? 'Polkadot cîzdana xwe veke'
: 'TronLink an cîzdana xwe veke'}
? t('deposit.stepOpenPolkadot')
: t('deposit.stepOpenTrc20')}
</span>
</li>
<li className="flex gap-2">
<span className="text-green-400 font-bold">
{selectedNetwork === 'trc20' ? '3' : '4'}.
</span>
<span>USDT bişîne navnîşana jorîn</span>
<span>{t('deposit.stepSendUsdt')}</span>
</li>
<li className="flex gap-2">
<span className="text-green-400 font-bold">
{selectedNetwork === 'trc20' ? '4' : '5'}.
</span>
<span>wUSDT di nav çend hûrdeman de li hesabê te be</span>
<span>{t('deposit.stepReceive')}</span>
</li>
</ol>
</div>
{/* Processing Time */}
<p className="text-center text-xs text-muted-foreground">
Dema pêvajoyê: ~1-5 hûrdem
{t('deposit.processingTime')}
</p>
</>
)}
+16 -15
View File
@@ -8,6 +8,7 @@ import { Eye, EyeOff, Wallet, Unlock, Trash2 } from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { formatAddress } from '@/lib/wallet-service';
import { useTranslation } from '@/i18n';
interface Props {
onConnected: () => void;
@@ -17,6 +18,7 @@ interface Props {
export function WalletConnect({ onConnected, onDelete }: Props) {
const { address, connect, error: walletError } = useWallet();
const { hapticImpact, hapticNotification } = useTelegram();
const { t } = useTranslation();
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
@@ -26,7 +28,7 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
const handleConnect = async () => {
if (!password) {
setError('Şîfre (password) binivîse');
setError(t('walletConnect.enterPassword'));
return;
}
@@ -39,7 +41,7 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
hapticNotification('success');
onConnected();
} catch (err) {
setError(err instanceof Error ? err.message : 'Şîfre (password) çewt e');
setError(err instanceof Error ? err.message : t('walletConnect.wrongPassword'));
hapticNotification('error');
} finally {
setIsLoading(false);
@@ -58,11 +60,8 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
<div className="w-16 h-16 mx-auto bg-red-500/20 rounded-full flex items-center justify-center mb-4">
<Trash2 className="w-8 h-8 text-red-500" />
</div>
<h2 className="text-xl font-semibold mb-2">Wallet Bibe?</h2>
<p className="text-muted-foreground text-sm">
Ev çalakî nayê paşvekişandin. Eger seed phrase&apos;ê te tune be, tu nikarî gihîştina
wallet&apos;ê xwe bistînî.
</p>
<h2 className="text-xl font-semibold mb-2">{t('walletConnect.deleteTitle')}</h2>
<p className="text-muted-foreground text-sm">{t('walletConnect.deleteDescription')}</p>
</div>
<div className="flex gap-3">
@@ -70,13 +69,13 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
onClick={() => setShowDeleteConfirm(false)}
className="flex-1 py-3 bg-muted rounded-xl font-semibold"
>
Betal
{t('common.cancel')}
</button>
<button
onClick={handleDelete}
className="flex-1 py-3 bg-red-500 text-white rounded-xl font-semibold"
>
Bibe
{t('walletConnect.deleteButton')}
</button>
</div>
</div>
@@ -89,7 +88,7 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
<div className="w-16 h-16 mx-auto bg-primary/20 rounded-full flex items-center justify-center mb-4">
<Wallet className="w-8 h-8 text-primary" />
</div>
<h2 className="text-xl font-semibold mb-2">Wallet Veke</h2>
<h2 className="text-xl font-semibold mb-2">{t('walletConnect.openWallet')}</h2>
{address && (
<p className="text-muted-foreground text-sm font-mono">{formatAddress(address)}</p>
)}
@@ -97,7 +96,9 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Şîfre (Password)</label>
<label className="text-sm text-muted-foreground">
{t('walletConnect.passwordLabel')}
</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
@@ -105,7 +106,7 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
onChange={(e) => setPassword(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleConnect()}
className="w-full px-4 py-3 bg-muted rounded-xl pr-12"
placeholder="Şîfre (password) binivîse"
placeholder={t('walletConnect.passwordPlaceholder')}
autoFocus
/>
<button
@@ -130,11 +131,11 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50 flex items-center justify-center gap-2"
>
{isLoading ? (
'Tê vekirin...'
t('walletConnect.connecting')
) : (
<>
<Unlock className="w-4 h-4" />
Connect
{t('walletConnect.connect')}
</>
)}
</button>
@@ -143,7 +144,7 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
onClick={() => setShowDeleteConfirm(true)}
className="w-full py-3 text-red-400 text-sm"
>
Wallet bibe
{t('walletConnect.deleteWalletLink')}
</button>
</div>
</div>
+54 -58
View File
@@ -17,6 +17,7 @@ import {
} from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
type Step = 'password' | 'backup' | 'verify' | 'complete';
@@ -34,6 +35,7 @@ interface Props {
export function WalletCreate({ onComplete, onBack }: Props) {
const { generateNewWallet, confirmWallet, isInitialized } = useWallet();
const { hapticImpact, hapticNotification } = useTelegram();
const { t } = useTranslation();
const [step, setStep] = useState<Step>('password');
const [password, setPassword] = useState('');
@@ -84,12 +86,12 @@ export function WalletCreate({ onComplete, onBack }: Props) {
setError('');
if (!isInitialized) {
setError('Wallet service amade nîne. Ji kerema xwe bisekinin.');
setError(t('walletCreate.walletServiceNotReady'));
return;
}
if (!allPasswordRulesPass) {
setError('Ji kerema xwe hemû şertên şîfre (password) bicîh bînin');
setError(t('walletCreate.passwordRequirementsNotMet'));
hapticNotification('error');
return;
}
@@ -104,9 +106,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
setStep('backup');
} catch (err) {
console.error('Wallet generation error:', err);
setError(
err instanceof Error ? err.message : 'Wallet çênebû. Ji kerema xwe dîsa biceribînin'
);
setError(err instanceof Error ? err.message : t('walletCreate.walletCreationFailed'));
hapticNotification('error');
}
};
@@ -122,7 +122,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
// Step 2: Backup - Proceed to verify (only if all conditions checked)
const handleBackupContinue = () => {
if (!allConditionsChecked) {
setError('Ji kerema xwe hemû şertan bipejirînin');
setError(t('walletCreate.acceptAllConditions'));
return;
}
@@ -183,7 +183,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
originalWords.every((word, idx) => word === enteredWords[idx]);
if (!isCorrect) {
setError('Rêza peyvan ne rast e. Ji kerema xwe dîsa biceribînin');
setError(t('walletCreate.wrongOrder'));
hapticNotification('error');
return;
}
@@ -197,7 +197,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
hapticNotification('success');
setStep('complete');
} catch (err) {
setError(err instanceof Error ? err.message : 'Wallet çênebû');
setError(err instanceof Error ? err.message : t('walletCreate.walletCreationFailed'));
hapticNotification('error');
} finally {
setIsLoading(false);
@@ -213,26 +213,26 @@ export function WalletCreate({ onComplete, onBack }: Props) {
<div className="p-4 space-y-6">
<button onClick={onBack} className="flex items-center gap-2 text-muted-foreground">
<ArrowLeft className="w-4 h-4" />
<span>Paş</span>
<span>{t('common.back')}</span>
</button>
<div className="text-center">
<h2 className="text-xl font-semibold mb-2">Şîfre (Password) Diyar Bike</h2>
<p className="text-muted-foreground text-sm">
Ev şîfre (password) ji bo vekirina wallet&apos;ê were bikaranîn
</p>
<h2 className="text-xl font-semibold mb-2">{t('walletCreate.setPassword')}</h2>
<p className="text-muted-foreground text-sm">{t('walletCreate.passwordDescription')}</p>
</div>
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Şîfre (Password)</label>
<label className="text-sm text-muted-foreground">
{t('walletCreate.passwordLabel')}
</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-3 bg-muted rounded-xl pr-12"
placeholder="Herî kêm 12 tîp (min 12 characters)"
placeholder={t('walletCreate.passwordPlaceholder')}
/>
<button
type="button"
@@ -245,20 +245,24 @@ export function WalletCreate({ onComplete, onBack }: Props) {
</div>
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Şîfre Dubare (Confirm Password)</label>
<label className="text-sm text-muted-foreground">
{t('walletCreate.confirmPasswordLabel')}
</label>
<input
type={showPassword ? 'text' : 'password'}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full px-4 py-3 bg-muted rounded-xl"
placeholder="Şîfre dubare binivîse (confirm password)"
placeholder={t('walletCreate.confirmPasswordPlaceholder')}
/>
</div>
{/* Real-time password strength indicator */}
{password.length > 0 && (
<div className="p-3 bg-muted/50 rounded-xl space-y-2">
<p className="text-xs text-muted-foreground font-medium">Şertên Şîfre (Password):</p>
<p className="text-xs text-muted-foreground font-medium">
{t('walletCreate.passwordRequirements')}
</p>
<div className="grid grid-cols-1 gap-1.5 text-xs">
<div
className={`flex items-center gap-2 ${passwordRules.minLength ? 'text-green-400' : 'text-red-400'}`}
@@ -268,7 +272,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 12 tîp (min 12 characters)</span>
<span>{t('walletCreate.ruleMinLength')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasLowercase ? 'text-green-400' : 'text-red-400'}`}
@@ -278,7 +282,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 tîpa biçûk (a-z)</span>
<span>{t('walletCreate.ruleLowercase')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasUppercase ? 'text-green-400' : 'text-red-400'}`}
@@ -288,7 +292,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 tîpa mezin (A-Z)</span>
<span>{t('walletCreate.ruleUppercase')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasNumber ? 'text-green-400' : 'text-red-400'}`}
@@ -298,7 +302,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 hejmar (0-9)</span>
<span>{t('walletCreate.ruleNumber')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasSpecialChar ? 'text-green-400' : 'text-red-400'}`}
@@ -308,7 +312,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 sembola taybetî (!@#$%...)</span>
<span>{t('walletCreate.ruleSpecialChar')}</span>
</div>
{confirmPassword.length > 0 && (
<div
@@ -319,7 +323,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Şîfre (password) hev digirin</span>
<span>{t('walletCreate.rulePasswordsMatch')}</span>
</div>
)}
</div>
@@ -338,12 +342,12 @@ export function WalletCreate({ onComplete, onBack }: Props) {
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50 flex items-center justify-center gap-2"
>
{!isInitialized
? 'Tê amadekirin...'
? t('walletCreate.preparing')
: isLoading
? 'Tê çêkirin...'
? t('walletCreate.creating')
: allPasswordRulesPass
? 'Berdewam'
: 'Şertên şîfre (password) bicîh bînin'}
? t('common.continue')
: t('walletCreate.meetPasswordRequirements')}
{!isLoading && allPasswordRulesPass && isInitialized && (
<ArrowRight className="w-4 h-4" />
)}
@@ -359,18 +363,13 @@ export function WalletCreate({ onComplete, onBack }: Props) {
return (
<div className="p-4 space-y-6">
<div className="text-center">
<h2 className="text-xl font-semibold mb-2">Seed Phrase Paşguh Bike</h2>
<p className="text-muted-foreground text-sm">
Ev 12 peyv wallet&apos;ê te ne. Wan li cihekî ewle binivîse!
</p>
<h2 className="text-xl font-semibold mb-2">{t('walletCreate.backupTitle')}</h2>
<p className="text-muted-foreground text-sm">{t('walletCreate.backupDescription')}</p>
</div>
<div className="p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-xl flex items-start gap-3">
<AlertTriangle className="w-5 h-5 text-yellow-500 shrink-0 mt-0.5" />
<p className="text-sm text-yellow-200">
<strong>Girîng:</strong> Ev peyvan tenê yek car têne xuyang kirin. Eger te ev peyv winda
bikin, tu nikarî gihîştina wallet&apos;ê xwe bistînî.
</p>
<p className="text-sm text-yellow-200">{t('walletCreate.backupWarning')}</p>
</div>
<div className="grid grid-cols-3 gap-2">
@@ -389,12 +388,12 @@ export function WalletCreate({ onComplete, onBack }: Props) {
{copied ? (
<>
<Check className="w-4 h-4 text-green-400" />
<span className="text-green-400">Hat kopîkirin!</span>
<span className="text-green-400">{t('walletCreate.copiedMnemonic')}</span>
</>
) : (
<>
<Copy className="w-4 h-4" />
<span>Kopî Bike</span>
<span>{t('walletCreate.copyMnemonic')}</span>
</>
)}
</button>
@@ -410,7 +409,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
}
className="mt-1 w-5 h-5 accent-primary"
/>
<span className="text-sm">Min ev 12 peyv li cihekî ewle nivîsandine</span>
<span className="text-sm">{t('walletCreate.conditionWrittenDown')}</span>
</label>
<label className="flex items-start gap-3 p-3 bg-muted rounded-xl cursor-pointer">
@@ -420,9 +419,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
onChange={(e) => setConditions((prev) => ({ ...prev, neverShare: e.target.checked }))}
className="mt-1 w-5 h-5 accent-primary"
/>
<span className="text-sm">
Ez fêm dikim ku ez nikarim ev peyvan bi kesî re parve bikim
</span>
<span className="text-sm">{t('walletCreate.conditionNeverShare')}</span>
</label>
<label className="flex items-start gap-3 p-3 bg-muted rounded-xl cursor-pointer">
@@ -432,10 +429,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
onChange={(e) => setConditions((prev) => ({ ...prev, lossRisk: e.target.checked }))}
className="mt-1 w-5 h-5 accent-primary"
/>
<span className="text-sm">
Ez fêm dikim ku eger van peyvan winda bikim ez nikarim gihîştina wallet&apos;ê xwe
bistînim
</span>
<span className="text-sm">{t('walletCreate.conditionLossRisk')}</span>
</label>
</div>
@@ -450,7 +444,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
disabled={!allConditionsChecked}
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50 flex items-center justify-center gap-2"
>
{allConditionsChecked ? 'Berdewam' : 'Hemû şertan bipejirînin'}
{allConditionsChecked ? t('common.continue') : t('walletCreate.acceptAllConditions')}
{allConditionsChecked && <ArrowRight className="w-4 h-4" />}
</button>
</div>
@@ -461,24 +455,26 @@ export function WalletCreate({ onComplete, onBack }: Props) {
return (
<div className="p-4 space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold">Peyvan Verast Bike</h2>
<h2 className="text-lg font-semibold">{t('walletCreate.verifyWords')}</h2>
<button
onClick={handleReset}
className="flex items-center gap-1 text-sm text-muted-foreground"
>
<RotateCcw className="w-4 h-4" />
<span>Reset</span>
<span>{t('walletCreate.reset')}</span>
</button>
</div>
<p className="text-muted-foreground text-sm text-center">
Ji kerema xwe peyvan bi rêza rast bixin nav qutîkê
{t('walletCreate.verifyDescription')}
</p>
{/* Destination area - where user builds the correct order */}
<div className="min-h-[120px] p-4 bg-muted/50 border-2 border-dashed border-border rounded-xl">
{destinationWords.length === 0 ? (
<p className="text-center text-muted-foreground text-sm">Peyvan li vir bixin...</p>
<p className="text-center text-muted-foreground text-sm">
{t('walletCreate.dropWordsHere')}
</p>
) : (
<div className="flex flex-wrap gap-2">
{destinationWords.map((word, idx) => (
@@ -525,10 +521,10 @@ export function WalletCreate({ onComplete, onBack }: Props) {
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50"
>
{isLoading
? 'Tê tomarkirin...'
? t('walletCreate.saving')
: canVerify
? 'Verast Bike'
: `${destinationWords.length}/12 peyv`}
? t('walletCreate.verify')
: t('walletCreate.wordsCount', { count: destinationWords.length })}
</button>
</div>
);
@@ -542,12 +538,12 @@ export function WalletCreate({ onComplete, onBack }: Props) {
</div>
<div>
<h2 className="text-xl font-semibold mb-2">Wallet Hat Çêkirin!</h2>
<p className="text-muted-foreground text-sm">Wallet&apos;ê te amade ye</p>
<h2 className="text-xl font-semibold mb-2">{t('walletCreate.walletCreated')}</h2>
<p className="text-muted-foreground text-sm">{t('walletCreate.walletReady')}</p>
</div>
<div className="p-4 bg-muted rounded-xl">
<p className="text-xs text-muted-foreground mb-1">Navnîşana te</p>
<p className="text-xs text-muted-foreground mb-1">{t('walletCreate.yourAddress')}</p>
<p className="font-mono text-sm break-all">{address}</p>
</div>
@@ -555,7 +551,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
onClick={onComplete}
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold"
>
Dest Bike
{t('walletCreate.getStarted')}
</button>
</div>
);
+31 -25
View File
@@ -8,6 +8,7 @@ import { Eye, EyeOff, ArrowLeft, ArrowRight, Check, AlertTriangle } from 'lucide
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { validatePassword } from '@/lib/crypto';
import { useTranslation } from '@/i18n';
interface Props {
onComplete: () => void;
@@ -17,6 +18,7 @@ interface Props {
export function WalletImport({ onComplete, onBack }: Props) {
const { importWallet } = useWallet();
const { hapticImpact, hapticNotification } = useTelegram();
const { t } = useTranslation();
const [mnemonic, setMnemonic] = useState('');
const [password, setPassword] = useState('');
@@ -49,18 +51,18 @@ export function WalletImport({ onComplete, onBack }: Props) {
// Validate mnemonic
const words = mnemonic.trim().split(/\s+/);
if (words.length !== 12 && words.length !== 24) {
setError('Seed phrase divê 12 an 24 peyv be');
setError(t('walletImport.seedPhraseInvalid'));
return;
}
// Validate password using crypto.ts rules
const passwordValidation = validatePassword(password);
if (!passwordValidation.valid) {
setError(passwordValidation.message || 'Şîfre (password) ne derbasdar e');
setError(passwordValidation.message || t('walletImport.passwordInvalid'));
return;
}
if (password !== confirmPassword) {
setError('Şîfre (password) hev nagirin');
setError(t('walletImport.passwordsMismatch'));
return;
}
@@ -72,7 +74,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
hapticNotification('success');
onComplete();
} catch (err) {
setError(err instanceof Error ? err.message : 'Import neserketî');
setError(err instanceof Error ? err.message : t('walletImport.importFailed'));
hapticNotification('error');
} finally {
setIsLoading(false);
@@ -83,24 +85,24 @@ export function WalletImport({ onComplete, onBack }: Props) {
<div className="p-4 space-y-6">
<button onClick={onBack} className="flex items-center gap-2 text-muted-foreground">
<ArrowLeft className="w-4 h-4" />
<span>Paş</span>
<span>{t('common.back')}</span>
</button>
<div className="text-center">
<h2 className="text-xl font-semibold mb-2">Wallet Import Bike</h2>
<p className="text-muted-foreground text-sm">
Seed phrase&apos;ê wallet&apos;ê xwe heyî binivîse
</p>
<h2 className="text-xl font-semibold mb-2">{t('walletImport.title')}</h2>
<p className="text-muted-foreground text-sm">{t('walletImport.description')}</p>
</div>
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Seed Phrase (12 an 24 peyv)</label>
<label className="text-sm text-muted-foreground">
{t('walletImport.seedPhraseLabel')}
</label>
<textarea
value={mnemonic}
onChange={(e) => setMnemonic(e.target.value)}
className="w-full px-4 py-3 bg-muted rounded-xl resize-none h-28 font-mono text-sm"
placeholder="Peyvên xwe bi valahî cuda binivîse..."
placeholder={t('walletImport.seedPhrasePlaceholder')}
/>
<p className="text-xs text-muted-foreground">
{mnemonic.trim().split(/\s+/).filter(Boolean).length} / 12 peyv
@@ -108,14 +110,14 @@ export function WalletImport({ onComplete, onBack }: Props) {
</div>
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Şîfreya (New Password)</label>
<label className="text-sm text-muted-foreground">{t('walletImport.newPassword')}</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-3 bg-muted rounded-xl pr-12"
placeholder="Herî kêm 12 tîp (min 12 characters)"
placeholder={t('walletCreate.passwordPlaceholder')}
/>
<button
type="button"
@@ -128,20 +130,24 @@ export function WalletImport({ onComplete, onBack }: Props) {
</div>
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Şîfre Dubare (Confirm Password)</label>
<label className="text-sm text-muted-foreground">
{t('walletCreate.confirmPasswordLabel')}
</label>
<input
type={showPassword ? 'text' : 'password'}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full px-4 py-3 bg-muted rounded-xl"
placeholder="Şîfre dubare binivîse (confirm password)"
placeholder={t('walletCreate.confirmPasswordPlaceholder')}
/>
</div>
{/* Real-time password strength indicator */}
{password.length > 0 && (
<div className="p-3 bg-muted/50 rounded-xl space-y-2">
<p className="text-xs text-muted-foreground font-medium">Şertên Şîfre (Password):</p>
<p className="text-xs text-muted-foreground font-medium">
{t('walletCreate.passwordRequirements')}
</p>
<div className="grid grid-cols-1 gap-1.5 text-xs">
<div
className={`flex items-center gap-2 ${passwordRules.minLength ? 'text-green-400' : 'text-red-400'}`}
@@ -151,7 +157,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 12 tîp (min 12 characters)</span>
<span>{t('walletCreate.ruleMinLength')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasLowercase ? 'text-green-400' : 'text-red-400'}`}
@@ -161,7 +167,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 tîpa biçûk (a-z)</span>
<span>{t('walletCreate.ruleLowercase')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasUppercase ? 'text-green-400' : 'text-red-400'}`}
@@ -171,7 +177,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 tîpa mezin (A-Z)</span>
<span>{t('walletCreate.ruleUppercase')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasNumber ? 'text-green-400' : 'text-red-400'}`}
@@ -181,7 +187,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 hejmar (0-9)</span>
<span>{t('walletCreate.ruleNumber')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasSpecialChar ? 'text-green-400' : 'text-red-400'}`}
@@ -191,7 +197,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 sembola taybetî (!@#$%...)</span>
<span>{t('walletCreate.ruleSpecialChar')}</span>
</div>
{confirmPassword.length > 0 && (
<div
@@ -202,7 +208,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Şîfre (password) hev digirin</span>
<span>{t('walletCreate.rulePasswordsMatch')}</span>
</div>
)}
</div>
@@ -221,10 +227,10 @@ export function WalletImport({ onComplete, onBack }: Props) {
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50 flex items-center justify-center gap-2"
>
{isLoading
? 'Tê import kirin...'
? t('walletImport.importing')
: allPasswordRulesPass
? 'Import Bike'
: 'Şertên şîfre (password) bicîh bînin'}
? t('walletImport.importButton')
: t('walletCreate.meetPasswordRequirements')}
{!isLoading && allPasswordRulesPass && <ArrowRight className="w-4 h-4" />}
</button>
</div>
+9 -8
View File
@@ -4,6 +4,7 @@
*/
import { Wallet, Plus, Download } from 'lucide-react';
import { useTranslation } from '@/i18n';
interface Props {
onCreate: () => void;
@@ -11,6 +12,8 @@ interface Props {
}
export function WalletSetup({ onCreate, onImport }: Props) {
const { t } = useTranslation();
return (
<div className="p-4 space-y-8">
<div className="text-center pt-8">
@@ -18,7 +21,7 @@ export function WalletSetup({ onCreate, onImport }: Props) {
<Wallet className="w-10 h-10 text-primary" />
</div>
<h1 className="text-2xl font-bold mb-2">Pezkuwi Wallet</h1>
<p className="text-muted-foreground">Berîka fermî ya Pezkuwichain</p>
<p className="text-muted-foreground">{t('walletSetup.officialWallet')}</p>
</div>
<div className="space-y-3">
@@ -30,8 +33,8 @@ export function WalletSetup({ onCreate, onImport }: Props) {
<Plus className="w-6 h-6" />
</div>
<div className="text-left">
<p className="font-semibold">Wallet Çêbike</p>
<p className="text-sm opacity-80">Wallet&apos;ekî bi seed phrase çêbike</p>
<p className="font-semibold">{t('walletSetup.createNew')}</p>
<p className="text-sm opacity-80">{t('walletSetup.createNewDesc')}</p>
</div>
</button>
@@ -43,16 +46,14 @@ export function WalletSetup({ onCreate, onImport }: Props) {
<Download className="w-6 h-6 text-primary" />
</div>
<div className="text-left">
<p className="font-semibold">Wallet Import Bike</p>
<p className="text-sm text-muted-foreground">
Seed phrase&apos;ê xwe heyî bi kar bîne
</p>
<p className="font-semibold">{t('walletSetup.importWallet')}</p>
<p className="text-sm text-muted-foreground">{t('walletSetup.importWalletDesc')}</p>
</div>
</button>
</div>
<p className="text-center text-xs text-muted-foreground px-4">
Wallet&apos;ê te bi ewlehî li cîhaza te hilanîn. Em tu carî gihîştina mifteyên te tune.
{t('walletSetup.securityNote')}
</p>
</div>
);