Skip to content

Commit

Permalink
feat: app directory (#541)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexgoff authored Oct 1, 2024
1 parent abc98e9 commit 8d7ce6c
Show file tree
Hide file tree
Showing 37 changed files with 683 additions and 333 deletions.
8 changes: 8 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,12 @@ module.exports = {
"plugin:storybook/recommended",
"plugin:cypress/recommended",
],
overrides: [
{
files: ["*.ts", "*.tsx"],
rules: {
"no-undef": "off",
},
},
],
};
12 changes: 12 additions & 0 deletions app/[locale]/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client";

import { FunctionComponent } from "react";
import { useTranslation } from "react-i18next";
import Error from "@/components/organisms/Error";

const ErrorPage: FunctionComponent<ErrorProps> = ({ error }) => {
const { t } = useTranslation();
return <Error title={t("error-title")} message={error.message} />;
};

export default ErrorPage;
80 changes: 80 additions & 0 deletions app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -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<Metadata> {
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<PropsWithChildren<LocaleProps>> = async ({
params: { locale },
children,
}) => {
return (
<html lang={locale}>
<head></head>
<body className={SourceSansPro.variable}>
<I18NextClientProvider {...{ locale }}>
<Suspense>
<AuthenticationContextProvider>
<StyledComponentsRegistry>
<GoogleOAuthProvider clientId={GOOGLE_APP_ID}>
<GlobalStyles />
<PageWrapper {...{ locale }}>{children}</PageWrapper>
</GoogleOAuthProvider>
</StyledComponentsRegistry>
</AuthenticationContextProvider>
</Suspense>
</I18NextClientProvider>
<RootScripts {...{ locale }} />
</body>
</html>
);
};

export default LocaleLayout;
72 changes: 72 additions & 0 deletions app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -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<Metadata> {
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<LocaleProps> = 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 <HomePageTemplate {...{ section, data }} />;
};

export default RootPage;
81 changes: 81 additions & 0 deletions app/[locale]/scripts.tsx
Original file line number Diff line number Diff line change
@@ -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 && (
<Script
id="plausible-script"
data-domain={PLAUSIBLE_DOMAIN}
src="https://plausible.io/js/plausible.js"
/>
)}
{SURVEY_SPARROW && (
<>
<div id="ss_survey_widget" />
<Script
id="SS_SCRIPT"
dangerouslySetInnerHTML={{
__html: `
function sparrowLaunch(opts) {
// eslint-disable-next-line no-var, one-var
var e = "ss-widget",
t = "script",
a = document,
r = window,
l = localStorage;
// eslint-disable-next-line no-var, one-var
var s,
n,
c,
rm = a.getElementById("SS_SCRIPT");
r.SS_WIDGET_TOKEN = "tt-tTTCZh648ZKwCNkh91aodV";
r.SS_ACCOUNT = "rockmanetal.surveysparrow.com";
r.SS_SURVEY_NAME = "rubin-pop-up---chat-survey";
if (
!a.getElementById(e) &&
!l.getItem("removed-ss-widget-tt-tTTCZh648ZKwCNkh91aodV")
) {
// eslint-disable-next-line no-var
var S = function () {
S.update(arguments);
};
S.args = [];
S.update = function (e) {
S.args.push(e);
};
r.SparrowLauncher = S;
s = a.getElementsByTagName(t);
c = s[s.length - 1];
n = a.createElement(t);
n.type = "text/javascript";
n.async = !0;
n.id = e;
n.src = [
"https://",
"rockmanetal.surveysparrow.com/widget/",
r.SS_WIDGET_TOKEN,
"?",
"customParams=",
JSON.stringify(opts),
].join("");
c.parentNode.insertBefore(n, c);
r.SS_VARIABLES = opts;
rm.parentNode.removeChild(rm);
}
}
sparrowLaunch({sparrowLang: "${locale}"});
`,
}}
/>
</>
)}
</>
);
};

export default RootScripts;
9 changes: 9 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import "focus-visible";
import "@/styles/styles.scss";
import { FunctionComponent, PropsWithChildren } from "react";

const RootLayout: FunctionComponent<PropsWithChildren> = ({ children }) => {
return <>{children}</>;
};

export default RootLayout;
45 changes: 45 additions & 0 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { FunctionComponent, Suspense } from "react";
import SourceSansPro from "@/lib/styles/font";
import StyledComponentsRegistry from "@/lib/styles/registry";
import GlobalStyles from "@/styles/globalStyles";
import I18NextClientProvider from "@/contexts/i18next";
import { AuthenticationContextProvider } from "@/contexts/Authentication";
import { GoogleOAuthProvider } from "@react-oauth/google";
import PageWrapper from "@/components/organisms/PageWrapper";
import { useTranslation } from "@/lib/i18n";
import { getLocale } from "@/lib/i18n/server";
import Error from "@/components/organisms/Error";

const GOOGLE_APP_ID = process.env.NEXT_PUBLIC_GOOGLE_APP_ID || "";

const NotFound: FunctionComponent = async () => {
const locale = getLocale();
const { t } = await useTranslation(locale, "translation");

return (
<html lang={locale}>
<head></head>
<body className={SourceSansPro.variable}>
<I18NextClientProvider {...{ locale }}>
<Suspense>
<AuthenticationContextProvider>
<StyledComponentsRegistry>
<GoogleOAuthProvider clientId={GOOGLE_APP_ID}>
<GlobalStyles />
<PageWrapper {...{ locale }}>
<Error
title={t("page-not-found-title")}
message={t("page-not-found-text")}
/>
</PageWrapper>
</GoogleOAuthProvider>
</StyledComponentsRegistry>
</AuthenticationContextProvider>
</Suspense>
</I18NextClientProvider>
</body>
</html>
);
};

export default NotFound;
2 changes: 2 additions & 0 deletions components/global/Body/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default function Body({ children, description, openGraphImage, title }) {
userProfilePage,
footerContent,
contactForm,
localeInfo: { locale },
}) => {
const {
siteTitle,
Expand All @@ -34,6 +35,7 @@ export default function Body({ children, description, openGraphImage, title }) {
<Header
navItems={headerNavItems}
userProfilePage={userProfilePage}
locale={locale}
/>
<main id="page-content">{children}</main>
<Footer
Expand Down
1 change: 1 addition & 0 deletions components/global/Footer/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"use client";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import Social from "./Social";
Expand Down
Loading

0 comments on commit 8d7ce6c

Please sign in to comment.