diff --git a/app/[locale]/[[...slug]]/category-page-preview.tsx b/app/[locale]/[[...slug]]/category-page-preview.tsx new file mode 100644 index 000000000..d2e8abea3 --- /dev/null +++ b/app/[locale]/[[...slug]]/category-page-preview.tsx @@ -0,0 +1,26 @@ +'use client' + +import PreviewBanner from 'components/ui/preview-banner' +import { usePreview } from 'lib/sanity/sanity.preview' +import CategoryPage from './category-page' + +export default function CategoryPagePreview({ + query, + queryParams, +}: { + query: string + queryParams: { + [key: string]: any + } +}) { + const data = usePreview(null, query, queryParams) + + const { title, _type } = data + + return ( + <> + + + + ) +} \ No newline at end of file diff --git a/app/[locale]/[[...slug]]/home-page-preview.tsx b/app/[locale]/[[...slug]]/home-page-preview.tsx new file mode 100644 index 000000000..567f24f60 --- /dev/null +++ b/app/[locale]/[[...slug]]/home-page-preview.tsx @@ -0,0 +1,26 @@ +'use client' + +import PreviewBanner from 'components/ui/preview-banner' +import { usePreview } from 'lib/sanity/sanity.preview' +import HomePage from './home-page' + +export default function HomePagePreview({ + query, + queryParams, +}: { + query: string + queryParams: { + [key: string]: any + } +}) { + const data = usePreview(null, query, queryParams) + + const { title, _type } = data + + return ( + <> + + + + ) +} \ No newline at end of file diff --git a/app/[locale]/[[...slug]]/page.tsx b/app/[locale]/[[...slug]]/page.tsx index 9ddfece67..35dcf0f51 100644 --- a/app/[locale]/[[...slug]]/page.tsx +++ b/app/[locale]/[[...slug]]/page.tsx @@ -1,19 +1,73 @@ +import PreviewSuspense from 'components/preview-suspense'; import getQueryFromSlug from 'helpers/getQueryFromSlug'; import { docQuery } from 'lib/sanity/queries'; import { client } from 'lib/sanity/sanity.client'; import type { Metadata } from 'next'; +import { draftMode } from 'next/headers'; import CategoryPage from './category-page'; +import CategoryPagePreview from './category-page-preview'; import HomePage from './home-page'; +import HomePagePreview from './home-page-preview'; import ProductPage from './product-page'; +import ProductPagePreview from './product-page-preview'; import SinglePage from './single-page'; +import SinglePagePreview from './single-page-preview'; + +/** + * Render pages depending on type. + */ +export default async function Page({ + params, +}: { + params: { slug: string[], locale: string }; +}) { + const { isEnabled } = draftMode(); + + const { slug, locale } = params; + + const { query = '', queryParams, docType } = getQueryFromSlug(slug, locale) + + const pageData = await client.fetch(query, queryParams) + + const data = filterDataToSingleItem(pageData, isEnabled) + + if (isEnabled) { + return ( + + {docType === 'home' && ( + + )} + {docType === 'page' && ( + + )} + {docType === 'product' && ( + + )} + {docType === 'category' && ( + + )} + + ) + } + + return ( + <> + {docType === 'home' && } + {docType === 'product' && } + {docType === 'category' && } + {docType === 'page' && } + + ) +} + +// Background revalidate once every day. +export const revalidate = 86400; /** * Get paths for each page. */ export async function generateStaticParams() { - const paths = await client.fetch(docQuery, { - next: { revalidate: 10 }, - }) + const paths = await client.fetch(docQuery) return paths.map((path: { slug: string, @@ -79,30 +133,4 @@ export async function generateMetadata({ params }: {params: { slug: string[], lo ], }, } -} - -/** - * Render pages depending on type. - */ -export default async function Page({ - params, -}: { - params: { slug: string[], locale: string }; -}) { - const { slug, locale } = params; - - const { query = '', queryParams, docType } = getQueryFromSlug(slug, locale) - - const pageData = await client.fetch(query, queryParams) - - const data = filterDataToSingleItem(pageData, false) - - return ( - <> - {docType === 'home' && } - {docType === 'product' && } - {docType === 'category' && } - {docType === 'page' && } - - ) } \ No newline at end of file diff --git a/app/[locale]/[[...slug]]/product-page-preview.tsx b/app/[locale]/[[...slug]]/product-page-preview.tsx new file mode 100644 index 000000000..21ed044dd --- /dev/null +++ b/app/[locale]/[[...slug]]/product-page-preview.tsx @@ -0,0 +1,26 @@ +'use client' + +import PreviewBanner from 'components/ui/preview-banner' +import { usePreview } from 'lib/sanity/sanity.preview' +import ProductPage from './product-page' + +export default function ProductPagePreview({ + query, + queryParams, +}: { + query: string + queryParams: { + [key: string]: any + } +}) { + const data = usePreview(null, query, queryParams) + + const { title, _type } = data + + return ( + <> + + + + ) +} \ No newline at end of file diff --git a/app/[locale]/[[...slug]]/single-page-preview.tsx b/app/[locale]/[[...slug]]/single-page-preview.tsx new file mode 100644 index 000000000..c09e74193 --- /dev/null +++ b/app/[locale]/[[...slug]]/single-page-preview.tsx @@ -0,0 +1,26 @@ +'use client' + +import PreviewBanner from 'components/ui/preview-banner' +import { usePreview } from 'lib/sanity/sanity.preview' +import SinglePage from './single-page' + +export default function SinglePagePreview({ + query, + queryParams, +}: { + query: string + queryParams: { + [key: string]: any + } +}) { + const data = usePreview(null, query, queryParams) + + const { title, _type } = data + + return ( + <> + + + + ) +} \ No newline at end of file diff --git a/app/api/draft/route.ts b/app/api/draft/route.ts new file mode 100644 index 000000000..fa394649f --- /dev/null +++ b/app/api/draft/route.ts @@ -0,0 +1,99 @@ +// route handler enabling draft mode +import { categoryQuery, homePageQuery, pageQuery, productQuery } from 'lib/sanity/queries'; +import { client } from 'lib/sanity/sanity.client'; +import { draftMode } from 'next/headers'; + +const draftSecret = process.env.NEXT_PUBLIC_SANITY_DRAFT_TOKEN + +export async function GET(request: Request) { +// Enable Draft Mode by setting the cookie +draftMode().enable(); + // Parse query string parameters + const { searchParams } = new URL(request.url); + const secret = searchParams.get('secret'); + const slug = searchParams.get('slug'); + const locale = searchParams.get('locale'); + const type = searchParams.get('type'); + + // Make sure there's a valid draft token. + if (secret !== draftSecret) { + return new Response('Invalid token', { status: 401 }); + } + + // Make sure there's a slug provided. + if (!slug) { + return new Response('No slug provided', { status: 401 }); + } + + // Make sure there's a locale provided. + if (!locale) { + return new Response('No locale provided', { status: 401 }); + } + + // Make sure there's a type provided. + if (!type) { + return new Response('No type provided', { status: 401 }); + } + + // Types available for preview - Check if the post with the given `slug` exists + const home = await client.fetch(homePageQuery, { + slug: slug, + locale: locale, + }) + + const page = await client.fetch(pageQuery, { + slug: slug, + locale: locale, + }) + + const product = await client.fetch(productQuery, { + slug: slug, + locale: locale, + }) + + const category = await client.fetch(categoryQuery, { + slug: slug, + locale: locale, + }) + + + draftMode().enable(); + + // Redirect to the path from the fetched post + // We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities + if (home && type === 'home') { + return new Response(null, { + status: 307, + headers: { + Location: `/${home.locale}/${home.slug}`, + }, + }) + } + + if (page && type === 'page') { + return new Response(null, { + status: 307, + headers: { + Location: `/${page.locale}/${page.slug}`, + }, + }) + } + + if (product && type === 'product') { + return new Response(null, { + status: 307, + headers: { + Location: `/${product.locale}/${product.slug}`, + }, + }) + } + + if (category && type === 'category') { + return new Response(null, { + status: 307, + headers: { + Location: `/${category.locale}/${category.slug}`, + }, + }) + } +} \ No newline at end of file diff --git a/app/api/exit-draft/route.ts b/app/api/exit-draft/route.ts new file mode 100644 index 000000000..6cb3d88f0 --- /dev/null +++ b/app/api/exit-draft/route.ts @@ -0,0 +1,12 @@ +import { draftMode } from 'next/headers'; + +export async function GET() { + draftMode().disable(); + + return new Response(null, { + status: 307, + headers: { + Location: `/`, + }, + }) +} \ No newline at end of file diff --git a/components/preview-suspense.tsx b/components/preview-suspense.tsx new file mode 100644 index 000000000..d45e24877 --- /dev/null +++ b/components/preview-suspense.tsx @@ -0,0 +1,4 @@ +'use client' + +// Once rollup supports 'use client' module directives then 'next-sanity' will include them and this re-export will no longer be necessary +export { PreviewSuspense as default } from 'next-sanity/preview' diff --git a/components/ui/preview-banner/index.tsx b/components/ui/preview-banner/index.tsx new file mode 100644 index 000000000..f1e59a4c1 --- /dev/null +++ b/components/ui/preview-banner/index.tsx @@ -0,0 +1 @@ +export { default } from './preview-banner'; diff --git a/components/ui/preview-banner/preview-banner.tsx b/components/ui/preview-banner/preview-banner.tsx new file mode 100644 index 000000000..d7f8ffe16 --- /dev/null +++ b/components/ui/preview-banner/preview-banner.tsx @@ -0,0 +1,29 @@ +'use client' + +import { useTranslations } from 'next-intl' +import Link from 'next/link' + +interface PreviewBannerProps { + title?: string +} + +const PreviewBanner = ({ title }: PreviewBannerProps) => { + const t = useTranslations('ui.previewBanner') + return ( +
+ {title && ( +

+ {t('titlePart')} {title} +

+ )} + + {t('exitPreviewLabel')} + +
+ ) +} + +export default PreviewBanner \ No newline at end of file