@wakastellar/ui 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blocks/auth-2fa/index.d.ts +38 -0
- package/dist/blocks/chat-interface/index.d.ts +66 -0
- package/dist/blocks/checkout-flow/index.d.ts +76 -0
- package/dist/blocks/dashboard-kpi/index.d.ts +69 -0
- package/dist/blocks/deployment-dashboard/index.d.ts +68 -0
- package/dist/blocks/index.d.ts +7 -0
- package/dist/blocks/player-profile/index.d.ts +78 -0
- package/dist/index.cjs.js +136 -134
- package/dist/index.es.js +17304 -15120
- package/package.json +1 -1
- package/src/blocks/auth-2fa/index.tsx +655 -0
- package/src/blocks/chat-interface/index.tsx +611 -0
- package/src/blocks/checkout-flow/index.tsx +771 -0
- package/src/blocks/dashboard-kpi/index.tsx +424 -0
- package/src/blocks/deployment-dashboard/index.tsx +609 -0
- package/src/blocks/index.ts +24 -0
- package/src/blocks/player-profile/index.tsx +541 -0
- package/src/components/language-selector/index.tsx +21 -11
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../utils"
|
|
5
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../../components/card"
|
|
6
|
+
import { Button } from "../../components/button"
|
|
7
|
+
import { Badge } from "../../components/badge"
|
|
8
|
+
import { Progress } from "../../components/progress"
|
|
9
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../../components/avatar"
|
|
10
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/tabs"
|
|
11
|
+
import { Separator } from "../../components/separator"
|
|
12
|
+
import {
|
|
13
|
+
Trophy,
|
|
14
|
+
Star,
|
|
15
|
+
Zap,
|
|
16
|
+
Target,
|
|
17
|
+
Medal,
|
|
18
|
+
Crown,
|
|
19
|
+
Flame,
|
|
20
|
+
TrendingUp,
|
|
21
|
+
Award,
|
|
22
|
+
Shield,
|
|
23
|
+
Swords,
|
|
24
|
+
Heart,
|
|
25
|
+
Clock,
|
|
26
|
+
Calendar,
|
|
27
|
+
Users,
|
|
28
|
+
Gamepad2,
|
|
29
|
+
} from "lucide-react"
|
|
30
|
+
|
|
31
|
+
// ============================================
|
|
32
|
+
// TYPES
|
|
33
|
+
// ============================================
|
|
34
|
+
|
|
35
|
+
export interface PlayerStats {
|
|
36
|
+
label: string
|
|
37
|
+
value: string | number
|
|
38
|
+
icon?: React.ReactNode
|
|
39
|
+
color?: "default" | "blue" | "green" | "yellow" | "red" | "purple"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface Achievement {
|
|
43
|
+
id: string
|
|
44
|
+
name: string
|
|
45
|
+
description: string
|
|
46
|
+
icon?: React.ReactNode
|
|
47
|
+
rarity?: "common" | "rare" | "epic" | "legendary"
|
|
48
|
+
unlockedAt?: Date | string
|
|
49
|
+
progress?: number
|
|
50
|
+
maxProgress?: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PlayerBadge {
|
|
54
|
+
id: string
|
|
55
|
+
name: string
|
|
56
|
+
icon?: React.ReactNode
|
|
57
|
+
color?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface MatchHistory {
|
|
61
|
+
id: string
|
|
62
|
+
result: "win" | "loss" | "draw"
|
|
63
|
+
score?: string
|
|
64
|
+
opponent?: string
|
|
65
|
+
mode?: string
|
|
66
|
+
date: Date | string
|
|
67
|
+
xpGained?: number
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface PlayerProfileProps {
|
|
71
|
+
/** Player name */
|
|
72
|
+
name: string
|
|
73
|
+
/** Player avatar URL */
|
|
74
|
+
avatar?: string
|
|
75
|
+
/** Player title/rank */
|
|
76
|
+
title?: string
|
|
77
|
+
/** Current level */
|
|
78
|
+
level: number
|
|
79
|
+
/** Current XP */
|
|
80
|
+
currentXP: number
|
|
81
|
+
/** XP needed for next level */
|
|
82
|
+
maxXP: number
|
|
83
|
+
/** Total XP earned */
|
|
84
|
+
totalXP?: number
|
|
85
|
+
/** Current rank */
|
|
86
|
+
rank?: string
|
|
87
|
+
/** Rank icon */
|
|
88
|
+
rankIcon?: React.ReactNode
|
|
89
|
+
/** Player stats */
|
|
90
|
+
stats?: PlayerStats[]
|
|
91
|
+
/** Achievements */
|
|
92
|
+
achievements?: Achievement[]
|
|
93
|
+
/** Badges */
|
|
94
|
+
badges?: PlayerBadge[]
|
|
95
|
+
/** Match history */
|
|
96
|
+
matchHistory?: MatchHistory[]
|
|
97
|
+
/** Streak count */
|
|
98
|
+
streak?: number
|
|
99
|
+
/** Join date */
|
|
100
|
+
joinDate?: Date | string
|
|
101
|
+
/** Total playtime */
|
|
102
|
+
playtime?: string
|
|
103
|
+
/** Friends count */
|
|
104
|
+
friendsCount?: number
|
|
105
|
+
/** Is online */
|
|
106
|
+
isOnline?: boolean
|
|
107
|
+
/** Custom className */
|
|
108
|
+
className?: string
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ============================================
|
|
112
|
+
// SUBCOMPONENTS
|
|
113
|
+
// ============================================
|
|
114
|
+
|
|
115
|
+
const rarityColors = {
|
|
116
|
+
common: "bg-slate-500/20 text-slate-400 border-slate-500/30",
|
|
117
|
+
rare: "bg-blue-500/20 text-blue-400 border-blue-500/30",
|
|
118
|
+
epic: "bg-purple-500/20 text-purple-400 border-purple-500/30",
|
|
119
|
+
legendary: "bg-amber-500/20 text-amber-400 border-amber-500/30",
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const rarityGlow = {
|
|
123
|
+
common: "",
|
|
124
|
+
rare: "shadow-blue-500/20",
|
|
125
|
+
epic: "shadow-purple-500/20",
|
|
126
|
+
legendary: "shadow-amber-500/30 shadow-lg",
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function XPBar({ current, max, level }: { current: number; max: number; level: number }) {
|
|
130
|
+
const percentage = (current / max) * 100
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className="space-y-2">
|
|
134
|
+
<div className="flex items-center justify-between text-sm">
|
|
135
|
+
<div className="flex items-center gap-2">
|
|
136
|
+
<div className="h-8 w-8 rounded-full bg-primary flex items-center justify-center text-primary-foreground font-bold">
|
|
137
|
+
{level}
|
|
138
|
+
</div>
|
|
139
|
+
<span className="text-muted-foreground">Level {level}</span>
|
|
140
|
+
</div>
|
|
141
|
+
<span className="text-muted-foreground">
|
|
142
|
+
{current.toLocaleString()} / {max.toLocaleString()} XP
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div className="relative h-3 rounded-full bg-muted overflow-hidden">
|
|
146
|
+
<div
|
|
147
|
+
className="absolute inset-y-0 left-0 bg-gradient-to-r from-primary to-primary/80 rounded-full transition-all duration-500"
|
|
148
|
+
style={{ width: `${percentage}%` }}
|
|
149
|
+
/>
|
|
150
|
+
<div
|
|
151
|
+
className="absolute inset-y-0 left-0 bg-gradient-to-r from-white/20 to-transparent rounded-full"
|
|
152
|
+
style={{ width: `${percentage}%` }}
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function StatCard({ stat }: { stat: PlayerStats }) {
|
|
160
|
+
const colorClasses = {
|
|
161
|
+
default: "text-foreground",
|
|
162
|
+
blue: "text-blue-500",
|
|
163
|
+
green: "text-green-500",
|
|
164
|
+
yellow: "text-yellow-500",
|
|
165
|
+
red: "text-red-500",
|
|
166
|
+
purple: "text-purple-500",
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div className="flex items-center gap-3 p-3 rounded-lg bg-muted/50">
|
|
171
|
+
{stat.icon && (
|
|
172
|
+
<div className={cn("h-10 w-10 rounded-lg bg-background flex items-center justify-center", colorClasses[stat.color || "default"])}>
|
|
173
|
+
{stat.icon}
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
<div>
|
|
177
|
+
<p className="text-2xl font-bold">{stat.value}</p>
|
|
178
|
+
<p className="text-sm text-muted-foreground">{stat.label}</p>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function AchievementCard({ achievement }: { achievement: Achievement }) {
|
|
185
|
+
const isUnlocked = achievement.unlockedAt || (achievement.progress !== undefined && achievement.maxProgress !== undefined && achievement.progress >= achievement.maxProgress)
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<div
|
|
189
|
+
className={cn(
|
|
190
|
+
"p-4 rounded-lg border transition-all",
|
|
191
|
+
isUnlocked
|
|
192
|
+
? cn(rarityColors[achievement.rarity || "common"], rarityGlow[achievement.rarity || "common"])
|
|
193
|
+
: "bg-muted/30 border-dashed opacity-60"
|
|
194
|
+
)}
|
|
195
|
+
>
|
|
196
|
+
<div className="flex items-start gap-3">
|
|
197
|
+
<div
|
|
198
|
+
className={cn(
|
|
199
|
+
"h-12 w-12 rounded-lg flex items-center justify-center",
|
|
200
|
+
isUnlocked ? "bg-background/50" : "bg-muted"
|
|
201
|
+
)}
|
|
202
|
+
>
|
|
203
|
+
{achievement.icon || <Trophy className="h-6 w-6" />}
|
|
204
|
+
</div>
|
|
205
|
+
<div className="flex-1 min-w-0">
|
|
206
|
+
<div className="flex items-center gap-2">
|
|
207
|
+
<h4 className="font-medium truncate">{achievement.name}</h4>
|
|
208
|
+
{achievement.rarity && (
|
|
209
|
+
<Badge variant="outline" className="text-xs capitalize">
|
|
210
|
+
{achievement.rarity}
|
|
211
|
+
</Badge>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
<p className="text-sm text-muted-foreground mt-1">{achievement.description}</p>
|
|
215
|
+
{achievement.progress !== undefined && achievement.maxProgress !== undefined && !isUnlocked && (
|
|
216
|
+
<div className="mt-2">
|
|
217
|
+
<Progress value={(achievement.progress / achievement.maxProgress) * 100} className="h-1.5" />
|
|
218
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
219
|
+
{achievement.progress} / {achievement.maxProgress}
|
|
220
|
+
</p>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
{achievement.unlockedAt && (
|
|
224
|
+
<p className="text-xs text-muted-foreground mt-2">
|
|
225
|
+
Unlocked {typeof achievement.unlockedAt === "string" ? achievement.unlockedAt : achievement.unlockedAt.toLocaleDateString()}
|
|
226
|
+
</p>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function MatchHistoryItem({ match }: { match: MatchHistory }) {
|
|
235
|
+
const resultColors = {
|
|
236
|
+
win: "text-green-500 bg-green-500/10",
|
|
237
|
+
loss: "text-red-500 bg-red-500/10",
|
|
238
|
+
draw: "text-yellow-500 bg-yellow-500/10",
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const resultIcons = {
|
|
242
|
+
win: <Trophy className="h-4 w-4" />,
|
|
243
|
+
loss: <Swords className="h-4 w-4" />,
|
|
244
|
+
draw: <Shield className="h-4 w-4" />,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<div className="flex items-center gap-4 p-3 rounded-lg bg-muted/50">
|
|
249
|
+
<div className={cn("h-10 w-10 rounded-lg flex items-center justify-center", resultColors[match.result])}>
|
|
250
|
+
{resultIcons[match.result]}
|
|
251
|
+
</div>
|
|
252
|
+
<div className="flex-1 min-w-0">
|
|
253
|
+
<div className="flex items-center gap-2">
|
|
254
|
+
<span className="font-medium capitalize">{match.result}</span>
|
|
255
|
+
{match.score && <span className="text-muted-foreground">{match.score}</span>}
|
|
256
|
+
</div>
|
|
257
|
+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
258
|
+
{match.opponent && <span>vs {match.opponent}</span>}
|
|
259
|
+
{match.mode && <Badge variant="outline" className="text-xs">{match.mode}</Badge>}
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
<div className="text-right">
|
|
263
|
+
{match.xpGained && (
|
|
264
|
+
<p className="text-sm font-medium text-green-500">+{match.xpGained} XP</p>
|
|
265
|
+
)}
|
|
266
|
+
<p className="text-xs text-muted-foreground">
|
|
267
|
+
{typeof match.date === "string" ? match.date : match.date.toLocaleDateString()}
|
|
268
|
+
</p>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ============================================
|
|
275
|
+
// MAIN COMPONENT
|
|
276
|
+
// ============================================
|
|
277
|
+
|
|
278
|
+
export function PlayerProfile({
|
|
279
|
+
name,
|
|
280
|
+
avatar,
|
|
281
|
+
title,
|
|
282
|
+
level,
|
|
283
|
+
currentXP,
|
|
284
|
+
maxXP,
|
|
285
|
+
totalXP,
|
|
286
|
+
rank,
|
|
287
|
+
rankIcon,
|
|
288
|
+
stats,
|
|
289
|
+
achievements,
|
|
290
|
+
badges,
|
|
291
|
+
matchHistory,
|
|
292
|
+
streak,
|
|
293
|
+
joinDate,
|
|
294
|
+
playtime,
|
|
295
|
+
friendsCount,
|
|
296
|
+
isOnline = false,
|
|
297
|
+
className,
|
|
298
|
+
}: PlayerProfileProps) {
|
|
299
|
+
return (
|
|
300
|
+
<div className={cn("space-y-6", className)}>
|
|
301
|
+
{/* Header Card */}
|
|
302
|
+
<Card>
|
|
303
|
+
<CardContent className="pt-6">
|
|
304
|
+
<div className="flex flex-col md:flex-row gap-6 items-start">
|
|
305
|
+
{/* Avatar & Basic Info */}
|
|
306
|
+
<div className="flex items-center gap-4">
|
|
307
|
+
<div className="relative">
|
|
308
|
+
<Avatar className="h-24 w-24 border-4 border-primary/20">
|
|
309
|
+
<AvatarImage src={avatar} alt={name} />
|
|
310
|
+
<AvatarFallback className="text-2xl">{name.slice(0, 2).toUpperCase()}</AvatarFallback>
|
|
311
|
+
</Avatar>
|
|
312
|
+
{isOnline && (
|
|
313
|
+
<div className="absolute bottom-1 right-1 h-5 w-5 rounded-full bg-green-500 border-2 border-background" />
|
|
314
|
+
)}
|
|
315
|
+
<div className="absolute -top-2 -right-2 h-8 w-8 rounded-full bg-primary flex items-center justify-center text-primary-foreground font-bold text-sm">
|
|
316
|
+
{level}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
<div>
|
|
320
|
+
<h2 className="text-2xl font-bold">{name}</h2>
|
|
321
|
+
{title && <p className="text-muted-foreground">{title}</p>}
|
|
322
|
+
{rank && (
|
|
323
|
+
<div className="flex items-center gap-2 mt-2">
|
|
324
|
+
{rankIcon || <Medal className="h-5 w-5 text-yellow-500" />}
|
|
325
|
+
<span className="font-medium">{rank}</span>
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
{/* Quick Stats */}
|
|
332
|
+
<div className="flex-1 grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
333
|
+
{streak !== undefined && (
|
|
334
|
+
<div className="text-center p-3 rounded-lg bg-orange-500/10">
|
|
335
|
+
<Flame className="h-6 w-6 text-orange-500 mx-auto" />
|
|
336
|
+
<p className="text-2xl font-bold mt-1">{streak}</p>
|
|
337
|
+
<p className="text-xs text-muted-foreground">Day Streak</p>
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
{totalXP !== undefined && (
|
|
341
|
+
<div className="text-center p-3 rounded-lg bg-purple-500/10">
|
|
342
|
+
<Zap className="h-6 w-6 text-purple-500 mx-auto" />
|
|
343
|
+
<p className="text-2xl font-bold mt-1">{totalXP.toLocaleString()}</p>
|
|
344
|
+
<p className="text-xs text-muted-foreground">Total XP</p>
|
|
345
|
+
</div>
|
|
346
|
+
)}
|
|
347
|
+
{playtime && (
|
|
348
|
+
<div className="text-center p-3 rounded-lg bg-blue-500/10">
|
|
349
|
+
<Clock className="h-6 w-6 text-blue-500 mx-auto" />
|
|
350
|
+
<p className="text-2xl font-bold mt-1">{playtime}</p>
|
|
351
|
+
<p className="text-xs text-muted-foreground">Playtime</p>
|
|
352
|
+
</div>
|
|
353
|
+
)}
|
|
354
|
+
{friendsCount !== undefined && (
|
|
355
|
+
<div className="text-center p-3 rounded-lg bg-green-500/10">
|
|
356
|
+
<Users className="h-6 w-6 text-green-500 mx-auto" />
|
|
357
|
+
<p className="text-2xl font-bold mt-1">{friendsCount}</p>
|
|
358
|
+
<p className="text-xs text-muted-foreground">Friends</p>
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
{/* XP Bar */}
|
|
365
|
+
<div className="mt-6">
|
|
366
|
+
<XPBar current={currentXP} max={maxXP} level={level} />
|
|
367
|
+
</div>
|
|
368
|
+
|
|
369
|
+
{/* Badges */}
|
|
370
|
+
{badges && badges.length > 0 && (
|
|
371
|
+
<div className="mt-6">
|
|
372
|
+
<h3 className="text-sm font-medium text-muted-foreground mb-3">Badges</h3>
|
|
373
|
+
<div className="flex flex-wrap gap-2">
|
|
374
|
+
{badges.map((badge) => (
|
|
375
|
+
<div
|
|
376
|
+
key={badge.id}
|
|
377
|
+
className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-muted text-sm"
|
|
378
|
+
style={badge.color ? { backgroundColor: `${badge.color}20`, color: badge.color } : undefined}
|
|
379
|
+
>
|
|
380
|
+
{badge.icon}
|
|
381
|
+
<span>{badge.name}</span>
|
|
382
|
+
</div>
|
|
383
|
+
))}
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
)}
|
|
387
|
+
</CardContent>
|
|
388
|
+
</Card>
|
|
389
|
+
|
|
390
|
+
{/* Tabs Section */}
|
|
391
|
+
<Tabs defaultValue="stats" className="w-full">
|
|
392
|
+
<TabsList className="w-full justify-start">
|
|
393
|
+
<TabsTrigger value="stats">Stats</TabsTrigger>
|
|
394
|
+
<TabsTrigger value="achievements">Achievements</TabsTrigger>
|
|
395
|
+
<TabsTrigger value="history">Match History</TabsTrigger>
|
|
396
|
+
</TabsList>
|
|
397
|
+
|
|
398
|
+
{/* Stats Tab */}
|
|
399
|
+
<TabsContent value="stats" className="mt-4">
|
|
400
|
+
<Card>
|
|
401
|
+
<CardHeader>
|
|
402
|
+
<CardTitle>Player Statistics</CardTitle>
|
|
403
|
+
<CardDescription>Your performance metrics</CardDescription>
|
|
404
|
+
</CardHeader>
|
|
405
|
+
<CardContent>
|
|
406
|
+
{stats && stats.length > 0 ? (
|
|
407
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
408
|
+
{stats.map((stat, index) => (
|
|
409
|
+
<StatCard key={index} stat={stat} />
|
|
410
|
+
))}
|
|
411
|
+
</div>
|
|
412
|
+
) : (
|
|
413
|
+
<p className="text-muted-foreground text-center py-8">No statistics available</p>
|
|
414
|
+
)}
|
|
415
|
+
</CardContent>
|
|
416
|
+
</Card>
|
|
417
|
+
</TabsContent>
|
|
418
|
+
|
|
419
|
+
{/* Achievements Tab */}
|
|
420
|
+
<TabsContent value="achievements" className="mt-4">
|
|
421
|
+
<Card>
|
|
422
|
+
<CardHeader>
|
|
423
|
+
<CardTitle>Achievements</CardTitle>
|
|
424
|
+
<CardDescription>
|
|
425
|
+
{achievements?.filter((a) => a.unlockedAt).length || 0} / {achievements?.length || 0} unlocked
|
|
426
|
+
</CardDescription>
|
|
427
|
+
</CardHeader>
|
|
428
|
+
<CardContent>
|
|
429
|
+
{achievements && achievements.length > 0 ? (
|
|
430
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
431
|
+
{achievements.map((achievement) => (
|
|
432
|
+
<AchievementCard key={achievement.id} achievement={achievement} />
|
|
433
|
+
))}
|
|
434
|
+
</div>
|
|
435
|
+
) : (
|
|
436
|
+
<p className="text-muted-foreground text-center py-8">No achievements yet</p>
|
|
437
|
+
)}
|
|
438
|
+
</CardContent>
|
|
439
|
+
</Card>
|
|
440
|
+
</TabsContent>
|
|
441
|
+
|
|
442
|
+
{/* Match History Tab */}
|
|
443
|
+
<TabsContent value="history" className="mt-4">
|
|
444
|
+
<Card>
|
|
445
|
+
<CardHeader>
|
|
446
|
+
<CardTitle>Match History</CardTitle>
|
|
447
|
+
<CardDescription>Your recent games</CardDescription>
|
|
448
|
+
</CardHeader>
|
|
449
|
+
<CardContent>
|
|
450
|
+
{matchHistory && matchHistory.length > 0 ? (
|
|
451
|
+
<div className="space-y-3">
|
|
452
|
+
{matchHistory.map((match) => (
|
|
453
|
+
<MatchHistoryItem key={match.id} match={match} />
|
|
454
|
+
))}
|
|
455
|
+
</div>
|
|
456
|
+
) : (
|
|
457
|
+
<p className="text-muted-foreground text-center py-8">No matches played yet</p>
|
|
458
|
+
)}
|
|
459
|
+
</CardContent>
|
|
460
|
+
</Card>
|
|
461
|
+
</TabsContent>
|
|
462
|
+
</Tabs>
|
|
463
|
+
|
|
464
|
+
{/* Footer Info */}
|
|
465
|
+
{joinDate && (
|
|
466
|
+
<div className="flex items-center justify-center gap-2 text-sm text-muted-foreground">
|
|
467
|
+
<Calendar className="h-4 w-4" />
|
|
468
|
+
<span>
|
|
469
|
+
Member since {typeof joinDate === "string" ? joinDate : joinDate.toLocaleDateString()}
|
|
470
|
+
</span>
|
|
471
|
+
</div>
|
|
472
|
+
)}
|
|
473
|
+
</div>
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ============================================
|
|
478
|
+
// PRESET DATA
|
|
479
|
+
// ============================================
|
|
480
|
+
|
|
481
|
+
export const defaultPlayerStats: PlayerStats[] = [
|
|
482
|
+
{ label: "Matches Played", value: 342, icon: <Gamepad2 className="h-5 w-5" />, color: "blue" },
|
|
483
|
+
{ label: "Wins", value: 189, icon: <Trophy className="h-5 w-5" />, color: "green" },
|
|
484
|
+
{ label: "Win Rate", value: "55.3%", icon: <TrendingUp className="h-5 w-5" />, color: "yellow" },
|
|
485
|
+
{ label: "Best Score", value: "12,450", icon: <Star className="h-5 w-5" />, color: "purple" },
|
|
486
|
+
{ label: "Kills", value: "1,234", icon: <Target className="h-5 w-5" />, color: "red" },
|
|
487
|
+
{ label: "Assists", value: 876, icon: <Heart className="h-5 w-5" />, color: "default" },
|
|
488
|
+
]
|
|
489
|
+
|
|
490
|
+
export const defaultAchievements: Achievement[] = [
|
|
491
|
+
{
|
|
492
|
+
id: "first-win",
|
|
493
|
+
name: "First Victory",
|
|
494
|
+
description: "Win your first match",
|
|
495
|
+
rarity: "common",
|
|
496
|
+
unlockedAt: new Date("2024-01-15"),
|
|
497
|
+
icon: <Trophy className="h-6 w-6 text-yellow-500" />,
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
id: "streak-master",
|
|
501
|
+
name: "Streak Master",
|
|
502
|
+
description: "Achieve a 10-day login streak",
|
|
503
|
+
rarity: "rare",
|
|
504
|
+
unlockedAt: new Date("2024-02-20"),
|
|
505
|
+
icon: <Flame className="h-6 w-6 text-orange-500" />,
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
id: "legendary-player",
|
|
509
|
+
name: "Legendary Player",
|
|
510
|
+
description: "Reach level 50",
|
|
511
|
+
rarity: "legendary",
|
|
512
|
+
progress: 42,
|
|
513
|
+
maxProgress: 50,
|
|
514
|
+
icon: <Crown className="h-6 w-6 text-amber-500" />,
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
id: "social-butterfly",
|
|
518
|
+
name: "Social Butterfly",
|
|
519
|
+
description: "Add 25 friends",
|
|
520
|
+
rarity: "epic",
|
|
521
|
+
progress: 18,
|
|
522
|
+
maxProgress: 25,
|
|
523
|
+
icon: <Users className="h-6 w-6 text-purple-500" />,
|
|
524
|
+
},
|
|
525
|
+
]
|
|
526
|
+
|
|
527
|
+
export const defaultBadges: PlayerBadge[] = [
|
|
528
|
+
{ id: "beta", name: "Beta Tester", icon: <Shield className="h-4 w-4" />, color: "#3b82f6" },
|
|
529
|
+
{ id: "premium", name: "Premium", icon: <Crown className="h-4 w-4" />, color: "#f59e0b" },
|
|
530
|
+
{ id: "verified", name: "Verified", icon: <Award className="h-4 w-4" />, color: "#10b981" },
|
|
531
|
+
]
|
|
532
|
+
|
|
533
|
+
export const defaultMatchHistory: MatchHistory[] = [
|
|
534
|
+
{ id: "1", result: "win", score: "3-1", opponent: "xShadowx", mode: "Ranked", date: new Date("2024-03-10"), xpGained: 150 },
|
|
535
|
+
{ id: "2", result: "win", score: "2-0", opponent: "ProGamer99", mode: "Ranked", date: new Date("2024-03-10"), xpGained: 175 },
|
|
536
|
+
{ id: "3", result: "loss", score: "1-3", opponent: "NightHawk", mode: "Ranked", date: new Date("2024-03-09"), xpGained: 50 },
|
|
537
|
+
{ id: "4", result: "draw", score: "2-2", opponent: "StormRider", mode: "Casual", date: new Date("2024-03-09"), xpGained: 75 },
|
|
538
|
+
{ id: "5", result: "win", score: "5-2", opponent: "BlazeMaster", mode: "Ranked", date: new Date("2024-03-08"), xpGained: 200 },
|
|
539
|
+
]
|
|
540
|
+
|
|
541
|
+
export default PlayerProfile
|
|
@@ -240,10 +240,16 @@ export function LanguageSelector({
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
// Variant compact - Bouton avec drapeau et chevron avec dropdown
|
|
243
|
+
// En mode compact sans labels, on n'affiche que le drapeau (ou l'icône globe)
|
|
244
|
+
const isIconOnly = !showLabels
|
|
245
|
+
|
|
243
246
|
const dropdownContent = isOpen && mounted ? (
|
|
244
|
-
<div
|
|
247
|
+
<div
|
|
245
248
|
ref={dropdownRef}
|
|
246
|
-
className=
|
|
249
|
+
className={cn(
|
|
250
|
+
"fixed z-[9999] rounded-md border border-border bg-popover text-popover-foreground p-2 shadow-lg",
|
|
251
|
+
isIconOnly ? "min-w-[50px]" : "min-w-[180px]"
|
|
252
|
+
)}
|
|
247
253
|
style={{
|
|
248
254
|
top: `${dropdownPosition.top}px`,
|
|
249
255
|
left: `${dropdownPosition.left}px`,
|
|
@@ -260,23 +266,25 @@ export function LanguageSelector({
|
|
|
260
266
|
handleLanguageChange(language.code)
|
|
261
267
|
}}
|
|
262
268
|
type="button"
|
|
269
|
+
title={language.label}
|
|
263
270
|
className={cn(
|
|
264
271
|
"flex items-center gap-2 rounded-sm px-2 py-2 text-sm hover:bg-accent hover:text-accent-foreground transition-colors cursor-pointer",
|
|
265
|
-
value === language.code && "bg-accent"
|
|
272
|
+
value === language.code && "bg-accent",
|
|
273
|
+
isIconOnly && "justify-center"
|
|
266
274
|
)}
|
|
267
275
|
>
|
|
268
276
|
{showFlags && language.flagEmoji && (
|
|
269
277
|
<span className="text-lg">{language.flagEmoji}</span>
|
|
270
278
|
)}
|
|
271
279
|
{showFlags && language.flag && !language.flagEmoji && (
|
|
272
|
-
<img
|
|
273
|
-
src={language.flag}
|
|
280
|
+
<img
|
|
281
|
+
src={language.flag}
|
|
274
282
|
alt={language.label}
|
|
275
283
|
className="h-4 w-6 object-cover rounded"
|
|
276
284
|
/>
|
|
277
285
|
)}
|
|
278
|
-
<span className="flex-1 text-left">{language.label}</span>
|
|
279
|
-
{value === language.code && (
|
|
286
|
+
{!isIconOnly && <span className="flex-1 text-left">{language.label}</span>}
|
|
287
|
+
{!isIconOnly && value === language.code && (
|
|
280
288
|
<Check className="h-4 w-4 shrink-0" />
|
|
281
289
|
)}
|
|
282
290
|
</button>
|
|
@@ -297,20 +305,22 @@ export function LanguageSelector({
|
|
|
297
305
|
}
|
|
298
306
|
}}
|
|
299
307
|
className={cn(
|
|
300
|
-
"inline-flex items-center
|
|
308
|
+
"inline-flex items-center justify-center rounded-md border border-input bg-transparent text-sm hover:bg-accent hover:text-accent-foreground transition-colors",
|
|
309
|
+
isIconOnly ? "h-9 w-9 p-0" : "gap-2 px-3 py-2",
|
|
301
310
|
isLoading && "opacity-50 cursor-not-allowed",
|
|
302
311
|
className
|
|
303
312
|
)}
|
|
304
313
|
disabled={isLoading}
|
|
305
314
|
type="button"
|
|
306
315
|
aria-label={ariaLabel}
|
|
316
|
+
title={selectedLanguage?.label || ariaLabel}
|
|
307
317
|
>
|
|
308
318
|
{showFlags && selectedLanguage?.flagEmoji && (
|
|
309
319
|
<span className="text-lg">{selectedLanguage.flagEmoji}</span>
|
|
310
320
|
)}
|
|
311
321
|
{showFlags && selectedLanguage?.flag && !selectedLanguage?.flagEmoji && (
|
|
312
|
-
<img
|
|
313
|
-
src={selectedLanguage.flag}
|
|
322
|
+
<img
|
|
323
|
+
src={selectedLanguage.flag}
|
|
314
324
|
alt={selectedLanguage.label}
|
|
315
325
|
className="h-4 w-6 object-cover rounded"
|
|
316
326
|
/>
|
|
@@ -319,7 +329,7 @@ export function LanguageSelector({
|
|
|
319
329
|
{showLabels && (
|
|
320
330
|
<span>{selectedLanguage?.label || "Langue"}</span>
|
|
321
331
|
)}
|
|
322
|
-
<ChevronDown className="h-4 w-4" />
|
|
332
|
+
{!isIconOnly && <ChevronDown className="h-4 w-4" />}
|
|
323
333
|
</button>
|
|
324
334
|
|
|
325
335
|
{mounted && typeof document !== "undefined" && dropdownContent && createPortal(
|