From 1bf28854329175d9dbe3afaf9b055fe67007d0ee Mon Sep 17 00:00:00 2001 From: Harry Ross Date: Wed, 8 May 2024 17:45:58 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A2=20App=20Router=20PoC=20-=20Product?= =?UTF-8?q?s=20Pages=20(#2420)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added first iteration of products index * fixed menuwrapper reference * fixed broken undefined * fixed next/router * added draft mode stuff * simplified client + server pages * updated schema * added cache + analytics scripts * added next/third-parties for GTM * added loading fallback * added page stub code * added generateStaticParams to dynamic route * added force-static to layout for megamenu * added basic notfound page * added back old way of doing gtm * added dynamic routing * made lint fix * added caching for megamenu * added icons as metadata * fixed import * fixed broken type declarations * fixed reference to error boundary * added timestamp to layout * changed to be string * added viewport tag * added gh actions cache * changed ot be hash of pnpm lock * removed change to workflow * removed timestamps, added better typing * changed data fetching logic * removed dynamic params, added footer * added timestamp rerun * renamed draft api url * added similar behaviour of pages * made it force static * added back cache thing * Removing i18n from the config * Adding Tina for the dynamic component * Adding updated pnpm.lock * Fixing clientpage path * adding App router breadcrumb * Added megamenu styling * Adding updated pnpm and tina lock * Adding use client tag for domain query * Adding metadata for the /product route * Fixing the default MetaData issue * Adding metadata for the dynamic products' route * Removing unused import * Removed the comments that are completed * Adding 404 page for App Routing * Fixing layout for the loading page * removing metadatabase prop * Adding breadcrumb for the dynamic routes of /products * Adding switch for Draft mode * Cleanup for the edit mode * Adding updated pnpm lock * Adding logic of enabling draft on login * Refactoring the component names as per their route * Fixing typos with file name * Fixing the naming conventions for product dynamic routes * Fixing relative path for the dynamic product routes * Adding metabase URL for metadata * Adding hostname to redirect when it gets login * Adding live banner component for the app routing * Updating references * removing false true * Splitting code for better readability * Fixing seo route with product review component * fixing the data issue with global variable * Removing the redirect * Adding isPreview variable to improve readability * removing themeColor as it's not supported by metadata * Fixing the import for userRouter carousel * Removing the async function for production collection * Adding comments for the authentication on enabling draft mode * Adding some more modular imports * Adding updated pnpm-lock file * Adding doc link for dynamic props * Adding GTM using thirdparty scripts --------- Co-authored-by: Aman Kumar [SSW] <71385247+amankumarrr@users.noreply.github.com> --- app/api/disable-draft/route.ts | 8 + app/api/enable-draft/route.ts | 9 + app/components/breadcrumb.tsx | 50 +++ app/layout.tsx | 88 +++++ app/live-steam-banner/live-stream.tsx | 118 ++++++ app/live-steam-banner/liveStreamWidget.tsx | 366 ++++++++++++++++++ app/loading.tsx | 7 + app/meta-data/default.ts | 73 ++++ app/not-found.tsx | 15 + app/products/[filename]/page.tsx | 77 ++++ app/products/[filename]/products-content.tsx | 31 ++ app/products/[filename]/products-preview.tsx | 14 + app/products/page.tsx | 36 ++ app/products/products-index-preview.tsx | 14 + app/products/products-index.tsx | 37 ++ app/utils/get-live-stream-data.ts | 19 + app/utils/get-mega-menu.ts | 10 + components/blocks/aboutUs.tsx | 2 + components/blocks/carousel.tsx | 3 +- components/blocks/domainFromQuery.tsx | 2 + components/blocks/downloadBlock.tsx | 2 + components/blocks/fixedTabsLayout.tsx | 2 + components/blocks/jotFormEmbed.tsx | 2 + components/blocks/mdxComponentRenderer.tsx | 8 +- components/blocks/newslettersTable.tsx | 2 + components/blocks/recurringEvent.tsx | 2 + components/blocks/tableLayout.tsx | 2 + components/blocks/testimonialsCard.tsx | 2 + components/blocks/testimonialsList.tsx | 2 + components/blocks/upcomingEvents.tsx | 2 + components/blocks/youtubePlaylist.tsx | 2 + components/bookingForm/bookingForm.tsx | 2 + components/button/button.tsx | 5 + components/button/utilityButton.tsx | 2 + components/embeds/tweetEmbed.tsx | 2 + components/filter/FilterGroup.tsx | 2 + components/filter/clients.tsx | 2 + components/filter/events.tsx | 2 + components/filter/opportunities.tsx | 2 + components/layout/analytics.tsx | 1 + components/layout/footer.tsx | 113 ------ components/layout/footer/copyright-info.tsx | 22 ++ components/layout/footer/deployment-info.tsx | 33 ++ components/layout/footer/divider.tsx | 1 + components/layout/footer/footer.tsx | 27 ++ components/layout/footer/pre-footer.tsx | 10 + components/layout/footer/site-info.tsx | 28 ++ components/layout/layout.tsx | 5 +- components/layout/theme.tsx | 2 + components/liveStream/liveStreamBanner.tsx | 2 + components/liveStream/liveStreamWidget.tsx | 19 +- components/offices/testimonialPanel.tsx | 2 + components/server/MenuWrapper.tsx | 19 + components/testimonials/TestimonialRow.tsx | 2 + components/usergroup/latestTech/index.tsx | 2 + components/usergroup/readMore.tsx | 2 + .../util/{ => error}/error-boundary.tsx | 0 components/util/error/error-page.tsx | 77 ++++ components/util/error/error.tsx | 136 +++++++ components/util/videoCards.tsx | 2 + components/videoModal.tsx | 2 + hooks/useSeo.ts | 40 ++ next.config.mjs | 4 - package.json | 2 + pages/_error.tsx | 2 +- pages/products/[filename].tsx | 86 ---- pages/products/index.tsx | 57 --- pnpm-lock.yaml | 111 ++++-- tailwind.config.js | 1 + tina/collections/products.tsx | 3 + tina/config.tsx | 16 +- tsconfig.json | 4 +- 72 files changed, 1542 insertions(+), 317 deletions(-) create mode 100644 app/api/disable-draft/route.ts create mode 100644 app/api/enable-draft/route.ts create mode 100644 app/components/breadcrumb.tsx create mode 100644 app/layout.tsx create mode 100644 app/live-steam-banner/live-stream.tsx create mode 100644 app/live-steam-banner/liveStreamWidget.tsx create mode 100644 app/loading.tsx create mode 100644 app/meta-data/default.ts create mode 100644 app/not-found.tsx create mode 100644 app/products/[filename]/page.tsx create mode 100644 app/products/[filename]/products-content.tsx create mode 100644 app/products/[filename]/products-preview.tsx create mode 100644 app/products/page.tsx create mode 100644 app/products/products-index-preview.tsx create mode 100644 app/products/products-index.tsx create mode 100644 app/utils/get-live-stream-data.ts create mode 100644 app/utils/get-mega-menu.ts delete mode 100644 components/layout/footer.tsx create mode 100644 components/layout/footer/copyright-info.tsx create mode 100644 components/layout/footer/deployment-info.tsx create mode 100644 components/layout/footer/divider.tsx create mode 100644 components/layout/footer/footer.tsx create mode 100644 components/layout/footer/pre-footer.tsx create mode 100644 components/layout/footer/site-info.tsx create mode 100644 components/server/MenuWrapper.tsx rename components/util/{ => error}/error-boundary.tsx (100%) create mode 100644 components/util/error/error-page.tsx create mode 100644 components/util/error/error.tsx create mode 100644 hooks/useSeo.ts delete mode 100644 pages/products/[filename].tsx delete mode 100644 pages/products/index.tsx diff --git a/app/api/disable-draft/route.ts b/app/api/disable-draft/route.ts new file mode 100644 index 0000000000..0846a3e6b4 --- /dev/null +++ b/app/api/disable-draft/route.ts @@ -0,0 +1,8 @@ +import { draftMode } from "next/headers"; + +export async function GET() { + draftMode().disable(); + return new Response("Draft mode disabled", { + status: 200, + }); +} diff --git a/app/api/enable-draft/route.ts b/app/api/enable-draft/route.ts new file mode 100644 index 0000000000..384ba245a3 --- /dev/null +++ b/app/api/enable-draft/route.ts @@ -0,0 +1,9 @@ +import { draftMode } from "next/headers"; + +export async function GET() { + //TODO: ADD Tina Authentication + draftMode().enable(); + return new Response("Draft mode enabled", { + status: 200, + }); +} diff --git a/app/components/breadcrumb.tsx b/app/components/breadcrumb.tsx new file mode 100644 index 0000000000..3cc48a1274 --- /dev/null +++ b/app/components/breadcrumb.tsx @@ -0,0 +1,50 @@ +import NextBreadcrumbs from "@marketsystems/nextjs13-appdir-breadcrumbs"; +import React, { FC } from "react"; +import { tinaField } from "tinacms/dist/react"; + +interface BreadcrumbsProps { + path: string; + suffix: string; + title: string; + seoSchema?: { + title?: string; + }; +} +export const Breadcrumbs: FC = (props) => { + const listItemStyling = + "breadcrumb_item inline text-xs text-gray-700 no-underline not-first:before:content-bread not-first:before:px-2 before:list-none"; + + return ( +
+ +
+ ); +}; diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000000..357ccf39f2 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,88 @@ +import "styles.css"; + +// import { CustomLink } from "@/components/customLink"; +// import { Footer } from "@/components/layout/footer"; +import classNames from "classnames"; +import { Open_Sans } from "next/font/google"; +// import Head from "next/head"; +// import { Theme } from "../components/layout/theme"; +import { Footer } from "@/components/layout/footer/footer"; +import { MenuWrapper } from "@/components/server/MenuWrapper"; +import ChatBaseBot from "@/components/zendeskButton/chatBaseBot"; +import { Metadata, Viewport } from "next"; + +import { EventInfo } from "@/services/server/events"; +import { GoogleTagManager } from "@next/third-parties/google"; +import dayjs from "dayjs"; +import advancedFormat from "dayjs/plugin/advancedFormat"; +import isBetween from "dayjs/plugin/isBetween"; +import relativeTime from "dayjs/plugin/relativeTime"; +import timezone from "dayjs/plugin/timezone"; +import utc from "dayjs/plugin/utc"; +import Head from "next/head"; +import { LiveSteam } from "./live-steam-banner/live-stream"; +import { DEFAULT } from "./meta-data/default"; +import { getLiveStreamData } from "./utils/get-live-stream-data"; +import { getMegamenu } from "./utils/get-mega-menu"; + +dayjs.extend(relativeTime); +dayjs.extend(timezone); +dayjs.extend(utc); +dayjs.extend(advancedFormat); +dayjs.extend(isBetween); + +const openSans = Open_Sans({ + variable: "--open-sans-font", + subsets: ["latin"], +}); + +export const DEFAULT_METADATA: Metadata = { + ...DEFAULT, +}; + +export const viewport: Viewport = { + themeColor: "#ffffff", +}; + +export default async function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + const menuData = await getMegamenu(); + const liveStreamData: EventInfo = await getLiveStreamData(); + + return ( + + + + + + + + + + {/* */} + {/* Ensures next/font CSS variable is accessible for all components */} +
+
+ + + +
+
{children}
+ +
+
+ {/*
*/} + + + + + ); +} diff --git a/app/live-steam-banner/live-stream.tsx b/app/live-steam-banner/live-stream.tsx new file mode 100644 index 0000000000..bc3b7f1f37 --- /dev/null +++ b/app/live-steam-banner/live-stream.tsx @@ -0,0 +1,118 @@ +"use client"; + +import dayjs from "dayjs"; +import advancedFormat from "dayjs/plugin/advancedFormat"; +import isBetween from "dayjs/plugin/isBetween"; + +import relativeTime from "dayjs/plugin/relativeTime"; +import timezone from "dayjs/plugin/timezone"; +import utc from "dayjs/plugin/utc"; +import dynamic from "next/dynamic"; + +import { EventInfo } from "@/services/server/events"; +import { PropsWithChildren, useEffect, useState } from "react"; + +dayjs.extend(relativeTime); +dayjs.extend(timezone); +dayjs.extend(utc); +dayjs.extend(advancedFormat); +dayjs.extend(isBetween); + +const LiveStreamWidget = dynamic( + () => { + return import("./liveStreamWidget").then((mod) => mod.LiveStreamWidget); + }, + { + loading: () => <>, + ssr: true, + } +); + +const LiveStreamBanner = dynamic( + () => { + return import("@/components/liveStream/liveStreamBanner").then( + (mod) => mod.LiveStreamBanner + ); + }, + { + loading: () => <>, + ssr: true, + } +); + +const INTERVAL_MINUTES = 1; + +interface LiveStreamProps extends PropsWithChildren { + event: EventInfo; +} + +export function LiveSteam({ event, children }: LiveStreamProps) { + const [countdownMins, setCountdownMins] = useState(); + const [liveStreamDelayMinutes, setLiveStreamDelayMinutes] = useState(0); + + useEffect(() => { + if (!event?.StartDateTime || !event?.EndDateTime) { + return; + } + + const rightnow = dayjs()?.utc(); + + const liveDelay = event.SSW_LiveStreamDelayMinutes ?? 0; + if (!liveStreamDelayMinutes && event.SSW_DelayedLiveStreamStart) { + setLiveStreamDelayMinutes(liveDelay); + } + + const start = dayjs(event.StartDateTime).add(liveDelay, "minute"); + const minsToStart = start.diff(rightnow, "minute"); + setCountdownMins(minsToStart); + + const timer = setInterval( + () => { + setCountdownMins((countdownMins) => { + if (!countdownMins) return minsToStart; + return countdownMins - INTERVAL_MINUTES; + }); + }, + INTERVAL_MINUTES * 60 * 1000 + ); + + return () => clearInterval(timer); + }, [event, liveStreamDelayMinutes]); + + const rightnow = dayjs().utc(); + + const isLive = + countdownMins && + countdownMins <= 0 && + !!event && + rightnow.isBefore(event?.EndDateTime); + + const showBanner = + !!event && + dayjs().isBetween( + dayjs(event.StartShowBannerDateTime), + dayjs(event.EndShowBannerDateTime), + null, + "[)" + ); + + return ( + <> + {showBanner && ( + + )} +
+ {isLive && ( + + )} + {children} +
+ + ); +} diff --git a/app/live-steam-banner/liveStreamWidget.tsx b/app/live-steam-banner/liveStreamWidget.tsx new file mode 100644 index 0000000000..f1e876a1c7 --- /dev/null +++ b/app/live-steam-banner/liveStreamWidget.tsx @@ -0,0 +1,366 @@ +"use client"; + +import "react-tooltip/dist/react-tooltip.css"; + +import { InlineJotForm } from "@/components/blocks"; +import { CustomLink } from "@/components/customLink"; +import { YouTubeEmbed } from "@/components/embeds/youtubeEmbed"; +import { SocialIcons } from "@/components/socialIcons/socialIcons"; +import layoutData, { default as globals } from "@/content/global/index.json"; +import { getYouTubeId } from "@/helpers/embeds"; +import { sanitiseXSS } from "@/helpers/validator"; +import { SpeakerInfo } from "@/services/server/events"; +import axios from "axios"; +import classNames from "classnames"; +import Image from "next/image"; +import { useSearchParams } from "next/navigation"; +import Script from "next/script"; +import { useEffect, useState } from "react"; +import { TfiAngleDown, TfiAngleUp } from "react-icons/tfi"; +import { Tooltip } from "react-tooltip"; +import { LiveStreamProps } from "../../hooks/useLiveStreamProps"; + +type LiveStreamWidgetProps = { + isLive?: boolean; +} & LiveStreamProps; + +export const LiveStreamWidget = ({ isLive, event }: LiveStreamWidgetProps) => { + const eventDescriptionCollapseId = "eventDescription"; + + const [speakersInfo, setSpeakersInfo] = useState([]); + const [youtubeUrls, setYoutubeUrls] = useState<{ + videoUrl?: string; + chatUrl?: string; + liveStreamUrl?: string; + }>({}); + const [collapseMap, setCollapseMap] = useState<{ [key: string]: boolean }>({ + [eventDescriptionCollapseId]: true, + }); + const [eventDescriptionCollapsable, setEventDescriptionCollapsable] = + useState(); + + const param = useSearchParams(); + + const collapsableWidgetRefCallback = (e: HTMLDivElement) => { + !!e && + !!e.clientHeight && + (e.style.maxHeight = `${document.body.offsetHeight}px`); + }; + + const collapsableEventDescriptionRefCallback = (e: HTMLDivElement) => { + if (e) { + const collapsable = e.scrollHeight > e.clientHeight; + + if (eventDescriptionCollapsable == undefined) { + setEventDescriptionCollapsable(collapsable); + } + } + }; + + useEffect(() => { + const fetchLiveStreamInfo = async () => { + if ((!isLive && !param.get("liveStream")) || !event) { + return; + } + + setYoutubeUrls({ + videoUrl: `https://www.youtube.com/embed/${event?.YouTubeId}?rel=0&autoplay=1`, + chatUrl: `https://www.youtube.com/live_chat?v=${event?.YouTubeId}&embed_domain=${window.location.hostname}`, + liveStreamUrl: `https://www.youtube.com/watch?v=${event?.YouTubeId}`, + }); + + const ids: string[] = []; + const emails: string[] = []; + + if (event?.ExternalPresenters?.length) { + const presenterIds = event.ExternalPresenters.map((presenter) => + presenter.LookupId.toString() + ); + ids.push(...presenterIds); + } + + if (event?.InternalPresenters?.results?.length) { + emails.push(...event.InternalPresenters.results.map((i) => i.EMail)); + } + + const speakersInfo: SpeakerInfo[] = []; + + if (ids.length || emails.length) { + const idsParam = ids.map((id) => `ids=${id}`).join("&"); + const emailsParam = emails.map((email) => `emails=${email}`).join("&"); + + const remoteSpeakersInfoRes = await axios.get( + `/api/get-speakers?${idsParam}&${emailsParam}` + ); + + if ( + remoteSpeakersInfoRes.status === 200 && + remoteSpeakersInfoRes.data.length + ) { + speakersInfo.push(...remoteSpeakersInfoRes.data); + } + } else { + speakersInfo.push({ + Title: event.Presenter, + PresenterProfileLink: event?.PresenterProfileUrl?.Url, + }); + } + setSpeakersInfo(speakersInfo); + }; + + fetchLiveStreamInfo(); + }, [isLive, event, param]); + + if (!event) { + return <>; + } + + return ( + <> + + // ); }; diff --git a/components/layout/footer.tsx b/components/layout/footer.tsx deleted file mode 100644 index 9674c5bed3..0000000000 --- a/components/layout/footer.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import dayjs from "dayjs"; -import dynamic from "next/dynamic"; -import Image from "next/image"; -import { BuiltOnAzure } from "../blocks"; -import { CustomLink } from "../customLink"; -import { SocialIcons } from "../socialIcons/socialIcons"; -import { Container } from "../util/container"; -import { Section } from "../util/section"; - -export const Footer = () => { - return ( -
- - -
-
- -
-
-
- - -
-
-
- ); -}; - -const Divider = () => |; - -const CopyrightInfo = () => { - const chooseIssueURL = `https://github.com/${process.env.NEXT_PUBLIC_GITHUB_REPOSITORY}/issues/new/choose`; - return ( - <> -
- © 1990-{new Date().getFullYear()} SSW. All rights reserved. -
-
- FEEDBACK TO SSW - - - TERMS AND CONDITIONS - - - PRIVACY -
- - ); -}; - -const DeploymentInfo = () => { - const deploymentDate = process.env.NEXT_PUBLIC_GITHUB_RUN_DATE - ? dayjs.utc(process.env.NEXT_PUBLIC_GITHUB_RUN_DATE).fromNow() - : "XXX"; - const deploymentNumber = process.env.NEXT_PUBLIC_GITHUB_RUN_NUMBER || "XXX"; - - const deploymentLinkInfo = { - deploymentDate, - repo: process.env.NEXT_PUBLIC_GITHUB_REPOSITORY, - runId: process.env.NEXT_PUBLIC_GITHUB_RUN_ID, - deploymentNumber, - }; - - const DynamicDeploymentLink = dynamic(() => import("./deploymentLink"), { - ssr: false, - }); - - return ( -
- This website is under{" "} - - CONSTANT CONTINUOUS DEPLOYMENT - - .  - -
- ); -}; - -const SiteInfo = () => ( -
- - SITEMAP - - - - HEALTH CHECK - health check logo - -
-); -export const PreFooter = () => { - return ( -
- -
- ); -}; diff --git a/components/layout/footer/copyright-info.tsx b/components/layout/footer/copyright-info.tsx new file mode 100644 index 0000000000..766cda44e7 --- /dev/null +++ b/components/layout/footer/copyright-info.tsx @@ -0,0 +1,22 @@ +import { CustomLink } from "@/components/customLink"; +import { Divider } from "./divider"; + +export const CopyrightInfo = () => { + const chooseIssueURL = `https://github.com/${process.env.NEXT_PUBLIC_GITHUB_REPOSITORY}/issues/new/choose`; + return ( + <> +
+ © 1990-{new Date().getFullYear()} SSW. All rights reserved. +
+
+ FEEDBACK TO SSW + + + TERMS AND CONDITIONS + + + PRIVACY +
+ + ); +}; diff --git a/components/layout/footer/deployment-info.tsx b/components/layout/footer/deployment-info.tsx new file mode 100644 index 0000000000..9edde51cfe --- /dev/null +++ b/components/layout/footer/deployment-info.tsx @@ -0,0 +1,33 @@ +import { CustomLink } from "@/components/customLink"; +import dayjs from "dayjs"; + +import dynamic from "next/dynamic"; + +export const DeploymentInfo = () => { + const deploymentDate = process.env.NEXT_PUBLIC_GITHUB_RUN_DATE + ? dayjs.utc(process.env.NEXT_PUBLIC_GITHUB_RUN_DATE).fromNow() + : "XXX"; + const deploymentNumber = process.env.NEXT_PUBLIC_GITHUB_RUN_NUMBER || "XXX"; + + const deploymentLinkInfo = { + deploymentDate, + repo: process.env.NEXT_PUBLIC_GITHUB_REPOSITORY, + runId: process.env.NEXT_PUBLIC_GITHUB_RUN_ID, + deploymentNumber, + }; + + const DynamicDeploymentLink = dynamic(() => import("../deploymentLink"), { + ssr: false, + }); + + return ( +
+ This website is under{" "} + + CONSTANT CONTINUOUS DEPLOYMENT + + .  + +
+ ); +}; diff --git a/components/layout/footer/divider.tsx b/components/layout/footer/divider.tsx new file mode 100644 index 0000000000..42b61fed18 --- /dev/null +++ b/components/layout/footer/divider.tsx @@ -0,0 +1 @@ +export const Divider = () => |; diff --git a/components/layout/footer/footer.tsx b/components/layout/footer/footer.tsx new file mode 100644 index 0000000000..58ace5451b --- /dev/null +++ b/components/layout/footer/footer.tsx @@ -0,0 +1,27 @@ +import { SocialIcons } from "../../socialIcons/socialIcons"; +import { Container } from "../../util/container"; +import { CopyrightInfo } from "./copyright-info"; +import { DeploymentInfo } from "./deployment-info"; +import { SiteInfo } from "./site-info"; + +export const Footer = () => { + return ( +
+ + +
+
+ +
+
+
+ + +
+
+
+ ); +}; diff --git a/components/layout/footer/pre-footer.tsx b/components/layout/footer/pre-footer.tsx new file mode 100644 index 0000000000..07cfe85490 --- /dev/null +++ b/components/layout/footer/pre-footer.tsx @@ -0,0 +1,10 @@ +import { BuiltOnAzure } from "@/components/blocks"; +import { Section } from "@/components/util/section"; + +export const PreFooter = () => { + return ( +
+ +
+ ); +}; diff --git a/components/layout/footer/site-info.tsx b/components/layout/footer/site-info.tsx new file mode 100644 index 0000000000..f75b6ddc63 --- /dev/null +++ b/components/layout/footer/site-info.tsx @@ -0,0 +1,28 @@ +import { CustomLink } from "@/components/customLink"; +import Image from "next/image"; +import { Divider } from "./divider"; + +export const SiteInfo = () => ( +
+ + SITEMAP + + + + HEALTH CHECK + health check logo + +
+); diff --git a/components/layout/layout.tsx b/components/layout/layout.tsx index 62be616e2c..c891725a47 100644 --- a/components/layout/layout.tsx +++ b/components/layout/layout.tsx @@ -2,7 +2,8 @@ import classNames from "classnames"; import Head from "next/head"; import { useRouter } from "next/router"; import { useLiveStreamProps } from "../../hooks/useLiveStreamProps"; -import { Footer, PreFooter } from "./footer"; +import { Footer } from "./footer/footer"; +import { PreFooter } from "./footer/pre-footer"; import { Theme } from "./theme"; import { useAppInsightsContext } from "@microsoft/applicationinsights-react-js"; @@ -12,7 +13,7 @@ import { Open_Sans } from "next/font/google"; import { useReportWebVitals } from "next/web-vitals"; import { MegaMenuLayout, NavMenuGroup } from "ssw.megamenu"; import { CustomLink } from "../customLink"; -import { ErrorBoundary } from "../util/error-boundary"; +import { ErrorBoundary } from "../util/error/error-boundary"; const openSans = Open_Sans({ variable: "--open-sans-font", diff --git a/components/layout/theme.tsx b/components/layout/theme.tsx index 7f2540de67..b21c76b3bc 100644 --- a/components/layout/theme.tsx +++ b/components/layout/theme.tsx @@ -1,3 +1,5 @@ +"use client"; + import * as React from "react"; const ThemeContext = React.createContext({}); diff --git a/components/liveStream/liveStreamBanner.tsx b/components/liveStream/liveStreamBanner.tsx index b6910e7166..f8a04399a1 100644 --- a/components/liveStream/liveStreamBanner.tsx +++ b/components/liveStream/liveStreamBanner.tsx @@ -1,3 +1,5 @@ +"use client"; + import classNames from "classnames"; import dayjs from "dayjs"; import { useEffect, useState } from "react"; diff --git a/components/liveStream/liveStreamWidget.tsx b/components/liveStream/liveStreamWidget.tsx index 16350cb7af..e2b6b52f0b 100644 --- a/components/liveStream/liveStreamWidget.tsx +++ b/components/liveStream/liveStreamWidget.tsx @@ -1,23 +1,26 @@ +"use client"; + import "react-tooltip/dist/react-tooltip.css"; import axios from "axios"; import classNames from "classnames"; import Image from "next/image"; -import { useRouter } from "next/router"; +import { useSearchParams } from "next/navigation"; import Script from "next/script"; import { useEffect, useState } from "react"; import { TfiAngleDown, TfiAngleUp } from "react-icons/tfi"; import { Tooltip } from "react-tooltip"; -import layoutData from "../../content/global/index.json"; +import layoutData, { + default as globals, +} from "../../content/global/index.json"; import { getYouTubeId } from "../../helpers/embeds"; import { sanitiseXSS } from "../../helpers/validator"; import { LiveStreamProps } from "../../hooks/useLiveStreamProps"; import { SpeakerInfo } from "../../services/server/events"; +import { InlineJotForm } from "../blocks"; import { CustomLink } from "../customLink"; import { YouTubeEmbed } from "../embeds/youtubeEmbed"; import { SocialIcons } from "../socialIcons/socialIcons"; -import { InlineJotForm } from "../blocks"; -import { default as globals } from "../../content/global/index.json"; type LiveStreamWidgetProps = { isLive?: boolean; @@ -38,7 +41,7 @@ export const LiveStreamWidget = ({ isLive, event }: LiveStreamWidgetProps) => { const [eventDescriptionCollapsable, setEventDescriptionCollapsable] = useState(); - const router = useRouter(); + const param = useSearchParams(); const collapsableWidgetRefCallback = (e: HTMLDivElement) => { !!e && @@ -58,7 +61,7 @@ export const LiveStreamWidget = ({ isLive, event }: LiveStreamWidgetProps) => { useEffect(() => { const fetchLiveStreamInfo = async () => { - if ((!isLive && !router.query.liveStream) || !event) { + if ((!isLive && !param.get("liveStream")) || !event) { return; } @@ -108,7 +111,7 @@ export const LiveStreamWidget = ({ isLive, event }: LiveStreamWidgetProps) => { }; fetchLiveStreamInfo(); - }, [isLive, event, router.query.liveStream]); + }, [isLive, event, param]); if (!event) { return <>; @@ -176,7 +179,7 @@ export const LiveStreamWidget = ({ isLive, event }: LiveStreamWidgetProps) => { {/* custom fixed width and height to have best looking and fixed size for different screens */}
( + + )} + /> + ); +} diff --git a/components/testimonials/TestimonialRow.tsx b/components/testimonials/TestimonialRow.tsx index 7cfee32bef..7528ce3be3 100644 --- a/components/testimonials/TestimonialRow.tsx +++ b/components/testimonials/TestimonialRow.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useEffect, useState } from "react"; import { useEditState } from "tinacms/dist/react"; import { diff --git a/components/usergroup/latestTech/index.tsx b/components/usergroup/latestTech/index.tsx index dbfff13dbb..b302312a1f 100644 --- a/components/usergroup/latestTech/index.tsx +++ b/components/usergroup/latestTech/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useEffect, useState } from "react"; import type { Template } from "tinacms"; import { Badge } from "./badge"; diff --git a/components/usergroup/readMore.tsx b/components/usergroup/readMore.tsx index 1a27eff105..09c3a46a15 100644 --- a/components/usergroup/readMore.tsx +++ b/components/usergroup/readMore.tsx @@ -1,3 +1,5 @@ +"use client"; + import classNames from "classnames"; import { useEffect, useState } from "react"; import { MdOutlineKeyboardArrowDown } from "react-icons/md"; diff --git a/components/util/error-boundary.tsx b/components/util/error/error-boundary.tsx similarity index 100% rename from components/util/error-boundary.tsx rename to components/util/error/error-boundary.tsx diff --git a/components/util/error/error-page.tsx b/components/util/error/error-page.tsx new file mode 100644 index 0000000000..2267ab0d97 --- /dev/null +++ b/components/util/error/error-page.tsx @@ -0,0 +1,77 @@ +import classNames from "classnames"; +import { NavMenuGroup } from "ssw.megamenu"; +import { CustomLink } from "../../customLink"; +import { Layout } from "../../layout"; +import { Container } from "../container"; +import { ErrorText } from "./error"; + +import React from "react"; + +type ErrorPageProps = { + menu?: { + menuGroups: NavMenuGroup[]; + }; + code?: string; + title?: string; + tipText?: React.ReactNode; + details?: string; + exitButtonCallback?: () => void; +}; + +export const ErrorPage = (props: ErrorPageProps) => { + return ( + + +
+
+

+ + {props.code || "Error"} + +

+ +
+
+ Visit{" "} + + SSW homepage + {" "} + to find out how we can help you. +
+ + {props.code === "404" && ( +
+ Learn more about{" "} + + having a useful 404 error page + + . +
+ )} +
+
+ +
+ + +
+
+
+ ); +}; diff --git a/components/util/error/error.tsx b/components/util/error/error.tsx new file mode 100644 index 0000000000..d0bb95b195 --- /dev/null +++ b/components/util/error/error.tsx @@ -0,0 +1,136 @@ +"use client"; + +import { CustomLink } from "@/components/customLink"; +import { Disclosure } from "@headlessui/react"; +import classNames from "classnames"; +import { BiChevronRight } from "react-icons/bi"; +import { FaXmark } from "react-icons/fa6"; +import { Container } from "../container"; + +type ErrorPageProps = { + code?: string; + title?: string; + tipText?: React.ReactNode; + details?: string; + exitButtonCallback?: () => void; +}; + +export const ErrorPage = (props: ErrorPageProps) => { + return ( + +
+
+

+ + {props.code || "Error"} + +

+ +
+
+ Visit{" "} + + SSW homepage + {" "} + to find out how we can help you. +
+ + {props.code === "404" && ( +
+ Learn more about{" "} + + having a useful 404 error page + + . +
+ )} +
+
+ +
+ + +
+
+ ); +}; + +type ErrorTextProps = { + title?: string; + tipText?: React.ReactNode; + details?: string; + exitButtonCallback?: () => void; +}; + +export const ErrorText = (props: ErrorTextProps) => { + return ( +
+ {props.exitButtonCallback && ( +
+ +
+ )} + +

+ {props.title || "We're sorry, something has gone wrong here."} +

+ {props.tipText || ( +
+

+

+ For help, please submit a bug report issue on our GitHub at{" "} + + github.com/SSWConsulting/SSW.Website + {" "} + or send us an email at{" "} + info@ssw.com.au. +

+
+ )} +
+ {props.details && ( + + {({ open }) => ( + <> + +
+ See details{" "} + +
+
+ +
+                  {props.details}
+                
+
+ + )} +
+ )} +
+ ); +}; diff --git a/components/util/videoCards.tsx b/components/util/videoCards.tsx index 13de208e49..f67a1acbb1 100644 --- a/components/util/videoCards.tsx +++ b/components/util/videoCards.tsx @@ -1,3 +1,5 @@ +"use client"; + import classNames from "classnames"; import Image from "next/image"; import { FC } from "react"; diff --git a/components/videoModal.tsx b/components/videoModal.tsx index 53d14b7bb4..a7663ae2ed 100644 --- a/components/videoModal.tsx +++ b/components/videoModal.tsx @@ -1,3 +1,5 @@ +"use client"; + import classNames from "classnames"; import dynamic from "next/dynamic"; import Image from "next/image"; diff --git a/hooks/useSeo.ts b/hooks/useSeo.ts new file mode 100644 index 0000000000..c6af28e730 --- /dev/null +++ b/hooks/useSeo.ts @@ -0,0 +1,40 @@ +import { DEFAULT_METADATA } from "app/layout"; +import { Metadata } from "next"; + +export function useSEO(seo) { + if (!seo) return null; + + let dynamicSEO: Metadata = {}; + const DEFAULT_OPEN_GRAPH_IMAGE = { ...DEFAULT_METADATA.openGraph.images[0] }; + + dynamicSEO = { + title: seo.title, + description: seo.description, + alternates: { + canonical: seo.canonical, + }, + openGraph: { + title: seo.title, + description: seo.description, + url: seo.canonical, + images: seo.images || DEFAULT_OPEN_GRAPH_IMAGE, + }, + twitter: { + site: seo.canonical, + }, + }; + + // Remove null values from SEO object + Object.keys(dynamicSEO).forEach((key) => { + if (!dynamicSEO[key]) { + delete dynamicSEO[key]; + } + }); + + const seoProps = { + ...DEFAULT_METADATA, + ...dynamicSEO, + }; + + return { seoProps }; +} diff --git a/next.config.mjs b/next.config.mjs index c1e9f78ac9..fd8690a3b3 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -4,10 +4,6 @@ const withNextPluginPreval = createNextPluginPreval(); /** @type {import('next').NextConfig} */ const config = { - i18n: { - locales: ["en"], - defaultLocale: "en", - }, poweredByHeader: false, images: { deviceSizes: [384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840], diff --git a/package.json b/package.json index 713b9fd711..937cf41dac 100644 --- a/package.json +++ b/package.json @@ -52,9 +52,11 @@ "@headlessui/react": "^1.7.19", "@headlessui/tailwindcss": "^0.2.0", "@heroicons/react": "^2.1.1", + "@marketsystems/nextjs13-appdir-breadcrumbs": "^1.0.4", "@microsoft/applicationinsights-react-js": "17.1.2", "@microsoft/applicationinsights-web": "3.1.2", "@next/bundle-analyzer": "^14.0.4", + "@next/third-parties": "^14.1.4", "@tailwindcss/typography": "^0.5.10", "@tanstack/react-query": "^5.29.2", "@tanstack/react-query-devtools": "^5.27.0", diff --git a/pages/_error.tsx b/pages/_error.tsx index 711aa4feb4..ec5c88dae9 100644 --- a/pages/_error.tsx +++ b/pages/_error.tsx @@ -1,4 +1,4 @@ -import { ErrorPage } from "@/components/util/error-page"; +import { ErrorPage } from "@/components/util/error/error-page"; import { useAppInsightsContext } from "@microsoft/applicationinsights-react-js"; import { NextPageContext } from "next"; import { ErrorProps } from "next/error"; diff --git a/pages/products/[filename].tsx b/pages/products/[filename].tsx deleted file mode 100644 index 36d5bc33bd..0000000000 --- a/pages/products/[filename].tsx +++ /dev/null @@ -1,86 +0,0 @@ -import client from "@/tina/client"; -import { InferGetStaticPropsType } from "next"; -import { useTina } from "tinacms/dist/react"; -import { TinaMarkdown } from "tinacms/dist/rich-text"; -import { Breadcrumbs } from "../../components/blocks/breadcrumbs"; -import { componentRenderer } from "../../components/blocks/mdxComponentRenderer"; -import { Layout } from "../../components/layout"; -import { Container } from "../../components/util/container"; -import { Section } from "../../components/util/section"; -import { SEO } from "../../components/util/seo"; -import { removeExtension } from "../../services/client/utils.service"; - -export default function OfficePage( - props: InferGetStaticPropsType -) { - const { data } = useTina({ - data: props.data, - query: props.query, - variables: props.variables, - }); - - return ( - <> - - -
- -
- - - -
- - ); -} - -export const getStaticProps = async ({ params }) => { - const tinaProps = await client.queries.productContentQuery({ - relativePath: `${params.filename}.mdx`, - }); - - if (tinaProps.data.products.seo && !tinaProps.data.products.seo.canonical) { - tinaProps.data.products.seo.canonical = `${tinaProps.data.global.header.url}products/${params.filename}`; - } - - return { - props: { - data: tinaProps.data, - query: tinaProps.query, - variables: tinaProps.variables, - }, - }; -}; - -export const getStaticPaths = async () => { - let PageListData = await client.queries.productsConnection(); - const allPagesListData = PageListData; - - while (PageListData.data.productsConnection.pageInfo.hasNextPage) { - const lastCursor = PageListData.data.productsConnection.pageInfo.endCursor; - PageListData = await client.queries.productsConnection({ - after: lastCursor, - }); - - allPagesListData.data.productsConnection.edges.push( - ...PageListData.data.productsConnection.edges - ); - } - return { - paths: allPagesListData.data.productsConnection.edges.map((page) => ({ - params: { filename: page.node._sys.filename }, - })), - fallback: false, - }; -}; diff --git a/pages/products/index.tsx b/pages/products/index.tsx deleted file mode 100644 index 12530f76bd..0000000000 --- a/pages/products/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { client } from "@/tina/client"; -import { useTina } from "tinacms/dist/react"; - -import { InferGetStaticPropsType } from "next"; -import { Breadcrumbs } from "../../components/blocks/breadcrumbs"; -import { PageCard } from "../../components/blocks/pageCards"; -import { Layout } from "../../components/layout"; -import { Container } from "../../components/util/container"; -import { SEO } from "../../components/util/seo"; - -export default function ProductsIndex( - props: InferGetStaticPropsType -) { - const { data } = useTina({ - query: props.query, - variables: props.variables, - data: props.data, - }); - - return ( - - - - - {data.productsIndex.title && ( -

{data.productsIndex.title}

- )} - {data.productsIndex.subTitle && ( -

{data.productsIndex.subTitle}

- )} -
-
- {data.productsIndex.productsList?.map((product, index) => ( - - ))} -
-
-
-
- ); -} - -export const getStaticProps = async () => { - const tinaProps = await client.queries.productsIndexQuery(); - - const seo = tinaProps.data.productsIndex.seo; - if (seo && !seo.canonical) { - seo.canonical = `${tinaProps.data.global.header.url}/products`; - } - - return { - props: { - ...tinaProps, - seo, - }, - }; -}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0ee8d0e37..fea13db253 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@heroicons/react': specifier: ^2.1.1 version: 2.1.1(react@18.3.1) + '@marketsystems/nextjs13-appdir-breadcrumbs': + specifier: ^1.0.4 + version: 1.0.4(next@14.2.3)(react@18.3.1) '@microsoft/applicationinsights-react-js': specifier: 17.1.2 version: 17.1.2(history@5.3.0)(react@18.3.1)(tslib@2.6.2) @@ -35,6 +38,9 @@ importers: '@next/bundle-analyzer': specifier: ^14.0.4 version: 14.0.4 + '@next/third-parties': + specifier: ^14.1.4 + version: 14.2.1(next@14.2.3)(react@18.3.1) '@tailwindcss/typography': specifier: ^0.5.10 version: 0.5.10(tailwindcss@3.4.3) @@ -387,9 +393,8 @@ packages: resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.23.4 + '@babel/highlight': 7.22.20 chalk: 2.4.2 - dev: true /@babel/code-frame@7.23.5: resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} @@ -455,7 +460,7 @@ packages: resolution: {integrity: sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.20 jsesc: 2.5.2 @@ -474,13 +479,13 @@ packages: resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15: resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 /@babel/helper-compilation-targets@7.22.15: resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} @@ -606,7 +611,7 @@ packages: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 /@babel/helper-member-expression-to-functions@7.23.0: resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==} @@ -618,7 +623,7 @@ packages: resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 /@babel/helper-module-transforms@7.23.0(@babel/core@7.23.2): resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==} @@ -664,7 +669,7 @@ packages: resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 /@babel/helper-plugin-utils@7.22.5: resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} @@ -721,19 +726,19 @@ packages: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 /@babel/helper-skip-transparent-expression-wrappers@7.22.5: resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 /@babel/helper-string-parser@7.22.5: resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} @@ -769,7 +774,7 @@ packages: dependencies: '@babel/template': 7.22.15 '@babel/traverse': 7.23.2 - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 transitivePeerDependencies: - supports-color dev: true @@ -784,6 +789,14 @@ packages: transitivePeerDependencies: - supports-color + /@babel/highlight@7.22.20: + resolution: {integrity: sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.20 + chalk: 2.4.2 + js-tokens: 4.0.0 + /@babel/highlight@7.23.4: resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} engines: {node: '>=6.9.0'} @@ -798,7 +811,6 @@ packages: hasBin: true dependencies: '@babel/types': 7.23.9 - dev: true /@babel/parser@7.23.9: resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==} @@ -2184,7 +2196,7 @@ packages: '@babel/helper-module-imports': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.23.2) - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 dev: true /@babel/plugin-transform-react-jsx@7.22.15(@babel/core@7.23.9): @@ -2198,7 +2210,7 @@ packages: '@babel/helper-module-imports': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.23.9) - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 dev: true /@babel/plugin-transform-react-pure-annotations@7.22.5(@babel/core@7.23.2): @@ -2641,7 +2653,7 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 esutils: 2.0.3 dev: true @@ -2652,7 +2664,7 @@ packages: dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 esutils: 2.0.3 dev: false @@ -2725,9 +2737,9 @@ packages: resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.23.5 - '@babel/parser': 7.23.9 - '@babel/types': 7.23.9 + '@babel/code-frame': 7.22.13 + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 /@babel/template@7.23.9: resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==} @@ -2741,14 +2753,14 @@ packages: resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.22.13 '@babel/generator': 7.23.0 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.23.9 - '@babel/types': 7.23.9 + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: @@ -3620,7 +3632,7 @@ packages: react: ^16 || ^17 || ^18 react-dom: ^16 || ^17 || ^18 dependencies: - '@tanstack/react-virtual': 3.2.1(react-dom@17.0.2)(react@17.0.2) + '@tanstack/react-virtual': 3.3.0(react-dom@17.0.2)(react@17.0.2) client-only: 0.0.1 react: 17.0.2 react-dom: 17.0.2(react@17.0.2) @@ -3633,7 +3645,7 @@ packages: react: ^16 || ^17 || ^18 react-dom: ^16 || ^17 || ^18 dependencies: - '@tanstack/react-virtual': 3.2.1(react-dom@18.3.1)(react@18.3.1) + '@tanstack/react-virtual': 3.3.0(react-dom@18.3.1)(react@18.3.1) client-only: 0.0.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -4230,6 +4242,16 @@ packages: '@lezer/common': 1.1.0 dev: true + /@marketsystems/nextjs13-appdir-breadcrumbs@1.0.4(next@14.2.3)(react@18.3.1): + resolution: {integrity: sha512-SiPlbB6j0zuNAcFnmvv20tb5FwUBTjs3o3DYnRm554FbhnVj0RQC8xgx+vpYkzU4G5pgJnZWhGnMVF7W2QqCKQ==} + peerDependencies: + next: ^13.2.0 + react: ^18.2.0 + dependencies: + next: 14.2.3(@babel/core@7.23.9)(@opentelemetry/api@1.8.0)(@playwright/test@1.42.1)(react-dom@18.3.1)(react@18.3.1) + react: 18.3.1 + dev: false + /@microsoft/applicationinsights-analytics-js@3.1.2(tslib@2.6.2): resolution: {integrity: sha512-HIlptHMIX3cGqTOUrdVjWb5FpYvs1xmosrIf7pnU0Y0/BER382fHCb/4BAB5mU32h/UlPX8to/d6Q20fSCtYAw==} peerDependencies: @@ -4571,6 +4593,17 @@ packages: requiresBuild: true optional: true + /@next/third-parties@14.2.1(next@14.2.3)(react@18.3.1): + resolution: {integrity: sha512-iHVq3uHT1BLR4O8LXJ/AJeRCATnsxWNq5S223BYhrQMceMUr/5TtKIroQwJpXrbwxkOr7SFX72mZqPTXxd/TnQ==} + peerDependencies: + next: ^13.0.0 || ^14.0.0 + react: ^18.2.0 + dependencies: + next: 14.2.3(@babel/core@7.23.9)(@opentelemetry/api@1.8.0)(@playwright/test@1.42.1)(react-dom@18.3.1)(react@18.3.1) + react: 18.3.1 + third-party-capital: 1.0.20 + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -6170,7 +6203,7 @@ packages: resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} engines: {node: '>=14'} dependencies: - '@babel/types': 7.23.9 + '@babel/types': 7.23.0 entities: 4.5.0 dev: true @@ -6304,29 +6337,29 @@ packages: react: 18.3.1 dev: false - /@tanstack/react-virtual@3.2.1(react-dom@17.0.2)(react@17.0.2): - resolution: {integrity: sha512-i9Nt0ssIh2bSjomJZlr6Iq5usT/9+ewo2/fKHRNk6kjVKS8jrhXbnO8NEawarCuBx/efv0xpoUUKKGxa0cQb4Q==} + /@tanstack/react-virtual@3.3.0(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-QFxmTSZBniq15S0vSZ55P4ToXquMXwJypPXyX/ux7sYo6a2FX3/zWoRLLc4eIOGWTjvzqcIVNKhcuFb+OZL3aQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@tanstack/virtual-core': 3.2.1 + '@tanstack/virtual-core': 3.3.0 react: 17.0.2 react-dom: 17.0.2(react@17.0.2) dev: true - /@tanstack/react-virtual@3.2.1(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-i9Nt0ssIh2bSjomJZlr6Iq5usT/9+ewo2/fKHRNk6kjVKS8jrhXbnO8NEawarCuBx/efv0xpoUUKKGxa0cQb4Q==} + /@tanstack/react-virtual@3.3.0(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-QFxmTSZBniq15S0vSZ55P4ToXquMXwJypPXyX/ux7sYo6a2FX3/zWoRLLc4eIOGWTjvzqcIVNKhcuFb+OZL3aQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@tanstack/virtual-core': 3.2.1 + '@tanstack/virtual-core': 3.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - /@tanstack/virtual-core@3.2.1: - resolution: {integrity: sha512-nO0d4vRzsmpBQCJYyClNHPPoUMI4nXNfrm6IcCRL33ncWMoNVpURh9YebEHPw8KrtsP2VSJIHE4gf4XFGk1OGg==} + /@tanstack/virtual-core@3.3.0: + resolution: {integrity: sha512-A0004OAa1FcUkPHeeGoKgBrAgjH+uHdDPrw1L7RpkwnODYqRvoilqsHPs8cyTjMg1byZBbiNpQAq2TlFLIaQag==} /@testing-library/dom@10.0.0: resolution: {integrity: sha512-PmJPnogldqoVFf+EwbHvbBJ98MmqASV8kLrBYgsDNxQcFMeIS7JFL48sfyXvuMtgmWO/wMhh25odr+8VhDmn4g==} @@ -6938,12 +6971,12 @@ packages: /@types/eslint-scope@3.7.7: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: - '@types/eslint': 8.56.9 + '@types/eslint': 8.56.10 '@types/estree': 1.0.5 dev: false - /@types/eslint@8.56.9: - resolution: {integrity: sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==} + /@types/eslint@8.56.10: + resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} dependencies: '@types/estree': 1.0.5 '@types/json-schema': 7.0.14 @@ -18892,6 +18925,10 @@ packages: dependencies: any-promise: 1.3.0 + /third-party-capital@1.0.20: + resolution: {integrity: sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==} + dev: false + /throttle-debounce@3.0.1: resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} engines: {node: '>=10'} diff --git a/tailwind.config.js b/tailwind.config.js index 2272936afc..19c47f648d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -51,6 +51,7 @@ export const platform = [ export default { // mode: "jit", content: [ + "./app/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", "./pages/**/*.{js,ts,jsx,tsx}", "node_modules/ssw.megamenu/**/*.js", diff --git a/tina/collections/products.tsx b/tina/collections/products.tsx index 07ee30bc81..c1ac69a0b5 100644 --- a/tina/collections/products.tsx +++ b/tina/collections/products.tsx @@ -13,6 +13,9 @@ export const productsIndexSchema: Collection = { create: false, delete: false, }, + router: () => { + return "/products"; + }, }, fields: [ // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/tina/config.tsx b/tina/config.tsx index 7e6061249d..0ac35f8aea 100644 --- a/tina/config.tsx +++ b/tina/config.tsx @@ -74,8 +74,12 @@ const config = defineStaticConfig({ }; }, }, - cmsCallback: (cms: TinaCMS) => { + cmsCallback: async (cms: TinaCMS) => { cms.flags.set("branch-switcher", true); + // for local development, enable draft mode since we can access the Tina Edit mode without login + if (process.env.NODE_ENV === "development") { + await fetch("/api/enable-draft"); + } return cms; }, schema: { @@ -118,6 +122,16 @@ const config = defineStaticConfig({ userGroupGlobalSchema, ], }, + admin: { + authHooks: { + onLogin: async () => { + await fetch("/api/enable-draft"); + }, + onLogout: async () => { + await fetch("/api/disable-draft"); + }, + }, + }, search: { tina: { indexerToken: process.env.TINA_SEARCH_TOKEN, diff --git a/tsconfig.json b/tsconfig.json index 98b0bcc6b5..61ec1e8aad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,9 @@ "@/tina/*": ["tina/__generated__/*"], "@/tina-collections/*": ["tina/collections/*"], "@/context/*": ["context/*"], - "@/services/*": ["services/*"] + "@/services/*": ["services/*"], + "@/content/*": ["content/*"], + "@/helpers/*": ["helpers/*"] }, "plugins": [ {