kofi-stack-template-generator 2.1.27 → 2.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +336 -9
- package/package.json +8 -8
- package/src/templates.generated.ts +70 -11
- package/templates/marketing/payload/src/Footer/config.ts.hbs +178 -0
- package/templates/marketing/payload/src/Footer/hooks/revalidateFooter.ts.hbs +13 -0
- package/templates/marketing/payload/src/Footer/index.ts.hbs +1 -0
- package/templates/marketing/payload/src/Header/RowLabel.tsx.hbs +21 -0
- package/templates/marketing/payload/src/Header/config.ts.hbs +208 -0
- package/templates/marketing/payload/src/Header/hooks/revalidateHeader.ts.hbs +13 -0
- package/templates/marketing/payload/src/Header/index.ts.hbs +1 -0
- package/templates/marketing/payload/src/access/anyone.ts.hbs +3 -0
- package/templates/marketing/payload/src/access/authenticated.ts.hbs +9 -0
- package/templates/marketing/payload/src/access/authenticatedOrPublished.ts.hbs +13 -0
- package/templates/marketing/payload/src/access/index.ts.hbs +3 -0
- package/templates/marketing/payload/src/app/(frontend)/next/seed/route.ts.hbs +31 -0
- package/templates/marketing/payload/src/collections/Categories/index.ts.hbs +28 -0
- package/templates/marketing/payload/src/collections/FAQs/index.ts.hbs +100 -0
- package/templates/marketing/payload/src/collections/Media.ts.hbs +148 -28
- package/templates/marketing/payload/src/collections/Pages/hooks/revalidatePage.ts.hbs +43 -0
- package/templates/marketing/payload/src/collections/Pages/index.ts.hbs +142 -0
- package/templates/marketing/payload/src/collections/Posts/hooks/populateAuthors.ts.hbs +41 -0
- package/templates/marketing/payload/src/collections/Posts/hooks/revalidatePost.ts.hbs +44 -0
- package/templates/marketing/payload/src/collections/Posts/index.ts.hbs +244 -0
- package/templates/marketing/payload/src/collections/Users/index.ts.hbs +26 -0
- package/templates/marketing/payload/src/collections/index.ts.hbs +6 -4
- package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/index.scss.hbs +12 -0
- package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/index.tsx.hbs +89 -0
- package/templates/marketing/payload/src/components/BeforeDashboard/index.scss.hbs +24 -0
- package/templates/marketing/payload/src/components/BeforeDashboard/index.tsx.hbs +69 -0
- package/templates/marketing/payload/src/components/BeforeLogin/index.tsx.hbs +14 -0
- package/templates/marketing/payload/src/components/Link/index.tsx.hbs +79 -0
- package/templates/marketing/payload/src/components/Media/index.tsx.hbs +67 -0
- package/templates/marketing/payload/src/components/RichText/index.tsx.hbs +44 -0
- package/templates/marketing/payload/src/endpoints/seed/home.ts.hbs +76 -0
- package/templates/marketing/payload/src/endpoints/seed/image-1.ts.hbs +5 -0
- package/templates/marketing/payload/src/endpoints/seed/image-2.ts.hbs +5 -0
- package/templates/marketing/payload/src/endpoints/seed/image-hero.ts.hbs +5 -0
- package/templates/marketing/payload/src/endpoints/seed/index.ts.hbs +235 -0
- package/templates/marketing/payload/src/endpoints/seed/post-1.ts.hbs +252 -0
- package/templates/marketing/payload/src/fields/defaultLexical.ts.hbs +73 -0
- package/templates/marketing/payload/src/fields/index.ts.hbs +3 -0
- package/templates/marketing/payload/src/fields/link.ts.hbs +139 -0
- package/templates/marketing/payload/src/fields/linkGroup.ts.hbs +28 -0
- package/templates/marketing/payload/src/globals/index.ts.hbs +2 -2
- package/templates/marketing/payload/src/heros/HighImpact/index.tsx.hbs +53 -0
- package/templates/marketing/payload/src/heros/LowImpact/index.tsx.hbs +48 -0
- package/templates/marketing/payload/src/heros/MediumImpact/index.tsx.hbs +46 -0
- package/templates/marketing/payload/src/heros/PostHero/index.tsx.hbs +68 -0
- package/templates/marketing/payload/src/heros/ProductShowcase/index.tsx.hbs +88 -0
- package/templates/marketing/payload/src/heros/RenderHero.tsx.hbs +27 -0
- package/templates/marketing/payload/src/heros/config.ts.hbs +112 -0
- package/templates/marketing/payload/src/heros/index.ts.hbs +7 -0
- package/templates/marketing/payload/src/hooks/index.ts.hbs +2 -0
- package/templates/marketing/payload/src/hooks/populatePublishedAt.ts.hbs +15 -0
- package/templates/marketing/payload/src/hooks/revalidateRedirects.ts.hbs +11 -0
- package/templates/marketing/payload/src/payload.config.ts.hbs +32 -8
- package/templates/marketing/payload/src/providers/HeaderTheme/index.tsx.hbs +34 -0
- package/templates/marketing/payload/src/providers/Theme/InitTheme/index.tsx.hbs +44 -0
- package/templates/marketing/payload/src/providers/Theme/index.tsx.hbs +60 -0
- package/templates/marketing/payload/src/providers/Theme/shared.ts.hbs +17 -0
- package/templates/marketing/payload/src/providers/Theme/types.ts.hbs +10 -0
- package/templates/marketing/payload/src/providers/index.tsx.hbs +18 -0
- package/templates/marketing/payload/src/utilities/canUseDOM.ts.hbs +1 -0
- package/templates/marketing/payload/src/utilities/deepMerge.ts.hbs +35 -0
- package/templates/marketing/payload/src/utilities/formatAuthors.ts.hbs +24 -0
- package/templates/marketing/payload/src/utilities/formatDateTime.ts.hbs +13 -0
- package/templates/marketing/payload/src/utilities/generateMeta.ts.hbs +87 -0
- package/templates/marketing/payload/src/utilities/generatePreviewPath.ts.hbs +33 -0
- package/templates/marketing/payload/src/utilities/getURL.ts.hbs +26 -0
- package/templates/marketing/payload/src/utilities/index.ts.hbs +8 -0
- package/templates/marketing/payload/src/utilities/mergeOpenGraph.ts.hbs +26 -0
- package/templates/marketing/payload/src/collections/Pages.ts.hbs +0 -66
- package/templates/marketing/payload/src/collections/Posts.ts.hbs +0 -65
- package/templates/marketing/payload/src/collections/Users.ts.hbs +0 -25
- package/templates/marketing/payload/src/globals/Navigation.ts.hbs +0 -51
- package/templates/marketing/payload/src/globals/SiteSettings.ts.hbs +0 -49
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import type React from "react"
|
|
4
|
+
import { createContext, use, useCallback, useEffect, useState } from "react"
|
|
5
|
+
|
|
6
|
+
import type { Theme, ThemeContextType } from "./types"
|
|
7
|
+
|
|
8
|
+
import canUseDOM from "@/utilities/canUseDOM"
|
|
9
|
+
import { defaultTheme, getImplicitPreference, themeLocalStorageKey } from "./shared"
|
|
10
|
+
import { themeIsValid } from "./types"
|
|
11
|
+
|
|
12
|
+
const initialContext: ThemeContextType = {
|
|
13
|
+
setTheme: () => null,
|
|
14
|
+
theme: undefined,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ThemeContext = createContext(initialContext)
|
|
18
|
+
|
|
19
|
+
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
|
|
20
|
+
const [theme, setThemeState] = useState<Theme | undefined>(
|
|
21
|
+
canUseDOM ? (document.documentElement.getAttribute("data-theme") as Theme) : undefined,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const setTheme = useCallback((themeToSet: Theme | null) => {
|
|
25
|
+
if (themeToSet === null) {
|
|
26
|
+
// 'auto' mode - use system preference
|
|
27
|
+
const implicitPreference = getImplicitPreference()
|
|
28
|
+
const resolvedTheme = implicitPreference || defaultTheme
|
|
29
|
+
document.documentElement.setAttribute("data-theme", resolvedTheme)
|
|
30
|
+
setThemeState(resolvedTheme)
|
|
31
|
+
} else {
|
|
32
|
+
setThemeState(themeToSet)
|
|
33
|
+
document.documentElement.setAttribute("data-theme", themeToSet)
|
|
34
|
+
}
|
|
35
|
+
}, [])
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
let themeToSet: Theme = defaultTheme
|
|
39
|
+
const preference = window.localStorage.getItem(themeLocalStorageKey)
|
|
40
|
+
|
|
41
|
+
if (themeIsValid(preference)) {
|
|
42
|
+
// User explicitly chose light or dark
|
|
43
|
+
themeToSet = preference
|
|
44
|
+
} else if (preference === "auto") {
|
|
45
|
+
// User explicitly chose system preference
|
|
46
|
+
const implicitPreference = getImplicitPreference()
|
|
47
|
+
if (implicitPreference) {
|
|
48
|
+
themeToSet = implicitPreference
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// If no preference, use defaultTheme (light)
|
|
52
|
+
|
|
53
|
+
document.documentElement.setAttribute("data-theme", themeToSet)
|
|
54
|
+
setThemeState(themeToSet)
|
|
55
|
+
}, [])
|
|
56
|
+
|
|
57
|
+
return <ThemeContext value=\{{ setTheme, theme }}>{children}</ThemeContext>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const useTheme = (): ThemeContextType => use(ThemeContext)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Theme } from "./types"
|
|
2
|
+
|
|
3
|
+
export const themeLocalStorageKey = "payload-theme"
|
|
4
|
+
|
|
5
|
+
export const defaultTheme = "light"
|
|
6
|
+
|
|
7
|
+
export const getImplicitPreference = (): Theme | null => {
|
|
8
|
+
const mediaQuery = "(prefers-color-scheme: dark)"
|
|
9
|
+
const mql = window.matchMedia(mediaQuery)
|
|
10
|
+
const hasImplicitPreference = typeof mql.matches === "boolean"
|
|
11
|
+
|
|
12
|
+
if (hasImplicitPreference) {
|
|
13
|
+
return mql.matches ? "dark" : "light"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Theme = "dark" | "light"
|
|
2
|
+
|
|
3
|
+
export interface ThemeContextType {
|
|
4
|
+
setTheme: (theme: Theme | null) => void
|
|
5
|
+
theme?: Theme | null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function themeIsValid(string: null | string): string is Theme {
|
|
9
|
+
return string ? ["dark", "light"].includes(string) : false
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import type React from "react"
|
|
4
|
+
|
|
5
|
+
import { HeaderThemeProvider } from "./HeaderTheme"
|
|
6
|
+
import { ThemeProvider } from "./Theme"
|
|
7
|
+
|
|
8
|
+
export const Providers: React.FC<{
|
|
9
|
+
children: React.ReactNode
|
|
10
|
+
}> = ({ children }) => {
|
|
11
|
+
return (
|
|
12
|
+
<ThemeProvider>
|
|
13
|
+
<HeaderThemeProvider>
|
|
14
|
+
{children}
|
|
15
|
+
</HeaderThemeProvider>
|
|
16
|
+
</ThemeProvider>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default !!(typeof window !== "undefined" && window.document && window.document.createElement)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Simple object check.
|
|
6
|
+
* @param item
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function isObject(item: unknown): item is object {
|
|
10
|
+
return typeof item === "object" && !Array.isArray(item)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Deep merge two objects.
|
|
15
|
+
* @param target
|
|
16
|
+
* @param ...sources
|
|
17
|
+
*/
|
|
18
|
+
export default function deepMerge<T, R>(target: T, source: R): T {
|
|
19
|
+
const output = { ...target }
|
|
20
|
+
if (isObject(target) && isObject(source)) {
|
|
21
|
+
Object.keys(source).forEach((key) => {
|
|
22
|
+
if (isObject(source[key])) {
|
|
23
|
+
if (!(key in target)) {
|
|
24
|
+
Object.assign(output, { [key]: source[key] })
|
|
25
|
+
} else {
|
|
26
|
+
output[key] = deepMerge(target[key], source[key])
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
Object.assign(output, { [key]: source[key] })
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return output
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Post } from "@/payload-types"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Formats an array of populatedAuthors from Posts into a prettified string.
|
|
5
|
+
* @param authors - The populatedAuthors array from a Post.
|
|
6
|
+
* @returns A prettified string of authors.
|
|
7
|
+
* @example
|
|
8
|
+
*
|
|
9
|
+
* [Author1, Author2] becomes 'Author1 and Author2'
|
|
10
|
+
* [Author1, Author2, Author3] becomes 'Author1, Author2, and Author3'
|
|
11
|
+
*
|
|
12
|
+
*/
|
|
13
|
+
export const formatAuthors = (
|
|
14
|
+
authors: NonNullable<NonNullable<Post["populatedAuthors"]>[number]>[],
|
|
15
|
+
) => {
|
|
16
|
+
// Ensure we don't have any authors without a name
|
|
17
|
+
const authorNames = authors.map((author) => author.name).filter(Boolean)
|
|
18
|
+
|
|
19
|
+
if (authorNames.length === 0) return ""
|
|
20
|
+
if (authorNames.length === 1) return authorNames[0]
|
|
21
|
+
if (authorNames.length === 2) return `${authorNames[0]} and ${authorNames[1]}`
|
|
22
|
+
|
|
23
|
+
return `${authorNames.slice(0, -1).join(", ")} and ${authorNames[authorNames.length - 1]}`
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const formatDateTime = (timestamp: string): string => {
|
|
2
|
+
const now = new Date()
|
|
3
|
+
let date = now
|
|
4
|
+
if (timestamp) date = new Date(timestamp)
|
|
5
|
+
const months = date.getMonth()
|
|
6
|
+
const days = date.getDate()
|
|
7
|
+
|
|
8
|
+
const MM = months + 1 < 10 ? `0${months + 1}` : months + 1
|
|
9
|
+
const DD = days < 10 ? `0${days}` : days
|
|
10
|
+
const YYYY = date.getFullYear()
|
|
11
|
+
|
|
12
|
+
return `${MM}/${DD}/${YYYY}`
|
|
13
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Metadata } from "next"
|
|
2
|
+
|
|
3
|
+
import type { Config, Media, Page, Post } from "../payload-types"
|
|
4
|
+
|
|
5
|
+
import { getServerSideURL } from "./getURL"
|
|
6
|
+
import { mergeOpenGraph } from "./mergeOpenGraph"
|
|
7
|
+
|
|
8
|
+
const getImageURL = (image?: Media | Config["db"]["defaultIDType"] | null) => {
|
|
9
|
+
const serverUrl = getServerSideURL()
|
|
10
|
+
|
|
11
|
+
let url = `${serverUrl}/og-image.png`
|
|
12
|
+
|
|
13
|
+
if (image && typeof image === "object" && "url" in image) {
|
|
14
|
+
const ogUrl = image.sizes?.og?.url
|
|
15
|
+
|
|
16
|
+
url = ogUrl ? `${serverUrl}${ogUrl}` : `${serverUrl}${image.url}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return url
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Default keywords for SEO
|
|
23
|
+
const defaultKeywords = [
|
|
24
|
+
"{{projectName}}",
|
|
25
|
+
"website",
|
|
26
|
+
"Next.js",
|
|
27
|
+
"Payload CMS",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export const generateMeta = async (args: {
|
|
31
|
+
doc: Partial<Page> | Partial<Post> | null
|
|
32
|
+
}): Promise<Metadata> => {
|
|
33
|
+
const { doc } = args
|
|
34
|
+
const serverUrl = getServerSideURL()
|
|
35
|
+
|
|
36
|
+
const ogImage = getImageURL(doc?.meta?.image)
|
|
37
|
+
|
|
38
|
+
const title = doc?.meta?.title
|
|
39
|
+
? `${doc?.meta?.title} | {{projectName}}`
|
|
40
|
+
: "{{projectName}}"
|
|
41
|
+
|
|
42
|
+
const description =
|
|
43
|
+
doc?.meta?.description ||
|
|
44
|
+
"Welcome to {{projectName}}. Built with Payload CMS and Next.js."
|
|
45
|
+
|
|
46
|
+
// Generate canonical URL
|
|
47
|
+
const slug = Array.isArray(doc?.slug) ? doc?.slug.join("/") : doc?.slug || ""
|
|
48
|
+
const canonicalUrl = slug === "home" ? serverUrl : `${serverUrl}/${slug}`
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
title,
|
|
52
|
+
description,
|
|
53
|
+
keywords: defaultKeywords,
|
|
54
|
+
authors: [{ name: "{{projectName}}", url: serverUrl }],
|
|
55
|
+
creator: "{{projectName}}",
|
|
56
|
+
publisher: "{{projectName}}",
|
|
57
|
+
robots: {
|
|
58
|
+
index: true,
|
|
59
|
+
follow: true,
|
|
60
|
+
googleBot: {
|
|
61
|
+
index: true,
|
|
62
|
+
follow: true,
|
|
63
|
+
"max-video-preview": -1,
|
|
64
|
+
"max-image-preview": "large",
|
|
65
|
+
"max-snippet": -1,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
alternates: {
|
|
69
|
+
canonical: canonicalUrl,
|
|
70
|
+
},
|
|
71
|
+
openGraph: mergeOpenGraph({
|
|
72
|
+
description,
|
|
73
|
+
images: ogImage
|
|
74
|
+
? [
|
|
75
|
+
{
|
|
76
|
+
url: ogImage,
|
|
77
|
+
width: 1200,
|
|
78
|
+
height: 630,
|
|
79
|
+
alt: title,
|
|
80
|
+
},
|
|
81
|
+
]
|
|
82
|
+
: undefined,
|
|
83
|
+
title,
|
|
84
|
+
url: canonicalUrl,
|
|
85
|
+
}),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CollectionSlug, PayloadRequest } from "payload"
|
|
2
|
+
|
|
3
|
+
const collectionPrefixMap: Partial<Record<CollectionSlug, string>> = {
|
|
4
|
+
posts: "/posts",
|
|
5
|
+
pages: "",
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
collection: keyof typeof collectionPrefixMap
|
|
10
|
+
slug: string
|
|
11
|
+
req: PayloadRequest
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const generatePreviewPath = ({ collection, slug }: Props) => {
|
|
15
|
+
// Allow empty strings, e.g. for the homepage
|
|
16
|
+
if (slug === undefined || slug === null) {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Encode to support slugs with special characters
|
|
21
|
+
const encodedSlug = encodeURIComponent(slug)
|
|
22
|
+
|
|
23
|
+
const encodedParams = new URLSearchParams({
|
|
24
|
+
slug: encodedSlug,
|
|
25
|
+
collection,
|
|
26
|
+
path: `${collectionPrefixMap[collection]}/${encodedSlug}`,
|
|
27
|
+
previewSecret: process.env.PREVIEW_SECRET || "",
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const url = `/next/preview?${encodedParams.toString()}`
|
|
31
|
+
|
|
32
|
+
return url
|
|
33
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import canUseDOM from "./canUseDOM"
|
|
2
|
+
|
|
3
|
+
export const getServerSideURL = () => {
|
|
4
|
+
return (
|
|
5
|
+
process.env.NEXT_PUBLIC_SERVER_URL ||
|
|
6
|
+
(process.env.VERCEL_PROJECT_PRODUCTION_URL
|
|
7
|
+
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
|
|
8
|
+
: "http://localhost:3000")
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const getClientSideURL = () => {
|
|
13
|
+
if (canUseDOM) {
|
|
14
|
+
const protocol = window.location.protocol
|
|
15
|
+
const domain = window.location.hostname
|
|
16
|
+
const port = window.location.port
|
|
17
|
+
|
|
18
|
+
return `${protocol}//${domain}${port ? `:${port}` : ""}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (process.env.VERCEL_PROJECT_PRODUCTION_URL) {
|
|
22
|
+
return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return process.env.NEXT_PUBLIC_SERVER_URL || ""
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as deepMerge, isObject } from "./deepMerge"
|
|
2
|
+
export { default as canUseDOM } from "./canUseDOM"
|
|
3
|
+
export { getServerSideURL, getClientSideURL } from "./getURL"
|
|
4
|
+
export { generatePreviewPath } from "./generatePreviewPath"
|
|
5
|
+
export { mergeOpenGraph } from "./mergeOpenGraph"
|
|
6
|
+
export { generateMeta } from "./generateMeta"
|
|
7
|
+
export { formatAuthors } from "./formatAuthors"
|
|
8
|
+
export { formatDateTime } from "./formatDateTime"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Metadata } from "next"
|
|
2
|
+
import { getServerSideURL } from "./getURL"
|
|
3
|
+
|
|
4
|
+
const defaultOpenGraph: Metadata["openGraph"] = {
|
|
5
|
+
type: "website",
|
|
6
|
+
description:
|
|
7
|
+
"Welcome to {{projectName}}. Built with Payload CMS and Next.js.",
|
|
8
|
+
images: [
|
|
9
|
+
{
|
|
10
|
+
url: `${getServerSideURL()}/og-image.png`,
|
|
11
|
+
width: 1200,
|
|
12
|
+
height: 630,
|
|
13
|
+
alt: "{{projectName}}",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
siteName: "{{projectName}}",
|
|
17
|
+
title: "{{projectName}}",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const mergeOpenGraph = (og?: Metadata["openGraph"]): Metadata["openGraph"] => {
|
|
21
|
+
return {
|
|
22
|
+
...defaultOpenGraph,
|
|
23
|
+
...og,
|
|
24
|
+
images: og?.images ? og.images : defaultOpenGraph.images,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { CollectionConfig } from 'payload'
|
|
2
|
-
import {
|
|
3
|
-
HeroBlock,
|
|
4
|
-
LogoBannerBlock,
|
|
5
|
-
FeaturesBlock,
|
|
6
|
-
BenefitsBlock,
|
|
7
|
-
PricingBlock,
|
|
8
|
-
TestimonialsBlock,
|
|
9
|
-
FAQBlock,
|
|
10
|
-
ContentBlock,
|
|
11
|
-
CTABlock,
|
|
12
|
-
} from '../blocks'
|
|
13
|
-
|
|
14
|
-
export const Pages: CollectionConfig = {
|
|
15
|
-
slug: 'pages',
|
|
16
|
-
admin: {
|
|
17
|
-
useAsTitle: 'title',
|
|
18
|
-
defaultColumns: ['title', 'slug', 'status', 'updatedAt'],
|
|
19
|
-
},
|
|
20
|
-
access: {
|
|
21
|
-
read: () => true,
|
|
22
|
-
},
|
|
23
|
-
fields: [
|
|
24
|
-
{
|
|
25
|
-
name: 'title',
|
|
26
|
-
type: 'text',
|
|
27
|
-
required: true,
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
name: 'slug',
|
|
31
|
-
type: 'text',
|
|
32
|
-
required: true,
|
|
33
|
-
unique: true,
|
|
34
|
-
admin: {
|
|
35
|
-
position: 'sidebar',
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: 'status',
|
|
40
|
-
type: 'select',
|
|
41
|
-
options: [
|
|
42
|
-
{ label: 'Draft', value: 'draft' },
|
|
43
|
-
{ label: 'Published', value: 'published' },
|
|
44
|
-
],
|
|
45
|
-
defaultValue: 'draft',
|
|
46
|
-
admin: {
|
|
47
|
-
position: 'sidebar',
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: 'layout',
|
|
52
|
-
type: 'blocks',
|
|
53
|
-
blocks: [
|
|
54
|
-
HeroBlock,
|
|
55
|
-
LogoBannerBlock,
|
|
56
|
-
FeaturesBlock,
|
|
57
|
-
BenefitsBlock,
|
|
58
|
-
PricingBlock,
|
|
59
|
-
TestimonialsBlock,
|
|
60
|
-
FAQBlock,
|
|
61
|
-
ContentBlock,
|
|
62
|
-
CTABlock,
|
|
63
|
-
],
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { CollectionConfig } from 'payload'
|
|
2
|
-
|
|
3
|
-
export const Posts: CollectionConfig = {
|
|
4
|
-
slug: 'posts',
|
|
5
|
-
admin: {
|
|
6
|
-
useAsTitle: 'title',
|
|
7
|
-
defaultColumns: ['title', 'author', 'status', 'publishedAt'],
|
|
8
|
-
},
|
|
9
|
-
access: {
|
|
10
|
-
read: () => true,
|
|
11
|
-
},
|
|
12
|
-
fields: [
|
|
13
|
-
{
|
|
14
|
-
name: 'title',
|
|
15
|
-
type: 'text',
|
|
16
|
-
required: true,
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
name: 'slug',
|
|
20
|
-
type: 'text',
|
|
21
|
-
required: true,
|
|
22
|
-
unique: true,
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
name: 'excerpt',
|
|
26
|
-
type: 'textarea',
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
name: 'content',
|
|
30
|
-
type: 'richText',
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: 'featuredImage',
|
|
34
|
-
type: 'upload',
|
|
35
|
-
relationTo: 'media',
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'author',
|
|
39
|
-
type: 'relationship',
|
|
40
|
-
relationTo: 'users',
|
|
41
|
-
admin: {
|
|
42
|
-
position: 'sidebar',
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: 'status',
|
|
47
|
-
type: 'select',
|
|
48
|
-
options: [
|
|
49
|
-
{ label: 'Draft', value: 'draft' },
|
|
50
|
-
{ label: 'Published', value: 'published' },
|
|
51
|
-
],
|
|
52
|
-
defaultValue: 'draft',
|
|
53
|
-
admin: {
|
|
54
|
-
position: 'sidebar',
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
name: 'publishedAt',
|
|
59
|
-
type: 'date',
|
|
60
|
-
admin: {
|
|
61
|
-
position: 'sidebar',
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { CollectionConfig } from 'payload'
|
|
2
|
-
|
|
3
|
-
export const Users: CollectionConfig = {
|
|
4
|
-
slug: 'users',
|
|
5
|
-
admin: {
|
|
6
|
-
useAsTitle: 'email',
|
|
7
|
-
},
|
|
8
|
-
auth: true,
|
|
9
|
-
fields: [
|
|
10
|
-
{
|
|
11
|
-
name: 'name',
|
|
12
|
-
type: 'text',
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
name: 'role',
|
|
16
|
-
type: 'select',
|
|
17
|
-
options: [
|
|
18
|
-
{ label: 'Admin', value: 'admin' },
|
|
19
|
-
{ label: 'Editor', value: 'editor' },
|
|
20
|
-
],
|
|
21
|
-
defaultValue: 'editor',
|
|
22
|
-
required: true,
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { GlobalConfig } from 'payload'
|
|
2
|
-
|
|
3
|
-
export const Navigation: GlobalConfig = {
|
|
4
|
-
slug: 'navigation',
|
|
5
|
-
access: {
|
|
6
|
-
read: () => true,
|
|
7
|
-
},
|
|
8
|
-
fields: [
|
|
9
|
-
{
|
|
10
|
-
name: 'items',
|
|
11
|
-
type: 'array',
|
|
12
|
-
fields: [
|
|
13
|
-
{
|
|
14
|
-
name: 'label',
|
|
15
|
-
type: 'text',
|
|
16
|
-
required: true,
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
name: 'link',
|
|
20
|
-
type: 'group',
|
|
21
|
-
fields: [
|
|
22
|
-
{
|
|
23
|
-
name: 'type',
|
|
24
|
-
type: 'radio',
|
|
25
|
-
options: [
|
|
26
|
-
{ label: 'Internal', value: 'internal' },
|
|
27
|
-
{ label: 'External', value: 'external' },
|
|
28
|
-
],
|
|
29
|
-
defaultValue: 'internal',
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
name: 'page',
|
|
33
|
-
type: 'relationship',
|
|
34
|
-
relationTo: 'pages',
|
|
35
|
-
admin: {
|
|
36
|
-
condition: (_, siblingData) => siblingData?.type === 'internal',
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: 'url',
|
|
41
|
-
type: 'text',
|
|
42
|
-
admin: {
|
|
43
|
-
condition: (_, siblingData) => siblingData?.type === 'external',
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { GlobalConfig } from 'payload'
|
|
2
|
-
|
|
3
|
-
export const SiteSettings: GlobalConfig = {
|
|
4
|
-
slug: 'site-settings',
|
|
5
|
-
access: {
|
|
6
|
-
read: () => true,
|
|
7
|
-
},
|
|
8
|
-
fields: [
|
|
9
|
-
{
|
|
10
|
-
name: 'siteName',
|
|
11
|
-
type: 'text',
|
|
12
|
-
required: true,
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
name: 'siteDescription',
|
|
16
|
-
type: 'textarea',
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
name: 'logo',
|
|
20
|
-
type: 'upload',
|
|
21
|
-
relationTo: 'media',
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: 'favicon',
|
|
25
|
-
type: 'upload',
|
|
26
|
-
relationTo: 'media',
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
name: 'socialLinks',
|
|
30
|
-
type: 'array',
|
|
31
|
-
fields: [
|
|
32
|
-
{
|
|
33
|
-
name: 'platform',
|
|
34
|
-
type: 'select',
|
|
35
|
-
options: [
|
|
36
|
-
{ label: 'Twitter', value: 'twitter' },
|
|
37
|
-
{ label: 'GitHub', value: 'github' },
|
|
38
|
-
{ label: 'LinkedIn', value: 'linkedin' },
|
|
39
|
-
{ label: 'Discord', value: 'discord' },
|
|
40
|
-
],
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: 'url',
|
|
44
|
-
type: 'text',
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
}
|