tr]:last:border-b-0', className)}
38 | {...props}
39 | />
40 | )
41 | }
42 |
43 | function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
44 | return (
45 |
53 | )
54 | }
55 |
56 | function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
57 | return (
58 | [role=checkbox]]:translate-y-[2px]',
62 | className
63 | )}
64 | {...props}
65 | />
66 | )
67 | }
68 |
69 | function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
70 | return (
71 | | [role=checkbox]]:translate-y-[2px]',
75 | className
76 | )}
77 | {...props}
78 | />
79 | )
80 | }
81 |
82 | function TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {
83 | return (
84 |
89 | )
90 | }
91 |
92 | export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
93 |
--------------------------------------------------------------------------------
/src/lib/payload/generate-preview-path.ts:
--------------------------------------------------------------------------------
1 | import type { CollectionSlug, PayloadRequest } from 'payload'
2 |
3 | /**
4 | * Maps collection slugs to their corresponding URL prefixes.
5 | * This determines the URL structure for different content types.
6 | */
7 | const collectionPrefixMap: Partial> = {}
8 |
9 | type Props = {
10 | collection: keyof typeof collectionPrefixMap
11 | slug: string
12 | req: PayloadRequest
13 | }
14 |
15 | /**
16 | * Generates a preview URL for Payload CMS content.
17 | * This allows editors to preview unpublished or draft content.
18 | *
19 | * @param {Props} options - Options for generating the preview path
20 | * @param {keyof typeof collectionPrefixMap} options.collection - The collection the document belongs to
21 | * @param {string} options.slug - The slug of the document
22 | * @param {PayloadRequest} options.req - The Payload request object
23 | * @returns {string} The complete preview URL
24 | *
25 | * ## When to use:
26 | *
27 | * 1. **CMS Preview Functionality**:
28 | * - When implementing preview functionality in Payload CMS
29 | * - For "View Preview" buttons in the admin panel
30 | *
31 | * 2. **Draft Content**:
32 | * - For previewing unpublished or draft content
33 | * - When content needs review before publishing
34 | *
35 | * 3. **Admin Hooks**:
36 | * - In Payload hooks for collections with preview support
37 | * - For custom admin components that need preview links
38 | *
39 | * 4. **Content Workflow**:
40 | * - As part of content approval workflows
41 | * - For editorial review processes
42 | *
43 | * ## Example usage:
44 | *
45 | * ```typescript
46 | * // In a Payload collection config
47 | * export const Posts = {
48 | * slug: 'posts',
49 | * admin: {
50 | * preview: (doc, { req }) => {
51 | * if (!doc?.slug) return '';
52 | *
53 | * return generatePreviewPath({
54 | * collection: 'posts',
55 | * slug: doc.slug,
56 | * req
57 | * });
58 | * }
59 | * },
60 | * // rest of collection config
61 | * };
62 | * ```
63 | */
64 | export const generatePreviewPath = ({ collection, slug, req }: Props) => {
65 | const path = `${collectionPrefixMap[collection]}/${slug}`
66 |
67 | const params = {
68 | slug,
69 | collection,
70 | path,
71 | }
72 |
73 | const encodedParams = new URLSearchParams()
74 |
75 | Object.entries(params).forEach(([key, value]) => {
76 | encodedParams.append(key, value)
77 | })
78 |
79 | const isProduction =
80 | process.env.NODE_ENV === 'production' || Boolean(process.env.VERCEL_PROJECT_PRODUCTION_URL)
81 | const protocol = isProduction ? 'https:' : req.protocol
82 |
83 | const url = `${protocol}//${req.host}/next/preview?${encodedParams.toString()}`
84 | console.log('🔥PREVIEW URL', url)
85 |
86 | return url
87 | }
88 |
--------------------------------------------------------------------------------
/src/lib/payload/get-cached-document.ts:
--------------------------------------------------------------------------------
1 | import { unstable_cache } from 'next/cache'
2 |
3 | import configPromise from '@payload-config'
4 | import { getPayload } from 'payload'
5 | import type { Config } from '@/payload-types'
6 |
7 | type Collection = keyof Config['collections']
8 |
9 | /**
10 | * Fetches a document from a Payload CMS collection by slug.
11 | *
12 | * @param {Collection} collection - The collection to fetch from
13 | * @param {string} slug - The slug of the document to fetch
14 | * @param {number} depth - The depth of relationships to populate
15 | * @returns {Promise} The document or undefined if not found
16 | */
17 | export async function getDocument(
18 | collection: T,
19 | slug: string,
20 | depth = 0,
21 | draft = false
22 | ) {
23 | const payload = await getPayload({ config: configPromise })
24 |
25 | const page = await payload.find({
26 | collection,
27 | depth,
28 | draft,
29 | where: {
30 | slug: {
31 | equals: slug,
32 | },
33 | },
34 | })
35 |
36 | return page.docs[0]
37 | }
38 |
39 | /**
40 | * Returns a cached version of the document fetch function.
41 | * Uses Next.js unstable_cache for server-side caching with appropriate cache tags.
42 | *
43 | * @param {Collection} collection - The collection to fetch from
44 | * @param {string} slug - The slug of the document to fetch
45 | * @returns {Promise} The cached document
46 | *
47 | * ## When to use:
48 | *
49 | * 1. **Page Data**:
50 | * - For fetching content for specific pages by slug
51 | * - In generateMetadata functions to get SEO data
52 | *
53 | * 2. **Dynamic Routes**:
54 | * - In [slug] route handlers to fetch the corresponding content
55 | * - For blog posts, product pages, or other content-driven pages
56 | *
57 | * 3. **Performance Optimization**:
58 | * - To cache frequently accessed documents
59 | * - When the same document is needed in multiple components
60 | *
61 | * 4. **Server Components**:
62 | * - For data fetching in Server Components
63 | * - When rendering content from the CMS
64 | *
65 | * ## Example usage:
66 | *
67 | * ```tsx
68 | * // In a dynamic [slug] page.tsx
69 | * import { getCachedDocument } from '@/lib/utils/getDocument';
70 | *
71 | * export default async function Page({ params }: { params: { slug: string } }) {
72 | * const post = await getCachedDocument('posts', params.slug);
73 | *
74 | * if (!post) {
75 | * notFound();
76 | * }
77 | *
78 | * return (
79 | *
80 | * {post.title}
81 | *
82 | *
83 | * );
84 | * }
85 | * ```
86 | */
87 | export const getCachedDocument = (collection: T, slug: string) =>
88 | unstable_cache(async () => getDocument(collection, slug), [collection, slug], {
89 | tags: [`${collection}_${slug}`],
90 | })
91 |
--------------------------------------------------------------------------------
/src/components/payload/rich-text/index.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils'
2 |
3 | import type {
4 | DefaultNodeTypes,
5 | DefaultTypedEditorState,
6 | SerializedBlockNode,
7 | SerializedInlineBlockNode,
8 | SerializedLinkNode,
9 | } from '@payloadcms/richtext-lexical'
10 | import {
11 | BlockquoteJSXConverter,
12 | RichText as ConvertRichText,
13 | defaultJSXConverters,
14 | type JSXConvertersFunction,
15 | LinkJSXConverter,
16 | } from '@payloadcms/richtext-lexical/react'
17 | import { CopyRightInlineBlock } from '@/blocks/copyright-inline-block/component'
18 | import { GalleryBlock } from '@/blocks/gallery-block/component'
19 | import { MediaBlock } from '@/blocks/media-block/component'
20 | import type {
21 | CopyRightInlineBlock as CopyRightInlineBlockProps,
22 | GalleryBlock as GalleryBlockProps,
23 | MediaBlock as MediaBlockProps,
24 | } from '@/payload-types'
25 |
26 | type NodeTypes =
27 | | DefaultNodeTypes
28 | | SerializedBlockNode
29 | | SerializedInlineBlockNode
30 |
31 | const internalDocToHref = ({ linkNode }: { linkNode: SerializedLinkNode }) => {
32 | const { value, relationTo } = linkNode.fields.doc!
33 | if (typeof value !== 'object') {
34 | throw new Error('Expected value to be an object')
35 | }
36 | const slug = value.slug
37 | return relationTo === 'blog' ? `/blog/${slug}` : `/${slug}`
38 | }
39 |
40 | const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
41 | ...defaultConverters,
42 | ...defaultJSXConverters,
43 | ...LinkJSXConverter({ internalDocToHref }),
44 | ...BlockquoteJSXConverter,
45 | blocks: {
46 | mediaBlock: ({ node }) => (
47 |
56 | ),
57 | galleryBlock: ({ node }) => ,
58 | },
59 | inlineBlocks: {
60 | copyRightInlineBlock: ({ node }) => ,
61 | },
62 | })
63 |
64 | type Props = {
65 | data: DefaultTypedEditorState
66 | enableGutter?: boolean
67 | enableProse?: boolean
68 | } & React.HTMLAttributes
69 |
70 | export default function RichText(props: Props) {
71 | const { className, enableProse = true, enableGutter = true, ...rest } = props
72 | return (
73 |
86 | )
87 | }
88 |
--------------------------------------------------------------------------------
|