"use client"; import { FC, useEffect } from "react"; import { cookie as prismicCookie } from "@prismicio/client"; import { useRouter } from "next/navigation"; type PrismicPreviewClientProps = { repositoryName: string; isDraftMode: boolean; updatePreviewURL?: string; exitPreviewURL?: string; }; export const PrismicPreviewClient: FC = (props) => { const { repositoryName, isDraftMode, updatePreviewURL = "/api/preview", exitPreviewURL = "/api/exit-preview", } = props; const { refresh } = useRouter(); useEffect(() => { const controller = new AbortController(); window.addEventListener("prismicPreviewUpdate", onUpdate, { signal: controller.signal, }); window.addEventListener("prismicPreviewEnd", onEnd, { signal: controller.signal, }); const cookie = getPrismicPreviewCookie(window.document.cookie); const cookieRepositoryName = cookie ? (decodeURIComponent(cookie).match(/"([^"]+)\.prismic\.io"/) || [])[1] : undefined; const hasCookieForRepository = cookieRepositoryName === repositoryName; // Start the preview for preview share links. Previews from // share links do not go to the `updatePreviewURL` like a normal // preview. if (hasCookieForRepository && !isDraftMode) { console.log("starting preview link"); // 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 draft mode // is active. globalThis .fetch(updatePreviewURL, { redirect: "manual", signal: controller.signal, }) .then((res) => { if (res.type !== "opaqueredirect") { console.error( `[] Failed to start the preview using "${updatePreviewURL}". Does it exist?`, ); return; } refresh(); }) .catch(() => { // noop }); } function onUpdate(event: Event) { event.preventDefault(); refresh(); } function onEnd(event: Event) { event.preventDefault(); globalThis .fetch(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(() => { // noop }); } return () => controller.abort(); }, [repositoryName, isDraftMode, updatePreviewURL, exitPreviewURL, refresh]); return null; }; /** * Returns the value of a cookie from a given cookie store. * * @param cookieJar - The stringified cookie store from which to read the * cookie. * * @returns The value of the cookie, if it exists. */ function getPrismicPreviewCookie(cookieJar: string): string | undefined { function readValue(value: string): string { return value.replace(/%3B/g, ";"); } const cookies = cookieJar.split("; "); let value: string | undefined; for (const cookie of cookies) { const parts = cookie.split("="); const name = readValue(parts[0]).replace(/%3D/g, "="); if (name === prismicCookie.preview) { value = readValue(parts.slice(1).join("=")); continue; } } return value; }