@devrongx/games 0.2.0 → 0.2.1
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/package.json +1 -1
- package/src/matches/MatchCalendar.tsx +133 -93
package/package.json
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
import { useRef } from "react";
|
|
5
5
|
import { motion } from "framer-motion";
|
|
6
|
-
import { Calendar, MapPin, Loader2 } from "lucide-react";
|
|
6
|
+
import { Calendar, MapPin, Loader2, Zap } from "lucide-react";
|
|
7
7
|
import { useTDMatches } from "./useTDMatches";
|
|
8
8
|
import type { ITDMatch } from "./types";
|
|
9
9
|
|
|
10
|
-
const OUTFIT = { fontFamily: "Outfit, sans-serif" };
|
|
11
|
-
|
|
12
10
|
// TD match status enum values
|
|
13
11
|
const MATCH_STATUS = {
|
|
14
12
|
UPCOMING: 1,
|
|
@@ -16,20 +14,12 @@ const MATCH_STATUS = {
|
|
|
16
14
|
COMPLETED: 5,
|
|
17
15
|
} as const;
|
|
18
16
|
|
|
19
|
-
function formatMatchDate(iso: string): {
|
|
17
|
+
function formatMatchDate(iso: string): { dayMonth: string; time: string } {
|
|
20
18
|
const d = new Date(iso);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
weekday: d.toLocaleString("en-US", { weekday: "short" }).toUpperCase(),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getStatusLabel(match: ITDMatch): { text: string; color: string } {
|
|
30
|
-
if (match.status === MATCH_STATUS.LIVE) return { text: "LIVE", color: "#f83cc5" };
|
|
31
|
-
if (match.status === MATCH_STATUS.COMPLETED) return { text: "COMPLETED", color: "rgba(255,255,255,0.35)" };
|
|
32
|
-
return { text: "UPCOMING", color: "#22E3E8" };
|
|
19
|
+
const day = d.getDate();
|
|
20
|
+
const month = d.toLocaleString("en-US", { month: "short" }).toUpperCase();
|
|
21
|
+
const time = d.toLocaleString("en-US", { hour: "numeric", minute: "2-digit", hour12: true });
|
|
22
|
+
return { dayMonth: `${day} ${month}`, time };
|
|
33
23
|
}
|
|
34
24
|
|
|
35
25
|
function getTeamShortName(match: ITDMatch, team: "a" | "b"): string {
|
|
@@ -39,81 +29,139 @@ function getTeamShortName(match: ITDMatch, team: "a" | "b"): string {
|
|
|
39
29
|
|
|
40
30
|
interface MatchCardProps {
|
|
41
31
|
match: ITDMatch;
|
|
32
|
+
index: number;
|
|
42
33
|
}
|
|
43
34
|
|
|
44
|
-
function MatchCard({ match }: MatchCardProps) {
|
|
45
|
-
const status = getStatusLabel(match);
|
|
35
|
+
function MatchCard({ match, index }: MatchCardProps) {
|
|
46
36
|
const date = match.scheduled_start_at ? formatMatchDate(match.scheduled_start_at) : null;
|
|
37
|
+
const isLive = match.status === MATCH_STATUS.LIVE;
|
|
47
38
|
const isCompleted = match.status === MATCH_STATUS.COMPLETED;
|
|
39
|
+
const isUpcoming = match.status === MATCH_STATUS.UPCOMING;
|
|
40
|
+
const venue = match.venue?.city || match.venue?.name;
|
|
48
41
|
|
|
49
42
|
return (
|
|
50
|
-
<div
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}}
|
|
43
|
+
<motion.div
|
|
44
|
+
initial={{ opacity: 0, y: 12 }}
|
|
45
|
+
animate={{ opacity: 1, y: 0 }}
|
|
46
|
+
transition={{ duration: 0.35, delay: index * 0.04 }}
|
|
47
|
+
className="shrink-0"
|
|
48
|
+
style={{ scrollSnapAlign: "start" }}
|
|
57
49
|
>
|
|
58
50
|
<div
|
|
59
|
-
className="
|
|
60
|
-
style={{
|
|
51
|
+
className="relative w-[140px] rounded-2xl overflow-hidden"
|
|
52
|
+
style={{
|
|
53
|
+
background: isLive
|
|
54
|
+
? "linear-gradient(160deg, rgba(248,60,197,0.15), rgba(248,60,197,0.03))"
|
|
55
|
+
: isCompleted
|
|
56
|
+
? "rgba(255,255,255,0.03)"
|
|
57
|
+
: "linear-gradient(160deg, rgba(34,227,232,0.08), rgba(153,69,255,0.04))",
|
|
58
|
+
border: isLive
|
|
59
|
+
? "1px solid rgba(248,60,197,0.3)"
|
|
60
|
+
: isCompleted
|
|
61
|
+
? "1px solid rgba(255,255,255,0.06)"
|
|
62
|
+
: "1px solid rgba(34,227,232,0.12)",
|
|
63
|
+
}}
|
|
61
64
|
>
|
|
62
|
-
{/*
|
|
63
|
-
{
|
|
64
|
-
<div className="flex items-center gap-1
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
{/* Live pulse indicator */}
|
|
66
|
+
{isLive && (
|
|
67
|
+
<div className="absolute top-2 right-2 flex items-center gap-1">
|
|
68
|
+
<span className="relative flex h-2 w-2">
|
|
69
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#f83cc5] opacity-75" />
|
|
70
|
+
<span className="relative inline-flex rounded-full h-2 w-2 bg-[#f83cc5]" />
|
|
68
71
|
</span>
|
|
69
72
|
</div>
|
|
70
73
|
)}
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
<div className="flex flex-col items-center px-3 pt-3 pb-2.5">
|
|
76
|
+
{/* Date */}
|
|
77
|
+
{date && (
|
|
78
|
+
<div className="flex flex-col items-center mb-2.5">
|
|
79
|
+
<span
|
|
80
|
+
className="text-[11px] font-semibold tracking-wide"
|
|
81
|
+
style={{
|
|
82
|
+
fontFamily: "Outfit, sans-serif",
|
|
83
|
+
color: isLive ? "#f83cc5" : isCompleted ? "rgba(255,255,255,0.25)" : "rgba(34,227,232,0.7)",
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
{date.dayMonth}
|
|
87
|
+
</span>
|
|
88
|
+
<span
|
|
89
|
+
className="text-[10px] tracking-wide"
|
|
90
|
+
style={{
|
|
91
|
+
fontFamily: "Outfit, sans-serif",
|
|
92
|
+
color: isCompleted ? "rgba(255,255,255,0.2)" : "rgba(255,255,255,0.35)",
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
{date.time}
|
|
96
|
+
</span>
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{/* Teams */}
|
|
101
|
+
<div className="flex items-center gap-1.5 w-full justify-center">
|
|
87
102
|
<span
|
|
88
|
-
className="text-[
|
|
89
|
-
style={
|
|
103
|
+
className="barlowcondensedBold text-[22px] leading-none"
|
|
104
|
+
style={{ color: isCompleted ? "rgba(255,255,255,0.35)" : "#fff" }}
|
|
90
105
|
>
|
|
91
|
-
{match
|
|
106
|
+
{getTeamShortName(match, "a")}
|
|
107
|
+
</span>
|
|
108
|
+
<span
|
|
109
|
+
className="text-[9px] barlowCondensedSemiBold"
|
|
110
|
+
style={{ color: isCompleted ? "rgba(255,255,255,0.15)" : "rgba(255,255,255,0.25)" }}
|
|
111
|
+
>
|
|
112
|
+
v
|
|
113
|
+
</span>
|
|
114
|
+
<span
|
|
115
|
+
className="barlowcondensedBold text-[22px] leading-none"
|
|
116
|
+
style={{ color: isCompleted ? "rgba(255,255,255,0.35)" : "#fff" }}
|
|
117
|
+
>
|
|
118
|
+
{getTeamShortName(match, "b")}
|
|
92
119
|
</span>
|
|
93
120
|
</div>
|
|
94
|
-
)}
|
|
95
|
-
|
|
96
|
-
{/* Status badge */}
|
|
97
|
-
<div
|
|
98
|
-
className="mt-2 px-2 py-0.5 rounded-full"
|
|
99
|
-
style={{ background: `${status.color}15` }}
|
|
100
|
-
>
|
|
101
|
-
<span
|
|
102
|
-
className="text-[9px] barlowcondensedBold tracking-wider"
|
|
103
|
-
style={{ color: status.color }}
|
|
104
|
-
>
|
|
105
|
-
{status.text}
|
|
106
|
-
</span>
|
|
107
|
-
</div>
|
|
108
121
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
122
|
+
{/* Venue */}
|
|
123
|
+
{venue && (
|
|
124
|
+
<div className="flex items-center gap-1 mt-1.5">
|
|
125
|
+
<MapPin size={8} className="shrink-0" style={{ color: "rgba(255,255,255,0.2)" }} />
|
|
126
|
+
<span
|
|
127
|
+
className="text-[9px] truncate max-w-[110px]"
|
|
128
|
+
style={{ fontFamily: "Outfit, sans-serif", color: "rgba(255,255,255,0.2)" }}
|
|
129
|
+
>
|
|
130
|
+
{venue}
|
|
131
|
+
</span>
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{/* Status */}
|
|
136
|
+
<div className="mt-2">
|
|
137
|
+
{isLive ? (
|
|
138
|
+
<div className="flex items-center gap-1 px-2 py-0.5 rounded-full" style={{ background: "rgba(248,60,197,0.15)" }}>
|
|
139
|
+
<Zap size={8} style={{ color: "#f83cc5" }} />
|
|
140
|
+
<span className="text-[8px] barlowcondensedBold tracking-widest" style={{ color: "#f83cc5" }}>
|
|
141
|
+
LIVE
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
) : isCompleted ? (
|
|
145
|
+
<span
|
|
146
|
+
className="text-[9px] tracking-wide"
|
|
147
|
+
style={{ fontFamily: "Outfit, sans-serif", color: "rgba(255,255,255,0.2)" }}
|
|
148
|
+
>
|
|
149
|
+
{match.winner ? `${match.winner.short_name || match.winner.name} won` : "Completed"}
|
|
150
|
+
</span>
|
|
151
|
+
) : isUpcoming ? (
|
|
152
|
+
<div
|
|
153
|
+
className="px-2 py-0.5 rounded-full"
|
|
154
|
+
style={{ background: "rgba(34,227,232,0.08)", border: "1px solid rgba(34,227,232,0.15)" }}
|
|
155
|
+
>
|
|
156
|
+
<span className="text-[8px] barlowcondensedBold tracking-widest" style={{ color: "rgba(34,227,232,0.6)" }}>
|
|
157
|
+
UPCOMING
|
|
158
|
+
</span>
|
|
159
|
+
</div>
|
|
160
|
+
) : null}
|
|
113
161
|
</div>
|
|
114
|
-
|
|
162
|
+
</div>
|
|
115
163
|
</div>
|
|
116
|
-
</div>
|
|
164
|
+
</motion.div>
|
|
117
165
|
);
|
|
118
166
|
}
|
|
119
167
|
|
|
@@ -125,13 +173,13 @@ export function MatchCalendar({ tournamentId }: MatchCalendarProps) {
|
|
|
125
173
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
126
174
|
const { matches, isLoading, error } = useTDMatches({
|
|
127
175
|
tournamentId,
|
|
128
|
-
limit:
|
|
176
|
+
limit: 50,
|
|
129
177
|
});
|
|
130
178
|
|
|
131
179
|
if (isLoading) {
|
|
132
180
|
return (
|
|
133
|
-
<div className="flex items-center justify-center py-
|
|
134
|
-
<Loader2 size={
|
|
181
|
+
<div className="flex items-center justify-center py-4">
|
|
182
|
+
<Loader2 size={16} className="animate-spin" style={{ color: "rgba(255,255,255,0.2)" }} />
|
|
135
183
|
</div>
|
|
136
184
|
);
|
|
137
185
|
}
|
|
@@ -139,35 +187,27 @@ export function MatchCalendar({ tournamentId }: MatchCalendarProps) {
|
|
|
139
187
|
if (error || matches.length === 0) return null;
|
|
140
188
|
|
|
141
189
|
return (
|
|
142
|
-
<div className="relative z-30 w-full mb-
|
|
190
|
+
<div className="relative z-30 w-full mb-2 mt-2">
|
|
143
191
|
{/* Section header */}
|
|
144
|
-
<div className="flex items-center gap-2 px-5 mb-
|
|
145
|
-
<Calendar size={
|
|
146
|
-
<span className="barlowCondensedSemiBold text-[
|
|
147
|
-
|
|
192
|
+
<div className="flex items-center gap-2 px-5 mb-2.5">
|
|
193
|
+
<Calendar size={12} style={{ color: "rgba(34,227,232,0.6)" }} />
|
|
194
|
+
<span className="barlowCondensedSemiBold text-[13px] tracking-wider" style={{ color: "rgba(255,255,255,0.45)" }}>
|
|
195
|
+
MATCHES
|
|
148
196
|
</span>
|
|
149
|
-
<div className="flex-1 h-px
|
|
150
|
-
<span className="text-[
|
|
151
|
-
{matches.length}
|
|
197
|
+
<div className="flex-1 h-px" style={{ background: "rgba(255,255,255,0.04)" }} />
|
|
198
|
+
<span className="text-[10px]" style={{ fontFamily: "Outfit, sans-serif", color: "rgba(255,255,255,0.2)" }}>
|
|
199
|
+
{matches.length}
|
|
152
200
|
</span>
|
|
153
201
|
</div>
|
|
154
202
|
|
|
155
203
|
{/* Scrollable match strip */}
|
|
156
204
|
<div
|
|
157
205
|
ref={scrollRef}
|
|
158
|
-
className="flex gap-
|
|
206
|
+
className="flex gap-2.5 overflow-x-auto px-5 pb-2 scrollbar-hidden"
|
|
159
207
|
style={{ scrollSnapType: "x mandatory", WebkitOverflowScrolling: "touch" }}
|
|
160
208
|
>
|
|
161
|
-
{matches.map((match) => (
|
|
162
|
-
<
|
|
163
|
-
key={match.id}
|
|
164
|
-
initial={{ opacity: 0, scale: 0.95 }}
|
|
165
|
-
animate={{ opacity: 1, scale: 1 }}
|
|
166
|
-
transition={{ duration: 0.3 }}
|
|
167
|
-
style={{ scrollSnapAlign: "start" }}
|
|
168
|
-
>
|
|
169
|
-
<MatchCard match={match} />
|
|
170
|
-
</motion.div>
|
|
209
|
+
{matches.map((match, i) => (
|
|
210
|
+
<MatchCard key={match.id} match={match} index={i} />
|
|
171
211
|
))}
|
|
172
212
|
</div>
|
|
173
213
|
</div>
|