This commit is contained in:
6
src/utils/cn.ts
Executable file
6
src/utils/cn.ts
Executable file
@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export default function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
7
src/utils/generateBreadcrumbsUrl.ts
Normal file
7
src/utils/generateBreadcrumbsUrl.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default function generateBreadcrumbsUrl(docs: any, lastDoc: any) {
|
||||
let prefix = ''
|
||||
// You might want different prefixes for different collections.
|
||||
switch (lastDoc._collection) {
|
||||
}
|
||||
return docs.reduce((url: any, doc: any) => `${url}/${doc.slug ?? ''}`, prefix)
|
||||
}
|
37
src/utils/generateMeta.ts
Normal file
37
src/utils/generateMeta.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import type { Metadata } from 'next'
|
||||
import _get from 'lodash/get'
|
||||
import deepmerge from 'deepmerge'
|
||||
import { getDocument } from '@/utils/getDocument'
|
||||
import normalizePath from './normalizePath'
|
||||
|
||||
const defaultTitle = 'Payload SaaS Starter'
|
||||
const defaultDescription = 'An open-source website built with Payload and Next.js.'
|
||||
const siteName = 'Payload SaaS Starter'
|
||||
|
||||
const defaultOpenGraph: Metadata['openGraph'] = {
|
||||
type: 'website',
|
||||
description: defaultDescription,
|
||||
siteName,
|
||||
title: defaultTitle,
|
||||
}
|
||||
|
||||
export const generateMeta = async (path: string | string[]): Promise<Metadata> => {
|
||||
const doc = await getDocument({
|
||||
collection: 'pages',
|
||||
path,
|
||||
depth: 1,
|
||||
})
|
||||
const metaTitle = _get(doc, 'meta.title', null)
|
||||
const title = metaTitle ?? (_get(doc, 'title', defaultTitle) as string)
|
||||
const description = _get(doc, 'meta.description', defaultDescription)
|
||||
const ogImage = _get(doc, 'meta.image.url', `/api/og${normalizePath(path, true)}image.jpg`)
|
||||
|
||||
return {
|
||||
description,
|
||||
openGraph: deepmerge(defaultOpenGraph, {
|
||||
description,
|
||||
images: ogImage ? [{ url: ogImage }] : undefined,
|
||||
}),
|
||||
title: { absolute: title },
|
||||
}
|
||||
}
|
13
src/utils/generateRandomString.ts
Normal file
13
src/utils/generateRandomString.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export default function generateRandomString(
|
||||
length: number,
|
||||
characters: string = 'abcdefghijklmnopqrstuvwxyz0123456789',
|
||||
): string {
|
||||
let result = ''
|
||||
const charactersLength = characters.length
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
80
src/utils/getDocument.ts
Normal file
80
src/utils/getDocument.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import type { Config } from 'types/payload-types'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
import normalizePath from '@/utils/normalizePath'
|
||||
import { getCurrentUser, getPayload } from '@/lib/payload'
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
type Collection = keyof Config['collections'] | string
|
||||
type Path = string | string[]
|
||||
type CacheOption = 'noDraft' | true | false
|
||||
|
||||
export const generateDocumentCacheKey = (collection: Collection, path?: Path): string => {
|
||||
return `${collection}_path_${normalizePath(path, false)}`
|
||||
}
|
||||
|
||||
export const generateDocumentCacheParams = (collection: Collection, path?: Path): string[] => {
|
||||
return [collection, normalizePath(path)]
|
||||
}
|
||||
|
||||
export const conditionalCache = async <T>(
|
||||
fetchFunction: () => Promise<T>,
|
||||
cacheKey: string,
|
||||
cache: boolean | undefined,
|
||||
revalidate: false | number | undefined,
|
||||
): Promise<T> => {
|
||||
if (!cache) {
|
||||
return fetchFunction()
|
||||
}
|
||||
return unstable_cache(fetchFunction, [cacheKey], { revalidate, tags: [cacheKey] })()
|
||||
}
|
||||
|
||||
interface GetDocumentParams<K extends keyof Config['collections']> {
|
||||
collection: K
|
||||
path?: Path
|
||||
depth?: number
|
||||
cache?: CacheOption
|
||||
revalidate?: false | number | undefined
|
||||
}
|
||||
|
||||
export const getDocument = async <K extends keyof Config['collections']>({
|
||||
collection,
|
||||
path,
|
||||
depth = 0,
|
||||
cache = 'noDraft',
|
||||
revalidate = false,
|
||||
}: GetDocumentParams<K>): Promise<Config['collections'][K] | null> => {
|
||||
const { isEnabled: draft } = draftMode()
|
||||
const payload = await getPayload()
|
||||
const user = draft ? await getCurrentUser() : null
|
||||
|
||||
const normalizedPath = normalizePath(path, false)
|
||||
const where = { path: { equals: normalizedPath } }
|
||||
|
||||
const cacheKey = generateDocumentCacheKey(collection, path)
|
||||
const shouldCache = draft ? false : !!cache
|
||||
return conditionalCache(
|
||||
async () => {
|
||||
return await payload
|
||||
.find({
|
||||
collection,
|
||||
draft,
|
||||
depth,
|
||||
limit: 1,
|
||||
overrideAccess: false,
|
||||
user,
|
||||
where,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching document:', error)
|
||||
return null
|
||||
})
|
||||
.then((result) => {
|
||||
if (!result || !result.docs || result.docs.length === 0) return null
|
||||
return result.docs.at(0) as Config['collections'][K] | null
|
||||
})
|
||||
},
|
||||
cacheKey,
|
||||
shouldCache,
|
||||
revalidate,
|
||||
)
|
||||
}
|
9
src/utils/normalizePath.ts
Normal file
9
src/utils/normalizePath.ts
Normal file
@ -0,0 +1,9 @@
|
||||
const normalizePath = (path?: string | string[], keepTrailingSlash: boolean = false): string => {
|
||||
if (!path) return '/'
|
||||
if (Array.isArray(path)) path = path.join('/')
|
||||
path = `/${path}/`.replace(/\/+/g, '/')
|
||||
path = path !== '/' && !keepTrailingSlash ? path.replace(/\/$/, '') : path
|
||||
return path
|
||||
}
|
||||
|
||||
export default normalizePath
|
Reference in New Issue
Block a user