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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
npx lint-staged
npx lint-staged
76 changes: 76 additions & 0 deletions app/[locale]/developers/tutorials/_components/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use client"

import React, { useState } from "react"
import { FaGithub } from "react-icons/fa"

import Translation from "@/components/Translation"
import { Button } from "@/components/ui/buttons/Button"
import { ButtonLink } from "@/components/ui/buttons/Button"
import Modal from "@/components/ui/dialog-modal"
import { Flex } from "@/components/ui/flex"

import { trackCustomEvent } from "@/lib/utils/matomo"

import { useBreakpointValue } from "@/hooks/useBreakpointValue"

const TutorialSubmitModal = ({
dir,
}: Pick<React.HTMLAttributes<React.JSX.Element>, "dir">) => {
const [isModalOpen, setModalOpen] = useState(false)

const modalSize = useBreakpointValue({ base: "xl", md: "md" } as const)

return (
<>
<Modal
open={isModalOpen}
onOpenChange={(open) => setModalOpen(open)}
size={modalSize}
contentProps={{ dir }}
title={
<Translation id="page-developers-tutorials:page-tutorial-submit-btn" />
}
>
<p className="mb-6">
<Translation id="page-developers-tutorials:page-tutorial-listing-policy-intro" />
</p>
<Flex className="flex-col gap-2 md:flex-row">
<Flex className="w-full flex-col justify-between rounded-sm border border-border p-4">
<b>
<Translation id="page-developers-tutorials:page-tutorial-create-an-issue" />
</b>
<p className="mb-6">
<Translation id="page-developers-tutorials:page-tutorial-create-an-issue-desc" />
</p>
<ButtonLink
variant="outline"
href="https://github.com/ethereum/ethereum-org-website/issues/new?assignees=&labels=Type%3A+Feature&template=suggest_tutorial.yaml&title="
>
<FaGithub />
<Translation id="page-developers-tutorials:page-tutorial-raise-issue-btn" />
</ButtonLink>
</Flex>
</Flex>
</Modal>

<Button
className="px-3 py-2 text-body"
variant="outline"
onClick={() => {
setModalOpen(true)
trackCustomEvent({
eventCategory: "tutorials tags",
eventAction: "click",
eventName: "submit",
})
}}
>
<Translation id="page-developers-tutorials:page-tutorial-submit-btn" />
</Button>
</>
)
}

TutorialSubmitModal.displayName = "TutorialSubmitModal"

export default TutorialSubmitModal
90 changes: 10 additions & 80 deletions app/[locale]/developers/tutorials/_components/tutorials.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@ import React, {
useState,
} from "react"
import { useLocale } from "next-intl"
import { FaGithub } from "react-icons/fa"

import { ITutorial, Lang } from "@/lib/types"

import Emoji from "@/components/Emoji"
import FeedbackCard from "@/components/FeedbackCard"
import MainArticle from "@/components/MainArticle"
import Translation from "@/components/Translation"
import { getSkillTranslationId } from "@/components/TutorialMetadata"
import TutorialTags from "@/components/TutorialTags"
import { Button, ButtonLink } from "@/components/ui/buttons/Button"
import Modal from "@/components/ui/dialog-modal"
import { Button } from "@/components/ui/buttons/Button"
import { Flex, FlexProps } from "@/components/ui/flex"
import { Tag, TagButton } from "@/components/ui/tag"

Expand All @@ -36,12 +32,6 @@ import externalTutorials from "@/data/externalTutorials.json"

import { DEFAULT_LOCALE } from "@/lib/constants"

import { useBreakpointValue } from "@/hooks/useBreakpointValue"

type LinkFlexProps = FlexProps & {
href: string
}

const FilterTag = forwardRef<
HTMLButtonElement,
{ isActive: boolean; name: string } & ButtonHTMLAttributes<HTMLButtonElement>
Expand All @@ -66,6 +56,10 @@ const Text = ({ className, ...props }: HTMLAttributes<HTMLHeadElement>) => (
<p className={cn("mb-6", className)} {...props} />
)

type LinkFlexProps = FlexProps & {
href: string
}

const LinkFlex = ({ href, children, ...props }: LinkFlexProps) => {
return (
<Flex asChild {...props}>
Expand All @@ -85,15 +79,11 @@ const published = (locale: string, published: string) => {
) : null
}

type TutorialPageProps = {
type TutorialsListProps = {
internalTutorials: ITutorial[]
contentNotTranslated: boolean
}

const TutorialPage = ({
internalTutorials,
contentNotTranslated,
}: TutorialPageProps) => {
const TutorialsList = ({ internalTutorials }: TutorialsListProps) => {
const locale = useLocale()
const effectiveLocale = internalTutorials.length > 0 ? locale : DEFAULT_LOCALE
const filteredTutorialsByLang = useMemo(
Expand All @@ -111,7 +101,6 @@ const TutorialPage = ({
[filteredTutorialsByLang]
)

const [isModalOpen, setModalOpen] = useState(false)
const [filteredTutorials, setFilteredTutorials] = useState(
filteredTutorialsByLang
)
Expand Down Expand Up @@ -152,66 +141,8 @@ const TutorialPage = ({
setSelectedTags([...tempSelectedTags])
}

const dir = contentNotTranslated ? "ltr" : "unset"

const modalSize = useBreakpointValue({ base: "xl", md: "md" } as const)
return (
<MainArticle
className={`mx-auto my-0 mt-16 flex w-full flex-col items-center ${dir}`}
>
<h1 className="no-italic mb-4 text-center font-monospace text-[2rem] font-semibold uppercase leading-[1.4] max-sm:mx-4 max-sm:mt-4 sm:mb-[1.625rem]">
<Translation id="page-developers-tutorials:page-tutorial-title" />
</h1>
<Text className="mb-4 text-center leading-xs text-body-medium">
<Translation id="page-developers-tutorials:page-tutorial-subtitle" />
</Text>

<Modal
open={isModalOpen}
onOpenChange={(open) => setModalOpen(open)}
size={modalSize}
contentProps={{ dir }}
title={
<Translation id="page-developers-tutorials:page-tutorial-submit-btn" />
}
>
<Text>
<Translation id="page-developers-tutorials:page-tutorial-listing-policy-intro" />
</Text>
<Flex className="flex-col gap-2 md:flex-row">
<Flex className="w-full flex-col justify-between rounded-sm border border-border p-4">
<b>
<Translation id="page-developers-tutorials:page-tutorial-create-an-issue" />
</b>
<Text>
<Translation id="page-developers-tutorials:page-tutorial-create-an-issue-desc" />
</Text>
<ButtonLink
variant="outline"
href="https://github.com/ethereum/ethereum-org-website/issues/new?assignees=&labels=Type%3A+Feature&template=suggest_tutorial.yaml&title="
>
<FaGithub />
<Translation id="page-developers-tutorials:page-tutorial-raise-issue-btn" />
</ButtonLink>
</Flex>
</Flex>
</Modal>

<Button
className="px-3 py-2 text-body"
variant="outline"
onClick={() => {
setModalOpen(true)
trackCustomEvent({
eventCategory: "tutorials tags",
eventAction: "click",
eventName: "submit",
})
}}
>
<Translation id="page-developers-tutorials:page-tutorial-submit-btn" />
</Button>

<>
<div className="my-8 w-full shadow-table-box md:w-2/3">
<Flex className="m-8 flex-col justify-center border-b border-border px-0 pb-4 pt-4 md:pb-8">
<Flex className="mb-4 max-w-full flex-wrap items-center gap-2">
Expand Down Expand Up @@ -312,9 +243,8 @@ const TutorialPage = ({
)
})}
</div>
<FeedbackCard />
</MainArticle>
</>
)
}

export default TutorialPage
export default TutorialsList
58 changes: 53 additions & 5 deletions app/[locale]/developers/tutorials/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { pick } from "lodash"
import dynamic from "next/dynamic"
import {
getMessages,
getTranslations,
Expand All @@ -7,20 +8,51 @@ import {

import { Lang } from "@/lib/types"

import FeedbackCard from "@/components/FeedbackCard"
import I18nProvider from "@/components/I18nProvider"
import MainArticle from "@/components/MainArticle"
import { Skeleton, SkeletonCardContent } from "@/components/ui/skeleton"

import { existsNamespace } from "@/lib/utils/existsNamespace"
import { getTutorialsData } from "@/lib/utils/md"
import { getMetadata } from "@/lib/utils/metadata"
import { getRequiredNamespacesForPage } from "@/lib/utils/translations"

import Tutorials from "./_components/tutorials"
const TutorialsList = dynamic(() => import("./_components/tutorials"), {
ssr: false,
loading: () => (
<div className="mt-8 w-full md:w-2/3">
<div className="grid w-full grid-cols-3 gap-2 px-8 pb-16 pt-12 sm:grid-cols-4 lg:gap-4 2xl:grid-cols-5">
{Array.from({ length: 30 }).map((_, index) => (
<Skeleton key={"tag" + index} className="h-8 rounded-full" />
))}
</div>
{Array.from({ length: 5 }).map((_, index) => (
<SkeletonCardContent key={"card" + index} className="p-8" />
))}
</div>
),
})

const TutorialSubmitModal = dynamic(() => import("./_components/modal"), {
ssr: false,
loading: () => (
<div className="w-full max-w-40 rounded border p-3">
<Skeleton className="h-5 w-full" />
</div>
),
})

const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
const { locale } = await params

setRequestLocale(locale)

const t = await getTranslations({
locale,
namespace: "page-developers-tutorials",
})

// Get i18n messages
const allMessages = await getMessages({ locale })
const requiredNamespaces = getRequiredNamespacesForPage(
Expand All @@ -29,13 +61,29 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
const messages = pick(allMessages, requiredNamespaces)

const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const dir = contentNotTranslated ? "ltr" : "unset"

const internalTutorials = await getTutorialsData(locale)

return (
<I18nProvider locale={locale} messages={messages}>
<Tutorials
internalTutorials={getTutorialsData(locale)}
contentNotTranslated={contentNotTranslated}
/>
<MainArticle
className="mx-auto my-0 mt-16 flex w-full flex-col items-center"
dir={dir}
>
<h1 className="no-italic mb-4 text-center font-monospace text-[2rem] font-semibold uppercase leading-[1.4] max-sm:mx-4 max-sm:mt-4 sm:mb-[1.625rem]">
{t("page-tutorial-title")}
</h1>
<p className="mb-4 text-center leading-xs text-body-medium">
{t("page-tutorial-subtitle")}
</p>

<TutorialSubmitModal dir={dir} />

<TutorialsList internalTutorials={internalTutorials} />

<FeedbackCard />
</MainArticle>
</I18nProvider>
)
}
Expand Down
2 changes: 2 additions & 0 deletions docs/review-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ Adding new products is currently a low-to-medium priority (depending on the type

Adding new tutorials to [ethereum.org](http://ethereum.org) is currently low-priority. We are currently in the middle of an epic to revamp our tutorials. As part of this, we’ll be reviewing our existing tutorials, purging outdated or low-quality tutorials, and refining our listing criteria for future tutorials to meet our increased standards. Please always create an issue to discuss the usefulness of your proposed tutorial before opening a PR.

New tutorials should be placed in `public/content/developers/tutorials/your-tutorial-name/index.md`, with `your-tutorial-name` added to `src/data/internalTutorials.json` for inclusion.

**Timeline:** PRs should be closed or merged within 30 days of opening.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"markdown-checker": "ts-node -O '{ \"module\": \"commonjs\" }' src/scripts/markdownChecker.ts",
"events-import": "ts-node -O '{ \"module\": \"commonjs\" }' src/scripts/events-import.ts",
"crowdin-needs-review": "ts-node -O '{ \"module\": \"commonjs\" }' src/scripts/crowdin/reports/generateReviewReport.ts",
"update-tutorials": "ts-node -O '{ \"module\": \"commonjs\" }' src/scripts/update-tutorials-list.ts",
"prepare": "husky"
},
"dependencies": {
Expand Down
22 changes: 16 additions & 6 deletions src/components/ui/skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,18 @@ type SkeletonCardProps = {
className?: string
}

const SkeletonCardContent = ({ className }: SkeletonCardProps) => (
<CardContent className={cn("cursor-default space-y-3", className)}>
<Skeleton className="h-6 w-3/4" />
<Skeleton className="h-4 w-1/2" />
<Skeleton className="h-4 w-1/3" />
</CardContent>
)

const SkeletonCard = ({ className }: SkeletonCardProps) => (
<Card className={cn("cursor-default", className)}>
<CardBanner />
<CardContent className="space-y-3">
<Skeleton className="h-6 w-3/4" />
<Skeleton className="h-4 w-1/2" />
<Skeleton className="h-4 w-1/3" />
</CardContent>
<SkeletonCardContent />
</Card>
)

Expand All @@ -85,4 +89,10 @@ const SkeletonCardGrid = ({ className }: SkeletonCardGridProps) => (
</div>
)

export { Skeleton, SkeletonCardGrid, SkeletonLines }
export {
Skeleton,
SkeletonCard,
SkeletonCardContent,
SkeletonCardGrid,
SkeletonLines,
}
Loading