Layout mostly complete. Keep working.
@ -34,7 +34,7 @@ class UserStatsResponse(BaseModel):
|
||||
tier_name: str
|
||||
house_fee: float
|
||||
xp_to_next_tier: int
|
||||
tier_progress: float
|
||||
tier_progress_percent: float
|
||||
total_wagered: float
|
||||
total_won: float
|
||||
net_profit: float
|
||||
@ -418,7 +418,7 @@ async def get_my_stats(
|
||||
tier_name=TIER_CONFIG[stats.tier][2],
|
||||
house_fee=TIER_CONFIG[stats.tier][1],
|
||||
xp_to_next_tier=stats.xp_to_next_tier(),
|
||||
tier_progress=stats.tier_progress_percent(),
|
||||
tier_progress_percent=stats.tier_progress_percent(),
|
||||
total_wagered=stats.total_wagered,
|
||||
total_won=stats.total_won,
|
||||
net_profit=stats.net_profit,
|
||||
|
||||
BIN
binance-more.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
binance.png
|
Before Width: | Height: | Size: 309 KiB |
BIN
coinex-header.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
@ -23,10 +23,17 @@ import {
|
||||
RewardsIndex,
|
||||
LeaderboardPage,
|
||||
AchievementsPage,
|
||||
LootBoxesPage,
|
||||
AirdropsPage,
|
||||
ActivityPage,
|
||||
} from './pages/rewards'
|
||||
|
||||
// New pages for More dropdown
|
||||
import VIP from './pages/VIP'
|
||||
import Affiliate from './pages/Affiliate'
|
||||
import Referral from './pages/Referral'
|
||||
import Launchpool from './pages/Launchpool'
|
||||
import Megadrop from './pages/Megadrop'
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
@ -69,12 +76,22 @@ function App() {
|
||||
<Route path="/watchlist" element={<Watchlist />} />
|
||||
<Route path="/how-it-works" element={<HowItWorks />} />
|
||||
<Route path="/events/:id" element={<EventDetail />} />
|
||||
|
||||
{/* Rewards Routes */}
|
||||
<Route path="/rewards" element={<RewardsIndex />} />
|
||||
<Route path="/rewards/leaderboard" element={<LeaderboardPage />} />
|
||||
<Route path="/rewards/achievements" element={<AchievementsPage />} />
|
||||
<Route path="/rewards/loot-boxes" element={<LootBoxesPage />} />
|
||||
<Route path="/rewards/airdrops" element={<AirdropsPage />} />
|
||||
<Route path="/rewards/loot-boxes" element={<Navigate to="/rewards/airdrops" replace />} />
|
||||
<Route path="/rewards/activity" element={<ActivityPage />} />
|
||||
|
||||
{/* More Dropdown Routes */}
|
||||
<Route path="/vip" element={<VIP />} />
|
||||
<Route path="/affiliate" element={<Affiliate />} />
|
||||
<Route path="/referral" element={<Referral />} />
|
||||
<Route path="/launchpool" element={<Launchpool />} />
|
||||
<Route path="/megadrop" element={<Megadrop />} />
|
||||
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
|
||||
174
frontend/src/components/gamification/Airdrops.tsx
Normal file
@ -0,0 +1,174 @@
|
||||
import { useState } from 'react'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { getMyLootBoxes, openLootBox } from '../../api/gamification'
|
||||
import type { LootBox, LootBoxRarity, LootBoxOpenResult } from '../../types/gamification'
|
||||
|
||||
const RARITY_CONFIG: Record<LootBoxRarity, { color: string; bg: string; border: string; icon: string }> = {
|
||||
common: {
|
||||
color: 'text-gray-600',
|
||||
bg: 'bg-gray-100',
|
||||
border: 'border-gray-300',
|
||||
icon: '📦',
|
||||
},
|
||||
uncommon: {
|
||||
color: 'text-green-600',
|
||||
bg: 'bg-green-50',
|
||||
border: 'border-green-300',
|
||||
icon: '🎁',
|
||||
},
|
||||
rare: {
|
||||
color: 'text-blue-600',
|
||||
bg: 'bg-blue-50',
|
||||
border: 'border-blue-300',
|
||||
icon: '💎',
|
||||
},
|
||||
epic: {
|
||||
color: 'text-purple-600',
|
||||
bg: 'bg-purple-50',
|
||||
border: 'border-purple-300',
|
||||
icon: '👑',
|
||||
},
|
||||
legendary: {
|
||||
color: 'text-yellow-600',
|
||||
bg: 'bg-yellow-50',
|
||||
border: 'border-yellow-400',
|
||||
icon: '🌟',
|
||||
},
|
||||
}
|
||||
|
||||
const REWARD_ICONS: Record<string, string> = {
|
||||
bonus_cash: '💰',
|
||||
xp_boost: '⚡',
|
||||
fee_reduction: '📉',
|
||||
free_bet: '🎟️',
|
||||
avatar_frame: '🖼️',
|
||||
badge: '🏅',
|
||||
nothing: '💨',
|
||||
}
|
||||
|
||||
export default function Airdrops() {
|
||||
const queryClient = useQueryClient()
|
||||
const [openingId, setOpeningId] = useState<number | null>(null)
|
||||
const [result, setResult] = useState<LootBoxOpenResult | null>(null)
|
||||
const [showResult, setShowResult] = useState(false)
|
||||
|
||||
const { data: airdrops = [], isLoading } = useQuery({
|
||||
queryKey: ['my-loot-boxes'],
|
||||
queryFn: getMyLootBoxes,
|
||||
retry: 1,
|
||||
})
|
||||
|
||||
const openMutation = useMutation({
|
||||
mutationFn: openLootBox,
|
||||
onSuccess: (data) => {
|
||||
setResult(data)
|
||||
setShowResult(true)
|
||||
queryClient.invalidateQueries({ queryKey: ['my-loot-boxes'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['my-stats'] })
|
||||
},
|
||||
onSettled: () => {
|
||||
setOpeningId(null)
|
||||
},
|
||||
})
|
||||
|
||||
const unclaimedAirdrops = airdrops.filter((box: LootBox) => !box.opened)
|
||||
|
||||
const handleClaim = (box: LootBox) => {
|
||||
setOpeningId(box.id)
|
||||
setResult(null)
|
||||
setShowResult(false)
|
||||
openMutation.mutate(box.id)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm p-6">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<span className="text-2xl">🪂</span> Airdrops
|
||||
{unclaimedAirdrops.length > 0 && (
|
||||
<span className="bg-red-500 text-white text-xs px-2 py-0.5 rounded-full">
|
||||
{unclaimedAirdrops.length}
|
||||
</span>
|
||||
)}
|
||||
</h3>
|
||||
|
||||
{/* Result Modal */}
|
||||
{showResult && result && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-2xl p-6 max-w-sm w-full text-center shadow-xl">
|
||||
<div className="text-6xl mb-4">{REWARD_ICONS[result.reward_type] || '🪂'}</div>
|
||||
<h4 className="text-xl font-bold text-gray-900 mb-2">
|
||||
{result.reward_type === 'nothing' ? 'Better luck next time!' : 'You received:'}
|
||||
</h4>
|
||||
<p className="text-2xl font-bold text-green-600 mb-2">{result.reward_value}</p>
|
||||
<p className="text-gray-600 mb-4">{result.message}</p>
|
||||
<button
|
||||
onClick={() => setShowResult(false)}
|
||||
className="bg-primary hover:bg-primary/90 text-white px-6 py-2 rounded-lg font-medium transition-colors"
|
||||
>
|
||||
Awesome!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{[...Array(3)].map((_, i) => (
|
||||
<div key={i} className="h-24 bg-gray-100 rounded-lg animate-pulse" />
|
||||
))}
|
||||
</div>
|
||||
) : unclaimedAirdrops.length === 0 ? (
|
||||
<div className="text-center py-6">
|
||||
<p className="text-4xl mb-2">🪂</p>
|
||||
<p className="text-gray-500">No airdrops available</p>
|
||||
<p className="text-sm text-gray-400">Earn them through achievements & VIP level ups!</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{unclaimedAirdrops.map((box: LootBox) => {
|
||||
const config = RARITY_CONFIG[box.rarity]
|
||||
const isOpening = openingId === box.id
|
||||
|
||||
return (
|
||||
<button
|
||||
key={box.id}
|
||||
onClick={() => handleClaim(box)}
|
||||
disabled={isOpening || openMutation.isPending}
|
||||
className={`${config.bg} ${config.border} border-2 rounded-lg p-3 text-center transition-all hover:scale-105 disabled:opacity-50 disabled:hover:scale-100`}
|
||||
>
|
||||
<div className={`text-3xl mb-1 ${isOpening ? 'animate-spin' : 'animate-bounce'}`}>
|
||||
{isOpening ? '🎰' : config.icon}
|
||||
</div>
|
||||
<p className={`text-xs font-medium capitalize ${config.color}`}>
|
||||
{box.rarity}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 truncate">{box.source}</p>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Claimed History (collapsed) */}
|
||||
{airdrops.filter((b: LootBox) => b.opened).length > 0 && (
|
||||
<details className="mt-4">
|
||||
<summary className="text-sm text-gray-500 cursor-pointer hover:text-gray-700">
|
||||
View claimed airdrops ({airdrops.filter((b: LootBox) => b.opened).length})
|
||||
</summary>
|
||||
<div className="mt-2 space-y-1 max-h-32 overflow-y-auto">
|
||||
{airdrops
|
||||
.filter((b: LootBox) => b.opened)
|
||||
.map((box: LootBox) => (
|
||||
<div key={box.id} className="flex items-center gap-2 text-xs text-gray-500">
|
||||
<span>{RARITY_CONFIG[box.rarity].icon}</span>
|
||||
<span className="capitalize">{box.rarity}</span>
|
||||
<span>→</span>
|
||||
<span>{box.reward_value || 'Nothing'}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
227
frontend/src/components/gamification/VIPProgress.tsx
Normal file
@ -0,0 +1,227 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getMyStats, getTierInfo } from '../../api/gamification'
|
||||
import type { TierInfo } from '../../types/gamification'
|
||||
|
||||
// Map backend tier names to VIP display names
|
||||
const VIP_NAMES: Record<string, string> = {
|
||||
'Bronze I': 'VIP Bronze I',
|
||||
'Bronze II': 'VIP Bronze II',
|
||||
'Bronze III': 'VIP Bronze III',
|
||||
'Silver I': 'VIP Silver I',
|
||||
'Silver II': 'VIP Silver II',
|
||||
'Silver III': 'VIP Silver III',
|
||||
'Gold I': 'VIP Gold I',
|
||||
'Gold II': 'VIP Gold II',
|
||||
'Gold III': 'VIP Gold III',
|
||||
'Platinum': 'Institutional Platinum',
|
||||
'Diamond': 'Institutional Diamond',
|
||||
}
|
||||
|
||||
const VIP_ICONS: Record<string, string> = {
|
||||
'Bronze I': '🥉',
|
||||
'Bronze II': '🥉',
|
||||
'Bronze III': '🥉',
|
||||
'Silver I': '🥈',
|
||||
'Silver II': '🥈',
|
||||
'Silver III': '🥈',
|
||||
'Gold I': '🥇',
|
||||
'Gold II': '🥇',
|
||||
'Gold III': '🥇',
|
||||
'Platinum': '💎',
|
||||
'Diamond': '👑',
|
||||
}
|
||||
|
||||
const VIP_COLORS: Record<string, { text: string; bg: string; border: string }> = {
|
||||
'Bronze I': { text: 'text-amber-700', bg: 'bg-amber-500', border: 'border-amber-400' },
|
||||
'Bronze II': { text: 'text-amber-700', bg: 'bg-amber-500', border: 'border-amber-400' },
|
||||
'Bronze III': { text: 'text-amber-700', bg: 'bg-amber-500', border: 'border-amber-400' },
|
||||
'Silver I': { text: 'text-gray-600', bg: 'bg-gray-400', border: 'border-gray-400' },
|
||||
'Silver II': { text: 'text-gray-600', bg: 'bg-gray-400', border: 'border-gray-400' },
|
||||
'Silver III': { text: 'text-gray-600', bg: 'bg-gray-400', border: 'border-gray-400' },
|
||||
'Gold I': { text: 'text-yellow-600', bg: 'bg-yellow-400', border: 'border-yellow-400' },
|
||||
'Gold II': { text: 'text-yellow-600', bg: 'bg-yellow-400', border: 'border-yellow-400' },
|
||||
'Gold III': { text: 'text-yellow-600', bg: 'bg-yellow-400', border: 'border-yellow-400' },
|
||||
'Platinum': { text: 'text-cyan-600', bg: 'bg-cyan-400', border: 'border-cyan-400' },
|
||||
'Diamond': { text: 'text-purple-600', bg: 'bg-purple-400', border: 'border-purple-400' },
|
||||
}
|
||||
|
||||
interface VIPProgressProps {
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
export default function VIPProgress({ compact = false }: VIPProgressProps) {
|
||||
const { data: stats, isLoading: loadingStats } = useQuery({
|
||||
queryKey: ['my-stats'],
|
||||
queryFn: getMyStats,
|
||||
retry: 1,
|
||||
})
|
||||
|
||||
const { data: tiers = [], isLoading: loadingTiers } = useQuery({
|
||||
queryKey: ['tier-info'],
|
||||
queryFn: getTierInfo,
|
||||
retry: 1,
|
||||
})
|
||||
|
||||
const isLoading = loadingStats || loadingTiers
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm p-6">
|
||||
<div className="h-32 bg-gray-100 rounded-lg animate-pulse" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!stats) {
|
||||
return null
|
||||
}
|
||||
|
||||
const vipColors = VIP_COLORS[stats.tier_name] || VIP_COLORS['Bronze I']
|
||||
const vipIcon = VIP_ICONS[stats.tier_name] || '🏅'
|
||||
const vipName = VIP_NAMES[stats.tier_name] || stats.tier_name
|
||||
const isInstitutional = stats.tier >= 9 // Platinum and Diamond are institutional
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-3xl">{vipIcon}</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className={`font-bold ${vipColors.text}`}>{vipName}</span>
|
||||
<span className="text-xs text-gray-500">{stats.xp.toLocaleString()} XP</span>
|
||||
</div>
|
||||
<div className="h-2 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full ${vipColors.bg} transition-all duration-500`}
|
||||
style={{ width: `${stats.tier_progress_percent}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
{stats.tier < 10
|
||||
? `${stats.xp_to_next_tier.toLocaleString()} XP to next VIP level`
|
||||
: 'Max VIP level reached!'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm p-6">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<span className="text-2xl">👑</span> Your VIP Level
|
||||
</h3>
|
||||
|
||||
{/* Current VIP Level Display */}
|
||||
<div className={`border-2 ${vipColors.border} rounded-xl p-4 mb-4 bg-gradient-to-br from-gray-50 to-transparent`}>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-5xl">{vipIcon}</div>
|
||||
<div className="flex-1">
|
||||
<p className={`text-2xl font-bold ${vipColors.text}`}>{vipName}</p>
|
||||
<p className="text-gray-500 text-sm">
|
||||
{isInstitutional ? (
|
||||
<span className="text-purple-600 font-medium">Institutional (Whale)</span>
|
||||
) : (
|
||||
`VIP Level ${stats.tier} of 10`
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-green-600 font-bold text-lg">{stats.house_fee}%</p>
|
||||
<p className="text-xs text-gray-500">House Fee</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* XP Progress */}
|
||||
<div className="mt-4">
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span className="text-gray-600">{stats.xp.toLocaleString()} XP</span>
|
||||
{stats.tier < 10 && (
|
||||
<span className="text-gray-600">
|
||||
{(stats.xp + stats.xp_to_next_tier).toLocaleString()} XP
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-3 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full ${vipColors.bg} transition-all duration-500`}
|
||||
style={{ width: `${stats.tier_progress_percent}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-center text-sm text-gray-500 mt-2">
|
||||
{stats.tier < 10
|
||||
? `${stats.xp_to_next_tier.toLocaleString()} XP to next VIP level`
|
||||
: '🎉 Max VIP level reached!'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Summary */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-4">
|
||||
<div className="bg-gray-50 rounded-lg p-3 text-center border">
|
||||
<p className={`text-lg font-bold ${stats.net_profit >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
${stats.net_profit >= 0 ? '+' : ''}{stats.net_profit.toLocaleString()}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">Net Profit</p>
|
||||
</div>
|
||||
<div className="bg-gray-50 rounded-lg p-3 text-center border">
|
||||
<p className="text-lg font-bold text-gray-900">${stats.total_wagered.toLocaleString()}</p>
|
||||
<p className="text-xs text-gray-500">Total Wagered</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* VIP Level Roadmap */}
|
||||
<details className="group">
|
||||
<summary className="text-sm text-gray-500 cursor-pointer hover:text-gray-700 flex items-center gap-1">
|
||||
<span className="group-open:rotate-90 transition-transform">▶</span>
|
||||
View all VIP levels & benefits
|
||||
</summary>
|
||||
<div className="mt-3 space-y-1 max-h-48 overflow-y-auto">
|
||||
{tiers.map((tier: TierInfo) => {
|
||||
const isCurrentTier = tier.tier === stats.tier
|
||||
const isUnlocked = tier.tier <= stats.tier
|
||||
const colors = VIP_COLORS[tier.name] || VIP_COLORS['Bronze I']
|
||||
const displayName = VIP_NAMES[tier.name] || tier.name
|
||||
const tierIsInstitutional = tier.tier >= 9
|
||||
|
||||
return (
|
||||
<div
|
||||
key={tier.tier}
|
||||
className={`flex items-center gap-2 p-2 rounded-lg text-sm ${
|
||||
isCurrentTier
|
||||
? `${colors.border} border bg-gray-50`
|
||||
: isUnlocked
|
||||
? 'bg-gray-50'
|
||||
: 'bg-white opacity-60'
|
||||
}`}
|
||||
>
|
||||
<span className="text-lg">{VIP_ICONS[tier.name] || '🏅'}</span>
|
||||
<div className="flex-1">
|
||||
<span className={isUnlocked ? colors.text : 'text-gray-400'}>
|
||||
{displayName}
|
||||
</span>
|
||||
{tierIsInstitutional && (
|
||||
<span className="ml-1 text-xs text-purple-500">(Whale)</span>
|
||||
)}
|
||||
<span className="text-gray-400 text-xs ml-2">
|
||||
{tier.min_xp.toLocaleString()} XP
|
||||
</span>
|
||||
</div>
|
||||
<span className={`font-medium ${isUnlocked ? 'text-green-600' : 'text-gray-400'}`}>
|
||||
{tier.house_fee}%
|
||||
</span>
|
||||
{isCurrentTier && (
|
||||
<span className="text-xs bg-primary text-white px-1.5 py-0.5 rounded">
|
||||
YOU
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
export { default as Leaderboard } from './Leaderboard'
|
||||
export { default as WhaleTracker } from './WhaleTracker'
|
||||
export { default as Achievements } from './Achievements'
|
||||
export { default as LootBoxes } from './LootBoxes'
|
||||
export { default as Airdrops } from './Airdrops'
|
||||
export { default as LootBoxes } from './LootBoxes' // Keep for backward compatibility
|
||||
export { default as ActivityFeed } from './ActivityFeed'
|
||||
export { default as TierProgress } from './TierProgress'
|
||||
export { default as VIPProgress } from './VIPProgress'
|
||||
export { default as TierProgress } from './TierProgress' // Keep for backward compatibility
|
||||
|
||||
@ -1,19 +1,59 @@
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useAuthStore } from '@/store'
|
||||
import { Wallet, LogOut, User, ChevronDown, Trophy, Medal, Gift, Activity, LayoutDashboard } from 'lucide-react'
|
||||
import {
|
||||
Wallet,
|
||||
LogOut,
|
||||
User,
|
||||
ChevronDown,
|
||||
Gift,
|
||||
Crown,
|
||||
Users,
|
||||
Rocket,
|
||||
Zap,
|
||||
Share2,
|
||||
Settings,
|
||||
Receipt
|
||||
} from 'lucide-react'
|
||||
import { useWeb3Wallet } from '@/blockchain/hooks/useWeb3Wallet'
|
||||
import { Button } from '@/components/common/Button'
|
||||
|
||||
const REWARDS_DROPDOWN = [
|
||||
{ path: '/rewards', label: 'Overview', icon: LayoutDashboard },
|
||||
{ path: '/rewards/leaderboard', label: 'Leaderboard', icon: Trophy },
|
||||
{ path: '/rewards/achievements', label: 'Achievements', icon: Medal },
|
||||
{ path: '/rewards/loot-boxes', label: 'Loot Boxes', icon: Gift },
|
||||
{ path: '/rewards/activity', label: 'Activity', icon: Activity },
|
||||
// More dropdown menu items
|
||||
const MORE_DROPDOWN = [
|
||||
{
|
||||
path: '/vip',
|
||||
label: 'VIP & Institutional',
|
||||
description: 'Your trusted digital betting platform for VIPs and institutions',
|
||||
icon: Crown
|
||||
},
|
||||
{
|
||||
path: '/affiliate',
|
||||
label: 'Affiliate',
|
||||
description: 'Earn up to 50% commission per bet from referrals',
|
||||
icon: Users
|
||||
},
|
||||
{
|
||||
path: '/referral',
|
||||
label: 'Referral',
|
||||
description: 'Invite friends to earn either a commission rebate or a one-time reward',
|
||||
icon: Share2
|
||||
},
|
||||
{
|
||||
path: '/launchpool',
|
||||
label: 'Launchpool',
|
||||
description: 'Discover and gain access to new bets',
|
||||
icon: Rocket
|
||||
},
|
||||
{
|
||||
path: '/megadrop',
|
||||
label: 'Megadrop',
|
||||
description: 'Lock your bets and complete quests for boosted airdrop rewards',
|
||||
icon: Zap
|
||||
},
|
||||
]
|
||||
|
||||
function RewardsDropdown() {
|
||||
// Reusable dropdown hook
|
||||
function useDropdown() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
@ -27,29 +67,38 @@ function RewardsDropdown() {
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [])
|
||||
|
||||
return { isOpen, setIsOpen, dropdownRef }
|
||||
}
|
||||
|
||||
function MoreDropdown() {
|
||||
const { isOpen, setIsOpen, dropdownRef } = useDropdown()
|
||||
|
||||
return (
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="flex items-center gap-1 text-gray-700 hover:text-primary transition-colors"
|
||||
>
|
||||
Rewards
|
||||
More
|
||||
<ChevronDown size={16} className={`transition-transform ${isOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute top-full left-0 mt-2 w-48 bg-white rounded-lg shadow-lg border py-1 z-50">
|
||||
{REWARDS_DROPDOWN.map((item) => {
|
||||
<div className="absolute top-full left-0 mt-2 w-72 bg-white rounded-lg shadow-lg border py-2 z-50">
|
||||
{MORE_DROPDOWN.map((item) => {
|
||||
const Icon = item.icon
|
||||
return (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="flex items-center gap-2 px-4 py-2 text-gray-700 hover:bg-gray-50 hover:text-primary transition-colors"
|
||||
className="flex items-start gap-3 px-4 py-3 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<Icon size={16} />
|
||||
{item.label}
|
||||
<Icon size={20} className="text-primary mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">{item.label}</p>
|
||||
<p className="text-xs text-gray-500 mt-0.5">{item.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
@ -59,103 +108,167 @@ function RewardsDropdown() {
|
||||
)
|
||||
}
|
||||
|
||||
export const Header = () => {
|
||||
function ProfileDropdown() {
|
||||
const { user, logout } = useAuthStore()
|
||||
const { walletAddress, isConnected, connectWallet, disconnectWallet } = useWeb3Wallet()
|
||||
const { isOpen, setIsOpen, dropdownRef } = useDropdown()
|
||||
|
||||
if (!user) return null
|
||||
|
||||
return (
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors"
|
||||
>
|
||||
<User size={18} />
|
||||
<span className="font-medium">{user.display_name || user.username}</span>
|
||||
<ChevronDown size={16} className={`transition-transform ${isOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute top-full right-0 mt-2 w-56 bg-white rounded-lg shadow-lg border py-2 z-50">
|
||||
{/* User info header */}
|
||||
<div className="px-4 py-2 border-b">
|
||||
<p className="font-medium text-gray-900">{user.display_name || user.username}</p>
|
||||
<p className="text-sm text-gray-500">{user.email}</p>
|
||||
</div>
|
||||
|
||||
{/* Menu items */}
|
||||
<div className="py-1">
|
||||
<Link
|
||||
to="/profile"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="flex items-center gap-3 px-4 py-2 text-gray-700 hover:bg-gray-50 hover:text-primary transition-colors"
|
||||
>
|
||||
<Settings size={16} />
|
||||
Profile Settings
|
||||
</Link>
|
||||
<Link
|
||||
to="/my-bets"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="flex items-center gap-3 px-4 py-2 text-gray-700 hover:bg-gray-50 hover:text-primary transition-colors"
|
||||
>
|
||||
<Receipt size={16} />
|
||||
My Bets
|
||||
</Link>
|
||||
<Link
|
||||
to="/wallet"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="flex items-center gap-3 px-4 py-2 text-gray-700 hover:bg-gray-50 hover:text-primary transition-colors"
|
||||
>
|
||||
<Wallet size={16} />
|
||||
Wallet
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Web3 Wallet section */}
|
||||
<div className="border-t py-1">
|
||||
{isConnected ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
disconnectWallet()
|
||||
setIsOpen(false)
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-4 py-2 text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<span className="text-green-600">⛓</span>
|
||||
<div className="text-left">
|
||||
<p className="text-sm font-medium text-green-700">Wallet Connected</p>
|
||||
<p className="text-xs text-gray-500">{walletAddress?.substring(0, 6)}...{walletAddress?.substring(38)}</p>
|
||||
</div>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => {
|
||||
connectWallet()
|
||||
setIsOpen(false)
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-4 py-2 text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<span>⛓</span>
|
||||
<span className="text-sm">Connect Web3 Wallet</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Logout */}
|
||||
<div className="border-t py-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
logout()
|
||||
setIsOpen(false)
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-4 py-2 text-gray-700 hover:bg-red-50 hover:text-red-600 transition-colors"
|
||||
>
|
||||
<LogOut size={16} />
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Header = () => {
|
||||
const { user } = useAuthStore()
|
||||
|
||||
return (
|
||||
<header className="bg-white shadow-sm border-b">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
<div className="flex items-center h-16">
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center">
|
||||
<h1 className="text-2xl font-bold text-primary">H2H</h1>
|
||||
</Link>
|
||||
|
||||
{user ? (
|
||||
// Logged in navigation
|
||||
<nav className="flex items-center gap-6">
|
||||
<Link to="/sports" className="text-gray-700 hover:text-primary transition-colors">
|
||||
Sports
|
||||
</Link>
|
||||
<Link to="/live" className="text-gray-700 hover:text-primary transition-colors">
|
||||
Live
|
||||
</Link>
|
||||
<Link to="/new-bets" className="text-gray-700 hover:text-primary transition-colors">
|
||||
New Bets
|
||||
</Link>
|
||||
{/* Left-justified navigation links */}
|
||||
<nav className="flex items-center gap-6 ml-8">
|
||||
<Link to="/sports" className="text-gray-700 hover:text-primary transition-colors">
|
||||
Markets
|
||||
</Link>
|
||||
<Link to="/live" className="text-gray-700 hover:text-primary transition-colors">
|
||||
Live
|
||||
</Link>
|
||||
<Link to="/new-bets" className="text-gray-700 hover:text-primary transition-colors">
|
||||
New Bets
|
||||
</Link>
|
||||
<Link to="/how-it-works" className="text-gray-700 hover:text-primary transition-colors">
|
||||
How It Works
|
||||
</Link>
|
||||
{/* Watchlist only shows for logged-in users */}
|
||||
{user && (
|
||||
<Link to="/watchlist" className="text-gray-700 hover:text-primary transition-colors">
|
||||
Watchlist
|
||||
</Link>
|
||||
<RewardsDropdown />
|
||||
)}
|
||||
<MoreDropdown />
|
||||
{/* Rewards Center button - styled like CoinEx */}
|
||||
<Link
|
||||
to="/rewards"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 border border-orange-400 text-orange-500 rounded-full hover:bg-orange-50 transition-colors text-sm font-medium"
|
||||
>
|
||||
<Gift size={16} />
|
||||
Reward Center
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-4 pl-4 border-l">
|
||||
{/* Right side - Auth buttons or Profile */}
|
||||
<div className="ml-auto flex items-center gap-4">
|
||||
{user ? (
|
||||
<>
|
||||
{/* Admin link if admin */}
|
||||
{user.is_admin && (
|
||||
<Link to="/admin" className="text-gray-700 hover:text-primary transition-colors">
|
||||
Admin
|
||||
</Link>
|
||||
)}
|
||||
<Link to="/my-bets" className="text-gray-700 hover:text-primary transition-colors">
|
||||
My Bets
|
||||
</Link>
|
||||
<Link to="/wallet" className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
|
||||
<Wallet size={18} />
|
||||
Wallet
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Web3 Wallet Connection */}
|
||||
{isConnected ? (
|
||||
<button
|
||||
onClick={disconnectWallet}
|
||||
className="flex items-center gap-2 px-3 py-1.5 bg-green-100 text-green-800 rounded-lg hover:bg-green-200 transition-colors text-sm font-medium"
|
||||
>
|
||||
<span>⛓️</span>
|
||||
<span>{walletAddress?.substring(0, 6)}...{walletAddress?.substring(38)}</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={connectWallet}
|
||||
className="flex items-center gap-2 px-3 py-1.5 bg-blue-100 text-blue-800 rounded-lg hover:bg-blue-200 transition-colors text-sm font-medium"
|
||||
>
|
||||
<span>⛓️</span>
|
||||
<span>Connect Wallet</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-4 pl-4 border-l">
|
||||
<Link to="/profile" className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
|
||||
<User size={18} />
|
||||
{user.display_name || user.username}
|
||||
</Link>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="flex items-center gap-2 text-gray-700 hover:text-error transition-colors"
|
||||
>
|
||||
<LogOut size={18} />
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
) : (
|
||||
// Non-logged in navigation
|
||||
<nav className="flex items-center gap-6">
|
||||
<Link to="/sports" className="text-gray-700 hover:text-primary transition-colors">
|
||||
Sports
|
||||
</Link>
|
||||
<Link to="/live" className="text-gray-700 hover:text-primary transition-colors">
|
||||
Live
|
||||
</Link>
|
||||
<Link to="/new-bets" className="text-gray-700 hover:text-primary transition-colors">
|
||||
New Bets
|
||||
</Link>
|
||||
<Link to="/watchlist" className="text-gray-700 hover:text-primary transition-colors">
|
||||
Watchlist
|
||||
</Link>
|
||||
<RewardsDropdown />
|
||||
<Link to="/how-it-works" className="text-gray-700 hover:text-primary transition-colors">
|
||||
How It Works
|
||||
</Link>
|
||||
<div className="flex items-center gap-3 pl-4 border-l">
|
||||
{/* Profile dropdown */}
|
||||
<ProfileDropdown />
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center gap-3">
|
||||
<Link to="/login">
|
||||
<Button variant="secondary" size="sm">
|
||||
Log In
|
||||
@ -167,8 +280,8 @@ export const Header = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -13,7 +13,7 @@ const NAV_ITEMS = [
|
||||
{ path: '/rewards', label: 'Overview', icon: LayoutDashboard, exact: true },
|
||||
{ path: '/rewards/leaderboard', label: 'Leaderboard', icon: Trophy },
|
||||
{ path: '/rewards/achievements', label: 'Achievements', icon: Medal },
|
||||
{ path: '/rewards/loot-boxes', label: 'Loot Boxes', icon: Gift },
|
||||
{ path: '/rewards/airdrops', label: 'Airdrops', icon: Gift },
|
||||
{ path: '/rewards/activity', label: 'Activity', icon: Activity },
|
||||
]
|
||||
|
||||
|
||||
95
frontend/src/pages/Affiliate.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import { Header } from '@/components/layout/Header'
|
||||
import { Users, DollarSign, TrendingUp, BarChart3, Wallet } from 'lucide-react'
|
||||
|
||||
const AFFILIATE_FEATURES = [
|
||||
{
|
||||
icon: DollarSign,
|
||||
title: 'Up to 50% Commission',
|
||||
description: 'Earn up to 50% commission on every bet your referrals place'
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
title: 'Lifetime Earnings',
|
||||
description: 'Continue earning from your referrals for as long as they bet'
|
||||
},
|
||||
{
|
||||
icon: BarChart3,
|
||||
title: 'Real-Time Dashboard',
|
||||
description: 'Track your earnings, clicks, and conversions in real-time'
|
||||
},
|
||||
{
|
||||
icon: Wallet,
|
||||
title: 'Weekly Payouts',
|
||||
description: 'Receive your commissions every week, no minimum threshold'
|
||||
}
|
||||
]
|
||||
|
||||
export default function Affiliate() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="bg-gradient-to-r from-green-900 to-green-800 text-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<div className="text-center max-w-3xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 bg-green-500/20 text-green-400 px-4 py-2 rounded-full mb-6">
|
||||
<Users size={20} />
|
||||
<span className="font-medium">Affiliate Program</span>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4">
|
||||
Earn Up to 50% Commission Per Bet
|
||||
</h1>
|
||||
<p className="text-green-100 text-lg mb-8">
|
||||
Join our affiliate program and start earning passive income by promoting H2H to your audience.
|
||||
</p>
|
||||
<button className="bg-white text-green-900 hover:bg-green-50 px-8 py-3 rounded-lg font-medium transition-colors">
|
||||
Become an Affiliate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{AFFILIATE_FEATURES.map((feature, index) => {
|
||||
const Icon = feature.icon
|
||||
return (
|
||||
<div key={index} className="bg-white rounded-xl shadow-sm p-6 text-center">
|
||||
<div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mx-auto mb-4">
|
||||
<Icon className="text-green-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-2">{feature.title}</h3>
|
||||
<p className="text-gray-600 text-sm">{feature.description}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Commission Tiers */}
|
||||
<div className="bg-white py-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-12">Commission Structure</h2>
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
|
||||
<span className="font-medium text-gray-900">0-10 referrals/month</span>
|
||||
<span className="text-xl font-bold text-green-600">30% Commission</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
|
||||
<span className="font-medium text-gray-900">11-50 referrals/month</span>
|
||||
<span className="text-xl font-bold text-green-600">40% Commission</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-4 bg-green-50 rounded-lg border-2 border-green-500">
|
||||
<span className="font-medium text-gray-900">50+ referrals/month</span>
|
||||
<span className="text-xl font-bold text-green-600">50% Commission</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
123
frontend/src/pages/Launchpool.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import { Header } from '@/components/layout/Header'
|
||||
import { Rocket, Clock, Trophy, ArrowRight } from 'lucide-react'
|
||||
|
||||
const UPCOMING_POOLS = [
|
||||
{
|
||||
name: 'UFC 300 Special',
|
||||
description: 'Exclusive early access to UFC 300 betting markets',
|
||||
starts: 'Jan 15, 2026',
|
||||
reward: '2x XP Boost',
|
||||
status: 'upcoming'
|
||||
},
|
||||
{
|
||||
name: 'Super Bowl LXIII',
|
||||
description: 'Premium props and side bets for the big game',
|
||||
starts: 'Feb 1, 2026',
|
||||
reward: 'VIP Airdrop',
|
||||
status: 'upcoming'
|
||||
},
|
||||
{
|
||||
name: 'March Madness Early Bird',
|
||||
description: 'Get first access to tournament brackets',
|
||||
starts: 'Mar 1, 2026',
|
||||
reward: 'Exclusive Badge',
|
||||
status: 'upcoming'
|
||||
}
|
||||
]
|
||||
|
||||
export default function Launchpool() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="bg-gradient-to-r from-blue-900 to-blue-800 text-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<div className="text-center max-w-3xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 bg-blue-500/20 text-blue-300 px-4 py-2 rounded-full mb-6">
|
||||
<Rocket size={20} />
|
||||
<span className="font-medium">Launchpool</span>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4">
|
||||
Discover and Gain Early Access to New Bets
|
||||
</h1>
|
||||
<p className="text-blue-100 text-lg mb-8">
|
||||
Be the first to access exclusive betting markets before they go live. Place early bets to earn rewards and priority access.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* How It Works */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-12">How Launchpool Works</h2>
|
||||
<div className="grid md:grid-cols-4 gap-6">
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-xl font-bold text-blue-600">1</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-gray-900 mb-2">Browse Pools</h3>
|
||||
<p className="text-gray-600 text-sm">Explore upcoming betting events and markets</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-xl font-bold text-blue-600">2</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-gray-900 mb-2">Reserve Your Spot</h3>
|
||||
<p className="text-gray-600 text-sm">Commit funds to secure early access to the pool</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-xl font-bold text-blue-600">3</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-gray-900 mb-2">Earn Rewards</h3>
|
||||
<p className="text-gray-600 text-sm">Get XP boosts, badges, and exclusive perks</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-xl font-bold text-blue-600">4</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-gray-900 mb-2">Get Early Access</h3>
|
||||
<p className="text-gray-600 text-sm">Place bets before the general public</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upcoming Pools */}
|
||||
<div className="bg-white py-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-12">Upcoming Pools</h2>
|
||||
<div className="space-y-4 max-w-3xl mx-auto">
|
||||
{UPCOMING_POOLS.map((pool, index) => (
|
||||
<div key={index} className="bg-gray-50 rounded-xl p-6 flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<Rocket className="text-blue-600" size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-bold text-gray-900">{pool.name}</h3>
|
||||
<p className="text-sm text-gray-600">{pool.description}</p>
|
||||
<div className="flex items-center gap-4 mt-2">
|
||||
<span className="text-xs text-gray-500 flex items-center gap-1">
|
||||
<Clock size={12} />
|
||||
Starts {pool.starts}
|
||||
</span>
|
||||
<span className="text-xs text-blue-600 flex items-center gap-1">
|
||||
<Trophy size={12} />
|
||||
{pool.reward}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors flex items-center gap-2">
|
||||
Join Pool
|
||||
<ArrowRight size={16} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
185
frontend/src/pages/Megadrop.tsx
Normal file
@ -0,0 +1,185 @@
|
||||
import { Header } from '@/components/layout/Header'
|
||||
import { Zap, Lock, CheckCircle, Gift, Star } from 'lucide-react'
|
||||
import { useAuthStore } from '@/store'
|
||||
|
||||
const QUESTS = [
|
||||
{
|
||||
title: 'Place Your First Bet',
|
||||
description: 'Make any bet of $10 or more',
|
||||
reward: '100 Megadrop Points',
|
||||
completed: false
|
||||
},
|
||||
{
|
||||
title: 'Win 3 Bets in a Row',
|
||||
description: 'Build a winning streak',
|
||||
reward: '500 Megadrop Points',
|
||||
completed: false
|
||||
},
|
||||
{
|
||||
title: 'Refer a Friend',
|
||||
description: 'Invite someone who places a bet',
|
||||
reward: '250 Megadrop Points',
|
||||
completed: false
|
||||
},
|
||||
{
|
||||
title: 'Reach VIP Bronze',
|
||||
description: 'Earn 1,000 XP to unlock Bronze tier',
|
||||
reward: '1,000 Megadrop Points',
|
||||
completed: false
|
||||
},
|
||||
{
|
||||
title: 'Complete Daily Check-in',
|
||||
description: 'Log in 7 days in a row',
|
||||
reward: '300 Megadrop Points',
|
||||
completed: false
|
||||
}
|
||||
]
|
||||
|
||||
export default function Megadrop() {
|
||||
const { user } = useAuthStore()
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="bg-gradient-to-r from-yellow-600 to-orange-600 text-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<div className="text-center max-w-3xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 bg-yellow-500/20 text-yellow-100 px-4 py-2 rounded-full mb-6">
|
||||
<Zap size={20} />
|
||||
<span className="font-medium">Megadrop</span>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4">
|
||||
Lock Your Bets, Complete Quests, Earn Boosted Airdrops
|
||||
</h1>
|
||||
<p className="text-yellow-100 text-lg mb-8">
|
||||
Participate in Megadrop events to earn exclusive rewards. Lock your stakes, complete quests, and climb the leaderboard for massive airdrop bonuses.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Bar */}
|
||||
<div className="bg-white border-b">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div className="grid grid-cols-4 gap-6">
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-bold text-gray-900">$500K</p>
|
||||
<p className="text-sm text-gray-600">Total Airdrop Pool</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-bold text-gray-900">12,345</p>
|
||||
<p className="text-sm text-gray-600">Participants</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-bold text-gray-900">5 Days</p>
|
||||
<p className="text-sm text-gray-600">Time Remaining</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-bold text-orange-600">{user ? '0' : '-'}</p>
|
||||
<p className="text-sm text-gray-600">Your Points</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Lock Section */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{/* Lock Bets */}
|
||||
<div className="bg-white rounded-xl shadow-sm p-6">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center">
|
||||
<Lock className="text-orange-600" size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-bold text-gray-900">Lock Your Stake</h3>
|
||||
<p className="text-sm text-gray-600">Earn points based on locked amount</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Lock Amount</span>
|
||||
<span className="font-medium">$0.00</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Lock Period</span>
|
||||
<span className="font-medium">30 Days</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Daily Points</span>
|
||||
<span className="font-medium text-orange-600">0 pts/day</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="w-full mt-6 bg-orange-600 hover:bg-orange-700 text-white py-3 rounded-lg font-medium transition-colors">
|
||||
{user ? 'Lock Stakes' : 'Sign In to Lock'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Your Rewards */}
|
||||
<div className="bg-white rounded-xl shadow-sm p-6">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 bg-yellow-100 rounded-lg flex items-center justify-center">
|
||||
<Gift className="text-yellow-600" size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-bold text-gray-900">Your Rewards</h3>
|
||||
<p className="text-sm text-gray-600">Estimated airdrop based on points</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center py-8">
|
||||
<p className="text-4xl font-bold text-gray-900">$0.00</p>
|
||||
<p className="text-gray-600 mt-2">Estimated Airdrop Value</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Your Points</span>
|
||||
<span className="font-medium">0</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm mt-2">
|
||||
<span className="text-gray-600">Your Rank</span>
|
||||
<span className="font-medium">{user ? '#-' : 'Sign in'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quests */}
|
||||
<div className="bg-white py-12">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-8">Complete Quests for Bonus Points</h2>
|
||||
<div className="space-y-4">
|
||||
{QUESTS.map((quest, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
|
||||
quest.completed ? 'bg-green-100' : 'bg-gray-200'
|
||||
}`}>
|
||||
{quest.completed ? (
|
||||
<CheckCircle className="text-green-600" size={20} />
|
||||
) : (
|
||||
<Star className="text-gray-400" size={16} />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900">{quest.title}</h4>
|
||||
<p className="text-sm text-gray-600">{quest.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="text-sm font-medium text-orange-600">{quest.reward}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
113
frontend/src/pages/Referral.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import { Header } from '@/components/layout/Header'
|
||||
import { Share2, Copy, Check } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useAuthStore } from '@/store'
|
||||
|
||||
export default function Referral() {
|
||||
const { user } = useAuthStore()
|
||||
const [copied, setCopied] = useState(false)
|
||||
const referralLink = user ? `https://h2h.bet/ref/${user.username}` : 'https://h2h.bet/ref/YOURCODE'
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(referralLink)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="bg-gradient-to-r from-purple-900 to-purple-800 text-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<div className="text-center max-w-3xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 bg-purple-500/20 text-purple-300 px-4 py-2 rounded-full mb-6">
|
||||
<Share2 size={20} />
|
||||
<span className="font-medium">Referral Program</span>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4">
|
||||
Invite Friends, Earn Rewards
|
||||
</h1>
|
||||
<p className="text-purple-100 text-lg mb-8">
|
||||
Share your referral link and earn either a commission rebate or a one-time reward for each friend who joins.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Referral Link Box */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<div className="bg-white rounded-xl shadow-lg p-6 max-w-2xl mx-auto">
|
||||
<h3 className="font-bold text-gray-900 mb-4">Your Referral Link</h3>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={referralLink}
|
||||
readOnly
|
||||
className="flex-1 bg-gray-100 rounded-lg px-4 py-3 text-gray-700 font-mono text-sm"
|
||||
/>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="bg-primary hover:bg-primary/90 text-white px-6 py-3 rounded-lg font-medium transition-colors flex items-center gap-2"
|
||||
>
|
||||
{copied ? <Check size={18} /> : <Copy size={18} />}
|
||||
{copied ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
{!user && (
|
||||
<p className="text-sm text-gray-500 mt-3">
|
||||
Sign in to get your personalized referral link
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Reward Options */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-12">Choose Your Reward Type</h2>
|
||||
<div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
<div className="bg-white rounded-xl shadow-sm p-8 border-2 border-purple-500">
|
||||
<div className="text-4xl mb-4">💰</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">Commission Rebate</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Earn 20% of your friend's betting fees forever. The more they bet, the more you earn.
|
||||
</p>
|
||||
<div className="text-2xl font-bold text-purple-600">20% Lifetime</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm p-8">
|
||||
<div className="text-4xl mb-4">🎁</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">One-Time Reward</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Get an instant $25 bonus when your friend makes their first bet of $50 or more.
|
||||
</p>
|
||||
<div className="text-2xl font-bold text-green-600">$25 Instant</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
{user && (
|
||||
<div className="bg-white py-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-12">Your Referral Stats</h2>
|
||||
<div className="grid grid-cols-3 gap-6 max-w-2xl mx-auto">
|
||||
<div className="text-center">
|
||||
<p className="text-3xl font-bold text-gray-900">0</p>
|
||||
<p className="text-gray-600">Friends Invited</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-3xl font-bold text-green-600">$0</p>
|
||||
<p className="text-gray-600">Total Earned</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-3xl font-bold text-purple-600">0</p>
|
||||
<p className="text-gray-600">Pending Rewards</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
118
frontend/src/pages/VIP.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import { Header } from '@/components/layout/Header'
|
||||
import { Crown, Shield, Zap, Users, TrendingUp, Award } from 'lucide-react'
|
||||
|
||||
const VIP_BENEFITS = [
|
||||
{
|
||||
icon: Crown,
|
||||
title: 'Exclusive VIP Status',
|
||||
description: 'Join an elite group of high-volume bettors with premium benefits'
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
title: 'Lower Fees',
|
||||
description: 'Enjoy significantly reduced house fees on all your bets'
|
||||
},
|
||||
{
|
||||
icon: Zap,
|
||||
title: 'Priority Support',
|
||||
description: '24/7 dedicated account manager and priority customer support'
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: 'Institutional Access',
|
||||
description: 'API access and custom solutions for institutional traders'
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
title: 'Higher Limits',
|
||||
description: 'Increased betting limits and exclusive high-stakes events'
|
||||
},
|
||||
{
|
||||
icon: Award,
|
||||
title: 'Exclusive Rewards',
|
||||
description: 'Access to VIP-only airdrops, events, and promotional offers'
|
||||
}
|
||||
]
|
||||
|
||||
export default function VIP() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="bg-gradient-to-r from-gray-900 to-gray-800 text-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<div className="text-center max-w-3xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 bg-yellow-500/20 text-yellow-400 px-4 py-2 rounded-full mb-6">
|
||||
<Crown size={20} />
|
||||
<span className="font-medium">VIP & Institutional Program</span>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4">
|
||||
Your Trusted Digital Betting Platform for VIPs and Institutions
|
||||
</h1>
|
||||
<p className="text-gray-300 text-lg mb-8">
|
||||
Experience premium benefits, lower fees, and exclusive access designed for high-volume traders and institutional clients.
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center">
|
||||
<button className="bg-primary hover:bg-primary/90 text-white px-8 py-3 rounded-lg font-medium transition-colors">
|
||||
Apply for VIP Status
|
||||
</button>
|
||||
<button className="bg-white/10 hover:bg-white/20 text-white px-8 py-3 rounded-lg font-medium transition-colors">
|
||||
Contact Sales
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Benefits Grid */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-12">VIP Benefits</h2>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{VIP_BENEFITS.map((benefit, index) => {
|
||||
const Icon = benefit.icon
|
||||
return (
|
||||
<div key={index} className="bg-white rounded-xl shadow-sm p-6 hover:shadow-md transition-shadow">
|
||||
<div className="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mb-4">
|
||||
<Icon className="text-primary" size={24} />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-2">{benefit.title}</h3>
|
||||
<p className="text-gray-600">{benefit.description}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* VIP Tiers */}
|
||||
<div className="bg-white py-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-12">VIP Levels</h2>
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="border rounded-xl p-6 text-center">
|
||||
<div className="text-4xl mb-4">🥉</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">VIP Bronze</h3>
|
||||
<p className="text-gray-600 mb-4">Starting tier for active traders</p>
|
||||
<p className="text-2xl font-bold text-primary">8% Fees</p>
|
||||
</div>
|
||||
<div className="border-2 border-primary rounded-xl p-6 text-center relative">
|
||||
<div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-primary text-white px-3 py-1 rounded-full text-sm">
|
||||
Popular
|
||||
</div>
|
||||
<div className="text-4xl mb-4">🥇</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">VIP Gold</h3>
|
||||
<p className="text-gray-600 mb-4">For high-volume traders</p>
|
||||
<p className="text-2xl font-bold text-primary">5% Fees</p>
|
||||
</div>
|
||||
<div className="border rounded-xl p-6 text-center bg-gradient-to-b from-gray-900 to-gray-800 text-white">
|
||||
<div className="text-4xl mb-4">💎</div>
|
||||
<h3 className="text-xl font-bold mb-2">Institutional</h3>
|
||||
<p className="text-gray-300 mb-4">For whales & institutions</p>
|
||||
<p className="text-2xl font-bold text-yellow-400">Custom Rates</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
75
frontend/src/pages/rewards/AirdropsPage.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { useAuthStore } from '@/store'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { RewardsLayout } from '@/components/layout/RewardsLayout'
|
||||
import { Airdrops, VIPProgress } from '@/components/gamification'
|
||||
|
||||
export default function AirdropsPage() {
|
||||
const { isAuthenticated } = useAuthStore()
|
||||
|
||||
return (
|
||||
<RewardsLayout
|
||||
title="Airdrops"
|
||||
subtitle="Claim your rewards and prizes"
|
||||
>
|
||||
{isAuthenticated ? (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2">
|
||||
<Airdrops />
|
||||
|
||||
{/* How to earn airdrops */}
|
||||
<div className="mt-6 bg-white rounded-xl shadow-sm p-6">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">How to Earn Airdrops</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-2xl">⬆️</span>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">Level Up VIP</p>
|
||||
<p className="text-sm text-gray-500">Reach a new VIP level to earn an airdrop</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-2xl">🎖️</span>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">Achievements</p>
|
||||
<p className="text-sm text-gray-500">Unlock achievements for bonus airdrops</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-2xl">📅</span>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">Daily Rewards</p>
|
||||
<p className="text-sm text-gray-500">Log in daily to earn streak rewards</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-2xl">🔥</span>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">Win Streaks</p>
|
||||
<p className="text-sm text-gray-500">Build winning streaks for rare airdrops</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<VIPProgress />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-xl shadow-sm p-8 text-center max-w-md mx-auto">
|
||||
<span className="text-6xl mb-4 block">🔒</span>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">Sign In Required</h3>
|
||||
<p className="text-gray-500 mb-6">
|
||||
Sign in to view and claim your airdrops.
|
||||
</p>
|
||||
<Link
|
||||
to="/login"
|
||||
className="bg-primary hover:bg-primary/90 text-white px-6 py-2 rounded-lg font-medium transition-colors inline-block"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</RewardsLayout>
|
||||
)
|
||||
}
|
||||
@ -1,13 +1,13 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useAuthStore } from '@/store'
|
||||
import { RewardsLayout } from '@/components/layout/RewardsLayout'
|
||||
import { Leaderboard, WhaleTracker, TierProgress } from '@/components/gamification'
|
||||
import { Leaderboard, WhaleTracker, VIPProgress } from '@/components/gamification'
|
||||
import { Trophy, Medal, Gift, Activity, ArrowRight } from 'lucide-react'
|
||||
|
||||
const QUICK_LINKS = [
|
||||
{ path: '/rewards/leaderboard', label: 'Leaderboard', icon: Trophy, description: 'See top players and rankings' },
|
||||
{ path: '/rewards/achievements', label: 'Achievements', icon: Medal, description: 'Track your progress and badges' },
|
||||
{ path: '/rewards/loot-boxes', label: 'Loot Boxes', icon: Gift, description: 'Open rewards and claim prizes' },
|
||||
{ path: '/rewards/airdrops', label: 'Airdrops', icon: Gift, description: 'Claim rewards and prizes' },
|
||||
{ path: '/rewards/activity', label: 'Activity', icon: Activity, description: 'View recent wins and bets' },
|
||||
]
|
||||
|
||||
@ -47,7 +47,7 @@ export default function RewardsIndex() {
|
||||
{/* Left Column - Personal Stats (if authenticated) */}
|
||||
{isAuthenticated ? (
|
||||
<div>
|
||||
<TierProgress />
|
||||
<VIPProgress />
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-xl shadow-sm p-6 flex flex-col items-center justify-center text-center">
|
||||
@ -76,28 +76,28 @@ export default function RewardsIndex() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tier Benefits Info */}
|
||||
{/* VIP Benefits Info */}
|
||||
<div className="mt-8 bg-white rounded-xl shadow-sm p-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<span>💡</span> How Tiers Work
|
||||
<span>💡</span> How VIP Levels Work
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
|
||||
<div className="bg-gray-50 rounded-lg p-4 border">
|
||||
<p className="text-primary font-bold mb-1">🎯 Earn XP</p>
|
||||
<p className="text-gray-600">
|
||||
Place bets, win, and complete achievements to earn XP and level up your tier.
|
||||
Place bets, win, and complete achievements to earn XP and level up your VIP status.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-50 rounded-lg p-4 border">
|
||||
<p className="text-primary font-bold mb-1">📉 Lower Fees</p>
|
||||
<p className="text-gray-600">
|
||||
Higher tiers mean lower house fees! Start at 10% and work your way down to 5%.
|
||||
Higher VIP levels mean lower house fees! Start at 10% and work your way down to 5%.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-50 rounded-lg p-4 border">
|
||||
<p className="text-primary font-bold mb-1">🎁 Unlock Rewards</p>
|
||||
<p className="text-primary font-bold mb-1">🪂 Unlock Rewards</p>
|
||||
<p className="text-gray-600">
|
||||
Earn loot boxes, achievement badges, and exclusive perks as you climb the ranks.
|
||||
Earn airdrops, achievement badges, and exclusive perks as you climb the VIP ranks.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export { default as RewardsIndex } from './RewardsIndex'
|
||||
export { default as LeaderboardPage } from './LeaderboardPage'
|
||||
export { default as AchievementsPage } from './AchievementsPage'
|
||||
export { default as LootBoxesPage } from './LootBoxesPage'
|
||||
export { default as AirdropsPage } from './AirdropsPage'
|
||||
export { default as ActivityPage } from './ActivityPage'
|
||||
|
||||
|
Before Width: | Height: | Size: 675 KiB |
|
Before Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 542 KiB After Width: | Height: | Size: 609 KiB |
|
Before Width: | Height: | Size: 541 KiB After Width: | Height: | Size: 607 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 675 KiB |