diff --git a/app/[locale]/bug-bounty/_components/bug-bounty.tsx b/app/[locale]/bug-bounty/_components/bug-bounty.tsx index 6a6813a92b5..c7711b64673 100644 --- a/app/[locale]/bug-bounty/_components/bug-bounty.tsx +++ b/app/[locale]/bug-bounty/_components/bug-bounty.tsx @@ -2,7 +2,7 @@ import { HTMLAttributes } from "react" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" /* Uncomment for Bug Bounty Banner: */ import BugBountyBanner from "@/components/Banners/BugBountyBanner" @@ -13,6 +13,7 @@ import CardList from "@/components/CardList" import Emoji from "@/components/Emoji" import ExpandableCard from "@/components/ExpandableCard" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import { Image, type ImageProps } from "@/components/Image" import Leaderboard from "@/components/Leaderboard" import MainArticle from "@/components/MainArticle" @@ -224,7 +225,10 @@ const sortBountyHuntersFn = (a: BountyHuntersArg, b: BountyHuntersArg) => { return b.score - a.score } -const BugBountiesPage = () => { +const BugBountiesPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const pathname = usePathname() const { t } = useTranslation("page-bug-bounty") @@ -768,6 +772,11 @@ const BugBountiesPage = () => { + diff --git a/app/[locale]/bug-bounty/page.tsx b/app/[locale]/bug-bounty/page.tsx index 5c7bc2a5cdc..4d27d1ac6b1 100644 --- a/app/[locale]/bug-bounty/page.tsx +++ b/app/[locale]/bug-bounty/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { type Params } from "@/lib/types" +import type { CommitHistory, Lang, Params } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,20 @@ export default async function Page({ params }: { params: Promise }) { const requiredNamespaces = getRequiredNamespacesForPage("/bug-bounty") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "bug-bounty", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/dapps/_components/dapps.tsx b/app/[locale]/dapps/_components/dapps.tsx index a514bd5aa66..e9c41203194 100644 --- a/app/[locale]/dapps/_components/dapps.tsx +++ b/app/[locale]/dapps/_components/dapps.tsx @@ -11,7 +11,7 @@ import React, { import { useSearchParams } from "next/navigation" import { useLocale } from "next-intl" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" import BoxGrid from "@/components/BoxGrid" import Callout from "@/components/Callout" @@ -20,6 +20,7 @@ import Card from "@/components/Card" import DocLink from "@/components/DocLink" import Emoji from "@/components/Emoji" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import GhostCard from "@/components/GhostCard" import { Image } from "@/components/Image" import InfoBanner from "@/components/InfoBanner" @@ -270,7 +271,10 @@ interface Categories { [key: string]: Category } -const DappsPage = () => { +const DappsPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation(["page-dapps", "common"]) const searchParams = useSearchParams() const locale = useLocale() @@ -1530,6 +1534,11 @@ const DappsPage = () => { + diff --git a/app/[locale]/dapps/page.tsx b/app/[locale]/dapps/page.tsx index 422fb87b0b9..db19d93875a 100644 --- a/app/[locale]/dapps/page.tsx +++ b/app/[locale]/dapps/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Params } from "@/lib/types" +import type { CommitHistory, Lang, Params } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -22,9 +23,16 @@ export default async function Page({ params }: { params: Promise }) { const requiredNamespaces = getRequiredNamespacesForPage("/dapps") const pickedMessages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo("dapps", locale as Lang, commitHistoryCache) + return ( - + ) } diff --git a/app/[locale]/eth/_components/eth.tsx b/app/[locale]/eth/_components/eth.tsx index 8e1990fdeb2..6130958f5c2 100644 --- a/app/[locale]/eth/_components/eth.tsx +++ b/app/[locale]/eth/_components/eth.tsx @@ -2,7 +2,7 @@ import type { ComponentProps, HTMLAttributes } from "react" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" import ActionCard from "@/components/ActionCard" import CalloutBanner from "@/components/CalloutBanner" @@ -11,6 +11,7 @@ import CardList from "@/components/CardList" import EthPriceCard from "@/components/EthPriceCard" import EthVideo from "@/components/EthVideo" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import HorizontalCard from "@/components/HorizontalCard" import { Image } from "@/components/Image" import InfoBanner from "@/components/InfoBanner" @@ -168,7 +169,10 @@ const CentralActionCard = (props: ComponentProps) => ( ) -const EthPage = () => { +const EthPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation("page-eth") const pathname = usePathname() @@ -448,6 +452,11 @@ const EthPage = () => { + diff --git a/app/[locale]/eth/page.tsx b/app/[locale]/eth/page.tsx index ede37c897b5..bc1dbe58b60 100644 --- a/app/[locale]/eth/page.tsx +++ b/app/[locale]/eth/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -28,9 +29,16 @@ export default async function Page({ const requiredNamespaces = getRequiredNamespacesForPage("/eth") const pickedMessages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo("eth", locale as Lang, commitHistoryCache) + return ( - + ) } diff --git a/app/[locale]/gas/_components/gas.tsx b/app/[locale]/gas/_components/gas.tsx index 79fdcdd3022..7730c5ae519 100644 --- a/app/[locale]/gas/_components/gas.tsx +++ b/app/[locale]/gas/_components/gas.tsx @@ -2,11 +2,14 @@ import { BaseHTMLAttributes, ComponentPropsWithRef } from "react" +import { PageWithContributorsProps } from "@/lib/types" + import Callout from "@/components/Callout" import Card from "@/components/Card" import Emoji from "@/components/Emoji" import ExpandableCard from "@/components/ExpandableCard" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import GhostCard from "@/components/GhostCard" import HorizontalCard from "@/components/HorizontalCard" import { Image } from "@/components/Image" @@ -78,7 +81,10 @@ const H3 = ({

) -const GasPage = () => { +const GasPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation("page-gas") const benefits = [ @@ -389,6 +395,11 @@ const GasPage = () => { + diff --git a/app/[locale]/gas/page.tsx b/app/[locale]/gas/page.tsx index da0d304490b..cb4c21c8ef2 100644 --- a/app/[locale]/gas/page.tsx +++ b/app/[locale]/gas/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,16 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/gas") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo("gas", locale as Lang, commitHistoryCache) + return ( - + ) } diff --git a/app/[locale]/get-eth/_components/get-eth.tsx b/app/[locale]/get-eth/_components/get-eth.tsx index 3bc1bc54216..a2a1bfa5472 100644 --- a/app/[locale]/get-eth/_components/get-eth.tsx +++ b/app/[locale]/get-eth/_components/get-eth.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from "react" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" import CalloutBanner from "@/components/CalloutBanner" import CardList, { @@ -12,6 +12,7 @@ import CentralizedExchanges from "@/components/CentralizedExchanges" import Emoji from "@/components/Emoji" import EthPriceCard from "@/components/EthPriceCard" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" import Translation from "@/components/Translation" @@ -63,11 +64,15 @@ const TwoColumnContent = (props: ChildOnlyProp) => (
) -type Props = { +type Props = PageWithContributorsProps & { lastDataUpdateDate: string } -const GetEthPage = ({ lastDataUpdateDate }: Props) => { +const GetEthPage = ({ + lastDataUpdateDate, + contributors, + lastEditLocaleTimestamp, +}: Props) => { const { t } = useTranslation("page-get-eth") const walletImageWidth = useBreakpointValue({ @@ -352,6 +357,12 @@ const GetEthPage = ({ lastDataUpdateDate }: Props) => {
+ + diff --git a/app/[locale]/get-eth/page.tsx b/app/[locale]/get-eth/page.tsx index f41dc4116ea..ffbc9a52eeb 100644 --- a/app/[locale]/get-eth/page.tsx +++ b/app/[locale]/get-eth/page.tsx @@ -5,11 +5,12 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" -import { getLastModifiedDateByPath } from "@/lib/utils/gh" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" +import { getLastGitCommitDateByPath } from "@/lib/utils/gh" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -26,7 +27,7 @@ export default async function Page({ setRequestLocale(locale) - const lastDataUpdateDate = getLastModifiedDateByPath( + const lastDataUpdateDate = getLastGitCommitDateByPath( "src/data/exchangesByCountry.ts" ) @@ -35,9 +36,21 @@ export default async function Page({ const requiredNamespaces = getRequiredNamespacesForPage("/get-eth") const pickedMessages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "get-eth", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/layer-2/learn/_components/learn.tsx b/app/[locale]/layer-2/learn/_components/learn.tsx index 4596b6ab2b2..fca0ae52ce7 100644 --- a/app/[locale]/layer-2/learn/_components/learn.tsx +++ b/app/[locale]/layer-2/learn/_components/learn.tsx @@ -1,7 +1,10 @@ "use client" +import { PageWithContributorsProps } from "@/lib/types" + import Callout from "@/components/Callout" import Card from "@/components/Card" +import FileContributors from "@/components/FileContributors" import { ContentHero, type ContentHeroProps } from "@/components/Hero" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" @@ -19,7 +22,10 @@ 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" -const Layer2Learn = () => { +const Layer2Learn = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation("page-layer-2-learn") const pathname = usePathname() @@ -274,6 +280,11 @@ const Layer2Learn = () => {

+
diff --git a/app/[locale]/layer-2/learn/page.tsx b/app/[locale]/layer-2/learn/page.tsx index f34506409db..38966be5b6a 100644 --- a/app/[locale]/layer-2/learn/page.tsx +++ b/app/[locale]/layer-2/learn/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,20 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/layer-2/learn") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "layer-2/learn", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/learn/_components/learn.tsx b/app/[locale]/learn/_components/learn.tsx index 8b8058afe78..428303a2b12 100644 --- a/app/[locale]/learn/_components/learn.tsx +++ b/app/[locale]/learn/_components/learn.tsx @@ -2,13 +2,18 @@ import type { HTMLAttributes, ReactNode } from "react" -import type { ChildOnlyProp, ToCItem } from "@/lib/types" +import type { + ChildOnlyProp, + PageWithContributorsProps, + ToCItem, +} from "@/lib/types" import OriginalCard, { type CardProps as OriginalCardProps, } from "@/components/Card" import DocLink from "@/components/DocLink" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import { HubHero } from "@/components/Hero" import type { HubHeroProps } from "@/components/Hero/HubHero" import { Image, type ImageProps } from "@/components/Image" @@ -111,7 +116,10 @@ const ImageHeight200 = ({ src, alt }: ImageProps) => ( {alt} ) -const LearnPage = () => { +const LearnPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation("page-learn") const tocItems = [ @@ -681,6 +689,11 @@ const LearnPage = () => { + diff --git a/app/[locale]/learn/page.tsx b/app/[locale]/learn/page.tsx index ac96b2a9303..2499272af27 100644 --- a/app/[locale]/learn/page.tsx +++ b/app/[locale]/learn/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { type Params } from "@/lib/types" +import type { CommitHistory, Lang, Params } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,16 @@ export default async function Page({ params }: { params: Promise }) { const requiredNamespaces = getRequiredNamespacesForPage("/learn") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo("learn", locale as Lang, commitHistoryCache) + return ( - + ) } diff --git a/app/[locale]/roadmap/vision/_components/vision.tsx b/app/[locale]/roadmap/vision/_components/vision.tsx index a8bc8a05fd6..8c2a745772f 100644 --- a/app/[locale]/roadmap/vision/_components/vision.tsx +++ b/app/[locale]/roadmap/vision/_components/vision.tsx @@ -2,12 +2,13 @@ import type { ComponentProps, ComponentPropsWithRef } from "react" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" import Breadcrumbs from "@/components/Breadcrumbs" import Card from "@/components/Card" import Emoji from "@/components/Emoji" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import InfoBanner from "@/components/InfoBanner" import MainArticle from "@/components/MainArticle" import PageHero, { @@ -90,7 +91,10 @@ const TrilemmaContent = (props: ChildOnlyProp) => ( /> ) -const VisionPage = () => { +const VisionPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation(["page-roadmap-vision", "page-upgrades-index"]) const pathname = usePathname() @@ -240,6 +244,11 @@ const VisionPage = () => { + diff --git a/app/[locale]/roadmap/vision/page.tsx b/app/[locale]/roadmap/vision/page.tsx index a3fdf6e5e95..dececb01be6 100644 --- a/app/[locale]/roadmap/vision/page.tsx +++ b/app/[locale]/roadmap/vision/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,20 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/roadmap/vision") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "roadmap/vision", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/run-a-node/_components/run-a-node.tsx b/app/[locale]/run-a-node/_components/run-a-node.tsx index c4f3716ef14..0ed6747c249 100644 --- a/app/[locale]/run-a-node/_components/run-a-node.tsx +++ b/app/[locale]/run-a-node/_components/run-a-node.tsx @@ -4,11 +4,12 @@ import { HTMLAttributes } from "react" import type { ReactNode } from "react" import { FaDiscord } from "react-icons/fa" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" import Emoji from "@/components/Emoji" import ExpandableCard from "@/components/ExpandableCard" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import type { IconBaseType } from "@/components/icons/icon-base" import { DecentralizationGlyphIcon, @@ -204,7 +205,10 @@ type RunANodeCard = { alt: string } -const RunANodePage = () => { +const RunANodePage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation("page-run-a-node") const heroContent = { title: t("page-run-a-node-title"), @@ -756,6 +760,11 @@ const RunANodePage = () => { + diff --git a/app/[locale]/run-a-node/page.tsx b/app/[locale]/run-a-node/page.tsx index 65ab9816cda..9e083b5e734 100644 --- a/app/[locale]/run-a-node/page.tsx +++ b/app/[locale]/run-a-node/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,20 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/run-a-node") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "run-a-node", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/staking/_components/staking.tsx b/app/[locale]/staking/_components/staking.tsx index d12ace0b8aa..733d00ad66a 100644 --- a/app/[locale]/staking/_components/staking.tsx +++ b/app/[locale]/staking/_components/staking.tsx @@ -2,12 +2,17 @@ import { type HTMLAttributes, ReactNode } from "react" -import type { ChildOnlyProp, StakingStatsData } from "@/lib/types" +import type { + ChildOnlyProp, + PageWithContributorsProps, + StakingStatsData, +} from "@/lib/types" import { List as ButtonDropdownList } from "@/components/ButtonDropdown" import Card from "@/components/Card" import ExpandableCard from "@/components/ExpandableCard" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import LeftNavBar from "@/components/LeftNavBar" import { ContentContainer, Page } from "@/components/MdComponents" import MobileButtonDropdown from "@/components/MobileButtonDropdown" @@ -100,11 +105,15 @@ const StyledCard = (props: { ) -type Props = { +type Props = PageWithContributorsProps & { data: StakingStatsData } -const StakingPage = ({ data }: Props) => { +const StakingPage = ({ + data, + contributors, + lastEditLocaleTimestamp, +}: Props) => { const { t } = useTranslation("page-staking") const heroContent = { @@ -539,6 +548,11 @@ const StakingPage = ({ data }: Props) => { +
diff --git a/app/[locale]/staking/page.tsx b/app/[locale]/staking/page.tsx index 84c7a466c42..37d909903d1 100644 --- a/app/[locale]/staking/page.tsx +++ b/app/[locale]/staking/page.tsx @@ -6,6 +6,7 @@ import { } from "next-intl/server" import { + CommitHistory, EpochResponse, EthStoreResponse, Lang, @@ -14,6 +15,7 @@ import { import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { dataLoader } from "@/lib/utils/data/dataLoader" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -71,9 +73,21 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/staking") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "staking", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/wallets/_components/wallets.tsx b/app/[locale]/wallets/_components/wallets.tsx index 9f633396037..000b5a7c6cc 100644 --- a/app/[locale]/wallets/_components/wallets.tsx +++ b/app/[locale]/wallets/_components/wallets.tsx @@ -3,10 +3,13 @@ import { ComponentPropsWithRef } from "react" import { useLocale } from "next-intl" +import { PageWithContributorsProps } from "@/lib/types" + import Callout from "@/components/Callout" import Card from "@/components/Card" import CardList from "@/components/CardList" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import HorizontalCard from "@/components/HorizontalCard" import { Image } from "@/components/Image" import ListenToPlayer from "@/components/ListenToPlayer" @@ -35,7 +38,10 @@ export const StyledCard = (props: ComponentPropsWithRef) => ( /> ) -const WalletsPage = () => { +const WalletsPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const pathname = usePathname() const locale = useLocale() const { t } = useTranslation("page-wallets") @@ -418,6 +424,11 @@ const WalletsPage = () => {
+
diff --git a/app/[locale]/wallets/page.tsx b/app/[locale]/wallets/page.tsx index c83a90cf3bc..b4187b8dc98 100644 --- a/app/[locale]/wallets/page.tsx +++ b/app/[locale]/wallets/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,20 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/wallets") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "wallets", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx b/app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx index e50d251ac0c..cf81c8f7ec4 100644 --- a/app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx +++ b/app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx @@ -5,7 +5,12 @@ import { useLocale } from "next-intl" import type { HTMLAttributes } from "react" import { MdInfoOutline } from "react-icons/md" -import type { ChildOnlyProp, Lang, MetricReturnData } from "@/lib/types" +import type { + ChildOnlyProp, + Lang, + MetricReturnData, + PageWithContributorsProps, +} from "@/lib/types" import AdoptionChart from "@/components/AdoptionChart" import { @@ -19,6 +24,7 @@ import Callout from "@/components/Callout" import Card from "@/components/Card" import EnergyConsumptionChart from "@/components/EnergyConsumptionChart" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import { Image } from "@/components/Image" import ListenToPlayer from "@/components/ListenToPlayer" import MainArticle from "@/components/MainArticle" @@ -160,11 +166,15 @@ const Image400 = ({ src }: Pick) => ( ) -type Props = { +type Props = PageWithContributorsProps & { data: MetricReturnData } -const WhatIsEthereumPage = ({ data }: Props) => { +const WhatIsEthereumPage = ({ + data, + contributors, + lastEditLocaleTimestamp, +}: Props) => { const { t } = useTranslation(["page-what-is-ethereum", "learn-quizzes"]) const pathname = usePathname() const locale = useLocale() @@ -835,6 +845,11 @@ const WhatIsEthereumPage = ({ data }: Props) => {
+
diff --git a/app/[locale]/what-is-ethereum/page.tsx b/app/[locale]/what-is-ethereum/page.tsx index 41adad5a2f6..dc9a31746fd 100644 --- a/app/[locale]/what-is-ethereum/page.tsx +++ b/app/[locale]/what-is-ethereum/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { dataLoader } from "@/lib/utils/data/dataLoader" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -29,12 +30,24 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/what-is-ethereum") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "what-is-ethereum", + locale as Lang, + commitHistoryCache + ) + // Load data const [data] = await loadData() return ( - + ) } diff --git a/src/components/FileContributors.tsx b/src/components/FileContributors.tsx index bc5b3414621..8ac09e32321 100644 --- a/src/components/FileContributors.tsx +++ b/src/components/FileContributors.tsx @@ -6,15 +6,16 @@ import type { ChildOnlyProp, FileContributor } from "@/lib/types" import Translation from "@/components/Translation" import { Button } from "@/components/ui/buttons/Button" -import { Flex, VStack } from "@/components/ui/flex" +import { Center, Flex } from "@/components/ui/flex" import { ListItem, UnorderedList } from "@/components/ui/list" import { ScrollArea } from "@/components/ui/scroll-area" +import { cn } from "@/lib/utils/cn" import { trackCustomEvent } from "@/lib/utils/matomo" import { Avatar } from "./ui/avatar" import Modal from "./ui/dialog-modal" -import InlineLink from "./ui/Link" +import { LinkBox, LinkOverlay } from "./ui/link-box" import { useBreakpointValue } from "@/hooks/useBreakpointValue" @@ -27,17 +28,52 @@ const ContributorList = ({ children }: Required) => ( const ContributorAvatar = ({ contributor, label, -}: ContributorProps & { label?: string }) => ( + className, +}: ContributorProps & { label?: string; className?: string }) => ( ) +const ContributorAvatarGroup = ({ + contributors, +}: { + contributors: FileContributor[] +}) => { + if (!contributors.length) return null + + const maxVisibleAvatars = contributors.length >= 3 ? 3 : contributors.length + const remainingCount = contributors.length > 3 ? contributors.length - 3 : 0 + + return ( + + {contributors.slice(0, maxVisibleAvatars).map((contributor, index) => ( +
+ +
+ ))} + {remainingCount > 0 && ( +
+ +{remainingCount} +
+ )} +
+ ) +} + type ContributorProps = { contributor: FileContributor } const Contributor = ({ contributor }: ContributorProps) => ( @@ -45,6 +81,11 @@ const Contributor = ({ contributor }: ContributorProps) => ( contributor={contributor} label={"@" + contributor.login} /> + {contributor.html_url.includes("crowdin.com") && ( +

+ +

+ )}
) @@ -52,24 +93,16 @@ type FlexProps = BaseHTMLAttributes & { asChild?: boolean } export type FileContributorsProps = FlexProps & { contributors: FileContributor[] lastEditLocaleTimestamp: string + className?: string } const FileContributors = ({ contributors, lastEditLocaleTimestamp, + className, ...props }: FileContributorsProps) => { const [isModalOpen, setModalOpen] = useState(false) - - const lastContributor: FileContributor = contributors.length - ? contributors[0] - : ({ - avatar_url: "", - login: "", - html_url: "", - date: Date.now().toString(), - } as FileContributor) - const modalSize = useBreakpointValue({ base: "xl", md: "md" } as const) return ( @@ -92,35 +125,33 @@ const FileContributors = ({ - - - - -

- :{" "} - - @{lastContributor.login} - - , {lastEditLocaleTimestamp} + + +

+ {lastEditLocaleTimestamp}

+ + + + + +
- - - -
) diff --git a/src/intl/en/common.json b/src/intl/en/common.json index 15618378555..eb973b4226f 100644 --- a/src/intl/en/common.json +++ b/src/intl/en/common.json @@ -31,7 +31,7 @@ "content-standardization": "Content standardization", "contributing": "Contributing", "contributors": "Contributors", - "contributors-thanks": "Everyone who has contributed to this page – thank you!", + "contributors-thanks": "Everyone who has contributed to this page – thank you!", "cookie-policy": "Cookie policy", "copied": "Copied", "copy": "Copy", @@ -199,7 +199,7 @@ "language-zh-tw": "Chinese Traditional", "languages": "Languages", "last-24-hrs": "Last 24 hours", - "last-edit": "Last edit", + "page-last-update": "Page last update:", "last-updated": "Last updated", "layer-2": "Layer 2", "learn": "Learn", @@ -449,5 +449,6 @@ "withdrawals": "Staking withdrawals", "wrapped-ether": "Wrapped Ether", "yes": "Yes", - "zero-knowledge-proofs": "Zero-knowledge proofs" + "zero-knowledge-proofs": "Zero-knowledge proofs", + "translator": "Translator" } diff --git a/src/layouts/ContentLayout.tsx b/src/layouts/ContentLayout.tsx index 230d5d5bcf0..6ec2409e940 100644 --- a/src/layouts/ContentLayout.tsx +++ b/src/layouts/ContentLayout.tsx @@ -1,6 +1,9 @@ import type { HTMLAttributes } from "react" +import { FileContributor } from "@/lib/types" + import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import LeftNavBar, { LeftNavBarProps } from "@/components/LeftNavBar" import { ContentContainer, Page } from "@/components/MdComponents" import MobileButtonDropdown from "@/components/MobileButtonDropdown" @@ -9,6 +12,8 @@ type ContentLayoutProps = HTMLAttributes & Pick & { children: React.ReactNode heroSection: React.ReactNode + contributors: FileContributor[] + lastEditLocaleTimestamp: string } export const ContentLayout = ({ @@ -17,6 +22,8 @@ export const ContentLayout = ({ tocItems, maxDepth, heroSection, + contributors, + lastEditLocaleTimestamp, ...props }: ContentLayoutProps) => { return ( @@ -33,9 +40,14 @@ export const ContentLayout = ({ {children} + + - {dropdownLinks && } diff --git a/src/layouts/Static.tsx b/src/layouts/Static.tsx index 5d0159b8814..0bd2a331ac0 100644 --- a/src/layouts/Static.tsx +++ b/src/layouts/Static.tsx @@ -10,6 +10,7 @@ import Callout from "@/components/Callout" import Contributors from "@/components/Contributors" import EnergyConsumptionChart from "@/components/EnergyConsumptionChart" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import GlossaryDefinition from "@/components/Glossary/GlossaryDefinition" import GlossaryTooltip from "@/components/Glossary/GlossaryTooltip" import { HubHero } from "@/components/Hero" @@ -78,7 +79,11 @@ export const staticComponents = { type StaticLayoutProps = ChildOnlyProp & Pick< MdPageContent, - "slug" | "tocItems" | "lastEditLocaleTimestamp" | "contentNotTranslated" + | "slug" + | "tocItems" + | "lastEditLocaleTimestamp" + | "contentNotTranslated" + | "contributors" > & { frontmatter: StaticFrontmatter } @@ -89,6 +94,7 @@ export const StaticLayout = ({ tocItems, lastEditLocaleTimestamp, contentNotTranslated, + contributors, }: StaticLayoutProps) => { const locale = useLocale() @@ -135,6 +141,11 @@ export const StaticLayout = ({ /> {children} + diff --git a/src/layouts/Tutorial.tsx b/src/layouts/Tutorial.tsx index 69dde183913..f7cbcc5c667 100644 --- a/src/layouts/Tutorial.tsx +++ b/src/layouts/Tutorial.tsx @@ -123,6 +123,7 @@ export const TutorialLayout = ({ /> {children} diff --git a/src/layouts/md/Roadmap.tsx b/src/layouts/md/Roadmap.tsx index 9a38cdcf7ed..42a99514679 100644 --- a/src/layouts/md/Roadmap.tsx +++ b/src/layouts/md/Roadmap.tsx @@ -23,7 +23,14 @@ export const roadmapComponents = { } type RoadmapLayoutProps = ChildOnlyProp & - Pick & { + Pick< + MdPageContent, + | "slug" + | "tocItems" + | "contentNotTranslated" + | "contributors" + | "lastEditLocaleTimestamp" + > & { frontmatter: RoadmapFrontmatter } export const RoadmapLayout = ({ @@ -31,6 +38,8 @@ export const RoadmapLayout = ({ frontmatter, slug, tocItems, + contributors, + lastEditLocaleTimestamp, contentNotTranslated, }: RoadmapLayoutProps) => { const { t } = useTranslation("common") @@ -99,6 +108,8 @@ export const RoadmapLayout = ({ tocItems={tocItems} dropdownLinks={dropdownLinks} maxDepth={frontmatter.sidebarDepth} + contributors={contributors} + lastEditLocaleTimestamp={lastEditLocaleTimestamp} heroSection={ slug === "/roadmap/" ? ( & { + Pick< + MdPageContent, + | "slug" + | "tocItems" + | "contentNotTranslated" + | "contributors" + | "lastEditLocaleTimestamp" + > & { frontmatter: StakingFrontmatter } @@ -84,6 +91,8 @@ export const StakingLayout = ({ slug, tocItems, contentNotTranslated, + contributors, + lastEditLocaleTimestamp, }: StakingLayoutProps) => { const { t } = useTranslation("page-staking") @@ -164,6 +173,8 @@ export const StakingLayout = ({ tocItems={tocItems} dropdownLinks={dropdownLinks} maxDepth={frontmatter.sidebarDepth} + contributors={contributors} + lastEditLocaleTimestamp={lastEditLocaleTimestamp} heroSection={} > {children} diff --git a/src/layouts/md/Translatathon.tsx b/src/layouts/md/Translatathon.tsx index 24b6ba50eac..1927b16a957 100644 --- a/src/layouts/md/Translatathon.tsx +++ b/src/layouts/md/Translatathon.tsx @@ -100,7 +100,10 @@ export const translatathonComponents = { } type TranslatathonLayoutProps = ChildOnlyProp & - Pick & { + Pick< + MdPageContent, + "slug" | "tocItems" | "contributors" | "lastEditLocaleTimestamp" + > & { frontmatter: SharedFrontmatter } @@ -109,6 +112,8 @@ export const TranslatathonLayout = ({ frontmatter, slug, tocItems, + contributors, + lastEditLocaleTimestamp, }: TranslatathonLayoutProps) => { const dropdownLinks: ButtonDropdownList = { text: "Translatathon menu", @@ -181,6 +186,8 @@ export const TranslatathonLayout = ({ dir="ltr" tocItems={tocItems} dropdownLinks={dropdownLinks} + contributors={contributors} + lastEditLocaleTimestamp={lastEditLocaleTimestamp} heroSection={} > {children} diff --git a/src/layouts/md/Upgrade.tsx b/src/layouts/md/Upgrade.tsx index 40128e528f2..de874db2dc8 100644 --- a/src/layouts/md/Upgrade.tsx +++ b/src/layouts/md/Upgrade.tsx @@ -24,7 +24,11 @@ export const upgradeComponents = { type UpgradeLayoutProps = ChildOnlyProp & Pick< MdPageContent, - "slug" | "tocItems" | "lastEditLocaleTimestamp" | "contentNotTranslated" + | "slug" + | "tocItems" + | "lastEditLocaleTimestamp" + | "contentNotTranslated" + | "contributors" > & { frontmatter: UpgradeFrontmatter } @@ -35,6 +39,7 @@ export const UpgradeLayout = ({ tocItems, lastEditLocaleTimestamp, contentNotTranslated, + contributors, }: UpgradeLayoutProps) => { const { t } = useTranslation("page-upgrades") @@ -91,6 +96,8 @@ export const UpgradeLayout = ({ dir={contentNotTranslated ? "ltr" : "unset"} tocItems={tocItems} dropdownLinks={dropdownLinks} + contributors={contributors} + lastEditLocaleTimestamp={lastEditLocaleTimestamp} heroSection={} > {children} diff --git a/src/layouts/md/UseCases.tsx b/src/layouts/md/UseCases.tsx index 79537a32130..6024cba5162 100644 --- a/src/layouts/md/UseCases.tsx +++ b/src/layouts/md/UseCases.tsx @@ -26,7 +26,14 @@ export const useCasesComponents = { } type UseCasesLayoutProps = ChildOnlyProp & - Pick & { + Pick< + MdPageContent, + | "slug" + | "tocItems" + | "contentNotTranslated" + | "contributors" + | "lastEditLocaleTimestamp" + > & { frontmatter: UseCasesFrontmatter } export const UseCasesLayout = ({ @@ -35,6 +42,8 @@ export const UseCasesLayout = ({ slug, tocItems, contentNotTranslated, + contributors, + lastEditLocaleTimestamp, }: UseCasesLayoutProps) => { const { t } = useTranslation("template-usecase") @@ -172,6 +181,8 @@ export const UseCasesLayout = ({ tocItems={tocItems} dropdownLinks={dropdownLinks} maxDepth={frontmatter.sidebarDepth} + contributors={contributors} + lastEditLocaleTimestamp={lastEditLocaleTimestamp} heroSection={} > {children} diff --git a/src/layouts/stories/ContentLayout.stories.tsx b/src/layouts/stories/ContentLayout.stories.tsx index 28f3911db7a..0fdd70ad47a 100644 --- a/src/layouts/stories/ContentLayout.stories.tsx +++ b/src/layouts/stories/ContentLayout.stories.tsx @@ -62,6 +62,21 @@ export const ContentLayout: StoryObj = { ], }, maxDepth: 2, + contributors: [ + { + login: "github", + avatar_url: "/", + html_url: "https://github.com", + date: "2025-04-20T12:00:00.000Z", + }, + { + login: "crowdin", + avatar_url: "/", + html_url: "https://crowdin.com", + date: "2025-04-20T12:00:00.000Z", + }, + ], + lastEditLocaleTimestamp: "MM DD, YY", heroSection: (
Hero section diff --git a/src/lib/api/fetchGitHistory.ts b/src/lib/api/fetchGitHistory.ts index 2f54f15c106..edf299252d5 100644 --- a/src/lib/api/fetchGitHistory.ts +++ b/src/lib/api/fetchGitHistory.ts @@ -46,7 +46,7 @@ async function fetchWithRateLimit(filepath: string): Promise { } // Fetch commit history and save it to a JSON file -export const fetchAndCacheGitContributors = async ( +export const fetchAndCacheGitHubContributors = async ( filepath: string, cache: CommitHistory ) => { diff --git a/src/lib/md/data.ts b/src/lib/md/data.ts index 99690ac7d67..0a6db9a3df6 100644 --- a/src/lib/md/data.ts +++ b/src/lib/md/data.ts @@ -6,11 +6,10 @@ import { FileContributor, Frontmatter, Lang, - Layout, ToCItem, } from "@/lib/types" -import { getFileContributorInfo } from "@/lib/utils/contributors" +import { getMarkdownFileContributorInfo } from "@/lib/utils/contributors" import { getLocaleTimestamp } from "@/lib/utils/time" import { compile } from "./compile" @@ -22,7 +21,6 @@ interface GetPageDataParams { locale: string slug: string components: MDXRemoteProps["components"] - layout?: Layout scope?: Record } @@ -40,7 +38,6 @@ export async function getPageData({ locale, slug, components, - layout: layoutFromProps, scope, }: GetPageDataParams): Promise { const slugArray = slug.split("/") @@ -55,8 +52,6 @@ export async function getPageData({ scope, }) - const layout = layoutFromProps || frontmatter.template || "static" - // Process TOC items const tocItems = tocNodeItems.length === 1 && "items" in tocNodeItems[0] @@ -64,13 +59,13 @@ export async function getPageData({ : tocNodeItems // Get contributor information - const { contributors, lastUpdatedDate } = await getFileContributorInfo( - slug, - locale, - frontmatter.lang as string, - layout, - commitHistoryCache - ) + const { contributors, lastUpdatedDate } = + await getMarkdownFileContributorInfo( + slug, + locale, + frontmatter.lang as string, + commitHistoryCache + ) // Format timestamp const lastEditLocaleTimestamp = getLocaleTimestamp( diff --git a/src/lib/types.ts b/src/lib/types.ts index bcc4550cc36..a464d46e3de 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -395,7 +395,7 @@ export type FileContributor = { login: string avatar_url: string html_url: string - date?: string + date: string } type FilePath = string @@ -972,6 +972,11 @@ export type EventCardProps = { imageUrl?: string } +export type PageWithContributorsProps = { + contributors: FileContributor[] + lastEditLocaleTimestamp: string +} + export type BreakpointKey = keyof typeof screens export type MaturityLevel = diff --git a/src/lib/utils/contributors.ts b/src/lib/utils/contributors.ts index bb1ad128d6c..6222e0d887a 100644 --- a/src/lib/utils/contributors.ts +++ b/src/lib/utils/contributors.ts @@ -1,6 +1,6 @@ import { join } from "path" -import type { CommitHistory, FileContributor, Lang, Layout } from "@/lib/types" +import type { CommitHistory, FileContributor, Lang } from "@/lib/types" import { CONTENT_DIR, CONTENT_PATH, DEFAULT_LOCALE } from "@/lib/constants" @@ -8,41 +8,95 @@ import { convertToFileContributorFromCrowdin, getCrowdinContributors, } from "./crowdin" -import { getLastModifiedDate } from "./gh" +import { getAppPageLastCommitDate, getMarkdownLastCommitDate } from "./gh" +import { getLocaleTimestamp } from "./time" -import { fetchAndCacheGitContributors } from "@/lib/api/fetchGitHistory" +import { fetchAndCacheGitHubContributors } from "@/lib/api/fetchGitHistory" -export const getFileContributorInfo = async ( +export const getMarkdownFileContributorInfo = async ( slug: string, locale: string, fileLang: string, - layout: Layout, cache: CommitHistory ) => { const mdPath = join(CONTENT_PATH, slug) const mdDir = join(CONTENT_DIR, slug) - const gitContributors = await fetchAndCacheGitContributors( + const gitHubContributors = await fetchAndCacheGitHubContributors( join("/", mdDir, "index.md"), cache ) - const latestCommitDate = getLastModifiedDate(slug, locale!) - const gitHubLastEdit = gitContributors[0]?.date + const latestCommitDate = getMarkdownLastCommitDate(slug, locale!) + const gitHubLastEdit = gitHubContributors[0]?.date const lastUpdatedDate = gitHubLastEdit || latestCommitDate - const crowdinContributors = ["docs", "tutorial"].includes(layout) - ? convertToFileContributorFromCrowdin( - getCrowdinContributors(mdPath, locale as Lang) - ) - : [] + const crowdinContributors = convertToFileContributorFromCrowdin( + getCrowdinContributors(mdPath, locale as Lang) + ) - const useGitHubContributors: boolean = + const englishOnly: boolean = fileLang === DEFAULT_LOCALE || crowdinContributors.length === 0 - const contributors: FileContributor[] = useGitHubContributors - ? gitContributors - : crowdinContributors + const contributors: FileContributor[] = englishOnly + ? gitHubContributors + : [...crowdinContributors, ...gitHubContributors] return { contributors, lastUpdatedDate } } + +/** + * Returns an array of possible historical file paths for a given page, + * accounting for different directory structures and migrations over time. + * + * @param pagePath - The relative path of the page (without extension). + * @returns An array of strings representing all historical file paths for the page. + * + * @remarks + * This function is used to track all possible locations a page may have existed in the repository, + * which is useful for aggregating git history and contributor information. + * + * @note + * If a page is migrated or its location changes, ensure the new path is added to this list. + * This maintains a complete historical record for accurate git history tracking. + */ +const getAllHistoricalPaths = (pagePath: string): string[] => [ + join("src/pages", `${pagePath}.tsx`), + join("src/pages", pagePath, "index.tsx"), + join("src/pages/[locale]", `${pagePath}.tsx`), + join("src/pages/[locale]", pagePath, "index.tsx"), + join("app/[locale]", pagePath, "page.tsx"), + join("app/[locale]", pagePath, "_components", `${pagePath}.tsx`), +] + +export const getAppPageContributorInfo = async ( + pagePath: string, + locale: Lang, + cache: CommitHistory +) => { + // TODO: Incorporate Crowdin contributor information + + const gitHubContributors = await getAllHistoricalPaths(pagePath).reduce( + async (acc, path) => { + const contributors = await fetchAndCacheGitHubContributors(path, cache) + return [...(await acc), ...contributors] + }, + Promise.resolve([] as FileContributor[]) + ) + + const uniqueGitHubContributors = gitHubContributors.filter( + (contributor, index, self) => + index === self.findIndex((t) => t.login === contributor.login) + ) + + const latestCommitDate = getAppPageLastCommitDate(gitHubContributors) + const lastEditLocaleTimestamp = getLocaleTimestamp(locale, latestCommitDate) + + if (!uniqueGitHubContributors.length || !lastEditLocaleTimestamp) { + throw new Error( + `No contributors found, path: ${pagePath}, locale: ${locale}` + ) + } + + return { contributors: uniqueGitHubContributors, lastEditLocaleTimestamp } +} diff --git a/src/lib/utils/crowdin.ts b/src/lib/utils/crowdin.ts index 9da6717b48b..cdffedcfcd0 100644 --- a/src/lib/utils/crowdin.ts +++ b/src/lib/utils/crowdin.ts @@ -30,4 +30,5 @@ export const convertToFileContributorFromCrowdin = ( login: username, avatar_url: avatarUrl, html_url: `https://crowdin.com/profile/${username}`, + date: new Date(0).toString(), })) diff --git a/src/lib/utils/gh.ts b/src/lib/utils/gh.ts index c54c84bcd6b..d2b4dfd1fd1 100644 --- a/src/lib/utils/gh.ts +++ b/src/lib/utils/gh.ts @@ -4,6 +4,8 @@ import { join } from "path" import { CONTENT_DIR, DEFAULT_LOCALE, TRANSLATIONS_DIR } from "@/lib/constants" +import { FileContributor } from "../types" + const getGitLogFromPath = (path: string): string => { // git command to show file last commit info const gitCommand = `git log -1 -- ${path}` @@ -25,8 +27,27 @@ const extractDateFromGitLogInfo = (logInfo: string): string => { } } +export const getAppPageLastCommitDate = ( + gitHubContributors: FileContributor[] +) => + gitHubContributors + .reduce((latest, contributor) => { + const commitDate = new Date(contributor.date) + return commitDate > latest ? commitDate : latest + }, new Date(0)) + .toString() + +export const getLastGitCommitDateByPath = (path: string): string => { + if (!fs.existsSync(path)) throw new Error(`File not found: ${path}`) + const logInfo = getGitLogFromPath(path) + return extractDateFromGitLogInfo(logInfo) +} + // This util filters the git log to get the file last commit info, and then the commit date (last update) -export const getLastModifiedDate = (slug: string, locale: string): string => { +export const getMarkdownLastCommitDate = ( + slug: string, + locale: string +): string => { const translatedContentPath = join(TRANSLATIONS_DIR, locale, slug, "index.md") const contentIsNotTranslated = !fs.existsSync(translatedContentPath) let filePath = "" @@ -39,14 +60,7 @@ export const getLastModifiedDate = (slug: string, locale: string): string => { filePath = join(TRANSLATIONS_DIR, locale, slug, "index.md") } - const logInfo = getGitLogFromPath(filePath) - return extractDateFromGitLogInfo(logInfo) -} - -export const getLastModifiedDateByPath = (path: string): string => { - if (!fs.existsSync(path)) throw new Error(`File not found: ${path}`) - const logInfo = getGitLogFromPath(path) - return extractDateFromGitLogInfo(logInfo) + return getLastGitCommitDateByPath(filePath) } const LABELS_TO_SEARCH = [