add support for previewing pages
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import { cookies, draftMode } from "next/headers";
|
||||
|
||||
export async function POST() {
|
||||
(await draftMode()).disable();
|
||||
(await cookies()).delete("preview-token");
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { cookies, draftMode } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
// Wagtail-headless-preview directs the editor's preview iframe here with
|
||||
// ?content_type=app.Model&token=<signed>. We stash the token in a cookie,
|
||||
// enable Next.js draft mode, and redirect to the type-dispatching renderer.
|
||||
export async function GET(req: NextRequest) {
|
||||
const token = req.nextUrl.searchParams.get("token");
|
||||
const contentType = req.nextUrl.searchParams.get("content_type");
|
||||
if (!token || !contentType) {
|
||||
return new Response("missing token/content_type", { status: 400 });
|
||||
}
|
||||
|
||||
(await draftMode()).enable();
|
||||
(await cookies()).set("preview-token", token, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
maxAge: 60 * 60 * 24,
|
||||
path: "/",
|
||||
});
|
||||
|
||||
redirect("/preview/render");
|
||||
}
|
||||
@@ -3,9 +3,15 @@ import "server-only";
|
||||
import { cacheExchange, createClient, fetchExchange } from "@urql/core";
|
||||
import { registerUrql } from "@urql/next/rsc";
|
||||
|
||||
const wagtailBaseUrl = process.env.WAGTAIL_BASE_URL;
|
||||
if (!wagtailBaseUrl) {
|
||||
throw new Error("WAGTAIL_BASE_URL is not set");
|
||||
}
|
||||
const graphqlEndpoint = `${wagtailBaseUrl.replace(/\/$/, "")}/api/graphql/`;
|
||||
|
||||
const makeClient = () => {
|
||||
return createClient({
|
||||
url: process.env.GRAPHQL_ENDPOINT ?? "",
|
||||
url: graphqlEndpoint,
|
||||
exchanges: [cacheExchange, fetchExchange],
|
||||
// requestPolicy: "network-only",
|
||||
fetchOptions: { next: { revalidate: 0 } },
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import "@/css/main.scss";
|
||||
import { Header } from "@/components/layout/Header";
|
||||
import { Footer } from "@/components/layout/Footer";
|
||||
import { PreviewBanner } from "@/components/general/PreviewBanner";
|
||||
import { Metadata } from "next";
|
||||
import { draftMode } from "next/headers";
|
||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||
|
||||
const baseUrlMetadata = process.env.URL
|
||||
@@ -26,11 +28,12 @@ export const metadata: Metadata = {
|
||||
...baseUrlMetadata,
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const isPreview = (await draftMode()).isEnabled;
|
||||
return (
|
||||
<html lang="no">
|
||||
<head>
|
||||
@@ -44,6 +47,7 @@ export default function RootLayout({
|
||||
)}
|
||||
</head>
|
||||
<body>
|
||||
{isPreview && <PreviewBanner />}
|
||||
<NuqsAdapter>
|
||||
<Header />
|
||||
{children}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { getClient } from "@/app/client";
|
||||
import { graphql } from "@/gql";
|
||||
import {
|
||||
AssociationFragment,
|
||||
AssociationIndexFragment,
|
||||
ContactIndexFragment,
|
||||
EventFragment,
|
||||
GenericFragment,
|
||||
HomeFragment,
|
||||
NewsFragment,
|
||||
NewsIndexFragment,
|
||||
SponsorsPageFragment,
|
||||
StudioFragment,
|
||||
VenueFragment,
|
||||
VenueIndexFragment,
|
||||
VenueRentalIndexFragment,
|
||||
} from "@/gql/graphql";
|
||||
import {
|
||||
AssociationIndexView,
|
||||
allAssociationsQuery,
|
||||
} from "@/components/associations/AssociationIndexView";
|
||||
import { AssociationPageView } from "@/components/associations/AssociationPageView";
|
||||
import { ContactIndexView } from "@/components/contact/ContactIndexView";
|
||||
import { EventIndexView } from "@/components/events/EventIndexView";
|
||||
import { EventPageView } from "@/components/events/EventPageView";
|
||||
import { GenericPageView } from "@/components/general/GenericPageView";
|
||||
import { HomePageView, homeQuery } from "@/components/home/HomePageView";
|
||||
import { NewsIndexView } from "@/components/news/NewsIndexView";
|
||||
import { NewsPageView } from "@/components/news/NewsPageView";
|
||||
import { SponsorsPageView } from "@/components/sponsor/SponsorsPageView";
|
||||
import { StudioPageView } from "@/components/studio/StudioPageView";
|
||||
import {
|
||||
VenueIndexView,
|
||||
venueIndexQuery,
|
||||
} from "@/components/venues/VenueIndexView";
|
||||
import { VenuePageView } from "@/components/venues/VenuePageView";
|
||||
import {
|
||||
VenueRentalIndexView,
|
||||
venueRentalIndexQuery,
|
||||
} from "@/components/venues/VenueRentalIndexView";
|
||||
import {
|
||||
EventCategory,
|
||||
EventOrganizer,
|
||||
eventsOverviewQuery,
|
||||
} from "@/lib/event";
|
||||
import { newsQuery } from "@/lib/news";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = 0;
|
||||
|
||||
const previewPageQuery = graphql(`
|
||||
query previewPage($token: String!) {
|
||||
page: page(token: $token) {
|
||||
__typename
|
||||
... on GenericPage { ...Generic }
|
||||
... on StudioPage { ...Studio }
|
||||
... on SponsorsPage { ...SponsorsPage }
|
||||
... on HomePage { ...Home }
|
||||
... on EventPage { ...Event }
|
||||
... on NewsPage { ...News }
|
||||
... on AssociationPage { ...Association }
|
||||
... on VenuePage { ...Venue }
|
||||
... on NewsIndex { ...NewsIndex }
|
||||
... on AssociationIndex { ...AssociationIndex }
|
||||
... on VenueIndex { ...VenueIndex }
|
||||
... on VenueRentalIndex { ...VenueRentalIndex }
|
||||
... on ContactIndex { ...ContactIndex }
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
function ExpiredPreview() {
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<h1>Preview session expired</h1>
|
||||
<p>Click Preview again in the Wagtail admin to start a new session.</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function UnsupportedType({ typename }: { typename: string }) {
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<h1>Preview not available</h1>
|
||||
<p>
|
||||
Type <code>{typename}</code> cannot be previewed.
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function PreviewRender() {
|
||||
const token = (await cookies()).get("preview-token")?.value;
|
||||
if (!token) {
|
||||
return <ExpiredPreview />;
|
||||
}
|
||||
|
||||
const { data, error } = await getClient().query(previewPageQuery, { token });
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.page) {
|
||||
return <ExpiredPreview />;
|
||||
}
|
||||
|
||||
const page = data.page;
|
||||
switch (page.__typename) {
|
||||
case "GenericPage":
|
||||
return <GenericPageView page={page as GenericFragment} />;
|
||||
case "StudioPage":
|
||||
return <StudioPageView page={page as StudioFragment} />;
|
||||
case "SponsorsPage":
|
||||
return <SponsorsPageView page={page as SponsorsPageFragment} />;
|
||||
case "EventPage":
|
||||
return <EventPageView event={page as EventFragment} />;
|
||||
case "NewsPage":
|
||||
return <NewsPageView news={page as NewsFragment} />;
|
||||
case "AssociationPage":
|
||||
return <AssociationPageView association={page as AssociationFragment} />;
|
||||
case "VenuePage":
|
||||
return <VenuePageView venue={page as VenueFragment} />;
|
||||
|
||||
case "HomePage": {
|
||||
const { data: aux } = await getClient().query(homeQuery, {});
|
||||
const events = (aux?.events?.futureEvents ?? []) as EventFragment[];
|
||||
const news = (aux?.news ?? []) as NewsFragment[];
|
||||
return (
|
||||
<HomePageView home={page as HomeFragment} events={events} news={news} />
|
||||
);
|
||||
}
|
||||
|
||||
case "EventIndex": {
|
||||
const { data: aux } = await getClient().query(eventsOverviewQuery, {});
|
||||
const events = (aux?.events?.futureEvents ?? []) as EventFragment[];
|
||||
const eventCategories = (aux?.eventCategories ?? []) as EventCategory[];
|
||||
const eventOrganizers = (aux?.eventOrganizers ?? []) as EventOrganizer[];
|
||||
const venues = (aux?.venues ?? []) as VenueFragment[];
|
||||
return (
|
||||
<EventIndexView
|
||||
events={events}
|
||||
eventCategories={eventCategories}
|
||||
eventOrganizers={eventOrganizers}
|
||||
venues={venues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
case "NewsIndex": {
|
||||
const { data: aux } = await getClient().query(newsQuery, {});
|
||||
const news = (aux?.news ?? []) as NewsFragment[];
|
||||
return <NewsIndexView index={page as NewsIndexFragment} news={news} />;
|
||||
}
|
||||
|
||||
case "AssociationIndex": {
|
||||
const { data: aux } = await getClient().query(allAssociationsQuery, {});
|
||||
const associations = (aux?.associations ?? []) as AssociationFragment[];
|
||||
return (
|
||||
<AssociationIndexView
|
||||
index={page as AssociationIndexFragment}
|
||||
associations={associations}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
case "VenueIndex": {
|
||||
const { data: aux } = await getClient().query(venueIndexQuery, {});
|
||||
const venues = (aux?.venues ?? []) as VenueFragment[];
|
||||
return (
|
||||
<VenueIndexView index={page as VenueIndexFragment} venues={venues} />
|
||||
);
|
||||
}
|
||||
|
||||
case "VenueRentalIndex": {
|
||||
const { data: aux } = await getClient().query(venueRentalIndexQuery, {});
|
||||
const venues = (aux?.venues ?? []) as VenueFragment[];
|
||||
return (
|
||||
<VenueRentalIndexView
|
||||
index={page as VenueRentalIndexFragment}
|
||||
venues={venues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
case "ContactIndex":
|
||||
return <ContactIndexView index={page as ContactIndexFragment} />;
|
||||
|
||||
default:
|
||||
return <UnsupportedType typename={page.__typename ?? "unknown"} />;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user