Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions app/[locale]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { CodeExample } from "@/lib/interfaces"

import ActivityStats from "@/components/ActivityStats"
import { ChevronNext } from "@/components/Chevron"
import DevconnectBannerVariation1 from "@/components/DevconnectBanner/Variation1"
import HomeHero from "@/components/Hero/HomeHero"
import FusakaHero from "@/components/Hero/FusakaHero"
// import HomeHero from "@/components/Hero/HomeHero"
import BentoCard from "@/components/Homepage/BentoCard"
import CodeExamples from "@/components/Homepage/CodeExamples"
import HomepageSectionImage from "@/components/Homepage/HomepageSectionImage"
Expand Down Expand Up @@ -436,8 +436,8 @@ const Page = async ({ params }: { params: PageParams }) => {
<>
<IndexPageJsonLD locale={locale} />
<MainArticle className="flex w-full flex-col items-center" dir={dir}>
<DevconnectBannerVariation1 />
<HomeHero />
{/* <HomeHero /> */}
<FusakaHero />
<div className="w-full space-y-32 px-4 md:mx-6 lg:space-y-48">
<div className="my-20 grid w-full grid-cols-2 gap-x-4 gap-y-8 md:grid-cols-4 md:gap-x-10">
{subHeroCTAs.map(
Expand Down
4 changes: 0 additions & 4 deletions src/components/DevconnectBanner/Variation1/banner.svg

This file was deleted.

54 changes: 0 additions & 54 deletions src/components/DevconnectBanner/Variation1/index.tsx

This file was deleted.

53 changes: 0 additions & 53 deletions src/components/DevconnectBanner/Variation2/index.tsx

This file was deleted.

179 changes: 179 additions & 0 deletions src/components/Hero/FusakaHero/FusakaCountdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"use client"

import { useEffect, useState } from "react"
import humanizeDuration from "humanize-duration"
import { useLocale, useTranslations } from "next-intl"

const fusakaDate = new Date("2025-12-03T21:49:11.000Z")
const fusakaDateTime = fusakaDate.getTime()
const SECONDS = 1000

type TimeUnits = {
days: number
hours: number
minutes: number
seconds: number | null
isExpired: boolean
}

type TimeLabels = {
days: string
hours: string
minutes: string
seconds: string
}

const getTimeUnits = (): TimeUnits => {
const now = Date.now()
const timeLeft = fusakaDateTime - now

if (timeLeft < 0) {
return {
days: 0,
hours: 0,
minutes: 0,
seconds: null,
isExpired: true,
}
}

const days = Math.floor(timeLeft / (24 * 60 * 60 * 1000))
const hours = Math.floor(
(timeLeft % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000)
)
const minutes = Math.floor((timeLeft % (60 * 60 * 1000)) / (60 * 1000))
const seconds =
days === 0 ? Math.floor((timeLeft % (60 * 1000)) / 1000) : null

return {
days,
hours,
minutes,
seconds,
isExpired: false,
}
}

const getTimeLabels = (locale: string): TimeLabels => {
const baseOptions = {
round: true,
language: locale,
}

try {
// Use humanizeDuration to get translated unit names (plural forms)
// Format 2 units of each type to get plural forms
const twoDays = humanizeDuration(2 * 24 * 60 * 60 * 1000, {
...baseOptions,
units: ["d"],
})
const twoHours = humanizeDuration(2 * 60 * 60 * 1000, {
...baseOptions,
units: ["h"],
})
const twoMinutes = humanizeDuration(2 * 60 * 1000, {
...baseOptions,
units: ["m"],
})
const twoSeconds = humanizeDuration(2 * 1000, {
...baseOptions,
units: ["s"],
})

// Extract unit names (remove the number)
const extractUnit = (str: string): string => {
// Remove leading numbers, whitespace, and any separators
// Handles formats like "1 day", "1d", "1 jour", etc.
return str
.replace(/^\d+\s*/, "") // Remove leading number and space
.replace(/^\d+/, "") // Remove any remaining leading number (for formats like "1d")
.trim()
.split(/\s+/)[0] // Take first word in case of multiple words
}

return {
days: extractUnit(twoDays),
hours: extractUnit(twoHours),
minutes: extractUnit(twoMinutes),
seconds: extractUnit(twoSeconds),
}
} catch {
// Fallback to English if translation fails
return {
days: "days",
hours: "hours",
minutes: "minutes",
seconds: "seconds",
}
}
}

const FusakaCountdown = () => {
const locale = useLocale()
const t = useTranslations("page-index")
const [timeUnits, setTimeUnits] = useState<TimeUnits>(() => getTimeUnits())
const [labels, setLabels] = useState<TimeLabels>(() => getTimeLabels(locale))

useEffect(() => {
setLabels(getTimeLabels(locale))
}, [locale])

useEffect(() => {
const updateCountdown = () => {
setTimeUnits(getTimeUnits())
}

const interval = setInterval(updateCountdown, SECONDS)

return () => clearInterval(interval)
}, [])

if (timeUnits.isExpired) {
return (
<p className="text-2xl font-extrabold text-white">
{t("page-index-fusaka-live-now")}
</p>
)
}

return (
<div className="flex items-center justify-center gap-4">
{timeUnits.days > 0 && (
<div className="flex flex-col items-center">
<p className="text-xl font-extrabold text-white md:text-3xl">
{String(timeUnits.days).padStart(2, "0")}
</p>
<p className="text-xs font-bold uppercase text-white">
{labels.days}
</p>
</div>
)}
<div className="flex flex-col items-center">
<p className="text-xl font-extrabold text-white md:text-3xl">
{String(timeUnits.hours).padStart(2, "0")}
</p>
<p className="text-xs font-bold uppercase text-white">{labels.hours}</p>
</div>
<div className="flex flex-col items-center">
<p className="text-xl font-extrabold text-white md:text-3xl">
{String(timeUnits.minutes).padStart(2, "0")}
</p>
<p className="text-xs font-bold uppercase text-white">
{labels.minutes}
</p>
</div>
{timeUnits.seconds !== null && (
<div className="flex flex-col items-center">
<p className="text-xl font-extrabold text-white md:text-3xl">
{String(timeUnits.seconds).padStart(2, "0")}
</p>
<p className="text-xs font-bold uppercase text-white">
{labels.seconds}
</p>
</div>
)}
</div>
)
}

export default FusakaCountdown
70 changes: 70 additions & 0 deletions src/components/Hero/FusakaHero/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { getLocale, getTranslations } from "next-intl/server"

import LanguageMorpher from "@/components/Homepage/LanguageMorpher"
import { Image } from "@/components/Image"
import { LinkBox, LinkOverlay } from "@/components/ui/link-box"

import FusakaCountdown from "./FusakaCountdown"

import RoadmapFusakaImage from "@/public/images/roadmap/roadmap-fusaka.png"

const FusakaHero = async () => {
const locale = getLocale()
const t = await getTranslations({ locale, namespace: "page-index" })

return (
<div className="relative w-full">
<LinkBox className="bg-[#333369] p-2 text-center text-white">
<div className="flex flex-col items-center justify-between gap-2 md:flex-row md:gap-16">
<div className="flex flex-col items-center justify-center">
<p className="text-xl font-extrabold uppercase !leading-none md:text-2xl">
FUSAKA
</p>
<p className="text-sm font-bold uppercase text-purple-100">
{t("page-index-fusaka-network-upgrade")}
</p>
</div>
<p className="text-xs text-white md:text-sm">
{t("page-index-fusaka-description")}{" "}
<LinkOverlay
href="/roadmap/fusaka"
className="text-white hover:text-purple-300"
>
{t("page-index-fusaka-read-more")}
</LinkOverlay>
.
</p>
<div className="flex flex-row items-center justify-center gap-4 md:mt-0 md:flex-col md:gap-0">
<p className="text-xs font-bold uppercase text-gray-200">
{t.rich("page-index-fusaka-going-live-in", {
br: () => <br className="md:hidden" />,
})}
</p>
<FusakaCountdown />
</div>
</div>
</LinkBox>

<div className="relative z-0 h-[240px] overflow-hidden md:h-[480px]">
<Image
src={RoadmapFusakaImage}
alt="Fusaka Hero"
className="h-full w-full object-cover"
priority
/>
</div>

<div className="flex flex-col items-center px-4 py-10 text-center">
<LanguageMorpher />
<div className="flex flex-col items-center gap-y-5 md:max-w-2xl">
<h1 className="font-black">{t("page-index-title")}</h1>
<p className="max-w-96 text-md text-body-medium md:text-lg">
{t("page-index-description")}
</p>
</div>
</div>
</div>
)
}

export default FusakaHero
7 changes: 6 additions & 1 deletion src/intl/am/page-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,10 @@
"page-index-values-open-ethereum-label": "ለሁሉም ክፍት የሆነ",
"page-index-values-open-ethereum-content-0": "Ethereum በሁሉም ሰው ሊገኝ የሚችል ነው። ማንኛውም ሰው ሊመለከተው፣ ሊጠቀምበት እና ለሁሉም የተሻለ እንዲሆን ለማድረግ ኮዱን ሊያሻሽል ይችላል።",
"page-index-devconnect-banner-join-the-biggest-ethereum-event-of-the-year": "የአመቱ ትልቁን የ Ethereum ዝግጅት ይቀላቀሉ",
"page-index-devconnect-banner-get-tickets": "ቲኬቶችን ያግኙ"
"page-index-devconnect-banner-get-tickets": "ቲኬቶችን ያግኙ",
"page-index-fusaka-network-upgrade": "የአውታረ መረብ ማሻሻያ",
"page-index-fusaka-description": "ለበለጠ ፈጣን፣ ደህንነቱ የተጠበቀ እና ለተጠቃሚዎች ምቹ የሆነ Ethereum አውታረ መረብ |",
"page-index-fusaka-read-more": "ተጨማሪ ያንብቡ",
"page-index-fusaka-going-live-in": "በቀጣይ ይጀምራል<br></br>",
"page-index-fusaka-live-now": "አሁን በቀጥታ"
}
Loading