kofi-stack-template-generator 2.1.46 → 2.1.47

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.
@@ -1,9 +1,9 @@
1
1
 
2
- > kofi-stack-template-generator@2.1.46 build /Users/theodenanyoh/Documents/Krumalabs/create-kofi-stack-v2/packages/template-generator
2
+ > kofi-stack-template-generator@2.1.47 build /Users/theodenanyoh/Documents/Krumalabs/create-kofi-stack-v2/packages/template-generator
3
3
  > pnpm run prebuild && tsup src/index.ts --format esm --dts
4
4
 
5
5
 
6
- > kofi-stack-template-generator@2.1.46 prebuild /Users/theodenanyoh/Documents/Krumalabs/create-kofi-stack-v2/packages/template-generator
6
+ > kofi-stack-template-generator@2.1.47 prebuild /Users/theodenanyoh/Documents/Krumalabs/create-kofi-stack-v2/packages/template-generator
7
7
  > node scripts/generate-templates.js
8
8
 
9
9
  Generating templates.generated.ts...
@@ -14,7 +14,7 @@ CLI tsup v8.5.1
14
14
  CLI Target: es2022
15
15
  ESM Build start
16
16
  ESM dist/index.js 2.27 MB
17
- ESM ⚡️ Build success in 64ms
17
+ ESM ⚡️ Build success in 57ms
18
18
  DTS Build start
19
- DTS ⚡️ Build success in 498ms
19
+ DTS ⚡️ Build success in 519ms
20
20
  DTS dist/index.d.ts 2.96 KB
package/dist/index.js CHANGED
@@ -1417,13 +1417,13 @@ GCS_CREDENTIALS="{}" # JSON service account key
1417
1417
  "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",
1418
1418
  "marketing/payload/package.json.hbs": '{\n "name": "@repo/marketing",\n "version": "0.1.0",\n "private": true,\n "type": "module",\n "scripts": {\n "build": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap && next build",\n "dev": "cross-env NODE_OPTIONS=--no-deprecation next dev --port 3000",\n "generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",\n "generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",\n "lint": "biome check .",\n "lint:fix": "biome check --write .",\n "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",\n "start": "cross-env NODE_OPTIONS=--no-deprecation next start",\n "typecheck": "tsc --noEmit",\n "db:push": "payload migrate",\n "db:seed": "tsx src/seed.ts"\n },\n "dependencies": {\n "@payloadcms/admin-bar": "^3.0.0",\n "@payloadcms/db-postgres": "^3.0.0",\n "@payloadcms/email-resend": "^3.0.0",\n "@payloadcms/live-preview-react": "^3.0.0",\n "@payloadcms/next": "^3.0.0",\n "@payloadcms/plugin-form-builder": "^3.0.0",\n "@payloadcms/plugin-nested-docs": "^3.0.0",\n "@payloadcms/plugin-redirects": "^3.0.0",\n "@payloadcms/plugin-search": "^3.0.0",\n "@payloadcms/plugin-seo": "^3.0.0",\n "@payloadcms/richtext-lexical": "^3.0.0",\n "@payloadcms/storage-vercel-blob": "^3.0.0",\n "@payloadcms/ui": "^3.0.0",\n "@radix-ui/react-accordion": "^1.2.0",\n "@radix-ui/react-checkbox": "^1.0.4",\n "@radix-ui/react-label": "^2.0.2",\n "@radix-ui/react-select": "^2.0.0",\n "@radix-ui/react-slot": "^1.0.2",\n "@repo/ui": "workspace:*",\n "class-variance-authority": "^0.7.1",\n "clsx": "^2.1.1",\n "cross-env": "^7.0.3",\n "geist": "^1.3.0",\n "lucide-react": "^0.562.0",\n "next": "^15.4.10",\n "payload": "^3.70.0",\n "posthog-js": "^1.200.0",\n "prism-react-renderer": "^2.4.1",\n "react": "^19.0.0",\n "react-dom": "^19.0.0",\n "react-hook-form": "^7.71.1",\n "sharp": "^0.34.0",\n "stripe": "^17.7.0",\n "tailwind-merge": "^3.4.0"\n },\n "devDependencies": {\n "@repo/config-typescript": "workspace:*",\n "@tailwindcss/postcss": "^4.0.0",\n "@tailwindcss/typography": "^0.5.19",\n "@types/node": "^20.0.0",\n "@types/react": "^19.0.0",\n "@types/react-dom": "^19.0.0",\n "postcss": "^8.4.0",\n "sass": "^1.86.0",\n "tailwindcss": "^4.0.0",\n "tsx": "^4.0.0",\n "tw-animate-css": "^1.4.0",\n "typescript": "^5.0.0"\n }\n}\n',
1419
1419
  "marketing/payload/postcss.config.mjs.hbs": "export default {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n}\n",
1420
- "marketing/payload/src/Footer/Component.client.tsx": '"use client"\n\nimport Link from "next/link"\nimport type React from "react"\nimport { useState } from "react"\n\nimport type { Footer, Page, Post } from "@/payload-types"\n\nimport { Logo } from "@/components/Logo/Logo"\nimport { Button } from "@/components/ui/button"\nimport { Input } from "@/components/ui/input"\nimport { ThemeSelector } from "@/providers/Theme/ThemeSelector"\nimport { cn } from "@/utilities/ui"\n\ninterface FooterClientProps {\n data: Footer\n}\n\n// Social icons as inline SVGs for flexibility\nconst SocialIcons = {\n twitter: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />\n </svg>\n ),\n instagram: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z"\n clipRule="evenodd"\n />\n </svg>\n ),\n linkedin: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />\n </svg>\n ),\n github: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"\n clipRule="evenodd"\n />\n </svg>\n ),\n youtube: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M19.812 5.418c.861.23 1.538.907 1.768 1.768C21.998 8.746 22 12 22 12s0 3.255-.418 4.814a2.504 2.504 0 0 1-1.768 1.768c-1.56.419-7.814.419-7.814.419s-6.255 0-7.814-.419a2.505 2.505 0 0 1-1.768-1.768C2 15.255 2 12 2 12s0-3.255.417-4.814a2.507 2.507 0 0 1 1.768-1.768C5.744 5 11.998 5 11.998 5s6.255 0 7.814.418ZM15.194 12 10 15V9l5.194 3Z"\n clipRule="evenodd"\n />\n </svg>\n ),\n}\n\n// Helper function to get URL from link\nfunction getLinkUrl(link: {\n type?: ("reference" | "custom") | null\n reference?:\n | { relationTo: "pages"; value: number | Page }\n | { relationTo: "posts"; value: number | Post }\n | null\n url?: string | null\n}): string {\n if (link.type === "reference" && link.reference) {\n const value = link.reference.value\n if (typeof value === "object" && "slug" in value) {\n const prefix = link.reference.relationTo === "posts" ? "/posts" : ""\n return `${prefix}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\nexport const FooterClient: React.FC<FooterClientProps> = ({ data }) => {\n const currentYear = new Date().getFullYear()\n const [email, setEmail] = useState("")\n const [isSubmitting, setIsSubmitting] = useState(false)\n const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null)\n\n const { columns, socialLinks, newsletter, copyrightText, bottomLinks } = data\n\n const handleNewsletterSubmit = async (e: React.FormEvent) => {\n e.preventDefault()\n if (!email || isSubmitting) return\n\n setIsSubmitting(true)\n setMessage(null)\n\n try {\n const response = await fetch("/api/newsletter", {\n method: "POST",\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify({ email }),\n })\n\n const result = await response.json()\n\n if (result.success) {\n setMessage({ type: "success", text: result.message })\n setEmail("")\n } else {\n setMessage({ type: "error", text: result.message })\n }\n } catch {\n setMessage({ type: "error", text: "Something went wrong. Please try again." })\n } finally {\n setIsSubmitting(false)\n }\n }\n\n // Check if any social links are configured\n const hasSocialLinks =\n socialLinks?.twitter ||\n socialLinks?.instagram ||\n socialLinks?.linkedin ||\n socialLinks?.github ||\n socialLinks?.youtube\n\n return (\n <footer className="mt-auto border-t border-border bg-background">\n {/* Main Footer Content */}\n <div className="container mx-auto px-4 py-12 lg:py-16">\n <div className="grid grid-cols-2 gap-8 md:grid-cols-3 lg:grid-cols-6">\n {/* Link Columns */}\n {columns?.map((column) => (\n <div key={column.id || column.title} className="col-span-1">\n <h3 className="mb-4 text-sm font-medium text-foreground">{column.title}</h3>\n <ul className="space-y-3">\n {column.links?.map((linkItem) => (\n <li key={linkItem.id || linkItem.link.label}>\n <Link\n href={getLinkUrl(linkItem.link)}\n className="text-sm text-muted-foreground transition-colors hover:text-foreground"\n {...(linkItem.link.newTab\n ? { target: "_blank", rel: "noopener noreferrer" }\n : {})}\n >\n {linkItem.link.label}\n </Link>\n </li>\n ))}\n </ul>\n </div>\n ))}\n\n {/* Newsletter Column */}\n {newsletter?.enabled && (\n <div className="col-span-2 md:col-span-3 lg:col-span-2">\n <h3 className="mb-4 text-sm font-medium text-foreground">\n {newsletter.title || "Newsletter"}\n </h3>\n <p className="mb-4 text-sm text-muted-foreground">\n {newsletter.description || "Stay up to date with the latest updates."}\n </p>\n <form\n onSubmit={handleNewsletterSubmit}\n className="space-y-3"\n suppressHydrationWarning\n >\n <div className="flex gap-2">\n <Input\n type="email"\n placeholder={newsletter.placeholder || "Enter your email"}\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n className="flex-1"\n disabled={isSubmitting}\n required\n suppressHydrationWarning\n />\n <Button type="submit" disabled={isSubmitting} suppressHydrationWarning>\n {isSubmitting ? "..." : newsletter.buttonText || "Subscribe"}\n </Button>\n </div>\n {message && (\n <p\n className={cn(\n "text-sm",\n message.type === "success" ? "text-green-600" : "text-red-600",\n )}\n >\n {message.text}\n </p>\n )}\n </form>\n </div>\n )}\n </div>\n\n {/* Social Links */}\n {hasSocialLinks && (\n <div className="mt-10 flex items-center gap-4 border-t border-border pt-8">\n {socialLinks?.twitter && (\n <a\n href={socialLinks.twitter}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="X (Twitter)"\n >\n {SocialIcons.twitter}\n </a>\n )}\n {socialLinks?.instagram && (\n <a\n href={socialLinks.instagram}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="Instagram"\n >\n {SocialIcons.instagram}\n </a>\n )}\n {socialLinks?.linkedin && (\n <a\n href={socialLinks.linkedin}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="LinkedIn"\n >\n {SocialIcons.linkedin}\n </a>\n )}\n {socialLinks?.github && (\n <a\n href={socialLinks.github}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="GitHub"\n >\n {SocialIcons.github}\n </a>\n )}\n {socialLinks?.youtube && (\n <a\n href={socialLinks.youtube}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="YouTube"\n >\n {SocialIcons.youtube}\n </a>\n )}\n </div>\n )}\n </div>\n\n {/* Bottom Bar */}\n <div className="border-t border-border">\n <div className="container mx-auto flex flex-col items-center justify-between gap-4 px-4 py-6 sm:flex-row">\n {/* Logo and Copyright */}\n <div className="flex items-center gap-4">\n <Link href="/" className="flex items-center">\n <Logo variant="auto" className="h-6 w-6" />\n </Link>\n <p className="text-sm text-muted-foreground">\n \xA9 {currentYear} {copyrightText || "DirectoryHub"}\n </p>\n </div>\n\n {/* Bottom Links and Theme Selector */}\n <div className="flex items-center gap-6">\n {bottomLinks?.map((linkItem) => (\n <Link\n key={linkItem.id || linkItem.link.label}\n href={getLinkUrl(linkItem.link)}\n className="text-sm text-muted-foreground transition-colors hover:text-foreground"\n {...(linkItem.link.newTab ? { target: "_blank", rel: "noopener noreferrer" } : {})}\n >\n {linkItem.link.label}\n </Link>\n ))}\n <ThemeSelector />\n </div>\n </div>\n </div>\n </footer>\n )\n}\n',
1421
- "marketing/payload/src/Footer/Component.tsx": 'import { getCachedGlobal } from "@/utilities/getGlobals"\n\nimport type { Footer as FooterType } from "@/payload-types"\n\nimport { FooterClient } from "./Component.client"\n\nexport async function Footer() {\n const footerData: FooterType = await getCachedGlobal("footer", 1)()\n\n return <FooterClient data={footerData} />\n}\n',
1420
+ "marketing/payload/src/Footer/Component.client.tsx": '"use client"\n\nimport Link from "next/link"\nimport type React from "react"\nimport { useState } from "react"\n\nimport type { Footer, Page, Post } from "@/payload-types"\n\nimport { Logo } from "@/components/Logo/Logo"\nimport { Button } from "@/components/ui/button"\nimport { Input } from "@/components/ui/input"\nimport { ThemeSelector } from "@/providers/Theme/ThemeSelector"\nimport { cn } from "@/utilities/ui"\n\ninterface FooterClientProps {\n data: Footer | null\n}\n\n// Social icons as inline SVGs for flexibility\nconst SocialIcons = {\n twitter: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />\n </svg>\n ),\n instagram: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z"\n clipRule="evenodd"\n />\n </svg>\n ),\n linkedin: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />\n </svg>\n ),\n github: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"\n clipRule="evenodd"\n />\n </svg>\n ),\n youtube: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M19.812 5.418c.861.23 1.538.907 1.768 1.768C21.998 8.746 22 12 22 12s0 3.255-.418 4.814a2.504 2.504 0 0 1-1.768 1.768c-1.56.419-7.814.419-7.814.419s-6.255 0-7.814-.419a2.505 2.505 0 0 1-1.768-1.768C2 15.255 2 12 2 12s0-3.255.417-4.814a2.507 2.507 0 0 1 1.768-1.768C5.744 5 11.998 5 11.998 5s6.255 0 7.814.418ZM15.194 12 10 15V9l5.194 3Z"\n clipRule="evenodd"\n />\n </svg>\n ),\n}\n\n// Helper function to get URL from link\nfunction getLinkUrl(link: {\n type?: ("reference" | "custom") | null\n reference?:\n | { relationTo: "pages"; value: number | Page }\n | { relationTo: "posts"; value: number | Post }\n | null\n url?: string | null\n}): string {\n if (link.type === "reference" && link.reference) {\n const value = link.reference.value\n if (typeof value === "object" && "slug" in value) {\n const prefix = link.reference.relationTo === "posts" ? "/posts" : ""\n return `${prefix}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\nexport const FooterClient: React.FC<FooterClientProps> = ({ data }) => {\n const currentYear = new Date().getFullYear()\n const [email, setEmail] = useState("")\n const [isSubmitting, setIsSubmitting] = useState(false)\n const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null)\n\n // Show minimal footer when database isn\'t initialized yet\n if (!data) {\n return (\n <footer className="mt-auto border-t border-border bg-background">\n <div className="container mx-auto flex flex-col items-center justify-between gap-4 px-4 py-6 sm:flex-row">\n <p className="text-sm text-muted-foreground">\n Site not configured yet. Visit <Link href="/admin" className="underline hover:text-foreground">/admin</Link> to set up.\n </p>\n <ThemeSelector />\n </div>\n </footer>\n )\n }\n\n const { columns, socialLinks, newsletter, copyrightText, bottomLinks } = data\n\n const handleNewsletterSubmit = async (e: React.FormEvent) => {\n e.preventDefault()\n if (!email || isSubmitting) return\n\n setIsSubmitting(true)\n setMessage(null)\n\n try {\n const response = await fetch("/api/newsletter", {\n method: "POST",\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify({ email }),\n })\n\n const result = await response.json()\n\n if (result.success) {\n setMessage({ type: "success", text: result.message })\n setEmail("")\n } else {\n setMessage({ type: "error", text: result.message })\n }\n } catch {\n setMessage({ type: "error", text: "Something went wrong. Please try again." })\n } finally {\n setIsSubmitting(false)\n }\n }\n\n // Check if any social links are configured\n const hasSocialLinks =\n socialLinks?.twitter ||\n socialLinks?.instagram ||\n socialLinks?.linkedin ||\n socialLinks?.github ||\n socialLinks?.youtube\n\n return (\n <footer className="mt-auto border-t border-border bg-background">\n {/* Main Footer Content */}\n <div className="container mx-auto px-4 py-12 lg:py-16">\n <div className="grid grid-cols-2 gap-8 md:grid-cols-3 lg:grid-cols-6">\n {/* Link Columns */}\n {columns?.map((column) => (\n <div key={column.id || column.title} className="col-span-1">\n <h3 className="mb-4 text-sm font-medium text-foreground">{column.title}</h3>\n <ul className="space-y-3">\n {column.links?.map((linkItem) => (\n <li key={linkItem.id || linkItem.link.label}>\n <Link\n href={getLinkUrl(linkItem.link)}\n className="text-sm text-muted-foreground transition-colors hover:text-foreground"\n {...(linkItem.link.newTab\n ? { target: "_blank", rel: "noopener noreferrer" }\n : {})}\n >\n {linkItem.link.label}\n </Link>\n </li>\n ))}\n </ul>\n </div>\n ))}\n\n {/* Newsletter Column */}\n {newsletter?.enabled && (\n <div className="col-span-2 md:col-span-3 lg:col-span-2">\n <h3 className="mb-4 text-sm font-medium text-foreground">\n {newsletter.title || "Newsletter"}\n </h3>\n <p className="mb-4 text-sm text-muted-foreground">\n {newsletter.description || "Stay up to date with the latest updates."}\n </p>\n <form\n onSubmit={handleNewsletterSubmit}\n className="space-y-3"\n suppressHydrationWarning\n >\n <div className="flex gap-2">\n <Input\n type="email"\n placeholder={newsletter.placeholder || "Enter your email"}\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n className="flex-1"\n disabled={isSubmitting}\n required\n suppressHydrationWarning\n />\n <Button type="submit" disabled={isSubmitting} suppressHydrationWarning>\n {isSubmitting ? "..." : newsletter.buttonText || "Subscribe"}\n </Button>\n </div>\n {message && (\n <p\n className={cn(\n "text-sm",\n message.type === "success" ? "text-green-600" : "text-red-600",\n )}\n >\n {message.text}\n </p>\n )}\n </form>\n </div>\n )}\n </div>\n\n {/* Social Links */}\n {hasSocialLinks && (\n <div className="mt-10 flex items-center gap-4 border-t border-border pt-8">\n {socialLinks?.twitter && (\n <a\n href={socialLinks.twitter}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="X (Twitter)"\n >\n {SocialIcons.twitter}\n </a>\n )}\n {socialLinks?.instagram && (\n <a\n href={socialLinks.instagram}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="Instagram"\n >\n {SocialIcons.instagram}\n </a>\n )}\n {socialLinks?.linkedin && (\n <a\n href={socialLinks.linkedin}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="LinkedIn"\n >\n {SocialIcons.linkedin}\n </a>\n )}\n {socialLinks?.github && (\n <a\n href={socialLinks.github}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="GitHub"\n >\n {SocialIcons.github}\n </a>\n )}\n {socialLinks?.youtube && (\n <a\n href={socialLinks.youtube}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="YouTube"\n >\n {SocialIcons.youtube}\n </a>\n )}\n </div>\n )}\n </div>\n\n {/* Bottom Bar */}\n <div className="border-t border-border">\n <div className="container mx-auto flex flex-col items-center justify-between gap-4 px-4 py-6 sm:flex-row">\n {/* Logo and Copyright */}\n <div className="flex items-center gap-4">\n <Link href="/" className="flex items-center">\n <Logo variant="auto" className="h-6 w-6" />\n </Link>\n <p className="text-sm text-muted-foreground">\n \xA9 {currentYear} {copyrightText || "DirectoryHub"}\n </p>\n </div>\n\n {/* Bottom Links and Theme Selector */}\n <div className="flex items-center gap-6">\n {bottomLinks?.map((linkItem) => (\n <Link\n key={linkItem.id || linkItem.link.label}\n href={getLinkUrl(linkItem.link)}\n className="text-sm text-muted-foreground transition-colors hover:text-foreground"\n {...(linkItem.link.newTab ? { target: "_blank", rel: "noopener noreferrer" } : {})}\n >\n {linkItem.link.label}\n </Link>\n ))}\n <ThemeSelector />\n </div>\n </div>\n </div>\n </footer>\n )\n}\n',
1421
+ "marketing/payload/src/Footer/Component.tsx": 'import { getCachedGlobal } from "@/utilities/getGlobals"\n\nimport type { Footer as FooterType } from "@/payload-types"\n\nimport { FooterClient } from "./Component.client"\n\nexport async function Footer() {\n const footerData = (await getCachedGlobal("footer", 1)()) as FooterType | null\n\n return <FooterClient data={footerData} />\n}\n',
1422
1422
  "marketing/payload/src/Footer/RowLabel.tsx": '"use client"\nimport type { Footer } from "@/payload-types"\nimport { type RowLabelProps, useRowLabel } from "@payloadcms/ui"\n\ntype FooterLink = NonNullable<NonNullable<Footer["columns"]>[number]["links"]>[number]\n\nexport const RowLabel: React.FC<RowLabelProps> = () => {\n const data = useRowLabel<FooterLink>()\n\n const label = data?.data?.link?.label\n ? `Link ${data.rowNumber !== undefined ? data.rowNumber + 1 : ""}: ${data?.data?.link?.label}`\n : "Row"\n\n return <div>{label}</div>\n}\n',
1423
1423
  "marketing/payload/src/Footer/config.ts": 'import type { GlobalConfig } from "payload"\n\nimport { link } from "@/fields/link"\nimport { revalidateFooter } from "./hooks/revalidateFooter"\n\nexport const Footer: GlobalConfig = {\n slug: "footer",\n access: {\n read: () => true,\n },\n fields: [\n // Link Columns - organized groups of links\n {\n name: "columns",\n type: "array",\n label: "Link Columns",\n maxRows: 5,\n admin: {\n initCollapsed: true,\n description: "Add columns of links (e.g., Solutions, Resources, Company)",\n },\n fields: [\n {\n name: "title",\n type: "text",\n required: true,\n label: "Column Title",\n },\n {\n name: "links",\n type: "array",\n label: "Links",\n maxRows: 10,\n fields: [\n link({\n appearances: false,\n }),\n ],\n admin: {\n initCollapsed: true,\n },\n },\n ],\n },\n // Social Links\n {\n name: "socialLinks",\n type: "group",\n label: "Social Links",\n admin: {\n description: "Add your social media profile URLs",\n },\n fields: [\n {\n name: "twitter",\n type: "text",\n label: "X (Twitter)",\n admin: {\n placeholder: "https://x.com/yourhandle",\n },\n },\n {\n name: "instagram",\n type: "text",\n label: "Instagram",\n admin: {\n placeholder: "https://instagram.com/yourhandle",\n },\n },\n {\n name: "linkedin",\n type: "text",\n label: "LinkedIn",\n admin: {\n placeholder: "https://linkedin.com/company/yourcompany",\n },\n },\n {\n name: "github",\n type: "text",\n label: "GitHub",\n admin: {\n placeholder: "https://github.com/yourorg",\n },\n },\n {\n name: "youtube",\n type: "text",\n label: "YouTube",\n admin: {\n placeholder: "https://youtube.com/@yourchannel",\n },\n },\n ],\n },\n // Newsletter Section\n {\n name: "newsletter",\n type: "group",\n label: "Newsletter",\n admin: {\n description: "Configure the newsletter signup section",\n },\n fields: [\n {\n name: "enabled",\n type: "checkbox",\n label: "Enable Newsletter Signup",\n defaultValue: true,\n },\n {\n name: "title",\n type: "text",\n label: "Title",\n defaultValue: "Newsletter",\n admin: {\n condition: (_, siblingData) => siblingData?.enabled,\n },\n },\n {\n name: "description",\n type: "textarea",\n label: "Description",\n defaultValue: "Stay up to date with the latest updates and news.",\n admin: {\n condition: (_, siblingData) => siblingData?.enabled,\n },\n },\n {\n name: "buttonText",\n type: "text",\n label: "Button Text",\n defaultValue: "Subscribe",\n admin: {\n condition: (_, siblingData) => siblingData?.enabled,\n },\n },\n {\n name: "placeholder",\n type: "text",\n label: "Email Placeholder",\n defaultValue: "Enter your email",\n admin: {\n condition: (_, siblingData) => siblingData?.enabled,\n },\n },\n ],\n },\n // Copyright and Bottom Bar\n {\n name: "copyrightText",\n type: "text",\n label: "Copyright Text",\n defaultValue: "DirectoryHub",\n admin: {\n description: "Company name for copyright (year is added automatically)",\n },\n },\n {\n name: "bottomLinks",\n type: "array",\n label: "Bottom Bar Links",\n maxRows: 4,\n admin: {\n description: "Links shown in the bottom bar (e.g., Contact Support, Privacy Policy)",\n initCollapsed: true,\n },\n fields: [\n link({\n appearances: false,\n }),\n ],\n },\n ],\n hooks: {\n afterChange: [revalidateFooter],\n },\n}\n',
1424
1424
  "marketing/payload/src/Footer/hooks/revalidateFooter.ts": 'import type { GlobalAfterChangeHook } from "payload"\n\nimport { revalidateTag } from "next/cache"\n\nexport const revalidateFooter: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {\n if (!context.disableRevalidate) {\n payload.logger.info("Revalidating footer")\n\n revalidateTag("global_footer")\n }\n\n return doc\n}\n',
1425
- "marketing/payload/src/Header/Component.client.tsx": '"use client"\nimport { useHeaderTheme } from "@/providers/HeaderTheme"\nimport Link from "next/link"\nimport type React from "react"\nimport { useEffect, useMemo, useState } from "react"\n\nimport type { Header, Page, Post } from "@/payload-types"\n\nimport { Logo } from "@/components/Logo/Logo"\nimport { Button } from "@/components/ui/button"\nimport { MobileMenu } from "./MobileMenu"\nimport { HeaderNav } from "./Nav"\n\ninterface HeaderClientProps {\n data: Header\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n type?: "custom" | "reference" | null\n reference?: {\n relationTo: "pages" | "posts"\n value: Page | Post | string | number\n } | null\n url?: string | null\n}): string => {\n if (link.type === "reference" && link.reference) {\n const { relationTo, value } = link.reference\n if (typeof value === "object" && value.slug) {\n return relationTo === "pages" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\nexport const HeaderClient: React.FC<HeaderClientProps> = ({ data }) => {\n /* Storing the value in a useState to avoid hydration errors */\n const [theme, setTheme] = useState<string | null>(null)\n const { headerTheme, setHeaderTheme } = useHeaderTheme()\n\n useEffect(() => {\n setHeaderTheme(null)\n }, [setHeaderTheme])\n\n useEffect(() => {\n if (headerTheme && headerTheme !== theme) setTheme(headerTheme)\n }, [headerTheme, theme])\n\n // Get the primary CTA (first right-positioned button item) for mobile view\n const primaryCta = useMemo(() => {\n const rightButtonItems = data?.navItems?.filter(\n (item) =>\n (item.position || "left") === "right" &&\n item.type === "link" &&\n (item.appearance || "button") === "button",\n )\n return rightButtonItems?.[0]\n }, [data?.navItems])\n\n return (\n <header\n className="sticky top-0 z-20 border-b border-border bg-background"\n {...(theme ? { "data-theme": theme } : {})}\n >\n <div className="container mx-auto px-4 h-16 flex justify-between items-center">\n {/* Left section: Logo + Left Nav Links */}\n <div className="flex items-center gap-8">\n <Link href="/" className="flex items-center gap-2">\n <Logo loading="eager" priority="high" variant="auto" />\n <span className="text-xl font-semibold hidden sm:inline">DirectoryHub</span>\n </Link>\n {/* Left nav - hidden on tablet and below, visible on desktop */}\n <HeaderNav data={data} position="left" className="hidden lg:flex gap-6 items-center" />\n </div>\n\n {/* Right section: Right Nav Links (CTAs) */}\n <div className="flex items-center gap-3">\n {/* Full right nav - hidden on mobile and tablet, visible on desktop */}\n <HeaderNav data={data} position="right" className="hidden lg:flex gap-4 items-center" />\n\n {/* Primary CTA for tablet/mobile - visible on tablet and below, hidden on desktop */}\n {primaryCta?.link && (\n <Button asChild className="lg:hidden" size="sm">\n <Link href={getLinkHref(primaryCta.link)}>{primaryCta.label}</Link>\n </Button>\n )}\n\n {/* Mobile menu hamburger - visible on tablet and below */}\n <MobileMenu data={data} />\n </div>\n </div>\n </header>\n )\n}\n',
1426
- "marketing/payload/src/Header/Component.tsx": 'import { getCachedGlobal } from "@/utilities/getGlobals"\nimport { HeaderClient } from "./Component.client"\n\nimport type { Header as HeaderType } from "@/payload-types"\n\nexport async function Header() {\n const headerData: HeaderType = await getCachedGlobal("header", 1)()\n\n return <HeaderClient data={headerData} />\n}\n',
1425
+ "marketing/payload/src/Header/Component.client.tsx": '"use client"\nimport { useHeaderTheme } from "@/providers/HeaderTheme"\nimport Link from "next/link"\nimport type React from "react"\nimport { useEffect, useMemo, useState } from "react"\n\nimport type { Header, Page, Post } from "@/payload-types"\n\nimport { Logo } from "@/components/Logo/Logo"\nimport { Button } from "@/components/ui/button"\nimport { MobileMenu } from "./MobileMenu"\nimport { HeaderNav } from "./Nav"\n\ninterface HeaderClientProps {\n data: Header | null\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n type?: "custom" | "reference" | null\n reference?: {\n relationTo: "pages" | "posts"\n value: Page | Post | string | number\n } | null\n url?: string | null\n}): string => {\n if (link.type === "reference" && link.reference) {\n const { relationTo, value } = link.reference\n if (typeof value === "object" && value.slug) {\n return relationTo === "pages" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\nexport const HeaderClient: React.FC<HeaderClientProps> = ({ data }) => {\n /* Storing the value in a useState to avoid hydration errors */\n const [theme, setTheme] = useState<string | null>(null)\n const { headerTheme, setHeaderTheme } = useHeaderTheme()\n\n useEffect(() => {\n setHeaderTheme(null)\n }, [setHeaderTheme])\n\n useEffect(() => {\n if (headerTheme && headerTheme !== theme) setTheme(headerTheme)\n }, [headerTheme, theme])\n\n // Get the primary CTA (first right-positioned button item) for mobile view\n const primaryCta = useMemo(() => {\n const rightButtonItems = data?.navItems?.filter(\n (item) =>\n (item.position || "left") === "right" &&\n item.type === "link" &&\n (item.appearance || "button") === "button",\n )\n return rightButtonItems?.[0]\n }, [data?.navItems])\n\n // Show setup header when database isn\'t initialized yet\n if (!data) {\n return (\n <header className="sticky top-0 z-20 border-b border-border bg-background">\n <div className="container mx-auto px-4 h-16 flex justify-between items-center">\n <div className="flex items-center gap-2">\n <span className="text-xl font-semibold">Welcome</span>\n </div>\n <Button asChild size="sm">\n <Link href="/admin">Setup Site \u2192</Link>\n </Button>\n </div>\n </header>\n )\n }\n\n return (\n <header\n className="sticky top-0 z-20 border-b border-border bg-background"\n {...(theme ? { "data-theme": theme } : {})}\n >\n <div className="container mx-auto px-4 h-16 flex justify-between items-center">\n {/* Left section: Logo + Left Nav Links */}\n <div className="flex items-center gap-8">\n <Link href="/" className="flex items-center gap-2">\n <Logo loading="eager" priority="high" variant="auto" />\n <span className="text-xl font-semibold hidden sm:inline">DirectoryHub</span>\n </Link>\n {/* Left nav - hidden on tablet and below, visible on desktop */}\n <HeaderNav data={data} position="left" className="hidden lg:flex gap-6 items-center" />\n </div>\n\n {/* Right section: Right Nav Links (CTAs) */}\n <div className="flex items-center gap-3">\n {/* Full right nav - hidden on mobile and tablet, visible on desktop */}\n <HeaderNav data={data} position="right" className="hidden lg:flex gap-4 items-center" />\n\n {/* Primary CTA for tablet/mobile - visible on tablet and below, hidden on desktop */}\n {primaryCta?.link && (\n <Button asChild className="lg:hidden" size="sm">\n <Link href={getLinkHref(primaryCta.link)}>{primaryCta.label}</Link>\n </Button>\n )}\n\n {/* Mobile menu hamburger - visible on tablet and below */}\n <MobileMenu data={data} />\n </div>\n </div>\n </header>\n )\n}\n',
1426
+ "marketing/payload/src/Header/Component.tsx": 'import { getCachedGlobal } from "@/utilities/getGlobals"\nimport { HeaderClient } from "./Component.client"\n\nimport type { Header as HeaderType } from "@/payload-types"\n\nexport async function Header() {\n const headerData = (await getCachedGlobal("header", 1)()) as HeaderType | null\n\n return <HeaderClient data={headerData} />\n}\n',
1427
1427
  "marketing/payload/src/Header/MegaMenu/index.tsx": '"use client"\n\nimport { Media } from "@/components/Media"\nimport { cn } from "@/utilities/ui"\nimport {\n BarChart3,\n Building,\n ChevronDown,\n Database,\n DollarSign,\n Globe,\n Layers,\n Layout,\n type LucideIcon,\n Rocket,\n Search,\n Settings,\n Shield,\n Store,\n Target,\n Users,\n Zap,\n} from "lucide-react"\nimport Link from "next/link"\nimport type React from "react"\nimport { useState } from "react"\n\nimport type { Header, Media as MediaType, Page, Post } from "@/payload-types"\n\ntype NavItem = NonNullable<Header["navItems"]>[number]\n\nconst iconMap: Record<string, LucideIcon> = {\n layout: Layout,\n dollarSign: DollarSign,\n search: Search,\n settings: Settings,\n zap: Zap,\n layers: Layers,\n users: Users,\n building: Building,\n globe: Globe,\n store: Store,\n rocket: Rocket,\n target: Target,\n barChart: BarChart3,\n shield: Shield,\n database: Database,\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n type?: "custom" | "reference" | null\n reference?: {\n relationTo: "pages" | "posts"\n value: Page | Post | string | number\n } | null\n url?: string | null\n}): string => {\n if (link.type === "reference" && link.reference) {\n const { relationTo, value } = link.reference\n if (typeof value === "object" && value.slug) {\n return relationTo === "pages" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\ninterface MegaMenuProps {\n item: NavItem\n}\n\nexport const MegaMenu: React.FC<MegaMenuProps> = ({ item }) => {\n const [isOpen, setIsOpen] = useState(false)\n\n const columns = item.megaMenuColumns || []\n const featuredItem = item.featuredItem\n\n return (\n <div\n className="relative"\n onMouseEnter={() => setIsOpen(true)}\n onMouseLeave={() => setIsOpen(false)}\n >\n {/* Trigger */}\n <button\n type="button"\n className={cn(\n "flex items-center gap-1 text-sm font-medium transition-colors hover:text-primary",\n isOpen && "text-primary",\n )}\n onClick={() => setIsOpen(!isOpen)}\n >\n {item.label}\n <ChevronDown\n className={cn("h-4 w-4 transition-transform duration-200", isOpen && "rotate-180")}\n />\n </button>\n\n {/* Dropdown */}\n {isOpen && (\n <div className="absolute left-1/2 -translate-x-1/2 top-full pt-4 z-50">\n <div\n className={cn(\n "bg-background border border-border rounded-xl shadow-xl overflow-hidden",\n "animate-in fade-in-0 zoom-in-95 duration-200",\n featuredItem?.enabled ? "min-w-[700px]" : "min-w-[500px]",\n )}\n >\n <div className="flex">\n {/* Menu Columns */}\n <div className={cn("flex-1 p-6", columns.length > 1 ? "grid grid-cols-2 gap-8" : "")}>\n {columns.map((column) => (\n <div key={column.columnLabel || "column"}>\n {column.columnLabel && (\n <div className="mb-4">\n <h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">\n {column.columnLabel}\n </h3>\n {column.columnDescription && (\n <p className="text-xs text-muted-foreground mt-1">\n {column.columnDescription}\n </p>\n )}\n </div>\n )}\n <ul className="space-y-1">\n {column.items?.map((menuItem) => {\n const Icon =\n menuItem.icon && menuItem.icon !== "none" ? iconMap[menuItem.icon] : null\n const href = menuItem.link ? getLinkHref(menuItem.link) : "#"\n\n return (\n <li key={menuItem.label}>\n <Link\n href={href}\n className="flex items-start gap-3 p-2 rounded-lg hover:bg-muted transition-colors group"\n onClick={() => setIsOpen(false)}\n >\n {Icon && (\n <div className="flex-shrink-0 w-9 h-9 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">\n <Icon className="w-4 h-4 text-primary" />\n </div>\n )}\n <div className="flex-1 min-w-0">\n <div className="text-sm font-medium group-hover:text-primary transition-colors">\n {menuItem.label}\n </div>\n {menuItem.description && (\n <div className="text-xs text-muted-foreground mt-0.5 line-clamp-2">\n {menuItem.description}\n </div>\n )}\n </div>\n </Link>\n </li>\n )\n })}\n </ul>\n </div>\n ))}\n </div>\n\n {/* Featured Section */}\n {featuredItem?.enabled && (\n <div className="w-64 bg-muted/50 p-6 border-l border-border">\n {featuredItem.image && typeof featuredItem.image === "object" && (\n <div className="rounded-lg overflow-hidden mb-4 aspect-video">\n <Media\n resource={featuredItem.image as MediaType}\n imgClassName="w-full h-full object-cover"\n />\n </div>\n )}\n {featuredItem.heading && (\n <h4 className="font-semibold text-sm mb-2">{featuredItem.heading}</h4>\n )}\n {featuredItem.description && (\n <p className="text-xs text-muted-foreground mb-4">{featuredItem.description}</p>\n )}\n {featuredItem.link && (\n <Link\n href={getLinkHref(featuredItem.link)}\n className="text-xs font-medium text-primary hover:underline"\n onClick={() => setIsOpen(false)}\n >\n {featuredItem.link.label || "Learn more"} \u2192\n </Link>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n )\n}\n',
1428
1428
  "marketing/payload/src/Header/MobileMenu/HamburgerIcon.tsx": '"use client"\n\nimport { cn } from "@/utilities/ui"\nimport type React from "react"\n\ninterface HamburgerIconProps {\n isOpen: boolean\n onClick: () => void\n className?: string\n}\n\n/**\n * Animated 2-line hamburger icon that morphs into an X when open\n */\nexport const HamburgerIcon: React.FC<HamburgerIconProps> = ({ isOpen, onClick, className }) => {\n return (\n <button\n type="button"\n onClick={onClick}\n className={cn(\n "relative w-8 h-8 flex items-center justify-center focus:outline-none",\n "lg:hidden", // Only show on tablet and below\n className,\n )}\n aria-label={isOpen ? "Close menu" : "Open menu"}\n aria-expanded={isOpen}\n >\n <div className="relative w-6 h-4 flex flex-col justify-between">\n {/* Top line */}\n <span\n className={cn(\n "absolute left-0 w-full h-0.5 bg-foreground rounded-full",\n "transition-all duration-300 ease-in-out origin-center",\n isOpen ? "top-1/2 -translate-y-1/2 rotate-45" : "top-0 translate-y-0 rotate-0",\n )}\n />\n {/* Bottom line */}\n <span\n className={cn(\n "absolute left-0 w-full h-0.5 bg-foreground rounded-full",\n "transition-all duration-300 ease-in-out origin-center",\n isOpen ? "top-1/2 -translate-y-1/2 -rotate-45" : "bottom-0 translate-y-0 rotate-0",\n )}\n />\n </div>\n </button>\n )\n}\n',
1429
1429
  "marketing/payload/src/Header/MobileMenu/index.tsx": '"use client"\n\nimport { Button } from "@/components/ui/button"\nimport { cn } from "@/utilities/ui"\nimport {\n BarChart3,\n Building,\n ChevronRight,\n Database,\n DollarSign,\n Globe,\n Layers,\n Layout,\n type LucideIcon,\n Rocket,\n Search,\n Settings,\n Shield,\n Store,\n Target,\n Users,\n Zap,\n} from "lucide-react"\nimport Link from "next/link"\nimport type React from "react"\nimport { useEffect, useState } from "react"\n\nimport type { Header, Page, Post } from "@/payload-types"\nimport { HamburgerIcon } from "./HamburgerIcon"\n\nconst iconMap: Record<string, LucideIcon> = {\n layout: Layout,\n dollarSign: DollarSign,\n search: Search,\n settings: Settings,\n zap: Zap,\n layers: Layers,\n users: Users,\n building: Building,\n globe: Globe,\n store: Store,\n rocket: Rocket,\n target: Target,\n barChart: BarChart3,\n shield: Shield,\n database: Database,\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n type?: "custom" | "reference" | null\n reference?: {\n relationTo: "pages" | "posts"\n value: Page | Post | string | number\n } | null\n url?: string | null\n}): string => {\n if (link.type === "reference" && link.reference) {\n const { relationTo, value } = link.reference\n if (typeof value === "object" && value.slug) {\n return relationTo === "pages" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\ninterface MobileMenuProps {\n data: Header\n}\n\nexport const MobileMenu: React.FC<MobileMenuProps> = ({ data }) => {\n const [isOpen, setIsOpen] = useState(false)\n const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set())\n\n const navItems = data?.navItems || []\n const leftItems = navItems.filter((item) => (item.position || "left") === "left")\n const rightItems = navItems.filter((item) => (item.position || "left") === "right")\n\n // Separate CTA items (typically the primary action buttons)\n const ctaItems = rightItems.filter((item) => item.type === "link")\n\n // Prevent body scroll when menu is open\n useEffect(() => {\n if (isOpen) {\n document.body.style.overflow = "hidden"\n } else {\n document.body.style.overflow = ""\n }\n return () => {\n document.body.style.overflow = ""\n }\n }, [isOpen])\n\n const toggleExpanded = (id: string) => {\n setExpandedItems((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }\n\n const closeMenu = () => {\n setIsOpen(false)\n setExpandedItems(new Set())\n }\n\n return (\n <>\n {/* Hamburger button - visible on tablet and below */}\n <HamburgerIcon isOpen={isOpen} onClick={() => setIsOpen(!isOpen)} />\n\n {/* Mobile menu overlay */}\n <div\n className={cn(\n "fixed inset-0 z-50 lg:hidden",\n "transition-opacity duration-300",\n isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none",\n )}\n >\n {/* Backdrop */}\n <div\n className="absolute inset-0 bg-background/80 backdrop-blur-sm"\n onClick={closeMenu}\n onKeyDown={(e) => {\n if (e.key === "Enter" || e.key === " ") {\n e.preventDefault()\n closeMenu()\n }\n }}\n role="button"\n tabIndex={0}\n aria-label="Close menu"\n />\n\n {/* Menu panel */}\n <div\n className={cn(\n "absolute top-0 right-0 h-full w-full max-w-md bg-background border-l border-border",\n "flex flex-col",\n "transition-transform duration-300 ease-out",\n isOpen ? "translate-x-0" : "translate-x-full",\n )}\n >\n {/* Header with close button */}\n <div className="flex items-center justify-between p-4 border-b border-border">\n <Link href="/" onClick={closeMenu} className="text-xl font-semibold">\n DirectoryHub\n </Link>\n <HamburgerIcon isOpen={isOpen} onClick={closeMenu} className="lg:block" />\n </div>\n\n {/* Nav items */}\n <div className="flex-1 overflow-y-auto p-4">\n <nav className="space-y-1">\n {leftItems.map((item, index) => {\n const itemId = item.id || `item-${index}`\n const isExpanded = expandedItems.has(itemId)\n const hasMegaMenu = item.type === "megaMenu"\n\n if (hasMegaMenu) {\n return (\n <div key={itemId}>\n {/* Expandable item */}\n <button\n type="button"\n onClick={() => toggleExpanded(itemId)}\n className={cn(\n "w-full flex items-center justify-between py-4 text-lg font-medium",\n "border-b border-border/50 transition-colors hover:text-primary",\n )}\n >\n <span>{item.label}</span>\n <ChevronRight\n className={cn(\n "w-5 h-5 transition-transform duration-200",\n isExpanded && "rotate-90",\n )}\n />\n </button>\n\n {/* Expanded content */}\n <div\n className={cn(\n "overflow-hidden transition-all duration-300",\n isExpanded ? "max-h-[1000px] opacity-100" : "max-h-0 opacity-0",\n )}\n >\n <div className="py-2 pl-4 space-y-4">\n {item.megaMenuColumns?.map((column, colIndex) => (\n <div key={column.columnLabel || `col-${colIndex}`}>\n {column.columnLabel && (\n <p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-2">\n {column.columnLabel}\n </p>\n )}\n <ul className="space-y-1">\n {column.items?.map((menuItem) => {\n const Icon =\n menuItem.icon && menuItem.icon !== "none"\n ? iconMap[menuItem.icon]\n : null\n const href = menuItem.link ? getLinkHref(menuItem.link) : "#"\n\n return (\n <li key={menuItem.label}>\n <Link\n href={href}\n onClick={closeMenu}\n className="flex items-center gap-3 py-2 text-sm hover:text-primary transition-colors"\n >\n {Icon && <Icon className="w-4 h-4 text-muted-foreground" />}\n <span>{menuItem.label}</span>\n </Link>\n </li>\n )\n })}\n </ul>\n </div>\n ))}\n </div>\n </div>\n </div>\n )\n }\n\n // Simple link item\n const link = item.link\n if (!link) return null\n const href = getLinkHref(link)\n\n return (\n <Link\n key={itemId}\n href={href}\n onClick={closeMenu}\n className={cn(\n "block py-4 text-lg font-medium",\n "border-b border-border/50 transition-colors hover:text-primary",\n )}\n >\n {item.label}\n </Link>\n )\n })}\n </nav>\n </div>\n\n {/* Bottom CTA section */}\n <div className="p-4 border-t border-border space-y-3">\n {ctaItems.map((item, index) => {\n const link = item.link\n if (!link) return null\n const href = getLinkHref(link)\n const itemAppearance = item.appearance || "button"\n const isButton = itemAppearance === "button"\n\n if (isButton) {\n // First button item is primary (filled), others are outline\n const buttonItems = ctaItems.filter((i) => (i.appearance || "button") === "button")\n const buttonIndex = buttonItems.findIndex((i) => i.id === item.id)\n const isPrimary = buttonIndex === 0\n\n return (\n <Button\n key={item.id || `cta-${index}`}\n asChild\n variant={isPrimary ? "default" : "outline"}\n className="w-full"\n size="lg"\n >\n <Link href={href} onClick={closeMenu}>\n {item.label}\n </Link>\n </Button>\n )\n }\n\n // Link appearance - render as text link\n return (\n <Link\n key={item.id || `cta-${index}`}\n href={href}\n onClick={closeMenu}\n className="block w-full text-center py-3 text-sm font-medium hover:text-primary transition-colors"\n >\n {item.label}\n </Link>\n )\n })}\n </div>\n </div>\n </div>\n </>\n )\n}\n',
@@ -10176,7 +10176,7 @@ export const post3: (args: PostArgs) => RequiredDataFromCollectionSlug<"posts">
10176
10176
  "marketing/payload/src/lib/convex.ts": 'import { ConvexHttpClient } from "convex/browser"\n\n/**\n * Convex HTTP client for server-side data fetching in the marketing site.\n * Used for fetching documentation content from Convex.\n */\nconst convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL\n\nif (!convexUrl) {\n console.warn("[Convex] NEXT_PUBLIC_CONVEX_URL is not set. Documentation features will not work.")\n}\n\nexport const convex = new ConvexHttpClient(convexUrl || "")\n',
10177
10177
  "marketing/payload/src/lib/docs-source.ts": 'import { api } from "../../../../convex/_generated/api"\nimport { convex } from "./convex"\n\n/**\n * Fetch published docs from Convex\n */\nexport async function getDocsFromConvex() {\n try {\n if (!process.env.NEXT_PUBLIC_CONVEX_URL) {\n console.error(\n "[Docs] NEXT_PUBLIC_CONVEX_URL environment variable is not set. Docs will not be available.",\n )\n return []\n }\n\n const docs = await convex.query(api.docs.listPublished)\n\n if (process.env.NODE_ENV === "development") {\n console.log(`[Docs] Fetched ${docs.length} published document(s) from Convex`)\n }\n\n return docs\n } catch (error) {\n console.error("[Docs] Failed to fetch docs from Convex:", error)\n return []\n }\n}\n\n/**\n * Fetch a single doc by slug from Convex\n */\nexport async function getDocBySlug(slug: string) {\n try {\n if (!process.env.NEXT_PUBLIC_CONVEX_URL) {\n console.error(\n "[Docs] NEXT_PUBLIC_CONVEX_URL environment variable is not set. Docs will not be available.",\n )\n return null\n }\n\n const doc = await convex.query(api.docs.getBySlug, { slug })\n return doc\n } catch (error) {\n console.error(`[Docs] Failed to fetch doc "${slug}" from Convex:`, error)\n return null\n }\n}\n\n/**\n * Build page tree for navigation from docs\n */\nexport function buildPageTree(docs: Awaited<ReturnType<typeof getDocsFromConvex>>) {\n // Sort docs by order, then alphabetically\n const sortedDocs = [...docs].sort((a, b) => {\n if (a.order !== undefined && b.order !== undefined) {\n return a.order - b.order\n }\n if (a.order !== undefined) return -1\n if (b.order !== undefined) return 1\n return a.title.localeCompare(b.title)\n })\n\n // Group docs by parent\n const topLevel = sortedDocs.filter((d) => !d.parentSlug)\n const byParent = new Map<string, typeof sortedDocs>()\n\n for (const doc of sortedDocs) {\n if (doc.parentSlug) {\n const existing = byParent.get(doc.parentSlug) || []\n existing.push(doc)\n byParent.set(doc.parentSlug, existing)\n }\n }\n\n // Build tree structure\n const buildChildren = (parentSlug: string): PageTreeItem[] => {\n const children = byParent.get(parentSlug) || []\n return children.map((doc) => ({\n type: "page" as const,\n name: doc.title,\n url: `/docs/${doc.slug}`,\n children: buildChildren(doc.slug),\n }))\n }\n\n const tree: PageTreeItem[] = topLevel.map((doc) => {\n const children = buildChildren(doc.slug)\n if (children.length > 0) {\n return {\n type: "folder" as const,\n name: doc.title,\n index: {\n type: "page" as const,\n name: doc.title,\n url: `/docs/${doc.slug}`,\n },\n children,\n }\n }\n return {\n type: "page" as const,\n name: doc.title,\n url: `/docs/${doc.slug}`,\n }\n })\n\n return {\n name: "Documentation",\n children: tree,\n }\n}\n\n// Types for page tree\ntype PageTreeItem =\n | {\n type: "page"\n name: string\n url: string\n children?: PageTreeItem[]\n }\n | {\n type: "folder"\n name: string\n index?: { type: "page"; name: string; url: string }\n children: PageTreeItem[]\n }\n | {\n type: "separator"\n name: string\n }\n\n/**\n * Get all page slugs for static generation\n */\nexport async function getAllDocSlugs(): Promise<string[][]> {\n const docs = await getDocsFromConvex()\n return docs.map((doc) => doc.slug.split("/"))\n}\n',
10178
10178
  "marketing/payload/src/lib/mdx.tsx": 'import { compile, run } from "@mdx-js/mdx"\nimport type { ReactNode } from "react"\nimport { createElement } from "react"\nimport * as runtime from "react/jsx-runtime"\n\ninterface TableOfContentsItem {\n title: string\n url: string\n depth: number\n}\n\n/**\n * Compile MDX content to React components\n */\nexport async function compileMDX(source: string): Promise<{\n content: ReactNode\n toc: TableOfContentsItem[]\n}> {\n // Extract headings for TOC before compilation\n const toc = extractTableOfContents(source)\n\n try {\n // Compile MDX to JavaScript\n const compiled = await compile(source, {\n outputFormat: "function-body",\n development: false,\n })\n\n // Run the compiled code\n const { default: MDXContent } = await run(String(compiled), {\n ...runtime,\n baseUrl: import.meta.url,\n })\n\n // Create the content element with custom components\n const content = createElement(MDXContent, {\n components: getMDXComponents(),\n })\n\n return { content, toc }\n } catch (error) {\n console.error("MDX compilation error:", error)\n\n // Return a fallback with the raw content\n return {\n content: createElement(\n "div",\n { className: "prose dark:prose-invert" },\n createElement("p", { className: "text-red-500" }, "Error rendering content"),\n createElement("pre", { className: "text-sm" }, source),\n ),\n toc,\n }\n }\n}\n\n/**\n * Extract table of contents from markdown headings\n */\nfunction extractTableOfContents(source: string): TableOfContentsItem[] {\n const headingRegex = /^(#{1,6})\\s+(.+)$/gm\n const toc: TableOfContentsItem[] = []\n\n const matches = source.matchAll(headingRegex)\n for (const match of matches) {\n const depth = match[1]?.length ?? 1\n const title = match[2]?.trim() ?? ""\n\n // Create URL-friendly slug\n const url = `#${title\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, "")\n .replace(/\\s+/g, "-")}`\n\n toc.push({ title, url, depth })\n }\n\n return toc\n}\n\n/**\n * Custom MDX components for styling\n */\nfunction getMDXComponents() {\n return {\n h1: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n createElement("h1", {\n ...props,\n className: "scroll-m-20 text-4xl font-bold tracking-tight",\n id: slugify(String(props.children)),\n }),\n h2: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n createElement("h2", {\n ...props,\n className:\n "scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0 mt-10",\n id: slugify(String(props.children)),\n }),\n h3: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n createElement("h3", {\n ...props,\n className: "scroll-m-20 text-2xl font-semibold tracking-tight mt-8",\n id: slugify(String(props.children)),\n }),\n h4: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n createElement("h4", {\n ...props,\n className: "scroll-m-20 text-xl font-semibold tracking-tight mt-6",\n id: slugify(String(props.children)),\n }),\n p: (props: React.HTMLAttributes<HTMLParagraphElement>) =>\n createElement("p", {\n ...props,\n className: "leading-7 [&:not(:first-child)]:mt-6",\n }),\n ul: (props: React.HTMLAttributes<HTMLUListElement>) =>\n createElement("ul", {\n ...props,\n className: "my-6 ml-6 list-disc [&>li]:mt-2",\n }),\n ol: (props: React.HTMLAttributes<HTMLOListElement>) =>\n createElement("ol", {\n ...props,\n className: "my-6 ml-6 list-decimal [&>li]:mt-2",\n }),\n li: (props: React.HTMLAttributes<HTMLLIElement>) => createElement("li", { ...props }),\n blockquote: (props: React.HTMLAttributes<HTMLQuoteElement>) =>\n createElement("blockquote", {\n ...props,\n className: "mt-6 border-l-2 pl-6 italic",\n }),\n code: (props: React.HTMLAttributes<HTMLElement>) => {\n // Check if this is an inline code or a code block\n const isInline = typeof props.children === "string"\n if (isInline) {\n return createElement("code", {\n ...props,\n className:\n "relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold",\n })\n }\n return createElement("code", { ...props })\n },\n pre: (props: React.HTMLAttributes<HTMLPreElement>) =>\n createElement("pre", {\n ...props,\n className: "mb-4 mt-6 overflow-x-auto rounded-lg border bg-zinc-950 py-4 dark:bg-zinc-900",\n }),\n a: (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) =>\n createElement("a", {\n ...props,\n className: "font-medium text-primary underline underline-offset-4",\n }),\n table: (props: React.HTMLAttributes<HTMLTableElement>) =>\n createElement(\n "div",\n { className: "my-6 w-full overflow-y-auto" },\n createElement("table", { ...props, className: "w-full" }),\n ),\n tr: (props: React.HTMLAttributes<HTMLTableRowElement>) =>\n createElement("tr", {\n ...props,\n className: "m-0 border-t p-0 even:bg-muted",\n }),\n th: (props: React.HTMLAttributes<HTMLTableCellElement>) =>\n createElement("th", {\n ...props,\n className:\n "border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right",\n }),\n td: (props: React.HTMLAttributes<HTMLTableCellElement>) =>\n createElement("td", {\n ...props,\n className:\n "border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right",\n }),\n hr: () => createElement("hr", { className: "my-4 md:my-8" }),\n img: (props: React.ImgHTMLAttributes<HTMLImageElement>) =>\n createElement("img", {\n ...props,\n className: "rounded-md border",\n }),\n }\n}\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, "")\n .replace(/\\s+/g, "-")\n}\n',
10179
- "marketing/payload/src/payload.config.ts.hbs": 'import path from "node:path"\nimport { fileURLToPath } from "node:url"\nimport { postgresAdapter } from "@payloadcms/db-postgres"\nimport { resendAdapter } from "@payloadcms/email-resend"\nimport type { SharpDependency } from "payload"\nimport { type PayloadRequest, buildConfig } from "payload"\nimport sharp from "sharp"\n\nimport { defaultLexical } from "@/fields/defaultLexical"\nimport { Footer } from "./Footer/config"\nimport { Header } from "./Header/config"\nimport { Categories } from "./collections/Categories"\nimport { FAQs } from "./collections/FAQs"\nimport { Media } from "./collections/Media"\nimport { Pages } from "./collections/Pages"\nimport { Posts } from "./collections/Posts"\nimport { Users } from "./collections/Users"\nimport { plugins } from "./plugins"\nimport { getServerSideURL } from "./utilities/getURL"\n\nconst filename = fileURLToPath(import.meta.url)\nconst dirname = path.dirname(filename)\n\nexport default buildConfig({\n admin: {\n components: {\n // The `BeforeLogin` component renders a message that you see while logging into your admin panel.\n // Feel free to delete this at any time. Simply remove the line below.\n beforeLogin: ["@/components/BeforeLogin"],\n // The `BeforeDashboard` component renders the \'welcome\' block that you see after logging into your admin panel.\n // Feel free to delete this at any time. Simply remove the line below.\n beforeDashboard: ["@/components/BeforeDashboard"],\n },\n importMap: {\n baseDir: path.resolve(dirname),\n },\n user: Users.slug,\n livePreview: {\n breakpoints: [\n {\n label: "Mobile",\n name: "mobile",\n width: 375,\n height: 667,\n },\n {\n label: "Tablet",\n name: "tablet",\n width: 768,\n height: 1024,\n },\n {\n label: "Desktop",\n name: "desktop",\n width: 1440,\n height: 900,\n },\n ],\n },\n },\n // This config helps us configure global or default features that the other editors can inherit\n editor: defaultLexical,\n db: postgresAdapter({\n pool: {\n connectionString: process.env.DATABASE_URL || "",\n },\n // Disable auto-push in dev mode to prevent constraint name truncation issues\n // Use migrations instead: pnpm payload migrate\n push: false,\n }),\n collections: [Pages, Posts, Media, Categories, FAQs, Users],\n cors: [getServerSideURL()].filter(Boolean),\n globals: [Header, Footer],\n plugins,\n secret: process.env.PAYLOAD_SECRET,\n sharp: sharp as unknown as SharpDependency,\n email: resendAdapter({\n apiKey: process.env.RESEND_API_KEY || "",\n defaultFromAddress: process.env.RESEND_FROM_EMAIL || "noreply@example.com",\n defaultFromName: "{{projectName}}",\n }),\n typescript: {\n outputFile: path.resolve(dirname, "payload-types.ts"),\n },\n jobs: {\n access: {\n run: ({ req }: { req: PayloadRequest }): boolean => {\n // Allow logged in users to execute this endpoint (default)\n if (req.user) return true\n\n // If there is no logged in user, then check\n // for the Vercel Cron secret to be present as an\n // Authorization header:\n const authHeader = req.headers.get("authorization")\n return authHeader === `Bearer ${process.env.CRON_SECRET}`\n },\n },\n tasks: [],\n },\n})\n',
10179
+ "marketing/payload/src/payload.config.ts.hbs": 'import path from "node:path"\nimport { fileURLToPath } from "node:url"\nimport { postgresAdapter } from "@payloadcms/db-postgres"\nimport { resendAdapter } from "@payloadcms/email-resend"\nimport type { SharpDependency } from "payload"\nimport { type PayloadRequest, buildConfig } from "payload"\nimport sharp from "sharp"\n\nimport { defaultLexical } from "@/fields/defaultLexical"\nimport { Footer } from "./Footer/config"\nimport { Header } from "./Header/config"\nimport { Categories } from "./collections/Categories"\nimport { FAQs } from "./collections/FAQs"\nimport { Media } from "./collections/Media"\nimport { Pages } from "./collections/Pages"\nimport { Posts } from "./collections/Posts"\nimport { Users } from "./collections/Users"\nimport { plugins } from "./plugins"\nimport { getServerSideURL } from "./utilities/getURL"\n\nconst filename = fileURLToPath(import.meta.url)\nconst dirname = path.dirname(filename)\n\nexport default buildConfig({\n admin: {\n components: {\n // The `BeforeLogin` component renders a message that you see while logging into your admin panel.\n // Feel free to delete this at any time. Simply remove the line below.\n beforeLogin: ["@/components/BeforeLogin"],\n // The `BeforeDashboard` component renders the \'welcome\' block that you see after logging into your admin panel.\n // Feel free to delete this at any time. Simply remove the line below.\n beforeDashboard: ["@/components/BeforeDashboard"],\n },\n importMap: {\n baseDir: path.resolve(dirname),\n },\n user: Users.slug,\n livePreview: {\n breakpoints: [\n {\n label: "Mobile",\n name: "mobile",\n width: 375,\n height: 667,\n },\n {\n label: "Tablet",\n name: "tablet",\n width: 768,\n height: 1024,\n },\n {\n label: "Desktop",\n name: "desktop",\n width: 1440,\n height: 900,\n },\n ],\n },\n },\n // This config helps us configure global or default features that the other editors can inherit\n editor: defaultLexical,\n db: postgresAdapter({\n pool: {\n connectionString: process.env.DATABASE_URL || "",\n },\n // Auto-sync schema in development for better DX (first launch works immediately)\n // In production, use explicit migrations: pnpm payload migrate\n push: process.env.NODE_ENV !== "production",\n }),\n collections: [Pages, Posts, Media, Categories, FAQs, Users],\n cors: [getServerSideURL()].filter(Boolean),\n globals: [Header, Footer],\n plugins,\n secret: process.env.PAYLOAD_SECRET,\n sharp: sharp as unknown as SharpDependency,\n email: resendAdapter({\n apiKey: process.env.RESEND_API_KEY || "",\n defaultFromAddress: process.env.RESEND_FROM_EMAIL || "noreply@example.com",\n defaultFromName: "{{projectName}}",\n }),\n typescript: {\n outputFile: path.resolve(dirname, "payload-types.ts"),\n },\n jobs: {\n access: {\n run: ({ req }: { req: PayloadRequest }): boolean => {\n // Allow logged in users to execute this endpoint (default)\n if (req.user) return true\n\n // If there is no logged in user, then check\n // for the Vercel Cron secret to be present as an\n // Authorization header:\n const authHeader = req.headers.get("authorization")\n return authHeader === `Bearer ${process.env.CRON_SECRET}`\n },\n },\n tasks: [],\n },\n})\n',
10180
10180
  "marketing/payload/src/plugins/index.ts": 'import { revalidateRedirects } from "@/hooks/revalidateRedirects"\nimport { beforeSyncWithSearch } from "@/search/beforeSync"\nimport { searchFields } from "@/search/fieldOverrides"\nimport { formBuilderPlugin } from "@payloadcms/plugin-form-builder"\nimport { nestedDocsPlugin } from "@payloadcms/plugin-nested-docs"\nimport { redirectsPlugin } from "@payloadcms/plugin-redirects"\nimport { searchPlugin } from "@payloadcms/plugin-search"\nimport { seoPlugin } from "@payloadcms/plugin-seo"\nimport type { GenerateTitle, GenerateURL } from "@payloadcms/plugin-seo/types"\nimport { FixedToolbarFeature, HeadingFeature, lexicalEditor } from "@payloadcms/richtext-lexical"\nimport { vercelBlobStorage } from "@payloadcms/storage-vercel-blob"\nimport type { Plugin } from "payload"\n\nimport type { Page, Post } from "@/payload-types"\nimport { getServerSideURL } from "@/utilities/getURL"\n\nconst generateTitle: GenerateTitle<Post | Page> = ({ doc }) => {\n return doc?.title ? `${doc.title} | Payload Website Template` : "Payload Website Template"\n}\n\nconst generateURL: GenerateURL<Post | Page> = ({ doc }) => {\n const url = getServerSideURL()\n\n return doc?.slug ? `${url}/${doc.slug}` : url\n}\n\nexport const plugins: Plugin[] = [\n // Vercel Blob Storage for media uploads in production\n ...(process.env.BLOB_READ_WRITE_TOKEN\n ? [\n vercelBlobStorage({\n collections: {\n media: true,\n },\n token: process.env.BLOB_READ_WRITE_TOKEN,\n // Enable client-side uploads to bypass Vercel serverless function body size limits\n // This allows uploads up to 500MB directly to Vercel Blob Storage\n clientUploads: true,\n }),\n ]\n : []),\n redirectsPlugin({\n collections: ["pages", "posts"],\n overrides: {\n // @ts-expect-error - This is a valid override, mapped fields don\'t resolve to the same type\n fields: ({ defaultFields }) => {\n return defaultFields.map((field) => {\n if ("name" in field && field.name === "from") {\n return {\n ...field,\n admin: {\n description: "You will need to rebuild the website when changing this field.",\n },\n }\n }\n return field\n })\n },\n hooks: {\n afterChange: [revalidateRedirects],\n },\n },\n }),\n nestedDocsPlugin({\n collections: ["categories"],\n generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ""),\n }),\n seoPlugin({\n generateTitle,\n generateURL,\n }),\n formBuilderPlugin({\n fields: {\n payment: false,\n },\n formOverrides: {\n fields: ({ defaultFields }) => {\n return defaultFields.map((field) => {\n if ("name" in field && field.name === "confirmationMessage") {\n return {\n ...field,\n editor: lexicalEditor({\n features: ({ rootFeatures }) => {\n return [\n ...rootFeatures,\n FixedToolbarFeature(),\n HeadingFeature({ enabledHeadingSizes: ["h1", "h2", "h3", "h4"] }),\n ]\n },\n }),\n }\n }\n return field\n })\n },\n },\n }),\n searchPlugin({\n collections: ["posts"],\n beforeSync: beforeSyncWithSearch,\n searchOverrides: {\n fields: ({ defaultFields }) => {\n return [...defaultFields, ...searchFields]\n },\n },\n }),\n]\n',
10181
10181
  "marketing/payload/src/providers/HeaderTheme/index.tsx": '"use client"\n\nimport type { Theme } from "@/providers/Theme/types"\n\nimport type React from "react"\nimport { createContext, use, useCallback, useState } from "react"\n\nimport canUseDOM from "@/utilities/canUseDOM"\n\nexport interface ContextType {\n headerTheme?: Theme | null\n setHeaderTheme: (theme: Theme | null) => void\n}\n\nconst initialContext: ContextType = {\n headerTheme: undefined,\n setHeaderTheme: () => null,\n}\n\nconst HeaderThemeContext = createContext(initialContext)\n\nexport const HeaderThemeProvider = ({ children }: { children: React.ReactNode }) => {\n const [headerTheme, setThemeState] = useState<Theme | undefined | null>(\n canUseDOM ? (document.documentElement.getAttribute("data-theme") as Theme) : undefined,\n )\n\n const setHeaderTheme = useCallback((themeToSet: Theme | null) => {\n setThemeState(themeToSet)\n }, [])\n\n return <HeaderThemeContext value={{ headerTheme, setHeaderTheme }}>{children}</HeaderThemeContext>\n}\n\nexport const useHeaderTheme = (): ContextType => use(HeaderThemeContext)\n',
10182
10182
  "marketing/payload/src/providers/PostHogProvider.tsx": '"use client"\n\nimport { usePathname, useSearchParams } from "next/navigation"\nimport posthog from "posthog-js"\nimport { Suspense, useEffect } from "react"\n\nfunction PostHogPageviewTracker() {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n\n useEffect(() => {\n // Only track in production with PostHog configured\n if (process.env.NODE_ENV !== "production") return\n if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) return\n\n // Track pageviews when the route changes\n const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : "")\n posthog.capture("$pageview", { $current_url: url })\n }, [pathname, searchParams])\n\n return null\n}\n\nexport function PostHogProvider({ children }: { children: React.ReactNode }) {\n return (\n <>\n <Suspense fallback={null}>\n <PostHogPageviewTracker />\n </Suspense>\n {children}\n </>\n )\n}\n',
@@ -10303,7 +10303,7 @@ export const useTheme = (): ThemeContextType => use(ThemeContext)
10303
10303
  "marketing/payload/src/utilities/generateMeta.ts": 'import type { Metadata } from "next"\n\nimport type { Config, Media, Page, Post } from "../payload-types"\n\nimport { getServerSideURL } from "./getURL"\nimport { mergeOpenGraph } from "./mergeOpenGraph"\n\nconst getImageURL = (image?: Media | Config["db"]["defaultIDType"] | null) => {\n const serverUrl = getServerSideURL()\n\n let url = `${serverUrl}/website-template-OG.webp`\n\n if (image && typeof image === "object" && "url" in image) {\n const ogUrl = image.sizes?.og?.url\n\n url = ogUrl ? `${serverUrl}${ogUrl}` : `${serverUrl}${image.url}`\n }\n\n return url\n}\n\n// Default keywords for SEO\nconst defaultKeywords = [\n "directory builder",\n "no-code directory",\n "directory website",\n "niche directory",\n "business directory software",\n "directory platform",\n "monetize directory",\n "SEO directory",\n "directory SaaS",\n "create directory website",\n]\n\nexport const generateMeta = async (args: {\n doc: Partial<Page> | Partial<Post> | null\n}): Promise<Metadata> => {\n const { doc } = args\n const serverUrl = getServerSideURL()\n\n const ogImage = getImageURL(doc?.meta?.image)\n\n const title = doc?.meta?.title\n ? `${doc?.meta?.title} | DirectoryHub`\n : "DirectoryHub - Build Directories That Generate Real Business"\n\n const description =\n doc?.meta?.description ||\n "Launch a profitable directory business in minutes. The no-code platform to build, manage, and monetize niche directory websites with built-in payments, SEO, and multi-tenant scalability."\n\n // Generate canonical URL\n const slug = Array.isArray(doc?.slug) ? doc?.slug.join("/") : doc?.slug || ""\n const canonicalUrl = slug === "home" ? serverUrl : `${serverUrl}/${slug}`\n\n return {\n title,\n description,\n keywords: defaultKeywords,\n authors: [{ name: "DirectoryHub", url: serverUrl }],\n creator: "DirectoryHub",\n publisher: "DirectoryHub",\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n "max-video-preview": -1,\n "max-image-preview": "large",\n "max-snippet": -1,\n },\n },\n alternates: {\n canonical: canonicalUrl,\n },\n openGraph: mergeOpenGraph({\n description,\n images: ogImage\n ? [\n {\n url: ogImage,\n width: 1200,\n height: 630,\n alt: title,\n },\n ]\n : undefined,\n title,\n url: canonicalUrl,\n }),\n }\n}\n',
10304
10304
  "marketing/payload/src/utilities/generatePreviewPath.ts": 'import type { CollectionSlug, PayloadRequest } from "payload"\n\nconst collectionPrefixMap: Partial<Record<CollectionSlug, string>> = {\n posts: "/posts",\n pages: "",\n}\n\ntype Props = {\n collection: keyof typeof collectionPrefixMap\n slug: string\n req: PayloadRequest\n}\n\nexport const generatePreviewPath = ({ collection, slug }: Props) => {\n // Allow empty strings, e.g. for the homepage\n if (slug === undefined || slug === null) {\n return null\n }\n\n // Encode to support slugs with special characters\n const encodedSlug = encodeURIComponent(slug)\n\n const encodedParams = new URLSearchParams({\n slug: encodedSlug,\n collection,\n path: `${collectionPrefixMap[collection]}/${encodedSlug}`,\n previewSecret: process.env.PREVIEW_SECRET || "",\n })\n\n const url = `/next/preview?${encodedParams.toString()}`\n\n return url\n}\n',
10305
10305
  "marketing/payload/src/utilities/getDocument.ts": 'import type { Config } from "src/payload-types"\n\nimport configPromise from "@payload-config"\nimport { unstable_cache } from "next/cache"\nimport { getPayload } from "payload"\n\ntype Collection = keyof Config["collections"]\n\nasync function getDocument(collection: Collection, slug: string, depth = 2) {\n const payload = await getPayload({ config: configPromise })\n\n const page = await payload.find({\n collection,\n depth,\n where: {\n slug: {\n equals: slug,\n },\n },\n })\n\n return page.docs[0]\n}\n\n/**\n * Returns a unstable_cache function mapped with the cache tag for the slug\n * @param depth - Depth for populating relationships (default: 2 for rich text content)\n */\nexport const getCachedDocument = (collection: Collection, slug: string, depth = 2) =>\n unstable_cache(async () => getDocument(collection, slug, depth), [collection, slug], {\n tags: [`${collection}_${slug}`],\n })\n',
10306
- "marketing/payload/src/utilities/getGlobals.ts": 'import type { Config } from "src/payload-types"\n\nimport configPromise from "@payload-config"\nimport { unstable_cache } from "next/cache"\nimport { getPayload } from "payload"\n\ntype Global = keyof Config["globals"]\n\nasync function getGlobal(slug: Global, depth = 0) {\n const payload = await getPayload({ config: configPromise })\n\n const global = await payload.findGlobal({\n slug,\n depth,\n })\n\n return global\n}\n\n/**\n * Returns a unstable_cache function mapped with the cache tag for the slug\n */\nexport const getCachedGlobal = (slug: Global, depth = 0) =>\n unstable_cache(async () => getGlobal(slug, depth), [slug], {\n tags: [`global_${slug}`],\n })\n',
10306
+ "marketing/payload/src/utilities/getGlobals.ts": 'import type { Config } from "src/payload-types"\n\nimport configPromise from "@payload-config"\nimport { unstable_cache } from "next/cache"\nimport { getPayload } from "payload"\n\ntype Global = keyof Config["globals"]\n\nasync function getGlobal(slug: Global, depth = 0) {\n try {\n const payload = await getPayload({ config: configPromise })\n\n const global = await payload.findGlobal({\n slug,\n depth,\n })\n\n return global\n } catch (error) {\n // Database tables may not exist yet on first launch\n // Return null so components can show a setup UI instead of crashing\n console.warn(`Could not fetch global "${String(slug)}". Database may not be initialized yet.`)\n return null\n }\n}\n\n/**\n * Returns a unstable_cache function mapped with the cache tag for the slug\n */\nexport const getCachedGlobal = (slug: Global, depth = 0) =>\n unstable_cache(async () => getGlobal(slug, depth), [slug], {\n tags: [`global_${slug}`],\n })\n',
10307
10307
  "marketing/payload/src/utilities/getMeUser.ts": 'import { cookies } from "next/headers"\nimport { redirect } from "next/navigation"\n\nimport type { User } from "../payload-types"\nimport { getClientSideURL } from "./getURL"\n\nexport const getMeUser = async (args?: {\n nullUserRedirect?: string\n validUserRedirect?: string\n}): Promise<{\n token: string\n user: User\n}> => {\n const { nullUserRedirect, validUserRedirect } = args || {}\n const cookieStore = await cookies()\n const token = cookieStore.get("payload-token")?.value\n\n const meUserReq = await fetch(`${getClientSideURL()}/api/users/me`, {\n headers: {\n Authorization: `JWT ${token}`,\n },\n })\n\n const {\n user,\n }: {\n user: User\n } = await meUserReq.json()\n\n if (validUserRedirect && meUserReq.ok && user) {\n redirect(validUserRedirect)\n }\n\n if (nullUserRedirect && (!meUserReq.ok || !user)) {\n redirect(nullUserRedirect)\n }\n\n // Token will exist here because if it doesn\'t the user will be redirected\n return {\n token: token!,\n user,\n }\n}\n',
10308
10308
  "marketing/payload/src/utilities/getMediaUrl.ts": 'import { getClientSideURL } from "@/utilities/getURL"\n\n/**\n * Processes media resource URL to ensure proper formatting\n * @param url The original URL from the resource\n * @param cacheTag Optional cache tag to append to the URL\n * @returns Properly formatted URL with cache tag if provided\n */\nexport const getMediaUrl = (url: string | null | undefined, cacheTag?: string | null): string => {\n if (!url) return ""\n\n if (cacheTag && cacheTag !== "") {\n cacheTag = encodeURIComponent(cacheTag)\n }\n\n // Check if URL already has http/https protocol\n if (url.startsWith("http://") || url.startsWith("https://")) {\n return cacheTag ? `${url}?${cacheTag}` : url\n }\n\n // Otherwise prepend client-side URL\n const baseUrl = getClientSideURL()\n return cacheTag ? `${baseUrl}${url}?${cacheTag}` : `${baseUrl}${url}`\n}\n',
10309
10309
  "marketing/payload/src/utilities/getRedirects.ts": `import configPromise from "@payload-config"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kofi-stack-template-generator",
3
- "version": "2.1.46",
3
+ "version": "2.1.47",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "typecheck": "tsc --noEmit"
18
18
  },
19
19
  "dependencies": {
20
- "kofi-stack-types": "^2.1.46",
20
+ "kofi-stack-types": "^2.1.47",
21
21
  "handlebars": "^4.7.8",
22
22
  "memfs": "^4.9.0"
23
23
  },
@@ -1,6 +1,6 @@
1
1
  // Auto-generated file. Do not edit manually.
2
2
  // Run 'pnpm prebuild' to regenerate.
3
- // Generated: 2026-01-19T03:53:20.096Z
3
+ // Generated: 2026-01-19T15:16:39.292Z
4
4
  // Template count: 339
5
5
 
6
6
  export const EMBEDDED_TEMPLATES: Record<string, string> = {
@@ -53,13 +53,13 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
53
53
  "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",
54
54
  "marketing/payload/package.json.hbs": "{\n \"name\": \"@repo/marketing\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"build\": \"cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap && next build\",\n \"dev\": \"cross-env NODE_OPTIONS=--no-deprecation next dev --port 3000\",\n \"generate:importmap\": \"cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap\",\n \"generate:types\": \"cross-env NODE_OPTIONS=--no-deprecation payload generate:types\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"payload\": \"cross-env NODE_OPTIONS=--no-deprecation payload\",\n \"start\": \"cross-env NODE_OPTIONS=--no-deprecation next start\",\n \"typecheck\": \"tsc --noEmit\",\n \"db:push\": \"payload migrate\",\n \"db:seed\": \"tsx src/seed.ts\"\n },\n \"dependencies\": {\n \"@payloadcms/admin-bar\": \"^3.0.0\",\n \"@payloadcms/db-postgres\": \"^3.0.0\",\n \"@payloadcms/email-resend\": \"^3.0.0\",\n \"@payloadcms/live-preview-react\": \"^3.0.0\",\n \"@payloadcms/next\": \"^3.0.0\",\n \"@payloadcms/plugin-form-builder\": \"^3.0.0\",\n \"@payloadcms/plugin-nested-docs\": \"^3.0.0\",\n \"@payloadcms/plugin-redirects\": \"^3.0.0\",\n \"@payloadcms/plugin-search\": \"^3.0.0\",\n \"@payloadcms/plugin-seo\": \"^3.0.0\",\n \"@payloadcms/richtext-lexical\": \"^3.0.0\",\n \"@payloadcms/storage-vercel-blob\": \"^3.0.0\",\n \"@payloadcms/ui\": \"^3.0.0\",\n \"@radix-ui/react-accordion\": \"^1.2.0\",\n \"@radix-ui/react-checkbox\": \"^1.0.4\",\n \"@radix-ui/react-label\": \"^2.0.2\",\n \"@radix-ui/react-select\": \"^2.0.0\",\n \"@radix-ui/react-slot\": \"^1.0.2\",\n \"@repo/ui\": \"workspace:*\",\n \"class-variance-authority\": \"^0.7.1\",\n \"clsx\": \"^2.1.1\",\n \"cross-env\": \"^7.0.3\",\n \"geist\": \"^1.3.0\",\n \"lucide-react\": \"^0.562.0\",\n \"next\": \"^15.4.10\",\n \"payload\": \"^3.70.0\",\n \"posthog-js\": \"^1.200.0\",\n \"prism-react-renderer\": \"^2.4.1\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"react-hook-form\": \"^7.71.1\",\n \"sharp\": \"^0.34.0\",\n \"stripe\": \"^17.7.0\",\n \"tailwind-merge\": \"^3.4.0\"\n },\n \"devDependencies\": {\n \"@repo/config-typescript\": \"workspace:*\",\n \"@tailwindcss/postcss\": \"^4.0.0\",\n \"@tailwindcss/typography\": \"^0.5.19\",\n \"@types/node\": \"^20.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"postcss\": \"^8.4.0\",\n \"sass\": \"^1.86.0\",\n \"tailwindcss\": \"^4.0.0\",\n \"tsx\": \"^4.0.0\",\n \"tw-animate-css\": \"^1.4.0\",\n \"typescript\": \"^5.0.0\"\n }\n}\n",
55
55
  "marketing/payload/postcss.config.mjs.hbs": "export default {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n}\n",
56
- "marketing/payload/src/Footer/Component.client.tsx": "\"use client\"\n\nimport Link from \"next/link\"\nimport type React from \"react\"\nimport { useState } from \"react\"\n\nimport type { Footer, Page, Post } from \"@/payload-types\"\n\nimport { Logo } from \"@/components/Logo/Logo\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { ThemeSelector } from \"@/providers/Theme/ThemeSelector\"\nimport { cn } from \"@/utilities/ui\"\n\ninterface FooterClientProps {\n\tdata: Footer\n}\n\n// Social icons as inline SVGs for flexibility\nconst SocialIcons = {\n\ttwitter: (\n\t\t<svg className=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n\t\t\t<path d=\"M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z\" />\n\t\t</svg>\n\t),\n\tinstagram: (\n\t\t<svg className=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n\t\t\t<path\n\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\td=\"M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z\"\n\t\t\t\tclipRule=\"evenodd\"\n\t\t\t/>\n\t\t</svg>\n\t),\n\tlinkedin: (\n\t\t<svg className=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n\t\t\t<path d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z\" />\n\t\t</svg>\n\t),\n\tgithub: (\n\t\t<svg className=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n\t\t\t<path\n\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\td=\"M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z\"\n\t\t\t\tclipRule=\"evenodd\"\n\t\t\t/>\n\t\t</svg>\n\t),\n\tyoutube: (\n\t\t<svg className=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n\t\t\t<path\n\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\td=\"M19.812 5.418c.861.23 1.538.907 1.768 1.768C21.998 8.746 22 12 22 12s0 3.255-.418 4.814a2.504 2.504 0 0 1-1.768 1.768c-1.56.419-7.814.419-7.814.419s-6.255 0-7.814-.419a2.505 2.505 0 0 1-1.768-1.768C2 15.255 2 12 2 12s0-3.255.417-4.814a2.507 2.507 0 0 1 1.768-1.768C5.744 5 11.998 5 11.998 5s6.255 0 7.814.418ZM15.194 12 10 15V9l5.194 3Z\"\n\t\t\t\tclipRule=\"evenodd\"\n\t\t\t/>\n\t\t</svg>\n\t),\n}\n\n// Helper function to get URL from link\nfunction getLinkUrl(link: {\n\ttype?: (\"reference\" | \"custom\") | null\n\treference?:\n\t\t| { relationTo: \"pages\"; value: number | Page }\n\t\t| { relationTo: \"posts\"; value: number | Post }\n\t\t| null\n\turl?: string | null\n}): string {\n\tif (link.type === \"reference\" && link.reference) {\n\t\tconst value = link.reference.value\n\t\tif (typeof value === \"object\" && \"slug\" in value) {\n\t\t\tconst prefix = link.reference.relationTo === \"posts\" ? \"/posts\" : \"\"\n\t\t\treturn `${prefix}/${value.slug}`\n\t\t}\n\t}\n\treturn link.url || \"#\"\n}\n\nexport const FooterClient: React.FC<FooterClientProps> = ({ data }) => {\n\tconst currentYear = new Date().getFullYear()\n\tconst [email, setEmail] = useState(\"\")\n\tconst [isSubmitting, setIsSubmitting] = useState(false)\n\tconst [message, setMessage] = useState<{ type: \"success\" | \"error\"; text: string } | null>(null)\n\n\tconst { columns, socialLinks, newsletter, copyrightText, bottomLinks } = data\n\n\tconst handleNewsletterSubmit = async (e: React.FormEvent) => {\n\t\te.preventDefault()\n\t\tif (!email || isSubmitting) return\n\n\t\tsetIsSubmitting(true)\n\t\tsetMessage(null)\n\n\t\ttry {\n\t\t\tconst response = await fetch(\"/api/newsletter\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ email }),\n\t\t\t})\n\n\t\t\tconst result = await response.json()\n\n\t\t\tif (result.success) {\n\t\t\t\tsetMessage({ type: \"success\", text: result.message })\n\t\t\t\tsetEmail(\"\")\n\t\t\t} else {\n\t\t\t\tsetMessage({ type: \"error\", text: result.message })\n\t\t\t}\n\t\t} catch {\n\t\t\tsetMessage({ type: \"error\", text: \"Something went wrong. Please try again.\" })\n\t\t} finally {\n\t\t\tsetIsSubmitting(false)\n\t\t}\n\t}\n\n\t// Check if any social links are configured\n\tconst hasSocialLinks =\n\t\tsocialLinks?.twitter ||\n\t\tsocialLinks?.instagram ||\n\t\tsocialLinks?.linkedin ||\n\t\tsocialLinks?.github ||\n\t\tsocialLinks?.youtube\n\n\treturn (\n\t\t<footer className=\"mt-auto border-t border-border bg-background\">\n\t\t\t{/* Main Footer Content */}\n\t\t\t<div className=\"container mx-auto px-4 py-12 lg:py-16\">\n\t\t\t\t<div className=\"grid grid-cols-2 gap-8 md:grid-cols-3 lg:grid-cols-6\">\n\t\t\t\t\t{/* Link Columns */}\n\t\t\t\t\t{columns?.map((column) => (\n\t\t\t\t\t\t<div key={column.id || column.title} className=\"col-span-1\">\n\t\t\t\t\t\t\t<h3 className=\"mb-4 text-sm font-medium text-foreground\">{column.title}</h3>\n\t\t\t\t\t\t\t<ul className=\"space-y-3\">\n\t\t\t\t\t\t\t\t{column.links?.map((linkItem) => (\n\t\t\t\t\t\t\t\t\t<li key={linkItem.id || linkItem.link.label}>\n\t\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\t\thref={getLinkUrl(linkItem.link)}\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-sm text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\t\t\t\t{...(linkItem.link.newTab\n\t\t\t\t\t\t\t\t\t\t\t\t? { target: \"_blank\", rel: \"noopener noreferrer\" }\n\t\t\t\t\t\t\t\t\t\t\t\t: {})}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{linkItem.link.label}\n\t\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\n\t\t\t\t\t{/* Newsletter Column */}\n\t\t\t\t\t{newsletter?.enabled && (\n\t\t\t\t\t\t<div className=\"col-span-2 md:col-span-3 lg:col-span-2\">\n\t\t\t\t\t\t\t<h3 className=\"mb-4 text-sm font-medium text-foreground\">\n\t\t\t\t\t\t\t\t{newsletter.title || \"Newsletter\"}\n\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t<p className=\"mb-4 text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t{newsletter.description || \"Stay up to date with the latest updates.\"}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<form\n\t\t\t\t\t\t\t\tonSubmit={handleNewsletterSubmit}\n\t\t\t\t\t\t\t\tclassName=\"space-y-3\"\n\t\t\t\t\t\t\t\tsuppressHydrationWarning\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\t\t\t\t\tplaceholder={newsletter.placeholder || \"Enter your email\"}\n\t\t\t\t\t\t\t\t\t\tvalue={email}\n\t\t\t\t\t\t\t\t\t\tonChange={(e) => setEmail(e.target.value)}\n\t\t\t\t\t\t\t\t\t\tclassName=\"flex-1\"\n\t\t\t\t\t\t\t\t\t\tdisabled={isSubmitting}\n\t\t\t\t\t\t\t\t\t\trequired\n\t\t\t\t\t\t\t\t\t\tsuppressHydrationWarning\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Button type=\"submit\" disabled={isSubmitting} suppressHydrationWarning>\n\t\t\t\t\t\t\t\t\t\t{isSubmitting ? \"...\" : newsletter.buttonText || \"Subscribe\"}\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t{message && (\n\t\t\t\t\t\t\t\t\t<p\n\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\"text-sm\",\n\t\t\t\t\t\t\t\t\t\t\tmessage.type === \"success\" ? \"text-green-600\" : \"text-red-600\",\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{message.text}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</form>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Social Links */}\n\t\t\t\t{hasSocialLinks && (\n\t\t\t\t\t<div className=\"mt-10 flex items-center gap-4 border-t border-border pt-8\">\n\t\t\t\t\t\t{socialLinks?.twitter && (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={socialLinks.twitter}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"X (Twitter)\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{SocialIcons.twitter}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{socialLinks?.instagram && (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={socialLinks.instagram}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"Instagram\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{SocialIcons.instagram}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{socialLinks?.linkedin && (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={socialLinks.linkedin}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"LinkedIn\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{SocialIcons.linkedin}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{socialLinks?.github && (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={socialLinks.github}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"GitHub\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{SocialIcons.github}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{socialLinks?.youtube && (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={socialLinks.youtube}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"YouTube\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{SocialIcons.youtube}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Bottom Bar */}\n\t\t\t<div className=\"border-t border-border\">\n\t\t\t\t<div className=\"container mx-auto flex flex-col items-center justify-between gap-4 px-4 py-6 sm:flex-row\">\n\t\t\t\t\t{/* Logo and Copyright */}\n\t\t\t\t\t<div className=\"flex items-center gap-4\">\n\t\t\t\t\t\t<Link href=\"/\" className=\"flex items-center\">\n\t\t\t\t\t\t\t<Logo variant=\"auto\" className=\"h-6 w-6\" />\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t© {currentYear} {copyrightText || \"DirectoryHub\"}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Bottom Links and Theme Selector */}\n\t\t\t\t\t<div className=\"flex items-center gap-6\">\n\t\t\t\t\t\t{bottomLinks?.map((linkItem) => (\n\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\tkey={linkItem.id || linkItem.link.label}\n\t\t\t\t\t\t\t\thref={getLinkUrl(linkItem.link)}\n\t\t\t\t\t\t\t\tclassName=\"text-sm text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\t{...(linkItem.link.newTab ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {})}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{linkItem.link.label}\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t))}\n\t\t\t\t\t\t<ThemeSelector />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</footer>\n\t)\n}\n",
57
- "marketing/payload/src/Footer/Component.tsx": "import { getCachedGlobal } from \"@/utilities/getGlobals\"\n\nimport type { Footer as FooterType } from \"@/payload-types\"\n\nimport { FooterClient } from \"./Component.client\"\n\nexport async function Footer() {\n\tconst footerData: FooterType = await getCachedGlobal(\"footer\", 1)()\n\n\treturn <FooterClient data={footerData} />\n}\n",
56
+ "marketing/payload/src/Footer/Component.client.tsx": "\"use client\"\n\nimport Link from \"next/link\"\nimport type React from \"react\"\nimport { useState } from \"react\"\n\nimport type { Footer, Page, Post } from \"@/payload-types\"\n\nimport { Logo } from \"@/components/Logo/Logo\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { ThemeSelector } from \"@/providers/Theme/ThemeSelector\"\nimport { cn } from \"@/utilities/ui\"\n\ninterface FooterClientProps {\n\tdata: Footer | null\n}\n\n// Social icons as inline SVGs for flexibility\nconst SocialIcons = {\n\ttwitter: (\n\t\t<svg className=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n\t\t\t<path d=\"M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z\" />\n\t\t</svg>\n\t),\n\tinstagram: (\n\t\t<svg className=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n\t\t\t<path\n\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\td=\"M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z\"\n\t\t\t\tclipRule=\"evenodd\"\n\t\t\t/>\n\t\t</svg>\n\t),\n\tlinkedin: (\n\t\t<svg className=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n\t\t\t<path d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z\" />\n\t\t</svg>\n\t),\n\tgithub: (\n\t\t<svg className=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n\t\t\t<path\n\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\td=\"M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z\"\n\t\t\t\tclipRule=\"evenodd\"\n\t\t\t/>\n\t\t</svg>\n\t),\n\tyoutube: (\n\t\t<svg className=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n\t\t\t<path\n\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\td=\"M19.812 5.418c.861.23 1.538.907 1.768 1.768C21.998 8.746 22 12 22 12s0 3.255-.418 4.814a2.504 2.504 0 0 1-1.768 1.768c-1.56.419-7.814.419-7.814.419s-6.255 0-7.814-.419a2.505 2.505 0 0 1-1.768-1.768C2 15.255 2 12 2 12s0-3.255.417-4.814a2.507 2.507 0 0 1 1.768-1.768C5.744 5 11.998 5 11.998 5s6.255 0 7.814.418ZM15.194 12 10 15V9l5.194 3Z\"\n\t\t\t\tclipRule=\"evenodd\"\n\t\t\t/>\n\t\t</svg>\n\t),\n}\n\n// Helper function to get URL from link\nfunction getLinkUrl(link: {\n\ttype?: (\"reference\" | \"custom\") | null\n\treference?:\n\t\t| { relationTo: \"pages\"; value: number | Page }\n\t\t| { relationTo: \"posts\"; value: number | Post }\n\t\t| null\n\turl?: string | null\n}): string {\n\tif (link.type === \"reference\" && link.reference) {\n\t\tconst value = link.reference.value\n\t\tif (typeof value === \"object\" && \"slug\" in value) {\n\t\t\tconst prefix = link.reference.relationTo === \"posts\" ? \"/posts\" : \"\"\n\t\t\treturn `${prefix}/${value.slug}`\n\t\t}\n\t}\n\treturn link.url || \"#\"\n}\n\nexport const FooterClient: React.FC<FooterClientProps> = ({ data }) => {\n\tconst currentYear = new Date().getFullYear()\n\tconst [email, setEmail] = useState(\"\")\n\tconst [isSubmitting, setIsSubmitting] = useState(false)\n\tconst [message, setMessage] = useState<{ type: \"success\" | \"error\"; text: string } | null>(null)\n\n\t// Show minimal footer when database isn't initialized yet\n\tif (!data) {\n\t\treturn (\n\t\t\t<footer className=\"mt-auto border-t border-border bg-background\">\n\t\t\t\t<div className=\"container mx-auto flex flex-col items-center justify-between gap-4 px-4 py-6 sm:flex-row\">\n\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\tSite not configured yet. Visit <Link href=\"/admin\" className=\"underline hover:text-foreground\">/admin</Link> to set up.\n\t\t\t\t\t</p>\n\t\t\t\t\t<ThemeSelector />\n\t\t\t\t</div>\n\t\t\t</footer>\n\t\t)\n\t}\n\n\tconst { columns, socialLinks, newsletter, copyrightText, bottomLinks } = data\n\n\tconst handleNewsletterSubmit = async (e: React.FormEvent) => {\n\t\te.preventDefault()\n\t\tif (!email || isSubmitting) return\n\n\t\tsetIsSubmitting(true)\n\t\tsetMessage(null)\n\n\t\ttry {\n\t\t\tconst response = await fetch(\"/api/newsletter\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ email }),\n\t\t\t})\n\n\t\t\tconst result = await response.json()\n\n\t\t\tif (result.success) {\n\t\t\t\tsetMessage({ type: \"success\", text: result.message })\n\t\t\t\tsetEmail(\"\")\n\t\t\t} else {\n\t\t\t\tsetMessage({ type: \"error\", text: result.message })\n\t\t\t}\n\t\t} catch {\n\t\t\tsetMessage({ type: \"error\", text: \"Something went wrong. Please try again.\" })\n\t\t} finally {\n\t\t\tsetIsSubmitting(false)\n\t\t}\n\t}\n\n\t// Check if any social links are configured\n\tconst hasSocialLinks =\n\t\tsocialLinks?.twitter ||\n\t\tsocialLinks?.instagram ||\n\t\tsocialLinks?.linkedin ||\n\t\tsocialLinks?.github ||\n\t\tsocialLinks?.youtube\n\n\treturn (\n\t\t<footer className=\"mt-auto border-t border-border bg-background\">\n\t\t\t{/* Main Footer Content */}\n\t\t\t<div className=\"container mx-auto px-4 py-12 lg:py-16\">\n\t\t\t\t<div className=\"grid grid-cols-2 gap-8 md:grid-cols-3 lg:grid-cols-6\">\n\t\t\t\t\t{/* Link Columns */}\n\t\t\t\t\t{columns?.map((column) => (\n\t\t\t\t\t\t<div key={column.id || column.title} className=\"col-span-1\">\n\t\t\t\t\t\t\t<h3 className=\"mb-4 text-sm font-medium text-foreground\">{column.title}</h3>\n\t\t\t\t\t\t\t<ul className=\"space-y-3\">\n\t\t\t\t\t\t\t\t{column.links?.map((linkItem) => (\n\t\t\t\t\t\t\t\t\t<li key={linkItem.id || linkItem.link.label}>\n\t\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\t\thref={getLinkUrl(linkItem.link)}\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-sm text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\t\t\t\t{...(linkItem.link.newTab\n\t\t\t\t\t\t\t\t\t\t\t\t? { target: \"_blank\", rel: \"noopener noreferrer\" }\n\t\t\t\t\t\t\t\t\t\t\t\t: {})}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{linkItem.link.label}\n\t\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\n\t\t\t\t\t{/* Newsletter Column */}\n\t\t\t\t\t{newsletter?.enabled && (\n\t\t\t\t\t\t<div className=\"col-span-2 md:col-span-3 lg:col-span-2\">\n\t\t\t\t\t\t\t<h3 className=\"mb-4 text-sm font-medium text-foreground\">\n\t\t\t\t\t\t\t\t{newsletter.title || \"Newsletter\"}\n\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t<p className=\"mb-4 text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t{newsletter.description || \"Stay up to date with the latest updates.\"}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<form\n\t\t\t\t\t\t\t\tonSubmit={handleNewsletterSubmit}\n\t\t\t\t\t\t\t\tclassName=\"space-y-3\"\n\t\t\t\t\t\t\t\tsuppressHydrationWarning\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\t\t\t\t\tplaceholder={newsletter.placeholder || \"Enter your email\"}\n\t\t\t\t\t\t\t\t\t\tvalue={email}\n\t\t\t\t\t\t\t\t\t\tonChange={(e) => setEmail(e.target.value)}\n\t\t\t\t\t\t\t\t\t\tclassName=\"flex-1\"\n\t\t\t\t\t\t\t\t\t\tdisabled={isSubmitting}\n\t\t\t\t\t\t\t\t\t\trequired\n\t\t\t\t\t\t\t\t\t\tsuppressHydrationWarning\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Button type=\"submit\" disabled={isSubmitting} suppressHydrationWarning>\n\t\t\t\t\t\t\t\t\t\t{isSubmitting ? \"...\" : newsletter.buttonText || \"Subscribe\"}\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t{message && (\n\t\t\t\t\t\t\t\t\t<p\n\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\"text-sm\",\n\t\t\t\t\t\t\t\t\t\t\tmessage.type === \"success\" ? \"text-green-600\" : \"text-red-600\",\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{message.text}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</form>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Social Links */}\n\t\t\t\t{hasSocialLinks && (\n\t\t\t\t\t<div className=\"mt-10 flex items-center gap-4 border-t border-border pt-8\">\n\t\t\t\t\t\t{socialLinks?.twitter && (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={socialLinks.twitter}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"X (Twitter)\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{SocialIcons.twitter}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{socialLinks?.instagram && (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={socialLinks.instagram}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"Instagram\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{SocialIcons.instagram}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{socialLinks?.linkedin && (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={socialLinks.linkedin}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"LinkedIn\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{SocialIcons.linkedin}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{socialLinks?.github && (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={socialLinks.github}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"GitHub\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{SocialIcons.github}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{socialLinks?.youtube && (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={socialLinks.youtube}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"YouTube\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{SocialIcons.youtube}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Bottom Bar */}\n\t\t\t<div className=\"border-t border-border\">\n\t\t\t\t<div className=\"container mx-auto flex flex-col items-center justify-between gap-4 px-4 py-6 sm:flex-row\">\n\t\t\t\t\t{/* Logo and Copyright */}\n\t\t\t\t\t<div className=\"flex items-center gap-4\">\n\t\t\t\t\t\t<Link href=\"/\" className=\"flex items-center\">\n\t\t\t\t\t\t\t<Logo variant=\"auto\" className=\"h-6 w-6\" />\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t© {currentYear} {copyrightText || \"DirectoryHub\"}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Bottom Links and Theme Selector */}\n\t\t\t\t\t<div className=\"flex items-center gap-6\">\n\t\t\t\t\t\t{bottomLinks?.map((linkItem) => (\n\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\tkey={linkItem.id || linkItem.link.label}\n\t\t\t\t\t\t\t\thref={getLinkUrl(linkItem.link)}\n\t\t\t\t\t\t\t\tclassName=\"text-sm text-muted-foreground transition-colors hover:text-foreground\"\n\t\t\t\t\t\t\t\t{...(linkItem.link.newTab ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {})}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{linkItem.link.label}\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t))}\n\t\t\t\t\t\t<ThemeSelector />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</footer>\n\t)\n}\n",
57
+ "marketing/payload/src/Footer/Component.tsx": "import { getCachedGlobal } from \"@/utilities/getGlobals\"\n\nimport type { Footer as FooterType } from \"@/payload-types\"\n\nimport { FooterClient } from \"./Component.client\"\n\nexport async function Footer() {\n\tconst footerData = (await getCachedGlobal(\"footer\", 1)()) as FooterType | null\n\n\treturn <FooterClient data={footerData} />\n}\n",
58
58
  "marketing/payload/src/Footer/RowLabel.tsx": "\"use client\"\nimport type { Footer } from \"@/payload-types\"\nimport { type RowLabelProps, useRowLabel } from \"@payloadcms/ui\"\n\ntype FooterLink = NonNullable<NonNullable<Footer[\"columns\"]>[number][\"links\"]>[number]\n\nexport const RowLabel: React.FC<RowLabelProps> = () => {\n\tconst data = useRowLabel<FooterLink>()\n\n\tconst label = data?.data?.link?.label\n\t\t? `Link ${data.rowNumber !== undefined ? data.rowNumber + 1 : \"\"}: ${data?.data?.link?.label}`\n\t\t: \"Row\"\n\n\treturn <div>{label}</div>\n}\n",
59
59
  "marketing/payload/src/Footer/config.ts": "import type { GlobalConfig } from \"payload\"\n\nimport { link } from \"@/fields/link\"\nimport { revalidateFooter } from \"./hooks/revalidateFooter\"\n\nexport const Footer: GlobalConfig = {\n\tslug: \"footer\",\n\taccess: {\n\t\tread: () => true,\n\t},\n\tfields: [\n\t\t// Link Columns - organized groups of links\n\t\t{\n\t\t\tname: \"columns\",\n\t\t\ttype: \"array\",\n\t\t\tlabel: \"Link Columns\",\n\t\t\tmaxRows: 5,\n\t\t\tadmin: {\n\t\t\t\tinitCollapsed: true,\n\t\t\t\tdescription: \"Add columns of links (e.g., Solutions, Resources, Company)\",\n\t\t\t},\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tname: \"title\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tlabel: \"Column Title\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"links\",\n\t\t\t\t\ttype: \"array\",\n\t\t\t\t\tlabel: \"Links\",\n\t\t\t\t\tmaxRows: 10,\n\t\t\t\t\tfields: [\n\t\t\t\t\t\tlink({\n\t\t\t\t\t\t\tappearances: false,\n\t\t\t\t\t\t}),\n\t\t\t\t\t],\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tinitCollapsed: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t// Social Links\n\t\t{\n\t\t\tname: \"socialLinks\",\n\t\t\ttype: \"group\",\n\t\t\tlabel: \"Social Links\",\n\t\t\tadmin: {\n\t\t\t\tdescription: \"Add your social media profile URLs\",\n\t\t\t},\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tname: \"twitter\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tlabel: \"X (Twitter)\",\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tplaceholder: \"https://x.com/yourhandle\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"instagram\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tlabel: \"Instagram\",\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tplaceholder: \"https://instagram.com/yourhandle\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"linkedin\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tlabel: \"LinkedIn\",\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tplaceholder: \"https://linkedin.com/company/yourcompany\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"github\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tlabel: \"GitHub\",\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tplaceholder: \"https://github.com/yourorg\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"youtube\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tlabel: \"YouTube\",\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tplaceholder: \"https://youtube.com/@yourchannel\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t// Newsletter Section\n\t\t{\n\t\t\tname: \"newsletter\",\n\t\t\ttype: \"group\",\n\t\t\tlabel: \"Newsletter\",\n\t\t\tadmin: {\n\t\t\t\tdescription: \"Configure the newsletter signup section\",\n\t\t\t},\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tname: \"enabled\",\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tlabel: \"Enable Newsletter Signup\",\n\t\t\t\t\tdefaultValue: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"title\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\tdefaultValue: \"Newsletter\",\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.enabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"description\",\n\t\t\t\t\ttype: \"textarea\",\n\t\t\t\t\tlabel: \"Description\",\n\t\t\t\t\tdefaultValue: \"Stay up to date with the latest updates and news.\",\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.enabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"buttonText\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tlabel: \"Button Text\",\n\t\t\t\t\tdefaultValue: \"Subscribe\",\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.enabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"placeholder\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tlabel: \"Email Placeholder\",\n\t\t\t\t\tdefaultValue: \"Enter your email\",\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.enabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t// Copyright and Bottom Bar\n\t\t{\n\t\t\tname: \"copyrightText\",\n\t\t\ttype: \"text\",\n\t\t\tlabel: \"Copyright Text\",\n\t\t\tdefaultValue: \"DirectoryHub\",\n\t\t\tadmin: {\n\t\t\t\tdescription: \"Company name for copyright (year is added automatically)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"bottomLinks\",\n\t\t\ttype: \"array\",\n\t\t\tlabel: \"Bottom Bar Links\",\n\t\t\tmaxRows: 4,\n\t\t\tadmin: {\n\t\t\t\tdescription: \"Links shown in the bottom bar (e.g., Contact Support, Privacy Policy)\",\n\t\t\t\tinitCollapsed: true,\n\t\t\t},\n\t\t\tfields: [\n\t\t\t\tlink({\n\t\t\t\t\tappearances: false,\n\t\t\t\t}),\n\t\t\t],\n\t\t},\n\t],\n\thooks: {\n\t\tafterChange: [revalidateFooter],\n\t},\n}\n",
60
60
  "marketing/payload/src/Footer/hooks/revalidateFooter.ts": "import type { GlobalAfterChangeHook } from \"payload\"\n\nimport { revalidateTag } from \"next/cache\"\n\nexport const revalidateFooter: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {\n\tif (!context.disableRevalidate) {\n\t\tpayload.logger.info(\"Revalidating footer\")\n\n\t\trevalidateTag(\"global_footer\")\n\t}\n\n\treturn doc\n}\n",
61
- "marketing/payload/src/Header/Component.client.tsx": "\"use client\"\nimport { useHeaderTheme } from \"@/providers/HeaderTheme\"\nimport Link from \"next/link\"\nimport type React from \"react\"\nimport { useEffect, useMemo, useState } from \"react\"\n\nimport type { Header, Page, Post } from \"@/payload-types\"\n\nimport { Logo } from \"@/components/Logo/Logo\"\nimport { Button } from \"@/components/ui/button\"\nimport { MobileMenu } from \"./MobileMenu\"\nimport { HeaderNav } from \"./Nav\"\n\ninterface HeaderClientProps {\n\tdata: Header\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n\ttype?: \"custom\" | \"reference\" | null\n\treference?: {\n\t\trelationTo: \"pages\" | \"posts\"\n\t\tvalue: Page | Post | string | number\n\t} | null\n\turl?: string | null\n}): string => {\n\tif (link.type === \"reference\" && link.reference) {\n\t\tconst { relationTo, value } = link.reference\n\t\tif (typeof value === \"object\" && value.slug) {\n\t\t\treturn relationTo === \"pages\" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n\t\t}\n\t}\n\treturn link.url || \"#\"\n}\n\nexport const HeaderClient: React.FC<HeaderClientProps> = ({ data }) => {\n\t/* Storing the value in a useState to avoid hydration errors */\n\tconst [theme, setTheme] = useState<string | null>(null)\n\tconst { headerTheme, setHeaderTheme } = useHeaderTheme()\n\n\tuseEffect(() => {\n\t\tsetHeaderTheme(null)\n\t}, [setHeaderTheme])\n\n\tuseEffect(() => {\n\t\tif (headerTheme && headerTheme !== theme) setTheme(headerTheme)\n\t}, [headerTheme, theme])\n\n\t// Get the primary CTA (first right-positioned button item) for mobile view\n\tconst primaryCta = useMemo(() => {\n\t\tconst rightButtonItems = data?.navItems?.filter(\n\t\t\t(item) =>\n\t\t\t\t(item.position || \"left\") === \"right\" &&\n\t\t\t\titem.type === \"link\" &&\n\t\t\t\t(item.appearance || \"button\") === \"button\",\n\t\t)\n\t\treturn rightButtonItems?.[0]\n\t}, [data?.navItems])\n\n\treturn (\n\t\t<header\n\t\t\tclassName=\"sticky top-0 z-20 border-b border-border bg-background\"\n\t\t\t{...(theme ? { \"data-theme\": theme } : {})}\n\t\t>\n\t\t\t<div className=\"container mx-auto px-4 h-16 flex justify-between items-center\">\n\t\t\t\t{/* Left section: Logo + Left Nav Links */}\n\t\t\t\t<div className=\"flex items-center gap-8\">\n\t\t\t\t\t<Link href=\"/\" className=\"flex items-center gap-2\">\n\t\t\t\t\t\t<Logo loading=\"eager\" priority=\"high\" variant=\"auto\" />\n\t\t\t\t\t\t<span className=\"text-xl font-semibold hidden sm:inline\">DirectoryHub</span>\n\t\t\t\t\t</Link>\n\t\t\t\t\t{/* Left nav - hidden on tablet and below, visible on desktop */}\n\t\t\t\t\t<HeaderNav data={data} position=\"left\" className=\"hidden lg:flex gap-6 items-center\" />\n\t\t\t\t</div>\n\n\t\t\t\t{/* Right section: Right Nav Links (CTAs) */}\n\t\t\t\t<div className=\"flex items-center gap-3\">\n\t\t\t\t\t{/* Full right nav - hidden on mobile and tablet, visible on desktop */}\n\t\t\t\t\t<HeaderNav data={data} position=\"right\" className=\"hidden lg:flex gap-4 items-center\" />\n\n\t\t\t\t\t{/* Primary CTA for tablet/mobile - visible on tablet and below, hidden on desktop */}\n\t\t\t\t\t{primaryCta?.link && (\n\t\t\t\t\t\t<Button asChild className=\"lg:hidden\" size=\"sm\">\n\t\t\t\t\t\t\t<Link href={getLinkHref(primaryCta.link)}>{primaryCta.label}</Link>\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Mobile menu hamburger - visible on tablet and below */}\n\t\t\t\t\t<MobileMenu data={data} />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</header>\n\t)\n}\n",
62
- "marketing/payload/src/Header/Component.tsx": "import { getCachedGlobal } from \"@/utilities/getGlobals\"\nimport { HeaderClient } from \"./Component.client\"\n\nimport type { Header as HeaderType } from \"@/payload-types\"\n\nexport async function Header() {\n\tconst headerData: HeaderType = await getCachedGlobal(\"header\", 1)()\n\n\treturn <HeaderClient data={headerData} />\n}\n",
61
+ "marketing/payload/src/Header/Component.client.tsx": "\"use client\"\nimport { useHeaderTheme } from \"@/providers/HeaderTheme\"\nimport Link from \"next/link\"\nimport type React from \"react\"\nimport { useEffect, useMemo, useState } from \"react\"\n\nimport type { Header, Page, Post } from \"@/payload-types\"\n\nimport { Logo } from \"@/components/Logo/Logo\"\nimport { Button } from \"@/components/ui/button\"\nimport { MobileMenu } from \"./MobileMenu\"\nimport { HeaderNav } from \"./Nav\"\n\ninterface HeaderClientProps {\n\tdata: Header | null\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n\ttype?: \"custom\" | \"reference\" | null\n\treference?: {\n\t\trelationTo: \"pages\" | \"posts\"\n\t\tvalue: Page | Post | string | number\n\t} | null\n\turl?: string | null\n}): string => {\n\tif (link.type === \"reference\" && link.reference) {\n\t\tconst { relationTo, value } = link.reference\n\t\tif (typeof value === \"object\" && value.slug) {\n\t\t\treturn relationTo === \"pages\" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n\t\t}\n\t}\n\treturn link.url || \"#\"\n}\n\nexport const HeaderClient: React.FC<HeaderClientProps> = ({ data }) => {\n\t/* Storing the value in a useState to avoid hydration errors */\n\tconst [theme, setTheme] = useState<string | null>(null)\n\tconst { headerTheme, setHeaderTheme } = useHeaderTheme()\n\n\tuseEffect(() => {\n\t\tsetHeaderTheme(null)\n\t}, [setHeaderTheme])\n\n\tuseEffect(() => {\n\t\tif (headerTheme && headerTheme !== theme) setTheme(headerTheme)\n\t}, [headerTheme, theme])\n\n\t// Get the primary CTA (first right-positioned button item) for mobile view\n\tconst primaryCta = useMemo(() => {\n\t\tconst rightButtonItems = data?.navItems?.filter(\n\t\t\t(item) =>\n\t\t\t\t(item.position || \"left\") === \"right\" &&\n\t\t\t\titem.type === \"link\" &&\n\t\t\t\t(item.appearance || \"button\") === \"button\",\n\t\t)\n\t\treturn rightButtonItems?.[0]\n\t}, [data?.navItems])\n\n\t// Show setup header when database isn't initialized yet\n\tif (!data) {\n\t\treturn (\n\t\t\t<header className=\"sticky top-0 z-20 border-b border-border bg-background\">\n\t\t\t\t<div className=\"container mx-auto px-4 h-16 flex justify-between items-center\">\n\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">Welcome</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<Button asChild size=\"sm\">\n\t\t\t\t\t\t<Link href=\"/admin\">Setup Site →</Link>\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t</header>\n\t\t)\n\t}\n\n\treturn (\n\t\t<header\n\t\t\tclassName=\"sticky top-0 z-20 border-b border-border bg-background\"\n\t\t\t{...(theme ? { \"data-theme\": theme } : {})}\n\t\t>\n\t\t\t<div className=\"container mx-auto px-4 h-16 flex justify-between items-center\">\n\t\t\t\t{/* Left section: Logo + Left Nav Links */}\n\t\t\t\t<div className=\"flex items-center gap-8\">\n\t\t\t\t\t<Link href=\"/\" className=\"flex items-center gap-2\">\n\t\t\t\t\t\t<Logo loading=\"eager\" priority=\"high\" variant=\"auto\" />\n\t\t\t\t\t\t<span className=\"text-xl font-semibold hidden sm:inline\">DirectoryHub</span>\n\t\t\t\t\t</Link>\n\t\t\t\t\t{/* Left nav - hidden on tablet and below, visible on desktop */}\n\t\t\t\t\t<HeaderNav data={data} position=\"left\" className=\"hidden lg:flex gap-6 items-center\" />\n\t\t\t\t</div>\n\n\t\t\t\t{/* Right section: Right Nav Links (CTAs) */}\n\t\t\t\t<div className=\"flex items-center gap-3\">\n\t\t\t\t\t{/* Full right nav - hidden on mobile and tablet, visible on desktop */}\n\t\t\t\t\t<HeaderNav data={data} position=\"right\" className=\"hidden lg:flex gap-4 items-center\" />\n\n\t\t\t\t\t{/* Primary CTA for tablet/mobile - visible on tablet and below, hidden on desktop */}\n\t\t\t\t\t{primaryCta?.link && (\n\t\t\t\t\t\t<Button asChild className=\"lg:hidden\" size=\"sm\">\n\t\t\t\t\t\t\t<Link href={getLinkHref(primaryCta.link)}>{primaryCta.label}</Link>\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Mobile menu hamburger - visible on tablet and below */}\n\t\t\t\t\t<MobileMenu data={data} />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</header>\n\t)\n}\n",
62
+ "marketing/payload/src/Header/Component.tsx": "import { getCachedGlobal } from \"@/utilities/getGlobals\"\nimport { HeaderClient } from \"./Component.client\"\n\nimport type { Header as HeaderType } from \"@/payload-types\"\n\nexport async function Header() {\n\tconst headerData = (await getCachedGlobal(\"header\", 1)()) as HeaderType | null\n\n\treturn <HeaderClient data={headerData} />\n}\n",
63
63
  "marketing/payload/src/Header/MegaMenu/index.tsx": "\"use client\"\n\nimport { Media } from \"@/components/Media\"\nimport { cn } from \"@/utilities/ui\"\nimport {\n\tBarChart3,\n\tBuilding,\n\tChevronDown,\n\tDatabase,\n\tDollarSign,\n\tGlobe,\n\tLayers,\n\tLayout,\n\ttype LucideIcon,\n\tRocket,\n\tSearch,\n\tSettings,\n\tShield,\n\tStore,\n\tTarget,\n\tUsers,\n\tZap,\n} from \"lucide-react\"\nimport Link from \"next/link\"\nimport type React from \"react\"\nimport { useState } from \"react\"\n\nimport type { Header, Media as MediaType, Page, Post } from \"@/payload-types\"\n\ntype NavItem = NonNullable<Header[\"navItems\"]>[number]\n\nconst iconMap: Record<string, LucideIcon> = {\n\tlayout: Layout,\n\tdollarSign: DollarSign,\n\tsearch: Search,\n\tsettings: Settings,\n\tzap: Zap,\n\tlayers: Layers,\n\tusers: Users,\n\tbuilding: Building,\n\tglobe: Globe,\n\tstore: Store,\n\trocket: Rocket,\n\ttarget: Target,\n\tbarChart: BarChart3,\n\tshield: Shield,\n\tdatabase: Database,\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n\ttype?: \"custom\" | \"reference\" | null\n\treference?: {\n\t\trelationTo: \"pages\" | \"posts\"\n\t\tvalue: Page | Post | string | number\n\t} | null\n\turl?: string | null\n}): string => {\n\tif (link.type === \"reference\" && link.reference) {\n\t\tconst { relationTo, value } = link.reference\n\t\tif (typeof value === \"object\" && value.slug) {\n\t\t\treturn relationTo === \"pages\" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n\t\t}\n\t}\n\treturn link.url || \"#\"\n}\n\ninterface MegaMenuProps {\n\titem: NavItem\n}\n\nexport const MegaMenu: React.FC<MegaMenuProps> = ({ item }) => {\n\tconst [isOpen, setIsOpen] = useState(false)\n\n\tconst columns = item.megaMenuColumns || []\n\tconst featuredItem = item.featuredItem\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"relative\"\n\t\t\tonMouseEnter={() => setIsOpen(true)}\n\t\t\tonMouseLeave={() => setIsOpen(false)}\n\t\t>\n\t\t\t{/* Trigger */}\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"flex items-center gap-1 text-sm font-medium transition-colors hover:text-primary\",\n\t\t\t\t\tisOpen && \"text-primary\",\n\t\t\t\t)}\n\t\t\t\tonClick={() => setIsOpen(!isOpen)}\n\t\t\t>\n\t\t\t\t{item.label}\n\t\t\t\t<ChevronDown\n\t\t\t\t\tclassName={cn(\"h-4 w-4 transition-transform duration-200\", isOpen && \"rotate-180\")}\n\t\t\t\t/>\n\t\t\t</button>\n\n\t\t\t{/* Dropdown */}\n\t\t\t{isOpen && (\n\t\t\t\t<div className=\"absolute left-1/2 -translate-x-1/2 top-full pt-4 z-50\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"bg-background border border-border rounded-xl shadow-xl overflow-hidden\",\n\t\t\t\t\t\t\t\"animate-in fade-in-0 zoom-in-95 duration-200\",\n\t\t\t\t\t\t\tfeaturedItem?.enabled ? \"min-w-[700px]\" : \"min-w-[500px]\",\n\t\t\t\t\t\t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex\">\n\t\t\t\t\t\t\t{/* Menu Columns */}\n\t\t\t\t\t\t\t<div className={cn(\"flex-1 p-6\", columns.length > 1 ? \"grid grid-cols-2 gap-8\" : \"\")}>\n\t\t\t\t\t\t\t\t{columns.map((column) => (\n\t\t\t\t\t\t\t\t\t<div key={column.columnLabel || \"column\"}>\n\t\t\t\t\t\t\t\t\t\t{column.columnLabel && (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"mb-4\">\n\t\t\t\t\t\t\t\t\t\t\t\t<h3 className=\"text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{column.columnLabel}\n\t\t\t\t\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t\t\t\t\t{column.columnDescription && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground mt-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{column.columnDescription}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t<ul className=\"space-y-1\">\n\t\t\t\t\t\t\t\t\t\t\t{column.items?.map((menuItem) => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst Icon =\n\t\t\t\t\t\t\t\t\t\t\t\t\tmenuItem.icon && menuItem.icon !== \"none\" ? iconMap[menuItem.icon] : null\n\t\t\t\t\t\t\t\t\t\t\t\tconst href = menuItem.link ? getLinkHref(menuItem.link) : \"#\"\n\n\t\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<li key={menuItem.label}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thref={href}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"flex items-start gap-3 p-2 rounded-lg hover:bg-muted transition-colors group\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => setIsOpen(false)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{Icon && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex-shrink-0 w-9 h-9 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon className=\"w-4 h-4 text-primary\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex-1 min-w-0\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-sm font-medium group-hover:text-primary transition-colors\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{menuItem.label}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{menuItem.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-xs text-muted-foreground mt-0.5 line-clamp-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{menuItem.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t{/* Featured Section */}\n\t\t\t\t\t\t\t{featuredItem?.enabled && (\n\t\t\t\t\t\t\t\t<div className=\"w-64 bg-muted/50 p-6 border-l border-border\">\n\t\t\t\t\t\t\t\t\t{featuredItem.image && typeof featuredItem.image === \"object\" && (\n\t\t\t\t\t\t\t\t\t\t<div className=\"rounded-lg overflow-hidden mb-4 aspect-video\">\n\t\t\t\t\t\t\t\t\t\t\t<Media\n\t\t\t\t\t\t\t\t\t\t\t\tresource={featuredItem.image as MediaType}\n\t\t\t\t\t\t\t\t\t\t\t\timgClassName=\"w-full h-full object-cover\"\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t{featuredItem.heading && (\n\t\t\t\t\t\t\t\t\t\t<h4 className=\"font-semibold text-sm mb-2\">{featuredItem.heading}</h4>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t{featuredItem.description && (\n\t\t\t\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground mb-4\">{featuredItem.description}</p>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t{featuredItem.link && (\n\t\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\t\thref={getLinkHref(featuredItem.link)}\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-xs font-medium text-primary hover:underline\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => setIsOpen(false)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{featuredItem.link.label || \"Learn more\"} →\n\t\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t)\n}\n",
64
64
  "marketing/payload/src/Header/MobileMenu/HamburgerIcon.tsx": "\"use client\"\n\nimport { cn } from \"@/utilities/ui\"\nimport type React from \"react\"\n\ninterface HamburgerIconProps {\n\tisOpen: boolean\n\tonClick: () => void\n\tclassName?: string\n}\n\n/**\n * Animated 2-line hamburger icon that morphs into an X when open\n */\nexport const HamburgerIcon: React.FC<HamburgerIconProps> = ({ isOpen, onClick, className }) => {\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tonClick={onClick}\n\t\t\tclassName={cn(\n\t\t\t\t\"relative w-8 h-8 flex items-center justify-center focus:outline-none\",\n\t\t\t\t\"lg:hidden\", // Only show on tablet and below\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\taria-label={isOpen ? \"Close menu\" : \"Open menu\"}\n\t\t\taria-expanded={isOpen}\n\t\t>\n\t\t\t<div className=\"relative w-6 h-4 flex flex-col justify-between\">\n\t\t\t\t{/* Top line */}\n\t\t\t\t<span\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"absolute left-0 w-full h-0.5 bg-foreground rounded-full\",\n\t\t\t\t\t\t\"transition-all duration-300 ease-in-out origin-center\",\n\t\t\t\t\t\tisOpen ? \"top-1/2 -translate-y-1/2 rotate-45\" : \"top-0 translate-y-0 rotate-0\",\n\t\t\t\t\t)}\n\t\t\t\t/>\n\t\t\t\t{/* Bottom line */}\n\t\t\t\t<span\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"absolute left-0 w-full h-0.5 bg-foreground rounded-full\",\n\t\t\t\t\t\t\"transition-all duration-300 ease-in-out origin-center\",\n\t\t\t\t\t\tisOpen ? \"top-1/2 -translate-y-1/2 -rotate-45\" : \"bottom-0 translate-y-0 rotate-0\",\n\t\t\t\t\t)}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</button>\n\t)\n}\n",
65
65
  "marketing/payload/src/Header/MobileMenu/index.tsx": "\"use client\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/utilities/ui\"\nimport {\n\tBarChart3,\n\tBuilding,\n\tChevronRight,\n\tDatabase,\n\tDollarSign,\n\tGlobe,\n\tLayers,\n\tLayout,\n\ttype LucideIcon,\n\tRocket,\n\tSearch,\n\tSettings,\n\tShield,\n\tStore,\n\tTarget,\n\tUsers,\n\tZap,\n} from \"lucide-react\"\nimport Link from \"next/link\"\nimport type React from \"react\"\nimport { useEffect, useState } from \"react\"\n\nimport type { Header, Page, Post } from \"@/payload-types\"\nimport { HamburgerIcon } from \"./HamburgerIcon\"\n\nconst iconMap: Record<string, LucideIcon> = {\n\tlayout: Layout,\n\tdollarSign: DollarSign,\n\tsearch: Search,\n\tsettings: Settings,\n\tzap: Zap,\n\tlayers: Layers,\n\tusers: Users,\n\tbuilding: Building,\n\tglobe: Globe,\n\tstore: Store,\n\trocket: Rocket,\n\ttarget: Target,\n\tbarChart: BarChart3,\n\tshield: Shield,\n\tdatabase: Database,\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n\ttype?: \"custom\" | \"reference\" | null\n\treference?: {\n\t\trelationTo: \"pages\" | \"posts\"\n\t\tvalue: Page | Post | string | number\n\t} | null\n\turl?: string | null\n}): string => {\n\tif (link.type === \"reference\" && link.reference) {\n\t\tconst { relationTo, value } = link.reference\n\t\tif (typeof value === \"object\" && value.slug) {\n\t\t\treturn relationTo === \"pages\" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n\t\t}\n\t}\n\treturn link.url || \"#\"\n}\n\ninterface MobileMenuProps {\n\tdata: Header\n}\n\nexport const MobileMenu: React.FC<MobileMenuProps> = ({ data }) => {\n\tconst [isOpen, setIsOpen] = useState(false)\n\tconst [expandedItems, setExpandedItems] = useState<Set<string>>(new Set())\n\n\tconst navItems = data?.navItems || []\n\tconst leftItems = navItems.filter((item) => (item.position || \"left\") === \"left\")\n\tconst rightItems = navItems.filter((item) => (item.position || \"left\") === \"right\")\n\n\t// Separate CTA items (typically the primary action buttons)\n\tconst ctaItems = rightItems.filter((item) => item.type === \"link\")\n\n\t// Prevent body scroll when menu is open\n\tuseEffect(() => {\n\t\tif (isOpen) {\n\t\t\tdocument.body.style.overflow = \"hidden\"\n\t\t} else {\n\t\t\tdocument.body.style.overflow = \"\"\n\t\t}\n\t\treturn () => {\n\t\t\tdocument.body.style.overflow = \"\"\n\t\t}\n\t}, [isOpen])\n\n\tconst toggleExpanded = (id: string) => {\n\t\tsetExpandedItems((prev) => {\n\t\t\tconst next = new Set(prev)\n\t\t\tif (next.has(id)) {\n\t\t\t\tnext.delete(id)\n\t\t\t} else {\n\t\t\t\tnext.add(id)\n\t\t\t}\n\t\t\treturn next\n\t\t})\n\t}\n\n\tconst closeMenu = () => {\n\t\tsetIsOpen(false)\n\t\tsetExpandedItems(new Set())\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t{/* Hamburger button - visible on tablet and below */}\n\t\t\t<HamburgerIcon isOpen={isOpen} onClick={() => setIsOpen(!isOpen)} />\n\n\t\t\t{/* Mobile menu overlay */}\n\t\t\t<div\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"fixed inset-0 z-50 lg:hidden\",\n\t\t\t\t\t\"transition-opacity duration-300\",\n\t\t\t\t\tisOpen ? \"opacity-100 pointer-events-auto\" : \"opacity-0 pointer-events-none\",\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t{/* Backdrop */}\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"absolute inset-0 bg-background/80 backdrop-blur-sm\"\n\t\t\t\t\tonClick={closeMenu}\n\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\tif (e.key === \"Enter\" || e.key === \" \") {\n\t\t\t\t\t\t\te.preventDefault()\n\t\t\t\t\t\t\tcloseMenu()\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t\trole=\"button\"\n\t\t\t\t\ttabIndex={0}\n\t\t\t\t\taria-label=\"Close menu\"\n\t\t\t\t/>\n\n\t\t\t\t{/* Menu panel */}\n\t\t\t\t<div\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"absolute top-0 right-0 h-full w-full max-w-md bg-background border-l border-border\",\n\t\t\t\t\t\t\"flex flex-col\",\n\t\t\t\t\t\t\"transition-transform duration-300 ease-out\",\n\t\t\t\t\t\tisOpen ? \"translate-x-0\" : \"translate-x-full\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{/* Header with close button */}\n\t\t\t\t\t<div className=\"flex items-center justify-between p-4 border-b border-border\">\n\t\t\t\t\t\t<Link href=\"/\" onClick={closeMenu} className=\"text-xl font-semibold\">\n\t\t\t\t\t\t\tDirectoryHub\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t<HamburgerIcon isOpen={isOpen} onClick={closeMenu} className=\"lg:block\" />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Nav items */}\n\t\t\t\t\t<div className=\"flex-1 overflow-y-auto p-4\">\n\t\t\t\t\t\t<nav className=\"space-y-1\">\n\t\t\t\t\t\t\t{leftItems.map((item, index) => {\n\t\t\t\t\t\t\t\tconst itemId = item.id || `item-${index}`\n\t\t\t\t\t\t\t\tconst isExpanded = expandedItems.has(itemId)\n\t\t\t\t\t\t\t\tconst hasMegaMenu = item.type === \"megaMenu\"\n\n\t\t\t\t\t\t\t\tif (hasMegaMenu) {\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t<div key={itemId}>\n\t\t\t\t\t\t\t\t\t\t\t{/* Expandable item */}\n\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => toggleExpanded(itemId)}\n\t\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"w-full flex items-center justify-between py-4 text-lg font-medium\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"border-b border-border/50 transition-colors hover:text-primary\",\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<span>{item.label}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t<ChevronRight\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"w-5 h-5 transition-transform duration-200\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tisExpanded && \"rotate-90\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t</button>\n\n\t\t\t\t\t\t\t\t\t\t\t{/* Expanded content */}\n\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"overflow-hidden transition-all duration-300\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tisExpanded ? \"max-h-[1000px] opacity-100\" : \"max-h-0 opacity-0\",\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"py-2 pl-4 space-y-4\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{item.megaMenuColumns?.map((column, colIndex) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div key={column.columnLabel || `col-${colIndex}`}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{column.columnLabel && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{column.columnLabel}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<ul className=\"space-y-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{column.items?.map((menuItem) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst Icon =\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmenuItem.icon && menuItem.icon !== \"none\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? iconMap[menuItem.icon]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst href = menuItem.link ? getLinkHref(menuItem.link) : \"#\"\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<li key={menuItem.label}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thref={href}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={closeMenu}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"flex items-center gap-3 py-2 text-sm hover:text-primary transition-colors\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{Icon && <Icon className=\"w-4 h-4 text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span>{menuItem.label}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Simple link item\n\t\t\t\t\t\t\t\tconst link = item.link\n\t\t\t\t\t\t\t\tif (!link) return null\n\t\t\t\t\t\t\t\tconst href = getLinkHref(link)\n\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\tkey={itemId}\n\t\t\t\t\t\t\t\t\t\thref={href}\n\t\t\t\t\t\t\t\t\t\tonClick={closeMenu}\n\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\"block py-4 text-lg font-medium\",\n\t\t\t\t\t\t\t\t\t\t\t\"border-b border-border/50 transition-colors hover:text-primary\",\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{item.label}\n\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</nav>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Bottom CTA section */}\n\t\t\t\t\t<div className=\"p-4 border-t border-border space-y-3\">\n\t\t\t\t\t\t{ctaItems.map((item, index) => {\n\t\t\t\t\t\t\tconst link = item.link\n\t\t\t\t\t\t\tif (!link) return null\n\t\t\t\t\t\t\tconst href = getLinkHref(link)\n\t\t\t\t\t\t\tconst itemAppearance = item.appearance || \"button\"\n\t\t\t\t\t\t\tconst isButton = itemAppearance === \"button\"\n\n\t\t\t\t\t\t\tif (isButton) {\n\t\t\t\t\t\t\t\t// First button item is primary (filled), others are outline\n\t\t\t\t\t\t\t\tconst buttonItems = ctaItems.filter((i) => (i.appearance || \"button\") === \"button\")\n\t\t\t\t\t\t\t\tconst buttonIndex = buttonItems.findIndex((i) => i.id === item.id)\n\t\t\t\t\t\t\t\tconst isPrimary = buttonIndex === 0\n\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tkey={item.id || `cta-${index}`}\n\t\t\t\t\t\t\t\t\t\tasChild\n\t\t\t\t\t\t\t\t\t\tvariant={isPrimary ? \"default\" : \"outline\"}\n\t\t\t\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t\t\t\t\tsize=\"lg\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<Link href={href} onClick={closeMenu}>\n\t\t\t\t\t\t\t\t\t\t\t{item.label}\n\t\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Link appearance - render as text link\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\tkey={item.id || `cta-${index}`}\n\t\t\t\t\t\t\t\t\thref={href}\n\t\t\t\t\t\t\t\t\tonClick={closeMenu}\n\t\t\t\t\t\t\t\t\tclassName=\"block w-full text-center py-3 text-sm font-medium hover:text-primary transition-colors\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{item.label}\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t})}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</>\n\t)\n}\n",
@@ -263,7 +263,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
263
263
  "marketing/payload/src/lib/convex.ts": "import { ConvexHttpClient } from \"convex/browser\"\n\n/**\n * Convex HTTP client for server-side data fetching in the marketing site.\n * Used for fetching documentation content from Convex.\n */\nconst convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL\n\nif (!convexUrl) {\n\tconsole.warn(\"[Convex] NEXT_PUBLIC_CONVEX_URL is not set. Documentation features will not work.\")\n}\n\nexport const convex = new ConvexHttpClient(convexUrl || \"\")\n",
264
264
  "marketing/payload/src/lib/docs-source.ts": "import { api } from \"../../../../convex/_generated/api\"\nimport { convex } from \"./convex\"\n\n/**\n * Fetch published docs from Convex\n */\nexport async function getDocsFromConvex() {\n\ttry {\n\t\tif (!process.env.NEXT_PUBLIC_CONVEX_URL) {\n\t\t\tconsole.error(\n\t\t\t\t\"[Docs] NEXT_PUBLIC_CONVEX_URL environment variable is not set. Docs will not be available.\",\n\t\t\t)\n\t\t\treturn []\n\t\t}\n\n\t\tconst docs = await convex.query(api.docs.listPublished)\n\n\t\tif (process.env.NODE_ENV === \"development\") {\n\t\t\tconsole.log(`[Docs] Fetched ${docs.length} published document(s) from Convex`)\n\t\t}\n\n\t\treturn docs\n\t} catch (error) {\n\t\tconsole.error(\"[Docs] Failed to fetch docs from Convex:\", error)\n\t\treturn []\n\t}\n}\n\n/**\n * Fetch a single doc by slug from Convex\n */\nexport async function getDocBySlug(slug: string) {\n\ttry {\n\t\tif (!process.env.NEXT_PUBLIC_CONVEX_URL) {\n\t\t\tconsole.error(\n\t\t\t\t\"[Docs] NEXT_PUBLIC_CONVEX_URL environment variable is not set. Docs will not be available.\",\n\t\t\t)\n\t\t\treturn null\n\t\t}\n\n\t\tconst doc = await convex.query(api.docs.getBySlug, { slug })\n\t\treturn doc\n\t} catch (error) {\n\t\tconsole.error(`[Docs] Failed to fetch doc \"${slug}\" from Convex:`, error)\n\t\treturn null\n\t}\n}\n\n/**\n * Build page tree for navigation from docs\n */\nexport function buildPageTree(docs: Awaited<ReturnType<typeof getDocsFromConvex>>) {\n\t// Sort docs by order, then alphabetically\n\tconst sortedDocs = [...docs].sort((a, b) => {\n\t\tif (a.order !== undefined && b.order !== undefined) {\n\t\t\treturn a.order - b.order\n\t\t}\n\t\tif (a.order !== undefined) return -1\n\t\tif (b.order !== undefined) return 1\n\t\treturn a.title.localeCompare(b.title)\n\t})\n\n\t// Group docs by parent\n\tconst topLevel = sortedDocs.filter((d) => !d.parentSlug)\n\tconst byParent = new Map<string, typeof sortedDocs>()\n\n\tfor (const doc of sortedDocs) {\n\t\tif (doc.parentSlug) {\n\t\t\tconst existing = byParent.get(doc.parentSlug) || []\n\t\t\texisting.push(doc)\n\t\t\tbyParent.set(doc.parentSlug, existing)\n\t\t}\n\t}\n\n\t// Build tree structure\n\tconst buildChildren = (parentSlug: string): PageTreeItem[] => {\n\t\tconst children = byParent.get(parentSlug) || []\n\t\treturn children.map((doc) => ({\n\t\t\ttype: \"page\" as const,\n\t\t\tname: doc.title,\n\t\t\turl: `/docs/${doc.slug}`,\n\t\t\tchildren: buildChildren(doc.slug),\n\t\t}))\n\t}\n\n\tconst tree: PageTreeItem[] = topLevel.map((doc) => {\n\t\tconst children = buildChildren(doc.slug)\n\t\tif (children.length > 0) {\n\t\t\treturn {\n\t\t\t\ttype: \"folder\" as const,\n\t\t\t\tname: doc.title,\n\t\t\t\tindex: {\n\t\t\t\t\ttype: \"page\" as const,\n\t\t\t\t\tname: doc.title,\n\t\t\t\t\turl: `/docs/${doc.slug}`,\n\t\t\t\t},\n\t\t\t\tchildren,\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\ttype: \"page\" as const,\n\t\t\tname: doc.title,\n\t\t\turl: `/docs/${doc.slug}`,\n\t\t}\n\t})\n\n\treturn {\n\t\tname: \"Documentation\",\n\t\tchildren: tree,\n\t}\n}\n\n// Types for page tree\ntype PageTreeItem =\n\t| {\n\t\t\ttype: \"page\"\n\t\t\tname: string\n\t\t\turl: string\n\t\t\tchildren?: PageTreeItem[]\n\t }\n\t| {\n\t\t\ttype: \"folder\"\n\t\t\tname: string\n\t\t\tindex?: { type: \"page\"; name: string; url: string }\n\t\t\tchildren: PageTreeItem[]\n\t }\n\t| {\n\t\t\ttype: \"separator\"\n\t\t\tname: string\n\t }\n\n/**\n * Get all page slugs for static generation\n */\nexport async function getAllDocSlugs(): Promise<string[][]> {\n\tconst docs = await getDocsFromConvex()\n\treturn docs.map((doc) => doc.slug.split(\"/\"))\n}\n",
265
265
  "marketing/payload/src/lib/mdx.tsx": "import { compile, run } from \"@mdx-js/mdx\"\nimport type { ReactNode } from \"react\"\nimport { createElement } from \"react\"\nimport * as runtime from \"react/jsx-runtime\"\n\ninterface TableOfContentsItem {\n\ttitle: string\n\turl: string\n\tdepth: number\n}\n\n/**\n * Compile MDX content to React components\n */\nexport async function compileMDX(source: string): Promise<{\n\tcontent: ReactNode\n\ttoc: TableOfContentsItem[]\n}> {\n\t// Extract headings for TOC before compilation\n\tconst toc = extractTableOfContents(source)\n\n\ttry {\n\t\t// Compile MDX to JavaScript\n\t\tconst compiled = await compile(source, {\n\t\t\toutputFormat: \"function-body\",\n\t\t\tdevelopment: false,\n\t\t})\n\n\t\t// Run the compiled code\n\t\tconst { default: MDXContent } = await run(String(compiled), {\n\t\t\t...runtime,\n\t\t\tbaseUrl: import.meta.url,\n\t\t})\n\n\t\t// Create the content element with custom components\n\t\tconst content = createElement(MDXContent, {\n\t\t\tcomponents: getMDXComponents(),\n\t\t})\n\n\t\treturn { content, toc }\n\t} catch (error) {\n\t\tconsole.error(\"MDX compilation error:\", error)\n\n\t\t// Return a fallback with the raw content\n\t\treturn {\n\t\t\tcontent: createElement(\n\t\t\t\t\"div\",\n\t\t\t\t{ className: \"prose dark:prose-invert\" },\n\t\t\t\tcreateElement(\"p\", { className: \"text-red-500\" }, \"Error rendering content\"),\n\t\t\t\tcreateElement(\"pre\", { className: \"text-sm\" }, source),\n\t\t\t),\n\t\t\ttoc,\n\t\t}\n\t}\n}\n\n/**\n * Extract table of contents from markdown headings\n */\nfunction extractTableOfContents(source: string): TableOfContentsItem[] {\n\tconst headingRegex = /^(#{1,6})\\s+(.+)$/gm\n\tconst toc: TableOfContentsItem[] = []\n\n\tconst matches = source.matchAll(headingRegex)\n\tfor (const match of matches) {\n\t\tconst depth = match[1]?.length ?? 1\n\t\tconst title = match[2]?.trim() ?? \"\"\n\n\t\t// Create URL-friendly slug\n\t\tconst url = `#${title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9\\s-]/g, \"\")\n\t\t\t.replace(/\\s+/g, \"-\")}`\n\n\t\ttoc.push({ title, url, depth })\n\t}\n\n\treturn toc\n}\n\n/**\n * Custom MDX components for styling\n */\nfunction getMDXComponents() {\n\treturn {\n\t\th1: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n\t\t\tcreateElement(\"h1\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"scroll-m-20 text-4xl font-bold tracking-tight\",\n\t\t\t\tid: slugify(String(props.children)),\n\t\t\t}),\n\t\th2: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n\t\t\tcreateElement(\"h2\", {\n\t\t\t\t...props,\n\t\t\t\tclassName:\n\t\t\t\t\t\"scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0 mt-10\",\n\t\t\t\tid: slugify(String(props.children)),\n\t\t\t}),\n\t\th3: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n\t\t\tcreateElement(\"h3\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"scroll-m-20 text-2xl font-semibold tracking-tight mt-8\",\n\t\t\t\tid: slugify(String(props.children)),\n\t\t\t}),\n\t\th4: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n\t\t\tcreateElement(\"h4\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"scroll-m-20 text-xl font-semibold tracking-tight mt-6\",\n\t\t\t\tid: slugify(String(props.children)),\n\t\t\t}),\n\t\tp: (props: React.HTMLAttributes<HTMLParagraphElement>) =>\n\t\t\tcreateElement(\"p\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"leading-7 [&:not(:first-child)]:mt-6\",\n\t\t\t}),\n\t\tul: (props: React.HTMLAttributes<HTMLUListElement>) =>\n\t\t\tcreateElement(\"ul\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"my-6 ml-6 list-disc [&>li]:mt-2\",\n\t\t\t}),\n\t\tol: (props: React.HTMLAttributes<HTMLOListElement>) =>\n\t\t\tcreateElement(\"ol\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"my-6 ml-6 list-decimal [&>li]:mt-2\",\n\t\t\t}),\n\t\tli: (props: React.HTMLAttributes<HTMLLIElement>) => createElement(\"li\", { ...props }),\n\t\tblockquote: (props: React.HTMLAttributes<HTMLQuoteElement>) =>\n\t\t\tcreateElement(\"blockquote\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"mt-6 border-l-2 pl-6 italic\",\n\t\t\t}),\n\t\tcode: (props: React.HTMLAttributes<HTMLElement>) => {\n\t\t\t// Check if this is an inline code or a code block\n\t\t\tconst isInline = typeof props.children === \"string\"\n\t\t\tif (isInline) {\n\t\t\t\treturn createElement(\"code\", {\n\t\t\t\t\t...props,\n\t\t\t\t\tclassName:\n\t\t\t\t\t\t\"relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold\",\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn createElement(\"code\", { ...props })\n\t\t},\n\t\tpre: (props: React.HTMLAttributes<HTMLPreElement>) =>\n\t\t\tcreateElement(\"pre\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"mb-4 mt-6 overflow-x-auto rounded-lg border bg-zinc-950 py-4 dark:bg-zinc-900\",\n\t\t\t}),\n\t\ta: (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) =>\n\t\t\tcreateElement(\"a\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"font-medium text-primary underline underline-offset-4\",\n\t\t\t}),\n\t\ttable: (props: React.HTMLAttributes<HTMLTableElement>) =>\n\t\t\tcreateElement(\n\t\t\t\t\"div\",\n\t\t\t\t{ className: \"my-6 w-full overflow-y-auto\" },\n\t\t\t\tcreateElement(\"table\", { ...props, className: \"w-full\" }),\n\t\t\t),\n\t\ttr: (props: React.HTMLAttributes<HTMLTableRowElement>) =>\n\t\t\tcreateElement(\"tr\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"m-0 border-t p-0 even:bg-muted\",\n\t\t\t}),\n\t\tth: (props: React.HTMLAttributes<HTMLTableCellElement>) =>\n\t\t\tcreateElement(\"th\", {\n\t\t\t\t...props,\n\t\t\t\tclassName:\n\t\t\t\t\t\"border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right\",\n\t\t\t}),\n\t\ttd: (props: React.HTMLAttributes<HTMLTableCellElement>) =>\n\t\t\tcreateElement(\"td\", {\n\t\t\t\t...props,\n\t\t\t\tclassName:\n\t\t\t\t\t\"border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right\",\n\t\t\t}),\n\t\thr: () => createElement(\"hr\", { className: \"my-4 md:my-8\" }),\n\t\timg: (props: React.ImgHTMLAttributes<HTMLImageElement>) =>\n\t\t\tcreateElement(\"img\", {\n\t\t\t\t...props,\n\t\t\t\tclassName: \"rounded-md border\",\n\t\t\t}),\n\t}\n}\n\nfunction slugify(text: string): string {\n\treturn text\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9\\s-]/g, \"\")\n\t\t.replace(/\\s+/g, \"-\")\n}\n",
266
- "marketing/payload/src/payload.config.ts.hbs": "import path from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport { postgresAdapter } from \"@payloadcms/db-postgres\"\nimport { resendAdapter } from \"@payloadcms/email-resend\"\nimport type { SharpDependency } from \"payload\"\nimport { type PayloadRequest, buildConfig } from \"payload\"\nimport sharp from \"sharp\"\n\nimport { defaultLexical } from \"@/fields/defaultLexical\"\nimport { Footer } from \"./Footer/config\"\nimport { Header } from \"./Header/config\"\nimport { Categories } from \"./collections/Categories\"\nimport { FAQs } from \"./collections/FAQs\"\nimport { Media } from \"./collections/Media\"\nimport { Pages } from \"./collections/Pages\"\nimport { Posts } from \"./collections/Posts\"\nimport { Users } from \"./collections/Users\"\nimport { plugins } from \"./plugins\"\nimport { getServerSideURL } from \"./utilities/getURL\"\n\nconst filename = fileURLToPath(import.meta.url)\nconst dirname = path.dirname(filename)\n\nexport default buildConfig({\n\tadmin: {\n\t\tcomponents: {\n\t\t\t// The `BeforeLogin` component renders a message that you see while logging into your admin panel.\n\t\t\t// Feel free to delete this at any time. Simply remove the line below.\n\t\t\tbeforeLogin: [\"@/components/BeforeLogin\"],\n\t\t\t// The `BeforeDashboard` component renders the 'welcome' block that you see after logging into your admin panel.\n\t\t\t// Feel free to delete this at any time. Simply remove the line below.\n\t\t\tbeforeDashboard: [\"@/components/BeforeDashboard\"],\n\t\t},\n\t\timportMap: {\n\t\t\tbaseDir: path.resolve(dirname),\n\t\t},\n\t\tuser: Users.slug,\n\t\tlivePreview: {\n\t\t\tbreakpoints: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Mobile\",\n\t\t\t\t\tname: \"mobile\",\n\t\t\t\t\twidth: 375,\n\t\t\t\t\theight: 667,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Tablet\",\n\t\t\t\t\tname: \"tablet\",\n\t\t\t\t\twidth: 768,\n\t\t\t\t\theight: 1024,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Desktop\",\n\t\t\t\t\tname: \"desktop\",\n\t\t\t\t\twidth: 1440,\n\t\t\t\t\theight: 900,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t},\n\t// This config helps us configure global or default features that the other editors can inherit\n\teditor: defaultLexical,\n\tdb: postgresAdapter({\n\t\tpool: {\n\t\t\tconnectionString: process.env.DATABASE_URL || \"\",\n\t\t},\n\t\t// Disable auto-push in dev mode to prevent constraint name truncation issues\n\t\t// Use migrations instead: pnpm payload migrate\n\t\tpush: false,\n\t}),\n\tcollections: [Pages, Posts, Media, Categories, FAQs, Users],\n\tcors: [getServerSideURL()].filter(Boolean),\n\tglobals: [Header, Footer],\n\tplugins,\n\tsecret: process.env.PAYLOAD_SECRET,\n\tsharp: sharp as unknown as SharpDependency,\n\temail: resendAdapter({\n\t\tapiKey: process.env.RESEND_API_KEY || \"\",\n\t\tdefaultFromAddress: process.env.RESEND_FROM_EMAIL || \"noreply@example.com\",\n\t\tdefaultFromName: \"{{projectName}}\",\n\t}),\n\ttypescript: {\n\t\toutputFile: path.resolve(dirname, \"payload-types.ts\"),\n\t},\n\tjobs: {\n\t\taccess: {\n\t\t\trun: ({ req }: { req: PayloadRequest }): boolean => {\n\t\t\t\t// Allow logged in users to execute this endpoint (default)\n\t\t\t\tif (req.user) return true\n\n\t\t\t\t// If there is no logged in user, then check\n\t\t\t\t// for the Vercel Cron secret to be present as an\n\t\t\t\t// Authorization header:\n\t\t\t\tconst authHeader = req.headers.get(\"authorization\")\n\t\t\t\treturn authHeader === `Bearer ${process.env.CRON_SECRET}`\n\t\t\t},\n\t\t},\n\t\ttasks: [],\n\t},\n})\n",
266
+ "marketing/payload/src/payload.config.ts.hbs": "import path from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport { postgresAdapter } from \"@payloadcms/db-postgres\"\nimport { resendAdapter } from \"@payloadcms/email-resend\"\nimport type { SharpDependency } from \"payload\"\nimport { type PayloadRequest, buildConfig } from \"payload\"\nimport sharp from \"sharp\"\n\nimport { defaultLexical } from \"@/fields/defaultLexical\"\nimport { Footer } from \"./Footer/config\"\nimport { Header } from \"./Header/config\"\nimport { Categories } from \"./collections/Categories\"\nimport { FAQs } from \"./collections/FAQs\"\nimport { Media } from \"./collections/Media\"\nimport { Pages } from \"./collections/Pages\"\nimport { Posts } from \"./collections/Posts\"\nimport { Users } from \"./collections/Users\"\nimport { plugins } from \"./plugins\"\nimport { getServerSideURL } from \"./utilities/getURL\"\n\nconst filename = fileURLToPath(import.meta.url)\nconst dirname = path.dirname(filename)\n\nexport default buildConfig({\n\tadmin: {\n\t\tcomponents: {\n\t\t\t// The `BeforeLogin` component renders a message that you see while logging into your admin panel.\n\t\t\t// Feel free to delete this at any time. Simply remove the line below.\n\t\t\tbeforeLogin: [\"@/components/BeforeLogin\"],\n\t\t\t// The `BeforeDashboard` component renders the 'welcome' block that you see after logging into your admin panel.\n\t\t\t// Feel free to delete this at any time. Simply remove the line below.\n\t\t\tbeforeDashboard: [\"@/components/BeforeDashboard\"],\n\t\t},\n\t\timportMap: {\n\t\t\tbaseDir: path.resolve(dirname),\n\t\t},\n\t\tuser: Users.slug,\n\t\tlivePreview: {\n\t\t\tbreakpoints: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Mobile\",\n\t\t\t\t\tname: \"mobile\",\n\t\t\t\t\twidth: 375,\n\t\t\t\t\theight: 667,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Tablet\",\n\t\t\t\t\tname: \"tablet\",\n\t\t\t\t\twidth: 768,\n\t\t\t\t\theight: 1024,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Desktop\",\n\t\t\t\t\tname: \"desktop\",\n\t\t\t\t\twidth: 1440,\n\t\t\t\t\theight: 900,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t},\n\t// This config helps us configure global or default features that the other editors can inherit\n\teditor: defaultLexical,\n\tdb: postgresAdapter({\n\t\tpool: {\n\t\t\tconnectionString: process.env.DATABASE_URL || \"\",\n\t\t},\n\t\t// Auto-sync schema in development for better DX (first launch works immediately)\n\t\t// In production, use explicit migrations: pnpm payload migrate\n\t\tpush: process.env.NODE_ENV !== \"production\",\n\t}),\n\tcollections: [Pages, Posts, Media, Categories, FAQs, Users],\n\tcors: [getServerSideURL()].filter(Boolean),\n\tglobals: [Header, Footer],\n\tplugins,\n\tsecret: process.env.PAYLOAD_SECRET,\n\tsharp: sharp as unknown as SharpDependency,\n\temail: resendAdapter({\n\t\tapiKey: process.env.RESEND_API_KEY || \"\",\n\t\tdefaultFromAddress: process.env.RESEND_FROM_EMAIL || \"noreply@example.com\",\n\t\tdefaultFromName: \"{{projectName}}\",\n\t}),\n\ttypescript: {\n\t\toutputFile: path.resolve(dirname, \"payload-types.ts\"),\n\t},\n\tjobs: {\n\t\taccess: {\n\t\t\trun: ({ req }: { req: PayloadRequest }): boolean => {\n\t\t\t\t// Allow logged in users to execute this endpoint (default)\n\t\t\t\tif (req.user) return true\n\n\t\t\t\t// If there is no logged in user, then check\n\t\t\t\t// for the Vercel Cron secret to be present as an\n\t\t\t\t// Authorization header:\n\t\t\t\tconst authHeader = req.headers.get(\"authorization\")\n\t\t\t\treturn authHeader === `Bearer ${process.env.CRON_SECRET}`\n\t\t\t},\n\t\t},\n\t\ttasks: [],\n\t},\n})\n",
267
267
  "marketing/payload/src/plugins/index.ts": "import { revalidateRedirects } from \"@/hooks/revalidateRedirects\"\nimport { beforeSyncWithSearch } from \"@/search/beforeSync\"\nimport { searchFields } from \"@/search/fieldOverrides\"\nimport { formBuilderPlugin } from \"@payloadcms/plugin-form-builder\"\nimport { nestedDocsPlugin } from \"@payloadcms/plugin-nested-docs\"\nimport { redirectsPlugin } from \"@payloadcms/plugin-redirects\"\nimport { searchPlugin } from \"@payloadcms/plugin-search\"\nimport { seoPlugin } from \"@payloadcms/plugin-seo\"\nimport type { GenerateTitle, GenerateURL } from \"@payloadcms/plugin-seo/types\"\nimport { FixedToolbarFeature, HeadingFeature, lexicalEditor } from \"@payloadcms/richtext-lexical\"\nimport { vercelBlobStorage } from \"@payloadcms/storage-vercel-blob\"\nimport type { Plugin } from \"payload\"\n\nimport type { Page, Post } from \"@/payload-types\"\nimport { getServerSideURL } from \"@/utilities/getURL\"\n\nconst generateTitle: GenerateTitle<Post | Page> = ({ doc }) => {\n\treturn doc?.title ? `${doc.title} | Payload Website Template` : \"Payload Website Template\"\n}\n\nconst generateURL: GenerateURL<Post | Page> = ({ doc }) => {\n\tconst url = getServerSideURL()\n\n\treturn doc?.slug ? `${url}/${doc.slug}` : url\n}\n\nexport const plugins: Plugin[] = [\n\t// Vercel Blob Storage for media uploads in production\n\t...(process.env.BLOB_READ_WRITE_TOKEN\n\t\t? [\n\t\t\t\tvercelBlobStorage({\n\t\t\t\t\tcollections: {\n\t\t\t\t\t\tmedia: true,\n\t\t\t\t\t},\n\t\t\t\t\ttoken: process.env.BLOB_READ_WRITE_TOKEN,\n\t\t\t\t\t// Enable client-side uploads to bypass Vercel serverless function body size limits\n\t\t\t\t\t// This allows uploads up to 500MB directly to Vercel Blob Storage\n\t\t\t\t\tclientUploads: true,\n\t\t\t\t}),\n\t\t\t]\n\t\t: []),\n\tredirectsPlugin({\n\t\tcollections: [\"pages\", \"posts\"],\n\t\toverrides: {\n\t\t\t// @ts-expect-error - This is a valid override, mapped fields don't resolve to the same type\n\t\t\tfields: ({ defaultFields }) => {\n\t\t\t\treturn defaultFields.map((field) => {\n\t\t\t\t\tif (\"name\" in field && field.name === \"from\") {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t...field,\n\t\t\t\t\t\t\tadmin: {\n\t\t\t\t\t\t\t\tdescription: \"You will need to rebuild the website when changing this field.\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn field\n\t\t\t\t})\n\t\t\t},\n\t\t\thooks: {\n\t\t\t\tafterChange: [revalidateRedirects],\n\t\t\t},\n\t\t},\n\t}),\n\tnestedDocsPlugin({\n\t\tcollections: [\"categories\"],\n\t\tgenerateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, \"\"),\n\t}),\n\tseoPlugin({\n\t\tgenerateTitle,\n\t\tgenerateURL,\n\t}),\n\tformBuilderPlugin({\n\t\tfields: {\n\t\t\tpayment: false,\n\t\t},\n\t\tformOverrides: {\n\t\t\tfields: ({ defaultFields }) => {\n\t\t\t\treturn defaultFields.map((field) => {\n\t\t\t\t\tif (\"name\" in field && field.name === \"confirmationMessage\") {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t...field,\n\t\t\t\t\t\t\teditor: lexicalEditor({\n\t\t\t\t\t\t\t\tfeatures: ({ rootFeatures }) => {\n\t\t\t\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\t\t\t...rootFeatures,\n\t\t\t\t\t\t\t\t\t\tFixedToolbarFeature(),\n\t\t\t\t\t\t\t\t\t\tHeadingFeature({ enabledHeadingSizes: [\"h1\", \"h2\", \"h3\", \"h4\"] }),\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn field\n\t\t\t\t})\n\t\t\t},\n\t\t},\n\t}),\n\tsearchPlugin({\n\t\tcollections: [\"posts\"],\n\t\tbeforeSync: beforeSyncWithSearch,\n\t\tsearchOverrides: {\n\t\t\tfields: ({ defaultFields }) => {\n\t\t\t\treturn [...defaultFields, ...searchFields]\n\t\t\t},\n\t\t},\n\t}),\n]\n",
268
268
  "marketing/payload/src/providers/HeaderTheme/index.tsx": "\"use client\"\n\nimport type { Theme } from \"@/providers/Theme/types\"\n\nimport type React from \"react\"\nimport { createContext, use, useCallback, useState } from \"react\"\n\nimport canUseDOM from \"@/utilities/canUseDOM\"\n\nexport interface ContextType {\n\theaderTheme?: Theme | null\n\tsetHeaderTheme: (theme: Theme | null) => void\n}\n\nconst initialContext: ContextType = {\n\theaderTheme: undefined,\n\tsetHeaderTheme: () => null,\n}\n\nconst HeaderThemeContext = createContext(initialContext)\n\nexport const HeaderThemeProvider = ({ children }: { children: React.ReactNode }) => {\n\tconst [headerTheme, setThemeState] = useState<Theme | undefined | null>(\n\t\tcanUseDOM ? (document.documentElement.getAttribute(\"data-theme\") as Theme) : undefined,\n\t)\n\n\tconst setHeaderTheme = useCallback((themeToSet: Theme | null) => {\n\t\tsetThemeState(themeToSet)\n\t}, [])\n\n\treturn <HeaderThemeContext value={{ headerTheme, setHeaderTheme }}>{children}</HeaderThemeContext>\n}\n\nexport const useHeaderTheme = (): ContextType => use(HeaderThemeContext)\n",
269
269
  "marketing/payload/src/providers/PostHogProvider.tsx": "\"use client\"\n\nimport { usePathname, useSearchParams } from \"next/navigation\"\nimport posthog from \"posthog-js\"\nimport { Suspense, useEffect } from \"react\"\n\nfunction PostHogPageviewTracker() {\n\tconst pathname = usePathname()\n\tconst searchParams = useSearchParams()\n\n\tuseEffect(() => {\n\t\t// Only track in production with PostHog configured\n\t\tif (process.env.NODE_ENV !== \"production\") return\n\t\tif (!process.env.NEXT_PUBLIC_POSTHOG_KEY) return\n\n\t\t// Track pageviews when the route changes\n\t\tconst url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : \"\")\n\t\tposthog.capture(\"$pageview\", { $current_url: url })\n\t}, [pathname, searchParams])\n\n\treturn null\n}\n\nexport function PostHogProvider({ children }: { children: React.ReactNode }) {\n\treturn (\n\t\t<>\n\t\t\t<Suspense fallback={null}>\n\t\t\t\t<PostHogPageviewTracker />\n\t\t\t</Suspense>\n\t\t\t{children}\n\t\t</>\n\t)\n}\n",
@@ -285,7 +285,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
285
285
  "marketing/payload/src/utilities/generateMeta.ts": "import type { Metadata } from \"next\"\n\nimport type { Config, Media, Page, Post } from \"../payload-types\"\n\nimport { getServerSideURL } from \"./getURL\"\nimport { mergeOpenGraph } from \"./mergeOpenGraph\"\n\nconst getImageURL = (image?: Media | Config[\"db\"][\"defaultIDType\"] | null) => {\n\tconst serverUrl = getServerSideURL()\n\n\tlet url = `${serverUrl}/website-template-OG.webp`\n\n\tif (image && typeof image === \"object\" && \"url\" in image) {\n\t\tconst ogUrl = image.sizes?.og?.url\n\n\t\turl = ogUrl ? `${serverUrl}${ogUrl}` : `${serverUrl}${image.url}`\n\t}\n\n\treturn url\n}\n\n// Default keywords for SEO\nconst defaultKeywords = [\n\t\"directory builder\",\n\t\"no-code directory\",\n\t\"directory website\",\n\t\"niche directory\",\n\t\"business directory software\",\n\t\"directory platform\",\n\t\"monetize directory\",\n\t\"SEO directory\",\n\t\"directory SaaS\",\n\t\"create directory website\",\n]\n\nexport const generateMeta = async (args: {\n\tdoc: Partial<Page> | Partial<Post> | null\n}): Promise<Metadata> => {\n\tconst { doc } = args\n\tconst serverUrl = getServerSideURL()\n\n\tconst ogImage = getImageURL(doc?.meta?.image)\n\n\tconst title = doc?.meta?.title\n\t\t? `${doc?.meta?.title} | DirectoryHub`\n\t\t: \"DirectoryHub - Build Directories That Generate Real Business\"\n\n\tconst description =\n\t\tdoc?.meta?.description ||\n\t\t\"Launch a profitable directory business in minutes. The no-code platform to build, manage, and monetize niche directory websites with built-in payments, SEO, and multi-tenant scalability.\"\n\n\t// Generate canonical URL\n\tconst slug = Array.isArray(doc?.slug) ? doc?.slug.join(\"/\") : doc?.slug || \"\"\n\tconst canonicalUrl = slug === \"home\" ? serverUrl : `${serverUrl}/${slug}`\n\n\treturn {\n\t\ttitle,\n\t\tdescription,\n\t\tkeywords: defaultKeywords,\n\t\tauthors: [{ name: \"DirectoryHub\", url: serverUrl }],\n\t\tcreator: \"DirectoryHub\",\n\t\tpublisher: \"DirectoryHub\",\n\t\trobots: {\n\t\t\tindex: true,\n\t\t\tfollow: true,\n\t\t\tgoogleBot: {\n\t\t\t\tindex: true,\n\t\t\t\tfollow: true,\n\t\t\t\t\"max-video-preview\": -1,\n\t\t\t\t\"max-image-preview\": \"large\",\n\t\t\t\t\"max-snippet\": -1,\n\t\t\t},\n\t\t},\n\t\talternates: {\n\t\t\tcanonical: canonicalUrl,\n\t\t},\n\t\topenGraph: mergeOpenGraph({\n\t\t\tdescription,\n\t\t\timages: ogImage\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\turl: ogImage,\n\t\t\t\t\t\t\twidth: 1200,\n\t\t\t\t\t\t\theight: 630,\n\t\t\t\t\t\t\talt: title,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: undefined,\n\t\t\ttitle,\n\t\t\turl: canonicalUrl,\n\t\t}),\n\t}\n}\n",
286
286
  "marketing/payload/src/utilities/generatePreviewPath.ts": "import type { CollectionSlug, PayloadRequest } from \"payload\"\n\nconst collectionPrefixMap: Partial<Record<CollectionSlug, string>> = {\n\tposts: \"/posts\",\n\tpages: \"\",\n}\n\ntype Props = {\n\tcollection: keyof typeof collectionPrefixMap\n\tslug: string\n\treq: PayloadRequest\n}\n\nexport const generatePreviewPath = ({ collection, slug }: Props) => {\n\t// Allow empty strings, e.g. for the homepage\n\tif (slug === undefined || slug === null) {\n\t\treturn null\n\t}\n\n\t// Encode to support slugs with special characters\n\tconst encodedSlug = encodeURIComponent(slug)\n\n\tconst encodedParams = new URLSearchParams({\n\t\tslug: encodedSlug,\n\t\tcollection,\n\t\tpath: `${collectionPrefixMap[collection]}/${encodedSlug}`,\n\t\tpreviewSecret: process.env.PREVIEW_SECRET || \"\",\n\t})\n\n\tconst url = `/next/preview?${encodedParams.toString()}`\n\n\treturn url\n}\n",
287
287
  "marketing/payload/src/utilities/getDocument.ts": "import type { Config } from \"src/payload-types\"\n\nimport configPromise from \"@payload-config\"\nimport { unstable_cache } from \"next/cache\"\nimport { getPayload } from \"payload\"\n\ntype Collection = keyof Config[\"collections\"]\n\nasync function getDocument(collection: Collection, slug: string, depth = 2) {\n\tconst payload = await getPayload({ config: configPromise })\n\n\tconst page = await payload.find({\n\t\tcollection,\n\t\tdepth,\n\t\twhere: {\n\t\t\tslug: {\n\t\t\t\tequals: slug,\n\t\t\t},\n\t\t},\n\t})\n\n\treturn page.docs[0]\n}\n\n/**\n * Returns a unstable_cache function mapped with the cache tag for the slug\n * @param depth - Depth for populating relationships (default: 2 for rich text content)\n */\nexport const getCachedDocument = (collection: Collection, slug: string, depth = 2) =>\n\tunstable_cache(async () => getDocument(collection, slug, depth), [collection, slug], {\n\t\ttags: [`${collection}_${slug}`],\n\t})\n",
288
- "marketing/payload/src/utilities/getGlobals.ts": "import type { Config } from \"src/payload-types\"\n\nimport configPromise from \"@payload-config\"\nimport { unstable_cache } from \"next/cache\"\nimport { getPayload } from \"payload\"\n\ntype Global = keyof Config[\"globals\"]\n\nasync function getGlobal(slug: Global, depth = 0) {\n\tconst payload = await getPayload({ config: configPromise })\n\n\tconst global = await payload.findGlobal({\n\t\tslug,\n\t\tdepth,\n\t})\n\n\treturn global\n}\n\n/**\n * Returns a unstable_cache function mapped with the cache tag for the slug\n */\nexport const getCachedGlobal = (slug: Global, depth = 0) =>\n\tunstable_cache(async () => getGlobal(slug, depth), [slug], {\n\t\ttags: [`global_${slug}`],\n\t})\n",
288
+ "marketing/payload/src/utilities/getGlobals.ts": "import type { Config } from \"src/payload-types\"\n\nimport configPromise from \"@payload-config\"\nimport { unstable_cache } from \"next/cache\"\nimport { getPayload } from \"payload\"\n\ntype Global = keyof Config[\"globals\"]\n\nasync function getGlobal(slug: Global, depth = 0) {\n\ttry {\n\t\tconst payload = await getPayload({ config: configPromise })\n\n\t\tconst global = await payload.findGlobal({\n\t\t\tslug,\n\t\t\tdepth,\n\t\t})\n\n\t\treturn global\n\t} catch (error) {\n\t\t// Database tables may not exist yet on first launch\n\t\t// Return null so components can show a setup UI instead of crashing\n\t\tconsole.warn(`Could not fetch global \"${String(slug)}\". Database may not be initialized yet.`)\n\t\treturn null\n\t}\n}\n\n/**\n * Returns a unstable_cache function mapped with the cache tag for the slug\n */\nexport const getCachedGlobal = (slug: Global, depth = 0) =>\n\tunstable_cache(async () => getGlobal(slug, depth), [slug], {\n\t\ttags: [`global_${slug}`],\n\t})\n",
289
289
  "marketing/payload/src/utilities/getMeUser.ts": "import { cookies } from \"next/headers\"\nimport { redirect } from \"next/navigation\"\n\nimport type { User } from \"../payload-types\"\nimport { getClientSideURL } from \"./getURL\"\n\nexport const getMeUser = async (args?: {\n\tnullUserRedirect?: string\n\tvalidUserRedirect?: string\n}): Promise<{\n\ttoken: string\n\tuser: User\n}> => {\n\tconst { nullUserRedirect, validUserRedirect } = args || {}\n\tconst cookieStore = await cookies()\n\tconst token = cookieStore.get(\"payload-token\")?.value\n\n\tconst meUserReq = await fetch(`${getClientSideURL()}/api/users/me`, {\n\t\theaders: {\n\t\t\tAuthorization: `JWT ${token}`,\n\t\t},\n\t})\n\n\tconst {\n\t\tuser,\n\t}: {\n\t\tuser: User\n\t} = await meUserReq.json()\n\n\tif (validUserRedirect && meUserReq.ok && user) {\n\t\tredirect(validUserRedirect)\n\t}\n\n\tif (nullUserRedirect && (!meUserReq.ok || !user)) {\n\t\tredirect(nullUserRedirect)\n\t}\n\n\t// Token will exist here because if it doesn't the user will be redirected\n\treturn {\n\t\ttoken: token!,\n\t\tuser,\n\t}\n}\n",
290
290
  "marketing/payload/src/utilities/getMediaUrl.ts": "import { getClientSideURL } from \"@/utilities/getURL\"\n\n/**\n * Processes media resource URL to ensure proper formatting\n * @param url The original URL from the resource\n * @param cacheTag Optional cache tag to append to the URL\n * @returns Properly formatted URL with cache tag if provided\n */\nexport const getMediaUrl = (url: string | null | undefined, cacheTag?: string | null): string => {\n\tif (!url) return \"\"\n\n\tif (cacheTag && cacheTag !== \"\") {\n\t\tcacheTag = encodeURIComponent(cacheTag)\n\t}\n\n\t// Check if URL already has http/https protocol\n\tif (url.startsWith(\"http://\") || url.startsWith(\"https://\")) {\n\t\treturn cacheTag ? `${url}?${cacheTag}` : url\n\t}\n\n\t// Otherwise prepend client-side URL\n\tconst baseUrl = getClientSideURL()\n\treturn cacheTag ? `${baseUrl}${url}?${cacheTag}` : `${baseUrl}${url}`\n}\n",
291
291
  "marketing/payload/src/utilities/getRedirects.ts": "import configPromise from \"@payload-config\"\nimport { unstable_cache } from \"next/cache\"\nimport { getPayload } from \"payload\"\n\nexport async function getRedirects(depth = 1) {\n\tconst payload = await getPayload({ config: configPromise })\n\n\tconst { docs: redirects } = await payload.find({\n\t\tcollection: \"redirects\",\n\t\tdepth,\n\t\tlimit: 0,\n\t\tpagination: false,\n\t})\n\n\treturn redirects\n}\n\n/**\n * Returns a unstable_cache function mapped with the cache tag for 'redirects'.\n *\n * Cache all redirects together to avoid multiple fetches.\n */\nexport const getCachedRedirects = () =>\n\tunstable_cache(async () => getRedirects(), [\"redirects\"], {\n\t\ttags: [\"redirects\"],\n\t})\n",
@@ -13,7 +13,7 @@ import { ThemeSelector } from "@/providers/Theme/ThemeSelector"
13
13
  import { cn } from "@/utilities/ui"
14
14
 
15
15
  interface FooterClientProps {
16
- data: Footer
16
+ data: Footer | null
17
17
  }
18
18
 
19
19
  // Social icons as inline SVGs for flexibility
@@ -82,6 +82,20 @@ export const FooterClient: React.FC<FooterClientProps> = ({ data }) => {
82
82
  const [isSubmitting, setIsSubmitting] = useState(false)
83
83
  const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null)
84
84
 
85
+ // Show minimal footer when database isn't initialized yet
86
+ if (!data) {
87
+ return (
88
+ <footer className="mt-auto border-t border-border bg-background">
89
+ <div className="container mx-auto flex flex-col items-center justify-between gap-4 px-4 py-6 sm:flex-row">
90
+ <p className="text-sm text-muted-foreground">
91
+ Site not configured yet. Visit <Link href="/admin" className="underline hover:text-foreground">/admin</Link> to set up.
92
+ </p>
93
+ <ThemeSelector />
94
+ </div>
95
+ </footer>
96
+ )
97
+ }
98
+
85
99
  const { columns, socialLinks, newsletter, copyrightText, bottomLinks } = data
86
100
 
87
101
  const handleNewsletterSubmit = async (e: React.FormEvent) => {
@@ -5,7 +5,7 @@ import type { Footer as FooterType } from "@/payload-types"
5
5
  import { FooterClient } from "./Component.client"
6
6
 
7
7
  export async function Footer() {
8
- const footerData: FooterType = await getCachedGlobal("footer", 1)()
8
+ const footerData = (await getCachedGlobal("footer", 1)()) as FooterType | null
9
9
 
10
10
  return <FooterClient data={footerData} />
11
11
  }
@@ -12,7 +12,7 @@ import { MobileMenu } from "./MobileMenu"
12
12
  import { HeaderNav } from "./Nav"
13
13
 
14
14
  interface HeaderClientProps {
15
- data: Header
15
+ data: Header | null
16
16
  }
17
17
 
18
18
  // Helper to get href from link data
@@ -57,6 +57,22 @@ export const HeaderClient: React.FC<HeaderClientProps> = ({ data }) => {
57
57
  return rightButtonItems?.[0]
58
58
  }, [data?.navItems])
59
59
 
60
+ // Show setup header when database isn't initialized yet
61
+ if (!data) {
62
+ return (
63
+ <header className="sticky top-0 z-20 border-b border-border bg-background">
64
+ <div className="container mx-auto px-4 h-16 flex justify-between items-center">
65
+ <div className="flex items-center gap-2">
66
+ <span className="text-xl font-semibold">Welcome</span>
67
+ </div>
68
+ <Button asChild size="sm">
69
+ <Link href="/admin">Setup Site →</Link>
70
+ </Button>
71
+ </div>
72
+ </header>
73
+ )
74
+ }
75
+
60
76
  return (
61
77
  <header
62
78
  className="sticky top-0 z-20 border-b border-border bg-background"
@@ -4,7 +4,7 @@ import { HeaderClient } from "./Component.client"
4
4
  import type { Header as HeaderType } from "@/payload-types"
5
5
 
6
6
  export async function Header() {
7
- const headerData: HeaderType = await getCachedGlobal("header", 1)()
7
+ const headerData = (await getCachedGlobal("header", 1)()) as HeaderType | null
8
8
 
9
9
  return <HeaderClient data={headerData} />
10
10
  }
@@ -64,9 +64,9 @@ export default buildConfig({
64
64
  pool: {
65
65
  connectionString: process.env.DATABASE_URL || "",
66
66
  },
67
- // Disable auto-push in dev mode to prevent constraint name truncation issues
68
- // Use migrations instead: pnpm payload migrate
69
- push: false,
67
+ // Auto-sync schema in development for better DX (first launch works immediately)
68
+ // In production, use explicit migrations: pnpm payload migrate
69
+ push: process.env.NODE_ENV !== "production",
70
70
  }),
71
71
  collections: [Pages, Posts, Media, Categories, FAQs, Users],
72
72
  cors: [getServerSideURL()].filter(Boolean),
@@ -7,14 +7,21 @@ import { getPayload } from "payload"
7
7
  type Global = keyof Config["globals"]
8
8
 
9
9
  async function getGlobal(slug: Global, depth = 0) {
10
- const payload = await getPayload({ config: configPromise })
10
+ try {
11
+ const payload = await getPayload({ config: configPromise })
11
12
 
12
- const global = await payload.findGlobal({
13
- slug,
14
- depth,
15
- })
13
+ const global = await payload.findGlobal({
14
+ slug,
15
+ depth,
16
+ })
16
17
 
17
- return global
18
+ return global
19
+ } catch (error) {
20
+ // Database tables may not exist yet on first launch
21
+ // Return null so components can show a setup UI instead of crashing
22
+ console.warn(`Could not fetch global "${String(slug)}". Database may not be initialized yet.`)
23
+ return null
24
+ }
18
25
  }
19
26
 
20
27
  /**