Skip to content

Commit 6083783

Browse files
authored
Merge pull request #15310 from ethereum/roadmap-page
Roadmap page
2 parents b2f034d + 83b19c7 commit 6083783

28 files changed

+1464
-121
lines changed
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
"use client"
2+
3+
import { useEffect, useState } from "react"
4+
5+
import { Image } from "@/components/Image"
6+
import { ButtonLink } from "@/components/ui/buttons/Button"
7+
import {
8+
Carousel,
9+
type CarouselApi,
10+
CarouselContent,
11+
CarouselItem,
12+
CarouselNext,
13+
CarouselPrevious,
14+
} from "@/components/ui/carousel"
15+
16+
import { cn } from "@/lib/utils/cn"
17+
import { formatDate } from "@/lib/utils/date"
18+
19+
import { releasesData } from "@/data/roadmap/releases"
20+
21+
const findLatestReleaseIndex = () => {
22+
const today = new Date()
23+
const twoMonthsFromNow = new Date()
24+
twoMonthsFromNow.setMonth(today.getMonth() + 2)
25+
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+
})
31+
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+
}
44+
45+
return upcomingReleaseIndex
46+
}
47+
48+
const ReleaseCarousel = () => {
49+
const todayDate = new Date()
50+
const twoMonthsFromNow = new Date()
51+
twoMonthsFromNow.setMonth(todayDate.getMonth() + 2)
52+
53+
const [api1, setApi1] = useState<CarouselApi>()
54+
const [api2, setApi2] = useState<CarouselApi>()
55+
const [currentIndex, setCurrentIndex] = useState(() =>
56+
findLatestReleaseIndex()
57+
)
58+
59+
useEffect(() => {
60+
if (!api1 || !api2) {
61+
return
62+
}
63+
64+
api1.on("select", () => {
65+
setCurrentIndex(api1.selectedScrollSnap())
66+
api2.scrollTo(api1.selectedScrollSnap())
67+
})
68+
69+
api2.on("select", () => {
70+
setCurrentIndex(api2.selectedScrollSnap())
71+
api1.scrollTo(api2.selectedScrollSnap())
72+
})
73+
}, [api1, api2])
74+
75+
return (
76+
<div className="w-full max-w-[100vw] overflow-hidden">
77+
<div className="mx-auto w-full max-w-screen-2xl px-4 sm:px-6">
78+
<div className="w-full rounded-2xl bg-background-highlight py-6">
79+
<div className="flex flex-col gap-6">
80+
{/* First Carousel */}
81+
<Carousel
82+
setApi={setApi1}
83+
className="w-full px-16"
84+
opts={{
85+
align: "center",
86+
containScroll: false,
87+
loop: false,
88+
startIndex: findLatestReleaseIndex(),
89+
}}
90+
>
91+
<CarouselContent>
92+
{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+
103+
return (
104+
<CarouselItem
105+
key={release.releaseName}
106+
className="w-full md:basis-1/3"
107+
>
108+
<div className="flex w-full flex-col items-center justify-center gap-3">
109+
<div className="mb-3 !h-6">
110+
{labelType === 1 && (
111+
<div
112+
className={cn(
113+
"w-fit rounded-lg bg-primary-low-contrast px-2 py-1",
114+
currentIndex !== index && "hidden"
115+
)}
116+
>
117+
<p className="text-sm font-bold">In production</p>
118+
</div>
119+
)}
120+
{labelType === 2 && (
121+
<div
122+
className={cn(
123+
"w-fit rounded-lg bg-warning-light px-2 py-1",
124+
currentIndex !== index && "hidden"
125+
)}
126+
>
127+
<p className="text-sm font-bold text-black">
128+
Coming soon
129+
</p>
130+
</div>
131+
)}
132+
{labelType === 3 && (
133+
<div
134+
className={cn(
135+
"w-fit rounded-lg bg-card-gradient-secondary-hover px-2 py-1",
136+
currentIndex !== index && "hidden"
137+
)}
138+
>
139+
<p className="text-sm font-bold">
140+
In development
141+
</p>
142+
</div>
143+
)}
144+
</div>
145+
<div className="flex w-full items-center justify-center text-center">
146+
<div
147+
className={cn(
148+
"flex h-1 flex-1",
149+
index !== 0
150+
? nextRelease
151+
? "bg-gradient-to-r from-primary to-primary-low-contrast"
152+
: releaseDate.getTime() < todayDate.getTime()
153+
? "bg-primary"
154+
: "bg-primary-low-contrast"
155+
: "bg-transparent"
156+
)}
157+
/>
158+
<div
159+
className={cn(
160+
"h-7 w-7 rounded-full",
161+
releaseDate.getTime() < todayDate.getTime()
162+
? "bg-primary"
163+
: "bg-primary-low-contrast",
164+
nextRelease &&
165+
"border-2 border-primary bg-background"
166+
)}
167+
/>
168+
<div
169+
className={cn(
170+
"flex h-1 flex-1",
171+
index !== releasesData.length - 1
172+
? index < findLatestReleaseIndex()
173+
? "bg-primary"
174+
: "bg-primary-low-contrast"
175+
: "bg-transparent"
176+
)}
177+
/>
178+
</div>
179+
<div className="flex flex-col items-center justify-center text-center">
180+
<p className="text-md font-bold">
181+
{release.releaseName}
182+
</p>
183+
<p className="font-mono text-sm text-body-medium">
184+
{formatDate(release.releaseDate)}
185+
</p>
186+
</div>
187+
</div>
188+
</CarouselItem>
189+
)
190+
})}
191+
</CarouselContent>
192+
<div className="lg:hidden">
193+
<CarouselPrevious />
194+
<CarouselNext />
195+
</div>
196+
</Carousel>
197+
198+
{/* Second Carousel */}
199+
<Carousel
200+
setApi={setApi2}
201+
className="w-full px-4 lg:px-16"
202+
opts={{
203+
align: "center",
204+
containScroll: false,
205+
loop: false,
206+
startIndex: findLatestReleaseIndex(),
207+
}}
208+
>
209+
<CarouselContent>
210+
{releasesData.map((release) => (
211+
<CarouselItem
212+
key={release.releaseName}
213+
className="w-full pl-4"
214+
>
215+
<div className="flex w-full flex-col gap-6 lg:flex-row">
216+
<div className="w-full flex-1 rounded-2xl">
217+
<Image
218+
src={release.image}
219+
alt={release.releaseName}
220+
className="h-[240px] rounded-2xl object-cover md:h-[266px] lg:h-[551px]"
221+
/>
222+
</div>
223+
<div className="flex flex-1 flex-col gap-8">
224+
<div>
225+
<h2 className="text-4xl font-bold lg:text-6xl">
226+
{release.releaseName}
227+
</h2>
228+
<p className="text-md">
229+
{formatDate(release.releaseDate)}
230+
</p>
231+
</div>
232+
233+
<div>
234+
<p className="mb-3 text-xl font-bold">
235+
Main features
236+
</p>
237+
<div className="flex flex-col gap-4">
238+
{release.content}
239+
</div>
240+
</div>
241+
<ButtonLink
242+
href={release.href}
243+
className="w-full lg:w-fit"
244+
>
245+
Learn more
246+
</ButtonLink>
247+
</div>
248+
</div>
249+
</CarouselItem>
250+
))}
251+
</CarouselContent>
252+
<div className="hidden lg:block">
253+
<CarouselPrevious />
254+
<CarouselNext />
255+
</div>
256+
</Carousel>
257+
</div>
258+
</div>
259+
</div>
260+
</div>
261+
)
262+
}
263+
264+
export default ReleaseCarousel

0 commit comments

Comments
 (0)