diff --git a/.github/ISSUE_TEMPLATE/suggest_resource.yaml b/.github/ISSUE_TEMPLATE/suggest_resource.yaml new file mode 100644 index 00000000000..6df8803f163 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/suggest_resource.yaml @@ -0,0 +1,63 @@ +name: Suggest a resource +description: Suggest a new resource to list on ethereum.org/resources +title: Suggest a resource +labels: ["resource πŸ“š"] +body: + - type: markdown + attributes: + value: | + Before suggesting a resource, make sure you've read [our listing policy](https://www.ethereum.org/en/contributing/adding-resources/). + - type: markdown + attributes: + value: Only continue with the issue if your resource meets the criteria listed there. + - type: markdown + attributes: + value: If it does, complete the following information which we need to accurately list the resource. + - type: markdown + id: resource_info + attributes: + value: "## Resource info" + - type: input + id: resource_name + attributes: + label: Name + description: Please provide the official name of the resource + validations: + required: true + - type: input + id: resource_url + attributes: + label: Resource URL + description: Please provide a URL to the resource + validations: + required: true + - type: textarea + id: resource_description + attributes: + label: Description + description: Please provide a short 1-2 sentence description of the resource + validations: + required: true + - type: textarea + id: resource_logo + attributes: + label: Logo + description: | + Please provide an SVG or transparent PNG + Tip: You can attach images by clicking this area to highlight it and then dragging files in. + - type: input + id: resource_category + attributes: + label: Category + description: Please specify a best fit to categorize the resource (e.g., DeFi, NFT, Scaling, etc.) + - type: checkboxes + id: resource_work_on + attributes: + label: Would you like to work on this issue? + options: + - label: "Yes" + required: false + - label: "No" + required: false + validations: + required: true diff --git a/public/_redirects b/public/_redirects index d6ca9de7a30..e2b3b5fae35 100644 --- a/public/_redirects +++ b/public/_redirects @@ -177,3 +177,7 @@ /*/deprecated-software /:splat/dapps/ 301! /*/enterprise/private-ethereum/ /:splat/enterprise/ 301! + +/dashboards /en/resources 301! + +/*/dashboards /:splat/resources 301! diff --git a/public/content/contributing/adding-resources/index.md b/public/content/contributing/adding-resources/index.md new file mode 100644 index 00000000000..c79de073d60 --- /dev/null +++ b/public/content/contributing/adding-resources/index.md @@ -0,0 +1,53 @@ +--- +title: Adding Resources +description: The policy we use when adding resources to ethereum.org +lang: en +--- + +# Adding Resources {#adding-resources} + +We want to make sure we list the best resources possible while keeping users safe and confident. + +Anyone is free to suggest new resources to add to the resource dashboard on ethereum.org, currently found at [ethereum.org/resources](/resources/). + +Although we welcome new additions, the current resources were chosen based on an experience we're trying to create for our users. These are based on some of our design principles: + +- _Inspirational_: anything on ethereum.org should offer something new to users +- _A good story_: what's listed should provide an "aha" moment +- _Credible_: everything should be legitimate businesses/projects to minimize risk to users + +Overall **ethereum.org aims to provide a seamless onboarding experience for new users**. For that reason, we add resources based on their: + +- ease of use +- accuracy +- maintenance + +## The decision framework {#decision-framework} + +### Criteria {#criteria} + +- **Honest and accurate listing information** - Any suggested listings must come with honest and accurate information. Products that falsify information will be removed. +- **Active project** – The resource should be maintained by an active team to ensure quality and support for users. Outdated resources are subject to removal. + +### Product Ordering {#product-ordering} + +We reserve the right to order products based on their impact. New products will generally be added to the bottom of the list unless otherwise specified. + +## Maintenance {#maintenance} + +As the Ethereum ecosystem evolves, we will routinely check our content to: + +- Ensure that all resources listed still fulfill our criteria +- Verify there aren't products that have been suggested that meet more of our criteria than the ones currently listed + +You can help with this by checking and letting us know. [Create an issue](https://github.com/ethereum/ethereum-org-website/issues/new?template=bug_report.yaml) or send an email to [website@ethereum.org](mailto:website@ethereum.org). + +--- + +## Add your resource {#add-your-resource} + +If you want to add a resource to ethereum.org and it meets the criteria, create an issue on GitHub. + + + Create an issue + diff --git a/public/images/resources/beaconcha-in.png b/public/images/resources/beaconcha-in.png new file mode 100644 index 00000000000..58cb0f5e3cf Binary files /dev/null and b/public/images/resources/beaconcha-in.png differ diff --git a/public/images/resources/blobsguru.png b/public/images/resources/blobsguru.png new file mode 100644 index 00000000000..19ce09eb8c8 Binary files /dev/null and b/public/images/resources/blobsguru.png differ diff --git a/public/images/resources/blocknative.png b/public/images/resources/blocknative.png new file mode 100644 index 00000000000..3c91bce7722 Binary files /dev/null and b/public/images/resources/blocknative.png differ diff --git a/public/images/resources/blockscout.webp b/public/images/resources/blockscout.webp new file mode 100644 index 00000000000..2224fa970e8 Binary files /dev/null and b/public/images/resources/blockscout.webp differ diff --git a/public/images/resources/cryptowerk.png b/public/images/resources/cryptowerk.png new file mode 100644 index 00000000000..efc0405bb9a Binary files /dev/null and b/public/images/resources/cryptowerk.png differ diff --git a/public/images/resources/defi-llama.png b/public/images/resources/defi-llama.png new file mode 100644 index 00000000000..c0f44698920 Binary files /dev/null and b/public/images/resources/defi-llama.png differ diff --git a/public/images/resources/defi-market-cap.png b/public/images/resources/defi-market-cap.png new file mode 100644 index 00000000000..64371a8c8ef Binary files /dev/null and b/public/images/resources/defi-market-cap.png differ diff --git a/public/images/resources/defi-scan.png b/public/images/resources/defi-scan.png new file mode 100644 index 00000000000..c10eca423be Binary files /dev/null and b/public/images/resources/defi-scan.png differ diff --git a/public/images/resources/eas.png b/public/images/resources/eas.png new file mode 100644 index 00000000000..f6d4b65cb45 Binary files /dev/null and b/public/images/resources/eas.png differ diff --git a/public/images/resources/eigenphi.png b/public/images/resources/eigenphi.png new file mode 100644 index 00000000000..8cfa8873cff Binary files /dev/null and b/public/images/resources/eigenphi.png differ diff --git a/public/images/resources/eth-glyph-black.png b/public/images/resources/eth-glyph-black.png new file mode 100644 index 00000000000..991c4d27aa5 Binary files /dev/null and b/public/images/resources/eth-glyph-black.png differ diff --git a/public/images/resources/eth-glyph-blue-circle.png b/public/images/resources/eth-glyph-blue-circle.png new file mode 100644 index 00000000000..b811169710d Binary files /dev/null and b/public/images/resources/eth-glyph-blue-circle.png differ diff --git a/public/images/resources/eth-glyph-e-org.png b/public/images/resources/eth-glyph-e-org.png new file mode 100644 index 00000000000..dfe97a1db86 Binary files /dev/null and b/public/images/resources/eth-glyph-e-org.png differ diff --git a/public/images/resources/eth-glyph-rainbow.frame.png b/public/images/resources/eth-glyph-rainbow.frame.png new file mode 100644 index 00000000000..ff150fe28e4 Binary files /dev/null and b/public/images/resources/eth-glyph-rainbow.frame.png differ diff --git a/public/images/resources/etherealize.png b/public/images/resources/etherealize.png new file mode 100644 index 00000000000..999fc0c5c30 Binary files /dev/null and b/public/images/resources/etherealize.png differ diff --git a/public/images/resources/etherscan.png b/public/images/resources/etherscan.png new file mode 100644 index 00000000000..77cba5c68c8 Binary files /dev/null and b/public/images/resources/etherscan.png differ diff --git a/public/images/resources/ethproofs.png b/public/images/resources/ethproofs.png new file mode 100644 index 00000000000..d40c4730fff Binary files /dev/null and b/public/images/resources/ethproofs.png differ diff --git a/public/images/resources/ethstaker.png b/public/images/resources/ethstaker.png new file mode 100644 index 00000000000..ad100752a55 Binary files /dev/null and b/public/images/resources/ethstaker.png differ diff --git a/public/images/resources/farcaster.png b/public/images/resources/farcaster.png new file mode 100644 index 00000000000..e063ce20dc6 Binary files /dev/null and b/public/images/resources/farcaster.png differ diff --git a/public/images/resources/growthepie.png b/public/images/resources/growthepie.png new file mode 100644 index 00000000000..1a633a3b216 Binary files /dev/null and b/public/images/resources/growthepie.png differ diff --git a/public/images/resources/l2beat.png b/public/images/resources/l2beat.png new file mode 100644 index 00000000000..66d6a7ab17e Binary files /dev/null and b/public/images/resources/l2beat.png differ diff --git a/public/images/resources/mev-watch.png b/public/images/resources/mev-watch.png new file mode 100644 index 00000000000..ae7257c4cc0 Binary files /dev/null and b/public/images/resources/mev-watch.png differ diff --git a/public/images/resources/nftgo.png b/public/images/resources/nftgo.png new file mode 100644 index 00000000000..d2deb18179c Binary files /dev/null and b/public/images/resources/nftgo.png differ diff --git a/public/images/resources/nodewatch.png b/public/images/resources/nodewatch.png new file mode 100644 index 00000000000..3c7927d38a6 Binary files /dev/null and b/public/images/resources/nodewatch.png differ diff --git a/public/images/resources/rated-network.png b/public/images/resources/rated-network.png new file mode 100644 index 00000000000..606e084ce40 Binary files /dev/null and b/public/images/resources/rated-network.png differ diff --git a/public/images/resources/relayscan.png b/public/images/resources/relayscan.png new file mode 100644 index 00000000000..b8eb5155749 Binary files /dev/null and b/public/images/resources/relayscan.png differ diff --git a/public/images/resources/rwa.png b/public/images/resources/rwa.png new file mode 100644 index 00000000000..85d737daf0c Binary files /dev/null and b/public/images/resources/rwa.png differ diff --git a/public/images/resources/stablecoins-wtf.png b/public/images/resources/stablecoins-wtf.png new file mode 100644 index 00000000000..32c7b8e92db Binary files /dev/null and b/public/images/resources/stablecoins-wtf.png differ diff --git a/public/images/resources/supermajority.png b/public/images/resources/supermajority.png new file mode 100644 index 00000000000..dfaf8f9262d Binary files /dev/null and b/public/images/resources/supermajority.png differ diff --git a/public/images/resources/txcity.png b/public/images/resources/txcity.png new file mode 100644 index 00000000000..f317fcd0e58 Binary files /dev/null and b/public/images/resources/txcity.png differ diff --git a/public/images/resources/ultrasound-money.png b/public/images/resources/ultrasound-money.png new file mode 100644 index 00000000000..147154193b5 Binary files /dev/null and b/public/images/resources/ultrasound-money.png differ diff --git a/public/images/resources/visa-onchain-analytcs.png b/public/images/resources/visa-onchain-analytcs.png new file mode 100644 index 00000000000..06454e4fb26 Binary files /dev/null and b/public/images/resources/visa-onchain-analytcs.png differ diff --git a/src/components/AreaChart/index.tsx b/src/components/AreaChart/index.tsx new file mode 100644 index 00000000000..94ab25f068f --- /dev/null +++ b/src/components/AreaChart/index.tsx @@ -0,0 +1,138 @@ +"use client" + +import { FaArrowTrendUp } from "react-icons/fa6" +import { + Area, + AreaChart as RechartsAreaChart, + CartesianGrid, + XAxis, +} from "recharts" + +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart" + +type AreaChartDataPoint = { xValue: string; yValue: number } + +/** + * AreaChartProps defines the properties for the AreaChart component. + * + * @property {AreaChartDataPoint[]} data - The data to be displayed in the chart. Each object should have an `xValue` and `yValue` property. + * @property {string} [title] - The title of the chart. + * @property {string} [description] - The description of the chart. + * @property {string} [footerText] - The footer text of the chart. + * @property {string} [footerSubText] - The footer subtext of the chart. + */ +type AreaChartProps = { + data: AreaChartDataPoint[] + title?: string + description?: string + footerText?: string + footerSubText?: string +} + +const defaultChartConfig = { + value: { + label: "Value", + color: "hsl(var(--accent-a))", + }, +} satisfies ChartConfig + +/** + * AreaChart component renders an area chart with the provided data and optional title, description, footer text, and footer subtext. + * + * @param {AreaChartProps} props - The properties for the AreaChart component. + * @returns {JSX.Element} The rendered AreaChart component. + */ +export function AreaChart({ + data, + title, + description, + footerText, + footerSubText, +}: AreaChartProps) { + return ( + + + {title && {title}} + {description && {description}} + + + + + + value.slice(0, 3)} + /> + } /> + + + + + + + + + + + {(footerText || footerSubText) && ( + +
+
+ {footerText && ( +
+ {footerText} +
+ )} + {footerSubText && ( +
+ {footerSubText} +
+ )} +
+
+
+ )} +
+ ) +} diff --git a/src/components/BarChart/index.tsx b/src/components/BarChart/index.tsx new file mode 100644 index 00000000000..983b63f8fae --- /dev/null +++ b/src/components/BarChart/index.tsx @@ -0,0 +1,120 @@ +"use client" + +import { FaArrowTrendUp } from "react-icons/fa6" +import { + Bar, + BarChart as RechartsBarChart, + CartesianGrid, + XAxis, +} from "recharts" + +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart" + +type BarChartDataPoint = { category: string; value: number } + +/** + * BarChartProps defines the properties for the BarChart component. + * + * @property {BarChartItem[]} data - The data to be displayed in the chart. Each object should have a `category` and `value` property. + * @property {string} [title] - The title of the chart. + * @property {string} [description] - The description of the chart. + * @property {string} [footerText] - The footer text of the chart. + * @property {string} [footerSubText] - The footer subtext of the chart. + */ +type BarChartProps = { + data: BarChartDataPoint[] + title?: string + description?: string + footerText?: string + footerSubText?: string +} + +const defaultChartConfig = { + value: { + label: "Value", + color: "hsl(var(--accent-a))", + }, +} satisfies ChartConfig + +/** + * BarChart component renders a bar chart with the provided data and optional title, description, footer text, and footer subtext. + * + * @param {BarChartProps} props - The properties for the BarChart component. + * @returns {JSX.Element} The rendered BarChart component. + */ +export function BarChart({ + data, + title, + description, + footerText, + footerSubText, +}: BarChartProps) { + return ( + + + {title && {title}} + {description && {description}} + + + + + + + } /> + + + + + {(footerText || footerSubText) && ( + +
+
+ {footerText && ( +
+ {footerText} +
+ )} + {footerSubText && ( +
+ {footerSubText} +
+ )} +
+
+
+ )} +
+ ) +} diff --git a/src/components/RadialChart/index.tsx b/src/components/RadialChart/index.tsx new file mode 100644 index 00000000000..3732e54f4a7 --- /dev/null +++ b/src/components/RadialChart/index.tsx @@ -0,0 +1,134 @@ +"use client" + +import { type ReactNode, useEffect, useState } from "react" +import { useRouter } from "next/router" +import { MdInfoOutline } from "react-icons/md" +import { PolarAngleAxis, RadialBar, RadialBarChart } from "recharts" + +import { cn } from "@/lib/utils/cn" +import { isValidDate } from "@/lib/utils/date" + +import Tooltip from "../Tooltip" +import Link from "../ui/Link" + +import { useTranslation } from "@/hooks/useTranslation" + +/** + * RadialChartProps defines the properties for the RadialChart component. + * + * @property {number} value - The value to be displayed in the chart. + * @property {number} [totalValue] - The total value for the chart. If not provided, the chart will display the value as a percentage. + * @property {ReactNode} label - The label for the chart. + * @property {string} [sourceName] - The name of the data source. + * @property {string} [sourceUrl] - The URL of the data source. + * @property {number | string} [lastUpdated] - The last updated timestamp for the data. + * @property {string} [className] - Additional class names for the chart container. + * @property {string} [displayValue] - The custom display value to be shown. + */ +type RadialChartProps = { + value: number + totalValue?: number + label: ReactNode + sourceName?: string + sourceUrl?: string + lastUpdated?: number | string + className?: string + displayValue?: string +} + +/** + * RadialChart component renders a radial chart with the provided value and optional total value, label, source name, source URL, last updated timestamp, additional class names, and custom display value. + * + * @param {RadialChartProps} props - The properties for the RadialChart component. + * @returns {JSX.Element} The rendered RadialChart component. + */ +const RadialChart = ({ + value, + totalValue, + label, + sourceName, + sourceUrl, + lastUpdated, + className, + displayValue, +}: RadialChartProps) => { + const { t } = useTranslation("common") + const { locale } = useRouter() + const [isMounted, setIsMounted] = useState(false) + + useEffect(() => { + setIsMounted(true) + }, []) + + const lastUpdatedDisplay = + lastUpdated && isValidDate(lastUpdated) + ? new Intl.DateTimeFormat(locale, { + dateStyle: "medium", + }).format(new Date(lastUpdated)) + : "" + + const data = [{ value }] + + if (!isMounted) return null + + return ( +
+
+ + + + +
+ {displayValue || value} +
+
+
+
+ {label} + {sourceName && sourceUrl && ( + +

+ {t("data-provided-by")}{" "} + {sourceName} +

+ {lastUpdated && ( +

+ {t("last-updated")}: {lastUpdatedDisplay} +

+ )} + + } + > + +
+ )} +
+
+
+ ) +} + +export default RadialChart diff --git a/src/components/Resources/index.tsx b/src/components/Resources/index.tsx new file mode 100644 index 00000000000..ca6d46323e0 --- /dev/null +++ b/src/components/Resources/index.tsx @@ -0,0 +1,58 @@ +import Link from "@/components/ui/Link" +import { Tag } from "@/components/ui/tag" + +import { cn } from "@/lib/utils/cn" + +import { Image } from "../Image" + +import { Item } from "./types" + +export const DashboardBox = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) + +type ItemProps = React.HTMLAttributes & { + item: Item +} + +export const ResourceItem = ({ + item: { title, description, href, imgSrc }, + className, +}: ItemProps) => ( + +
+ {title} +
+
+

{title}

+

{description}

+ {href} +
+ +) + +export const ResourcesContainer = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) diff --git a/src/components/Resources/types.ts b/src/components/Resources/types.ts new file mode 100644 index 00000000000..f9f271c8758 --- /dev/null +++ b/src/components/Resources/types.ts @@ -0,0 +1,22 @@ +import { StaticImageData } from "next/image" + +export type Item = { + title: string + description: string + href: string + imgSrc: StaticImageData +} + +export type DashboardBox = { + title: string + metric?: React.ReactNode + items: Item[] + className?: string +} + +export type DashboardSection = { + key: string + title: string + boxes: DashboardBox[] + icon?: React.ReactNode +} diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx new file mode 100644 index 00000000000..a2ed295d903 --- /dev/null +++ b/src/components/Resources/useResources.tsx @@ -0,0 +1,570 @@ +import { useEffect, useState } from "react" +import { useRouter } from "next/router" + +import { Lang } from "@/lib/types" + +import SectionIconArrowsFullscreen from "@/components/icons/arrows-fullscreen.svg" +import SectionIconEthGlyph from "@/components/icons/eth-glyph.svg" +import SectionIconEthWallet from "@/components/icons/eth-wallet.svg" +import SectionIconHeartPulse from "@/components/icons/heart-pulse.svg" +import SectionIconPrivacy from "@/components/icons/privacy.svg" + +import { getLocaleForNumberFormat } from "@/lib/utils/translations" + +import BigNumber from "../BigNumber" +import RadialChart from "../RadialChart" + +import type { DashboardBox, DashboardSection } from "./types" + +import { useTranslation } from "@/hooks/useTranslation" +import IconBeaconchain from "@/public/images/resources/beaconcha-in.png" +import IconBlobsGuru from "@/public/images/resources/blobsguru.png" +import IconBlocknative from "@/public/images/resources/blocknative.png" +import IconBlockscout from "@/public/images/resources/blockscout.webp" +import IconCryptwerk from "@/public/images/resources/cryptowerk.png" +import IconDefiLlama from "@/public/images/resources/defi-llama.png" +import IconDefiMarketCap from "@/public/images/resources/defi-market-cap.png" +import IconDefiScan from "@/public/images/resources/defi-scan.png" +import IconEas from "@/public/images/resources/eas.png" +import IconEigenphi from "@/public/images/resources/eigenphi.png" +import IconEthGlyphBlack from "@/public/images/resources/eth-glyph-black.png" +import IconEthGlyphBlueCircle from "@/public/images/resources/eth-glyph-blue-circle.png" +import IconEthGlyphEOrg from "@/public/images/resources/eth-glyph-e-org.png" +import IconEthGlyphRainbowFrame from "@/public/images/resources/eth-glyph-rainbow.frame.png" +import IconEtherealize from "@/public/images/resources/etherealize.png" +import IconEtherscan from "@/public/images/resources/etherscan.png" +import IconEthproofs from "@/public/images/resources/ethproofs.png" +import IconEthstaker from "@/public/images/resources/ethstaker.png" +import IconFarcaster from "@/public/images/resources/farcaster.png" +import IconGrowthepie from "@/public/images/resources/growthepie.png" +import IconL2beat from "@/public/images/resources/l2beat.png" +import IconNftgo from "@/public/images/resources/nftgo.png" +import IconNodewatch from "@/public/images/resources/nodewatch.png" +import IconRatedNetwork from "@/public/images/resources/rated-network.png" +import IconRelayscan from "@/public/images/resources/relayscan.png" +import IconRwa from "@/public/images/resources/rwa.png" +import IconStablecoinsWtf from "@/public/images/resources/stablecoins-wtf.png" +import IconSupermajority from "@/public/images/resources/supermajority.png" +import IconTxCity from "@/public/images/resources/txcity.png" +import IconUltrasoundMoney from "@/public/images/resources/ultrasound-money.png" +import IconVisaOnchainAnalytics from "@/public/images/resources/visa-onchain-analytcs.png" + +const formatSmallUSD = (value: number, locale: string): string => + new Intl.NumberFormat(locale, { + style: "currency", + currency: "USD", + notation: "compact", + minimumSignificantDigits: 2, + maximumSignificantDigits: 2, + }).format(value) + +export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { + const { t } = useTranslation("page-resources") + const { locale } = useRouter() + const localeForNumberFormat = getLocaleForNumberFormat(locale! as Lang) + + const medianTxCost = + "error" in txCostsMedianUsd + ? { error: txCostsMedianUsd.error } + : { + ...txCostsMedianUsd, + value: formatSmallUSD(txCostsMedianUsd.value, localeForNumberFormat), + } + + const [timeToNextBlock, setTimeToNextBlock] = useState(12) + + useEffect(() => { + const genesisTime = new Date("2020-12-01T12:00:23Z").getTime() + const updateTime = () => { + const now = Date.now() + const timeElapsed = (now - genesisTime) / 1000 + const timeToNext = 12 - (timeElapsed % 12) + setTimeToNextBlock(Math.floor(timeToNext) || 12) + } + + updateTime() + const interval = setInterval(updateTime, 1000) + return () => clearInterval(interval) + }, []) + + const networkBoxes: DashboardBox[] = [ + { + title: t("page-resources-network-layer2-title"), + metric: ( + + Median transaction cost on Ethereum networks + + ), + items: [ + { + title: "L2 Beat", + description: t("page-resources-network-layer2-l2beat-description"), + href: "https://l2beat.com/", + imgSrc: IconL2beat, + }, + { + title: "Growthepie", + description: t( + "page-resources-network-layer2-growthepie-description" + ), + href: "https://www.growthepie.xyz/", + imgSrc: IconGrowthepie, + }, + { + title: "L2 Fees", + description: t("page-resources-network-layer2-l2fees-description"), + href: "https://l2fees.info/", + imgSrc: IconEthGlyphBlueCircle, + }, + ], + }, + { + title: t("page-resources-block-explorers-title"), + metric: ( + + ), + items: [ + { + title: "Blockscout", + description: t( + "page-resources-block-explorers-blockscout-description" + ), + href: "https://eth.blockscout.com", + imgSrc: IconBlockscout, + }, + { + title: "Etherscan", + description: t( + "page-resources-block-explorers-etherscan-description" + ), + href: "https://etherscan.io", + imgSrc: IconEtherscan, + }, + { + title: "Beaconcha.in", + description: t( + "page-resources-block-explorers-beaconchain-description" + ), + href: "https://beaconcha.in", + imgSrc: IconBeaconchain, + }, + { + title: "Txcity.io", + description: t("page-resources-block-explorers-txcity-description"), + href: "https://txcity.io/", + imgSrc: IconTxCity, + }, + ], + }, + { + title: t("page-resources-eth-asset-title"), + // TODO: Add RadialChart metric + items: [ + { + title: "Etherealize Dashboard", + description: t("page-resources-eth-asset-etherealize-description"), + href: "https://dashboard.etherealize.io/", + imgSrc: IconEtherealize, + }, + { + title: "Ultra Sound Money", + description: t("page-resources-eth-asset-ultrasound-description"), + href: "https://ultrasound.money/", + imgSrc: IconUltrasoundMoney, + }, + { + title: "ETH is Money", + description: t("page-resources-eth-asset-ethismoney-description"), + href: "https://www.ethismoney.xyz/", + imgSrc: IconEthGlyphBlueCircle, + }, + { + title: "Ethereum Now", + description: t("page-resources-eth-asset-ethernow-description"), + href: "https://www.ethernow.xyz", + imgSrc: IconBlocknative, + }, + ], + }, + { + title: t("page-resources-gas-title"), + // TODO: Add metric + items: [ + { + title: "Ethereum Gas Tracker", + description: t("page-resources-gas-etherscan-description"), + href: "https://etherscan.io/gastracker", + imgSrc: IconEthGlyphBlueCircle, + }, + { + title: "Blocknative Gas Estimator", + description: t("page-resources-gas-blocknative-description"), + href: "https://www.blocknative.com/gas-estimator", + imgSrc: IconBlocknative, + }, + { + title: "GasFees.io", + description: t("page-resources-gas-gasfees-description"), + href: "https://www.gasfees.io/", + imgSrc: IconEthGlyphBlueCircle, + }, + ], + }, + ] + + const usingBoxes: DashboardBox[] = [ + { + title: t("page-resources-defi-title"), + // TODO: Add big number metric + items: [ + { + title: "DeFi Llama", + description: t("page-resources-defi-defillama-description"), + href: "https://defillama.com", + imgSrc: IconDefiLlama, + }, + { + title: "DeFi Market Cap", + description: t("page-resources-defi-defimarketcap-description"), + href: "https://defimarketcap.io", + imgSrc: IconDefiMarketCap, + }, + { + title: "EigenPhi", + description: t("page-resources-defi-eigenphi-description"), + href: "https://www.eigenphi.io", + imgSrc: IconEigenphi, + }, + { + title: "DeFiScan", + description: t("page-resources-defi-defiscan-description"), + href: "https://defiscan.info", + imgSrc: IconDefiScan, + }, + ], + }, + { + title: t("page-resources-stablecoins-title"), + // TODO: Add big number metric + items: [ + { + title: "stablecoins.wtf", + description: t( + "page-resources-stablecoins-stablecoinswtf-description" + ), + href: "https://stablecoins.wtf/", + imgSrc: IconStablecoinsWtf, + }, + { + title: "Visa Onchain Analytics Dashboard", + description: t("page-resources-stablecoins-visa-description"), + href: "https://visaonchainanalytics.com", + imgSrc: IconVisaOnchainAnalytics, + }, + { + title: "Real World Assets", + description: t("page-resources-stablecoins-rwa-description"), + href: "https://app.rwa.xyz/stablecoins", + imgSrc: IconRwa, + }, + ], + }, + { + title: t("page-resources-nft-title"), + // TODO: Add bar chart metric + items: [ + { + title: "Etherscan - Top NFT", + description: t("page-resources-nft-etherscan-description"), + href: "https://etherscan.io/nft-top-contracts", + imgSrc: IconEtherscan, + }, + { + title: "NFTgo", + description: t("page-resources-nft-nftgo-description"), + href: "https://nftgo.io/macro/market-overview", + imgSrc: IconNftgo, + }, + ], + }, + { + title: t("page-resources-applications-title"), + items: [ + { + title: "Ethereum Ecosystem", + description: t("page-resources-applications-ecosystem-description"), + href: "https://www.ethereum-ecosystem.com/apps", + imgSrc: IconEthGlyphEOrg, + }, + { + title: "Farcaster Network", + description: t("page-resources-applications-farcaster-description"), + href: "https://www.farcaster.network", + imgSrc: IconFarcaster, + }, + { + title: "Dapp Radar", + description: t("page-resources-applications-dappradar-description"), + href: "https://dappradar.com", + imgSrc: IconEthGlyphBlueCircle, + }, + ], + }, + { + title: t("page-resources-adoption-title"), + items: [ + { + title: "Ethereum Adoption", + description: t( + "page-resources-adoption-ethereumadoption-description" + ), + href: "https://ethereumadoption.com", + imgSrc: IconEthGlyphEOrg, + }, + { + title: "Cryptowerk", + description: t("page-resources-adoption-cryptowerk-description"), + href: "https://cryptwerk.com/analytics/ethereum/", + imgSrc: IconCryptwerk, + }, + ], + }, + ] + + const scalingBoxes: DashboardBox[] = [ + { + title: t("page-resources-roadmap-title"), + // TODO: Add metric + items: [ + { + title: "Ethereum Roadmap", + description: t("page-resources-roadmap-ethroadmap-description"), + href: "https://ethroadmap.com", + imgSrc: IconEthGlyphRainbowFrame, + }, + ], + }, + { + title: t("page-resources-blobs-title"), + // TODO: Add metric + items: [ + { + title: "Blob Scan", + description: t("page-resources-blobs-blobscan-description"), + href: "https://blobscan.com", + imgSrc: IconEthGlyphBlueCircle, + }, + { + title: "Blobsguru", + description: t("page-resources-blobs-blobsguru-description"), + href: "https://blobs.guru", + imgSrc: IconBlobsGuru, + }, + ], + }, + ] + + const resilienceBoxes: DashboardBox[] = [ + { + title: t("page-resources-nodes-title"), + // TODO: Add big number metric + items: [ + { + title: "Node Watch", + description: t("page-resources-nodes-nodewatch-description"), + href: "https://nodewatch.io", + imgSrc: IconNodewatch, + }, + { + title: "Ethernodes", + description: t("page-resources-nodes-ethernodes-description"), + href: "https://ethernodes.org", + imgSrc: IconEthGlyphBlueCircle, + }, + { + title: "Etherscan - Ethereum Node Tracker", + description: t("page-resources-nodes-etherscan-description"), + href: "https://etherscan.io/nodetracker", + imgSrc: IconEtherscan, + }, + { + title: "luckystaker.com", + description: t("page-resources-nodes-luckystaker-description"), + href: "https://luckystaker.com", + imgSrc: IconEthstaker, + }, + { + title: "Ethereum Validator Queue", + description: t("page-resources-nodes-validatorqueue-description"), + href: "https://www.validatorqueue.com", + imgSrc: IconEthGlyphBlueCircle, + }, + ], + }, + { + title: t("page-resources-network-resilience-title"), + items: [ + { + title: "Neutrality Watch", + description: t( + "page-resources-network-resilience-neutralitywatch-description" + ), + href: "https://eth.neutralitywatch.com", + imgSrc: IconEthGlyphBlueCircle, + }, + { + title: "Project Sunshine", + description: t( + "page-resources-network-resilience-sunshine-description" + ), + href: "https://ethsunshine.com", + imgSrc: IconEthGlyphEOrg, + }, + { + title: "Client Diversity", + description: t( + "page-resources-network-resilience-clientdiversity-description" + ), + href: "https://clientdiversity.org", + imgSrc: IconEthGlyphEOrg, + }, + { + title: "Super Majority", + description: t( + "page-resources-network-resilience-supermajority-description" + ), + href: "https://supermajority.info", + imgSrc: IconSupermajority, + }, + ], + }, + { + title: t("page-resources-attestations-title"), + items: [ + { + title: "Ethereum Attestation Service", + description: t("page-resources-attestations-eas-description"), + href: "https://easscan.org", + imgSrc: IconEas, + }, + ], + }, + ] + + const privacySecurityBoxes: DashboardBox[] = [ + { + title: t("page-resources-relays-title"), + // TODO: Add big number metric + items: [ + { + title: "Beaconchain Relays", + description: t("page-resources-relays-beaconchain-description"), + href: "https://beaconcha.in/relays", + imgSrc: IconBeaconchain, + }, + { + title: "Relay Landscape | Ethereum Mainnet", + description: t("page-resources-relays-ratednetwork-description"), + href: "https://explorer.rated.network/relays?network=mainnet", + imgSrc: IconRatedNetwork, + }, + { + title: "Relay Scan", + description: t("page-resources-relays-relayscan-description"), + href: "https://www.relayscan.io", + imgSrc: IconRelayscan, + }, + ], + }, + { + title: t("page-resources-mev-title"), + // TODO: Add big number metric + items: [ + { + title: "MEV-Boost Dashboard", + description: t("page-resources-mev-mevboost-description"), + href: "https://mevboost.pics", + imgSrc: IconEthGlyphBlack, + }, + { + title: "MEV Watch", + description: t("page-resources-mev-mevwatch-description"), + href: "https://www.mevwatch.info", + imgSrc: IconEthGlyphBlueCircle, + }, + ], + }, + { + title: t("page-resources-zk-adoption-title"), + items: [ + { + title: "Ethproofs", + description: t("page-resources-zk-adoption-ethproofs-description"), + href: "https://ethproofs.org", + imgSrc: IconEthproofs, + }, + { + title: "L2beat - ZK Catalog", + description: t("page-resources-zk-adoption-l2beat-description"), + href: "https://l2beat.com/zk-catalog", + imgSrc: IconL2beat, + }, + ], + }, + { + title: t("page-resources-mempool-title"), + items: [ + { + title: "Ethereum Mempool Dashboard", + description: t("page-resources-mempool-mempool-description"), + href: "https://mempool.pics", + imgSrc: IconEthGlyphBlueCircle, + }, + ], + }, + ] + + const resources = [ + { + key: "network", + title: t("page-resources-network-title"), + icon: , + boxes: networkBoxes, + }, + { + key: "using", + title: t("page-resources-using-title"), + icon: , + boxes: usingBoxes, + }, + { + key: "scaling", + title: t("page-resources-scaling-title"), + icon: , + boxes: scalingBoxes, + }, + { + key: "resilience", + title: t("page-resources-resilience-title"), + icon: , + boxes: resilienceBoxes, + }, + { + key: "privacy-security", + title: t("page-resources-privacy-security-title"), + icon: , + boxes: privacySecurityBoxes, + }, + ] + + return resources +} diff --git a/src/components/icons/arrows-fullscreen.svg b/src/components/icons/arrows-fullscreen.svg new file mode 100644 index 00000000000..e42cbd799c9 --- /dev/null +++ b/src/components/icons/arrows-fullscreen.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/icons/heart-pulse.svg b/src/components/icons/heart-pulse.svg new file mode 100644 index 00000000000..8ebc032ac10 --- /dev/null +++ b/src/components/icons/heart-pulse.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/icons/privacy.svg b/src/components/icons/privacy.svg new file mode 100644 index 00000000000..a5e3a73f64d --- /dev/null +++ b/src/components/icons/privacy.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/icons/stack.svg b/src/components/icons/stack.svg new file mode 100644 index 00000000000..055fa9c6c01 --- /dev/null +++ b/src/components/icons/stack.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/ui/Link.tsx b/src/components/ui/Link.tsx index 531c5cd0577..a892a31c04c 100644 --- a/src/components/ui/Link.tsx +++ b/src/components/ui/Link.tsx @@ -104,7 +104,7 @@ export const BaseLink = forwardRef(function Link( {!hideArrow && ( diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx index 70bdd2b2be5..c81d7d2c305 100644 --- a/src/components/ui/accordion.tsx +++ b/src/components/ui/accordion.tsx @@ -30,10 +30,12 @@ const AccordionTrigger = React.forwardRef< )} {...props} > - {children} - {!hideIcon && ( - - )} + <> + {children} + {!hideIcon && ( + + )} + )) diff --git a/src/intl/en/page-index.json b/src/intl/en/page-index.json index c5b02c273d3..b1207c423f4 100644 --- a/src/intl/en/page-index.json +++ b/src/intl/en/page-index.json @@ -2,6 +2,7 @@ "page-index-activity-description": "Activity from all Ethereum networks", "page-index-activity-tag": "Activity", "page-index-activity-header": "The strongest ecosystem", + "page-index-activity-action": "More Ethereum activity", "page-index-bento-header": "A new way to use the internet", "page-index-bento-assets-action": "More on NFTs", "page-index-bento-assets-content": "Art, certificates or even real estate can be tokenized. Anything can be a tradable token. Ownership is public and verifiable.", diff --git a/src/intl/en/page-resources.json b/src/intl/en/page-resources.json new file mode 100644 index 00000000000..73a60e98b3d --- /dev/null +++ b/src/intl/en/page-resources.json @@ -0,0 +1,87 @@ +{ + "page-resources-network-title": "The network", + "page-resources-using-title": "Using Ethereum", + "page-resources-scaling-title": "Scaling Ethereum", + "page-resources-resilience-title": "Ethereum resilience", + "page-resources-privacy-security-title": "Privacy & security", + "page-resources-network-layer2-title": "Ethereum Networks - Layer 2", + "page-resources-network-layer2-l2beat-description": "L2BEAT was created to provide transparent and verifiable insights into emerging layer two (L2) technologies which, in line with the rollup-centric Ethereum scaling roadmap, are aimed at scaling Ethereum.", + "page-resources-network-layer2-growthepie-description": "Mastering Ethereum Layer 2s. Your gateway to curated analytics and knowledge.", + "page-resources-network-layer2-l2fees-description": "How much does it cost to use Layer 2?", + "page-resources-block-explorers-title": "Block explorers", + "page-resources-block-explorers-blockscout-description": "Open-source block explorer by Blockscout with complete blockchain data and APIs for Ethereum networks.", + "page-resources-block-explorers-etherscan-description": "Etherscan is a block explorer and analytics platform for Ethereum, a decentralized smart contracts platform.", + "page-resources-block-explorers-beaconchain-description": "Open source Ethereum explorer showing the Ethereum Mainnet πŸš€.", + "page-resources-block-explorers-txcity-description": "A funny visualizer of the Ethereum blocks in real-time.", + "page-resources-eth-asset-title": "ETH the asset", + "page-resources-eth-asset-etherealize-description": "Ethereum is the largest, most secure, and most open blockchain for the world to use. And Ethereum is open for business.", + "page-resources-eth-asset-ultrasound-description": "Ultra sound money is an Ethereum meme focusing on the likely decrease of the ETH supply.", + "page-resources-eth-asset-ethismoney-description": "ETH is money is a tribe of believers who hold, stake, and propagate ETH as money.", + "page-resources-eth-asset-ethernow-description": "Ethernow enables you to see what is happening at the core of Ethereum, in real-time. Go hands-on now.", + "page-resources-gas-title": "Gas", + "page-resources-gas-etherscan-description": "Track all the KPIs on gas.", + "page-resources-gas-blocknative-description": "Web3's most accurate gas fee prediction.", + "page-resources-gas-gasfees-description": "Gas costs data tracker for Ethereum networks.", + "page-resources-defi-title": "DeFi", + "page-resources-defi-defillama-description": "DefiLlama is the largest TVL aggregator for DeFi (Decentralized Finance).", + "page-resources-defi-defimarketcap-description": "Top 100 DeFi tokens by market capitalization.", + "page-resources-defi-eigenphi-description": "Wanna understand DeFi transactions and trading strategies?", + "page-resources-defi-defiscan-description": "Verifiable insights into the maturity and risks of DeFi.", + "page-resources-stablecoins-title": "Stablecoins", + "page-resources-stablecoins-stablecoinswtf-description": "The purpose of this website is to educate degens about stablecoins.", + "page-resources-stablecoins-visa-description": "The Visa Onchain Analytics Dashboard showcases how fiat-backed stablecoins move via public blockchains globally.", + "page-resources-stablecoins-rwa-description": "Explore the activity behind crypto and asset-backed stablecoins.", + "page-resources-nft-title": "NFT", + "page-resources-nft-etherscan-description": "Top NFT contracts.", + "page-resources-nft-nftgo-description": "Real-time global NFT market data.", + "page-resources-applications-title": "Applications", + "page-resources-applications-ecosystem-description": "Immerse yourself in the Ethereum ecosystem and get familiar with hundreds of popular apps & tools.", + "page-resources-applications-farcaster-description": "Data from Farcaster usage.", + "page-resources-applications-dappradar-description": "Explore top blockchain dapps, NFTs, games, DeFi projects, tokens, and airdrops. Track rankings, explore market insights, find trending projects, and unlock rewards with the world’s dapp store.", + "page-resources-adoption-title": "Ethereum Adoption", + "page-resources-adoption-ethereumadoption-description": "Ethereum Censorability Monitor.", + "page-resources-adoption-cryptowerk-description": "Ethereum adoption analytics based on Cryptwerk merchants database - map, countries, companies, businesses, categories, rating.", + "page-resources-roadmap-title": "Ethereum Roadmap", + "page-resources-roadmap-ethroadmap-description": "Detailed visualization on Ethereum roadmap and the next network upgrade.", + "page-resources-blobs-title": "Blobs", + "page-resources-blobs-blobscan-description": "Comprehensive blob scanner.", + "page-resources-blobs-blobsguru-description": "Ethereum Blobs Explorer: Analyze L2 transactions & EIP-4844 data.", + "page-resources-nodes-title": "Nodes", + "page-resources-nodes-nodewatch-description": "Overview of the nodes.", + "page-resources-nodes-ethernodes-description": "Ethereum Mainnet statistics.", + "page-resources-nodes-etherscan-description": "Daily.", + "page-resources-nodes-luckystaker-description": "Daily proposal probability of getting a block.", + "page-resources-nodes-validatorqueue-description": "A dashboard showing the Ethereum validator enter and exit queue and estimated wait times.", + "page-resources-network-resilience-title": "Network resilience", + "page-resources-network-resilience-neutralitywatch-description": "Ethereum Censorability Monitor.", + "page-resources-network-resilience-sunshine-description": "A dashboard to measure the health of Ethereum's decentralization.", + "page-resources-network-resilience-clientdiversity-description": "Improve Ethereum's resilience by using a minority client.", + "page-resources-network-resilience-supermajority-description": "The supermajority client risk of the Ethereum execution layer, especially the client usage of staking services.", + "page-resources-attestations-title": "Attestations", + "page-resources-attestations-eas-description": "EAS enables anyone to create and validate on-chain and off-chain attestations on Ethereum.", + "page-resources-relays-title": "Relays", + "page-resources-relays-beaconchain-description": "Validators can use relays to outsource their block production to entities specialized in extracting extra revenue.", + "page-resources-relays-ratednetwork-description": "MEV relay market share, total value relayed, value per block, and other statistics for Ethereum network.", + "page-resources-relays-relayscan-description": "MEV-Boost analytics.", + "page-resources-mev-title": "MEV", + "page-resources-mev-mevboost-description": "The purpose of this website is to educate degens about stablecoins.", + "page-resources-mev-mevwatch-description": "Some MEV-Boost relays are regulated under OFAC and will censor certain transactions. Use this tool to observe the effect it's having on Ethereum blocks.", + "page-resources-zk-adoption-title": "ZK adoption", + "page-resources-zk-adoption-ethproofs-description": "SNARKs that scale Ethereum.", + "page-resources-zk-adoption-l2beat-description": "ZK Catalog by L2BEAT is a community-driven resource offering detailed insights into the ZK technology utilized by various blockchain projects.", + "page-resources-mempool-title": "Mempool", + "page-resources-mempool-mempool-description": "Selected comparative visualizations on Ethereum's mempool.", + "page-resources-meta-title": "Dashboard of Ethereum Resources", + "page-resources-meta-description": "Discover a list of community-curated resources to stay updated on all major Ethereum ecosystem developments.", + "page-resources-hero-title": "Resources", + "page-resources-hero-header": "Ethereum Dashboards", + "page-resources-hero-description": "Discover a list of community-curated resources to stay updated on all major Ethereum ecosystem developments.", + "page-resources-find-more": "Find more great resources on ethereumdashboards.com", + "page-resources-contribute-title": "Contribute", + "page-resources-contribute-description": "This dashboard is a living page that requires frequent updates. Help find the best resources to give an overview of the Ethereum ecosystem.", + "page-resources-suggest-resource": "Suggest a resource", + "page-resources-found-bug": "Found a bug", + "page-resources-whats-on-this-page": "What's on this page", + "page-resources-banner-notification-message": "Resources dashboard is new!", + "page-resources-share-feedback": "Please share your feedback with us" +} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 4149b2465c9..1eaaafc2f43 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -28,7 +28,7 @@ export const LOCALES_CODES = BUILD_LOCALES export const SITE_URL = "https://ethereum.org" export const DISCORD_PATH = "/discord/" export const GITHUB_REPO_URL = - "https://github.com/ethereum/ethereum-org-website" + "https://github.com/ethereum/ethereum-org-website/" export const EDIT_CONTENT_URL = `https://github.com/ethereum/ethereum-org-website/tree/dev/` export const MAIN_CONTENT_ID = "main-content" export const WEBSITE_EMAIL = "website@ethereum.org" diff --git a/src/lib/utils/translations.ts b/src/lib/utils/translations.ts index 14e1542a005..112693767f5 100644 --- a/src/lib/utils/translations.ts +++ b/src/lib/utils/translations.ts @@ -97,6 +97,10 @@ const getRequiredNamespacesForPath = (relativePath: string) => { primaryNamespace = "page-history" } + if (path.startsWith("/resources/")) { + primaryNamespace = "page-resources" + } + if (path.startsWith("/stablecoins/")) { primaryNamespace = "page-stablecoins" } diff --git a/src/pages/[locale]/index.tsx b/src/pages/[locale]/index.tsx index 0b7b9523b1e..0039b723ce2 100644 --- a/src/pages/[locale]/index.tsx +++ b/src/pages/[locale]/index.tsx @@ -364,6 +364,18 @@ const HomePage = ({ }> + {/* className="mt-12 mx-auto" */} + +
+ + {t("page-index:page-index-activity-action")} + +
diff --git a/src/pages/[locale]/resources.tsx b/src/pages/[locale]/resources.tsx new file mode 100644 index 00000000000..aaef0c2754e --- /dev/null +++ b/src/pages/[locale]/resources.tsx @@ -0,0 +1,227 @@ +import { motion } from "framer-motion" +import { GetStaticProps } from "next" +import { FaGithub } from "react-icons/fa6" + +import type { BasePageProps, Lang, Params } 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" +import { ButtonLink } from "@/components/ui/buttons/Button" +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 { 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, + } +} + +export const getStaticProps = (async ({ params }) => { + const { locale = DEFAULT_LOCALE } = params || ({} as { locale: string }) + + const [growThePieData] = await loadData() + const { txCostsMedianUsd } = growThePieData + + const requiredNamespaces = getRequiredNamespacesForPage("/resources") + + const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2]) + + const lastDeployDate = getLastDeployDate() + const lastDeployLocaleTimestamp = getLocaleTimestamp( + locale as Lang, + lastDeployDate + ) + + const messages = await loadNamespaces(locale as string, requiredNamespaces) + + return { + props: { + messages, + contentNotTranslated, + lastDeployLocaleTimestamp, + txCostsMedianUsd, + }, + } +}) satisfies GetStaticProps + +const ResourcesPage = ({ txCostsMedianUsd }) => { + const { t } = useTranslation("page-resources") + const resourceSections = useResources({ txCostsMedianUsd }) + const activeSection = useActiveHash( + resourceSections.map(({ key }) => key), + "0% 0% -70% 0%" + ).replace(/^#/, "") + + return ( + + + + + {t("page-resources-banner-notification-message")}{" "} + + {t("page-resources-share-feedback")} + + + + + +
+
+ {t("page-resources-whats-on-this-page")} +
+ +
+ + {resourceSections.map(({ key, icon, title: sectionTitle, boxes }) => ( +
+
+
+ {icon || } +
+

{sectionTitle}

+
+
+ {boxes.map(({ title, metric, items, className }) => ( +
+
+ {title} +
+
+ {metric && metric} + + {items.map((item) => ( + + ))} + +
+
+ ))} +
+
+ ))} + +
+ +
+ +
+
+
+

{t("page-resources-contribute-title")}

+

{t("page-resources-contribute-description")}

+
+
+ {/* TODO: Add issue template for resource listing and redirect to new template */} + + {t("page-resources-suggest-resource")} + + + {t("page-resources-found-bug")} + +
+
+
+
+ ) +} + +export default ResourcesPage diff --git a/src/styles/global.css b/src/styles/global.css index 2898047b069..148dddd783a 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -81,7 +81,7 @@ @layer base { * { - @apply border-border; + @apply border-border scroll-smooth; } body { diff --git a/src/styles/semantic-tokens.css b/src/styles/semantic-tokens.css index 08716c5735b..0d4149f169d 100644 --- a/src/styles/semantic-tokens.css +++ b/src/styles/semantic-tokens.css @@ -25,7 +25,7 @@ --border: var(--gray-200); --border-high-contrast: var(--gray-400); - --border-low-contrast: var(--gray-50); + --border-low-contrast: var(--gray-100); --border-hover: var(--gray-300); --primary: var(--purple-600); @@ -150,7 +150,7 @@ --border: var(--gray-600); --border-high-contrast: var(--gray-300); - --border-low-contrast: var(--black); + --border-low-contrast: var(--gray-900); --border-hover: var(--gray-500); --primary: var(--purple-400);