This commit is contained in:
Henrik Larsson 2023-08-21 10:16:11 +02:00
parent 1baa3c1f8d
commit e49b0a06ae
12 changed files with 193 additions and 74 deletions

View File

@ -1,17 +1,14 @@
import CategoryPage from '@/components/pages/category-page'; import CategoryPage from '@/components/pages/category-page';
import ProductPage from '@/components/pages/product-page'; import ProductPage from '@/components/pages/product-page';
import SearchPage from '@/components/pages/search-page'; import SearchPage from '@/components/pages/search-page';
import SearchPagePreview from '@/components/pages/search-page-preview';
import SinglePage from '@/components/pages/single-page'; import SinglePage from '@/components/pages/single-page';
import SinglePagePreview from '@/components/pages/single-page-preview'; // import PreviewProvider from '@/components/preview-provider';
import PreviewProvider from '@/components/preview-provider';
import getQueryFromSlug from '@/helpers/get-query-from-slug'; import getQueryFromSlug from '@/helpers/get-query-from-slug';
import { getCachedClient } from 'lib/sanity/sanity.client'; import { getCategory, getPage, getProduct, getSearch } from '@/lib/sanity/sanity.fetch';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { draftMode } from 'next/headers';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
export const dynamic = 'force-dynamic'; export const runtime = 'edge';
export async function generateMetadata({ export async function generateMetadata({
params params
@ -20,18 +17,23 @@ export async function generateMetadata({
}): Promise<Metadata> { }): Promise<Metadata> {
const { slug, locale } = params; const { slug, locale } = params;
const { query = '', queryParams } = getQueryFromSlug(slug, locale); const { queryParams, docType } = getQueryFromSlug(slug, locale);
const page = await getCachedClient()(query, queryParams); let page;
docType === 'page' && (page = await getPage(queryParams.slug, queryParams.locale));
docType === 'product' && (page = await getProduct(queryParams.slug, queryParams.locale));
docType === 'category' && (page = await getCategory(queryParams.slug, queryParams.locale));
docType === 'search' && (page = await getSearch(queryParams.slug, queryParams.locale));
if (!page) return notFound(); if (!page) return notFound();
return { return {
title: `${page.seo?.title || page.title}`, title: `${page.seo?.title || page.title}`,
description: page.seo?.description || page.bodySummary, description: page.seo?.description,
openGraph: { openGraph: {
publishedTime: page.createdAt, // publishedTime: page.createdAt,
modifiedTime: page.updatedAt, // modifiedTime: page.updatedAt,
type: 'article' type: 'article'
} }
}; };
@ -39,49 +41,38 @@ export async function generateMetadata({
interface PageParams { interface PageParams {
params: { params: {
locale: string;
slug: string[]; slug: string[];
locale: string;
}; };
} }
export default async function Page({ params }: PageParams) { export default async function Page({ params }: PageParams) {
const preview = draftMode().isEnabled ? { token: process.env.SANITY_API_READ_TOKEN } : undefined;
const { slug, locale } = params; const { slug, locale } = params;
const { query = '', queryParams, docType } = getQueryFromSlug(slug, locale); const { queryParams, docType } = getQueryFromSlug(slug, locale);
let pageData; let data;
if (docType === 'page') { if (docType === 'page') {
pageData = await getCachedClient()(query, queryParams); data = await getPage(queryParams.slug, queryParams.locale);
} else if (docType === 'product') { } else if (docType === 'product') {
pageData = await getCachedClient()(query, queryParams); data = await getProduct(queryParams.slug, queryParams.locale);
} else if (docType === 'category') { } else if (docType === 'category') {
pageData = await getCachedClient()(query, queryParams); data = await getCategory(queryParams.slug, queryParams.locale);
} else if (docType === 'search') { } else if (docType === 'search') {
pageData = await getCachedClient()(query, queryParams); data = await getSearch(queryParams.slug, queryParams.locale);
} else {
return;
} }
if (!pageData) return notFound(); if (!data) {
notFound();
if (preview && preview.token) {
return (
<PreviewProvider token={preview.token}>
{docType === 'page' && <SinglePagePreview initialData={pageData} params={queryParams} />}
{docType === 'search' && <SearchPagePreview initialData={pageData} params={queryParams} />}
</PreviewProvider>
);
} }
return ( return (
<> <>
{docType === 'page' && <SinglePage data={pageData} />} {docType === 'page' && <SinglePage data={data} />}
{docType === 'product' && <ProductPage data={pageData} />} {docType === 'product' && <ProductPage data={data} />}
{docType === 'category' && <CategoryPage data={pageData} />} {docType === 'category' && <CategoryPage data={data} />}
{docType === 'search' && <SearchPage data={pageData} />} {docType === 'search' && <SearchPage data={data} />}
</> </>
); );
} }

View File

@ -1,8 +1,11 @@
import PreviewProvider from '@/components/preview/preview-provider';
import { token } from '@/lib/sanity/sanity.fetch';
import { Analytics } from '@vercel/analytics/react'; import { Analytics } from '@vercel/analytics/react';
import Footer from 'components/layout/footer/footer'; import Footer from 'components/layout/footer/footer';
import Header from 'components/layout/header/header'; import Header from 'components/layout/header/header';
import { NextIntlClientProvider } from 'next-intl'; import { NextIntlClientProvider } from 'next-intl';
import { Inter } from 'next/font/google'; import { Inter } from 'next/font/google';
import { draftMode } from 'next/headers';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { ReactNode, Suspense } from 'react'; import { ReactNode, Suspense } from 'react';
import { supportedLanguages } from '../../../i18n-config'; import { supportedLanguages } from '../../../i18n-config';
@ -54,7 +57,9 @@ export default async function LocaleLayout({ children, params: { locale } }: Loc
notFound(); notFound();
} }
return ( const isDraftMode = draftMode().isEnabled;
const layout = (
<html lang={locale} className={inter.variable}> <html lang={locale} className={inter.variable}>
<body className="flex min-h-screen flex-col"> <body className="flex min-h-screen flex-col">
<NextIntlClientProvider locale={locale} messages={messages}> <NextIntlClientProvider locale={locale} messages={messages}>
@ -70,4 +75,10 @@ export default async function LocaleLayout({ children, params: { locale } }: Loc
</body> </body>
</html> </html>
); );
if (isDraftMode) {
return <PreviewProvider token={token!}>{layout}</PreviewProvider>;
}
return layout;
} }

View File

@ -8,7 +8,6 @@ import { draftMode } from 'next/headers';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
export const runtime = 'edge'; export const runtime = 'edge';
export const dynamic = 'force-dynamic';
export async function generateMetadata({ export async function generateMetadata({
params params
@ -31,16 +30,12 @@ interface HomePageParams {
} }
export default async function IndexPage({ params }: HomePageParams) { export default async function IndexPage({ params }: HomePageParams) {
// const preview = draftMode().isEnabled ? { token: process.env.SANITY_API_READ_TOKEN } : undefined;
const data = await getHomePage(params.locale); const data = await getHomePage(params.locale);
if (!data && !draftMode().isEnabled) { if (!data && !draftMode().isEnabled) {
notFound(); notFound();
} }
console.log('Preview:', draftMode().isEnabled);
return ( return (
<LiveQuery <LiveQuery
enabled={draftMode().isEnabled} enabled={draftMode().isEnabled}

View File

@ -1,8 +1,7 @@
import { previewSecretId } from '@/lib/sanity/sanity.api' // import { previewSecretId } from '@/lib/sanity/sanity.api'
import { client } from '@/lib/sanity/sanity.client' // import { client } from '@/lib/sanity/sanity.client'
import { token } from '@/lib/sanity/sanity.fetch' import { token } from '@/lib/sanity/sanity.fetch'
import { draftMode } from 'next/headers' import { draftMode } from 'next/headers'
import { isValidSecret } from 'sanity-plugin-iframe-pane/is-valid-secret'
export const runtime = 'edge' export const runtime = 'edge'
@ -18,24 +17,27 @@ export async function GET(request: Request) {
'The `SANITY_API_READ_TOKEN` environment variable is required.', 'The `SANITY_API_READ_TOKEN` environment variable is required.',
) )
} }
if (!secret) { if (!secret) {
return new Response('Invalid secret', { status: 401 }) return new Response('Invalid secret', { status: 401 })
} }
const authenticatedClient = client.withConfig({ token }) // const authenticatedClient = client.withConfig({ token })
const validSecret = await isValidSecret( // const validSecret = await isValidSecret(
authenticatedClient, // authenticatedClient,
previewSecretId, // previewSecretId,
secret, // secret,
) // )
if (!validSecret) { // if (!validSecret) {
return new Response('Invalid secret', { status: 401 }) // return new Response('Invalid secret', { status: 401 })
} // }
draftMode().enable() draftMode().enable()
console.log(draftMode())
if (type === 'home') { if (type === 'home') {
return new Response(null, { return new Response(null, {
status: 307, status: 307,

View File

@ -1,24 +1,22 @@
'use client'; 'use client';
import { homePageQuery } from '@/lib/sanity/queries'; import dynamic from 'next/dynamic';
import { useLiveQuery } from '@sanity/preview-kit';
import PreviewBanner from '../ui/preview-banner'; import PreviewBanner from '../ui/preview-banner';
import HomePage from './home-page'; import type { IndexPageParams } from './home-page';
interface HomePagePreviewParams { const HomePage = dynamic(() => import('./home-page'));
initialData: [];
params: {
locale: string;
};
}
export default function HomePagePreview({ initialData, params }: HomePagePreviewParams) { export default function HomePagePreview({ data }: IndexPageParams) {
const [data] = useLiveQuery(initialData, homePageQuery, params); if (!data) {
return (
<div className="text-center">Please start editing your Home document to see the preview!</div>
);
}
return ( return (
<> <>
<HomePage data={data} />;{/* @ts-ignore */} <HomePage data={data} />
<PreviewBanner title={data?.title} type={data?._type} /> <PreviewBanner title={data?.title ? data.title : ''} />
</> </>
); );
} }

View File

@ -1,12 +1,11 @@
import DynamicContentManager from '@/components/layout/dynamic-content-manager/dynamic-content-manager'; import DynamicContentManager from '@/components/layout/dynamic-content-manager/dynamic-content-manager';
import type { HomePagePayload } from '@/lib/sanity/sanity.types'; import type { HomePagePayload } from '@/lib/sanity/sanity.types';
interface IndexPageParams {
export type IndexPageParams = {
data: HomePagePayload | null; data: HomePagePayload | null;
} };
export default function HomePage({ data }: IndexPageParams) { export default function HomePage({ data }: IndexPageParams) {
// console.log(data);
return ( return (
<> <>
<DynamicContentManager content={data?.content} />; <DynamicContentManager content={data?.content} />;

View File

@ -0,0 +1,30 @@
'use client';
import dynamic from 'next/dynamic';
import { suspend } from 'suspend-react';
const LiveQueryProvider = dynamic(() => import('next-sanity/preview'));
// suspend-react cache is global, so we use a unique key to avoid collisions
const UniqueKey = Symbol('lib/sanity.client');
export default function PreviewProvider({
children,
token
}: {
children: React.ReactNode;
token: string;
}) {
const { client } = suspend(() => import('@/lib/sanity/sanity.client'), [UniqueKey]);
if (!token) throw new TypeError('Missing token');
return (
<LiveQueryProvider
client={client}
token={token}
// Uncomment below to see debug reports
logger={console}
>
{children}
</LiveQueryProvider>
);
}

View File

@ -4,7 +4,7 @@ import { useTranslations } from 'next-intl';
import Link from 'next/link'; import Link from 'next/link';
interface PreviewBannerProps { interface PreviewBannerProps {
title: string; title?: string;
type?: string; type?: string;
} }

View File

@ -7,9 +7,9 @@ import { draftMode } from 'next/headers'
import { revalidateSecret } from './sanity.api' import { revalidateSecret } from './sanity.api'
import { homePageQuery } from './queries' import { categoryQuery, homePageQuery, pageQuery, productQuery, searchPageQuery } from './queries'
import { HomePagePayload } from './sanity.types' import { CategoryPayload, HomePagePayload, PagePayload, ProductPayload, SearchPayload } from './sanity.types'
export const token = process.env.SANITY_API_READ_TOKEN export const token = process.env.SANITY_API_READ_TOKEN
@ -56,6 +56,38 @@ export function getHomePage(locale: string) {
return sanityFetch<HomePagePayload | null>({ return sanityFetch<HomePagePayload | null>({
query: homePageQuery, query: homePageQuery,
params: { locale }, params: { locale },
tags: ['home', 'products', 'categories'], tags: ['home', 'products', 'categories', 'page'],
})
}
export function getPage(slug: string, locale: string) {
return sanityFetch<PagePayload | null>({
query: pageQuery,
params: { slug, locale },
tags: ['page'],
})
}
export function getProduct(slug: string, locale: string) {
return sanityFetch<ProductPayload | null>({
query: productQuery,
params: { slug, locale },
tags: ['product'],
})
}
export function getCategory(slug: string, locale: string) {
return sanityFetch<CategoryPayload | null>({
query: categoryQuery,
params: { slug, locale },
tags: ['category'],
})
}
export function getSearch(slug: string, locale: string) {
return sanityFetch<SearchPayload | null>({
query: searchPageQuery,
params: { slug, locale },
tags: ['search'],
}) })
} }

View File

@ -9,4 +9,53 @@ export interface HomePagePayload {
description?: string; description?: string;
image: Image image: Image
} }
}
export interface PagePayload {
content?: []
title?: string
_type?: string
slug?: string
seo?: {
title?: string;
description?: string;
image: Image
}
}
export interface ProductPayload {
title?: string
name?: string
description?: string
images?: Image[]
currencyCode?: string
_type?: string
slug?: string
seo?: {
title?: string;
description?: string;
image: Image
}
}
export interface CategoryPayload {
title?: string
_type?: string
slug?: string
seo?: {
title?: string;
description?: string;
image: Image
}
}
export interface SearchPayload {
title?: string
_type?: string
slug?: string
seo?: {
title?: string;
description?: string;
image: Image
}
} }

View File

@ -57,6 +57,7 @@
"slug": "^8.2.2", "slug": "^8.2.2",
"slugify": "^1.6.5", "slugify": "^1.6.5",
"styled-components": "^5.3.10", "styled-components": "^5.3.10",
"suspend-react": "^0.1.3",
"tailwind-merge": "^1.12.0", "tailwind-merge": "^1.12.0",
"tailwindcss-animate": "^1.0.5" "tailwindcss-animate": "^1.0.5"
}, },

11
pnpm-lock.yaml generated
View File

@ -119,6 +119,9 @@ dependencies:
styled-components: styled-components:
specifier: ^5.3.10 specifier: ^5.3.10
version: 5.3.11(@babel/core@7.22.10)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) version: 5.3.11(@babel/core@7.22.10)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0)
suspend-react:
specifier: ^0.1.3
version: 0.1.3(react@18.2.0)
tailwind-merge: tailwind-merge:
specifier: ^1.12.0 specifier: ^1.12.0
version: 1.14.0 version: 1.14.0
@ -8169,6 +8172,14 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/suspend-react@0.1.3(react@18.2.0):
resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==}
peerDependencies:
react: '>=17.0'
dependencies:
react: 18.2.0
dev: false
/symbol-tree@3.2.4: /symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: false dev: false