From 3e76c9c95ff3afe596d02a4cb6fe05b45af57b60 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Mon, 26 Jan 2026 23:24:41 +0300 Subject: [PATCH] feat(telegram): redesign all sections with production-quality UI - Rewrite AnnouncementsSection with shadcn/ui Card components - Rewrite ForumSection with proper thread/reply UI - Rewrite APKSection with version history and download features - All sections now use consistent design patterns - Turkish localization for all UI text - Bottom navigation instead of sidebar - Remove obsolete sub-components (AnnouncementCard, ThreadCard, etc.) Co-Authored-By: Claude Opus 4.5 --- web/src/telegram/TelegramApp.tsx | 222 +++++-- web/src/telegram/components/APK/index.tsx | 507 +++++++++------ .../Announcements/AnnouncementCard.tsx | 154 ----- .../components/Announcements/index.tsx | 270 ++++++-- .../telegram/components/Forum/ThreadCard.tsx | 121 ---- .../telegram/components/Forum/ThreadView.tsx | 197 ------ web/src/telegram/components/Forum/index.tsx | 477 ++++++++++++-- web/src/telegram/components/Rewards/index.tsx | 564 ++++++++-------- web/src/telegram/components/Sidebar.tsx | 124 ---- web/src/telegram/components/Wallet/index.tsx | 611 +++++++++--------- 10 files changed, 1657 insertions(+), 1590 deletions(-) delete mode 100644 web/src/telegram/components/Announcements/AnnouncementCard.tsx delete mode 100644 web/src/telegram/components/Forum/ThreadCard.tsx delete mode 100644 web/src/telegram/components/Forum/ThreadView.tsx delete mode 100644 web/src/telegram/components/Sidebar.tsx diff --git a/web/src/telegram/TelegramApp.tsx b/web/src/telegram/TelegramApp.tsx index 507a05b2..45e5ee5c 100644 --- a/web/src/telegram/TelegramApp.tsx +++ b/web/src/telegram/TelegramApp.tsx @@ -1,130 +1,212 @@ import { useState, useEffect } from 'react'; import { useTelegram } from './hooks/useTelegram'; -import { usePezkuwiApi } from './hooks/usePezkuwiApi'; -import { Sidebar, Section, Announcements, Forum, Rewards, APK, Wallet } from './components'; -import { Loader2, AlertCircle, RefreshCw } from 'lucide-react'; +import { usePezkuwi } from '@/contexts/PezkuwiContext'; +import { useWallet } from '@/contexts/WalletContext'; +import { Loader2, Megaphone, MessageCircle, Gift, Smartphone, Wallet, AlertCircle, RefreshCw } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +// Sections +import { AnnouncementsSection } from './components/Announcements'; +import { ForumSection } from './components/Forum'; +import { RewardsSection } from './components/Rewards'; +import { APKSection } from './components/APK'; +import { WalletSection } from './components/Wallet'; + +export type Section = 'announcements' | 'forum' | 'rewards' | 'apk' | 'wallet'; + +interface NavItem { + id: Section; + icon: React.ReactNode; + label: string; + color: string; +} + +const navItems: NavItem[] = [ + { id: 'announcements', icon: , label: 'Duyurular', color: 'text-yellow-500' }, + { id: 'forum', icon: , label: 'Forum', color: 'text-blue-500' }, + { id: 'rewards', icon: , label: 'Rewards', color: 'text-purple-500' }, + { id: 'apk', icon: , label: 'APK', color: 'text-green-500' }, + { id: 'wallet', icon: , label: 'Wallet', color: 'text-cyan-500' }, +]; export function TelegramApp() { const { isReady: isTelegramReady, isTelegram, - user, startParam, - colorScheme, setHeaderColor, setBackgroundColor, - enableClosingConfirmation, + hapticSelection, } = useTelegram(); - const { isReady: isApiReady, error: apiError, reconnect, isConnecting } = usePezkuwiApi(); + const { api, isApiReady, error: apiError } = usePezkuwi(); + const { isConnected } = useWallet(); const [activeSection, setActiveSection] = useState
('announcements'); + const [isRetrying, setIsRetrying] = useState(false); // Handle referral from startParam useEffect(() => { if (startParam) { - // Store referral address in localStorage for later use localStorage.setItem('referrerAddress', startParam); - if (import.meta.env.DEV) { - console.log('[TelegramApp] Referral address from startParam:', startParam); - } + console.log('[TelegramApp] Referral from startParam:', startParam); } }, [startParam]); - // Setup Telegram theme colors + // Setup Telegram theme useEffect(() => { if (isTelegram) { - // Set header and background colors to match our dark theme - setHeaderColor('#111827'); // gray-900 - setBackgroundColor('#111827'); - - // Enable closing confirmation when user has unsaved changes - // (disabled for now, can be enabled per-section) - // enableClosingConfirmation(); + setHeaderColor('#030712'); // gray-950 + setBackgroundColor('#030712'); } }, [isTelegram, setHeaderColor, setBackgroundColor]); - // Render the active section content - const renderContent = () => { + const handleNavClick = (section: Section) => { + if (isTelegram) hapticSelection(); + setActiveSection(section); + }; + + const handleRetry = () => { + setIsRetrying(true); + window.location.reload(); + }; + + // Render active section + const renderSection = () => { switch (activeSection) { case 'announcements': - return ; + return ; case 'forum': - return ; + return ; case 'rewards': - return ; + return ; case 'apk': - return ; + return ; case 'wallet': - return ; + return ; default: - return ; + return ; } }; // Loading state if (!isTelegramReady) { return ( -
-
- - Loading... -
+
+ Pezkuwi { + e.currentTarget.style.display = 'none'; + }} + /> + + Pezkuwi Mini App yükleniyor...
); } - // API connection error - if (apiError && !isConnecting) { + // API Error state + if (apiError && !isApiReady) { return ( -
-
-
- -
-

Connection Error

-

- Unable to connect to Pezkuwichain network. Please check your connection and try again. -

- +
+
+
+

Bağlantı Hatası

+

+ Pezkuwichain ağına bağlanılamadı. Lütfen internet bağlantınızı kontrol edin. +

+
); } return ( -
- {/* Sidebar */} - +
+ {/* Header */} +
+
+
+ P +
+
+

Pezkuwichain

+
+
+ + {isApiReady ? 'Bağlı' : 'Bağlanıyor...'} + +
+
+
- {/* Main content */} -
- {/* API connecting indicator */} - {!isApiReady && isConnecting && ( -
- - Connecting to network... + {isConnected && ( +
+
+ Cüzdan Bağlı
)} +
- {/* Content area */} -
- {renderContent()} + {/* API connecting banner */} + {!isApiReady && ( +
+ + Blockchain ağına bağlanılıyor...
+ )} + + {/* Main content */} +
+ {renderSection()}
+ + {/* Bottom Navigation */} +
); } diff --git a/web/src/telegram/components/APK/index.tsx b/web/src/telegram/components/APK/index.tsx index 879c851c..f15eb324 100644 --- a/web/src/telegram/components/APK/index.tsx +++ b/web/src/telegram/components/APK/index.tsx @@ -1,6 +1,15 @@ -import { useState } from 'react'; -import { Smartphone, Download, Clock, CheckCircle2, AlertCircle, ExternalLink, FileText, Shield } from 'lucide-react'; +import { useState, useEffect } from 'react'; import { useTelegram } from '../../hooks/useTelegram'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + Smartphone, Download, Clock, CheckCircle2, ExternalLink, + Shield, FileText, Wifi, ChevronDown, ChevronUp, AlertCircle, + Github, Star, Package, Loader2 +} from 'lucide-react'; import { cn } from '@/lib/utils'; interface AppVersion { @@ -11,47 +20,52 @@ interface AppVersion { changelog: string[]; isLatest?: boolean; minAndroidVersion?: string; + downloads?: number; } +// Mock versions - will be replaced with GitHub API const appVersions: AppVersion[] = [ { version: '1.2.0', - releaseDate: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2), // 2 days ago + releaseDate: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2), downloadUrl: 'https://github.com/pezkuwichain/pezwallet/releases/download/v1.2.0/pezwallet-v1.2.0.apk', size: '45.2 MB', isLatest: true, minAndroidVersion: '7.0', + downloads: 1234, changelog: [ - 'New: Telegram Mini App integration', - 'New: Improved staking interface', - 'Fix: Balance refresh issues', - 'Fix: Transaction history loading', - 'Improved: Overall performance', + 'Yeni: Telegram Mini App entegrasyonu', + 'Yeni: Geliştirilmiş staking arayüzü', + 'Düzeltme: Bakiye yenileme sorunları', + 'Düzeltme: İşlem geçmişi yüklemesi', + 'İyileştirme: Genel performans', ], }, { version: '1.1.2', - releaseDate: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14), // 14 days ago + releaseDate: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14), downloadUrl: 'https://github.com/pezkuwichain/pezwallet/releases/download/v1.1.2/pezwallet-v1.1.2.apk', size: '44.8 MB', minAndroidVersion: '7.0', + downloads: 856, changelog: [ - 'Fix: Critical security update', - 'Fix: Wallet connection stability', - 'Improved: Transaction signing', + 'Düzeltme: Kritik güvenlik güncellemesi', + 'Düzeltme: Cüzdan bağlantı kararlılığı', + 'İyileştirme: İşlem imzalama', ], }, { version: '1.1.0', - releaseDate: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30), // 30 days ago + releaseDate: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30), downloadUrl: 'https://github.com/pezkuwichain/pezwallet/releases/download/v1.1.0/pezwallet-v1.1.0.apk', size: '44.5 MB', minAndroidVersion: '7.0', + downloads: 2341, changelog: [ - 'New: Multi-language support', - 'New: Dark theme improvements', - 'New: QR code scanning', - 'Fix: Various bug fixes', + 'Yeni: Çoklu dil desteği', + 'Yeni: Geliştirilmiş karanlık tema', + 'Yeni: QR kod tarama', + 'Düzeltme: Çeşitli hata düzeltmeleri', ], }, ]; @@ -59,51 +73,168 @@ const appVersions: AppVersion[] = [ const features = [ { icon: , - title: 'Secure Wallet', - description: 'Your keys, your crypto. Full self-custody.', + title: 'Güvenli Cüzdan', + description: 'Anahtarlarınız, kriptonuz. Tam self-custody.', + color: 'text-green-500', + bgColor: 'bg-green-500/20', }, { icon: , - title: 'Citizenship Management', - description: 'Apply for citizenship and manage your Tiki.', + title: 'Vatandaşlık Yönetimi', + description: 'Vatandaşlık başvurusu ve Tiki yönetimi.', + color: 'text-purple-500', + bgColor: 'bg-purple-500/20', }, { - icon: , - title: 'Offline Support', - description: 'View balances and history offline.', + icon: , + title: 'Çevrimdışı Destek', + description: 'Bakiye ve geçmişi çevrimdışı görüntüleyin.', + color: 'text-blue-500', + bgColor: 'bg-blue-500/20', }, ]; -export function APK() { - const { hapticImpact, openLink, showConfirm } = useTelegram(); - const [expandedVersion, setExpandedVersion] = useState(appVersions[0]?.version || null); - const [downloading, setDownloading] = useState(null); - +function VersionCard({ + version, + isExpanded, + onToggle, + onDownload, + isDownloading +}: { + version: AppVersion; + isExpanded: boolean; + onToggle: () => void; + onDownload: () => void; + isDownloading: boolean; +}) { const formatDate = (date: Date) => { - return date.toLocaleDateString('en-US', { + return date.toLocaleDateString('tr-TR', { year: 'numeric', month: 'short', day: 'numeric', }); }; + return ( + + + + + {/* Expanded content */} + {isExpanded && ( +
+
+

+ + Değişiklikler +

+
    + {version.changelog.map((item, idx) => ( +
  • + + {item} +
  • + ))} +
+ + {version.minAndroidVersion && ( +
+ + Android {version.minAndroidVersion} veya üstü gerekli +
+ )} + + +
+
+ )} +
+
+ ); +} + +export function APKSection() { + const { hapticImpact, openLink, showConfirm } = useTelegram(); + const [expandedVersion, setExpandedVersion] = useState(appVersions[0]?.version || null); + const [downloading, setDownloading] = useState(null); + const handleDownload = async (version: AppVersion) => { hapticImpact('medium'); const confirmed = await showConfirm( - `Download Pezwallet v${version.version} (${version.size})?` + `Pezwallet v${version.version} (${version.size}) indirilsin mi?` ); if (confirmed) { setDownloading(version.version); - - // Open download link openLink(version.downloadUrl); - - // Reset downloading state after a delay - setTimeout(() => { - setDownloading(null); - }, 3000); + setTimeout(() => setDownloading(null), 3000); } }; @@ -115,197 +246,149 @@ export function APK() { const latestVersion = appVersions.find(v => v.isLatest); return ( -
+
{/* Header */} -
-
- -

Pezwallet APK

+
+
+

+ + Pezwallet APK +

+
-
{/* Content */} -
+
{/* App Banner */} -
-
- {/* App Icon */} -
- P -
-
-

Pezwallet

-

- Official wallet app for Pezkuwichain -

- {latestVersion && ( - - )} -
-
-
- - {/* Features */} -
- {features.map((feature, index) => ( -
-
- {feature.icon} + + +
+ {/* App Icon */} +
+ P
-
-

{feature.title}

-

{feature.description}

-
-
- ))} -
- - {/* Installation Guide */} -
-
- -
-

Installation Guide

-
    -
  1. Download the APK file
  2. -
  3. Open your device's Settings
  4. -
  5. Enable "Install from unknown sources"
  6. -
  7. Open the downloaded APK file
  8. -
  9. Follow the installation prompts
  10. -
-
-
-
- - {/* Version History */} -
-
-

Version History

-
- -
- {appVersions.map((version) => ( -
- - - {/* Expanded content */} - {expandedVersion === version.version && ( -
-
-

Changelog

-
    - {version.changelog.map((item, idx) => ( -
  • - - {item} -
  • - ))} -
- - {version.minAndroidVersion && ( -
- Requires Android {version.minAndroidVersion} or higher -
- )} - - -
-
+ )}
+
+ + + + {/* Features */} + + + + + Özellikler + + + + {features.map((feature, index) => ( +
+
+ {feature.icon} +
+
+

{feature.title}

+

{feature.description}

+
+
))} -
-
+ + + + {/* Installation Guide */} + + + + Kurulum Rehberi +
    +
  1. APK dosyasını indirin
  2. +
  3. Cihaz Ayarlarını açın
  4. +
  5. "Bilinmeyen kaynaklardan yükleme"yi etkinleştirin
  6. +
  7. İndirilen APK dosyasını açın
  8. +
  9. Kurulum talimatlarını izleyin
  10. +
+
+
+ + {/* Version History */} + + + + + Sürüm Geçmişi + + + + {appVersions.map((version) => ( + setExpandedVersion( + expandedVersion === version.version ? null : version.version + )} + onDownload={() => handleDownload(version)} + isDownloading={downloading === version.version} + /> + ))} + + {/* Footer note */} -
-

Always verify downloads from official sources

-

- +

+

+ Daima resmi kaynaklardan indirdiğinizi doğrulayın

+
); } -export default APK; +export default APKSection; diff --git a/web/src/telegram/components/Announcements/AnnouncementCard.tsx b/web/src/telegram/components/Announcements/AnnouncementCard.tsx deleted file mode 100644 index bb38c53f..00000000 --- a/web/src/telegram/components/Announcements/AnnouncementCard.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { useState } from 'react'; -import { ThumbsUp, ThumbsDown, Clock, User } from 'lucide-react'; -import { cn } from '@/lib/utils'; -import { useTelegram } from '../../hooks/useTelegram'; - -export interface Announcement { - id: string; - title: string; - content: string; - author: string; - authorAvatar?: string; - createdAt: Date; - likes: number; - dislikes: number; - userReaction?: 'like' | 'dislike' | null; - isPinned?: boolean; - imageUrl?: string; -} - -interface AnnouncementCardProps { - announcement: Announcement; - onReact: (id: string, reaction: 'like' | 'dislike') => void; -} - -export function AnnouncementCard({ announcement, onReact }: AnnouncementCardProps) { - const { hapticImpact, isTelegram } = useTelegram(); - const [isExpanded, setIsExpanded] = useState(false); - - const handleReact = (reaction: 'like' | 'dislike') => { - if (isTelegram) { - hapticImpact('light'); - } - onReact(announcement.id, reaction); - }; - - const formatDate = (date: Date) => { - const now = new Date(); - const diff = now.getTime() - date.getTime(); - const hours = Math.floor(diff / (1000 * 60 * 60)); - const days = Math.floor(hours / 24); - - if (hours < 1) return 'Just now'; - if (hours < 24) return `${hours}h ago`; - if (days < 7) return `${days}d ago`; - return date.toLocaleDateString('tr-TR', { day: 'numeric', month: 'short' }); - }; - - const truncatedContent = announcement.content.length > 200 && !isExpanded - ? announcement.content.slice(0, 200) + '...' - : announcement.content; - - return ( -
- {/* Header */} -
-
- {/* Author avatar */} -
- {announcement.authorAvatar ? ( - {announcement.author} - ) : ( - - )} -
- -
-

{announcement.author}

-
- - {formatDate(announcement.createdAt)} -
-
-
- - {announcement.isPinned && ( - - Pinned - - )} -
- - {/* Title */} -

{announcement.title}

- - {/* Content */} -

- {truncatedContent} -

- - {/* Show more/less button */} - {announcement.content.length > 200 && ( - - )} - - {/* Image */} - {announcement.imageUrl && ( -
- -
- )} - - {/* Reactions */} -
- - - -
-
- ); -} diff --git a/web/src/telegram/components/Announcements/index.tsx b/web/src/telegram/components/Announcements/index.tsx index dd391615..4b16e81d 100644 --- a/web/src/telegram/components/Announcements/index.tsx +++ b/web/src/telegram/components/Announcements/index.tsx @@ -1,41 +1,208 @@ import { useState, useEffect } from 'react'; -import { Megaphone, RefreshCw } from 'lucide-react'; -import { AnnouncementCard, Announcement } from './AnnouncementCard'; import { useTelegram } from '../../hooks/useTelegram'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + Megaphone, RefreshCw, ThumbsUp, ThumbsDown, Pin, Clock, + User, ChevronDown, ChevronUp, Bell +} from 'lucide-react'; +import { cn } from '@/lib/utils'; + +export interface Announcement { + id: string; + title: string; + content: string; + author: string; + authorAvatar?: string; + createdAt: Date; + likes: number; + dislikes: number; + userReaction?: 'like' | 'dislike' | null; + isPinned?: boolean; + imageUrl?: string; +} // Mock data - will be replaced with API calls const mockAnnouncements: Announcement[] = [ { id: '1', - title: 'Pezkuwichain Mainnet Launch!', - content: 'We are excited to announce that Pezkuwichain mainnet is now live! This marks a significant milestone in our journey towards building a decentralized digital state for the Kurdish people.\n\nKey features:\n- Fast transaction finality (6 seconds)\n- Low gas fees\n- Native staking support\n- Democratic governance\n\nStart exploring at app.pezkuwichain.io', - author: 'Pezkuwi Team', - createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2), // 2 hours ago + title: 'Pezkuwichain Mainnet Yayında!', + content: 'Pezkuwichain mainnet artık aktif! Bu, Kürt halkı için merkezi olmayan bir dijital devlet inşa etme yolculuğumuzda önemli bir kilometre taşı.\n\nÖne çıkan özellikler:\n- Hızlı işlem kesinliği (6 saniye)\n- Düşük gas ücretleri\n- Yerleşik staking desteği\n- Demokratik yönetişim\n\nKeşfetmeye başlayın: app.pezkuwichain.io', + author: 'Pezkuwi Ekibi', + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2), likes: 142, dislikes: 3, isPinned: true, }, { id: '2', - title: 'New Referral Program', - content: 'Invite friends and earn rewards! For every friend who completes KYC, you will receive bonus points that contribute to your trust score.\n\nShare your referral link from the Rewards section.', - author: 'Pezkuwi Team', - createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24), // 1 day ago + title: 'Yeni Referral Programı', + content: 'Arkadaşlarınızı davet edin ve ödüller kazanın! KYC tamamlayan her arkadaşınız için trust score\'unuza katkıda bulunan bonus puanlar alacaksınız.\n\nReferans linkinizi Rewards bölümünden paylaşın.', + author: 'Pezkuwi Ekibi', + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24), likes: 89, dislikes: 5, }, { id: '3', - title: 'Staking Rewards Update', - content: 'We have updated the staking rewards mechanism. Citizens who stake HEZ will now earn PEZ tokens as rewards each epoch.\n\nMinimum stake: 10 HEZ\nEpoch duration: 7 days', - author: 'Pezkuwi Team', - createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3), // 3 days ago + title: 'Staking Ödülleri Güncellendi', + content: 'Staking ödül mekanizmasını güncelledik. HEZ stake eden vatandaşlar artık her epoch\'ta PEZ token ödülü kazanacak.\n\nMinimum stake: 10 HEZ\nEpoch süresi: 7 gün', + author: 'Pezkuwi Ekibi', + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3), likes: 67, dislikes: 2, }, ]; -export function Announcements() { +function AnnouncementCard({ + announcement, + onReact +}: { + announcement: Announcement; + onReact: (id: string, reaction: 'like' | 'dislike') => void; +}) { + const { hapticImpact } = useTelegram(); + const [isExpanded, setIsExpanded] = useState(false); + + const handleReact = (reaction: 'like' | 'dislike') => { + hapticImpact('light'); + onReact(announcement.id, reaction); + }; + + const formatDate = (date: Date) => { + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const hours = Math.floor(diff / (1000 * 60 * 60)); + const days = Math.floor(hours / 24); + + if (hours < 1) return 'Az önce'; + if (hours < 24) return `${hours} saat önce`; + if (days < 7) return `${days} gün önce`; + return date.toLocaleDateString('tr-TR', { day: 'numeric', month: 'short' }); + }; + + const shouldTruncate = announcement.content.length > 200; + const displayContent = shouldTruncate && !isExpanded + ? announcement.content.slice(0, 200) + '...' + : announcement.content; + + return ( + + + {/* Header */} +
+
+
+ {announcement.authorAvatar ? ( + {announcement.author} + ) : ( + + )} +
+
+

{announcement.author}

+
+ + {formatDate(announcement.createdAt)} +
+
+
+ {announcement.isPinned && ( + + + Sabitlendi + + )} +
+ + {/* Title */} +

{announcement.title}

+ + {/* Content */} +

+ {displayContent} +

+ + {/* Show more/less button */} + {shouldTruncate && ( + + )} + + {/* Image */} + {announcement.imageUrl && ( +
+ +
+ )} + + {/* Reactions */} +
+ + + +
+
+
+ ); +} + +export function AnnouncementsSection() { const { hapticNotification } = useTelegram(); const [announcements, setAnnouncements] = useState(mockAnnouncements); const [isLoading, setIsLoading] = useState(false); @@ -64,41 +231,68 @@ export function Announcements() { const handleRefresh = async () => { setIsLoading(true); hapticNotification('success'); - - // Simulate API call await new Promise(resolve => setTimeout(resolve, 1000)); - - // In production, this would fetch from API setAnnouncements(mockAnnouncements); setIsLoading(false); }; + // Sort: pinned first, then by date + const sortedAnnouncements = [...announcements].sort((a, b) => { + if (a.isPinned && !b.isPinned) return -1; + if (!a.isPinned && b.isPinned) return 1; + return b.createdAt.getTime() - a.createdAt.getTime(); + }); + return ( -
+
{/* Header */} -
-
- -

Duyurular

+
+
+

+ + Duyurular +

+ +
+ + {/* Stats */} +
+
+ + {announcements.length} Duyuru +
+
+ + + {announcements.filter(a => a.isPinned).length} Sabitlenmiş + +
-
- {/* Content */} -
- {announcements.length === 0 ? ( -
+ {/* Announcements List */} +
+ {isLoading ? ( + <> + {[...Array(3)].map((_, i) => ( + + ))} + + ) : sortedAnnouncements.length === 0 ? ( +
-

No announcements yet

+

Henüz duyuru yok

) : ( - announcements.map(announcement => ( + sortedAnnouncements.map(announcement => ( void; -} - -export function ThreadCard({ thread, onClick }: ThreadCardProps) { - const { hapticSelection, isTelegram } = useTelegram(); - - const handleClick = () => { - if (isTelegram) { - hapticSelection(); - } - onClick(); - }; - - const formatDate = (date: Date) => { - const now = new Date(); - const diff = now.getTime() - date.getTime(); - const hours = Math.floor(diff / (1000 * 60 * 60)); - const days = Math.floor(hours / 24); - - if (hours < 1) return 'Just now'; - if (hours < 24) return `${hours}h ago`; - if (days < 7) return `${days}d ago`; - return date.toLocaleDateString('tr-TR', { day: 'numeric', month: 'short' }); - }; - - const truncatedContent = thread.content.length > 100 - ? thread.content.slice(0, 100) + '...' - : thread.content; - - return ( - - ); -} diff --git a/web/src/telegram/components/Forum/ThreadView.tsx b/web/src/telegram/components/Forum/ThreadView.tsx deleted file mode 100644 index 1e4697a7..00000000 --- a/web/src/telegram/components/Forum/ThreadView.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { useState } from 'react'; -import { ArrowLeft, Send, User, Clock, ThumbsUp } from 'lucide-react'; -import { ForumThread } from './ThreadCard'; -import { useTelegram } from '../../hooks/useTelegram'; - -export interface ForumReply { - id: string; - content: string; - author: string; - authorAddress?: string; - createdAt: Date; - likes: number; - userLiked?: boolean; -} - -interface ThreadViewProps { - thread: ForumThread; - replies: ForumReply[]; - onBack: () => void; - onReply: (content: string) => void; - onLikeReply: (replyId: string) => void; - isConnected: boolean; -} - -export function ThreadView({ - thread, - replies, - onBack, - onReply, - onLikeReply, - isConnected -}: ThreadViewProps) { - const { hapticImpact, showBackButton, hideBackButton, isTelegram } = useTelegram(); - const [replyContent, setReplyContent] = useState(''); - const [isSubmitting, setIsSubmitting] = useState(false); - - // Setup Telegram back button - useState(() => { - if (isTelegram) { - showBackButton(onBack); - return () => hideBackButton(); - } - }); - - const formatDate = (date: Date) => { - return date.toLocaleDateString('tr-TR', { - day: 'numeric', - month: 'short', - year: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - }; - - const handleSubmitReply = async () => { - if (!replyContent.trim() || isSubmitting) return; - - setIsSubmitting(true); - if (isTelegram) { - hapticImpact('medium'); - } - - try { - await onReply(replyContent); - setReplyContent(''); - } finally { - setIsSubmitting(false); - } - }; - - return ( -
- {/* Header */} -
- -

{thread.title}

-
- - {/* Content */} -
- {/* Original post */} -
-
-
- -
-
-

{thread.author}

-
- - {formatDate(thread.createdAt)} -
-
-
- - {/* Tags */} - {thread.tags && thread.tags.length > 0 && ( -
- {thread.tags.map(tag => ( - - {tag} - - ))} -
- )} - -

- {thread.content} -

-
- - {/* Replies */} -
-

- {replies.length} {replies.length === 1 ? 'Reply' : 'Replies'} -

- -
- {replies.map(reply => ( -
-
-
-
- -
-
- {reply.author} - - {formatDate(reply.createdAt)} - -
-
- -
-

{reply.content}

-
- ))} - - {replies.length === 0 && ( -
- No replies yet. Be the first to reply! -
- )} -
-
-
- - {/* Reply input */} - {isConnected ? ( -
-
- setReplyContent(e.target.value)} - placeholder="Write a reply..." - className="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm placeholder-gray-500 focus:outline-none focus:border-green-500" - onKeyDown={(e) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - handleSubmitReply(); - } - }} - /> - -
-
- ) : ( -
- Connect wallet to reply -
- )} -
- ); -} diff --git a/web/src/telegram/components/Forum/index.tsx b/web/src/telegram/components/Forum/index.tsx index 81d17830..b5e97ddf 100644 --- a/web/src/telegram/components/Forum/index.tsx +++ b/web/src/telegram/components/Forum/index.tsx @@ -1,91 +1,405 @@ import { useState } from 'react'; -import { MessageCircle, Plus, RefreshCw, Search } from 'lucide-react'; -import { ThreadCard, ForumThread } from './ThreadCard'; -import { ThreadView, ForumReply } from './ThreadView'; import { useTelegram } from '../../hooks/useTelegram'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; +import { useWallet } from '@/contexts/WalletContext'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Input } from '@/components/ui/input'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + MessageCircle, Plus, RefreshCw, Search, Pin, Clock, User, + Eye, ChevronRight, ArrowLeft, Send, ThumbsUp, MessageSquare, Hash +} from 'lucide-react'; +import { cn } from '@/lib/utils'; -// Mock data - will be replaced with API calls +export interface ForumThread { + id: string; + title: string; + content: string; + author: string; + authorAddress?: string; + createdAt: Date; + replyCount: number; + viewCount: number; + lastReplyAt?: Date; + lastReplyAuthor?: string; + isPinned?: boolean; + tags?: string[]; +} + +export interface ForumReply { + id: string; + content: string; + author: string; + authorAddress?: string; + createdAt: Date; + likes: number; + userLiked?: boolean; +} + +// Mock data const mockThreads: ForumThread[] = [ { id: '1', - title: 'Welcome to Pezkuwi Forum!', - content: 'This is the official community forum for Pezkuwi citizens. Feel free to discuss anything related to our digital state, governance, development, and more.\n\nPlease be respectful and follow our community guidelines.', + title: 'Pezkuwi Forum\'a Hoş Geldiniz!', + content: 'Bu, Pezkuwi vatandaşları için resmi topluluk forumudur. Dijital devletimiz, yönetişim, geliştirme ve daha fazlası hakkında serbestçe tartışabilirsiniz.\n\nLütfen saygılı olun ve topluluk kurallarımıza uyun.', author: 'Admin', createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7), replyCount: 45, viewCount: 1234, isPinned: true, - tags: ['announcement', 'rules'], + tags: ['duyuru', 'kurallar'], lastReplyAt: new Date(Date.now() - 1000 * 60 * 30), - lastReplyAuthor: 'NewCitizen', + lastReplyAuthor: 'YeniVatandaş', }, { id: '2', - title: 'How to stake HEZ and earn rewards?', - content: 'Hi everyone! I just got my first HEZ tokens and want to start staking. Can someone explain the process step by step? What is the minimum amount required?', - author: 'CryptoNewbie', + title: 'HEZ nasıl stake edilir ve ödül kazanılır?', + content: 'Herkese merhaba! İlk HEZ tokenlarımı aldım ve stake etmeye başlamak istiyorum. Biri adım adım süreci açıklayabilir mi? Minimum miktar ne kadar?', + author: 'KriptoYeni', createdAt: new Date(Date.now() - 1000 * 60 * 60 * 5), replyCount: 12, viewCount: 256, - tags: ['staking', 'help'], + tags: ['staking', 'yardım'], lastReplyAt: new Date(Date.now() - 1000 * 60 * 60 * 2), lastReplyAuthor: 'StakingPro', }, { id: '3', - title: 'Proposal: Add support for Kurdish language in the app', - content: 'As a Kurdish digital state, I think it would be great to have full Kurdish language support (Kurmanci and Sorani) in all our applications.\n\nWhat do you think?', - author: 'KurdishDev', + title: 'Öneri: Uygulamaya Kürtçe dil desteği eklenmeli', + content: 'Kürt dijital devleti olarak, tüm uygulamalarımızda tam Kürtçe dil desteği (Kurmancî ve Soranî) olması gerektiğini düşünüyorum.\n\nNe düşünüyorsunuz?', + author: 'KürtGeliştirici', createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2), replyCount: 28, viewCount: 567, - tags: ['proposal', 'localization'], + tags: ['öneri', 'yerelleştirme'], lastReplyAt: new Date(Date.now() - 1000 * 60 * 60 * 4), - lastReplyAuthor: 'LanguageExpert', + lastReplyAuthor: 'DilUzmanı', }, { id: '4', - title: 'Bug report: Wallet balance not updating', - content: 'After making a transfer, my wallet balance doesn\'t update immediately. I have to refresh the page multiple times. Is anyone else experiencing this issue?', - author: 'TechUser', + title: 'Hata: Cüzdan bakiyesi güncellenmiyor', + content: 'Transfer yaptıktan sonra cüzdan bakiyem hemen güncellenmiyor. Sayfayı birkaç kez yenilemem gerekiyor. Bu sorunu yaşayan başka var mı?', + author: 'TeknikKullanıcı', createdAt: new Date(Date.now() - 1000 * 60 * 60 * 12), replyCount: 8, viewCount: 89, - tags: ['bug', 'wallet'], + tags: ['hata', 'cüzdan'], lastReplyAt: new Date(Date.now() - 1000 * 60 * 60 * 6), - lastReplyAuthor: 'DevTeam', + lastReplyAuthor: 'GeliştiriciEkibi', }, ]; const mockReplies: ForumReply[] = [ { id: '1', - content: 'Great to be here! Looking forward to participating in the community.', - author: 'NewCitizen', + content: 'Burada olmak harika! Topluluğa katılmak için sabırsızlanıyorum.', + author: 'YeniVatandaş', createdAt: new Date(Date.now() - 1000 * 60 * 30), likes: 5, }, { id: '2', - content: 'Welcome! Make sure to check out the staking guide in the docs section.', - author: 'Helper', + content: 'Hoş geldiniz! Dokümantasyon bölümündeki staking rehberini kontrol etmeyi unutmayın.', + author: 'Yardımcı', createdAt: new Date(Date.now() - 1000 * 60 * 60), likes: 12, }, ]; -export function Forum() { +function ThreadCard({ thread, onClick }: { thread: ForumThread; onClick: () => void }) { + const { hapticSelection } = useTelegram(); + + const handleClick = () => { + hapticSelection(); + onClick(); + }; + + const formatDate = (date: Date) => { + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const hours = Math.floor(diff / (1000 * 60 * 60)); + const days = Math.floor(hours / 24); + + if (hours < 1) return 'Az önce'; + if (hours < 24) return `${hours}s önce`; + if (days < 7) return `${days}g önce`; + return date.toLocaleDateString('tr-TR', { day: 'numeric', month: 'short' }); + }; + + return ( + + +
+
+ {/* Title with pin badge */} +
+ {thread.isPinned && ( + + + Sabit + + )} +

{thread.title}

+
+ + {/* Preview */} +

+ {thread.content.length > 100 ? thread.content.slice(0, 100) + '...' : thread.content} +

+ + {/* Tags */} + {thread.tags && thread.tags.length > 0 && ( +
+ {thread.tags.slice(0, 3).map(tag => ( +
+ + {tag} +
+ ))} +
+ )} + + {/* Meta info */} +
+
+ + {thread.author} +
+
+ + {formatDate(thread.createdAt)} +
+
+ + {thread.replyCount} +
+
+ + {thread.viewCount} +
+
+
+ + +
+ + {/* Last reply info */} + {thread.lastReplyAt && thread.lastReplyAuthor && ( +
+ Son yanıt: {thread.lastReplyAuthor}{' '} + · {formatDate(thread.lastReplyAt)} +
+ )} +
+
+ ); +} + +function ThreadView({ + thread, + replies, + onBack, + onReply, + onLikeReply, + isConnected +}: { + thread: ForumThread; + replies: ForumReply[]; + onBack: () => void; + onReply: (content: string) => void; + onLikeReply: (replyId: string) => void; + isConnected: boolean; +}) { + const { hapticImpact } = useTelegram(); + const [replyContent, setReplyContent] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + const formatDate = (date: Date) => { + return date.toLocaleDateString('tr-TR', { + day: 'numeric', + month: 'short', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + }; + + const handleSubmitReply = async () => { + if (!replyContent.trim() || isSubmitting) return; + + setIsSubmitting(true); + hapticImpact('medium'); + + try { + await onReply(replyContent); + setReplyContent(''); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+ {/* Header */} +
+ +

{thread.title}

+
+ + {/* Content */} +
+ {/* Original post */} +
+ + +
+
+ +
+
+

{thread.author}

+
+ + {formatDate(thread.createdAt)} +
+
+
+ + {/* Tags */} + {thread.tags && thread.tags.length > 0 && ( +
+ {thread.tags.map(tag => ( + + + {tag} + + ))} +
+ )} + +

+ {thread.content} +

+
+
+
+ + {/* Replies */} +
+
+ + + {replies.length} Yanıt + +
+ +
+ {replies.map(reply => ( + + +
+
+
+ +
+
+ {reply.author} + + {formatDate(reply.createdAt)} + +
+
+ +
+

{reply.content}

+
+
+ ))} + + {replies.length === 0 && ( +
+ +

Henüz yanıt yok. İlk yanıtı siz yazın!

+
+ )} +
+
+
+ + {/* Reply input */} + {isConnected ? ( +
+
+ setReplyContent(e.target.value)} + placeholder="Yanıtınızı yazın..." + className="flex-1 bg-gray-800 border-gray-700 text-white placeholder:text-gray-500" + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSubmitReply(); + } + }} + /> + +
+
+ ) : ( +
+ Yanıt yazmak için cüzdanınızı bağlayın +
+ )} +
+ ); +} + +export function ForumSection() { const { hapticNotification, showAlert } = useTelegram(); const { selectedAccount } = usePezkuwi(); + const { isConnected } = useWallet(); + const [threads, setThreads] = useState(mockThreads); const [selectedThread, setSelectedThread] = useState(null); const [replies, setReplies] = useState(mockReplies); const [isLoading, setIsLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(''); - const isConnected = !!selectedAccount; - const handleRefresh = async () => { setIsLoading(true); hapticNotification('success'); @@ -96,11 +410,10 @@ export function Forum() { const handleCreateThread = () => { if (!isConnected) { - showAlert('Please connect your wallet to create a thread'); + showAlert('Konu oluşturmak için cüzdanınızı bağlayın'); return; } - // TODO: Implement thread creation modal - showAlert('Thread creation coming soon!'); + showAlert('Konu oluşturma özelliği yakında!'); }; const handleReply = async (content: string) => { @@ -109,7 +422,7 @@ export function Forum() { const newReply: ForumReply = { id: String(Date.now()), content, - author: selectedAccount?.meta?.name || 'Anonymous', + author: selectedAccount?.meta?.name || 'Anonim', createdAt: new Date(), likes: 0, }; @@ -155,57 +468,81 @@ export function Forum() { } return ( -
+
{/* Header */} -
-
- -

Forum

+
+
+

+ + Forum +

+
+ + +
-
- - -
-
- {/* Search */} -
-
+ {/* Search */} +
- setSearchQuery(e.target.value)} - placeholder="Search threads..." - className="w-full bg-gray-800 border border-gray-700 rounded-lg pl-9 pr-3 py-2 text-white text-sm placeholder-gray-500 focus:outline-none focus:border-green-500" + placeholder="Konularda ara..." + className="pl-9 bg-gray-900 border-gray-800 text-white placeholder:text-gray-500" />
+ + {/* Stats */} +
+
+ + {threads.length} Konu +
+
+ + + {threads.filter(t => t.isPinned).length} Sabitlenmiş + +
+
- {/* Content */} -
- {sortedThreads.length === 0 ? ( -
+ {/* Thread List */} +
+ {isLoading ? ( + <> + {[...Array(4)].map((_, i) => ( + + ))} + + ) : sortedThreads.length === 0 ? ( +
-

{searchQuery ? 'No threads found' : 'No threads yet'}

+

{searchQuery ? 'Konu bulunamadı' : 'Henüz konu yok'}

{!searchQuery && ( - + İlk konuyu oluştur + )}
) : ( @@ -222,4 +559,4 @@ export function Forum() { ); } -export default Forum; +export default ForumSection; diff --git a/web/src/telegram/components/Rewards/index.tsx b/web/src/telegram/components/Rewards/index.tsx index 7f79b67d..0ac15934 100644 --- a/web/src/telegram/components/Rewards/index.tsx +++ b/web/src/telegram/components/Rewards/index.tsx @@ -1,82 +1,42 @@ import { useState, useEffect } from 'react'; -import { Gift, Users, Trophy, Calendar, Copy, Check, Share2, Loader2, Star } from 'lucide-react'; -import { useTelegram } from '../../hooks/useTelegram'; -import { usePezkuwiApi } from '../../hooks/usePezkuwiApi'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; -import { getReferralStats, calculateReferralScore, ReferralStats } from '@shared/lib/referral'; -import { getAllScores, UserScores, getScoreRating, getScoreColor } from '@shared/lib/scores'; +import { useReferral } from '@/contexts/ReferralContext'; +import { useWallet } from '@/contexts/WalletContext'; +import { useTelegram } from '../../hooks/useTelegram'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { getReferralStats, ReferralStats, getMyReferrals, calculateReferralScore } from '@shared/lib/referral'; import { getStakingInfo, StakingInfo } from '@shared/lib/staking'; +import { + Gift, Users, Trophy, Copy, Check, Share2, Loader2, RefreshCw, + UserPlus, Award, Star, Calendar, Zap, ChevronRight, Clock +} from 'lucide-react'; import { cn } from '@/lib/utils'; -interface DailyTask { - id: string; - title: string; - description: string; - reward: number; - completed: boolean; - progress?: number; - maxProgress?: number; -} +export function RewardsSection() { + const { api, isApiReady, selectedAccount } = usePezkuwi(); + const { stats, myReferrals, loading: referralLoading, refreshStats } = useReferral(); + const { isConnected } = useWallet(); + const { hapticNotification, hapticImpact, showAlert, openTelegramLink } = useTelegram(); -const dailyTasks: DailyTask[] = [ - { - id: 'login', - title: 'Daily Login', - description: 'Open the app daily', - reward: 5, - completed: true, - }, - { - id: 'forum', - title: 'Forum Activity', - description: 'Post or reply in forum', - reward: 10, - completed: false, - }, - { - id: 'referral', - title: 'Invite a Friend', - description: 'Invite a new user to join', - reward: 50, - completed: false, - }, -]; - -export function Rewards() { - const { hapticNotification, hapticImpact, showAlert, user, openTelegramLink } = useTelegram(); - const { api, isReady: isApiReady } = usePezkuwiApi(); - const { selectedAccount } = usePezkuwi(); - - const [referralStats, setReferralStats] = useState(null); - const [userScores, setUserScores] = useState(null); const [stakingInfo, setStakingInfo] = useState(null); const [isLoading, setIsLoading] = useState(false); const [copied, setCopied] = useState(false); - const [claimingEpoch, setClaimingEpoch] = useState(null); - const isConnected = !!selectedAccount; const address = selectedAccount?.address; + const referralLink = address ? `https://t.me/pezkuwichain_bot?start=${address}` : ''; - // Generate referral link - const referralLink = address - ? `https://t.me/pezkuwichain?start=${address}` - : ''; - - // Fetch data when connected + // Fetch staking data useEffect(() => { if (!api || !isApiReady || !address) return; const fetchData = async () => { setIsLoading(true); try { - const [stats, scores, staking] = await Promise.all([ - getReferralStats(api, address), - getAllScores(api, address), - getStakingInfo(api, address), - ]); - - setReferralStats(stats); - setUserScores(scores); + const staking = await getStakingInfo(api, address); setStakingInfo(staking); } catch (err) { console.error('Failed to fetch rewards data:', err); @@ -90,267 +50,301 @@ export function Rewards() { const handleCopyLink = async () => { if (!referralLink) return; - try { await navigator.clipboard.writeText(referralLink); setCopied(true); hapticNotification('success'); setTimeout(() => setCopied(false), 2000); } catch { - showAlert('Failed to copy link'); + showAlert('Link kopyalanamadı'); } }; const handleShare = () => { if (!referralLink) return; - hapticImpact('medium'); - const text = `Join Pezkuwichain - The Digital Kurdish State! Use my referral link:`; - const shareUrl = `https://t.me/share/url?url=${encodeURIComponent(referralLink)}&text=${encodeURIComponent(text)}`; - openTelegramLink(shareUrl); + const text = encodeURIComponent('Pezkuwichain - Kürt Dijital Devleti! Referans linkimle katıl:'); + openTelegramLink(`https://t.me/share/url?url=${encodeURIComponent(referralLink)}&text=${text}`); }; - const handleClaimEpoch = async (epoch: number) => { - if (!api || !selectedAccount) return; - - setClaimingEpoch(epoch); - hapticImpact('medium'); - - try { - // TODO: Implement actual claim transaction - await new Promise(resolve => setTimeout(resolve, 2000)); - hapticNotification('success'); - showAlert(`Successfully claimed rewards for epoch ${epoch}!`); - } catch (err) { - hapticNotification('error'); - showAlert('Failed to claim rewards'); - } finally { - setClaimingEpoch(null); - } + const handleRefresh = async () => { + hapticNotification('success'); + await refreshStats(); }; - if (!isConnected) { + // Not connected state + if (!isConnected || !selectedAccount) { return ( -
-
- -

Rewards

-
-
- -

Connect your wallet to view rewards and referrals

+
+
+
+ +
+

Ödüller ve Referanslar

+

+ Referans programına katılmak ve ödüllerinizi görmek için cüzdanınızı bağlayın. +

+
+
+ + Arkadaş Davet +
+
+ + Puan Kazan +
+
+ + PEZ Ödülü +
+
); } return ( -
- {/* Header */} -
- -

Rewards

+
+ {/* Header Stats */} +
+
+

+ + Ödüller +

+ +
+ + {/* Stats Cards */} +
+ + +
+ +
+ {referralLoading ? ( + + ) : ( +

{stats?.referralCount || 0}

+ )} +

Referans

+
+
+ + + +
+ +
+ {referralLoading ? ( + + ) : ( +

{stats?.referralScore || 0}

+ )} +

Puan

+
+
+ + + +
+ +
+ {isLoading ? ( + + ) : ( +

{stakingInfo?.stakingScore || 0}

+ )} +

Staking

+
+
+
- {/* Content */} -
- {isLoading ? ( -
- -
- ) : ( - <> - {/* Score Overview */} - {userScores && ( -
-
-
- - Trust Score -
- - {getScoreRating(userScores.totalScore)} - -
-
- {userScores.totalScore} -
-
-
- Staking:{' '} - {userScores.stakingScore} -
-
- Referral:{' '} - {userScores.referralScore} -
-
- Tiki:{' '} - {userScores.tikiScore} -
-
- Trust:{' '} - {userScores.trustScore} -
-
+ {/* Referral Invite Section */} +
+ + +
+
+
- )} - - {/* Referral Section */} -
-
- - Referral Program +
+

Arkadaşını Davet Et

+

Her referans için puan kazan!

- - {referralStats && ( -
-
-
- {referralStats.referralCount} -
-
Referrals
-
-
-
- {referralStats.referralScore} -
-
Score
-
-
- )} - - {/* Referral link */} -
-
Your referral link
-
- - {referralLink} - - -
-
- - - - {/* Who invited me */} - {referralStats?.whoInvitedMe && ( -
- Invited by:{' '} - - {referralStats.whoInvitedMe.slice(0, 8)}...{referralStats.whoInvitedMe.slice(-6)} - -
- )}
- {/* Epoch Rewards */} - {stakingInfo?.pezRewards && stakingInfo.pezRewards.hasPendingClaim && ( -
-
- - Epoch Rewards -
- -
-
- Current Epoch - - #{stakingInfo.pezRewards.currentEpoch} - -
-
- Claimable - - {stakingInfo.pezRewards.totalClaimable} PEZ - -
-
- -
- {stakingInfo.pezRewards.claimableRewards.map(reward => ( -
-
-
Epoch #{reward.epoch}
-
{reward.amount} PEZ
-
- -
- ))} -
+ {/* Referral Link */} +
+

Referans Linkin

+
+ {referralLink} +
- )} +
- {/* Daily Tasks */} -
-
- - Daily Tasks + + + +
+ + {/* Score System Info */} +
+ + + + + Puan Sistemi + + + +
+
+ 1-10 referans +
+ ×10 puan +
+
+
+ 11-50 referans +
+ 100 + ×5 puan +
+
+
+ 51-100 referans +
+ 300 + ×4 puan +
+
+
+ 101+ referans +
+ 500 (Max) +
+
+
+
+ + {/* Epoch Rewards */} + {stakingInfo?.pezRewards?.hasPendingClaim && ( +
+ + +
+
+ + Epoch Ödülleri +
+ + Epoch #{stakingInfo.pezRewards.currentEpoch} +
-
- {dailyTasks.map(task => ( -
-
-
- - {task.title} - - {task.completed && ( - - )} -
-
{task.description}
-
-
- +{task.reward} pts -
+
+

Bekleyen PEZ

+

+ {stakingInfo.pezRewards.totalClaimable} PEZ +

+
+ +
+ {stakingInfo.pezRewards.claimableRewards.map((reward) => ( +
+ Epoch #{reward.epoch} + {reward.amount} PEZ
))}
-
- - )} -
+ + +
+
+
+ )} + + {/* My Referrals List */} + {myReferrals && myReferrals.length > 0 && ( +
+ + + + + Referanslarım ({myReferrals.length}) + + + + {myReferrals.slice(0, 5).map((referral, index) => ( +
+
+
+ {index + 1} +
+ + {referral.slice(0, 6)}...{referral.slice(-4)} + +
+ + KYC Onaylı + +
+ ))} + {myReferrals.length > 5 && ( +

+ +{myReferrals.length - 5} daha fazla +

+ )} +
+
+
+ )} + + {/* Who invited me */} + {stats?.whoInvitedMe && ( +
+ + + + Davet eden: + + {stats.whoInvitedMe.slice(0, 8)}...{stats.whoInvitedMe.slice(-6)} + + + +
+ )}
); } -export default Rewards; +export default RewardsSection; diff --git a/web/src/telegram/components/Sidebar.tsx b/web/src/telegram/components/Sidebar.tsx deleted file mode 100644 index efb89737..00000000 --- a/web/src/telegram/components/Sidebar.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { cn } from '@/lib/utils'; -import { useTelegram } from '../hooks/useTelegram'; -import { - Megaphone, - MessageCircle, - Gift, - Smartphone, - Wallet -} from 'lucide-react'; - -export type Section = 'announcements' | 'forum' | 'rewards' | 'apk' | 'wallet'; - -interface SidebarItem { - id: Section; - icon: React.ReactNode; - label: string; - emoji: string; -} - -const sidebarItems: SidebarItem[] = [ - { - id: 'announcements', - icon: , - label: 'Duyurular', - emoji: '📢' - }, - { - id: 'forum', - icon: , - label: 'Forum', - emoji: '💬' - }, - { - id: 'rewards', - icon: , - label: 'Rewards', - emoji: '🎁' - }, - { - id: 'apk', - icon: , - label: 'APK', - emoji: '📱' - }, - { - id: 'wallet', - icon: , - label: 'Wallet', - emoji: '💳' - }, -]; - -interface SidebarProps { - activeSection: Section; - onSectionChange: (section: Section) => void; -} - -export function Sidebar({ activeSection, onSectionChange }: SidebarProps) { - const { hapticSelection, isTelegram } = useTelegram(); - - const handleClick = (section: Section) => { - if (isTelegram) { - hapticSelection(); - } - onSectionChange(section); - }; - - return ( -
- {/* Logo at top */} -
-
- P -
-
- - {/* Divider */} -
- - {/* Navigation items */} - - - {/* Bottom section - could add settings or user avatar here */} -
-
- v1 -
-
-
- ); -} - -export default Sidebar; diff --git a/web/src/telegram/components/Wallet/index.tsx b/web/src/telegram/components/Wallet/index.tsx index 61e860f5..983cee4a 100644 --- a/web/src/telegram/components/Wallet/index.tsx +++ b/web/src/telegram/components/Wallet/index.tsx @@ -1,98 +1,45 @@ import { useState, useEffect } from 'react'; -import { Wallet as WalletIcon, Send, ArrowDownToLine, RefreshCw, Copy, Check, Loader2, TrendingUp, Clock, ExternalLink } from 'lucide-react'; -import { useTelegram } from '../../hooks/useTelegram'; -import { usePezkuwiApi } from '../../hooks/usePezkuwiApi'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; -import { formatBalance, CHAIN_CONFIG, formatAddress } from '@shared/lib/wallet'; +import { useWallet } from '@/contexts/WalletContext'; +import { useTelegram } from '../../hooks/useTelegram'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Skeleton } from '@/components/ui/skeleton'; +import { getAllScores, UserScores, getScoreRating } from '@shared/lib/scores'; import { getStakingInfo, StakingInfo } from '@shared/lib/staking'; +import { formatBalance, formatAddress, CHAIN_CONFIG } from '@shared/lib/wallet'; +import { + Wallet, Send, ArrowDownToLine, TrendingUp, Copy, Check, ExternalLink, + Loader2, RefreshCw, Trophy, Users, Star, Award, Coins, Clock, Zap +} from 'lucide-react'; import { cn } from '@/lib/utils'; -interface TokenBalance { - symbol: string; - name: string; - balance: string; - balanceUsd?: string; - icon?: string; - isNative?: boolean; -} - -interface Transaction { - id: string; - type: 'send' | 'receive' | 'stake' | 'unstake' | 'claim'; - amount: string; - symbol: string; - address?: string; - timestamp: Date; - status: 'pending' | 'confirmed' | 'failed'; -} - -// Mock recent transactions - will be replaced with actual data -const mockTransactions: Transaction[] = [ - { - id: '1', - type: 'receive', - amount: '100.00', - symbol: 'HEZ', - address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', - timestamp: new Date(Date.now() - 1000 * 60 * 60 * 2), - status: 'confirmed', - }, - { - id: '2', - type: 'stake', - amount: '50.00', - symbol: 'HEZ', - timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24), - status: 'confirmed', - }, - { - id: '3', - type: 'claim', - amount: '5.25', - symbol: 'PEZ', - timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3), - status: 'confirmed', - }, -]; - -export function Wallet() { +export function WalletSection() { + const { api, isApiReady, selectedAccount, connectWallet, disconnectWallet, accounts } = usePezkuwi(); + const { balances, refreshBalances, isConnected } = useWallet(); const { hapticNotification, hapticImpact, showAlert, openLink } = useTelegram(); - const { api, isReady: isApiReady } = usePezkuwiApi(); - const { selectedAccount, connectWallet, disconnectWallet } = usePezkuwi(); - const [balances, setBalances] = useState([]); + const [scores, setScores] = useState(null); const [stakingInfo, setStakingInfo] = useState(null); - const [transactions, setTransactions] = useState(mockTransactions); const [isLoading, setIsLoading] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [copied, setCopied] = useState(false); - const isConnected = !!selectedAccount; const address = selectedAccount?.address; - // Fetch balances when connected + // Fetch data when connected useEffect(() => { if (!api || !isApiReady || !address) return; const fetchData = async () => { setIsLoading(true); try { - // Fetch native balance - const accountInfo = await api.query.system.account(address); - const { data } = accountInfo.toJSON() as { data: { free: string; reserved: string } }; - const freeBalance = BigInt(data.free || 0); - - const nativeBalance: TokenBalance = { - symbol: CHAIN_CONFIG.symbol, - name: 'Hezar Token', - balance: formatBalance(freeBalance.toString()), - isNative: true, - }; - - setBalances([nativeBalance]); - - // Fetch staking info - const staking = await getStakingInfo(api, address); + const [userScores, staking] = await Promise.all([ + getAllScores(api, address), + getStakingInfo(api, address), + ]); + setScores(userScores); setStakingInfo(staking); } catch (err) { console.error('Failed to fetch wallet data:', err); @@ -106,26 +53,19 @@ export function Wallet() { const handleRefresh = async () => { if (!api || !address || isRefreshing) return; - setIsRefreshing(true); hapticNotification('success'); try { - const accountInfo = await api.query.system.account(address); - const { data } = accountInfo.toJSON() as { data: { free: string } }; - const freeBalance = BigInt(data.free || 0); - - setBalances([{ - symbol: CHAIN_CONFIG.symbol, - name: 'Hezar Token', - balance: formatBalance(freeBalance.toString()), - isNative: true, - }]); - - const staking = await getStakingInfo(api, address); + await refreshBalances(); + const [userScores, staking] = await Promise.all([ + getAllScores(api, address), + getStakingInfo(api, address), + ]); + setScores(userScores); setStakingInfo(staking); } catch (err) { - showAlert('Failed to refresh'); + showAlert('Yenileme başarısız'); } finally { setIsRefreshing(false); } @@ -133,14 +73,13 @@ export function Wallet() { const handleCopyAddress = async () => { if (!address) return; - try { await navigator.clipboard.writeText(address); setCopied(true); hapticNotification('success'); setTimeout(() => setCopied(false), 2000); } catch { - showAlert('Failed to copy address'); + showAlert('Adres kopyalanamadı'); } }; @@ -149,273 +88,307 @@ export function Wallet() { connectWallet(); }; - const handleDisconnect = () => { - hapticImpact('medium'); - disconnectWallet(); - }; - const handleOpenExplorer = () => { if (!address) return; hapticImpact('light'); openLink(`https://explorer.pezkuwichain.io/account/${address}`); }; - const getTransactionIcon = (type: Transaction['type']) => { - switch (type) { - case 'send': - return ; - case 'receive': - return ; - case 'stake': - return ; - case 'unstake': - return ; - case 'claim': - return ; - default: - return null; - } - }; - - const formatTimestamp = (date: Date) => { - const now = new Date(); - const diff = now.getTime() - date.getTime(); - const hours = Math.floor(diff / (1000 * 60 * 60)); - const days = Math.floor(hours / 24); - - if (hours < 1) return 'Just now'; - if (hours < 24) return `${hours}h ago`; - if (days < 7) return `${days}d ago`; - return date.toLocaleDateString('tr-TR', { day: 'numeric', month: 'short' }); - }; - - if (!isConnected) { + // Not connected state + if (!isConnected || !selectedAccount) { return ( -
-
- -

Wallet

-
+
-
- +
+
-

Connect Your Wallet

-

- Connect your Pezkuwi wallet to view balances, stake tokens, and manage your assets. +

Cüzdanınızı Bağlayın

+

+ Bakiyelerinizi görüntülemek, stake etmek ve işlem yapmak için Pezkuwi cüzdanınızı bağlayın.

- + + Cüzdan Bağla + + +
+
+ + HEZ & PEZ +
+
+ + Staking +
+
+ + Rewards +
+
); } return ( -
- {/* Header */} -
-
- -

Wallet

-
-
- - -
-
- - {/* Content */} -
- {isLoading ? ( -
- -
- ) : ( - <> - {/* Address Card */} -
-
-
- - {selectedAccount?.meta?.name || 'Account'} +
+ {/* Account Card */} +
+ + +
+
+
+ + {selectedAccount?.meta?.name?.charAt(0) || 'P'} -
-
- - {formatAddress(address || '')} - - +
+

+ {selectedAccount?.meta?.name || 'Pezkuwi Hesabı'} +

+
+ + {formatAddress(address || '')} + + +
+
+ + +
- {/* Balance Card */} -
-
-
Total Balance
-
- {balances[0]?.balance || '0.00'} {CHAIN_CONFIG.symbol} + {/* Balance Display */} +
+

Toplam Bakiye

+ {isLoading ? ( + + ) : ( +
+ + {balances?.HEZ || '0.00'} + + {CHAIN_CONFIG.symbol}
- {stakingInfo && parseFloat(stakingInfo.bonded) > 0 && ( -
- Staked: {stakingInfo.bonded} {CHAIN_CONFIG.symbol} -
- )} -
+ )} + {balances?.PEZ && parseFloat(balances.PEZ) > 0 && ( +

+ + {balances.PEZ} PEZ +

+ )}
{/* Quick Actions */} -
-
- - - -
+
+ + +
+ + +
- {/* Staking Info */} - {stakingInfo && parseFloat(stakingInfo.bonded) > 0 && ( -
-
-
- - Staking Overview -
-
-
-
Bonded
-
{stakingInfo.bonded}
-
-
-
Active
-
{stakingInfo.active}
-
- {stakingInfo.stakingScore !== null && ( -
-
Staking Score
-
{stakingInfo.stakingScore}
-
- )} -
-
Nominations
-
{stakingInfo.nominations.length}
-
-
+ {/* Scores Section */} +
+

+ + Puanlarınız +

+ + {isLoading ? ( +
+ {[...Array(4)].map((_, i) => ( + + ))} +
+ ) : scores ? ( + <> + {/* Total Score Banner */} + + +
+

Toplam Skor

+

{scores.totalScore}

-
- )} + + {getScoreRating(scores.totalScore)} + + + - {/* Recent Transactions */} -
-
-
-

Recent Activity

-
+ {/* Individual Scores */} +
+ + +
+
+ +
+ Trust +
+

{scores.trustScore}

+
+
- {transactions.length === 0 ? ( -
- No recent transactions + + +
+
+ +
+ Referral
- ) : ( -
- {transactions.map(tx => ( -
-
-
- {getTransactionIcon(tx.type)} -
-
-
- {tx.type} -
-
- {formatTimestamp(tx.timestamp)} -
-
-
-
-
- {tx.type === 'send' || tx.type === 'stake' ? '-' : '+'} - {tx.amount} {tx.symbol} -
-
- {tx.status} -
-
-
- ))} +

{scores.referralScore}

+ + + + + +
+
+ +
+ Staking
- )} -
+

{scores.stakingScore}

+
+
+ + + +
+
+ +
+ Tiki +
+

{scores.tikiScore}

+
+
- )} + ) : null} +
+ + {/* Staking Info */} + {stakingInfo && parseFloat(stakingInfo.bonded) > 0 && ( +
+

+ + Staking Durumu +

+ + + +
+
+

Stake Edilmiş

+

{stakingInfo.bonded} HEZ

+
+
+

Aktif

+

{stakingInfo.active} HEZ

+
+ {stakingInfo.stakingScore !== null && ( +
+

Staking Skoru

+

{stakingInfo.stakingScore}/100

+
+ )} +
+

Nominasyonlar

+

{stakingInfo.nominations.length}

+
+
+ + {/* PEZ Rewards */} + {stakingInfo.pezRewards?.hasPendingClaim && ( +
+
+
+ + Bekleyen PEZ +
+ + {stakingInfo.pezRewards.totalClaimable} PEZ + +
+ +
+ )} +
+
+
+ )} + + {/* Disconnect Button */} +
+
); } -export default Wallet; +export default WalletSection;