diff --git a/app/[locale]/roadmap/_components/ReleaseCarousel.tsx b/app/[locale]/roadmap/_components/ReleaseCarousel.tsx index c092d5eebe0..f16955305b7 100644 --- a/app/[locale]/roadmap/_components/ReleaseCarousel.tsx +++ b/app/[locale]/roadmap/_components/ReleaseCarousel.tsx @@ -1,6 +1,7 @@ "use client" -import { useEffect, useState } from "react" +import { useCallback, useEffect, useMemo, useState } from "react" +import { useLocale } from "next-intl" import { Image } from "@/components/Image" import { ButtonLink } from "@/components/ui/buttons/Button" @@ -16,45 +17,39 @@ import { import { cn } from "@/lib/utils/cn" import { formatDate } from "@/lib/utils/date" -import { releasesData } from "@/data/roadmap/releases" +import { Release, releasesData } from "@/data/roadmap/releases" -const findLatestReleaseIndex = () => { - const today = new Date() - const twoMonthsFromNow = new Date() - twoMonthsFromNow.setMonth(today.getMonth() + 2) +const ReleaseCarousel = () => { + const locale = useLocale() - // First try to find a release within the next 2 months - const upcomingReleaseIndex = releasesData.findIndex((release) => { - const releaseDate = new Date(release.releaseDate) - return releaseDate > today && releaseDate <= twoMonthsFromNow - }) + const [api1, setApi1] = useState() + const [api2, setApi2] = useState() - // If no upcoming release found, find the most recent release up to today - if (upcomingReleaseIndex === -1) { - const pastReleases = releasesData.filter( - (release) => new Date(release.releaseDate) <= today - ) - if (pastReleases.length > 0) { - const mostRecentRelease = pastReleases[pastReleases.length - 1] - return releasesData.findIndex( - (release) => release.releaseDate === mostRecentRelease.releaseDate - ) - } - } + const startIndex = useMemo(() => { + const now = new Date() - return upcomingReleaseIndex -} + // Production: has a releaseDate in the past + const productionReleases = releasesData.filter((release) => { + if (!("releaseDate" in release) || !release.releaseDate) return false + const releaseDate = new Date(release.releaseDate) + return releaseDate <= now + }) -const ReleaseCarousel = () => { - const todayDate = new Date() - const twoMonthsFromNow = new Date() - twoMonthsFromNow.setMonth(todayDate.getMonth() + 2) + // Upcoming: has a releaseDate, but is in the future + const upcomingReleases = releasesData.filter((release) => { + if (!("releaseDate" in release) || !release.releaseDate) return false + const releaseDate = new Date(release.releaseDate) + return releaseDate > now + }) - const [api1, setApi1] = useState() - const [api2, setApi2] = useState() - const [currentIndex, setCurrentIndex] = useState(() => - findLatestReleaseIndex() - ) + // If upcoming releases exist, start index after production releases + if (upcomingReleases.length > 0) return productionReleases.length + + // If no upcoming releases, start at the last production release + return productionReleases.length - 1 + }, []) + + const [currentIndex, setCurrentIndex] = useState(startIndex) useEffect(() => { if (!api1 || !api2) { @@ -72,6 +67,27 @@ const ReleaseCarousel = () => { }) }, [api1, api2]) + const getStatus = useCallback((release: Release) => { + if (!("releaseDate" in release) || !release.releaseDate) return "dev" + if (new Date(release.releaseDate) <= new Date()) return "prod" + return "soon" + }, []) + + const getDisplayDate = (release: Release): string => { + if (!("releaseDate" in release || "plannedReleaseYear" in release)) + return "" + + if ("plannedReleaseYear" in release && release.plannedReleaseYear) + return new Intl.DateTimeFormat(locale, { + year: "numeric", + }).format(new Date(Number(release.plannedReleaseYear), 0, 1)) + + if ("releaseDate" in release && release.releaseDate) + return formatDate(release.releaseDate) + + return "" + } + return (
@@ -85,21 +101,13 @@ const ReleaseCarousel = () => { align: "center", containScroll: false, loop: false, - startIndex: findLatestReleaseIndex(), + startIndex, }} > {releasesData.map((release, index) => { - const releaseDate = new Date(release.releaseDate) - const nextRelease = - releaseDate > todayDate && releaseDate <= twoMonthsFromNow - const labelType = - releaseDate < todayDate - ? 1 - : releaseDate < twoMonthsFromNow - ? 2 - : 3 - + const status = getStatus(release) + const displayDate = getDisplayDate(release) return ( { >
- {labelType === 1 && ( + {status === "prod" && (
{

In production

)} - {labelType === 2 && ( + {status === "soon" && (
{

)} - {labelType === 3 && ( + {status === "dev" && (
{
)}
+ {/* Line-circle-line decoration —•— */}
{
@@ -181,7 +190,7 @@ const ReleaseCarousel = () => { {release.releaseName}

- {formatDate(release.releaseDate)} + {displayDate}

@@ -203,7 +212,7 @@ const ReleaseCarousel = () => { align: "center", containScroll: false, loop: false, - startIndex: findLatestReleaseIndex(), + startIndex, }} > @@ -225,9 +234,7 @@ const ReleaseCarousel = () => {

{release.releaseName}

-

- {formatDate(release.releaseDate)} -

+

{getDisplayDate(release)}

diff --git a/src/data/roadmap/releases.tsx b/src/data/roadmap/releases.tsx index ca06fd300d1..70d57f6ffc5 100644 --- a/src/data/roadmap/releases.tsx +++ b/src/data/roadmap/releases.tsx @@ -6,14 +6,36 @@ import Layer2HubHeroImage from "@/public/images/heroes/layer-2-hub-hero.jpg" import QuizzesHubHeroImage from "@/public/images/heroes/quizzes-hub-hero.png" import FusakaImage from "@/public/images/roadmap/roadmap-fusaka.png" import PectraImage from "@/public/images/roadmap/roadmap-pectra.png" -interface Release { + +type DateString = + `2${number}${number}${number}-${number}${number}-${number}${number}` +type YearString = `2${number}${number}${number}` + +interface BaseRelease { image: StaticImageData releaseName: string - releaseDate: string content: React.ReactNode href: string } +interface ReleaseWithDate extends BaseRelease { + releaseDate: DateString + plannedReleaseYear?: never +} + +interface ReleaseWithYear extends BaseRelease { + releaseDate?: never + plannedReleaseYear: YearString +} + +interface ReleaseUnscheduled extends BaseRelease { + releaseDate?: never + plannedReleaseYear?: never +} + +// Release may have either a releaseDate or a plannedReleaseYear, but not both. +export type Release = ReleaseWithDate | ReleaseWithYear | ReleaseUnscheduled + export const releasesData: Release[] = [ { image: DevelopersHubHeroImage, @@ -144,7 +166,7 @@ export const releasesData: Release[] = [ { image: FusakaImage, releaseName: "Fusaka", - releaseDate: "2025", + plannedReleaseYear: "2025", content: (

@@ -172,7 +194,7 @@ export const releasesData: Release[] = [ { image: GuidesHubHeroImage, releaseName: "Glamsterdam", - releaseDate: "2026", + plannedReleaseYear: "2026", content: (

Discussed for Glamsterdam