From a6b5b929885c469404d907a8bfe9690b22c2646f Mon Sep 17 00:00:00 2001 From: raffazizzi Date: Wed, 24 Jul 2024 15:04:32 -0400 Subject: [PATCH 1/8] first stab at migrating to TypeScript --- .gitignore | 2 + gatsby-browser.js | 7 - gatsby-config.js => gatsby-config.ts | 9 +- gatsby-node.js => gatsby-node.ts | 311 +- gatsby-ssr.js => gatsby-ssr.tsx | 13 +- package.json | 4 +- .../{event-time.js => event-time.tsx} | 38 +- src/components/footer.js | 17 - src/components/footer.tsx | 13 + src/components/header.js | 30 - src/components/header.tsx | 22 + src/components/{layout.js => layout.tsx} | 8 +- src/components/{nav.js => nav.tsx} | 7 +- .../{paginator.js => paginator.tsx} | 7 +- src/components/{person.js => person.tsx} | 50 +- .../{research-time.js => research-time.tss} | 24 +- src/components/{seo.js => seo.tsx} | 18 +- .../{supporter-list.js => supporter-list.tsx} | 16 +- .../{taxonomy-list.js => taxonomy-list.tsx} | 7 +- src/pages/{404.js => 404.tsx} | 0 src/pages/{fellowships.js => fellowships.tsx} | 0 src/pages/{index.js => index.tsx} | 0 src/pages/{internships.js => internships.tsx} | 0 .../{opportunities.js => opportunities.tsx} | 0 ...partner-with-us.js => partner-with-us.tsx} | 0 src/pages/{people-past.js => people-past.tsx} | 42 +- src/pages/{people.js => people.tsx} | 40 +- src/pages/{values.js => values.tsx} | 0 src/pages/{what-we-do.js => what-we-do.tsx} | 0 .../{writing-group.js => writing-group.tsx} | 0 src/svg/svg.d.ts | 1 + .../{dialogue-index.js => dialogue-index.tsx} | 46 +- src/templates/{dialogue.js => dialogue.tsx} | 78 +- .../{event-index.js => event-index.tsx} | 30 +- src/templates/{event.js => event.tsx} | 60 +- src/templates/{person.js => person.tsx} | 16 +- .../{post-index.js => post-index.tsx} | 20 +- src/templates/{post.js => post.tsx} | 15 +- .../{research-index.js => research-index.tsx} | 36 +- src/templates/{research.js => research.tsx} | 95 +- tsconfig.json | 102 + yarn.lock | 11800 ++++++++++++++++ 42 files changed, 12563 insertions(+), 421 deletions(-) delete mode 100644 gatsby-browser.js rename gatsby-config.js => gatsby-config.ts (98%) rename gatsby-node.js => gatsby-node.ts (63%) rename gatsby-ssr.js => gatsby-ssr.tsx (58%) rename src/components/{event-time.js => event-time.tsx} (53%) delete mode 100644 src/components/footer.js create mode 100644 src/components/footer.tsx delete mode 100644 src/components/header.js create mode 100644 src/components/header.tsx rename src/components/{layout.js => layout.tsx} (91%) rename src/components/{nav.js => nav.tsx} (82%) rename src/components/{paginator.js => paginator.tsx} (85%) rename src/components/{person.js => person.tsx} (81%) rename src/components/{research-time.js => research-time.tss} (53%) rename src/components/{seo.js => seo.tsx} (86%) rename src/components/{supporter-list.js => supporter-list.tsx} (78%) rename src/components/{taxonomy-list.js => taxonomy-list.tsx} (76%) rename src/pages/{404.js => 404.tsx} (100%) rename src/pages/{fellowships.js => fellowships.tsx} (100%) rename src/pages/{index.js => index.tsx} (100%) rename src/pages/{internships.js => internships.tsx} (100%) rename src/pages/{opportunities.js => opportunities.tsx} (100%) rename src/pages/{partner-with-us.js => partner-with-us.tsx} (100%) rename src/pages/{people-past.js => people-past.tsx} (57%) rename src/pages/{people.js => people.tsx} (70%) rename src/pages/{values.js => values.tsx} (100%) rename src/pages/{what-we-do.js => what-we-do.tsx} (100%) rename src/pages/{writing-group.js => writing-group.tsx} (100%) create mode 100644 src/svg/svg.d.ts rename src/templates/{dialogue-index.js => dialogue-index.tsx} (71%) rename src/templates/{dialogue.js => dialogue.tsx} (64%) rename src/templates/{event-index.js => event-index.tsx} (84%) rename src/templates/{event.js => event.tsx} (70%) rename src/templates/{person.js => person.tsx} (84%) rename src/templates/{post-index.js => post-index.tsx} (88%) rename src/templates/{post.js => post.tsx} (80%) rename src/templates/{research-index.js => research-index.tsx} (70%) rename src/templates/{research.js => research.tsx} (54%) create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 7d1f355732..ffa3cc4767 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +gatsby-types.d.ts + # Logs logs *.log diff --git a/gatsby-browser.js b/gatsby-browser.js deleted file mode 100644 index b1e5c316b7..0000000000 --- a/gatsby-browser.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Implement Gatsby's Browser APIs in this file. - * - * See: https://www.gatsbyjs.org/docs/browser-apis/ - */ - -// You can delete this file if you're not using it diff --git a/gatsby-config.js b/gatsby-config.ts similarity index 98% rename from gatsby-config.js rename to gatsby-config.ts index 7786e562a6..d39f8ddf22 100644 --- a/gatsby-config.js +++ b/gatsby-config.ts @@ -1,10 +1,13 @@ -require("dotenv").config(); +import "dotenv/config"; +import type { GatsbyConfig } from "gatsby"; + const baseId = process.env.AIRTABLE_MITH_BASE_ID; const basePath = process.env.BASEPATH -module.exports = { +const config: GatsbyConfig = { pathPrefix: basePath, + graphqlTypegen: true, siteMetadata: { title: `MITH`, siteUrl: "https://mith.umd.edu", @@ -438,3 +441,5 @@ module.exports = { }, ], } + +export default config; \ No newline at end of file diff --git a/gatsby-node.js b/gatsby-node.ts similarity index 63% rename from gatsby-node.js rename to gatsby-node.ts index 7fc56123e2..1b8ba8db36 100644 --- a/gatsby-node.js +++ b/gatsby-node.ts @@ -1,20 +1,31 @@ -const path = require('path') - -exports.createPages = async ({ actions: { createPage }, graphql }) => { - await makePeople(createPage, graphql) - await makePosts(createPage, graphql) - await makePostIndex(createPage, graphql) - await makeResearch(createPage, graphql) - await makeResearchIndex(createPage, graphql) - await makeEvents(createPage, graphql) - await makeEventIndex(createPage, graphql) - await makeDialogues(createPage, graphql) - await makeDialogueIndex(createPage, graphql) +import type { Actions, CreatePagesArgs, GatsbyNode } from "gatsby"; +import path from "path"; + +interface IMakePages { + createPage: Actions["createPage"] + graphql: CreatePagesArgs["graphql"] } -async function makePeople(createPage, graphql) { +type PeopleImage = NonNullable["headshot"] + +export const createPages: GatsbyNode["createPages"] = async ({ actions: { createPage }, graphql }) => { + + const utils: IMakePages = {createPage, graphql}; + + await makePeople(utils) + await makePosts(utils) + await makePostIndex(utils) + await makeResearch(utils) + await makeResearchIndex(utils) + await makeEvents(utils) + await makeEventIndex(utils) + await makeDialogues(utils) + await makeDialogueIndex(utils) +} + +async function makePeople({createPage, graphql}: IMakePages) { const results = await graphql(` - query PagePeopleQuery { + query PagePeople { allAirtablePeople(filter: {data: {group_type: {eq: "Staff"}}}) { nodes { data { @@ -58,15 +69,13 @@ async function makePeople(createPage, graphql) { } `) - for (const node of results.data.allAirtablePeople.nodes) { + const {nodes} = (results.data as Queries.PagePeopleQuery).allAirtablePeople; + + for (const node of nodes) { const person = node.data - // Simplify fields - if (person.bio) { - person.bio = person.bio.childMarkdownRemark.html - } createPage({ - path: `/people/${person.id}/`, - component: require.resolve(`./src/templates/person.js`), + path: `/people/${person?.id}/`, + component: path.resolve(`./src/templates/person.tsx`), context: { ...person } @@ -74,9 +83,9 @@ async function makePeople(createPage, graphql) { } } -async function makePosts(createPage, graphql) { +async function makePosts({createPage, graphql}: IMakePages) { const results = await graphql(` - query { + query PagePosts { allFile(filter: {sourceInstanceName: {eq: "news"}}) { nodes { childMarkdownRemark { @@ -87,13 +96,18 @@ async function makePosts(createPage, graphql) { } } `) + + const {nodes} = (results.data as Queries.PagePostsQuery).allFile; - for (const _post of results.data.allFile.nodes) { + for (const _post of nodes) { const post = _post.childMarkdownRemark - const slug = path.basename(post.fileAbsolutePath, '.md') + if (!post?.fileAbsolutePath) { + console.error(`No markdown path for post.`) + } + const slug = path.basename(post?.fileAbsolutePath || "", '.md') createPage({ path: `/news/${slug}/`, - component: require.resolve(`./src/templates/post.js`), + component: path.resolve(`./src/templates/post.tsx`), context: { slug, ...post @@ -102,10 +116,10 @@ async function makePosts(createPage, graphql) { } } -async function makePostIndex(createPage, graphql) { +async function makePostIndex({createPage, graphql}: IMakePages) { console.log(`making post index`) const results = await graphql(` - query { + query PagePostIndex { allFile(filter: {sourceInstanceName: {eq: "news"}}) { pageInfo { itemCount @@ -114,14 +128,14 @@ async function makePostIndex(createPage, graphql) { } `) - const numPosts = results.data.allFile.pageInfo.itemCount + const numPosts = (results.data as Queries.PagePostIndexQuery).allFile.pageInfo.itemCount const postsPerPage = 25 const numPages = Math.ceil(numPosts / postsPerPage) Array.from({ length: numPages }).forEach((_, i) => { createPage({ path: i === 0 ? `/news` : `/news/${i + 1}`, - component: path.resolve("./src/templates/post-index.js"), + component: path.resolve("./src/templates/post-index.tsx"), context: { limit: postsPerPage, skip: i * postsPerPage, @@ -132,9 +146,9 @@ async function makePostIndex(createPage, graphql) { }) } -async function makeResearchIndex(createPage, graphql) { +async function makeResearchIndex({createPage, graphql}: IMakePages) { const results = await graphql(` - query { + query PageResearchIndex { allAirtableResearchItems { pageInfo { itemCount @@ -143,14 +157,14 @@ async function makeResearchIndex(createPage, graphql) { } `) - const numItems = results.data.allAirtableResearchItems.pageInfo.itemCount + const numItems = (results.data as Queries.PageResearchIndexQuery).allAirtableResearchItems.pageInfo.itemCount const itemsPerPage = 30 const numPages = Math.ceil(numItems / itemsPerPage) Array.from({ length: numItems }).forEach((_, i) => { createPage({ path: i === 0 ? `/research` : `/research/${i + 1}/`, - component: path.resolve("./src/templates/research-index.js"), + component: path.resolve("./src/templates/research-index.tsx"), context: { limit: itemsPerPage, skip: i * itemsPerPage, @@ -161,9 +175,9 @@ async function makeResearchIndex(createPage, graphql) { }) } -async function makeResearch(createPage, graphql) { +async function makeResearch({createPage, graphql}: IMakePages) { const results = await graphql(` - query { + query PageResearch { allAirtableResearchItems { nodes { data { @@ -229,7 +243,7 @@ async function makeResearch(createPage, graphql) { id } } - } + } id } linked_directors { @@ -317,24 +331,38 @@ async function makeResearch(createPage, graphql) { } `) - for (const node of results.data.allAirtableResearchItems.nodes) { - const item = node.data + for (const node of (results.data as Queries.PageResearchQuery).allAirtableResearchItems.nodes) { + + // These extended types are defined to accommodate the data merging below that brings together participants with their affiliations. + // TODO: Can these types be simplified? + type Affiliation = + NonNullable["linked_internal_participant_affiliations"]>[number] + | NonNullable["linked_external_participant_affiliations"]>[number] + type ExtendedLinkedParticipant = NonNullable["linked_participants"]>[number] &{ + affiliations?: Affiliation[] + } + type ExtendedPageResearchQuery = Queries.PageResearchQuery["allAirtableResearchItems"]["nodes"][number]["data"] & { + participants?: ExtendedLinkedParticipant[] + directors?: ExtendedLinkedParticipant[] + } + + const item = node.data as ExtendedPageResearchQuery if (item.linked_participants) { item.participants = item.linked_participants.map(p => { - const new_p = Object.assign({}, p); - const id = p.data.id + const new_p: ExtendedLinkedParticipant = Object.assign({}, p); + const id = p?.data?.id - if (p.data.group_type.includes("Staff") || p.data.group_type.includes("Past")) { + if (p?.data?.group_type?.includes("Staff") || p?.data?.group_type?.includes("Past")) { // Lookup staff members in internal participants if (item.linked_internal_participant_affiliations) { for (const aff of item.linked_internal_participant_affiliations) { - if (!aff.data.linked_person[0]) continue; - if (aff.data.linked_person[0].data.id === id) { - if (new_p.data.affiliations) { - new_p.data.affiliations.push(aff) + if (!aff?.data?.linked_person?.[0]) continue; + if (aff?.data?.linked_person[0].data?.id === id) { + if (new_p.affiliations) { + new_p.affiliations.push(aff) } else { - new_p.data.affiliations = [aff] + new_p.affiliations = [aff] } break; } @@ -344,12 +372,12 @@ async function makeResearch(createPage, graphql) { // Lookup other people in external participants if (item.linked_external_participant_affiliations) { for (const aff of item.linked_external_participant_affiliations) { - if (!aff.data.linked_person[0].data) continue; - if (aff.data.linked_person[0].data.id === id) { - if (new_p.data.affiliations) { - new_p.data.affiliations.push(aff) + if (!aff?.data?.linked_person?.[0]?.data) continue; + if (aff?.data?.linked_person[0].data?.id === id) { + if (new_p.affiliations) { + new_p.affiliations.push(aff) } else { - new_p.data.affiliations = [aff] + new_p.affiliations = [aff] } break; } @@ -361,16 +389,16 @@ async function makeResearch(createPage, graphql) { } if (item.linked_directors) { item.directors = item.linked_directors.map(p => { - const new_p = Object.assign({}, p); - const id = p.data.id + const new_p: ExtendedLinkedParticipant = Object.assign({}, p); + const id = p?.data?.id if (item.linked_director_affiliations) { for (const aff of item.linked_director_affiliations) { - if (!aff.data.linked_person[0].data) continue; + if (!aff?.data?.linked_person?.[0]?.data) continue; if (aff.data.linked_person[0].data.id === id) { - if (new_p.data.affiliations) { - new_p.data.affiliations.push(aff) + if (new_p.affiliations) { + new_p.affiliations.push(aff as unknown as Affiliation) } else { - new_p.data.affiliations = [aff] + new_p.affiliations = [aff as unknown as Affiliation] } break; } @@ -382,7 +410,7 @@ async function makeResearch(createPage, graphql) { createPage({ path: `/research/${item.id}/`, - component: require.resolve(`./src/templates/research.js`), + component: path.resolve(`./src/templates/research.tsx`), context: { ...item } @@ -391,9 +419,9 @@ async function makeResearch(createPage, graphql) { } -async function makeEventIndex(createPage, graphql) { +async function makeEventIndex({createPage, graphql}: IMakePages) { const results = await graphql(` - query { + query PageEventIndex { allAirtableEvents { pageInfo { itemCount @@ -402,14 +430,15 @@ async function makeEventIndex(createPage, graphql) { } `) - const numItems = results.data.allAirtableEvents.pageInfo.itemCount + + const numItems = (results.data as Queries.PageEventIndexQuery).allAirtableEvents.pageInfo.itemCount const itemsPerPage = 30 const numPages = Math.ceil(numItems / itemsPerPage) Array.from({ length: numItems }).forEach((_, i) => { createPage({ path: i === 0 ? `/events/` : `/events/${i + 1}/`, - component: path.resolve("./src/templates/event-index.js"), + component: path.resolve("./src/templates/event-index.tsx"), context: { limit: itemsPerPage, skip: i * itemsPerPage, @@ -421,9 +450,9 @@ async function makeEventIndex(createPage, graphql) { } -async function makeEvents(createPage, graphql) { +async function makeEvents({createPage, graphql}: IMakePages) { const results = await graphql(` - query { + query PageEvent { allAirtableEvents { nodes { data { @@ -540,6 +569,7 @@ async function makeEvents(createPage, graphql) { backgroundColor: "rgba(255,255,255,0)" ) } + publicURL } } } @@ -614,22 +644,34 @@ async function makeEvents(createPage, graphql) { } `) - for (const node of results.data.allAirtableEvents.nodes) { - const item = node.data + // These extended types are defined to accommodate the data merging below that brings together participants with their affiliations. + // TODO: Can these types be simplified? + type Affiliation = + NonNullable["linked_participant_affiliations"]>[number] + type ExtendedLinkedParticipantEvent = NonNullable["linked_participants"]>[number] & { + affiliations?: Affiliation[] + } + type ExtendedPageEventQuery = Queries.PageEventQuery["allAirtableEvents"]["nodes"][number]["data"] & { + participants?: ExtendedLinkedParticipantEvent[] + directors?: ExtendedLinkedParticipantEvent[] + } + + for (const node of (results.data as Queries.PageEventQuery).allAirtableEvents.nodes) { + const item = node.data as ExtendedPageEventQuery // Attach linked participant affiliations if (item.linked_participants) { item.participants = item.linked_participants.map(p => { - const new_p = Object.assign({}, p); - const id = p.data.id + const new_p: ExtendedLinkedParticipantEvent = Object.assign({}, p); + const id = p?.data?.id // Lookup staff members in internal participants if (item.linked_participant_affiliations) { for (const aff of item.linked_participant_affiliations) { - if (!aff.data.linked_person[0]) continue; - if (aff.data.linked_person[0].data.id === id) { - if (new_p.data.affiliations) { - new_p.data.affiliations.push(aff) + if (!aff?.data?.linked_person?.[0]) continue; + if (aff.data.linked_person?.[0]?.data?.id === id) { + if (new_p.affiliations) { + new_p.affiliations.push(aff) } else { - new_p.data.affiliations = [aff] + new_p.affiliations = [aff] } break; } @@ -639,34 +681,42 @@ async function makeEvents(createPage, graphql) { }) } + type ExtendedSpeakerData = NonNullable["speakers"]>[number] & { + headshot: PeopleImage + bio: NonNullable["linked_person"]>[number]>["data"]>["bio"] + } + // Attach headshot and speakers bio from people and identities table if (item.speakers) { item.speakers.forEach(sp => { - results.data.allAirtablePeople.nodes.map(_pers => { + (results.data as Queries.PageEventQuery).allAirtablePeople.nodes.map(_pers => { const pers = _pers.data - if (pers.slug === sp.data.slug) { - if (pers.headshot) { - sp.data.headshot = pers.headshot + if (pers?.slug === sp?.data?.slug) { + if (pers?.headshot && sp?.data) { + (sp.data as unknown as ExtendedSpeakerData).headshot = pers?.headshot } } - }) - results.data.allAirtableIdentities.nodes.map(_pers => { + }); + + (results.data as Queries.PageEventQuery).allAirtableIdentities.nodes.map(_pers => { const pers = _pers.data - if (pers.linked_person[0].data.slug === sp.data.slug) { - if (pers.linked_person[0].data.bio) { - sp.data.bio = pers.linked_person[0].data.bio + if (pers?.linked_person?.[0]?.data?.slug === sp?.data?.slug) { + if (pers?.linked_person?.[0]?.data?.bio && sp?.data) { + (sp.data as unknown as ExtendedSpeakerData).bio = pers.linked_person[0].data.bio } } }) // Lookup speaker affiliation + // TODO: typing here is a little fudged. if (item.speaker_affiliations) { for (const aff of item.speaker_affiliations) { - if (!aff.data.linked_person[0].data) continue; - if (aff.data.linked_person[0].data.slug === sp.data.slug) { - if (sp.data.affiliations) { - sp.data.affiliations.push(aff) + if (!aff?.data?.linked_person?.[0]?.data) continue; + if (aff.data.linked_person[0].data.slug === sp?.data?.slug) { + const spData = sp.data as unknown as ExtendedLinkedParticipantEvent + if (spData.affiliations) { + spData.affiliations.push(aff as Affiliation) } else { - sp.data.affiliations = [aff] + spData.affiliations = [aff as Affiliation] } break; } @@ -674,19 +724,24 @@ async function makeEvents(createPage, graphql) { } }) } + + type ExtendedResearchItem = NonNullable & { + image?: PeopleImage + } + if (item.linked_research_item) { item.linked_research_item.forEach(ri => { - results.data.allAirtableResearchItems.nodes.map(_r => { + (results.data as Queries.PageEventQuery).allAirtableResearchItems.nodes.map(_r => { const r = _r.data - if (ri.data.id === r.id) { - ri.data.image = r.image + if (ri?.data?.id === r?.id && ri?.data && r) { + (ri.data as unknown as ExtendedResearchItem).image = r.image } }) }) } createPage({ path: `/events/${item.id}/`, - component: require.resolve(`./src/templates/event.js`), + component: path.resolve(`./src/templates/event.tsx`), context: { ...item } @@ -695,9 +750,9 @@ async function makeEvents(createPage, graphql) { } -async function makeDialogueIndex(createPage, graphql) { +async function makeDialogueIndex({createPage, graphql}: IMakePages) { const results = await graphql(` - query { + query PageDialogueIndex { allAirtableEvents { pageInfo { itemCount @@ -723,19 +778,23 @@ async function makeDialogueIndex(createPage, graphql) { } `) - const numItems = results.data.allAirtableEvents.pageInfo.itemCount + const numItems = (results.data as Queries.PageDialogueIndexQuery).allAirtableEvents.pageInfo.itemCount const itemsPerPage = 10 const numPages = Math.ceil(numItems / itemsPerPage) - const headshots = results.data.allAirtablePeople.nodes.reduce((people, node) => { - people[node.data.slug] = node.data.headshot ? node.data.headshot : undefined + const peopleAccumulator: {[key: string]: PeopleImage | undefined} = {} + + const headshots = (results.data as Queries.PageDialogueIndexQuery).allAirtablePeople.nodes.reduce((people, node) => { + if (node?.data?.slug) { + people[node.data.slug] = node.data.headshot ? node.data.headshot : undefined + } return people - }, {}) + }, peopleAccumulator) Array.from({ length: numItems }).forEach((_, i) => { createPage({ path: i === 0 ? `/digital-dialogues/` : `/digital-dialogues/${i + 1}/`, - component: path.resolve("./src/templates/dialogue-index.js"), + component: path.resolve("./src/templates/dialogue-index.tsx"), context: { limit: itemsPerPage, skip: i * itemsPerPage, @@ -748,9 +807,9 @@ async function makeDialogueIndex(createPage, graphql) { } -async function makeDialogues(createPage, graphql) { +async function makeDialogues({createPage, graphql}: IMakePages) { const results = await graphql(` - query { + query PageDialogue { allAirtableEvents( filter: {data: {event_type: {eq: "Digital Dialogue"}}} sort: {data: {start_date: DESC}} @@ -888,46 +947,60 @@ async function makeDialogues(createPage, graphql) { } `) - for (const node of results.data.allAirtableEvents.nodes) { +type ExtendedSpeakerData = NonNullable["speakers"]>[number]> & { + headshot: PeopleImage + bio: NonNullable["linked_person"]>[number]>["data"]>["bio"] +} + +type Affiliation = + NonNullable["speaker_affiliations"]>[number] +type ExtendedSpeakers = NonNullable["speakers"]>[number] & { + affiliations?: Affiliation[] +} + + for (const node of (results.data as Queries.PageDialogueQuery).allAirtableEvents.nodes) { const item = node.data - if (item.speakers) { + if (item?.speakers) { // Attach headshot and speakers bio from people and identities table item.speakers.forEach(sp => { - results.data.allAirtablePeople.nodes.map(_pers => { + (results.data as Queries.PageDialogueQuery).allAirtablePeople.nodes.map(_pers => { const pers = _pers.data - if (pers.slug === sp.data.slug) { - if (pers.headshot) { - sp.data.headshot = pers.headshot + if (pers?.slug === sp?.data?.slug) { + if (pers?.headshot && sp?.data) { + (sp.data as unknown as ExtendedSpeakerData).headshot = pers?.headshot } } - }) - results.data.allAirtableIdentities.nodes.map(_pers => { + }); + (results.data as Queries.PageDialogueQuery).allAirtableIdentities.nodes.map(_pers => { const pers = _pers.data - if (pers.linked_person[0].data.slug === sp.data.slug) { - if (pers.linked_person[0].data.bio) { - sp.data.bio = pers.linked_person[0].data.bio + if (pers?.linked_person?.[0]?.data?.slug === sp?.data?.slug) { + if (pers?.linked_person?.[0]?.data?.bio && sp?.data) { + (sp.data as unknown as ExtendedSpeakerData).bio = pers.linked_person[0].data.bio } } }) // Lookup speaker affiliation + // TODO: typing here is a little fudged. if (item.speaker_affiliations) { for (const aff of item.speaker_affiliations) { - if (!aff.data.linked_person[0].data) continue; - if (aff.data.linked_person[0].data.slug === sp.data.slug) { - if (sp.data.affiliations) { - sp.data.affiliations.push(aff) + if (!aff?.data?.linked_person?.[0]?.data) continue; + if (aff.data.linked_person[0].data.slug === sp?.data?.slug) { + const spData = sp.data as unknown as ExtendedSpeakers + if (spData.affiliations) { + spData.affiliations.push(aff as Affiliation) } else { - sp.data.affiliations = [aff] + spData.affiliations = [aff as Affiliation] } break; } } } + }) } createPage({ - path: `/digital-dialogues/${item.id}/`, - component: require.resolve(`./src/templates/dialogue.js`), + path: `/digital-dialogues/${item?.id}/`, + component: path.resolve(`./src/templates/dialogue.tsx`), context: { ...item } diff --git a/gatsby-ssr.js b/gatsby-ssr.tsx similarity index 58% rename from gatsby-ssr.js rename to gatsby-ssr.tsx index d461b9ed2f..b0755560e7 100644 --- a/gatsby-ssr.js +++ b/gatsby-ssr.tsx @@ -1,17 +1,12 @@ -/** - * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. - * - * See: https://www.gatsbyjs.org/docs/ssr-apis/ - */ import React from 'react'; +import type { GatsbySSR } from "gatsby"; const UMDBrandComponent = [ -] +]; -export const onRenderBody = ( - {setPostBodyComponents}, - pluginOptions +export const onRenderBody: GatsbySSR["onRenderBody"] = ( + {setPostBodyComponents} ) => { setPostBodyComponents(UMDBrandComponent); }; \ No newline at end of file diff --git a/package.json b/package.json index bf22942d9c..16ec734ab7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "develop": "gatsby develop", "test": "playwright test", "clean": "gatsby clean", - "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"" + "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"", + "listt": "tsc --listFiles" }, "repository": { "type": "git", @@ -29,6 +30,7 @@ "@fortawesome/free-regular-svg-icons": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@types/react-helmet": "^6.1.11", "gatsby": "^5.13.6", "gatsby-plugin-feed": "^5.13.1", "gatsby-plugin-google-analytics": "^5.13.1", diff --git a/src/components/event-time.js b/src/components/event-time.tsx similarity index 53% rename from src/components/event-time.js rename to src/components/event-time.tsx index a79eda3374..53f2a910e9 100644 --- a/src/components/event-time.js +++ b/src/components/event-time.tsx @@ -5,35 +5,41 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' dayjs.extend(localizedFormat) -const EventTime = ({start, end, icon}) => { +interface Props { + start: number + end?: number + icon?: string +} + +const EventTime = ({start, end, icon}: Props) => { const iconCalendar = icon ? : '' const iconClock = icon ? : '' - start = dayjs(start) + const startDate = dayjs(start) - let startEl = '' - if (start.hour() === 0) { - startEl =