Skip to content

Commit 5a958ea

Browse files
committed
feat: add video courses section
additional adjustments/patches
1 parent ecbe68c commit 5a958ea

File tree

13 files changed

+196
-45
lines changed

13 files changed

+196
-45
lines changed

app/[locale]/developers/_components/BuilderCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const BuilderCard = ({ path, className }: BuildCardProps) => (
1717
<Image
1818
src={path.imgSrc}
1919
alt={path.imgAlt}
20-
className="object-fit mx-auto h-44"
20+
className="mx-auto h-44 object-contain"
2121
sizes="(max-width: 480px) calc(100vw - 6rem), 285px"
2222
/>
2323
<div className="h-full space-y-1">
@@ -33,7 +33,7 @@ const BuilderCard = ({ path, className }: BuildCardProps) => (
3333
<h3 className="text-lg font-bold">{path.title}</h3>
3434
<p className="mb-4 text-sm text-body-medium">{path.description}</p>
3535
</div>
36-
<ButtonLink href={path.url} className="sm:w-fit">
36+
<ButtonLink href={path.href} className="sm:w-fit">
3737
{path.button}
3838
</ButtonLink>
3939
</Card>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Image } from "@/components/Image"
2+
import Link from "@/components/ui/Link"
3+
import { Tag } from "@/components/ui/tag"
4+
5+
import { cn } from "@/lib/utils/cn"
6+
7+
import type { VideoCourse } from "../types"
8+
9+
type BuildCardProps = {
10+
course: VideoCourse
11+
className?: string
12+
}
13+
14+
const VideoCourseCard = ({ course, className }: BuildCardProps) => (
15+
<Link
16+
href={course.href}
17+
className={cn(
18+
"group flex h-full w-fit flex-col gap-8 rounded-4xl no-underline",
19+
className
20+
)}
21+
hideArrow
22+
>
23+
<div className="h-fit w-full overflow-hidden rounded-2xl">
24+
<Image
25+
src={course.imgSrc}
26+
alt={course.imgAlt}
27+
className="mx-auto h-44 rounded-2xl object-cover transition-transform group-hover:scale-105 group-hover:transition-transform"
28+
sizes="(max-width: 480px) calc(100vw - 2rem), 300px"
29+
/>
30+
</div>
31+
<div className="h-full space-y-1">
32+
<Tag
33+
status="warning"
34+
size="small"
35+
className="mb-0 rounded-[4px] px-1 py-px font-bold normal-case"
36+
>
37+
{Math.round(course.hours)}-hour course
38+
</Tag>
39+
<h3 className="groupfont-bold text-lg text-body group-hover:underline">
40+
{course.title}
41+
</h3>
42+
<p className="mb-4 text-sm text-body-medium">{course.description}</p>
43+
</div>
44+
</Link>
45+
)
46+
47+
export default VideoCourseCard
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use client"
2+
3+
import { Swiper, SwiperSlide } from "@/components/ui/swiper"
4+
5+
import type { VideoCourse } from "../../types"
6+
import VideoCourseCard from "../VideoCourseCard"
7+
8+
import { useBreakpointValue } from "@/hooks/useBreakpointValue"
9+
10+
type VideoCourseSwiperProps = {
11+
courses: VideoCourse[]
12+
}
13+
14+
const VideoCourseSwiper = ({ courses }: VideoCourseSwiperProps) => {
15+
const slidesPerView = useBreakpointValue({
16+
base: 1.25,
17+
sm: 1.6,
18+
md: 2.7,
19+
lg: 4.4,
20+
xl: 5,
21+
})
22+
const spaceBetween = useBreakpointValue({
23+
base: 16,
24+
sm: 12,
25+
md: 16,
26+
lg: 24,
27+
xl: 32,
28+
})
29+
30+
return (
31+
<Swiper spaceBetween={spaceBetween} slidesPerView={slidesPerView}>
32+
{courses.map((course, idx) => (
33+
<SwiperSlide
34+
key={idx}
35+
className="max-xl:first:ms-8 max-xl:[&:last-child_div]:pe-16"
36+
>
37+
<VideoCourseCard course={course} />
38+
</SwiperSlide>
39+
))}
40+
</Swiper>
41+
)
42+
}
43+
44+
export default VideoCourseSwiper
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import dynamic from "next/dynamic"
2+
3+
import Loading from "./loading"
4+
5+
export default dynamic(() => import("."), { ssr: false, loading: Loading })
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { SkeletonCardGrid } from "@/components/ui/skeleton"
2+
3+
const Loading = () => <SkeletonCardGrid />
4+
5+
export default Loading

app/[locale]/developers/page.tsx

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { getTranslations } from "next-intl/server"
33
import type { Lang } from "@/lib/types"
44
import { ChildOnlyProp } from "@/lib/types"
55

6-
import CalloutSSR from "@/components/CalloutSSR"
76
import { CopyButton } from "@/components/CopyToClipboard"
87
import FeedbackCard from "@/components/FeedbackCard"
98
import HubHero from "@/components/Hero/HubHero"
@@ -19,15 +18,15 @@ import { getMetadata } from "@/lib/utils/metadata"
1918
import { screens } from "@/lib/utils/screen"
2019

2120
import BuilderCard from "./_components/BuilderCard"
22-
import BuilderSwiper from "./_components/BuilderSwiper"
21+
import BuilderSwiper from "./_components/BuilderSwiper/lazy"
2322
import SpeedRunCard from "./_components/SpeedRunCard"
24-
import { getBuilderPaths } from "./utils"
23+
import VideoCourseSwiper from "./_components/VideoCourseSwiper/lazy"
24+
import { getBuilderPaths, getVideoCourses } from "./utils"
2525

2626
import resourcesBanner from "@/public/images/developers/resources-banner.png"
2727
import scaffoldDebugScreenshot from "@/public/images/developers/scaffold-debug-screenshot.png"
2828
import stackExchangeScreenshot from "@/public/images/developers/stack-exchange-screenshot.png"
2929
import tutorialTagsBanner from "@/public/images/developers/tutorial-tags-banner.png"
30-
import developersImage from "@/public/images/developers-eth-blocks.png"
3130
import dogeImage from "@/public/images/doge-computer.png"
3231
import heroImage from "@/public/images/heroes/developers-hub-hero.jpg"
3332

@@ -43,12 +42,6 @@ const Column = (props: ChildOnlyProp) => (
4342
const RightColumn = (props: ChildOnlyProp) => (
4443
<div className="mb-6 me-0 w-full flex-1 basis-1/3" {...props} />
4544
)
46-
const IntroColumn = (props: ChildOnlyProp) => (
47-
<div
48-
className="mb-6 me-0 mt-0 w-full flex-1 basis-1/3 sm:me-8 lg:mt-32"
49-
{...props}
50-
/>
51-
)
5245

5346
const DevelopersPage = async ({
5447
params,
@@ -71,6 +64,9 @@ const DevelopersPage = async ({
7164
description: t("page-developers-speedrunethereum-description"),
7265
ctaLabel: t("page-developers-speedrunethereum-link"),
7366
}
67+
68+
const courses = await getVideoCourses()
69+
7470
return (
7571
<VStack className="mx-auto my-0 w-full">
7672
<HubHero
@@ -237,33 +233,16 @@ const DevelopersPage = async ({
237233
</Card>
238234
</div>
239235

240-
<div className="flex w-full flex-col items-start justify-between lg:flex-row lg:items-center">
241-
<IntroColumn>
242-
<H2>{t("page-developers-about")}</H2>
243-
<p className="mb-6 leading-xs text-body-medium">
244-
{t("page-developers-about-desc")}
245-
</p>
246-
<Text>{t("page-developers-about-desc-2")}</Text>
247-
<Text>
248-
{t("page-developers-feedback")}{" "}
249-
<InlineLink href="https://discord.gg/ethereum-org">
250-
{t("page-developers-discord")}
251-
</InlineLink>
252-
</Text>
253-
</IntroColumn>
254-
<CalloutSSR
255-
className="flex-auto md:flex-[1_1_416px]"
256-
image={developersImage}
257-
title={t("page-developers-improve-ethereum")}
258-
description={t("page-developers-improve-ethereum-desc")}
259-
alt={t("alt-eth-blocks")}
260-
>
261-
<div>
262-
<ButtonLink href="https://github.com/ethereum/ethereum-org-website">
263-
{t("page-developers-contribute")}
264-
</ButtonLink>
265-
</div>
266-
</CalloutSSR>
236+
<div className="space-y-2">
237+
<h2>Video courses</h2>
238+
<p>
239+
Want to kickstart your professional career in blockchain? These
240+
courses will prepare you to get hired as blockchain developer.
241+
</p>
242+
243+
<div className="w-screen max-xl:-ms-8 xl:w-full">
244+
<VideoCourseSwiper courses={courses} />
245+
</div>
267246
</div>
268247
</MainArticle>
269248

app/[locale]/developers/types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import type { StaticImageData } from "next/image"
22
import type { ReactNode } from "react"
33

4-
export type DevelopersPath = {
4+
type CardInfo = {
55
imgSrc: StaticImageData
66
imgAlt: string
77
title: ReactNode
88
description: ReactNode
9-
url: string
9+
href: string
10+
}
11+
12+
export type DevelopersPath = CardInfo & {
1013
button: ReactNode
1114
tag?: string
1215
}
16+
17+
export type VideoCourse = CardInfo & {
18+
hours: number
19+
}

app/[locale]/developers/utils.ts

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { getLocale, getTranslations } from "next-intl/server"
22

3-
import type { DevelopersPath } from "./types"
3+
import type { DevelopersPath, VideoCourse } from "./types"
44

5+
import cyfrinBasicBanner from "@/public/images/developers/cyfrin-basic-banner.webp"
6+
import cyfrinFoundryAdvancedBanner from "@/public/images/developers/cyfrin-foundry-advanced-banner.webp"
7+
import cyfrinFoundryFundamentalsBanner from "@/public/images/developers/cyfrin-foundry-fundamentals-banner.webp"
8+
import cyfrinSecurityBanner from "@/public/images/developers/cyfrin-security-banner.webp"
9+
import cyfrinSolidityBanner from "@/public/images/developers/cyfrin-solidity-banner.webp"
510
import speedrunNFT from "@/public/images/developers/speedrun-nft.png"
611
import speedrunStakingApp from "@/public/images/developers/speedrun-staking-app.png"
712
import speedrunTokenVendor from "@/public/images/developers/speedrun-token-vendor.png"
@@ -19,7 +24,7 @@ export const getBuilderPaths = async (): Promise<DevelopersPath[]> => {
1924
imgAlt: "Speedrun Ethereum NFT banner",
2025
title: "Simple NFT Example", // t("page-developers-learn"),
2126
description: "Create a public NFT to learn the basics of scaffold-eth.", // t("page-developers-learn-desc"),
22-
url: "https://speedrunethereum.com/challenge/simple-nft-example",
27+
href: "https://speedrunethereum.com/challenge/simple-nft-example",
2328
button: t("page-developers-start-quest"),
2429
tag: "Challenge #0",
2530
},
@@ -28,7 +33,7 @@ export const getBuilderPaths = async (): Promise<DevelopersPath[]> => {
2833
imgAlt: "Speedrun Ethereum staking app banner",
2934
title: "Staking App", // t("page-developers-learn-tutorials"),
3035
description: "Write a smart contract where users pool funds together.", // t("page-developers-learn-tutorials-desc"),
31-
url: "https://speedrunethereum.com/challenge/decentralized-staking",
36+
href: "https://speedrunethereum.com/challenge/decentralized-staking",
3237
button: t("page-developers-start-quest"),
3338
tag: "Challenge #1",
3439
},
@@ -38,9 +43,68 @@ export const getBuilderPaths = async (): Promise<DevelopersPath[]> => {
3843
title: "Create a token", // t("page-developers-resources"),
3944
description:
4045
"Build a digital currency and a smart conract that trades it.", // t("page-developers-start-desc"),
41-
url: "https://speedrunethereum.com/challenge/token-vendor",
46+
href: "https://speedrunethereum.com/challenge/token-vendor",
4247
button: t("page-developers-start-quest"),
4348
tag: "Challenge #2",
4449
},
4550
]
4651
}
52+
53+
export const getVideoCourses = async (): Promise<VideoCourse[]> => {
54+
// const locale = await getLocale()
55+
// const t = await getTranslations({
56+
// locale,
57+
// namespace: "page-developers-index",
58+
// })
59+
60+
return [
61+
{
62+
title: "Blockchain basics",
63+
description:
64+
"Learn how blockchains and smart contracts work, create a wallet, and sign your first transaction.",
65+
hours: 3,
66+
imgSrc: cyfrinBasicBanner,
67+
imgAlt: "Cyfrin Updraft Blockchain basics course banner",
68+
href: "https://updraft.cyfrin.io/courses/blockchain-basics",
69+
},
70+
{
71+
title: "Solidity smart contract development",
72+
description:
73+
"Solidity Programming is your gateway to web3 development in Ethereum compatible ecosystems.",
74+
hours: 3,
75+
imgSrc: cyfrinSolidityBanner,
76+
imgAlt:
77+
"Cyfrin Updraft Solidity smart contract development course banner",
78+
href: "https://updraft.cyfrin.io/courses/solidity",
79+
},
80+
{
81+
title: "Foundry fundamentals",
82+
description:
83+
"Level up your Solidity development skills with Foundry and advanced web3 development concepts and tools.",
84+
hours: 3,
85+
imgSrc: cyfrinFoundryFundamentalsBanner,
86+
imgAlt: "Cyfrin Updraft Foundry fundamentals course banner",
87+
href: "https://updraft.cyfrin.io/courses/foundry",
88+
},
89+
{
90+
title: "Advanced foundry",
91+
description:
92+
"Master web3 development techniques with Advanced Foundry for Solidity smart contract development.",
93+
hours: 3,
94+
imgSrc: cyfrinFoundryAdvancedBanner,
95+
imgAlt: "Cyfrin Updraft Advanced foundry course banner",
96+
href: "https://updraft.cyfrin.io/courses/advanced-foundry",
97+
},
98+
{
99+
title: "Smart contract security", // "Learn smart contract auditing, security, and DeFi",
100+
description:
101+
"Start your career as a smart contract security researcher! Learn smart contract auditing and the best practices.",
102+
hours: 3,
103+
imgSrc: cyfrinSecurityBanner,
104+
imgAlt: "Cyfrin Updraft Blockchain basics course banner",
105+
href: "https://updraft.cyfrin.io/courses/security",
106+
},
107+
]
108+
109+
return []
110+
}
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)