kofi-stack-template-generator 2.1.49 → 2.1.50

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.
Files changed (25) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/dist/index.js +104 -33
  3. package/package.json +2 -2
  4. package/src/templates.generated.ts +23 -19
  5. package/templates/marketing/payload/public/favicon.ico +0 -0
  6. package/templates/marketing/payload/public/favicon.svg +6 -0
  7. package/templates/marketing/payload/public/logo-light.svg +6 -0
  8. package/templates/marketing/payload/public/logo.svg +6 -0
  9. package/templates/marketing/payload/src/Footer/Component.client.tsx +1 -1
  10. package/templates/marketing/payload/src/Footer/config.ts +1 -1
  11. package/templates/marketing/payload/src/Header/Component.client.tsx +1 -1
  12. package/templates/marketing/payload/src/Header/MobileMenu/index.tsx +1 -1
  13. package/templates/marketing/payload/src/app/(docs)/docs/[[...slug]]/page.tsx +6 -6
  14. package/templates/marketing/payload/src/app/(docs)/docs/layout.tsx +1 -1
  15. package/templates/marketing/payload/src/app/(docs)/layout.tsx +3 -3
  16. package/templates/marketing/payload/src/app/(frontend)/api/newsletter/route.ts +15 -15
  17. package/templates/marketing/payload/src/app/(frontend)/layout.tsx +17 -17
  18. package/templates/marketing/payload/src/app/(frontend)/posts/[slug]/BlogPostContent.tsx +5 -5
  19. package/templates/marketing/payload/src/app/(frontend)/posts/page.tsx +2 -2
  20. package/templates/marketing/payload/src/components/JsonLd/index.tsx +19 -19
  21. package/templates/marketing/payload/src/components/Logo/Logo.tsx +1 -1
  22. package/templates/marketing/payload/src/components/TableOfContents/index.tsx +3 -3
  23. package/templates/marketing/payload/src/heros/ProductShowcase/AnimatedMockup.tsx +3 -3
  24. package/templates/marketing/payload/src/utilities/generateMeta.ts +16 -16
  25. package/templates/marketing/payload/src/utilities/mergeOpenGraph.ts +4 -4
@@ -1,7 +1,7 @@
1
1
  // Auto-generated file. Do not edit manually.
2
2
  // Run 'pnpm prebuild' to regenerate.
3
- // Generated: 2026-01-19T15:34:57.079Z
4
- // Template count: 339
3
+ // Generated: 2026-01-19T15:43:08.059Z
4
+ // Template count: 343
5
5
 
6
6
  export const EMBEDDED_TEMPLATES: Record<string, string> = {
7
7
  "admin/next.config.ts.hbs": "import type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n transpilePackages: ['@repo/ui', '@repo/backend'],\n}\n\nexport default nextConfig\n",
@@ -53,16 +53,20 @@ 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 | 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",
56
+ "marketing/payload/public/favicon.ico": "AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA",
57
+ "marketing/payload/public/favicon.svg": "PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cmVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHJ4PSI2IiBmaWxsPSIjMEYxRjNEIi8+CiAgPHBhdGggZD0iTTE2IDZDMTEuMDI5IDYgNyAxMC4wMjkgNyAxNUM3IDE2LjUgNy40IDE3LjkgOC4xIDE5LjFMMTYgMjZMMjMuOSAxOS4xQzI0LjYgMTcuOSAyNSAxNi41IDI1IDE1QzI1IDEwLjAyOSAyMC45NzEgNiAxNiA2WiIgZmlsbD0iIzNEQTlBMyIvPgogIDxwYXRoIGQ9Ik0xNiA5QzEyLjY4NiA5IDEwIDExLjY4NiAxMCAxNUMxMCAxNi4xIDEwLjMgMTcuMSAxMC44IDE4TDE2IDIzTDIxLjIgMThDMjEuNyAxNy4xIDIyIDE2LjEgMjIgMTVDMjIgMTEuNjg2IDE5LjMxNCA5IDE2IDlaIiBmaWxsPSIjMEYxRjNEIi8+CiAgPHRleHQgeD0iMTYiIHk9IjE4IiBmb250LWZhbWlseT0ic3lzdGVtLXVpLCAtYXBwbGUtc3lzdGVtLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjEwIiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+UzwvdGV4dD4KPC9zdmc+Cg==",
58
+ "marketing/payload/public/logo-light.svg": "PHN2ZyB3aWR0aD0iMzQiIGhlaWdodD0iMzQiIHZpZXdCb3g9IjAgMCAzNCAzNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cmVjdCB3aWR0aD0iMzQiIGhlaWdodD0iMzQiIHJ4PSI4IiBmaWxsPSIjM0RBOUEzIi8+CiAgPHBhdGggZD0iTTE3IDdDMTIuMDI5IDcgOCAxMS4wMjkgOCAxNkM4IDE3LjUgOC40IDE4LjkgOS4xIDIwLjFMMTcgMjdMMjQuOSAyMC4xQzI1LjYgMTguOSAyNiAxNy41IDI2IDE2QzI2IDExLjAyOSAyMS45NzEgNyAxNyA3WiIgZmlsbD0id2hpdGUiLz4KICA8cGF0aCBkPSJNMTcgMTBDMTMuNjg2IDEwIDExIDEyLjY4NiAxMSAxNkMxMSAxNy4xIDExLjMgMTguMSAxMS44IDE5TDE3IDI0TDIyLjIgMTlDMjIuNyAxOC4xIDIzIDE3LjEgMjMgMTZDMjMgMTIuNjg2IDIwLjMxNCAxMCAxNyAxMFoiIGZpbGw9IiMzREE5QTMiLz4KICA8dGV4dCB4PSIxNyIgeT0iMTkiIGZvbnQtZmFtaWx5PSJzeXN0ZW0tdWksIC1hcHBsZS1zeXN0ZW0sIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTAiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMwRjFGM0QiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlM8L3RleHQ+Cjwvc3ZnPgo=",
59
+ "marketing/payload/public/logo.svg": "PHN2ZyB3aWR0aD0iMzQiIGhlaWdodD0iMzQiIHZpZXdCb3g9IjAgMCAzNCAzNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cmVjdCB3aWR0aD0iMzQiIGhlaWdodD0iMzQiIHJ4PSI4IiBmaWxsPSIjMEYxRjNEIi8+CiAgPHBhdGggZD0iTTE3IDdDMTIuMDI5IDcgOCAxMS4wMjkgOCAxNkM4IDE3LjUgOC40IDE4LjkgOS4xIDIwLjFMMTcgMjdMMjQuOSAyMC4xQzI1LjYgMTguOSAyNiAxNy41IDI2IDE2QzI2IDExLjAyOSAyMS45NzEgNyAxNyA3WiIgZmlsbD0iIzNEQTlBMyIvPgogIDxwYXRoIGQ9Ik0xNyAxMEMxMy42ODYgMTAgMTEgMTIuNjg2IDExIDE2QzExIDE3LjEgMTEuMyAxOC4xIDExLjggMTlMMTcgMjRMMjIuMiAxOUMyMi43IDE4LjEgMjMgMTcuMSAyMyAxNkMyMyAxMi42ODYgMjAuMzE0IDEwIDE3IDEwWiIgZmlsbD0iIzBGMUYzRCIvPgogIDx0ZXh0IHg9IjE3IiB5PSIxOSIgZm9udC1mYW1pbHk9InN5c3RlbS11aSwgLWFwcGxlLXN5c3RlbSwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMCIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlM8L3RleHQ+Cjwvc3ZnPgo=",
60
+ "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 || \"SaaSify\"}\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
61
  "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
62
  "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
- "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",
63
+ "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: \"SaaSify\",\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
64
  "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 | 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",
65
+ "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\">SaaSify</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
66
  "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
67
  "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
68
  "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
- "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",
69
+ "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\tSaaSify\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",
66
70
  "marketing/payload/src/Header/Nav/index.tsx": "\"use client\"\n\nimport React from \"react\"\n\nimport type { Header as HeaderType } from \"@/payload-types\"\n\nimport { CTATracker } from \"@/components/Analytics\"\nimport { CMSLink } from \"@/components/Link\"\nimport { MegaMenu } from \"../MegaMenu\"\n\ntype Position = \"left\" | \"right\"\n\ninterface HeaderNavProps {\n\tdata: HeaderType\n\tposition?: Position\n\tclassName?: string\n}\n\nexport const HeaderNav: React.FC<HeaderNavProps> = ({ data, position, className }) => {\n\tconst allNavItems = data?.navItems || []\n\n\t// Filter items by position if specified\n\tconst navItems = position\n\t\t? allNavItems.filter((item) => (item.position || \"left\") === position)\n\t\t: allNavItems\n\n\tif (navItems.length === 0) return null\n\n\treturn (\n\t\t<nav className={className}>\n\t\t\t{navItems.map((item, i) => {\n\t\t\t\tconst isRightPosition = (item.position || \"left\") === \"right\"\n\n\t\t\t\t// Handle mega menu type\n\t\t\t\tif (item.type === \"megaMenu\") {\n\t\t\t\t\treturn <MegaMenu key={item.id || i} item={item} />\n\t\t\t\t}\n\n\t\t\t\t// Handle simple link type\n\t\t\t\tconst link = item.link\n\t\t\t\tif (!link) return null\n\n\t\t\t\t// Determine appearance based on position and CMS setting\n\t\t\t\t// Right-positioned items use their appearance setting (button = 'default', link = 'link')\n\t\t\t\t// Left-positioned items always use link styling\n\t\t\t\tconst itemAppearance = item.appearance || \"button\"\n\t\t\t\tconst appearance = isRightPosition\n\t\t\t\t\t? itemAppearance === \"button\"\n\t\t\t\t\t\t? \"default\"\n\t\t\t\t\t\t: \"link\"\n\t\t\t\t\t: \"link\"\n\n\t\t\t\t// Track CTA clicks for button-style items in the header\n\t\t\t\tconst isButton = appearance === \"default\"\n\t\t\t\tconst linkContent = (\n\t\t\t\t\t<CMSLink\n\t\t\t\t\t\t{...link}\n\t\t\t\t\t\tlabel={item.label}\n\t\t\t\t\t\tappearance={appearance}\n\t\t\t\t\t\tclassName={appearance === \"link\" ? \"text-sm font-medium\" : undefined}\n\t\t\t\t\t/>\n\t\t\t\t)\n\n\t\t\t\tif (isButton) {\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<CTATracker key={item.id || i} location=\"header\" variant={item.label || `nav_${i}`}>\n\t\t\t\t\t\t\t{linkContent}\n\t\t\t\t\t\t</CTATracker>\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\treturn <React.Fragment key={item.id || i}>{linkContent}</React.Fragment>\n\t\t\t})}\n\t\t</nav>\n\t)\n}\n",
67
71
  "marketing/payload/src/Header/RowLabel.tsx": "\"use client\"\nimport type { Header } from \"@/payload-types\"\nimport { type RowLabelProps, useRowLabel } from \"@payloadcms/ui\"\n\nexport const RowLabel: React.FC<RowLabelProps> = () => {\n\tconst data = useRowLabel<NonNullable<Header[\"navItems\"]>[number]>()\n\n\tconst itemLabel = data?.data?.label\n\tconst itemType = data?.data?.type === \"megaMenu\" ? \"Mega Menu\" : \"Link\"\n\tconst isRight = (data?.data?.position || \"left\") === \"right\"\n\tconst itemPosition = isRight ? \"Right\" : \"Left\"\n\tconst itemAppearance = isRight\n\t\t? ` · ${(data?.data?.appearance || \"button\") === \"button\" ? \"Button\" : \"Link\"}`\n\t\t: \"\"\n\n\tconst label = itemLabel\n\t\t? `${data.rowNumber !== undefined ? data.rowNumber + 1 : \"\"}. ${itemLabel} — ${itemType} (${itemPosition}${itemAppearance})`\n\t\t: \"Row\"\n\n\treturn <div>{label}</div>\n}\n",
68
72
  "marketing/payload/src/Header/config.ts": "import type { GlobalConfig } from \"payload\"\n\nimport { link } from \"@/fields/link\"\nimport { revalidateHeader } from \"./hooks/revalidateHeader\"\n\nexport const Header: GlobalConfig = {\n\tslug: \"header\",\n\taccess: {\n\t\tread: () => true,\n\t},\n\tfields: [\n\t\t{\n\t\t\tname: \"navItems\",\n\t\t\ttype: \"array\",\n\t\t\tmaxRows: 8,\n\t\t\tadmin: {\n\t\t\t\tinitCollapsed: true,\n\t\t\t\tcomponents: {\n\t\t\t\t\tRowLabel: \"@/Header/RowLabel#RowLabel\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tname: \"type\",\n\t\t\t\t\ttype: \"select\",\n\t\t\t\t\tdefaultValue: \"link\",\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{ label: \"Simple Link\", value: \"link\" },\n\t\t\t\t\t\t{ label: \"Mega Menu\", value: \"megaMenu\" },\n\t\t\t\t\t],\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tdescription: \"Choose between a simple link or a mega menu dropdown\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"label\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tlabel: \"Nav Item Label\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"position\",\n\t\t\t\t\ttype: \"select\",\n\t\t\t\t\tdefaultValue: \"left\",\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{ label: \"Left (Near Logo)\", value: \"left\" },\n\t\t\t\t\t\t{ label: \"Right (CTA Section)\", value: \"right\" },\n\t\t\t\t\t],\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tdescription: \"Control where this nav item appears in the header\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"appearance\",\n\t\t\t\t\ttype: \"select\",\n\t\t\t\t\tdefaultValue: \"button\",\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{ label: \"Button\", value: \"button\" },\n\t\t\t\t\t\t{ label: \"Link\", value: \"link\" },\n\t\t\t\t\t],\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tdescription: \"How this item should be displayed\",\n\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.position === \"right\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t// Simple link fields\n\t\t\t\tlink({\n\t\t\t\t\tappearances: false,\n\t\t\t\t\tdisableLabel: true,\n\t\t\t\t\toverrides: {\n\t\t\t\t\t\tadmin: {\n\t\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.type === \"link\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\t// Mega menu fields\n\t\t\t\t{\n\t\t\t\t\tname: \"megaMenuColumns\",\n\t\t\t\t\ttype: \"array\",\n\t\t\t\t\tlabel: \"Menu Columns\",\n\t\t\t\t\tmaxRows: 4,\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.type === \"megaMenu\",\n\t\t\t\t\t},\n\t\t\t\t\tfields: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"columnLabel\",\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\tlabel: \"Column Heading\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"columnDescription\",\n\t\t\t\t\t\t\ttype: \"textarea\",\n\t\t\t\t\t\t\tlabel: \"Column Description\",\n\t\t\t\t\t\t\tadmin: {\n\t\t\t\t\t\t\t\tdescription: \"Optional description shown below the column heading\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"items\",\n\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\tlabel: \"Menu Items\",\n\t\t\t\t\t\t\tmaxRows: 8,\n\t\t\t\t\t\t\tfields: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tname: \"label\",\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\t\t\t\tlabel: \"Item Label\",\n\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\t\tname: \"description\",\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\tlabel: \"Item Description\",\n\t\t\t\t\t\t\t\t\tadmin: {\n\t\t\t\t\t\t\t\t\t\tdescription: \"Short description shown below the label\",\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\t{\n\t\t\t\t\t\t\t\t\tname: \"icon\",\n\t\t\t\t\t\t\t\t\ttype: \"select\",\n\t\t\t\t\t\t\t\t\tlabel: \"Icon\",\n\t\t\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t\t\t{ label: \"None\", value: \"none\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Layout\", value: \"layout\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Dollar Sign\", value: \"dollarSign\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Search\", value: \"search\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Settings\", value: \"settings\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Zap\", value: \"zap\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Layers\", value: \"layers\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Users\", value: \"users\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Building\", value: \"building\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Globe\", value: \"globe\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Store\", value: \"store\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Rocket\", value: \"rocket\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Target\", value: \"target\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"BarChart\", value: \"barChart\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Shield\", value: \"shield\" },\n\t\t\t\t\t\t\t\t\t\t{ label: \"Database\", value: \"database\" },\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\tdefaultValue: \"none\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tlink({\n\t\t\t\t\t\t\t\t\tappearances: false,\n\t\t\t\t\t\t\t\t\tdisableLabel: true,\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},\n\t\t\t\t// Featured section for mega menu\n\t\t\t\t{\n\t\t\t\t\tname: \"featuredItem\",\n\t\t\t\t\ttype: \"group\",\n\t\t\t\t\tlabel: \"Featured Section\",\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.type === \"megaMenu\",\n\t\t\t\t\t\tdescription: \"Optional featured content shown in the mega menu\",\n\t\t\t\t\t},\n\t\t\t\t\tfields: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"enabled\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tlabel: \"Show Featured Section\",\n\t\t\t\t\t\t\tdefaultValue: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"heading\",\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\tlabel: \"Featured Heading\",\n\t\t\t\t\t\t\tadmin: {\n\t\t\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.enabled,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"description\",\n\t\t\t\t\t\t\ttype: \"textarea\",\n\t\t\t\t\t\t\tlabel: \"Featured Description\",\n\t\t\t\t\t\t\tadmin: {\n\t\t\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.enabled,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"image\",\n\t\t\t\t\t\t\ttype: \"upload\",\n\t\t\t\t\t\t\trelationTo: \"media\",\n\t\t\t\t\t\t\tlabel: \"Featured Image\",\n\t\t\t\t\t\t\tadmin: {\n\t\t\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.enabled,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tlink({\n\t\t\t\t\t\t\tappearances: false,\n\t\t\t\t\t\t\toverrides: {\n\t\t\t\t\t\t\t\tadmin: {\n\t\t\t\t\t\t\t\t\tcondition: (_, siblingData) => siblingData?.enabled,\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},\n\t\t\t],\n\t\t},\n\t],\n\thooks: {\n\t\tafterChange: [revalidateHeader],\n\t},\n}\n",
@@ -70,30 +74,30 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
70
74
  "marketing/payload/src/access/anyone.ts": "import type { Access } from \"payload\"\n\nexport const anyone: Access = () => true\n",
71
75
  "marketing/payload/src/access/authenticated.ts": "import type { AccessArgs } from \"payload\"\n\nimport type { User } from \"@/payload-types\"\n\ntype isAuthenticated = (args: AccessArgs<User>) => boolean\n\nexport const authenticated: isAuthenticated = ({ req: { user } }) => {\n\treturn Boolean(user)\n}\n",
72
76
  "marketing/payload/src/access/authenticatedOrPublished.ts": "import type { Access } from \"payload\"\n\nexport const authenticatedOrPublished: Access = ({ req: { user } }) => {\n\tif (user) {\n\t\treturn true\n\t}\n\n\treturn {\n\t\t_status: {\n\t\t\tequals: \"published\",\n\t\t},\n\t}\n}\n",
73
- "marketing/payload/src/app/(docs)/docs/[[...slug]]/page.tsx": "import { getDocBySlug, getDocsFromConvex } from \"@/lib/docs-source\"\nimport { compileMDX } from \"@/lib/mdx\"\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from \"fumadocs-ui/page\"\nimport type { Metadata } from \"next\"\nimport Link from \"next/link\"\nimport { notFound, redirect } from \"next/navigation\"\n\n// Make this route dynamic so new docs appear immediately without rebuild\n// This ensures docs added via admin portal show up right away\nexport const dynamic = \"force-dynamic\"\n\ninterface DocsPageProps {\n\tparams: Promise<{\n\t\tslug?: string[]\n\t}>\n}\n\nexport default async function Page({ params }: DocsPageProps) {\n\tconst { slug } = await params\n\tconst slugPath = slug?.join(\"/\") || \"\"\n\n\t// If this is the index page (no slug), check for an \"index\" doc first\n\tif (!slugPath) {\n\t\t// Try to find a doc with slug \"index\" to use as the home page\n\t\tconst indexDoc = await getDocBySlug(\"index\")\n\n\t\tif (indexDoc) {\n\t\t\t// Found an index doc, render it as the home page\n\t\t\tconst { content, toc } = await compileMDX(indexDoc.content)\n\t\t\treturn (\n\t\t\t\t<DocsPage toc={toc}>\n\t\t\t\t\t<DocsTitle>{indexDoc.title}</DocsTitle>\n\t\t\t\t\t{indexDoc.description && <DocsDescription>{indexDoc.description}</DocsDescription>}\n\t\t\t\t\t<DocsBody>{content}</DocsBody>\n\t\t\t\t</DocsPage>\n\t\t\t)\n\t\t}\n\n\t\t// No index doc found, check if there are other docs\n\t\tconst docs = await getDocsFromConvex()\n\n\t\t// If there are docs, redirect to the first one\n\t\tif (docs.length > 0 && docs[0]) {\n\t\t\tredirect(`/docs/${docs[0].slug}`)\n\t\t}\n\n\t\t// Otherwise show an empty state\n\t\treturn (\n\t\t\t<DocsPage>\n\t\t\t\t<DocsTitle>Documentation</DocsTitle>\n\t\t\t\t<DocsDescription>Welcome to DirectoryHub documentation</DocsDescription>\n\t\t\t\t<DocsBody>\n\t\t\t\t\t<div className=\"flex flex-col items-center justify-center py-12 text-center\">\n\t\t\t\t\t\t<h2 className=\"text-xl font-semibold mb-4\">No documentation yet</h2>\n\t\t\t\t\t\t<p className=\"text-muted-foreground mb-6\">\n\t\t\t\t\t\t\tDocumentation is coming soon. Check back later!\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\thref=\"/\"\n\t\t\t\t\t\t\tclassName=\"inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tBack to Home\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t</div>\n\t\t\t\t</DocsBody>\n\t\t\t</DocsPage>\n\t\t)\n\t}\n\n\t// Try to find the doc\n\tconst doc = await getDocBySlug(slugPath)\n\n\tif (!doc) {\n\t\tnotFound()\n\t}\n\n\t// Compile MDX content\n\tconst { content, toc } = await compileMDX(doc.content)\n\n\treturn (\n\t\t<DocsPage toc={toc}>\n\t\t\t<DocsTitle>{doc.title}</DocsTitle>\n\t\t\t{doc.description && <DocsDescription>{doc.description}</DocsDescription>}\n\t\t\t<DocsBody>{content}</DocsBody>\n\t\t</DocsPage>\n\t)\n}\n\n// Removed generateStaticParams to make route fully dynamic\n// This allows new docs to appear immediately without requiring a rebuild\n// export async function generateStaticParams() {\n// \tconst slugs = await getAllDocSlugs()\n// \treturn [{ slug: [] }, ...slugs.map((slug) => ({ slug }))]\n// }\n\nexport async function generateMetadata({ params }: DocsPageProps): Promise<Metadata> {\n\tconst { slug } = await params\n\tconst slugPath = slug?.join(\"/\") || \"index\"\n\n\tconst doc = await getDocBySlug(slugPath)\n\n\tif (!doc) {\n\t\treturn {\n\t\t\ttitle: \"Documentation | DirectoryHub\",\n\t\t}\n\t}\n\n\treturn {\n\t\ttitle: `${doc.title} | DirectoryHub Docs`,\n\t\tdescription: doc.description || `Learn about ${doc.title} in DirectoryHub documentation.`,\n\t\topenGraph: {\n\t\t\ttitle: `${doc.title} | DirectoryHub Docs`,\n\t\t\tdescription: doc.description || `Learn about ${doc.title} in DirectoryHub documentation.`,\n\t\t\ttype: \"article\",\n\t\t},\n\t}\n}\n",
74
- "marketing/payload/src/app/(docs)/docs/layout.tsx": "import { buildPageTree, getDocsFromConvex } from \"@/lib/docs-source\"\nimport { DocsLayout } from \"fumadocs-ui/layouts/docs\"\nimport { RootProvider } from \"fumadocs-ui/provider\"\nimport type { ReactNode } from \"react\"\n\n// Make layout dynamic so navigation updates when new docs are added\nexport const dynamic = \"force-dynamic\"\n\nexport default async function DocsLayoutWrapper({\n\tchildren,\n}: {\n\tchildren: ReactNode\n}) {\n\tconst docs = await getDocsFromConvex()\n\tconst pageTree = buildPageTree(docs)\n\n\treturn (\n\t\t<RootProvider\n\t\t\ttheme={{\n\t\t\t\tenabled: true,\n\t\t\t\tdefaultTheme: \"light\",\n\t\t\t\tattribute: \"data-theme\",\n\t\t\t}}\n\t\t>\n\t\t\t<DocsLayout\n\t\t\t\ttree={pageTree}\n\t\t\t\tnav={{\n\t\t\t\t\ttitle: \"DirectoryHub Docs\",\n\t\t\t\t\turl: \"/docs\",\n\t\t\t\t}}\n\t\t\t\tsidebar={{\n\t\t\t\t\tdefaultOpenLevel: 1,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</DocsLayout>\n\t\t</RootProvider>\n\t)\n}\n",
75
- "marketing/payload/src/app/(docs)/layout.tsx": "import { getServerSideURL } from \"@/utilities/getURL\"\nimport { cn } from \"@/utilities/ui\"\nimport { GeistMono } from \"geist/font/mono\"\nimport type { Metadata } from \"next\"\nimport { Inter } from \"next/font/google\"\nimport type { ReactNode } from \"react\"\n\n// Import both - globals.css for Tailwind base + fumadocs styles\nimport \"../(frontend)/globals.css\"\nimport \"fumadocs-ui/style.css\"\n\nconst inter = Inter({\n\tsubsets: [\"latin\"],\n\tvariable: \"--font-inter\",\n\tdisplay: \"swap\",\n})\n\nexport default function DocsRootLayout({ children }: { children: ReactNode }) {\n\treturn (\n\t\t<html\n\t\t\tclassName={cn(inter.variable, GeistMono.variable)}\n\t\t\tlang=\"en\"\n\t\t\tsuppressHydrationWarning\n\t\t\tdata-theme=\"light\"\n\t\t>\n\t\t\t<head>\n\t\t\t\t<link href=\"/favicon.ico\" rel=\"icon\" sizes=\"32x32\" />\n\t\t\t\t<link href=\"/favicon.svg\" rel=\"icon\" type=\"image/svg+xml\" />\n\t\t\t</head>\n\t\t\t<body className=\"font-sans antialiased\" suppressHydrationWarning>\n\t\t\t\t{children}\n\t\t\t</body>\n\t\t</html>\n\t)\n}\n\nexport const metadata: Metadata = {\n\tmetadataBase: new URL(getServerSideURL()),\n\ttitle: {\n\t\tdefault: \"Documentation | DirectoryHub\",\n\t\ttemplate: \"%s | DirectoryHub Docs\",\n\t},\n\tdescription: \"DirectoryHub documentation - learn how to build and manage directory websites.\",\n}\n",
77
+ "marketing/payload/src/app/(docs)/docs/[[...slug]]/page.tsx": "import { getDocBySlug, getDocsFromConvex } from \"@/lib/docs-source\"\nimport { compileMDX } from \"@/lib/mdx\"\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from \"fumadocs-ui/page\"\nimport type { Metadata } from \"next\"\nimport Link from \"next/link\"\nimport { notFound, redirect } from \"next/navigation\"\n\n// Make this route dynamic so new docs appear immediately without rebuild\n// This ensures docs added via admin portal show up right away\nexport const dynamic = \"force-dynamic\"\n\ninterface DocsPageProps {\n\tparams: Promise<{\n\t\tslug?: string[]\n\t}>\n}\n\nexport default async function Page({ params }: DocsPageProps) {\n\tconst { slug } = await params\n\tconst slugPath = slug?.join(\"/\") || \"\"\n\n\t// If this is the index page (no slug), check for an \"index\" doc first\n\tif (!slugPath) {\n\t\t// Try to find a doc with slug \"index\" to use as the home page\n\t\tconst indexDoc = await getDocBySlug(\"index\")\n\n\t\tif (indexDoc) {\n\t\t\t// Found an index doc, render it as the home page\n\t\t\tconst { content, toc } = await compileMDX(indexDoc.content)\n\t\t\treturn (\n\t\t\t\t<DocsPage toc={toc}>\n\t\t\t\t\t<DocsTitle>{indexDoc.title}</DocsTitle>\n\t\t\t\t\t{indexDoc.description && <DocsDescription>{indexDoc.description}</DocsDescription>}\n\t\t\t\t\t<DocsBody>{content}</DocsBody>\n\t\t\t\t</DocsPage>\n\t\t\t)\n\t\t}\n\n\t\t// No index doc found, check if there are other docs\n\t\tconst docs = await getDocsFromConvex()\n\n\t\t// If there are docs, redirect to the first one\n\t\tif (docs.length > 0 && docs[0]) {\n\t\t\tredirect(`/docs/${docs[0].slug}`)\n\t\t}\n\n\t\t// Otherwise show an empty state\n\t\treturn (\n\t\t\t<DocsPage>\n\t\t\t\t<DocsTitle>Documentation</DocsTitle>\n\t\t\t\t<DocsDescription>Welcome to SaaSify documentation</DocsDescription>\n\t\t\t\t<DocsBody>\n\t\t\t\t\t<div className=\"flex flex-col items-center justify-center py-12 text-center\">\n\t\t\t\t\t\t<h2 className=\"text-xl font-semibold mb-4\">No documentation yet</h2>\n\t\t\t\t\t\t<p className=\"text-muted-foreground mb-6\">\n\t\t\t\t\t\t\tDocumentation is coming soon. Check back later!\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\thref=\"/\"\n\t\t\t\t\t\t\tclassName=\"inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tBack to Home\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t</div>\n\t\t\t\t</DocsBody>\n\t\t\t</DocsPage>\n\t\t)\n\t}\n\n\t// Try to find the doc\n\tconst doc = await getDocBySlug(slugPath)\n\n\tif (!doc) {\n\t\tnotFound()\n\t}\n\n\t// Compile MDX content\n\tconst { content, toc } = await compileMDX(doc.content)\n\n\treturn (\n\t\t<DocsPage toc={toc}>\n\t\t\t<DocsTitle>{doc.title}</DocsTitle>\n\t\t\t{doc.description && <DocsDescription>{doc.description}</DocsDescription>}\n\t\t\t<DocsBody>{content}</DocsBody>\n\t\t</DocsPage>\n\t)\n}\n\n// Removed generateStaticParams to make route fully dynamic\n// This allows new docs to appear immediately without requiring a rebuild\n// export async function generateStaticParams() {\n// \tconst slugs = await getAllDocSlugs()\n// \treturn [{ slug: [] }, ...slugs.map((slug) => ({ slug }))]\n// }\n\nexport async function generateMetadata({ params }: DocsPageProps): Promise<Metadata> {\n\tconst { slug } = await params\n\tconst slugPath = slug?.join(\"/\") || \"index\"\n\n\tconst doc = await getDocBySlug(slugPath)\n\n\tif (!doc) {\n\t\treturn {\n\t\t\ttitle: \"Documentation | SaaSify\",\n\t\t}\n\t}\n\n\treturn {\n\t\ttitle: `${doc.title} | SaaSify Docs`,\n\t\tdescription: doc.description || `Learn about ${doc.title} in SaaSify documentation.`,\n\t\topenGraph: {\n\t\t\ttitle: `${doc.title} | SaaSify Docs`,\n\t\t\tdescription: doc.description || `Learn about ${doc.title} in SaaSify documentation.`,\n\t\t\ttype: \"article\",\n\t\t},\n\t}\n}\n",
78
+ "marketing/payload/src/app/(docs)/docs/layout.tsx": "import { buildPageTree, getDocsFromConvex } from \"@/lib/docs-source\"\nimport { DocsLayout } from \"fumadocs-ui/layouts/docs\"\nimport { RootProvider } from \"fumadocs-ui/provider\"\nimport type { ReactNode } from \"react\"\n\n// Make layout dynamic so navigation updates when new docs are added\nexport const dynamic = \"force-dynamic\"\n\nexport default async function DocsLayoutWrapper({\n\tchildren,\n}: {\n\tchildren: ReactNode\n}) {\n\tconst docs = await getDocsFromConvex()\n\tconst pageTree = buildPageTree(docs)\n\n\treturn (\n\t\t<RootProvider\n\t\t\ttheme={{\n\t\t\t\tenabled: true,\n\t\t\t\tdefaultTheme: \"light\",\n\t\t\t\tattribute: \"data-theme\",\n\t\t\t}}\n\t\t>\n\t\t\t<DocsLayout\n\t\t\t\ttree={pageTree}\n\t\t\t\tnav={{\n\t\t\t\t\ttitle: \"SaaSify Docs\",\n\t\t\t\t\turl: \"/docs\",\n\t\t\t\t}}\n\t\t\t\tsidebar={{\n\t\t\t\t\tdefaultOpenLevel: 1,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</DocsLayout>\n\t\t</RootProvider>\n\t)\n}\n",
79
+ "marketing/payload/src/app/(docs)/layout.tsx": "import { getServerSideURL } from \"@/utilities/getURL\"\nimport { cn } from \"@/utilities/ui\"\nimport { GeistMono } from \"geist/font/mono\"\nimport type { Metadata } from \"next\"\nimport { Inter } from \"next/font/google\"\nimport type { ReactNode } from \"react\"\n\n// Import both - globals.css for Tailwind base + fumadocs styles\nimport \"../(frontend)/globals.css\"\nimport \"fumadocs-ui/style.css\"\n\nconst inter = Inter({\n\tsubsets: [\"latin\"],\n\tvariable: \"--font-inter\",\n\tdisplay: \"swap\",\n})\n\nexport default function DocsRootLayout({ children }: { children: ReactNode }) {\n\treturn (\n\t\t<html\n\t\t\tclassName={cn(inter.variable, GeistMono.variable)}\n\t\t\tlang=\"en\"\n\t\t\tsuppressHydrationWarning\n\t\t\tdata-theme=\"light\"\n\t\t>\n\t\t\t<head>\n\t\t\t\t<link href=\"/favicon.ico\" rel=\"icon\" sizes=\"32x32\" />\n\t\t\t\t<link href=\"/favicon.svg\" rel=\"icon\" type=\"image/svg+xml\" />\n\t\t\t</head>\n\t\t\t<body className=\"font-sans antialiased\" suppressHydrationWarning>\n\t\t\t\t{children}\n\t\t\t</body>\n\t\t</html>\n\t)\n}\n\nexport const metadata: Metadata = {\n\tmetadataBase: new URL(getServerSideURL()),\n\ttitle: {\n\t\tdefault: \"Documentation | SaaSify\",\n\t\ttemplate: \"%s | SaaSify Docs\",\n\t},\n\tdescription: \"SaaSify documentation - learn how to use the platform and boost your team productivity.\",\n}\n",
76
80
  "marketing/payload/src/app/(frontend)/(sitemaps)/pages-sitemap.xml/route.ts": "import config from \"@payload-config\"\nimport { getServerSideSitemap } from \"next-sitemap\"\nimport { unstable_cache } from \"next/cache\"\nimport { getPayload } from \"payload\"\n\nconst getPagesSitemap = unstable_cache(\n\tasync () => {\n\t\tconst payload = await getPayload({ config })\n\t\tconst SITE_URL =\n\t\t\tprocess.env.NEXT_PUBLIC_SERVER_URL ||\n\t\t\tprocess.env.VERCEL_PROJECT_PRODUCTION_URL ||\n\t\t\t\"https://example.com\"\n\n\t\tconst results = await payload.find({\n\t\t\tcollection: \"pages\",\n\t\t\toverrideAccess: false,\n\t\t\tdraft: false,\n\t\t\tdepth: 0,\n\t\t\tlimit: 1000,\n\t\t\tpagination: false,\n\t\t\twhere: {\n\t\t\t\t_status: {\n\t\t\t\t\tequals: \"published\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tselect: {\n\t\t\t\tslug: true,\n\t\t\t\tupdatedAt: true,\n\t\t\t},\n\t\t})\n\n\t\tconst dateFallback = new Date().toISOString()\n\n\t\tconst defaultSitemap = [\n\t\t\t{\n\t\t\t\tloc: `${SITE_URL}/search`,\n\t\t\t\tlastmod: dateFallback,\n\t\t\t},\n\t\t\t{\n\t\t\t\tloc: `${SITE_URL}/posts`,\n\t\t\t\tlastmod: dateFallback,\n\t\t\t},\n\t\t]\n\n\t\tconst sitemap = results.docs\n\t\t\t? results.docs\n\t\t\t\t\t.filter((page) => Boolean(page?.slug))\n\t\t\t\t\t.map((page) => {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tloc: page?.slug === \"home\" ? `${SITE_URL}/` : `${SITE_URL}/${page?.slug}`,\n\t\t\t\t\t\t\tlastmod: page.updatedAt || dateFallback,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t: []\n\n\t\treturn [...defaultSitemap, ...sitemap]\n\t},\n\t[\"pages-sitemap\"],\n\t{\n\t\ttags: [\"pages-sitemap\"],\n\t},\n)\n\nexport async function GET() {\n\tconst sitemap = await getPagesSitemap()\n\n\treturn getServerSideSitemap(sitemap)\n}\n",
77
81
  "marketing/payload/src/app/(frontend)/(sitemaps)/posts-sitemap.xml/route.ts": "import config from \"@payload-config\"\nimport { getServerSideSitemap } from \"next-sitemap\"\nimport { unstable_cache } from \"next/cache\"\nimport { getPayload } from \"payload\"\n\nconst getPostsSitemap = unstable_cache(\n\tasync () => {\n\t\tconst payload = await getPayload({ config })\n\t\tconst SITE_URL =\n\t\t\tprocess.env.NEXT_PUBLIC_SERVER_URL ||\n\t\t\tprocess.env.VERCEL_PROJECT_PRODUCTION_URL ||\n\t\t\t\"https://example.com\"\n\n\t\tconst results = await payload.find({\n\t\t\tcollection: \"posts\",\n\t\t\toverrideAccess: false,\n\t\t\tdraft: false,\n\t\t\tdepth: 0,\n\t\t\tlimit: 1000,\n\t\t\tpagination: false,\n\t\t\twhere: {\n\t\t\t\t_status: {\n\t\t\t\t\tequals: \"published\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tselect: {\n\t\t\t\tslug: true,\n\t\t\t\tupdatedAt: true,\n\t\t\t},\n\t\t})\n\n\t\tconst dateFallback = new Date().toISOString()\n\n\t\tconst sitemap = results.docs\n\t\t\t? results.docs\n\t\t\t\t\t.filter((post) => Boolean(post?.slug))\n\t\t\t\t\t.map((post) => ({\n\t\t\t\t\t\tloc: `${SITE_URL}/posts/${post?.slug}`,\n\t\t\t\t\t\tlastmod: post.updatedAt || dateFallback,\n\t\t\t\t\t}))\n\t\t\t: []\n\n\t\treturn sitemap\n\t},\n\t[\"posts-sitemap\"],\n\t{\n\t\ttags: [\"posts-sitemap\"],\n\t},\n)\n\nexport async function GET() {\n\tconst sitemap = await getPostsSitemap()\n\n\treturn getServerSideSitemap(sitemap)\n}\n",
78
82
  "marketing/payload/src/app/(frontend)/[slug]/page.client.tsx": "\"use client\"\nimport { useHeaderTheme } from \"@/providers/HeaderTheme\"\nimport React, { useEffect } from \"react\"\n\nconst PageClient: React.FC = () => {\n\t/* Force the header to be dark mode while we have an image behind it */\n\tconst { setHeaderTheme } = useHeaderTheme()\n\n\tuseEffect(() => {\n\t\tsetHeaderTheme(\"light\")\n\t}, [setHeaderTheme])\n\treturn <React.Fragment />\n}\n\nexport default PageClient\n",
79
83
  "marketing/payload/src/app/(frontend)/[slug]/page.tsx": "import type { Metadata } from \"next\"\n\nimport { PayloadRedirects } from \"@/components/PayloadRedirects\"\nimport { homeStatic } from \"@/endpoints/seed/home-static\"\nimport configPromise from \"@payload-config\"\nimport { draftMode } from \"next/headers\"\nimport { type RequiredDataFromCollectionSlug, getPayload } from \"payload\"\nimport { cache } from \"react\"\n\nimport { RenderBlocks } from \"@/blocks/RenderBlocks\"\nimport { LivePreviewListener } from \"@/components/LivePreviewListener\"\nimport { RenderHero } from \"@/heros/RenderHero\"\nimport { generateMeta } from \"@/utilities/generateMeta\"\nimport PageClient from \"./page.client\"\n\nexport async function generateStaticParams() {\n\tconst payload = await getPayload({ config: configPromise })\n\tconst pages = await payload.find({\n\t\tcollection: \"pages\",\n\t\tdraft: false,\n\t\tlimit: 1000,\n\t\toverrideAccess: false,\n\t\tpagination: false,\n\t\tselect: {\n\t\t\tslug: true,\n\t\t},\n\t})\n\n\tconst params = pages.docs\n\t\t?.filter((doc) => {\n\t\t\treturn doc.slug !== \"home\"\n\t\t})\n\t\t.map(({ slug }) => {\n\t\t\treturn { slug }\n\t\t})\n\n\treturn params\n}\n\ntype Args = {\n\tparams: Promise<{\n\t\tslug?: string\n\t}>\n}\n\nexport default async function Page({ params: paramsPromise }: Args) {\n\tconst { isEnabled: draft } = await draftMode()\n\tconst { slug = \"home\" } = await paramsPromise\n\t// Decode to support slugs with special characters\n\tconst decodedSlug = decodeURIComponent(slug)\n\tconst url = `/${decodedSlug}`\n\tlet page: RequiredDataFromCollectionSlug<\"pages\"> | null\n\n\tpage = await queryPageBySlug({\n\t\tslug: decodedSlug,\n\t})\n\n\t// Remove this code once your website is seeded\n\tif (!page && slug === \"home\") {\n\t\tpage = homeStatic\n\t}\n\n\tif (!page) {\n\t\treturn <PayloadRedirects url={url} />\n\t}\n\n\tconst { hero, layout } = page\n\n\treturn (\n\t\t<article className=\"pt-16 pb-24\">\n\t\t\t<PageClient />\n\t\t\t{/* Allows redirects for valid pages too */}\n\t\t\t<PayloadRedirects disableNotFound url={url} />\n\n\t\t\t{draft && <LivePreviewListener />}\n\n\t\t\t<RenderHero {...hero} />\n\t\t\t<RenderBlocks blocks={layout} />\n\t\t</article>\n\t)\n}\n\nexport async function generateMetadata({ params: paramsPromise }: Args): Promise<Metadata> {\n\tconst { slug = \"home\" } = await paramsPromise\n\t// Decode to support slugs with special characters\n\tconst decodedSlug = decodeURIComponent(slug)\n\tconst page = await queryPageBySlug({\n\t\tslug: decodedSlug,\n\t})\n\n\treturn generateMeta({ doc: page })\n}\n\nconst queryPageBySlug = cache(async ({ slug }: { slug: string }) => {\n\tconst { isEnabled: draft } = await draftMode()\n\n\tconst payload = await getPayload({ config: configPromise })\n\n\tconst result = await payload.find({\n\t\tcollection: \"pages\",\n\t\tdepth: 2, // Populate relationships in rich text content\n\t\tdraft,\n\t\tlimit: 1,\n\t\tpagination: false,\n\t\toverrideAccess: draft,\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 result.docs?.[0] || null\n})\n",
80
84
  "marketing/payload/src/app/(frontend)/api/docs-search/route.ts": "import { getDocsFromConvex } from \"@/lib/docs-source\"\nimport { NextResponse } from \"next/server\"\n\n/**\n * Search API for documentation\n * Returns matching docs based on query\n */\nexport async function GET(request: Request) {\n\tconst { searchParams } = new URL(request.url)\n\tconst query = searchParams.get(\"q\")?.toLowerCase() || \"\"\n\n\tif (!query || query.length < 2) {\n\t\treturn NextResponse.json({ results: [] })\n\t}\n\n\ttry {\n\t\tconst docs = await getDocsFromConvex()\n\n\t\t// Simple search implementation\n\t\tconst results = docs\n\t\t\t.filter((doc) => {\n\t\t\t\tconst titleMatch = doc.title.toLowerCase().includes(query)\n\t\t\t\tconst descriptionMatch = doc.description?.toLowerCase().includes(query)\n\t\t\t\tconst contentMatch = doc.content.toLowerCase().includes(query)\n\t\t\t\treturn titleMatch || descriptionMatch || contentMatch\n\t\t\t})\n\t\t\t.map((doc) => ({\n\t\t\t\tid: doc._id,\n\t\t\t\ttitle: doc.title,\n\t\t\t\tdescription: doc.description || \"\",\n\t\t\t\turl: `/docs/${doc.slug}`,\n\t\t\t\t// Extract a snippet from the content\n\t\t\t\tsnippet: extractSnippet(doc.content, query),\n\t\t\t}))\n\t\t\t.slice(0, 10) // Limit results\n\n\t\treturn NextResponse.json({ results })\n\t} catch (error) {\n\t\tconsole.error(\"Search error:\", error)\n\t\treturn NextResponse.json({ results: [], error: \"Search failed\" }, { status: 500 })\n\t}\n}\n\n/**\n * Extract a snippet around the search query\n */\nfunction extractSnippet(content: string, query: string): string {\n\tconst lowerContent = content.toLowerCase()\n\tconst index = lowerContent.indexOf(query)\n\n\tif (index === -1) {\n\t\t// Return first 150 chars if query not found in content\n\t\treturn `${content.slice(0, 150).trim()}...`\n\t}\n\n\t// Get 50 chars before and 100 chars after the match\n\tconst start = Math.max(0, index - 50)\n\tconst end = Math.min(content.length, index + query.length + 100)\n\n\tlet snippet = content.slice(start, end).trim()\n\n\t// Add ellipsis if needed\n\tif (start > 0) snippet = `...${snippet}`\n\tif (end < content.length) snippet = `${snippet}...`\n\n\treturn snippet\n}\n",
81
- "marketing/payload/src/app/(frontend)/api/newsletter/route.ts": "import { NextResponse } from \"next/server\"\n\nconst RESEND_API_URL = \"https://api.resend.com\"\n\n/**\n * Email validation regex\n */\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/**\n * Company address for CAN-SPAM compliance\n */\nconst COMPANY_ADDRESS = \"KrumaLabs • 102 West Main Street #501, New Albany, OH 43054\"\n\n/**\n * DirectoryHub logo URL\n */\nconst LOGO_URL = \"https://directoryhub.app/logo.png\"\n\n/**\n * Generate newsletter confirmation email HTML with logo and branding\n */\nfunction renderNewsletterConfirmationEmail(unsubscribeUrl?: string): string {\n\tconst emailStyles = {\n\t\tmain: `\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n line-height: 1.6;\n color: #333;\n background-color: #f5f5f5;\n margin: 0;\n padding: 20px;\n `,\n\t\tcontainer: `\n background-color: #ffffff;\n border-radius: 8px;\n margin: 0 auto;\n padding: 0;\n max-width: 600px;\n `,\n\t\theader: `\n padding: 32px 32px 24px;\n border-bottom: 1px solid #e5e5e5;\n `,\n\t\tlogoText: `\n color: #0070f3;\n font-size: 24px;\n font-weight: 600;\n margin: 0;\n padding: 0;\n line-height: 1;\n `,\n\t\theading: `\n color: #111111;\n font-size: 24px;\n font-weight: 600;\n line-height: 1.4;\n margin: 0 0 24px;\n `,\n\t\tcontent: `\n padding: 32px;\n `,\n\t\tparagraph: `\n font-size: 16px;\n color: #444444;\n margin: 0 0 16px;\n line-height: 1.6;\n `,\n\t\tparagraphSmall: `\n font-size: 14px;\n color: #666666;\n margin: 0 0 16px;\n line-height: 1.6;\n `,\n\t\tbutton: `\n background-color: #0070f3;\n color: white;\n padding: 14px 32px;\n text-decoration: none;\n border-radius: 6px;\n display: inline-block;\n font-weight: 500;\n font-size: 16px;\n `,\n\t\tfooter: `\n padding: 24px 32px;\n text-align: center;\n `,\n\t\tfooterText: `\n color: #666666;\n font-size: 14px;\n line-height: 1.6;\n margin: 0 0 8px;\n `,\n\t\tfooterTextSmall: `\n color: #999999;\n font-size: 12px;\n line-height: 1.6;\n margin: 16px 0 0;\n `,\n\t\tfooterAddress: `\n color: #999999;\n font-size: 11px;\n line-height: 1.6;\n margin: 8px 0 0;\n `,\n\t\tlink: `\n color: #0070f3;\n text-decoration: underline;\n `,\n\t}\n\n\treturn `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"${emailStyles.main}\">\n <div style=\"${emailStyles.container}\">\n <!-- Header with Logo -->\n <div style=\"${emailStyles.header}\">\n <a href=\"https://directoryhub.app\" style=\"display: inline-flex; align-items: center; gap: 12px; text-decoration: none; color: inherit;\">\n <img src=\"${LOGO_URL}\" alt=\"DirectoryHub\" width=\"32\" height=\"32\" style=\"display: block; width: 32px; height: 32px;\">\n <span style=\"${emailStyles.logoText}\">DirectoryHub</span>\n </a>\n </div>\n \n <!-- Content -->\n <div style=\"padding: 32px 32px 0;\"><h1 style=\"${emailStyles.heading}\">Welcome to the Newsletter!</h1></div>\n <div style=\"${emailStyles.content}\">\n <p style=\"${emailStyles.paragraph}\">\n Thanks for subscribing to the DirectoryHub newsletter! 🎉\n </p>\n <p style=\"${emailStyles.paragraph}\">\n You'll now receive updates about:\n </p>\n <ul style=\"${emailStyles.paragraph}\">\n <li>New features and product updates</li>\n <li>Tips for building better directories</li>\n <li>Industry insights and best practices</li>\n <li>Special announcements and offers</li>\n </ul>\n <p style=\"${emailStyles.paragraph}\">\n We respect your inbox and only send emails when we have something valuable to share.\n </p>\n <div style=\"margin: 32px 0; text-align: center;\">\n <a href=\"https://directoryhub.app\" style=\"${emailStyles.button}\">Visit DirectoryHub</a>\n </div>\n <p style=\"${emailStyles.paragraphSmall}\">\n If you didn't subscribe to this newsletter, you can safely ignore this email or\n <a href=\"${unsubscribeUrl || \"https://directoryhub.app/unsubscribe\"}\" style=\"${emailStyles.link}\">unsubscribe here</a>.\n </p>\n </div>\n \n <!-- Divider -->\n <hr style=\"border: none; border-top: 1px solid #e5e5e5; margin: 0 32px;\">\n \n <!-- Footer -->\n <div style=\"${emailStyles.footer}\">\n <p style=\"${emailStyles.footerText}\">DirectoryHub - Build beautiful directory websites</p>\n <p style=\"${emailStyles.footerText}\">\n <a href=\"https://directoryhub.app\" style=\"${emailStyles.link}\">Visit our website</a>\n <span style=\"color: #999999;\"> • </span>\n <a href=\"https://directoryhub.app/support\" style=\"${emailStyles.link}\">Support</a>\n ${unsubscribeUrl ? `<span style=\"color: #999999;\"> • </span><a href=\"${unsubscribeUrl}\" style=\"${emailStyles.link}\">Unsubscribe</a>` : \"\"}\n </p>\n <p style=\"${emailStyles.footerTextSmall}\">You're receiving this email because you signed up for the DirectoryHub newsletter.</p>\n <p style=\"${emailStyles.footerAddress}\">${COMPANY_ADDRESS}</p>\n </div>\n </div>\n</body>\n</html>`.trim()\n}\n\nexport async function POST(request: Request) {\n\ttry {\n\t\tconst body = await request.json()\n\t\tconst { email } = body\n\n\t\tif (!email) {\n\t\t\treturn NextResponse.json({ success: false, message: \"Email is required.\" }, { status: 400 })\n\t\t}\n\n\t\t// Normalize and validate email\n\t\tconst normalizedEmail = email.toLowerCase().trim()\n\t\tif (!EMAIL_REGEX.test(normalizedEmail)) {\n\t\t\treturn NextResponse.json(\n\t\t\t\t{ success: false, message: \"Please enter a valid email address.\" },\n\t\t\t\t{ status: 400 },\n\t\t\t)\n\t\t}\n\n\t\tconst apiKey = process.env.RESEND_API_KEY\n\t\tconst audienceId = process.env.RESEND_AUDIENCE_NEWSLETTER\n\t\t// Marketing emails use \"Theo from DirectoryHub\" as sender\n\t\tconst fromEmail = \"Theo from DirectoryHub <theo@notifications.directoryhub.app>\"\n\n\t\t// In development without API key, just return success\n\t\tif (!apiKey) {\n\t\t\tconsole.warn(\"RESEND_API_KEY not set - newsletter signup simulated\")\n\t\t\treturn NextResponse.json({\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: \"Thanks for subscribing! Check your inbox for a confirmation email.\",\n\t\t\t})\n\t\t}\n\n\t\t// Call Convex newsletter subscribe action via HTTP\n\t\t// This ensures consistent handling and uses the proper addNewsletterSubscriber function\n\t\tconst convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL || process.env.CONVEX_URL\n\t\tif (!convexUrl) {\n\t\t\tconsole.warn(\"CONVEX_URL not set - newsletter signup will fail\")\n\t\t\treturn NextResponse.json(\n\t\t\t\t{ success: false, message: \"Server configuration error. Please try again later.\" },\n\t\t\t\t{ status: 500 },\n\t\t\t)\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await fetch(`${convexUrl}/api/newsletter/subscribe`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\temail: normalizedEmail,\n\t\t\t\t}),\n\t\t\t})\n\n\t\t\tconst result = await response.json()\n\n\t\t\tif (!result.success) {\n\t\t\t\treturn NextResponse.json(result, { status: response.status })\n\t\t\t}\n\n\t\t\treturn NextResponse.json(result)\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Newsletter subscription error:\", error)\n\t\t\treturn NextResponse.json(\n\t\t\t\t{ success: false, message: \"Something went wrong. Please try again later.\" },\n\t\t\t\t{ status: 500 },\n\t\t\t)\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"Newsletter subscription error:\", error)\n\n\t\t// Check if it's a duplicate subscriber error\n\t\tif (error instanceof Error && error.message.includes(\"already exists\")) {\n\t\t\treturn NextResponse.json({\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: \"You're already subscribed to our newsletter!\",\n\t\t\t})\n\t\t}\n\n\t\treturn NextResponse.json(\n\t\t\t{ success: false, message: \"Something went wrong. Please try again later.\" },\n\t\t\t{ status: 500 },\n\t\t)\n\t}\n}\n",
85
+ "marketing/payload/src/app/(frontend)/api/newsletter/route.ts": "import { NextResponse } from \"next/server\"\n\nconst RESEND_API_URL = \"https://api.resend.com\"\n\n/**\n * Email validation regex\n */\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/**\n * Company address for CAN-SPAM compliance\n */\nconst COMPANY_ADDRESS = \"KrumaLabs • 102 West Main Street #501, New Albany, OH 43054\"\n\n/**\n * SaaSify logo URL\n */\nconst LOGO_URL = \"/logo.png\"\n\n/**\n * Generate newsletter confirmation email HTML with logo and branding\n */\nfunction renderNewsletterConfirmationEmail(unsubscribeUrl?: string): string {\n\tconst emailStyles = {\n\t\tmain: `\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n line-height: 1.6;\n color: #333;\n background-color: #f5f5f5;\n margin: 0;\n padding: 20px;\n `,\n\t\tcontainer: `\n background-color: #ffffff;\n border-radius: 8px;\n margin: 0 auto;\n padding: 0;\n max-width: 600px;\n `,\n\t\theader: `\n padding: 32px 32px 24px;\n border-bottom: 1px solid #e5e5e5;\n `,\n\t\tlogoText: `\n color: #0070f3;\n font-size: 24px;\n font-weight: 600;\n margin: 0;\n padding: 0;\n line-height: 1;\n `,\n\t\theading: `\n color: #111111;\n font-size: 24px;\n font-weight: 600;\n line-height: 1.4;\n margin: 0 0 24px;\n `,\n\t\tcontent: `\n padding: 32px;\n `,\n\t\tparagraph: `\n font-size: 16px;\n color: #444444;\n margin: 0 0 16px;\n line-height: 1.6;\n `,\n\t\tparagraphSmall: `\n font-size: 14px;\n color: #666666;\n margin: 0 0 16px;\n line-height: 1.6;\n `,\n\t\tbutton: `\n background-color: #0070f3;\n color: white;\n padding: 14px 32px;\n text-decoration: none;\n border-radius: 6px;\n display: inline-block;\n font-weight: 500;\n font-size: 16px;\n `,\n\t\tfooter: `\n padding: 24px 32px;\n text-align: center;\n `,\n\t\tfooterText: `\n color: #666666;\n font-size: 14px;\n line-height: 1.6;\n margin: 0 0 8px;\n `,\n\t\tfooterTextSmall: `\n color: #999999;\n font-size: 12px;\n line-height: 1.6;\n margin: 16px 0 0;\n `,\n\t\tfooterAddress: `\n color: #999999;\n font-size: 11px;\n line-height: 1.6;\n margin: 8px 0 0;\n `,\n\t\tlink: `\n color: #0070f3;\n text-decoration: underline;\n `,\n\t}\n\n\treturn `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"${emailStyles.main}\">\n <div style=\"${emailStyles.container}\">\n <!-- Header with Logo -->\n <div style=\"${emailStyles.header}\">\n <a href=\"/\" style=\"display: inline-flex; align-items: center; gap: 12px; text-decoration: none; color: inherit;\">\n <img src=\"${LOGO_URL}\" alt=\"SaaSify\" width=\"32\" height=\"32\" style=\"display: block; width: 32px; height: 32px;\">\n <span style=\"${emailStyles.logoText}\">SaaSify</span>\n </a>\n </div>\n \n <!-- Content -->\n <div style=\"padding: 32px 32px 0;\"><h1 style=\"${emailStyles.heading}\">Welcome to the Newsletter!</h1></div>\n <div style=\"${emailStyles.content}\">\n <p style=\"${emailStyles.paragraph}\">\n Thanks for subscribing to the SaaSify newsletter! 🎉\n </p>\n <p style=\"${emailStyles.paragraph}\">\n You'll now receive updates about:\n </p>\n <ul style=\"${emailStyles.paragraph}\">\n <li>New features and product updates</li>\n <li>Tips for boosting team productivity</li>\n <li>Industry insights and best practices</li>\n <li>Special announcements and offers</li>\n </ul>\n <p style=\"${emailStyles.paragraph}\">\n We respect your inbox and only send emails when we have something valuable to share.\n </p>\n <div style=\"margin: 32px 0; text-align: center;\">\n <a href=\"/\" style=\"${emailStyles.button}\">Visit SaaSify</a>\n </div>\n <p style=\"${emailStyles.paragraphSmall}\">\n If you didn't subscribe to this newsletter, you can safely ignore this email or\n <a href=\"${unsubscribeUrl || \"/unsubscribe\"}\" style=\"${emailStyles.link}\">unsubscribe here</a>.\n </p>\n </div>\n \n <!-- Divider -->\n <hr style=\"border: none; border-top: 1px solid #e5e5e5; margin: 0 32px;\">\n \n <!-- Footer -->\n <div style=\"${emailStyles.footer}\">\n <p style=\"${emailStyles.footerText}\">SaaSify - The modern platform for growing teams</p>\n <p style=\"${emailStyles.footerText}\">\n <a href=\"/\" style=\"${emailStyles.link}\">Visit our website</a>\n <span style=\"color: #999999;\"> • </span>\n <a href=\"/support\" style=\"${emailStyles.link}\">Support</a>\n ${unsubscribeUrl ? `<span style=\"color: #999999;\"> • </span><a href=\"${unsubscribeUrl}\" style=\"${emailStyles.link}\">Unsubscribe</a>` : \"\"}\n </p>\n <p style=\"${emailStyles.footerTextSmall}\">You're receiving this email because you signed up for the SaaSify newsletter.</p>\n <p style=\"${emailStyles.footerAddress}\">${COMPANY_ADDRESS}</p>\n </div>\n </div>\n</body>\n</html>`.trim()\n}\n\nexport async function POST(request: Request) {\n\ttry {\n\t\tconst body = await request.json()\n\t\tconst { email } = body\n\n\t\tif (!email) {\n\t\t\treturn NextResponse.json({ success: false, message: \"Email is required.\" }, { status: 400 })\n\t\t}\n\n\t\t// Normalize and validate email\n\t\tconst normalizedEmail = email.toLowerCase().trim()\n\t\tif (!EMAIL_REGEX.test(normalizedEmail)) {\n\t\t\treturn NextResponse.json(\n\t\t\t\t{ success: false, message: \"Please enter a valid email address.\" },\n\t\t\t\t{ status: 400 },\n\t\t\t)\n\t\t}\n\n\t\tconst apiKey = process.env.RESEND_API_KEY\n\t\tconst audienceId = process.env.RESEND_AUDIENCE_NEWSLETTER\n\t\t// Marketing emails sender - update with your domain\n\t\tconst fromEmail = \"SaaSify Team <hello@notifications.saasify.com>\"\n\n\t\t// In development without API key, just return success\n\t\tif (!apiKey) {\n\t\t\tconsole.warn(\"RESEND_API_KEY not set - newsletter signup simulated\")\n\t\t\treturn NextResponse.json({\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: \"Thanks for subscribing! Check your inbox for a confirmation email.\",\n\t\t\t})\n\t\t}\n\n\t\t// Call Convex newsletter subscribe action via HTTP\n\t\t// This ensures consistent handling and uses the proper addNewsletterSubscriber function\n\t\tconst convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL || process.env.CONVEX_URL\n\t\tif (!convexUrl) {\n\t\t\tconsole.warn(\"CONVEX_URL not set - newsletter signup will fail\")\n\t\t\treturn NextResponse.json(\n\t\t\t\t{ success: false, message: \"Server configuration error. Please try again later.\" },\n\t\t\t\t{ status: 500 },\n\t\t\t)\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await fetch(`${convexUrl}/api/newsletter/subscribe`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\temail: normalizedEmail,\n\t\t\t\t}),\n\t\t\t})\n\n\t\t\tconst result = await response.json()\n\n\t\t\tif (!result.success) {\n\t\t\t\treturn NextResponse.json(result, { status: response.status })\n\t\t\t}\n\n\t\t\treturn NextResponse.json(result)\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Newsletter subscription error:\", error)\n\t\t\treturn NextResponse.json(\n\t\t\t\t{ success: false, message: \"Something went wrong. Please try again later.\" },\n\t\t\t\t{ status: 500 },\n\t\t\t)\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"Newsletter subscription error:\", error)\n\n\t\t// Check if it's a duplicate subscriber error\n\t\tif (error instanceof Error && error.message.includes(\"already exists\")) {\n\t\t\treturn NextResponse.json({\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: \"You're already subscribed to our newsletter!\",\n\t\t\t})\n\t\t}\n\n\t\treturn NextResponse.json(\n\t\t\t{ success: false, message: \"Something went wrong. Please try again later.\" },\n\t\t\t{ status: 500 },\n\t\t)\n\t}\n}\n",
82
86
  "marketing/payload/src/app/(frontend)/api/pricing/route.ts": "import { NextResponse } from \"next/server\"\nimport Stripe from \"stripe\"\n\n/**\n * Stripe Lookup Keys - defined in Stripe Dashboard for each price\n * These must match the lookup keys configured in your Stripe Dashboard\n */\nconst STRIPE_LOOKUP_KEYS = {\n\tfree_monthly: \"free_monthly\",\n\tfree_annual: \"free_annual\",\n\tpro_monthly: \"pro_monthly\",\n\tpro_annual: \"pro_annual\",\n\tbusiness_monthly: \"business_monthly\",\n\tbusiness_annual: \"business_annual\",\n} as const\n\n/**\n * Default product metadata when Stripe metadata is missing\n */\nconst DEFAULT_PRODUCT_INFO: Record<\n\tstring,\n\t{ description: string; popular: boolean; order: number }\n> = {\n\tfree: { description: \"Perfect for getting started\", popular: false, order: 1 },\n\tpro: { description: \"For growing businesses\", popular: false, order: 2 },\n\tbusiness: { description: \"For teams and agencies\", popular: true, order: 3 },\n}\n\n/**\n * Feature type from Stripe Entitlements\n */\ninterface PlanFeature {\n\tid: string\n\tlookupKey: string\n\tname: string\n\tmetadata?: Record<string, string>\n}\n\n/**\n * All features type for comparison table\n */\ninterface AllFeature {\n\tid: string\n\tlookupKey: string\n\tname: string\n\tcategory?: string\n\tmetadata?: Record<string, string>\n}\n\n/**\n * Plan pricing type with features from Stripe\n */\ninterface PlanPricing {\n\tplanId: string\n\tstripeProductId: string\n\tname: string\n\tdescription: string\n\tfeatures: PlanFeature[]\n\tmonthlyPriceId?: string\n\tmonthlyAmount: number\n\tannualPriceId?: string\n\tannualAmount: number\n\tannualDiscount: number\n\tpopular: boolean\n\torder: number\n}\n\n/**\n * Fetch pricing and features from Stripe and return formatted data\n * GET /api/pricing\n *\n * Returns JSON with plan pricing and features for the marketing site\n */\nexport async function GET() {\n\ttry {\n\t\tconst stripeSecretKey = process.env.STRIPE_SECRET_KEY\n\n\t\tif (!stripeSecretKey) {\n\t\t\treturn NextResponse.json(\n\t\t\t\t{\n\t\t\t\t\tplans: [],\n\t\t\t\t\tallFeatures: [],\n\t\t\t\t\tlastFetched: Date.now(),\n\t\t\t\t\terror: \"Stripe not configured\",\n\t\t\t\t},\n\t\t\t\t{ status: 200 },\n\t\t\t)\n\t\t}\n\n\t\tconst stripe = new Stripe(stripeSecretKey)\n\n\t\t// Fetch all features for comparison table\n\t\tconst allFeatures: AllFeature[] = []\n\t\ttry {\n\t\t\tconst featuresResponse = await stripe.entitlements.features.list({ limit: 100 })\n\t\t\tfor (const feature of featuresResponse.data) {\n\t\t\t\tallFeatures.push({\n\t\t\t\t\tid: feature.id,\n\t\t\t\t\tlookupKey: feature.lookup_key,\n\t\t\t\t\tname: feature.name,\n\t\t\t\t\tcategory: feature.metadata?.category,\n\t\t\t\t\tmetadata: feature.metadata as Record<string, string> | undefined,\n\t\t\t\t})\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.error(\"Error fetching Stripe features:\", err)\n\t\t}\n\n\t\t// Fetch prices with product info\n\t\tconst priceMap = new Map<string, { id: string; amount: number; productId: string }>()\n\n\t\ttry {\n\t\t\tconst allLookupKeys = Object.values(STRIPE_LOOKUP_KEYS)\n\t\t\tconst prices = await stripe.prices.list({\n\t\t\t\tlookup_keys: allLookupKeys,\n\t\t\t\tactive: true,\n\t\t\t\texpand: [\"data.product\"],\n\t\t\t\tlimit: 20,\n\t\t\t})\n\n\t\t\tfor (const price of prices.data) {\n\t\t\t\tif (price.lookup_key && price.unit_amount !== null) {\n\t\t\t\t\tconst productId = typeof price.product === \"string\" ? price.product : price.product?.id\n\t\t\t\t\tif (productId) {\n\t\t\t\t\t\tpriceMap.set(price.lookup_key, {\n\t\t\t\t\t\t\tid: price.id,\n\t\t\t\t\t\t\tamount: price.unit_amount,\n\t\t\t\t\t\t\tproductId,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (stripeError) {\n\t\t\tconsole.error(\"Error fetching Stripe prices:\", stripeError)\n\t\t}\n\n\t\t// Group prices by product\n\t\tconst productPrices = new Map<\n\t\t\tstring,\n\t\t\t{ monthly?: { id: string; amount: number }; annual?: { id: string; amount: number } }\n\t\t>()\n\n\t\tfor (const [lookupKey, priceData] of priceMap.entries()) {\n\t\t\tconst isAnnual = lookupKey.endsWith(\"_annual\")\n\t\t\tconst existingPrices = productPrices.get(priceData.productId) || {}\n\n\t\t\tif (isAnnual) {\n\t\t\t\texistingPrices.annual = { id: priceData.id, amount: priceData.amount }\n\t\t\t} else {\n\t\t\t\texistingPrices.monthly = { id: priceData.id, amount: priceData.amount }\n\t\t\t}\n\n\t\t\tproductPrices.set(priceData.productId, existingPrices)\n\t\t}\n\n\t\t// Build plans from products\n\t\tconst plans: PlanPricing[] = []\n\n\t\tfor (const [productId, prices] of productPrices.entries()) {\n\t\t\t// Fetch product details\n\t\t\tlet product: Stripe.Product | null = null\n\t\t\ttry {\n\t\t\t\tconst fetched = await stripe.products.retrieve(productId)\n\t\t\t\tif (!fetched.deleted) {\n\t\t\t\t\tproduct = fetched\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Error fetching product ${productId}:`, err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (!product) continue\n\n\t\t\t// Get plan ID from product metadata\n\t\t\tconst planId = product.metadata?.plan_id\n\t\t\tif (!planId) {\n\t\t\t\tconsole.warn(`Product ${productId} missing plan_id metadata, skipping`)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Fetch features for this product\n\t\t\tconst features: PlanFeature[] = []\n\t\t\ttry {\n\t\t\t\tconst productFeatures = await stripe.products.listFeatures(productId, {\n\t\t\t\t\tlimit: 100,\n\t\t\t\t})\n\t\t\t\tfor (const pf of productFeatures.data) {\n\t\t\t\t\tfeatures.push({\n\t\t\t\t\t\tid: pf.entitlement_feature.id,\n\t\t\t\t\t\tlookupKey: pf.entitlement_feature.lookup_key,\n\t\t\t\t\t\tname: pf.entitlement_feature.name,\n\t\t\t\t\t\tmetadata: pf.entitlement_feature.metadata as Record<string, string> | undefined,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Error fetching features for product ${productId}:`, err)\n\t\t\t}\n\n\t\t\t// Get metadata or use defaults\n\t\t\tconst defaultInfo = DEFAULT_PRODUCT_INFO[planId] || {\n\t\t\t\tdescription: \"\",\n\t\t\t\tpopular: false,\n\t\t\t\torder: 99,\n\t\t\t}\n\t\t\tconst description =\n\t\t\t\tproduct.metadata?.description || product.description || defaultInfo.description\n\t\t\tconst popular = product.metadata?.popular === \"true\" || defaultInfo.popular\n\t\t\tconst order = Number.parseInt(product.metadata?.order || String(defaultInfo.order), 10)\n\n\t\t\tconst monthlyAmount = prices.monthly?.amount ?? 0\n\t\t\tconst annualAmount = prices.annual?.amount ?? 0\n\n\t\t\t// Calculate annual discount\n\t\t\tconst yearlyMonthlyEquivalent = monthlyAmount * 12\n\t\t\tconst annualDiscount =\n\t\t\t\tyearlyMonthlyEquivalent > 0\n\t\t\t\t\t? Math.round(((yearlyMonthlyEquivalent - annualAmount) / yearlyMonthlyEquivalent) * 100)\n\t\t\t\t\t: 0\n\n\t\t\tplans.push({\n\t\t\t\tplanId,\n\t\t\t\tstripeProductId: productId,\n\t\t\t\tname: product.name,\n\t\t\t\tdescription,\n\t\t\t\tfeatures,\n\t\t\t\tmonthlyPriceId: prices.monthly?.id,\n\t\t\t\tmonthlyAmount,\n\t\t\t\tannualPriceId: prices.annual?.id,\n\t\t\t\tannualAmount,\n\t\t\t\tannualDiscount: Math.max(0, annualDiscount),\n\t\t\t\tpopular,\n\t\t\t\torder,\n\t\t\t})\n\t\t}\n\n\t\t// Sort plans by order\n\t\tplans.sort((a, b) => a.order - b.order)\n\n\t\treturn NextResponse.json(\n\t\t\t{\n\t\t\t\tplans,\n\t\t\t\tallFeatures,\n\t\t\t\tlastFetched: Date.now(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: {\n\t\t\t\t\t// Cache for 1 hour on CDN, revalidate in background\n\t\t\t\t\t\"Cache-Control\": \"public, s-maxage=3600, stale-while-revalidate=7200\",\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\t} catch (error) {\n\t\tconsole.error(\"Pricing API error:\", error)\n\n\t\treturn NextResponse.json(\n\t\t\t{\n\t\t\t\tplans: [],\n\t\t\t\tallFeatures: [],\n\t\t\t\tlastFetched: Date.now(),\n\t\t\t\terror: \"Failed to fetch pricing\",\n\t\t\t},\n\t\t\t{ status: 200 },\n\t\t)\n\t}\n}\n",
83
87
  "marketing/payload/src/app/(frontend)/globals.css": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n@plugin \"@tailwindcss/typography\";\n\n@custom-variant dark (&:where([data-theme=\"dark\"], [data-theme=\"dark\"] *));\n\n@theme {\n\t/* Colors */\n\t--color-background: hsl(var(--background));\n\t--color-foreground: hsl(var(--foreground));\n\n\t--color-card: hsl(var(--card));\n\t--color-card-foreground: hsl(var(--card-foreground));\n\n\t--color-popover: hsl(var(--popover));\n\t--color-popover-foreground: hsl(var(--popover-foreground));\n\n\t--color-primary: hsl(var(--primary));\n\t--color-primary-foreground: hsl(var(--primary-foreground));\n\n\t--color-secondary: hsl(var(--secondary));\n\t--color-secondary-foreground: hsl(var(--secondary-foreground));\n\n\t--color-muted: hsl(var(--muted));\n\t--color-muted-foreground: hsl(var(--muted-foreground));\n\n\t--color-accent: hsl(var(--accent));\n\t--color-accent-foreground: hsl(var(--accent-foreground));\n\n\t--color-destructive: hsl(var(--destructive));\n\t--color-destructive-foreground: hsl(var(--destructive-foreground));\n\n\t--color-border: hsla(var(--border));\n\t--color-input: hsl(var(--input));\n\t--color-ring: hsl(var(--ring));\n\n\t--color-success: hsl(var(--success));\n\t--color-warning: hsl(var(--warning));\n\t--color-error: hsl(var(--error));\n\n\t/* Border radius */\n\t--radius-sm: calc(var(--radius) - 4px);\n\t--radius-md: calc(var(--radius) - 2px);\n\t--radius-lg: var(--radius);\n\t--radius-xl: calc(var(--radius) + 4px);\n\n\t/* Fonts */\n\t--font-sans: var(--font-inter), system-ui, sans-serif;\n\t--font-mono: var(--font-geist-mono), monospace;\n\n\t/* Container - matching web app */\n\t--container-sm: 40rem;\n\t--container-md: 48rem;\n\t--container-lg: 64rem;\n\t--container-xl: 80rem;\n\t--container-2xl: 96rem;\n\n\t/* Animations */\n\t--animate-accordion-down: accordion-down 0.2s ease-out;\n\t--animate-accordion-up: accordion-up 0.2s ease-out;\n}\n\n@keyframes accordion-down {\n\tfrom {\n\t\theight: 0;\n\t}\n\tto {\n\t\theight: var(--radix-accordion-content-height);\n\t}\n}\n\n@keyframes accordion-up {\n\tfrom {\n\t\theight: var(--radix-accordion-content-height);\n\t}\n\tto {\n\t\theight: 0;\n\t}\n}\n\n:root {\n\t--background: 0 0% 98%; /* #f7f9fc */\n\t--foreground: 220 59% 15%; /* #0f1f3d */\n\n\t--card: 217 46% 96%; /* #e9eff8 */\n\t--card-foreground: 220 59% 15%; /* #0f1f3d */\n\n\t--popover: 0 0% 100%;\n\t--popover-foreground: 220 59% 15%;\n\n\t--primary: 220 59% 15%; /* navy */\n\t--primary-foreground: 0 0% 100%;\n\n\t--secondary: 173 55% 32%; /* darker teal for WCAG AA contrast */\n\t--secondary-foreground: 220 59% 15%;\n\n\t--muted: 217 46% 96%; /* mist */\n\t--muted-foreground: 220 20% 25%; /* darkened further for WCAG AA on card backgrounds */\n\n\t--accent: 213 35% 95%; /* soft accent */\n\t--accent-foreground: 220 59% 15%;\n\n\t--destructive: 0 68% 65%; /* #e76a6a */\n\t--destructive-foreground: 0 0% 100%;\n\n\t--border: 216 23% 89%; /* #e1e6ef */\n\t--input: 216 23% 89%;\n\t--ring: 220 59% 15%;\n\n\t--radius: 0.75rem;\n\n\t--success: 157 48% 47%; /* #3fae8c */\n\t--warning: 41 54% 62%; /* #d9b36a */\n\t--error: 0 68% 65%; /* #e76a6a */\n}\n\n[data-theme=\"dark\"] {\n\t--background: 219 53% 12%; /* #0b162c */\n\t--foreground: 216 33% 93%; /* #f7f9fc */\n\n\t--card: 220 52% 11%; /* #101c32 */\n\t--card-foreground: 216 33% 93%;\n\n\t--popover: 220 52% 11%;\n\t--popover-foreground: 216 33% 93%;\n\n\t--primary: 220 59% 15%;\n\t--primary-foreground: 0 0% 100%;\n\n\t--secondary: 173 46% 44%;\n\t--secondary-foreground: 219 53% 12%;\n\n\t--muted: 221 53% 12%;\n\t--muted-foreground: 217 23% 75%; /* improved contrast for WCAG AA */\n\n\t--accent: 222 44% 16%;\n\t--accent-foreground: 216 33% 93%;\n\n\t--destructive: 0 70% 71%;\n\t--destructive-foreground: 219 53% 12%;\n\n\t--border: 222 30% 21%;\n\t--input: 222 30% 21%;\n\t--ring: 173 46% 44%;\n\n\t--success: 157 48% 47%;\n\t--warning: 41 54% 62%;\n\t--error: 0 70% 71%;\n}\n\n* {\n\tborder-color: var(--color-border);\n}\n\nbody {\n\tbackground-color: var(--color-background);\n\tcolor: var(--color-foreground);\n\tmin-height: 100vh;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\nhtml {\n\topacity: 0;\n}\n\nhtml[data-theme=\"dark\"],\nhtml[data-theme=\"light\"] {\n\topacity: initial;\n}\n\n/* Container utility - matching web app */\n.container {\n\twidth: 100%;\n\tmargin-left: auto;\n\tmargin-right: auto;\n\tpadding-left: 1rem;\n\tpadding-right: 1rem;\n\tmax-width: 96rem;\n}\n\n/* Hero typography - matching web app exactly */\n.hero-content h1 {\n\tfont-size: 2.25rem !important; /* text-4xl */\n\tline-height: 2.5rem !important; /* text-4xl line-height */\n\tfont-weight: 700 !important; /* font-bold */\n\tletter-spacing: -0.025em !important; /* tracking-tight */\n\tmargin-bottom: 1.5rem !important; /* mb-6 */\n\tcolor: hsl(var(--foreground)) !important;\n}\n\n.hero-content p {\n\tfont-size: 1.25rem !important; /* text-xl */\n\tline-height: 1.75rem !important; /* text-xl line-height */\n\tcolor: hsl(var(--muted-foreground)) !important;\n\tmax-width: 42rem; /* max-w-2xl */\n\tmargin-left: auto;\n\tmargin-right: auto;\n\tmargin-bottom: 2rem !important; /* mb-8 */\n}\n\n.hero-content--dark h1 {\n\tcolor: white !important;\n}\n\n.hero-content--dark p {\n\tcolor: rgba(255, 255, 255, 0.8) !important;\n}\n\n@media (min-width: 768px) {\n\t.hero-content h1 {\n\t\tfont-size: 3.75rem !important; /* md:text-6xl */\n\t\tline-height: 1 !important; /* text-6xl line-height */\n\t}\n}\n\n@media (min-width: 640px) {\n\t.container {\n\t\tpadding-left: 1.5rem;\n\t\tpadding-right: 1.5rem;\n\t}\n}\n\n@media (min-width: 1024px) {\n\t.container {\n\t\tpadding-left: 2rem;\n\t\tpadding-right: 2rem;\n\t}\n}\n\n/* ========================================\n PRODUCT SHOWCASE HERO STYLES\n ======================================== */\n\n/* Left-aligned hero content */\n.hero-content--left h1 {\n\ttext-align: left !important;\n\tmargin-left: 0 !important;\n\tmargin-right: 0 !important;\n}\n\n.hero-content--left p {\n\ttext-align: left !important;\n\tmargin-left: 0 !important;\n\tmargin-right: 0 !important;\n}\n\n/* Hero showcase section */\n.hero-showcase {\n\tposition: relative;\n\tborder-radius: 12px;\n\toverflow: hidden;\n\tpadding: 3rem;\n\tmin-height: 500px;\n}\n\n@media (min-width: 768px) {\n\t.hero-showcase {\n\t\tmin-height: 600px;\n\t}\n}\n\n@media (min-width: 1024px) {\n\t.hero-showcase {\n\t\tmin-height: 700px;\n\t}\n}\n\n/* Hero background image */\n.hero-bg-image {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 0;\n}\n\n.hero-bg-image img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tobject-position: center;\n}\n\n/* Mockup centered within background */\n.hero-mockup-centered {\n\tposition: relative;\n\tz-index: 10;\n\tmax-width: 800px;\n\tmargin: 0 auto;\n\twidth: 100%;\n}\n\n@media (min-width: 768px) {\n\t.hero-mockup-centered {\n\t\tmax-width: 900px;\n\t}\n}\n\n@media (min-width: 1024px) {\n\t.hero-mockup-centered {\n\t\tmax-width: 1000px;\n\t}\n}\n\n.mockup-wrapper {\n\tbackground: hsl(var(--background));\n\tborder-radius: 12px;\n\tbox-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 12px 24px -8px rgba(0, 0, 0, 0.15), 0 0 0 1px\n\t\trgba(0, 0, 0, 0.05);\n\toverflow: hidden;\n\tanimation: mockup-entrance 0.8s ease-out;\n\twidth: 100%;\n}\n\n.mockup-wrapper img {\n\twidth: 100%;\n\theight: auto;\n\tdisplay: block;\n}\n\n@keyframes mockup-entrance {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translateY(30px);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n}\n\n/* Browser chrome */\n.mockup-chrome {\n\tdisplay: flex;\n\talign-items: center;\n\tpadding: 12px 16px;\n\tbackground: hsl(var(--muted));\n\tborder-bottom: 1px solid hsl(var(--border));\n}\n\n.mockup-chrome-dots {\n\tdisplay: flex;\n\tgap: 6px;\n}\n\n.mockup-chrome-dots .dot {\n\twidth: 12px;\n\theight: 12px;\n\tborder-radius: 50%;\n}\n\n.mockup-chrome-dots .dot-red {\n\tbackground: #ff5f56;\n}\n.mockup-chrome-dots .dot-yellow {\n\tbackground: #ffbd2e;\n}\n.mockup-chrome-dots .dot-green {\n\tbackground: #27ca40;\n}\n\n.mockup-chrome-title {\n\tflex: 1;\n\ttext-align: center;\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcolor: hsl(var(--muted-foreground));\n}\n\n.mockup-chrome-actions {\n\twidth: 60px;\n}\n\n/* App content layout */\n.mockup-content {\n\tdisplay: flex;\n\tmin-height: 400px;\n}\n\n.mockup-sidebar {\n\twidth: 220px;\n\tbackground: hsl(var(--card));\n\tborder-right: 1px solid hsl(var(--border));\n\tflex-shrink: 0;\n}\n\n.sidebar-header {\n\tpadding: 16px;\n\tborder-bottom: 1px solid hsl(var(--border));\n}\n\n.sidebar-logo {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n}\n\n.logo-icon {\n\twidth: 28px;\n\theight: 28px;\n\tbackground: hsl(var(--primary));\n\tcolor: white;\n\tborder-radius: 6px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-weight: 700;\n\tfont-size: 14px;\n}\n\n.logo-text {\n\tfont-weight: 600;\n\tfont-size: 14px;\n\tcolor: hsl(var(--foreground));\n}\n\n.sidebar-nav {\n\tpadding: 8px;\n}\n\n.sidebar-item {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 10px 12px;\n\tborder-radius: 6px;\n\tfont-size: 13px;\n\tcolor: hsl(var(--muted-foreground));\n\ttransition: all 0.15s ease;\n\tcursor: pointer;\n}\n\n.sidebar-item:hover {\n\tbackground: hsl(var(--accent));\n\tcolor: hsl(var(--accent-foreground));\n}\n\n.sidebar-item--active {\n\tbackground: hsl(var(--primary) / 0.12);\n\tcolor: hsl(var(--primary));\n}\n\n.sidebar-icon {\n\tfont-size: 16px;\n}\n\n.sidebar-label {\n\tflex: 1;\n}\n\n.sidebar-badge {\n\tbackground: hsl(var(--primary));\n\tcolor: hsl(var(--primary-foreground));\n\tfont-size: 11px;\n\tpadding: 2px 6px;\n\tborder-radius: 10px;\n\tfont-weight: 500;\n}\n\n/* Main content area */\n.mockup-main {\n\tflex: 1;\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground: hsl(var(--background));\n}\n\n.main-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 16px 20px;\n\tborder-bottom: 1px solid hsl(var(--border));\n}\n\n.header-title {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.header-title h2 {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tmargin: 0;\n\tcolor: hsl(var(--foreground));\n}\n\n.header-breadcrumb {\n\tfont-size: 12px;\n\tcolor: hsl(var(--muted-foreground));\n}\n\n.header-actions {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.action-btn {\n\tpadding: 8px 16px;\n\tbackground: hsl(var(--primary));\n\tcolor: hsl(var(--primary-foreground));\n\tborder: none;\n\tborder-radius: 6px;\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: all 0.3s ease;\n}\n\n.action-btn--success {\n\tbackground: #27ca40;\n}\n\n.action-btn--featured {\n\tbackground: hsl(var(--primary));\n}\n\n/* Split view */\n.main-split {\n\tdisplay: flex;\n\tflex: 1;\n}\n\n.editor-panel {\n\tflex: 1;\n\tpadding: 20px;\n\tborder-right: 1px solid hsl(var(--border));\n}\n\n.editor-section {\n\tmargin-bottom: 20px;\n}\n\n.editor-label {\n\tdisplay: block;\n\tfont-size: 12px;\n\tfont-weight: 500;\n\tcolor: hsl(var(--muted-foreground));\n\tmargin-bottom: 6px;\n}\n\n.editor-input {\n\tbackground: hsl(var(--muted));\n\tborder: 1px solid hsl(var(--border));\n\tborder-radius: 6px;\n\tpadding: 10px 12px;\n\tfont-size: 14px;\n\tcolor: hsl(var(--foreground));\n\tdisplay: flex;\n\talign-items: center;\n}\n\n.input-text {\n\tflex: 1;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.input-cursor {\n\twidth: 2px;\n\theight: 18px;\n\tbackground: #6c4cff;\n\tanimation: blink 1s infinite;\n\tmargin-left: 2px;\n}\n\n@keyframes blink {\n\t0%,\n\t50% {\n\t\topacity: 1;\n\t}\n\t51%,\n\t100% {\n\t\topacity: 0;\n\t}\n}\n\n.editor-select {\n\tbackground: hsl(var(--muted));\n\tborder: 1px solid hsl(var(--border));\n\tborder-radius: 6px;\n\tpadding: 10px 12px;\n\tfont-size: 14px;\n\tcolor: hsl(var(--foreground));\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n}\n\n.select-arrow {\n\tfont-size: 10px;\n\tcolor: hsl(var(--muted-foreground));\n}\n\n.editor-textarea {\n\tbackground: hsl(var(--muted));\n\tborder: 1px solid hsl(var(--border));\n\tborder-radius: 6px;\n\tpadding: 10px 12px;\n\tfont-size: 14px;\n\tcolor: hsl(var(--foreground));\n\tmin-height: 80px;\n}\n\n.textarea-text {\n\ttransition: all 0.5s ease;\n}\n\n.textarea-text--complete {\n\tcolor: hsl(var(--foreground));\n}\n\n/* Preview panel */\n.preview-panel {\n\twidth: 320px;\n\tpadding: 20px;\n\tbackground: hsl(var(--muted) / 0.5);\n}\n\n.preview-header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tmargin-bottom: 16px;\n}\n\n.preview-label {\n\tfont-size: 12px;\n\tfont-weight: 500;\n\tcolor: hsl(var(--muted-foreground));\n}\n\n.preview-url {\n\tfont-size: 11px;\n\tcolor: hsl(var(--muted-foreground));\n\tfont-family: monospace;\n}\n\n.preview-card {\n\tbackground: hsl(var(--card));\n\tborder: 1px solid hsl(var(--border));\n\tborder-radius: 10px;\n\toverflow: hidden;\n\tposition: relative;\n\ttransition: all 0.3s ease;\n}\n\n.preview-badge {\n\tposition: absolute;\n\ttop: 10px;\n\tright: 10px;\n\tbackground: hsl(var(--primary));\n\tcolor: white;\n\tfont-size: 11px;\n\tpadding: 4px 8px;\n\tborder-radius: 4px;\n\tfont-weight: 500;\n\tanimation: badge-pop 0.3s ease;\n}\n\n@keyframes badge-pop {\n\tfrom {\n\t\ttransform: scale(0);\n\t\topacity: 0;\n\t}\n\tto {\n\t\ttransform: scale(1);\n\t\topacity: 1;\n\t}\n}\n\n.preview-image {\n\theight: 120px;\n\tbackground: linear-gradient(135deg, hsl(var(--muted)) 0%, hsl(var(--accent)) 100%);\n}\n\n.preview-image-placeholder {\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 40px;\n}\n\n.preview-content {\n\tpadding: 16px;\n}\n\n.preview-category-tag {\n\tdisplay: inline-block;\n\tfont-size: 11px;\n\tcolor: hsl(var(--primary));\n\tfont-weight: 500;\n\tmargin-bottom: 8px;\n}\n\n.preview-title {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tmargin: 0 0 8px;\n\tcolor: hsl(var(--foreground));\n}\n\n.preview-description {\n\tfont-size: 13px;\n\tcolor: hsl(var(--muted-foreground));\n\tline-height: 1.5;\n\tmargin: 0 0 12px;\n}\n\n.preview-meta {\n\tdisplay: flex;\n\tgap: 8px;\n\tfont-size: 12px;\n}\n\n.meta-rating {\n\tcolor: #ffc24a;\n}\n\n.meta-reviews {\n\tcolor: hsl(var(--muted-foreground));\n}\n\n/* State indicators */\n.mockup-indicators {\n\tdisplay: flex;\n\tjustify-content: center;\n\tgap: 24px;\n\tpadding: 16px;\n\tborder-top: 1px solid hsl(var(--border));\n}\n\n.indicator {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tbackground: none;\n\tborder: none;\n\tcursor: pointer;\n\tpadding: 6px 12px;\n\tborder-radius: 20px;\n\ttransition: all 0.2s ease;\n}\n\n.indicator:hover {\n\tbackground: hsl(var(--accent));\n}\n\n.indicator--active {\n\tbackground: hsl(var(--primary) / 0.12);\n}\n\n.indicator-dot {\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: hsl(var(--muted-foreground));\n\ttransition: all 0.2s ease;\n}\n\n.indicator--active .indicator-dot {\n\tbackground: hsl(var(--primary));\n\tbox-shadow: 0 0 0 3px hsl(var(--primary) / 0.2);\n}\n\n.indicator-label {\n\tfont-size: 12px;\n\tcolor: hsl(var(--muted-foreground));\n}\n\n.indicator--active .indicator-label {\n\tcolor: hsl(var(--foreground));\n\tfont-weight: 500;\n}\n\n/* ========================================\n LOGO BANNER STYLES\n ======================================== */\n\n.logo-scroll-container {\n\toverflow: hidden;\n\tmask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);\n}\n\n.logo-scroll-track {\n\tdisplay: flex;\n\tanimation: logo-scroll 30s linear infinite;\n}\n\n.logo-scroll-track:hover {\n\tanimation-play-state: paused;\n}\n\n@keyframes logo-scroll {\n\t0% {\n\t\ttransform: translateX(0);\n\t}\n\t100% {\n\t\ttransform: translateX(-50%);\n\t}\n}\n\n.logo-item {\n\tflex-shrink: 0;\n\tmin-width: 150px;\n}\n\n/* ========================================\n RESPONSIVE ADJUSTMENTS\n ======================================== */\n\n@media (max-width: 768px) {\n\t.mockup-content {\n\t\tflex-direction: column;\n\t}\n\n\t.mockup-sidebar {\n\t\twidth: 100%;\n\t\tborder-right: none;\n\t\tborder-bottom: 1px solid hsl(var(--border));\n\t}\n\n\t.sidebar-nav {\n\t\tdisplay: flex;\n\t\toverflow-x: auto;\n\t\tpadding: 8px;\n\t\tgap: 4px;\n\t}\n\n\t.sidebar-item {\n\t\twhite-space: nowrap;\n\t}\n\n\t.main-split {\n\t\tflex-direction: column;\n\t}\n\n\t.editor-panel {\n\t\tborder-right: none;\n\t\tborder-bottom: 1px solid hsl(var(--border));\n\t}\n\n\t.preview-panel {\n\t\twidth: 100%;\n\t}\n\n\t.mockup-indicators {\n\t\tflex-wrap: wrap;\n\t\tgap: 12px;\n\t}\n\n\t.indicator-label {\n\t\tdisplay: none;\n\t}\n}\n\n/* ========================================\n BIRD-INSPIRED BLOCK STYLES\n ======================================== */\n\n/* Industry Tabs Animation */\n.industry-tab-content {\n\tanimation: tab-fade-in 0.3s ease-out;\n}\n\n@keyframes tab-fade-in {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translateY(10px);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n}\n\n/* Feature Showcase Entrance Animation */\n@keyframes feature-slide-in {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translateX(-20px);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: translateX(0);\n\t}\n}\n\n.feature-showcase-content {\n\tanimation: feature-slide-in 0.5s ease-out;\n}\n\n/* Testimonial Card Hover Effects */\n.testimonial-card {\n\ttransition: transform 0.3s ease, box-shadow 0.3s ease;\n}\n\n.testimonial-card:hover {\n\ttransform: translateY(-4px);\n\tbox-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.15);\n}\n\n/* Stat Number Animation */\n@keyframes stat-count-up {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translateY(20px);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n}\n\n.stat-number {\n\tanimation: stat-count-up 0.6s ease-out;\n}\n\n/* Trust Column Icon Animation */\n.trust-icon {\n\ttransition: transform 0.2s ease, background-color 0.2s ease;\n}\n\n.trust-icon:hover {\n\ttransform: scale(1.1);\n}\n\n/* Final CTA Background Animation */\n@keyframes subtle-float {\n\t0%,\n\t100% {\n\t\ttransform: translateY(0);\n\t}\n\t50% {\n\t\ttransform: translateY(-10px);\n\t}\n}\n\n.cta-decorative {\n\tanimation: subtle-float 6s ease-in-out infinite;\n}\n\n/* Feature Grid Card Entrance */\n@keyframes card-entrance {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translateY(20px);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n}\n\n.feature-card {\n\tanimation: card-entrance 0.4s ease-out;\n\tanimation-fill-mode: both;\n}\n\n.feature-card:nth-child(1) {\n\tanimation-delay: 0.1s;\n}\n.feature-card:nth-child(2) {\n\tanimation-delay: 0.2s;\n}\n.feature-card:nth-child(3) {\n\tanimation-delay: 0.3s;\n}\n.feature-card:nth-child(4) {\n\tanimation-delay: 0.4s;\n}\n.feature-card:nth-child(5) {\n\tanimation-delay: 0.5s;\n}\n.feature-card:nth-child(6) {\n\tanimation-delay: 0.6s;\n}\n\n/* Smooth Section Transitions */\nsection {\n\tscroll-margin-top: 80px;\n}\n\n/* Button Hover Enhancement */\n.cta-button {\n\tposition: relative;\n\toverflow: hidden;\n}\n\n.cta-button::after {\n\tcontent: \"\";\n\tposition: absolute;\n\tinset: 0;\n\tbackground: linear-gradient(rgba(255, 255, 255, 0.1), transparent);\n\topacity: 0;\n\ttransition: opacity 0.3s ease;\n}\n\n.cta-button:hover::after {\n\topacity: 1;\n}\n\n/* Responsive Image Container */\n.feature-image-container {\n\tposition: relative;\n\tborder-radius: 12px;\n\toverflow: hidden;\n\tbox-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15);\n\ttransition: transform 0.3s ease, box-shadow 0.3s ease;\n}\n\n.feature-image-container:hover {\n\ttransform: translateY(-4px);\n\tbox-shadow: 0 30px 60px -15px rgba(0, 0, 0, 0.2);\n}\n",
84
- "marketing/payload/src/app/(frontend)/layout.tsx": "import type { Metadata } from \"next\"\n\nimport { cn } from \"@/utilities/ui\"\nimport { GeistMono } from \"geist/font/mono\"\nimport { Inter } from \"next/font/google\"\nimport type React from \"react\"\n\nimport { Footer } from \"@/Footer/Component\"\nimport { Header } from \"@/Header/Component\"\nimport { AdminBar } from \"@/components/AdminBar\"\nimport { JsonLdSchemas } from \"@/components/JsonLd\"\nimport { Providers } from \"@/providers\"\nimport { InitTheme } from \"@/providers/Theme/InitTheme\"\nimport { mergeOpenGraph } from \"@/utilities/mergeOpenGraph\"\nimport { draftMode } from \"next/headers\"\n\nimport { getServerSideURL } from \"@/utilities/getURL\"\nimport \"./globals.css\"\n\nconst inter = Inter({\n\tsubsets: [\"latin\"],\n\tvariable: \"--font-inter\",\n\tdisplay: \"swap\",\n})\n\nexport default async function RootLayout({ children }: { children: React.ReactNode }) {\n\tconst { isEnabled } = await draftMode()\n\n\treturn (\n\t\t<html className={cn(inter.variable, GeistMono.variable)} lang=\"en\" suppressHydrationWarning>\n\t\t\t<head>\n\t\t\t\t<InitTheme />\n\t\t\t\t{/* Favicon */}\n\t\t\t\t<link href=\"/favicon.ico\" rel=\"icon\" sizes=\"32x32\" />\n\t\t\t\t<link href=\"/favicon.svg\" rel=\"icon\" type=\"image/svg+xml\" />\n\n\t\t\t\t{/* Preconnect to external resources for performance */}\n\t\t\t\t<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n\t\t\t\t<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossOrigin=\"anonymous\" />\n\t\t\t\t{/* Preconnect to PostHog proxy for faster analytics initialization */}\n\t\t\t\t<link rel=\"preconnect\" href=\"/ingest\" />\n\n\t\t\t\t{/* DNS prefetch for common external services */}\n\t\t\t\t<link rel=\"dns-prefetch\" href=\"https://www.google-analytics.com\" />\n\t\t\t\t<link rel=\"dns-prefetch\" href=\"https://www.googletagmanager.com\" />\n\n\t\t\t\t{/* JSON-LD Structured Data */}\n\t\t\t\t<JsonLdSchemas />\n\t\t\t</head>\n\t\t\t<body className=\"font-sans antialiased\" suppressHydrationWarning>\n\t\t\t\t<Providers>\n\t\t\t\t\t<AdminBar\n\t\t\t\t\t\tadminBarProps={{\n\t\t\t\t\t\t\tpreview: isEnabled,\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\n\t\t\t\t\t<Header />\n\t\t\t\t\t<main id=\"main-content\" className=\"flex-1\">\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</main>\n\t\t\t\t\t<Footer />\n\t\t\t\t</Providers>\n\t\t\t</body>\n\t\t</html>\n\t)\n}\n\nexport const metadata: Metadata = {\n\tmetadataBase: new URL(getServerSideURL()),\n\ttitle: {\n\t\tdefault: \"DirectoryHub - Build Directories That Generate Real Business\",\n\t\ttemplate: \"%s | DirectoryHub\",\n\t},\n\tdescription:\n\t\t\"Launch a profitable directory business in minutes. The no-code platform to build, manage, and monetize niche directory websites.\",\n\tkeywords: [\n\t\t\"directory builder\",\n\t\t\"no-code directory\",\n\t\t\"directory website\",\n\t\t\"niche directory\",\n\t\t\"business directory software\",\n\t\t\"directory platform\",\n\t\t\"monetize directory\",\n\t],\n\tauthors: [{ name: \"DirectoryHub\", url: getServerSideURL() }],\n\tcreator: \"DirectoryHub\",\n\tpublisher: \"DirectoryHub\",\n\trobots: {\n\t\tindex: true,\n\t\tfollow: true,\n\t\tgoogleBot: {\n\t\t\tindex: true,\n\t\t\tfollow: true,\n\t\t\t\"max-video-preview\": -1,\n\t\t\t\"max-image-preview\": \"large\",\n\t\t\t\"max-snippet\": -1,\n\t\t},\n\t},\n\topenGraph: mergeOpenGraph(),\n\ttwitter: {\n\t\tcard: \"summary_large_image\",\n\t\tcreator: \"@directoryhub\",\n\t\tsite: \"@directoryhub\",\n\t\ttitle: \"DirectoryHub - Build Directories That Generate Real Business\",\n\t\tdescription:\n\t\t\t\"Launch a profitable directory business in minutes. The no-code platform to build, manage, and monetize niche directory websites.\",\n\t},\n\tverification: {\n\t\t// Add your verification codes here when available\n\t\t// google: 'your-google-verification-code',\n\t\t// yandex: 'your-yandex-verification-code',\n\t},\n}\n",
88
+ "marketing/payload/src/app/(frontend)/layout.tsx": "import type { Metadata } from \"next\"\n\nimport { cn } from \"@/utilities/ui\"\nimport { GeistMono } from \"geist/font/mono\"\nimport { Inter } from \"next/font/google\"\nimport type React from \"react\"\n\nimport { Footer } from \"@/Footer/Component\"\nimport { Header } from \"@/Header/Component\"\nimport { AdminBar } from \"@/components/AdminBar\"\nimport { JsonLdSchemas } from \"@/components/JsonLd\"\nimport { Providers } from \"@/providers\"\nimport { InitTheme } from \"@/providers/Theme/InitTheme\"\nimport { mergeOpenGraph } from \"@/utilities/mergeOpenGraph\"\nimport { draftMode } from \"next/headers\"\n\nimport { getServerSideURL } from \"@/utilities/getURL\"\nimport \"./globals.css\"\n\nconst inter = Inter({\n\tsubsets: [\"latin\"],\n\tvariable: \"--font-inter\",\n\tdisplay: \"swap\",\n})\n\nexport default async function RootLayout({ children }: { children: React.ReactNode }) {\n\tconst { isEnabled } = await draftMode()\n\n\treturn (\n\t\t<html className={cn(inter.variable, GeistMono.variable)} lang=\"en\" suppressHydrationWarning>\n\t\t\t<head>\n\t\t\t\t<InitTheme />\n\t\t\t\t{/* Favicon */}\n\t\t\t\t<link href=\"/favicon.ico\" rel=\"icon\" sizes=\"32x32\" />\n\t\t\t\t<link href=\"/favicon.svg\" rel=\"icon\" type=\"image/svg+xml\" />\n\n\t\t\t\t{/* Preconnect to external resources for performance */}\n\t\t\t\t<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n\t\t\t\t<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossOrigin=\"anonymous\" />\n\t\t\t\t{/* Preconnect to PostHog proxy for faster analytics initialization */}\n\t\t\t\t<link rel=\"preconnect\" href=\"/ingest\" />\n\n\t\t\t\t{/* DNS prefetch for common external services */}\n\t\t\t\t<link rel=\"dns-prefetch\" href=\"https://www.google-analytics.com\" />\n\t\t\t\t<link rel=\"dns-prefetch\" href=\"https://www.googletagmanager.com\" />\n\n\t\t\t\t{/* JSON-LD Structured Data */}\n\t\t\t\t<JsonLdSchemas />\n\t\t\t</head>\n\t\t\t<body className=\"font-sans antialiased\" suppressHydrationWarning>\n\t\t\t\t<Providers>\n\t\t\t\t\t<AdminBar\n\t\t\t\t\t\tadminBarProps={{\n\t\t\t\t\t\t\tpreview: isEnabled,\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\n\t\t\t\t\t<Header />\n\t\t\t\t\t<main id=\"main-content\" className=\"flex-1\">\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</main>\n\t\t\t\t\t<Footer />\n\t\t\t\t</Providers>\n\t\t\t</body>\n\t\t</html>\n\t)\n}\n\nexport const metadata: Metadata = {\n\tmetadataBase: new URL(getServerSideURL()),\n\ttitle: {\n\t\tdefault: \"SaaSify - The Modern Platform for Growing Teams\",\n\t\ttemplate: \"%s | SaaSify\",\n\t},\n\tdescription:\n\t\t\"Streamline workflows, boost productivity, and scale your business with one powerful platform.\",\n\tkeywords: [\n\t\t\"SaaS platform\",\n\t\t\"team productivity\",\n\t\t\"workflow automation\",\n\t\t\"business software\",\n\t\t\"collaboration tools\",\n\t\t\"project management\",\n\t\t\"team collaboration\",\n\t],\n\tauthors: [{ name: \"SaaSify\", url: getServerSideURL() }],\n\tcreator: \"SaaSify\",\n\tpublisher: \"SaaSify\",\n\trobots: {\n\t\tindex: true,\n\t\tfollow: true,\n\t\tgoogleBot: {\n\t\t\tindex: true,\n\t\t\tfollow: true,\n\t\t\t\"max-video-preview\": -1,\n\t\t\t\"max-image-preview\": \"large\",\n\t\t\t\"max-snippet\": -1,\n\t\t},\n\t},\n\topenGraph: mergeOpenGraph(),\n\ttwitter: {\n\t\tcard: \"summary_large_image\",\n\t\tcreator: \"@saasify\",\n\t\tsite: \"@saasify\",\n\t\ttitle: \"SaaSify - The Modern Platform for Growing Teams\",\n\t\tdescription:\n\t\t\t\"Streamline workflows, boost productivity, and scale your business with one powerful platform.\",\n\t},\n\tverification: {\n\t\t// Add your verification codes here when available\n\t\t// google: 'your-google-verification-code',\n\t\t// yandex: 'your-yandex-verification-code',\n\t},\n}\n",
85
89
  "marketing/payload/src/app/(frontend)/next/exit-preview/route.ts": "import { draftMode } from \"next/headers\"\n\nexport async function GET(): Promise<Response> {\n\tconst draft = await draftMode()\n\tdraft.disable()\n\treturn new Response(\"Draft mode is disabled\")\n}\n",
86
90
  "marketing/payload/src/app/(frontend)/next/preview/route.ts": "import type { CollectionSlug, PayloadRequest } from \"payload\"\nimport { getPayload } from \"payload\"\n\nimport { draftMode } from \"next/headers\"\nimport { redirect } from \"next/navigation\"\nimport type { NextRequest } from \"next/server\"\n\nimport configPromise from \"@payload-config\"\n\nexport async function GET(req: NextRequest): Promise<Response> {\n\tconst payload = await getPayload({ config: configPromise })\n\n\tconst { searchParams } = new URL(req.url)\n\n\tconst path = searchParams.get(\"path\")\n\tconst collection = searchParams.get(\"collection\") as CollectionSlug\n\tconst slug = searchParams.get(\"slug\")\n\tconst previewSecret = searchParams.get(\"previewSecret\")\n\n\tif (previewSecret !== process.env.PREVIEW_SECRET) {\n\t\treturn new Response(\"You are not allowed to preview this page\", { status: 403 })\n\t}\n\n\tif (!path || !collection || !slug) {\n\t\treturn new Response(\"Insufficient search params\", { status: 404 })\n\t}\n\n\tif (!path.startsWith(\"/\")) {\n\t\treturn new Response(\"This endpoint can only be used for relative previews\", { status: 500 })\n\t}\n\n\tlet user\n\n\ttry {\n\t\tuser = await payload.auth({\n\t\t\treq: req as unknown as PayloadRequest,\n\t\t\theaders: req.headers,\n\t\t})\n\t} catch (error) {\n\t\tpayload.logger.error({ err: error }, \"Error verifying token for live preview\")\n\t\treturn new Response(\"You are not allowed to preview this page\", { status: 403 })\n\t}\n\n\tconst draft = await draftMode()\n\n\tif (!user) {\n\t\tdraft.disable()\n\t\treturn new Response(\"You are not allowed to preview this page\", { status: 403 })\n\t}\n\n\t// You can add additional checks here to see if the user is allowed to preview this page\n\n\tdraft.enable()\n\n\tredirect(path)\n}\n",
87
91
  "marketing/payload/src/app/(frontend)/next/seed/route.ts": "import { seed } from \"@/endpoints/seed\"\nimport config from \"@payload-config\"\nimport { headers } from \"next/headers\"\nimport { createLocalReq, getPayload } from \"payload\"\n\nexport const maxDuration = 60 // This function can run for a maximum of 60 seconds\n\nexport async function POST(): Promise<Response> {\n\tconst payload = await getPayload({ config })\n\tconst requestHeaders = await headers()\n\n\t// Authenticate by passing request headers\n\tconst { user } = await payload.auth({ headers: requestHeaders })\n\n\tif (!user) {\n\t\treturn new Response(\"Action forbidden.\", { status: 403 })\n\t}\n\n\ttry {\n\t\t// Create a Payload request object to pass to the Local API for transactions\n\t\t// At this point you should pass in a user, locale, and any other context you need for the Local API\n\t\tconst payloadReq = await createLocalReq({ user }, payload)\n\n\t\tawait seed({ payload, req: payloadReq })\n\n\t\treturn Response.json({ success: true })\n\t} catch (e) {\n\t\tpayload.logger.error({ err: e, message: \"Error seeding data\" })\n\t\treturn new Response(\"Error seeding data.\", { status: 500 })\n\t}\n}\n",
88
92
  "marketing/payload/src/app/(frontend)/not-found.tsx": "import Link from \"next/link\"\n\nimport { Button } from \"@/components/ui/button\"\n\nexport default function NotFound() {\n\treturn (\n\t\t<div className=\"container py-28\">\n\t\t\t<div className=\"prose max-w-none\">\n\t\t\t\t<h1 style={{ marginBottom: 0 }}>404</h1>\n\t\t\t\t<p className=\"mb-4\">This page could not be found.</p>\n\t\t\t</div>\n\t\t\t<Button asChild variant=\"default\">\n\t\t\t\t<Link href=\"/\">Go home</Link>\n\t\t\t</Button>\n\t\t</div>\n\t)\n}\n",
89
93
  "marketing/payload/src/app/(frontend)/page.tsx": "import PageTemplate, { generateMetadata } from \"./[slug]/page\"\n\nexport default PageTemplate\n\nexport { generateMetadata }\n",
90
94
  "marketing/payload/src/app/(frontend)/posts/BlogPageClient.tsx": "\"use client\"\n\nimport { Card, type CardPostData } from \"@/components/Card\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport type { Category } from \"@/payload-types\"\nimport { useHeaderTheme } from \"@/providers/HeaderTheme\"\nimport { Search, X } from \"lucide-react\"\nimport { useEffect, useMemo, useState } from \"react\"\n\ninterface BlogPageClientProps {\n\tinitialPosts: CardPostData[]\n\tcategories: Category[]\n\ttotalPosts: number\n}\n\nexport const BlogPageClient: React.FC<BlogPageClientProps> = ({\n\tinitialPosts,\n\tcategories,\n\ttotalPosts,\n}) => {\n\tconst { setHeaderTheme } = useHeaderTheme()\n\tconst [searchQuery, setSearchQuery] = useState(\"\")\n\tconst [selectedCategory, setSelectedCategory] = useState<string | null>(null)\n\n\tuseEffect(() => {\n\t\tsetHeaderTheme(\"light\")\n\t}, [setHeaderTheme])\n\n\t// Filter posts based on search query and selected category\n\tconst filteredPosts = useMemo(() => {\n\t\treturn initialPosts.filter((post) => {\n\t\t\t// Search filter\n\t\t\tconst matchesSearch =\n\t\t\t\tsearchQuery === \"\" ||\n\t\t\t\tpost.title?.toLowerCase().includes(searchQuery.toLowerCase()) ||\n\t\t\t\tpost.meta?.description?.toLowerCase().includes(searchQuery.toLowerCase())\n\n\t\t\t// Category filter\n\t\t\tconst matchesCategory =\n\t\t\t\t!selectedCategory ||\n\t\t\t\t(Array.isArray(post.categories) &&\n\t\t\t\t\tpost.categories.some((cat) => {\n\t\t\t\t\t\tif (typeof cat === \"object\" && cat !== null) {\n\t\t\t\t\t\t\treturn String(cat.id) === selectedCategory\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn String(cat) === selectedCategory\n\t\t\t\t\t}))\n\n\t\t\treturn matchesSearch && matchesCategory\n\t\t})\n\t}, [initialPosts, searchQuery, selectedCategory])\n\n\t// Get featured post (first post when no filters)\n\tconst featuredPost = searchQuery === \"\" && !selectedCategory ? filteredPosts[0] : null\n\tconst regularPosts = featuredPost ? filteredPosts.slice(1) : filteredPosts\n\n\tconst clearFilters = () => {\n\t\tsetSearchQuery(\"\")\n\t\tsetSelectedCategory(null)\n\t}\n\n\tconst hasActiveFilters = searchQuery !== \"\" || selectedCategory !== null\n\n\treturn (\n\t\t<div className=\"pt-24 pb-24\">\n\t\t\t{/* Hero Section */}\n\t\t\t<div className=\"container mb-12\">\n\t\t\t\t<div className=\"max-w-4xl\">\n\t\t\t\t\t<h1 className=\"text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight mb-4\">\n\t\t\t\t\t\tBlog & Resources\n\t\t\t\t\t</h1>\n\t\t\t\t\t<p className=\"text-lg md:text-xl text-muted-foreground mb-8\">\n\t\t\t\t\t\tLearn how to build, grow, and monetize directory websites. Strategies, tutorials, and\n\t\t\t\t\t\tsuccess stories to help you succeed.\n\t\t\t\t\t</p>\n\n\t\t\t\t\t{/* Search Bar */}\n\t\t\t\t\t<div className=\"relative max-w-xl\">\n\t\t\t\t\t\t<Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground\" />\n\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Search articles...\"\n\t\t\t\t\t\t\tvalue={searchQuery}\n\t\t\t\t\t\t\tonChange={(e) => setSearchQuery(e.target.value)}\n\t\t\t\t\t\t\tclassName=\"pl-10 pr-10 h-12 text-base\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{searchQuery && (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => setSearchQuery(\"\")}\n\t\t\t\t\t\t\t\tclassName=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n\t\t\t\t\t\t\t\taria-label=\"Clear search\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<X className=\"h-5 w-5\" aria-hidden=\"true\" />\n\t\t\t\t\t\t\t</button>\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\n\t\t\t{/* Category Filters */}\n\t\t\t<div className=\"container mb-8\">\n\t\t\t\t<div className=\"flex flex-wrap gap-2\">\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant={selectedCategory === null ? \"default\" : \"outline\"}\n\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\tonClick={() => setSelectedCategory(null)}\n\t\t\t\t\t>\n\t\t\t\t\t\tAll Posts\n\t\t\t\t\t</Button>\n\t\t\t\t\t{categories.map((category) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tkey={category.id}\n\t\t\t\t\t\t\tvariant={selectedCategory === String(category.id) ? \"default\" : \"outline\"}\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tonClick={() => setSelectedCategory(String(category.id))}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{category.title}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Results Count & Clear Filters */}\n\t\t\t<div className=\"container mb-8\">\n\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t{hasActiveFilters ? (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\tShowing {filteredPosts.length} of {totalPosts} articles\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<>{totalPosts} articles</>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</p>\n\t\t\t\t\t{hasActiveFilters && (\n\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" onClick={clearFilters}>\n\t\t\t\t\t\t\tClear filters\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Featured Post */}\n\t\t\t{featuredPost && (\n\t\t\t\t<div className=\"container mb-12\">\n\t\t\t\t\t<div className=\"relative\">\n\t\t\t\t\t\t<span className=\"absolute -top-3 left-4 bg-primary text-primary-foreground text-xs font-medium px-2 py-1 rounded z-10\">\n\t\t\t\t\t\t\tFeatured\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<Card\n\t\t\t\t\t\t\tdoc={featuredPost}\n\t\t\t\t\t\t\trelationTo=\"posts\"\n\t\t\t\t\t\t\tshowCategories\n\t\t\t\t\t\t\tclassName=\"lg:grid lg:grid-cols-2 lg:gap-8\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Posts Grid */}\n\t\t\t<div className=\"container\">\n\t\t\t\t{regularPosts.length > 0 ? (\n\t\t\t\t\t<div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8\">\n\t\t\t\t\t\t{regularPosts.map((post, index) => (\n\t\t\t\t\t\t\t<Card\n\t\t\t\t\t\t\t\tkey={post.slug || index}\n\t\t\t\t\t\t\t\tdoc={post}\n\t\t\t\t\t\t\t\trelationTo=\"posts\"\n\t\t\t\t\t\t\t\tshowCategories\n\t\t\t\t\t\t\t\tclassName=\"h-full\"\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) : (\n\t\t\t\t\t<div className=\"text-center py-16\">\n\t\t\t\t\t\t<p className=\"text-lg text-muted-foreground mb-4\">\n\t\t\t\t\t\t\tNo articles found matching your criteria.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<Button variant=\"outline\" onClick={clearFilters}>\n\t\t\t\t\t\t\tClear filters\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n",
91
- "marketing/payload/src/app/(frontend)/posts/[slug]/BlogPostContent.tsx": "\"use client\"\n\nimport type { HeadingItem } from \"@/utilities/extractHeadings\"\nimport type { DefaultTypedEditorState } from \"@payloadcms/richtext-lexical\"\n\nimport RichText from \"@/components/RichText\"\nimport { TableOfContents } from \"@/components/TableOfContents\"\nimport { slugify } from \"@/utilities/extractHeadings\"\nimport { useEffect, useRef } from \"react\"\n\ninterface BlogPostContentProps {\n\tcontent: DefaultTypedEditorState\n\theadings: HeadingItem[]\n}\n\nexport function BlogPostContent({ content, headings }: BlogPostContentProps) {\n\tconst contentRef = useRef<HTMLDivElement>(null)\n\n\t// Add IDs to headings after mount for TOC linking\n\tuseEffect(() => {\n\t\tif (!contentRef.current) return\n\n\t\tconst headingElements = contentRef.current.querySelectorAll(\"h1, h2, h3, h4, h5, h6\")\n\t\tfor (const heading of headingElements) {\n\t\t\tconst text = heading.textContent || \"\"\n\t\t\tconst id = slugify(text)\n\t\t\tif (!heading.id) {\n\t\t\t\theading.id = id\n\t\t\t}\n\t\t}\n\t}, [])\n\n\treturn (\n\t\t<div className=\"container py-12\">\n\t\t\t<div className=\"flex flex-col lg:flex-row gap-8 lg:gap-12\">\n\t\t\t\t{/* Sticky TOC Sidebar - Hidden on mobile */}\n\t\t\t\t<aside className=\"hidden lg:block lg:w-64 xl:w-72 flex-shrink-0\">\n\t\t\t\t\t<div className=\"sticky top-24\">\n\t\t\t\t\t\t<TableOfContents\n\t\t\t\t\t\t\theadings={headings}\n\t\t\t\t\t\t\tsignUpCta={{\n\t\t\t\t\t\t\t\ttitle: \"Experience DirectoryHub\",\n\t\t\t\t\t\t\t\tdescription: \"Start building your directory today\",\n\t\t\t\t\t\t\t\tbuttonText: \"Sign up for free\",\n\t\t\t\t\t\t\t\tbuttonLink: \"https://app.directoryhub.app/sign-up\",\n\t\t\t\t\t\t\t\timageSrc: \"/media/hero-dashboard-500x500.webp\",\n\t\t\t\t\t\t\t\timageAlt: \"DirectoryHub dashboard feature\",\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</aside>\n\n\t\t\t\t{/* Main Content */}\n\t\t\t\t<main className=\"flex-1 min-w-0\" ref={contentRef}>\n\t\t\t\t\t{content && (\n\t\t\t\t\t\t<RichText\n\t\t\t\t\t\t\tclassName=\"max-w-none prose-headings:scroll-mt-24\"\n\t\t\t\t\t\t\tdata={content}\n\t\t\t\t\t\t\tenableGutter={false}\n\t\t\t\t\t\t\tenableProse={true}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t</main>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n",
95
+ "marketing/payload/src/app/(frontend)/posts/[slug]/BlogPostContent.tsx": "\"use client\"\n\nimport type { HeadingItem } from \"@/utilities/extractHeadings\"\nimport type { DefaultTypedEditorState } from \"@payloadcms/richtext-lexical\"\n\nimport RichText from \"@/components/RichText\"\nimport { TableOfContents } from \"@/components/TableOfContents\"\nimport { slugify } from \"@/utilities/extractHeadings\"\nimport { useEffect, useRef } from \"react\"\n\ninterface BlogPostContentProps {\n\tcontent: DefaultTypedEditorState\n\theadings: HeadingItem[]\n}\n\nexport function BlogPostContent({ content, headings }: BlogPostContentProps) {\n\tconst contentRef = useRef<HTMLDivElement>(null)\n\n\t// Add IDs to headings after mount for TOC linking\n\tuseEffect(() => {\n\t\tif (!contentRef.current) return\n\n\t\tconst headingElements = contentRef.current.querySelectorAll(\"h1, h2, h3, h4, h5, h6\")\n\t\tfor (const heading of headingElements) {\n\t\t\tconst text = heading.textContent || \"\"\n\t\t\tconst id = slugify(text)\n\t\t\tif (!heading.id) {\n\t\t\t\theading.id = id\n\t\t\t}\n\t\t}\n\t}, [])\n\n\treturn (\n\t\t<div className=\"container py-12\">\n\t\t\t<div className=\"flex flex-col lg:flex-row gap-8 lg:gap-12\">\n\t\t\t\t{/* Sticky TOC Sidebar - Hidden on mobile */}\n\t\t\t\t<aside className=\"hidden lg:block lg:w-64 xl:w-72 flex-shrink-0\">\n\t\t\t\t\t<div className=\"sticky top-24\">\n\t\t\t\t\t\t<TableOfContents\n\t\t\t\t\t\t\theadings={headings}\n\t\t\t\t\t\t\tsignUpCta={{\n\t\t\t\t\t\t\t\ttitle: \"Experience SaaSify\",\n\t\t\t\t\t\t\t\tdescription: \"Start boosting your team's productivity today\",\n\t\t\t\t\t\t\t\tbuttonText: \"Start free trial\",\n\t\t\t\t\t\t\t\tbuttonLink: \"/pricing\",\n\t\t\t\t\t\t\t\timageSrc: \"/media/hero-dashboard-500x500.webp\",\n\t\t\t\t\t\t\t\timageAlt: \"SaaSify dashboard feature\",\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</aside>\n\n\t\t\t\t{/* Main Content */}\n\t\t\t\t<main className=\"flex-1 min-w-0\" ref={contentRef}>\n\t\t\t\t\t{content && (\n\t\t\t\t\t\t<RichText\n\t\t\t\t\t\t\tclassName=\"max-w-none prose-headings:scroll-mt-24\"\n\t\t\t\t\t\t\tdata={content}\n\t\t\t\t\t\t\tenableGutter={false}\n\t\t\t\t\t\t\tenableProse={true}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t</main>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n",
92
96
  "marketing/payload/src/app/(frontend)/posts/[slug]/page.client.tsx": "\"use client\"\nimport { useHeaderTheme } from \"@/providers/HeaderTheme\"\nimport React, { useEffect } from \"react\"\n\nconst PageClient: React.FC = () => {\n\t/* Force the header to be dark mode while we have an image behind it */\n\tconst { setHeaderTheme } = useHeaderTheme()\n\n\tuseEffect(() => {\n\t\tsetHeaderTheme(\"dark\")\n\t}, [setHeaderTheme])\n\treturn <React.Fragment />\n}\n\nexport default PageClient\n",
93
97
  "marketing/payload/src/app/(frontend)/posts/[slug]/page.tsx": "import type { Metadata } from \"next\"\n\nimport { RelatedPosts } from \"@/blocks/RelatedPosts/Component\"\nimport { BlogCTA } from \"@/components/BlogCTA\"\nimport { PayloadRedirects } from \"@/components/PayloadRedirects\"\nimport configPromise from \"@payload-config\"\nimport { draftMode } from \"next/headers\"\nimport { getPayload } from \"payload\"\nimport { cache } from \"react\"\n\nimport { LivePreviewListener } from \"@/components/LivePreviewListener\"\nimport { PostHero } from \"@/heros/PostHero\"\nimport { extractHeadingsFromLexical } from \"@/utilities/extractHeadings\"\nimport { generateMeta } from \"@/utilities/generateMeta\"\nimport { BlogPostContent } from \"./BlogPostContent\"\nimport PageClient from \"./page.client\"\n\nexport async function generateStaticParams() {\n\tconst payload = await getPayload({ config: configPromise })\n\tconst posts = await payload.find({\n\t\tcollection: \"posts\",\n\t\tdraft: false,\n\t\tlimit: 1000,\n\t\toverrideAccess: false,\n\t\tpagination: false,\n\t\tselect: {\n\t\t\tslug: true,\n\t\t},\n\t})\n\n\tconst params = posts.docs.map(({ slug }) => {\n\t\treturn { slug }\n\t})\n\n\treturn params\n}\n\ntype Args = {\n\tparams: Promise<{\n\t\tslug?: string\n\t}>\n}\n\nexport default async function Post({ params: paramsPromise }: Args) {\n\tconst { isEnabled: draft } = await draftMode()\n\tconst { slug = \"\" } = await paramsPromise\n\t// Decode to support slugs with special characters\n\tconst decodedSlug = decodeURIComponent(slug)\n\tconst url = `/posts/${decodedSlug}`\n\tconst post = await queryPostBySlug({ slug: decodedSlug })\n\n\tif (!post) return <PayloadRedirects url={url} />\n\n\t// Extract headings for table of contents\n\tconst headings = extractHeadingsFromLexical(post.content)\n\n\treturn (\n\t\t<article className=\"pb-0\">\n\t\t\t<PageClient />\n\n\t\t\t{/* Allows redirects for valid pages too */}\n\t\t\t<PayloadRedirects disableNotFound url={url} />\n\n\t\t\t{draft && <LivePreviewListener />}\n\n\t\t\t{/* Hero Section */}\n\t\t\t<PostHero post={post} />\n\n\t\t\t{/* Two-column layout with TOC and content */}\n\t\t\t<BlogPostContent content={post.content} headings={headings} />\n\n\t\t\t{/* Related Posts */}\n\t\t\t{post.relatedPosts && post.relatedPosts.length > 0 && (\n\t\t\t\t<div className=\"container py-12 border-t border-border\">\n\t\t\t\t\t<h2 className=\"text-2xl font-bold mb-8\">Related Articles</h2>\n\t\t\t\t\t<RelatedPosts\n\t\t\t\t\t\tclassName=\"max-w-none\"\n\t\t\t\t\t\tdocs={post.relatedPosts.filter((post) => typeof post === \"object\")}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Bottom CTA */}\n\t\t\t<BlogCTA />\n\t\t</article>\n\t)\n}\n\nexport async function generateMetadata({ params: paramsPromise }: Args): Promise<Metadata> {\n\tconst { slug = \"\" } = await paramsPromise\n\t// Decode to support slugs with special characters\n\tconst decodedSlug = decodeURIComponent(slug)\n\tconst post = await queryPostBySlug({ slug: decodedSlug })\n\n\treturn generateMeta({ doc: post })\n}\n\nconst queryPostBySlug = cache(async ({ slug }: { slug: string }) => {\n\tconst { isEnabled: draft } = await draftMode()\n\n\tconst payload = await getPayload({ config: configPromise })\n\n\tconst result = await payload.find({\n\t\tcollection: \"posts\",\n\t\tdepth: 2, // Populate relationships in rich text content\n\t\tdraft,\n\t\tlimit: 1,\n\t\toverrideAccess: draft,\n\t\tpagination: false,\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 result.docs?.[0] || null\n})\n",
94
98
  "marketing/payload/src/app/(frontend)/posts/page/[pageNumber]/page.client.tsx": "\"use client\"\nimport { useHeaderTheme } from \"@/providers/HeaderTheme\"\nimport React, { useEffect } from \"react\"\n\nconst PageClient: React.FC = () => {\n\t/* Force the header to be dark mode while we have an image behind it */\n\tconst { setHeaderTheme } = useHeaderTheme()\n\n\tuseEffect(() => {\n\t\tsetHeaderTheme(\"light\")\n\t}, [setHeaderTheme])\n\treturn <React.Fragment />\n}\n\nexport default PageClient\n",
95
99
  "marketing/payload/src/app/(frontend)/posts/page/[pageNumber]/page.tsx": "import type { Metadata } from \"next/types\"\n\nimport { CollectionArchive } from \"@/components/CollectionArchive\"\nimport { PageRange } from \"@/components/PageRange\"\nimport { Pagination } from \"@/components/Pagination\"\nimport configPromise from \"@payload-config\"\nimport { notFound } from \"next/navigation\"\nimport { getPayload } from \"payload\"\nimport PageClient from \"./page.client\"\n\nexport const revalidate = 600\n\ntype Args = {\n\tparams: Promise<{\n\t\tpageNumber: string\n\t}>\n}\n\nexport default async function Page({ params: paramsPromise }: Args) {\n\tconst { pageNumber } = await paramsPromise\n\tconst payload = await getPayload({ config: configPromise })\n\n\tconst sanitizedPageNumber = Number(pageNumber)\n\n\tif (!Number.isInteger(sanitizedPageNumber)) notFound()\n\n\tconst posts = await payload.find({\n\t\tcollection: \"posts\",\n\t\tdepth: 1,\n\t\tlimit: 12,\n\t\tpage: sanitizedPageNumber,\n\t\toverrideAccess: false,\n\t})\n\n\treturn (\n\t\t<div className=\"pt-24 pb-24\">\n\t\t\t<PageClient />\n\t\t\t<div className=\"container mb-16\">\n\t\t\t\t<div className=\"prose dark:prose-invert max-w-none\">\n\t\t\t\t\t<h1>Posts</h1>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div className=\"container mb-8\">\n\t\t\t\t<PageRange\n\t\t\t\t\tcollection=\"posts\"\n\t\t\t\t\tcurrentPage={posts.page}\n\t\t\t\t\tlimit={12}\n\t\t\t\t\ttotalDocs={posts.totalDocs}\n\t\t\t\t/>\n\t\t\t</div>\n\n\t\t\t<CollectionArchive posts={posts.docs} />\n\n\t\t\t<div className=\"container\">\n\t\t\t\t{posts?.page && posts?.totalPages > 1 && (\n\t\t\t\t\t<Pagination page={posts.page} totalPages={posts.totalPages} />\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nexport async function generateMetadata({ params: paramsPromise }: Args): Promise<Metadata> {\n\tconst { pageNumber } = await paramsPromise\n\treturn {\n\t\ttitle: `Payload Website Template Posts Page ${pageNumber || \"\"}`,\n\t}\n}\n\nexport async function generateStaticParams() {\n\tconst payload = await getPayload({ config: configPromise })\n\tconst { totalDocs } = await payload.count({\n\t\tcollection: \"posts\",\n\t\toverrideAccess: false,\n\t})\n\n\tconst totalPages = Math.ceil(totalDocs / 10)\n\n\tconst pages: { pageNumber: string }[] = []\n\n\tfor (let i = 1; i <= totalPages; i++) {\n\t\tpages.push({ pageNumber: String(i) })\n\t}\n\n\treturn pages\n}\n",
96
- "marketing/payload/src/app/(frontend)/posts/page.tsx": "import type { Metadata } from \"next/types\"\n\nimport configPromise from \"@payload-config\"\nimport { getPayload } from \"payload\"\nimport { BlogPageClient } from \"./BlogPageClient\"\n\nexport const dynamic = \"force-static\"\nexport const revalidate = 600\n\nexport default async function Page() {\n\tconst payload = await getPayload({ config: configPromise })\n\n\tconst posts = await payload.find({\n\t\tcollection: \"posts\",\n\t\tdepth: 1,\n\t\tlimit: 100,\n\t\toverrideAccess: false,\n\t\tselect: {\n\t\t\ttitle: true,\n\t\t\tslug: true,\n\t\t\tcategories: true,\n\t\t\tmeta: true,\n\t\t\tpublishedAt: true,\n\t\t},\n\t\tsort: \"-publishedAt\",\n\t})\n\n\tconst categories = await payload.find({\n\t\tcollection: \"categories\",\n\t\tlimit: 50,\n\t\toverrideAccess: false,\n\t})\n\n\treturn (\n\t\t<BlogPageClient\n\t\t\tinitialPosts={posts.docs}\n\t\t\tcategories={categories.docs}\n\t\t\ttotalPosts={posts.totalDocs}\n\t\t/>\n\t)\n}\n\nexport function generateMetadata(): Metadata {\n\treturn {\n\t\ttitle: \"Blog — DirectoryHub Resources & Guides\",\n\t\tdescription:\n\t\t\t\"Learn how to build, grow, and monetize directory websites. Tips, strategies, and success stories from the DirectoryHub team.\",\n\t}\n}\n",
100
+ "marketing/payload/src/app/(frontend)/posts/page.tsx": "import type { Metadata } from \"next/types\"\n\nimport configPromise from \"@payload-config\"\nimport { getPayload } from \"payload\"\nimport { BlogPageClient } from \"./BlogPageClient\"\n\nexport const dynamic = \"force-static\"\nexport const revalidate = 600\n\nexport default async function Page() {\n\tconst payload = await getPayload({ config: configPromise })\n\n\tconst posts = await payload.find({\n\t\tcollection: \"posts\",\n\t\tdepth: 1,\n\t\tlimit: 100,\n\t\toverrideAccess: false,\n\t\tselect: {\n\t\t\ttitle: true,\n\t\t\tslug: true,\n\t\t\tcategories: true,\n\t\t\tmeta: true,\n\t\t\tpublishedAt: true,\n\t\t},\n\t\tsort: \"-publishedAt\",\n\t})\n\n\tconst categories = await payload.find({\n\t\tcollection: \"categories\",\n\t\tlimit: 50,\n\t\toverrideAccess: false,\n\t})\n\n\treturn (\n\t\t<BlogPageClient\n\t\t\tinitialPosts={posts.docs}\n\t\t\tcategories={categories.docs}\n\t\t\ttotalPosts={posts.totalDocs}\n\t\t/>\n\t)\n}\n\nexport function generateMetadata(): Metadata {\n\treturn {\n\t\ttitle: \"Blog — SaaSify Resources & Guides\",\n\t\tdescription:\n\t\t\t\"Tips, strategies, and insights for growing teams. Learn how to boost productivity and scale your business with SaaSify.\",\n\t}\n}\n",
97
101
  "marketing/payload/src/app/(frontend)/search/page.client.tsx": "\"use client\"\nimport { useHeaderTheme } from \"@/providers/HeaderTheme\"\nimport React, { useEffect } from \"react\"\n\nconst PageClient: React.FC = () => {\n\t/* Force the header to be dark mode while we have an image behind it */\n\tconst { setHeaderTheme } = useHeaderTheme()\n\n\tuseEffect(() => {\n\t\tsetHeaderTheme(\"light\")\n\t}, [setHeaderTheme])\n\treturn <React.Fragment />\n}\n\nexport default PageClient\n",
98
102
  "marketing/payload/src/app/(frontend)/search/page.tsx": "import type { Metadata } from \"next/types\"\n\nimport type { CardPostData } from \"@/components/Card\"\nimport { CollectionArchive } from \"@/components/CollectionArchive\"\nimport { Search } from \"@/search/Component\"\nimport configPromise from \"@payload-config\"\nimport { getPayload } from \"payload\"\nimport PageClient from \"./page.client\"\n\ntype Args = {\n\tsearchParams: Promise<{\n\t\tq: string\n\t}>\n}\nexport default async function Page({ searchParams: searchParamsPromise }: Args) {\n\tconst { q: query } = await searchParamsPromise\n\tconst payload = await getPayload({ config: configPromise })\n\n\tconst posts = await payload.find({\n\t\tcollection: \"search\",\n\t\tdepth: 1,\n\t\tlimit: 12,\n\t\tselect: {\n\t\t\ttitle: true,\n\t\t\tslug: true,\n\t\t\tcategories: true,\n\t\t\tmeta: true,\n\t\t},\n\t\t// pagination: false reduces overhead if you don't need totalDocs\n\t\tpagination: false,\n\t\t...(query\n\t\t\t? {\n\t\t\t\t\twhere: {\n\t\t\t\t\t\tor: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttitle: {\n\t\t\t\t\t\t\t\t\tlike: query,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"meta.description\": {\n\t\t\t\t\t\t\t\t\tlike: query,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"meta.title\": {\n\t\t\t\t\t\t\t\t\tlike: query,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tslug: {\n\t\t\t\t\t\t\t\t\tlike: query,\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}\n\t\t\t: {}),\n\t})\n\n\treturn (\n\t\t<div className=\"pt-24 pb-24\">\n\t\t\t<PageClient />\n\t\t\t<div className=\"container mb-16\">\n\t\t\t\t<div className=\"prose dark:prose-invert max-w-none text-center\">\n\t\t\t\t\t<h1 className=\"mb-8 lg:mb-16\">Search</h1>\n\n\t\t\t\t\t<div className=\"max-w-[50rem] mx-auto\">\n\t\t\t\t\t\t<Search />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{posts.totalDocs > 0 ? (\n\t\t\t\t<CollectionArchive posts={posts.docs as CardPostData[]} />\n\t\t\t) : (\n\t\t\t\t<div className=\"container\">No results found.</div>\n\t\t\t)}\n\t\t</div>\n\t)\n}\n\nexport function generateMetadata(): Metadata {\n\treturn {\n\t\ttitle: `Payload Website Template Search`,\n\t}\n}\n",
99
103
  "marketing/payload/src/app/(payload)/admin/[[...segments]]/not-found.tsx": "/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */\n/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */\nimport type { Metadata } from \"next\"\n\nimport config from \"@payload-config\"\nimport { NotFoundPage, generatePageMetadata } from \"@payloadcms/next/views\"\nimport { importMap } from \"../importMap\"\n\ntype Args = {\n\tparams: Promise<{\n\t\tsegments: string[]\n\t}>\n\tsearchParams: Promise<{\n\t\t[key: string]: string | string[]\n\t}>\n}\n\nexport const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>\n\tgeneratePageMetadata({ config, params, searchParams })\n\nconst NotFound = ({ params, searchParams }: Args) =>\n\tNotFoundPage({ config, params, searchParams, importMap })\n\nexport default NotFound\n",
@@ -186,10 +190,10 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
186
190
  "marketing/payload/src/components/BlogCTA/index.tsx": "\"use client\"\n\nimport { CTATracker } from \"@/components/Analytics\"\nimport Link from \"next/link\"\n\ninterface BlogCTAProps {\n\theadline?: string\n\tsubheading?: string\n\tprimaryButtonText?: string\n\tprimaryButtonLink?: string\n\tsecondaryButtonText?: string\n\tsecondaryButtonLink?: string\n}\n\nexport function BlogCTA({\n\theadline = \"Ready to launch your directory?\",\n\tsubheading = \"Join hundreds of founders who chose the faster path to a profitable directory business.\",\n\tprimaryButtonText = \"Get started for free\",\n\tprimaryButtonLink = \"/sign-up\",\n\tsecondaryButtonText = \"Book a demo\",\n\tsecondaryButtonLink = \"/contact\",\n}: BlogCTAProps) {\n\treturn (\n\t\t<section className=\"relative overflow-hidden\">\n\t\t\t{/* Background */}\n\t\t\t<div\n\t\t\t\tclassName=\"absolute inset-0\"\n\t\t\t\tstyle={{\n\t\t\t\t\tbackground: \"linear-gradient(180deg, #0F1F3D 0%, #101F3C 50%, #0F1F3D 100%)\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{/* Decorative elements */}\n\t\t\t\t<div className=\"absolute inset-0 opacity-10\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"w-full h-full\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tbackgroundImage: `\n radial-gradient(circle at 20% 80%, rgba(255,255,255,0.1) 0%, transparent 50%),\n radial-gradient(circle at 80% 20%, rgba(255,255,255,0.1) 0%, transparent 50%)\n `,\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Content */}\n\t\t\t<div className=\"container relative z-10 py-16 md:py-20 lg:py-24\">\n\t\t\t\t<div className=\"max-w-3xl mx-auto text-center\">\n\t\t\t\t\t<h2 className=\"text-3xl md:text-4xl lg:text-5xl font-bold mb-6 leading-tight text-white\">\n\t\t\t\t\t\t{headline}\n\t\t\t\t\t</h2>\n\n\t\t\t\t\t<p className=\"text-lg md:text-xl mb-8 max-w-2xl mx-auto text-white/80\">{subheading}</p>\n\n\t\t\t\t\t<div className=\"flex flex-wrap justify-center gap-4\">\n\t\t\t\t\t\t<CTATracker location=\"blog_cta\" variant=\"primary\">\n\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\thref={primaryButtonLink}\n\t\t\t\t\t\t\t\tclassName=\"inline-flex items-center justify-center px-6 py-3 text-base font-medium bg-white text-[#0F1F3D] hover:bg-white/90 rounded-lg transition-colors\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{primaryButtonText}\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t</CTATracker>\n\t\t\t\t\t\t<CTATracker location=\"blog_cta\" variant=\"secondary\">\n\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\thref={secondaryButtonLink}\n\t\t\t\t\t\t\t\tclassName=\"inline-flex items-center justify-center px-6 py-3 text-base font-medium bg-white/10 border border-white text-white hover:bg-white/20 rounded-lg transition-colors\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{secondaryButtonText}\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t</CTATracker>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</section>\n\t)\n}\n",
187
191
  "marketing/payload/src/components/Card/index.tsx": "\"use client\"\nimport { cn } from \"@/utilities/ui\"\nimport useClickableCard from \"@/utilities/useClickableCard\"\nimport Link from \"next/link\"\nimport type React from \"react\"\nimport { Fragment } from \"react\"\n\nimport type { Post } from \"@/payload-types\"\n\nimport { Media } from \"@/components/Media\"\n\nexport type CardPostData = Pick<Post, \"slug\" | \"categories\" | \"meta\" | \"title\">\n\nexport const Card: React.FC<{\n\talignItems?: \"center\"\n\tclassName?: string\n\tdoc?: CardPostData\n\trelationTo?: \"posts\"\n\tshowCategories?: boolean\n\ttitle?: string\n}> = (props) => {\n\tconst { card, link } = useClickableCard({})\n\tconst { className, doc, relationTo, showCategories, title: titleFromProps } = props\n\n\tconst { slug, categories, meta, title } = doc || {}\n\tconst { description, image: metaImage } = meta || {}\n\n\tconst hasCategories = categories && Array.isArray(categories) && categories.length > 0\n\tconst titleToUse = titleFromProps || title\n\tconst sanitizedDescription = description?.replace(/\\s/g, \" \") // replace non-breaking space with white space\n\tconst href = `/${relationTo}/${slug}`\n\n\treturn (\n\t\t<article\n\t\t\tclassName={cn(\n\t\t\t\t\"border border-border rounded-lg overflow-hidden bg-card hover:cursor-pointer\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tref={card.ref}\n\t\t>\n\t\t\t<div className=\"relative w-full \">\n\t\t\t\t{!metaImage && <div className=\"\">No image</div>}\n\t\t\t\t{metaImage && typeof metaImage !== \"string\" && <Media resource={metaImage} size=\"33vw\" />}\n\t\t\t</div>\n\t\t\t<div className=\"p-4\">\n\t\t\t\t{showCategories && hasCategories && (\n\t\t\t\t\t<div className=\"uppercase text-sm mb-4\">\n\t\t\t\t\t\t{showCategories && hasCategories && (\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{categories?.map((category, index) => {\n\t\t\t\t\t\t\t\t\tif (typeof category === \"object\") {\n\t\t\t\t\t\t\t\t\t\tconst { title: titleFromCategory } = category\n\n\t\t\t\t\t\t\t\t\t\tconst categoryTitle = titleFromCategory || \"Untitled category\"\n\n\t\t\t\t\t\t\t\t\t\tconst isLast = index === categories.length - 1\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t<Fragment key={index}>\n\t\t\t\t\t\t\t\t\t\t\t\t{categoryTitle}\n\t\t\t\t\t\t\t\t\t\t\t\t{!isLast && <Fragment>, &nbsp;</Fragment>}\n\t\t\t\t\t\t\t\t\t\t\t</Fragment>\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t{titleToUse && (\n\t\t\t\t\t<div className=\"prose\">\n\t\t\t\t\t\t<h3>\n\t\t\t\t\t\t\t<Link className=\"not-prose\" href={href} ref={link.ref}>\n\t\t\t\t\t\t\t\t{titleToUse}\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t</h3>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t{description && <div className=\"mt-2\">{description && <p>{sanitizedDescription}</p>}</div>}\n\t\t\t</div>\n\t\t</article>\n\t)\n}\n",
188
192
  "marketing/payload/src/components/CollectionArchive/index.tsx": "import { cn } from \"@/utilities/ui\"\nimport type React from \"react\"\n\nimport { Card, type CardPostData } from \"@/components/Card\"\n\nexport type Props = {\n\tposts: CardPostData[]\n}\n\nexport const CollectionArchive: React.FC<Props> = (props) => {\n\tconst { posts } = props\n\n\treturn (\n\t\t<div className={cn(\"container\")}>\n\t\t\t<div>\n\t\t\t\t<div className=\"grid grid-cols-4 sm:grid-cols-8 lg:grid-cols-12 gap-y-4 gap-x-4 lg:gap-y-8 lg:gap-x-8 xl:gap-x-8\">\n\t\t\t\t\t{posts?.map((result, index) => {\n\t\t\t\t\t\tif (typeof result === \"object\" && result !== null) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<div className=\"col-span-4\" key={index}>\n\t\t\t\t\t\t\t\t\t<Card className=\"h-full\" doc={result} relationTo=\"posts\" showCategories />\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}\n\n\t\t\t\t\t\treturn null\n\t\t\t\t\t})}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n",
189
- "marketing/payload/src/components/JsonLd/index.tsx": "import { getServerSideURL } from \"@/utilities/getURL\"\nimport type React from \"react\"\n\nconst siteUrl = getServerSideURL()\n\n/**\n * Organization Schema - Company information\n */\nexport const OrganizationSchema: React.FC = () => {\n\tconst schema = {\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"Organization\",\n\t\tname: \"DirectoryHub\",\n\t\tdescription: \"The no-code platform to build, manage, and monetize niche directory websites.\",\n\t\turl: siteUrl,\n\t\tlogo: `${siteUrl}/logo.svg`,\n\t\tsameAs: [\n\t\t\t\"https://twitter.com/directoryhub\",\n\t\t\t\"https://linkedin.com/company/directoryhub\",\n\t\t\t\"https://github.com/directoryhub\",\n\t\t],\n\t\tcontactPoint: {\n\t\t\t\"@type\": \"ContactPoint\",\n\t\t\tcontactType: \"customer support\",\n\t\t\temail: \"support@directoryhub.com\",\n\t\t},\n\t\tfounder: {\n\t\t\t\"@type\": \"Person\",\n\t\t\tname: \"DirectoryHub Team\",\n\t\t},\n\t\tfoundingDate: \"2024\",\n\t}\n\n\treturn (\n\t\t<script\n\t\t\ttype=\"application/ld+json\"\n\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires dangerouslySetInnerHTML for proper SEO\n\t\t\tdangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n\t\t/>\n\t)\n}\n\n/**\n * Software Application Schema - Product details\n */\nexport const SoftwareApplicationSchema: React.FC = () => {\n\tconst schema = {\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"SoftwareApplication\",\n\t\tname: \"DirectoryHub\",\n\t\tdescription:\n\t\t\t\"Launch a profitable directory business in minutes. Create, monetize, and grow with DirectoryHub - the no-code directory builder.\",\n\t\tapplicationCategory: \"BusinessApplication\",\n\t\toperatingSystem: \"Web\",\n\t\turl: siteUrl,\n\t\toffers: {\n\t\t\t\"@type\": \"AggregateOffer\",\n\t\t\tpriceCurrency: \"USD\",\n\t\t\tlowPrice: \"0\",\n\t\t\thighPrice: \"199\",\n\t\t\tofferCount: \"3\",\n\t\t},\n\t\taggregateRating: {\n\t\t\t\"@type\": \"AggregateRating\",\n\t\t\tratingValue: \"4.9\",\n\t\t\tratingCount: \"127\",\n\t\t\tbestRating: \"5\",\n\t\t\tworstRating: \"1\",\n\t\t},\n\t\tfeatureList: [\n\t\t\t\"No-code directory builder\",\n\t\t\t\"Built-in payment processing\",\n\t\t\t\"SEO optimization\",\n\t\t\t\"Custom domains\",\n\t\t\t\"Multi-tenant architecture\",\n\t\t\t\"Analytics dashboard\",\n\t\t],\n\t\tscreenshot: `${siteUrl}/website-template-OG.webp`,\n\t}\n\n\treturn (\n\t\t<script\n\t\t\ttype=\"application/ld+json\"\n\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires dangerouslySetInnerHTML for proper SEO\n\t\t\tdangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n\t\t/>\n\t)\n}\n\n/**\n * WebSite Schema - Site search and navigation\n */\nexport const WebSiteSchema: React.FC = () => {\n\tconst schema = {\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"WebSite\",\n\t\tname: \"DirectoryHub\",\n\t\tdescription: \"The no-code platform to build, manage, and monetize niche directory websites.\",\n\t\turl: siteUrl,\n\t\tpotentialAction: {\n\t\t\t\"@type\": \"SearchAction\",\n\t\t\ttarget: {\n\t\t\t\t\"@type\": \"EntryPoint\",\n\t\t\t\turlTemplate: `${siteUrl}/search?q={search_term_string}`,\n\t\t\t},\n\t\t\t\"query-input\": \"required name=search_term_string\",\n\t\t},\n\t\tpublisher: {\n\t\t\t\"@type\": \"Organization\",\n\t\t\tname: \"DirectoryHub\",\n\t\t\tlogo: {\n\t\t\t\t\"@type\": \"ImageObject\",\n\t\t\t\turl: `${siteUrl}/logo.svg`,\n\t\t\t},\n\t\t},\n\t}\n\n\treturn (\n\t\t<script\n\t\t\ttype=\"application/ld+json\"\n\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires dangerouslySetInnerHTML for proper SEO\n\t\t\tdangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n\t\t/>\n\t)\n}\n\n/**\n * Combined JSON-LD component for all schemas\n */\nexport const JsonLdSchemas: React.FC = () => {\n\treturn (\n\t\t<>\n\t\t\t<OrganizationSchema />\n\t\t\t<SoftwareApplicationSchema />\n\t\t\t<WebSiteSchema />\n\t\t</>\n\t)\n}\n",
193
+ "marketing/payload/src/components/JsonLd/index.tsx": "import { getServerSideURL } from \"@/utilities/getURL\"\nimport type React from \"react\"\n\nconst siteUrl = getServerSideURL()\n\n/**\n * Organization Schema - Company information\n */\nexport const OrganizationSchema: React.FC = () => {\n\tconst schema = {\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"Organization\",\n\t\tname: \"SaaSify\",\n\t\tdescription: \"The modern platform for growing teams. Streamline workflows and boost productivity.\",\n\t\turl: siteUrl,\n\t\tlogo: `${siteUrl}/logo.svg`,\n\t\tsameAs: [\n\t\t\t\"https://twitter.com/saasify\",\n\t\t\t\"https://linkedin.com/company/saasify\",\n\t\t\t\"https://github.com/saasify\",\n\t\t],\n\t\tcontactPoint: {\n\t\t\t\"@type\": \"ContactPoint\",\n\t\t\tcontactType: \"customer support\",\n\t\t\temail: \"support@saasify.com\",\n\t\t},\n\t\tfounder: {\n\t\t\t\"@type\": \"Person\",\n\t\t\tname: \"SaaSify Team\",\n\t\t},\n\t\tfoundingDate: \"2024\",\n\t}\n\n\treturn (\n\t\t<script\n\t\t\ttype=\"application/ld+json\"\n\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires dangerouslySetInnerHTML for proper SEO\n\t\t\tdangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n\t\t/>\n\t)\n}\n\n/**\n * Software Application Schema - Product details\n */\nexport const SoftwareApplicationSchema: React.FC = () => {\n\tconst schema = {\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"SoftwareApplication\",\n\t\tname: \"SaaSify\",\n\t\tdescription:\n\t\t\t\"The modern platform for growing teams. Streamline workflows, boost productivity, and scale your business.\",\n\t\tapplicationCategory: \"BusinessApplication\",\n\t\toperatingSystem: \"Web\",\n\t\turl: siteUrl,\n\t\toffers: {\n\t\t\t\"@type\": \"AggregateOffer\",\n\t\t\tpriceCurrency: \"USD\",\n\t\t\tlowPrice: \"0\",\n\t\t\thighPrice: \"99\",\n\t\t\tofferCount: \"3\",\n\t\t},\n\t\taggregateRating: {\n\t\t\t\"@type\": \"AggregateRating\",\n\t\t\tratingValue: \"4.9\",\n\t\t\tratingCount: \"127\",\n\t\t\tbestRating: \"5\",\n\t\t\tworstRating: \"1\",\n\t\t},\n\t\tfeatureList: [\n\t\t\t\"Team collaboration tools\",\n\t\t\t\"Workflow automation\",\n\t\t\t\"Real-time analytics\",\n\t\t\t\"100+ integrations\",\n\t\t\t\"Enterprise security\",\n\t\t\t\"Custom dashboards\",\n\t\t],\n\t\tscreenshot: `${siteUrl}/website-template-OG.webp`,\n\t}\n\n\treturn (\n\t\t<script\n\t\t\ttype=\"application/ld+json\"\n\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires dangerouslySetInnerHTML for proper SEO\n\t\t\tdangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n\t\t/>\n\t)\n}\n\n/**\n * WebSite Schema - Site search and navigation\n */\nexport const WebSiteSchema: React.FC = () => {\n\tconst schema = {\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"WebSite\",\n\t\tname: \"SaaSify\",\n\t\tdescription: \"The modern platform for growing teams. Streamline workflows and boost productivity.\",\n\t\turl: siteUrl,\n\t\tpotentialAction: {\n\t\t\t\"@type\": \"SearchAction\",\n\t\t\ttarget: {\n\t\t\t\t\"@type\": \"EntryPoint\",\n\t\t\t\turlTemplate: `${siteUrl}/search?q={search_term_string}`,\n\t\t\t},\n\t\t\t\"query-input\": \"required name=search_term_string\",\n\t\t},\n\t\tpublisher: {\n\t\t\t\"@type\": \"Organization\",\n\t\t\tname: \"SaaSify\",\n\t\t\tlogo: {\n\t\t\t\t\"@type\": \"ImageObject\",\n\t\t\t\turl: `${siteUrl}/logo.svg`,\n\t\t\t},\n\t\t},\n\t}\n\n\treturn (\n\t\t<script\n\t\t\ttype=\"application/ld+json\"\n\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires dangerouslySetInnerHTML for proper SEO\n\t\t\tdangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n\t\t/>\n\t)\n}\n\n/**\n * Combined JSON-LD component for all schemas\n */\nexport const JsonLdSchemas: React.FC = () => {\n\treturn (\n\t\t<>\n\t\t\t<OrganizationSchema />\n\t\t\t<SoftwareApplicationSchema />\n\t\t\t<WebSiteSchema />\n\t\t</>\n\t)\n}\n",
190
194
  "marketing/payload/src/components/Link/index.tsx": "import { Button, type ButtonProps } from \"@/components/ui/button\"\nimport { cn } from \"@/utilities/ui\"\nimport Link from \"next/link\"\nimport type React from \"react\"\n\nimport type { Page, Post } from \"@/payload-types\"\n\ntype CMSLinkType = {\n\tappearance?: \"inline\" | ButtonProps[\"variant\"]\n\tchildren?: React.ReactNode\n\tclassName?: string\n\tlabel?: string | null\n\tnewTab?: boolean | null\n\treference?: {\n\t\trelationTo: \"pages\" | \"posts\"\n\t\tvalue: Page | Post | string | number\n\t} | null\n\tsize?: ButtonProps[\"size\"] | null\n\ttype?: \"custom\" | \"reference\" | null\n\turl?: string | null\n}\n\nexport const CMSLink: React.FC<CMSLinkType> = (props) => {\n\tconst {\n\t\ttype,\n\t\tappearance = \"inline\",\n\t\tchildren,\n\t\tclassName,\n\t\tlabel,\n\t\tnewTab,\n\t\treference,\n\t\tsize: sizeFromProps,\n\t\turl,\n\t} = props\n\n\tconst href =\n\t\ttype === \"reference\" && typeof reference?.value === \"object\" && reference.value.slug\n\t\t\t? `${reference?.relationTo !== \"pages\" ? `/${reference?.relationTo}` : \"\"}/${\n\t\t\t\t\treference.value.slug\n\t\t\t\t}`\n\t\t\t: url\n\n\tif (!href) return null\n\n\tconst size = appearance === \"link\" ? \"clear\" : sizeFromProps\n\tconst newTabProps = newTab ? { rel: \"noopener noreferrer\", target: \"_blank\" } : {}\n\n\t/* Ensure we don't break any styles set by richText */\n\tif (appearance === \"inline\") {\n\t\treturn (\n\t\t\t<Link className={cn(className)} href={href || url || \"\"} {...newTabProps}>\n\t\t\t\t{label && label}\n\t\t\t\t{children && children}\n\t\t\t</Link>\n\t\t)\n\t}\n\n\treturn (\n\t\t<Button asChild className={className} size={size} variant={appearance}>\n\t\t\t<Link className={cn(className)} href={href || url || \"\"} {...newTabProps}>\n\t\t\t\t{label && label}\n\t\t\t\t{children && children}\n\t\t\t</Link>\n\t\t</Button>\n\t)\n}\n",
191
195
  "marketing/payload/src/components/LivePreviewListener/index.tsx": "\"use client\"\nimport { getClientSideURL } from \"@/utilities/getURL\"\nimport { RefreshRouteOnSave as PayloadLivePreview } from \"@payloadcms/live-preview-react\"\nimport { useRouter } from \"next/navigation\"\nimport type React from \"react\"\n\nexport const LivePreviewListener: React.FC = () => {\n\tconst router = useRouter()\n\treturn <PayloadLivePreview refresh={router.refresh} serverURL={getClientSideURL()} />\n}\n",
192
- "marketing/payload/src/components/Logo/Logo.tsx": "\"use client\"\n\nimport { useTheme } from \"@/providers/Theme\"\nimport clsx from \"clsx\"\n\ninterface Props {\n\tclassName?: string\n\tloading?: \"lazy\" | \"eager\"\n\tpriority?: \"auto\" | \"high\" | \"low\"\n\tvariant?: \"default\" | \"light\" | \"auto\"\n}\n\nexport const Logo = (props: Props) => {\n\tconst {\n\t\tloading: loadingFromProps,\n\t\tpriority: priorityFromProps,\n\t\tclassName,\n\t\tvariant = \"auto\",\n\t} = props\n\n\tconst { theme } = useTheme()\n\tconst loading = loadingFromProps || \"lazy\"\n\tconst priority = priorityFromProps || \"low\"\n\n\t// Determine which logo to show\n\tlet src = \"/logo.svg\"\n\tif (variant === \"light\") {\n\t\tsrc = \"/logo-light.svg\"\n\t} else if (variant === \"auto\" && theme === \"dark\") {\n\t\tsrc = \"/logo-light.svg\"\n\t}\n\n\treturn (\n\t\t/* eslint-disable @next/next/no-img-element */\n\t\t<img\n\t\t\talt=\"DirectoryHub Logo\"\n\t\t\twidth={34}\n\t\t\theight={34}\n\t\t\tloading={loading}\n\t\t\tfetchPriority={priority}\n\t\t\tdecoding=\"async\"\n\t\t\tclassName={clsx(\"w-[34px] h-[34px]\", className)}\n\t\t\tsrc={src}\n\t\t/>\n\t)\n}\n",
196
+ "marketing/payload/src/components/Logo/Logo.tsx": "\"use client\"\n\nimport { useTheme } from \"@/providers/Theme\"\nimport clsx from \"clsx\"\n\ninterface Props {\n\tclassName?: string\n\tloading?: \"lazy\" | \"eager\"\n\tpriority?: \"auto\" | \"high\" | \"low\"\n\tvariant?: \"default\" | \"light\" | \"auto\"\n}\n\nexport const Logo = (props: Props) => {\n\tconst {\n\t\tloading: loadingFromProps,\n\t\tpriority: priorityFromProps,\n\t\tclassName,\n\t\tvariant = \"auto\",\n\t} = props\n\n\tconst { theme } = useTheme()\n\tconst loading = loadingFromProps || \"lazy\"\n\tconst priority = priorityFromProps || \"low\"\n\n\t// Determine which logo to show\n\tlet src = \"/logo.svg\"\n\tif (variant === \"light\") {\n\t\tsrc = \"/logo-light.svg\"\n\t} else if (variant === \"auto\" && theme === \"dark\") {\n\t\tsrc = \"/logo-light.svg\"\n\t}\n\n\treturn (\n\t\t/* eslint-disable @next/next/no-img-element */\n\t\t<img\n\t\t\talt=\"SaaSify Logo\"\n\t\t\twidth={34}\n\t\t\theight={34}\n\t\t\tloading={loading}\n\t\t\tfetchPriority={priority}\n\t\t\tdecoding=\"async\"\n\t\t\tclassName={clsx(\"w-[34px] h-[34px]\", className)}\n\t\t\tsrc={src}\n\t\t/>\n\t)\n}\n",
193
197
  "marketing/payload/src/components/Media/ImageMedia/index.tsx": "\"use client\"\n\nimport type { StaticImageData } from \"next/image\"\n\nimport { cn } from \"@/utilities/ui\"\nimport NextImage from \"next/image\"\nimport type React from \"react\"\n\nimport type { Props as MediaProps } from \"../types\"\n\nimport { cssVariables } from \"@/cssVariables\"\nimport { getMediaUrl } from \"@/utilities/getMediaUrl\"\n\nconst { breakpoints } = cssVariables\n\n// A base64 encoded image to use as a placeholder while the image is loading\nconst placeholderBlur =\n\t\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABchJREFUWEdtlwtTG0kMhHtGM7N+AAdcDsjj///EBLzenbtuadbLJaZUTlHB+tRqSesETB3IABqQG1KbUFqDlQorBSmboqeEBcC1d8zrCixXYGZcgMsFmH8B+AngHdurAmXKOE8nHOoBrU6opcGswPi5KSP9CcBaQ9kACJH/ALAA1xm4zMD8AczvQCcAQeJVAZsy7nYApTSUzwCHUKACeUJi9TsFci7AHmDtuHYqQIC9AgQYKnSwNAig4NyOOwXq/xU47gDYggarjIpsRSEA3Fqw7AGkwgW4fgALAdiC2btKgNZwbgdMbEFpqFR2UyCR8xwAhf8bUHIGk1ckMyB5C1YkeWAdAPQBAeiD6wVYPoD1HUgXwFagZAGc6oSpTmilopoD5GzISQD3odcNIFca0BUQQM5YA2DpHV0AYURBDIAL0C+ugC0C4GedSsVUmwC8/4w8TPiwU6AClJ5RWL1PgQNkrABWdKB3YF3cBwRY5lsI4ApkKpCQi+FIgFJU/TDgDuAxAAwonJuKpGD1rkCXCR1ALyrAUSSEQAhwBdYZ6DPAgSUA2c1wKIZmRcHxMzMYR9DH8NlbkAwwApSAcABwBwTAbb6owAr0AFiZPILVEyCtMmK2jCkTwFDNUNj7nJETQx744gCUmgkZVGJUHyakEZE4W91jtGFA9KsD8Z3JFYDlhGYZLWcllwJMnplcPy+csFAgAAaIDOgeuAGoB96GLZg4kmtfMjnr6ig5oSoySsoy3ya/FMivXZWxwr0KIf9nACbfqcBEgmBSAtAlIT83R+70IWpyACamIjf5E1Iqb9ECVmnoI/FvAIRk8s2J0Y5IquQDgB+5wpScw5AUTC75VTmTs+72NUzoCvQIaAXv5Q8PDAZKLD+MxLv3RFE7KlsQChgBIlKiCv5ByaZv3gJZNm8AnVMhAN+EjrtTYQMICJpu6/0aiQnhClANlz+Bw0cIWa8ev0sBrtrhAyaXEnrfGfATQJiRKih5vKeOHNXXPFrgyamAADh0Q4F2/sESojomDS9o9k0b0H83xjB8qL+JNoTjN+enjpaBpingRh4e8MSugudM030A8FeqMI6PFIgNyPehkpZWGFEAARIQdH5LcAAqIACHkAJqg4OoBccHAuz76wr4BbzFOEa8iBuAZB8AtJHLP2VgMgJw/EIBowo7HxCAH3V6dAXEE/vZ5aZIA8BP8RKhm7Cp8BnAMnAQADdgQDA520AVIpScP+enHz0Gwp25h4i2dPg5FkDXrbsdJikQwXuWgaM5gEMk1AgH4DKKFjDf3bMD+FjEeIxLlRKYnBk2BbquvSDCAQ4gwZiMAAmH4gBTyRtEsYxi7gP6QSrc//39BrDNqG8rtYTmC4BV1SfMhOhaumFCT87zy4pPhQBZEK1kQVRjJBBi7AOlePgyAPYjwlvtagx9e/dnQraAyS894TIkkAIEYMKEc8k4EqJ68lZ5jjNqcQC2QteQOf7659umwBgPybNtK4dg9WvnMyFwXYGP7uEO1lwJgAnPNeMYMVXbIIYKFioI4PGFt+BWPVfmWJdjW2lTUnLGCswECAgaUy86iwA1464ajo0QhgMBFGyBoZahANsMpMfXr1JA1SN29m5lqgXj+UPV85uRA7yv/KYUO4Tk7Hc1AZwbIRzg0AyNj2UlAMwfSLSMnl7fdAbcxHuA27YaAMvaQ4GOjwX4RTUGAG8Ge14N963g1AynqUiFqRX9noasxT4b8entNRQYyamk/3tYcHsO7R3XJRRYOn4tw4iUnwBM5gDnySGOreAwAGo8F9IDHEcq8Pz2Kg/oXCpuIL6tOPD8LsDn0ABYQoGFRowlsAEUPPDrGAGowAbgKsgDMmE8mDy/vXQ9IAwI7u4wta+gAdAdgB64Ah9SgD4IgGKhwACoAjgNgFDhtxY8f33ZTMjqdTAiHMBPrn8ZWkEfzFdX4Oc1AHg3+ADbvN8PU8WdFKg4Tt6CQy2+D4YHaMT/JP4XzbAq98cPDIUAAAAASUVORK5CYII=\"\n\nexport const ImageMedia: React.FC<MediaProps> = (props) => {\n\tconst {\n\t\talt: altFromProps,\n\t\tfill,\n\t\tpictureClassName,\n\t\timgClassName,\n\t\tpriority,\n\t\tresource,\n\t\tsize: sizeFromProps,\n\t\tsrc: srcFromProps,\n\t\tloading: loadingFromProps,\n\t} = props\n\n\tlet width: number | undefined\n\tlet height: number | undefined\n\tlet alt = altFromProps\n\tlet src: StaticImageData | string = srcFromProps || \"\"\n\n\tif (!src && resource && typeof resource === \"object\") {\n\t\tconst { alt: altFromResource, height: fullHeight, url, width: fullWidth } = resource\n\n\t\twidth = fullWidth!\n\t\theight = fullHeight!\n\t\talt = altFromResource || \"\"\n\n\t\tconst cacheTag = resource.updatedAt\n\n\t\tsrc = getMediaUrl(url, cacheTag)\n\t}\n\n\tconst loading = loadingFromProps || (!priority ? \"lazy\" : undefined)\n\n\t// NOTE: this is used by the browser to determine which image to download at different screen sizes\n\t// The sizes attribute should specify display width in CSS pixels or viewport units\n\tconst sizes = sizeFromProps\n\t\t? sizeFromProps\n\t\t: Object.entries(breakpoints)\n\t\t\t\t.map(([, value]) => `(max-width: ${value}px) ${value}px`)\n\t\t\t\t.concat([\"100vw\"])\n\t\t\t\t.join(\", \")\n\n\treturn (\n\t\t<picture className={cn(pictureClassName)}>\n\t\t\t<NextImage\n\t\t\t\talt={alt || \"\"}\n\t\t\t\tclassName={cn(imgClassName)}\n\t\t\t\tfill={fill}\n\t\t\t\theight={!fill ? height : undefined}\n\t\t\t\tplaceholder=\"blur\"\n\t\t\t\tblurDataURL={placeholderBlur}\n\t\t\t\tpriority={priority}\n\t\t\t\tfetchPriority={priority ? \"high\" : \"auto\"}\n\t\t\t\tquality={fill ? 65 : 75}\n\t\t\t\tloading={loading}\n\t\t\t\tsizes={sizes}\n\t\t\t\tsrc={src}\n\t\t\t\twidth={!fill ? width : undefined}\n\t\t\t/>\n\t\t</picture>\n\t)\n}\n",
194
198
  "marketing/payload/src/components/Media/VideoMedia/index.tsx": "\"use client\"\n\nimport { cn } from \"@/utilities/ui\"\nimport type React from \"react\"\nimport { useEffect, useRef } from \"react\"\n\nimport type { Props as MediaProps } from \"../types\"\n\nimport { getMediaUrl } from \"@/utilities/getMediaUrl\"\n\nexport const VideoMedia: React.FC<MediaProps> = (props) => {\n\tconst { onClick, resource, videoClassName } = props\n\n\tconst videoRef = useRef<HTMLVideoElement>(null)\n\t// const [showFallback] = useState<boolean>()\n\n\tuseEffect(() => {\n\t\tconst { current: video } = videoRef\n\t\tif (video) {\n\t\t\tvideo.addEventListener(\"suspend\", () => {\n\t\t\t\t// setShowFallback(true);\n\t\t\t\t// console.warn('Video was suspended, rendering fallback image.')\n\t\t\t})\n\t\t}\n\t}, [])\n\n\tif (resource && typeof resource === \"object\") {\n\t\tconst { filename } = resource\n\n\t\treturn (\n\t\t\t<video\n\t\t\t\tautoPlay\n\t\t\t\tclassName={cn(videoClassName)}\n\t\t\t\tcontrols={false}\n\t\t\t\tloop\n\t\t\t\tmuted\n\t\t\t\tonClick={onClick}\n\t\t\t\tplaysInline\n\t\t\t\tref={videoRef}\n\t\t\t>\n\t\t\t\t<source src={getMediaUrl(`/media/${filename}`)} />\n\t\t\t</video>\n\t\t)\n\t}\n\n\treturn null\n}\n",
195
199
  "marketing/payload/src/components/Media/index.tsx": "import type React from \"react\"\nimport { Fragment } from \"react\"\n\nimport type { Props } from \"./types\"\n\nimport { ImageMedia } from \"./ImageMedia\"\nimport { VideoMedia } from \"./VideoMedia\"\n\nexport const Media: React.FC<Props> = (props) => {\n\tconst { className, htmlElement = \"div\", resource } = props\n\n\tconst isVideo = typeof resource === \"object\" && resource?.mimeType?.includes(\"video\")\n\tconst Tag = htmlElement || Fragment\n\n\treturn (\n\t\t<Tag\n\t\t\t{...(htmlElement !== null\n\t\t\t\t? {\n\t\t\t\t\t\tclassName,\n\t\t\t\t\t}\n\t\t\t\t: {})}\n\t\t>\n\t\t\t{isVideo ? <VideoMedia {...props} /> : <ImageMedia {...props} />}\n\t\t</Tag>\n\t)\n}\n",
@@ -198,7 +202,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
198
202
  "marketing/payload/src/components/Pagination/index.tsx": "\"use client\"\nimport {\n\tPagination as PaginationComponent,\n\tPaginationContent,\n\tPaginationEllipsis,\n\tPaginationItem,\n\tPaginationLink,\n\tPaginationNext,\n\tPaginationPrevious,\n} from \"@/components/ui/pagination\"\nimport { cn } from \"@/utilities/ui\"\nimport { useRouter } from \"next/navigation\"\nimport type React from \"react\"\n\nexport const Pagination: React.FC<{\n\tclassName?: string\n\tpage: number\n\ttotalPages: number\n}> = (props) => {\n\tconst router = useRouter()\n\n\tconst { className, page, totalPages } = props\n\tconst hasNextPage = page < totalPages\n\tconst hasPrevPage = page > 1\n\n\tconst hasExtraPrevPages = page - 1 > 1\n\tconst hasExtraNextPages = page + 1 < totalPages\n\n\treturn (\n\t\t<div className={cn(\"my-12\", className)}>\n\t\t\t<PaginationComponent>\n\t\t\t\t<PaginationContent>\n\t\t\t\t\t<PaginationItem>\n\t\t\t\t\t\t<PaginationPrevious\n\t\t\t\t\t\t\tdisabled={!hasPrevPage}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\trouter.push(`/posts/page/${page - 1}`)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</PaginationItem>\n\n\t\t\t\t\t{hasExtraPrevPages && (\n\t\t\t\t\t\t<PaginationItem>\n\t\t\t\t\t\t\t<PaginationEllipsis />\n\t\t\t\t\t\t</PaginationItem>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{hasPrevPage && (\n\t\t\t\t\t\t<PaginationItem>\n\t\t\t\t\t\t\t<PaginationLink\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\trouter.push(`/posts/page/${page - 1}`)\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{page - 1}\n\t\t\t\t\t\t\t</PaginationLink>\n\t\t\t\t\t\t</PaginationItem>\n\t\t\t\t\t)}\n\n\t\t\t\t\t<PaginationItem>\n\t\t\t\t\t\t<PaginationLink\n\t\t\t\t\t\t\tisActive\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\trouter.push(`/posts/page/${page}`)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{page}\n\t\t\t\t\t\t</PaginationLink>\n\t\t\t\t\t</PaginationItem>\n\n\t\t\t\t\t{hasNextPage && (\n\t\t\t\t\t\t<PaginationItem>\n\t\t\t\t\t\t\t<PaginationLink\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\trouter.push(`/posts/page/${page + 1}`)\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{page + 1}\n\t\t\t\t\t\t\t</PaginationLink>\n\t\t\t\t\t\t</PaginationItem>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{hasExtraNextPages && (\n\t\t\t\t\t\t<PaginationItem>\n\t\t\t\t\t\t\t<PaginationEllipsis />\n\t\t\t\t\t\t</PaginationItem>\n\t\t\t\t\t)}\n\n\t\t\t\t\t<PaginationItem>\n\t\t\t\t\t\t<PaginationNext\n\t\t\t\t\t\t\tdisabled={!hasNextPage}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\trouter.push(`/posts/page/${page + 1}`)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</PaginationItem>\n\t\t\t\t</PaginationContent>\n\t\t\t</PaginationComponent>\n\t\t</div>\n\t)\n}\n",
199
203
  "marketing/payload/src/components/PayloadRedirects/index.tsx": "import type { Page, Post } from \"@/payload-types\"\nimport type React from \"react\"\n\nimport { getCachedDocument } from \"@/utilities/getDocument\"\nimport { getCachedRedirects } from \"@/utilities/getRedirects\"\nimport { notFound, redirect } from \"next/navigation\"\n\ninterface Props {\n\tdisableNotFound?: boolean\n\turl: string\n}\n\n/* This component helps us with SSR based dynamic redirects */\nexport const PayloadRedirects: React.FC<Props> = async ({ disableNotFound, url }) => {\n\tconst redirects = await getCachedRedirects()()\n\n\tconst redirectItem = redirects.find((redirect) => redirect.from === url)\n\n\tif (redirectItem) {\n\t\tif (redirectItem.to?.url) {\n\t\t\tredirect(redirectItem.to.url)\n\t\t}\n\n\t\tlet redirectUrl: string\n\n\t\tif (typeof redirectItem.to?.reference?.value === \"string\") {\n\t\t\tconst collection = redirectItem.to?.reference?.relationTo\n\t\t\tconst id = redirectItem.to?.reference?.value\n\n\t\t\tconst document = (await getCachedDocument(collection, id)()) as Page | Post\n\t\t\tredirectUrl = `${redirectItem.to?.reference?.relationTo !== \"pages\" ? `/${redirectItem.to?.reference?.relationTo}` : \"\"}/${\n\t\t\t\tdocument?.slug\n\t\t\t}`\n\t\t} else {\n\t\t\tredirectUrl = `${redirectItem.to?.reference?.relationTo !== \"pages\" ? `/${redirectItem.to?.reference?.relationTo}` : \"\"}/${\n\t\t\t\ttypeof redirectItem.to?.reference?.value === \"object\"\n\t\t\t\t\t? redirectItem.to?.reference?.value?.slug\n\t\t\t\t\t: \"\"\n\t\t\t}`\n\t\t}\n\n\t\tif (redirectUrl) redirect(redirectUrl)\n\t}\n\n\tif (disableNotFound) return null\n\n\tnotFound()\n}\n",
200
204
  "marketing/payload/src/components/RichText/index.tsx": "import { MediaBlock } from \"@/blocks/MediaBlock/Component\"\nimport type {\n\tDefaultNodeTypes,\n\tDefaultTypedEditorState,\n\tSerializedBlockNode,\n\tSerializedLinkNode,\n\tSerializedRelationshipNode,\n} from \"@payloadcms/richtext-lexical\"\nimport {\n\tRichText as ConvertRichText,\n\ttype JSXConvertersFunction,\n\tLinkJSXConverter,\n} from \"@payloadcms/richtext-lexical/react\"\n\nimport { CodeBlock, type CodeBlockProps } from \"@/blocks/Code/Component\"\n\nimport { BannerBlock } from \"@/blocks/Banner/Component\"\nimport { CallToActionBlock } from \"@/blocks/CallToAction/Component\"\nimport type {\n\tBannerBlock as BannerBlockProps,\n\tCallToActionBlock as CTABlockProps,\n\tMediaBlock as MediaBlockProps,\n\tPage,\n\tPost,\n} from \"@/payload-types\"\nimport { cn } from \"@/utilities/ui\"\nimport Link from \"next/link\"\n\ntype NodeTypes =\n\t| DefaultNodeTypes\n\t| SerializedBlockNode<CTABlockProps | MediaBlockProps | BannerBlockProps | CodeBlockProps>\n\nconst internalDocToHref = ({ linkNode }: { linkNode: SerializedLinkNode }) => {\n\tconst { value, relationTo } = linkNode.fields.doc!\n\tif (typeof value !== \"object\") {\n\t\tthrow new Error(\"Expected value to be an object\")\n\t}\n\tconst slug = value.slug\n\treturn relationTo === \"posts\" ? `/posts/${slug}` : `/${slug}`\n}\n\n/**\n * Renders relationship nodes embedded in rich text content.\n * These are created when using the RelationshipFeature in Lexical editor.\n */\nconst RelationshipComponent = ({ node }: { node: SerializedRelationshipNode }) => {\n\tconst { relationTo, value } = node\n\n\t// If value is not populated (just an ID), we can't render it\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn null\n\t}\n\n\t// Handle different collection types\n\tswitch (relationTo) {\n\t\tcase \"posts\": {\n\t\t\tconst post = value as Post\n\t\t\treturn (\n\t\t\t\t<Link\n\t\t\t\t\thref={`/posts/${post.slug}`}\n\t\t\t\t\tclassName=\"not-prose my-4 block rounded-lg border border-border bg-card p-4 transition-colors hover:bg-accent\"\n\t\t\t\t>\n\t\t\t\t\t<span className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground\">\n\t\t\t\t\t\tRelated Post\n\t\t\t\t\t</span>\n\t\t\t\t\t<h4 className=\"mt-1 font-semibold text-foreground\">{post.title}</h4>\n\t\t\t\t\t{post.meta?.description && (\n\t\t\t\t\t\t<p className=\"mt-1 text-sm text-muted-foreground line-clamp-2\">\n\t\t\t\t\t\t\t{post.meta.description}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t)}\n\t\t\t\t</Link>\n\t\t\t)\n\t\t}\n\t\tcase \"pages\": {\n\t\t\tconst page = value as Page\n\t\t\treturn (\n\t\t\t\t<Link\n\t\t\t\t\thref={`/${page.slug}`}\n\t\t\t\t\tclassName=\"not-prose my-4 block rounded-lg border border-border bg-card p-4 transition-colors hover:bg-accent\"\n\t\t\t\t>\n\t\t\t\t\t<span className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground\">\n\t\t\t\t\t\tRelated Page\n\t\t\t\t\t</span>\n\t\t\t\t\t<h4 className=\"mt-1 font-semibold text-foreground\">{page.title}</h4>\n\t\t\t\t</Link>\n\t\t\t)\n\t\t}\n\t\tdefault: {\n\t\t\t// Fallback for other collection types - render basic info if available\n\t\t\tconst doc = value as { title?: string; name?: string; slug?: string }\n\t\t\tconst title = doc.title || doc.name || \"Related Content\"\n\t\t\treturn (\n\t\t\t\t<div className=\"not-prose my-4 rounded-lg border border-border bg-card p-4\">\n\t\t\t\t\t<span className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground\">\n\t\t\t\t\t\t{relationTo}\n\t\t\t\t\t</span>\n\t\t\t\t\t<h4 className=\"mt-1 font-semibold text-foreground\">{title}</h4>\n\t\t\t\t</div>\n\t\t\t)\n\t\t}\n\t}\n}\n\nconst jsxConverters: JSXConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({\n\t...defaultConverters,\n\t...LinkJSXConverter({ internalDocToHref }),\n\t// Add relationship node converter - this is missing from defaultConverters\n\trelationship: ({ node }) => <RelationshipComponent node={node} />,\n\tblocks: {\n\t\tbanner: ({ node }) => <BannerBlock className=\"col-start-2 mb-4\" {...node.fields} />,\n\t\tmediaBlock: ({ node }) => (\n\t\t\t<MediaBlock\n\t\t\t\tclassName=\"col-start-1 col-span-3\"\n\t\t\t\timgClassName=\"m-0\"\n\t\t\t\t{...node.fields}\n\t\t\t\tcaptionClassName=\"mx-auto max-w-[48rem]\"\n\t\t\t\tenableGutter={false}\n\t\t\t\tdisableInnerContainer={true}\n\t\t\t/>\n\t\t),\n\t\tcode: ({ node }) => <CodeBlock className=\"col-start-2\" {...node.fields} />,\n\t\tcta: ({ node }) => <CallToActionBlock {...node.fields} />,\n\t},\n})\n\ntype Props = {\n\tdata: DefaultTypedEditorState\n\tenableGutter?: boolean\n\tenableProse?: boolean\n} & React.HTMLAttributes<HTMLDivElement>\n\nexport default function RichText(props: Props) {\n\tconst { className, enableProse = true, enableGutter = true, ...rest } = props\n\treturn (\n\t\t<ConvertRichText\n\t\t\tconverters={jsxConverters}\n\t\t\tclassName={cn(\n\t\t\t\t\"payload-richtext\",\n\t\t\t\t{\n\t\t\t\t\tcontainer: enableGutter,\n\t\t\t\t\t\"prose prose-lg dark:prose-invert prose-headings:font-bold prose-headings:tracking-tight prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-blockquote:border-l-primary prose-blockquote:not-italic prose-li:marker:text-muted-foreground\":\n\t\t\t\t\t\tenableProse,\n\t\t\t\t},\n\t\t\t\t// max-w-none should come after prose to properly override\n\t\t\t\t!enableGutter && \"max-w-none\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...rest}\n\t\t/>\n\t)\n}\n",
201
- "marketing/payload/src/components/TableOfContents/index.tsx": "\"use client\"\n\nimport type { HeadingItem } from \"@/utilities/extractHeadings\"\nimport { cn } from \"@/utilities/ui\"\nimport Image from \"next/image\"\nimport Link from \"next/link\"\nimport { useEffect, useState } from \"react\"\n\ninterface TableOfContentsProps {\n\theadings: HeadingItem[]\n\tsignUpCta?: {\n\t\ttitle?: string\n\t\tdescription?: string\n\t\tbuttonText?: string\n\t\tbuttonLink?: string\n\t\timageSrc?: string\n\t\timageAlt?: string\n\t}\n}\n\nexport function TableOfContents({ headings, signUpCta }: TableOfContentsProps) {\n\tconst [activeId, setActiveId] = useState<string>(\"\")\n\n\tuseEffect(() => {\n\t\tconst observer = new IntersectionObserver(\n\t\t\t(entries) => {\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tif (entry.isIntersecting) {\n\t\t\t\t\t\tsetActiveId(entry.target.id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\trootMargin: \"-80px 0px -80% 0px\",\n\t\t\t\tthreshold: 0,\n\t\t\t},\n\t\t)\n\n\t\t// Observe all heading elements\n\t\tfor (const heading of headings) {\n\t\t\tconst element = document.getElementById(heading.id)\n\t\t\tif (element) {\n\t\t\t\tobserver.observe(element)\n\t\t\t}\n\t\t}\n\n\t\treturn () => observer.disconnect()\n\t}, [headings])\n\n\tconst handleClick = (e: React.MouseEvent<HTMLAnchorElement>, id: string) => {\n\t\te.preventDefault()\n\t\tconst element = document.getElementById(id)\n\t\tif (element) {\n\t\t\tconst yOffset = -100\n\t\t\tconst y = element.getBoundingClientRect().top + window.pageYOffset + yOffset\n\t\t\twindow.scrollTo({ top: y, behavior: \"smooth\" })\n\t\t\tsetActiveId(id)\n\t\t}\n\t}\n\n\tif (headings.length === 0) {\n\t\treturn null\n\t}\n\n\treturn (\n\t\t<nav className=\"flex flex-col gap-6\">\n\t\t\t{/* TOC Header */}\n\t\t\t<div>\n\t\t\t\t<h4 className=\"text-sm font-semibold text-foreground mb-4\">On this page</h4>\n\t\t\t\t<ul className=\"space-y-2\">\n\t\t\t\t\t{headings.map((heading) => (\n\t\t\t\t\t\t<li key={heading.id} style={{ paddingLeft: `${(heading.depth - 2) * 12}px` }}>\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={`#${heading.id}`}\n\t\t\t\t\t\t\t\tonClick={(e) => handleClick(e, heading.id)}\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"block text-sm leading-relaxed transition-colors duration-200\",\n\t\t\t\t\t\t\t\t\t\"hover:text-foreground\",\n\t\t\t\t\t\t\t\t\tactiveId === heading.id ? \"text-[#3DA9A3] font-medium\" : \"text-muted-foreground\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{heading.text}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t))}\n\t\t\t\t</ul>\n\t\t\t</div>\n\n\t\t\t{/* Sign-up CTA */}\n\t\t\t{signUpCta && (\n\t\t\t\t<div className=\"mt-4 p-4 bg-[#F7F9FC] dark:bg-[#0F1F3D]/30 rounded-xl border border-[#E1E6EF] dark:border-[#0F1F3D]\">\n\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t{/* App Feature Mockup */}\n\t\t\t\t\t\t{signUpCta.imageSrc && (\n\t\t\t\t\t\t\t<div className=\"w-full aspect-square rounded-lg overflow-hidden bg-white dark:bg-[#0F1F3D] border border-[#E1E6EF] dark:border-[#0F1F3D]\">\n\t\t\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\t\t\tsrc={signUpCta.imageSrc}\n\t\t\t\t\t\t\t\t\talt={signUpCta.imageAlt || \"DirectoryHub app feature\"}\n\t\t\t\t\t\t\t\t\twidth={200}\n\t\t\t\t\t\t\t\t\theight={200}\n\t\t\t\t\t\t\t\t\tclassName=\"w-full h-full object-cover\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{/* Text */}\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<p className=\"text-sm font-semibold text-foreground\">\n\t\t\t\t\t\t\t\t{signUpCta.title || \"Experience DirectoryHub\"}\n\t\t\t\t\t\t\t</p>\n\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{signUpCta.description || \"Start building your directory today\"}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* CTA Button */}\n\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\thref={signUpCta.buttonLink || \"https://app.directoryhub.app/sign-up\"}\n\t\t\t\t\t\t\tclassName=\"inline-flex items-center justify-center px-4 py-2 text-sm font-medium text-white bg-[#0F1F3D] hover:bg-[#0B162C] dark:bg-[#3DA9A3] dark:hover:bg-[#3DA9A3]/90 rounded-lg transition-colors\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{signUpCta.buttonText || \"Sign up for free\"}\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</nav>\n\t)\n}\n",
205
+ "marketing/payload/src/components/TableOfContents/index.tsx": "\"use client\"\n\nimport type { HeadingItem } from \"@/utilities/extractHeadings\"\nimport { cn } from \"@/utilities/ui\"\nimport Image from \"next/image\"\nimport Link from \"next/link\"\nimport { useEffect, useState } from \"react\"\n\ninterface TableOfContentsProps {\n\theadings: HeadingItem[]\n\tsignUpCta?: {\n\t\ttitle?: string\n\t\tdescription?: string\n\t\tbuttonText?: string\n\t\tbuttonLink?: string\n\t\timageSrc?: string\n\t\timageAlt?: string\n\t}\n}\n\nexport function TableOfContents({ headings, signUpCta }: TableOfContentsProps) {\n\tconst [activeId, setActiveId] = useState<string>(\"\")\n\n\tuseEffect(() => {\n\t\tconst observer = new IntersectionObserver(\n\t\t\t(entries) => {\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tif (entry.isIntersecting) {\n\t\t\t\t\t\tsetActiveId(entry.target.id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\trootMargin: \"-80px 0px -80% 0px\",\n\t\t\t\tthreshold: 0,\n\t\t\t},\n\t\t)\n\n\t\t// Observe all heading elements\n\t\tfor (const heading of headings) {\n\t\t\tconst element = document.getElementById(heading.id)\n\t\t\tif (element) {\n\t\t\t\tobserver.observe(element)\n\t\t\t}\n\t\t}\n\n\t\treturn () => observer.disconnect()\n\t}, [headings])\n\n\tconst handleClick = (e: React.MouseEvent<HTMLAnchorElement>, id: string) => {\n\t\te.preventDefault()\n\t\tconst element = document.getElementById(id)\n\t\tif (element) {\n\t\t\tconst yOffset = -100\n\t\t\tconst y = element.getBoundingClientRect().top + window.pageYOffset + yOffset\n\t\t\twindow.scrollTo({ top: y, behavior: \"smooth\" })\n\t\t\tsetActiveId(id)\n\t\t}\n\t}\n\n\tif (headings.length === 0) {\n\t\treturn null\n\t}\n\n\treturn (\n\t\t<nav className=\"flex flex-col gap-6\">\n\t\t\t{/* TOC Header */}\n\t\t\t<div>\n\t\t\t\t<h4 className=\"text-sm font-semibold text-foreground mb-4\">On this page</h4>\n\t\t\t\t<ul className=\"space-y-2\">\n\t\t\t\t\t{headings.map((heading) => (\n\t\t\t\t\t\t<li key={heading.id} style={{ paddingLeft: `${(heading.depth - 2) * 12}px` }}>\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref={`#${heading.id}`}\n\t\t\t\t\t\t\t\tonClick={(e) => handleClick(e, heading.id)}\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"block text-sm leading-relaxed transition-colors duration-200\",\n\t\t\t\t\t\t\t\t\t\"hover:text-foreground\",\n\t\t\t\t\t\t\t\t\tactiveId === heading.id ? \"text-[#3DA9A3] font-medium\" : \"text-muted-foreground\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{heading.text}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t))}\n\t\t\t\t</ul>\n\t\t\t</div>\n\n\t\t\t{/* Sign-up CTA */}\n\t\t\t{signUpCta && (\n\t\t\t\t<div className=\"mt-4 p-4 bg-[#F7F9FC] dark:bg-[#0F1F3D]/30 rounded-xl border border-[#E1E6EF] dark:border-[#0F1F3D]\">\n\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t{/* App Feature Mockup */}\n\t\t\t\t\t\t{signUpCta.imageSrc && (\n\t\t\t\t\t\t\t<div className=\"w-full aspect-square rounded-lg overflow-hidden bg-white dark:bg-[#0F1F3D] border border-[#E1E6EF] dark:border-[#0F1F3D]\">\n\t\t\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\t\t\tsrc={signUpCta.imageSrc}\n\t\t\t\t\t\t\t\t\talt={signUpCta.imageAlt || \"SaaSify app feature\"}\n\t\t\t\t\t\t\t\t\twidth={200}\n\t\t\t\t\t\t\t\t\theight={200}\n\t\t\t\t\t\t\t\t\tclassName=\"w-full h-full object-cover\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{/* Text */}\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<p className=\"text-sm font-semibold text-foreground\">\n\t\t\t\t\t\t\t\t{signUpCta.title || \"Experience SaaSify\"}\n\t\t\t\t\t\t\t</p>\n\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{signUpCta.description || \"Start building your directory today\"}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* CTA Button */}\n\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\thref={signUpCta.buttonLink || \"/pricing\"}\n\t\t\t\t\t\t\tclassName=\"inline-flex items-center justify-center px-4 py-2 text-sm font-medium text-white bg-[#0F1F3D] hover:bg-[#0B162C] dark:bg-[#3DA9A3] dark:hover:bg-[#3DA9A3]/90 rounded-lg transition-colors\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{signUpCta.buttonText || \"Sign up for free\"}\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</nav>\n\t)\n}\n",
202
206
  "marketing/payload/src/components/ui/accordion.tsx": "\"use client\"\n\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\"\nimport { ChevronDownIcon } from \"lucide-react\"\nimport type * as React from \"react\"\n\nimport { cn } from \"@/utilities/ui\"\n\nfunction Accordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>) {\n\treturn <AccordionPrimitive.Root data-slot=\"accordion\" {...props} />\n}\n\nfunction AccordionItem({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof AccordionPrimitive.Item>) {\n\treturn (\n\t\t<AccordionPrimitive.Item\n\t\t\tdata-slot=\"accordion-item\"\n\t\t\tclassName={cn(\"border-b last:border-b-0\", className)}\n\t\t\t{...props}\n\t\t/>\n\t)\n}\n\nfunction AccordionTrigger({\n\tclassName,\n\tchildren,\n\t...props\n}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {\n\treturn (\n\t\t<AccordionPrimitive.Header className=\"flex\">\n\t\t\t<AccordionPrimitive.Trigger\n\t\t\t\tdata-slot=\"accordion-trigger\"\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t\t<ChevronDownIcon className=\"text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200\" />\n\t\t\t</AccordionPrimitive.Trigger>\n\t\t</AccordionPrimitive.Header>\n\t)\n}\n\nfunction AccordionContent({\n\tclassName,\n\tchildren,\n\t...props\n}: React.ComponentProps<typeof AccordionPrimitive.Content>) {\n\treturn (\n\t\t<AccordionPrimitive.Content\n\t\t\tdata-slot=\"accordion-content\"\n\t\t\tclassName=\"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm\"\n\t\t\t{...props}\n\t\t>\n\t\t\t<div className={cn(\"pt-0 pb-4\", className)}>{children}</div>\n\t\t</AccordionPrimitive.Content>\n\t)\n}\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent }\n",
203
207
  "marketing/payload/src/components/ui/button.tsx": "import { cn } from \"@/utilities/ui\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { type VariantProps, cva } from \"class-variance-authority\"\nimport type * as React from \"react\"\n\nconst buttonVariants = cva(\n\t\"inline-flex items-center justify-center whitespace-nowrap rounded text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n\t{\n\t\tdefaultVariants: {\n\t\t\tsize: \"default\",\n\t\t\tvariant: \"default\",\n\t\t},\n\t\tvariants: {\n\t\t\tsize: {\n\t\t\t\tclear: \"\",\n\t\t\t\tdefault: \"h-10 px-4 py-2\",\n\t\t\t\ticon: \"h-10 w-10\",\n\t\t\t\tlg: \"h-11 rounded px-8\",\n\t\t\t\tsm: \"h-9 rounded px-3\",\n\t\t\t},\n\t\t\tvariant: {\n\t\t\t\tdefault: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n\t\t\t\tdestructive: \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n\t\t\t\tghost: \"hover:bg-card hover:text-accent-foreground\",\n\t\t\t\tlink: \"text-primary items-start justify-start underline-offset-4 hover:underline\",\n\t\t\t\toutline: \"border border-border bg-background hover:bg-card hover:text-accent-foreground\",\n\t\t\t\tsecondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n\t\t\t},\n\t\t},\n\t},\n)\n\nexport interface ButtonProps\n\textends React.ButtonHTMLAttributes<HTMLButtonElement>,\n\t\tVariantProps<typeof buttonVariants> {\n\tasChild?: boolean\n\tref?: React.Ref<HTMLButtonElement>\n}\n\nconst Button: React.FC<ButtonProps> = ({\n\tasChild = false,\n\tclassName,\n\tsize,\n\tvariant,\n\tref,\n\t...props\n}) => {\n\tconst Comp = asChild ? Slot : \"button\"\n\treturn <Comp className={cn(buttonVariants({ className, size, variant }))} ref={ref} {...props} />\n}\n\nexport { Button, buttonVariants }\n",
204
208
  "marketing/payload/src/components/ui/card.tsx": "import { cn } from \"@/utilities/ui\"\nimport type * as React from \"react\"\n\nconst Card: React.FC<\n\t{ ref?: React.Ref<HTMLDivElement> } & React.HTMLAttributes<HTMLDivElement>\n> = ({ className, ref, ...props }) => (\n\t<div\n\t\tclassName={cn(\"rounded-lg border bg-card text-card-foreground shadow-sm\", className)}\n\t\tref={ref}\n\t\t{...props}\n\t/>\n)\n\nconst CardHeader: React.FC<\n\t{ ref?: React.Ref<HTMLDivElement> } & React.HTMLAttributes<HTMLDivElement>\n> = ({ className, ref, ...props }) => (\n\t<div className={cn(\"flex flex-col space-y-1.5 p-6\", className)} ref={ref} {...props} />\n)\n\nconst CardTitle: React.FC<\n\t{ ref?: React.Ref<HTMLHeadingElement> } & React.HTMLAttributes<HTMLHeadingElement>\n> = ({ className, ref, ...props }) => (\n\t<h3\n\t\tclassName={cn(\"text-2xl font-semibold leading-none tracking-tight\", className)}\n\t\tref={ref}\n\t\t{...props}\n\t/>\n)\n\nconst CardDescription: React.FC<\n\t{ ref?: React.Ref<HTMLParagraphElement> } & React.HTMLAttributes<HTMLParagraphElement>\n> = ({ className, ref, ...props }) => (\n\t<p className={cn(\"text-sm text-muted-foreground\", className)} ref={ref} {...props} />\n)\n\nconst CardContent: React.FC<\n\t{ ref?: React.Ref<HTMLDivElement> } & React.HTMLAttributes<HTMLDivElement>\n> = ({ className, ref, ...props }) => (\n\t<div className={cn(\"p-6 pt-0\", className)} ref={ref} {...props} />\n)\n\nconst CardFooter: React.FC<\n\t{ ref?: React.Ref<HTMLDivElement> } & React.HTMLAttributes<HTMLDivElement>\n> = ({ className, ref, ...props }) => (\n\t<div className={cn(\"flex items-center p-6 pt-0\", className)} ref={ref} {...props} />\n)\n\nexport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }\n",
@@ -254,7 +258,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
254
258
  "marketing/payload/src/heros/LowImpact/index.tsx": "import type React from \"react\"\n\nimport type { Page } from \"@/payload-types\"\n\nimport { CMSLink } from \"@/components/Link\"\nimport RichText from \"@/components/RichText\"\n\ntype LowImpactHeroType =\n\t| {\n\t\t\tchildren?: React.ReactNode\n\t\t\trichText?: never\n\t\t\tlinks?: never\n\t }\n\t| (Omit<Page[\"hero\"], \"richText\"> & {\n\t\t\tchildren?: never\n\t\t\trichText?: Page[\"hero\"][\"richText\"]\n\t })\n\nexport const LowImpactHero: React.FC<LowImpactHeroType> = ({ children, richText, links }) => {\n\treturn (\n\t\t<div className=\"py-20 md:py-32\">\n\t\t\t<div className=\"container mx-auto px-4\">\n\t\t\t\t<div className=\"text-center max-w-4xl mx-auto\">\n\t\t\t\t\t{children ||\n\t\t\t\t\t\t(richText && (\n\t\t\t\t\t\t\t<RichText\n\t\t\t\t\t\t\t\tclassName=\"mb-8 hero-content\"\n\t\t\t\t\t\t\t\tdata={richText}\n\t\t\t\t\t\t\t\tenableGutter={false}\n\t\t\t\t\t\t\t\tenableProse={false}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t{Array.isArray(links) && links.length > 0 && (\n\t\t\t\t\t\t<ul className=\"flex flex-wrap justify-center gap-4\">\n\t\t\t\t\t\t\t{links.map(({ link }, i) => {\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<li key={i}>\n\t\t\t\t\t\t\t\t\t\t<CMSLink {...link} size=\"lg\" />\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})}\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n",
255
259
  "marketing/payload/src/heros/MediumImpact/index.tsx": "\"use client\"\n\nimport type React from \"react\"\n\nimport type { Page } from \"@/payload-types\"\n\nimport { CTATracker } from \"@/components/Analytics\"\nimport { CMSLink } from \"@/components/Link\"\nimport { Media } from \"@/components/Media\"\nimport RichText from \"@/components/RichText\"\n\nexport const MediumImpactHero: React.FC<Page[\"hero\"]> = ({ links, media, richText }) => {\n\treturn (\n\t\t<div className=\"py-20 md:py-28\">\n\t\t\t<div className=\"container mx-auto px-4\">\n\t\t\t\t<div className=\"text-center max-w-4xl mx-auto mb-12 hero-content\">\n\t\t\t\t\t{richText && (\n\t\t\t\t\t\t<RichText className=\"mb-8\" data={richText} enableGutter={false} enableProse={false} />\n\t\t\t\t\t)}\n\n\t\t\t\t\t{Array.isArray(links) && links.length > 0 && (\n\t\t\t\t\t\t<ul className=\"flex flex-wrap justify-center gap-4\">\n\t\t\t\t\t\t\t{links.map(({ link }, i) => {\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: CMS links don't have stable IDs\n\t\t\t\t\t\t\t\t\t<li key={i}>\n\t\t\t\t\t\t\t\t\t\t<CTATracker location=\"hero_medium_impact\" variant={link?.label || `cta_${i}`}>\n\t\t\t\t\t\t\t\t\t\t\t<CMSLink {...link} size=\"lg\" />\n\t\t\t\t\t\t\t\t\t\t</CTATracker>\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})}\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{media && typeof media === \"object\" && (\n\t\t\t\t\t<div className=\"rounded-xl overflow-hidden border border-border shadow-2xl\">\n\t\t\t\t\t\t<Media imgClassName=\"w-full\" priority resource={media} />\n\t\t\t\t\t\t{media?.caption && (\n\t\t\t\t\t\t\t<div className=\"mt-3 text-center\">\n\t\t\t\t\t\t\t\t<RichText data={media.caption} enableGutter={false} />\n\t\t\t\t\t\t\t</div>\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\t\t</div>\n\t)\n}\n",
256
260
  "marketing/payload/src/heros/PostHero/index.tsx": "import type React from \"react\"\nimport { formatDateTime } from \"src/utilities/formatDateTime\"\n\nimport type { Post } from \"@/payload-types\"\n\nimport { formatAuthors } from \"@/utilities/formatAuthors\"\nimport Link from \"next/link\"\n\nexport const PostHero: React.FC<{\n\tpost: Post\n}> = ({ post }) => {\n\tconst { categories, populatedAuthors, publishedAt, title } = post\n\n\tconst hasAuthors =\n\t\tpopulatedAuthors && populatedAuthors.length > 0 && formatAuthors(populatedAuthors) !== \"\"\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"relative\"\n\t\t\tstyle={{\n\t\t\t\tbackground: \"linear-gradient(180deg, #0F1F3D 0%, #101F3C 50%, #0F1F3D 100%)\",\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"container py-16 md:py-20 lg:py-24\">\n\t\t\t\t<div className=\"max-w-3xl\">\n\t\t\t\t\t{/* Category Badge */}\n\t\t\t\t\t{categories && categories.length > 0 && (\n\t\t\t\t\t\t<div className=\"flex items-center gap-2 mb-6\">\n\t\t\t\t\t\t\t{categories.map((category) => {\n\t\t\t\t\t\t\t\tif (typeof category === \"object\" && category !== null) {\n\t\t\t\t\t\t\t\t\tconst { title: categoryTitle, slug, id } = category\n\t\t\t\t\t\t\t\t\tconst titleToUse = categoryTitle || \"Untitled category\"\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\t\tkey={id || slug}\n\t\t\t\t\t\t\t\t\t\t\thref={`/posts?category=${slug}`}\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"inline-flex items-center px-3 py-1 text-xs font-medium uppercase tracking-wider text-[#3DA9A3] bg-[#3DA9A3]/10 rounded-full hover:bg-[#3DA9A3]/20 transition-colors\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{titleToUse}\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}\n\t\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Title */}\n\t\t\t\t\t<h1 className=\"text-3xl md:text-4xl lg:text-5xl font-bold text-white leading-tight mb-8\">\n\t\t\t\t\t\t{title}\n\t\t\t\t\t</h1>\n\n\t\t\t\t\t{/* Meta Information */}\n\t\t\t\t\t<div className=\"flex items-center gap-6 text-sm text-white/70\">\n\t\t\t\t\t\t{hasAuthors && (\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t<span className=\"text-white/50\">By</span>\n\t\t\t\t\t\t\t\t<span className=\"text-white font-medium\">{formatAuthors(populatedAuthors)}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{hasAuthors && publishedAt && <span className=\"text-white/30\">•</span>}\n\t\t\t\t\t\t{publishedAt && (\n\t\t\t\t\t\t\t<time dateTime={publishedAt} className=\"text-white/70\">\n\t\t\t\t\t\t\t\t{formatDateTime(publishedAt)}\n\t\t\t\t\t\t\t</time>\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</div>\n\t)\n}\n",
257
- "marketing/payload/src/heros/ProductShowcase/AnimatedMockup.tsx": "\"use client\"\n\nimport { cn } from \"@/utilities/ui\"\nimport type React from \"react\"\nimport { useEffect, useState } from \"react\"\n\ninterface MockupState {\n\tid: number\n\tlabel: string\n\tsidebarActive: string\n\tpreviewTitle: string\n\tpreviewDescription: string\n\tpreviewCategory: string\n\tpreviewStatus: \"draft\" | \"published\" | \"featured\"\n\tpreviewUrl?: string\n}\n\nconst mockupStates: MockupState[] = [\n\t{\n\t\tid: 1,\n\t\tlabel: \"Setup & styling\",\n\t\tsidebarActive: \"templates\",\n\t\tpreviewTitle: \"Atlas Directory\",\n\t\tpreviewDescription: \"Apply your brand, typography, and layout in minutes.\",\n\t\tpreviewCategory: \"Design systems\",\n\t\tpreviewStatus: \"draft\",\n\t\tpreviewUrl: \"atlas.directory/home\",\n\t},\n\t{\n\t\tid: 2,\n\t\tlabel: \"Plans & pricing\",\n\t\tsidebarActive: \"billing\",\n\t\tpreviewTitle: \"Pro Listing Plan\",\n\t\tpreviewDescription: \"Recurring billing, featured placements, and add-ons configured.\",\n\t\tpreviewCategory: \"Monetization\",\n\t\tpreviewStatus: \"draft\",\n\t\tpreviewUrl: \"atlas.directory/billing\",\n\t},\n\t{\n\t\tid: 3,\n\t\tlabel: \"SEO & publishing\",\n\t\tsidebarActive: \"seo\",\n\t\tpreviewTitle: \"Atlas Directory\",\n\t\tpreviewDescription: \"Schema, sitemap, and custom domain are ready to publish.\",\n\t\tpreviewCategory: \"SEO & domains\",\n\t\tpreviewStatus: \"published\",\n\t\tpreviewUrl: \"atlas.directory/launch\",\n\t},\n\t{\n\t\tid: 4,\n\t\tlabel: \"Payouts live\",\n\t\tsidebarActive: \"overview\",\n\t\tpreviewTitle: \"Atlas Directory\",\n\t\tpreviewDescription: \"Subscribers active, payouts scheduled to Stripe, featured slots sold.\",\n\t\tpreviewCategory: \"Revenue\",\n\t\tpreviewStatus: \"featured\",\n\t\tpreviewUrl: \"atlas.directory/analytics\",\n\t},\n]\n\nexport const AnimatedMockup: React.FC = () => {\n\tconst [currentState, setCurrentState] = useState(0)\n\tconst [isPaused, setIsPaused] = useState(false)\n\n\tuseEffect(() => {\n\t\tif (isPaused) return\n\n\t\tconst interval = setInterval(() => {\n\t\t\tsetCurrentState((prev) => (prev + 1) % mockupStates.length)\n\t\t}, 3000)\n\n\t\treturn () => clearInterval(interval)\n\t}, [isPaused])\n\n\tconst state = mockupStates[currentState]\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"mockup-wrapper\"\n\t\t\tonMouseEnter={() => setIsPaused(true)}\n\t\t\tonMouseLeave={() => setIsPaused(false)}\n\t\t>\n\t\t\t{/* Browser Chrome */}\n\t\t\t<div className=\"mockup-chrome\">\n\t\t\t\t<div className=\"mockup-chrome-dots\">\n\t\t\t\t\t<span className=\"dot dot-red\" />\n\t\t\t\t\t<span className=\"dot dot-yellow\" />\n\t\t\t\t\t<span className=\"dot dot-green\" />\n\t\t\t\t</div>\n\t\t\t\t<div className=\"mockup-chrome-title\">DirectoryHub</div>\n\t\t\t\t<div className=\"mockup-chrome-actions\" />\n\t\t\t</div>\n\n\t\t\t{/* App Content */}\n\t\t\t<div className=\"mockup-content\">\n\t\t\t\t{/* Sidebar */}\n\t\t\t\t<div className=\"mockup-sidebar\">\n\t\t\t\t\t<div className=\"sidebar-header\">\n\t\t\t\t\t\t<div className=\"sidebar-logo\">\n\t\t\t\t\t\t\t<span className=\"logo-icon\">D</span>\n\t\t\t\t\t\t\t<span className=\"logo-text\">My Directory</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<nav className=\"sidebar-nav\">\n\t\t\t\t\t\t<SidebarItem icon=\"📊\" label=\"Overview\" active={state.sidebarActive === \"overview\"} />\n\t\t\t\t\t\t<SidebarItem icon=\"🖼️\" label=\"Templates\" active={state.sidebarActive === \"templates\"} />\n\t\t\t\t\t\t<SidebarItem\n\t\t\t\t\t\t\ticon=\"📋\"\n\t\t\t\t\t\t\tlabel=\"Listings\"\n\t\t\t\t\t\t\tactive={state.sidebarActive === \"listings\"}\n\t\t\t\t\t\t\tbadge={24}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SidebarItem\n\t\t\t\t\t\t\ticon=\"💳\"\n\t\t\t\t\t\t\tlabel=\"Plans & Billing\"\n\t\t\t\t\t\t\tactive={state.sidebarActive === \"billing\"}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SidebarItem\n\t\t\t\t\t\t\ticon=\"⚡\"\n\t\t\t\t\t\t\tlabel=\"Automations\"\n\t\t\t\t\t\t\tactive={state.sidebarActive === \"automations\"}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SidebarItem icon=\"🔎\" label=\"SEO\" active={state.sidebarActive === \"seo\"} />\n\t\t\t\t\t\t<SidebarItem icon=\"⚙️\" label=\"Settings\" active={state.sidebarActive === \"settings\"} />\n\t\t\t\t\t</nav>\n\t\t\t\t</div>\n\n\t\t\t\t{/* Main Content Area */}\n\t\t\t\t<div className=\"mockup-main\">\n\t\t\t\t\t{/* Header */}\n\t\t\t\t\t<div className=\"main-header\">\n\t\t\t\t\t\t<div className=\"header-title\">\n\t\t\t\t\t\t\t<h2>{state.label}</h2>\n\t\t\t\t\t\t\t<span className=\"header-breadcrumb\">DirectoryHub / {state.previewTitle}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"header-actions\">\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"action-btn\",\n\t\t\t\t\t\t\t\t\tstate.previewStatus === \"published\" && \"action-btn--success\",\n\t\t\t\t\t\t\t\t\tstate.previewStatus === \"featured\" && \"action-btn--featured\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{state.previewStatus === \"draft\" && \"Save Draft\"}\n\t\t\t\t\t\t\t\t{state.previewStatus === \"published\" && \"✓ Published\"}\n\t\t\t\t\t\t\t\t{state.previewStatus === \"featured\" && \"⭐ Featured\"}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Split View: Editor + Preview */}\n\t\t\t\t\t<div className=\"main-split\">\n\t\t\t\t\t\t{/* Editor Panel */}\n\t\t\t\t\t\t<div className=\"editor-panel\">\n\t\t\t\t\t\t\t<div className=\"editor-section\">\n\t\t\t\t\t\t\t\t<span className=\"editor-label\">Listing Name</span>\n\t\t\t\t\t\t\t\t<div className=\"editor-input\">\n\t\t\t\t\t\t\t\t\t<span className=\"input-text\">{state.previewTitle}</span>\n\t\t\t\t\t\t\t\t\t<span className=\"input-cursor\" />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"editor-section\">\n\t\t\t\t\t\t\t\t<span className=\"editor-label\">Category</span>\n\t\t\t\t\t\t\t\t<div className=\"editor-select\">\n\t\t\t\t\t\t\t\t\t<span>{state.previewCategory}</span>\n\t\t\t\t\t\t\t\t\t<span className=\"select-arrow\">▼</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"editor-section\">\n\t\t\t\t\t\t\t\t<span className=\"editor-label\">Description</span>\n\t\t\t\t\t\t\t\t<div className=\"editor-textarea\">\n\t\t\t\t\t\t\t\t\t<span className={cn(\"textarea-text\", state.id >= 2 && \"textarea-text--complete\")}>\n\t\t\t\t\t\t\t\t\t\t{state.previewDescription}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* Preview Panel */}\n\t\t\t\t\t\t<div className=\"preview-panel\">\n\t\t\t\t\t\t\t<div className=\"preview-header\">\n\t\t\t\t\t\t\t\t<span className=\"preview-label\">Live Preview</span>\n\t\t\t\t\t\t\t\t<span className=\"preview-url\">{state.previewUrl ?? \"directoryhub.app/live\"}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"preview-card\">\n\t\t\t\t\t\t\t\t{state.previewStatus === \"featured\" && (\n\t\t\t\t\t\t\t\t\t<div className=\"preview-badge\">⭐ Featured</div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t<div className=\"preview-image\">\n\t\t\t\t\t\t\t\t\t<div className=\"preview-image-placeholder\">\n\t\t\t\t\t\t\t\t\t\t<span>🏢</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div className=\"preview-content\">\n\t\t\t\t\t\t\t\t\t<span className=\"preview-category-tag\">{state.previewCategory}</span>\n\t\t\t\t\t\t\t\t\t<h3 className=\"preview-title\">{state.previewTitle}</h3>\n\t\t\t\t\t\t\t\t\t<p className=\"preview-description\">{state.previewDescription}</p>\n\t\t\t\t\t\t\t\t\t<div className=\"preview-meta\">\n\t\t\t\t\t\t\t\t\t\t<span className=\"meta-rating\">★★★★★</span>\n\t\t\t\t\t\t\t\t\t\t<span className=\"meta-reviews\">24 reviews</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\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</div>\n\n\t\t\t{/* State Indicator */}\n\t\t\t<div className=\"mockup-indicators\">\n\t\t\t\t{mockupStates.map((s, i) => (\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tkey={s.id}\n\t\t\t\t\t\tonClick={() => setCurrentState(i)}\n\t\t\t\t\t\tclassName={cn(\"indicator\", i === currentState && \"indicator--active\")}\n\t\t\t\t\t>\n\t\t\t\t\t\t<span className=\"indicator-dot\" />\n\t\t\t\t\t\t<span className=\"indicator-label\">{s.label}</span>\n\t\t\t\t\t</button>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\ninterface SidebarItemProps {\n\ticon: string\n\tlabel: string\n\tactive?: boolean\n\tbadge?: number\n}\n\nconst SidebarItem: React.FC<SidebarItemProps> = ({ icon, label, active, badge }) => (\n\t<div className={cn(\"sidebar-item\", active && \"sidebar-item--active\")}>\n\t\t<span className=\"sidebar-icon\">{icon}</span>\n\t\t<span className=\"sidebar-label\">{label}</span>\n\t\t{badge && <span className=\"sidebar-badge\">{badge}</span>}\n\t</div>\n)\n",
261
+ "marketing/payload/src/heros/ProductShowcase/AnimatedMockup.tsx": "\"use client\"\n\nimport { cn } from \"@/utilities/ui\"\nimport type React from \"react\"\nimport { useEffect, useState } from \"react\"\n\ninterface MockupState {\n\tid: number\n\tlabel: string\n\tsidebarActive: string\n\tpreviewTitle: string\n\tpreviewDescription: string\n\tpreviewCategory: string\n\tpreviewStatus: \"draft\" | \"published\" | \"featured\"\n\tpreviewUrl?: string\n}\n\nconst mockupStates: MockupState[] = [\n\t{\n\t\tid: 1,\n\t\tlabel: \"Setup & styling\",\n\t\tsidebarActive: \"templates\",\n\t\tpreviewTitle: \"Atlas Directory\",\n\t\tpreviewDescription: \"Apply your brand, typography, and layout in minutes.\",\n\t\tpreviewCategory: \"Design systems\",\n\t\tpreviewStatus: \"draft\",\n\t\tpreviewUrl: \"atlas.directory/home\",\n\t},\n\t{\n\t\tid: 2,\n\t\tlabel: \"Plans & pricing\",\n\t\tsidebarActive: \"billing\",\n\t\tpreviewTitle: \"Pro Listing Plan\",\n\t\tpreviewDescription: \"Recurring billing, featured placements, and add-ons configured.\",\n\t\tpreviewCategory: \"Monetization\",\n\t\tpreviewStatus: \"draft\",\n\t\tpreviewUrl: \"atlas.directory/billing\",\n\t},\n\t{\n\t\tid: 3,\n\t\tlabel: \"SEO & publishing\",\n\t\tsidebarActive: \"seo\",\n\t\tpreviewTitle: \"Atlas Directory\",\n\t\tpreviewDescription: \"Schema, sitemap, and custom domain are ready to publish.\",\n\t\tpreviewCategory: \"SEO & domains\",\n\t\tpreviewStatus: \"published\",\n\t\tpreviewUrl: \"atlas.directory/launch\",\n\t},\n\t{\n\t\tid: 4,\n\t\tlabel: \"Payouts live\",\n\t\tsidebarActive: \"overview\",\n\t\tpreviewTitle: \"Atlas Directory\",\n\t\tpreviewDescription: \"Subscribers active, payouts scheduled to Stripe, featured slots sold.\",\n\t\tpreviewCategory: \"Revenue\",\n\t\tpreviewStatus: \"featured\",\n\t\tpreviewUrl: \"atlas.directory/analytics\",\n\t},\n]\n\nexport const AnimatedMockup: React.FC = () => {\n\tconst [currentState, setCurrentState] = useState(0)\n\tconst [isPaused, setIsPaused] = useState(false)\n\n\tuseEffect(() => {\n\t\tif (isPaused) return\n\n\t\tconst interval = setInterval(() => {\n\t\t\tsetCurrentState((prev) => (prev + 1) % mockupStates.length)\n\t\t}, 3000)\n\n\t\treturn () => clearInterval(interval)\n\t}, [isPaused])\n\n\tconst state = mockupStates[currentState]\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"mockup-wrapper\"\n\t\t\tonMouseEnter={() => setIsPaused(true)}\n\t\t\tonMouseLeave={() => setIsPaused(false)}\n\t\t>\n\t\t\t{/* Browser Chrome */}\n\t\t\t<div className=\"mockup-chrome\">\n\t\t\t\t<div className=\"mockup-chrome-dots\">\n\t\t\t\t\t<span className=\"dot dot-red\" />\n\t\t\t\t\t<span className=\"dot dot-yellow\" />\n\t\t\t\t\t<span className=\"dot dot-green\" />\n\t\t\t\t</div>\n\t\t\t\t<div className=\"mockup-chrome-title\">SaaSify</div>\n\t\t\t\t<div className=\"mockup-chrome-actions\" />\n\t\t\t</div>\n\n\t\t\t{/* App Content */}\n\t\t\t<div className=\"mockup-content\">\n\t\t\t\t{/* Sidebar */}\n\t\t\t\t<div className=\"mockup-sidebar\">\n\t\t\t\t\t<div className=\"sidebar-header\">\n\t\t\t\t\t\t<div className=\"sidebar-logo\">\n\t\t\t\t\t\t\t<span className=\"logo-icon\">D</span>\n\t\t\t\t\t\t\t<span className=\"logo-text\">My Directory</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<nav className=\"sidebar-nav\">\n\t\t\t\t\t\t<SidebarItem icon=\"📊\" label=\"Overview\" active={state.sidebarActive === \"overview\"} />\n\t\t\t\t\t\t<SidebarItem icon=\"🖼️\" label=\"Templates\" active={state.sidebarActive === \"templates\"} />\n\t\t\t\t\t\t<SidebarItem\n\t\t\t\t\t\t\ticon=\"📋\"\n\t\t\t\t\t\t\tlabel=\"Listings\"\n\t\t\t\t\t\t\tactive={state.sidebarActive === \"listings\"}\n\t\t\t\t\t\t\tbadge={24}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SidebarItem\n\t\t\t\t\t\t\ticon=\"💳\"\n\t\t\t\t\t\t\tlabel=\"Plans & Billing\"\n\t\t\t\t\t\t\tactive={state.sidebarActive === \"billing\"}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SidebarItem\n\t\t\t\t\t\t\ticon=\"⚡\"\n\t\t\t\t\t\t\tlabel=\"Automations\"\n\t\t\t\t\t\t\tactive={state.sidebarActive === \"automations\"}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SidebarItem icon=\"🔎\" label=\"SEO\" active={state.sidebarActive === \"seo\"} />\n\t\t\t\t\t\t<SidebarItem icon=\"⚙️\" label=\"Settings\" active={state.sidebarActive === \"settings\"} />\n\t\t\t\t\t</nav>\n\t\t\t\t</div>\n\n\t\t\t\t{/* Main Content Area */}\n\t\t\t\t<div className=\"mockup-main\">\n\t\t\t\t\t{/* Header */}\n\t\t\t\t\t<div className=\"main-header\">\n\t\t\t\t\t\t<div className=\"header-title\">\n\t\t\t\t\t\t\t<h2>{state.label}</h2>\n\t\t\t\t\t\t\t<span className=\"header-breadcrumb\">SaaSify / {state.previewTitle}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"header-actions\">\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"action-btn\",\n\t\t\t\t\t\t\t\t\tstate.previewStatus === \"published\" && \"action-btn--success\",\n\t\t\t\t\t\t\t\t\tstate.previewStatus === \"featured\" && \"action-btn--featured\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{state.previewStatus === \"draft\" && \"Save Draft\"}\n\t\t\t\t\t\t\t\t{state.previewStatus === \"published\" && \"✓ Published\"}\n\t\t\t\t\t\t\t\t{state.previewStatus === \"featured\" && \"⭐ Featured\"}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Split View: Editor + Preview */}\n\t\t\t\t\t<div className=\"main-split\">\n\t\t\t\t\t\t{/* Editor Panel */}\n\t\t\t\t\t\t<div className=\"editor-panel\">\n\t\t\t\t\t\t\t<div className=\"editor-section\">\n\t\t\t\t\t\t\t\t<span className=\"editor-label\">Listing Name</span>\n\t\t\t\t\t\t\t\t<div className=\"editor-input\">\n\t\t\t\t\t\t\t\t\t<span className=\"input-text\">{state.previewTitle}</span>\n\t\t\t\t\t\t\t\t\t<span className=\"input-cursor\" />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"editor-section\">\n\t\t\t\t\t\t\t\t<span className=\"editor-label\">Category</span>\n\t\t\t\t\t\t\t\t<div className=\"editor-select\">\n\t\t\t\t\t\t\t\t\t<span>{state.previewCategory}</span>\n\t\t\t\t\t\t\t\t\t<span className=\"select-arrow\">▼</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"editor-section\">\n\t\t\t\t\t\t\t\t<span className=\"editor-label\">Description</span>\n\t\t\t\t\t\t\t\t<div className=\"editor-textarea\">\n\t\t\t\t\t\t\t\t\t<span className={cn(\"textarea-text\", state.id >= 2 && \"textarea-text--complete\")}>\n\t\t\t\t\t\t\t\t\t\t{state.previewDescription}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* Preview Panel */}\n\t\t\t\t\t\t<div className=\"preview-panel\">\n\t\t\t\t\t\t\t<div className=\"preview-header\">\n\t\t\t\t\t\t\t\t<span className=\"preview-label\">Live Preview</span>\n\t\t\t\t\t\t\t\t<span className=\"preview-url\">{state.previewUrl ?? \"saasify.app/live\"}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"preview-card\">\n\t\t\t\t\t\t\t\t{state.previewStatus === \"featured\" && (\n\t\t\t\t\t\t\t\t\t<div className=\"preview-badge\">⭐ Featured</div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t<div className=\"preview-image\">\n\t\t\t\t\t\t\t\t\t<div className=\"preview-image-placeholder\">\n\t\t\t\t\t\t\t\t\t\t<span>🏢</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div className=\"preview-content\">\n\t\t\t\t\t\t\t\t\t<span className=\"preview-category-tag\">{state.previewCategory}</span>\n\t\t\t\t\t\t\t\t\t<h3 className=\"preview-title\">{state.previewTitle}</h3>\n\t\t\t\t\t\t\t\t\t<p className=\"preview-description\">{state.previewDescription}</p>\n\t\t\t\t\t\t\t\t\t<div className=\"preview-meta\">\n\t\t\t\t\t\t\t\t\t\t<span className=\"meta-rating\">★★★★★</span>\n\t\t\t\t\t\t\t\t\t\t<span className=\"meta-reviews\">24 reviews</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\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</div>\n\n\t\t\t{/* State Indicator */}\n\t\t\t<div className=\"mockup-indicators\">\n\t\t\t\t{mockupStates.map((s, i) => (\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tkey={s.id}\n\t\t\t\t\t\tonClick={() => setCurrentState(i)}\n\t\t\t\t\t\tclassName={cn(\"indicator\", i === currentState && \"indicator--active\")}\n\t\t\t\t\t>\n\t\t\t\t\t\t<span className=\"indicator-dot\" />\n\t\t\t\t\t\t<span className=\"indicator-label\">{s.label}</span>\n\t\t\t\t\t</button>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\ninterface SidebarItemProps {\n\ticon: string\n\tlabel: string\n\tactive?: boolean\n\tbadge?: number\n}\n\nconst SidebarItem: React.FC<SidebarItemProps> = ({ icon, label, active, badge }) => (\n\t<div className={cn(\"sidebar-item\", active && \"sidebar-item--active\")}>\n\t\t<span className=\"sidebar-icon\">{icon}</span>\n\t\t<span className=\"sidebar-label\">{label}</span>\n\t\t{badge && <span className=\"sidebar-badge\">{badge}</span>}\n\t</div>\n)\n",
258
262
  "marketing/payload/src/heros/ProductShowcase/index.tsx": "\"use client\"\n\nimport { useHeaderTheme } from \"@/providers/HeaderTheme\"\nimport Image from \"next/image\"\nimport type React from \"react\"\nimport { useEffect } from \"react\"\n\nimport type { Page } from \"@/payload-types\"\n\nimport { CTATracker } from \"@/components/Analytics\"\nimport { CMSLink } from \"@/components/Link\"\nimport { Media } from \"@/components/Media\"\nimport RichText from \"@/components/RichText\"\nimport { AnimatedMockup } from \"./AnimatedMockup\"\n\nexport const ProductShowcaseHero: React.FC<Page[\"hero\"]> = ({\n\tlinks,\n\trichText,\n\tmedia,\n\tbackgroundMedia,\n}) => {\n\tconst { setHeaderTheme } = useHeaderTheme()\n\tconst hasMedia = media && typeof media === \"object\"\n\tconst hasBackgroundMedia = backgroundMedia && typeof backgroundMedia === \"object\"\n\n\tuseEffect(() => {\n\t\tsetHeaderTheme(\"light\")\n\t}, [setHeaderTheme])\n\n\treturn (\n\t\t<div className=\"relative overflow-hidden\">\n\t\t\t{/* Hero Content - Left Aligned */}\n\t\t\t<div className=\"container mx-auto px-4 pt-8 pb-16 md:pt-16 md:pb-24\">\n\t\t\t\t<div className=\"max-w-2xl\">\n\t\t\t\t\t{richText && (\n\t\t\t\t\t\t<RichText\n\t\t\t\t\t\t\tclassName=\"mb-8 hero-content hero-content--left\"\n\t\t\t\t\t\t\tdata={richText}\n\t\t\t\t\t\t\tenableGutter={false}\n\t\t\t\t\t\t\tenableProse={false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t\t{Array.isArray(links) && links.length > 0 && (\n\t\t\t\t\t\t<ul className=\"flex flex-wrap gap-4\">\n\t\t\t\t\t\t\t{links.map(({ link }, i) => {\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: Links are static and don't reorder\n\t\t\t\t\t\t\t\t\t<li key={i}>\n\t\t\t\t\t\t\t\t\t\t<CTATracker\n\t\t\t\t\t\t\t\t\t\t\tlocation=\"hero_product_showcase\"\n\t\t\t\t\t\t\t\t\t\t\tvariant={link?.label || `cta_${i}`}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<CMSLink {...link} size=\"lg\" />\n\t\t\t\t\t\t\t\t\t\t</CTATracker>\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})}\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Product Mockup Section (Cursor-style) */}\n\t\t\t<div className=\"container mx-auto px-4\">\n\t\t\t\t<div className=\"hero-showcase\">\n\t\t\t\t\t{/* Background Image - LCP element, needs fetchPriority=\"high\" */}\n\t\t\t\t\t<div className=\"hero-bg-image\">\n\t\t\t\t\t\t{hasBackgroundMedia ? (\n\t\t\t\t\t\t\t<Media\n\t\t\t\t\t\t\t\tresource={backgroundMedia}\n\t\t\t\t\t\t\t\tfill\n\t\t\t\t\t\t\t\timgClassName=\"object-cover\"\n\t\t\t\t\t\t\t\tpriority\n\t\t\t\t\t\t\t\tsize=\"100vw\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\t\tsrc=\"/media/hero-bg.png\"\n\t\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\t\tfill\n\t\t\t\t\t\t\t\tsizes=\"(max-width: 1280px) 100vw, 1280px\"\n\t\t\t\t\t\t\t\tclassName=\"object-cover\"\n\t\t\t\t\t\t\t\tpriority\n\t\t\t\t\t\t\t\tfetchPriority=\"high\"\n\t\t\t\t\t\t\t\tquality={75}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Mockup - centered within background */}\n\t\t\t\t\t<div className=\"hero-mockup-centered\">\n\t\t\t\t\t\t{hasMedia ? (\n\t\t\t\t\t\t\t<div className=\"mockup-wrapper\">\n\t\t\t\t\t\t\t\t<Media\n\t\t\t\t\t\t\t\t\tresource={media}\n\t\t\t\t\t\t\t\t\timgClassName=\"w-full h-auto object-contain\"\n\t\t\t\t\t\t\t\t\tsize=\"(max-width: 768px) 100vw, (max-width: 1024px) 80vw, 960px\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<AnimatedMockup />\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</div>\n\t)\n}\n",
259
263
  "marketing/payload/src/heros/RenderHero.tsx": "import type React from \"react\"\n\nimport type { Page } from \"@/payload-types\"\n\nimport { HighImpactHero } from \"@/heros/HighImpact\"\nimport { LowImpactHero } from \"@/heros/LowImpact\"\nimport { MediumImpactHero } from \"@/heros/MediumImpact\"\nimport { ProductShowcaseHero } from \"@/heros/ProductShowcase\"\n\nconst heroes = {\n\thighImpact: HighImpactHero,\n\tlowImpact: LowImpactHero,\n\tmediumImpact: MediumImpactHero,\n\tproductShowcase: ProductShowcaseHero,\n}\n\nexport const RenderHero: React.FC<Page[\"hero\"]> = (props) => {\n\tconst { type } = props || {}\n\n\tif (!type || type === \"none\") return null\n\n\tconst HeroToRender = heroes[type]\n\n\tif (!HeroToRender) return null\n\n\treturn <HeroToRender {...props} />\n}\n",
260
264
  "marketing/payload/src/heros/config.ts": "import type { Field } from \"payload\"\n\nimport {\n\tAlignFeature,\n\tBlockquoteFeature,\n\tChecklistFeature,\n\tEXPERIMENTAL_TableFeature,\n\tFixedToolbarFeature,\n\tHeadingFeature,\n\tIndentFeature,\n\tInlineCodeFeature,\n\tInlineToolbarFeature,\n\tOrderedListFeature,\n\tRelationshipFeature,\n\tStrikethroughFeature,\n\tSubscriptFeature,\n\tSuperscriptFeature,\n\tUnorderedListFeature,\n\tUploadFeature,\n\tlexicalEditor,\n} from \"@payloadcms/richtext-lexical\"\n\nimport { linkGroup } from \"@/fields/linkGroup\"\n\nexport const hero: Field = {\n\tname: \"hero\",\n\ttype: \"group\",\n\tfields: [\n\t\t{\n\t\t\tname: \"type\",\n\t\t\ttype: \"select\",\n\t\t\tdefaultValue: \"lowImpact\",\n\t\t\tlabel: \"Type\",\n\t\t\toptions: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"None\",\n\t\t\t\t\tvalue: \"none\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Product Showcase\",\n\t\t\t\t\tvalue: \"productShowcase\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"High Impact\",\n\t\t\t\t\tvalue: \"highImpact\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Medium Impact\",\n\t\t\t\t\tvalue: \"mediumImpact\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"Low Impact\",\n\t\t\t\t\tvalue: \"lowImpact\",\n\t\t\t\t},\n\t\t\t],\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tname: \"richText\",\n\t\t\ttype: \"richText\",\n\t\t\teditor: lexicalEditor({\n\t\t\t\tfeatures: ({ rootFeatures }) => {\n\t\t\t\t\treturn [\n\t\t\t\t\t\t...rootFeatures,\n\t\t\t\t\t\tHeadingFeature({ enabledHeadingSizes: [\"h1\", \"h2\", \"h3\", \"h4\"] }),\n\t\t\t\t\t\tFixedToolbarFeature(),\n\t\t\t\t\t\tInlineToolbarFeature(),\n\t\t\t\t\t\tStrikethroughFeature(),\n\t\t\t\t\t\tSubscriptFeature(),\n\t\t\t\t\t\tSuperscriptFeature(),\n\t\t\t\t\t\tInlineCodeFeature(),\n\t\t\t\t\t\tBlockquoteFeature(),\n\t\t\t\t\t\tUnorderedListFeature(),\n\t\t\t\t\t\tOrderedListFeature(),\n\t\t\t\t\t\tChecklistFeature(),\n\t\t\t\t\t\tAlignFeature(),\n\t\t\t\t\t\tIndentFeature(),\n\t\t\t\t\t\tRelationshipFeature(),\n\t\t\t\t\t\tUploadFeature(),\n\t\t\t\t\t\tEXPERIMENTAL_TableFeature(),\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t}),\n\t\t\tlabel: false,\n\t\t},\n\t\tlinkGroup({\n\t\t\toverrides: {\n\t\t\t\tmaxRows: 2,\n\t\t\t},\n\t\t}),\n\t\t{\n\t\t\tname: \"media\",\n\t\t\ttype: \"upload\",\n\t\t\tadmin: {\n\t\t\t\tcondition: (_, { type } = {}) =>\n\t\t\t\t\t[\"highImpact\", \"mediumImpact\", \"productShowcase\"].includes(type),\n\t\t\t},\n\t\t\trelationTo: \"media\",\n\t\t\trequired: false,\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\tvalidate: (value: any, { siblingData }: { siblingData?: { type?: string } }) => {\n\t\t\t\tif ([\"highImpact\", \"mediumImpact\"].includes(siblingData?.type ?? \"\") && !value) {\n\t\t\t\t\treturn \"Media is required for high impact and medium impact hero types\"\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"backgroundMedia\",\n\t\t\ttype: \"upload\",\n\t\t\tadmin: {\n\t\t\t\tcondition: (_, { type } = {}) => type === \"productShowcase\",\n\t\t\t\tdescription: \"Optional background illustration. Falls back to default if not set.\",\n\t\t\t},\n\t\t\trelationTo: \"media\",\n\t\t\trequired: false,\n\t\t\tlabel: \"Background Image\",\n\t\t},\n\t],\n\tlabel: false,\n}\n",
@@ -282,7 +286,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
282
286
  "marketing/payload/src/utilities/extractHeadings.ts": "import type { DefaultTypedEditorState } from \"@payloadcms/richtext-lexical\"\n\nexport interface HeadingItem {\n\tid: string\n\ttext: string\n\tdepth: number\n}\n\n/**\n * Slugify a string for use as an anchor ID\n */\nexport function 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\t\t.replace(/-+/g, \"-\")\n\t\t.trim()\n}\n\n/**\n * Extract text content from a Lexical node recursively\n */\nfunction extractTextFromNode(node: Record<string, unknown>): string {\n\tif (node.type === \"text\" && typeof node.text === \"string\") {\n\t\treturn node.text\n\t}\n\n\tif (Array.isArray(node.children)) {\n\t\treturn node.children\n\t\t\t.map((child) => extractTextFromNode(child as Record<string, unknown>))\n\t\t\t.join(\"\")\n\t}\n\n\treturn \"\"\n}\n\n/**\n * Get heading depth from tag name\n */\nfunction getHeadingDepth(tag: string): number {\n\tconst depthMap: Record<string, number> = {\n\t\th1: 1,\n\t\th2: 2,\n\t\th3: 3,\n\t\th4: 4,\n\t\th5: 5,\n\t\th6: 6,\n\t}\n\treturn depthMap[tag] || 2\n}\n\n/**\n * Extract headings from Lexical editor state for table of contents\n */\nexport function extractHeadingsFromLexical(content: DefaultTypedEditorState): HeadingItem[] {\n\tconst headings: HeadingItem[] = []\n\n\tif (!content?.root?.children) {\n\t\treturn headings\n\t}\n\n\tfor (const node of content.root.children) {\n\t\tconst nodeRecord = node as Record<string, unknown>\n\t\tif (nodeRecord.type === \"heading\" && typeof nodeRecord.tag === \"string\") {\n\t\t\tconst text = extractTextFromNode(nodeRecord)\n\t\t\tif (text.trim()) {\n\t\t\t\theadings.push({\n\t\t\t\t\tid: slugify(text),\n\t\t\t\t\ttext: text.trim(),\n\t\t\t\t\tdepth: getHeadingDepth(nodeRecord.tag),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\treturn headings\n}\n",
283
287
  "marketing/payload/src/utilities/formatAuthors.ts": "import type { Post } from \"@/payload-types\"\n\n/**\n * Formats an array of populatedAuthors from Posts into a prettified string.\n * @param authors - The populatedAuthors array from a Post.\n * @returns A prettified string of authors.\n * @example\n *\n * [Author1, Author2] becomes 'Author1 and Author2'\n * [Author1, Author2, Author3] becomes 'Author1, Author2, and Author3'\n *\n */\nexport const formatAuthors = (\n\tauthors: NonNullable<NonNullable<Post[\"populatedAuthors\"]>[number]>[],\n) => {\n\t// Ensure we don't have any authors without a name\n\tconst authorNames = authors.map((author) => author.name).filter(Boolean)\n\n\tif (authorNames.length === 0) return \"\"\n\tif (authorNames.length === 1) return authorNames[0]\n\tif (authorNames.length === 2) return `${authorNames[0]} and ${authorNames[1]}`\n\n\treturn `${authorNames.slice(0, -1).join(\", \")} and ${authorNames[authorNames.length - 1]}`\n}\n",
284
288
  "marketing/payload/src/utilities/formatDateTime.ts": "export const formatDateTime = (timestamp: string): string => {\n\tconst now = new Date()\n\tlet date = now\n\tif (timestamp) date = new Date(timestamp)\n\tconst months = date.getMonth()\n\tconst days = date.getDate()\n\t// const hours = date.getHours();\n\t// const minutes = date.getMinutes();\n\t// const seconds = date.getSeconds();\n\n\tconst MM = months + 1 < 10 ? `0${months + 1}` : months + 1\n\tconst DD = days < 10 ? `0${days}` : days\n\tconst YYYY = date.getFullYear()\n\t// const AMPM = hours < 12 ? 'AM' : 'PM';\n\t// const HH = hours > 12 ? hours - 12 : hours;\n\t// const MinMin = (minutes < 10) ? `0${minutes}` : minutes;\n\t// const SS = (seconds < 10) ? `0${seconds}` : seconds;\n\n\treturn `${MM}/${DD}/${YYYY}`\n}\n",
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",
289
+ "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\"SaaS platform\",\n\t\"team productivity\",\n\t\"workflow automation\",\n\t\"business software\",\n\t\"collaboration tools\",\n\t\"project management\",\n\t\"team collaboration\",\n\t\"business automation\",\n\t\"startup tools\",\n\t\"productivity software\",\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} | SaaSify`\n\t\t: \"SaaSify - The Modern Platform for Growing Teams\"\n\n\tconst description =\n\t\tdoc?.meta?.description ||\n\t\t\"Streamline workflows, boost productivity, and scale your business with one powerful platform. The modern solution for teams that want to work smarter.\"\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: \"SaaSify\", url: serverUrl }],\n\t\tcreator: \"SaaSify\",\n\t\tpublisher: \"SaaSify\",\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
290
  "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
291
  "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
292
  "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",
@@ -290,7 +294,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
290
294
  "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
295
  "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",
292
296
  "marketing/payload/src/utilities/getURL.ts": "import canUseDOM from \"./canUseDOM\"\n\nexport const getServerSideURL = () => {\n\treturn (\n\t\tprocess.env.NEXT_PUBLIC_SERVER_URL ||\n\t\t(process.env.VERCEL_PROJECT_PRODUCTION_URL\n\t\t\t? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`\n\t\t\t: \"http://localhost:3000\")\n\t)\n}\n\nexport const getClientSideURL = () => {\n\tif (canUseDOM) {\n\t\tconst protocol = window.location.protocol\n\t\tconst domain = window.location.hostname\n\t\tconst port = window.location.port\n\n\t\treturn `${protocol}//${domain}${port ? `:${port}` : \"\"}`\n\t}\n\n\tif (process.env.VERCEL_PROJECT_PRODUCTION_URL) {\n\t\treturn `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`\n\t}\n\n\treturn process.env.NEXT_PUBLIC_SERVER_URL || \"\"\n}\n",
293
- "marketing/payload/src/utilities/mergeOpenGraph.ts": "import type { Metadata } from \"next\"\nimport { getServerSideURL } from \"./getURL\"\n\nconst defaultOpenGraph: Metadata[\"openGraph\"] = {\n\ttype: \"website\",\n\tdescription:\n\t\t\"Launch a profitable directory business in minutes. Create, monetize, and grow with DirectoryHub.\",\n\timages: [\n\t\t{\n\t\t\turl: `${getServerSideURL()}/website-template-OG.webp`,\n\t\t\twidth: 1200,\n\t\t\theight: 630,\n\t\t\talt: \"DirectoryHub - Create. Monetize. Grow.\",\n\t\t},\n\t],\n\tsiteName: \"DirectoryHub\",\n\ttitle: \"DirectoryHub - Launch a Profitable Directory Business in Minutes\",\n}\n\nexport const mergeOpenGraph = (og?: Metadata[\"openGraph\"]): Metadata[\"openGraph\"] => {\n\treturn {\n\t\t...defaultOpenGraph,\n\t\t...og,\n\t\timages: og?.images ? og.images : defaultOpenGraph.images,\n\t}\n}\n",
297
+ "marketing/payload/src/utilities/mergeOpenGraph.ts": "import type { Metadata } from \"next\"\nimport { getServerSideURL } from \"./getURL\"\n\nconst defaultOpenGraph: Metadata[\"openGraph\"] = {\n\ttype: \"website\",\n\tdescription:\n\t\t\"Streamline workflows, boost productivity, and scale your business with one powerful platform.\",\n\timages: [\n\t\t{\n\t\t\turl: `${getServerSideURL()}/website-template-OG.webp`,\n\t\t\twidth: 1200,\n\t\t\theight: 630,\n\t\t\talt: \"SaaSify - The Modern Platform for Growing Teams\",\n\t\t},\n\t],\n\tsiteName: \"SaaSify\",\n\ttitle: \"SaaSify - The Modern Platform for Growing Teams\",\n}\n\nexport const mergeOpenGraph = (og?: Metadata[\"openGraph\"]): Metadata[\"openGraph\"] => {\n\treturn {\n\t\t...defaultOpenGraph,\n\t\t...og,\n\t\timages: og?.images ? og.images : defaultOpenGraph.images,\n\t}\n}\n",
294
298
  "marketing/payload/src/utilities/toKebabCase.ts": "export const toKebabCase = (string: string): string =>\n\tstring\n\t\t?.replace(/([a-z])([A-Z])/g, \"$1-$2\")\n\t\t.replace(/\\s+/g, \"-\")\n\t\t.toLowerCase()\n",
295
299
  "marketing/payload/src/utilities/ui.ts": "/**\n * Utility functions for UI components automatically added by ShadCN and used in a few of our frontend components and blocks.\n *\n * Other functions may be exported from here in the future or by installing other shadcn components.\n */\n\nimport { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs))\n}\n",
296
300
  "marketing/payload/src/utilities/useClickableCard.ts": "\"use client\"\nimport type { RefObject } from \"react\"\n\nimport { useRouter } from \"next/navigation\"\nimport { useCallback, useEffect, useRef } from \"react\"\n\ntype UseClickableCardType<T extends HTMLElement> = {\n\tcard: {\n\t\tref: RefObject<T | null>\n\t}\n\tlink: {\n\t\tref: RefObject<HTMLAnchorElement | null>\n\t}\n}\n\ninterface Props {\n\texternal?: boolean\n\tnewTab?: boolean\n\tscroll?: boolean\n}\n\nfunction useClickableCard<T extends HTMLElement>({\n\texternal = false,\n\tnewTab = false,\n\tscroll = true,\n}: Props): UseClickableCardType<T> {\n\tconst router = useRouter()\n\tconst card = useRef<T>(null)\n\tconst link = useRef<HTMLAnchorElement>(null)\n\tconst timeDown = useRef<number>(0)\n\tconst hasActiveParent = useRef<boolean>(false)\n\tconst pressedButton = useRef<number>(0)\n\n\tconst handleMouseDown = useCallback(\n\t\t(e: MouseEvent) => {\n\t\t\tif (e.target) {\n\t\t\t\tconst target = e.target as Element\n\n\t\t\t\tconst timeNow = +new Date()\n\t\t\t\tconst parent = target?.closest(\"a\")\n\n\t\t\t\tpressedButton.current = e.button\n\n\t\t\t\tif (!parent) {\n\t\t\t\t\thasActiveParent.current = false\n\t\t\t\t\ttimeDown.current = timeNow\n\t\t\t\t} else {\n\t\t\t\t\thasActiveParent.current = true\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t\t[router, card, link, timeDown],\n\t)\n\n\tconst handleMouseUp = useCallback(\n\t\t(e: MouseEvent) => {\n\t\t\tif (link.current?.href) {\n\t\t\t\tconst timeNow = +new Date()\n\t\t\t\tconst difference = timeNow - timeDown.current\n\n\t\t\t\tif (link.current?.href && difference <= 250) {\n\t\t\t\t\tif (!hasActiveParent.current && pressedButton.current === 0 && !e.ctrlKey) {\n\t\t\t\t\t\tif (external) {\n\t\t\t\t\t\t\tconst target = newTab ? \"_blank\" : \"_self\"\n\t\t\t\t\t\t\twindow.open(link.current.href, target)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trouter.push(link.current.href, { scroll })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t\t[router, card, link, timeDown],\n\t)\n\n\tuseEffect(() => {\n\t\tconst cardNode = card.current\n\n\t\tconst abortController = new AbortController()\n\n\t\tif (cardNode) {\n\t\t\tcardNode.addEventListener(\"mousedown\", handleMouseDown, {\n\t\t\t\tsignal: abortController.signal,\n\t\t\t})\n\t\t\tcardNode.addEventListener(\"mouseup\", handleMouseUp, {\n\t\t\t\tsignal: abortController.signal,\n\t\t\t})\n\t\t}\n\n\t\treturn () => {\n\t\t\tabortController.abort()\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [card, link, router])\n\n\treturn {\n\t\tcard: {\n\t\t\tref: card,\n\t\t},\n\t\tlink: {\n\t\t\tref: link,\n\t\t},\n\t}\n}\n\nexport default useClickableCard\n",