kofi-stack-template-generator 2.1.21 → 2.1.23

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 CHANGED
@@ -579,7 +579,25 @@ GCS_CREDENTIALS="{}" # JSON service account key
579
579
  }
580
580
  `,
581
581
  "marketing/payload/postcss.config.mjs.hbs": "export default {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n}\n",
582
- "marketing/payload/src/app/(frontend)/layout.tsx.hbs": "export default function FrontendLayout({\n children,\n}: {\n children: React.ReactNode\n}) {\n return <>{children}</>\n}\n",
582
+ "marketing/payload/src/app/(frontend)/layout.tsx.hbs": `import type { Metadata } from 'next'
583
+
584
+ export const metadata: Metadata = {
585
+ title: '{{projectName}} - Marketing',
586
+ description: 'Built with Payload CMS and Next.js',
587
+ }
588
+
589
+ export default function FrontendLayout({
590
+ children,
591
+ }: {
592
+ children: React.ReactNode
593
+ }) {
594
+ return (
595
+ <html lang="en">
596
+ <body>{children}</body>
597
+ </html>
598
+ )
599
+ }
600
+ `,
583
601
  "marketing/payload/src/app/(frontend)/page.tsx.hbs": `import { getPayload } from 'payload'
584
602
  import config from '@/payload.config'
585
603
 
@@ -671,28 +689,9 @@ export default async function HomePage() {
671
689
  "marketing/payload/src/app/(payload)/api/graphql-playground/route.ts.hbs": "import config from '@/payload.config'\nimport { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'\nimport { importMap } from '../../importMap.js'\n\nexport const GET = GRAPHQL_PLAYGROUND_GET(config, importMap)\n",
672
690
  "marketing/payload/src/app/(payload)/custom.scss.hbs": "/* Custom Payload admin styles */\n",
673
691
  "marketing/payload/src/app/(payload)/importMap.js.hbs": "export const importMap = {}\n",
674
- "marketing/payload/src/app/(payload)/layout.tsx.hbs": "import type { Metadata } from 'next'\nimport config from '@/payload.config'\nimport { RootLayout } from '@payloadcms/next/layouts'\nimport { importMap } from './importMap.js'\nimport './custom.scss'\n\nexport const metadata: Metadata = {\n title: 'Admin Panel',\n}\n\ntype LayoutArgs = {\n children: React.ReactNode\n}\n\nconst Layout = ({ children }: LayoutArgs) =>\n RootLayout({ children, config, importMap })\n\nexport default Layout\n",
692
+ "marketing/payload/src/app/(payload)/layout.tsx.hbs": "import type { Metadata } from 'next'\nimport config from '@/payload.config'\nimport { RootLayout, handleServerFunctions } from '@payloadcms/next/layouts'\nimport type { ServerFunctionClient } from 'payload'\nimport { importMap } from './importMap.js'\nimport './custom.scss'\n\nexport const metadata: Metadata = {\n title: 'Admin Panel',\n}\n\ntype LayoutArgs = {\n children: React.ReactNode\n}\n\nconst serverFunction: ServerFunctionClient = async function (args) {\n 'use server'\n return handleServerFunctions({ ...args, config, importMap })\n}\n\nconst Layout = ({ children }: LayoutArgs) =>\n RootLayout({ children, config, importMap, serverFunction })\n\nexport default Layout\n",
675
693
  "marketing/payload/src/app/globals.css.hbs": '@import "tailwindcss";\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --font-sans: var(--font-geist-sans);\n --font-mono: var(--font-geist-mono);\n --color-ring: var(--ring);\n --color-input: var(--input);\n --color-border: var(--border);\n --color-destructive: var(--destructive);\n --color-accent-foreground: var(--accent-foreground);\n --color-accent: var(--accent);\n --color-muted-foreground: var(--muted-foreground);\n --color-muted: var(--muted);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-secondary: var(--secondary);\n --color-primary-foreground: var(--primary-foreground);\n --color-primary: var(--primary);\n --color-popover-foreground: var(--popover-foreground);\n --color-popover: var(--popover);\n --color-card-foreground: var(--card-foreground);\n --color-card: var(--card);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-md: calc(var(--radius) - 2px);\n --radius-lg: var(--radius);\n --radius-xl: calc(var(--radius) + 4px);\n}\n\n:root {\n --radius: 0.625rem;\n --background: oklch(1 0 0);\n --foreground: oklch(0.145 0 0);\n --card: oklch(1 0 0);\n --card-foreground: oklch(0.145 0 0);\n --popover: oklch(1 0 0);\n --popover-foreground: oklch(0.145 0 0);\n --primary: oklch(0.205 0 0);\n --primary-foreground: oklch(0.985 0 0);\n --secondary: oklch(0.97 0 0);\n --secondary-foreground: oklch(0.205 0 0);\n --muted: oklch(0.97 0 0);\n --muted-foreground: oklch(0.556 0 0);\n --accent: oklch(0.97 0 0);\n --accent-foreground: oklch(0.205 0 0);\n --destructive: oklch(0.577 0.245 27.325);\n --border: oklch(0.922 0 0);\n --input: oklch(0.922 0 0);\n --ring: oklch(0.708 0 0);\n}\n\n.dark {\n --background: oklch(0.145 0 0);\n --foreground: oklch(0.985 0 0);\n --card: oklch(0.205 0 0);\n --card-foreground: oklch(0.985 0 0);\n --popover: oklch(0.205 0 0);\n --popover-foreground: oklch(0.985 0 0);\n --primary: oklch(0.922 0 0);\n --primary-foreground: oklch(0.205 0 0);\n --secondary: oklch(0.269 0 0);\n --secondary-foreground: oklch(0.985 0 0);\n --muted: oklch(0.269 0 0);\n --muted-foreground: oklch(0.708 0 0);\n --accent: oklch(0.269 0 0);\n --accent-foreground: oklch(0.985 0 0);\n --destructive: oklch(0.704 0.191 22.216);\n --border: oklch(1 0 0 / 10%);\n --input: oklch(1 0 0 / 15%);\n --ring: oklch(0.556 0 0);\n}\n\n@layer base {\n * {\n @apply border-border outline-ring/50;\n }\n\n body {\n @apply bg-background text-foreground;\n }\n}\n',
676
- "marketing/payload/src/app/layout.tsx.hbs": `import type { Metadata } from 'next'
677
- import './globals.css'
678
-
679
- export const metadata: Metadata = {
680
- title: 'Marketing Site',
681
- description: 'Built with Payload CMS',
682
- }
683
-
684
- export default function RootLayout({
685
- children,
686
- }: {
687
- children: React.ReactNode
688
- }) {
689
- return (
690
- <html lang="en">
691
- <body>{children}</body>
692
- </html>
693
- )
694
- }
695
- `,
694
+ "marketing/payload/src/app/layout.tsx.hbs": "import './globals.css'\n\n// Root layout is minimal - each route group has its own complete layout\n// This prevents nested <html>/<body> tags between frontend and Payload admin\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode\n}) {\n return children\n}\n",
696
695
  "marketing/payload/src/blocks/Benefits.ts.hbs": "import type { Block } from 'payload'\n\nexport const BenefitsBlock: Block = {\n slug: 'benefits',\n labels: { singular: 'Benefits', plural: 'Benefits' },\n fields: [\n { name: 'label', type: 'text' },\n { name: 'heading', type: 'text', required: true },\n { name: 'description', type: 'textarea' },\n { name: 'image', type: 'upload', relationTo: 'media' },\n {\n name: 'imagePosition',\n type: 'select',\n options: [\n { label: 'Left', value: 'left' },\n { label: 'Right', value: 'right' },\n ],\n defaultValue: 'right',\n },\n {\n name: 'benefits',\n type: 'array',\n fields: [{ name: 'text', type: 'text', required: true }],\n },\n {\n name: 'cta',\n type: 'group',\n fields: [\n { name: 'label', type: 'text' },\n { name: 'url', type: 'text' },\n ],\n },\n ],\n}\n",
697
696
  "marketing/payload/src/blocks/CTA.ts.hbs": "import type { Block } from 'payload'\n\nexport const CTABlock: Block = {\n slug: 'cta',\n labels: { singular: 'CTA', plural: 'CTAs' },\n fields: [\n { name: 'heading', type: 'text' },\n { name: 'description', type: 'textarea' },\n {\n name: 'style',\n type: 'select',\n options: [\n { label: 'Light', value: 'light' },\n { label: 'Dark', value: 'dark' },\n { label: 'Primary', value: 'primary' },\n ],\n defaultValue: 'dark',\n },\n {\n name: 'buttons',\n type: 'array',\n maxRows: 2,\n fields: [\n { name: 'label', type: 'text', required: true },\n { name: 'url', type: 'text', required: true },\n {\n name: 'variant',\n type: 'select',\n options: [\n { label: 'Primary', value: 'primary' },\n { label: 'Secondary', value: 'secondary' },\n { label: 'Outline', value: 'outline' },\n ],\n defaultValue: 'primary',\n },\n ],\n },\n ],\n}\n",
698
697
  "marketing/payload/src/blocks/Content.ts.hbs": "import type { Block } from 'payload'\n\nexport const ContentBlock: Block = {\n slug: 'content',\n labels: { singular: 'Content', plural: 'Content' },\n fields: [\n { name: 'content', type: 'richText' },\n ],\n}\n",
@@ -711,7 +710,7 @@ export default function RootLayout({
711
710
  "marketing/payload/src/globals/Navigation.ts.hbs": "import type { GlobalConfig } from 'payload'\n\nexport const Navigation: GlobalConfig = {\n slug: 'navigation',\n access: {\n read: () => true,\n },\n fields: [\n {\n name: 'items',\n type: 'array',\n fields: [\n {\n name: 'label',\n type: 'text',\n required: true,\n },\n {\n name: 'link',\n type: 'group',\n fields: [\n {\n name: 'type',\n type: 'radio',\n options: [\n { label: 'Internal', value: 'internal' },\n { label: 'External', value: 'external' },\n ],\n defaultValue: 'internal',\n },\n {\n name: 'page',\n type: 'relationship',\n relationTo: 'pages',\n admin: {\n condition: (_, siblingData) => siblingData?.type === 'internal',\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n condition: (_, siblingData) => siblingData?.type === 'external',\n },\n },\n ],\n },\n ],\n },\n ],\n}\n",
712
711
  "marketing/payload/src/globals/SiteSettings.ts.hbs": "import type { GlobalConfig } from 'payload'\n\nexport const SiteSettings: GlobalConfig = {\n slug: 'site-settings',\n access: {\n read: () => true,\n },\n fields: [\n {\n name: 'siteName',\n type: 'text',\n required: true,\n },\n {\n name: 'siteDescription',\n type: 'textarea',\n },\n {\n name: 'logo',\n type: 'upload',\n relationTo: 'media',\n },\n {\n name: 'favicon',\n type: 'upload',\n relationTo: 'media',\n },\n {\n name: 'socialLinks',\n type: 'array',\n fields: [\n {\n name: 'platform',\n type: 'select',\n options: [\n { label: 'Twitter', value: 'twitter' },\n { label: 'GitHub', value: 'github' },\n { label: 'LinkedIn', value: 'linkedin' },\n { label: 'Discord', value: 'discord' },\n ],\n },\n {\n name: 'url',\n type: 'text',\n },\n ],\n },\n ],\n}\n",
713
712
  "marketing/payload/src/globals/index.ts.hbs": "export { SiteSettings } from './SiteSettings'\nexport { Navigation } from './Navigation'\n",
714
- "marketing/payload/src/payload.config.ts.hbs": "import path from 'path'\nimport { fileURLToPath } from 'url'\nimport { buildConfig } from 'payload'\nimport { postgresAdapter } from '@payloadcms/db-postgres'\nimport { lexicalEditor } from '@payloadcms/richtext-lexical'\nimport { seoPlugin } from '@payloadcms/plugin-seo'\nimport { resendAdapter } from '@payloadcms/email-resend'\n{{#if (eq integrations.payloadStorage 's3')}}\nimport { s3Storage } from '@payloadcms/storage-s3'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'r2')}}\nimport { s3Storage } from '@payloadcms/storage-s3'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'vercel-blob')}}\nimport { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'gcs')}}\nimport { gcsStorage } from '@payloadcms/storage-gcs'\n{{/if}}\n\nimport { Pages } from './collections/Pages'\nimport { Media } from './collections/Media'\nimport { Users } from './collections/Users'\nimport { Posts } from './collections/Posts'\nimport { SiteSettings } from './globals/SiteSettings'\nimport { Navigation } from './globals/Navigation'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\n\nexport default buildConfig({\n admin: {\n user: Users.slug,\n importMap: {\n baseDir: path.resolve(__dirname),\n },\n },\n db: postgresAdapter({\n pool: {\n connectionString: process.env.DATABASE_URL,\n },\n }),\n editor: lexicalEditor(),\n email: resendAdapter({\n defaultFromAddress: process.env.RESEND_FROM_EMAIL || 'noreply@example.com',\n defaultFromName: '{{projectName}}',\n apiKey: process.env.RESEND_API_KEY || '',\n }),\n collections: [Users, Media, Pages, Posts],\n globals: [SiteSettings, Navigation],\n plugins: [\n{{#if (eq integrations.payloadStorage 's3')}}\n s3Storage({\n collections: {\n media: true,\n },\n bucket: process.env.S3_BUCKET!,\n config: {\n credentials: {\n accessKeyId: process.env.S3_ACCESS_KEY_ID!,\n secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,\n },\n region: process.env.S3_REGION!,\n endpoint: process.env.S3_ENDPOINT,\n forcePathStyle: true,\n },\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'r2')}}\n s3Storage({\n collections: {\n media: true,\n },\n bucket: process.env.R2_BUCKET!,\n config: {\n credentials: {\n accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n },\n region: 'auto',\n endpoint: process.env.R2_ENDPOINT!,\n forcePathStyle: true,\n },\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'vercel-blob')}}\n vercelBlobStorage({\n collections: {\n media: true,\n },\n token: process.env.BLOB_READ_WRITE_TOKEN!,\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'gcs')}}\n gcsStorage({\n collections: {\n media: true,\n },\n bucket: process.env.GCS_BUCKET!,\n options: {\n projectId: process.env.GCS_PROJECT_ID,\n credentials: JSON.parse(process.env.GCS_CREDENTIALS || '{}'),\n },\n }),\n{{/if}}\n seoPlugin({\n collections: ['pages', 'posts'],\n uploadsCollection: 'media',\n generateTitle: ({ doc }) => `${doc?.title ?? ''} | {{projectName}}`,\n generateDescription: ({ doc }) => doc?.excerpt ?? '',\n }),\n ],\n secret: process.env.PAYLOAD_SECRET!,\n typescript: {\n outputFile: path.resolve(__dirname, 'payload-types.ts'),\n },\n{{#if (eq integrations.payloadStorage 'local')}}\n upload: {\n limits: {\n fileSize: 5000000, // 5MB\n },\n },\n{{/if}}\n})\n",
713
+ "marketing/payload/src/payload.config.ts.hbs": "import path from 'path'\nimport { fileURLToPath } from 'url'\nimport { buildConfig } from 'payload'\nimport { postgresAdapter } from '@payloadcms/db-postgres'\nimport { lexicalEditor } from '@payloadcms/richtext-lexical'\nimport { seoPlugin } from '@payloadcms/plugin-seo'\nimport { resendAdapter } from '@payloadcms/email-resend'\nimport sharp from 'sharp'\n{{#if (eq integrations.payloadStorage 's3')}}\nimport { s3Storage } from '@payloadcms/storage-s3'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'r2')}}\nimport { s3Storage } from '@payloadcms/storage-s3'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'vercel-blob')}}\nimport { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'gcs')}}\nimport { gcsStorage } from '@payloadcms/storage-gcs'\n{{/if}}\n\nimport { Pages } from './collections/Pages'\nimport { Media } from './collections/Media'\nimport { Users } from './collections/Users'\nimport { Posts } from './collections/Posts'\nimport { SiteSettings } from './globals/SiteSettings'\nimport { Navigation } from './globals/Navigation'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\n\nexport default buildConfig({\n admin: {\n user: Users.slug,\n importMap: {\n baseDir: path.resolve(__dirname),\n },\n },\n db: postgresAdapter({\n pool: {\n connectionString: process.env.DATABASE_URL,\n },\n }),\n editor: lexicalEditor(),\n email: resendAdapter({\n defaultFromAddress: process.env.RESEND_FROM_EMAIL || 'noreply@example.com',\n defaultFromName: '{{projectName}}',\n apiKey: process.env.RESEND_API_KEY || '',\n }),\n collections: [Users, Media, Pages, Posts],\n globals: [SiteSettings, Navigation],\n plugins: [\n{{#if (eq integrations.payloadStorage 's3')}}\n s3Storage({\n collections: {\n media: true,\n },\n bucket: process.env.S3_BUCKET!,\n config: {\n credentials: {\n accessKeyId: process.env.S3_ACCESS_KEY_ID!,\n secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,\n },\n region: process.env.S3_REGION!,\n endpoint: process.env.S3_ENDPOINT,\n forcePathStyle: true,\n },\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'r2')}}\n s3Storage({\n collections: {\n media: true,\n },\n bucket: process.env.R2_BUCKET!,\n config: {\n credentials: {\n accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n },\n region: 'auto',\n endpoint: process.env.R2_ENDPOINT!,\n forcePathStyle: true,\n },\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'vercel-blob')}}\n vercelBlobStorage({\n collections: {\n media: true,\n },\n token: process.env.BLOB_READ_WRITE_TOKEN!,\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'gcs')}}\n gcsStorage({\n collections: {\n media: true,\n },\n bucket: process.env.GCS_BUCKET!,\n options: {\n projectId: process.env.GCS_PROJECT_ID,\n credentials: JSON.parse(process.env.GCS_CREDENTIALS || '{}'),\n },\n }),\n{{/if}}\n seoPlugin({\n collections: ['pages', 'posts'],\n uploadsCollection: 'media',\n generateTitle: ({ doc }) => `${doc?.title ?? ''} | {{projectName}}`,\n generateDescription: ({ doc }) => doc?.excerpt ?? '',\n }),\n ],\n secret: process.env.PAYLOAD_SECRET!,\n sharp,\n typescript: {\n outputFile: path.resolve(__dirname, 'payload-types.ts'),\n },\n{{#if (eq integrations.payloadStorage 'local')}}\n upload: {\n limits: {\n fileSize: 5000000, // 5MB\n },\n },\n{{/if}}\n})\n",
715
714
  "marketing/payload/tsconfig.json.hbs": '{\n "compilerOptions": {\n "target": "ES2017",\n "lib": ["dom", "dom.iterable", "esnext"],\n "allowJs": true,\n "skipLibCheck": true,\n "strict": true,\n "noEmit": true,\n "esModuleInterop": true,\n "module": "esnext",\n "moduleResolution": "bundler",\n "resolveJsonModule": true,\n "isolatedModules": true,\n "jsx": "preserve",\n "incremental": true,\n "plugins": [{ "name": "next" }],\n "paths": {\n "@/*": ["./src/*"]\n }\n },\n "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],\n "exclude": ["node_modules"]\n}\n',
716
715
  "monorepo/package.json.hbs": '{\n "name": "{{projectName}}",\n "version": "0.1.0",\n "private": true,\n "scripts": {\n "dev": "node scripts/dev.mjs",\n "dev:all": "turbo dev",\n "dev:web": "turbo -F @repo/web dev",\n "dev:backend": "turbo -F @repo/backend dev",\n "dev:setup": "pnpm --filter @repo/backend dev:setup",\n "build": "turbo build",\n "lint": "turbo lint",\n "lint:fix": "turbo lint:fix",\n "format": "turbo format",\n "typecheck": "turbo typecheck",\n "test": "turbo test",\n "test:e2e": "turbo test:e2e",\n "clean": "turbo clean && rm -rf node_modules",\n "prepare": "husky"\n },\n "devDependencies": {\n "turbo": "^2.0.0",\n "husky": "^9.0.0",\n "lint-staged": "^15.0.0"\n },\n "packageManager": "pnpm@9.0.0",\n "lint-staged": {\n "*.{js,ts,jsx,tsx}": ["biome check --apply"],\n "*.{json,md}": ["biome format --write"]\n }\n}\n',
717
716
  "monorepo/pnpm-workspace.yaml.hbs": 'packages:\n - "apps/*"\n - "packages/*"\n',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kofi-stack-template-generator",
3
- "version": "2.1.21",
3
+ "version": "2.1.23",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,6 +1,6 @@
1
1
  // Auto-generated file. Do not edit manually.
2
2
  // Run 'pnpm prebuild' to regenerate.
3
- // Generated: 2026-01-15T02:49:40.894Z
3
+ // Generated: 2026-01-15T03:14:32.904Z
4
4
  // Template count: 90
5
5
 
6
6
  export const EMBEDDED_TEMPLATES: Record<string, string> = {
@@ -28,7 +28,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
28
28
  "marketing/payload/next.config.ts.hbs": "import { withPayload } from '@payloadcms/next/withPayload'\nimport type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n transpilePackages: ['@repo/ui'],\n}\n\nexport default withPayload(nextConfig)\n",
29
29
  "marketing/payload/package.json.hbs": "{\n \"name\": \"@repo/marketing\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev -p 3001\",\n \"build\": \"next build\",\n \"start\": \"next start\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"typecheck\": \"tsc --noEmit\",\n \"db:push\": \"payload migrate\",\n \"db:seed\": \"tsx src/seed.ts\"\n },\n \"dependencies\": {\n \"next\": \"^15.4.10\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"payload\": \"^3.70.0\",\n \"@payloadcms/db-postgres\": \"^3.0.0\",\n \"@payloadcms/next\": \"^3.0.0\",\n \"@payloadcms/richtext-lexical\": \"^3.0.0\",\n \"@payloadcms/email-resend\": \"^3.0.0\",\n \"@payloadcms/plugin-seo\": \"^3.0.0\",\n{{#if (eq integrations.payloadStorage 's3')}}\n \"@payloadcms/storage-s3\": \"^3.0.0\",\n{{/if}}\n{{#if (eq integrations.payloadStorage 'r2')}}\n \"@payloadcms/storage-s3\": \"^3.0.0\",\n{{/if}}\n{{#if (eq integrations.payloadStorage 'vercel-blob')}}\n \"@payloadcms/storage-vercel-blob\": \"^3.0.0\",\n{{/if}}\n{{#if (eq integrations.payloadStorage 'gcs')}}\n \"@payloadcms/storage-gcs\": \"^3.0.0\",\n{{/if}}\n \"sharp\": \"^0.33.0\",\n \"@repo/ui\": \"workspace:*\"\n },\n \"devDependencies\": {\n \"@repo/config-typescript\": \"workspace:*\",\n \"@types/node\": \"^20.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"tailwindcss\": \"^4.0.0\",\n \"@tailwindcss/postcss\": \"^4.0.0\",\n \"postcss\": \"^8.4.0\",\n \"sass\": \"^1.86.0\",\n \"typescript\": \"^5.0.0\",\n \"tsx\": \"^4.0.0\"\n }\n}\n",
30
30
  "marketing/payload/postcss.config.mjs.hbs": "export default {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n}\n",
31
- "marketing/payload/src/app/(frontend)/layout.tsx.hbs": "export default function FrontendLayout({\n children,\n}: {\n children: React.ReactNode\n}) {\n return <>{children}</>\n}\n",
31
+ "marketing/payload/src/app/(frontend)/layout.tsx.hbs": "import type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n title: '{{projectName}} - Marketing',\n description: 'Built with Payload CMS and Next.js',\n}\n\nexport default function FrontendLayout({\n children,\n}: {\n children: React.ReactNode\n}) {\n return (\n <html lang=\"en\">\n <body>{children}</body>\n </html>\n )\n}\n",
32
32
  "marketing/payload/src/app/(frontend)/page.tsx.hbs": "import { getPayload } from 'payload'\nimport config from '@/payload.config'\n\nexport default async function HomePage() {\n // Check if DATABASE_URL is configured\n if (!process.env.DATABASE_URL || process.env.DATABASE_URL.includes('[PASSWORD]')) {\n return (\n <main className=\"min-h-screen bg-gradient-to-b from-gray-50 to-white\">\n <div className=\"container mx-auto px-4 py-16\">\n <div className=\"max-w-2xl mx-auto text-center\">\n <h1 className=\"text-4xl font-bold text-gray-900 mb-4\">\n Welcome to Your Marketing Site\n </h1>\n <p className=\"text-lg text-gray-600 mb-8\">\n Built with Payload CMS and Next.js\n </p>\n <div className=\"bg-amber-50 border border-amber-200 rounded-lg p-6 text-left\">\n <h2 className=\"text-lg font-semibold text-amber-800 mb-2\">\n Database Setup Required\n </h2>\n <p className=\"text-amber-700 mb-4\">\n To get started, configure your PostgreSQL database:\n </p>\n <ol className=\"list-decimal list-inside text-amber-700 space-y-2\">\n <li>Create a Supabase project at <a href=\"https://supabase.com\" className=\"underline\" target=\"_blank\" rel=\"noopener noreferrer\">supabase.com</a></li>\n <li>Copy your database connection string</li>\n <li>Update <code className=\"bg-amber-100 px-1 rounded\">DATABASE_URL</code> in <code className=\"bg-amber-100 px-1 rounded\">apps/marketing/.env.local</code></li>\n <li>Restart the development server</li>\n </ol>\n </div>\n <div className=\"mt-8\">\n <a\n href=\"/admin\"\n className=\"inline-flex items-center px-6 py-3 bg-gray-900 text-white rounded-lg hover:bg-gray-800 transition-colors\"\n >\n Go to Admin Panel\n </a>\n </div>\n </div>\n </div>\n </main>\n )\n }\n\n try {\n const payload = await getPayload({ config })\n\n const settings = await payload.findGlobal({\n slug: 'site-settings',\n })\n\n return (\n <main className=\"min-h-screen\">\n <div className=\"container mx-auto px-4 py-16\">\n <h1 className=\"text-4xl font-bold\">{settings.siteName || 'Marketing Site'}</h1>\n <p className=\"mt-4 text-lg text-gray-600\">{settings.siteDescription || 'Built with Payload CMS'}</p>\n </div>\n </main>\n )\n } catch (error) {\n return (\n <main className=\"min-h-screen bg-gradient-to-b from-red-50 to-white\">\n <div className=\"container mx-auto px-4 py-16\">\n <div className=\"max-w-2xl mx-auto text-center\">\n <h1 className=\"text-4xl font-bold text-gray-900 mb-4\">\n Database Connection Error\n </h1>\n <div className=\"bg-red-50 border border-red-200 rounded-lg p-6 text-left\">\n <p className=\"text-red-700 mb-4\">\n Could not connect to the database. Please check your configuration:\n </p>\n <ul className=\"list-disc list-inside text-red-700 space-y-2\">\n <li>Verify <code className=\"bg-red-100 px-1 rounded\">DATABASE_URL</code> is correct</li>\n <li>Ensure your database is running and accessible</li>\n <li>Check that the database credentials are valid</li>\n </ul>\n </div>\n </div>\n </div>\n </main>\n )\n }\n}\n",
33
33
  "marketing/payload/src/app/(payload)/admin/[[...segments]]/not-found.tsx.hbs": "import config from '@/payload.config'\nimport { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'\nimport { importMap } from '../../importMap.js'\nimport type { Metadata } from 'next'\n\ntype Args = {\n params: Promise<{ segments: string[] }>\n searchParams: Promise<Record<string, string | string[]>>\n}\n\nexport const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>\n generatePageMetadata({ config, params, searchParams })\n\nconst NotFound = ({ params, searchParams }: Args) =>\n NotFoundPage({ config, importMap, params, searchParams })\n\nexport default NotFound\n",
34
34
  "marketing/payload/src/app/(payload)/admin/[[...segments]]/page.tsx.hbs": "import config from '@/payload.config'\nimport { RootPage, generatePageMetadata } from '@payloadcms/next/views'\nimport { importMap } from '../../importMap.js'\nimport type { Metadata } from 'next'\n\ntype Args = {\n params: Promise<{ segments: string[] }>\n searchParams: Promise<Record<string, string | string[]>>\n}\n\nexport const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>\n generatePageMetadata({ config, params, searchParams })\n\nconst Page = ({ params, searchParams }: Args) =>\n RootPage({ config, importMap, params, searchParams })\n\nexport default Page\n",
@@ -37,9 +37,9 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
37
37
  "marketing/payload/src/app/(payload)/api/graphql-playground/route.ts.hbs": "import config from '@/payload.config'\nimport { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'\nimport { importMap } from '../../importMap.js'\n\nexport const GET = GRAPHQL_PLAYGROUND_GET(config, importMap)\n",
38
38
  "marketing/payload/src/app/(payload)/custom.scss.hbs": "/* Custom Payload admin styles */\n",
39
39
  "marketing/payload/src/app/(payload)/importMap.js.hbs": "export const importMap = {}\n",
40
- "marketing/payload/src/app/(payload)/layout.tsx.hbs": "import type { Metadata } from 'next'\nimport config from '@/payload.config'\nimport { RootLayout } from '@payloadcms/next/layouts'\nimport { importMap } from './importMap.js'\nimport './custom.scss'\n\nexport const metadata: Metadata = {\n title: 'Admin Panel',\n}\n\ntype LayoutArgs = {\n children: React.ReactNode\n}\n\nconst Layout = ({ children }: LayoutArgs) =>\n RootLayout({ children, config, importMap })\n\nexport default Layout\n",
40
+ "marketing/payload/src/app/(payload)/layout.tsx.hbs": "import type { Metadata } from 'next'\nimport config from '@/payload.config'\nimport { RootLayout, handleServerFunctions } from '@payloadcms/next/layouts'\nimport type { ServerFunctionClient } from 'payload'\nimport { importMap } from './importMap.js'\nimport './custom.scss'\n\nexport const metadata: Metadata = {\n title: 'Admin Panel',\n}\n\ntype LayoutArgs = {\n children: React.ReactNode\n}\n\nconst serverFunction: ServerFunctionClient = async function (args) {\n 'use server'\n return handleServerFunctions({ ...args, config, importMap })\n}\n\nconst Layout = ({ children }: LayoutArgs) =>\n RootLayout({ children, config, importMap, serverFunction })\n\nexport default Layout\n",
41
41
  "marketing/payload/src/app/globals.css.hbs": "@import \"tailwindcss\";\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --font-sans: var(--font-geist-sans);\n --font-mono: var(--font-geist-mono);\n --color-ring: var(--ring);\n --color-input: var(--input);\n --color-border: var(--border);\n --color-destructive: var(--destructive);\n --color-accent-foreground: var(--accent-foreground);\n --color-accent: var(--accent);\n --color-muted-foreground: var(--muted-foreground);\n --color-muted: var(--muted);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-secondary: var(--secondary);\n --color-primary-foreground: var(--primary-foreground);\n --color-primary: var(--primary);\n --color-popover-foreground: var(--popover-foreground);\n --color-popover: var(--popover);\n --color-card-foreground: var(--card-foreground);\n --color-card: var(--card);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-md: calc(var(--radius) - 2px);\n --radius-lg: var(--radius);\n --radius-xl: calc(var(--radius) + 4px);\n}\n\n:root {\n --radius: 0.625rem;\n --background: oklch(1 0 0);\n --foreground: oklch(0.145 0 0);\n --card: oklch(1 0 0);\n --card-foreground: oklch(0.145 0 0);\n --popover: oklch(1 0 0);\n --popover-foreground: oklch(0.145 0 0);\n --primary: oklch(0.205 0 0);\n --primary-foreground: oklch(0.985 0 0);\n --secondary: oklch(0.97 0 0);\n --secondary-foreground: oklch(0.205 0 0);\n --muted: oklch(0.97 0 0);\n --muted-foreground: oklch(0.556 0 0);\n --accent: oklch(0.97 0 0);\n --accent-foreground: oklch(0.205 0 0);\n --destructive: oklch(0.577 0.245 27.325);\n --border: oklch(0.922 0 0);\n --input: oklch(0.922 0 0);\n --ring: oklch(0.708 0 0);\n}\n\n.dark {\n --background: oklch(0.145 0 0);\n --foreground: oklch(0.985 0 0);\n --card: oklch(0.205 0 0);\n --card-foreground: oklch(0.985 0 0);\n --popover: oklch(0.205 0 0);\n --popover-foreground: oklch(0.985 0 0);\n --primary: oklch(0.922 0 0);\n --primary-foreground: oklch(0.205 0 0);\n --secondary: oklch(0.269 0 0);\n --secondary-foreground: oklch(0.985 0 0);\n --muted: oklch(0.269 0 0);\n --muted-foreground: oklch(0.708 0 0);\n --accent: oklch(0.269 0 0);\n --accent-foreground: oklch(0.985 0 0);\n --destructive: oklch(0.704 0.191 22.216);\n --border: oklch(1 0 0 / 10%);\n --input: oklch(1 0 0 / 15%);\n --ring: oklch(0.556 0 0);\n}\n\n@layer base {\n * {\n @apply border-border outline-ring/50;\n }\n\n body {\n @apply bg-background text-foreground;\n }\n}\n",
42
- "marketing/payload/src/app/layout.tsx.hbs": "import type { Metadata } from 'next'\nimport './globals.css'\n\nexport const metadata: Metadata = {\n title: 'Marketing Site',\n description: 'Built with Payload CMS',\n}\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode\n}) {\n return (\n <html lang=\"en\">\n <body>{children}</body>\n </html>\n )\n}\n",
42
+ "marketing/payload/src/app/layout.tsx.hbs": "import './globals.css'\n\n// Root layout is minimal - each route group has its own complete layout\n// This prevents nested <html>/<body> tags between frontend and Payload admin\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode\n}) {\n return children\n}\n",
43
43
  "marketing/payload/src/blocks/Benefits.ts.hbs": "import type { Block } from 'payload'\n\nexport const BenefitsBlock: Block = {\n slug: 'benefits',\n labels: { singular: 'Benefits', plural: 'Benefits' },\n fields: [\n { name: 'label', type: 'text' },\n { name: 'heading', type: 'text', required: true },\n { name: 'description', type: 'textarea' },\n { name: 'image', type: 'upload', relationTo: 'media' },\n {\n name: 'imagePosition',\n type: 'select',\n options: [\n { label: 'Left', value: 'left' },\n { label: 'Right', value: 'right' },\n ],\n defaultValue: 'right',\n },\n {\n name: 'benefits',\n type: 'array',\n fields: [{ name: 'text', type: 'text', required: true }],\n },\n {\n name: 'cta',\n type: 'group',\n fields: [\n { name: 'label', type: 'text' },\n { name: 'url', type: 'text' },\n ],\n },\n ],\n}\n",
44
44
  "marketing/payload/src/blocks/CTA.ts.hbs": "import type { Block } from 'payload'\n\nexport const CTABlock: Block = {\n slug: 'cta',\n labels: { singular: 'CTA', plural: 'CTAs' },\n fields: [\n { name: 'heading', type: 'text' },\n { name: 'description', type: 'textarea' },\n {\n name: 'style',\n type: 'select',\n options: [\n { label: 'Light', value: 'light' },\n { label: 'Dark', value: 'dark' },\n { label: 'Primary', value: 'primary' },\n ],\n defaultValue: 'dark',\n },\n {\n name: 'buttons',\n type: 'array',\n maxRows: 2,\n fields: [\n { name: 'label', type: 'text', required: true },\n { name: 'url', type: 'text', required: true },\n {\n name: 'variant',\n type: 'select',\n options: [\n { label: 'Primary', value: 'primary' },\n { label: 'Secondary', value: 'secondary' },\n { label: 'Outline', value: 'outline' },\n ],\n defaultValue: 'primary',\n },\n ],\n },\n ],\n}\n",
45
45
  "marketing/payload/src/blocks/Content.ts.hbs": "import type { Block } from 'payload'\n\nexport const ContentBlock: Block = {\n slug: 'content',\n labels: { singular: 'Content', plural: 'Content' },\n fields: [\n { name: 'content', type: 'richText' },\n ],\n}\n",
@@ -58,7 +58,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
58
58
  "marketing/payload/src/globals/Navigation.ts.hbs": "import type { GlobalConfig } from 'payload'\n\nexport const Navigation: GlobalConfig = {\n slug: 'navigation',\n access: {\n read: () => true,\n },\n fields: [\n {\n name: 'items',\n type: 'array',\n fields: [\n {\n name: 'label',\n type: 'text',\n required: true,\n },\n {\n name: 'link',\n type: 'group',\n fields: [\n {\n name: 'type',\n type: 'radio',\n options: [\n { label: 'Internal', value: 'internal' },\n { label: 'External', value: 'external' },\n ],\n defaultValue: 'internal',\n },\n {\n name: 'page',\n type: 'relationship',\n relationTo: 'pages',\n admin: {\n condition: (_, siblingData) => siblingData?.type === 'internal',\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n condition: (_, siblingData) => siblingData?.type === 'external',\n },\n },\n ],\n },\n ],\n },\n ],\n}\n",
59
59
  "marketing/payload/src/globals/SiteSettings.ts.hbs": "import type { GlobalConfig } from 'payload'\n\nexport const SiteSettings: GlobalConfig = {\n slug: 'site-settings',\n access: {\n read: () => true,\n },\n fields: [\n {\n name: 'siteName',\n type: 'text',\n required: true,\n },\n {\n name: 'siteDescription',\n type: 'textarea',\n },\n {\n name: 'logo',\n type: 'upload',\n relationTo: 'media',\n },\n {\n name: 'favicon',\n type: 'upload',\n relationTo: 'media',\n },\n {\n name: 'socialLinks',\n type: 'array',\n fields: [\n {\n name: 'platform',\n type: 'select',\n options: [\n { label: 'Twitter', value: 'twitter' },\n { label: 'GitHub', value: 'github' },\n { label: 'LinkedIn', value: 'linkedin' },\n { label: 'Discord', value: 'discord' },\n ],\n },\n {\n name: 'url',\n type: 'text',\n },\n ],\n },\n ],\n}\n",
60
60
  "marketing/payload/src/globals/index.ts.hbs": "export { SiteSettings } from './SiteSettings'\nexport { Navigation } from './Navigation'\n",
61
- "marketing/payload/src/payload.config.ts.hbs": "import path from 'path'\nimport { fileURLToPath } from 'url'\nimport { buildConfig } from 'payload'\nimport { postgresAdapter } from '@payloadcms/db-postgres'\nimport { lexicalEditor } from '@payloadcms/richtext-lexical'\nimport { seoPlugin } from '@payloadcms/plugin-seo'\nimport { resendAdapter } from '@payloadcms/email-resend'\n{{#if (eq integrations.payloadStorage 's3')}}\nimport { s3Storage } from '@payloadcms/storage-s3'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'r2')}}\nimport { s3Storage } from '@payloadcms/storage-s3'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'vercel-blob')}}\nimport { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'gcs')}}\nimport { gcsStorage } from '@payloadcms/storage-gcs'\n{{/if}}\n\nimport { Pages } from './collections/Pages'\nimport { Media } from './collections/Media'\nimport { Users } from './collections/Users'\nimport { Posts } from './collections/Posts'\nimport { SiteSettings } from './globals/SiteSettings'\nimport { Navigation } from './globals/Navigation'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\n\nexport default buildConfig({\n admin: {\n user: Users.slug,\n importMap: {\n baseDir: path.resolve(__dirname),\n },\n },\n db: postgresAdapter({\n pool: {\n connectionString: process.env.DATABASE_URL,\n },\n }),\n editor: lexicalEditor(),\n email: resendAdapter({\n defaultFromAddress: process.env.RESEND_FROM_EMAIL || 'noreply@example.com',\n defaultFromName: '{{projectName}}',\n apiKey: process.env.RESEND_API_KEY || '',\n }),\n collections: [Users, Media, Pages, Posts],\n globals: [SiteSettings, Navigation],\n plugins: [\n{{#if (eq integrations.payloadStorage 's3')}}\n s3Storage({\n collections: {\n media: true,\n },\n bucket: process.env.S3_BUCKET!,\n config: {\n credentials: {\n accessKeyId: process.env.S3_ACCESS_KEY_ID!,\n secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,\n },\n region: process.env.S3_REGION!,\n endpoint: process.env.S3_ENDPOINT,\n forcePathStyle: true,\n },\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'r2')}}\n s3Storage({\n collections: {\n media: true,\n },\n bucket: process.env.R2_BUCKET!,\n config: {\n credentials: {\n accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n },\n region: 'auto',\n endpoint: process.env.R2_ENDPOINT!,\n forcePathStyle: true,\n },\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'vercel-blob')}}\n vercelBlobStorage({\n collections: {\n media: true,\n },\n token: process.env.BLOB_READ_WRITE_TOKEN!,\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'gcs')}}\n gcsStorage({\n collections: {\n media: true,\n },\n bucket: process.env.GCS_BUCKET!,\n options: {\n projectId: process.env.GCS_PROJECT_ID,\n credentials: JSON.parse(process.env.GCS_CREDENTIALS || '{}'),\n },\n }),\n{{/if}}\n seoPlugin({\n collections: ['pages', 'posts'],\n uploadsCollection: 'media',\n generateTitle: ({ doc }) => `${doc?.title ?? ''} | {{projectName}}`,\n generateDescription: ({ doc }) => doc?.excerpt ?? '',\n }),\n ],\n secret: process.env.PAYLOAD_SECRET!,\n typescript: {\n outputFile: path.resolve(__dirname, 'payload-types.ts'),\n },\n{{#if (eq integrations.payloadStorage 'local')}}\n upload: {\n limits: {\n fileSize: 5000000, // 5MB\n },\n },\n{{/if}}\n})\n",
61
+ "marketing/payload/src/payload.config.ts.hbs": "import path from 'path'\nimport { fileURLToPath } from 'url'\nimport { buildConfig } from 'payload'\nimport { postgresAdapter } from '@payloadcms/db-postgres'\nimport { lexicalEditor } from '@payloadcms/richtext-lexical'\nimport { seoPlugin } from '@payloadcms/plugin-seo'\nimport { resendAdapter } from '@payloadcms/email-resend'\nimport sharp from 'sharp'\n{{#if (eq integrations.payloadStorage 's3')}}\nimport { s3Storage } from '@payloadcms/storage-s3'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'r2')}}\nimport { s3Storage } from '@payloadcms/storage-s3'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'vercel-blob')}}\nimport { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'\n{{/if}}\n{{#if (eq integrations.payloadStorage 'gcs')}}\nimport { gcsStorage } from '@payloadcms/storage-gcs'\n{{/if}}\n\nimport { Pages } from './collections/Pages'\nimport { Media } from './collections/Media'\nimport { Users } from './collections/Users'\nimport { Posts } from './collections/Posts'\nimport { SiteSettings } from './globals/SiteSettings'\nimport { Navigation } from './globals/Navigation'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\n\nexport default buildConfig({\n admin: {\n user: Users.slug,\n importMap: {\n baseDir: path.resolve(__dirname),\n },\n },\n db: postgresAdapter({\n pool: {\n connectionString: process.env.DATABASE_URL,\n },\n }),\n editor: lexicalEditor(),\n email: resendAdapter({\n defaultFromAddress: process.env.RESEND_FROM_EMAIL || 'noreply@example.com',\n defaultFromName: '{{projectName}}',\n apiKey: process.env.RESEND_API_KEY || '',\n }),\n collections: [Users, Media, Pages, Posts],\n globals: [SiteSettings, Navigation],\n plugins: [\n{{#if (eq integrations.payloadStorage 's3')}}\n s3Storage({\n collections: {\n media: true,\n },\n bucket: process.env.S3_BUCKET!,\n config: {\n credentials: {\n accessKeyId: process.env.S3_ACCESS_KEY_ID!,\n secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,\n },\n region: process.env.S3_REGION!,\n endpoint: process.env.S3_ENDPOINT,\n forcePathStyle: true,\n },\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'r2')}}\n s3Storage({\n collections: {\n media: true,\n },\n bucket: process.env.R2_BUCKET!,\n config: {\n credentials: {\n accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n },\n region: 'auto',\n endpoint: process.env.R2_ENDPOINT!,\n forcePathStyle: true,\n },\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'vercel-blob')}}\n vercelBlobStorage({\n collections: {\n media: true,\n },\n token: process.env.BLOB_READ_WRITE_TOKEN!,\n }),\n{{/if}}\n{{#if (eq integrations.payloadStorage 'gcs')}}\n gcsStorage({\n collections: {\n media: true,\n },\n bucket: process.env.GCS_BUCKET!,\n options: {\n projectId: process.env.GCS_PROJECT_ID,\n credentials: JSON.parse(process.env.GCS_CREDENTIALS || '{}'),\n },\n }),\n{{/if}}\n seoPlugin({\n collections: ['pages', 'posts'],\n uploadsCollection: 'media',\n generateTitle: ({ doc }) => `${doc?.title ?? ''} | {{projectName}}`,\n generateDescription: ({ doc }) => doc?.excerpt ?? '',\n }),\n ],\n secret: process.env.PAYLOAD_SECRET!,\n sharp,\n typescript: {\n outputFile: path.resolve(__dirname, 'payload-types.ts'),\n },\n{{#if (eq integrations.payloadStorage 'local')}}\n upload: {\n limits: {\n fileSize: 5000000, // 5MB\n },\n },\n{{/if}}\n})\n",
62
62
  "marketing/payload/tsconfig.json.hbs": "{\n \"compilerOptions\": {\n \"target\": \"ES2017\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"skipLibCheck\": true,\n \"strict\": true,\n \"noEmit\": true,\n \"esModuleInterop\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"resolveJsonModule\": true,\n \"isolatedModules\": true,\n \"jsx\": \"preserve\",\n \"incremental\": true,\n \"plugins\": [{ \"name\": \"next\" }],\n \"paths\": {\n \"@/*\": [\"./src/*\"]\n }\n },\n \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\", \".next/dev/types/**/*.ts\"],\n \"exclude\": [\"node_modules\"]\n}\n",
63
63
  "monorepo/package.json.hbs": "{\n \"name\": \"{{projectName}}\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"node scripts/dev.mjs\",\n \"dev:all\": \"turbo dev\",\n \"dev:web\": \"turbo -F @repo/web dev\",\n \"dev:backend\": \"turbo -F @repo/backend dev\",\n \"dev:setup\": \"pnpm --filter @repo/backend dev:setup\",\n \"build\": \"turbo build\",\n \"lint\": \"turbo lint\",\n \"lint:fix\": \"turbo lint:fix\",\n \"format\": \"turbo format\",\n \"typecheck\": \"turbo typecheck\",\n \"test\": \"turbo test\",\n \"test:e2e\": \"turbo test:e2e\",\n \"clean\": \"turbo clean && rm -rf node_modules\",\n \"prepare\": \"husky\"\n },\n \"devDependencies\": {\n \"turbo\": \"^2.0.0\",\n \"husky\": \"^9.0.0\",\n \"lint-staged\": \"^15.0.0\"\n },\n \"packageManager\": \"pnpm@9.0.0\",\n \"lint-staged\": {\n \"*.{js,ts,jsx,tsx}\": [\"biome check --apply\"],\n \"*.{json,md}\": [\"biome format --write\"]\n }\n}\n",
64
64
  "monorepo/pnpm-workspace.yaml.hbs": "packages:\n - \"apps/*\"\n - \"packages/*\"\n",
@@ -1,7 +1,18 @@
1
+ import type { Metadata } from 'next'
2
+
3
+ export const metadata: Metadata = {
4
+ title: '{{projectName}} - Marketing',
5
+ description: 'Built with Payload CMS and Next.js',
6
+ }
7
+
1
8
  export default function FrontendLayout({
2
9
  children,
3
10
  }: {
4
11
  children: React.ReactNode
5
12
  }) {
6
- return <>{children}</>
13
+ return (
14
+ <html lang="en">
15
+ <body>{children}</body>
16
+ </html>
17
+ )
7
18
  }
@@ -1,6 +1,7 @@
1
1
  import type { Metadata } from 'next'
2
2
  import config from '@/payload.config'
3
- import { RootLayout } from '@payloadcms/next/layouts'
3
+ import { RootLayout, handleServerFunctions } from '@payloadcms/next/layouts'
4
+ import type { ServerFunctionClient } from 'payload'
4
5
  import { importMap } from './importMap.js'
5
6
  import './custom.scss'
6
7
 
@@ -12,7 +13,12 @@ type LayoutArgs = {
12
13
  children: React.ReactNode
13
14
  }
14
15
 
16
+ const serverFunction: ServerFunctionClient = async function (args) {
17
+ 'use server'
18
+ return handleServerFunctions({ ...args, config, importMap })
19
+ }
20
+
15
21
  const Layout = ({ children }: LayoutArgs) =>
16
- RootLayout({ children, config, importMap })
22
+ RootLayout({ children, config, importMap, serverFunction })
17
23
 
18
24
  export default Layout
@@ -1,19 +1,11 @@
1
- import type { Metadata } from 'next'
2
1
  import './globals.css'
3
2
 
4
- export const metadata: Metadata = {
5
- title: 'Marketing Site',
6
- description: 'Built with Payload CMS',
7
- }
8
-
3
+ // Root layout is minimal - each route group has its own complete layout
4
+ // This prevents nested <html>/<body> tags between frontend and Payload admin
9
5
  export default function RootLayout({
10
6
  children,
11
7
  }: {
12
8
  children: React.ReactNode
13
9
  }) {
14
- return (
15
- <html lang="en">
16
- <body>{children}</body>
17
- </html>
18
- )
10
+ return children
19
11
  }
@@ -5,6 +5,7 @@ import { postgresAdapter } from '@payloadcms/db-postgres'
5
5
  import { lexicalEditor } from '@payloadcms/richtext-lexical'
6
6
  import { seoPlugin } from '@payloadcms/plugin-seo'
7
7
  import { resendAdapter } from '@payloadcms/email-resend'
8
+ import sharp from 'sharp'
8
9
  {{#if (eq integrations.payloadStorage 's3')}}
9
10
  import { s3Storage } from '@payloadcms/storage-s3'
10
11
  {{/if}}
@@ -111,6 +112,7 @@ export default buildConfig({
111
112
  }),
112
113
  ],
113
114
  secret: process.env.PAYLOAD_SECRET!,
115
+ sharp,
114
116
  typescript: {
115
117
  outputFile: path.resolve(__dirname, 'payload-types.ts'),
116
118
  },