}) => {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/layer-2/learn")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({ locale, namespace: "page-layer-2-learn" })
+
+ return await getMetadata({
+ locale,
+ slug: ["layer-2", "learn"],
+ title: t("page-layer-2-learn-meta-title"),
+ description: t("page-layer-2-learn-description"),
+ image: "/images/layer-2/learn-hero.png",
+ })
+}
+
+export default Page
diff --git a/app/[locale]/layer-2/networks/_components/networks.tsx b/app/[locale]/layer-2/networks/_components/networks.tsx
new file mode 100644
index 00000000000..8bb38fe63e8
--- /dev/null
+++ b/app/[locale]/layer-2/networks/_components/networks.tsx
@@ -0,0 +1,107 @@
+"use client"
+
+import Callout from "@/components/Callout"
+import { ContentHero, ContentHeroProps } from "@/components/Hero"
+import Layer2NetworksTable from "@/components/Layer2NetworksTable"
+import MainArticle from "@/components/MainArticle"
+import NetworkMaturity from "@/components/NetworkMaturity"
+import { ButtonLink } from "@/components/ui/buttons/Button"
+
+import useTranslation from "@/hooks/useTranslation"
+import { usePathname } from "@/i18n/routing"
+import Callout2Image from "@/public/images/layer-2/layer-2-walking.png"
+import Callout1Image from "@/public/images/man-and-dog-playing.png"
+
+const Layer2Networks = ({ layer2Data, locale, mainnetData }) => {
+ const pathname = usePathname()
+ const { t } = useTranslation(["page-layer-2-networks", "common"])
+
+ const heroProps: ContentHeroProps = {
+ breadcrumbs: { slug: pathname, startDepth: 1 },
+ heroImg: "/images/layer-2/learn-hero.png",
+ blurDataURL: "/images/layer-2/learn-hero.png",
+ title: t("common:nav-networks-explore-networks-label"),
+ description: t("page-layer-2-networks-hero-description"),
+ }
+
+ return (
+
+
+
+
+
+
+
+
{t("page-layer-2-networks-more-advanced-title")}
+
+
+ {t("page-layer-2-networks-more-advanced-descripton-1")}{" "}
+
+ {t("page-layer-2-networks-more-advanced-descripton-2")}
+
+
+
{t("page-layer-2-networks-more-advanced-descripton-3")}
+
+
+
+ {t("page-layer-2-networks-more-advanced-link-1")}
+
+
+ {t("page-layer-2-networks-more-advanced-link-2")}
+
+
+
+
+
+
+
+
+
+
+
+ {t("common:learn-more")}
+
+
+
+
+
+
+ {t("common:learn-more")}
+
+
+
+
+
+ )
+}
+
+export default Layer2Networks
diff --git a/app/[locale]/layer-2/networks/page.tsx b/app/[locale]/layer-2/networks/page.tsx
new file mode 100644
index 00000000000..703af806208
--- /dev/null
+++ b/app/[locale]/layer-2/networks/page.tsx
@@ -0,0 +1,146 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import { Lang } from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { dataLoader } from "@/lib/utils/data/dataLoader"
+import { getMetadata } from "@/lib/utils/metadata"
+import { networkMaturity } from "@/lib/utils/networkMaturity"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+
+import { ethereumNetworkData, layer2Data } from "@/data/networks/networks"
+import { walletsData } from "@/data/wallets/wallet-data"
+
+import { BASE_TIME_UNIT } from "@/lib/constants"
+
+import Layer2Networks from "./_components/networks"
+
+import { loadMessages } from "@/i18n/loadMessages"
+import { fetchEthereumMarketcap } from "@/lib/api/fetchEthereumMarketcap"
+import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie"
+import { fetchGrowThePieBlockspace } from "@/lib/api/fetchGrowThePieBlockspace"
+import { fetchGrowThePieMaster } from "@/lib/api/fetchGrowThePieMaster"
+import { fetchL2beat } from "@/lib/api/fetchL2beat"
+
+// In seconds
+const REVALIDATE_TIME = BASE_TIME_UNIT * 1
+
+const loadData = dataLoader(
+ [
+ ["ethereumMarketcapData", fetchEthereumMarketcap],
+ ["growThePieData", fetchGrowThePie],
+ ["growThePieBlockspaceData", fetchGrowThePieBlockspace],
+ ["growThePieMasterData", fetchGrowThePieMaster],
+ ["l2beatData", fetchL2beat],
+ ],
+ REVALIDATE_TIME * 1000
+)
+
+const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
+ const { locale } = await params
+
+ const [
+ ethereumMarketcapData,
+ growThePieData,
+ growThePieBlockspaceData,
+ growThePieMasterData,
+ l2beatData,
+ ] = await loadData()
+
+ const layer2DataCompiled = layer2Data
+ .map((network) => {
+ return {
+ ...network,
+ txCosts: growThePieData.dailyTxCosts[network.growthepieID],
+ tvl: l2beatData.data.projects[network.l2beatID].tvs.breakdown.total,
+ networkMaturity: networkMaturity(
+ l2beatData.data.projects[network.l2beatID]
+ ),
+ activeAddresses: growThePieData.activeAddresses[network.growthepieID],
+ blockspaceData:
+ (growThePieBlockspaceData || {})[network.growthepieID] || null,
+ launchDate:
+ (growThePieMasterData?.launchDates || {})[
+ network.growthepieID.replace(/_/g, "-")
+ ] || null,
+ walletsSupported: walletsData
+ .filter((wallet) =>
+ wallet.supported_chains.includes(network.chainName)
+ )
+ .map((wallet) => wallet.name),
+ walletsSupportedCount: `${
+ walletsData.filter((wallet) =>
+ wallet.supported_chains.includes(network.chainName)
+ ).length
+ }/${walletsData.length}`,
+ }
+ })
+ .sort((a, b) => {
+ const maturityOrder = {
+ robust: 4,
+ maturing: 3,
+ developing: 2,
+ emerging: 1,
+ }
+
+ const maturityDiff =
+ maturityOrder[b.networkMaturity] - maturityOrder[a.networkMaturity]
+
+ if (maturityDiff === 0) {
+ return (b.tvl || 0) - (a.tvl || 0)
+ }
+
+ return maturityDiff
+ })
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/layer-2/networks")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ const props = {
+ locale,
+ layer2Data: layer2DataCompiled,
+ mainnetData: {
+ ...ethereumNetworkData,
+ txCosts: growThePieData.dailyTxCosts.ethereum,
+ tvl: "value" in ethereumMarketcapData ? ethereumMarketcapData.value : 0,
+ walletsSupported: walletsData
+ .filter((wallet) =>
+ wallet.supported_chains.includes("Ethereum Mainnet")
+ )
+ .map((wallet) => wallet.name),
+ },
+ }
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({
+ locale,
+ namespace: "page-layer-2-networks",
+ })
+
+ return await getMetadata({
+ locale,
+ slug: ["layer-2", "networks"],
+ title: t("page-layer-2-networks-meta-title"),
+ description: t("page-layer-2-networks-hero-description"),
+ image: "/images/layer-2/learn-hero.png",
+ })
+}
+
+export default Page
diff --git a/app/[locale]/layer-2/page.tsx b/app/[locale]/layer-2/page.tsx
new file mode 100644
index 00000000000..ff6ba761a79
--- /dev/null
+++ b/app/[locale]/layer-2/page.tsx
@@ -0,0 +1,95 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import { Lang } from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { dataLoader } from "@/lib/utils/data/dataLoader"
+import { getMetadata } from "@/lib/utils/metadata"
+import { networkMaturity } from "@/lib/utils/networkMaturity"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+
+import { layer2Data } from "@/data/networks/networks"
+
+import { BASE_TIME_UNIT } from "@/lib/constants"
+
+import Layer2Page from "./_components/layer-2"
+
+import { loadMessages } from "@/i18n/loadMessages"
+import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie"
+import { fetchL2beat } from "@/lib/api/fetchL2beat"
+
+// In seconds
+const REVALIDATE_TIME = BASE_TIME_UNIT * 24
+
+const loadData = dataLoader(
+ [
+ ["growThePieData", fetchGrowThePie],
+ ["l2beatData", fetchL2beat],
+ ],
+ REVALIDATE_TIME * 1000
+)
+
+const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
+ const { locale } = await params
+
+ const [growThePieData, l2beatData] = await loadData()
+
+ const getRandomL2s = () => {
+ let randomL2s = layer2Data.filter(
+ (network) =>
+ networkMaturity(l2beatData.data.projects[network.l2beatID]) === "robust"
+ )
+
+ if (randomL2s.length === 0) {
+ randomL2s = layer2Data.filter(
+ (network) =>
+ networkMaturity(l2beatData.data.projects[network.l2beatID]) ===
+ "maturing"
+ )
+ }
+
+ return randomL2s.sort(() => 0.5 - Math.random()).slice(0, 3)
+ }
+
+ const randomL2s = layer2Data.sort(() => 0.5 - Math.random()).slice(0, 9)
+
+ const userRandomL2s = getRandomL2s()
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/layer-2")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({ locale, namespace: "page-layer-2" })
+
+ return await getMetadata({
+ locale,
+ slug: ["layer-2"],
+ title: t("page-layer-2-meta-title"),
+ description: t("page-layer-2-meta-description"),
+ image: "/images/layer-2/learn-hero.png",
+ })
+}
+
+export default Page
diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx
index fa29d6fe59b..49682d1d126 100644
--- a/app/[locale]/layout.tsx
+++ b/app/[locale]/layout.tsx
@@ -1,9 +1,13 @@
+import { Suspense } from "react"
import pick from "lodash.pick"
+import { IBM_Plex_Mono, Inter } from "next/font/google"
import { notFound } from "next/navigation"
import { getMessages, setRequestLocale } from "next-intl/server"
import { Lang } from "@/lib/types"
+import Matomo from "@/components/Matomo"
+
import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
import { getLocaleTimestamp } from "@/lib/utils/time"
@@ -14,6 +18,20 @@ import "@/styles/global.css"
import { routing } from "@/i18n/routing"
import { BaseLayout } from "@/layouts/BaseLayout"
+const inter = Inter({
+ subsets: ["latin"],
+ display: "swap",
+ variable: "--font-inter",
+ preload: true,
+})
+
+const ibmPlexMono = IBM_Plex_Mono({
+ subsets: ["latin"],
+ weight: ["400"],
+ display: "swap",
+ variable: "--font-mono",
+})
+
export default async function LocaleLayout({
children,
params: { locale },
@@ -38,9 +56,17 @@ export default async function LocaleLayout({
)
return (
-
+
+
+
+
+
{children}
diff --git a/src/pages/[locale]/learn.tsx b/app/[locale]/learn/_components/learn.tsx
similarity index 94%
rename from src/pages/[locale]/learn.tsx
rename to app/[locale]/learn/_components/learn.tsx
index 3bb00881083..8b8058afe78 100644
--- a/src/pages/[locale]/learn.tsx
+++ b/app/[locale]/learn/_components/learn.tsx
@@ -1,13 +1,8 @@
-import { GetStaticProps } from "next"
+"use client"
+
import type { HTMLAttributes, ReactNode } from "react"
-import type {
- BasePageProps,
- ChildOnlyProp,
- Lang,
- Params,
- ToCItem,
-} from "@/lib/types"
+import type { ChildOnlyProp, ToCItem } from "@/lib/types"
import OriginalCard, {
type CardProps as OriginalCardProps,
@@ -20,21 +15,12 @@ import { Image, type ImageProps } from "@/components/Image"
import LeftNavBar from "@/components/LeftNavBar"
import MainArticle from "@/components/MainArticle"
import { ContentContainer } from "@/components/MdComponents"
-import PageMetadata from "@/components/PageMetadata"
import { ButtonLink } from "@/components/ui/buttons/Button"
import { Center, Flex, Stack } from "@/components/ui/flex"
import InlineLink from "@/components/ui/Link"
import { ListItem, UnorderedList } from "@/components/ui/list"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-
-import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
-
import useTranslation from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
import developersEthBlocks from "@/public/images/developers-eth-blocks.png"
import dogeComputer from "@/public/images/doge-computer.png"
import enterprise from "@/public/images/enterprise-eth.png"
@@ -125,37 +111,6 @@ const ImageHeight200 = ({ src, alt }: ImageProps) => (
)
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const requiredNamespaces = getRequiredNamespacesForPage("/learn")
-
- const contentNotTranslated = !existsNamespace(locale, requiredNamespaces[2])
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const messages = await loadNamespaces(locale, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- },
- }
-}) satisfies GetStaticProps
-
const LearnPage = () => {
const { t } = useTranslation("page-learn")
@@ -214,12 +169,6 @@ const LearnPage = () => {
return (
-
-
}) {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/learn")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({ locale, namespace: "page-learn" })
+
+ return await getMetadata({
+ locale,
+ slug: ["learn"],
+ title: t("page-learn-meta-title"),
+ description: t("hero-subtitle"),
+ image: "/images/heroes/learn-hub-hero.png",
+ })
+}
diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx
new file mode 100644
index 00000000000..bdd024553e1
--- /dev/null
+++ b/app/[locale]/page.tsx
@@ -0,0 +1,134 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import type { AllMetricData, CommunityBlog, Lang } from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { dataLoader } from "@/lib/utils/data/dataLoader"
+import { isValidDate } from "@/lib/utils/date"
+import { existsNamespace } from "@/lib/utils/existsNamespace"
+import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
+import { polishRSSList } from "@/lib/utils/rss"
+import { getLocaleTimestamp } from "@/lib/utils/time"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+
+import {
+ BASE_TIME_UNIT,
+ BLOG_FEEDS,
+ BLOGS_WITHOUT_FEED,
+ CALENDAR_DISPLAY_COUNT,
+ RSS_DISPLAY_COUNT,
+} from "@/lib/constants"
+
+import HomePage from "./_components/home"
+
+import { loadMessages } from "@/i18n/loadMessages"
+import { fetchCommunityEvents } from "@/lib/api/calendarEvents"
+import { fetchEthPrice } from "@/lib/api/fetchEthPrice"
+import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie"
+import { fetchAttestantPosts } from "@/lib/api/fetchPosts"
+import { fetchRSS } from "@/lib/api/fetchRSS"
+import { fetchTotalEthStaked } from "@/lib/api/fetchTotalEthStaked"
+import { fetchTotalValueLocked } from "@/lib/api/fetchTotalValueLocked"
+
+// API calls
+const fetchXmlBlogFeeds = async () => {
+ return await fetchRSS(BLOG_FEEDS)
+}
+
+// In seconds
+const REVALIDATE_TIME = BASE_TIME_UNIT * 1
+
+const loadData = dataLoader(
+ [
+ ["ethPrice", fetchEthPrice],
+ ["totalEthStaked", fetchTotalEthStaked],
+ ["totalValueLocked", fetchTotalValueLocked],
+ ["growThePieData", fetchGrowThePie],
+ ["communityEvents", fetchCommunityEvents],
+ ["attestantPosts", fetchAttestantPosts],
+ ["rssData", fetchXmlBlogFeeds],
+ ],
+ REVALIDATE_TIME * 1000
+)
+
+const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
+ const { locale } = await params
+
+ const [
+ ethPrice,
+ totalEthStaked,
+ totalValueLocked,
+ growThePieData,
+ communityEvents,
+ attestantPosts,
+ xmlBlogs,
+ ] = await loadData()
+
+ const metricResults: AllMetricData = {
+ ethPrice,
+ totalEthStaked,
+ totalValueLocked,
+ txCount: growThePieData.txCount,
+ txCostsMedianUsd: growThePieData.txCostsMedianUsd,
+ }
+
+ const calendar = communityEvents.upcomingEventData
+ .sort((a, b) => {
+ const dateA = isValidDate(a.date) ? new Date(a.date).getTime() : -Infinity
+ const dateB = isValidDate(b.date) ? new Date(b.date).getTime() : -Infinity
+ return dateA - dateB
+ })
+ .slice(0, CALENDAR_DISPLAY_COUNT)
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ // check if the translated page content file exists for locale
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[0])
+
+ // load last deploy date to pass to Footer in RootLayout
+ const lastDeployDate = getLastDeployDate()
+ const lastDeployLocaleTimestamp = getLocaleTimestamp(
+ locale as Lang,
+ lastDeployDate
+ )
+
+ // RSS feed items
+ const polishedRssItems = polishRSSList(attestantPosts, ...xmlBlogs)
+ const rssItems = polishedRssItems.slice(0, RSS_DISPLAY_COUNT)
+
+ const blogLinks = polishedRssItems.map(({ source, sourceUrl }) => ({
+ name: source,
+ href: sourceUrl,
+ })) as CommunityBlog[]
+ blogLinks.push(...BLOGS_WITHOUT_FEED)
+
+ const props = {
+ calendar,
+ contentNotTranslated,
+ lastDeployLocaleTimestamp,
+ metricResults,
+ rssData: { rssItems, blogLinks },
+ }
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata() {
+ const t = await getTranslations()
+
+ return {
+ title: t("page-index.page-index-meta-title"),
+ description: t("page-index.page-index-meta-description"),
+ }
+}
+
+export default Page
diff --git a/src/pages/[locale]/quizzes.tsx b/app/[locale]/quizzes/_components/quizzes.tsx
similarity index 70%
rename from src/pages/[locale]/quizzes.tsx
rename to app/[locale]/quizzes/_components/quizzes.tsx
index fbf90bfcdaa..2c5f00cb842 100644
--- a/src/pages/[locale]/quizzes.tsx
+++ b/app/[locale]/quizzes/_components/quizzes.tsx
@@ -1,13 +1,13 @@
+"use client"
+
import { useMemo, useState } from "react"
-import { GetStaticProps, InferGetStaticPropsType, NextPage } from "next"
import { FaGithub } from "react-icons/fa"
-import { BasePageProps, Lang, Params, QuizKey, QuizStatus } from "@/lib/types"
+import { QuizKey, QuizStatus } from "@/lib/types"
import FeedbackCard from "@/components/FeedbackCard"
import { HubHero } from "@/components/Hero"
import MainArticle from "@/components/MainArticle"
-import PageMetadata from "@/components/PageMetadata"
import QuizWidget from "@/components/Quiz/QuizWidget"
import QuizzesList from "@/components/Quiz/QuizzesList"
import QuizzesModal from "@/components/Quiz/QuizzesModal"
@@ -16,19 +16,14 @@ import { useLocalQuizData } from "@/components/Quiz/useLocalQuizData"
import { ButtonLink } from "@/components/ui/buttons/Button"
import { Flex, HStack, Stack } from "@/components/ui/flex"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
import { trackCustomEvent } from "@/lib/utils/matomo"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
import { ethereumBasicsQuizzes, usingEthereumQuizzes } from "@/data/quizzes"
-import { DEFAULT_LOCALE, INITIAL_QUIZ, LOCALES_CODES } from "@/lib/constants"
+import { INITIAL_QUIZ } from "@/lib/constants"
import { useDisclosure } from "@/hooks/useDisclosure"
import { useTranslation } from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
import HeroImage from "@/public/images/heroes/quizzes-hub-hero.png"
const handleGHAdd = () =>
@@ -38,40 +33,7 @@ const handleGHAdd = () =>
eventName: "GH_add",
})
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const requiredNamespaces = getRequiredNamespacesForPage("/quizzes")
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const messages = await loadNamespaces(locale, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- },
- }
-}) satisfies GetStaticProps
-
-const QuizzesHubPage: NextPage<
- InferGetStaticPropsType
-> = () => {
+const QuizzesPage = () => {
const { t } = useTranslation("learn-quizzes")
const [userStats, updateUserStats] = useLocalQuizData()
@@ -90,11 +52,6 @@ const QuizzesHubPage: NextPage<
return (
-
{t("want-more-quizzes")}
-
{t("contribute")}
}) => {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/quizzes")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({ locale })
+
+ return await getMetadata({
+ locale,
+ slug: ["quizzes"],
+ title: t("common.quizzes-title"),
+ description: t("learn-quizzes.quizzes-subtitle"),
+ image: "/images/heroes/quizzes-hub-hero.png",
+ })
+}
+
+export default Page
diff --git a/src/pages/[locale]/resources.tsx b/app/[locale]/resources/_components/resources.tsx
similarity index 76%
rename from src/pages/[locale]/resources.tsx
rename to app/[locale]/resources/_components/resources.tsx
index 129e12ae176..3f3a01ccbb6 100644
--- a/src/pages/[locale]/resources.tsx
+++ b/app/[locale]/resources/_components/resources.tsx
@@ -1,14 +1,14 @@
+"use client"
+
import { motion } from "framer-motion"
-import { GetStaticProps } from "next"
import { FaGithub } from "react-icons/fa6"
-import type { BasePageProps, Lang, Params } from "@/lib/types"
+import type { MetricReturnData } from "@/lib/types"
import BannerNotification from "@/components/Banners/BannerNotification"
import { HubHero } from "@/components/Hero"
import StackIcon from "@/components/icons/stack.svg"
import MainArticle from "@/components/MainArticle"
-import PageMetadata from "@/components/PageMetadata"
import { ResourceItem, ResourcesContainer } from "@/components/Resources"
import { useResources } from "@/components/Resources/useResources"
import Translation from "@/components/Translation"
@@ -17,69 +17,18 @@ import Link from "@/components/ui/Link"
import { Section } from "@/components/ui/section"
import { cn } from "@/lib/utils/cn"
-import { dataLoader } from "@/lib/utils/data/dataLoader"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-import {
- BASE_TIME_UNIT,
- DEFAULT_LOCALE,
- GITHUB_REPO_URL,
- LOCALES_CODES,
-} from "@/lib/constants"
+import { GITHUB_REPO_URL } from "@/lib/constants"
import { useActiveHash } from "@/hooks/useActiveHash"
import { useTranslation } from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
-import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie"
import heroImg from "@/public/images/heroes/guides-hub-hero.jpg"
-// In seconds
-const REVALIDATE_TIME = BASE_TIME_UNIT * 1
-
-const loadData = dataLoader(
- [["growThePieData", fetchGrowThePie]],
- REVALIDATE_TIME * 1000
-)
-
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
+interface ResourcesPageProps {
+ txCostsMedianUsd: MetricReturnData
}
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || ({} as { locale: string })
-
- const [growThePieData] = await loadData()
- const { txCostsMedianUsd } = growThePieData
-
- const requiredNamespaces = getRequiredNamespacesForPage("/resources")
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const messages = await loadNamespaces(locale as string, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- txCostsMedianUsd,
- },
- }
-}) satisfies GetStaticProps
-
-const ResourcesPage = ({ txCostsMedianUsd }) => {
+const ResourcesPage = ({ txCostsMedianUsd }: ResourcesPageProps) => {
const { t } = useTranslation("page-resources")
const resourceSections = useResources({ txCostsMedianUsd })
const activeSection = useActiveHash(
@@ -89,12 +38,6 @@ const ResourcesPage = ({ txCostsMedianUsd }) => {
return (
-
-
{t("page-resources-banner-notification-message")}{" "}
}) => {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/resources")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ // Load data
+ const [growThePieData] = await loadData()
+ const { txCostsMedianUsd } = growThePieData
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({ locale, namespace: "page-resources" })
+
+ return await getMetadata({
+ locale,
+ slug: ["resources"],
+ title: t("page-resources-meta-title"),
+ description: t("page-resources-meta-description"),
+ image: "/images/heroes/guides-hub-hero.jpg",
+ })
+}
+
+export default Page
diff --git a/src/pages/[locale]/roadmap/vision.tsx b/app/[locale]/roadmap/vision/_components/vision.tsx
similarity index 85%
rename from src/pages/[locale]/roadmap/vision.tsx
rename to app/[locale]/roadmap/vision/_components/vision.tsx
index 19f5ecf1db0..a8bc8a05fd6 100644
--- a/src/pages/[locale]/roadmap/vision.tsx
+++ b/app/[locale]/roadmap/vision/_components/vision.tsx
@@ -1,7 +1,8 @@
-import { GetStaticProps } from "next"
+"use client"
+
import type { ComponentProps, ComponentPropsWithRef } from "react"
-import type { BasePageProps, ChildOnlyProp, Lang, Params } from "@/lib/types"
+import type { ChildOnlyProp } from "@/lib/types"
import Breadcrumbs from "@/components/Breadcrumbs"
import Card from "@/components/Card"
@@ -12,7 +13,6 @@ import MainArticle from "@/components/MainArticle"
import PageHero, {
type ContentType as PageHeroContent,
} from "@/components/PageHero"
-import PageMetadata from "@/components/PageMetadata"
import Trilemma from "@/components/Trilemma"
import { ButtonLink } from "@/components/ui/buttons/Button"
import { Divider } from "@/components/ui/divider"
@@ -21,15 +21,8 @@ import InlineLink from "@/components/ui/Link"
import { List, ListItem } from "@/components/ui/list"
import { cn } from "@/lib/utils/cn"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-
-import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
import useTranslation from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
import { usePathname } from "@/i18n/routing"
import oldship from "@/public/images/upgrades/oldship.png"
@@ -97,37 +90,6 @@ const TrilemmaContent = (props: ChildOnlyProp) => (
/>
)
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const requiredNamespaces = getRequiredNamespacesForPage("/roadmap/vision")
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const messages = await loadNamespaces(locale, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- },
- }
-}) satisfies GetStaticProps
-
const VisionPage = () => {
const { t } = useTranslation(["page-roadmap-vision", "page-upgrades-index"])
const pathname = usePathname()
@@ -155,10 +117,6 @@ const VisionPage = () => {
return (
-
diff --git a/app/[locale]/roadmap/vision/page.tsx b/app/[locale]/roadmap/vision/page.tsx
new file mode 100644
index 00000000000..fbf4b375f74
--- /dev/null
+++ b/app/[locale]/roadmap/vision/page.tsx
@@ -0,0 +1,47 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import { Lang } from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { getMetadata } from "@/lib/utils/metadata"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+
+import VisionPage from "./_components/vision"
+
+import { loadMessages } from "@/i18n/loadMessages"
+
+const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/roadmap/vision")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({ locale, namespace: "page-roadmap-vision" })
+
+ return await getMetadata({
+ locale,
+ slug: ["roadmap", "vision"],
+ title: t("page-roadmap-vision-meta-title"),
+ description: t("page-roadmap-vision-meta-desc"),
+ })
+}
+
+export default Page
diff --git a/src/pages/[locale]/run-a-node.tsx b/app/[locale]/run-a-node/_components/run-a-node.tsx
similarity index 94%
rename from src/pages/[locale]/run-a-node.tsx
rename to app/[locale]/run-a-node/_components/run-a-node.tsx
index d375ef613bc..c4f3716ef14 100644
--- a/src/pages/[locale]/run-a-node.tsx
+++ b/app/[locale]/run-a-node/_components/run-a-node.tsx
@@ -1,9 +1,10 @@
+"use client"
+
import { HTMLAttributes } from "react"
-import type { GetStaticProps } from "next/types"
import type { ReactNode } from "react"
import { FaDiscord } from "react-icons/fa"
-import type { BasePageProps, ChildOnlyProp, Lang, Params } from "@/lib/types"
+import type { ChildOnlyProp } from "@/lib/types"
import Emoji from "@/components/Emoji"
import ExpandableCard from "@/components/ExpandableCard"
@@ -22,7 +23,6 @@ import {
import { Image } from "@/components/Image"
import MainArticle from "@/components/MainArticle"
import PageHero from "@/components/PageHero"
-import PageMetadata from "@/components/PageMetadata"
import { StandaloneQuizWidget as QuizWidget } from "@/components/Quiz/QuizWidget"
import Translation from "@/components/Translation"
import { Button, ButtonLink } from "@/components/ui/buttons/Button"
@@ -31,16 +31,9 @@ import { Center, Flex, Stack, VStack } from "@/components/ui/flex"
import InlineLink from "@/components/ui/Link"
import { cn } from "@/lib/utils/cn"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-
-import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
import { useTranslation } from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
-import { InfoGrid } from "@/layouts/md/Staking"
+import { InfoGrid } from "@/layouts"
import community from "@/public/images/enterprise-eth.png"
import hackathon from "@/public/images/hackathon_transparent.png"
import impact from "@/public/images/impact_transparent.png"
@@ -211,37 +204,6 @@ type RunANodeCard = {
alt: string
}
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const requiredNamespaces = getRequiredNamespacesForPage("/run-a-node")
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const messages = await loadNamespaces(locale, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- },
- }
-}) satisfies GetStaticProps
-
const RunANodePage = () => {
const { t } = useTranslation("page-run-a-node")
const heroContent = {
@@ -331,11 +293,6 @@ const RunANodePage = () => {
return (
-
diff --git a/app/[locale]/run-a-node/page.tsx b/app/[locale]/run-a-node/page.tsx
new file mode 100644
index 00000000000..7ba2a806b94
--- /dev/null
+++ b/app/[locale]/run-a-node/page.tsx
@@ -0,0 +1,48 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import { Lang } from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { getMetadata } from "@/lib/utils/metadata"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+
+import RunANodePage from "./_components/run-a-node"
+
+import { loadMessages } from "@/i18n/loadMessages"
+
+const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/run-a-node")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({ locale, namespace: "page-run-a-node" })
+
+ return await getMetadata({
+ locale,
+ slug: ["run-a-node"],
+ title: t("page-run-a-node-meta-title"),
+ description: t("page-run-a-node-meta-description"),
+ image: "/images/run-a-node/ethereum-inside.png",
+ })
+}
+
+export default Page
diff --git a/src/pages/[locale]/stablecoins.tsx b/app/[locale]/stablecoins/_components/stablecoins.tsx
similarity index 78%
rename from src/pages/[locale]/stablecoins.tsx
rename to app/[locale]/stablecoins/_components/stablecoins.tsx
index 85eea8329c2..2804641ba0d 100644
--- a/src/pages/[locale]/stablecoins.tsx
+++ b/app/[locale]/stablecoins/_components/stablecoins.tsx
@@ -1,9 +1,8 @@
+"use client"
+
import { BaseHTMLAttributes } from "react"
-import { GetStaticProps } from "next/types"
import { MdHelpOutline } from "react-icons/md"
-import { BasePageProps, Lang, Params } from "@/lib/types"
-
import CalloutBanner from "@/components/CalloutBanner"
import DataProductCard from "@/components/DataProductCard"
import Emoji from "@/components/Emoji"
@@ -14,7 +13,6 @@ import { Image } from "@/components/Image"
import InfoBanner from "@/components/InfoBanner"
import MainArticle from "@/components/MainArticle"
import PageHero from "@/components/PageHero"
-import PageMetadata from "@/components/PageMetadata"
import ProductList from "@/components/ProductList"
import { StandaloneQuizWidget } from "@/components/Quiz/QuizWidget"
import StablecoinAccordion from "@/components/StablecoinAccordion"
@@ -28,23 +26,13 @@ import { Flex, FlexProps } from "@/components/ui/flex"
import InlineLink from "@/components/ui/Link"
import { cn } from "@/lib/utils/cn"
-import { dataLoader } from "@/lib/utils/data/dataLoader"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-import { BASE_TIME_UNIT, DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
+import { Market } from "../page"
import { useTranslation } from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
-import {
- fetchEthereumEcosystemData,
- fetchEthereumStablecoinsData,
-} from "@/lib/api/stablecoinsData"
import summerfiImg from "@/public/images/dapps/summerfi.png"
import dogeComputerImg from "@/public/images/doge-computer.png"
-// -- daps
+// -- dapps
import aaveImg from "@/public/images/stablecoins/aave.png"
import compoundImg from "@/public/images/stablecoins/compound.png"
// Static assets
@@ -53,149 +41,11 @@ import heroImg from "@/public/images/stablecoins/hero.png"
import stablecoinsWtfImg from "@/public/images/stablecoins/tools/stablecoinswtf.png"
import usdcLargeImg from "@/public/images/stablecoins/usdc-large.png"
-type EthereumDataResponse = Array<{
- id: string
- name: string
- market_cap: number
- image: string
- symbol: string
-}>
-
-type StablecoinDataResponse = Array<{
- id: string
- name: string
- market_cap: number
- image: string
- symbol: string
-}>
-
-interface Market {
- name: string
- marketCap: string
- image: string
- type: string
- url: string
-}
-
-type Props = BasePageProps & {
+type Props = {
markets: Market[]
marketsHasError: boolean
}
-// In seconds
-const REVALIDATE_TIME = BASE_TIME_UNIT * 1
-
-const loadData = dataLoader<[EthereumDataResponse, StablecoinDataResponse]>(
- [
- ["ethereumEcosystemData", fetchEthereumEcosystemData],
- ["ethereumStablecoinsData", fetchEthereumStablecoinsData],
- ],
- REVALIDATE_TIME * 1000
-)
-
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const requiredNamespaces = getRequiredNamespacesForPage("/stablecoins")
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- let marketsHasError = false
- let markets: Market[] = []
-
- // Stablecoin types
- const FIAT = "FIAT"
- const CRYPTO = "CRYPTO"
- const ASSET = "ASSET"
- const ALGORITHMIC = "ALGORITHMIC"
-
- const stablecoins = {
- USDT: { type: FIAT, url: "https://tether.to/" },
- USDC: { type: FIAT, url: "https://www.coinbase.com/usdc" },
- DAI: { type: CRYPTO, url: "https://makerdao.com/en/" },
- BUSD: { type: FIAT, url: "https://www.binance.com/en/busd" },
- PAX: { type: FIAT, url: "https://www.paxos.com/pax/" },
- TUSD: { type: FIAT, url: "https://tusd.io/" },
- HUSD: { type: FIAT, url: "https://www.huobi.com/en-us/usd-deposit/" },
- SUSD: { type: CRYPTO, url: "https://www.synthetix.io/" },
- EURS: { type: FIAT, url: "https://eurs.stasis.net/" },
- USDK: { type: FIAT, url: "https://www.oklink.com/usdk" },
- MUSD: { type: CRYPTO, url: "https://mstable.org/" },
- USDX: { type: CRYPTO, url: "https://usdx.money/" },
- GUSD: { type: FIAT, url: "https://gemini.com/dollar" },
- SAI: { type: CRYPTO, url: "https://makerdao.com/en/whitepaper/sai/" },
- DUSD: { type: CRYPTO, url: "https://dusd.finance/" },
- PAXG: { type: ASSET, url: "https://www.paxos.com/paxgold/" },
- AMPL: { type: ALGORITHMIC, url: "https://www.ampleforth.org/" },
- FRAX: { type: ALGORITHMIC, url: "https://frax.finance/" },
- MIM: { type: ALGORITHMIC, url: "https://abracadabra.money/" },
- USDP: { type: FIAT, url: "https://paxos.com/usdp/" },
- FEI: { type: ALGORITHMIC, url: "https://fei.money/" },
- }
-
- try {
- const [ethereumEcosystemData, stablecoinsData] = await loadData()
-
- // Get the intersection of stablecoins and Ethereum tokens to only have a list of data for stablecoins in the Ethereum ecosystem
- const ethereumStablecoinData = stablecoinsData.filter(
- (stablecoin) =>
- ethereumEcosystemData.findIndex(
- // eslint-disable-next-line
- (etherToken) => stablecoin.id == etherToken.id
- ) > -1
- )
-
- marketsHasError = false
- markets = ethereumStablecoinData
- .filter((token) => {
- return stablecoins[token.symbol.toUpperCase()]
- })
- .map((token) => {
- return {
- name: token.name,
- marketCap: new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: "USD",
- minimumFractionDigits: 0,
- maximumFractionDigits: 0,
- }).format(token.market_cap),
- image: token.image,
- type: stablecoins[token.symbol.toUpperCase()].type,
- url: stablecoins[token.symbol.toUpperCase()].url,
- }
- })
- } catch (error) {
- console.error(error)
- markets = []
- marketsHasError = true
- }
-
- const messages = await loadNamespaces(locale, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- markets,
- marketsHasError,
- },
- }
-}) satisfies GetStaticProps
-
const Content = (props: BaseHTMLAttributes) => (
)
@@ -228,7 +78,7 @@ const H3 = ({
)
-const StablecoinsPage = ({ markets, marketsHasError }) => {
+const StablecoinsPage = ({ markets, marketsHasError }: Props) => {
const { t } = useTranslation("page-stablecoins")
const tooltipContent = (
@@ -409,11 +259,6 @@ const StablecoinsPage = ({ markets, marketsHasError }) => {
return (
-
diff --git a/app/[locale]/stablecoins/page.tsx b/app/[locale]/stablecoins/page.tsx
new file mode 100644
index 00000000000..5937d2f1194
--- /dev/null
+++ b/app/[locale]/stablecoins/page.tsx
@@ -0,0 +1,160 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import { Lang } from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { dataLoader } from "@/lib/utils/data/dataLoader"
+import { getMetadata } from "@/lib/utils/metadata"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+
+import { BASE_TIME_UNIT } from "@/lib/constants"
+
+import StablecoinsPage from "./_components/stablecoins"
+
+import { loadMessages } from "@/i18n/loadMessages"
+import {
+ fetchEthereumEcosystemData,
+ fetchEthereumStablecoinsData,
+} from "@/lib/api/stablecoinsData"
+
+type EthereumDataResponse = Array<{
+ id: string
+ name: string
+ market_cap: number
+ image: string
+ symbol: string
+}>
+
+type StablecoinDataResponse = Array<{
+ id: string
+ name: string
+ market_cap: number
+ image: string
+ symbol: string
+}>
+
+export interface Market {
+ name: string
+ marketCap: string
+ image: string
+ type: string
+ url: string
+}
+
+// In seconds
+const REVALIDATE_TIME = BASE_TIME_UNIT * 1
+
+const loadData = dataLoader<[EthereumDataResponse, StablecoinDataResponse]>(
+ [
+ ["ethereumEcosystemData", fetchEthereumEcosystemData],
+ ["ethereumStablecoinsData", fetchEthereumStablecoinsData],
+ ],
+ REVALIDATE_TIME * 1000
+)
+
+async function Page({ params }: { params: Promise<{ locale: Lang }> }) {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/stablecoins")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ let marketsHasError = false
+ let markets: Market[] = []
+
+ // Stablecoin types
+ const FIAT = "FIAT"
+ const CRYPTO = "CRYPTO"
+ const ASSET = "ASSET"
+ const ALGORITHMIC = "ALGORITHMIC"
+
+ const stablecoins = {
+ USDT: { type: FIAT, url: "https://tether.to/" },
+ USDC: { type: FIAT, url: "https://www.coinbase.com/usdc" },
+ DAI: { type: CRYPTO, url: "https://makerdao.com/en/" },
+ BUSD: { type: FIAT, url: "https://www.binance.com/en/busd" },
+ PAX: { type: FIAT, url: "https://www.paxos.com/pax/" },
+ TUSD: { type: FIAT, url: "https://tusd.io/" },
+ HUSD: { type: FIAT, url: "https://www.huobi.com/en-us/usd-deposit/" },
+ SUSD: { type: CRYPTO, url: "https://www.synthetix.io/" },
+ EURS: { type: FIAT, url: "https://eurs.stasis.net/" },
+ USDK: { type: FIAT, url: "https://www.oklink.com/usdk" },
+ MUSD: { type: CRYPTO, url: "https://mstable.org/" },
+ USDX: { type: CRYPTO, url: "https://usdx.money/" },
+ GUSD: { type: FIAT, url: "https://gemini.com/dollar" },
+ SAI: { type: CRYPTO, url: "https://makerdao.com/en/whitepaper/sai/" },
+ DUSD: { type: CRYPTO, url: "https://dusd.finance/" },
+ PAXG: { type: ASSET, url: "https://www.paxos.com/paxgold/" },
+ AMPL: { type: ALGORITHMIC, url: "https://www.ampleforth.org/" },
+ FRAX: { type: ALGORITHMIC, url: "https://frax.finance/" },
+ MIM: { type: ALGORITHMIC, url: "https://abracadabra.money/" },
+ USDP: { type: FIAT, url: "https://paxos.com/usdp/" },
+ FEI: { type: ALGORITHMIC, url: "https://fei.money/" },
+ }
+
+ try {
+ const [ethereumEcosystemData, stablecoinsData] = await loadData()
+
+ // Get the intersection of stablecoins and Ethereum tokens to only have a list of data for stablecoins in the Ethereum ecosystem
+ const ethereumStablecoinData = stablecoinsData.filter(
+ (stablecoin) =>
+ ethereumEcosystemData.findIndex(
+ // eslint-disable-next-line
+ (etherToken) => stablecoin.id == etherToken.id
+ ) > -1
+ )
+
+ marketsHasError = false
+ markets = ethereumStablecoinData
+ .filter((token) => {
+ return stablecoins[token.symbol.toUpperCase()]
+ })
+ .map((token) => {
+ return {
+ name: token.name,
+ marketCap: new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ }).format(token.market_cap),
+ image: token.image,
+ type: stablecoins[token.symbol.toUpperCase()].type,
+ url: stablecoins[token.symbol.toUpperCase()].url,
+ }
+ })
+ } catch (error) {
+ console.error(error)
+ markets = []
+ marketsHasError = true
+ }
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({ locale, namespace: "page-stablecoins" })
+
+ return await getMetadata({
+ locale,
+ slug: ["stablecoins"],
+ title: t("page-stablecoins-meta-title"),
+ description: t("page-stablecoins-meta-description"),
+ image: "/images/stablecoins/hero.png",
+ })
+}
+
+export default Page
diff --git a/src/pages/[locale]/staking/index.tsx b/app/[locale]/staking/_components/staking.tsx
similarity index 86%
rename from src/pages/[locale]/staking/index.tsx
rename to app/[locale]/staking/_components/staking.tsx
index d54b0d2439a..d12ace0b8aa 100644
--- a/src/pages/[locale]/staking/index.tsx
+++ b/app/[locale]/staking/_components/staking.tsx
@@ -1,15 +1,8 @@
+"use client"
+
import { type HTMLAttributes, ReactNode } from "react"
-import { GetStaticProps, InferGetStaticPropsType } from "next"
-import type {
- BasePageProps,
- ChildOnlyProp,
- EpochResponse,
- EthStoreResponse,
- Lang,
- Params,
- StakingStatsData,
-} from "@/lib/types"
+import type { ChildOnlyProp, StakingStatsData } from "@/lib/types"
import { List as ButtonDropdownList } from "@/components/ButtonDropdown"
import Card from "@/components/Card"
@@ -19,7 +12,6 @@ import LeftNavBar from "@/components/LeftNavBar"
import { ContentContainer, Page } from "@/components/MdComponents"
import MobileButtonDropdown from "@/components/MobileButtonDropdown"
import PageHero from "@/components/PageHero"
-import PageMetadata from "@/components/PageMetadata"
import StakingCommunityCallout from "@/components/Staking/StakingCommunityCallout"
import StakingHierarchy from "@/components/Staking/StakingHierarchy"
import StakingStatsBox from "@/components/Staking/StakingStatsBox"
@@ -34,16 +26,8 @@ import InlineLink from "@/components/ui/Link"
import { ListItem, UnorderedList } from "@/components/ui/list"
import { cn } from "@/lib/utils/cn"
-import { dataLoader } from "@/lib/utils/data/dataLoader"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-
-import { BASE_TIME_UNIT, DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
import useTranslation from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
import rhino from "@/public/images/upgrades/upgrade_rhino.png"
type BenefitsType = {
@@ -116,84 +100,11 @@ const StyledCard = (props: {
)
-const fetchBeaconchainData = async (): Promise => {
- // Fetch Beaconcha.in data
- const base = "https://beaconcha.in"
- const { href: ethstore } = new URL("api/v1/ethstore/latest", base)
- const { href: epoch } = new URL("api/v1/epoch/latest", base)
-
- // Get total ETH staked and current APR from ethstore endpoint
- const ethStoreResponse = await fetch(ethstore)
- if (!ethStoreResponse.ok)
- throw new Error("Network response from Beaconcha.in ETHSTORE was not ok")
- const ethStoreResponseJson: EthStoreResponse = await ethStoreResponse.json()
- const {
- data: { apr, effective_balances_sum_wei },
- } = ethStoreResponseJson
- const totalEffectiveBalance = effective_balances_sum_wei * 1e-18
- const totalEthStaked = Math.floor(totalEffectiveBalance)
-
- // Get total active validators from latest epoch endpoint
- const epochResponse = await fetch(epoch)
- if (!epochResponse.ok)
- throw new Error("Network response from Beaconcha.in EPOCH was not ok")
- const epochResponseJson: EpochResponse = await epochResponse.json()
- const {
- data: { validatorscount },
- } = epochResponseJson
-
- return { totalEthStaked, validatorscount, apr }
-}
-
-type Props = BasePageProps & {
+type Props = {
data: StakingStatsData
}
-// In seconds
-const REVALIDATE_TIME = BASE_TIME_UNIT * 1
-
-const loadData = dataLoader(
- [["stakingStatsData", fetchBeaconchainData]],
- REVALIDATE_TIME * 1000
-)
-
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const requiredNamespaces = getRequiredNamespacesForPage("/staking")
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- const [data] = await loadData()
-
- const messages = await loadNamespaces(locale, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- data,
- lastDeployLocaleTimestamp,
- },
- }
-}) satisfies GetStaticProps
-
-const StakingPage = ({
- data,
-}: InferGetStaticPropsType) => {
+const StakingPage = ({ data }: Props) => {
const { t } = useTranslation("page-staking")
const heroContent = {
@@ -326,11 +237,6 @@ const StakingPage = ({
return (
-
diff --git a/src/pages/[locale]/staking/deposit-contract.tsx b/app/[locale]/staking/deposit-contract/_components/deposit-contract.tsx
similarity index 89%
rename from src/pages/[locale]/staking/deposit-contract.tsx
rename to app/[locale]/staking/deposit-contract/_components/deposit-contract.tsx
index a5c4424a60a..8456b86c71a 100644
--- a/src/pages/[locale]/staking/deposit-contract.tsx
+++ b/app/[locale]/staking/deposit-contract/_components/deposit-contract.tsx
@@ -1,14 +1,9 @@
+"use client"
+
import { useEffect, useState } from "react"
import makeBlockie from "ethereum-blockies-base64"
-import { type GetStaticProps } from "next"
-import type {
- BasePageProps,
- ChildOnlyProp,
- Lang,
- Params,
- TranslationKey,
-} from "@/lib/types"
+import type { ChildOnlyProp, TranslationKey } from "@/lib/types"
import Breadcrumbs from "@/components/Breadcrumbs"
import CardList from "@/components/CardList"
@@ -18,7 +13,6 @@ import FeedbackCard from "@/components/FeedbackCard"
import { Image } from "@/components/Image"
import InfoBanner from "@/components/InfoBanner"
import MainArticle from "@/components/MainArticle"
-import PageMetadata from "@/components/PageMetadata"
import Tooltip from "@/components/Tooltip"
import Translation from "@/components/Translation"
import {
@@ -31,17 +25,9 @@ import Checkbox from "@/components/ui/checkbox"
import { Flex } from "@/components/ui/flex"
import InlineLink from "@/components/ui/Link"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-
import { DEPOSIT_CONTRACT_ADDRESS } from "@/data/addresses"
-import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
-
import useTranslation from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
import { usePathname } from "@/i18n/routing"
import consensys from "@/public/images/projects/consensys.png"
import etherscan from "@/public/images/projects/etherscan-logo-circle.png"
@@ -158,39 +144,6 @@ const CHUNKED_ADDRESS = DEPOSIT_CONTRACT_ADDRESS.match(/.{1,3}/g)?.join(" ")
const blockieSrc = makeBlockie(DEPOSIT_CONTRACT_ADDRESS)
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const requiredNamespaces = getRequiredNamespacesForPage(
- "/staking/deposit-contract"
- )
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const messages = await loadNamespaces(locale, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- },
- }
-}) satisfies GetStaticProps
-
const DepositContractPage = () => {
const pathname = usePathname()
@@ -304,10 +257,6 @@ const DepositContractPage = () => {
return (
-
{t("page-staking-deposit-contract-title")}
diff --git a/app/[locale]/staking/deposit-contract/page.tsx b/app/[locale]/staking/deposit-contract/page.tsx
new file mode 100644
index 00000000000..79374b999d4
--- /dev/null
+++ b/app/[locale]/staking/deposit-contract/page.tsx
@@ -0,0 +1,52 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import { Lang } from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { getMetadata } from "@/lib/utils/metadata"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+
+import DepositContractPage from "./_components/deposit-contract"
+
+import { loadMessages } from "@/i18n/loadMessages"
+
+const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage(
+ "/staking/deposit-contract"
+ )
+ const messages = pick(allMessages, requiredNamespaces)
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({
+ locale,
+ namespace: "page-staking-deposit-contract",
+ })
+
+ return await getMetadata({
+ locale,
+ slug: ["staking", "deposit-contract"],
+ title: t("page-staking-deposit-contract-meta-title"),
+ description: t("page-staking-deposit-contract-meta-description"),
+ })
+}
+
+export default Page
diff --git a/app/[locale]/staking/page.tsx b/app/[locale]/staking/page.tsx
new file mode 100644
index 00000000000..9de81a5f1ad
--- /dev/null
+++ b/app/[locale]/staking/page.tsx
@@ -0,0 +1,95 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import {
+ EpochResponse,
+ EthStoreResponse,
+ Lang,
+ StakingStatsData,
+} from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { dataLoader } from "@/lib/utils/data/dataLoader"
+import { getMetadata } from "@/lib/utils/metadata"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+
+import { BASE_TIME_UNIT } from "@/lib/constants"
+
+import StakingPage from "./_components/staking"
+
+import { loadMessages } from "@/i18n/loadMessages"
+
+const fetchBeaconchainData = async (): Promise => {
+ // Fetch Beaconcha.in data
+ const base = "https://beaconcha.in"
+ const { href: ethstore } = new URL("api/v1/ethstore/latest", base)
+ const { href: epoch } = new URL("api/v1/epoch/latest", base)
+
+ // Get total ETH staked and current APR from ethstore endpoint
+ const ethStoreResponse = await fetch(ethstore)
+ if (!ethStoreResponse.ok)
+ throw new Error("Network response from Beaconcha.in ETHSTORE was not ok")
+ const ethStoreResponseJson: EthStoreResponse = await ethStoreResponse.json()
+ const {
+ data: { apr, effective_balances_sum_wei },
+ } = ethStoreResponseJson
+ const totalEffectiveBalance = effective_balances_sum_wei * 1e-18
+ const totalEthStaked = Math.floor(totalEffectiveBalance)
+
+ // Get total active validators from latest epoch endpoint
+ const epochResponse = await fetch(epoch)
+ if (!epochResponse.ok)
+ throw new Error("Network response from Beaconcha.in EPOCH was not ok")
+ const epochResponseJson: EpochResponse = await epochResponse.json()
+ const {
+ data: { validatorscount },
+ } = epochResponseJson
+
+ return { totalEthStaked, validatorscount, apr }
+}
+
+// In seconds
+const REVALIDATE_TIME = BASE_TIME_UNIT * 1
+
+const loadData = dataLoader(
+ [["stakingStatsData", fetchBeaconchainData]],
+ REVALIDATE_TIME * 1000
+)
+
+const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
+ const { locale } = await params
+
+ const [data] = await loadData()
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/staking")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({ locale, namespace: "page-staking" })
+
+ return await getMetadata({
+ locale,
+ slug: ["staking"],
+ title: t("page-staking-meta-title"),
+ description: t("page-staking-meta-description"),
+ image: "/images/upgrades/upgrade_rhino.png",
+ })
+}
+
+export default Page
diff --git a/src/pages/[locale]/start/index.tsx b/app/[locale]/start/_components/start.tsx
similarity index 65%
rename from src/pages/[locale]/start/index.tsx
rename to app/[locale]/start/_components/start.tsx
index 3ba5ab90a75..568ab219830 100644
--- a/src/pages/[locale]/start/index.tsx
+++ b/app/[locale]/start/_components/start.tsx
@@ -1,25 +1,16 @@
-import { GetStaticProps } from "next"
+"use client"
+
import dynamic from "next/dynamic"
import { useLocale } from "next-intl"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
-import { BasePageProps, Lang, Wallet } from "@/lib/types"
+import { Wallet } from "@/lib/types"
import { Image } from "@/components/Image"
import MainArticle from "@/components/MainArticle"
-import PageMetadata from "@/components/PageMetadata"
import StartWithEthereumFlow from "@/components/StartWithEthereumFlow"
import ShareModal from "@/components/StartWithEthereumFlow/ShareModal"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-import { getNewToCryptoWallets } from "@/lib/utils/wallets"
-
-import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
-
-import loadNamespaces from "@/i18n/loadNamespaces"
import HeroImage from "@/public/images/heroes/developers-hub-hero.jpg"
import ManDogeImage from "@/public/images/start-with-ethereum/man-doge-playing.png"
@@ -30,43 +21,6 @@ const WalletProviders = dynamic(() => import("@/components/WalletProviders"), {
const queryClient = new QueryClient()
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const requiredNamespaces = getRequiredNamespacesForPage("/start")
-
- const contentNotTranslated = !existsNamespace(
- locale! as string,
- requiredNamespaces[2]
- )
-
- const messages = await loadNamespaces(locale as string, requiredNamespaces)
-
- const newToCryptoWallets = getNewToCryptoWallets()
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- newToCryptoWallets,
- },
- }
-}) satisfies GetStaticProps
-
const StartWithCryptoPage = ({
newToCryptoWallets,
}: {
@@ -78,12 +32,6 @@ const StartWithCryptoPage = ({
-
-
}) => {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/start")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ const newToCryptoWallets = getNewToCryptoWallets()
+ const wallets = newToCryptoWallets.map((wallet) => ({
+ ...wallet,
+ supportedLanguages: [],
+ }))
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ return await getMetadata({
+ locale,
+ slug: ["start"],
+ title: "Start with crypto",
+ description: "Your gateway to the world of ethereum",
+ image: "/images/heroes/developers-hub-hero.jpg",
+ })
+}
+
+export default Page
diff --git a/src/pages/[locale]/wallets/index.tsx b/app/[locale]/wallets/_components/wallets.tsx
similarity index 90%
rename from src/pages/[locale]/wallets/index.tsx
rename to app/[locale]/wallets/_components/wallets.tsx
index e28bca90d7b..9f633396037 100644
--- a/src/pages/[locale]/wallets/index.tsx
+++ b/app/[locale]/wallets/_components/wallets.tsx
@@ -1,9 +1,8 @@
+"use client"
+
import { ComponentPropsWithRef } from "react"
-import { GetStaticProps } from "next"
import { useLocale } from "next-intl"
-import { BasePageProps, Lang, Params } from "@/lib/types"
-
import Callout from "@/components/Callout"
import Card from "@/components/Card"
import CardList from "@/components/CardList"
@@ -13,7 +12,6 @@ import { Image } from "@/components/Image"
import ListenToPlayer from "@/components/ListenToPlayer"
import MainArticle from "@/components/MainArticle"
import PageHero from "@/components/PageHero"
-import PageMetadata from "@/components/PageMetadata"
import { StandaloneQuizWidget } from "@/components/Quiz/QuizWidget"
import { Simulator } from "@/components/Simulator"
import { SIMULATOR_ID } from "@/components/Simulator/constants"
@@ -21,17 +19,9 @@ import Translation from "@/components/Translation"
import { ButtonLink } from "@/components/ui/buttons/Button"
import { Divider } from "@/components/ui/divider"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-
import { walletOnboardingSimData } from "@/data/WalletSimulatorData"
-import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
-
import { useTranslation } from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
import { usePathname } from "@/i18n/routing"
import DappsImage from "@/public/images/doge-computer.png"
import ETHImage from "@/public/images/eth-logo.png"
@@ -45,37 +35,6 @@ export const StyledCard = (props: ComponentPropsWithRef) => (
/>
)
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const requiredNamespaces = getRequiredNamespacesForPage("/wallets")
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- const messages = await loadNamespaces(locale!, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- },
- }
-}) satisfies GetStaticProps
-
const WalletsPage = () => {
const pathname = usePathname()
const locale = useLocale()
@@ -219,12 +178,6 @@ const WalletsPage = () => {
return (
-
-
diff --git a/src/pages/[locale]/wallets/find-wallet.tsx b/app/[locale]/wallets/find-wallet/_components/find-wallet.tsx
similarity index 50%
rename from src/pages/[locale]/wallets/find-wallet.tsx
rename to app/[locale]/wallets/find-wallet/_components/find-wallet.tsx
index 622b8ccc09d..acdc94c9aad 100644
--- a/src/pages/[locale]/wallets/find-wallet.tsx
+++ b/app/[locale]/wallets/find-wallet/_components/find-wallet.tsx
@@ -1,36 +1,17 @@
-import { GetStaticProps, InferGetStaticPropsType } from "next"
+"use client"
-import type {
- BasePageProps,
- ChildOnlyProp,
- Lang,
- Params,
- Wallet,
-} from "@/lib/types"
+import type { ChildOnlyProp, Wallet } from "@/lib/types"
import BannerNotification from "@/components/Banners/BannerNotification"
import Breadcrumbs from "@/components/Breadcrumbs"
import FindWalletProductTable from "@/components/FindWalletProductTable"
import { Image } from "@/components/Image"
import MainArticle from "@/components/MainArticle"
-import PageMetadata from "@/components/PageMetadata"
import InlineLink from "@/components/ui/Link"
import { cn } from "@/lib/utils/cn"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-import {
- getNonSupportedLocaleWallets,
- getSupportedLanguages,
- getSupportedLocaleWallets,
-} from "@/lib/utils/wallets"
-
-import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
import { useTranslation } from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
import { usePathname } from "@/i18n/routing"
import HeroImage from "@/public/images/wallets/wallet-hero.png"
@@ -40,70 +21,16 @@ const Subtitle = ({ children }: ChildOnlyProp) => (
)
-type Props = BasePageProps & {
+type Props = {
wallets: Wallet[]
}
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const requiredNamespaces = getRequiredNamespacesForPage(
- "/wallets/find-wallet"
- )
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- const supportedLocaleWallets = getSupportedLocaleWallets(locale!)
- const noSupportedLocaleWallets = getNonSupportedLocaleWallets(locale!)
- const walletsData = supportedLocaleWallets.concat(noSupportedLocaleWallets)
-
- const wallets = walletsData.map((wallet) => ({
- ...wallet,
- supportedLanguages: getSupportedLanguages(
- wallet.languages_supported,
- locale!
- ),
- }))
-
- const messages = await loadNamespaces(locale!, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- wallets,
- },
- }
-}) satisfies GetStaticProps
-
-const FindWalletPage = ({
- wallets,
-}: InferGetStaticPropsType) => {
+const FindWalletPage = ({ wallets }: Props) => {
const pathname = usePathname()
const { t } = useTranslation("page-wallets-find-wallet")
return (
-
-
{t("page-find-wallet-footnote-1")}
diff --git a/app/[locale]/wallets/find-wallet/page.tsx b/app/[locale]/wallets/find-wallet/page.tsx
new file mode 100644
index 00000000000..24463a30dcb
--- /dev/null
+++ b/app/[locale]/wallets/find-wallet/page.tsx
@@ -0,0 +1,70 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import { Lang } from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { getMetadata } from "@/lib/utils/metadata"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+import {
+ getNonSupportedLocaleWallets,
+ getSupportedLanguages,
+ getSupportedLocaleWallets,
+} from "@/lib/utils/wallets"
+
+import FindWalletPage from "./_components/find-wallet"
+
+import { loadMessages } from "@/i18n/loadMessages"
+
+const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
+ const { locale } = await params
+
+ const supportedLocaleWallets = getSupportedLocaleWallets(locale!)
+ const noSupportedLocaleWallets = getNonSupportedLocaleWallets(locale!)
+ const walletsData = supportedLocaleWallets.concat(noSupportedLocaleWallets)
+
+ const wallets = walletsData.map((wallet) => ({
+ ...wallet,
+ supportedLanguages: getSupportedLanguages(
+ wallet.languages_supported,
+ locale!
+ ),
+ }))
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage(
+ "/wallets/find-wallet"
+ )
+ const messages = pick(allMessages, requiredNamespaces)
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({
+ locale,
+ namespace: "page-wallets-find-wallet",
+ })
+
+ return await getMetadata({
+ locale,
+ slug: ["wallets", "find-wallet"],
+ title: t("page-find-wallet-meta-title"),
+ description: t("page-find-wallet-meta-description"),
+ image: "/images/wallets/wallet-hero.png",
+ })
+}
+
+export default Page
diff --git a/app/[locale]/wallets/page.tsx b/app/[locale]/wallets/page.tsx
new file mode 100644
index 00000000000..bfd405ed23f
--- /dev/null
+++ b/app/[locale]/wallets/page.tsx
@@ -0,0 +1,48 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import { Lang } from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { getMetadata } from "@/lib/utils/metadata"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+
+import WalletsPage from "./_components/wallets"
+
+import { loadMessages } from "@/i18n/loadMessages"
+
+const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/wallets")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({ locale, namespace: "page-wallets" })
+
+ return await getMetadata({
+ locale,
+ slug: ["wallets"],
+ title: t("page-wallets-meta-title"),
+ description: t("page-wallets-meta-description"),
+ image: "/images/wallets/wallet-hero.png",
+ })
+}
+
+export default Page
diff --git a/src/pages/[locale]/what-is-ethereum.tsx b/app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx
similarity index 94%
rename from src/pages/[locale]/what-is-ethereum.tsx
rename to app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx
index 4196096120c..e50d251ac0c 100644
--- a/src/pages/[locale]/what-is-ethereum.tsx
+++ b/app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx
@@ -1,16 +1,11 @@
-import { GetStaticProps, InferGetStaticPropsType } from "next"
+"use client"
+
import type { ImageProps } from "next/image"
import { useLocale } from "next-intl"
import type { HTMLAttributes } from "react"
import { MdInfoOutline } from "react-icons/md"
-import type {
- BasePageProps,
- ChildOnlyProp,
- Lang,
- MetricReturnData,
- Params,
-} from "@/lib/types"
+import type { ChildOnlyProp, Lang, MetricReturnData } from "@/lib/types"
import AdoptionChart from "@/components/AdoptionChart"
import {
@@ -27,7 +22,6 @@ import FeedbackCard from "@/components/FeedbackCard"
import { Image } from "@/components/Image"
import ListenToPlayer from "@/components/ListenToPlayer"
import MainArticle from "@/components/MainArticle"
-import PageMetadata from "@/components/PageMetadata"
import { StandaloneQuizWidget } from "@/components/Quiz/QuizWidget"
import StatErrorMessage from "@/components/StatErrorMessage"
import Tooltip from "@/components/Tooltip"
@@ -44,22 +38,11 @@ import {
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { cn } from "@/lib/utils/cn"
-import { dataLoader } from "@/lib/utils/data/dataLoader"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
import { trackCustomEvent } from "@/lib/utils/matomo"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import {
- getLocaleForNumberFormat,
- getRequiredNamespacesForPage,
-} from "@/lib/utils/translations"
-
-import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
+import { getLocaleForNumberFormat } from "@/lib/utils/translations"
import useTranslation from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
import { usePathname } from "@/i18n/routing"
-import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie"
import dogeComputerImg from "@/public/images/doge-computer.png"
import ethImg from "@/public/images/eth.png"
import diffEthAndBtc from "@/public/images/eth.png"
@@ -177,49 +160,11 @@ const Image400 = ({ src }: Pick) => (
)
-type Props = BasePageProps & {
+type Props = {
data: MetricReturnData
}
-const loadData = dataLoader([["growThePieData", fetchGrowThePie]])
-
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const [data] = await loadData()
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const requiredNamespaces = getRequiredNamespacesForPage("/what-is-ethereum")
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- const messages = await loadNamespaces(locale, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- data: data.txCount,
- },
- }
-}) satisfies GetStaticProps
-
-const WhatIsEthereumPage = ({
- data,
-}: InferGetStaticPropsType) => {
+const WhatIsEthereumPage = ({ data }: Props) => {
const { t } = useTranslation(["page-what-is-ethereum", "learn-quizzes"])
const pathname = usePathname()
const locale = useLocale()
@@ -325,11 +270,6 @@ const WhatIsEthereumPage = ({
return (
-
diff --git a/app/[locale]/what-is-ethereum/page.tsx b/app/[locale]/what-is-ethereum/page.tsx
new file mode 100644
index 00000000000..b9e0285c2c2
--- /dev/null
+++ b/app/[locale]/what-is-ethereum/page.tsx
@@ -0,0 +1,58 @@
+import pick from "lodash.pick"
+import { getTranslations } from "next-intl/server"
+
+import { Lang } from "@/lib/types"
+
+import I18nProvider from "@/components/I18nProvider"
+
+import { dataLoader } from "@/lib/utils/data/dataLoader"
+import { getMetadata } from "@/lib/utils/metadata"
+import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
+
+import WhatIsEthereumPage from "./_components/what-is-ethereum"
+
+import { loadMessages } from "@/i18n/loadMessages"
+import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie"
+
+const loadData = dataLoader([["growThePieData", fetchGrowThePie]])
+
+const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
+ const { locale } = await params
+
+ // Get i18n messages
+ const allMessages = await loadMessages(locale)
+ const requiredNamespaces = getRequiredNamespacesForPage("/what-is-ethereum")
+ const messages = pick(allMessages, requiredNamespaces)
+
+ // Load data
+ const [data] = await loadData()
+
+ return (
+
+
+
+ )
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>
+}) {
+ const { locale } = await params
+
+ const t = await getTranslations({
+ locale,
+ namespace: "page-what-is-ethereum",
+ })
+
+ return await getMetadata({
+ locale,
+ slug: ["what-is-ethereum"],
+ title: t("page-what-is-ethereum-meta-title"),
+ description: t("page-what-is-ethereum-meta-description"),
+ image: "/images/what-is-ethereum.png",
+ })
+}
+
+export default Page
diff --git a/src/pages/api/gfi-issues-webhook.ts b/app/api/gfi-issues-webhook/route.ts
similarity index 69%
rename from src/pages/api/gfi-issues-webhook.ts
rename to app/api/gfi-issues-webhook/route.ts
index da398756abd..149a11d761d 100644
--- a/src/pages/api/gfi-issues-webhook.ts
+++ b/app/api/gfi-issues-webhook/route.ts
@@ -1,4 +1,4 @@
-import type { NextApiRequest, NextApiResponse } from "next"
+import { NextResponse } from "next/server"
import { normalizeLabels } from "@/lib/utils/gh"
@@ -11,34 +11,33 @@ const LABELS_TO_EMOJI = {
event: "🗓️",
}
-type ResponseData = {
- message: string
-}
-
const GFI_LABEL = "good first issue"
-export default async function handler(
- req: NextApiRequest,
- res: NextApiResponse
-) {
+export async function GET(req: Request) {
const { method } = req
if (method !== "POST") {
- return res.status(405).json({ message: "Method not allowed" })
+ return NextResponse.json({ message: "Method not allowed" }, { status: 405 })
}
- const { action, label, issue } = req.body
+ const { action, label, issue } = await req.json()
if (action !== "labeled") {
- return res.status(200).json({ message: "Not a label action" })
+ return NextResponse.json({ message: "Not a label action" }, { status: 200 })
}
if (label.name !== GFI_LABEL) {
- return res.status(200).json({ message: "Not a good first issue" })
+ return NextResponse.json(
+ { message: "Not a good first issue" },
+ { status: 200 }
+ )
}
if (issue.assignee) {
- return res.status(200).json({ message: "Issue already assigned" })
+ return NextResponse.json(
+ { message: "Issue already assigned" },
+ { status: 200 }
+ )
}
// send a notification to discord webhook
@@ -86,8 +85,14 @@ export default async function handler(
if (!discordRes.ok) {
const error = await discordRes.json()
console.log(error)
- return res.status(500).json({ message: "Error sending GFI to Discord" })
+ return NextResponse.json(
+ { message: "Error sending GFI to Discord" },
+ { status: 500 }
+ )
}
- res.status(200).json({ message: "New GFI sent to Discord!" })
+ return NextResponse.json(
+ { message: "New GFI sent to Discord!" },
+ { status: 200 }
+ )
}
diff --git a/src/pages/api/revalidate.ts b/app/api/revalidate/route.ts
similarity index 60%
rename from src/pages/api/revalidate.ts
rename to app/api/revalidate/route.ts
index d7d39309a00..7d199ce6b27 100644
--- a/src/pages/api/revalidate.ts
+++ b/app/api/revalidate/route.ts
@@ -1,13 +1,14 @@
-import type { NextApiRequest, NextApiResponse } from "next"
+import { revalidatePath } from "next/cache"
+import { NextRequest, NextResponse } from "next/server"
import i18nConfig from "../../../i18n.config.json"
-export default async function handler(
- req: NextApiRequest,
- res: NextApiResponse
-) {
- if (req.query.secret !== process.env.REVALIDATE_SECRET) {
- return res.status(401).json({ message: "Invalid secret" })
+export async function GET(req: NextRequest) {
+ const searchParams = req.nextUrl.searchParams
+ const secret = searchParams.get("secret")
+
+ if (secret !== process.env.REVALIDATE_SECRET) {
+ return NextResponse.json({ message: "Invalid secret" }, { status: 401 })
}
const BUILD_LOCALES = process.env.NEXT_PUBLIC_BUILD_LOCALES
@@ -16,12 +17,12 @@ export default async function handler(
? BUILD_LOCALES.split(",")
: i18nConfig.map(({ code }) => code)
- const path = req.query.path as string
+ const path = searchParams.get("path")
console.log("Revalidating", path)
try {
if (!path) {
- return res.status(400).json({ message: "No path provided" })
+ return NextResponse.json({ message: "No path provided" }, { status: 400 })
}
const hasLocaleInPath = locales.some((locale) =>
@@ -29,10 +30,10 @@ export default async function handler(
)
if (hasLocaleInPath) {
- await res.revalidate(path)
+ revalidatePath(path)
} else {
// First revalidate the default locale to cache the results
- await res.revalidate(`/en${path}`)
+ revalidatePath(`/en${path}`)
// Then revalidate all other locales
await Promise.all(
@@ -40,7 +41,7 @@ export default async function handler(
const localePath = `/${locale}${path}`
console.log(`Revalidating ${localePath}`)
try {
- await res.revalidate(localePath)
+ revalidatePath(localePath)
} catch (err) {
console.error(`Error revalidating ${localePath}`, err)
throw new Error(`Error revalidating ${localePath}`)
@@ -49,11 +50,11 @@ export default async function handler(
)
}
- return res.json({ revalidated: true })
+ return NextResponse.json({ revalidated: true })
} catch (err) {
console.error(err)
// If there was an error, Next.js will continue
// to show the last successfully generated page
- return res.status(500).send("Error revalidating")
+ return NextResponse.json({ message: "Error revalidating" }, { status: 500 })
}
}
diff --git a/app/favicon.ico b/app/favicon.ico
new file mode 100644
index 00000000000..9689910bf5a
Binary files /dev/null and b/app/favicon.ico differ
diff --git a/public/manifest.json b/app/manifest.json
similarity index 100%
rename from public/manifest.json
rename to app/manifest.json
diff --git a/next.config.js b/next.config.js
index 7dc9408b874..5c39cb7693e 100644
--- a/next.config.js
+++ b/next.config.js
@@ -71,6 +71,10 @@ module.exports = (phase, { defaultConfig }) => {
},
})
+ // WalletConnect related packages are not needed for the bundle
+ // https://docs.reown.com/appkit/next/core/installation#extra-configuration
+ config.externals.push("pino-pretty", "lokijs", "encoding")
+
return config
},
trailingSlash: true,
diff --git a/public/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf b/public/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf
deleted file mode 100644
index 81ca3dcc926..00000000000
Binary files a/public/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf and /dev/null differ
diff --git a/public/fonts/inter/cyrillic-ext.woff2 b/public/fonts/inter/cyrillic-ext.woff2
deleted file mode 100644
index a61a0be57fb..00000000000
Binary files a/public/fonts/inter/cyrillic-ext.woff2 and /dev/null differ
diff --git a/public/fonts/inter/cyrillic.woff2 b/public/fonts/inter/cyrillic.woff2
deleted file mode 100644
index b655a438842..00000000000
Binary files a/public/fonts/inter/cyrillic.woff2 and /dev/null differ
diff --git a/public/fonts/inter/greek-ext.woff2 b/public/fonts/inter/greek-ext.woff2
deleted file mode 100644
index 9117b5b040d..00000000000
Binary files a/public/fonts/inter/greek-ext.woff2 and /dev/null differ
diff --git a/public/fonts/inter/greek.woff2 b/public/fonts/inter/greek.woff2
deleted file mode 100644
index eb38b38ea07..00000000000
Binary files a/public/fonts/inter/greek.woff2 and /dev/null differ
diff --git a/public/fonts/inter/latin-ext.woff2 b/public/fonts/inter/latin-ext.woff2
deleted file mode 100644
index 3df865d7f00..00000000000
Binary files a/public/fonts/inter/latin-ext.woff2 and /dev/null differ
diff --git a/public/fonts/inter/latin.woff2 b/public/fonts/inter/latin.woff2
deleted file mode 100644
index 40255432a3c..00000000000
Binary files a/public/fonts/inter/latin.woff2 and /dev/null differ
diff --git a/public/fonts/inter/vietnamese.woff2 b/public/fonts/inter/vietnamese.woff2
deleted file mode 100644
index ce21ca172ea..00000000000
Binary files a/public/fonts/inter/vietnamese.woff2 and /dev/null differ
diff --git a/public/images/favicon.png b/public/images/eth-org-logo.png
similarity index 100%
rename from public/images/favicon.png
rename to public/images/eth-org-logo.png
diff --git a/src/components/Matomo.tsx b/src/components/Matomo.tsx
new file mode 100644
index 00000000000..297d2e25ead
--- /dev/null
+++ b/src/components/Matomo.tsx
@@ -0,0 +1,56 @@
+"use client"
+
+import { useEffect, useState } from "react"
+import { usePathname } from "next/navigation"
+import { init, push } from "@socialgouv/matomo-next"
+
+export default function Matomo() {
+ const pathname = usePathname()
+
+ const [inited, setInited] = useState(false)
+ const [previousPath, setPreviousPath] = useState("")
+
+ useEffect(() => {
+ if (!process.env.IS_PREVIEW_DEPLOY && !inited) {
+ init({
+ url: process.env.NEXT_PUBLIC_MATOMO_URL!,
+ siteId: process.env.NEXT_PUBLIC_MATOMO_SITE_ID!,
+ })
+
+ setInited(true)
+ }
+ }, [inited])
+
+ /**
+ * The @socialgouv/matomo-next does not work with next 13
+ * Code from https://github.com/SocialGouv/matomo-next/issues/99
+ */
+ useEffect(() => {
+ if (!pathname) {
+ return
+ }
+
+ if (!previousPath) {
+ return setPreviousPath(pathname)
+ }
+
+ push(["setReferrerUrl", `${previousPath}`])
+ push(["setCustomUrl", pathname])
+ push(["deleteCustomVariables", "page"])
+ setPreviousPath(pathname)
+ // In order to ensure that the page title had been updated,
+ // we delayed pushing the tracking to the next tick.
+ setTimeout(() => {
+ push(["setDocumentTitle", document.title])
+ push(["trackPageView"])
+ })
+ /**
+ * This is because we don't want to track previousPath
+ * could be a if (previousPath === pathname) return; instead
+ * But be sure to not send the tracking twice
+ */
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [pathname])
+
+ return <>>
+}
diff --git a/src/components/NetworkMaturity.tsx b/src/components/NetworkMaturity.tsx
index db753baf3e8..a0202cfd755 100644
--- a/src/components/NetworkMaturity.tsx
+++ b/src/components/NetworkMaturity.tsx
@@ -30,7 +30,9 @@ const NetworkMaturity = () => {
{" "}
({t("page-layer-2-network-maturity-component-3")}
),{" "}
- {t("page-layer-2-network-maturity-component-4")},{" "}
+
+ {t("page-layer-2-network-maturity-component-4")}
+ ,{" "}
{t("page-layer-2-network-maturity-component-5")}.{" "}
{t("page-layer-2-network-maturity-component-6")}
diff --git a/src/components/PageMetadata.tsx b/src/components/PageMetadata.tsx
deleted file mode 100644
index 603cb892630..00000000000
--- a/src/components/PageMetadata.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import Head from "next/head"
-import { useLocale } from "next-intl"
-
-import { getOgImage } from "@/lib/utils/metadata"
-import { filterRealLocales } from "@/lib/utils/translations"
-import { getFullUrl } from "@/lib/utils/url"
-
-import { DEFAULT_LOCALE, LOCALES_CODES, SITE_URL } from "@/lib/constants"
-
-import { useTranslation } from "@/hooks/useTranslation"
-import { usePathname } from "@/i18n/routing"
-
-type NameMeta = {
- name: string
- content: string
-}
-
-type PropMeta = {
- property: string
- content: string
-}
-
-export type Meta = NameMeta | PropMeta
-
-export type PageMetadataProps = {
- title: string
- description: string
- image?: string
- canonicalUrl?: string
- author?: string
-}
-
-const PageMetadata = ({
- description,
- title,
- image,
- canonicalUrl,
- author,
-}: PageMetadataProps) => {
- const locale = useLocale()
- const pathname = usePathname()
- const { t } = useTranslation()
-
- const locales = filterRealLocales(LOCALES_CODES)
-
- const desc = description || t("site-description")
- const siteTitle = t("site-title")
-
- // Remove any query params (?) or hash links (#)
- const path = pathname.replace(/[?#].*/, "")
- const slug = path.split("/")
-
- // Set canonical URL w/ language path to avoid duplicate content
- const url = getFullUrl(locale, path)
- const canonical = canonicalUrl || url
-
- // Set x-default URL for hreflang
- const xDefault = getFullUrl(DEFAULT_LOCALE, path)
-
- /* Set fallback ogImage based on path */
- const ogImage = image || getOgImage(slug)
-
- const ogImageUrl = new URL(ogImage, SITE_URL).href
- const metadata: Meta[] = [
- { name: `image`, content: ogImageUrl },
- { name: `description`, content: desc },
- { name: `docsearch:description`, content: desc },
- { name: `twitter:card`, content: `summary_large_image` },
- { name: `twitter:creator`, content: author || siteTitle },
- { name: `twitter:site`, content: author || siteTitle },
- { name: `twitter:title`, content: title },
- { name: `twitter:description`, content: desc },
- { name: `twitter:image`, content: ogImageUrl },
- { property: `og:title`, content: title },
- { property: `og:locale`, content: locale! },
- { property: `og:description`, content: desc },
- { property: `og:type`, content: `website` },
- { property: `og:url`, content: url },
- { property: `og:image`, content: ogImageUrl },
- { property: `og:site_name`, content: siteTitle },
- ]
-
- return (
-
- {title}
- {metadata.map((data) => (
-
- ))}
-
-
- {locales.map((loc) => (
-
- ))}
-
- )
-}
-
-export default PageMetadata
diff --git a/src/components/ProductTable/index.tsx b/src/components/ProductTable/index.tsx
index d2d2e412e62..00cfff764b7 100644
--- a/src/components/ProductTable/index.tsx
+++ b/src/components/ProductTable/index.tsx
@@ -6,7 +6,7 @@ import {
useMemo,
useState,
} from "react"
-import { useRouter } from "next/router"
+import { useSearchParams } from "next/navigation"
import { ColumnDef } from "@tanstack/react-table"
import type { FilterOption, TPresetFilters } from "@/lib/types"
@@ -47,7 +47,8 @@ const ProductTable = ({
matomoEventCategory,
meta,
}: ProductTableProps) => {
- const router = useRouter()
+ const searchParams = useSearchParams()
+
const [activePresets, setActivePresets] = useState([])
const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false)
@@ -74,18 +75,19 @@ const ProductTable = ({
// Update filters based on router query
useEffect(() => {
- if (Object.keys(router.query).length > 0) {
+ const query = Object.fromEntries(searchParams?.entries() ?? [])
+
+ if (Object.keys(query).length > 0) {
const updatedFilters = filters.map((filter) => ({
...filter,
items: filter.items.map((item) => ({
...item,
inputState:
- parseQueryParams(router.query[item.filterKey]) || item.inputState,
+ parseQueryParams(query[item.filterKey]) || item.inputState,
options: item.options.map((option) => ({
...option,
inputState:
- parseQueryParams(router.query[option.filterKey]) ||
- option.inputState,
+ parseQueryParams(query[option.filterKey]) || option.inputState,
})),
})),
}))
@@ -95,7 +97,7 @@ const ProductTable = ({
// router.replace(pathname, undefined, { shallow: true })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [router.query])
+ }, [searchParams])
// Update or remove preset filters
const handleSelectPreset = (idx: number) => {
diff --git a/src/components/RadialChart/index.tsx b/src/components/RadialChart/index.tsx
index 3732e54f4a7..e0f5207c9b4 100644
--- a/src/components/RadialChart/index.tsx
+++ b/src/components/RadialChart/index.tsx
@@ -1,7 +1,7 @@
"use client"
import { type ReactNode, useEffect, useState } from "react"
-import { useRouter } from "next/router"
+import { useLocale } from "next-intl"
import { MdInfoOutline } from "react-icons/md"
import { PolarAngleAxis, RadialBar, RadialBarChart } from "recharts"
@@ -53,7 +53,7 @@ const RadialChart = ({
displayValue,
}: RadialChartProps) => {
const { t } = useTranslation("common")
- const { locale } = useRouter()
+ const locale = useLocale()
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx
index a2ed295d903..3eb2e791d07 100644
--- a/src/components/Resources/useResources.tsx
+++ b/src/components/Resources/useResources.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"
-import { useRouter } from "next/router"
+import { useLocale } from "next-intl"
import { Lang } from "@/lib/types"
@@ -60,7 +60,7 @@ const formatSmallUSD = (value: number, locale: string): string =>
export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => {
const { t } = useTranslation("page-resources")
- const { locale } = useRouter()
+ const locale = useLocale()
const localeForNumberFormat = getLocaleForNumberFormat(locale! as Lang)
const medianTxCost =
diff --git a/src/components/StablecoinAccordion/useStablecoinAccordion.ts b/src/components/StablecoinAccordion/useStablecoinAccordion.ts
index 292f39d9c14..aa26a8c472c 100644
--- a/src/components/StablecoinAccordion/useStablecoinAccordion.ts
+++ b/src/components/StablecoinAccordion/useStablecoinAccordion.ts
@@ -12,6 +12,7 @@ import summerfiImg from "@/public/images/dapps/summerfi.png"
// Static assets
// -- dapps
import uniImg from "@/public/images/dapps/uni.png"
+import ethImg from "@/public/images/eth-org-logo.png"
import oneInchImg from "@/public/images/exchanges/1inch.png"
import binanceImg from "@/public/images/exchanges/binance.png"
// -- exchanges
@@ -19,7 +20,6 @@ import coinbaseImg from "@/public/images/exchanges/coinbase.png"
import coinmamaImg from "@/public/images/exchanges/coinmama.png"
import geminiImg from "@/public/images/exchanges/gemini.png"
import krakenImg from "@/public/images/exchanges/kraken.png"
-import ethImg from "@/public/images/favicon.png"
export const useStablecoinAccordion = () => {
const { t } = useTranslation("page-stablecoins")
diff --git a/src/components/TutorialMetadata.tsx b/src/components/TutorialMetadata.tsx
index 7bf25d9e69b..2fdade68c35 100644
--- a/src/components/TutorialMetadata.tsx
+++ b/src/components/TutorialMetadata.tsx
@@ -2,7 +2,7 @@
import { useLocale } from "next-intl"
-import type { Lang, TranslationKey } from "@/lib/types"
+import { Lang, Skill, TranslationKey } from "@/lib/types"
import { TutorialFrontmatter } from "@/lib/interfaces"
import CopyToClipboard from "@/components/CopyToClipboard"
@@ -23,12 +23,6 @@ export type TutorialMetadataProps = {
timeToRead: number
}
-export enum Skill {
- BEGINNER = "beginner",
- INTERMEDIATE = "intermediate",
- ADVANCED = "advanced",
-}
-
export const getSkillTranslationId = (skill: Skill): TranslationKey =>
`page-developers-tutorials:page-tutorial-${
Skill[skill.toUpperCase() as keyof typeof Skill]
diff --git a/src/i18n/loadNamespaces.ts b/src/i18n/loadNamespaces.ts
deleted file mode 100644
index c5940b2ebd1..00000000000
--- a/src/i18n/loadNamespaces.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { DEFAULT_LOCALE } from "@/lib/constants"
-
-export default async function loadNamespaces(
- locale: string,
- namespaces: string[]
-) {
- const byNamespace = await Promise.all(
- namespaces.map(async (namespace) => {
- try {
- const defaultNamespace = (
- await import(`../intl/${DEFAULT_LOCALE}/${namespace}.json`)
- ).default
- const localeNamespace = (
- await import(`../intl/${locale}/${namespace}.json`)
- ).default
-
- // Merge the namespaces to have default translations for keys that are not present in the locale
- return { ...defaultNamespace, ...localeNamespace }
- } catch (error) {
- // If the namespace is not found, return the default namespace
- return (await import(`../intl/${DEFAULT_LOCALE}/${namespace}.json`))
- .default
- }
- })
- )
-
- return byNamespace.reduce((acc, namespace, index) => {
- acc[namespaces[index]] = namespace
- return acc
- }, {})
-}
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 37191cf8a48..bcc4550cc36 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -980,3 +980,36 @@ export type MaturityLevel =
| "maturing"
| "developing"
| "emerging"
+
+// Tutorials
+export enum Skill {
+ BEGINNER = "beginner",
+ INTERMEDIATE = "intermediate",
+ ADVANCED = "advanced",
+}
+
+export interface IExternalTutorial {
+ url: string
+ title: string
+ description: string
+ author: string
+ authorGithub: string
+ tags: Array
+ skillLevel: string
+ timeToRead?: string
+ lang: string
+ publishDate: string
+}
+
+export interface ITutorial {
+ href: string
+ title: string
+ description: string
+ author: string
+ tags?: Array
+ skill?: Skill
+ timeToRead?: number | null
+ published?: string | null
+ lang: string
+ isExternal: boolean
+}
diff --git a/src/lib/utils/md.ts b/src/lib/utils/md.ts
index 0847cf2f00b..24c9414c540 100644
--- a/src/lib/utils/md.ts
+++ b/src/lib/utils/md.ts
@@ -5,9 +5,7 @@ import { extname, join } from "path"
import matter from "gray-matter"
import readingTime from "reading-time"
-import type { Frontmatter } from "@/lib/types"
-
-import { Skill } from "@/components/TutorialMetadata"
+import type { Frontmatter, ITutorial, Skill } from "@/lib/types"
import { dateToString } from "@/lib/utils/date"
@@ -15,8 +13,6 @@ import { CONTENT_DIR } from "@/lib/constants"
import { toPosixPath } from "./relativePath"
-import { ITutorial } from "@/pages/[locale]/developers/tutorials"
-
function getContentRoot() {
return join(process.cwd(), CONTENT_DIR)
}
diff --git a/src/lib/utils/tutorial.ts b/src/lib/utils/tutorial.ts
index c649e879561..16c4b5bd0b2 100644
--- a/src/lib/utils/tutorial.ts
+++ b/src/lib/utils/tutorial.ts
@@ -1,11 +1,4 @@
-import { Lang } from "@/lib/types"
-
-import { Skill } from "@/components/TutorialMetadata"
-
-import {
- IExternalTutorial,
- ITutorial,
-} from "@/pages/[locale]/developers/tutorials"
+import { IExternalTutorial, ITutorial, Lang, Skill } from "@/lib/types"
// Take all tutorials, and return a list of tutorials for a specific locale
export const filterTutorialsByLang = (
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
deleted file mode 100644
index 8a90b43cf29..00000000000
--- a/src/pages/404.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import type { GetStaticProps } from "next"
-
-import { BasePageProps, Lang } from "@/lib/types"
-
-import MainArticle from "@/components/MainArticle"
-import Translation from "@/components/Translation"
-import InlineLink from "@/components/ui/Link"
-
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-
-import { DEFAULT_LOCALE } from "@/lib/constants"
-
-import loadNamespaces from "@/i18n/loadNamespaces"
-
-export const getStaticProps = (async () => {
- // TODO: generate 404 pages for each locale when we finish the app router migration
- const locale = DEFAULT_LOCALE
-
- const requiredNamespaces = getRequiredNamespacesForPage("/")
-
- // Want to check common namespace, so looking at requiredNamespaces[0]
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[0])
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const messages = await loadNamespaces(locale!, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- },
- }
-}) satisfies GetStaticProps
-
-const NotFoundPage = () => (
-
-
-
-
-
-
- {" "}
-
-
-
- .
-
-
-
-)
-
-export default NotFoundPage
diff --git a/src/pages/[locale]/layer-2/networks.tsx b/src/pages/[locale]/layer-2/networks.tsx
deleted file mode 100644
index f395b10c998..00000000000
--- a/src/pages/[locale]/layer-2/networks.tsx
+++ /dev/null
@@ -1,245 +0,0 @@
-import type { GetStaticProps } from "next/types"
-
-import type { BasePageProps, Lang, Params } from "@/lib/types"
-
-import Callout from "@/components/Callout"
-import { ContentHero, ContentHeroProps } from "@/components/Hero"
-import Layer2NetworksTable from "@/components/Layer2NetworksTable"
-import MainArticle from "@/components/MainArticle"
-import NetworkMaturity from "@/components/NetworkMaturity"
-import PageMetadata from "@/components/PageMetadata"
-import { ButtonLink } from "@/components/ui/buttons/Button"
-
-import { dataLoader } from "@/lib/utils/data/dataLoader"
-import { existsNamespace } from "@/lib/utils/existsNamespace"
-import { getLastDeployDate } from "@/lib/utils/getLastDeployDate"
-import { networkMaturity } from "@/lib/utils/networkMaturity"
-import { getLocaleTimestamp } from "@/lib/utils/time"
-import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
-
-import { ethereumNetworkData, layer2Data } from "@/data/networks/networks"
-import { walletsData } from "@/data/wallets/wallet-data"
-
-import { BASE_TIME_UNIT, DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants"
-
-import useTranslation from "@/hooks/useTranslation"
-import loadNamespaces from "@/i18n/loadNamespaces"
-import { usePathname } from "@/i18n/routing"
-import { fetchEthereumMarketcap } from "@/lib/api/fetchEthereumMarketcap"
-import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie"
-import { fetchGrowThePieBlockspace } from "@/lib/api/fetchGrowThePieBlockspace"
-import { fetchGrowThePieMaster } from "@/lib/api/fetchGrowThePieMaster"
-import { fetchL2beat } from "@/lib/api/fetchL2beat"
-import Callout2Image from "@/public/images/layer-2/layer-2-walking.png"
-import Callout1Image from "@/public/images/man-and-dog-playing.png"
-
-// In seconds
-const REVALIDATE_TIME = BASE_TIME_UNIT * 1
-
-const loadData = dataLoader(
- [
- ["ethereumMarketcapData", fetchEthereumMarketcap],
- ["growThePieData", fetchGrowThePie],
- ["growThePieBlockspaceData", fetchGrowThePieBlockspace],
- ["growThePieMasterData", fetchGrowThePieMaster],
- ["l2beatData", fetchL2beat],
- ],
- REVALIDATE_TIME * 1000
-)
-
-export async function getStaticPaths() {
- return {
- paths: LOCALES_CODES.map((locale) => ({ params: { locale } })),
- fallback: false,
- }
-}
-
-export const getStaticProps = (async ({ params }) => {
- const { locale = DEFAULT_LOCALE } = params || {}
-
- const [
- ethereumMarketcapData,
- growThePieData,
- growThePieBlockspaceData,
- growThePieMasterData,
- l2beatData,
- ] = await loadData()
-
- const lastDeployDate = getLastDeployDate()
- const lastDeployLocaleTimestamp = getLocaleTimestamp(
- locale as Lang,
- lastDeployDate
- )
-
- const requiredNamespaces = getRequiredNamespacesForPage("/layer-2/networks")
-
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
-
- const layer2DataCompiled = layer2Data
- .map((network) => {
- return {
- ...network,
- txCosts: growThePieData.dailyTxCosts[network.growthepieID],
- tvl: l2beatData.data.projects[network.l2beatID].tvs.breakdown.total,
- networkMaturity: networkMaturity(
- l2beatData.data.projects[network.l2beatID]
- ),
- activeAddresses: growThePieData.activeAddresses[network.growthepieID],
- blockspaceData:
- (growThePieBlockspaceData || {})[network.growthepieID] || null,
- launchDate:
- (growThePieMasterData?.launchDates || {})[
- network.growthepieID.replace(/_/g, "-")
- ] || null,
- walletsSupported: walletsData
- .filter((wallet) =>
- wallet.supported_chains.includes(network.chainName)
- )
- .map((wallet) => wallet.name),
- walletsSupportedCount: `${
- walletsData.filter((wallet) =>
- wallet.supported_chains.includes(network.chainName)
- ).length
- }/${walletsData.length}`,
- }
- })
- .sort((a, b) => {
- const maturityOrder = {
- robust: 4,
- maturing: 3,
- developing: 2,
- emerging: 1,
- }
-
- const maturityDiff =
- maturityOrder[b.networkMaturity] - maturityOrder[a.networkMaturity]
-
- if (maturityDiff === 0) {
- return (b.tvl || 0) - (a.tvl || 0)
- }
-
- return maturityDiff
- })
-
- const messages = await loadNamespaces(locale, requiredNamespaces)
-
- return {
- props: {
- messages,
- contentNotTranslated,
- lastDeployLocaleTimestamp,
- locale,
- layer2Data: layer2DataCompiled,
- mainnetData: {
- ...ethereumNetworkData,
- txCosts: growThePieData.dailyTxCosts.ethereum,
- tvl: "value" in ethereumMarketcapData ? ethereumMarketcapData.value : 0,
- walletsSupported: walletsData
- .filter((wallet) =>
- wallet.supported_chains.includes("Ethereum Mainnet")
- )
- .map((wallet) => wallet.name),
- },
- },
- }
-}) satisfies GetStaticProps
-
-const Layer2Networks = ({ layer2Data, locale, mainnetData }) => {
- const pathname = usePathname()
- const { t } = useTranslation(["page-layer-2-networks", "common"])
-
- const heroProps: ContentHeroProps = {
- breadcrumbs: { slug: pathname, startDepth: 1 },
- heroImg: "/images/layer-2/learn-hero.png",
- blurDataURL: "/images/layer-2/learn-hero.png",
- title: t("common:nav-networks-explore-networks-label"),
- description: t("page-layer-2-networks-hero-description"),
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
{t("page-layer-2-networks-more-advanced-title")}
-
-
- {t("page-layer-2-networks-more-advanced-descripton-1")}{" "}
-
- {t("page-layer-2-networks-more-advanced-descripton-2")}
-
-
-
{t("page-layer-2-networks-more-advanced-descripton-3")}
-
-
-
- {t("page-layer-2-networks-more-advanced-link-1")}
-
-
- {t("page-layer-2-networks-more-advanced-link-2")}
-
-
-
-
-
-
-
-
-
-
-
- {t("common:learn-more")}
-
-
-
-
-
-
- {t("common:learn-more")}
-
-
-
-
-
- )
-}
-
-export default Layer2Networks
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
deleted file mode 100644
index 0136454beef..00000000000
--- a/src/pages/_app.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useEffect } from "react"
-import { useRouter } from "next/router"
-import { NextIntlClientProvider } from "next-intl"
-import { TooltipProvider } from "@radix-ui/react-tooltip"
-import { init } from "@socialgouv/matomo-next"
-
-import { AppPropsWithLayout } from "@/lib/types"
-
-import ThemeProvider from "@/components/ThemeProvider"
-
-import { DEFAULT_LOCALE } from "@/lib/constants"
-
-import "@/styles/global.css"
-
-import { FeedbackWidgetProvider } from "@/contexts/FeedbackWidgetContext"
-import { BaseLayout } from "@/layouts/BaseLayout"
-
-const App = ({ Component, pageProps }: AppPropsWithLayout) => {
- const router = useRouter()
-
- useEffect(() => {
- if (!process.env.IS_PREVIEW_DEPLOY) {
- init({
- url: process.env.NEXT_PUBLIC_MATOMO_URL!,
- siteId: process.env.NEXT_PUBLIC_MATOMO_SITE_ID!,
- })
- }
- }, [])
-
- // Per-Page Layouts: https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts#with-typescript
- // Use the layout defined at the page level, if available
- const getLayout = Component.getLayout ?? ((page) => page)
-
- return (
- {
- // Suppress errors by default, enable if needed to debug
- // console.error(error)
- }}
- getMessageFallback={({ key }) => {
- const keyOnly = key.split(".").pop()
- return keyOnly || key
- }}
- >
-
-
-
-
- {getLayout()}
-
-
-
-
-
- )
-}
-
-export default App
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx
deleted file mode 100644
index b9cb351f845..00000000000
--- a/src/pages/_document.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import NextDocument, {
- DocumentContext,
- Head,
- Html,
- Main,
- NextScript,
-} from "next/document"
-
-import { Lang } from "@/lib/types"
-
-import { isLangRightToLeft } from "@/lib/utils/translations"
-
-class Document extends NextDocument {
- static async getInitialProps(ctx: DocumentContext) {
- const initialProps = await NextDocument.getInitialProps(ctx)
- // TODO: Fix this! Hacky way to get locale to fix search
- // Get locale from query
- const locale = ctx.query?.locale || "en"
- return { ...initialProps, locale }
- }
-
- render() {
- const locale = this.props.locale || "en"
- const dir = isLangRightToLeft(locale as Lang) ? "rtl" : "ltr"
-
- return (
-
-
- {/* favicon */}
-
- {/* manifest */}
-
- {/* preload inter static web fonts */}
-
-
-
-
-
-
-
- )
- }
-}
-
-export default Document
diff --git a/src/styles/fonts.css b/src/styles/fonts.css
deleted file mode 100644
index 213b5fbc7fd..00000000000
--- a/src/styles/fonts.css
+++ /dev/null
@@ -1,256 +0,0 @@
-/* css imported from https://fonts.googleapis.com/css2?family=Inter:wght@400;700;900 */
-
-/* cyrillic-ext */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url(/fonts/inter/cyrillic-ext.woff2) format("woff2");
- unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
- U+FE2E-FE2F;
-}
-/* cyrillic */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url(/fonts/inter/cyrillic.woff2) format("woff2");
- unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
-}
-/* greek-ext */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url(/fonts/inter/greek-ext.woff2) format("woff2");
- unicode-range: U+1F00-1FFF;
-}
-/* greek */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url(/fonts/inter/greek.woff2) format("woff2");
- unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1,
- U+03A3-03FF;
-}
-/* vietnamese */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url(/fonts/inter/vietnamese.woff2) format("woff2");
- unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
- U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
- U+1EA0-1EF9, U+20AB;
-}
-/* latin-ext */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url(/fonts/inter/latin-ext.woff2) format("woff2");
- unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
- U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
-}
-/* latin */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url(/fonts/inter/latin.woff2) format("woff2");
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
- U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
- U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-
-/* cyrillic-ext */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url(/fonts/inter/cyrillic-ext.woff2) format("woff2");
- unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
- U+FE2E-FE2F;
-}
-/* cyrillic */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url(/fonts/inter/cyrillic.woff2) format("woff2");
- unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
-}
-/* greek-ext */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url(/fonts/inter/greek-ext.woff2) format("woff2");
- unicode-range: U+1F00-1FFF;
-}
-/* greek */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url(/fonts/inter/greek.woff2) format("woff2");
- unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1,
- U+03A3-03FF;
-}
-/* vietnamese */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url(/fonts/inter/vietnamese.woff2) format("woff2");
- unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
- U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
- U+1EA0-1EF9, U+20AB;
-}
-/* latin-ext */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url(/fonts/inter/latin-ext.woff2) format("woff2");
- unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
- U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
-}
-/* latin */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url(/fonts/inter/latin.woff2) format("woff2");
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
- U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
- U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-
-/* cyrillic-ext */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 900;
- src: url(/fonts/inter/cyrillic-ext.woff2) format("woff2");
- unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
- U+FE2E-FE2F;
-}
-/* cyrillic */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 900;
- src: url(/fonts/inter/cyrillic.woff2) format("woff2");
- unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
-}
-/* greek-ext */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 900;
- src: url(/fonts/inter/greek-ext.woff2) format("woff2");
- unicode-range: U+1F00-1FFF;
-}
-/* greek */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 900;
- src: url(/fonts/inter/greek.woff2) format("woff2");
- unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1,
- U+03A3-03FF;
-}
-/* vietnamese */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 900;
- src: url(/fonts/inter/vietnamese.woff2) format("woff2");
- unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
- U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
- U+1EA0-1EF9, U+20AB;
-}
-/* latin-ext */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 900;
- src: url(/fonts/inter/latin-ext.woff2) format("woff2");
- unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
- U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
-}
-/* latin */
-@font-face {
- font-family: "Inter";
- font-style: normal;
- font-weight: 900;
- src: url(/fonts/inter/latin.woff2) format("woff2");
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
- U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
- U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-
-/* css imported from https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400 */
-
-/* cyrillic-ext */
-@font-face {
- font-family: "IBM Plex Mono";
- font-style: normal;
- font-weight: 400;
- src: url(/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf) format("truetype");
- unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
- U+FE2E-FE2F;
-}
-/* cyrillic */
-@font-face {
- font-family: "IBM Plex Mono";
- font-style: normal;
- font-weight: 400;
- src: url(/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf) format("truetype");
- unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
-}
-/* vietnamese */
-@font-face {
- font-family: "IBM Plex Mono";
- font-style: normal;
- font-weight: 400;
- src: url(/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf) format("truetype");
- unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
- U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
- U+1EA0-1EF9, U+20AB;
-}
-/* latin-ext */
-@font-face {
- font-family: "IBM Plex Mono";
- font-style: normal;
- font-weight: 400;
- src: url(/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf) format("truetype");
- unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
- U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
-}
-/* latin */
-@font-face {
- font-family: "IBM Plex Mono";
- font-style: normal;
- font-weight: 400;
- src: url(/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf) format("truetype");
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
- U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
- U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
diff --git a/src/styles/global.css b/src/styles/global.css
index 8ddb8b9ebdb..8054a6d0371 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -5,7 +5,6 @@
@import "@docsearch/css";
@import "@/styles/colors.css";
@import "@/styles/semantic-tokens.css";
-@import "@/styles/fonts.css";
@import "@/styles/docsearch.css";
@import "@rainbow-me/rainbowkit/styles.css";
@@ -82,7 +81,7 @@
@layer base {
* {
- @apply border-border scroll-smooth;
+ @apply scroll-smooth border-border;
}
body {
diff --git a/tailwind.config.ts b/tailwind.config.ts
index c152326dfa9..e555b78700a 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -5,7 +5,7 @@ import { screens } from "./src/lib/utils/screen"
const config = {
darkMode: ["selector"],
- content: ["./src/**/*.{ts,tsx}"],
+ content: ["./src/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}"],
prefix: "",
theme: {
extend: {
@@ -15,6 +15,7 @@ const config = {
body: "var(--font-inter)",
monospace: "var(--font-mono)",
mono: "var(--font-mono)",
+ sans: "var(--font-inter)",
},
fontSize: {
"7xl": ["4rem", "1.1"], // [7xl, 6xs]