@chainloyalty/react 1.0.2 → 1.2.0
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/Leaderboard.d.ts.map +1 -1
- package/dist/Leaderboard.js +28 -23
- package/dist/Leaderboard.js.map +1 -1
- package/dist/RewardsDashboard.d.ts.map +1 -1
- package/dist/RewardsDashboard.js +28 -17
- package/dist/RewardsDashboard.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/theme.d.ts +36 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +76 -0
- package/dist/theme.js.map +1 -0
- package/package.json +2 -2
- package/src/Leaderboard.tsx +63 -53
- package/src/RewardsDashboard.tsx +69 -51
- package/src/index.ts +4 -0
- package/src/theme.ts +92 -0
package/src/Leaderboard.tsx
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
TrendingUp,
|
|
29
29
|
ExternalLink,
|
|
30
30
|
} from 'lucide-react';
|
|
31
|
+
import { getThemeColors } from './theme';
|
|
31
32
|
|
|
32
33
|
// ============================================================================
|
|
33
34
|
// Types & Interfaces
|
|
@@ -142,6 +143,7 @@ const getRankIcon = (rank: number): React.ReactNode => {
|
|
|
142
143
|
interface LeaderboardRowProps {
|
|
143
144
|
entry: LeaderboardEntry;
|
|
144
145
|
accentColor: string;
|
|
146
|
+
theme: ReturnType<typeof getThemeColors>;
|
|
145
147
|
isCurrentUser?: boolean;
|
|
146
148
|
onCopyAddress?: (address: string) => void;
|
|
147
149
|
}
|
|
@@ -149,6 +151,7 @@ interface LeaderboardRowProps {
|
|
|
149
151
|
const LeaderboardRow: React.FC<LeaderboardRowProps> = ({
|
|
150
152
|
entry,
|
|
151
153
|
accentColor,
|
|
154
|
+
theme,
|
|
152
155
|
isCurrentUser = false,
|
|
153
156
|
onCopyAddress,
|
|
154
157
|
}) => {
|
|
@@ -168,9 +171,11 @@ const LeaderboardRow: React.FC<LeaderboardRowProps> = ({
|
|
|
168
171
|
|
|
169
172
|
return (
|
|
170
173
|
<tr
|
|
171
|
-
className=
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
className="border-b transition-all hover:opacity-80"
|
|
175
|
+
style={{
|
|
176
|
+
borderColor: theme.border,
|
|
177
|
+
backgroundColor: isCurrentUser ? 'rgba(0,0,0,0.1)' : 'transparent'
|
|
178
|
+
}}
|
|
174
179
|
>
|
|
175
180
|
{/* Rank */}
|
|
176
181
|
<td className="px-4 py-3 text-sm">
|
|
@@ -190,12 +195,13 @@ const LeaderboardRow: React.FC<LeaderboardRowProps> = ({
|
|
|
190
195
|
{/* Wallet Address */}
|
|
191
196
|
<td className="px-4 py-3 text-sm">
|
|
192
197
|
<div className="flex items-center gap-2 group">
|
|
193
|
-
<span className="
|
|
198
|
+
<span className="font-mono" style={{ color: theme.foreground }}>
|
|
194
199
|
{shortenAddress(entry.walletAddress)}
|
|
195
200
|
</span>
|
|
196
201
|
<button
|
|
197
202
|
onClick={handleCopy}
|
|
198
|
-
className="p-1.5 rounded-md
|
|
203
|
+
className="p-1.5 rounded-md transition-all opacity-0 group-hover:opacity-100"
|
|
204
|
+
style={{ color: theme.mutedForeground }}
|
|
199
205
|
title={entry.walletAddress}
|
|
200
206
|
>
|
|
201
207
|
{copied ? (
|
|
@@ -205,7 +211,7 @@ const LeaderboardRow: React.FC<LeaderboardRowProps> = ({
|
|
|
205
211
|
)}
|
|
206
212
|
</button>
|
|
207
213
|
{entry.displayName && (
|
|
208
|
-
<span className="hidden sm:inline text-xs
|
|
214
|
+
<span className="hidden sm:inline text-xs" style={{ color: theme.mutedForeground }}>({entry.displayName})</span>
|
|
209
215
|
)}
|
|
210
216
|
</div>
|
|
211
217
|
</td>
|
|
@@ -216,7 +222,7 @@ const LeaderboardRow: React.FC<LeaderboardRowProps> = ({
|
|
|
216
222
|
<p className="font-bold" style={{ color: accentColor }}>
|
|
217
223
|
{entry.points.toLocaleString()}
|
|
218
224
|
</p>
|
|
219
|
-
<p className="text-xs
|
|
225
|
+
<p className="text-xs hidden sm:block" style={{ color: theme.mutedForeground }}>points</p>
|
|
220
226
|
</div>
|
|
221
227
|
</td>
|
|
222
228
|
|
|
@@ -225,12 +231,13 @@ const LeaderboardRow: React.FC<LeaderboardRowProps> = ({
|
|
|
225
231
|
<div
|
|
226
232
|
className="inline-flex items-center gap-1 px-2 py-1 rounded-lg"
|
|
227
233
|
style={{
|
|
228
|
-
backgroundColor:
|
|
229
|
-
borderColor:
|
|
234
|
+
backgroundColor: theme.muted,
|
|
235
|
+
borderColor: theme.border,
|
|
236
|
+
border: '1px solid',
|
|
230
237
|
}}
|
|
231
238
|
>
|
|
232
239
|
<Award className="w-4 h-4" style={{ color: accentColor }} />
|
|
233
|
-
<span className="font-semibold
|
|
240
|
+
<span className="font-semibold" style={{ color: theme.foreground }}>{entry.badgesCount}</span>
|
|
234
241
|
</div>
|
|
235
242
|
</td>
|
|
236
243
|
</tr>
|
|
@@ -254,9 +261,12 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
254
261
|
const [error, setError] = useState<string | null>(null);
|
|
255
262
|
const [currentPage, setCurrentPage] = useState(1);
|
|
256
263
|
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
264
|
+
// Get theme colors from host app's CSS variables
|
|
265
|
+
const theme = getThemeColors();
|
|
266
|
+
|
|
267
|
+
// Override with customColors if provided (backward compatibility)
|
|
268
|
+
const accentColor = customColors.accent1 || theme.accent;
|
|
269
|
+
const accentColor2 = customColors.accent2 || theme.primary;
|
|
260
270
|
|
|
261
271
|
// Fetch data when timeframe changes
|
|
262
272
|
useEffect(() => {
|
|
@@ -294,20 +304,21 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
294
304
|
|
|
295
305
|
return (
|
|
296
306
|
<div
|
|
297
|
-
className="rounded-lg border
|
|
298
|
-
style={{ backgroundColor }}
|
|
307
|
+
className="rounded-lg border overflow-hidden"
|
|
308
|
+
style={{ backgroundColor: theme.card, borderColor: theme.border }}
|
|
299
309
|
>
|
|
300
310
|
{/* Header */}
|
|
301
|
-
<div className="p-6 border
|
|
311
|
+
<div className="p-6" style={{ borderBottom: `1px solid ${theme.border}` }}>
|
|
302
312
|
<div className="flex items-center justify-between mb-4">
|
|
303
313
|
<div className="flex items-center gap-3">
|
|
304
314
|
<Trophy className="w-6 h-6" style={{ color: accentColor2 }} />
|
|
305
|
-
<h2 className="text-2xl font-bold
|
|
315
|
+
<h2 className="text-2xl font-bold" style={{ color: theme.foreground }}>Leaderboard</h2>
|
|
306
316
|
</div>
|
|
307
317
|
<button
|
|
308
318
|
onClick={handleRefresh}
|
|
309
319
|
disabled={loading}
|
|
310
|
-
className="p-2 rounded-lg
|
|
320
|
+
className="p-2 rounded-lg transition-all disabled:opacity-50"
|
|
321
|
+
style={{ color: theme.mutedForeground }}
|
|
311
322
|
title="Refresh leaderboard"
|
|
312
323
|
>
|
|
313
324
|
<RefreshCw className={`w-5 h-5 ${loading ? 'animate-spin' : ''}`} />
|
|
@@ -317,11 +328,11 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
317
328
|
{/* Stats */}
|
|
318
329
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 mb-4">
|
|
319
330
|
<div
|
|
320
|
-
className="p-3 rounded-lg border
|
|
321
|
-
style={{ backgroundColor:
|
|
331
|
+
className="p-3 rounded-lg border"
|
|
332
|
+
style={{ backgroundColor: theme.muted, borderColor: theme.border }}
|
|
322
333
|
>
|
|
323
|
-
<p className="text-xs
|
|
324
|
-
<p className="text-2xl font-bold
|
|
334
|
+
<p className="text-xs" style={{ color: theme.mutedForeground }}>Total Users</p>
|
|
335
|
+
<p className="text-2xl font-bold" style={{ color: theme.foreground }}>{data.length.toLocaleString()}</p>
|
|
325
336
|
</div>
|
|
326
337
|
|
|
327
338
|
{currentUserRank && (
|
|
@@ -330,10 +341,10 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
330
341
|
className="p-3 rounded-lg border"
|
|
331
342
|
style={{
|
|
332
343
|
borderColor: accentColor,
|
|
333
|
-
backgroundColor:
|
|
344
|
+
backgroundColor: theme.card,
|
|
334
345
|
}}
|
|
335
346
|
>
|
|
336
|
-
<p className="text-xs
|
|
347
|
+
<p className="text-xs" style={{ color: theme.mutedForeground }}>Your Rank</p>
|
|
337
348
|
<p className="text-2xl font-bold" style={{ color: accentColor }}>
|
|
338
349
|
#{currentUserRank.rank}
|
|
339
350
|
</p>
|
|
@@ -343,10 +354,10 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
343
354
|
className="p-3 rounded-lg border"
|
|
344
355
|
style={{
|
|
345
356
|
borderColor: accentColor2,
|
|
346
|
-
backgroundColor:
|
|
357
|
+
backgroundColor: theme.card,
|
|
347
358
|
}}
|
|
348
359
|
>
|
|
349
|
-
<p className="text-xs
|
|
360
|
+
<p className="text-xs" style={{ color: theme.mutedForeground }}>Your Points</p>
|
|
350
361
|
<p className="text-2xl font-bold" style={{ color: accentColor2 }}>
|
|
351
362
|
{currentUserRank.points.toLocaleString()}
|
|
352
363
|
</p>
|
|
@@ -358,7 +369,7 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
358
369
|
{/* Filters */}
|
|
359
370
|
{!hideTimeframeFilter && (
|
|
360
371
|
<div className="flex items-center gap-2">
|
|
361
|
-
<Calendar className="w-4 h-4
|
|
372
|
+
<Calendar className="w-4 h-4" style={{ color: theme.mutedForeground }} />
|
|
362
373
|
<div className="flex gap-2">
|
|
363
374
|
{(['alltime', 'month'] as const).map((tf) => (
|
|
364
375
|
<button
|
|
@@ -366,8 +377,8 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
366
377
|
onClick={() => setTimeframe(tf)}
|
|
367
378
|
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-all ${
|
|
368
379
|
timeframe === tf
|
|
369
|
-
? '
|
|
370
|
-
: '
|
|
380
|
+
? ''
|
|
381
|
+
: ''
|
|
371
382
|
}`}
|
|
372
383
|
style={
|
|
373
384
|
timeframe === tf
|
|
@@ -375,7 +386,7 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
375
386
|
backgroundColor: accentColor,
|
|
376
387
|
color: 'white',
|
|
377
388
|
}
|
|
378
|
-
: {}
|
|
389
|
+
: { color: theme.mutedForeground }
|
|
379
390
|
}
|
|
380
391
|
>
|
|
381
392
|
{tf === 'alltime' ? 'All Time' : 'This Month'}
|
|
@@ -387,12 +398,12 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
387
398
|
</div>
|
|
388
399
|
|
|
389
400
|
{/* Content */}
|
|
390
|
-
<div style={{ backgroundColor:
|
|
401
|
+
<div style={{ backgroundColor: theme.background }}>
|
|
391
402
|
{/* Loading State */}
|
|
392
403
|
{loading && !data.length && (
|
|
393
404
|
<div className="p-6 space-y-3">
|
|
394
405
|
{[...Array(5)].map((_, i) => (
|
|
395
|
-
<div key={i} className="h-12
|
|
406
|
+
<div key={i} className="h-12 rounded-lg animate-pulse" style={{ backgroundColor: theme.muted }} />
|
|
396
407
|
))}
|
|
397
408
|
</div>
|
|
398
409
|
)}
|
|
@@ -400,10 +411,10 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
400
411
|
{/* Error State */}
|
|
401
412
|
{error && !data.length && (
|
|
402
413
|
<div className="p-6 text-center">
|
|
403
|
-
<p className="
|
|
414
|
+
<p className="mb-4" style={{ color: '#ef4444' }}>{error}</p>
|
|
404
415
|
<button
|
|
405
416
|
onClick={handleRefresh}
|
|
406
|
-
className="px-4 py-2 rounded-lg text-
|
|
417
|
+
className="px-4 py-2 rounded-lg text-sm transition-all text-white"
|
|
407
418
|
style={{ backgroundColor: accentColor }}
|
|
408
419
|
>
|
|
409
420
|
Try Again
|
|
@@ -414,9 +425,9 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
414
425
|
{/* Empty State */}
|
|
415
426
|
{!loading && !error && data.length === 0 && (
|
|
416
427
|
<div className="p-12 text-center">
|
|
417
|
-
<TrendingUp className="w-12 h-12 mx-auto mb-4
|
|
418
|
-
<h3 className="text-lg font-semibold
|
|
419
|
-
<p
|
|
428
|
+
<TrendingUp className="w-12 h-12 mx-auto mb-4" style={{ color: theme.mutedForeground }} />
|
|
429
|
+
<h3 className="text-lg font-semibold mb-2" style={{ color: theme.foreground }}>No Leaderboard Data</h3>
|
|
430
|
+
<p style={{ color: theme.mutedForeground }}>The leaderboard will be populated as users start earning points.</p>
|
|
420
431
|
</div>
|
|
421
432
|
)}
|
|
422
433
|
|
|
@@ -425,17 +436,17 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
425
436
|
<div className="overflow-x-auto">
|
|
426
437
|
<table className="w-full">
|
|
427
438
|
<thead>
|
|
428
|
-
<tr className="border-b border
|
|
429
|
-
<th className="px-4 py-3 text-left text-xs font-semibold
|
|
439
|
+
<tr className="border-b" style={{ borderColor: theme.border, backgroundColor: theme.muted }}>
|
|
440
|
+
<th className="px-4 py-3 text-left text-xs font-semibold uppercase" style={{ color: theme.mutedForeground }}>
|
|
430
441
|
Rank
|
|
431
442
|
</th>
|
|
432
|
-
<th className="px-4 py-3 text-left text-xs font-semibold
|
|
443
|
+
<th className="px-4 py-3 text-left text-xs font-semibold uppercase" style={{ color: theme.mutedForeground }}>
|
|
433
444
|
Wallet
|
|
434
445
|
</th>
|
|
435
|
-
<th className="px-4 py-3 text-right text-xs font-semibold
|
|
446
|
+
<th className="px-4 py-3 text-right text-xs font-semibold uppercase" style={{ color: theme.mutedForeground }}>
|
|
436
447
|
Points
|
|
437
448
|
</th>
|
|
438
|
-
<th className="px-4 py-3 text-center text-xs font-semibold
|
|
449
|
+
<th className="px-4 py-3 text-center text-xs font-semibold uppercase" style={{ color: theme.mutedForeground }}>
|
|
439
450
|
Badges
|
|
440
451
|
</th>
|
|
441
452
|
</tr>
|
|
@@ -446,6 +457,7 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
446
457
|
key={entry.rank}
|
|
447
458
|
entry={entry}
|
|
448
459
|
accentColor={accentColor}
|
|
460
|
+
theme={theme}
|
|
449
461
|
isCurrentUser={
|
|
450
462
|
walletAddress
|
|
451
463
|
? entry.walletAddress.toLowerCase() === walletAddress.toLowerCase()
|
|
@@ -465,15 +477,16 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
465
477
|
|
|
466
478
|
{/* Pagination */}
|
|
467
479
|
{!compactMode && data.length > itemsPerPage && (
|
|
468
|
-
<div className="px-6 py-4 border-t
|
|
469
|
-
<div className="text-sm
|
|
480
|
+
<div className="px-6 py-4 border-t flex items-center justify-between" style={{ borderColor: theme.border }}>
|
|
481
|
+
<div className="text-sm" style={{ color: theme.mutedForeground }}>
|
|
470
482
|
Showing {startIndex + 1} to {Math.min(endIndex, data.length)} of {data.length}
|
|
471
483
|
</div>
|
|
472
484
|
<div className="flex gap-2">
|
|
473
485
|
<button
|
|
474
486
|
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
|
475
487
|
disabled={currentPage === 1}
|
|
476
|
-
className="px-3 py-1.5 rounded-lg text-sm font-medium
|
|
488
|
+
className="px-3 py-1.5 rounded-lg text-sm font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
|
489
|
+
style={{ color: theme.mutedForeground }}
|
|
477
490
|
>
|
|
478
491
|
← Previous
|
|
479
492
|
</button>
|
|
@@ -484,18 +497,14 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
484
497
|
<button
|
|
485
498
|
key={pageNum}
|
|
486
499
|
onClick={() => setCurrentPage(pageNum)}
|
|
487
|
-
className=
|
|
488
|
-
currentPage === pageNum
|
|
489
|
-
? 'text-white'
|
|
490
|
-
: 'text-gray-400 hover:text-white hover:bg-gray-700'
|
|
491
|
-
}`}
|
|
500
|
+
className="px-3 py-1.5 rounded-lg text-sm font-medium transition-all"
|
|
492
501
|
style={
|
|
493
502
|
currentPage === pageNum
|
|
494
503
|
? {
|
|
495
504
|
backgroundColor: accentColor,
|
|
496
505
|
color: 'white',
|
|
497
506
|
}
|
|
498
|
-
: {}
|
|
507
|
+
: { color: theme.mutedForeground }
|
|
499
508
|
}
|
|
500
509
|
>
|
|
501
510
|
{pageNum}
|
|
@@ -506,7 +515,8 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
506
515
|
<button
|
|
507
516
|
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
|
|
508
517
|
disabled={currentPage === totalPages}
|
|
509
|
-
className="px-3 py-1.5 rounded-lg text-sm font-medium
|
|
518
|
+
className="px-3 py-1.5 rounded-lg text-sm font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
|
519
|
+
style={{ color: theme.mutedForeground }}
|
|
510
520
|
>
|
|
511
521
|
Next →
|
|
512
522
|
</button>
|
|
@@ -515,7 +525,7 @@ export const Leaderboard: React.FC<LeaderboardProps> = ({
|
|
|
515
525
|
)}
|
|
516
526
|
|
|
517
527
|
{/* Footer */}
|
|
518
|
-
<div className="px-6 py-3 border-t
|
|
528
|
+
<div className="px-6 py-3 border-t flex items-center justify-end text-xs" style={{ borderColor: theme.border, color: theme.mutedForeground }}>
|
|
519
529
|
<span className="flex items-center gap-1">
|
|
520
530
|
<ExternalLink className="w-3 h-3" />
|
|
521
531
|
@chainloyalty/react
|
package/src/RewardsDashboard.tsx
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
RefreshCw,
|
|
31
31
|
Lock,
|
|
32
32
|
} from 'lucide-react';
|
|
33
|
+
import { getThemeColors } from './theme';
|
|
33
34
|
|
|
34
35
|
// ============================================================================
|
|
35
36
|
// Types & Interfaces
|
|
@@ -244,34 +245,40 @@ const getRarityColor = (rarity: string): string => {
|
|
|
244
245
|
interface RewardHistoryProps {
|
|
245
246
|
items: RewardHistoryItem[];
|
|
246
247
|
accentColor: string;
|
|
248
|
+
theme: ReturnType<typeof getThemeColors>;
|
|
247
249
|
}
|
|
248
250
|
|
|
249
|
-
const RewardHistory: React.FC<RewardHistoryProps> = ({ items, accentColor }) => {
|
|
251
|
+
const RewardHistory: React.FC<RewardHistoryProps> = ({ items, accentColor, theme }) => {
|
|
250
252
|
return (
|
|
251
253
|
<div className="space-y-3">
|
|
252
254
|
{items.map((item) => (
|
|
253
255
|
<div
|
|
254
256
|
key={item.id}
|
|
255
|
-
className="flex items-start justify-between p-3 rounded-lg border
|
|
256
|
-
style={{
|
|
257
|
+
className="flex items-start justify-between p-3 rounded-lg border transition-colors hover:opacity-80"
|
|
258
|
+
style={{
|
|
259
|
+
borderColor: theme.border,
|
|
260
|
+
borderLeftColor: accentColor,
|
|
261
|
+
borderLeftWidth: '3px',
|
|
262
|
+
backgroundColor: 'transparent',
|
|
263
|
+
}}
|
|
257
264
|
>
|
|
258
265
|
<div className="flex-1">
|
|
259
266
|
<div className="flex items-center gap-2">
|
|
260
|
-
<span className="text-sm font-semibold
|
|
261
|
-
<span className="text-xs px-2 py-0.5 rounded-full
|
|
267
|
+
<span className="text-sm font-semibold" style={{ color: theme.foreground }}>{item.title}</span>
|
|
268
|
+
<span className="text-xs px-2 py-0.5 rounded-full" style={{ backgroundColor: theme.muted, color: theme.mutedForeground }}>
|
|
262
269
|
{item.type}
|
|
263
270
|
</span>
|
|
264
271
|
</div>
|
|
265
272
|
{item.description && (
|
|
266
|
-
<p className="text-xs
|
|
273
|
+
<p className="text-xs mt-1" style={{ color: theme.mutedForeground }}>{item.description}</p>
|
|
267
274
|
)}
|
|
268
|
-
<p className="text-xs
|
|
275
|
+
<p className="text-xs mt-1" style={{ color: theme.mutedForeground }}>{formatDate(item.timestamp)}</p>
|
|
269
276
|
</div>
|
|
270
277
|
<div className="ml-4 text-right">
|
|
271
278
|
<p className="text-lg font-bold" style={{ color: accentColor }}>
|
|
272
279
|
+{item.points}
|
|
273
280
|
</p>
|
|
274
|
-
<p className="text-xs
|
|
281
|
+
<p className="text-xs" style={{ color: theme.mutedForeground }}>points</p>
|
|
275
282
|
</div>
|
|
276
283
|
</div>
|
|
277
284
|
))}
|
|
@@ -285,10 +292,11 @@ const RewardHistory: React.FC<RewardHistoryProps> = ({ items, accentColor }) =>
|
|
|
285
292
|
|
|
286
293
|
interface BadgeGridProps {
|
|
287
294
|
badges: Badge[];
|
|
295
|
+
theme: ReturnType<typeof getThemeColors>;
|
|
288
296
|
compactMode?: boolean;
|
|
289
297
|
}
|
|
290
298
|
|
|
291
|
-
const BadgeGrid: React.FC<BadgeGridProps> = ({ badges, compactMode = false }) => {
|
|
299
|
+
const BadgeGrid: React.FC<BadgeGridProps> = ({ badges, theme, compactMode = false }) => {
|
|
292
300
|
const displayBadges = compactMode ? badges.slice(0, 4) : badges;
|
|
293
301
|
|
|
294
302
|
return (
|
|
@@ -301,18 +309,19 @@ const BadgeGrid: React.FC<BadgeGridProps> = ({ badges, compactMode = false }) =>
|
|
|
301
309
|
className="group relative p-3 rounded-lg border transition-all hover:scale-105 cursor-pointer"
|
|
302
310
|
style={{
|
|
303
311
|
borderColor: getRarityColor(badge.rarity),
|
|
304
|
-
backgroundColor:
|
|
312
|
+
backgroundColor: theme.card,
|
|
313
|
+
opacity: 0.9,
|
|
305
314
|
}}
|
|
306
315
|
title={`${badge.name} - ${badge.description}`}
|
|
307
316
|
>
|
|
308
317
|
<div className="text-3xl mb-1 text-center">{badge.icon}</div>
|
|
309
|
-
<p className="text-xs font-semibold text-
|
|
310
|
-
<p className="text-xs text-
|
|
318
|
+
<p className="text-xs font-semibold text-center truncate" style={{ color: theme.foreground }}>{badge.name}</p>
|
|
319
|
+
<p className="text-xs text-center mt-0.5" style={{ color: theme.mutedForeground }}>
|
|
311
320
|
{new Date(badge.earnedAt).toLocaleDateString()}
|
|
312
321
|
</p>
|
|
313
322
|
|
|
314
323
|
{/* Tooltip on hover */}
|
|
315
|
-
<div className="hidden group-hover:block absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 p-2
|
|
324
|
+
<div className="hidden group-hover:block absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 p-2 border rounded-lg text-xs w-32 text-center whitespace-normal z-10" style={{ backgroundColor: theme.muted, borderColor: theme.border, color: theme.mutedForeground }}>
|
|
316
325
|
{badge.description}
|
|
317
326
|
</div>
|
|
318
327
|
</div>
|
|
@@ -328,9 +337,10 @@ const BadgeGrid: React.FC<BadgeGridProps> = ({ badges, compactMode = false }) =>
|
|
|
328
337
|
interface TierCardProps {
|
|
329
338
|
tier: TierInfo;
|
|
330
339
|
accentColor: string;
|
|
340
|
+
theme: ReturnType<typeof getThemeColors>;
|
|
331
341
|
}
|
|
332
342
|
|
|
333
|
-
const TierCard: React.FC<TierCardProps> = ({ tier, accentColor }) => {
|
|
343
|
+
const TierCard: React.FC<TierCardProps> = ({ tier, accentColor, theme }) => {
|
|
334
344
|
const progressPercent = (tier.currentProgress / tier.nextMilestone) * 100;
|
|
335
345
|
|
|
336
346
|
return (
|
|
@@ -338,24 +348,24 @@ const TierCard: React.FC<TierCardProps> = ({ tier, accentColor }) => {
|
|
|
338
348
|
className="p-4 rounded-lg border"
|
|
339
349
|
style={{
|
|
340
350
|
borderColor: accentColor,
|
|
341
|
-
backgroundColor:
|
|
351
|
+
backgroundColor: theme.card,
|
|
342
352
|
}}
|
|
343
353
|
>
|
|
344
354
|
<div className="flex items-start justify-between mb-3">
|
|
345
355
|
<div className="flex items-center gap-2">
|
|
346
356
|
<span className="text-3xl">{tier.icon}</span>
|
|
347
357
|
<div>
|
|
348
|
-
<p className="text-sm font-semibold
|
|
349
|
-
<p className="text-xs
|
|
358
|
+
<p className="text-sm font-semibold" style={{ color: theme.foreground }}>{tier.name}</p>
|
|
359
|
+
<p className="text-xs" style={{ color: theme.mutedForeground }}>Level {tier.level}</p>
|
|
350
360
|
</div>
|
|
351
361
|
</div>
|
|
352
362
|
</div>
|
|
353
363
|
<div className="space-y-2">
|
|
354
|
-
<div className="flex justify-between text-xs
|
|
364
|
+
<div className="flex justify-between text-xs" style={{ color: theme.mutedForeground }}>
|
|
355
365
|
<span>{tier.currentProgress.toLocaleString()} points</span>
|
|
356
366
|
<span>{tier.nextMilestone.toLocaleString()} goal</span>
|
|
357
367
|
</div>
|
|
358
|
-
<div className="w-full
|
|
368
|
+
<div className="w-full rounded-full h-2 overflow-hidden" style={{ backgroundColor: theme.muted }}>
|
|
359
369
|
<div
|
|
360
370
|
className="h-full rounded-full transition-all duration-500"
|
|
361
371
|
style={{
|
|
@@ -387,9 +397,13 @@ export const RewardsDashboard: React.FC<RewardsDashboardProps> = ({
|
|
|
387
397
|
const [error, setError] = useState<string | null>(null);
|
|
388
398
|
const [copied, setCopied] = useState(false);
|
|
389
399
|
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
|
|
400
|
+
// Get theme colors from host app's CSS variables
|
|
401
|
+
const theme = getThemeColors();
|
|
402
|
+
|
|
403
|
+
// Override with customColors if provided (backward compatibility)
|
|
404
|
+
const accentColor = customColors.accent1 || theme.accent;
|
|
405
|
+
const accentColor2 = customColors.accent2 || theme.primary;
|
|
406
|
+
const backgroundColor = customColors.background || theme.card;
|
|
393
407
|
|
|
394
408
|
// Fetch data when wallet changes
|
|
395
409
|
useEffect(() => {
|
|
@@ -433,21 +447,22 @@ export const RewardsDashboard: React.FC<RewardsDashboardProps> = ({
|
|
|
433
447
|
if (!walletAddress) {
|
|
434
448
|
return (
|
|
435
449
|
<div
|
|
436
|
-
className="p-6 rounded-lg border
|
|
437
|
-
style={{ backgroundColor }}
|
|
450
|
+
className="p-6 rounded-lg border max-w-2xl"
|
|
451
|
+
style={{ backgroundColor: theme.card, borderColor: theme.border }}
|
|
438
452
|
>
|
|
439
453
|
<div className="text-center">
|
|
440
|
-
<Lock className="w-12 h-12 mx-auto mb-4
|
|
441
|
-
<h2 className="text-xl font-bold
|
|
442
|
-
<p className="
|
|
454
|
+
<Lock className="w-12 h-12 mx-auto mb-4" style={{ color: theme.mutedForeground }} />
|
|
455
|
+
<h2 className="text-xl font-bold mb-2" style={{ color: theme.foreground }}>Connect Your Wallet</h2>
|
|
456
|
+
<p className="mb-6" style={{ color: theme.mutedForeground }}>
|
|
443
457
|
Connect your wallet to view your rewards, badges, and track your achievements.
|
|
444
458
|
</p>
|
|
445
459
|
{onConnectWallet && (
|
|
446
460
|
<button
|
|
447
461
|
onClick={onConnectWallet}
|
|
448
|
-
className="inline-flex items-center gap-2 px-6 py-2 rounded-lg font-semibold
|
|
462
|
+
className="inline-flex items-center gap-2 px-6 py-2 rounded-lg font-semibold transition-all hover:opacity-90"
|
|
449
463
|
style={{
|
|
450
464
|
backgroundColor: accentColor,
|
|
465
|
+
color: '#fff',
|
|
451
466
|
}}
|
|
452
467
|
>
|
|
453
468
|
<Wallet className="w-5 h-5" />
|
|
@@ -463,13 +478,13 @@ export const RewardsDashboard: React.FC<RewardsDashboardProps> = ({
|
|
|
463
478
|
if (loading && !data) {
|
|
464
479
|
return (
|
|
465
480
|
<div
|
|
466
|
-
className="p-6 rounded-lg border
|
|
467
|
-
style={{ backgroundColor }}
|
|
481
|
+
className="p-6 rounded-lg border max-w-2xl"
|
|
482
|
+
style={{ backgroundColor: theme.card, borderColor: theme.border }}
|
|
468
483
|
>
|
|
469
484
|
<div className="space-y-4">
|
|
470
|
-
<div className="h-20
|
|
471
|
-
<div className="h-40
|
|
472
|
-
<div className="h-32
|
|
485
|
+
<div className="h-20 rounded-lg animate-pulse" style={{ backgroundColor: theme.muted }} />
|
|
486
|
+
<div className="h-40 rounded-lg animate-pulse" style={{ backgroundColor: theme.muted }} />
|
|
487
|
+
<div className="h-32 rounded-lg animate-pulse" style={{ backgroundColor: theme.muted }} />
|
|
473
488
|
</div>
|
|
474
489
|
</div>
|
|
475
490
|
);
|
|
@@ -479,10 +494,10 @@ export const RewardsDashboard: React.FC<RewardsDashboardProps> = ({
|
|
|
479
494
|
if (error && !data) {
|
|
480
495
|
return (
|
|
481
496
|
<div
|
|
482
|
-
className="p-6 rounded-lg border
|
|
483
|
-
style={{ backgroundColor }}
|
|
497
|
+
className="p-6 rounded-lg border max-w-2xl"
|
|
498
|
+
style={{ backgroundColor: theme.card, borderColor: '#dc2626' }}
|
|
484
499
|
>
|
|
485
|
-
<p
|
|
500
|
+
<p style={{ color: '#ef4444' }}>Failed to load rewards: {error}</p>
|
|
486
501
|
<button
|
|
487
502
|
onClick={handleRefresh}
|
|
488
503
|
className="mt-4 px-4 py-2 rounded-lg text-white text-sm transition-all"
|
|
@@ -504,28 +519,30 @@ export const RewardsDashboard: React.FC<RewardsDashboardProps> = ({
|
|
|
504
519
|
{/* Header */}
|
|
505
520
|
<div className="p-6 border-b border-gray-700">
|
|
506
521
|
<div className="flex items-center justify-between mb-4">
|
|
507
|
-
<h2 className="text-2xl font-bold
|
|
522
|
+
<h2 className="text-2xl font-bold" style={{ color: theme.foreground }}>Rewards Dashboard</h2>
|
|
508
523
|
<button
|
|
509
524
|
onClick={handleRefresh}
|
|
510
525
|
disabled={loading}
|
|
511
|
-
className="p-2 rounded-lg
|
|
526
|
+
className="p-2 rounded-lg transition-all disabled:opacity-50"
|
|
527
|
+
style={{ color: theme.mutedForeground }}
|
|
512
528
|
>
|
|
513
529
|
<RefreshCw className={`w-5 h-5 ${loading ? 'animate-spin' : ''}`} />
|
|
514
530
|
</button>
|
|
515
531
|
</div>
|
|
516
532
|
|
|
517
533
|
{/* Wallet Display */}
|
|
518
|
-
<div className="flex items-center justify-between p-3 rounded-lg
|
|
534
|
+
<div className="flex items-center justify-between p-3 rounded-lg border" style={{ backgroundColor: theme.muted, borderColor: theme.border }}>
|
|
519
535
|
<div className="flex items-center gap-2">
|
|
520
536
|
<Wallet className="w-5 h-5" style={{ color: accentColor }} />
|
|
521
537
|
<div>
|
|
522
|
-
<p className="text-xs
|
|
523
|
-
<p className="text-sm font-mono font-semibold
|
|
538
|
+
<p className="text-xs" style={{ color: theme.mutedForeground }}>Connected Wallet</p>
|
|
539
|
+
<p className="text-sm font-mono font-semibold" style={{ color: theme.foreground }}>{shortenAddress(walletAddress)}</p>
|
|
524
540
|
</div>
|
|
525
541
|
</div>
|
|
526
542
|
<button
|
|
527
543
|
onClick={handleCopyAddress}
|
|
528
|
-
className="p-2 rounded-lg
|
|
544
|
+
className="p-2 rounded-lg transition-all"
|
|
545
|
+
style={{ color: theme.mutedForeground }}
|
|
529
546
|
title={walletAddress}
|
|
530
547
|
>
|
|
531
548
|
{copied ? <CheckCircle className="w-5 h-5" style={{ color: accentColor2 }} /> : <Copy className="w-5 h-5" />}
|
|
@@ -534,28 +551,28 @@ export const RewardsDashboard: React.FC<RewardsDashboardProps> = ({
|
|
|
534
551
|
</div>
|
|
535
552
|
|
|
536
553
|
{/* Main Content */}
|
|
537
|
-
<div className={`p-6 space-y-6 ${compactMode ? 'max-w-3xl' : ''}`} style={{backgroundColor:
|
|
554
|
+
<div className={`p-6 space-y-6 ${compactMode ? 'max-w-3xl' : ''}`} style={{backgroundColor: theme.background}}>
|
|
538
555
|
{/* Points Display */}
|
|
539
556
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
540
557
|
<div
|
|
541
558
|
className="p-6 rounded-lg border text-center"
|
|
542
559
|
style={{
|
|
543
560
|
borderColor: accentColor,
|
|
544
|
-
backgroundColor:
|
|
561
|
+
backgroundColor: theme.card,
|
|
545
562
|
}}
|
|
546
563
|
>
|
|
547
|
-
<p className="text-sm
|
|
564
|
+
<p className="text-sm mb-2" style={{ color: theme.mutedForeground }}>Total Points</p>
|
|
548
565
|
<p className="text-4xl font-bold" style={{ color: accentColor }}>
|
|
549
566
|
{data.totalPoints.toLocaleString()}
|
|
550
567
|
</p>
|
|
551
|
-
<div className="flex items-center justify-center gap-1 mt-2 text-xs
|
|
568
|
+
<div className="flex items-center justify-center gap-1 mt-2 text-xs" style={{ color: theme.mutedForeground }}>
|
|
552
569
|
<TrendingUp className="w-4 h-4" />
|
|
553
570
|
<span>Keep earning rewards</span>
|
|
554
571
|
</div>
|
|
555
572
|
</div>
|
|
556
573
|
|
|
557
574
|
{!hideTier && (
|
|
558
|
-
<TierCard tier={data.tier} accentColor={accentColor2} />
|
|
575
|
+
<TierCard tier={data.tier} accentColor={accentColor2} theme={theme} />
|
|
559
576
|
)}
|
|
560
577
|
</div>
|
|
561
578
|
|
|
@@ -564,11 +581,11 @@ export const RewardsDashboard: React.FC<RewardsDashboardProps> = ({
|
|
|
564
581
|
<div>
|
|
565
582
|
<div className="flex items-center gap-2 mb-4">
|
|
566
583
|
<Award className="w-5 h-5" style={{ color: accentColor }} />
|
|
567
|
-
<h3 className="text-lg font-semibold
|
|
584
|
+
<h3 className="text-lg font-semibold" style={{ color: theme.foreground }}>
|
|
568
585
|
Badges ({data.badges.length})
|
|
569
586
|
</h3>
|
|
570
587
|
</div>
|
|
571
|
-
<BadgeGrid badges={data.badges} compactMode={compactMode} />
|
|
588
|
+
<BadgeGrid badges={data.badges} theme={theme} compactMode={compactMode} />
|
|
572
589
|
</div>
|
|
573
590
|
)}
|
|
574
591
|
|
|
@@ -577,18 +594,19 @@ export const RewardsDashboard: React.FC<RewardsDashboardProps> = ({
|
|
|
577
594
|
<div>
|
|
578
595
|
<div className="flex items-center gap-2 mb-4">
|
|
579
596
|
<Zap className="w-5 h-5" style={{ color: accentColor2 }} />
|
|
580
|
-
<h3 className="text-lg font-semibold
|
|
597
|
+
<h3 className="text-lg font-semibold" style={{ color: theme.foreground }}>Recent Rewards</h3>
|
|
581
598
|
</div>
|
|
582
599
|
<RewardHistory
|
|
583
600
|
items={compactMode ? data.rewardHistory.slice(0, 3) : data.rewardHistory}
|
|
584
601
|
accentColor={accentColor}
|
|
602
|
+
theme={theme}
|
|
585
603
|
/>
|
|
586
604
|
</div>
|
|
587
605
|
)}
|
|
588
606
|
</div>
|
|
589
607
|
|
|
590
608
|
{/* Footer */}
|
|
591
|
-
<div className="px-6 py-3 border-t
|
|
609
|
+
<div className="px-6 py-3 border-t flex items-center justify-between text-xs" style={{ borderColor: theme.border, color: theme.mutedForeground }}>
|
|
592
610
|
<span>Last updated: {formatDate(data.lastUpdated)}</span>
|
|
593
611
|
<span className="flex items-center gap-1">
|
|
594
612
|
<ExternalLink className="w-3 h-3" />
|