Skip to content

Commit 952afe1

Browse files
authored
feat: custom speakers, individual speaker, and schedule pages for conf (graphql#1497)
* feat: custom speakers, individual speaker, and schedule pages for conf * fix: default avatar pink link * add social links * add trailing ... to long social handles * better social media icons * text justify on bio * better back to speakers link * ci: read gatsby's env var * debug * chore: remove debug * build: fix * feat: show concurrent events and events gloassary * some style fixes * custom event page * fix: speakers breaking the build * fix: event title, show event name * fix: tag styles * fix: sorting inconsistency on different browsers * new color palette for schedule * rearrange event tags * lots of improvements * fix: build issue
1 parent 3c78dfe commit 952afe1

File tree

19 files changed

+1195
-20
lines changed

19 files changed

+1195
-20
lines changed

.github/workflows/CI.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ jobs:
1313

1414
- run: yarn install
1515

16+
- name: Set Sched's API token env
17+
run: echo "SCHED_ACCESS_TOKEN=${{ secrets.SCHED_ACCESS_TOKEN }}" >> .env.production
18+
1619
# Verify it compiles
1720
- run: yarn build
1821

gatsby-node.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
import { ScheduleSession } from "./src/components/Conf/Schedule/ScheduleList"
2+
import { SchedSpeaker } from "./src/components/Conf/Speakers/Speaker"
13
import { GatsbyNode } from "gatsby"
24
import * as path from "path"
35
import { glob } from "glob"
6+
import _ from "lodash"
47
import { updateCodeData } from "./scripts/update-code-data/update-code-data"
58
import { organizeCodeData } from "./scripts/update-code-data/organize-code-data"
69
import { sortCodeData } from "./scripts/update-code-data/sort-code-data"
710
import redirects from "./redirects.json"
811

12+
require("dotenv").config({
13+
path: `.env.${process.env.NODE_ENV}`,
14+
})
15+
916
export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] =
1017
async ({ actions }) => {
1118
const gql = String.raw
@@ -111,12 +118,93 @@ export const onCreatePage: GatsbyNode["onCreatePage"] = async ({
111118
})
112119
}
113120

121+
const fetchData = async (url: string) => {
122+
try {
123+
const response = await fetch(url, {
124+
method: "POST",
125+
headers: {
126+
"Content-Type": "application/json",
127+
"User-Agent": "GraphQL Conf / GraphQL Foundation",
128+
},
129+
})
130+
const data = await response.json()
131+
return data
132+
} catch (error) {
133+
throw new Error(
134+
`Error fetching data from ${url}: ${error.message || error.toString()}`
135+
)
136+
}
137+
}
138+
114139
export const createPages: GatsbyNode["createPages"] = async ({
115140
actions,
116141
graphql,
117142
}) => {
118143
const { createPage, createRedirect } = actions
119144

145+
try {
146+
const schedAccessToken = process.env.SCHED_ACCESS_TOKEN
147+
148+
const schedule: ScheduleSession[] = await fetchData(
149+
`https://graphqlconf23.sched.com/api/session/list?api_key=${schedAccessToken}&format=json`
150+
)
151+
152+
const usernames: { username: string }[] = await fetchData(
153+
`https://graphqlconf23.sched.com/api/user/list?api_key=${schedAccessToken}&format=json&fields=username`
154+
)
155+
156+
// Fetch full info of each speaker individually and concurrently
157+
const speakers: SchedSpeaker[] = await Promise.all(
158+
usernames.map(async user => {
159+
await new Promise(resolve => setTimeout(resolve, 2000)) // 2 second delay between requests, rate limit is 30req/min
160+
return fetchData(
161+
`https://graphqlconf23.sched.com/api/user/get?api_key=${schedAccessToken}&by=username&term=${user.username}&format=json&fields=username,company,position,name,about,location,url,avatar,role,socialurls`
162+
)
163+
})
164+
)
165+
166+
// Create schedule page
167+
createPage({
168+
path: "/conf/schedule",
169+
component: path.resolve("./src/templates/schedule.tsx"),
170+
context: { schedule },
171+
})
172+
173+
// Create schedule events' pages
174+
schedule.forEach(event => {
175+
const eventSpeakers = speakers.filter(e =>
176+
event.speakers?.includes(e.name)
177+
)
178+
179+
createPage({
180+
path: `/conf/schedule/${event.id}`,
181+
component: path.resolve("./src/templates/event.tsx"),
182+
context: {
183+
event,
184+
speakers: eventSpeakers,
185+
},
186+
})
187+
})
188+
189+
// Create speakers list page
190+
createPage({
191+
path: "/conf/speakers",
192+
component: path.resolve("./src/templates/speakers.tsx"),
193+
context: { speakers },
194+
})
195+
196+
// Create a page for each speaker
197+
speakers.forEach(speaker => {
198+
createPage({
199+
path: `/conf/speakers/${speaker.username}`,
200+
component: path.resolve("./src/templates/speaker.tsx"),
201+
context: { speaker, schedule },
202+
})
203+
})
204+
} catch (error) {
205+
console.log("CATCH ME:", error)
206+
}
207+
120208
createRedirect({
121209
fromPath: "/conf/program",
122210
toPath: "/conf/schedule",

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"clsx": "1.2.1",
2727
"codemirror": "5.65.1",
2828
"codemirror-graphql": "1.3.2",
29+
"date-fns": "^2.30.0",
2930
"gatsby": "5.10.0",
3031
"gatsby-plugin-anchor-links": "1.2.1",
3132
"gatsby-plugin-feed": "5.10.0",
@@ -43,13 +44,15 @@
4344
"prismjs": "1.29.0",
4445
"react": "18.2.0",
4546
"react-dom": "18.2.0",
47+
"react-tooltip": "^5.18.1",
4648
"timeago.js": "4.0.2"
4749
},
4850
"devDependencies": {
4951
"@svgr/webpack": "^8.0.0",
5052
"@tailwindcss/typography": "0.5.9",
5153
"@types/codemirror": "5.60.7",
5254
"@types/prismjs": "1.26.0",
55+
"@types/react-tooltip": "^4.2.4",
5356
"@typescript-eslint/parser": "5.59.7",
5457
"autoprefixer": "10.4.14",
5558
"eslint": "8.42.0",

src/assets/css/global.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*@tailwind base;*/
1+
@tailwind base;
22
@tailwind components;
33
@tailwind utilities;
44

src/components/Conf/Header/index.tsx

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,96 @@
1-
import React from "react"
1+
import React, { useState } from "react"
22
import ButtonConf from "../Button"
3+
import { Cross2Icon } from "@radix-ui/react-icons"
34

45
interface LinkItem {
56
text: string
67
href: string
7-
noMobile?: boolean
88
}
99

1010
const links: LinkItem[] = [
1111
{ text: "Attend", href: "/conf/#attend" },
12+
{ text: "Speakers", href: "/conf/speakers/" },
1213
{ text: "Schedule", href: "/conf/schedule/" },
1314
{ text: "Sponsor", href: "/conf/sponsor/" },
1415
{ text: "Partner", href: "/conf/partner/" },
1516
{ text: "FAQ", href: "/conf/faq/" },
1617
]
1718

19+
const LinksList = () => (
20+
<>
21+
{links.map(link => (
22+
<a
23+
key={link.href}
24+
href={link.href}
25+
className="px-2.5 text-2xl md:text-lg text-white font-medium hover:text-white focus:text-white w-max"
26+
>
27+
{link.text}
28+
</a>
29+
))}
30+
</>
31+
)
32+
1833
const HeaderConf = () => {
34+
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false)
35+
36+
const handleDrawerClick = () => {
37+
setMobileDrawerOpen(!mobileDrawerOpen)
38+
}
39+
1940
return (
2041
<header className="bg-[#171E26] gap-2 shadow-lg px-5 h-[70px]">
21-
<div className="container flex items-center h-full gap-5 max-sm:justify-center">
42+
<div className="container flex items-center h-full gap-5 max-sm:justify-end justify-between">
2243
<a href="/conf/" className="shrink-0 max-sm:hidden">
2344
<img
2445
src="/img/conf/graphql-conf-logo-simple.svg"
25-
className="mt-[5px] mr-2 max-md:w-24"
46+
className="mt-[5px] mr-2"
2647
/>
2748
</a>
28-
{links.map(link => (
29-
<a
30-
key={link.href}
31-
href={link.href}
32-
className="text-sm sm:text-lg text-white font-medium hover:text-white focus:text-white"
33-
>
34-
{link.text}
35-
</a>
36-
))}
37-
<ButtonConf
38-
href="https://cvent.me/4zbxz9"
39-
className="ml-auto max-sm:hidden"
49+
50+
{mobileDrawerOpen && (
51+
<div
52+
onClick={handleDrawerClick}
53+
className="bg-black opacity-50 fixed inset-0 z-10"
54+
/>
55+
)}
56+
57+
<nav
58+
className={`lg:transform-none ${
59+
mobileDrawerOpen ? "translate-x-0" : "translate-x-full"
60+
} lg:w-full lg:justify-between justify-start fixed lg:static bg-[#171e26] h-full lg:h-auto right-0 top-0 transition-transform duration-200 ease-in-out z-20 lg:items-center items-start min-w-[75%] flex flex-col lg:flex-row py-10 px-8 lg:p-0`}
61+
>
62+
{mobileDrawerOpen && (
63+
<Cross2Icon
64+
onClick={handleDrawerClick}
65+
className="lg:hidden text-white w-9 h-9 mb-2 cursor-pointer"
66+
/>
67+
)}
68+
<div className="lg:block lg:gap-0 lg:mt-0 flex flex-col gap-5 mt-7">
69+
<LinksList />
70+
</div>
71+
72+
<div className="lg:flex items-center gap-5 mt-5 lg:mt-0 hidden">
73+
<ButtonConf
74+
href="https://cvent.me/4zbxz9"
75+
className="ml-auto lg:visible"
76+
>
77+
Buy a Ticket!
78+
</ButtonConf>
79+
</div>
80+
</nav>
81+
82+
<div
83+
className="flex items-center space-x-4 lg:hidden"
84+
onClick={handleDrawerClick}
4085
>
41-
Buy a Ticket!
42-
</ButtonConf>
86+
<ButtonConf href="https://cvent.me/4zbxz9" className="lg:hidden">
87+
Buy a Ticket!
88+
</ButtonConf>
89+
<div className="space-y-2 cursor-pointer py-3">
90+
<span className="block w-8 h-0.5 bg-white"></span>
91+
<span className="block w-5 h-0.5 bg-white"></span>
92+
</div>
93+
</div>
4394
</div>
4495
</header>
4596
)

0 commit comments

Comments
 (0)