kofi-stack-template-generator 2.1.46 → 2.1.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
 
2
- > kofi-stack-template-generator@2.1.46 build /Users/theodenanyoh/Documents/Krumalabs/create-kofi-stack-v2/packages/template-generator
2
+ > kofi-stack-template-generator@2.1.48 build /Users/theodenanyoh/Documents/Krumalabs/create-kofi-stack-v2/packages/template-generator
3
3
  > pnpm run prebuild && tsup src/index.ts --format esm --dts
4
4
 
5
5
 
6
- > kofi-stack-template-generator@2.1.46 prebuild /Users/theodenanyoh/Documents/Krumalabs/create-kofi-stack-v2/packages/template-generator
6
+ > kofi-stack-template-generator@2.1.48 prebuild /Users/theodenanyoh/Documents/Krumalabs/create-kofi-stack-v2/packages/template-generator
7
7
  > node scripts/generate-templates.js
8
8
 
9
9
  Generating templates.generated.ts...
@@ -14,7 +14,7 @@ CLI tsup v8.5.1
14
14
  CLI Target: es2022
15
15
  ESM Build start
16
16
  ESM dist/index.js 2.27 MB
17
- ESM ⚡️ Build success in 64ms
17
+ ESM ⚡️ Build success in 51ms
18
18
  DTS Build start
19
- DTS ⚡️ Build success in 498ms
19
+ DTS ⚡️ Build success in 528ms
20
20
  DTS dist/index.d.ts 2.96 KB
package/dist/index.js CHANGED
@@ -1417,13 +1417,13 @@ GCS_CREDENTIALS="{}" # JSON service account key
1417
1417
  "marketing/payload/next.config.ts.hbs": "import { withPayload } from '@payloadcms/next/withPayload'\nimport type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n transpilePackages: ['@repo/ui'],\n}\n\nexport default withPayload(nextConfig)\n",
1418
1418
  "marketing/payload/package.json.hbs": '{\n "name": "@repo/marketing",\n "version": "0.1.0",\n "private": true,\n "type": "module",\n "scripts": {\n "build": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap && next build",\n "dev": "cross-env NODE_OPTIONS=--no-deprecation next dev --port 3000",\n "generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",\n "generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",\n "lint": "biome check .",\n "lint:fix": "biome check --write .",\n "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",\n "start": "cross-env NODE_OPTIONS=--no-deprecation next start",\n "typecheck": "tsc --noEmit",\n "db:push": "payload migrate",\n "db:seed": "tsx src/seed.ts"\n },\n "dependencies": {\n "@payloadcms/admin-bar": "^3.0.0",\n "@payloadcms/db-postgres": "^3.0.0",\n "@payloadcms/email-resend": "^3.0.0",\n "@payloadcms/live-preview-react": "^3.0.0",\n "@payloadcms/next": "^3.0.0",\n "@payloadcms/plugin-form-builder": "^3.0.0",\n "@payloadcms/plugin-nested-docs": "^3.0.0",\n "@payloadcms/plugin-redirects": "^3.0.0",\n "@payloadcms/plugin-search": "^3.0.0",\n "@payloadcms/plugin-seo": "^3.0.0",\n "@payloadcms/richtext-lexical": "^3.0.0",\n "@payloadcms/storage-vercel-blob": "^3.0.0",\n "@payloadcms/ui": "^3.0.0",\n "@radix-ui/react-accordion": "^1.2.0",\n "@radix-ui/react-checkbox": "^1.0.4",\n "@radix-ui/react-label": "^2.0.2",\n "@radix-ui/react-select": "^2.0.0",\n "@radix-ui/react-slot": "^1.0.2",\n "@repo/ui": "workspace:*",\n "class-variance-authority": "^0.7.1",\n "clsx": "^2.1.1",\n "cross-env": "^7.0.3",\n "geist": "^1.3.0",\n "lucide-react": "^0.562.0",\n "next": "^15.4.10",\n "payload": "^3.70.0",\n "posthog-js": "^1.200.0",\n "prism-react-renderer": "^2.4.1",\n "react": "^19.0.0",\n "react-dom": "^19.0.0",\n "react-hook-form": "^7.71.1",\n "sharp": "^0.34.0",\n "stripe": "^17.7.0",\n "tailwind-merge": "^3.4.0"\n },\n "devDependencies": {\n "@repo/config-typescript": "workspace:*",\n "@tailwindcss/postcss": "^4.0.0",\n "@tailwindcss/typography": "^0.5.19",\n "@types/node": "^20.0.0",\n "@types/react": "^19.0.0",\n "@types/react-dom": "^19.0.0",\n "postcss": "^8.4.0",\n "sass": "^1.86.0",\n "tailwindcss": "^4.0.0",\n "tsx": "^4.0.0",\n "tw-animate-css": "^1.4.0",\n "typescript": "^5.0.0"\n }\n}\n',
1419
1419
  "marketing/payload/postcss.config.mjs.hbs": "export default {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n}\n",
1420
- "marketing/payload/src/Footer/Component.client.tsx": '"use client"\n\nimport Link from "next/link"\nimport type React from "react"\nimport { useState } from "react"\n\nimport type { Footer, Page, Post } from "@/payload-types"\n\nimport { Logo } from "@/components/Logo/Logo"\nimport { Button } from "@/components/ui/button"\nimport { Input } from "@/components/ui/input"\nimport { ThemeSelector } from "@/providers/Theme/ThemeSelector"\nimport { cn } from "@/utilities/ui"\n\ninterface FooterClientProps {\n data: Footer\n}\n\n// Social icons as inline SVGs for flexibility\nconst SocialIcons = {\n twitter: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />\n </svg>\n ),\n instagram: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z"\n clipRule="evenodd"\n />\n </svg>\n ),\n linkedin: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />\n </svg>\n ),\n github: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"\n clipRule="evenodd"\n />\n </svg>\n ),\n youtube: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M19.812 5.418c.861.23 1.538.907 1.768 1.768C21.998 8.746 22 12 22 12s0 3.255-.418 4.814a2.504 2.504 0 0 1-1.768 1.768c-1.56.419-7.814.419-7.814.419s-6.255 0-7.814-.419a2.505 2.505 0 0 1-1.768-1.768C2 15.255 2 12 2 12s0-3.255.417-4.814a2.507 2.507 0 0 1 1.768-1.768C5.744 5 11.998 5 11.998 5s6.255 0 7.814.418ZM15.194 12 10 15V9l5.194 3Z"\n clipRule="evenodd"\n />\n </svg>\n ),\n}\n\n// Helper function to get URL from link\nfunction getLinkUrl(link: {\n type?: ("reference" | "custom") | null\n reference?:\n | { relationTo: "pages"; value: number | Page }\n | { relationTo: "posts"; value: number | Post }\n | null\n url?: string | null\n}): string {\n if (link.type === "reference" && link.reference) {\n const value = link.reference.value\n if (typeof value === "object" && "slug" in value) {\n const prefix = link.reference.relationTo === "posts" ? "/posts" : ""\n return `${prefix}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\nexport const FooterClient: React.FC<FooterClientProps> = ({ data }) => {\n const currentYear = new Date().getFullYear()\n const [email, setEmail] = useState("")\n const [isSubmitting, setIsSubmitting] = useState(false)\n const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null)\n\n const { columns, socialLinks, newsletter, copyrightText, bottomLinks } = data\n\n const handleNewsletterSubmit = async (e: React.FormEvent) => {\n e.preventDefault()\n if (!email || isSubmitting) return\n\n setIsSubmitting(true)\n setMessage(null)\n\n try {\n const response = await fetch("/api/newsletter", {\n method: "POST",\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify({ email }),\n })\n\n const result = await response.json()\n\n if (result.success) {\n setMessage({ type: "success", text: result.message })\n setEmail("")\n } else {\n setMessage({ type: "error", text: result.message })\n }\n } catch {\n setMessage({ type: "error", text: "Something went wrong. Please try again." })\n } finally {\n setIsSubmitting(false)\n }\n }\n\n // Check if any social links are configured\n const hasSocialLinks =\n socialLinks?.twitter ||\n socialLinks?.instagram ||\n socialLinks?.linkedin ||\n socialLinks?.github ||\n socialLinks?.youtube\n\n return (\n <footer className="mt-auto border-t border-border bg-background">\n {/* Main Footer Content */}\n <div className="container mx-auto px-4 py-12 lg:py-16">\n <div className="grid grid-cols-2 gap-8 md:grid-cols-3 lg:grid-cols-6">\n {/* Link Columns */}\n {columns?.map((column) => (\n <div key={column.id || column.title} className="col-span-1">\n <h3 className="mb-4 text-sm font-medium text-foreground">{column.title}</h3>\n <ul className="space-y-3">\n {column.links?.map((linkItem) => (\n <li key={linkItem.id || linkItem.link.label}>\n <Link\n href={getLinkUrl(linkItem.link)}\n className="text-sm text-muted-foreground transition-colors hover:text-foreground"\n {...(linkItem.link.newTab\n ? { target: "_blank", rel: "noopener noreferrer" }\n : {})}\n >\n {linkItem.link.label}\n </Link>\n </li>\n ))}\n </ul>\n </div>\n ))}\n\n {/* Newsletter Column */}\n {newsletter?.enabled && (\n <div className="col-span-2 md:col-span-3 lg:col-span-2">\n <h3 className="mb-4 text-sm font-medium text-foreground">\n {newsletter.title || "Newsletter"}\n </h3>\n <p className="mb-4 text-sm text-muted-foreground">\n {newsletter.description || "Stay up to date with the latest updates."}\n </p>\n <form\n onSubmit={handleNewsletterSubmit}\n className="space-y-3"\n suppressHydrationWarning\n >\n <div className="flex gap-2">\n <Input\n type="email"\n placeholder={newsletter.placeholder || "Enter your email"}\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n className="flex-1"\n disabled={isSubmitting}\n required\n suppressHydrationWarning\n />\n <Button type="submit" disabled={isSubmitting} suppressHydrationWarning>\n {isSubmitting ? "..." : newsletter.buttonText || "Subscribe"}\n </Button>\n </div>\n {message && (\n <p\n className={cn(\n "text-sm",\n message.type === "success" ? "text-green-600" : "text-red-600",\n )}\n >\n {message.text}\n </p>\n )}\n </form>\n </div>\n )}\n </div>\n\n {/* Social Links */}\n {hasSocialLinks && (\n <div className="mt-10 flex items-center gap-4 border-t border-border pt-8">\n {socialLinks?.twitter && (\n <a\n href={socialLinks.twitter}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="X (Twitter)"\n >\n {SocialIcons.twitter}\n </a>\n )}\n {socialLinks?.instagram && (\n <a\n href={socialLinks.instagram}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="Instagram"\n >\n {SocialIcons.instagram}\n </a>\n )}\n {socialLinks?.linkedin && (\n <a\n href={socialLinks.linkedin}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="LinkedIn"\n >\n {SocialIcons.linkedin}\n </a>\n )}\n {socialLinks?.github && (\n <a\n href={socialLinks.github}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="GitHub"\n >\n {SocialIcons.github}\n </a>\n )}\n {socialLinks?.youtube && (\n <a\n href={socialLinks.youtube}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="YouTube"\n >\n {SocialIcons.youtube}\n </a>\n )}\n </div>\n )}\n </div>\n\n {/* Bottom Bar */}\n <div className="border-t border-border">\n <div className="container mx-auto flex flex-col items-center justify-between gap-4 px-4 py-6 sm:flex-row">\n {/* Logo and Copyright */}\n <div className="flex items-center gap-4">\n <Link href="/" className="flex items-center">\n <Logo variant="auto" className="h-6 w-6" />\n </Link>\n <p className="text-sm text-muted-foreground">\n \xA9 {currentYear} {copyrightText || "DirectoryHub"}\n </p>\n </div>\n\n {/* Bottom Links and Theme Selector */}\n <div className="flex items-center gap-6">\n {bottomLinks?.map((linkItem) => (\n <Link\n key={linkItem.id || linkItem.link.label}\n href={getLinkUrl(linkItem.link)}\n className="text-sm text-muted-foreground transition-colors hover:text-foreground"\n {...(linkItem.link.newTab ? { target: "_blank", rel: "noopener noreferrer" } : {})}\n >\n {linkItem.link.label}\n </Link>\n ))}\n <ThemeSelector />\n </div>\n </div>\n </div>\n </footer>\n )\n}\n',
1421
- "marketing/payload/src/Footer/Component.tsx": 'import { getCachedGlobal } from "@/utilities/getGlobals"\n\nimport type { Footer as FooterType } from "@/payload-types"\n\nimport { FooterClient } from "./Component.client"\n\nexport async function Footer() {\n const footerData: FooterType = await getCachedGlobal("footer", 1)()\n\n return <FooterClient data={footerData} />\n}\n',
1420
+ "marketing/payload/src/Footer/Component.client.tsx": '"use client"\n\nimport Link from "next/link"\nimport type React from "react"\nimport { useState } from "react"\n\nimport type { Footer, Page, Post } from "@/payload-types"\n\nimport { Logo } from "@/components/Logo/Logo"\nimport { Button } from "@/components/ui/button"\nimport { Input } from "@/components/ui/input"\nimport { ThemeSelector } from "@/providers/Theme/ThemeSelector"\nimport { cn } from "@/utilities/ui"\n\ninterface FooterClientProps {\n data: Footer | null\n}\n\n// Social icons as inline SVGs for flexibility\nconst SocialIcons = {\n twitter: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />\n </svg>\n ),\n instagram: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z"\n clipRule="evenodd"\n />\n </svg>\n ),\n linkedin: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />\n </svg>\n ),\n github: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"\n clipRule="evenodd"\n />\n </svg>\n ),\n youtube: (\n <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">\n <path\n fillRule="evenodd"\n d="M19.812 5.418c.861.23 1.538.907 1.768 1.768C21.998 8.746 22 12 22 12s0 3.255-.418 4.814a2.504 2.504 0 0 1-1.768 1.768c-1.56.419-7.814.419-7.814.419s-6.255 0-7.814-.419a2.505 2.505 0 0 1-1.768-1.768C2 15.255 2 12 2 12s0-3.255.417-4.814a2.507 2.507 0 0 1 1.768-1.768C5.744 5 11.998 5 11.998 5s6.255 0 7.814.418ZM15.194 12 10 15V9l5.194 3Z"\n clipRule="evenodd"\n />\n </svg>\n ),\n}\n\n// Helper function to get URL from link\nfunction getLinkUrl(link: {\n type?: ("reference" | "custom") | null\n reference?:\n | { relationTo: "pages"; value: number | Page }\n | { relationTo: "posts"; value: number | Post }\n | null\n url?: string | null\n}): string {\n if (link.type === "reference" && link.reference) {\n const value = link.reference.value\n if (typeof value === "object" && "slug" in value) {\n const prefix = link.reference.relationTo === "posts" ? "/posts" : ""\n return `${prefix}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\nexport const FooterClient: React.FC<FooterClientProps> = ({ data }) => {\n const currentYear = new Date().getFullYear()\n const [email, setEmail] = useState("")\n const [isSubmitting, setIsSubmitting] = useState(false)\n const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null)\n\n // Show minimal footer when database isn\'t initialized yet\n if (!data) {\n return (\n <footer className="mt-auto border-t border-border bg-background">\n <div className="container mx-auto flex flex-col items-center justify-between gap-4 px-4 py-6 sm:flex-row">\n <p className="text-sm text-muted-foreground">\n Site not configured yet. Visit <Link href="/admin" className="underline hover:text-foreground">/admin</Link> to set up.\n </p>\n <ThemeSelector />\n </div>\n </footer>\n )\n }\n\n const { columns, socialLinks, newsletter, copyrightText, bottomLinks } = data\n\n const handleNewsletterSubmit = async (e: React.FormEvent) => {\n e.preventDefault()\n if (!email || isSubmitting) return\n\n setIsSubmitting(true)\n setMessage(null)\n\n try {\n const response = await fetch("/api/newsletter", {\n method: "POST",\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify({ email }),\n })\n\n const result = await response.json()\n\n if (result.success) {\n setMessage({ type: "success", text: result.message })\n setEmail("")\n } else {\n setMessage({ type: "error", text: result.message })\n }\n } catch {\n setMessage({ type: "error", text: "Something went wrong. Please try again." })\n } finally {\n setIsSubmitting(false)\n }\n }\n\n // Check if any social links are configured\n const hasSocialLinks =\n socialLinks?.twitter ||\n socialLinks?.instagram ||\n socialLinks?.linkedin ||\n socialLinks?.github ||\n socialLinks?.youtube\n\n return (\n <footer className="mt-auto border-t border-border bg-background">\n {/* Main Footer Content */}\n <div className="container mx-auto px-4 py-12 lg:py-16">\n <div className="grid grid-cols-2 gap-8 md:grid-cols-3 lg:grid-cols-6">\n {/* Link Columns */}\n {columns?.map((column) => (\n <div key={column.id || column.title} className="col-span-1">\n <h3 className="mb-4 text-sm font-medium text-foreground">{column.title}</h3>\n <ul className="space-y-3">\n {column.links?.map((linkItem) => (\n <li key={linkItem.id || linkItem.link.label}>\n <Link\n href={getLinkUrl(linkItem.link)}\n className="text-sm text-muted-foreground transition-colors hover:text-foreground"\n {...(linkItem.link.newTab\n ? { target: "_blank", rel: "noopener noreferrer" }\n : {})}\n >\n {linkItem.link.label}\n </Link>\n </li>\n ))}\n </ul>\n </div>\n ))}\n\n {/* Newsletter Column */}\n {newsletter?.enabled && (\n <div className="col-span-2 md:col-span-3 lg:col-span-2">\n <h3 className="mb-4 text-sm font-medium text-foreground">\n {newsletter.title || "Newsletter"}\n </h3>\n <p className="mb-4 text-sm text-muted-foreground">\n {newsletter.description || "Stay up to date with the latest updates."}\n </p>\n <form\n onSubmit={handleNewsletterSubmit}\n className="space-y-3"\n suppressHydrationWarning\n >\n <div className="flex gap-2">\n <Input\n type="email"\n placeholder={newsletter.placeholder || "Enter your email"}\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n className="flex-1"\n disabled={isSubmitting}\n required\n suppressHydrationWarning\n />\n <Button type="submit" disabled={isSubmitting} suppressHydrationWarning>\n {isSubmitting ? "..." : newsletter.buttonText || "Subscribe"}\n </Button>\n </div>\n {message && (\n <p\n className={cn(\n "text-sm",\n message.type === "success" ? "text-green-600" : "text-red-600",\n )}\n >\n {message.text}\n </p>\n )}\n </form>\n </div>\n )}\n </div>\n\n {/* Social Links */}\n {hasSocialLinks && (\n <div className="mt-10 flex items-center gap-4 border-t border-border pt-8">\n {socialLinks?.twitter && (\n <a\n href={socialLinks.twitter}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="X (Twitter)"\n >\n {SocialIcons.twitter}\n </a>\n )}\n {socialLinks?.instagram && (\n <a\n href={socialLinks.instagram}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="Instagram"\n >\n {SocialIcons.instagram}\n </a>\n )}\n {socialLinks?.linkedin && (\n <a\n href={socialLinks.linkedin}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="LinkedIn"\n >\n {SocialIcons.linkedin}\n </a>\n )}\n {socialLinks?.github && (\n <a\n href={socialLinks.github}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="GitHub"\n >\n {SocialIcons.github}\n </a>\n )}\n {socialLinks?.youtube && (\n <a\n href={socialLinks.youtube}\n target="_blank"\n rel="noopener noreferrer"\n className="text-muted-foreground transition-colors hover:text-foreground"\n aria-label="YouTube"\n >\n {SocialIcons.youtube}\n </a>\n )}\n </div>\n )}\n </div>\n\n {/* Bottom Bar */}\n <div className="border-t border-border">\n <div className="container mx-auto flex flex-col items-center justify-between gap-4 px-4 py-6 sm:flex-row">\n {/* Logo and Copyright */}\n <div className="flex items-center gap-4">\n <Link href="/" className="flex items-center">\n <Logo variant="auto" className="h-6 w-6" />\n </Link>\n <p className="text-sm text-muted-foreground">\n \xA9 {currentYear} {copyrightText || "DirectoryHub"}\n </p>\n </div>\n\n {/* Bottom Links and Theme Selector */}\n <div className="flex items-center gap-6">\n {bottomLinks?.map((linkItem) => (\n <Link\n key={linkItem.id || linkItem.link.label}\n href={getLinkUrl(linkItem.link)}\n className="text-sm text-muted-foreground transition-colors hover:text-foreground"\n {...(linkItem.link.newTab ? { target: "_blank", rel: "noopener noreferrer" } : {})}\n >\n {linkItem.link.label}\n </Link>\n ))}\n <ThemeSelector />\n </div>\n </div>\n </div>\n </footer>\n )\n}\n',
1421
+ "marketing/payload/src/Footer/Component.tsx": 'import { getCachedGlobal } from "@/utilities/getGlobals"\n\nimport type { Footer as FooterType } from "@/payload-types"\n\nimport { FooterClient } from "./Component.client"\n\nexport async function Footer() {\n const footerData = (await getCachedGlobal("footer", 1)()) as FooterType | null\n\n return <FooterClient data={footerData} />\n}\n',
1422
1422
  "marketing/payload/src/Footer/RowLabel.tsx": '"use client"\nimport type { Footer } from "@/payload-types"\nimport { type RowLabelProps, useRowLabel } from "@payloadcms/ui"\n\ntype FooterLink = NonNullable<NonNullable<Footer["columns"]>[number]["links"]>[number]\n\nexport const RowLabel: React.FC<RowLabelProps> = () => {\n const data = useRowLabel<FooterLink>()\n\n const label = data?.data?.link?.label\n ? `Link ${data.rowNumber !== undefined ? data.rowNumber + 1 : ""}: ${data?.data?.link?.label}`\n : "Row"\n\n return <div>{label}</div>\n}\n',
1423
1423
  "marketing/payload/src/Footer/config.ts": 'import type { GlobalConfig } from "payload"\n\nimport { link } from "@/fields/link"\nimport { revalidateFooter } from "./hooks/revalidateFooter"\n\nexport const Footer: GlobalConfig = {\n slug: "footer",\n access: {\n read: () => true,\n },\n fields: [\n // Link Columns - organized groups of links\n {\n name: "columns",\n type: "array",\n label: "Link Columns",\n maxRows: 5,\n admin: {\n initCollapsed: true,\n description: "Add columns of links (e.g., Solutions, Resources, Company)",\n },\n fields: [\n {\n name: "title",\n type: "text",\n required: true,\n label: "Column Title",\n },\n {\n name: "links",\n type: "array",\n label: "Links",\n maxRows: 10,\n fields: [\n link({\n appearances: false,\n }),\n ],\n admin: {\n initCollapsed: true,\n },\n },\n ],\n },\n // Social Links\n {\n name: "socialLinks",\n type: "group",\n label: "Social Links",\n admin: {\n description: "Add your social media profile URLs",\n },\n fields: [\n {\n name: "twitter",\n type: "text",\n label: "X (Twitter)",\n admin: {\n placeholder: "https://x.com/yourhandle",\n },\n },\n {\n name: "instagram",\n type: "text",\n label: "Instagram",\n admin: {\n placeholder: "https://instagram.com/yourhandle",\n },\n },\n {\n name: "linkedin",\n type: "text",\n label: "LinkedIn",\n admin: {\n placeholder: "https://linkedin.com/company/yourcompany",\n },\n },\n {\n name: "github",\n type: "text",\n label: "GitHub",\n admin: {\n placeholder: "https://github.com/yourorg",\n },\n },\n {\n name: "youtube",\n type: "text",\n label: "YouTube",\n admin: {\n placeholder: "https://youtube.com/@yourchannel",\n },\n },\n ],\n },\n // Newsletter Section\n {\n name: "newsletter",\n type: "group",\n label: "Newsletter",\n admin: {\n description: "Configure the newsletter signup section",\n },\n fields: [\n {\n name: "enabled",\n type: "checkbox",\n label: "Enable Newsletter Signup",\n defaultValue: true,\n },\n {\n name: "title",\n type: "text",\n label: "Title",\n defaultValue: "Newsletter",\n admin: {\n condition: (_, siblingData) => siblingData?.enabled,\n },\n },\n {\n name: "description",\n type: "textarea",\n label: "Description",\n defaultValue: "Stay up to date with the latest updates and news.",\n admin: {\n condition: (_, siblingData) => siblingData?.enabled,\n },\n },\n {\n name: "buttonText",\n type: "text",\n label: "Button Text",\n defaultValue: "Subscribe",\n admin: {\n condition: (_, siblingData) => siblingData?.enabled,\n },\n },\n {\n name: "placeholder",\n type: "text",\n label: "Email Placeholder",\n defaultValue: "Enter your email",\n admin: {\n condition: (_, siblingData) => siblingData?.enabled,\n },\n },\n ],\n },\n // Copyright and Bottom Bar\n {\n name: "copyrightText",\n type: "text",\n label: "Copyright Text",\n defaultValue: "DirectoryHub",\n admin: {\n description: "Company name for copyright (year is added automatically)",\n },\n },\n {\n name: "bottomLinks",\n type: "array",\n label: "Bottom Bar Links",\n maxRows: 4,\n admin: {\n description: "Links shown in the bottom bar (e.g., Contact Support, Privacy Policy)",\n initCollapsed: true,\n },\n fields: [\n link({\n appearances: false,\n }),\n ],\n },\n ],\n hooks: {\n afterChange: [revalidateFooter],\n },\n}\n',
1424
1424
  "marketing/payload/src/Footer/hooks/revalidateFooter.ts": 'import type { GlobalAfterChangeHook } from "payload"\n\nimport { revalidateTag } from "next/cache"\n\nexport const revalidateFooter: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {\n if (!context.disableRevalidate) {\n payload.logger.info("Revalidating footer")\n\n revalidateTag("global_footer")\n }\n\n return doc\n}\n',
1425
- "marketing/payload/src/Header/Component.client.tsx": '"use client"\nimport { useHeaderTheme } from "@/providers/HeaderTheme"\nimport Link from "next/link"\nimport type React from "react"\nimport { useEffect, useMemo, useState } from "react"\n\nimport type { Header, Page, Post } from "@/payload-types"\n\nimport { Logo } from "@/components/Logo/Logo"\nimport { Button } from "@/components/ui/button"\nimport { MobileMenu } from "./MobileMenu"\nimport { HeaderNav } from "./Nav"\n\ninterface HeaderClientProps {\n data: Header\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n type?: "custom" | "reference" | null\n reference?: {\n relationTo: "pages" | "posts"\n value: Page | Post | string | number\n } | null\n url?: string | null\n}): string => {\n if (link.type === "reference" && link.reference) {\n const { relationTo, value } = link.reference\n if (typeof value === "object" && value.slug) {\n return relationTo === "pages" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\nexport const HeaderClient: React.FC<HeaderClientProps> = ({ data }) => {\n /* Storing the value in a useState to avoid hydration errors */\n const [theme, setTheme] = useState<string | null>(null)\n const { headerTheme, setHeaderTheme } = useHeaderTheme()\n\n useEffect(() => {\n setHeaderTheme(null)\n }, [setHeaderTheme])\n\n useEffect(() => {\n if (headerTheme && headerTheme !== theme) setTheme(headerTheme)\n }, [headerTheme, theme])\n\n // Get the primary CTA (first right-positioned button item) for mobile view\n const primaryCta = useMemo(() => {\n const rightButtonItems = data?.navItems?.filter(\n (item) =>\n (item.position || "left") === "right" &&\n item.type === "link" &&\n (item.appearance || "button") === "button",\n )\n return rightButtonItems?.[0]\n }, [data?.navItems])\n\n return (\n <header\n className="sticky top-0 z-20 border-b border-border bg-background"\n {...(theme ? { "data-theme": theme } : {})}\n >\n <div className="container mx-auto px-4 h-16 flex justify-between items-center">\n {/* Left section: Logo + Left Nav Links */}\n <div className="flex items-center gap-8">\n <Link href="/" className="flex items-center gap-2">\n <Logo loading="eager" priority="high" variant="auto" />\n <span className="text-xl font-semibold hidden sm:inline">DirectoryHub</span>\n </Link>\n {/* Left nav - hidden on tablet and below, visible on desktop */}\n <HeaderNav data={data} position="left" className="hidden lg:flex gap-6 items-center" />\n </div>\n\n {/* Right section: Right Nav Links (CTAs) */}\n <div className="flex items-center gap-3">\n {/* Full right nav - hidden on mobile and tablet, visible on desktop */}\n <HeaderNav data={data} position="right" className="hidden lg:flex gap-4 items-center" />\n\n {/* Primary CTA for tablet/mobile - visible on tablet and below, hidden on desktop */}\n {primaryCta?.link && (\n <Button asChild className="lg:hidden" size="sm">\n <Link href={getLinkHref(primaryCta.link)}>{primaryCta.label}</Link>\n </Button>\n )}\n\n {/* Mobile menu hamburger - visible on tablet and below */}\n <MobileMenu data={data} />\n </div>\n </div>\n </header>\n )\n}\n',
1426
- "marketing/payload/src/Header/Component.tsx": 'import { getCachedGlobal } from "@/utilities/getGlobals"\nimport { HeaderClient } from "./Component.client"\n\nimport type { Header as HeaderType } from "@/payload-types"\n\nexport async function Header() {\n const headerData: HeaderType = await getCachedGlobal("header", 1)()\n\n return <HeaderClient data={headerData} />\n}\n',
1425
+ "marketing/payload/src/Header/Component.client.tsx": '"use client"\nimport { useHeaderTheme } from "@/providers/HeaderTheme"\nimport Link from "next/link"\nimport type React from "react"\nimport { useEffect, useMemo, useState } from "react"\n\nimport type { Header, Page, Post } from "@/payload-types"\n\nimport { Logo } from "@/components/Logo/Logo"\nimport { Button } from "@/components/ui/button"\nimport { MobileMenu } from "./MobileMenu"\nimport { HeaderNav } from "./Nav"\n\ninterface HeaderClientProps {\n data: Header | null\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n type?: "custom" | "reference" | null\n reference?: {\n relationTo: "pages" | "posts"\n value: Page | Post | string | number\n } | null\n url?: string | null\n}): string => {\n if (link.type === "reference" && link.reference) {\n const { relationTo, value } = link.reference\n if (typeof value === "object" && value.slug) {\n return relationTo === "pages" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\nexport const HeaderClient: React.FC<HeaderClientProps> = ({ data }) => {\n /* Storing the value in a useState to avoid hydration errors */\n const [theme, setTheme] = useState<string | null>(null)\n const { headerTheme, setHeaderTheme } = useHeaderTheme()\n\n useEffect(() => {\n setHeaderTheme(null)\n }, [setHeaderTheme])\n\n useEffect(() => {\n if (headerTheme && headerTheme !== theme) setTheme(headerTheme)\n }, [headerTheme, theme])\n\n // Get the primary CTA (first right-positioned button item) for mobile view\n const primaryCta = useMemo(() => {\n const rightButtonItems = data?.navItems?.filter(\n (item) =>\n (item.position || "left") === "right" &&\n item.type === "link" &&\n (item.appearance || "button") === "button",\n )\n return rightButtonItems?.[0]\n }, [data?.navItems])\n\n // Show setup header when database isn\'t initialized yet\n if (!data) {\n return (\n <header className="sticky top-0 z-20 border-b border-border bg-background">\n <div className="container mx-auto px-4 h-16 flex justify-between items-center">\n <div className="flex items-center gap-2">\n <span className="text-xl font-semibold">Welcome</span>\n </div>\n <Button asChild size="sm">\n <Link href="/admin">Setup Site \u2192</Link>\n </Button>\n </div>\n </header>\n )\n }\n\n return (\n <header\n className="sticky top-0 z-20 border-b border-border bg-background"\n {...(theme ? { "data-theme": theme } : {})}\n >\n <div className="container mx-auto px-4 h-16 flex justify-between items-center">\n {/* Left section: Logo + Left Nav Links */}\n <div className="flex items-center gap-8">\n <Link href="/" className="flex items-center gap-2">\n <Logo loading="eager" priority="high" variant="auto" />\n <span className="text-xl font-semibold hidden sm:inline">DirectoryHub</span>\n </Link>\n {/* Left nav - hidden on tablet and below, visible on desktop */}\n <HeaderNav data={data} position="left" className="hidden lg:flex gap-6 items-center" />\n </div>\n\n {/* Right section: Right Nav Links (CTAs) */}\n <div className="flex items-center gap-3">\n {/* Full right nav - hidden on mobile and tablet, visible on desktop */}\n <HeaderNav data={data} position="right" className="hidden lg:flex gap-4 items-center" />\n\n {/* Primary CTA for tablet/mobile - visible on tablet and below, hidden on desktop */}\n {primaryCta?.link && (\n <Button asChild className="lg:hidden" size="sm">\n <Link href={getLinkHref(primaryCta.link)}>{primaryCta.label}</Link>\n </Button>\n )}\n\n {/* Mobile menu hamburger - visible on tablet and below */}\n <MobileMenu data={data} />\n </div>\n </div>\n </header>\n )\n}\n',
1426
+ "marketing/payload/src/Header/Component.tsx": 'import { getCachedGlobal } from "@/utilities/getGlobals"\nimport { HeaderClient } from "./Component.client"\n\nimport type { Header as HeaderType } from "@/payload-types"\n\nexport async function Header() {\n const headerData = (await getCachedGlobal("header", 1)()) as HeaderType | null\n\n return <HeaderClient data={headerData} />\n}\n',
1427
1427
  "marketing/payload/src/Header/MegaMenu/index.tsx": '"use client"\n\nimport { Media } from "@/components/Media"\nimport { cn } from "@/utilities/ui"\nimport {\n BarChart3,\n Building,\n ChevronDown,\n Database,\n DollarSign,\n Globe,\n Layers,\n Layout,\n type LucideIcon,\n Rocket,\n Search,\n Settings,\n Shield,\n Store,\n Target,\n Users,\n Zap,\n} from "lucide-react"\nimport Link from "next/link"\nimport type React from "react"\nimport { useState } from "react"\n\nimport type { Header, Media as MediaType, Page, Post } from "@/payload-types"\n\ntype NavItem = NonNullable<Header["navItems"]>[number]\n\nconst iconMap: Record<string, LucideIcon> = {\n layout: Layout,\n dollarSign: DollarSign,\n search: Search,\n settings: Settings,\n zap: Zap,\n layers: Layers,\n users: Users,\n building: Building,\n globe: Globe,\n store: Store,\n rocket: Rocket,\n target: Target,\n barChart: BarChart3,\n shield: Shield,\n database: Database,\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n type?: "custom" | "reference" | null\n reference?: {\n relationTo: "pages" | "posts"\n value: Page | Post | string | number\n } | null\n url?: string | null\n}): string => {\n if (link.type === "reference" && link.reference) {\n const { relationTo, value } = link.reference\n if (typeof value === "object" && value.slug) {\n return relationTo === "pages" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\ninterface MegaMenuProps {\n item: NavItem\n}\n\nexport const MegaMenu: React.FC<MegaMenuProps> = ({ item }) => {\n const [isOpen, setIsOpen] = useState(false)\n\n const columns = item.megaMenuColumns || []\n const featuredItem = item.featuredItem\n\n return (\n <div\n className="relative"\n onMouseEnter={() => setIsOpen(true)}\n onMouseLeave={() => setIsOpen(false)}\n >\n {/* Trigger */}\n <button\n type="button"\n className={cn(\n "flex items-center gap-1 text-sm font-medium transition-colors hover:text-primary",\n isOpen && "text-primary",\n )}\n onClick={() => setIsOpen(!isOpen)}\n >\n {item.label}\n <ChevronDown\n className={cn("h-4 w-4 transition-transform duration-200", isOpen && "rotate-180")}\n />\n </button>\n\n {/* Dropdown */}\n {isOpen && (\n <div className="absolute left-1/2 -translate-x-1/2 top-full pt-4 z-50">\n <div\n className={cn(\n "bg-background border border-border rounded-xl shadow-xl overflow-hidden",\n "animate-in fade-in-0 zoom-in-95 duration-200",\n featuredItem?.enabled ? "min-w-[700px]" : "min-w-[500px]",\n )}\n >\n <div className="flex">\n {/* Menu Columns */}\n <div className={cn("flex-1 p-6", columns.length > 1 ? "grid grid-cols-2 gap-8" : "")}>\n {columns.map((column) => (\n <div key={column.columnLabel || "column"}>\n {column.columnLabel && (\n <div className="mb-4">\n <h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">\n {column.columnLabel}\n </h3>\n {column.columnDescription && (\n <p className="text-xs text-muted-foreground mt-1">\n {column.columnDescription}\n </p>\n )}\n </div>\n )}\n <ul className="space-y-1">\n {column.items?.map((menuItem) => {\n const Icon =\n menuItem.icon && menuItem.icon !== "none" ? iconMap[menuItem.icon] : null\n const href = menuItem.link ? getLinkHref(menuItem.link) : "#"\n\n return (\n <li key={menuItem.label}>\n <Link\n href={href}\n className="flex items-start gap-3 p-2 rounded-lg hover:bg-muted transition-colors group"\n onClick={() => setIsOpen(false)}\n >\n {Icon && (\n <div className="flex-shrink-0 w-9 h-9 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">\n <Icon className="w-4 h-4 text-primary" />\n </div>\n )}\n <div className="flex-1 min-w-0">\n <div className="text-sm font-medium group-hover:text-primary transition-colors">\n {menuItem.label}\n </div>\n {menuItem.description && (\n <div className="text-xs text-muted-foreground mt-0.5 line-clamp-2">\n {menuItem.description}\n </div>\n )}\n </div>\n </Link>\n </li>\n )\n })}\n </ul>\n </div>\n ))}\n </div>\n\n {/* Featured Section */}\n {featuredItem?.enabled && (\n <div className="w-64 bg-muted/50 p-6 border-l border-border">\n {featuredItem.image && typeof featuredItem.image === "object" && (\n <div className="rounded-lg overflow-hidden mb-4 aspect-video">\n <Media\n resource={featuredItem.image as MediaType}\n imgClassName="w-full h-full object-cover"\n />\n </div>\n )}\n {featuredItem.heading && (\n <h4 className="font-semibold text-sm mb-2">{featuredItem.heading}</h4>\n )}\n {featuredItem.description && (\n <p className="text-xs text-muted-foreground mb-4">{featuredItem.description}</p>\n )}\n {featuredItem.link && (\n <Link\n href={getLinkHref(featuredItem.link)}\n className="text-xs font-medium text-primary hover:underline"\n onClick={() => setIsOpen(false)}\n >\n {featuredItem.link.label || "Learn more"} \u2192\n </Link>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n )\n}\n',
1428
1428
  "marketing/payload/src/Header/MobileMenu/HamburgerIcon.tsx": '"use client"\n\nimport { cn } from "@/utilities/ui"\nimport type React from "react"\n\ninterface HamburgerIconProps {\n isOpen: boolean\n onClick: () => void\n className?: string\n}\n\n/**\n * Animated 2-line hamburger icon that morphs into an X when open\n */\nexport const HamburgerIcon: React.FC<HamburgerIconProps> = ({ isOpen, onClick, className }) => {\n return (\n <button\n type="button"\n onClick={onClick}\n className={cn(\n "relative w-8 h-8 flex items-center justify-center focus:outline-none",\n "lg:hidden", // Only show on tablet and below\n className,\n )}\n aria-label={isOpen ? "Close menu" : "Open menu"}\n aria-expanded={isOpen}\n >\n <div className="relative w-6 h-4 flex flex-col justify-between">\n {/* Top line */}\n <span\n className={cn(\n "absolute left-0 w-full h-0.5 bg-foreground rounded-full",\n "transition-all duration-300 ease-in-out origin-center",\n isOpen ? "top-1/2 -translate-y-1/2 rotate-45" : "top-0 translate-y-0 rotate-0",\n )}\n />\n {/* Bottom line */}\n <span\n className={cn(\n "absolute left-0 w-full h-0.5 bg-foreground rounded-full",\n "transition-all duration-300 ease-in-out origin-center",\n isOpen ? "top-1/2 -translate-y-1/2 -rotate-45" : "bottom-0 translate-y-0 rotate-0",\n )}\n />\n </div>\n </button>\n )\n}\n',
1429
1429
  "marketing/payload/src/Header/MobileMenu/index.tsx": '"use client"\n\nimport { Button } from "@/components/ui/button"\nimport { cn } from "@/utilities/ui"\nimport {\n BarChart3,\n Building,\n ChevronRight,\n Database,\n DollarSign,\n Globe,\n Layers,\n Layout,\n type LucideIcon,\n Rocket,\n Search,\n Settings,\n Shield,\n Store,\n Target,\n Users,\n Zap,\n} from "lucide-react"\nimport Link from "next/link"\nimport type React from "react"\nimport { useEffect, useState } from "react"\n\nimport type { Header, Page, Post } from "@/payload-types"\nimport { HamburgerIcon } from "./HamburgerIcon"\n\nconst iconMap: Record<string, LucideIcon> = {\n layout: Layout,\n dollarSign: DollarSign,\n search: Search,\n settings: Settings,\n zap: Zap,\n layers: Layers,\n users: Users,\n building: Building,\n globe: Globe,\n store: Store,\n rocket: Rocket,\n target: Target,\n barChart: BarChart3,\n shield: Shield,\n database: Database,\n}\n\n// Helper to get href from link data\nconst getLinkHref = (link: {\n type?: "custom" | "reference" | null\n reference?: {\n relationTo: "pages" | "posts"\n value: Page | Post | string | number\n } | null\n url?: string | null\n}): string => {\n if (link.type === "reference" && link.reference) {\n const { relationTo, value } = link.reference\n if (typeof value === "object" && value.slug) {\n return relationTo === "pages" ? `/${value.slug}` : `/${relationTo}/${value.slug}`\n }\n }\n return link.url || "#"\n}\n\ninterface MobileMenuProps {\n data: Header\n}\n\nexport const MobileMenu: React.FC<MobileMenuProps> = ({ data }) => {\n const [isOpen, setIsOpen] = useState(false)\n const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set())\n\n const navItems = data?.navItems || []\n const leftItems = navItems.filter((item) => (item.position || "left") === "left")\n const rightItems = navItems.filter((item) => (item.position || "left") === "right")\n\n // Separate CTA items (typically the primary action buttons)\n const ctaItems = rightItems.filter((item) => item.type === "link")\n\n // Prevent body scroll when menu is open\n useEffect(() => {\n if (isOpen) {\n document.body.style.overflow = "hidden"\n } else {\n document.body.style.overflow = ""\n }\n return () => {\n document.body.style.overflow = ""\n }\n }, [isOpen])\n\n const toggleExpanded = (id: string) => {\n setExpandedItems((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }\n\n const closeMenu = () => {\n setIsOpen(false)\n setExpandedItems(new Set())\n }\n\n return (\n <>\n {/* Hamburger button - visible on tablet and below */}\n <HamburgerIcon isOpen={isOpen} onClick={() => setIsOpen(!isOpen)} />\n\n {/* Mobile menu overlay */}\n <div\n className={cn(\n "fixed inset-0 z-50 lg:hidden",\n "transition-opacity duration-300",\n isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none",\n )}\n >\n {/* Backdrop */}\n <div\n className="absolute inset-0 bg-background/80 backdrop-blur-sm"\n onClick={closeMenu}\n onKeyDown={(e) => {\n if (e.key === "Enter" || e.key === " ") {\n e.preventDefault()\n closeMenu()\n }\n }}\n role="button"\n tabIndex={0}\n aria-label="Close menu"\n />\n\n {/* Menu panel */}\n <div\n className={cn(\n "absolute top-0 right-0 h-full w-full max-w-md bg-background border-l border-border",\n "flex flex-col",\n "transition-transform duration-300 ease-out",\n isOpen ? "translate-x-0" : "translate-x-full",\n )}\n >\n {/* Header with close button */}\n <div className="flex items-center justify-between p-4 border-b border-border">\n <Link href="/" onClick={closeMenu} className="text-xl font-semibold">\n DirectoryHub\n </Link>\n <HamburgerIcon isOpen={isOpen} onClick={closeMenu} className="lg:block" />\n </div>\n\n {/* Nav items */}\n <div className="flex-1 overflow-y-auto p-4">\n <nav className="space-y-1">\n {leftItems.map((item, index) => {\n const itemId = item.id || `item-${index}`\n const isExpanded = expandedItems.has(itemId)\n const hasMegaMenu = item.type === "megaMenu"\n\n if (hasMegaMenu) {\n return (\n <div key={itemId}>\n {/* Expandable item */}\n <button\n type="button"\n onClick={() => toggleExpanded(itemId)}\n className={cn(\n "w-full flex items-center justify-between py-4 text-lg font-medium",\n "border-b border-border/50 transition-colors hover:text-primary",\n )}\n >\n <span>{item.label}</span>\n <ChevronRight\n className={cn(\n "w-5 h-5 transition-transform duration-200",\n isExpanded && "rotate-90",\n )}\n />\n </button>\n\n {/* Expanded content */}\n <div\n className={cn(\n "overflow-hidden transition-all duration-300",\n isExpanded ? "max-h-[1000px] opacity-100" : "max-h-0 opacity-0",\n )}\n >\n <div className="py-2 pl-4 space-y-4">\n {item.megaMenuColumns?.map((column, colIndex) => (\n <div key={column.columnLabel || `col-${colIndex}`}>\n {column.columnLabel && (\n <p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-2">\n {column.columnLabel}\n </p>\n )}\n <ul className="space-y-1">\n {column.items?.map((menuItem) => {\n const Icon =\n menuItem.icon && menuItem.icon !== "none"\n ? iconMap[menuItem.icon]\n : null\n const href = menuItem.link ? getLinkHref(menuItem.link) : "#"\n\n return (\n <li key={menuItem.label}>\n <Link\n href={href}\n onClick={closeMenu}\n className="flex items-center gap-3 py-2 text-sm hover:text-primary transition-colors"\n >\n {Icon && <Icon className="w-4 h-4 text-muted-foreground" />}\n <span>{menuItem.label}</span>\n </Link>\n </li>\n )\n })}\n </ul>\n </div>\n ))}\n </div>\n </div>\n </div>\n )\n }\n\n // Simple link item\n const link = item.link\n if (!link) return null\n const href = getLinkHref(link)\n\n return (\n <Link\n key={itemId}\n href={href}\n onClick={closeMenu}\n className={cn(\n "block py-4 text-lg font-medium",\n "border-b border-border/50 transition-colors hover:text-primary",\n )}\n >\n {item.label}\n </Link>\n )\n })}\n </nav>\n </div>\n\n {/* Bottom CTA section */}\n <div className="p-4 border-t border-border space-y-3">\n {ctaItems.map((item, index) => {\n const link = item.link\n if (!link) return null\n const href = getLinkHref(link)\n const itemAppearance = item.appearance || "button"\n const isButton = itemAppearance === "button"\n\n if (isButton) {\n // First button item is primary (filled), others are outline\n const buttonItems = ctaItems.filter((i) => (i.appearance || "button") === "button")\n const buttonIndex = buttonItems.findIndex((i) => i.id === item.id)\n const isPrimary = buttonIndex === 0\n\n return (\n <Button\n key={item.id || `cta-${index}`}\n asChild\n variant={isPrimary ? "default" : "outline"}\n className="w-full"\n size="lg"\n >\n <Link href={href} onClick={closeMenu}>\n {item.label}\n </Link>\n </Button>\n )\n }\n\n // Link appearance - render as text link\n return (\n <Link\n key={item.id || `cta-${index}`}\n href={href}\n onClick={closeMenu}\n className="block w-full text-center py-3 text-sm font-medium hover:text-primary transition-colors"\n >\n {item.label}\n </Link>\n )\n })}\n </div>\n </div>\n </div>\n </>\n )\n}\n',
@@ -5346,8 +5346,8 @@ export const analyticsPage = (): Partial<Page> => {
5346
5346
  }
5347
5347
  }
5348
5348
  `,
5349
- "marketing/payload/src/endpoints/seed/directoryhub/features/seo.ts": 'import type { Page } from "@/payload-types"\nimport { createParagraph } from "../richtext-helper"\n\nexport const securityPage = (): Partial<Page> => {\n return {\n slug: "features/security",\n _status: "published",\n title: "Security & Compliance",\n hero: {\n type: "lowImpact",\n richText: {\n root: {\n type: "root",\n children: [\n {\n type: "heading",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "Enterprise-grade security without the complexity",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n tag: "h1",\n version: 1,\n },\n {\n type: "paragraph",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "SOC 2 Type II certified with end-to-end encryption, SSO support, and comprehensive audit logs. Your data is protected by the same standards used by Fortune 500 companies.",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n textFormat: 0,\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n version: 1,\n },\n },\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "View security docs",\n url: "/security",\n },\n },\n ],\n },\n layout: [\n {\n blockType: "featureShowcase",\n blockName: "Data Encryption",\n label: "Data Protection",\n headline: "Your data encrypted at rest and in transit",\n description: createParagraph(\n "AES-256 encryption protects your data at rest, while TLS 1.3 secures all data in transit. Your information is protected by the same standards used by financial institutions.",\n ),\n link: {\n type: "custom",\n label: "Learn about encryption",\n url: "/security",\n appearance: "default",\n },\n imagePosition: "right",\n features: [\n { text: "AES-256 encryption at rest" },\n { text: "TLS 1.3 for data in transit" },\n { text: "Encrypted backups" },\n { text: "Key management best practices" },\n ],\n },\n {\n blockType: "featureShowcase",\n blockName: "Access Control",\n label: "Access Management",\n headline: "Granular permissions and SSO support",\n description: createParagraph(\n "Role-based access control lets you define exactly who can see and do what. SAML SSO integration works with Okta, Azure AD, Google Workspace, and other identity providers.",\n ),\n link: {\n type: "custom",\n label: "Explore access controls",\n url: "/security",\n appearance: "default",\n },\n imagePosition: "left",\n features: [\n { text: "Role-based access control (RBAC)" },\n { text: "SAML SSO integration" },\n { text: "Two-factor authentication" },\n { text: "Session management" },\n ],\n },\n {\n blockType: "featureShowcase",\n blockName: "Compliance",\n label: "Compliance & Auditing",\n headline: "Meet compliance requirements with confidence",\n description: createParagraph(\n "SOC 2 Type II certified with comprehensive audit logs and data governance tools. Export reports for auditors and meet GDPR, CCPA, and HIPAA requirements.",\n ),\n link: {\n type: "custom",\n label: "View compliance docs",\n url: "/security",\n appearance: "default",\n },\n imagePosition: "right",\n features: [\n { text: "SOC 2 Type II certified" },\n { text: "GDPR and CCPA compliant" },\n { text: "Comprehensive audit logs" },\n { text: "Data retention controls" },\n ],\n },\n {\n blockType: "bentoFeatures",\n blockName: "Security Features",\n heading: "Security you can trust",\n subheading: "Enterprise-grade protection without enterprise complexity",\n features: [\n {\n size: "small",\n style: "gradient",\n icon: "shield",\n stat: "SOC 2",\n title: "Certified",\n description: createParagraph("Type II certification verified annually."),\n },\n {\n size: "small",\n style: "accent",\n icon: "lock",\n title: "Zero Trust",\n description: createParagraph("Verify every request, every time."),\n },\n {\n size: "small",\n style: "default",\n icon: "globe",\n title: "Global Infrastructure",\n description: createParagraph("Redundant systems across regions."),\n },\n {\n size: "small",\n style: "primary",\n icon: "database",\n title: "Backup & Recovery",\n description: createParagraph("Automated backups with point-in-time recovery."),\n },\n {\n size: "small",\n style: "default",\n icon: "search",\n title: "Threat Detection",\n description: createParagraph("24/7 monitoring for suspicious activity."),\n },\n {\n size: "small",\n style: "default",\n icon: "barChart",\n title: "Security Reports",\n description: createParagraph("Regular penetration testing and audits."),\n },\n ],\n },\n {\n blockType: "proofBanner",\n blockName: "CTA Section",\n style: "centered",\n headline: "Security that scales with you",\n subtext: "Enterprise-grade protection from day one. SOC 2 certified, GDPR compliant.",\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "Request security docs",\n url: "/contact",\n },\n },\n ],\n },\n ],\n meta: {\n description:\n "Enterprise-grade security with SOC 2 Type II certification, end-to-end encryption, SSO support, and comprehensive compliance tools.",\n title: "Security & Compliance \u2014 SaaSify Enterprise Protection",\n },\n }\n}\n',
5350
- "marketing/payload/src/endpoints/seed/directoryhub/features/templates.ts": 'import type { Page } from "@/payload-types"\nimport { createParagraph } from "../richtext-helper"\n\nexport const integrationsPage = (): Partial<Page> => {\n return {\n slug: "features/integrations",\n _status: "published",\n title: "Integrations",\n hero: {\n type: "lowImpact",\n richText: {\n root: {\n type: "root",\n children: [\n {\n type: "heading",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "Connect all your tools in one place",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n tag: "h1",\n version: 1,\n },\n {\n type: "paragraph",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "One-click connections to 100+ apps your team already uses. Sync data, automate workflows, and eliminate context switching.",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n textFormat: 0,\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n version: 1,\n },\n },\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "View all integrations",\n url: "/integrations",\n },\n },\n ],\n },\n layout: [\n {\n blockType: "featureShowcase",\n blockName: "Native Integrations",\n label: "100+ Integrations",\n headline: "Connect your favorite tools instantly",\n description: createParagraph(\n "Native integrations with Slack, Salesforce, HubSpot, Jira, Notion, Google Workspace, Microsoft 365, and more. Set up in minutes, not days.",\n ),\n link: {\n type: "custom",\n label: "Browse integrations",\n url: "/integrations",\n appearance: "default",\n },\n imagePosition: "right",\n features: [\n { text: "100+ native integrations" },\n { text: "Two-way data sync" },\n { text: "Real-time updates" },\n { text: "No coding required" },\n ],\n },\n {\n blockType: "featureShowcase",\n blockName: "Custom Integrations",\n label: "Developer Tools",\n headline: "Build custom integrations with our API",\n description: createParagraph(\n "REST API and webhooks let you connect any tool or build custom integrations. Comprehensive documentation and SDKs for popular languages.",\n ),\n link: {\n type: "custom",\n label: "View API docs",\n url: "/developers",\n appearance: "default",\n },\n imagePosition: "left",\n features: [\n { text: "RESTful API access" },\n { text: "Custom webhooks" },\n { text: "SDKs for popular languages" },\n { text: "Detailed documentation" },\n ],\n },\n {\n blockType: "bentoFeatures",\n blockName: "Integration Categories",\n heading: "Integrations for every workflow",\n subheading:\n "Connect your entire tech stack and keep data flowing seamlessly",\n features: [\n {\n size: "small",\n style: "gradient",\n icon: "messageSquare",\n title: "Communication",\n description: createParagraph(\n "Slack, Microsoft Teams, Discord, and email integrations.",\n ),\n },\n {\n size: "small",\n style: "accent",\n icon: "users",\n title: "CRM & Sales",\n description: createParagraph(\n "Salesforce, HubSpot, Pipedrive, and more.",\n ),\n },\n {\n size: "small",\n style: "default",\n icon: "folder",\n title: "Project Management",\n description: createParagraph(\n "Jira, Asana, Monday.com, Trello integrations.",\n ),\n },\n {\n size: "small",\n style: "primary",\n icon: "database",\n title: "Data & Storage",\n description: createParagraph(\n "Google Drive, Dropbox, AWS S3, and databases.",\n ),\n },\n {\n size: "small",\n style: "default",\n icon: "barChart",\n title: "Analytics",\n description: createParagraph("Google Analytics, Mixpanel, Amplitude."),\n },\n {\n size: "small",\n style: "default",\n icon: "zap",\n title: "Automation",\n description: createParagraph("Zapier, Make, n8n for custom workflows."),\n },\n ],\n },\n {\n blockType: "proofBanner",\n blockName: "CTA Section",\n style: "centered",\n headline: "Connect your tools today",\n subtext: "One-click integrations with 100+ apps. Set up in minutes, not days.",\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "View all integrations",\n url: "/integrations",\n },\n },\n ],\n },\n ],\n meta: {\n description:\n "Connect SaaSify with 100+ tools including Slack, Salesforce, HubSpot, and more. Native integrations, custom webhooks, and REST API access.",\n title: "Integrations \u2014 Connect Your Tools with SaaSify",\n },\n }\n}\n',
5349
+ "marketing/payload/src/endpoints/seed/directoryhub/features/seo.ts": 'import type { Page } from "@/payload-types"\nimport { createParagraph } from "../richtext-helper"\n\nexport const securityPage = (): Partial<Page> => {\n return {\n slug: "features/security",\n _status: "published",\n title: "Security & Compliance",\n hero: {\n type: "lowImpact",\n richText: {\n root: {\n type: "root",\n children: [\n {\n type: "heading",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "Enterprise-grade security without the complexity",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n tag: "h1",\n version: 1,\n },\n {\n type: "paragraph",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "SOC 2 Type II certified with end-to-end encryption, SSO support, and comprehensive audit logs. Your data is protected by the same standards used by Fortune 500 companies.",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n textFormat: 0,\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n version: 1,\n },\n },\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "View security docs",\n url: "/security",\n },\n },\n ],\n },\n layout: [\n {\n blockType: "featureShowcase",\n blockName: "Data Encryption",\n label: "Data Protection",\n headline: "Your data encrypted at rest and in transit",\n description: createParagraph(\n "AES-256 encryption protects your data at rest, while TLS 1.3 secures all data in transit. Your information is protected by the same standards used by financial institutions.",\n ),\n link: {\n type: "custom",\n label: "Learn about encryption",\n url: "/security",\n appearance: "default",\n },\n imagePosition: "right",\n features: [\n { text: "AES-256 encryption at rest" },\n { text: "TLS 1.3 for data in transit" },\n { text: "Encrypted backups" },\n { text: "Key management best practices" },\n ],\n },\n {\n blockType: "featureShowcase",\n blockName: "Access Control",\n label: "Access Management",\n headline: "Granular permissions and SSO support",\n description: createParagraph(\n "Role-based access control lets you define exactly who can see and do what. SAML SSO integration works with Okta, Azure AD, Google Workspace, and other identity providers.",\n ),\n link: {\n type: "custom",\n label: "Explore access controls",\n url: "/security",\n appearance: "default",\n },\n imagePosition: "left",\n features: [\n { text: "Role-based access control (RBAC)" },\n { text: "SAML SSO integration" },\n { text: "Two-factor authentication" },\n { text: "Session management" },\n ],\n },\n {\n blockType: "featureShowcase",\n blockName: "Compliance",\n label: "Compliance & Auditing",\n headline: "Meet compliance requirements with confidence",\n description: createParagraph(\n "SOC 2 Type II certified with comprehensive audit logs and data governance tools. Export reports for auditors and meet GDPR, CCPA, and HIPAA requirements.",\n ),\n link: {\n type: "custom",\n label: "View compliance docs",\n url: "/security",\n appearance: "default",\n },\n imagePosition: "right",\n features: [\n { text: "SOC 2 Type II certified" },\n { text: "GDPR and CCPA compliant" },\n { text: "Comprehensive audit logs" },\n { text: "Data retention controls" },\n ],\n },\n {\n blockType: "bentoFeatures",\n blockName: "Security Features",\n heading: "Security you can trust",\n subheading: "Enterprise-grade protection without enterprise complexity",\n features: [\n {\n size: "small",\n style: "gradient",\n icon: "shield",\n stat: "SOC 2",\n title: "Certified",\n description: createParagraph("Type II certification verified annually."),\n },\n {\n size: "small",\n style: "accent",\n icon: "shield",\n title: "Zero Trust",\n description: createParagraph("Verify every request, every time."),\n },\n {\n size: "small",\n style: "default",\n icon: "globe",\n title: "Global Infrastructure",\n description: createParagraph("Redundant systems across regions."),\n },\n {\n size: "small",\n style: "primary",\n icon: "database",\n title: "Backup & Recovery",\n description: createParagraph("Automated backups with point-in-time recovery."),\n },\n {\n size: "small",\n style: "default",\n icon: "search",\n title: "Threat Detection",\n description: createParagraph("24/7 monitoring for suspicious activity."),\n },\n {\n size: "small",\n style: "default",\n icon: "barChart",\n title: "Security Reports",\n description: createParagraph("Regular penetration testing and audits."),\n },\n ],\n },\n {\n blockType: "proofBanner",\n blockName: "CTA Section",\n style: "centered",\n headline: "Security that scales with you",\n subtext: "Enterprise-grade protection from day one. SOC 2 certified, GDPR compliant.",\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "Request security docs",\n url: "/contact",\n },\n },\n ],\n },\n ],\n meta: {\n description:\n "Enterprise-grade security with SOC 2 Type II certification, end-to-end encryption, SSO support, and comprehensive compliance tools.",\n title: "Security & Compliance \u2014 SaaSify Enterprise Protection",\n },\n }\n}\n',
5350
+ "marketing/payload/src/endpoints/seed/directoryhub/features/templates.ts": 'import type { Page } from "@/payload-types"\nimport { createParagraph } from "../richtext-helper"\n\nexport const integrationsPage = (): Partial<Page> => {\n return {\n slug: "features/integrations",\n _status: "published",\n title: "Integrations",\n hero: {\n type: "lowImpact",\n richText: {\n root: {\n type: "root",\n children: [\n {\n type: "heading",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "Connect all your tools in one place",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n tag: "h1",\n version: 1,\n },\n {\n type: "paragraph",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "One-click connections to 100+ apps your team already uses. Sync data, automate workflows, and eliminate context switching.",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n textFormat: 0,\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n version: 1,\n },\n },\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "View all integrations",\n url: "/integrations",\n },\n },\n ],\n },\n layout: [\n {\n blockType: "featureShowcase",\n blockName: "Native Integrations",\n label: "100+ Integrations",\n headline: "Connect your favorite tools instantly",\n description: createParagraph(\n "Native integrations with Slack, Salesforce, HubSpot, Jira, Notion, Google Workspace, Microsoft 365, and more. Set up in minutes, not days.",\n ),\n link: {\n type: "custom",\n label: "Browse integrations",\n url: "/integrations",\n appearance: "default",\n },\n imagePosition: "right",\n features: [\n { text: "100+ native integrations" },\n { text: "Two-way data sync" },\n { text: "Real-time updates" },\n { text: "No coding required" },\n ],\n },\n {\n blockType: "featureShowcase",\n blockName: "Custom Integrations",\n label: "Developer Tools",\n headline: "Build custom integrations with our API",\n description: createParagraph(\n "REST API and webhooks let you connect any tool or build custom integrations. Comprehensive documentation and SDKs for popular languages.",\n ),\n link: {\n type: "custom",\n label: "View API docs",\n url: "/developers",\n appearance: "default",\n },\n imagePosition: "left",\n features: [\n { text: "RESTful API access" },\n { text: "Custom webhooks" },\n { text: "SDKs for popular languages" },\n { text: "Detailed documentation" },\n ],\n },\n {\n blockType: "bentoFeatures",\n blockName: "Integration Categories",\n heading: "Integrations for every workflow",\n subheading:\n "Connect your entire tech stack and keep data flowing seamlessly",\n features: [\n {\n size: "small",\n style: "gradient",\n icon: "globe",\n title: "Communication",\n description: createParagraph(\n "Slack, Microsoft Teams, Discord, and email integrations.",\n ),\n },\n {\n size: "small",\n style: "accent",\n icon: "users",\n title: "CRM & Sales",\n description: createParagraph(\n "Salesforce, HubSpot, Pipedrive, and more.",\n ),\n },\n {\n size: "small",\n style: "default",\n icon: "layers",\n title: "Project Management",\n description: createParagraph(\n "Jira, Asana, Monday.com, Trello integrations.",\n ),\n },\n {\n size: "small",\n style: "primary",\n icon: "database",\n title: "Data & Storage",\n description: createParagraph(\n "Google Drive, Dropbox, AWS S3, and databases.",\n ),\n },\n {\n size: "small",\n style: "default",\n icon: "barChart",\n title: "Analytics",\n description: createParagraph("Google Analytics, Mixpanel, Amplitude."),\n },\n {\n size: "small",\n style: "default",\n icon: "zap",\n title: "Automation",\n description: createParagraph("Zapier, Make, n8n for custom workflows."),\n },\n ],\n },\n {\n blockType: "proofBanner",\n blockName: "CTA Section",\n style: "centered",\n headline: "Connect your tools today",\n subtext: "One-click integrations with 100+ apps. Set up in minutes, not days.",\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "View all integrations",\n url: "/integrations",\n },\n },\n ],\n },\n ],\n meta: {\n description:\n "Connect SaaSify with 100+ tools including Slack, Salesforce, HubSpot, and more. Native integrations, custom webhooks, and REST API access.",\n title: "Integrations \u2014 Connect Your Tools with SaaSify",\n },\n }\n}\n',
5351
5351
  "marketing/payload/src/endpoints/seed/directoryhub/home.ts": `import type { Media, Page } from "@/payload-types"
5352
5352
  import { createParagraph } from "./richtext-helper"
5353
5353
 
@@ -7729,7 +7729,7 @@ export const directoryHubTerms = (): Partial<Page> => {
7729
7729
  }
7730
7730
  }
7731
7731
  `,
7732
- "marketing/payload/src/endpoints/seed/directoryhub/use-cases/b2b-vendor-hubs.ts": 'import type { Page } from "@/payload-types"\nimport { createParagraph } from "../richtext-helper"\n\nexport const marketingPage = (): Partial<Page> => {\n return {\n slug: "use-cases/marketing",\n _status: "published",\n title: "SaaSify for Marketing Teams",\n hero: {\n type: "lowImpact",\n richText: {\n root: {\n type: "root",\n children: [\n {\n type: "heading",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "Launch campaigns that actually convert",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n tag: "h1",\n version: 1,\n },\n {\n type: "paragraph",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "Campaign planning, asset management, and performance analytics that help your marketing team move faster and measure what matters.",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n textFormat: 0,\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n version: 1,\n },\n },\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "See a demo",\n url: "/demo",\n },\n },\n ],\n },\n layout: [\n {\n blockType: "featureShowcase",\n blockName: "Campaign Management",\n label: "Campaign Planning",\n headline: "Plan and execute campaigns in one place",\n description: createParagraph(\n "Visual campaign calendars, task assignments, and deadline tracking. See every campaign across channels and keep your team aligned on priorities.",\n ),\n link: {\n type: "custom",\n label: "See campaign features",\n url: "/features",\n appearance: "default",\n },\n imagePosition: "right",\n features: [\n { text: "Visual campaign calendar" },\n { text: "Multi-channel planning" },\n { text: "Task and deadline tracking" },\n { text: "Team collaboration tools" },\n ],\n },\n {\n blockType: "featureShowcase",\n blockName: "Asset Management",\n label: "Asset Library",\n headline: "Organize and share marketing assets",\n description: createParagraph(\n "Central repository for all your marketing materials. Version control, approval workflows, and easy sharing make it simple to keep everyone on brand.",\n ),\n link: {\n type: "custom",\n label: "Learn about asset management",\n url: "/features",\n appearance: "default",\n },\n imagePosition: "left",\n features: [\n { text: "Centralized asset library" },\n { text: "Version control" },\n { text: "Approval workflows" },\n { text: "Brand guidelines enforcement" },\n ],\n },\n {\n blockType: "featureShowcase",\n blockName: "Performance Analytics",\n label: "Analytics",\n headline: "Measure what matters",\n description: createParagraph(\n "Track campaign performance across channels. Custom dashboards show the metrics that matter most to your team and stakeholders.",\n ),\n link: {\n type: "custom",\n label: "See analytics features",\n url: "/features/analytics",\n appearance: "default",\n },\n imagePosition: "right",\n features: [\n { text: "Cross-channel analytics" },\n { text: "Custom dashboards" },\n { text: "Automated reporting" },\n { text: "ROI tracking" },\n ],\n },\n {\n blockType: "bentoFeatures",\n blockName: "Marketing Features",\n heading: "Tools that help you market smarter",\n subheading: "Everything your marketing team needs in one platform",\n features: [\n {\n size: "small",\n style: "gradient",\n icon: "rocket",\n stat: "3x",\n title: "Campaign Velocity",\n description: createParagraph("Launch campaigns faster than ever."),\n },\n {\n size: "small",\n style: "accent",\n icon: "folder",\n title: "Project Templates",\n description: createParagraph("Pre-built templates for common campaigns."),\n },\n {\n size: "small",\n style: "default",\n icon: "users",\n title: "Team Workload",\n description: createParagraph("Balance work across your team."),\n },\n {\n size: "small",\n style: "primary",\n icon: "barChart",\n title: "Performance Metrics",\n description: createParagraph("Track KPIs across all campaigns."),\n },\n {\n size: "small",\n style: "default",\n icon: "zap",\n title: "Integrations",\n description: createParagraph("Connect with HubSpot, Marketo, and more."),\n },\n {\n size: "small",\n style: "default",\n icon: "messageSquare",\n title: "Collaboration",\n description: createParagraph("Comments, feedback, and approvals in context."),\n },\n ],\n },\n {\n blockType: "proofBanner",\n blockName: "CTA Section",\n style: "centered",\n headline: "Supercharge your marketing team",\n subtext:\n "Campaign planning, asset management, and analytics. Start launching better campaigns today.",\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "Talk to sales",\n url: "/contact",\n },\n },\n ],\n },\n ],\n meta: {\n description:\n "SaaSify for marketing teams. Campaign planning, asset management, and performance analytics to help you launch campaigns that convert.",\n title: "Marketing Teams \u2014 SaaSify Use Case",\n },\n }\n}\n',
7732
+ "marketing/payload/src/endpoints/seed/directoryhub/use-cases/b2b-vendor-hubs.ts": 'import type { Page } from "@/payload-types"\nimport { createParagraph } from "../richtext-helper"\n\nexport const marketingPage = (): Partial<Page> => {\n return {\n slug: "use-cases/marketing",\n _status: "published",\n title: "SaaSify for Marketing Teams",\n hero: {\n type: "lowImpact",\n richText: {\n root: {\n type: "root",\n children: [\n {\n type: "heading",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "Launch campaigns that actually convert",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n tag: "h1",\n version: 1,\n },\n {\n type: "paragraph",\n children: [\n {\n type: "text",\n detail: 0,\n format: 0,\n mode: "normal",\n style: "",\n text: "Campaign planning, asset management, and performance analytics that help your marketing team move faster and measure what matters.",\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n textFormat: 0,\n version: 1,\n },\n ],\n direction: "ltr" as const,\n format: "" as const,\n indent: 0,\n version: 1,\n },\n },\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "See a demo",\n url: "/demo",\n },\n },\n ],\n },\n layout: [\n {\n blockType: "featureShowcase",\n blockName: "Campaign Management",\n label: "Campaign Planning",\n headline: "Plan and execute campaigns in one place",\n description: createParagraph(\n "Visual campaign calendars, task assignments, and deadline tracking. See every campaign across channels and keep your team aligned on priorities.",\n ),\n link: {\n type: "custom",\n label: "See campaign features",\n url: "/features",\n appearance: "default",\n },\n imagePosition: "right",\n features: [\n { text: "Visual campaign calendar" },\n { text: "Multi-channel planning" },\n { text: "Task and deadline tracking" },\n { text: "Team collaboration tools" },\n ],\n },\n {\n blockType: "featureShowcase",\n blockName: "Asset Management",\n label: "Asset Library",\n headline: "Organize and share marketing assets",\n description: createParagraph(\n "Central repository for all your marketing materials. Version control, approval workflows, and easy sharing make it simple to keep everyone on brand.",\n ),\n link: {\n type: "custom",\n label: "Learn about asset management",\n url: "/features",\n appearance: "default",\n },\n imagePosition: "left",\n features: [\n { text: "Centralized asset library" },\n { text: "Version control" },\n { text: "Approval workflows" },\n { text: "Brand guidelines enforcement" },\n ],\n },\n {\n blockType: "featureShowcase",\n blockName: "Performance Analytics",\n label: "Analytics",\n headline: "Measure what matters",\n description: createParagraph(\n "Track campaign performance across channels. Custom dashboards show the metrics that matter most to your team and stakeholders.",\n ),\n link: {\n type: "custom",\n label: "See analytics features",\n url: "/features/analytics",\n appearance: "default",\n },\n imagePosition: "right",\n features: [\n { text: "Cross-channel analytics" },\n { text: "Custom dashboards" },\n { text: "Automated reporting" },\n { text: "ROI tracking" },\n ],\n },\n {\n blockType: "bentoFeatures",\n blockName: "Marketing Features",\n heading: "Tools that help you market smarter",\n subheading: "Everything your marketing team needs in one platform",\n features: [\n {\n size: "small",\n style: "gradient",\n icon: "rocket",\n stat: "3x",\n title: "Campaign Velocity",\n description: createParagraph("Launch campaigns faster than ever."),\n },\n {\n size: "small",\n style: "accent",\n icon: "layers",\n title: "Project Templates",\n description: createParagraph("Pre-built templates for common campaigns."),\n },\n {\n size: "small",\n style: "default",\n icon: "users",\n title: "Team Workload",\n description: createParagraph("Balance work across your team."),\n },\n {\n size: "small",\n style: "primary",\n icon: "barChart",\n title: "Performance Metrics",\n description: createParagraph("Track KPIs across all campaigns."),\n },\n {\n size: "small",\n style: "default",\n icon: "zap",\n title: "Integrations",\n description: createParagraph("Connect with HubSpot, Marketo, and more."),\n },\n {\n size: "small",\n style: "default",\n icon: "globe",\n title: "Collaboration",\n description: createParagraph("Comments, feedback, and approvals in context."),\n },\n ],\n },\n {\n blockType: "proofBanner",\n blockName: "CTA Section",\n style: "centered",\n headline: "Supercharge your marketing team",\n subtext:\n "Campaign planning, asset management, and analytics. Start launching better campaigns today.",\n links: [\n {\n link: {\n type: "custom",\n appearance: "default",\n label: "Start free trial",\n url: "/sign-up",\n },\n },\n {\n link: {\n type: "custom",\n appearance: "outline",\n label: "Talk to sales",\n url: "/contact",\n },\n },\n ],\n },\n ],\n meta: {\n description:\n "SaaSify for marketing teams. Campaign planning, asset management, and performance analytics to help you launch campaigns that convert.",\n title: "Marketing Teams \u2014 SaaSify Use Case",\n },\n }\n}\n',
7733
7733
  "marketing/payload/src/endpoints/seed/directoryhub/use-cases/communities.ts": `import type { Page } from "@/payload-types"
7734
7734
  import { createParagraph } from "../richtext-helper"
7735
7735
 
@@ -7920,7 +7920,7 @@ export const productPage = (): Partial<Page> => {
7920
7920
  {
7921
7921
  size: "small",
7922
7922
  style: "default",
7923
- icon: "messageSquare",
7923
+ icon: "users",
7924
7924
  title: "Stakeholder Updates",
7925
7925
  description: createParagraph("Keep leadership informed automatically."),
7926
7926
  },
@@ -10176,7 +10176,7 @@ export const post3: (args: PostArgs) => RequiredDataFromCollectionSlug<"posts">
10176
10176
  "marketing/payload/src/lib/convex.ts": 'import { ConvexHttpClient } from "convex/browser"\n\n/**\n * Convex HTTP client for server-side data fetching in the marketing site.\n * Used for fetching documentation content from Convex.\n */\nconst convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL\n\nif (!convexUrl) {\n console.warn("[Convex] NEXT_PUBLIC_CONVEX_URL is not set. Documentation features will not work.")\n}\n\nexport const convex = new ConvexHttpClient(convexUrl || "")\n',
10177
10177
  "marketing/payload/src/lib/docs-source.ts": 'import { api } from "../../../../convex/_generated/api"\nimport { convex } from "./convex"\n\n/**\n * Fetch published docs from Convex\n */\nexport async function getDocsFromConvex() {\n try {\n if (!process.env.NEXT_PUBLIC_CONVEX_URL) {\n console.error(\n "[Docs] NEXT_PUBLIC_CONVEX_URL environment variable is not set. Docs will not be available.",\n )\n return []\n }\n\n const docs = await convex.query(api.docs.listPublished)\n\n if (process.env.NODE_ENV === "development") {\n console.log(`[Docs] Fetched ${docs.length} published document(s) from Convex`)\n }\n\n return docs\n } catch (error) {\n console.error("[Docs] Failed to fetch docs from Convex:", error)\n return []\n }\n}\n\n/**\n * Fetch a single doc by slug from Convex\n */\nexport async function getDocBySlug(slug: string) {\n try {\n if (!process.env.NEXT_PUBLIC_CONVEX_URL) {\n console.error(\n "[Docs] NEXT_PUBLIC_CONVEX_URL environment variable is not set. Docs will not be available.",\n )\n return null\n }\n\n const doc = await convex.query(api.docs.getBySlug, { slug })\n return doc\n } catch (error) {\n console.error(`[Docs] Failed to fetch doc "${slug}" from Convex:`, error)\n return null\n }\n}\n\n/**\n * Build page tree for navigation from docs\n */\nexport function buildPageTree(docs: Awaited<ReturnType<typeof getDocsFromConvex>>) {\n // Sort docs by order, then alphabetically\n const sortedDocs = [...docs].sort((a, b) => {\n if (a.order !== undefined && b.order !== undefined) {\n return a.order - b.order\n }\n if (a.order !== undefined) return -1\n if (b.order !== undefined) return 1\n return a.title.localeCompare(b.title)\n })\n\n // Group docs by parent\n const topLevel = sortedDocs.filter((d) => !d.parentSlug)\n const byParent = new Map<string, typeof sortedDocs>()\n\n for (const doc of sortedDocs) {\n if (doc.parentSlug) {\n const existing = byParent.get(doc.parentSlug) || []\n existing.push(doc)\n byParent.set(doc.parentSlug, existing)\n }\n }\n\n // Build tree structure\n const buildChildren = (parentSlug: string): PageTreeItem[] => {\n const children = byParent.get(parentSlug) || []\n return children.map((doc) => ({\n type: "page" as const,\n name: doc.title,\n url: `/docs/${doc.slug}`,\n children: buildChildren(doc.slug),\n }))\n }\n\n const tree: PageTreeItem[] = topLevel.map((doc) => {\n const children = buildChildren(doc.slug)\n if (children.length > 0) {\n return {\n type: "folder" as const,\n name: doc.title,\n index: {\n type: "page" as const,\n name: doc.title,\n url: `/docs/${doc.slug}`,\n },\n children,\n }\n }\n return {\n type: "page" as const,\n name: doc.title,\n url: `/docs/${doc.slug}`,\n }\n })\n\n return {\n name: "Documentation",\n children: tree,\n }\n}\n\n// Types for page tree\ntype PageTreeItem =\n | {\n type: "page"\n name: string\n url: string\n children?: PageTreeItem[]\n }\n | {\n type: "folder"\n name: string\n index?: { type: "page"; name: string; url: string }\n children: PageTreeItem[]\n }\n | {\n type: "separator"\n name: string\n }\n\n/**\n * Get all page slugs for static generation\n */\nexport async function getAllDocSlugs(): Promise<string[][]> {\n const docs = await getDocsFromConvex()\n return docs.map((doc) => doc.slug.split("/"))\n}\n',
10178
10178
  "marketing/payload/src/lib/mdx.tsx": 'import { compile, run } from "@mdx-js/mdx"\nimport type { ReactNode } from "react"\nimport { createElement } from "react"\nimport * as runtime from "react/jsx-runtime"\n\ninterface TableOfContentsItem {\n title: string\n url: string\n depth: number\n}\n\n/**\n * Compile MDX content to React components\n */\nexport async function compileMDX(source: string): Promise<{\n content: ReactNode\n toc: TableOfContentsItem[]\n}> {\n // Extract headings for TOC before compilation\n const toc = extractTableOfContents(source)\n\n try {\n // Compile MDX to JavaScript\n const compiled = await compile(source, {\n outputFormat: "function-body",\n development: false,\n })\n\n // Run the compiled code\n const { default: MDXContent } = await run(String(compiled), {\n ...runtime,\n baseUrl: import.meta.url,\n })\n\n // Create the content element with custom components\n const content = createElement(MDXContent, {\n components: getMDXComponents(),\n })\n\n return { content, toc }\n } catch (error) {\n console.error("MDX compilation error:", error)\n\n // Return a fallback with the raw content\n return {\n content: createElement(\n "div",\n { className: "prose dark:prose-invert" },\n createElement("p", { className: "text-red-500" }, "Error rendering content"),\n createElement("pre", { className: "text-sm" }, source),\n ),\n toc,\n }\n }\n}\n\n/**\n * Extract table of contents from markdown headings\n */\nfunction extractTableOfContents(source: string): TableOfContentsItem[] {\n const headingRegex = /^(#{1,6})\\s+(.+)$/gm\n const toc: TableOfContentsItem[] = []\n\n const matches = source.matchAll(headingRegex)\n for (const match of matches) {\n const depth = match[1]?.length ?? 1\n const title = match[2]?.trim() ?? ""\n\n // Create URL-friendly slug\n const url = `#${title\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, "")\n .replace(/\\s+/g, "-")}`\n\n toc.push({ title, url, depth })\n }\n\n return toc\n}\n\n/**\n * Custom MDX components for styling\n */\nfunction getMDXComponents() {\n return {\n h1: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n createElement("h1", {\n ...props,\n className: "scroll-m-20 text-4xl font-bold tracking-tight",\n id: slugify(String(props.children)),\n }),\n h2: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n createElement("h2", {\n ...props,\n className:\n "scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0 mt-10",\n id: slugify(String(props.children)),\n }),\n h3: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n createElement("h3", {\n ...props,\n className: "scroll-m-20 text-2xl font-semibold tracking-tight mt-8",\n id: slugify(String(props.children)),\n }),\n h4: (props: React.HTMLAttributes<HTMLHeadingElement>) =>\n createElement("h4", {\n ...props,\n className: "scroll-m-20 text-xl font-semibold tracking-tight mt-6",\n id: slugify(String(props.children)),\n }),\n p: (props: React.HTMLAttributes<HTMLParagraphElement>) =>\n createElement("p", {\n ...props,\n className: "leading-7 [&:not(:first-child)]:mt-6",\n }),\n ul: (props: React.HTMLAttributes<HTMLUListElement>) =>\n createElement("ul", {\n ...props,\n className: "my-6 ml-6 list-disc [&>li]:mt-2",\n }),\n ol: (props: React.HTMLAttributes<HTMLOListElement>) =>\n createElement("ol", {\n ...props,\n className: "my-6 ml-6 list-decimal [&>li]:mt-2",\n }),\n li: (props: React.HTMLAttributes<HTMLLIElement>) => createElement("li", { ...props }),\n blockquote: (props: React.HTMLAttributes<HTMLQuoteElement>) =>\n createElement("blockquote", {\n ...props,\n className: "mt-6 border-l-2 pl-6 italic",\n }),\n code: (props: React.HTMLAttributes<HTMLElement>) => {\n // Check if this is an inline code or a code block\n const isInline = typeof props.children === "string"\n if (isInline) {\n return createElement("code", {\n ...props,\n className:\n "relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold",\n })\n }\n return createElement("code", { ...props })\n },\n pre: (props: React.HTMLAttributes<HTMLPreElement>) =>\n createElement("pre", {\n ...props,\n className: "mb-4 mt-6 overflow-x-auto rounded-lg border bg-zinc-950 py-4 dark:bg-zinc-900",\n }),\n a: (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) =>\n createElement("a", {\n ...props,\n className: "font-medium text-primary underline underline-offset-4",\n }),\n table: (props: React.HTMLAttributes<HTMLTableElement>) =>\n createElement(\n "div",\n { className: "my-6 w-full overflow-y-auto" },\n createElement("table", { ...props, className: "w-full" }),\n ),\n tr: (props: React.HTMLAttributes<HTMLTableRowElement>) =>\n createElement("tr", {\n ...props,\n className: "m-0 border-t p-0 even:bg-muted",\n }),\n th: (props: React.HTMLAttributes<HTMLTableCellElement>) =>\n createElement("th", {\n ...props,\n className:\n "border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right",\n }),\n td: (props: React.HTMLAttributes<HTMLTableCellElement>) =>\n createElement("td", {\n ...props,\n className:\n "border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right",\n }),\n hr: () => createElement("hr", { className: "my-4 md:my-8" }),\n img: (props: React.ImgHTMLAttributes<HTMLImageElement>) =>\n createElement("img", {\n ...props,\n className: "rounded-md border",\n }),\n }\n}\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, "")\n .replace(/\\s+/g, "-")\n}\n',
10179
- "marketing/payload/src/payload.config.ts.hbs": 'import path from "node:path"\nimport { fileURLToPath } from "node:url"\nimport { postgresAdapter } from "@payloadcms/db-postgres"\nimport { resendAdapter } from "@payloadcms/email-resend"\nimport type { SharpDependency } from "payload"\nimport { type PayloadRequest, buildConfig } from "payload"\nimport sharp from "sharp"\n\nimport { defaultLexical } from "@/fields/defaultLexical"\nimport { Footer } from "./Footer/config"\nimport { Header } from "./Header/config"\nimport { Categories } from "./collections/Categories"\nimport { FAQs } from "./collections/FAQs"\nimport { Media } from "./collections/Media"\nimport { Pages } from "./collections/Pages"\nimport { Posts } from "./collections/Posts"\nimport { Users } from "./collections/Users"\nimport { plugins } from "./plugins"\nimport { getServerSideURL } from "./utilities/getURL"\n\nconst filename = fileURLToPath(import.meta.url)\nconst dirname = path.dirname(filename)\n\nexport default buildConfig({\n admin: {\n components: {\n // The `BeforeLogin` component renders a message that you see while logging into your admin panel.\n // Feel free to delete this at any time. Simply remove the line below.\n beforeLogin: ["@/components/BeforeLogin"],\n // The `BeforeDashboard` component renders the \'welcome\' block that you see after logging into your admin panel.\n // Feel free to delete this at any time. Simply remove the line below.\n beforeDashboard: ["@/components/BeforeDashboard"],\n },\n importMap: {\n baseDir: path.resolve(dirname),\n },\n user: Users.slug,\n livePreview: {\n breakpoints: [\n {\n label: "Mobile",\n name: "mobile",\n width: 375,\n height: 667,\n },\n {\n label: "Tablet",\n name: "tablet",\n width: 768,\n height: 1024,\n },\n {\n label: "Desktop",\n name: "desktop",\n width: 1440,\n height: 900,\n },\n ],\n },\n },\n // This config helps us configure global or default features that the other editors can inherit\n editor: defaultLexical,\n db: postgresAdapter({\n pool: {\n connectionString: process.env.DATABASE_URL || "",\n },\n // Disable auto-push in dev mode to prevent constraint name truncation issues\n // Use migrations instead: pnpm payload migrate\n push: false,\n }),\n collections: [Pages, Posts, Media, Categories, FAQs, Users],\n cors: [getServerSideURL()].filter(Boolean),\n globals: [Header, Footer],\n plugins,\n secret: process.env.PAYLOAD_SECRET,\n sharp: sharp as unknown as SharpDependency,\n email: resendAdapter({\n apiKey: process.env.RESEND_API_KEY || "",\n defaultFromAddress: process.env.RESEND_FROM_EMAIL || "noreply@example.com",\n defaultFromName: "{{projectName}}",\n }),\n typescript: {\n outputFile: path.resolve(dirname, "payload-types.ts"),\n },\n jobs: {\n access: {\n run: ({ req }: { req: PayloadRequest }): boolean => {\n // Allow logged in users to execute this endpoint (default)\n if (req.user) return true\n\n // If there is no logged in user, then check\n // for the Vercel Cron secret to be present as an\n // Authorization header:\n const authHeader = req.headers.get("authorization")\n return authHeader === `Bearer ${process.env.CRON_SECRET}`\n },\n },\n tasks: [],\n },\n})\n',
10179
+ "marketing/payload/src/payload.config.ts.hbs": 'import path from "node:path"\nimport { fileURLToPath } from "node:url"\nimport { postgresAdapter } from "@payloadcms/db-postgres"\nimport { resendAdapter } from "@payloadcms/email-resend"\nimport type { SharpDependency } from "payload"\nimport { type PayloadRequest, buildConfig } from "payload"\nimport sharp from "sharp"\n\nimport { defaultLexical } from "@/fields/defaultLexical"\nimport { Footer } from "./Footer/config"\nimport { Header } from "./Header/config"\nimport { Categories } from "./collections/Categories"\nimport { FAQs } from "./collections/FAQs"\nimport { Media } from "./collections/Media"\nimport { Pages } from "./collections/Pages"\nimport { Posts } from "./collections/Posts"\nimport { Users } from "./collections/Users"\nimport { plugins } from "./plugins"\nimport { getServerSideURL } from "./utilities/getURL"\n\nconst filename = fileURLToPath(import.meta.url)\nconst dirname = path.dirname(filename)\n\nexport default buildConfig({\n admin: {\n components: {\n // The `BeforeLogin` component renders a message that you see while logging into your admin panel.\n // Feel free to delete this at any time. Simply remove the line below.\n beforeLogin: ["@/components/BeforeLogin"],\n // The `BeforeDashboard` component renders the \'welcome\' block that you see after logging into your admin panel.\n // Feel free to delete this at any time. Simply remove the line below.\n beforeDashboard: ["@/components/BeforeDashboard"],\n },\n importMap: {\n baseDir: path.resolve(dirname),\n },\n user: Users.slug,\n livePreview: {\n breakpoints: [\n {\n label: "Mobile",\n name: "mobile",\n width: 375,\n height: 667,\n },\n {\n label: "Tablet",\n name: "tablet",\n width: 768,\n height: 1024,\n },\n {\n label: "Desktop",\n name: "desktop",\n width: 1440,\n height: 900,\n },\n ],\n },\n },\n // This config helps us configure global or default features that the other editors can inherit\n editor: defaultLexical,\n db: postgresAdapter({\n pool: {\n connectionString: process.env.DATABASE_URL || "",\n },\n // Auto-sync schema in development for better DX (first launch works immediately)\n // In production, use explicit migrations: pnpm payload migrate\n push: process.env.NODE_ENV !== "production",\n }),\n collections: [Pages, Posts, Media, Categories, FAQs, Users],\n cors: [getServerSideURL()].filter(Boolean),\n globals: [Header, Footer],\n plugins,\n secret: process.env.PAYLOAD_SECRET,\n sharp: sharp as unknown as SharpDependency,\n email: resendAdapter({\n apiKey: process.env.RESEND_API_KEY || "",\n defaultFromAddress: process.env.RESEND_FROM_EMAIL || "noreply@example.com",\n defaultFromName: "{{projectName}}",\n }),\n typescript: {\n outputFile: path.resolve(dirname, "payload-types.ts"),\n },\n jobs: {\n access: {\n run: ({ req }: { req: PayloadRequest }): boolean => {\n // Allow logged in users to execute this endpoint (default)\n if (req.user) return true\n\n // If there is no logged in user, then check\n // for the Vercel Cron secret to be present as an\n // Authorization header:\n const authHeader = req.headers.get("authorization")\n return authHeader === `Bearer ${process.env.CRON_SECRET}`\n },\n },\n tasks: [],\n },\n})\n',
10180
10180
  "marketing/payload/src/plugins/index.ts": 'import { revalidateRedirects } from "@/hooks/revalidateRedirects"\nimport { beforeSyncWithSearch } from "@/search/beforeSync"\nimport { searchFields } from "@/search/fieldOverrides"\nimport { formBuilderPlugin } from "@payloadcms/plugin-form-builder"\nimport { nestedDocsPlugin } from "@payloadcms/plugin-nested-docs"\nimport { redirectsPlugin } from "@payloadcms/plugin-redirects"\nimport { searchPlugin } from "@payloadcms/plugin-search"\nimport { seoPlugin } from "@payloadcms/plugin-seo"\nimport type { GenerateTitle, GenerateURL } from "@payloadcms/plugin-seo/types"\nimport { FixedToolbarFeature, HeadingFeature, lexicalEditor } from "@payloadcms/richtext-lexical"\nimport { vercelBlobStorage } from "@payloadcms/storage-vercel-blob"\nimport type { Plugin } from "payload"\n\nimport type { Page, Post } from "@/payload-types"\nimport { getServerSideURL } from "@/utilities/getURL"\n\nconst generateTitle: GenerateTitle<Post | Page> = ({ doc }) => {\n return doc?.title ? `${doc.title} | Payload Website Template` : "Payload Website Template"\n}\n\nconst generateURL: GenerateURL<Post | Page> = ({ doc }) => {\n const url = getServerSideURL()\n\n return doc?.slug ? `${url}/${doc.slug}` : url\n}\n\nexport const plugins: Plugin[] = [\n // Vercel Blob Storage for media uploads in production\n ...(process.env.BLOB_READ_WRITE_TOKEN\n ? [\n vercelBlobStorage({\n collections: {\n media: true,\n },\n token: process.env.BLOB_READ_WRITE_TOKEN,\n // Enable client-side uploads to bypass Vercel serverless function body size limits\n // This allows uploads up to 500MB directly to Vercel Blob Storage\n clientUploads: true,\n }),\n ]\n : []),\n redirectsPlugin({\n collections: ["pages", "posts"],\n overrides: {\n // @ts-expect-error - This is a valid override, mapped fields don\'t resolve to the same type\n fields: ({ defaultFields }) => {\n return defaultFields.map((field) => {\n if ("name" in field && field.name === "from") {\n return {\n ...field,\n admin: {\n description: "You will need to rebuild the website when changing this field.",\n },\n }\n }\n return field\n })\n },\n hooks: {\n afterChange: [revalidateRedirects],\n },\n },\n }),\n nestedDocsPlugin({\n collections: ["categories"],\n generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ""),\n }),\n seoPlugin({\n generateTitle,\n generateURL,\n }),\n formBuilderPlugin({\n fields: {\n payment: false,\n },\n formOverrides: {\n fields: ({ defaultFields }) => {\n return defaultFields.map((field) => {\n if ("name" in field && field.name === "confirmationMessage") {\n return {\n ...field,\n editor: lexicalEditor({\n features: ({ rootFeatures }) => {\n return [\n ...rootFeatures,\n FixedToolbarFeature(),\n HeadingFeature({ enabledHeadingSizes: ["h1", "h2", "h3", "h4"] }),\n ]\n },\n }),\n }\n }\n return field\n })\n },\n },\n }),\n searchPlugin({\n collections: ["posts"],\n beforeSync: beforeSyncWithSearch,\n searchOverrides: {\n fields: ({ defaultFields }) => {\n return [...defaultFields, ...searchFields]\n },\n },\n }),\n]\n',
10181
10181
  "marketing/payload/src/providers/HeaderTheme/index.tsx": '"use client"\n\nimport type { Theme } from "@/providers/Theme/types"\n\nimport type React from "react"\nimport { createContext, use, useCallback, useState } from "react"\n\nimport canUseDOM from "@/utilities/canUseDOM"\n\nexport interface ContextType {\n headerTheme?: Theme | null\n setHeaderTheme: (theme: Theme | null) => void\n}\n\nconst initialContext: ContextType = {\n headerTheme: undefined,\n setHeaderTheme: () => null,\n}\n\nconst HeaderThemeContext = createContext(initialContext)\n\nexport const HeaderThemeProvider = ({ children }: { children: React.ReactNode }) => {\n const [headerTheme, setThemeState] = useState<Theme | undefined | null>(\n canUseDOM ? (document.documentElement.getAttribute("data-theme") as Theme) : undefined,\n )\n\n const setHeaderTheme = useCallback((themeToSet: Theme | null) => {\n setThemeState(themeToSet)\n }, [])\n\n return <HeaderThemeContext value={{ headerTheme, setHeaderTheme }}>{children}</HeaderThemeContext>\n}\n\nexport const useHeaderTheme = (): ContextType => use(HeaderThemeContext)\n',
10182
10182
  "marketing/payload/src/providers/PostHogProvider.tsx": '"use client"\n\nimport { usePathname, useSearchParams } from "next/navigation"\nimport posthog from "posthog-js"\nimport { Suspense, useEffect } from "react"\n\nfunction PostHogPageviewTracker() {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n\n useEffect(() => {\n // Only track in production with PostHog configured\n if (process.env.NODE_ENV !== "production") return\n if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) return\n\n // Track pageviews when the route changes\n const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : "")\n posthog.capture("$pageview", { $current_url: url })\n }, [pathname, searchParams])\n\n return null\n}\n\nexport function PostHogProvider({ children }: { children: React.ReactNode }) {\n return (\n <>\n <Suspense fallback={null}>\n <PostHogPageviewTracker />\n </Suspense>\n {children}\n </>\n )\n}\n',
@@ -10303,7 +10303,7 @@ export const useTheme = (): ThemeContextType => use(ThemeContext)
10303
10303
  "marketing/payload/src/utilities/generateMeta.ts": 'import type { Metadata } from "next"\n\nimport type { Config, Media, Page, Post } from "../payload-types"\n\nimport { getServerSideURL } from "./getURL"\nimport { mergeOpenGraph } from "./mergeOpenGraph"\n\nconst getImageURL = (image?: Media | Config["db"]["defaultIDType"] | null) => {\n const serverUrl = getServerSideURL()\n\n let url = `${serverUrl}/website-template-OG.webp`\n\n if (image && typeof image === "object" && "url" in image) {\n const ogUrl = image.sizes?.og?.url\n\n url = ogUrl ? `${serverUrl}${ogUrl}` : `${serverUrl}${image.url}`\n }\n\n return url\n}\n\n// Default keywords for SEO\nconst defaultKeywords = [\n "directory builder",\n "no-code directory",\n "directory website",\n "niche directory",\n "business directory software",\n "directory platform",\n "monetize directory",\n "SEO directory",\n "directory SaaS",\n "create directory website",\n]\n\nexport const generateMeta = async (args: {\n doc: Partial<Page> | Partial<Post> | null\n}): Promise<Metadata> => {\n const { doc } = args\n const serverUrl = getServerSideURL()\n\n const ogImage = getImageURL(doc?.meta?.image)\n\n const title = doc?.meta?.title\n ? `${doc?.meta?.title} | DirectoryHub`\n : "DirectoryHub - Build Directories That Generate Real Business"\n\n const description =\n doc?.meta?.description ||\n "Launch a profitable directory business in minutes. The no-code platform to build, manage, and monetize niche directory websites with built-in payments, SEO, and multi-tenant scalability."\n\n // Generate canonical URL\n const slug = Array.isArray(doc?.slug) ? doc?.slug.join("/") : doc?.slug || ""\n const canonicalUrl = slug === "home" ? serverUrl : `${serverUrl}/${slug}`\n\n return {\n title,\n description,\n keywords: defaultKeywords,\n authors: [{ name: "DirectoryHub", url: serverUrl }],\n creator: "DirectoryHub",\n publisher: "DirectoryHub",\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n "max-video-preview": -1,\n "max-image-preview": "large",\n "max-snippet": -1,\n },\n },\n alternates: {\n canonical: canonicalUrl,\n },\n openGraph: mergeOpenGraph({\n description,\n images: ogImage\n ? [\n {\n url: ogImage,\n width: 1200,\n height: 630,\n alt: title,\n },\n ]\n : undefined,\n title,\n url: canonicalUrl,\n }),\n }\n}\n',
10304
10304
  "marketing/payload/src/utilities/generatePreviewPath.ts": 'import type { CollectionSlug, PayloadRequest } from "payload"\n\nconst collectionPrefixMap: Partial<Record<CollectionSlug, string>> = {\n posts: "/posts",\n pages: "",\n}\n\ntype Props = {\n collection: keyof typeof collectionPrefixMap\n slug: string\n req: PayloadRequest\n}\n\nexport const generatePreviewPath = ({ collection, slug }: Props) => {\n // Allow empty strings, e.g. for the homepage\n if (slug === undefined || slug === null) {\n return null\n }\n\n // Encode to support slugs with special characters\n const encodedSlug = encodeURIComponent(slug)\n\n const encodedParams = new URLSearchParams({\n slug: encodedSlug,\n collection,\n path: `${collectionPrefixMap[collection]}/${encodedSlug}`,\n previewSecret: process.env.PREVIEW_SECRET || "",\n })\n\n const url = `/next/preview?${encodedParams.toString()}`\n\n return url\n}\n',
10305
10305
  "marketing/payload/src/utilities/getDocument.ts": 'import type { Config } from "src/payload-types"\n\nimport configPromise from "@payload-config"\nimport { unstable_cache } from "next/cache"\nimport { getPayload } from "payload"\n\ntype Collection = keyof Config["collections"]\n\nasync function getDocument(collection: Collection, slug: string, depth = 2) {\n const payload = await getPayload({ config: configPromise })\n\n const page = await payload.find({\n collection,\n depth,\n where: {\n slug: {\n equals: slug,\n },\n },\n })\n\n return page.docs[0]\n}\n\n/**\n * Returns a unstable_cache function mapped with the cache tag for the slug\n * @param depth - Depth for populating relationships (default: 2 for rich text content)\n */\nexport const getCachedDocument = (collection: Collection, slug: string, depth = 2) =>\n unstable_cache(async () => getDocument(collection, slug, depth), [collection, slug], {\n tags: [`${collection}_${slug}`],\n })\n',
10306
- "marketing/payload/src/utilities/getGlobals.ts": 'import type { Config } from "src/payload-types"\n\nimport configPromise from "@payload-config"\nimport { unstable_cache } from "next/cache"\nimport { getPayload } from "payload"\n\ntype Global = keyof Config["globals"]\n\nasync function getGlobal(slug: Global, depth = 0) {\n const payload = await getPayload({ config: configPromise })\n\n const global = await payload.findGlobal({\n slug,\n depth,\n })\n\n return global\n}\n\n/**\n * Returns a unstable_cache function mapped with the cache tag for the slug\n */\nexport const getCachedGlobal = (slug: Global, depth = 0) =>\n unstable_cache(async () => getGlobal(slug, depth), [slug], {\n tags: [`global_${slug}`],\n })\n',
10306
+ "marketing/payload/src/utilities/getGlobals.ts": 'import type { Config } from "src/payload-types"\n\nimport configPromise from "@payload-config"\nimport { unstable_cache } from "next/cache"\nimport { getPayload } from "payload"\n\ntype Global = keyof Config["globals"]\n\nasync function getGlobal(slug: Global, depth = 0) {\n try {\n const payload = await getPayload({ config: configPromise })\n\n const global = await payload.findGlobal({\n slug,\n depth,\n })\n\n return global\n } catch (error) {\n // Database tables may not exist yet on first launch\n // Return null so components can show a setup UI instead of crashing\n console.warn(`Could not fetch global "${String(slug)}". Database may not be initialized yet.`)\n return null\n }\n}\n\n/**\n * Returns a unstable_cache function mapped with the cache tag for the slug\n */\nexport const getCachedGlobal = (slug: Global, depth = 0) =>\n unstable_cache(async () => getGlobal(slug, depth), [slug], {\n tags: [`global_${slug}`],\n })\n',
10307
10307
  "marketing/payload/src/utilities/getMeUser.ts": 'import { cookies } from "next/headers"\nimport { redirect } from "next/navigation"\n\nimport type { User } from "../payload-types"\nimport { getClientSideURL } from "./getURL"\n\nexport const getMeUser = async (args?: {\n nullUserRedirect?: string\n validUserRedirect?: string\n}): Promise<{\n token: string\n user: User\n}> => {\n const { nullUserRedirect, validUserRedirect } = args || {}\n const cookieStore = await cookies()\n const token = cookieStore.get("payload-token")?.value\n\n const meUserReq = await fetch(`${getClientSideURL()}/api/users/me`, {\n headers: {\n Authorization: `JWT ${token}`,\n },\n })\n\n const {\n user,\n }: {\n user: User\n } = await meUserReq.json()\n\n if (validUserRedirect && meUserReq.ok && user) {\n redirect(validUserRedirect)\n }\n\n if (nullUserRedirect && (!meUserReq.ok || !user)) {\n redirect(nullUserRedirect)\n }\n\n // Token will exist here because if it doesn\'t the user will be redirected\n return {\n token: token!,\n user,\n }\n}\n',
10308
10308
  "marketing/payload/src/utilities/getMediaUrl.ts": 'import { getClientSideURL } from "@/utilities/getURL"\n\n/**\n * Processes media resource URL to ensure proper formatting\n * @param url The original URL from the resource\n * @param cacheTag Optional cache tag to append to the URL\n * @returns Properly formatted URL with cache tag if provided\n */\nexport const getMediaUrl = (url: string | null | undefined, cacheTag?: string | null): string => {\n if (!url) return ""\n\n if (cacheTag && cacheTag !== "") {\n cacheTag = encodeURIComponent(cacheTag)\n }\n\n // Check if URL already has http/https protocol\n if (url.startsWith("http://") || url.startsWith("https://")) {\n return cacheTag ? `${url}?${cacheTag}` : url\n }\n\n // Otherwise prepend client-side URL\n const baseUrl = getClientSideURL()\n return cacheTag ? `${baseUrl}${url}?${cacheTag}` : `${baseUrl}${url}`\n}\n',
10309
10309
  "marketing/payload/src/utilities/getRedirects.ts": `import configPromise from "@payload-config"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kofi-stack-template-generator",
3
- "version": "2.1.46",
3
+ "version": "2.1.48",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "typecheck": "tsc --noEmit"
18
18
  },
19
19
  "dependencies": {
20
- "kofi-stack-types": "^2.1.46",
20
+ "kofi-stack-types": "^2.1.48",
21
21
  "handlebars": "^4.7.8",
22
22
  "memfs": "^4.9.0"
23
23
  },