From 8d2292a3fd1a33a8f683a59cc2c907812cd8ad3e Mon Sep 17 00:00:00 2001 From: pookmish Date: Thu, 1 Feb 2024 14:20:27 -0800 Subject: [PATCH 01/32] Refactored to use GraphQL API (#102) --- .eslintrc.json | 2 +- app/(admin)/admin/paragraph/[id]/page.tsx | 11 - .../paragraph/[id]/paragraph-preview.tsx | 70 - app/(public)/[...slug]/metadata.tsx | 86 +- app/(public)/[...slug]/page.tsx | 135 +- app/(public)/calendar/[id]/page.tsx | 5 + app/(public)/page.tsx | 18 +- app/(public)/search/page.tsx | 62 +- app/(public)/search/results.tsx | 73 - app/(public)/search/search.tsx | 103 + .../study-place/features/[uuid]/page.tsx | 40 +- .../(.)study-place/features/[uuid]/page.tsx | 27 +- app/api/apprevalidate/route.tsx | 15 - app/api/revalidate/route.tsx | 21 + app/api/search/route.tsx | 13 - codegen.ts | 14 + eslintrc.json | 3 +- next-env.d.ts | 1 - package.json | 45 +- src/components/layout/global-message.tsx | 5 +- src/components/layout/header.tsx | 14 +- src/components/menu/fallback-main-menu.tsx | 8 +- src/components/menu/main-menu.tsx | 25 +- src/components/menu/secondary-menu.tsx | 41 +- src/components/node/index.tsx | 19 +- src/components/node/node-card.tsx | 21 +- src/components/node/node-list-display.tsx | 21 +- src/components/node/stanford-course/card.tsx | 26 +- .../node/stanford-course/list-item.tsx | 31 +- .../node/stanford-course/page-display.tsx | 78 +- .../node/stanford-event-series/card.tsx | 15 +- .../node/stanford-event-series/list-item.tsx | 6 +- .../stanford-event-series/page-display.tsx | 38 +- src/components/node/stanford-event/card.tsx | 192 +- .../node/stanford-event/list-item.tsx | 9 +- .../node/stanford-event/page-display.tsx | 205 +- src/components/node/stanford-news/card.tsx | 24 +- .../node/stanford-news/list-item.tsx | 27 +- .../node/stanford-news/page-display.tsx | 57 +- src/components/node/stanford-page/card.tsx | 17 +- .../stanford-page/home-page/today-hours.tsx | 4 +- .../node/stanford-page/list-item.tsx | 9 +- .../node/stanford-page/page-display.tsx | 17 +- src/components/node/stanford-person/card.tsx | 17 +- .../node/stanford-person/horizontal-card.tsx | 38 +- .../node/stanford-person/libguide.tsx | 59 +- .../node/stanford-person/list-item.tsx | 20 +- .../node/stanford-person/page-display.tsx | 159 +- .../node/stanford-person/vertical-card.tsx | 40 +- .../node/stanford-publication/card.tsx | 14 +- .../node/stanford-publication/list-item.tsx | 15 +- .../stanford-publication/page-display.tsx | 97 +- src/components/node/sul-library/card.tsx | 6 +- .../node/sul-library/library-header.tsx | 49 +- src/components/node/sul-library/list-item.tsx | 6 +- .../node/sul-library/page-display.tsx | 26 +- src/components/node/sul-study-place/card.tsx | 44 +- .../node/sul-study-place/list-item.tsx | 5 +- .../sul-study-place/study-place-features.tsx | 7 +- src/components/paragraph/index.tsx | 132 +- src/components/paragraph/rows/one-column.tsx | 4 +- src/components/paragraph/rows/rows.tsx | 56 +- .../paragraph/rows/three-column.tsx | 12 +- src/components/paragraph/rows/two-column.tsx | 14 +- src/components/paragraph/stanford-banner.tsx | 29 +- src/components/paragraph/stanford-card.tsx | 43 +- src/components/paragraph/stanford-entity.tsx | 33 +- .../paragraph/stanford-image-gallery.tsx | 130 +- src/components/paragraph/stanford-lists.tsx | 198 +- .../paragraph/stanford-media-caption.tsx | 27 +- src/components/paragraph/stanford-spacer.tsx | 4 +- src/components/paragraph/stanford-wysiwyg.tsx | 4 +- src/components/paragraph/sul-button.tsx | 37 +- src/components/paragraph/sul-collection.tsx | 58 +- .../paragraph/sul-contact-card/index.tsx | 9 +- .../sul-contact-card/manual-fields-card.tsx | 60 +- .../node-reference-card-hours.tsx | 2 +- .../sul-contact-card/node-reference-card.tsx | 52 +- .../paragraph/sul-featured-collection.tsx | 85 +- src/components/paragraph/sul-libguides.tsx | 9 +- src/components/patterns/banner.tsx | 23 +- src/components/patterns/card.tsx | 86 +- .../patterns/elements/drupal-link.tsx | 11 +- src/components/patterns/horizontal-card.tsx | 59 +- src/components/patterns/link.tsx | 7 +- src/components/patterns/lockup.tsx | 4 +- src/components/patterns/wave.tsx | 4 +- src/components/utils/conditional.tsx | 16 - src/components/views/card-list.tsx | 4 +- .../shared-tags/shared-tags-card-view.tsx | 25 +- .../stanford-courses/course-card-view.tsx | 24 +- .../stanford-courses/course-list-view.tsx | 25 +- .../stanford-events/events-card-view.tsx | 25 +- .../stanford-events/events-list-view.tsx | 41 +- .../views/stanford-news/news-card-view.tsx | 24 +- .../views/stanford-news/news-list-view.tsx | 24 +- .../views/stanford-page/page-card-view.tsx | 24 +- .../views/stanford-page/page-list-view.tsx | 40 +- .../stanford-person/person-card-view.tsx | 23 +- .../publications-apa-view.tsx | 23 +- .../publications-chicago-view.tsx | 22 +- .../sul-study-place/study-place-filtering.tsx | 47 +- .../study-places-filtered-cards.tsx | 17 +- src/components/views/view.tsx | 84 +- src/lib/drupal/drupal.d.ts | 23 + src/lib/drupal/get-menu.tsx | 59 - src/lib/drupal/get-paths.tsx | 47 +- src/lib/drupal/get-resource.tsx | 115 +- src/lib/drupal/get-view.tsx | 28 - src/lib/drupal/is-draft-mode.tsx | 2 +- src/lib/drupal/translate-path.tsx | 51 - src/lib/drupal/utils.tsx | 16 +- src/lib/fetch-components.tsx | 17 - src/lib/format-html.tsx | 21 +- src/lib/gql/__generated__/drupal.ts | 5515 +++++++++++++++++ src/lib/gql/entity-queries.drupal.gql | 166 + src/lib/gql/fetcher.tsx | 55 + src/lib/gql/fragments.drupal.gql | 788 +++ src/lib/gql/menu.drupal.gql | 26 + src/lib/gql/route-query.drupal.gql | 18 + src/lib/gql/view-queries.drupal.gql | 114 + src/lib/hooks/useActiveTrail.tsx | 10 +- src/pages/api/revalidate.tsx | 17 - yarn.lock | 4185 +++++++++++-- 124 files changed, 12289 insertions(+), 2919 deletions(-) delete mode 100644 app/(admin)/admin/paragraph/[id]/page.tsx delete mode 100644 app/(admin)/admin/paragraph/[id]/paragraph-preview.tsx delete mode 100644 app/(public)/search/results.tsx create mode 100644 app/(public)/search/search.tsx delete mode 100644 app/api/apprevalidate/route.tsx create mode 100644 app/api/revalidate/route.tsx delete mode 100644 app/api/search/route.tsx create mode 100644 codegen.ts delete mode 100644 src/components/utils/conditional.tsx delete mode 100644 src/lib/drupal/get-menu.tsx delete mode 100644 src/lib/drupal/get-view.tsx delete mode 100644 src/lib/drupal/translate-path.tsx delete mode 100644 src/lib/fetch-components.tsx create mode 100644 src/lib/gql/__generated__/drupal.ts create mode 100644 src/lib/gql/entity-queries.drupal.gql create mode 100644 src/lib/gql/fetcher.tsx create mode 100644 src/lib/gql/fragments.drupal.gql create mode 100644 src/lib/gql/menu.drupal.gql create mode 100644 src/lib/gql/route-query.drupal.gql create mode 100644 src/lib/gql/view-queries.drupal.gql delete mode 100644 src/pages/api/revalidate.tsx diff --git a/.eslintrc.json b/.eslintrc.json index 3b5b147e..621fe13c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,5 +11,5 @@ ] }, "plugins": ["unused-imports"], - "ignorePatterns": [] + "ignorePatterns": ["**/__generated__/**/*"] } diff --git a/app/(admin)/admin/paragraph/[id]/page.tsx b/app/(admin)/admin/paragraph/[id]/page.tsx deleted file mode 100644 index 639e3a44..00000000 --- a/app/(admin)/admin/paragraph/[id]/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import ParagraphPreview from "./paragraph-preview"; - -const Page = ({}) => { - return ( -
- -
- ) -} - -export default Page; \ No newline at end of file diff --git a/app/(admin)/admin/paragraph/[id]/paragraph-preview.tsx b/app/(admin)/admin/paragraph/[id]/paragraph-preview.tsx deleted file mode 100644 index 537c0688..00000000 --- a/app/(admin)/admin/paragraph/[id]/paragraph-preview.tsx +++ /dev/null @@ -1,70 +0,0 @@ -"use client"; - -import {useCallback, useEffect, useLayoutEffect, useRef, useState} from "react"; -import {DrupalParagraph} from "next-drupal"; -import {deserialize} from "@/lib/drupal/deserialize"; -import dynamic from "next/dynamic"; -import {ArrowPathIcon} from "@heroicons/react/20/solid"; - -const Paragraph = dynamic(() => - import('../../../../../src/components/paragraph/index'), { - loading: () => -}); - -const ParagraphPreview = ({}) => { - const previewRef = useRef(null); - const [paragraph, setParagraph] = useState(null); - const [iframeId, setIframeId] = useState(null); - - const setParagraphData = useCallback(({data}: { data: string }) => { - try { - const jsonData = JSON.parse(data); - setParagraph(deserialize(jsonData) as DrupalParagraph) - } catch (e) { - setIframeId(data) - } - }, []); - - const emitComponentHeight = useCallback(() => { - - if ((previewRef.current?.clientHeight || 0) < 100) { - setTimeout(emitComponentHeight, 300); - return; - } - - if (iframeId && paragraph) { - const message = JSON.stringify({ - id: iframeId, - height: previewRef.current?.clientHeight - }) - window.parent.postMessage(message, '*'); - } - }, [iframeId, paragraph]); - - useEffect(() => { - window.addEventListener('message', setParagraphData); - window.addEventListener('resize', emitComponentHeight) - - return () => { - window.removeEventListener('message', setParagraphData); - window.removeEventListener('resize', emitComponentHeight) - } - }, [setParagraphData, emitComponentHeight]) - - useEffect(() => { - if (iframeId && paragraph) return; - - window.parent.postMessage(JSON.stringify({message: 'refresh'}), '*'); - }, [iframeId, paragraph]) - - useLayoutEffect(() => emitComponentHeight(), [emitComponentHeight, paragraph]); - - return ( -
- {paragraph && - - } -
- ) -} -export default ParagraphPreview; diff --git a/app/(public)/[...slug]/metadata.tsx b/app/(public)/[...slug]/metadata.tsx index 7e87f5d0..68129f08 100644 --- a/app/(public)/[...slug]/metadata.tsx +++ b/app/(public)/[...slug]/metadata.tsx @@ -1,26 +1,32 @@ -import {DrupalParagraph} from "next-drupal"; -import {BasicPage, Event, Library, News, Person, StanfordNode} from "@/lib/drupal/drupal"; +import { + NodeStanfordEvent, NodeStanfordNews, + NodeStanfordPage, NodeStanfordPerson, + NodeSulLibrary, + NodeUnion, + ParagraphStanfordWysiwyg, + ParagraphUnion +} from "@/lib/gql/__generated__/drupal"; -export const getNodeMetadata = (node: StanfordNode): Record => { +export const getNodeMetadata = (node: NodeUnion): Record => { let metadata: Record = {}; - switch (node.type) { - case 'node--stanford_page': + switch (node.__typename) { + case 'NodeStanfordPage': metadata = getMetadataForBasicPage(node); break; - case 'node--stanford_person': + case 'NodeStanfordPerson': metadata = getMetadataForPersonPage(node); break; - case 'node--stanford_event': + case 'NodeStanfordEvent': metadata = getMetadataForEventPage(node); break; - case 'node--stanford_news': + case 'NodeStanfordNews': metadata = getMetadataForNewsPage(node); break; - case 'node--sul_library': + case 'NodeSulLibrary': metadata = getMetadataForBranchPage(node); break; } @@ -30,15 +36,15 @@ export const getNodeMetadata = (node: StanfordNode): Record => { metadataBase: new URL('https://library.stanford.edu'), title: node.title + " | " + process.env.NEXT_PUBLIC_SITE_NAME, other: { - changed: node.changed, - path: node.path?.alias, + changed: node.changed.time, + path: node.path, } }; } -const getMetadataForBranchPage = (node: Library) => { - const firstHtml = getFirstTextFromParagraphs(node.su_library__paragraphs ?? []); - const image = node.su_library__contact_img?.field_media_image || node.su_library__banner?.field_media_image; +const getMetadataForBranchPage = (node: NodeSulLibrary) => { + const firstHtml = getFirstTextFromParagraphs(node.suLibraryParagraphs ?? []); + const image = node.suLibraryContactImg?.mediaImage || node.suLibraryBanner?.mediaImage; return { description: firstHtml ? getPlainText(firstHtml).split(' ').slice(0, 30).join(' ') : '', @@ -48,72 +54,73 @@ const getMetadataForBranchPage = (node: Library) => { description: firstHtml ? getPlainText(firstHtml).split(' ').slice(0, 30).join(' ') : '', images: [ { - url: image?.image_style_uri?.card_956x478, + url: image?.url, width: 956, height: 478, - alt: image?.resourceIdObjMeta?.alt ?? "", + alt: image?.alt || "", } ] } } } -const getMetadataForBasicPage = (node: BasicPage) => { - const firstHtml = getFirstTextFromParagraphs(node.su_page_components ?? []); +const getMetadataForBasicPage = (node: NodeStanfordPage) => { + const firstHtml = getFirstTextFromParagraphs(node.suPageComponents ?? []); return { - description: node.su_page_description ?? (firstHtml ? getPlainText(firstHtml).split(' ').slice(0, 30).join(' ') : ''), + description: node.suPageDescription ?? (firstHtml ? getPlainText(firstHtml).split(' ').slice(0, 30).join(' ') : ''), openGraph: { type: 'website', title: node.title, - description: node.su_page_description, + description: node.suPageDescription, images: [ { - url: node.su_page_image?.field_media_image?.image_style_uri?.card_956x478, + url: node.suPageImage?.mediaImage.url, width: 956, height: 478, - alt: node.su_page_image?.field_media_image?.resourceIdObjMeta?.alt ?? "", + alt: node.suPageImage?.mediaImage.alt || "", } ] } } } -const getMetadataForPersonPage = (node: Person) => { +const getMetadataForPersonPage = (node: NodeStanfordPerson) => { return { - description: node.su_person_full_title, + description: node.suPersonFullTitle, openGraph: { type: "profile", - firstName: node.su_person_first_name, - lastName: node.su_person_last_name + firstName: node.suPersonFirstName, + lastName: node.suPersonLastName } } } -const getMetadataForEventPage = (node: Event) => { +const getMetadataForEventPage = (node: NodeStanfordEvent) => { return { - description: getPlainText(node.body ?? '').split(' ').slice(0, 20).join(' '), + description: getPlainText(node.body?.processed ?? '').split(' ').slice(0, 20).join(' '), } } -const getMetadataForNewsPage = (node: News) => { + +const getMetadataForNewsPage = (node: NodeStanfordNews) => { let publishTime; - if (node.su_news_publishing_date) { - publishTime = new Date(node.su_news_publishing_date).toISOString() + if (node.suNewsPublishingDate) { + publishTime = new Date(node.suNewsPublishingDate.time).toISOString() } - const image = node.su_news_featured_media?.field_media_image || node.su_news_banner?.field_media_image; + const image = node.suNewsFeaturedMedia?.mediaImage || (node.suNewsBanner?.__typename === 'MediaImage' ? node.suNewsBanner.mediaImage : undefined); return { - description: node.su_news_dek, + description: node.suNewsDek, openGraph: { type: "article", - description: node.su_news_dek, + description: node.suNewsDek, publishedTime: publishTime ?? null, - tag: node.su_news_topics?.map(term => term.name) ?? [], + tag: node.suNewsTopics?.map(term => term.name) ?? [], images: [ { - url: image?.image_style_uri?.card_956x478, + url: image?.url, width: 956, height: 478, - alt: image?.resourceIdObjMeta?.alt ?? "", + alt: image?.alt || "", } ] } @@ -124,6 +131,7 @@ const getPlainText = (html: string) => { return html.replace(/(<([^>]+)>)/ig, '').replace(/ +/g, ' '); } -const getFirstTextFromParagraphs = (paragraphs: DrupalParagraph[]) => { - return paragraphs.find(p => p.type === 'paragraph--stanford_wysiwyg')?.su_wysiwyg_text +const getFirstTextFromParagraphs = (paragraphs: ParagraphUnion[]) => { + const firstWysiwyg = paragraphs.find(p => p.__typename === 'ParagraphStanfordWysiwyg') as ParagraphStanfordWysiwyg | undefined; + return firstWysiwyg?.suWysiwygText?.processed; } \ No newline at end of file diff --git a/app/(public)/[...slug]/page.tsx b/app/(public)/[...slug]/page.tsx index fba2c742..6ff5aa00 100644 --- a/app/(public)/[...slug]/page.tsx +++ b/app/(public)/[...slug]/page.tsx @@ -1,118 +1,55 @@ -import {getResourceFromContext} from "@/lib/drupal/get-resource"; -import {getAllDrupalPaths, pathIsValid} from "@/lib/drupal/get-paths"; +import {getNodePaths} from "@/lib/drupal/get-paths"; import NodePageDisplay from "@/components/node"; import {notFound, redirect} from "next/navigation"; -import {translatePathFromContext} from "@/lib/drupal/translate-path"; -import {DrupalMenuLinkContent} from "next-drupal"; import {Metadata} from "next"; import {getNodeMetadata} from "./metadata"; import LibraryHeader from "@/components/node/sul-library/library-header"; -import {PageProps, Params, StanfordNode} from "@/lib/drupal/drupal"; +import {PageProps} from "@/lib/drupal/drupal"; import InternalHeaderBanner from "@/components/patterns/internal-header-banner"; import SecondaryMenu from "@/components/menu/secondary-menu"; -import {getMenu} from "@/lib/drupal/get-menu"; + import {isDraftMode} from "@/lib/drupal/is-draft-mode"; import UnpublishedBanner from "@/components/patterns/unpublished-banner"; import {getPathFromContext} from "@/lib/drupal/utils"; -import {cache} from "react"; +import {getEntityFromPath, getMenu} from "@/lib/gql/fetcher"; +import {NodeUnion} from "@/lib/gql/__generated__/drupal"; export const revalidate = false; +export const dynamic = 'force-static'; -class RedirectError extends Error { - constructor(public message: string) { - super(message); - } -} - -const fetchNodeData = cache(async (params: Params): Promise<{ node: StanfordNode, fullWidth: boolean }> => { - const draftMode = isDraftMode(); +const NodePage = async ({params}: PageProps) => { const path = getPathFromContext({params}); - const valid = await pathIsValid(path) - if (!valid) throw new Error(); - - const pathInfo = await translatePathFromContext({params}, {draftMode}); - - // Check for redirect. - if (pathInfo?.redirect?.[0].to) { - const currentPath = '/' + (typeof params.slug === 'object' ? params.slug.join('/') : params.slug); - const [destination] = pathInfo.redirect; + const {redirect: routeRedirect, entity} = await getEntityFromPath(path, isDraftMode()); - if (destination.to != currentPath) { - throw new RedirectError(destination.to); - } - } + if (routeRedirect) redirect(routeRedirect.url); + if (!entity) notFound(); - if (!pathInfo || !pathInfo.jsonapi) { - throw new Error('Unable to translate path'); - } + const menuItems = await getMenu() - if (params?.slug?.[0] === 'node' && pathInfo?.entity?.path) { - throw new RedirectError(pathInfo.entity.path); - } - - const node = await getResourceFromContext(pathInfo.jsonapi.resourceName, {params}, {draftMode}) - if (!node) throw new Error(); - - const fullWidth: boolean = (node?.type === 'node--stanford_page' && node.layout_selection?.resourceIdObjMeta?.drupal_internal__target_id === 'stanford_basic_page_full') || - (node?.type === 'node--sul_library' && node.layout_selection?.resourceIdObjMeta?.drupal_internal__target_id === 'sul_library_full_width'); - - return {node, fullWidth} -}) - -export const generateMetadata = async ({params}: PageProps): Promise => { - if (isDraftMode()) return {}; - - try { - const {node} = await fetchNodeData(params); - if (!node) return {}; - - return getNodeMetadata(node); - } catch (e) { - // Probably a 404 or redirect page. - } - return {}; -} - -const NodePage = async ({params}: PageProps) => { - let tree: DrupalMenuLinkContent[] = []; - try { - ({tree} = await getMenu('main')); - } catch (e) { - } - - let nodeData; - try { - nodeData = await fetchNodeData(params); - } catch (e) { - if (e instanceof RedirectError) { - redirect(e.message); - } - notFound(); - } - const {node, fullWidth} = nodeData; - if (!node) notFound(); + const fullWidth = (entity.__typename === 'NodeStanfordPage' && entity.layoutSelection?.id === 'stanford_basic_page_full') || + (entity.__typename === 'NodeSulLibrary' && entity.layoutSelection?.id === 'sul_library_full_width'); return (
- {!node.status && + {!entity.status && } - {node.type === 'node--sul_library' && - + {entity.__typename === 'NodeSulLibrary' && + } - {node.type === 'node--stanford_news' && + {entity.__typename === 'NodeStanfordNews' &&

- {node.title} + {entity.title}

- {(node.su_news_topics && node.su_news_topics.length > 0) && + {entity.suNewsTopics &&
- {node.su_news_topics.slice(0, 1).map(topic => + {entity.suNewsTopics.slice(0, 1).map(topic => {topic.name} )}
@@ -121,43 +58,45 @@ const NodePage = async ({params}: PageProps) => { } - {!(node.type === 'node--sul_library' || node.type === 'node--stanford_news') && + {!(entity.__typename === 'NodeSulLibrary' || entity.__typename === 'NodeStanfordNews') &&

- {node.title} + {entity.title}

} {fullWidth &&
- +
} {!fullWidth &&
- +
- +
}
) } -export default NodePage; +export const generateMetadata = async ({params}: PageProps): Promise => { + if (isDraftMode()) return {}; + const path = getPathFromContext({params}); + const {entity} = await getEntityFromPath(path); + return entity ? getNodeMetadata(entity) : {} +} export const generateStaticParams = async () => { - const allPaths = await getAllDrupalPaths(); - const nodePaths = allPaths.get('node'); - - let params: Params[] = []; - if (nodePaths) { - params = nodePaths.map(path => ({slug: path.split('/')})) - } - return process.env.BUILD_COMPLETE === 'true' ? params : params.slice(0, 1); -} \ No newline at end of file + if (process.env.BUILD_COMPLETE !== 'true') return []; + const nodePaths = await getNodePaths(); + return nodePaths.map(path => ({slug: path.split('/')})); +} + +export default NodePage; \ No newline at end of file diff --git a/app/(public)/calendar/[id]/page.tsx b/app/(public)/calendar/[id]/page.tsx index 40bf968b..b545ba3e 100644 --- a/app/(public)/calendar/[id]/page.tsx +++ b/app/(public)/calendar/[id]/page.tsx @@ -7,6 +7,9 @@ export const metadata = { } } +export const revalidate = false; +export const dynamic = 'force-static'; + const Calendar = ({params: {id}}: {params: {id: string}}) => { return (
@@ -25,4 +28,6 @@ const Calendar = ({params: {id}}: {params: {id: string}}) => {
) } + + export default Calendar; diff --git a/app/(public)/page.tsx b/app/(public)/page.tsx index f37b5f0b..940124a3 100644 --- a/app/(public)/page.tsx +++ b/app/(public)/page.tsx @@ -1,28 +1,28 @@ -import {getResourceByPath} from "@/lib/drupal/get-resource"; -import {BasicPage, StanfordParagraph} from "@/lib/drupal/drupal"; import {Metadata} from "next"; import {getNodeMetadata} from "./[...slug]/metadata"; import HomePageBanner from "@/components/node/stanford-page/home-page/home-page-banner"; import {ParagraphRows} from "@/components/paragraph/rows/rows"; -import fetchComponents from "@/lib/fetch-components"; import {notFound} from "next/navigation"; +import {getEntityFromPath} from "@/lib/gql/fetcher"; +import {NodeStanfordPage} from "@/lib/gql/__generated__/drupal"; export const revalidate = false; export const generateMetadata = async (): Promise => { - const node = await getResourceByPath('/'); - return node ? getNodeMetadata(node) : {}; + const {entity} = await getEntityFromPath('/'); + return entity ? getNodeMetadata(entity) : {}; } const Page = async () => { - const node = await getResourceByPath('/'); - if (!node) notFound(); - node.su_page_components = await fetchComponents(node.su_page_components ?? []); + const {entity} = await getEntityFromPath('/') + if (!entity) notFound(); return (
- + {entity.suPageComponents && + + }
) } diff --git a/app/(public)/search/page.tsx b/app/(public)/search/page.tsx index d6f734d9..e7886b58 100644 --- a/app/(public)/search/page.tsx +++ b/app/(public)/search/page.tsx @@ -1,18 +1,62 @@ -import SearchForm from "@/components/search/search-form"; -import SearchResults from "./results"; +import {getSearchIndex} from "@/lib/drupal/get-search-index"; +import Search, {SearchResult} from "./search"; +import {StanfordNode, StanfordParagraph, WysiwygParagraph} from "@/lib/drupal/drupal"; + +export const revalidate = false; +export const dynamic = 'force-static'; const Page = () => { + const search = async (searchString: string): Promise => { + "use server"; + + // This still uses JSON API because GraphQL doesn't have an easy way to search for content. + const searchResults: StanfordNode[] = await getSearchIndex('full_site_content', {params: {'filter[fulltext]': searchString}}); + + return searchResults.map(node => ({ + id: node.id, + title: node.title, + path: node.path.alias, + changed: node.changed, + description: getNodeDescription(node) + })).slice(0, 20) + } + return (
- - +
) } +const getNodeDescription = (node: StanfordNode) => { + + switch (node.type) { + case 'node--stanford_page': + return node.su_page_description || getFirstTextFromParagraphs(node.su_page_components) + + case 'node--stanford_person': + return node.su_person_full_title + + case 'node--stanford_event': + return getTextSnippet(node.body) || getFirstTextFromParagraphs(node.su_event_components) + + case 'node--stanford_news': + return getFirstTextFromParagraphs(node.su_news_components); + + case 'node--sul_library': + return getFirstTextFromParagraphs(node.su_library__paragraphs); + } +} + +const getTextSnippet = (html?: string) => { + const snippet = html?.replace(/(<([^>]+)>)/ig, '').replace(/ +/g, ' ') + .split(' ').slice(0, 50).join(' '); + return snippet ? snippet + '...' : undefined; +} + +const getFirstTextFromParagraphs = (paragraphs?: StanfordParagraph[]) => { + const firstWysiwyg = paragraphs?.find(p => p.__typename === 'paragraph--stanford_wysiwyg') as WysiwygParagraph | undefined; + return getTextSnippet(firstWysiwyg?.su_wysiwyg_text); +} + export default Page; \ No newline at end of file diff --git a/app/(public)/search/results.tsx b/app/(public)/search/results.tsx deleted file mode 100644 index d5f879b5..00000000 --- a/app/(public)/search/results.tsx +++ /dev/null @@ -1,73 +0,0 @@ -"use client"; - -import {useSearchParams} from "next/navigation"; -import Loading from "@/components/patterns/icons/loading"; -import Link from "@/components/patterns/elements/drupal-link"; -import CachedClientFetch from "@/components/utils/cached-client-fetch"; -import useDataFetch from "@/lib/hooks/useDataFetch"; -import {Suspense} from "react"; -import {Metadata} from "next"; -import {StanfordNode} from "@/lib/drupal/drupal"; - -const SearchResults = () => { - return ( - - }> - - - - ) -} - -const SearchResultsList = () => { - const params = useSearchParams(); - const searchString = params ? params.get('q') as string : ''; - const {isLoading, data} = useDataFetch(`/api/search?q=${searchString}`) - - if (isLoading) return - - return ( -
- {(!data || data.length === 0) && -

No results found for your search. Please try again.

- } - {(data && data.length > 0) && -
    - {data.slice(0, 20).map((item, i) => -
  • - -
  • - )} -
- } -
- ) -} - -const SearchResultItem = ({item}: { item: Metadata }) => { - const lastUpdated = item.other?.changed ? new Date(item.other?.changed as string).toLocaleDateString('en-us', { - month: 'long', - day: 'numeric', - year: 'numeric' - }) : null; - - const title = (item.title as string)?.replace(' | ' + process.env.NEXT_PUBLIC_SITE_NAME, ''); - return ( - <> - -

- {title} -

- - {item?.description &&

{item.description}

} - - {lastUpdated && -
- Last Updated: {lastUpdated} -
- } - - ) -} - -export default SearchResults; \ No newline at end of file diff --git a/app/(public)/search/search.tsx b/app/(public)/search/search.tsx new file mode 100644 index 00000000..cbfdee8a --- /dev/null +++ b/app/(public)/search/search.tsx @@ -0,0 +1,103 @@ +"use client"; + +import {FormEvent, useEffect, useRef, useState} from "react"; +import {useSearchParams} from "next/navigation"; +import {ArrowPathIcon} from "@heroicons/react/20/solid"; +import Link from "@/components/patterns/elements/drupal-link"; + +export type SearchResult = { + id: string + title: string + path: string + changed: string + description?: string +} + +const Search = ({search}: { search: (_search: string) => Promise }) => { + const inputRef = useRef(null); + const params = useSearchParams(); + const [results, setResults] = useState([]) + const [searchString, setSearchString] = useState(params?.get('q') || '') + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + search(params?.get('q') || '').then(nodes => setResults(nodes)); + }, [params, search]) + + const onSubmit = (e: FormEvent) => { + e.preventDefault(); + setIsLoading(true) + + const searchString = inputRef.current?.value || ''; + search(searchString).then(results => { + setResults(results); + setSearchString(searchString); + setIsLoading(false) + }); + } + + return ( +
+
+
+ + +
+ +
+ +
+ Showing {results.length} {!searchString ? 'suggestions.' : `results for ${searchString}.`} +
+ + {isLoading && +
+ +
+ } + + {results.length === 0 &&
No results found for your search. Please try another keyword.
} + + {results.length > 0 && +
    + {results.map(result => +
  • + +
  • + )} +
+ } +
+ ) +} + +const SearchResultItem = ({item}: { item: SearchResult }) => { + const lastUpdated = new Date(item.changed as string).toLocaleDateString('en-us', { + month: 'long', + day: 'numeric', + year: 'numeric' + }); + return ( + <> + +

+ {item.title} +

+ + {item?.description &&

{item.description}

} + + {lastUpdated && +
+ Last Updated: {lastUpdated} +
+ } + + ) +} + +export default Search \ No newline at end of file diff --git a/app/(public)/study-place/features/[uuid]/page.tsx b/app/(public)/study-place/features/[uuid]/page.tsx index abc659a9..969d4d23 100644 --- a/app/(public)/study-place/features/[uuid]/page.tsx +++ b/app/(public)/study-place/features/[uuid]/page.tsx @@ -1,8 +1,8 @@ -import {getResource} from "@/lib/drupal/get-resource"; import StudyPlaceFeatures from "@/components/node/sul-study-place/study-place-features"; import InternalHeaderBanner from "@/components/patterns/internal-header-banner"; -import {StudyPlace} from "@/lib/drupal/drupal"; import {notFound} from "next/navigation"; +import {graphqlClient} from "@/lib/gql/fetcher"; +import {NodeUnion} from "@/lib/gql/__generated__/drupal"; export const metadata = { title: 'Study Place Features', @@ -11,13 +11,18 @@ export const metadata = { } } -const Page = async ({params: {uuid}}: {params: {uuid: string}}) => { - const node = await getResource('node--sul_study_place', uuid); - if(!node) notFound(); +export const revalidate = false; +export const dynamic = 'force-static'; + +const Page = async ({params: {uuid}}: { params: { uuid: string } }) => { + const query = await graphqlClient().Node({uuid}) + const node = query.node as NodeUnion; + if (!node) notFound(); + if (node.__typename !== 'NodeSulStudyPlace') notFound(); // Filter out empty terms and deduplicate terms by their ID. - const features = node.sul_study__features?.filter((term, index, self) => - term.name?.length > 0 && index === self.findIndex(t => t.id === term.id) + const features = node.sulStudyFeatures?.filter((term, index, self) => + term.name?.length > 0 && index === self.findIndex(t => t.id === term.id) ) ?? []; return ( @@ -29,17 +34,16 @@ const Page = async ({params: {uuid}}: {params: {uuid: string}}) => {
- ({id: feature.id, name: feature.name}))} - libCal={node.sul_study__libcal_id} - type={node.sul_study__type.name} - /> + ({id: feature.id, name: feature.name}))} + type={node.sulStudyType.name} + />
) diff --git a/app/@modal/(.)study-place/features/[uuid]/page.tsx b/app/@modal/(.)study-place/features/[uuid]/page.tsx index c994989f..1c9dd2d6 100644 --- a/app/@modal/(.)study-place/features/[uuid]/page.tsx +++ b/app/@modal/(.)study-place/features/[uuid]/page.tsx @@ -1,14 +1,16 @@ import InterceptionModal from "@/components/patterns/modals/interception-modal"; -import {getResource} from "@/lib/drupal/get-resource"; import StudyPlaceFeatures from "@/components/node/sul-study-place/study-place-features"; -import {DrupalTaxonomyTerm} from "next-drupal"; -import {StudyPlace} from "@/lib/drupal/drupal"; +import {graphqlClient} from "@/lib/gql/fetcher"; +import {NodeUnion} from "@/lib/gql/__generated__/drupal"; const Page = async ({params: {uuid}}: { params: { uuid: string } }) => { + const query = await graphqlClient().Node({uuid}) + const node = query.node as NodeUnion; + if (!node) return; + if (node.__typename !== 'NodeSulStudyPlace') return; - const node = await getResource('node--sul_study_place', uuid); // Filter out empty terms and deduplicate terms by their ID. - const features: DrupalTaxonomyTerm[] = node.sul_study__features?.filter((term, index, self) => + const features = node.sulStudyFeatures?.filter((term, index, self) => term.name?.length > 0 && index === self.findIndex(t => t.id === term.id) ) ?? []; @@ -16,15 +18,14 @@ const Page = async ({params: {uuid}}: { params: { uuid: string } }) => { ({id: feature.id, name: feature.name}))} - libCal={node.sul_study__libcal_id} - type={node.sul_study__type.name} + type={node.sulStudyType.name} /> ) diff --git a/app/api/apprevalidate/route.tsx b/app/api/apprevalidate/route.tsx deleted file mode 100644 index dedc92a2..00000000 --- a/app/api/apprevalidate/route.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import {NextRequest, NextResponse} from "next/server"; -import {revalidatePath} from "next/cache"; - -export const GET = async (request: NextRequest) => { - const secret = request.nextUrl.searchParams.get('secret'); - if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) { - return NextResponse.json({message: 'Invalid token'}, {status: 403}); - } - const slug = request.nextUrl.searchParams.get('slug'); - if (!slug) { - return NextResponse.json({message: 'Missing slug'}, {status: 400}); - } - revalidatePath(slug) - return NextResponse.json({revalidated: true, path: slug}); -} \ No newline at end of file diff --git a/app/api/revalidate/route.tsx b/app/api/revalidate/route.tsx new file mode 100644 index 00000000..6bce1e88 --- /dev/null +++ b/app/api/revalidate/route.tsx @@ -0,0 +1,21 @@ +import {NextRequest, NextResponse} from "next/server"; +import {revalidatePath, revalidateTag} from "next/cache"; + +export const revalidate = 0; + +export const GET = async (request: NextRequest) => { + + const secret = request.nextUrl.searchParams.get('secret'); + if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) return NextResponse.json({message: 'Invalid token'}, {status: 403}); + + let path = request.nextUrl.searchParams.get('slug'); + if (!path || path.startsWith('/node/')) return NextResponse.json({message: 'Invalid slug'}, {status: 400}); + + revalidatePath(path); + + const tagsInvalidated = ['paths', `paths:${path}`]; + if (path.startsWith('/tags/')) path.substring(6).split('/').map(tag => tagsInvalidated.push(tag)) + + tagsInvalidated.map(tag => revalidateTag(tag)); + return NextResponse.json({revalidated: true, path, tags: tagsInvalidated}); +} \ No newline at end of file diff --git a/app/api/search/route.tsx b/app/api/search/route.tsx deleted file mode 100644 index 683929e4..00000000 --- a/app/api/search/route.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import {NextRequest, NextResponse} from "next/server"; -import {getSearchIndex} from "@/lib/drupal/get-search-index"; -import {DrupalNode} from "next-drupal"; -import {getNodeMetadata} from "../../(public)/[...slug]/metadata"; -import fetchComponents from "@/lib/fetch-components"; -import {StanfordNode} from "@/lib/drupal/drupal"; - -export const GET = async (request: NextRequest) => { - const { searchParams } = new URL(request.url); - const data: DrupalNode[] = await getSearchIndex('full_site_content', {params: {'filter[fulltext]': searchParams.get('q')}}); - const fullContent = await fetchComponents(data); - return NextResponse.json(fullContent.map(node => getNodeMetadata(node))); -} diff --git a/codegen.ts b/codegen.ts new file mode 100644 index 00000000..41699afd --- /dev/null +++ b/codegen.ts @@ -0,0 +1,14 @@ +import {CodegenConfig} from '@graphql-codegen/cli'; + +const config: CodegenConfig = { + overwrite: true, + schema: `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}/graphql` as string, + documents: 'src/lib/gql/*.drupal.gql', + generates: { + 'src/lib/gql/__generated__/drupal.ts': { + plugins: ['typescript', 'typescript-operations', 'typescript-graphql-request'] + } + }, +}; + +export default config; \ No newline at end of file diff --git a/eslintrc.json b/eslintrc.json index 6386a336..604fd655 100644 --- a/eslintrc.json +++ b/eslintrc.json @@ -1,7 +1,6 @@ { "extends": [ - "next/core-web-vitals", - "plugin:storybook/recommended" + "next/core-web-vitals" ], "rules": { "react-hooks/exhaustive-deps": "off", diff --git a/next-env.d.ts b/next-env.d.ts index fd36f949..4f11a03d 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,5 @@ /// /// -/// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/package.json b/package.json index 7d020f5b..db4f1c8d 100644 --- a/package.json +++ b/package.json @@ -8,54 +8,61 @@ "build": "next build", "preview": "next build && next start", "export": "next build && next export", - "lint": "next lint && tsc" + "lint": "next lint && tsc", + "graphql": "DOTENV_CONFIG_PATH=./.env.local graphql-codegen --config codegen.ts -r dotenv/config" }, "dependencies": { "@formkit/auto-animate": "^0.8.1", "@heroicons/react": "^2.1.1", - "@mui/base": "^5.0.0-beta.29", + "@mui/base": "^5.0.0-beta.33", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/typography": "^0.5.10", - "@tanstack/react-query": "^5.17.5", - "@types/node": "^20.10.6", - "@types/react": "^18.2.47", + "@tanstack/react-query": "^5.18.0", + "@types/node": "^20.11.13", + "@types/react": "^18.2.48", "@uidotdev/usehooks": "^2.4.1", - "autoprefixer": "^10.4.16", - "axios": "^1.6.5", + "autoprefixer": "^10.4.17", + "axios": "^1.6.7", "critters": "^0.0.20", "debug": "^4.3.4", - "decanter": "^7.1.2", + "decanter": "^7.2.0", "drupal-jsonapi-params": "^2.3.1", - "html-react-parser": "^5.0.11", + "graphql": "^16.8.1", + "graphql-request": "^6.1.0", + "graphql-tag": "^2.12.6", + "html-react-parser": "^5.1.1", "jsona": "^1.12.1", - "next": "^13.5.6", + "next": "^14.1.0", "next-drupal": "^1.6.0", "nextjs-google-analytics": "^2.3.3", "postcss": "^8.4.33", "react": "^18.2.0", - "react-aria": "^3.31.0", + "react-aria": "^3.31.1", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.12", - "react-focus-lock": "^2.9.6", + "react-focus-lock": "^2.9.7", "react-intersection-observer": "^9.5.3", "react-obfuscate": "^3.6.9", "react-obfuscate-email": "^1.1.2", - "react-resize-detector": "^9.1.1", - "react-stately": "^3.29.0", + "react-resize-detector": "^10.0.1", + "react-stately": "^3.29.1", "react-tiny-oembed": "^1.1.0", "server-only": "^0.0.1", - "sharp": "^0.33.1", - "tailwind-merge": "^2.2.0", + "sharp": "^0.33.2", + "tailwind-merge": "^2.2.1", "tailwindcss": "^3.4.1", "typescript": "^5.3.3", - "usehooks-ts": "^2.9.1", + "usehooks-ts": "^2.10.0", "xml2js": "^0.6.2" }, "devDependencies": { - "@types/debug": "^4", + "@graphql-codegen/cli": "5.0.1", + "@graphql-codegen/typescript-graphql-request": "^6.1.0", + "@graphql-codegen/typescript-operations": "4.1.0", + "@types/debug": "^4.1.12", "@types/qs": "^6.9.11", "eslint": "^8.56.0", - "eslint-config-next": "^13.5.6", + "eslint-config-next": "^14.1.0", "eslint-plugin-unused-imports": "^3.0.0" }, "packageManager": "yarn@4.0.2" diff --git a/src/components/layout/global-message.tsx b/src/components/layout/global-message.tsx index 96aaf753..04e35b9f 100644 --- a/src/components/layout/global-message.tsx +++ b/src/components/layout/global-message.tsx @@ -8,7 +8,6 @@ import { ExclamationTriangleIcon, InformationCircleIcon } from "@heroicons/react/20/solid"; -import Conditional from "@/components/utils/conditional"; import {getConfigPageResource} from "@/lib/drupal/get-resource"; const GlobalMessage = async () => { @@ -66,9 +65,9 @@ const GlobalMessage = async () => {
- + {(configPage.su_global_msg_header) &&

{configPage.su_global_msg_header}

-
+ } {configPage.su_global_msg_message &&
diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index 1c012334..7df3ad0c 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -2,18 +2,12 @@ import Link from "@/components/patterns/elements/drupal-link"; import Lockup from "@/components/patterns/lockup"; import MainMenu from "@/components/menu/main-menu"; import GlobalMessage from "@/components/layout/global-message"; -import {DrupalMenuLinkContent} from "next-drupal"; -import {getMenu} from "@/lib/drupal/get-menu"; import FallbackMainMenu from "@/components/menu/fallback-main-menu"; import {Suspense} from "react"; +import {getMenu} from "@/lib/gql/fetcher"; const Header = async () => { - - let tree: DrupalMenuLinkContent[] = []; - try { - ({tree} = await getMenu('main')); - } catch (e) { - } + const menuItems = await getMenu(); return ( <> @@ -44,8 +38,8 @@ const Header = async () => {
- }> - + }> + diff --git a/src/components/menu/fallback-main-menu.tsx b/src/components/menu/fallback-main-menu.tsx index 96b3eafd..63035d4c 100644 --- a/src/components/menu/fallback-main-menu.tsx +++ b/src/components/menu/fallback-main-menu.tsx @@ -1,8 +1,8 @@ import Link from "@/components/patterns/elements/drupal-link"; import SearchModal from "@/components/search/search-modal"; -import {DrupalMenuLinkContent} from "next-drupal"; +import {MenuItem} from "@/lib/gql/__generated__/drupal"; -const FallbackMainMenu = ({menuItems}: { menuItems: DrupalMenuLinkContent[] }) => { +const FallbackMainMenu = ({menuItems}: { menuItems: MenuItem[] }) => { return (
- + } - {url.length === 0 && + {(!url || url.length === 0) && - + } - 0}> + {(children?.length > 0) &&
    - {items.map(item => + {children.map(item => )}
-
+ } ) } diff --git a/src/components/node/index.tsx b/src/components/node/index.tsx index dd0ecafc..d57a10e5 100644 --- a/src/components/node/index.tsx +++ b/src/components/node/index.tsx @@ -6,22 +6,23 @@ import StanfordPage from "@/components/node/stanford-page/page-display"; import StanfordPerson from "@/components/node/stanford-person/page-display"; import StanfordPublication from "@/components/node/stanford-publication/page-display"; import SulLibrary from "@/components/node/sul-library/page-display"; +import {NodeUnion} from "@/lib/gql/__generated__/drupal"; interface NodeProps { - node: any + node: NodeUnion } export const NodePageDisplay = ({node, ...props}: NodeProps) => { return ( <> - {node.type === "node--stanford_course" && } - {node.type === "node--stanford_event" && } - {node.type === "node--stanford_event_series" && } - {node.type === "node--stanford_news" && } - {node.type === "node--stanford_page" && } - {node.type === "node--stanford_person" && } - {node.type === "node--stanford_publication" && } - {node.type === "node--sul_library" && } + {node.__typename === "NodeStanfordCourse" && } + {node.__typename === "NodeStanfordEvent" && } + {node.__typename === "NodeStanfordEventSeries" && } + {node.__typename === "NodeStanfordNews" && } + {node.__typename === "NodeStanfordPage" && } + {node.__typename === "NodeStanfordPerson" && } + {node.__typename === "NodeStanfordPublication" && } + {node.__typename === "NodeSulLibrary" && } ) } diff --git a/src/components/node/node-card.tsx b/src/components/node/node-card.tsx index c98d930c..c5fe381f 100644 --- a/src/components/node/node-card.tsx +++ b/src/components/node/node-card.tsx @@ -8,23 +8,24 @@ import StanfordPersonCard from "@/components/node/stanford-person/card"; import StanfordPublicationCard from "@/components/node/stanford-publication/card"; import SulLibraryCard from "@/components/node/sul-library/card"; import SulStudyPlaceCard from "@/components/node/sul-study-place/card"; +import {NodeUnion} from "@/lib/gql/__generated__/drupal"; interface NodeProps extends PropsWithRef { - node: any + node: NodeUnion } const NodeCardDisplay = ({node, ...props}: NodeProps) => { return ( <> - {node.type === "node--stanford_course" && } - {node.type === "node--stanford_event" && } - {node.type === "node--stanford_event_series" && } - {node.type === "node--stanford_news" && } - {node.type === "node--stanford_page" && } - {node.type === "node--stanford_person" && } - {node.type === "node--stanford_publication" && } - {node.type === "node--sul_library" && } - {node.type === "node--sul_study_place" && } + {node.__typename === "NodeStanfordCourse" && } + {node.__typename === "NodeStanfordEvent" && } + {node.__typename === "NodeStanfordEventSeries" && } + {node.__typename === "NodeStanfordNews" && } + {node.__typename === "NodeStanfordPage" && } + {node.__typename === "NodeStanfordPerson" && } + {node.__typename === "NodeStanfordPublication" && } + {node.__typename === "NodeSulLibrary" && } + {node.__typename === "NodeSulStudyPlace" && } ) } diff --git a/src/components/node/node-list-display.tsx b/src/components/node/node-list-display.tsx index da82a16f..978b488e 100644 --- a/src/components/node/node-list-display.tsx +++ b/src/components/node/node-list-display.tsx @@ -7,22 +7,23 @@ import StanfordPersonListItem from "@/components/node/stanford-person/list-item" import StanfordPublicationListItem from "@/components/node/stanford-publication/list-item"; import SulLibraryListItem from "@/components/node/sul-library/list-item"; import SulStudyPlaceListItem from "@/components/node/sul-study-place/list-item"; +import {NodeUnion} from "@/lib/gql/__generated__/drupal"; interface NodeProps { - node: any + node: NodeUnion } const NodeListDisplay = ({node, ...props}: NodeProps) => { return ( <> - {node.type === "node--stanford_course" && } - {node.type === "node--stanford_event" && } - {node.type === "node--stanford_event_series" && } - {node.type === "node--stanford_news" && } - {node.type === "node--stanford_page" && } - {node.type === "node--stanford_person" && } - {node.type === "node--stanford_publication" && } - {node.type === "node--sul_library" && } - {node.type === "node--sul_study_place" && } + {node.__typename === "NodeStanfordCourse" && } + {node.__typename === "NodeStanfordEvent" && } + {node.__typename === "NodeStanfordEventSeries" && } + {node.__typename === "NodeStanfordNews" && } + {node.__typename === "NodeStanfordPage" && } + {node.__typename === "NodeStanfordPerson" && } + {node.__typename === "NodeStanfordPublication" && } + {node.__typename === "NodeSulLibrary" && } + {node.__typename === "NodeSulStudyPlace" && } ) } diff --git a/src/components/node/stanford-course/card.tsx b/src/components/node/stanford-course/card.tsx index 8f56f6a8..0332754e 100644 --- a/src/components/node/stanford-course/card.tsx +++ b/src/components/node/stanford-course/card.tsx @@ -1,35 +1,31 @@ -import Conditional from "@/components/utils/conditional"; import Link from "@/components/patterns/elements/drupal-link"; -import {Course} from "@/lib/drupal/drupal"; -import {PropsWithoutRef} from "react"; +import {NodeStanfordCourse} from "@/lib/gql/__generated__/drupal"; + interface Props { - node: Course + node: NodeStanfordCourse h3Heading?: boolean } -const StanfordCourseCard = ({node, h3Heading, ...props}: PropsWithoutRef) => { +const StanfordCourseCard = ({node, h3Heading, ...props}: Props) => { const HeadingElement = h3Heading ? 'h3' : 'h2'; return (
- - {node.su_course_subject?.name}{' '} - - - {node.su_course_code} - - + {node.suCourseSubject?.name} + {node.suCourseCode} + + {(node.suCourseAcademicYear) && - {' | '}{node.su_course_academic_year} + {' | '}{node.suCourseAcademicYear} - + }
{node.title} diff --git a/src/components/node/stanford-course/list-item.tsx b/src/components/node/stanford-course/list-item.tsx index 77cc882c..47d7aa8b 100644 --- a/src/components/node/stanford-course/list-item.tsx +++ b/src/components/node/stanford-course/list-item.tsx @@ -1,36 +1,33 @@ -import Conditional from "@/components/utils/conditional"; import Link from "@/components/patterns/elements/drupal-link"; import formatHtml from "@/lib/format-html"; -import {Course} from "@/lib/drupal/drupal"; +import {NodeStanfordCourse} from "@/lib/gql/__generated__/drupal"; -const StanfordCourseListItem = ({node, ...props}: { node: Course }) => { +const StanfordCourseListItem = ({node, ...props}: { node: NodeStanfordCourse }) => { return (
- - {node.su_course_subject?.name}{' '} - - - {node.su_course_code} - - + + {node.suCourseSubject?.name} + {node.suCourseCode} + + {(node.suCourseAcademicYear) && - {' | '}{node.su_course_academic_year} + {' | '}{node.suCourseAcademicYear} - + } - +

{node.title}

- + {(node.suCourseInstructors) &&

Instructors:

- {node.su_course_instructors?.map((instructor, index) => + {node.suCourseInstructors?.map((instructor, index) => {(index ? ', ' : '') + instructor} )}
-
- { node.body && <>{formatHtml(node.body)}} + } + { node.body?.processed && <>{formatHtml(node.body.processed)}}
) } diff --git a/src/components/node/stanford-course/page-display.tsx b/src/components/node/stanford-course/page-display.tsx index f8eb5206..991af859 100644 --- a/src/components/node/stanford-course/page-display.tsx +++ b/src/components/node/stanford-course/page-display.tsx @@ -1,52 +1,46 @@ -import "server-only"; -import Conditional from "@/components/utils/conditional"; import formatHtml from "@/lib/format-html"; -import {Course} from "@/lib/drupal/drupal"; import Link from "@/components/patterns/elements/drupal-link"; +import {NodeStanfordCourse} from "@/lib/gql/__generated__/drupal"; -const StanfordCourse = ({node, ...props}: { node: Course }) => { +const StanfordCourse = ({node, ...props}: { node: NodeStanfordCourse }) => { return (
- - {node.su_course_academic_year} - - - <>{formatHtml(node.body)} - - - {node.su_course_code} - - - {node.su_course_id} - - {node.su_course_link?.url && - {node.su_course_link?.title} + {node.suCourseAcademicYear} + {(node.body?.processed) && + <>{formatHtml(node.body.processed)} } - {/* - {node.su_course_link.title} - */} - - {node.su_course_quarters?.map(term => -
{term.name}
- )} -
- -
{node.su_course_subject?.name}
-
- - {node.su_course_tags?.map(term =>
{term.name}
)} -
- - {node.su_course_instructors?.map((instructor, index) =>
{instructor}
)} -
- - {node.su_shared_tags?.map(term =>
{term.name}
)} -
- - {node.su_course_section_units} - + {node.suCourseCode} + {node.suCourseId} + + {node.suCourseLink?.url && + {node.suCourseLink?.title} + } + + {node.suCourseQuarters && + <> + {node.suCourseQuarters?.map(term => +
{term.name}
+ )} + + } + {(node.suCourseSubject) && +
{node.suCourseSubject?.name}
+ } + {node.suCourseTags && +
+ {node.suCourseTags?.map(term =>
{term.name}
)} +
+ } + {node.suCourseInstructors && +
+ {node.suCourseInstructors?.map((instructor, index) => +
{instructor}
+ )} +
+ } + {node.suCourseSectionUnits} +
) } diff --git a/src/components/node/stanford-event-series/card.tsx b/src/components/node/stanford-event-series/card.tsx index 6e14ccca..44e25ae8 100644 --- a/src/components/node/stanford-event-series/card.tsx +++ b/src/components/node/stanford-event-series/card.tsx @@ -1,25 +1,22 @@ import Link from "@/components/patterns/elements/drupal-link"; import Card from "@/components/patterns/card"; -import Conditional from "@/components/utils/conditional"; -import {EventSeries} from "@/lib/drupal/drupal"; +import {NodeStanfordEventSeries} from "@/lib/gql/__generated__/drupal"; -const StanfordEventSeriesCard = ({node, ...props}: { node: EventSeries }) => { +const StanfordEventSeriesCard = ({node, ...props}: { node: NodeStanfordEventSeries }) => { return (
+ href={node.path}> {node.title} } footer={ - - - {node.su_event_series_subheadline} - - + + {node.suEventSeriesSubheadline} + } />
diff --git a/src/components/node/stanford-event-series/list-item.tsx b/src/components/node/stanford-event-series/list-item.tsx index fad74754..6fa0c7c5 100644 --- a/src/components/node/stanford-event-series/list-item.tsx +++ b/src/components/node/stanford-event-series/list-item.tsx @@ -1,11 +1,11 @@ import Link from "@/components/patterns/elements/drupal-link"; -import {EventSeries} from "@/lib/drupal/drupal"; +import {NodeStanfordEventSeries} from "@/lib/gql/__generated__/drupal"; -const StanfordEventSeriesListItem = ({node, ...props}: { node: EventSeries }) => { +const StanfordEventSeriesListItem = ({node, ...props}: { node: NodeStanfordEventSeries }) => { // Not being utilized anywhere currently return (
- +

{node.title}

diff --git a/src/components/node/stanford-event-series/page-display.tsx b/src/components/node/stanford-event-series/page-display.tsx index aec61d7f..be170dfc 100644 --- a/src/components/node/stanford-event-series/page-display.tsx +++ b/src/components/node/stanford-event-series/page-display.tsx @@ -1,38 +1,40 @@ -import "server-only"; -import Conditional from "@/components/utils/conditional"; -import {ParagraphRows} from "@/components/paragraph/rows/rows"; import NodeListDisplay from "@/components/node/node-list-display"; -import {EventSeries, StanfordParagraph} from "@/lib/drupal/drupal"; -import fetchComponents from "@/lib/fetch-components"; +import {NodeStanfordEventSeries} from "@/lib/gql/__generated__/drupal"; +import Paragraph from "@/components/paragraph"; + +const StanfordEventSeries = async ({node, ...props}: { node: NodeStanfordEventSeries }) => { -const StanfordEventSeries = async ({node, ...props}: { node: EventSeries }) => { - node.su_event_series_components = await fetchComponents(node.su_event_series_components || []); - node.su_event_series_components = node.su_event_series_components.filter(item => !!item?.id); return (
- + {node.suEventSeriesSubheadline &&

- {node.su_event_series_subheadline} + {node.suEventSeriesSubheadline}

-
- + } + {node.suEventSeriesDek &&
- {node.su_event_series_dek} + {node.suEventSeriesDek}
-
+ } - + {node.suEventSeriesComponents && + <> + {node.suEventSeriesComponents.map(paragraph => + + )} + + } - + {(node.suEventSeriesEvent) &&
- {node.su_event_series_event.map(item => + {node.suEventSeriesEvent.map(item =>
)}
-
+ }
) } diff --git a/src/components/node/stanford-event/card.tsx b/src/components/node/stanford-event/card.tsx index a6a2e8a0..587124fe 100644 --- a/src/components/node/stanford-event/card.tsx +++ b/src/components/node/stanford-event/card.tsx @@ -1,98 +1,21 @@ import {CalendarDaysIcon} from "@heroicons/react/20/solid"; import Link from "@/components/patterns/elements/drupal-link"; -import {Event} from "@/lib/drupal/drupal"; import Image from "next/image"; import {ClockIcon, MapPinIcon} from "@heroicons/react/24/outline"; -import {PropsWithoutRef} from "react"; -import {buildUrl} from "@/lib/drupal/utils"; -const getTimeString = (start: Date, end: Date): string => { - const startHour = parseInt(start.toLocaleTimeString("en-US", { - hour: "numeric", - hour12: false, - timeZone: 'America/Los_Angeles' - })) - const startMinute = parseInt(start.toLocaleTimeString("en-US", { - minute: "numeric", - hour12: false, - timeZone: 'America/Los_Angeles' - })) - - const endHour = parseInt(end.toLocaleTimeString("en-US", { - hour: "numeric", - hour12: false, - timeZone: 'America/Los_Angeles' - })) - const endMinute = parseInt(end.toLocaleTimeString("en-US", { - minute: "numeric", - hour12: false, - timeZone: 'America/Los_Angeles' - })) - - let dateTimeString: string; - - // Multiple days. - if (start.toLocaleDateString("en-US", {timeZone: 'America/Los_Angeles'}) != end.toLocaleDateString("en-US", {timeZone: 'America/Los_Angeles'})) { - dateTimeString = start.toLocaleDateString("en-US", { - month: "long", - day: "numeric", - year: "numeric", - timeZone: 'America/Los_Angeles' - }) + ' - ' + end.toLocaleDateString("en-US", { - month: "long", - day: "numeric", - year: "numeric", - timeZone: 'America/Los_Angeles' - }) - return dateTimeString; - } - - // All Day display. - if ( - (startHour === 24 || startHour === 0) && - startMinute === 0 && - endHour === 23 && - endMinute === 59 - ) { - return 'All Day'; - } - - - // Different start and end times. - if (startHour !== endHour || startMinute !== endMinute) { - dateTimeString = start.toLocaleTimeString("en-US", { - hour: "numeric", - minute: "numeric", - timeZone: 'America/Los_Angeles' - }); - dateTimeString += ' - '; - dateTimeString += end.toLocaleTimeString("en-US", { - hour: "numeric", - minute: "numeric", - timeZoneName: "short", - timeZone: 'America/Los_Angeles' - }) - return dateTimeString; - } - - // Start and end times are the same, just display the start time. - return start.toLocaleTimeString("en-US", { - hour: "numeric", - minute: "numeric", - timeZoneName: "short", - timeZone: 'America/Los_Angeles' - }) -} +import {buildUrl} from "@/lib/drupal/utils"; +import {NodeStanfordEvent} from "@/lib/gql/__generated__/drupal"; -interface Props extends PropsWithoutRef { - node: Event +interface Props { + node: NodeStanfordEvent h3Heading?: boolean } -const StanfordEventCard = ({node, h3Heading, ...props}: PropsWithoutRef) => { +const StanfordEventCard = ({node, h3Heading, ...props}: Props) => { + const HeadingElement = h3Heading ? 'h3' : 'h2'; - const start = new Date(node.su_event_date_time?.value); - const end = new Date(node.su_event_date_time?.end_value); + const start = new Date(node.suEventDateTime.value * 1000); + const end = new Date(node.suEventDateTime.end_value * 1000); const startMonth = start.toLocaleDateString("en-US", {month: "short", timeZone: 'America/Los_Angeles'}) const startDay = parseInt(start.toLocaleDateString("en-US", {day: "numeric", timeZone: 'America/Los_Angeles'})) @@ -102,8 +25,8 @@ const StanfordEventCard = ({node, h3Heading, ...props}: PropsWithoutRef) // Fix difference between server side render and client side render. Replace any strange characters. const dateTimeString = getTimeString(start, end).replace(/[^a-zA-Z0-9 ,:\-|]/, ' '); - const imageUrl = node.sul_event__image?.field_media_image?.uri.url; - const placeholder = node.sul_event__image?.field_media_image?.uri.base64; + const imageUrl = node.sulEventImage?.mediaImage.url; + const goToUrl = node.suEventSource?.url || node.path; return (
@@ -118,8 +41,6 @@ const StanfordEventCard = ({node, h3Heading, ...props}: PropsWithoutRef) alt="" fill sizes="(max-width: 768px) 100vw, (max-width: 900px) 50vw, (max-width: 1700px) 33vw, 500px" - placeholder={placeholder ? 'blur' : 'empty'} - blurDataURL={placeholder} /> } @@ -158,14 +79,14 @@ const StanfordEventCard = ({node, h3Heading, ...props}: PropsWithoutRef)
- {node.title} - {node.su_event_type?.[0]?.name && -
{node.su_event_type?.[0].name}
+ {node.suEventType?.[0]?.name && +
{node.suEventType?.[0].name}
} @@ -179,11 +100,11 @@ const StanfordEventCard = ({node, h3Heading, ...props}: PropsWithoutRef)
{dateTimeString}
- {node.su_event_map_link?.url && + {node.suEventMapLink?.url &&
- - {node.su_event_map_link?.title} + + {node.suEventMapLink?.title}
} @@ -192,4 +113,85 @@ const StanfordEventCard = ({node, h3Heading, ...props}: PropsWithoutRef)
) } + + +const getTimeString = (start: Date, end: Date): string => { + const startHour = parseInt(start.toLocaleTimeString("en-US", { + hour: "numeric", + hour12: false, + timeZone: 'America/Los_Angeles' + })) + const startMinute = parseInt(start.toLocaleTimeString("en-US", { + minute: "numeric", + hour12: false, + timeZone: 'America/Los_Angeles' + })) + + const endHour = parseInt(end.toLocaleTimeString("en-US", { + hour: "numeric", + hour12: false, + timeZone: 'America/Los_Angeles' + })) + const endMinute = parseInt(end.toLocaleTimeString("en-US", { + minute: "numeric", + hour12: false, + timeZone: 'America/Los_Angeles' + })) + + let dateTimeString: string; + + // Multiple days. + if (start.toLocaleDateString("en-US", {timeZone: 'America/Los_Angeles'}) != end.toLocaleDateString("en-US", {timeZone: 'America/Los_Angeles'})) { + dateTimeString = start.toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + timeZone: 'America/Los_Angeles' + }) + ' - ' + end.toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + timeZone: 'America/Los_Angeles' + }) + return dateTimeString; + } + + // All Day display. + if ( + (startHour === 24 || startHour === 0) && + startMinute === 0 && + endHour === 23 && + endMinute === 59 + ) { + return 'All Day'; + } + + + // Different start and end times. + if (startHour !== endHour || startMinute !== endMinute) { + dateTimeString = start.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "numeric", + timeZone: 'America/Los_Angeles' + }); + dateTimeString += ' - '; + dateTimeString += end.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "numeric", + timeZoneName: "short", + timeZone: 'America/Los_Angeles' + }) + return dateTimeString; + } + + // Start and end times are the same, just display the start time. + return start.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "numeric", + timeZoneName: "short", + timeZone: 'America/Los_Angeles' + }) +} + + export default StanfordEventCard; \ No newline at end of file diff --git a/src/components/node/stanford-event/list-item.tsx b/src/components/node/stanford-event/list-item.tsx index 74ba4196..362cb488 100644 --- a/src/components/node/stanford-event/list-item.tsx +++ b/src/components/node/stanford-event/list-item.tsx @@ -1,13 +1,14 @@ -import {Event} from "@/lib/drupal/drupal"; + import StanfordEventCard from "@/components/node/stanford-event/card"; -import {PropsWithoutRef} from "react"; +import {NodeStanfordEvent} from "@/lib/gql/__generated__/drupal"; + interface Props { - node: Event + node: NodeStanfordEvent h3Heading?: boolean } -const StanfordEventListItem = ({node, ...props}: PropsWithoutRef) => { +const StanfordEventListItem = ({node, ...props}: Props) => { // For now, just return the card. Change this if designs come through. return } diff --git a/src/components/node/stanford-event/page-display.tsx b/src/components/node/stanford-event/page-display.tsx index 259005f3..04c19416 100644 --- a/src/components/node/stanford-event/page-display.tsx +++ b/src/components/node/stanford-event/page-display.tsx @@ -1,25 +1,19 @@ -import "server-only"; import {CalendarIcon, MapIcon, PhoneIcon, UserGroupIcon} from "@heroicons/react/20/solid"; import Link from "@/components/patterns/elements/drupal-link"; import {DrupalLinkButton} from "@/components/patterns/link"; -import {Event, StanfordParagraph} from "@/lib/drupal/drupal"; -import fetchComponents from "@/lib/fetch-components"; - import formatHtml from "@/lib/format-html"; -import Paragraph from "@/components/paragraph"; import {redirect} from "next/navigation"; import EmailLink from "@/components/patterns/elements/email-link"; import TelephoneLink from "@/components/patterns/elements/telephone-link"; +import {NodeStanfordEvent} from "@/lib/gql/__generated__/drupal"; +import Paragraph from "@/components/paragraph"; -const StanfordEvent = async ({node, ...props}: { node: Event }) => { - if (node.su_event_source?.url) redirect(node.su_event_source.url) - - node.su_event_components = await fetchComponents(node.su_event_components || []); - node.su_event_components = node.su_event_components.filter(item => !!item?.id); +const StanfordEvent = async ({node, ...props}: { node: NodeStanfordEvent }) => { + if (node.suEventSource?.url) redirect(node.suEventSource.url) - const inPast = new Date(node.su_event_date_time?.end_value) < new Date(); - const start = new Date(node.su_event_date_time?.value); - const end = new Date(node.su_event_date_time?.end_value); + const inPast = new Date(node.suEventDateTime.end_value * 1000) < new Date(); + const start = new Date(node.suEventDateTime.value * 1000); + const end = new Date(node.suEventDateTime.end_value * 1000); let dateTimeString; if (start.getMonth() === end.getMonth() && start.getDate() === end.getDate() && start.getFullYear() === end.getFullYear()) { @@ -71,23 +65,27 @@ const StanfordEvent = async ({node, ...props}: { node: Event }) => {
{inPast &&
Past Event
} - {(node.su_event_type && node.su_event_type.length > 0) && node.su_event_type.map(term => -
- {term.name} + {(node.suEventType && node.suEventType.length > 0) && +
+ {node.suEventType.map(term => +
+ {term.name} +
+ )}
- )} + } - {node.su_event_subheadline &&

{node.su_event_subheadline}

} - {node.su_event_dek &&
{node.su_event_dek}
} + {node.suEventSubheadline &&

{node.suEventSubheadline}

} + {node.suEventDek &&
{node.suEventDek}
} - {node.su_event_sponsor && -
- {node.su_event_sponsor.map((sponsor, index) => -
- {sponsor} -
- )} -
+ {node.suEventSponsor && +
+ {node.suEventSponsor.map((sponsor, index) => +
+ {sponsor} +
+ )} +
}
@@ -97,111 +95,106 @@ const StanfordEvent = async ({node, ...props}: { node: Event }) => {
-
{inPast && -
- This event has passed. -
} +
+ This event has passed. +
}
- {(node.su_event_location || node.su_event_alt_loc) && -
- {node.su_event_location && -
-
- -

Location

-
-
-
{node.su_event_location.organization}
-
{node.su_event_location.address_line1}
-
{node.su_event_location.address_line2}
-
{node.su_event_location.locality}, {node.su_event_location.administrative_area} {node.su_event_location.postal_code}
-
-
- } - {node.su_event_alt_loc && -
-
- -

Location

-
-
- <>{node.su_event_alt_loc} -
-
- } - - {node.su_event_map_link && - - {node.su_event_map_link.title} - - } -
+ {(node.suEventLocation || node.suEventAltLoc) && +
+ {node.suEventLocation && +
+
+ +

Location

+
+
+
{node.suEventLocation.organization}
+
{node.suEventLocation.addressLine1}
+
{node.suEventLocation.addressLine2}
+
{node.suEventLocation.locality}, {node.suEventLocation.administrativeArea} {node.suEventLocation.postalCode}
+
+
+ } + {node.suEventAltLoc && +
+
+ +

Location

+
+
+ {node.suEventAltLoc} +
+
+ } + + {node.suEventMapLink?.url && + + {node.suEventMapLink.title} + + } +
}
- {(node.su_event_telephone || node.su_event_email) && -
-
- -

Contact

-
- {node.su_event_telephone && - - } - {node.su_event_email && - - } + {(node.suEventTelephone || node.suEventEmail) && +
+
+ +

Contact

+ {node.suEventTelephone && + + } + {node.suEventEmail && + + } +
} - {(node.su_event_audience && node.su_event_audience?.length > 0) && -
-
- -

This event is open to:

-
- {node.su_event_audience.map(audience => -
{audience.name}
- )} + {node.suEventAudience && +
+
+ +

This event is open to:

+ {node.suEventAudience.map(audience => +
{audience.name}
+ )} +
}
- {node.su_event_cta && -
- - {node.su_event_cta.title} - -
+ {node.suEventCta?.url && +
+ + {node.suEventCta.title} + +
}
- {node.su_event_source && - {node.su_event_source.title ? node.su_event_source.title : 'View this event'} - } - - {node.body && + {node.body?.processed &&
- {formatHtml(node.body)} + {formatHtml(node.body.processed)}
} - {node.su_event_components && + {node.suEventComponents &&
- {node.su_event_components.map(component => - + {node.suEventComponents.map(paragraph => + )}
} diff --git a/src/components/node/stanford-news/card.tsx b/src/components/node/stanford-news/card.tsx index e045a6b0..892400e5 100644 --- a/src/components/node/stanford-news/card.tsx +++ b/src/components/node/stanford-news/card.tsx @@ -1,23 +1,19 @@ import Link from "@/components/patterns/elements/drupal-link"; -import {News} from "@/lib/drupal/drupal"; import Image from "next/image"; -import {PropsWithoutRef} from "react"; import {buildUrl} from "@/lib/drupal/utils"; +import {NodeStanfordNews} from "@/lib/gql/__generated__/drupal"; interface Props { - node: News + node: NodeStanfordNews h3Heading?: boolean } -const StanfordNewsCard = ({node, h3Heading, ...props}: PropsWithoutRef) => { - const featuredImageUrl = node.su_news_featured_media?.field_media_image?.uri.url - const bannerImageUrl = node.su_news_banner?.type === 'media--image' && node.su_news_banner?.field_media_image?.uri.url; +const StanfordNewsCard = ({node, h3Heading, ...props}: Props) => { + const goToUrl = node.suNewsSource?.url || node.path; + const featuredImageUrl = node.suNewsFeaturedMedia?.mediaImage.url + const bannerImageUrl = node.suNewsBanner?.__typename === 'MediaImage' && node.suNewsBanner.mediaImage.url; const imageUrl = featuredImageUrl || bannerImageUrl - const featuredPlaceholder = node.su_news_featured_media?.field_media_image?.uri.base64 - const bannerPlaceholder = node.su_news_banner?.type === 'media--image' && node.su_news_banner?.field_media_image?.uri.base64; - const placeholder = featuredPlaceholder || bannerPlaceholder || undefined - const HeadingElement = h3Heading ? 'h3' : 'h2'; return (
@@ -29,22 +25,20 @@ const StanfordNewsCard = ({node, h3Heading, ...props}: PropsWithoutRef) = alt="" fill sizes="(max-width: 768px) 100vw, (max-width: 900px) 50vw, (max-width: 1700px) 33vw, 500px" - placeholder={placeholder ? 'blur' : 'empty'} - blurDataURL={placeholder} />
} - {node.title} - {node.su_news_topics?.[0]?.name && + {node.suNewsTopics?.[0]?.name &&
- {node.su_news_topics[0].name} + {node.suNewsTopics[0].name}
} diff --git a/src/components/node/stanford-news/list-item.tsx b/src/components/node/stanford-news/list-item.tsx index 3c1af022..efa90f74 100644 --- a/src/components/node/stanford-news/list-item.tsx +++ b/src/components/node/stanford-news/list-item.tsx @@ -1,38 +1,33 @@ import Image from "next/image"; import Link from "@/components/patterns/elements/drupal-link"; -import {News} from "@/lib/drupal/drupal"; import {formatDate} from "@/lib/format-date"; -import {PropsWithoutRef} from "react"; import {buildUrl} from "@/lib/drupal/utils"; +import {NodeStanfordNews} from "@/lib/gql/__generated__/drupal"; interface Props { - node: News + node: NodeStanfordNews h3Heading?: boolean } -const StanfordNewsListItem = ({node, h3Heading, ...props}: PropsWithoutRef) => { +const StanfordNewsListItem = ({node, h3Heading, ...props}: Props) => { const HeadingElement = h3Heading ? 'h3' : 'h2'; - const featuredImageUrl = node.su_news_featured_media?.field_media_image?.uri.url - const bannerImageUrl = node.su_news_banner?.type === 'media--image' && node.su_news_banner?.field_media_image?.uri.url; + const featuredImageUrl = node.suNewsFeaturedMedia?.mediaImage.url + const bannerImageUrl = node.suNewsBanner?.__typename === 'MediaImage' && node.suNewsBanner.mediaImage.url; const imageUrl = featuredImageUrl || bannerImageUrl - const featuredPlaceholder = node.su_news_featured_media?.field_media_image?.uri.base64 - const bannerPlaceholder = node.su_news_banner?.type === 'media--image' && node.su_news_banner?.field_media_image?.uri.base64; - const placeholder = featuredPlaceholder || bannerPlaceholder || undefined - - const topics = node.su_news_topics?.filter(topic => !!topic?.name) || []; + const goToUrl = node.suNewsSource?.url || node.path; return (
- {node.su_news_publishing_date && <>{formatDate(node.su_news_publishing_date + ' 12:00:00')}} + {node.suNewsPublishingDate && <>{formatDate(node.suNewsPublishingDate.time)}}
- + {node.title} - {node.su_news_dek &&
{node.su_news_dek}
} + {node.suNewsDek &&
{node.suNewsDek}
}
{imageUrl && }
- {topics.map((cardTopic, index) => + {node.suNewsTopics?.map((cardTopic, index) => {(index ? ', ' : '') + cardTopic.name} diff --git a/src/components/node/stanford-news/page-display.tsx b/src/components/node/stanford-news/page-display.tsx index 45915c11..3b8e9886 100644 --- a/src/components/node/stanford-news/page-display.tsx +++ b/src/components/node/stanford-news/page-display.tsx @@ -1,40 +1,32 @@ -import "server-only"; import Oembed from "@/components/patterns/elements/oembed"; import Image from "next/image"; import {EnvelopeIcon} from "@heroicons/react/20/solid"; - import LinkedInIcon from "@/components/patterns/icons/LinkedInIcon"; import TwitterIcon from "@/components/patterns/icons/TwitterIcon"; import FacebookIcon from "@/components/patterns/icons/FacebookIcon"; -import Conditional from "@/components/utils/conditional"; import NewsSocialLink from "@/components/node/stanford-news/news-social-link"; -import {News, StanfordParagraph} from "@/lib/drupal/drupal"; import {formatDate} from "@/lib/format-date"; import NewsPrintButton from "@/components/node/stanford-news/print-button"; -import fetchComponents from "@/lib/fetch-components"; -import Paragraph from "@/components/paragraph"; import {redirect} from "next/navigation"; import {buildUrl} from "@/lib/drupal/utils"; +import {NodeStanfordNews} from "@/lib/gql/__generated__/drupal"; +import Paragraph from "@/components/paragraph"; -const StanfordNews = async ({node, ...props}: { node: News }) => { +const StanfordNews = async ({node, ...props}: { node: NodeStanfordNews }) => { // Redirect the user to the external source. - if (node.su_news_source?.url && node.su_news_source?.url?.length > 0) redirect(node.su_news_source?.url); - - node.su_news_components = await fetchComponents(node.su_news_components || []) - node.su_news_components = node.su_news_components.filter(item => !!item?.id); + if (node.suNewsSource?.url) redirect(node.suNewsSource?.url); - const imageUrl = node.su_news_banner?.type === 'media--image' && node.su_news_banner?.field_media_image?.uri.url - const imageAlt = node.su_news_banner?.type === 'media--image' && node.su_news_banner?.field_media_image?.resourceIdObjMeta?.alt; - const placeholder = node.su_news_banner?.type === 'media--image' && node.su_news_banner?.field_media_image?.uri.base64; + const imageUrl = node.suNewsBanner?.__typename === 'MediaImage' && node.suNewsBanner.mediaImage.url + const imageAlt = node.suNewsBanner?.__typename === 'MediaImage' && node.suNewsBanner.mediaImage.alt; return (
- {node.su_news_dek &&
{node.su_news_dek}
} + {node.suNewsDek &&
{node.suNewsDek}
}
- + {!node.suNewsHideSocial &&
  • @@ -80,11 +72,11 @@ const StanfordNews = async ({node, ...props}: { node: News }) => {
-
+ }
- {node.su_news_publishing_date && <>{formatDate(node.su_news_publishing_date + ' 12:00:00')} | } - {node.su_news_byline} + {node.suNewsPublishingDate && <>{formatDate(node.suNewsPublishingDate.time)} | } + {node.suNewsByline}
@@ -97,38 +89,37 @@ const StanfordNews = async ({node, ...props}: { node: News }) => { src={buildUrl(imageUrl).toString()} alt={imageAlt || ''} fill - placeholder={placeholder ? 'blur' : 'empty'} - blurDataURL={placeholder || undefined} /> - {node.su_news_banner_media_caption && + {node.suNewsBannerMediaCaption &&
- {node.su_news_banner_media_caption} + {node.suNewsBannerMediaCaption}
} } - {node?.su_news_banner?.field_media_oembed_video && + {node.suNewsBanner?.__typename === 'MediaVideo' &&
- {node.su_news_banner_media_caption && + {node.suNewsBannerMediaCaption &&
- {node.su_news_banner_media_caption} + {node.suNewsBannerMediaCaption}
}
} - -
- {node.su_news_components.map(component => - - )} -
+ {node.suNewsComponents && +
+ {node.suNewsComponents.map(paragraph => + + )} +
+ }
) } diff --git a/src/components/node/stanford-page/card.tsx b/src/components/node/stanford-page/card.tsx index 833459c6..38210b23 100644 --- a/src/components/node/stanford-page/card.tsx +++ b/src/components/node/stanford-page/card.tsx @@ -1,19 +1,18 @@ import Image from "next/image"; import Link from "@/components/patterns/elements/drupal-link"; -import {BasicPage} from "@/lib/drupal/drupal"; -import {PropsWithoutRef} from "react"; import {buildUrl} from "@/lib/drupal/utils"; +import {NodeStanfordPage} from "@/lib/gql/__generated__/drupal"; interface Props { - node: BasicPage + node: NodeStanfordPage h3Heading?: boolean } -const StanfordPageCard = ({node, h3Heading, ...props}: PropsWithoutRef) => { +const StanfordPageCard = ({node, h3Heading, ...props}: Props) => { const HeadingElement = h3Heading ? 'h3' : 'h2'; - const imageUrl = node.su_page_image?.field_media_image.uri.url || node.su_page_banner?.su_banner_image?.field_media_image.uri.url; - const placeholder = node.su_page_image?.field_media_image?.uri.base64 || node.su_page_banner?.su_banner_image?.field_media_image?.uri.base64; + const imageUrl = node.suPageImage?.mediaImage.url || node.suPageBanner?.suBannerImage?.mediaImage.url + return (
{imageUrl && @@ -24,8 +23,6 @@ const StanfordPageCard = ({node, h3Heading, ...props}: PropsWithoutRef) = alt="" fill sizes="(max-width: 768px) 100vw, (max-width: 900px) 50vw, (max-width: 1700px) 33vw, 500px" - placeholder={placeholder ? 'blur' : 'empty'} - blurDataURL={placeholder} /> } @@ -34,12 +31,12 @@ const StanfordPageCard = ({node, h3Heading, ...props}: PropsWithoutRef) = + href={node.path}> {node.title} -

{node.su_page_description}

+

{node.suPageDescription}

) } diff --git a/src/components/node/stanford-page/home-page/today-hours.tsx b/src/components/node/stanford-page/home-page/today-hours.tsx index c8872390..3acfa632 100644 --- a/src/components/node/stanford-page/home-page/today-hours.tsx +++ b/src/components/node/stanford-page/home-page/today-hours.tsx @@ -3,7 +3,7 @@ import Card from "@/components/patterns/card"; import {ClockIcon} from "@heroicons/react/24/outline"; import Image from "next/image"; -import {PropsWithoutRef, useId, useState} from "react"; +import {HTMLAttributes, useId, useState} from "react"; import {DrupalImageMedia} from "@/lib/drupal/drupal"; import {ErrorBoundary} from "react-error-boundary"; import CachedClientFetch from "@/components/utils/cached-client-fetch"; @@ -12,7 +12,7 @@ import {Library} from "@/lib/drupal/drupal"; import SelectList from "@/components/patterns/elements/select-list"; import {buildUrl} from "@/lib/drupal/utils"; -interface HoursProps extends PropsWithoutRef { +type HoursProps = HTMLAttributes & { libraries: { id: string, title: string, su_library__hours?: string, su_library__contact_img?: DrupalImageMedia }[] } diff --git a/src/components/node/stanford-page/list-item.tsx b/src/components/node/stanford-page/list-item.tsx index 24dc7dcc..8c7e25fd 100644 --- a/src/components/node/stanford-page/list-item.tsx +++ b/src/components/node/stanford-page/list-item.tsx @@ -1,11 +1,12 @@ -import {BasicPage} from "@/lib/drupal/drupal"; + import StanfordPageCard from "@/components/node/stanford-page/card"; -import {PropsWithoutRef} from "react"; +import {NodeStanfordPage} from "@/lib/gql/__generated__/drupal"; + interface Props { - node: BasicPage + node: NodeStanfordPage h3Heading?: boolean } -const StanfordPageListItem = ({node, ...props}: PropsWithoutRef) => { +const StanfordPageListItem = ({node, ...props}: Props) => { // Without designs, use the card. return } diff --git a/src/components/node/stanford-page/page-display.tsx b/src/components/node/stanford-page/page-display.tsx index 01220370..acbaf72c 100644 --- a/src/components/node/stanford-page/page-display.tsx +++ b/src/components/node/stanford-page/page-display.tsx @@ -1,19 +1,14 @@ -import "server-only"; - import {ParagraphRows} from "@/components/paragraph/rows/rows"; -import {BasicPage, StanfordParagraph} from "@/lib/drupal/drupal"; -import fetchComponents from "@/lib/fetch-components"; -import {isDraftMode} from "@/lib/drupal/is-draft-mode"; +import {NodeStanfordPage} from "@/lib/gql/__generated__/drupal"; -const StanfordPage = async ({node}: { node: BasicPage }) => { - const draftMode = isDraftMode(); - node.su_page_components = await fetchComponents(node.su_page_components || [], {draftMode}); - node.su_page_components = node.su_page_components.filter(item => !!item?.id); +const StanfordPage = async ({node}: { node: NodeStanfordPage }) => { + const fullWidth = node.layoutSelection?.id === 'stanford_basic_page_full'; - const fullWidth = node.layout_selection?.resourceIdObjMeta?.drupal_internal__target_id === 'stanford_basic_page_full'; return (
- + {node.suPageComponents && + + }
) } diff --git a/src/components/node/stanford-person/card.tsx b/src/components/node/stanford-person/card.tsx index 704bb981..9dadb333 100644 --- a/src/components/node/stanford-person/card.tsx +++ b/src/components/node/stanford-person/card.tsx @@ -1,24 +1,17 @@ "use client"; -import {Person} from "@/lib/drupal/drupal"; - import VerticalPersonCard from "./vertical-card"; import HorizontalPersonCard from "./horizontal-card"; import {useResizeDetector} from "react-resize-detector"; -import {ErrorBoundary} from "react-error-boundary"; +import {NodeStanfordPerson} from "@/lib/gql/__generated__/drupal"; -const StanfordPersonCard = ({node, ...props}: { node: Person }) => { +const StanfordPersonCard = ({node, ...props}: { node: NodeStanfordPerson }) => { const {width, ref} = useResizeDetector(); const Card = (width && width < 510) ? VerticalPersonCard : HorizontalPersonCard; return ( - } - onError={e => console.error(e.message)} - > -
- -
-
+
+ +
) } export default StanfordPersonCard; \ No newline at end of file diff --git a/src/components/node/stanford-person/horizontal-card.tsx b/src/components/node/stanford-person/horizontal-card.tsx index 747f92cf..de277f20 100644 --- a/src/components/node/stanford-person/horizontal-card.tsx +++ b/src/components/node/stanford-person/horizontal-card.tsx @@ -1,24 +1,20 @@ import Image from "next/image"; import Link from "@/components/patterns/elements/drupal-link"; -import Conditional from "@/components/utils/conditional"; import {EnvelopeIcon} from "@heroicons/react/20/solid"; import LibCal from "./libcal"; -import {Person} from "@/lib/drupal/drupal"; -import {PropsWithoutRef} from "react"; + import EmailLink from "@/components/patterns/elements/email-link"; import {buildUrl} from "@/lib/drupal/utils"; +import {NodeStanfordPerson} from "@/lib/gql/__generated__/drupal"; -interface Props extends PropsWithoutRef { - node: Person +interface Props { + node: NodeStanfordPerson h3Heading?: boolean } -const HorizontalPersonCard = ({node, h3Heading, ...props}: PropsWithoutRef) => { +const HorizontalPersonCard = ({node, h3Heading, ...props}: Props) => { const HeadingElement = h3Heading ? 'h3' : 'h2'; - const imageUrl = node.su_person_photo?.field_media_image.uri.url; - const imageAlt = node.su_person_photo?.field_media_image?.resourceIdObjMeta?.alt ?? ''; - const placeholder = node.su_person_photo?.field_media_image?.uri.base64; - if (!node.path?.alias) console.error('Missing path alias for person card component: ' + node.id) + const imageUrl = node.suPersonPhoto?.mediaImage.url; return (
{imageAlt}
- {node.title} - -
{node.su_person_full_title}
-
+ {(node.suPersonFullTitle) && +
{node.suPersonFullTitle}
+ } - + {(node.suPersonEmail) &&
-
+ }
- + {node.sulPersonLibcalId && + + }
) diff --git a/src/components/node/stanford-person/libguide.tsx b/src/components/node/stanford-person/libguide.tsx index d25281c6..81efcd8c 100644 --- a/src/components/node/stanford-person/libguide.tsx +++ b/src/components/node/stanford-person/libguide.tsx @@ -1,13 +1,11 @@ "use client"; -import Conditional from "@/components/utils/conditional"; import Link from "@/components/patterns/elements/drupal-link"; -import {PropsWithoutRef, useEffect, useId, useRef, useState} from "react"; +import {HTMLAttributes, useEffect, useId, useRef, useState} from "react"; import {useAutoAnimate} from "@formkit/auto-animate/react"; import {LibGuide} from "@/lib/drupal/drupal"; -import {ErrorBoundary} from "react-error-boundary"; -interface Props extends PropsWithoutRef { +type Props = HTMLAttributes & { guides: LibGuide[] headingLevel?: number } @@ -18,36 +16,27 @@ const LibGuides = ({guides, headingLevel = 2, ...props}: Props) => { return (
- } - onError={e => console.error(e.message)} - > - 0}> -
- {headingLevel === 2 &&

Course Guides

} - {headingLevel === 3 &&

Course Guides

} - -
-
-
+ {(courseGuides.length > 0) && +
+ {headingLevel === 2 &&

Course Guides

} + {headingLevel === 3 &&

Course Guides

} + +
+ } - } - onError={e => console.error(e.message)} - > - 0}> -
- {headingLevel === 2 &&

Topic Guides

} - {headingLevel === 3 &&

Topic Guides

} - -
-
-
+ + {(topicGuides.length > 0) && +
+ {headingLevel === 2 &&

Topic Guides

} + {headingLevel === 3 &&

Topic Guides

} + +
+ }
) } -const LibGuideSection = ({heading, guides}: {heading: string, guides: LibGuide[]}) => { +const LibGuideSection = ({heading, guides}: { heading: string, guides: LibGuide[] }) => { const firstGuides = guides.slice(0, 5); const moreGuides = guides.slice(5) const moreGuideRef = useRef(null); @@ -69,18 +58,18 @@ const LibGuideSection = ({heading, guides}: {heading: string, guides: LibGuide[]
  • {guide.title}
  • )} - 0}> - + {(moreGuides.length > 0 && !showMore) && + <> {moreGuides.map((guide, i) =>
  • {guide.title}
  • )} -
    -
    + + } - 0}> + {moreGuides.length > 0 && - + } ) } diff --git a/src/components/node/stanford-person/list-item.tsx b/src/components/node/stanford-person/list-item.tsx index 5610c541..3332740f 100644 --- a/src/components/node/stanford-person/list-item.tsx +++ b/src/components/node/stanford-person/list-item.tsx @@ -1,13 +1,11 @@ -import {Person} from "@/lib/drupal/drupal"; import Image from "next/image"; import Link from "@/components/patterns/elements/drupal-link"; import LibCal from "./libcal"; import {buildUrl} from "@/lib/drupal/utils"; +import {NodeStanfordPerson} from "@/lib/gql/__generated__/drupal"; -const StanfordPersonListItem = ({node, ...props}: { node: Person }) => { - const imageUrl = node.su_person_photo?.field_media_image.uri.url; - const imageAlt = node.su_person_photo?.field_media_image?.resourceIdObjMeta?.alt || ''; - const placeholder = node.su_person_photo?.field_media_image?.uri.base64; +const StanfordPersonListItem = ({node, ...props}: { node: NodeStanfordPerson }) => { + const imageUrl = node.suPersonPhoto?.mediaImage.url; return (
    { className="relative rounded-full overflow-hidden aspect-[1/1] w-[130px] @lg:w-[215px] mx-auto"> {imageAlt} }
    -

    {node.title}

    -
    {node.su_person_short_title}
    +
    {node.suPersonShortTitle}
    - {node.sul_person__libcal_id && + {node.sulPersonLibcalId &&
    - +
    }
    diff --git a/src/components/node/stanford-person/page-display.tsx b/src/components/node/stanford-person/page-display.tsx index c7f665ac..88f65a3e 100644 --- a/src/components/node/stanford-person/page-display.tsx +++ b/src/components/node/stanford-person/page-display.tsx @@ -1,6 +1,4 @@ -import {Person, StanfordParagraph} from "@/lib/drupal/drupal"; import Image from "next/image"; -import Conditional from "@/components/utils/conditional"; import formatHtml from "@/lib/format-html"; import {DrupalLinkButton} from "@/components/patterns/link"; import {EnvelopeIcon, LinkIcon, MapIcon, PhoneIcon} from "@heroicons/react/20/solid"; @@ -8,19 +6,15 @@ import Link from "@/components/patterns/elements/drupal-link"; import LibCal from "@/components/node/stanford-person/libcal"; import LibGuides from "@/components/node/stanford-person/libguide"; import fetchLibGuides from "@/lib/libguides"; -import fetchComponents from "@/lib/fetch-components"; -import Paragraph from "@/components/paragraph"; import EmailLink from "@/components/patterns/elements/email-link"; import {buildUrl} from "@/lib/drupal/utils"; +import {NodeStanfordPerson} from "@/lib/gql/__generated__/drupal"; +import Paragraph from "@/components/paragraph"; -const StanfordPerson = async ({node, ...props}: { node: Person }) => { - node.su_person_components = await fetchComponents(node.su_person_components || []); - node.su_person_components = node.su_person_components.filter(item => !!item?.id); - node.lib_guides = node.sul_person__libguide_id ? await fetchLibGuides({accountId: node.sul_person__libguide_id}) : []; +const StanfordPerson = async ({node, ...props}: { node: NodeStanfordPerson }) => { - const imageUrl = node.su_person_photo?.field_media_image.uri.url; - const imageAlt = node.su_person_photo?.field_media_image?.resourceIdObjMeta?.alt ?? ''; - const placeholder = node.su_person_photo?.field_media_image?.uri.base64; + const libGuides = node.sulPersonLibguideId ? await fetchLibGuides({accountId: node.sulPersonLibguideId}) : []; + const imageUrl = node.suPersonPhoto?.mediaImage.url return (
    @@ -29,11 +23,10 @@ const StanfordPerson = async ({node, ...props}: { node: Person }) => {
    {imageAlt}
    @@ -42,41 +35,42 @@ const StanfordPerson = async ({node, ...props}: { node: Person }) => {
    - - {(node.su_person_full_title || node.su_person_short_title) && -
    {node.su_person_full_title || node.su_person_short_title}
    + {(node.suPersonFullTitle || node.suPersonShortTitle) && +
    {node.suPersonFullTitle || node.suPersonShortTitle}
    } - -
    Pronouns: {node.su_person_pronouns}
    -
    + {(node.suPersonPronouns) && +
    Pronouns: {node.suPersonPronouns}
    + }
    - + {node.sulPersonLibcalId && + + }
    {node.body &&
    {formatHtml(node.body)}
    } + className="type-1 rs-mt-6 sm:rs-mt-0 rs-mb-7 md:w-10/12 ">{formatHtml(node.body.processed)}
    } - {node.su_person_components && + {node.suPersonComponents &&
    - {node.su_person_components.map(component => - + {node.suPersonComponents.map(paragraph => + )}
    } - {node.lib_guides.length > 0 && - + {libGuides.length > 0 && + } - {(node.su_person_education && node.su_person_education.length > 0) && + {node.suPersonEducation &&

    Education

    - {node.su_person_education.map((education, index) => + {node.suPersonEducation.map((education, index) =>
    {education}
    @@ -84,29 +78,36 @@ const StanfordPerson = async ({node, ...props}: { node: Person }) => {
    } - {(node.su_person_research && node.su_person_research.length > 0) && + {node.suPersonResearch &&

    Research

    - {node.su_person_research.map((interest, index) => + {node.suPersonResearch.map((interest, index) =>
    - {formatHtml(interest)} + {formatHtml(interest.processed)}
    )}
    } - - {node.su_person_research_interests} - + {(node.suPersonResearchInterests) && +
    + {node.suPersonResearchInterests} +
    + } - {(node.su_person_affiliations && node.su_person_affiliations.length > 0) && + {node.suPersonAffiliations &&

    Stanford Affiliations

    - {node.su_person_affiliations.map((affiliation, index) => - {affiliation.title} + {node.suPersonAffiliations.map((affiliation, index) => { + if (!affiliation.url) return; + return ( + + {affiliation.title} + + ) + } )}
    } @@ -114,30 +115,30 @@ const StanfordPerson = async ({node, ...props}: { node: Person }) => {
    - {(node.su_person_telephone || node.su_person_mobile_phone || node.su_person_fax || node.su_person_mail_code || node.su_person_email) && + {(node.suPersonTelephone || node.suPersonMobilePhone || node.suPersonFax || node.suPersonMailCode || node.suPersonEmail) && <>

    Contact

      - -
    • p {node.su_person_telephone}
    • -
      - -
    • m {node.su_person_mobile_phone}
    • -
      - -
    • f {node.su_person_fax}
    • -
      - -
    • Mail Code: {node.su_person_mail_code}
    • -
      - {node.su_person_email && + {(node.suPersonTelephone) && +
    • p {node.suPersonTelephone}
    • + } + {(node.suPersonMobilePhone) && +
    • m {node.suPersonMobilePhone}
    • + } + {(node.suPersonFax) && +
    • f {node.suPersonFax}
    • + } + {(node.suPersonMailCode) && +
    • Mail Code: {node.suPersonMailCode}
    • + } + {node.suPersonEmail &&
    • @@ -148,56 +149,64 @@ const StanfordPerson = async ({node, ...props}: { node: Person }) => {
    - + {(node.suPersonLocationName || node.suPersonLocationAddress || node.suPersonMapUrl) && + <>

    Location

    - - {node.su_person_location_name} - + {node.suPersonLocationName && +
    + {node.suPersonLocationName} +
    + } - {node.su_person_location_address &&
    - {formatHtml(node.su_person_location_address)} -
    } + {node.suPersonLocationAddress && +
    + {formatHtml(node.suPersonLocationAddress.processed)} +
    + } - {node?.su_person_map_url?.url && + {node.suPersonMapUrl?.url &&
    - Map URL: {node.su_person_map_url.url} + Map URL: {node.suPersonMapUrl.url}
    } -
    + + }
    - {(node.su_person_links && node.su_person_links.length > 0) && + {node.suPersonLinks &&

    Links

    - {node.su_person_links.map((link, index) => -
    - * {link.title} -
    + {node.suPersonLinks.map((link, index) => { + if (!link.url) return; + return (
    + + * {link.title} + +
    ) + } )}
    } - {node?.su_person_profile_link && - - {node.su_person_profile_link.title} + {node.suPersonProfileLink?.url && + + {node.suPersonProfileLink.title} }
    - ) +) } export default StanfordPerson; \ No newline at end of file diff --git a/src/components/node/stanford-person/vertical-card.tsx b/src/components/node/stanford-person/vertical-card.tsx index 8524afe9..54753ca4 100644 --- a/src/components/node/stanford-person/vertical-card.tsx +++ b/src/components/node/stanford-person/vertical-card.tsx @@ -1,25 +1,20 @@ import Image from "next/image"; import Link from "@/components/patterns/elements/drupal-link"; -import Conditional from "@/components/utils/conditional"; import {EnvelopeIcon} from "@heroicons/react/20/solid"; import LibCal from "./libcal"; -import {Person} from "@/lib/drupal/drupal"; import EmailLink from "@/components/patterns/elements/email-link"; -import {PropsWithoutRef} from "react"; + import {buildUrl} from "@/lib/drupal/utils"; +import {NodeStanfordPerson} from "@/lib/gql/__generated__/drupal"; interface Props { - node: Person + node: NodeStanfordPerson h3Heading?: boolean } -const VerticalPersonCard = ({node, h3Heading, ...props}: PropsWithoutRef) => { +const VerticalPersonCard = ({node, h3Heading, ...props}: Props) => { const HeadingElement = h3Heading ? 'h3' : 'h2'; - const imageUrl = node.su_person_photo?.field_media_image.uri.url; - const imageAlt = node.su_person_photo?.field_media_image?.resourceIdObjMeta?.alt ?? ''; - const imageHeight = node.su_person_photo?.field_media_image?.resourceIdObjMeta?.height; - const imageWidth = node.su_person_photo?.field_media_image?.resourceIdObjMeta?.width; - const placeholder = node.su_person_photo?.field_media_image?.uri.base64; + const imageUrl = node.suPersonPhoto?.mediaImage.url return (
    )
    {imageAlt}
    @@ -46,22 +40,22 @@ const VerticalPersonCard = ({node, h3Heading, ...props}: PropsWithoutRef)
    {node.title}
    - -
    {node.su_person_full_title}
    -
    + {(node.suPersonFullTitle) && +
    {node.suPersonFullTitle}
    + } - {node.su_person_email && + {node.suPersonEmail &&
    @@ -69,7 +63,9 @@ const VerticalPersonCard = ({node, h3Heading, ...props}: PropsWithoutRef)
    - + {node.sulPersonLibcalId && + + }
    ) diff --git a/src/components/node/stanford-publication/card.tsx b/src/components/node/stanford-publication/card.tsx index 330f2472..601506ca 100644 --- a/src/components/node/stanford-publication/card.tsx +++ b/src/components/node/stanford-publication/card.tsx @@ -1,22 +1,20 @@ -import {Publication} from "@/lib/drupal/drupal"; + import Card from "@/components/patterns/card"; -import Conditional from "@/components/utils/conditional"; import Link from "@/components/patterns/elements/drupal-link"; +import {NodeStanfordPublication} from "@/lib/gql/__generated__/drupal"; -const StanfordPublicationCard = ({node, ...props}: { node: Publication }) => { - const topics = node.su_publication_topics?.filter(topic => !!topic?.name) || []; +const StanfordPublicationCard = ({node, ...props}: { node: NodeStanfordPublication }) => { + const topics = node.suPublicationTopics?.filter(topic => !!topic?.name) || []; return (
    - {node.su_publication_citation?.citation_type?.label} + {node.suPublicationCitation?.__typename}
    - } header={ - + {node.title} } diff --git a/src/components/node/stanford-publication/list-item.tsx b/src/components/node/stanford-publication/list-item.tsx index 6eef0b31..1e7fa9d6 100644 --- a/src/components/node/stanford-publication/list-item.tsx +++ b/src/components/node/stanford-publication/list-item.tsx @@ -1,28 +1,27 @@ import Link from "@/components/patterns/elements/drupal-link"; -import {DrupalPublicationCitation, Publication} from "@/lib/drupal/drupal"; +import {CitationUnion, NodeStanfordPublication} from "@/lib/gql/__generated__/drupal"; -const StanfordPublicationListItem = ({node, ...props}: {node:Publication}) => { +const StanfordPublicationListItem = ({node, ...props}: {node:NodeStanfordPublication}) => { return (
    - +

    {node.title}

    - {node.su_publication_citation && } + {node.suPublicationCitation && }
    ) } -const Citation = ({citation}: {citation: DrupalPublicationCitation}) => { +const Citation = ({citation}: {citation: CitationUnion}) => { return ( <> - {citation.su_author && citation.su_author.map((author, index) => + {citation.suAuthor && citation.suAuthor.map((author, index) =>
    {author.given} {author.family}
    )} - {citation.su_publisher} - {citation.su_page} + {citation.suPublisher} ) diff --git a/src/components/node/stanford-publication/page-display.tsx b/src/components/node/stanford-publication/page-display.tsx index 0123ad0a..5bba021c 100644 --- a/src/components/node/stanford-publication/page-display.tsx +++ b/src/components/node/stanford-publication/page-display.tsx @@ -1,47 +1,33 @@ -import "server-only"; -import {Publication, StanfordParagraph} from "@/lib/drupal/drupal"; -import Conditional from "@/components/utils/conditional"; import {ParagraphRows} from "@/components/paragraph/rows/rows"; import {DrupalLinkButton} from "@/components/patterns/link"; -import fetchComponents from "@/lib/fetch-components"; - -const StanfordPublication = async ({node, ...props}: { node: Publication }) => { - - node.su_publication_components = await fetchComponents(node.su_publication_components || []); - node.su_publication_components = node.su_publication_components.filter(item => !!item?.id); - - const getMonthName = (monthNumber: number) => { - const date = new Date(); - date.setMonth(monthNumber - 1); - - return date.toLocaleString('en-US', {month: 'long'}); - }; +import {NodeStanfordPublication} from "@/lib/gql/__generated__/drupal"; +const StanfordPublication = async ({node, ...props}: { node: NodeStanfordPublication }) => { return (
    - + {(node.suPublicationCitation?.__typename) &&
    - {node.su_publication_citation?.citation_type?.label} + {node.suPublicationCitation.__typename}
    -
    + }
    - 0}> + {node.suPublicationComponents &&
    - +
    -
    + }
    - {(node.su_publication_citation?.su_author && node.su_publication_citation?.su_author.length > 0) && + {node.suPublicationCitation?.suAuthor &&

    Author(s)

    - {node.su_publication_citation?.su_author.map((author, index) => + {node.suPublicationCitation.suAuthor.map((author, index) =>
    {author.given} {author.family}
    @@ -50,80 +36,45 @@ const StanfordPublication = async ({node, ...props}: { node: Publication }) => {
    } - + {node.suPublicationCitation?.suPublisher &&

    Publisher

    - {node.su_publication_citation?.su_publisher} + {node.suPublicationCitation.suPublisher}
    -
    + } - + {(node.suPublicationCitation?.__typename === 'SuArticleJournal' && node.suPublicationCitation.suJournalPublisher) &&

    Journal Name

    - {node.su_publication_citation?.su_journal_publisher} -
    -
    -
    - - -
    -

    Publication Date

    - {(node.su_publication_citation?.su_month && node.su_publication_citation?.su_day) && -
    - {getMonthName(node.su_publication_citation?.su_month)} {node.su_publication_citation?.su_day}, {node.su_publication_citation?.su_year} -
    - } - - {node.su_publication_citation?.su_month}, {node.su_publication_citation?.su_year} - - - {node.su_publication_citation?.su_year} - -
    -
    - - -
    -

    Type of Dissertation

    -
    - {node.su_publication_citation?.su_genre} -
    -
    -
    - - -
    -

    DOI

    -
    - {node.su_publication_citation?.su_doi} + {node.suPublicationCitation?.suJournalPublisher}
    -
    + }
    - {node.su_publication_cta && + {node.suPublicationCta?.url &&
    - {node.su_publication_cta.title} + {node.suPublicationCta.title}
    }
    - 0}> + {node.suPublicationTopics &&

    Related Topics

    - {node.su_publication_topics && node.su_publication_topics.map((term, index) => - {(index ? ', ' : '') + term.name} + {node.suPublicationTopics.map((term, index) => + + {(index ? ', ' : '') + term.name} + )}
    -
    + }
    ) } diff --git a/src/components/node/sul-library/card.tsx b/src/components/node/sul-library/card.tsx index d622ad93..e4863288 100644 --- a/src/components/node/sul-library/card.tsx +++ b/src/components/node/sul-library/card.tsx @@ -1,10 +1,10 @@ import Link from "@/components/patterns/elements/drupal-link"; -import {Library} from "@/lib/drupal/drupal"; +import {NodeSulLibrary} from "@/lib/gql/__generated__/drupal"; -const SulLibraryCard = ({node, ...props}: {node: Library}) => { +const SulLibraryCard = ({node, ...props}: {node: NodeSulLibrary}) => { return (
    -

    {node.title}

    diff --git a/src/components/node/sul-library/library-header.tsx b/src/components/node/sul-library/library-header.tsx index 7366f80a..f4090f97 100644 --- a/src/components/node/sul-library/library-header.tsx +++ b/src/components/node/sul-library/library-header.tsx @@ -1,4 +1,4 @@ -import {Library} from "@/lib/drupal/drupal"; + import Card from "@/components/patterns/card"; import {EnvelopeIcon, PhoneIcon} from "@heroicons/react/20/solid"; import Link from "@/components/patterns/elements/drupal-link"; @@ -8,15 +8,14 @@ import Image from "next/image"; import LibraryHeaderHours from "./library-hours"; import EmailLink from "@/components/patterns/elements/email-link"; import {buildUrl} from "@/lib/drupal/utils"; +import {NodeSulLibrary} from "@/lib/gql/__generated__/drupal"; -const LibraryHeader = ({node}: { node: Library }) => { - const bannerImageUrl = node.su_library__banner?.field_media_image.uri.url; - const bannerImageAlt = node.su_library__banner?.field_media_image?.resourceIdObjMeta?.alt ?? ''; - const bannerPlaceholder = node.su_library__banner?.field_media_image?.uri.base64; +const LibraryHeader = ({node}: { node: NodeSulLibrary }) => { + const bannerImageUrl = node.suLibraryBanner?.mediaImage.url; + const bannerImageAlt = node.suLibraryBanner?.mediaImage.alt|| ''; - const contactImageUrl = node.su_library__contact_img?.field_media_image.uri.url || bannerImageUrl; - const contactImageAlt = node.su_library__contact_img?.field_media_image?.resourceIdObjMeta?.alt ?? bannerImageAlt; - const contactPlaceholder = node.su_library__contact_img?.field_media_image?.uri.base64 ?? bannerPlaceholder; + const contactImageUrl = node.suLibraryContactImg?.mediaImage.url || bannerImageUrl; + const contactImageAlt = node.suLibraryContactImg?.mediaImage.alt || bannerImageAlt; return (
    @@ -29,8 +28,6 @@ const LibraryHeader = ({node}: { node: Library }) => { alt={bannerImageAlt} fill sizes="(max-width: 768px) 100vw, (max-width: 900px) 50vw, (max-width: 1700px) 33vw, 500px" - placeholder={bannerPlaceholder ? 'blur' : 'empty'} - blurDataURL={bannerPlaceholder} /> } @@ -54,51 +51,49 @@ const LibraryHeader = ({node}: { node: Library }) => { alt={contactImageAlt} fill sizes="(max-width: 768px) 100vw, (max-width: 900px) 50vw, (max-width: 1700px) 33vw, 500px" - placeholder={contactPlaceholder ? 'blur' : 'empty'} - blurDataURL={contactPlaceholder} /> } footer={ <>
    - {node.su_library__phone && + {node.suLibraryPhone &&
    - {node.su_library__phone} + {node.suLibraryPhone}
    } - {node.su_library__email && + {node.suLibraryEmail &&
    - +
    } - {node.su_library__address && + {node.suLibraryAddress &&
    - {node.su_library__map_link ? ( - -
    {node.su_library__address.address_line1}
    -
    {node.su_library__address.address_line2}
    -
    {node.su_library__address.locality}, {node.su_library__address.administrative_area} {node.su_library__address.postal_code}
    + {node.suLibraryMapLink?.url ? ( + +
    {node.suLibraryAddress.addressLine1}
    +
    {node.suLibraryAddress.addressLine2}
    +
    {node.suLibraryAddress.locality}, {node.suLibraryAddress.administrativeArea} {node.suLibraryAddress.postalCode}
    ) : ( <> -
    {node.su_library__address.address_line1}
    -
    {node.su_library__address.address_line2}
    -
    {node.su_library__address.locality}, {node.su_library__address.administrative_area} {node.su_library__address.postal_code}
    +
    {node.suLibraryAddress.addressLine1}
    +
    {node.suLibraryAddress.addressLine2}
    +
    {node.suLibraryAddress.locality}, {node.suLibraryAddress.administrativeArea} {node.suLibraryAddress.postalCode}
    )}
    }
    - {node.su_library__hours && - + {node.suLibraryHours && + } } diff --git a/src/components/node/sul-library/list-item.tsx b/src/components/node/sul-library/list-item.tsx index 3ce02e02..52eb8ea5 100644 --- a/src/components/node/sul-library/list-item.tsx +++ b/src/components/node/sul-library/list-item.tsx @@ -1,10 +1,10 @@ import Link from "@/components/patterns/elements/drupal-link"; -import {Library} from "@/lib/drupal/drupal"; +import {NodeSulLibrary} from "@/lib/gql/__generated__/drupal"; -const SulLibraryListItem = ({node, ...props}: {node: Library}) => { +const SulLibraryListItem = ({node, ...props}: {node: NodeSulLibrary}) => { return (
    - +

    {node.title}

    diff --git a/src/components/node/sul-library/page-display.tsx b/src/components/node/sul-library/page-display.tsx index 331677a4..a5ce25a2 100644 --- a/src/components/node/sul-library/page-display.tsx +++ b/src/components/node/sul-library/page-display.tsx @@ -1,36 +1,36 @@ -import {Library, StanfordParagraph} from "@/lib/drupal/drupal"; + import {ParagraphRows} from "@/components/paragraph/rows/rows"; -import fetchComponents from "@/lib/fetch-components"; import LibraryAdditionalHours from "@/components/node/sul-library/library-additional-hours"; import formatHtml from "@/lib/format-html"; +import {NodeSulLibrary} from "@/lib/gql/__generated__/drupal"; + +const SulLibrary = async ({node, ...props}: { node: NodeSulLibrary }) => { -const SulLibrary = async ({node, ...props}: { node: Library }) => { - node.su_library__paragraphs = await fetchComponents(node.su_library__paragraphs || []); - node.su_library__paragraphs = node.su_library__paragraphs.filter(item => !!item?.id); - const fullWidth = node.layout_selection?.resourceIdObjMeta?.drupal_internal__target_id === 'sul_library_full_width' + const fullWidth = node.layoutSelection?.id === 'sul_library_full_width' return (
    - {(node.sul_library__a11y || node.su_library__hours) && + {(node.sulLibraryA11y || node.suLibraryHours) &&
    - {node.sul_library__a11y && + {node.sulLibraryA11y &&

    Accessibility

    - {formatHtml(node.sul_library__a11y)} + {formatHtml(node.sulLibraryA11y.processed)}
    } - {node.su_library__hours && - + {node.suLibraryHours && + }
    } - - + {node.suLibraryParagraphs && + + }
    ) } diff --git a/src/components/node/sul-study-place/card.tsx b/src/components/node/sul-study-place/card.tsx index 369ef1d4..d7845cf2 100644 --- a/src/components/node/sul-study-place/card.tsx +++ b/src/components/node/sul-study-place/card.tsx @@ -1,25 +1,23 @@ -import {StudyPlace} from "@/lib/drupal/drupal"; + import Link from "@/components/patterns/elements/drupal-link"; import Image from "next/image"; import {MapPinIcon} from "@heroicons/react/24/outline"; -import Conditional from "@/components/utils/conditional"; import StudyPlaceHours from "./study-place-today-hours"; -import {DrupalTaxonomyTerm} from "next-drupal"; import {CalendarDaysIcon, ChevronRightIcon} from "@heroicons/react/20/solid"; import {buildUrl} from "@/lib/drupal/utils"; +import {NodeSulStudyPlace, TermUnion} from "@/lib/gql/__generated__/drupal"; -const SulStudyPlaceCard = ({node}: { node: StudyPlace }) => { +const SulStudyPlaceCard = ({node}: { node: NodeSulStudyPlace }) => { // Filter out empty terms and deduplicate terms by their ID. - const features: DrupalTaxonomyTerm[] = node.sul_study__features?.filter((term: DrupalTaxonomyTerm, index, self) => - term.name?.length > 0 && index === self.findIndex((t: DrupalTaxonomyTerm) => ( + const features: TermUnion[] = node.sulStudyFeatures?.filter((term, index, self) => + term.name?.length > 0 && index === self.findIndex((t) => ( t.id === term.id )) ) || []; - const imageUrl = node.sul_study__branch.su_library__contact_img?.field_media_image.uri.url - const imageAlt = node.sul_study__branch.su_library__contact_img?.field_media_image?.resourceIdObjMeta?.alt ?? ''; - const placeholder = node.sul_study__branch.su_library__contact_img?.field_media_image?.uri.base64; + const imageUrl = node.sulStudyBranch.suLibraryContactImg?.mediaImage.url + const imageAlt = node.sulStudyBranch.suLibraryContactImg?.mediaImage.alt|| ''; return ( <> @@ -32,49 +30,47 @@ const SulStudyPlaceCard = ({node}: { node: StudyPlace }) => { alt={imageAlt} fill sizes="(max-width: 768px) 100vw, (max-width: 900px) 50vw, (max-width: 1700px) 33vw, 500px" - placeholder={placeholder ? 'blur' : 'empty'} - blurDataURL={placeholder} /> } - + {(node.sulStudyLibcalId) &&
    - Reserve Space at {node.sul_study__branch.title} + Reserve Space at {node.sulStudyBranch.title}
    -
    + }
    -

    {node.sul_study__type.name}

    +

    {node.sulStudyType.name}

    - {node.sul_study__branch?.su_library__hours && - + {node.sulStudyBranch?.suLibraryHours && + }
    - -
    {node.sul_study__branch.title}
    +
    {node.sulStudyBranch.title}
    - {(node.sul_study__capacity || features) && + {(node.sulStudyCapacity || features) &&