Skip to content

Commit 01166d3

Browse files
committed
Add working groups to /federation page
1 parent cc00620 commit 01166d3

File tree

12 files changed

+453
-282
lines changed

12 files changed

+453
-282
lines changed

scripts/get-github-info/github-stats.json

Lines changed: 264 additions & 264 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2025-12-17T14:45:34.162Z
1+
2025-12-19T20:37:08.887Z

src/app/(main)/community/events/events-scrollview.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1+
import { clsx } from "clsx"
12
import { ReactNode } from "react"
23

3-
export function EventsScrollview({ children }: { children: ReactNode }) {
4+
export function EventsScrollview({
5+
children,
6+
className,
7+
}: {
8+
children: ReactNode
9+
className?: string
10+
}) {
411
return (
5-
<div className="xs:nextra-scrollbar relative -mx-6 grid w-fit max-w-full grid-flow-col grid-rows-2 gap-2 overflow-auto p-6 scrollview-fade-x-16 scrollview-fade has-[>:only-child]:grid-rows-1 max-sm:min-w-[100vw] sm:-mx-1 sm:px-1 lg:gap-4">
12+
<div
13+
className={clsx(
14+
"xs:nextra-scrollbar relative -mx-6 grid w-fit max-w-full grid-flow-col grid-rows-2 gap-2 overflow-auto p-6 scrollview-fade-x-16 scrollview-fade has-[>:only-child]:grid-rows-1 max-sm:min-w-[100vw] sm:-mx-1 sm:px-1 lg:gap-4",
15+
16+
className,
17+
)}
18+
>
619
{children}
720
</div>
821
)

src/app/(main)/community/events/get-all-events.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { join } from "node:path"
22
import { readFile } from "node:fs/promises"
3+
import { cache } from "react"
34

45
import { meetups } from "@/components/meetups"
56

@@ -14,7 +15,7 @@ const WORKING_GROUP_MEETINGS_FILE = join(
1415
"scripts/sync-working-groups/working-group-events.ndjson",
1516
)
1617

17-
export async function getAllEvents() {
18+
export const getAllEvents = cache(async () => {
1819
const workingGroupMeetings = await loadWorkingGroupMeetings()
1920

2021
let pastEvents: AnyEvent[] = []
@@ -77,9 +78,9 @@ export async function getAllEvents() {
7778
upcomingEvents = upcomingEvents.sort(sortByDate)
7879

7980
return { pastEvents, upcomingEvents }
80-
}
81+
})
8182

82-
async function loadWorkingGroupMeetings(): Promise<WorkingGroupMeeting[]> {
83+
export const loadWorkingGroupMeetings = cache(async () => {
8384
try {
8485
const raw = (await readFile(WORKING_GROUP_MEETINGS_FILE, "utf8")).trim()
8586
if (!raw) return []
@@ -91,4 +92,4 @@ async function loadWorkingGroupMeetings(): Promise<WorkingGroupMeeting[]> {
9192
console.error("Failed to read working group meetings", error)
9293
return []
9394
}
94-
}
95+
})

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ export function BlogPostsSection({
4747
{description}
4848
</p>
4949
</div>
50-
<Button href={readAllHref} variant="secondary" size="md">
50+
<Button
51+
href={readAllHref}
52+
variant="secondary"
53+
size="md"
54+
className="md:w-fit"
55+
>
5156
{readAllLabel}
5257
</Button>
5358
</header>

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,15 @@ export function CardsSection({
3333

3434
if (section.kind === "video") {
3535
cta = (
36-
<Button href="/resources/video" variant="secondary" size="md">
37-
Go to Video Resources Library
36+
<Button
37+
href="/resources/video"
38+
variant="secondary"
39+
size="md"
40+
className="shrink-0"
41+
>
42+
<span>
43+
Go to Video <span className="max-xl:hidden">Resources </span>Library
44+
</span>
3845
</Button>
3946
)
4047
}
@@ -80,7 +87,7 @@ export function CardsSection({
8087
<Button
8188
as="span"
8289
variant="primary"
83-
className="pointer-events-auto w-fit cursor-pointer"
90+
className="pointer-events-auto cursor-pointer md:w-fit"
8491
>
8592
Load more
8693
</Button>

src/app/(main)/resources/[category]/categories-config.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { LearnPagePath } from "@/components/learn-aggregator/learn-pages"
2-
import { Kind, Topic } from "@/resources/types"
1+
import type { LearnPagePath } from "@/components/learn-aggregator/learn-pages"
2+
import type { Kind, Topic } from "@/resources/types"
3+
import type { WorkingGroupMeeting } from "@/../scripts/sync-working-groups/sync-working-groups"
34

45
// TODO: If the pages need to be customized further, consider flattening [category]/page.tsx
56
// into multiple page files and defining the following texts in usual JSX.
@@ -95,6 +96,15 @@ export const categoriesConfig: CategoriesConfig = {
9596
heading: "Latest updates on federation & composition",
9697
text: "Read the latest announcements and technical deep dives.",
9798
},
99+
event: {
100+
heading: "Help shape the standards",
101+
text: "Join the Composite Schemas Working Group meetings to participate in the latest developments in Federation and Composite Schemas.",
102+
predicate: (event: WorkingGroupMeeting) => {
103+
return (
104+
event.summary?.toLowerCase().includes("composite schemas") || false
105+
)
106+
},
107+
},
98108
},
99109
},
100110
ai: {
@@ -109,6 +119,15 @@ export const categoriesConfig: CategoriesConfig = {
109119
heading: "Latest insights on AI & GraphQL",
110120
text: "Read the latest announcements and technical deep dives.",
111121
},
122+
event: {
123+
heading: "AI Working Group",
124+
text: "Help define the intersection of GraphQL and AI. Join the working group meetings to contribute to the latest developments.",
125+
predicate: (event: WorkingGroupMeeting) => {
126+
return (
127+
event.summary?.toLowerCase().includes("ai working group") || false
128+
)
129+
},
130+
},
112131
},
113132
},
114133
security: {
@@ -152,6 +171,7 @@ export const sectionKindNames: Record<Kind, string> = {
152171
book: "Books",
153172
"blog-or-newsletter": "Blogs & Newsletters",
154173
docs: "Documentation",
174+
event: "Upcoming events",
155175
}
156176

157177
export function slugify(name: string): string {
@@ -177,6 +197,11 @@ type CategoriesConfig = {
177197
text: string
178198
docs?: LearnPagePath[]
179199
}
200+
event?: {
201+
heading: string
202+
text: string
203+
predicate: (event: WorkingGroupMeeting) => boolean
204+
}
180205
}
181206
}
182207
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { clsx } from "clsx"
2+
import { EventCard } from "@/app/(main)/community/events/event-card"
3+
import { EventsScrollview } from "@/app/(main)/community/events/events-scrollview"
4+
import { loadWorkingGroupMeetings } from "@/app/(main)/community/events/get-all-events"
5+
import type { Topic } from "@/resources/types"
6+
7+
import { categoriesConfig, sectionIds } from "./categories-config"
8+
import { Eyebrow } from "@/_design-system/eyebrow"
9+
import { Button } from "@/app/conf/_design-system/button"
10+
11+
export async function CategoryWorkingGroups({
12+
className,
13+
category,
14+
}: {
15+
className?: string
16+
category: Topic
17+
}) {
18+
const predicate = categoriesConfig[category]?.sections.event?.predicate
19+
if (!predicate) return null
20+
const { heading, text } = categoriesConfig[category].sections.event || {}
21+
22+
const meetings = await loadWorkingGroupMeetings()
23+
24+
const events = meetings
25+
.filter(predicate)
26+
.filter(event => new Date(event.start).getTime() >= Date.now())
27+
28+
if (events.length === 0) return null
29+
30+
return (
31+
<section
32+
id={sectionIds.event}
33+
className={clsx("gql-section gql-container", className)}
34+
>
35+
<header className="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
36+
<div className="flex flex-col gap-4 xl:gap-6">
37+
<Eyebrow>Upcoming events</Eyebrow>
38+
<h2 className="typography-h2 max-w-[700px] text-pretty">{heading}</h2>
39+
<p className="typography-body-md max-w-[577px] text-neu-800">
40+
{text}
41+
</p>
42+
</div>
43+
<Button
44+
href="/community/events"
45+
variant="secondary"
46+
size="md"
47+
className="md:w-fit"
48+
>
49+
View all events
50+
</Button>
51+
</header>
52+
<EventsScrollview
53+
className={clsx(
54+
"mt-4 !max-w-[unset] lg:mt-10",
55+
events.length < 4 && "!grid-rows-1",
56+
)}
57+
>
58+
{events.map(event => (
59+
<EventCard
60+
key={event.id}
61+
href={event.htmlLink}
62+
date={new Date(event.start)}
63+
name={event.summary ?? "Working Group"}
64+
city="Online" // event.location is a zoom link, we could potentially use but we'd have to refactor the event-card to avoid nested anchors
65+
kind="working-group"
66+
className={clsx(events.length < 4 && "!w-full")}
67+
/>
68+
))}
69+
</EventsScrollview>
70+
</section>
71+
)
72+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ export function DocsSection({
4848
{text}
4949
</p>
5050
</div>
51-
<Button href="/learn" variant="secondary" size="md">
51+
<Button
52+
href="/learn"
53+
variant="secondary"
54+
size="md"
55+
className="md:w-fit"
56+
>
5257
Go to Learn
5358
</Button>
5459
</header>

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { CardsSection } from "./cards-section"
2323
import { DocsSection } from "./docs-section"
2424
import { LookingForMore } from "@/components/looking-for-more"
2525
import { unsafeKeys } from "@/app/conf/_design-system/utils/unsafe-keys"
26+
import { CategoryWorkingGroups } from "./category-working-groups"
27+
import { loadWorkingGroupMeetings } from "../../community/events/get-all-events"
2628

2729
interface PageParams {
2830
category: string
@@ -57,6 +59,8 @@ export default async function CategoryPage({ params }: { params: PageParams }) {
5759

5860
if (sections.length === 0) sections = grouped.map(group => group.kind)
5961

62+
sections = await ensureFutureEvents(sections, category)
63+
6064
const activePath: Item[] = [
6165
{
6266
name: "Home",
@@ -88,7 +92,7 @@ export default async function CategoryPage({ params }: { params: PageParams }) {
8892
>
8993
<TocHeroContents
9094
sections={sections.map(sectionLabel)}
91-
className="max-w-[528px]"
95+
className="max-w-[360px] [&:has(li:nth-child(3))]:max-w-[600px]"
9296
/>
9397
</ResourcesHero>
9498

@@ -121,6 +125,25 @@ export default async function CategoryPage({ params }: { params: PageParams }) {
121125
)
122126
}
123127

128+
/**
129+
* if there is no events in the future, we remove the section from the TOC
130+
*/
131+
async function ensureFutureEvents(sections: Kind[], category: Topic) {
132+
if (!sections.includes("event")) return sections
133+
134+
const events = await loadWorkingGroupMeetings()
135+
const predicate = categoriesConfig[category].sections.event?.predicate
136+
137+
if (predicate) {
138+
const futureEvents = events
139+
.filter(predicate)
140+
.filter(event => new Date(event.start).getTime() >= Date.now())
141+
142+
if (futureEvents.length === 0)
143+
return sections.filter(section => section !== "event")
144+
}
145+
}
146+
124147
function uniqueByTitle(resources: ResourceMetadata[]) {
125148
const seen = new Set<string>()
126149
return resources.filter(resource => {
@@ -197,6 +220,10 @@ function CategorySection({
197220
return <DocsSection {...docsSection} className={className} />
198221
}
199222

223+
case "event": {
224+
return <CategoryWorkingGroups category={category} className={className} />
225+
}
226+
200227
default:
201228
return (
202229
<CardsSection

0 commit comments

Comments
 (0)