Skip to content

Commit 7d31832

Browse files
authored
Merge pull request #15540 from ethereum/roadmap-fix
fix: start index for Roadmap carousel
2 parents a504139 + c390604 commit 7d31832

File tree

2 files changed

+95
-66
lines changed

2 files changed

+95
-66
lines changed

app/[locale]/roadmap/_components/ReleaseCarousel.tsx

Lines changed: 69 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client"
22

3-
import { useEffect, useState } from "react"
3+
import { useCallback, useEffect, useMemo, useState } from "react"
4+
import { useLocale } from "next-intl"
45

56
import { Image } from "@/components/Image"
67
import { ButtonLink } from "@/components/ui/buttons/Button"
@@ -16,45 +17,39 @@ import {
1617
import { cn } from "@/lib/utils/cn"
1718
import { formatDate } from "@/lib/utils/date"
1819

19-
import { releasesData } from "@/data/roadmap/releases"
20+
import { Release, releasesData } from "@/data/roadmap/releases"
2021

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()
2524

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>()
3127

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()
4430

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+
})
4737

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+
})
5244

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)
5853

5954
useEffect(() => {
6055
if (!api1 || !api2) {
@@ -72,6 +67,27 @@ const ReleaseCarousel = () => {
7267
})
7368
}, [api1, api2])
7469

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+
7591
return (
7692
<div className="w-full max-w-[100vw] overflow-hidden">
7793
<div className="mx-auto w-full max-w-screen-2xl px-4 sm:px-6">
@@ -85,29 +101,21 @@ const ReleaseCarousel = () => {
85101
align: "center",
86102
containScroll: false,
87103
loop: false,
88-
startIndex: findLatestReleaseIndex(),
104+
startIndex,
89105
}}
90106
>
91107
<CarouselContent>
92108
{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)
103111
return (
104112
<CarouselItem
105113
key={release.releaseName}
106114
className="w-full md:basis-1/3"
107115
>
108116
<div className="flex w-full flex-col items-center justify-center gap-3">
109117
<div className="mb-3 !h-6">
110-
{labelType === 1 && (
118+
{status === "prod" && (
111119
<div
112120
className={cn(
113121
"w-fit rounded-lg bg-primary-low-contrast px-2 py-1",
@@ -117,7 +125,7 @@ const ReleaseCarousel = () => {
117125
<p className="text-sm font-bold">In production</p>
118126
</div>
119127
)}
120-
{labelType === 2 && (
128+
{status === "soon" && (
121129
<div
122130
className={cn(
123131
"w-fit rounded-lg bg-warning-light px-2 py-1",
@@ -129,7 +137,7 @@ const ReleaseCarousel = () => {
129137
</p>
130138
</div>
131139
)}
132-
{labelType === 3 && (
140+
{status === "dev" && (
133141
<div
134142
className={cn(
135143
"w-fit rounded-lg bg-card-gradient-secondary-hover px-2 py-1",
@@ -142,14 +150,15 @@ const ReleaseCarousel = () => {
142150
</div>
143151
)}
144152
</div>
153+
{/* Line-circle-line decoration —•— */}
145154
<div className="flex w-full items-center justify-center text-center">
146155
<div
147156
className={cn(
148157
"flex h-1 flex-1",
149158
index !== 0
150-
? nextRelease
159+
? status === "soon"
151160
? "bg-gradient-to-r from-primary to-primary-low-contrast"
152-
: releaseDate.getTime() < todayDate.getTime()
161+
: status === "prod"
153162
? "bg-primary"
154163
: "bg-primary-low-contrast"
155164
: "bg-transparent"
@@ -158,21 +167,21 @@ const ReleaseCarousel = () => {
158167
<div
159168
className={cn(
160169
"h-7 w-7 rounded-full",
161-
releaseDate.getTime() < todayDate.getTime()
170+
status === "prod"
162171
? "bg-primary"
163172
: "bg-primary-low-contrast",
164-
nextRelease &&
173+
status === "soon" &&
165174
"border-2 border-primary bg-background"
166175
)}
167176
/>
168177
<div
169178
className={cn(
170179
"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"
176185
)}
177186
/>
178187
</div>
@@ -181,7 +190,7 @@ const ReleaseCarousel = () => {
181190
{release.releaseName}
182191
</p>
183192
<p className="font-mono text-sm text-body-medium">
184-
{formatDate(release.releaseDate)}
193+
{displayDate}
185194
</p>
186195
</div>
187196
</div>
@@ -203,7 +212,7 @@ const ReleaseCarousel = () => {
203212
align: "center",
204213
containScroll: false,
205214
loop: false,
206-
startIndex: findLatestReleaseIndex(),
215+
startIndex,
207216
}}
208217
>
209218
<CarouselContent>
@@ -225,9 +234,7 @@ const ReleaseCarousel = () => {
225234
<h2 className="text-4xl font-bold lg:text-6xl">
226235
{release.releaseName}
227236
</h2>
228-
<p className="text-md">
229-
{formatDate(release.releaseDate)}
230-
</p>
237+
<p className="text-md">{getDisplayDate(release)}</p>
231238
</div>
232239

233240
<div>

src/data/roadmap/releases.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,36 @@ import Layer2HubHeroImage from "@/public/images/heroes/layer-2-hub-hero.jpg"
66
import QuizzesHubHeroImage from "@/public/images/heroes/quizzes-hub-hero.png"
77
import FusakaImage from "@/public/images/roadmap/roadmap-fusaka.png"
88
import PectraImage from "@/public/images/roadmap/roadmap-pectra.png"
9-
interface Release {
9+
10+
type DateString =
11+
`2${number}${number}${number}-${number}${number}-${number}${number}`
12+
type YearString = `2${number}${number}${number}`
13+
14+
interface BaseRelease {
1015
image: StaticImageData
1116
releaseName: string
12-
releaseDate: string
1317
content: React.ReactNode
1418
href: string
1519
}
1620

21+
interface ReleaseWithDate extends BaseRelease {
22+
releaseDate: DateString
23+
plannedReleaseYear?: never
24+
}
25+
26+
interface ReleaseWithYear extends BaseRelease {
27+
releaseDate?: never
28+
plannedReleaseYear: YearString
29+
}
30+
31+
interface ReleaseUnscheduled extends BaseRelease {
32+
releaseDate?: never
33+
plannedReleaseYear?: never
34+
}
35+
36+
// Release may have either a releaseDate or a plannedReleaseYear, but not both.
37+
export type Release = ReleaseWithDate | ReleaseWithYear | ReleaseUnscheduled
38+
1739
export const releasesData: Release[] = [
1840
{
1941
image: DevelopersHubHeroImage,
@@ -144,7 +166,7 @@ export const releasesData: Release[] = [
144166
{
145167
image: FusakaImage,
146168
releaseName: "Fusaka",
147-
releaseDate: "2025",
169+
plannedReleaseYear: "2025",
148170
content: (
149171
<div>
150172
<p className="font-bold">
@@ -172,7 +194,7 @@ export const releasesData: Release[] = [
172194
{
173195
image: GuidesHubHeroImage,
174196
releaseName: "Glamsterdam",
175-
releaseDate: "2026",
197+
plannedReleaseYear: "2026",
176198
content: (
177199
<div>
178200
<p className="font-bold">Discussed for Glamsterdam</p>

0 commit comments

Comments
 (0)