import { type ReactNode, useEffect, FC } from "react"; import { useRouter } from "next/router"; import Script from "next/script"; import { getToolbarSrc, cookie as prismicCookie } from "@prismicio/client"; /** Props for ``. */ export type PrismicPreviewProps = { /** * The name of your Prismic repository. A Prismic Toolbar will be registered * using this repository. */ repositoryName: string; /** * The URL of your app's Prismic preview endpoint (default: `/api/preview`). * This URL will be fetched on preview update events. */ updatePreviewURL?: string; /** * The URL of your app's exit preview endpoint (default: `/api/exit-preview`). * This URL will be fetched on preview exit events. */ exitPreviewURL?: string; /** Children to render adjacent to the Prismic Toolbar. */ children?: ReactNode; }; /** * React component that sets up Prismic Previews using the Prismic Toolbar. When * the Prismic Toolbar send events to the browser, such as on preview updates * and exiting, this component will automatically refresh the page with the * changes. * * This component can be wrapped around your app or added anywhere in your app's * tree. It must be rendered on every page. */ export const PrismicPreview: FC = (props) => { const { repositoryName, updatePreviewURL = "/api/preview", exitPreviewURL = "/api/exit-preview", children, } = props; const router = useRouter(); const toolbarSrc = getToolbarSrc(repositoryName); useEffect(() => { const controller = new AbortController(); window.addEventListener("prismicPreviewUpdate", onUpdate, { signal: controller.signal, }); window.addEventListener("prismicPreviewEnd", onEnd, { signal: controller.signal, }); // Start the preview for preview share links. Previews from // share links do not go to the `updatePreviewURL` like a normal // preview. // // We check that the current URL is a descendant of the base // path to prevent infinite refrehes. if ( window.location.href.startsWith( window.location.origin + router.basePath, ) && getPreviewCookieRepositoryName() === repositoryName && !router.isPreview ) { start(); } function onEnd(event: Event) { event.preventDefault(); fetch(router.basePath + exitPreviewURL, { signal: controller.signal }) .then((res) => { if (!res.ok) { console.error( `[] Failed to exit Preview Mode using the "${exitPreviewURL}" API endpoint. Does it exist?`, ); return; } refresh(); }) .catch(() => {}); } function onUpdate(event: Event) { event.preventDefault(); start(); } function start() { // We check `opaqueredirect` because we don't care if // the redirect was successful or not. As long as it // redirects, we know the endpoint exists and at least // attempted to set preview data. fetch(router.basePath + updatePreviewURL, { redirect: "manual", signal: controller.signal, }) .then((res) => { if (res.type !== "opaqueredirect") { console.error( `[] Failed to start or update the preview using "${updatePreviewURL}". Does it exist?`, ); return; } refresh(); }) .catch(() => {}); } function refresh() { router.replace(router.asPath, undefined, { scroll: false }); } return () => controller.abort(); }, [exitPreviewURL, updatePreviewURL, repositoryName, router]); return ( <> {children}