mirror of
https://github.com/pezkuwichain/pezkuwi-telegram-miniapp.git
synced 2026-06-17 10:11:11 +00:00
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:
@@ -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 çêbû</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">
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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,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 nû heye!</h4>
|
||||
<p className="text-xs opacity-90 mt-0.5">
|
||||
Ji bo taybetmendiyên 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>
|
||||
|
||||
@@ -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 dê ji mîqdara we bê 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">
|
||||
⚠️ Vê 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 dê 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>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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 Jê Bibe?</h2>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Ev çalakî nayê paşvekişandin. Eger seed phrase'ê te tune be, tu nikarî gihîştina
|
||||
wallet'ê 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"
|
||||
>
|
||||
Jê 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 jê bibe
|
||||
{t('walletConnect.deleteWalletLink')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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) dê ji bo vekirina wallet'ê 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'ê 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'ê 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'ê 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'ê 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 Pê Bike
|
||||
{t('walletCreate.getStarted')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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'ê wallet'ê xwe yê 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 Nû (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>
|
||||
|
||||
@@ -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 Nû Çêbike</p>
|
||||
<p className="text-sm opacity-80">Wallet'ekî nû 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'ê xwe yê 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'ê te bi ewlehî li cîhaza te tê hilanîn. Em tu carî gihîştina mifteyên te tune.
|
||||
{t('walletSetup.securityNote')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user