diff --git a/.storybook/manager.ts b/.storybook/manager.ts index c7a18a5cc98..db394db3384 100644 --- a/.storybook/manager.ts +++ b/.storybook/manager.ts @@ -1,6 +1,6 @@ import { addons } from "@storybook/manager-api" -import favicon from "../public/images/favicon.png" +import favicon from "../public/images/eth-home-icon.png" import theme from "./theme" diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html deleted file mode 100644 index 8a8edd9bb12..00000000000 --- a/.storybook/preview-head.html +++ /dev/null @@ -1,3 +0,0 @@ - -<link rel="preload" href="/fonts/inter/latin.woff2" as="font" type="font/woff2" crossorigin="anonymous" /> -<link rel="preload" href="/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous" /> diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 1095a3011c2..9fe6f14429d 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,5 +1,6 @@ import isChromatic from "chromatic/isChromatic" import { MotionGlobalConfig } from "framer-motion" +import { IBM_Plex_Mono, Inter } from "next/font/google" import type { Preview } from "@storybook/react" import ThemeProvider from "@/components/ThemeProvider" @@ -9,11 +10,24 @@ import nextIntl, { baseLocales } from "./next-intl" import { withNextThemes } from "./withNextThemes" import "../src/styles/global.css" -import "../src/styles/fonts.css" import "../src/styles/docsearch.css" import "@docsearch/css" +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", +}) + MotionGlobalConfig.skipAnimations = isChromatic() export const breakpointSet: [token: string, value: string][] = [ @@ -39,11 +53,13 @@ const preview: Preview = { defaultTheme: "light", }), (Story) => ( - <ThemeProvider> - <TooltipProvider> - <Story /> - </TooltipProvider> - </ThemeProvider> + <div className={`${inter.variable} ${ibmPlexMono.variable}`}> + <ThemeProvider> + <TooltipProvider> + <Story /> + </TooltipProvider> + </ThemeProvider> + </div> ), ], parameters: { diff --git a/src/pages/[locale]/index.tsx b/app/[locale]/_components/home.tsx similarity index 88% rename from src/pages/[locale]/index.tsx rename to app/[locale]/_components/home.tsx index fa7033af847..6806354f2a5 100644 --- a/src/pages/[locale]/index.tsx +++ b/app/[locale]/_components/home.tsx @@ -1,5 +1,6 @@ +"use client" + import { Fragment, lazy, Suspense } from "react" -import type { GetStaticProps, InferGetStaticPropsType } from "next" import { FaDiscord, FaGithub } from "react-icons/fa6" import { IoMdCopy } from "react-icons/io" import { MdCheck } from "react-icons/md" @@ -8,10 +9,9 @@ import type { AllMetricData, BasePageProps, CommunityBlog, - Lang, - Params, RSSItem, } from "@/lib/types" +import { CommunityEvent } from "@/lib/interfaces" import { ChevronNext } from "@/components/Chevron" import CodeModal from "@/components/CodeModal" @@ -24,8 +24,13 @@ import Calendar from "@/components/icons/calendar.svg" import CalendarAdd from "@/components/icons/calendar-add.svg" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import { TranslatathonBanner } from "@/components/Translatathon/TranslatathonBanner" +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion" import { Button, ButtonLink } from "@/components/ui/buttons/Button" import SvgButtonLink, { type SvgButtonLinkProps, @@ -56,43 +61,13 @@ import { import WindowBox from "@/components/WindowBox" import { cn } from "@/lib/utils/cn" -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 { trackCustomEvent } from "@/lib/utils/matomo" -import { polishRSSList } from "@/lib/utils/rss" import { breakpointAsNumber } from "@/lib/utils/screen" -import { getLocaleTimestamp } from "@/lib/utils/time" -import { getRequiredNamespacesForPage } from "@/lib/utils/translations" - -import { - BASE_TIME_UNIT, - BLOG_FEEDS, - BLOGS_WITHOUT_FEED, - CALENDAR_DISPLAY_COUNT, - DEFAULT_LOCALE, - GITHUB_REPO_URL, - LOCALES_CODES, - RSS_DISPLAY_COUNT, -} from "@/lib/constants" -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "../../components/ui/accordion" +import { GITHUB_REPO_URL } from "@/lib/constants" import { useClipboard } from "@/hooks/useClipboard" -import loadNamespaces from "@/i18n/loadNamespaces" -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" import EventFallback from "@/public/images/events/event-placeholder.png" import BuildersImage from "@/public/images/heroes/developers-hub-hero.jpg" import ActivityImage from "@/public/images/heroes/layer-2-hub-hero.jpg" @@ -111,110 +86,17 @@ const Codeblock = lazy(() => const StatsBoxGrid = lazy(() => import("@/components/StatsBoxGrid")) -// API calls -const fetchXmlBlogFeeds = async () => { - return await fetchRSS(BLOG_FEEDS) -} - type Props = BasePageProps & { + calendar: CommunityEvent[] metricResults: AllMetricData rssData: { rssItems: RSSItem[]; blogLinks: CommunityBlog[] } } -// 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 -) - -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 [ - 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) - - // load i18n required namespaces for the given page - const requiredNamespaces = getRequiredNamespacesForPage("/") - - // 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 messages = await loadNamespaces(locale, requiredNamespaces) - - return { - props: { - messages, - calendar, - contentNotTranslated, - lastDeployLocaleTimestamp, - metricResults, - rssData: { rssItems, blogLinks }, - }, - } -}) satisfies GetStaticProps<Props, Params> - const HomePage = ({ calendar, metricResults, rssData: { rssItems, blogLinks }, -}: InferGetStaticPropsType<typeof getStaticProps>) => { +}: Props) => { const { t, locale, @@ -236,10 +118,6 @@ const HomePage = ({ return ( <MainArticle className="flex w-full flex-col items-center" dir={dir}> - <PageMetadata - title={t("page-index:page-index-meta-title")} - description={t("page-index:page-index-meta-description")} - /> <TranslatathonBanner /> <HomeHero heroImg={Hero} className="w-full" /> <div className="w-full space-y-32 px-4 md:mx-6 lg:space-y-48"> @@ -504,7 +382,7 @@ const HomePage = ({ <button key={title} className={cn( - "flex flex-col gap-y-0.5 border-t px-6 py-4 text-start hover:bg-background-highlight max-md:hidden", + "flex flex-col gap-y-0.5 border-t px-6 py-4 hover:bg-background-highlight max-md:hidden", isModalOpen && idx === activeCode && "bg-background-highlight" diff --git a/src/pages/[locale]/assets.tsx b/app/[locale]/assets/_components/assets.tsx similarity index 93% rename from src/pages/[locale]/assets.tsx rename to app/[locale]/assets/_components/assets.tsx index 77c87985265..7b4fd39e058 100644 --- a/src/pages/[locale]/assets.tsx +++ b/app/[locale]/assets/_components/assets.tsx @@ -1,19 +1,16 @@ +"use client" + import { HTMLAttributes } from "react" -import type { GetStaticProps } from "next/types" -import type { BasePageProps, ChildOnlyProp, Lang, Params } from "@/lib/types" +import type { ChildOnlyProp } from "@/lib/types" import AssetDownload from "@/components/AssetDownload" import FeedbackCard from "@/components/FeedbackCard" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import { Center, 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 efLogo from "@/public/images/ef-logo.png" // import efLogoWhite from "@/public/images/ef-logo-white.png" // import ethDiamondBlackHero from "@/public/images/assets/eth-diamond-black.png" @@ -24,13 +21,8 @@ import { getLocaleTimestamp } from "@/lib/utils/time" // import ethGifWaves from "@/public/images/eth-gif-waves.png" // import ethPortraitPurpleWhite from "@/public/images/assets/ethereum-logo-portrait-purple-white.png" // import leslieTheRhino from "@/public/images/upgrades/upgrade_rhino.png" -import { getRequiredNamespacesForPage } from "@/lib/utils/translations" - -import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants" - import useColorModeValue from "@/hooks/useColorModeValue" import { useTranslation } from "@/hooks/useTranslation" -import loadNamespaces from "@/i18n/loadNamespaces" import ethDiamondBlack from "@/public/images/assets/eth-diamond-black.png" import ethDiamondBlackGray from "@/public/images/assets/eth-diamond-black-gray.png" import ethDiamondBlackWhite from "@/public/images/assets/eth-diamond-black-white.jpg" @@ -98,37 +90,6 @@ const H3 = (props: ChildOnlyProp) => ( <h3 className="mb-0 mt-10 leading-xs" {...props} /> ) -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("assets") - - 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<BasePageProps, Params> - const AssetsPage = () => { // Ignore locale in the URL for SVG path in public directory to fix broken link // SVG path changes from /en/images => /images @@ -141,10 +102,6 @@ const AssetsPage = () => { ) return ( <Flex className="w-full flex-col"> - <PageMetadata - title={t("page-assets-meta-title")} - description={t("page-assets-meta-desc")} - /> <MainArticle className="px-8 py-4"> <Flex className="flex-col px-8 py-4"> <Center> diff --git a/app/[locale]/assets/page.tsx b/app/[locale]/assets/page.tsx new file mode 100644 index 00000000000..d6ba7d57ff9 --- /dev/null +++ b/app/[locale]/assets/page.tsx @@ -0,0 +1,49 @@ +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 AssetsPage from "./_components/assets" + +import { loadMessages } from "@/i18n/loadMessages" + +export default async function Page({ + params, +}: { + params: Promise<{ locale: Lang }> +}) { + const { locale } = await params + + // Get i18n messages + const allMessages = await loadMessages(locale) + const requiredNamespaces = getRequiredNamespacesForPage("/assets") + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <AssetsPage /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ locale, namespace: "page-assets" }) + + return await getMetadata({ + locale, + slug: ["assets"], + title: t("page-assets-meta-title"), + description: t("page-assets-meta-description"), + }) +} diff --git a/src/pages/[locale]/bug-bounty.tsx b/app/[locale]/bug-bounty/_components/bug-bounty.tsx similarity index 94% rename from src/pages/[locale]/bug-bounty.tsx rename to app/[locale]/bug-bounty/_components/bug-bounty.tsx index d8d3d42fb42..6a6813a92b5 100644 --- a/src/pages/[locale]/bug-bounty.tsx +++ b/app/[locale]/bug-bounty/_components/bug-bounty.tsx @@ -1,7 +1,8 @@ +"use client" + import { HTMLAttributes } from "react" -import type { GetStaticProps } from "next/types" -import type { BasePageProps, ChildOnlyProp, Lang, Params } from "@/lib/types" +import type { ChildOnlyProp } from "@/lib/types" /* Uncomment for Bug Bounty Banner: */ import BugBountyBanner from "@/components/Banners/BugBountyBanner" @@ -15,7 +16,6 @@ import FeedbackCard from "@/components/FeedbackCard" import { Image, type ImageProps } from "@/components/Image" import Leaderboard from "@/components/Leaderboard" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import Translation from "@/components/Translation" import { ButtonLink } from "@/components/ui/buttons/Button" import { Divider } from "@/components/ui/divider" @@ -24,19 +24,12 @@ import InlineLink from "@/components/ui/Link" import { ListItem, UnorderedList } 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 consensusData from "@/data/consensus-bounty-hunters.json" import executionData from "@/data/execution-bounty-hunters.json" -import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants" - import useColorModeValue from "@/hooks/useColorModeValue" import { useTranslation } from "@/hooks/useTranslation" -import loadNamespaces from "@/i18n/loadNamespaces" import { usePathname } from "@/i18n/routing" import besu from "@/public/images/upgrades/besu.png" import erigon from "@/public/images/upgrades/erigon.png" @@ -231,37 +224,6 @@ const sortBountyHuntersFn = (a: BountyHuntersArg, b: BountyHuntersArg) => { return b.score - a.score } -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("bug-bounty") - - 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<BasePageProps, Params> - const BugBountiesPage = () => { const pathname = usePathname() const { t } = useTranslation("page-bug-bounty") @@ -392,10 +354,6 @@ const BugBountiesPage = () => { } return ( <Page> - <PageMetadata - title={t("page-upgrades-bug-bounty-meta-title")} - description={t("page-upgrades-bug-bounty-meta-description")} - /> {/* Uncomment for Bug Bounty Banner: */} <BugBountyBanner /> <Content> diff --git a/app/[locale]/bug-bounty/page.tsx b/app/[locale]/bug-bounty/page.tsx new file mode 100644 index 00000000000..8d23795a2e2 --- /dev/null +++ b/app/[locale]/bug-bounty/page.tsx @@ -0,0 +1,45 @@ +import pick from "lodash.pick" +import { getTranslations } from "next-intl/server" + +import { type Params } from "@/lib/types" + +import I18nProvider from "@/components/I18nProvider" + +import { getMetadata } from "@/lib/utils/metadata" +import { getRequiredNamespacesForPage } from "@/lib/utils/translations" + +import BugBountiesPage from "./_components/bug-bounty" + +import { loadMessages } from "@/i18n/loadMessages" + +export default async function Page({ params }: { params: Promise<Params> }) { + const { locale } = await params + + // Get i18n messages + const allMessages = await loadMessages(locale) + const requiredNamespaces = getRequiredNamespacesForPage("/bug-bounty") + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <BugBountiesPage /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ locale, namespace: "page-bug-bounty" }) + + return await getMetadata({ + locale, + slug: ["bug-bounty"], + title: t("page-upgrades-bug-bounty-meta-title"), + description: t("page-upgrades-bug-bounty-meta-description"), + }) +} diff --git a/src/pages/[locale]/community.tsx b/app/[locale]/community/_components/community.tsx similarity index 88% rename from src/pages/[locale]/community.tsx rename to app/[locale]/community/_components/community.tsx index cb40ef2a0e8..3643d218505 100644 --- a/src/pages/[locale]/community.tsx +++ b/app/[locale]/community/_components/community.tsx @@ -1,7 +1,8 @@ +"use client" + import { BaseHTMLAttributes } from "react" -import { GetStaticProps } from "next" -import { BasePageProps, ChildOnlyProp, Lang, Params } from "@/lib/types" +import { ChildOnlyProp } from "@/lib/types" import { ICard, IGetInvolvedCard } from "@/lib/interfaces" import ActionCard from "@/components/ActionCard" @@ -12,65 +13,23 @@ import { HubHero } from "@/components/Hero" import type { HubHeroProps } from "@/components/Hero/HubHero" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import { ButtonLink, ButtonLinkProps } from "@/components/ui/buttons/Button" import { Divider } from "@/components/ui/divider" import { Flex } from "@/components/ui/flex" 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" -// Static assets import developersEthBlockImg from "@/public/images/developers-eth-blocks.png" import dogeComputerImg from "@/public/images/doge-computer.png" import ethImg from "@/public/images/eth.png" import financeTransparentImg from "@/public/images/finance_transparent.png" import futureTransparentImg from "@/public/images/future_transparent.png" import hackathonTransparentImg from "@/public/images/hackathon_transparent.png" -// -- Hero import communityHeroImg from "@/public/images/heroes/community-hero.png" -// -- Cards import upgradesCoreImg from "@/public/images/upgrades/core.png" import whatIsEthereumImg from "@/public/images/what-is-ethereum.png" -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("/community") - - 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<BasePageProps, Params> - const CardContainer = ({ children }: ChildOnlyProp) => { return <Flex className="-mx-4 flex-wrap">{children}</Flex> } @@ -199,10 +158,6 @@ const CommunityPage = () => { return ( <Page> - <PageMetadata - title={t("page-community-meta-title")} - description={t("page-community-meta-description")} - /> <HubHero {...heroContent} /> <Divider /> <Flex className="-mt-px h-full w-full flex-row-reverse items-center border-b border-b-border-high-contrast bg-[#ccfcff] py-8 ps-0 lg:h-[720px] lg:py-0 lg:ps-8 dark:bg-[#293233]"> diff --git a/app/[locale]/community/page.tsx b/app/[locale]/community/page.tsx new file mode 100644 index 00000000000..bcc1dc76f1e --- /dev/null +++ b/app/[locale]/community/page.tsx @@ -0,0 +1,49 @@ +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 CommunityPage from "./_components/community" + +import { loadMessages } from "@/i18n/loadMessages" + +export default async function Page({ + params, +}: { + params: Promise<{ locale: Lang }> +}) { + const { locale } = await params + + // Get i18n messages + const allMessages = await loadMessages(locale) + const requiredNamespaces = getRequiredNamespacesForPage("/community") + const pickedMessages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={pickedMessages}> + <CommunityPage /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ locale, namespace: "page-community" }) + + return await getMetadata({ + locale, + slug: ["community"], + title: t("page-community-meta-title"), + description: t("page-community-meta-description"), + }) +} diff --git a/src/pages/[locale]/contributing/translation-program/acknowledgements.tsx b/app/[locale]/contributing/translation-program/acknowledgements/_components/acknowledgements.tsx similarity index 82% rename from src/pages/[locale]/contributing/translation-program/acknowledgements.tsx rename to app/[locale]/contributing/translation-program/acknowledgements/_components/acknowledgements.tsx index 8d983e14aef..25a1e03ebf9 100644 --- a/src/pages/[locale]/contributing/translation-program/acknowledgements.tsx +++ b/app/[locale]/contributing/translation-program/acknowledgements/_components/acknowledgements.tsx @@ -1,34 +1,25 @@ -import { BaseHTMLAttributes } from "react" -import { GetStaticProps } from "next/types" +"use client" -import { BasePageProps, Lang, Params } from "@/lib/types" +import { BaseHTMLAttributes } from "react" import ActionCard from "@/components/ActionCard" import Breadcrumbs from "@/components/Breadcrumbs" import FeedbackCard from "@/components/FeedbackCard" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import TranslationLeaderboard from "@/components/TranslationLeaderboard" import { Flex } from "@/components/ui/flex" import InlineLink from "@/components/ui/Link" import { ListItem, OrderedList } 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 allTimeData from "@/data/translation-reports/alltime/alltime-data.json" import monthData from "@/data/translation-reports/month/month-data.json" import quarterData from "@/data/translation-reports/quarter/quarter-data.json" -import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants" - import useColorModeValue from "@/hooks/useColorModeValue" import { useTranslation } from "@/hooks/useTranslation" -import loadNamespaces from "@/i18n/loadNamespaces" import { usePathname } from "@/i18n/routing" import darkThemeCertificateImg from "@/public/images/certificates/dark-certificate.png" import lightThemeCertificateImg from "@/public/images/certificates/light-certificate.png" @@ -56,39 +47,6 @@ const Text = ({ <p className={cn("mb-6", className)} {...props} /> ) -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( - "/contributing/translation-program/acknowledgements" - ) - - const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2]) - - const messages = await loadNamespaces(locale, requiredNamespaces) - - return { - props: { - messages, - contentNotTranslated, - lastDeployLocaleTimestamp, - }, - } -}) satisfies GetStaticProps<BasePageProps, Params> - const TranslatorAcknowledgements = () => { const pathname = usePathname() const { t } = useTranslation( @@ -102,15 +60,6 @@ const TranslatorAcknowledgements = () => { return ( <Flex className="w-full flex-col items-center"> - <PageMetadata - title={t( - "page-contributing-translation-program-acknowledgements-meta-title" - )} - description={t( - "page-contributing-translation-program-acknowledgements-meta-description" - )} - /> - <Content> <Breadcrumbs slug={pathname} className="mt-12" /> <h1 className="my-8 leading-xs"> diff --git a/app/[locale]/contributing/translation-program/acknowledgements/page.tsx b/app/[locale]/contributing/translation-program/acknowledgements/page.tsx new file mode 100644 index 00000000000..2223f9d9449 --- /dev/null +++ b/app/[locale]/contributing/translation-program/acknowledgements/page.tsx @@ -0,0 +1,56 @@ +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 Acknowledgements from "./_components/acknowledgements" + +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( + "/contributing/translation-program/acknowledgements" + ) + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <Acknowledgements /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ + locale, + namespace: "page-contributing-translation-program-acknowledgements", + }) + + return await getMetadata({ + locale, + slug: ["contributing", "translation-program", "acknowledgements"], + title: t( + "page-contributing-translation-program-acknowledgements-meta-title" + ), + description: t( + "page-contributing-translation-program-acknowledgements-meta-description" + ), + }) +} + +export default Page diff --git a/src/pages/[locale]/contributing/translation-program/contributors.tsx b/app/[locale]/contributing/translation-program/contributors/_components/contributors.tsx similarity index 66% rename from src/pages/[locale]/contributing/translation-program/contributors.tsx rename to app/[locale]/contributing/translation-program/contributors/_components/contributors.tsx index aa33b7483b7..2ef3a9b022e 100644 --- a/src/pages/[locale]/contributing/translation-program/contributors.tsx +++ b/app/[locale]/contributing/translation-program/contributors/_components/contributors.tsx @@ -1,63 +1,23 @@ +"use client" + import { BaseHTMLAttributes } from "react" -import { GetStaticProps } from "next/types" -import { BasePageProps, CostLeaderboardData, Lang, Params } from "@/lib/types" +import { CostLeaderboardData } from "@/lib/types" import Breadcrumbs from "@/components/Breadcrumbs" import FeedbackCard from "@/components/FeedbackCard" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import { Flex } from "@/components/ui/flex" 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 allTimeData from "@/data/translation-reports/alltime/alltime-data.json" -import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants" - import { useTranslation } from "@/hooks/useTranslation" -import loadNamespaces from "@/i18n/loadNamespaces" import { usePathname } from "@/i18n/routing" -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( - "/contributing/translation-program/contributors" - ) - - const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2]) - - const messages = await loadNamespaces(locale, requiredNamespaces) - - return { - props: { - messages, - contentNotTranslated, - lastDeployLocaleTimestamp, - }, - } -}) satisfies GetStaticProps<BasePageProps, Params> - const Content = ({ ...props }: BaseHTMLAttributes<HTMLHeadingElement>) => ( <MainArticle className="w-full px-10 py-4" {...props} /> ) @@ -81,15 +41,6 @@ const Contributors = () => { return ( <Flex className="w-full flex-col items-center"> - <PageMetadata - title={t( - "page-contributing-translation-program-contributors-meta-title" - )} - description={t( - "page-contributing-translation-program-contributors-meta-description" - )} - /> - <Content> <Breadcrumbs slug={pathname} className="mt-12" /> <h1 className="my-8 leading-xs"> diff --git a/app/[locale]/contributing/translation-program/contributors/page.tsx b/app/[locale]/contributing/translation-program/contributors/page.tsx new file mode 100644 index 00000000000..e410d506133 --- /dev/null +++ b/app/[locale]/contributing/translation-program/contributors/page.tsx @@ -0,0 +1,54 @@ +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 Contributors from "./_components/contributors" + +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( + "/contributing/translation-program/contributors" + ) + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <Contributors /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ + locale, + namespace: "page-contributing-translation-program-contributors", + }) + + return await getMetadata({ + locale, + slug: ["contributing", "translation-program", "contributors"], + title: t("page-contributing-translation-program-contributors-meta-title"), + description: t( + "page-contributing-translation-program-contributors-meta-description" + ), + }) +} + +export default Page diff --git a/src/pages/[locale]/dapps.tsx b/app/[locale]/dapps/_components/dapps.tsx similarity index 97% rename from src/pages/[locale]/dapps.tsx rename to app/[locale]/dapps/_components/dapps.tsx index 09a7c143dd2..ae2b6dba55f 100644 --- a/src/pages/[locale]/dapps.tsx +++ b/app/[locale]/dapps/_components/dapps.tsx @@ -1,3 +1,5 @@ +"use client" + import React, { BaseHTMLAttributes, type ComponentPropsWithRef, @@ -6,11 +8,10 @@ import React, { useRef, useState, } from "react" -import { type GetStaticProps } from "next" -import { useRouter } from "next/router" +import { useSearchParams } from "next/navigation" import { useLocale } from "next-intl" -import type { BasePageProps, ChildOnlyProp, Lang, Params } from "@/lib/types" +import type { ChildOnlyProp } from "@/lib/types" import BoxGrid from "@/components/BoxGrid" import Callout from "@/components/Callout" @@ -24,7 +25,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 ProductCard from "@/components/ProductCard" import ProductListComponent, { type ProductListProps, @@ -37,16 +37,9 @@ import InlineLink, { BaseLink } from "@/components/ui/Link" import { Tag } from "@/components/ui/tag" import { cn } from "@/lib/utils/cn" -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 { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants" import { useTranslation } from "@/hooks/useTranslation" -import loadNamespaces from "@/i18n/loadNamespaces" import aave from "@/public/images/dapps/aave.png" import ankr from "@/public/images/dapps/ankr.png" import api3 from "@/public/images/dapps/api3.png" @@ -291,40 +284,9 @@ interface Categories { [key: string]: Category } -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("/dapps") - - 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<BasePageProps, Params> - const DappsPage = () => { const { t } = useTranslation(["page-dapps", "common"]) - const { query } = useRouter() + const searchParams = useSearchParams() const locale = useLocale() const [selectedCategory, setCategory] = useState<CategoryType>( @@ -332,9 +294,8 @@ const DappsPage = () => { ) const explore = useRef<HTMLDivElement>(null) + const queryParamCategories = searchParams?.get("category") || "" useEffect(() => { - // Fetch category on load - const queryParamCategories = (query.category as string) || "" const selectedCategory = queryParamCategories ? (queryParamCategories.split(",")[0] as CategoryType) : CategoryType.FINANCE // Default to finance category if empty @@ -356,7 +317,7 @@ const DappsPage = () => { behavior: "smooth", }) } - }, [query.category]) + }, [queryParamCategories]) const updatePath = ( selectedCategory: CategoryType, @@ -1207,11 +1168,6 @@ const DappsPage = () => { } return ( <Page> - <PageMetadata - title={t("common:decentralized-applications-dapps")} - description={t("page-dapps-desc")} - image="/images/doge-computer.png" - /> <PageHero content={heroContent} /> <Divider /> <Content> diff --git a/app/[locale]/dapps/page.tsx b/app/[locale]/dapps/page.tsx new file mode 100644 index 00000000000..51d8e7edbdb --- /dev/null +++ b/app/[locale]/dapps/page.tsx @@ -0,0 +1,46 @@ +import pick from "lodash.pick" +import { getTranslations } from "next-intl/server" + +import { Params } from "@/lib/types" + +import I18nProvider from "@/components/I18nProvider" + +import { getMetadata } from "@/lib/utils/metadata" +import { getRequiredNamespacesForPage } from "@/lib/utils/translations" + +import DappsPage from "./_components/dapps" + +import { loadMessages } from "@/i18n/loadMessages" + +export default async function Page({ params }: { params: Promise<Params> }) { + const { locale } = await params + + // Get i18n messages + const allMessages = await loadMessages(locale) + const requiredNamespaces = getRequiredNamespacesForPage("/dapps") + const pickedMessages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={pickedMessages}> + <DappsPage /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ locale }) + + return await getMetadata({ + locale, + slug: ["dapps"], + title: t("common.decentralized-applications-dapps"), + description: t("page-dapps.page-dapps-desc"), + image: "/images/doge-computer.png", + }) +} diff --git a/src/pages/[locale]/developers/index.tsx b/app/[locale]/developers/_components/developers.tsx similarity index 92% rename from src/pages/[locale]/developers/index.tsx rename to app/[locale]/developers/_components/developers.tsx index bf30b227584..6c28913a415 100644 --- a/src/pages/[locale]/developers/index.tsx +++ b/app/[locale]/developers/_components/developers.tsx @@ -1,7 +1,8 @@ +"use client" + import { HTMLAttributes, ReactNode } from "react" -import { GetStaticProps } from "next" -import { BasePageProps, ChildOnlyProp, Lang, Params } from "@/lib/types" +import { ChildOnlyProp } from "@/lib/types" import Callout from "@/components/Callout" import Card, { CardProps } from "@/components/Card" @@ -9,22 +10,14 @@ import FeedbackCard from "@/components/FeedbackCard" import HubHero from "@/components/Hero/HubHero" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import Translation from "@/components/Translation" import { ButtonLink } from "@/components/ui/buttons/Button" import { 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 SpeedRunEthereumImage from "@/public/images/dev-tools/speed-run-ethereum-banner.png" import DevelopersImage from "@/public/images/developers-eth-blocks.png" import DogeImage from "@/public/images/doge-computer.png" @@ -127,37 +120,6 @@ const SpeedRunEthereumBanner = ({ </div> ) -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("/developers") - - 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<BasePageProps, Params> - interface IDevelopersPath { emoji: string title: ReactNode @@ -220,10 +182,6 @@ const DevelopersPage = () => { return ( <Page> - <PageMetadata - title={t("page-developers-index:page-developer-meta-title")} - description={t("page-developers-index:page-developers-meta-desc")} - /> <HubHero heroImg={HeroImage} header={`${t("page-developers-index:page-developers-title-1")} ${t( diff --git a/src/pages/[locale]/developers/learning-tools.tsx b/app/[locale]/developers/learning-tools/_components/learning-tools.tsx similarity index 90% rename from src/pages/[locale]/developers/learning-tools.tsx rename to app/[locale]/developers/learning-tools/_components/learning-tools.tsx index 3317452e400..55e31800938 100644 --- a/src/pages/[locale]/developers/learning-tools.tsx +++ b/app/[locale]/developers/learning-tools/_components/learning-tools.tsx @@ -1,28 +1,21 @@ +"use client" + import { BaseHTMLAttributes } from "react" import shuffle from "lodash/shuffle" -import { GetStaticProps } from "next" -import { BasePageProps, Lang, LearningTool, Params } from "@/lib/types" +import { LearningTool } from "@/lib/types" import CalloutBanner from "@/components/CalloutBanner" import FeedbackCard from "@/components/FeedbackCard" import InfoBanner from "@/components/InfoBanner" import LearningToolsCardGrid from "@/components/LearningToolsCardGrid" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import Translation from "@/components/Translation" import { ButtonLink } from "@/components/ui/buttons/Button" 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 AlchemyUniversityImage from "@/public/images/dev-tools/alchemyuniversity.png" import AtlasImage from "@/public/images/dev-tools/atlas.png" import BloomTechImage from "@/public/images/dev-tools/bloomtech.png" @@ -119,39 +112,6 @@ const StackContainer = ({ /> ) -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( - "/developers/learning-tools" - ) - - 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<BasePageProps, Params> - const LearningToolsPage = () => { const { t } = useTranslation(["page-developers-learning-tools"]) @@ -440,14 +400,6 @@ const LearningToolsPage = () => { return ( <Page> - <PageMetadata - title={t( - "page-developers-learning-tools:page-learning-tools-meta-title" - )} - description={t( - "page-developers-learning-tools:page-learning-tools-meta-desc" - )} - /> <MainArticle className="w-full"> <div className="w-full"> <Header> diff --git a/app/[locale]/developers/learning-tools/page.tsx b/app/[locale]/developers/learning-tools/page.tsx new file mode 100644 index 00000000000..d21c8bbb653 --- /dev/null +++ b/app/[locale]/developers/learning-tools/page.tsx @@ -0,0 +1,54 @@ +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 LearningTools from "./_components/learning-tools" + +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( + "/developers/learning-tools" + ) + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <LearningTools /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ + locale, + namespace: "page-developers-learning-tools", + }) + + return await getMetadata({ + locale, + slug: ["developers", "learning-tools"], + title: t("page-developers-learning-tools:page-learning-tools-meta-title"), + description: t( + "page-developers-learning-tools:page-learning-tools-meta-desc" + ), + }) +} + +export default Page diff --git a/src/pages/[locale]/developers/local-environment.tsx b/app/[locale]/developers/local-environment/_components/local-environment.tsx similarity index 69% rename from src/pages/[locale]/developers/local-environment.tsx rename to app/[locale]/developers/local-environment/_components/local-environment.tsx index 219d142a632..beb970aa98a 100644 --- a/src/pages/[locale]/developers/local-environment.tsx +++ b/app/[locale]/developers/local-environment/_components/local-environment.tsx @@ -1,30 +1,21 @@ +"use client" + import { HTMLAttributes } from "react" -import { GetStaticProps, InferGetStaticPropsType } from "next" -import { BasePageProps, ChildOnlyProp, Lang, Params } from "@/lib/types" +import { ChildOnlyProp } from "@/lib/types" import { Framework } from "@/lib/interfaces" import FeedbackCard from "@/components/FeedbackCard" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import ProductCard from "@/components/ProductCard" import Translation from "@/components/Translation" import { Flex, VStack } from "@/components/ui/flex" 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 { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants" import { useTranslation } from "@/hooks/useTranslation" -import loadNamespaces from "@/i18n/loadNamespaces" -import { getLocalEnvironmentFrameworkData } from "@/lib/api/ghRepoData" import EthBlocksImage from "@/public/images/developers-eth-blocks.png" const Content = ({ children }: ChildOnlyProp) => { @@ -43,61 +34,15 @@ const Text = ({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) => ( <p className={cn("mb-6", className)} {...props} /> ) -const loadData = dataLoader([ - ["frameworksListData", getLocalEnvironmentFrameworkData], -]) - -type Props = BasePageProps & { +type Props = { frameworksList: Framework[] } -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( - "/developers/local-environment" - ) - - const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2]) - - const [frameworksListData] = await loadData() - - const lastDeployDate = getLastDeployDate() - const lastDeployLocaleTimestamp = getLocaleTimestamp( - locale as Lang, - lastDeployDate - ) - - const messages = await loadNamespaces(locale!, requiredNamespaces) - - return { - props: { - messages, - contentNotTranslated, - frameworksList: frameworksListData, - lastDeployLocaleTimestamp, - }, - } -}) satisfies GetStaticProps<Props, Params> - -const LocalEnvironmentPage = ({ - frameworksList, -}: InferGetStaticPropsType<typeof getStaticProps>) => { +const LocalEnvironmentPage = ({ frameworksList }: Props) => { const { t } = useTranslation("page-developers-local-environment") return ( <VStack className="mx-auto mt-16 w-full"> - <PageMetadata - title={t("page-local-environment-setup-meta-title")} - description={t("page-local-environment-setup-meta-desc")} - /> <div className="mb-8 w-full justify-center px-8 pb-8 pt-0 xl:pb-4 xl:pt-4"> <h1 className="mb-[1.625rem] text-center font-monospace text-[2rem] font-semibold uppercase not-italic leading-[1.4]"> <Translation id="page-developers-local-environment:page-local-environment-setup-title" /> diff --git a/app/[locale]/developers/local-environment/page.tsx b/app/[locale]/developers/local-environment/page.tsx new file mode 100644 index 00000000000..99f133cfd39 --- /dev/null +++ b/app/[locale]/developers/local-environment/page.tsx @@ -0,0 +1,60 @@ +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 LocalEnvironmentPage from "./_components/local-environment" + +import { loadMessages } from "@/i18n/loadMessages" +import { getLocalEnvironmentFrameworkData } from "@/lib/api/ghRepoData" + +const loadData = dataLoader([ + ["frameworksListData", getLocalEnvironmentFrameworkData], +]) + +const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { + const { locale } = await params + + const [frameworksListData] = await loadData() + + // Get i18n messages + const allMessages = await loadMessages(locale) + const requiredNamespaces = getRequiredNamespacesForPage( + "/developers/local-environment" + ) + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <LocalEnvironmentPage frameworksList={frameworksListData} /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ + locale, + namespace: "page-developers-local-environment", + }) + + return await getMetadata({ + locale, + slug: ["developers", "local-environment"], + title: t("page-local-environment-setup-meta-title"), + description: t("page-local-environment-setup-meta-desc"), + }) +} + +export default Page diff --git a/app/[locale]/developers/page.tsx b/app/[locale]/developers/page.tsx new file mode 100644 index 00000000000..d707c4a461e --- /dev/null +++ b/app/[locale]/developers/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 Developers from "./_components/developers" + +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("/developers") + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <Developers /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ locale, namespace: "page-developers" }) + + return await getMetadata({ + locale, + slug: ["developers"], + title: t("page-developers-index:page-developer-meta-title"), + description: t("page-developers-index:page-developers-meta-desc"), + }) +} + +export default Page diff --git a/src/pages/[locale]/developers/tutorials.tsx b/app/[locale]/developers/tutorials/_components/tutorials.tsx similarity index 81% rename from src/pages/[locale]/developers/tutorials.tsx rename to app/[locale]/developers/tutorials/_components/tutorials.tsx index 1d483a7929d..cb0649dc061 100644 --- a/src/pages/[locale]/developers/tutorials.tsx +++ b/app/[locale]/developers/tutorials/_components/tutorials.tsx @@ -1,3 +1,5 @@ +"use client" + import React, { type ButtonHTMLAttributes, forwardRef, @@ -6,18 +8,16 @@ import React, { useMemo, useState, } from "react" -import { GetStaticProps, InferGetServerSidePropsType } from "next" import { useLocale } from "next-intl" import { FaGithub } from "react-icons/fa" -import { BasePageProps, Lang, Params } from "@/lib/types" +import { ITutorial, Lang } from "@/lib/types" import Emoji from "@/components/Emoji" import FeedbackCard from "@/components/FeedbackCard" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import Translation from "@/components/Translation" -import { getSkillTranslationId, Skill } from "@/components/TutorialMetadata" +import { getSkillTranslationId } from "@/components/TutorialMetadata" import TutorialTags from "@/components/TutorialTags" import { Button, ButtonLink } from "@/components/ui/buttons/Button" import Modal from "@/components/ui/dialog-modal" @@ -25,12 +25,8 @@ import { Flex, FlexProps } from "@/components/ui/flex" import { Tag, TagButton } from "@/components/ui/tag" import { cn } from "@/lib/utils/cn" -import { existsNamespace } from "@/lib/utils/existsNamespace" -import { getLastDeployDate } from "@/lib/utils/getLastDeployDate" import { trackCustomEvent } from "@/lib/utils/matomo" -import { getTutorialsData } from "@/lib/utils/md" import { getLocaleTimestamp } from "@/lib/utils/time" -import { getRequiredNamespacesForPage } from "@/lib/utils/translations" import { filterTutorialsByLang, getSortedTutorialTagsForLang, @@ -38,15 +34,7 @@ import { import externalTutorials from "@/data/externalTutorials.json" -import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants" - import { useBreakpointValue } from "@/hooks/useBreakpointValue" -import { useTranslation } from "@/hooks/useTranslation" -import loadNamespaces from "@/i18n/loadNamespaces" - -type Props = BasePageProps & { - internalTutorials: ITutorial[] -} type LinkFlexProps = FlexProps & { href: string @@ -84,66 +72,6 @@ const LinkFlex = ({ href, children, ...props }: LinkFlexProps) => { ) } -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( - "/developers/tutorials" - ) - - 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, - internalTutorials: getTutorialsData(locale!), - lastDeployLocaleTimestamp, - }, - } -}) satisfies GetStaticProps<Props, Params> - -export interface IExternalTutorial { - url: string - title: string - description: string - author: string - authorGithub: string - tags: Array<string> - skillLevel: string - timeToRead?: string - lang: string - publishDate: string -} - -export interface ITutorial { - href: string - title: string - description: string - author: string - tags?: Array<string> - skill?: Skill - timeToRead?: number | null - published?: string | null - lang: string - isExternal: boolean -} - const published = (locale: string, published: string) => { const localeTimestamp = getLocaleTimestamp(locale as Lang, published) @@ -155,10 +83,15 @@ const published = (locale: string, published: string) => { ) : null } +type TutorialPageProps = { + internalTutorials: ITutorial[] + contentNotTranslated: boolean +} + const TutorialPage = ({ internalTutorials, contentNotTranslated, -}: InferGetServerSidePropsType<typeof getStaticProps>) => { +}: TutorialPageProps) => { const locale = useLocale() const filteredTutorialsByLang = useMemo( () => @@ -175,7 +108,6 @@ const TutorialPage = ({ [filteredTutorialsByLang] ) - const { t } = useTranslation() const [isModalOpen, setModalOpen] = useState(false) const [filteredTutorials, setFilteredTutorials] = useState( filteredTutorialsByLang @@ -224,12 +156,6 @@ const TutorialPage = ({ <MainArticle className={`mx-auto my-0 mt-16 flex w-full flex-col items-center ${dir}`} > - <PageMetadata - title={t("page-developers-tutorials:page-tutorials-meta-title")} - description={t( - "page-developers-tutorials:page-tutorials-meta-description" - )} - /> <h1 className="no-italic mb-4 text-center font-monospace text-[2rem] font-semibold uppercase leading-[1.4] max-sm:mx-4 max-sm:mt-4 sm:mb-[1.625rem]"> <Translation id="page-developers-tutorials:page-tutorial-title" /> </h1> diff --git a/app/[locale]/developers/tutorials/page.tsx b/app/[locale]/developers/tutorials/page.tsx new file mode 100644 index 00000000000..037fa4534eb --- /dev/null +++ b/app/[locale]/developers/tutorials/page.tsx @@ -0,0 +1,59 @@ +import pick from "lodash.pick" +import { getTranslations } from "next-intl/server" + +import { Lang } from "@/lib/types" + +import I18nProvider from "@/components/I18nProvider" + +import { existsNamespace } from "@/lib/utils/existsNamespace" +import { getTutorialsData } from "@/lib/utils/md" +import { getMetadata } from "@/lib/utils/metadata" +import { getRequiredNamespacesForPage } from "@/lib/utils/translations" + +import Tutorials from "./_components/tutorials" + +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( + "/developers/tutorials" + ) + const messages = pick(allMessages, requiredNamespaces) + + const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2]) + + return ( + <I18nProvider locale={locale} messages={messages}> + <Tutorials + internalTutorials={getTutorialsData(locale)} + contentNotTranslated={contentNotTranslated} + /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ + locale, + namespace: "page-developers-tutorials", + }) + + return await getMetadata({ + locale, + slug: ["developers", "tutorials"], + title: t("page-tutorials-meta-title"), + description: t("page-tutorials-meta-description"), + }) +} + +export default Page diff --git a/src/pages/[locale]/eth.tsx b/app/[locale]/eth/_components/eth.tsx similarity index 90% rename from src/pages/[locale]/eth.tsx rename to app/[locale]/eth/_components/eth.tsx index 5a0e84398eb..8e1990fdeb2 100644 --- a/src/pages/[locale]/eth.tsx +++ b/app/[locale]/eth/_components/eth.tsx @@ -1,7 +1,8 @@ -import { GetStaticProps } from "next" +"use client" + import type { ComponentProps, HTMLAttributes } from "react" -import type { BasePageProps, ChildOnlyProp, Lang, Params } from "@/lib/types" +import type { ChildOnlyProp } from "@/lib/types" import ActionCard from "@/components/ActionCard" import CalloutBanner from "@/components/CalloutBanner" @@ -15,7 +16,6 @@ import { Image } from "@/components/Image" import InfoBanner from "@/components/InfoBanner" import ListenToPlayer from "@/components/ListenToPlayer" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import { StandaloneQuizWidget } from "@/components/Quiz/QuizWidget" import Translation from "@/components/Translation" import { ButtonLink } from "@/components/ui/buttons/Button" @@ -25,15 +25,8 @@ import InlineLink from "@/components/ui/Link" import { ListItem, UnorderedList } 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 eth from "@/public/images/eth.png" import ethCat from "@/public/images/eth-gif-cat.png" @@ -175,37 +168,6 @@ const CentralActionCard = (props: ComponentProps<typeof ActionCard>) => ( <ActionCard className="my-8" imageWidth={260} {...props} /> ) -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("/eth") - - 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<BasePageProps, Params> - const EthPage = () => { const { t } = useTranslation("page-eth") const pathname = usePathname() @@ -304,11 +266,6 @@ const EthPage = () => { return ( <Page> - <PageMetadata - title={t("page-eth-whats-eth-meta-title")} - description={t("page-eth-whats-eth-meta-desc")} - image="/images/eth.png" - /> <Content> <HeroContainer> <Header> diff --git a/app/[locale]/eth/page.tsx b/app/[locale]/eth/page.tsx new file mode 100644 index 00000000000..658a59664e5 --- /dev/null +++ b/app/[locale]/eth/page.tsx @@ -0,0 +1,50 @@ +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 EthPage from "./_components/eth" + +import { loadMessages } from "@/i18n/loadMessages" + +export default async function Page({ + params, +}: { + params: Promise<{ locale: Lang }> +}) { + const { locale } = await params + + // Get i18n messages + const allMessages = await loadMessages(locale) + const requiredNamespaces = getRequiredNamespacesForPage("/eth") + const pickedMessages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={pickedMessages}> + <EthPage /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ locale, namespace: "page-eth" }) + + return await getMetadata({ + locale, + slug: ["eth"], + title: t("page-eth-whats-eth-meta-title"), + description: t("page-eth-whats-eth-meta-desc"), + image: "/images/eth.png", + }) +} diff --git a/src/pages/[locale]/gas.tsx b/app/[locale]/gas/_components/gas.tsx similarity index 90% rename from src/pages/[locale]/gas.tsx rename to app/[locale]/gas/_components/gas.tsx index 635b55dda97..3210d07ece7 100644 --- a/src/pages/[locale]/gas.tsx +++ b/app/[locale]/gas/_components/gas.tsx @@ -1,7 +1,6 @@ -import { BaseHTMLAttributes, ComponentPropsWithRef } from "react" -import { GetStaticProps } from "next/types" +"use client" -import { BasePageProps, Lang, Params } from "@/lib/types" +import { BaseHTMLAttributes, ComponentPropsWithRef } from "react" import Callout from "@/components/Callout" import Card from "@/components/Card" @@ -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 Translation from "@/components/Translation" import { ButtonLink } from "@/components/ui/buttons/Button" import { Divider } from "@/components/ui/divider" @@ -33,15 +31,8 @@ import { import { Tag } from "@/components/ui/tag" 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" // Static assets import dogeComputerImg from "@/public/images/doge-computer.png" import ethImg from "@/public/images/eth.png" @@ -86,37 +77,6 @@ const H3 = ({ <h3 className={cn("mb-8 mt-10 text-xl md:text-2xl", className)} {...props} /> ) -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("/gas") - - 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<BasePageProps, Params> - const GasPage = () => { const { t } = useTranslation("page-gas") @@ -157,10 +117,6 @@ const GasPage = () => { return ( <Page> - <PageMetadata - title={t("page-gas-meta-title")} - description={t("page-gas-meta-description")} - /> <div className="w-full bg-gradient-to-r from-accent-a/10 to-accent-c/10 dark:from-accent-a/20 dark:to-accent-c-hover/20"> <div className="pb-8"> <PageHero diff --git a/app/[locale]/gas/page.tsx b/app/[locale]/gas/page.tsx new file mode 100644 index 00000000000..81bfb91e6c3 --- /dev/null +++ b/app/[locale]/gas/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 GasPage from "./_components/gas" + +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("/gas") + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <GasPage /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ locale, namespace: "page-gas" }) + + return await getMetadata({ + locale, + slug: ["gas"], + title: t("page-gas-meta-title"), + description: t("page-gas-meta-description"), + }) +} + +export default Page diff --git a/src/pages/[locale]/get-eth.tsx b/app/[locale]/get-eth/_components/get-eth.tsx similarity index 87% rename from src/pages/[locale]/get-eth.tsx rename to app/[locale]/get-eth/_components/get-eth.tsx index 14f64b997e0..1917c012069 100644 --- a/src/pages/[locale]/get-eth.tsx +++ b/app/[locale]/get-eth/_components/get-eth.tsx @@ -1,7 +1,8 @@ -import type { GetStaticProps, InferGetStaticPropsType } from "next/types" +"use client" + import type { ReactNode } from "react" -import type { BasePageProps, ChildOnlyProp, Lang, Params } from "@/lib/types" +import type { ChildOnlyProp } from "@/lib/types" import CalloutBanner from "@/components/CalloutBanner" import CardList, { @@ -13,7 +14,6 @@ import EthPriceCard from "@/components/EthPriceCard" import FeedbackCard from "@/components/FeedbackCard" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import Translation from "@/components/Translation" import { Alert, AlertContent, AlertDescription } from "@/components/ui/alert" import { ButtonLink } from "@/components/ui/buttons/Button" @@ -29,18 +29,10 @@ import { Stack } 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 { getLastModifiedDateByPath } from "@/lib/utils/gh" import { trackCustomEvent } from "@/lib/utils/matomo" -import { getLocaleTimestamp } from "@/lib/utils/time" -import { getRequiredNamespacesForPage } from "@/lib/utils/translations" - -import { DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants" import { useBreakpointValue } from "@/hooks/useBreakpointValue" import { useTranslation } from "@/hooks/useTranslation" -import loadNamespaces from "@/i18n/loadNamespaces" import uniswap from "@/public/images/dapps/uni.png" import dapps from "@/public/images/doge-computer.png" import bancor from "@/public/images/exchanges/bancor.png" @@ -72,49 +64,11 @@ const TwoColumnContent = (props: ChildOnlyProp) => ( <div className="grid grid-cols-1 gap-16 lg:grid-cols-2" {...props} /> ) -type Props = BasePageProps & { +type Props = { lastDataUpdateDate: 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("get-eth") - - const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2]) - - const lastDataUpdateDate = getLastModifiedDateByPath( - "src/data/exchangesByCountry.ts" - ) - - const lastDeployDate = getLastDeployDate() - const lastDeployLocaleTimestamp = getLocaleTimestamp( - locale as Lang, - lastDeployDate - ) - - const messages = await loadNamespaces(locale, requiredNamespaces) - - return { - props: { - messages, - contentNotTranslated, - lastDeployLocaleTimestamp, - lastDataUpdateDate, - }, - } -}) satisfies GetStaticProps<Props, Params> - -const GetEthPage = ({ - lastDataUpdateDate, -}: InferGetStaticPropsType<typeof getStaticProps>) => { +const GetEthPage = ({ lastDataUpdateDate }: Props) => { const { t } = useTranslation("page-get-eth") const walletImageWidth = useBreakpointValue({ @@ -160,11 +114,6 @@ const GetEthPage = ({ return ( <MainArticle> <Stack className="gap-16 p-8"> - <PageMetadata - title={t("page-get-eth-meta-title")} - description={t("page-get-eth-meta-description")} - /> - <div className="relative flex w-full flex-col-reverse justify-center lg:mx-auto lg:mb-8 lg:flex-col"> <Image src={hero} diff --git a/app/[locale]/get-eth/page.tsx b/app/[locale]/get-eth/page.tsx new file mode 100644 index 00000000000..a00b068a07c --- /dev/null +++ b/app/[locale]/get-eth/page.tsx @@ -0,0 +1,54 @@ +import pick from "lodash.pick" +import { getTranslations } from "next-intl/server" + +import { Lang } from "@/lib/types" + +import I18nProvider from "@/components/I18nProvider" + +import { getLastModifiedDateByPath } from "@/lib/utils/gh" +import { getMetadata } from "@/lib/utils/metadata" +import { getRequiredNamespacesForPage } from "@/lib/utils/translations" + +import GetEthPage from "./_components/get-eth" + +import { loadMessages } from "@/i18n/loadMessages" + +export default async function Page({ + params, +}: { + params: Promise<{ locale: Lang }> +}) { + const { locale } = await params + + const lastDataUpdateDate = getLastModifiedDateByPath( + "src/data/exchangesByCountry.ts" + ) + + // Get i18n messages + const allMessages = await loadMessages(locale) + const requiredNamespaces = getRequiredNamespacesForPage("/get-eth") + const pickedMessages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={pickedMessages}> + <GetEthPage lastDataUpdateDate={lastDataUpdateDate} /> + </I18nProvider> + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + + const t = await getTranslations({ locale, namespace: "page-get-eth" }) + + return await getMetadata({ + locale, + slug: ["get-eth"], + title: t("page-get-eth-meta-title"), + description: t("page-get-eth-meta-desc"), + }) +} diff --git a/src/pages/[locale]/layer-2/index.tsx b/app/[locale]/layer-2/_components/layer-2.tsx similarity index 87% rename from src/pages/[locale]/layer-2/index.tsx rename to app/[locale]/layer-2/_components/layer-2.tsx index 3391a65c5f4..e7f071c1b92 100644 --- a/src/pages/[locale]/layer-2/index.tsx +++ b/app/[locale]/layer-2/_components/layer-2.tsx @@ -1,6 +1,6 @@ -import type { GetStaticProps } from "next/types" +"use client" -import type { BasePageProps, GrowThePieData, Lang, Params } from "@/lib/types" +import type { GrowThePieData, Lang } from "@/lib/types" import Callout from "@/components/Callout" import Card from "@/components/Card" @@ -8,111 +8,31 @@ import ExpandableCard from "@/components/ExpandableCard" import HubHero, { HubHeroProps } from "@/components/Hero/HubHero" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import { ButtonLink } from "@/components/ui/buttons/Button" import InlineLink from "@/components/ui/Link" -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 { layer2Data, Rollups } from "@/data/networks/networks" - -import { BASE_TIME_UNIT, DEFAULT_LOCALE, LOCALES_CODES } from "@/lib/constants" +import { Rollups } from "@/data/networks/networks" import useTranslation from "@/hooks/useTranslation" -import loadNamespaces from "@/i18n/loadNamespaces" -import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie" -import { fetchL2beat } from "@/lib/api/fetchL2beat" import HeroImage from "@/public/images/heroes/layer-2-hub-hero.jpg" import EthereumLogo from "@/public/images/layer-2/ethereum.png" import WalkingImage from "@/public/images/layer-2/layer-2-walking.png" import ExploreImage from "@/public/images/layer-2/learn-hero.png" import ManDogCardImage from "@/public/images/man-and-dog-playing.png" -// In seconds -const REVALIDATE_TIME = BASE_TIME_UNIT * 24 - -const loadData = dataLoader( - [ - ["growThePieData", fetchGrowThePie], - ["l2beatData", fetchL2beat], - ], - REVALIDATE_TIME * 1000 -) - -export async function getStaticPaths() { - return { - paths: LOCALES_CODES.map((locale) => ({ params: { locale } })), - fallback: false, - } +type Layer2HubProps = { + randomL2s: Rollups + userRandomL2s: Rollups + growThePieData: GrowThePieData + locale: Lang } -export const getStaticProps = (async ({ params }) => { - const { locale = DEFAULT_LOCALE } = params || {} - - const lastDeployDate = getLastDeployDate() - const lastDeployLocaleTimestamp = getLocaleTimestamp( - locale as Lang, - lastDeployDate - ) - - const requiredNamespaces = getRequiredNamespacesForPage("/layer-2") - - const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2]) - - 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() - - const messages = await loadNamespaces(locale, requiredNamespaces) - - return { - props: { - messages, - randomL2s, - userRandomL2s, - contentNotTranslated, - lastDeployLocaleTimestamp, - locale, - growThePieData, - }, - } -}) satisfies GetStaticProps<BasePageProps, Params> - const Layer2Hub = ({ randomL2s, userRandomL2s, growThePieData, locale, -}: { - randomL2s: Rollups - userRandomL2s: Rollups - growThePieData: GrowThePieData - locale: Lang -}) => { +}: Layer2HubProps) => { const { t } = useTranslation(["page-layer-2", "common"]) const medianTxCost = "error" in growThePieData.txCostsMedianUsd @@ -166,12 +86,6 @@ const Layer2Hub = ({ return ( <MainArticle className="relative flex flex-col"> - <PageMetadata - title={t("page-layer-2-meta-title")} - description={t("page-layer-2-meta-description")} - image="/images/layer-2/learn-hero.png" - /> - <HubHero {...heroContent} /> <div diff --git a/src/pages/[locale]/layer-2/learn.tsx b/app/[locale]/layer-2/learn/_components/learn.tsx similarity index 89% rename from src/pages/[locale]/layer-2/learn.tsx rename to app/[locale]/layer-2/learn/_components/learn.tsx index 68bc29e94b2..4596b6ab2b2 100644 --- a/src/pages/[locale]/layer-2/learn.tsx +++ b/app/[locale]/layer-2/learn/_components/learn.tsx @@ -1,26 +1,15 @@ -import { GetStaticProps } from "next" - -import type { BasePageProps, Lang, Params } from "@/lib/types" +"use client" import Callout from "@/components/Callout" import Card from "@/components/Card" import { ContentHero, type ContentHeroProps } from "@/components/Hero" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" -import PageMetadata from "@/components/PageMetadata" import { StandaloneQuizWidget } from "@/components/Quiz/QuizWidget" import Translation from "@/components/Translation" import { ButtonLink } from "@/components/ui/buttons/Button" -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 Callout2Image from "@/public/images/layer-2/learn-hero.png" import OptimisticRollupImage from "@/public/images/layer-2/optimistic_rollup.png" @@ -30,37 +19,6 @@ import Callout1Image from "@/public/images/man-and-dog-playing.png" import DAOImage from "@/public/images/use-cases/dao-2.png" import WhatIsEthereumImage from "@/public/images/what-is-ethereum.png" -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("/layer-2/learn") - - const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2]) - - const messages = await loadNamespaces(locale, requiredNamespaces) - - return { - props: { - messages, - contentNotTranslated, - lastDeployLocaleTimestamp, - }, - } -}) satisfies GetStaticProps<BasePageProps, Params> - const Layer2Learn = () => { const { t } = useTranslation("page-layer-2-learn") const pathname = usePathname() @@ -132,12 +90,6 @@ const Layer2Learn = () => { return ( <MainArticle className="relative flex flex-col"> - <PageMetadata - title={t("page-layer-2-learn-meta-title")} - description={t("page-layer-2-learn-description")} - image="/images/layer-2/learn-hero.png" - /> - <ContentHero {...heroProps} /> <div diff --git a/app/[locale]/layer-2/learn/page.tsx b/app/[locale]/layer-2/learn/page.tsx new file mode 100644 index 00000000000..9d9587cc7cc --- /dev/null +++ b/app/[locale]/layer-2/learn/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 LearnPage from "./_components/learn" + +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("/layer-2/learn") + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <LearnPage /> + </I18nProvider> + ) +} + +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 ( + <MainArticle className="relative flex flex-col"> + <ContentHero {...heroProps} /> + + <Layer2NetworksTable + layer2Data={layer2Data} + locale={locale} + mainnetData={mainnetData} + /> + + <div id="more-advanced-cta" className="w-full px-8 py-9"> + <div className="flex flex-col gap-8 bg-main-gradient px-12 py-14"> + <h3>{t("page-layer-2-networks-more-advanced-title")}</h3> + <div className="flex max-w-[768px] flex-col gap-8"> + <p> + {t("page-layer-2-networks-more-advanced-descripton-1")}{" "} + <strong> + {t("page-layer-2-networks-more-advanced-descripton-2")} + </strong> + </p> + <p>{t("page-layer-2-networks-more-advanced-descripton-3")}</p> + </div> + <div className="flex flex-col gap-6 sm:flex-row"> + <ButtonLink href="https://l2beat.com"> + {t("page-layer-2-networks-more-advanced-link-1")} + </ButtonLink> + <ButtonLink href="https://growthepie.xyz"> + {t("page-layer-2-networks-more-advanced-link-2")} + </ButtonLink> + </div> + </div> + </div> + + <NetworkMaturity /> + + <div + id="callout-cards" + className="flex w-full flex-col px-8 py-9 lg:flex-row lg:gap-16" + > + <Callout + image={Callout1Image} + title={t("page-layer-2-networks-callout-1-title")} + description={t("page-layer-2-networks-callout-1-description")} + > + <div> + <ButtonLink + href="/layer-2/" + customEventOptions={{ + eventCategory: "l2_networks", + eventAction: "button_click", + eventName: "bottom_hub", + }} + > + {t("common:learn-more")} + </ButtonLink> + </div> + </Callout> + <Callout + image={Callout2Image} + title={t("page-layer-2-networks-callout-2-title")} + description={t("page-layer-2-networks-callout-2-description")} + > + <div> + <ButtonLink + href="/layer-2/learn/" + customEventOptions={{ + eventCategory: "l2_networks", + eventAction: "button_click", + eventName: "bottom_learn", + }} + > + {t("common:learn-more")} + </ButtonLink> + </div> + </Callout> + </div> + </MainArticle> + ) +} + +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 ( + <I18nProvider locale={locale} messages={messages}> + <Layer2Networks {...props} /> + </I18nProvider> + ) +} + +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 ( + <I18nProvider locale={locale} messages={messages}> + <Layer2Page + randomL2s={randomL2s} + userRandomL2s={userRandomL2s} + growThePieData={growThePieData} + locale={locale} + /> + </I18nProvider> + ) +} + +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 ( - <html lang={locale} suppressHydrationWarning> + <html + lang={locale} + className={`${inter.variable} ${ibmPlexMono.variable}`} + suppressHydrationWarning + > <body> <Providers locale={locale} messages={messages}> + <Suspense> + <Matomo /> + </Suspense> + <BaseLayout lastDeployLocaleTimestamp={lastDeployLocaleTimestamp}> {children} </BaseLayout> 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) => ( <Image className="h-[200px] w-auto" src={src} alt={alt} /> ) -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<BasePageProps, Params> - const LearnPage = () => { const { t } = useTranslation("page-learn") @@ -214,12 +169,6 @@ const LearnPage = () => { return ( <div className="relative w-full"> - <PageMetadata - title={t("page-learn-meta-title")} - description={t("hero-subtitle")} - image="/images/heroes/learn-hub-hero.png" - /> - <HubHero {...heroContent} /> <Flex diff --git a/app/[locale]/learn/page.tsx b/app/[locale]/learn/page.tsx new file mode 100644 index 00000000000..f62594e6fa9 --- /dev/null +++ b/app/[locale]/learn/page.tsx @@ -0,0 +1,46 @@ +import pick from "lodash.pick" +import { getTranslations } from "next-intl/server" + +import { type Params } from "@/lib/types" + +import I18nProvider from "@/components/I18nProvider" + +import { getMetadata } from "@/lib/utils/metadata" +import { getRequiredNamespacesForPage } from "@/lib/utils/translations" + +import LearnPage from "./_components/learn" + +import { loadMessages } from "@/i18n/loadMessages" + +export default async function Page({ params }: { params: Promise<Params> }) { + const { locale } = await params + + // Get i18n messages + const allMessages = await loadMessages(locale) + const requiredNamespaces = getRequiredNamespacesForPage("/learn") + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <LearnPage /> + </I18nProvider> + ) +} + +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 ( + <I18nProvider locale={locale} messages={messages}> + <HomePage {...props} /> + </I18nProvider> + ) +} + +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<BasePageProps, Params> - -const QuizzesHubPage: NextPage< - InferGetStaticPropsType<typeof getStaticProps> -> = () => { +const QuizzesPage = () => { const { t } = useTranslation("learn-quizzes") const [userStats, updateUserStats] = useLocalQuizData() @@ -90,11 +52,6 @@ const QuizzesHubPage: NextPage< return ( <MainArticle> - <PageMetadata - title={t("common:quizzes-title")} - description={t("quizzes-subtitle")} - image="/images/heroes/quizzes-hub-hero.png" - /> <HubHero title={t("common:quizzes-title")} description={t("quizzes-subtitle")} @@ -133,7 +90,6 @@ const QuizzesHubPage: NextPage< <Flex className="items-center justify-between bg-background-highlight p-8 max-xl:flex-col max-xl:gap-4 lg:rounded-lg"> <div className="max-xl:text-center"> <p className="font-bold">{t("want-more-quizzes")}</p> - <p>{t("contribute")}</p> </div> <ButtonLink @@ -165,4 +121,4 @@ const QuizzesHubPage: NextPage< ) } -export default QuizzesHubPage +export default QuizzesPage diff --git a/app/[locale]/quizzes/page.tsx b/app/[locale]/quizzes/page.tsx new file mode 100644 index 00000000000..af58df49b86 --- /dev/null +++ b/app/[locale]/quizzes/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 QuizzesPage from "./_components/quizzes" + +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("/quizzes") + const messages = pick(allMessages, requiredNamespaces) + + return ( + <I18nProvider locale={locale} messages={messages}> + <QuizzesPage /> + </I18nProvider> + ) +} + +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<BasePageProps, Params> - -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 ( <MainArticle className="relative flex flex-col"> - <PageMetadata - title={t("page-resources-meta-title")} - description={t("page-resources-meta-description")} - image="/images/heroes/guides-hub-hero.jpg" - /> - <BannerNotification shouldShow className="text-center max-md:flex-col"> {t("page-resources-banner-notification-message")}{" "} <Link diff --git a/app/[locale]/resources/page.tsx b/app/[locale]/resources/page.tsx new file mode 100644 index 00000000000..65eb778f8d0 --- /dev/null +++ b/app/[locale]/resources/page.tsx @@ -0,0 +1,64 @@ +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 ResourcesPage from "./_components/resources" + +import { loadMessages } from "@/i18n/loadMessages" +import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie" + +// In seconds +const REVALIDATE_TIME = BASE_TIME_UNIT * 1 + +const loadData = dataLoader( + [["growThePieData", fetchGrowThePie]], + REVALIDATE_TIME * 1000 +) + +const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { + 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 ( + <I18nProvider locale={locale} messages={messages}> + <ResourcesPage txCostsMedianUsd={txCostsMedianUsd} /> + </I18nProvider> + ) +} + +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<BasePageProps, Params> - const VisionPage = () => { const { t } = useTranslation(["page-roadmap-vision", "page-upgrades-index"]) const pathname = usePathname() @@ -155,10 +117,6 @@ const VisionPage = () => { return ( <Page> - <PageMetadata - title={t("page-roadmap-vision-meta-title")} - description={t("page-roadmap-vision-meta-desc")} - /> <PageHero content={heroContent} /> <Divider /> <PageContent> 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 ( + <I18nProvider locale={locale} messages={messages}> + <VisionPage /> + </I18nProvider> + ) +} + +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<BasePageProps, Params> - const RunANodePage = () => { const { t } = useTranslation("page-run-a-node") const heroContent = { @@ -331,11 +293,6 @@ const RunANodePage = () => { return ( <GappedPage> - <PageMetadata - title={t("page-run-a-node-meta-title")} - description={t("page-run-a-node-meta-description")} - image="/images/run-a-node/ethereum-inside.png" - /> <div className="w-full bg-gradient-to-br from-accent-b/5 via-primary/10 to-accent-b/15 dark:from-accent-b/20 dark:via-primary/15 dark:to-accent-a/20"> <div className="pb-8"> <PageHero content={heroContent} isReverse /> 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 ( + <I18nProvider locale={locale} messages={messages}> + <RunANodePage /> + </I18nProvider> + ) +} + +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<Props, Params> - const Content = (props: BaseHTMLAttributes<HTMLDivElement>) => ( <div className="w-full px-8 py-4" {...props} /> ) @@ -228,7 +78,7 @@ const H3 = ({ <h3 className={cn("mb-8 mt-10", className)} {...props} /> ) -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 ( <Page asChild> <MainArticle> - <PageMetadata - title={t("page-stablecoins-meta-title")} - description={t("page-stablecoins-meta-description")} - image="/images/stablecoins/hero.png" - /> <PageHero isReverse content={heroContent} /> <Divider /> <Content> 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 ( + <I18nProvider locale={locale} messages={messages}> + <StablecoinsPage markets={markets} marketsHasError={marketsHasError} /> + </I18nProvider> + ) +} + +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: { </Card> ) -const fetchBeaconchainData = async (): Promise<StakingStatsData> => { - // 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<Props, Params> - -const StakingPage = ({ - data, -}: InferGetStaticPropsType<typeof getStaticProps>) => { +const StakingPage = ({ data }: Props) => { const { t } = useTranslation("page-staking") const heroContent = { @@ -326,11 +237,6 @@ const StakingPage = ({ return ( <PageContainer> - <PageMetadata - title={t("page-staking-meta-title")} - description={t("page-staking-meta-description")} - image="/images/upgrades/upgrade_rhino.png" - /> <HeroStatsWrapper> <PageHero content={heroContent} /> <StakingStatsBox data={data} /> 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<BasePageProps, Params> - const DepositContractPage = () => { const pathname = usePathname() @@ -304,10 +257,6 @@ const DepositContractPage = () => { return ( <MainArticle className="w-full"> <FlexBox> - <PageMetadata - title={t("page-staking-deposit-contract-meta-title")} - description={t("page-staking-deposit-contract-meta-desc")} - /> <LeftColumn> <Breadcrumbs slug={pathname} startDepth={1} /> <Title>{t("page-staking-deposit-contract-title")}</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 ( + <I18nProvider locale={locale} messages={messages}> + <DepositContractPage /> + </I18nProvider> + ) +} + +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<StakingStatsData> => { + // 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 ( + <I18nProvider locale={locale} messages={messages}> + <StakingPage data={data} /> + </I18nProvider> + ) +} + +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<BasePageProps> - const StartWithCryptoPage = ({ newToCryptoWallets, }: { @@ -78,12 +32,6 @@ const StartWithCryptoPage = ({ <QueryClientProvider client={queryClient}> <WalletProviders locale={locale}> <MainArticle className="flex w-full flex-col items-center overflow-x-hidden"> - <PageMetadata - title={"Start with crypto"} - description={"Your gateway to the world of ethereum"} - image={HeroImage.src} - /> - <div className="mb-16 h-[240px] w-full md:h-[380px] lg:h-[398px]"> <Image src={HeroImage} diff --git a/app/[locale]/start/page.tsx b/app/[locale]/start/page.tsx new file mode 100644 index 00000000000..5f7128d25be --- /dev/null +++ b/app/[locale]/start/page.tsx @@ -0,0 +1,52 @@ +import pick from "lodash.pick" + +import { Lang } from "@/lib/types" + +import I18nProvider from "@/components/I18nProvider" + +import { getMetadata } from "@/lib/utils/metadata" +import { getRequiredNamespacesForPage } from "@/lib/utils/translations" +import { getNewToCryptoWallets } from "@/lib/utils/wallets" + +import StartPage from "./_components/start" + +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("/start") + const messages = pick(allMessages, requiredNamespaces) + + const newToCryptoWallets = getNewToCryptoWallets() + const wallets = newToCryptoWallets.map((wallet) => ({ + ...wallet, + supportedLanguages: [], + })) + + return ( + <I18nProvider locale={locale} messages={messages}> + <StartPage newToCryptoWallets={wallets} /> + </I18nProvider> + ) +} + +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<typeof Card>) => ( /> ) -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<BasePageProps, Params> - const WalletsPage = () => { const pathname = usePathname() const locale = useLocale() @@ -219,12 +178,6 @@ const WalletsPage = () => { return ( <MainArticle className="mx-auto flex w-full flex-col items-center"> - <PageMetadata - title={t("page-wallets-meta-title")} - description={t("page-wallets-meta-description")} - image="/images/wallets/wallet-hero.png" - /> - <PageHero content={heroContent} isReverse /> <div className="mt-4 w-full border-t bg-background-highlight px-0 py-16 lg:mt-8"> 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) => ( </p> ) -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<Props, Params> - -const FindWalletPage = ({ - wallets, -}: InferGetStaticPropsType<typeof getStaticProps>) => { +const FindWalletPage = ({ wallets }: Props) => { const pathname = usePathname() const { t } = useTranslation("page-wallets-find-wallet") return ( <MainArticle className="relative flex flex-col"> - <PageMetadata - title={t("page-find-wallet-meta-title")} - description={t("page-find-wallet-meta-description")} - image="/images/wallets/wallet-hero.png" - /> - <BannerNotification shouldShow={true}> {t("page-find-wallet-footnote-1")} </BannerNotification> 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 ( + <I18nProvider locale={locale} messages={messages}> + <FindWalletPage wallets={wallets} /> + </I18nProvider> + ) +} + +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 ( + <I18nProvider locale={locale} messages={messages}> + <WalletsPage /> + </I18nProvider> + ) +} + +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<ImageProps, "src">) => ( <Image src={src} alt="" width={400} /> ) -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<Props, Params> - -const WhatIsEthereumPage = ({ - data, -}: InferGetStaticPropsType<typeof getStaticProps>) => { +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 ( <VStack className="mx-0 my-auto w-full gap-0" asChild> <MainArticle> - <PageMetadata - title={t("page-what-is-ethereum-meta-title")} - description={t("page-what-is-ethereum-meta-description")} - image="/images/what-is-ethereum.png" - /> <Content> <Flex className="flex-col-reverse items-center justify-between md:flex-row"> <Stack className="mb-6 gap-4" asChild> 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 ( + <I18nProvider locale={locale} messages={messages}> + <WhatIsEthereumPage data={data.txCount} /> + </I18nProvider> + ) +} + +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<ResponseData> -) { +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 = () => { </InlineLink>{" "} (<strong>{t("page-layer-2-network-maturity-component-3")}</strong> ),{" "} - <strong>{t("page-layer-2-network-maturity-component-4")}</strong>,{" "} + <strong> + {t("page-layer-2-network-maturity-component-4")} + </strong>,{" "} <strong>{t("page-layer-2-network-maturity-component-5")}</strong>.{" "} {t("page-layer-2-network-maturity-component-6")} </p> 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 ( - <Head> - <title>{title}</title> - {metadata.map((data) => ( - <meta - key={(data as NameMeta).name || (data as PropMeta).property} - {...data} - /> - ))} - <link rel="canonical" key={canonical} href={canonical} /> - <link rel="alternate" hrefLang="x-default" href={xDefault} /> - {locales.map((loc) => ( - <link - key={loc} - rel="alternate" - hrefLang={loc} - href={getFullUrl(loc, path)} - /> - ))} - </Head> - ) -} - -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 = <T,>({ matomoEventCategory, meta, }: ProductTableProps<T>) => { - const router = useRouter() + const searchParams = useSearchParams() + const [activePresets, setActivePresets] = useState<number[]>([]) const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false) @@ -74,18 +75,19 @@ const ProductTable = <T,>({ // 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 = <T,>({ // 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<string> + skillLevel: string + timeToRead?: string + lang: string + publishDate: string +} + +export interface ITutorial { + href: string + title: string + description: string + author: string + tags?: Array<string> + 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<BasePageProps> - -const NotFoundPage = () => ( - <div className="mx-auto mb-0 mt-16 flex w-full flex-col items-center"> - <MainArticle className="my-8 w-full space-y-8 px-8 py-4"> - <h1> - <Translation id="we-couldnt-find-that-page" /> - </h1> - <p> - <Translation id="try-using-search" />{" "} - <InlineLink href="/"> - <Translation id="return-home" /> - </InlineLink> - . - </p> - </MainArticle> - </div> -) - -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<BasePageProps, Params> - -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 ( - <MainArticle className="relative flex flex-col"> - <PageMetadata - title={t("page-layer-2-networks-meta-title")} - description={t("page-layer-2-networks-hero-description")} - image="/images/layer-2/learn-hero.png" - /> - - <ContentHero {...heroProps} /> - - <Layer2NetworksTable - layer2Data={layer2Data} - locale={locale} - mainnetData={mainnetData} - /> - - <div id="more-advanced-cta" className="w-full px-8 py-9"> - <div className="flex flex-col gap-8 bg-main-gradient px-12 py-14"> - <h3>{t("page-layer-2-networks-more-advanced-title")}</h3> - <div className="flex max-w-[768px] flex-col gap-8"> - <p> - {t("page-layer-2-networks-more-advanced-descripton-1")}{" "} - <strong> - {t("page-layer-2-networks-more-advanced-descripton-2")} - </strong> - </p> - <p>{t("page-layer-2-networks-more-advanced-descripton-3")}</p> - </div> - <div className="flex flex-col gap-6 sm:flex-row"> - <ButtonLink href="https://l2beat.com"> - {t("page-layer-2-networks-more-advanced-link-1")} - </ButtonLink> - <ButtonLink href="https://growthepie.xyz"> - {t("page-layer-2-networks-more-advanced-link-2")} - </ButtonLink> - </div> - </div> - </div> - - <NetworkMaturity /> - - <div - id="callout-cards" - className="flex w-full flex-col px-8 py-9 lg:flex-row lg:gap-16" - > - <Callout - image={Callout1Image} - title={t("page-layer-2-networks-callout-1-title")} - description={t("page-layer-2-networks-callout-1-description")} - > - <div> - <ButtonLink - href="/layer-2/" - customEventOptions={{ - eventCategory: "l2_networks", - eventAction: "button_click", - eventName: "bottom_hub", - }} - > - {t("common:learn-more")} - </ButtonLink> - </div> - </Callout> - <Callout - image={Callout2Image} - title={t("page-layer-2-networks-callout-2-title")} - description={t("page-layer-2-networks-callout-2-description")} - > - <div> - <ButtonLink - href="/layer-2/learn/" - customEventOptions={{ - eventCategory: "l2_networks", - eventAction: "button_click", - eventName: "bottom_learn", - }} - > - {t("common:learn-more")} - </ButtonLink> - </div> - </Callout> - </div> - </MainArticle> - ) -} - -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 ( - <NextIntlClientProvider - locale={(router.query.locale as string) || DEFAULT_LOCALE} - messages={pageProps.messages || {}} - onError={() => { - // Suppress errors by default, enable if needed to debug - // console.error(error) - }} - getMessageFallback={({ key }) => { - const keyOnly = key.split(".").pop() - return keyOnly || key - }} - > - <ThemeProvider> - <TooltipProvider> - <FeedbackWidgetProvider> - <BaseLayout - // contentIsOutdated={!!pageProps.frontmatter?.isOutdated} - // contentNotTranslated={pageProps.contentNotTranslated} - lastDeployLocaleTimestamp={pageProps.lastDeployLocaleTimestamp} - > - {getLayout(<Component {...pageProps} />)} - </BaseLayout> - </FeedbackWidgetProvider> - </TooltipProvider> - </ThemeProvider> - </NextIntlClientProvider> - ) -} - -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 ( - <Html dir={dir} lang={locale}> - <Head> - {/* favicon */} - <link rel="icon" type="image/x-icon" href="/images/favicon.png" /> - {/* manifest */} - <link rel="manifest" href="/manifest.json" /> - {/* preload inter static web fonts */} - <link - rel="preload" - href="/fonts/inter/latin.woff2" - as="font" - type="font/woff2" - crossOrigin="anonymous" - /> - </Head> - <body> - <Main /> - <NextScript /> - </body> - </Html> - ) - } -} - -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]