diff --git a/.eslintrc.js b/.eslintrc.js index 94955577..2907fa79 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -104,4 +104,12 @@ module.exports = { "plugin:storybook/recommended", "plugin:cypress/recommended", ], + overrides: [ + { + files: ["*.ts", "*.tsx"], + rules: { + "no-undef": "off", + }, + }, + ], }; diff --git a/app/[locale]/error.tsx b/app/[locale]/error.tsx new file mode 100644 index 00000000..3750bf93 --- /dev/null +++ b/app/[locale]/error.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { FunctionComponent } from "react"; +import { useTranslation } from "react-i18next"; +import Error from "@/components/organisms/Error"; + +const ErrorPage: FunctionComponent = ({ error }) => { + const { t } = useTranslation(); + return ; +}; + +export default ErrorPage; diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx new file mode 100644 index 00000000..ea09fb79 --- /dev/null +++ b/app/[locale]/layout.tsx @@ -0,0 +1,80 @@ +import { FunctionComponent, PropsWithChildren, Suspense } from "react"; +import { Metadata } from "next"; +import { GoogleOAuthProvider } from "@react-oauth/google"; +import GlobalStyles from "@/styles/globalStyles"; +import { getGlobalData } from "@/lib/api/globals"; +import { languages } from "@/lib/i18n/settings"; +import SourceSansPro from "@/lib/styles/font"; +import StyledComponentsRegistry from "@/lib/styles/registry"; +import I18NextClientProvider from "@/contexts/i18next"; +import { AuthenticationContextProvider } from "@/contexts/Authentication"; +import PageWrapper from "@/components/organisms/PageWrapper"; +import RootScripts from "./scripts"; + +const GOOGLE_APP_ID = process.env.NEXT_PUBLIC_GOOGLE_APP_ID || ""; + +export async function generateMetadata({ + params: { locale }, +}: LocaleProps): Promise { + const { siteInfo: metadata } = await getGlobalData(locale); + const { siteTitle, siteDescription, siteImage, language } = metadata; + const { url, width, height, altText: alt } = siteImage[0]; + + return { + title: { + default: siteTitle, + template: `%s | ${siteTitle}`, + }, + description: siteDescription, + manifest: "/site.webmanifest", + openGraph: { + locale: language, + images: [{ url, width, height, alt }], + }, + icons: { + icon: [ + { url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" }, + { url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" }, + ], + apple: [ + { url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }, + ], + }, + }; +} + +export const generateStaticParams = () => { + return languages.map((locale) => { + return { locale }; + }); +}; + +export const dynamicParams = false; + +const LocaleLayout: FunctionComponent> = async ({ + params: { locale }, + children, +}) => { + return ( + + + + + + + + + + {children} + + + + + + + + + ); +}; + +export default LocaleLayout; diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx new file mode 100644 index 00000000..3a328328 --- /dev/null +++ b/app/[locale]/page.tsx @@ -0,0 +1,72 @@ +import { FunctionComponent } from "react"; +import { notFound } from "next/navigation"; +import { draftMode } from "next/headers"; +import { getEntrySectionTypeByUri, getEntryDataByUri } from "@/api/entry"; +import { setEdcLog } from "@/lib/edc-log"; +import { getSiteFromLocale } from "@/lib/helpers/site"; +import { purgeNextjsStaticFiles } from "@/lib/purgeStaticFiles"; +import HomePageTemplate from "@/templates/HomePage"; +import { Metadata } from "next"; +import { getEntryMetadataByUri } from "@/lib/api/metadata"; + +const CRAFT_HOMEPAGE_URI = "__home__"; + +export async function generateMetadata({ + params: { locale }, + searchParams = {}, +}: LocaleProps): Promise { + const uri = CRAFT_HOMEPAGE_URI; + let previewToken: string | undefined; + + if (draftMode().isEnabled) { + previewToken = Array.isArray(searchParams.preview) + ? searchParams.preview[0] + : searchParams?.preview; + } + + const { + entry: { title, description }, + } = await getEntryMetadataByUri(uri, locale, previewToken); + + return { title, description }; +} + +const RootPage: FunctionComponent = async ({ + params: { locale }, + searchParams = {}, +}) => { + const runId = Date.now().toString(); + const site = getSiteFromLocale(locale); + const uri = CRAFT_HOMEPAGE_URI; + let previewToken: string | undefined; + + if (draftMode().isEnabled) { + previewToken = Array.isArray(searchParams.preview) + ? searchParams.preview[0] + : searchParams.preview; + } + + const entrySectionType = await getEntrySectionTypeByUri( + uri, + site, + previewToken + ); + + const { sectionHandle: section, typeHandle: type } = entrySectionType; + const data = await getEntryDataByUri(uri, section, type, site, previewToken); + + const currentId = data?.id || data?.entry?.id; + + // Handle 404 if there is no data + if (!currentId) { + setEdcLog(runId, "404 encountered building for " + uri, "BUILD_ERROR_404"); + await purgeNextjsStaticFiles(locale); + notFound(); + } + + setEdcLog(runId, "Done building for " + uri, "BUILD_COMPLETE"); + + return ; +}; + +export default RootPage; diff --git a/app/[locale]/scripts.tsx b/app/[locale]/scripts.tsx new file mode 100644 index 00000000..e3533118 --- /dev/null +++ b/app/[locale]/scripts.tsx @@ -0,0 +1,81 @@ +import { FunctionComponent } from "react"; +import Script from "next/script"; + +const PLAUSIBLE_DOMAIN = process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN; +const SURVEY_SPARROW = process.env.NEXT_PUBLIC_SURVEY_SPARROW; + +const RootScripts: FunctionComponent<{ locale: string }> = ({ locale }) => { + return ( + <> + {PLAUSIBLE_DOMAIN && ( +