1
1
"use client"
2
2
3
- import { useEffect , useState } from "react"
3
+ import { useCallback , useEffect , useMemo , useState } from "react"
4
+ import { useLocale } from "next-intl"
4
5
5
6
import { Image } from "@/components/Image"
6
7
import { ButtonLink } from "@/components/ui/buttons/Button"
@@ -16,45 +17,39 @@ import {
16
17
import { cn } from "@/lib/utils/cn"
17
18
import { formatDate } from "@/lib/utils/date"
18
19
19
- import { releasesData } from "@/data/roadmap/releases"
20
+ import { Release , releasesData } from "@/data/roadmap/releases"
20
21
21
- const findLatestReleaseIndex = ( ) => {
22
- const today = new Date ( )
23
- const twoMonthsFromNow = new Date ( )
24
- twoMonthsFromNow . setMonth ( today . getMonth ( ) + 2 )
22
+ const ReleaseCarousel = ( ) => {
23
+ const locale = useLocale ( )
25
24
26
- // First try to find a release within the next 2 months
27
- const upcomingReleaseIndex = releasesData . findIndex ( ( release ) => {
28
- const releaseDate = new Date ( release . releaseDate )
29
- return releaseDate > today && releaseDate <= twoMonthsFromNow
30
- } )
25
+ const [ api1 , setApi1 ] = useState < CarouselApi > ( )
26
+ const [ api2 , setApi2 ] = useState < CarouselApi > ( )
31
27
32
- // If no upcoming release found, find the most recent release up to today
33
- if ( upcomingReleaseIndex === - 1 ) {
34
- const pastReleases = releasesData . filter (
35
- ( release ) => new Date ( release . releaseDate ) <= today
36
- )
37
- if ( pastReleases . length > 0 ) {
38
- const mostRecentRelease = pastReleases [ pastReleases . length - 1 ]
39
- return releasesData . findIndex (
40
- ( release ) => release . releaseDate === mostRecentRelease . releaseDate
41
- )
42
- }
43
- }
28
+ const startIndex = useMemo ( ( ) => {
29
+ const now = new Date ( )
44
30
45
- return upcomingReleaseIndex
46
- }
31
+ // Production: has a releaseDate in the past
32
+ const productionReleases = releasesData . filter ( ( release ) => {
33
+ if ( ! ( "releaseDate" in release ) || ! release . releaseDate ) return false
34
+ const releaseDate = new Date ( release . releaseDate )
35
+ return releaseDate <= now
36
+ } )
47
37
48
- const ReleaseCarousel = ( ) => {
49
- const todayDate = new Date ( )
50
- const twoMonthsFromNow = new Date ( )
51
- twoMonthsFromNow . setMonth ( todayDate . getMonth ( ) + 2 )
38
+ // Upcoming: has a releaseDate, but is in the future
39
+ const upcomingReleases = releasesData . filter ( ( release ) => {
40
+ if ( ! ( "releaseDate" in release ) || ! release . releaseDate ) return false
41
+ const releaseDate = new Date ( release . releaseDate )
42
+ return releaseDate > now
43
+ } )
52
44
53
- const [ api1 , setApi1 ] = useState < CarouselApi > ( )
54
- const [ api2 , setApi2 ] = useState < CarouselApi > ( )
55
- const [ currentIndex , setCurrentIndex ] = useState ( ( ) =>
56
- findLatestReleaseIndex ( )
57
- )
45
+ // If upcoming releases exist, start index after production releases
46
+ if ( upcomingReleases . length > 0 ) return productionReleases . length
47
+
48
+ // If no upcoming releases, start at the last production release
49
+ return productionReleases . length - 1
50
+ } , [ ] )
51
+
52
+ const [ currentIndex , setCurrentIndex ] = useState ( startIndex )
58
53
59
54
useEffect ( ( ) => {
60
55
if ( ! api1 || ! api2 ) {
@@ -72,6 +67,27 @@ const ReleaseCarousel = () => {
72
67
} )
73
68
} , [ api1 , api2 ] )
74
69
70
+ const getStatus = useCallback ( ( release : Release ) => {
71
+ if ( ! ( "releaseDate" in release ) || ! release . releaseDate ) return "dev"
72
+ if ( new Date ( release . releaseDate ) <= new Date ( ) ) return "prod"
73
+ return "soon"
74
+ } , [ ] )
75
+
76
+ const getDisplayDate = ( release : Release ) : string => {
77
+ if ( ! ( "releaseDate" in release || "plannedReleaseYear" in release ) )
78
+ return ""
79
+
80
+ if ( "plannedReleaseYear" in release && release . plannedReleaseYear )
81
+ return new Intl . DateTimeFormat ( locale , {
82
+ year : "numeric" ,
83
+ } ) . format ( new Date ( Number ( release . plannedReleaseYear ) , 0 , 1 ) )
84
+
85
+ if ( "releaseDate" in release && release . releaseDate )
86
+ return formatDate ( release . releaseDate )
87
+
88
+ return ""
89
+ }
90
+
75
91
return (
76
92
< div className = "w-full max-w-[100vw] overflow-hidden" >
77
93
< div className = "mx-auto w-full max-w-screen-2xl px-4 sm:px-6" >
@@ -85,29 +101,21 @@ const ReleaseCarousel = () => {
85
101
align : "center" ,
86
102
containScroll : false ,
87
103
loop : false ,
88
- startIndex : findLatestReleaseIndex ( ) ,
104
+ startIndex,
89
105
} }
90
106
>
91
107
< CarouselContent >
92
108
{ releasesData . map ( ( release , index ) => {
93
- const releaseDate = new Date ( release . releaseDate )
94
- const nextRelease =
95
- releaseDate > todayDate && releaseDate <= twoMonthsFromNow
96
- const labelType =
97
- releaseDate < todayDate
98
- ? 1
99
- : releaseDate < twoMonthsFromNow
100
- ? 2
101
- : 3
102
-
109
+ const status = getStatus ( release )
110
+ const displayDate = getDisplayDate ( release )
103
111
return (
104
112
< CarouselItem
105
113
key = { release . releaseName }
106
114
className = "w-full md:basis-1/3"
107
115
>
108
116
< div className = "flex w-full flex-col items-center justify-center gap-3" >
109
117
< div className = "mb-3 !h-6" >
110
- { labelType === 1 && (
118
+ { status === "prod" && (
111
119
< div
112
120
className = { cn (
113
121
"w-fit rounded-lg bg-primary-low-contrast px-2 py-1" ,
@@ -117,7 +125,7 @@ const ReleaseCarousel = () => {
117
125
< p className = "text-sm font-bold" > In production</ p >
118
126
</ div >
119
127
) }
120
- { labelType === 2 && (
128
+ { status === "soon" && (
121
129
< div
122
130
className = { cn (
123
131
"w-fit rounded-lg bg-warning-light px-2 py-1" ,
@@ -129,7 +137,7 @@ const ReleaseCarousel = () => {
129
137
</ p >
130
138
</ div >
131
139
) }
132
- { labelType === 3 && (
140
+ { status === "dev" && (
133
141
< div
134
142
className = { cn (
135
143
"w-fit rounded-lg bg-card-gradient-secondary-hover px-2 py-1" ,
@@ -142,14 +150,15 @@ const ReleaseCarousel = () => {
142
150
</ div >
143
151
) }
144
152
</ div >
153
+ { /* Line-circle-line decoration —•— */ }
145
154
< div className = "flex w-full items-center justify-center text-center" >
146
155
< div
147
156
className = { cn (
148
157
"flex h-1 flex-1" ,
149
158
index !== 0
150
- ? nextRelease
159
+ ? status === "soon"
151
160
? "bg-gradient-to-r from-primary to-primary-low-contrast"
152
- : releaseDate . getTime ( ) < todayDate . getTime ( )
161
+ : status === "prod"
153
162
? "bg-primary"
154
163
: "bg-primary-low-contrast"
155
164
: "bg-transparent"
@@ -158,21 +167,21 @@ const ReleaseCarousel = () => {
158
167
< div
159
168
className = { cn (
160
169
"h-7 w-7 rounded-full" ,
161
- releaseDate . getTime ( ) < todayDate . getTime ( )
170
+ status === "prod"
162
171
? "bg-primary"
163
172
: "bg-primary-low-contrast" ,
164
- nextRelease &&
173
+ status === "soon" &&
165
174
"border-2 border-primary bg-background"
166
175
) }
167
176
/>
168
177
< div
169
178
className = { cn (
170
179
"flex h-1 flex-1" ,
171
- index !== releasesData . length - 1
172
- ? index < findLatestReleaseIndex ( )
173
- ? "bg-primary"
174
- : "bg-primary-low-contrast"
175
- : "bg-transparent"
180
+ index < startIndex
181
+ ? "bg-primary"
182
+ : "bg-primary-low-contrast" ,
183
+ index === releasesData . length - 1 &&
184
+ "bg-transparent"
176
185
) }
177
186
/>
178
187
</ div >
@@ -181,7 +190,7 @@ const ReleaseCarousel = () => {
181
190
{ release . releaseName }
182
191
</ p >
183
192
< p className = "font-mono text-sm text-body-medium" >
184
- { formatDate ( release . releaseDate ) }
193
+ { displayDate }
185
194
</ p >
186
195
</ div >
187
196
</ div >
@@ -203,7 +212,7 @@ const ReleaseCarousel = () => {
203
212
align : "center" ,
204
213
containScroll : false ,
205
214
loop : false ,
206
- startIndex : findLatestReleaseIndex ( ) ,
215
+ startIndex,
207
216
} }
208
217
>
209
218
< CarouselContent >
@@ -225,9 +234,7 @@ const ReleaseCarousel = () => {
225
234
< h2 className = "text-4xl font-bold lg:text-6xl" >
226
235
{ release . releaseName }
227
236
</ h2 >
228
- < p className = "text-md" >
229
- { formatDate ( release . releaseDate ) }
230
- </ p >
237
+ < p className = "text-md" > { getDisplayDate ( release ) } </ p >
231
238
</ div >
232
239
233
240
< div >
0 commit comments