Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 178 additions & 63 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ packages:
- 'templates/blank'
- 'templates/website'
- 'templates/ecommerce'
- 'templates/with-vercel-mongodb'

updateNotifier: false
2 changes: 1 addition & 1 deletion templates/website/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import redirects from './redirects.js'

const NEXT_PUBLIC_SERVER_URL = process.env.VERCEL_PROJECT_PRODUCTION_URL
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
: undefined || process.env.__NEXT_PRIVATE_ORIGIN || 'http://localhost:3000'
: undefined || process.env.__NEXT_PRIVATE_ORIGIN || 'http://127.0.0.1:3002'

/** @type {import('next').NextConfig} */
const nextConfig = {
Expand Down
2 changes: 1 addition & 1 deletion templates/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"build": "cross-env NODE_OPTIONS=--no-deprecation next build",
"postbuild": "next-sitemap --config next-sitemap.config.cjs",
"dev": "cross-env NODE_OPTIONS=--no-deprecation next dev",
"dev": "cross-env NODE_OPTIONS=--no-deprecation next dev -H 0.0.0.0 -p 3002",
"dev:prod": "cross-env NODE_OPTIONS=--no-deprecation rm -rf .next && pnpm build && pnpm start",
"generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
Expand Down
4 changes: 2 additions & 2 deletions templates/website/src/Footer/hooks/revalidateFooter.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { GlobalAfterChangeHook } from 'payload'

import { revalidateTag } from 'next/cache'
import { revalidateFooterTag } from '@/app/actions/revalidate'

export const revalidateFooter: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {
if (!context.disableRevalidate) {
payload.logger.info(`Revalidating footer`)

revalidateTag('global_footer')
revalidateFooterTag().catch(console.error)
}

return doc
Expand Down
4 changes: 2 additions & 2 deletions templates/website/src/Header/hooks/revalidateHeader.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { GlobalAfterChangeHook } from 'payload'

import { revalidateTag } from 'next/cache'
import { revalidateHeaderTag } from '@/app/actions/revalidate'

export const revalidateHeader: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {
if (!context.disableRevalidate) {
payload.logger.info(`Revalidating header`)

revalidateTag('global_header')
revalidateHeaderTag().catch(console.error)
}

return doc
Expand Down
17 changes: 14 additions & 3 deletions templates/website/src/app/(frontend)/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { RenderHero } from '@/heros/RenderHero'
import { generateMeta } from '@/utilities/generateMeta'
import PageClient from './page.client'
import { LivePreviewListener } from '@/components/LivePreviewListener'
import { SlideModeToggle } from '@/components/SlideModeToggle'

export async function generateStaticParams() {
const payload = await getPayload({ config: configPromise })
Expand Down Expand Up @@ -41,11 +42,20 @@ type Args = {
params: Promise<{
slug?: string
}>
searchParams: Promise<{
mode?: string
}>
}

export default async function Page({ params: paramsPromise }: Args) {
export default async function Page({
params: paramsPromise,
searchParams: searchParamsPromise,
}: Args) {
const { isEnabled: draft } = await draftMode()
const { slug = 'home' } = await paramsPromise
const searchParams = await searchParamsPromise
const slideMode = searchParams?.mode === 'slides'

// Decode to support slugs with special characters
const decodedSlug = decodeURIComponent(slug)
const url = '/' + decodedSlug
Expand All @@ -69,13 +79,14 @@ export default async function Page({ params: paramsPromise }: Args) {
return (
<article className="pt-16 pb-24">
<PageClient />
<SlideModeToggle />
{/* Allows redirects for valid pages too */}
<PayloadRedirects disableNotFound url={url} />

{draft && <LivePreviewListener />}

<RenderHero {...hero} />
<RenderBlocks blocks={layout} />
{!slideMode && <RenderHero {...hero} />}
<RenderBlocks blocks={layout} hero={hero} slideMode={slideMode} />
</article>
)
}
Expand Down
26 changes: 26 additions & 0 deletions templates/website/src/app/actions/revalidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use server'

import { revalidateTag, revalidatePath } from 'next/cache'

export async function revalidateFooterTag() {
revalidateTag('global_footer')
}

export async function revalidateHeaderTag() {
revalidateTag('global_header')
}

export async function revalidatePage(slug: string) {
revalidateTag(`pages_${slug}`)
revalidatePath(`/${slug}`)
}

export async function revalidatePost(slug: string) {
revalidateTag(`posts_${slug}`)
revalidatePath(`/posts/${slug}`)
revalidateTag('posts-sitemap')
}

export async function revalidateRedirects() {
revalidateTag('redirects')
}
88 changes: 65 additions & 23 deletions templates/website/src/blocks/RenderBlocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { CallToActionBlock } from '@/blocks/CallToAction/Component'
import { ContentBlock } from '@/blocks/Content/Component'
import { FormBlock } from '@/blocks/Form/Component'
import { MediaBlock } from '@/blocks/MediaBlock/Component'
import { SlideRenderer } from '@/components/SlideRenderer'
import { RenderHero } from '@/heros/RenderHero'

const blockComponents = {
archive: ArchiveBlock,
Expand All @@ -18,34 +20,74 @@ const blockComponents = {

export const RenderBlocks: React.FC<{
blocks: Page['layout'][0][]
hero?: Page['hero']
slideMode?: boolean
}> = (props) => {
const { blocks } = props
const { blocks, hero, slideMode } = props

const hasBlocks = blocks && Array.isArray(blocks) && blocks.length > 0

if (hasBlocks) {
return (
<Fragment>
{blocks.map((block, index) => {
const { blockType } = block

if (blockType && blockType in blockComponents) {
const Block = blockComponents[blockType]

if (Block) {
return (
<div className="my-16" key={index}>
{/* @ts-expect-error there may be some mismatch between the expected types here */}
<Block {...block} disableInnerContainer />
</div>
)
}
if (!hasBlocks && !hero) {
return null
}

// Render in slide mode if enabled
if (slideMode) {
const slides = blocks
.map((block, index) => {
const { blockType } = block

if (blockType && blockType in blockComponents) {
const Block = blockComponents[blockType]

if (Block) {
return (
// @ts-expect-error there may be some mismatch between the expected types here
<Block key={index} {...block} disableInnerContainer />
)
}
return null
})}
</Fragment>
)
}
return null
})
.filter((slide) => slide !== null) as React.ReactNode[]

// Prepend Hero as the first slide if it exists
if (hero && hero.type !== 'none') {
slides.unshift(
<div
key="hero"
className="relative w-full h-full flex items-center justify-center [&_*]:mt-0"
>
<div className="w-full">
<RenderHero {...hero} />
</div>
</div>,
)
}

return <SlideRenderer slides={slides} />
}

return null
// Normal rendering mode
return (
<Fragment>
{blocks.map((block, index) => {
const { blockType } = block

if (blockType && blockType in blockComponents) {
const Block = blockComponents[blockType]

if (Block) {
return (
<div className="my-16" key={index}>
{/* @ts-expect-error there may be some mismatch between the expected types here */}
<Block {...block} disableInnerContainer />
</div>
)
}
}
return null
})}
</Fragment>
)
}
21 changes: 9 additions & 12 deletions templates/website/src/collections/Pages/hooks/revalidatePage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook } from 'payload'

import { revalidatePath, revalidateTag } from 'next/cache'
import { revalidatePage as revalidatePageAction } from '@/app/actions/revalidate'

import type { Page } from '../../../payload-types'

Expand All @@ -11,32 +11,29 @@ export const revalidatePage: CollectionAfterChangeHook<Page> = ({
}) => {
if (!context.disableRevalidate) {
if (doc._status === 'published') {
const path = doc.slug === 'home' ? '/' : `/${doc.slug}`
const path = doc.slug === 'home' ? '' : doc.slug

payload.logger.info(`Revalidating page at path: ${path}`)
payload.logger.info(`Revalidating page at path: /${path}`)

revalidatePath(path)
revalidateTag('pages-sitemap')
revalidatePageAction(path).catch(console.error)
}

// If the page was previously published, we need to revalidate the old path
if (previousDoc?._status === 'published' && doc._status !== 'published') {
const oldPath = previousDoc.slug === 'home' ? '/' : `/${previousDoc.slug}`
const oldPath = previousDoc.slug === 'home' ? '' : previousDoc.slug

payload.logger.info(`Revalidating old page at path: ${oldPath}`)
payload.logger.info(`Revalidating old page at path: /${oldPath}`)

revalidatePath(oldPath)
revalidateTag('pages-sitemap')
revalidatePageAction(oldPath).catch(console.error)
}
}
return doc
}

export const revalidateDelete: CollectionAfterDeleteHook<Page> = ({ doc, req: { context } }) => {
if (!context.disableRevalidate) {
const path = doc?.slug === 'home' ? '/' : `/${doc?.slug}`
revalidatePath(path)
revalidateTag('pages-sitemap')
const path = doc?.slug === 'home' ? '' : doc?.slug || ''
revalidatePageAction(path).catch(console.error)
}

return doc
Expand Down
23 changes: 6 additions & 17 deletions templates/website/src/collections/Posts/hooks/revalidatePost.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook } from 'payload'

import { revalidatePath, revalidateTag } from 'next/cache'
import { revalidatePost as revalidatePostAction } from '@/app/actions/revalidate'

import type { Post } from '../../../payload-types'

Expand All @@ -11,33 +11,22 @@ export const revalidatePost: CollectionAfterChangeHook<Post> = ({
}) => {
if (!context.disableRevalidate) {
if (doc._status === 'published') {
const path = `/posts/${doc.slug}`

payload.logger.info(`Revalidating post at path: ${path}`)

revalidatePath(path)
revalidateTag('posts-sitemap')
payload.logger.info(`Revalidating post at path: /posts/${doc.slug}`)
revalidatePostAction(doc.slug).catch(console.error)
}

// If the post was previously published, we need to revalidate the old path
if (previousDoc._status === 'published' && doc._status !== 'published') {
const oldPath = `/posts/${previousDoc.slug}`

payload.logger.info(`Revalidating old post at path: ${oldPath}`)

revalidatePath(oldPath)
revalidateTag('posts-sitemap')
payload.logger.info(`Revalidating old post at path: /posts/${previousDoc.slug}`)
revalidatePostAction(previousDoc.slug).catch(console.error)
}
}
return doc
}

export const revalidateDelete: CollectionAfterDeleteHook<Post> = ({ doc, req: { context } }) => {
if (!context.disableRevalidate) {
const path = `/posts/${doc?.slug}`

revalidatePath(path)
revalidateTag('posts-sitemap')
revalidatePostAction(doc?.slug || '').catch(console.error)
}

return doc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const ImageMedia: React.FC<MediaProps> = (props) => {
sizes={sizes}
src={src}
width={!fill ? width : undefined}
unoptimized
/>
</picture>
)
Expand Down
48 changes: 48 additions & 0 deletions templates/website/src/components/SlideModeToggle/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use client'

import React from 'react'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
import { Presentation, LayoutGrid } from 'lucide-react'
import { Button } from '@/components/ui/button'

export const SlideModeToggle: React.FC = () => {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const isSlideMode = searchParams.get('mode') === 'slides'

const toggleSlideMode = () => {
const params = new URLSearchParams(searchParams.toString())

if (isSlideMode) {
params.delete('mode')
} else {
params.set('mode', 'slides')
}

const newUrl = params.toString() ? `${pathname}?${params.toString()}` : pathname
router.push(newUrl)
}

return (
<Button
variant="outline"
size="sm"
onClick={toggleSlideMode}
className="fixed top-20 right-4 z-40 bg-background/80 backdrop-blur-sm"
aria-label={isSlideMode ? 'Exit slide mode' : 'Enter slide mode'}
>
{isSlideMode ? (
<>
<LayoutGrid className="h-4 w-4 mr-2" />
Normal View
</>
) : (
<>
<Presentation className="h-4 w-4 mr-2" />
Slide View
</>
)}
</Button>
)
}
Loading
Loading