Skip to content

Commit d621660

Browse files
authored
Merge pull request #16644 from ethereum/fusakaHero
Fusaka banner
2 parents ce85fa9 + f5c1061 commit d621660

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+663
-189
lines changed

app/[locale]/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import type {
1212
import { CodeExample } from "@/lib/interfaces"
1313

1414
import ActivityStats from "@/components/ActivityStats"
15+
import FusakaBanner from "@/components/Banners/FusakaBanner"
1516
import { ChevronNext } from "@/components/Chevron"
16-
import DevconnectBannerVariation1 from "@/components/DevconnectBanner/Variation1"
1717
import HomeHero from "@/components/Hero/HomeHero"
1818
import BentoCard from "@/components/Homepage/BentoCard"
1919
import CodeExamples from "@/components/Homepage/CodeExamples"
@@ -96,6 +96,7 @@ import { fetchAttestantPosts } from "@/lib/api/fetchPosts"
9696
import { fetchRSS } from "@/lib/api/fetchRSS"
9797
import { fetchTotalValueLocked } from "@/lib/api/fetchTotalValueLocked"
9898
import EventFallback from "@/public/images/events/event-placeholder.png"
99+
import RoadmapFusakaImage from "@/public/images/roadmap/roadmap-fusaka.png"
99100

100101
const BentoCardSwiper = dynamic(
101102
() => import("@/components/Homepage/BentoCardSwiper"),
@@ -436,8 +437,8 @@ const Page = async ({ params }: { params: PageParams }) => {
436437
<>
437438
<IndexPageJsonLD locale={locale} />
438439
<MainArticle className="flex w-full flex-col items-center" dir={dir}>
439-
<DevconnectBannerVariation1 />
440-
<HomeHero />
440+
<FusakaBanner />
441+
<HomeHero image={RoadmapFusakaImage} alt="Fusaka Hero" />
441442
<div className="w-full space-y-32 px-4 md:mx-6 lg:space-y-48">
442443
<div className="my-20 grid w-full grid-cols-2 gap-x-4 gap-y-8 md:grid-cols-4 md:gap-x-10">
443444
{subHeroCTAs.map(
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
"use client"
2+
3+
import { useEffect, useState } from "react"
4+
import humanizeDuration from "humanize-duration"
5+
import { useLocale, useTranslations } from "next-intl"
6+
7+
const fusakaDate = new Date("2025-12-03T21:49:11.000Z")
8+
const fusakaDateTime = fusakaDate.getTime()
9+
const SECONDS = 1000
10+
11+
type TimeUnits = {
12+
days: number
13+
hours: number
14+
minutes: number
15+
seconds: number | null
16+
isExpired: boolean
17+
}
18+
19+
type TimeLabels = {
20+
days: string
21+
hours: string
22+
minutes: string
23+
seconds: string
24+
}
25+
26+
const getTimeUnits = (): TimeUnits => {
27+
const now = Date.now()
28+
const timeLeft = fusakaDateTime - now
29+
30+
if (timeLeft < 0) {
31+
return {
32+
days: 0,
33+
hours: 0,
34+
minutes: 0,
35+
seconds: null,
36+
isExpired: true,
37+
}
38+
}
39+
40+
const days = Math.floor(timeLeft / (24 * 60 * 60 * 1000))
41+
const hours = Math.floor(
42+
(timeLeft % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000)
43+
)
44+
const minutes = Math.floor((timeLeft % (60 * 60 * 1000)) / (60 * 1000))
45+
const seconds =
46+
days === 0 ? Math.floor((timeLeft % (60 * 1000)) / 1000) : null
47+
48+
return {
49+
days,
50+
hours,
51+
minutes,
52+
seconds,
53+
isExpired: false,
54+
}
55+
}
56+
57+
const getTimeLabels = (locale: string): TimeLabels => {
58+
const baseOptions = {
59+
round: true,
60+
language: locale,
61+
}
62+
63+
try {
64+
// Use humanizeDuration to get translated unit names (plural forms)
65+
// Format 2 units of each type to get plural forms
66+
const twoDays = humanizeDuration(2 * 24 * 60 * 60 * 1000, {
67+
...baseOptions,
68+
units: ["d"],
69+
})
70+
const twoHours = humanizeDuration(2 * 60 * 60 * 1000, {
71+
...baseOptions,
72+
units: ["h"],
73+
})
74+
const twoMinutes = humanizeDuration(2 * 60 * 1000, {
75+
...baseOptions,
76+
units: ["m"],
77+
})
78+
const twoSeconds = humanizeDuration(2 * 1000, {
79+
...baseOptions,
80+
units: ["s"],
81+
})
82+
83+
// Extract unit names (remove the number)
84+
const extractUnit = (str: string): string => {
85+
// Remove leading numbers, whitespace, and any separators
86+
// Handles formats like "1 day", "1d", "1 jour", etc.
87+
return str
88+
.replace(/^\d+\s*/, "") // Remove leading number and space
89+
.replace(/^\d+/, "") // Remove any remaining leading number (for formats like "1d")
90+
.trim()
91+
.split(/\s+/)[0] // Take first word in case of multiple words
92+
}
93+
94+
return {
95+
days: extractUnit(twoDays),
96+
hours: extractUnit(twoHours),
97+
minutes: extractUnit(twoMinutes),
98+
seconds: extractUnit(twoSeconds),
99+
}
100+
} catch {
101+
// Fallback to English if translation fails
102+
return {
103+
days: "days",
104+
hours: "hours",
105+
minutes: "minutes",
106+
seconds: "seconds",
107+
}
108+
}
109+
}
110+
111+
const FusakaCountdown = () => {
112+
const locale = useLocale()
113+
const t = useTranslations("page-index")
114+
const [timeUnits, setTimeUnits] = useState<TimeUnits>(() => getTimeUnits())
115+
const [labels, setLabels] = useState<TimeLabels>(() => getTimeLabels(locale))
116+
117+
useEffect(() => {
118+
setLabels(getTimeLabels(locale))
119+
}, [locale])
120+
121+
useEffect(() => {
122+
const updateCountdown = () => {
123+
setTimeUnits(getTimeUnits())
124+
}
125+
126+
const interval = setInterval(updateCountdown, SECONDS)
127+
128+
return () => clearInterval(interval)
129+
}, [])
130+
131+
if (timeUnits.isExpired) {
132+
return (
133+
<p className="text-2xl font-extrabold text-white">
134+
{t("page-index-fusaka-live-now")}
135+
</p>
136+
)
137+
}
138+
139+
return (
140+
<div className="flex items-center justify-center gap-4">
141+
{timeUnits.days > 0 && (
142+
<div className="flex flex-col items-center">
143+
<p className="text-xl font-extrabold text-white md:text-3xl">
144+
{String(timeUnits.days).padStart(2, "0")}
145+
</p>
146+
<p className="text-xs font-bold uppercase text-white">
147+
{labels.days}
148+
</p>
149+
</div>
150+
)}
151+
<div className="flex flex-col items-center">
152+
<p className="text-xl font-extrabold text-white md:text-3xl">
153+
{String(timeUnits.hours).padStart(2, "0")}
154+
</p>
155+
<p className="text-xs font-bold uppercase text-white">{labels.hours}</p>
156+
</div>
157+
<div className="flex flex-col items-center">
158+
<p className="text-xl font-extrabold text-white md:text-3xl">
159+
{String(timeUnits.minutes).padStart(2, "0")}
160+
</p>
161+
<p className="text-xs font-bold uppercase text-white">
162+
{labels.minutes}
163+
</p>
164+
</div>
165+
{timeUnits.seconds !== null && (
166+
<div className="flex flex-col items-center">
167+
<p className="text-xl font-extrabold text-white md:text-3xl">
168+
{String(timeUnits.seconds).padStart(2, "0")}
169+
</p>
170+
<p className="text-xs font-bold uppercase text-white">
171+
{labels.seconds}
172+
</p>
173+
</div>
174+
)}
175+
</div>
176+
)
177+
}
178+
179+
export default FusakaCountdown
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { getLocale, getTranslations } from "next-intl/server"
2+
3+
import { LinkBox, LinkOverlay } from "@/components/ui/link-box"
4+
5+
import FusakaCountdown from "./FusakaCountdown"
6+
7+
const FusakaBanner = async () => {
8+
const locale = getLocale()
9+
const t = await getTranslations({ locale, namespace: "page-index" })
10+
11+
return (
12+
<LinkBox className="w-full bg-[#333369] p-2 text-center text-white md:p-4 md:px-8">
13+
<div className="flex flex-col items-center justify-center gap-2 md:flex-row md:gap-16">
14+
<div className="flex flex-col items-center justify-center">
15+
<p className="text-xl font-extrabold uppercase !leading-none md:text-2xl">
16+
FUSAKA
17+
</p>
18+
<p className="text-sm font-bold uppercase text-purple-100">
19+
{t("page-index-fusaka-network-upgrade")}
20+
</p>
21+
</div>
22+
<p className="text-xs text-white md:text-sm">
23+
{t("page-index-fusaka-description")}{" "}
24+
<LinkOverlay
25+
href="/roadmap/fusaka"
26+
className="text-white hover:text-purple-300"
27+
>
28+
{t("page-index-fusaka-read-more")}
29+
</LinkOverlay>
30+
.
31+
</p>
32+
<div className="flex flex-row items-center justify-center gap-4 md:mt-0 md:flex-col md:gap-0">
33+
<p className="text-xs font-bold uppercase text-gray-200">
34+
{t.rich("page-index-fusaka-going-live-in", {
35+
br: () => <br className="md:hidden" />,
36+
})}
37+
</p>
38+
<FusakaCountdown />
39+
</div>
40+
</div>
41+
</LinkBox>
42+
)
43+
}
44+
45+
export default FusakaBanner

src/components/DevconnectBanner/Variation1/banner.svg

Lines changed: 0 additions & 4 deletions
This file was deleted.

src/components/DevconnectBanner/Variation1/index.tsx

Lines changed: 0 additions & 54 deletions
This file was deleted.

src/components/DevconnectBanner/Variation2/index.tsx

Lines changed: 0 additions & 53 deletions
This file was deleted.

0 commit comments

Comments
 (0)