add live + draft

This commit is contained in:
2024-10-27 20:12:54 +01:00
parent ff7910584c
commit e9abd5c6d4
20 changed files with 566 additions and 32 deletions

88
src/app/[slug]/page.tsx Normal file
View File

@@ -0,0 +1,88 @@
import { defineQuery, PortableText } from "next-sanity";
import imageUrlBuilder from "@sanity/image-url";
import type { SanityImageSource } from "@sanity/image-url/lib/types/types";
import { client, sanityFetch } from "../../sanity/client";
import Link from "next/link";
import Image from "next/image";
import { Post, SanityImageAsset } from "@/sanity/sanity.types";
import urlBuilder from "@sanity/image-url";
import {getImageDimensions} from '@sanity/asset-utils'
const POST_QUERY = defineQuery(`*[_type == "post" && slug.current == $slug][0]`);
const POSTS_QUERY = defineQuery(`*[_type == "post"]{slug}`)
type PageParams = Promise<{
slug: string;
}>
const { projectId, dataset } = client.config();
const urlFor = (source: SanityImageSource) =>
projectId && dataset
? imageUrlBuilder({ projectId, dataset }).image(source)
: null;
export async function generateStaticParams() {
const posts: Post[] = (await sanityFetch({ query: POSTS_QUERY, stega: false, perspective: "published" })).data;
return posts.map((post) => ({ slug: post.slug?.current ?? null })).filter((post) => post.slug);
}
export default async function PostPage(props: { params: PageParams }) {
const { slug } = await props.params;
const post: Post = (await sanityFetch({ query: POST_QUERY, params: { slug } })).data;
if (!post) {
return (
<main>
<p>Post not found</p>
</main>
);
}
const postImageUrl = post.mainImage ? urlFor(post.mainImage)?.width(550).height(310).url() : null;
function PortableImage({ value, isInline }: { value: SanityImageAsset; isInline: boolean }) {
const {width, height} = getImageDimensions(value)
return <Image
src={urlBuilder()
.image(value)
.width(isInline ? 100 : 600)
.fit('max')
.auto('format')
.withOptions({dataset, projectId})
.url()}
width={isInline ? 100 : 600}
height={height}
alt={value.altText || ' '}
loading="lazy"
style={{
display: isInline ? 'inline-block' : 'block',
aspectRatio: width / height,
borderRadius: ".6rem",
border: "1px solid rgba(255, 255, 255, .15)",
}}
/>;
}
return (
<main className="container mx-auto min-h-screen max-w-3xl p-8 flex flex-col gap-4">
<Link href="/" className="hover:underline">
Back to posts
</Link>
{postImageUrl && (
<Image
src={postImageUrl}
alt={`Banner for ${post.title}` }
className="aspect-video rounded-xl"
width="550"
height="310"
/>
)}
<h1 className="text-4xl font-bold mb-8">{post.title}</h1>
<div className="prose">
<p>Published: {new Date(post.publishedAt ?? "").toISOString().substring(0, 10)}</p>
{Array.isArray(post.body) && <PortableText value={post.body} components={ { types: { image: PortableImage } } } />}
</div>
</main>
);
}

View File

@@ -0,0 +1,6 @@
import { client } from '@/sanity/client';
import { defineEnableDraftMode } from 'next-sanity/draft-mode';
export const { GET } = defineEnableDraftMode({
client: client.withConfig({ token: process.env.SANITY_API_READ_TOKEN, stega: { studioUrl: 'https://vaporvee.sanity.studio', enabled: true } }),
})

42
src/app/layout.tsx Normal file
View File

@@ -0,0 +1,42 @@
import localFont from "next/font/local";
import "../styles/globals.scss";
import { draftMode } from "next/headers";
import { VisualEditing } from 'next-sanity'
import { SanityLive } from '@/sanity/client'
import { LiveErrorBoundary } from "./live-error-boundary";
import { Toaster } from "@/components/ui/sonner";
const geistSans = localFont({
src: "../fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "../fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const { isEnabled } = await draftMode();
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<LiveErrorBoundary>
<SanityLive />
</LiveErrorBoundary>
{isEnabled && <VisualEditing />}
<Toaster />
</body>
</html>
);
}

View File

@@ -0,0 +1,24 @@
"use client";
import { useEffect } from "react";
import {
ErrorBoundary as ReactErrorBoundary,
type FallbackProps,
} from "react-error-boundary";
export function LiveErrorBoundary({ children }: { children: React.ReactNode }) {
return (
<ReactErrorBoundary FallbackComponent={Fallback}>
{children}
</ReactErrorBoundary>
);
}
function Fallback({ error }: FallbackProps) {
useEffect(() => {
const msg = "Couldn't connect to Live Content API";
console.error(`${msg}: `, error);
}, [error]);
return null;
}

31
src/app/page.tsx Normal file
View File

@@ -0,0 +1,31 @@
import Link from "next/link";
import { type SanityDocument } from "next-sanity";
import { client } from "@/sanity/client";
const POSTS_QUERY = `*[
_type == "post"
&& defined(slug.current)
]|order(publishedAt desc)[0...12]{_id, title, slug, publishedAt}`;
const options = { next: { revalidate: 30 } };
export default async function IndexPage() {
const posts = await client.fetch<SanityDocument[]>(POSTS_QUERY, {}, options);
return (
<main className="container mx-auto min-h-screen max-w-3xl p-8">
<h1 className="text-4xl font-bold mb-8">Posts</h1>
<ul className="flex flex-col gap-y-4">
{posts.map((post) => (
<li className="hover:underline" key={post._id}>
<Link href={`/${post.slug.current}`}>
<h2 className="text-xl font-semibold">{post.title}</h2>
<p>{new Date(post.publishedAt).toLocaleDateString()}</p>
</Link>
</li>
))}
</ul>
</main>
);
}