Live preview when editting website! Very cool!

This commit is contained in:
2026-03-26 16:30:28 -05:00
parent b678e405c5
commit 475f1cad85
8 changed files with 78 additions and 6 deletions

View File

@@ -21,6 +21,7 @@
"@gib/backend": "workspace:*", "@gib/backend": "workspace:*",
"@gib/ui": "workspace:*", "@gib/ui": "workspace:*",
"@payloadcms/db-postgres": "^3.80.0", "@payloadcms/db-postgres": "^3.80.0",
"@payloadcms/live-preview-react": "^3.80.0",
"@payloadcms/next": "^3.80.0", "@payloadcms/next": "^3.80.0",
"@payloadcms/richtext-lexical": "^3.80.0", "@payloadcms/richtext-lexical": "^3.80.0",
"@sentry/nextjs": "^10.43.0", "@sentry/nextjs": "^10.43.0",

View File

@@ -333,6 +333,7 @@ export interface LandingPage {
commandLabel?: string | null; commandLabel?: string | null;
command?: string | null; command?: string | null;
}; };
_status?: ('draft' | 'published') | null;
updatedAt?: string | null; updatedAt?: string | null;
createdAt?: string | null; createdAt?: string | null;
} }
@@ -403,6 +404,7 @@ export interface LandingPageSelect<T extends boolean = true> {
commandLabel?: T; commandLabel?: T;
command?: T; command?: T;
}; };
_status?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
globalType?: T; globalType?: T;

View File

@@ -1,11 +1,24 @@
import { CTA, Features, Hero, TechStack } from '@/components/landing'; import { CTA, Features, Hero, TechStack } from '@/components/landing';
import { RefreshRouteOnSave } from '@/components/payload/refresh-route-on-save';
import { getLandingPageContent } from '@/lib/payload/get-landing-page-content'; import { getLandingPageContent } from '@/lib/payload/get-landing-page-content';
const Home = async () => { type HomeProps = {
const content = await getLandingPageContent(); searchParams: Promise<{
preview?: string | string[];
}>;
};
const Home = async ({ searchParams }: HomeProps) => {
const resolvedSearchParams = await searchParams;
const previewParam = resolvedSearchParams.preview;
const isPreview = Array.isArray(previewParam)
? previewParam.includes('true')
: previewParam === 'true';
const content = await getLandingPageContent(isPreview);
return ( return (
<main className='flex min-h-screen flex-col'> <main className='flex min-h-screen flex-col'>
{isPreview ? <RefreshRouteOnSave /> : null}
<Hero content={content.hero} /> <Hero content={content.hero} />
<Features content={content.features} /> <Features content={content.features} />
<TechStack content={content.techStack} /> <TechStack content={content.techStack} />

View File

@@ -0,0 +1,16 @@
'use client';
import { useRouter } from 'next/navigation';
import { env } from '@/env';
import { RefreshRouteOnSave as PayloadRefreshRouteOnSave } from '@payloadcms/live-preview-react';
export const RefreshRouteOnSave = () => {
const router = useRouter();
return (
<PayloadRefreshRouteOnSave
refresh={() => router.refresh()}
serverURL={env.NEXT_PUBLIC_SITE_URL}
/>
);
};

View File

@@ -8,13 +8,16 @@ import {
import { getPayloadClient } from './get-payload'; import { getPayloadClient } from './get-payload';
export const getLandingPageContent = cache( export const getLandingPageContent = cache(
async (): Promise<LandingPageContent> => { async (isPreview = false): Promise<LandingPageContent> => {
const payload = await getPayloadClient(); const payload = await getPayloadClient();
const landingPage = await ( const landingPage = await (
payload as { payload as {
findGlobal: (args: { slug: string }) => Promise<unknown>; findGlobal: (args: {
slug: string;
draft?: boolean;
}) => Promise<unknown>;
} }
).findGlobal({ slug: 'landing-page' }); ).findGlobal({ slug: 'landing-page', draft: isPreview });
return mergeLandingPageContent( return mergeLandingPageContent(
(landingPage as Partial<LandingPageContent> | null | undefined) ?? (landingPage as Partial<LandingPageContent> | null | undefined) ??

View File

@@ -8,6 +8,38 @@ export const LandingPage: GlobalConfig = {
access: { access: {
read: () => true, read: () => true,
}, },
admin: {
livePreview: {
url: '/?preview=true',
breakpoints: [
{
label: 'Mobile',
name: 'mobile',
width: 390,
height: 844,
},
{
label: 'Tablet',
name: 'tablet',
width: 768,
height: 1024,
},
{
label: 'Desktop',
name: 'desktop',
width: 1440,
height: 1024,
},
],
},
},
versions: {
drafts: {
autosave: {
interval: 500,
},
},
},
fields: [ fields: [
{ {
type: 'tabs', type: 'tabs',

View File

@@ -6,7 +6,7 @@ import {
} from '@convex-dev/auth/nextjs/server'; } from '@convex-dev/auth/nextjs/server';
const isSignInPage = createRouteMatcher(['/sign-in']); const isSignInPage = createRouteMatcher(['/sign-in']);
const isProtectedRoute = createRouteMatcher(['/profile']); const isProtectedRoute = createRouteMatcher(['/profile', '/admin']);
export default convexAuthNextjsMiddleware( export default convexAuthNextjsMiddleware(
async (request, { convexAuth }) => { async (request, { convexAuth }) => {

View File

@@ -73,6 +73,7 @@
"@gib/backend": "workspace:*", "@gib/backend": "workspace:*",
"@gib/ui": "workspace:*", "@gib/ui": "workspace:*",
"@payloadcms/db-postgres": "^3.80.0", "@payloadcms/db-postgres": "^3.80.0",
"@payloadcms/live-preview-react": "^3.80.0",
"@payloadcms/next": "^3.80.0", "@payloadcms/next": "^3.80.0",
"@payloadcms/richtext-lexical": "^3.80.0", "@payloadcms/richtext-lexical": "^3.80.0",
"@sentry/nextjs": "^10.43.0", "@sentry/nextjs": "^10.43.0",
@@ -1155,6 +1156,10 @@
"@payloadcms/graphql": ["@payloadcms/graphql@3.80.0", "", { "dependencies": { "graphql-scalars": "1.22.2", "pluralize": "8.0.0", "ts-essentials": "10.0.3", "tsx": "4.21.0" }, "peerDependencies": { "graphql": "^16.8.1", "payload": "3.80.0" }, "bin": { "payload-graphql": "bin.js" } }, "sha512-AlJcFI/R+4SRPWW51ny2BsIj+4j6qVxyn0W5Kz1f5MMfheuD844tc92O+IwmUsrRsb0l6zv3zI3G3M6V2WdFFQ=="], "@payloadcms/graphql": ["@payloadcms/graphql@3.80.0", "", { "dependencies": { "graphql-scalars": "1.22.2", "pluralize": "8.0.0", "ts-essentials": "10.0.3", "tsx": "4.21.0" }, "peerDependencies": { "graphql": "^16.8.1", "payload": "3.80.0" }, "bin": { "payload-graphql": "bin.js" } }, "sha512-AlJcFI/R+4SRPWW51ny2BsIj+4j6qVxyn0W5Kz1f5MMfheuD844tc92O+IwmUsrRsb0l6zv3zI3G3M6V2WdFFQ=="],
"@payloadcms/live-preview": ["@payloadcms/live-preview@3.80.0", "", {}, "sha512-O28f7DoiE7n5z1ukiquVNNyqJcLgzNP1Qb/g9tmY1ppe16c+jni0NbSRw55uAsZ0PdfZuiJNZ+50iubEtsXCdw=="],
"@payloadcms/live-preview-react": ["@payloadcms/live-preview-react@3.80.0", "", { "dependencies": { "@payloadcms/live-preview": "3.80.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.1 || ^19.1.2 || ^19.2.1", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.1 || ^19.1.2 || ^19.2.1" } }, "sha512-J0IUH9gow3wWJqUitJdS56z5Dj2cxBHZZjrnHwb4qF5bV3wChSDTLZvzRB8lZ1/m2vB7yGTgateHDLBZ6Q5PpA=="],
"@payloadcms/next": ["@payloadcms/next@3.80.0", "", { "dependencies": { "@dnd-kit/core": "6.3.1", "@dnd-kit/modifiers": "9.0.0", "@dnd-kit/sortable": "10.0.0", "@payloadcms/graphql": "3.80.0", "@payloadcms/translations": "3.80.0", "@payloadcms/ui": "3.80.0", "busboy": "^1.6.0", "dequal": "2.0.3", "file-type": "19.3.0", "graphql-http": "^1.22.0", "graphql-playground-html": "1.6.30", "http-status": "2.1.0", "path-to-regexp": "6.3.0", "qs-esm": "7.0.2", "sass": "1.77.4", "uuid": "10.0.0" }, "peerDependencies": { "graphql": "^16.8.1", "next": ">=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.0-canary.10 <17.0.0", "payload": "3.80.0" } }, "sha512-JL7Ydi/rm7hoLDzMO6H1mBQdvxJ5Dww26aomMOYMziUr9YzU6w+W5EnJgN3x2vH0myvDr9VUfNTTBNK616G5LQ=="], "@payloadcms/next": ["@payloadcms/next@3.80.0", "", { "dependencies": { "@dnd-kit/core": "6.3.1", "@dnd-kit/modifiers": "9.0.0", "@dnd-kit/sortable": "10.0.0", "@payloadcms/graphql": "3.80.0", "@payloadcms/translations": "3.80.0", "@payloadcms/ui": "3.80.0", "busboy": "^1.6.0", "dequal": "2.0.3", "file-type": "19.3.0", "graphql-http": "^1.22.0", "graphql-playground-html": "1.6.30", "http-status": "2.1.0", "path-to-regexp": "6.3.0", "qs-esm": "7.0.2", "sass": "1.77.4", "uuid": "10.0.0" }, "peerDependencies": { "graphql": "^16.8.1", "next": ">=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.0-canary.10 <17.0.0", "payload": "3.80.0" } }, "sha512-JL7Ydi/rm7hoLDzMO6H1mBQdvxJ5Dww26aomMOYMziUr9YzU6w+W5EnJgN3x2vH0myvDr9VUfNTTBNK616G5LQ=="],
"@payloadcms/richtext-lexical": ["@payloadcms/richtext-lexical@3.80.0", "", { "dependencies": { "@lexical/clipboard": "0.41.0", "@lexical/headless": "0.41.0", "@lexical/html": "0.41.0", "@lexical/link": "0.41.0", "@lexical/list": "0.41.0", "@lexical/mark": "0.41.0", "@lexical/react": "0.41.0", "@lexical/rich-text": "0.41.0", "@lexical/selection": "0.41.0", "@lexical/table": "0.41.0", "@lexical/utils": "0.41.0", "@payloadcms/translations": "3.80.0", "@payloadcms/ui": "3.80.0", "@types/uuid": "10.0.0", "acorn": "8.16.0", "bson-objectid": "2.0.4", "csstype": "3.1.3", "dequal": "2.0.3", "escape-html": "1.0.3", "jsox": "1.2.121", "lexical": "0.41.0", "mdast-util-from-markdown": "2.0.2", "mdast-util-mdx-jsx": "3.1.3", "micromark-extension-mdx-jsx": "3.0.1", "qs-esm": "7.0.2", "react-error-boundary": "4.1.2", "ts-essentials": "10.0.3", "uuid": "10.0.0" }, "peerDependencies": { "@faceless-ui/modal": "3.0.0", "@faceless-ui/scroll-info": "2.0.0", "@payloadcms/next": "3.80.0", "payload": "3.80.0", "react": "^19.0.1 || ^19.1.2 || ^19.2.1", "react-dom": "^19.0.1 || ^19.1.2 || ^19.2.1" } }, "sha512-P7F7VoCS4dZFxdausUdxc79t6tlMpeusf2QwdtyBnB4FwuC8sAc2TW9dxsP+N/gvAnEuPR6rAd/kUma1kQoJug=="], "@payloadcms/richtext-lexical": ["@payloadcms/richtext-lexical@3.80.0", "", { "dependencies": { "@lexical/clipboard": "0.41.0", "@lexical/headless": "0.41.0", "@lexical/html": "0.41.0", "@lexical/link": "0.41.0", "@lexical/list": "0.41.0", "@lexical/mark": "0.41.0", "@lexical/react": "0.41.0", "@lexical/rich-text": "0.41.0", "@lexical/selection": "0.41.0", "@lexical/table": "0.41.0", "@lexical/utils": "0.41.0", "@payloadcms/translations": "3.80.0", "@payloadcms/ui": "3.80.0", "@types/uuid": "10.0.0", "acorn": "8.16.0", "bson-objectid": "2.0.4", "csstype": "3.1.3", "dequal": "2.0.3", "escape-html": "1.0.3", "jsox": "1.2.121", "lexical": "0.41.0", "mdast-util-from-markdown": "2.0.2", "mdast-util-mdx-jsx": "3.1.3", "micromark-extension-mdx-jsx": "3.0.1", "qs-esm": "7.0.2", "react-error-boundary": "4.1.2", "ts-essentials": "10.0.3", "uuid": "10.0.0" }, "peerDependencies": { "@faceless-ui/modal": "3.0.0", "@faceless-ui/scroll-info": "2.0.0", "@payloadcms/next": "3.80.0", "payload": "3.80.0", "react": "^19.0.1 || ^19.1.2 || ^19.2.1", "react-dom": "^19.0.1 || ^19.1.2 || ^19.2.1" } }, "sha512-P7F7VoCS4dZFxdausUdxc79t6tlMpeusf2QwdtyBnB4FwuC8sAc2TW9dxsP+N/gvAnEuPR6rAd/kUma1kQoJug=="],