Skip to content

Commit 24a4756

Browse files
committed
Add breadcrumbs, extract cards section
1 parent eeda91b commit 24a4756

File tree

4 files changed

+142
-74
lines changed

4 files changed

+142
-74
lines changed

src/app/(main)/resources/[category]/blog-posts-section.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { Button } from "@/app/conf/_design-system/button"
44
import { Eyebrow } from "@/_design-system/eyebrow"
55
import { BlogCard } from "@/components/blog-page/blog-card"
66

7+
import { sectionId } from "./texts"
8+
79
export interface BlogPost {
810
href: string
911
title: string
@@ -28,7 +30,10 @@ export function BlogPostsSection({
2830
readAllLabel = "Read all GraphQL stories",
2931
}: BlogPostsSectionProps) {
3032
return (
31-
<section className="gql-container gql-section flex flex-col gap-10 lg:gap-16">
33+
<section
34+
id={sectionId("Blog posts")}
35+
className="gql-container gql-section flex flex-col gap-10 lg:gap-16"
36+
>
3237
<header className="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
3338
<div className="flex flex-col gap-3">
3439
<Eyebrow>Blog posts</Eyebrow>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Kind, ResourceMetadata, Topic } from "@/resources/types"
2+
import { Eyebrow } from "@/_design-system/eyebrow"
3+
import { Button } from "@/app/conf/_design-system/button"
4+
5+
import { ResourceHubCard } from "../resource-hub-card"
6+
7+
import { texts, sectionKindNames, sectionId } from "./texts"
8+
9+
function sectionLabel(kind: Kind) {
10+
return sectionKindNames[kind] ?? `${kind[0].toUpperCase()}${kind.slice(1)}`
11+
}
12+
13+
export function CardsSection({
14+
section,
15+
category,
16+
}: {
17+
section: { kind: Kind; resources: ResourceMetadata[] }
18+
category: Topic
19+
}) {
20+
const sectionData = texts[category].sections[section.kind]
21+
const heading = sectionData?.heading ?? sectionLabel(section.kind)
22+
const text = sectionData?.text
23+
const label = sectionLabel(section.kind)
24+
25+
return (
26+
<section
27+
id={sectionId(label)}
28+
className="gql-container gql-section flex flex-col gap-6"
29+
>
30+
<header className="flex items-center justify-between gap-4">
31+
<div className="flex flex-col gap-3">
32+
<Eyebrow>{sectionKindNames[section.kind]}</Eyebrow>
33+
<h2 className="typography-h3 text-pretty">{heading}</h2>
34+
{text && <p className="typography-body-md text-neu-800">{text}</p>}
35+
</div>
36+
{section.kind === "video" ? (
37+
<Button href="/resources/video" variant="primary" size="md">
38+
Go to full Video Resources Library
39+
</Button>
40+
) : (
41+
<span className="typography-menu text-neu-600">
42+
{section.resources.length} resources
43+
</span>
44+
)}
45+
</header>
46+
47+
<ul className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
48+
{section.resources.map(resource => (
49+
<li key={resource.url}>
50+
<ResourceHubCard
51+
href={resource.url}
52+
title={resource.title}
53+
author={resource.author}
54+
tags={resource.tags}
55+
/>
56+
</li>
57+
))}
58+
</ul>
59+
</section>
60+
)
61+
}

src/app/(main)/resources/[category]/page.tsx

Lines changed: 36 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -11,46 +11,14 @@ import {
1111
type Topic,
1212
} from "@/resources/types"
1313

14-
import { categoryNames, categorySubtitles } from "../subtitles"
1514
import { ResourcesHero } from "../resources-hero"
1615
import { TocHeroContents } from "@/components/toc-hero"
17-
import { Eyebrow } from "@/_design-system/eyebrow"
18-
import { ResourceHubCard } from "../resource-hub-card"
1916
import { BlogPostsSection } from "./blog-posts-section"
2017
import { CategoryToolsLibrariesSection } from "./category-tools-libraries-section"
18+
import { Breadcrumbs } from "@/_design-system/breadcrumbs"
2119

22-
const sectionKindNames: Record<Kind, string> = {
23-
video: "Featured videos",
24-
blog: "Blog posts",
25-
"tools-and-libraries": "Tools & Libraries",
26-
guide: "Guides",
27-
book: "Books",
28-
"blog-or-newsletter": "Blogs & Newsletters",
29-
}
30-
31-
// TODO: I'd prefer to have this in JSX over "JSON" objects
32-
const blogTitles: Partial<Record<Topic, string>> = {
33-
frontend: "Insights for frontend devs",
34-
backend: "Insights for backend devs",
35-
}
36-
37-
const blogDescriptions: Partial<Record<Topic, string>> = {
38-
frontend: "Stay up to date with insights from the GraphQL community.",
39-
backend: "Stay up to date with insights from the GraphQL community.",
40-
}
41-
42-
function sectionHeading(
43-
section: { kind: Kind; resources: ResourceMetadata[] },
44-
category: Topic,
45-
) {
46-
if (section.kind === "video") {
47-
if (category === "frontend") return "Master GraphQL on the frontend"
48-
// todo: paragraph: "Watch talks and tutorials from GraphQL Conf and community experts. See how teams integrate GraphQL on the frontend and learn from real-world case studies."
49-
if (category === "backend") return "Master GraphQL on the backend"
50-
}
51-
52-
return sectionLabel(section.kind)
53-
}
20+
import { sectionKindNames, sectionId, texts } from "./texts"
21+
import { CardsSection } from "./cards-section"
5422

5523
interface PageParams {
5624
category: string
@@ -68,8 +36,8 @@ export async function generateMetadata({
6836
const category = params.category as Topic
6937
if (!topics.includes(category)) return {}
7038

71-
const title = `${categoryNames[category]} Resources`
72-
const description = categorySubtitles[category]
39+
const title = `${texts[category].heading} Resources`
40+
const description = texts[category].subtitle
7341

7442
return { title, description }
7543
}
@@ -82,20 +50,45 @@ export default async function CategoryPage({ params }: { params: PageParams }) {
8250
const deduped = uniqueByTitle(resources)
8351
const grouped = groupByKind(deduped)
8452

53+
const activePath = [
54+
{
55+
name: "Home",
56+
route: "/",
57+
},
58+
{
59+
name: "Resource Hub",
60+
route: "/resources",
61+
},
62+
{
63+
name: texts[category].heading,
64+
route: `/resources/${category}`,
65+
},
66+
].map(item => ({
67+
...item,
68+
title: item.name,
69+
type: "page",
70+
children: [],
71+
frontMatter: {},
72+
}))
73+
8574
return (
8675
<main className="gql-all-anchors-focusable">
8776
<NavbarFixed />
8877

8978
<ResourcesHero
90-
heading={categoryNames[category]}
91-
text={categorySubtitles[category]}
79+
heading={texts[category].heading}
80+
text={texts[category].subtitle}
9281
>
9382
<TocHeroContents
9483
sections={grouped.map(section => sectionLabel(section.kind))}
9584
className="max-w-[528px]"
9685
/>
9786
</ResourcesHero>
9887

88+
<section className="gql-container gql-section">
89+
<Breadcrumbs activePath={activePath} />
90+
</section>
91+
9992
{grouped.map(section => (
10093
<CategorySection
10194
key={section.kind}
@@ -148,11 +141,12 @@ function CategorySection({
148141
}
149142

150143
if (section.kind === "blog") {
144+
const blogSection = texts[category].sections["blog-or-newsletter"]
151145
return (
152146
<BlogPostsSection
153-
title={blogTitles[category] ?? "Insights from the community"}
147+
title={blogSection?.heading ?? "Insights from the community"}
154148
description={
155-
blogDescriptions[category] ??
149+
blogSection?.text ??
156150
"Stay up to date with insights from the GraphQL community."
157151
}
158152
posts={section.resources.map(resource => ({
@@ -165,35 +159,5 @@ function CategorySection({
165159
)
166160
}
167161

168-
return (
169-
<section
170-
id={sectionKindNames[section.kind].toLowerCase().replace(/ /g, "-")}
171-
className="gql-container gql-section flex flex-col gap-6"
172-
>
173-
<header className="flex items-center justify-between gap-4">
174-
<div className="flex flex-col gap-3">
175-
<Eyebrow>{sectionKindNames[section.kind]}</Eyebrow>
176-
<h2 className="typography-h3 text-pretty">
177-
{sectionHeading(section, category)}
178-
</h2>
179-
</div>
180-
<span className="typography-menu text-neu-600">
181-
{section.resources.length} resources
182-
</span>
183-
</header>
184-
185-
<ul className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
186-
{section.resources.map(resource => (
187-
<li key={resource.url}>
188-
<ResourceHubCard
189-
href={resource.url}
190-
title={resource.title}
191-
author={resource.author}
192-
tags={resource.tags}
193-
/>
194-
</li>
195-
))}
196-
</ul>
197-
</section>
198-
)
162+
return <CardsSection section={section} category={category} />
199163
}

src/app/(main)/resources/[category]/texts.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Kind, Topic } from "@/resources/types"
22

3-
// @ts-ignore
43
export const texts: {
54
[key in Topic]: {
65
heading: string
@@ -115,4 +114,43 @@ export const texts: {
115114
},
116115
},
117116
},
117+
"schema-design": {
118+
heading: "Schema Design",
119+
subtitle: "Learn how to design and maintain GraphQL schemas.",
120+
sections: {},
121+
},
122+
"api-platform-and-gateways": {
123+
heading: "API Platform and Gateways",
124+
subtitle: "Learn how to build and deploy API Gateways and Supergraphs.",
125+
sections: {},
126+
},
127+
"developer-experience": {
128+
heading: "Developer Experience",
129+
subtitle: "Learn how to improve your developer experience.",
130+
sections: {},
131+
},
132+
tools: {
133+
heading: "Tools",
134+
subtitle: "Discover the best tools for GraphQL development.",
135+
sections: {},
136+
},
137+
}
138+
139+
export const sectionKindNames: Record<Kind, string> = {
140+
video: "Featured videos",
141+
blog: "Blog posts",
142+
"tools-and-libraries": "Tools & Libraries",
143+
guide: "Guides",
144+
book: "Books",
145+
"blog-or-newsletter": "Blogs & Newsletters",
146+
docs: "Documentation",
147+
}
148+
149+
export function sectionId(name: string): string {
150+
return name
151+
.toLowerCase()
152+
.replace(/ & /g, "-and-")
153+
.replace(/[^a-z0-9-]/g, "")
154+
.replace(/-+/g, "-")
155+
.replace(/^-|-$/g, "")
118156
}

0 commit comments

Comments
 (0)