kofi-stack-template-generator 2.1.36 → 2.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +6 -6
- package/dist/index.js +8059 -441
- package/package.json +1 -1
- package/src/templates.generated.ts +250 -95
- package/templates/integrations/posthog/src/components/providers/posthog-provider.tsx.hbs +4 -1
- package/templates/marketing/payload/package.json.hbs +41 -26
- package/templates/marketing/payload/src/Footer/Component.client.tsx +288 -0
- package/templates/marketing/payload/src/Footer/Component.tsx +11 -0
- package/templates/marketing/payload/src/Footer/RowLabel.tsx +15 -0
- package/templates/marketing/payload/src/Footer/config.ts +178 -0
- package/templates/marketing/payload/src/Footer/hooks/{revalidateFooter.ts.hbs → revalidateFooter.ts} +5 -5
- package/templates/marketing/payload/src/Header/Component.client.tsx +94 -0
- package/templates/marketing/payload/src/Header/Component.tsx +10 -0
- package/templates/marketing/payload/src/Header/MegaMenu/index.tsx +197 -0
- package/templates/marketing/payload/src/Header/MobileMenu/HamburgerIcon.tsx +48 -0
- package/templates/marketing/payload/src/Header/MobileMenu/index.tsx +299 -0
- package/templates/marketing/payload/src/Header/Nav/index.tsx +76 -0
- package/templates/marketing/payload/src/Header/RowLabel.tsx +21 -0
- package/templates/marketing/payload/src/Header/config.ts +208 -0
- package/templates/marketing/payload/src/Header/hooks/{revalidateHeader.ts.hbs → revalidateHeader.ts} +5 -5
- package/templates/marketing/payload/src/access/{authenticated.ts.hbs → authenticated.ts} +1 -1
- package/templates/marketing/payload/src/access/{authenticatedOrPublished.ts.hbs → authenticatedOrPublished.ts} +8 -8
- package/templates/marketing/payload/src/app/(docs)/docs/[[...slug]]/page.tsx +117 -0
- package/templates/marketing/payload/src/app/(docs)/docs/layout.tsx +39 -0
- package/templates/marketing/payload/src/app/(docs)/layout.tsx +44 -0
- package/templates/marketing/payload/src/app/(frontend)/(sitemaps)/pages-sitemap.xml/route.ts +68 -0
- package/templates/marketing/payload/src/app/(frontend)/(sitemaps)/posts-sitemap.xml/route.ts +55 -0
- package/templates/marketing/payload/src/app/(frontend)/[slug]/page.client.tsx +15 -0
- package/templates/marketing/payload/src/app/(frontend)/[slug]/page.tsx +114 -0
- package/templates/marketing/payload/src/app/(frontend)/api/docs-search/route.ts +67 -0
- package/templates/marketing/payload/src/app/(frontend)/api/newsletter/route.ts +260 -0
- package/templates/marketing/payload/src/app/(frontend)/api/pricing/route.ts +266 -0
- package/templates/marketing/payload/src/app/(frontend)/globals.css +1019 -0
- package/templates/marketing/payload/src/app/(frontend)/layout.tsx +114 -0
- package/templates/marketing/payload/src/app/(frontend)/next/exit-preview/route.ts +7 -0
- package/templates/marketing/payload/src/app/(frontend)/next/preview/route.ts +56 -0
- package/templates/marketing/payload/src/app/(frontend)/next/seed/route.ts +31 -0
- package/templates/marketing/payload/src/app/(frontend)/not-found.tsx +17 -0
- package/templates/marketing/payload/src/app/(frontend)/page.tsx +5 -0
- package/templates/marketing/payload/src/app/(frontend)/posts/BlogPageClient.tsx +190 -0
- package/templates/marketing/payload/src/app/(frontend)/posts/[slug]/BlogPostContent.tsx +67 -0
- package/templates/marketing/payload/src/app/(frontend)/posts/[slug]/page.client.tsx +15 -0
- package/templates/marketing/payload/src/app/(frontend)/posts/[slug]/page.tsx +118 -0
- package/templates/marketing/payload/src/app/(frontend)/posts/page/[pageNumber]/page.client.tsx +15 -0
- package/templates/marketing/payload/src/app/(frontend)/posts/page/[pageNumber]/page.tsx +87 -0
- package/templates/marketing/payload/src/app/(frontend)/posts/page.tsx +49 -0
- package/templates/marketing/payload/src/app/(frontend)/search/page.client.tsx +15 -0
- package/templates/marketing/payload/src/app/(frontend)/search/page.tsx +87 -0
- package/templates/marketing/payload/src/app/(payload)/admin/[[...segments]]/not-found.tsx +24 -0
- package/templates/marketing/payload/src/app/(payload)/admin/[[...segments]]/page.tsx +24 -0
- package/templates/marketing/payload/src/app/(payload)/admin/importMap.js +83 -0
- package/templates/marketing/payload/src/app/(payload)/api/[...slug]/{route.ts.hbs → route.ts} +13 -9
- package/templates/marketing/payload/src/app/(payload)/api/graphql/{route.ts.hbs → route.ts} +2 -2
- package/templates/marketing/payload/src/app/(payload)/api/graphql-playground/{route.ts.hbs → route.ts} +3 -3
- package/templates/marketing/payload/src/app/(payload)/custom.scss +0 -0
- package/templates/marketing/payload/src/app/(payload)/layout.tsx +31 -0
- package/templates/marketing/payload/src/blocks/ArchiveBlock/Component.tsx +65 -0
- package/templates/marketing/payload/src/blocks/ArchiveBlock/config.ts +120 -0
- package/templates/marketing/payload/src/blocks/Banner/Component.tsx +26 -0
- package/templates/marketing/payload/src/blocks/Banner/config.ts +67 -0
- package/templates/marketing/payload/src/blocks/BentoFeatures/Component.tsx +243 -0
- package/templates/marketing/payload/src/blocks/BentoFeatures/config.ts +147 -0
- package/templates/marketing/payload/src/blocks/CallToAction/Component.tsx +31 -0
- package/templates/marketing/payload/src/blocks/CallToAction/config.ts +68 -0
- package/templates/marketing/payload/src/blocks/Code/Component.client.tsx +33 -0
- package/templates/marketing/payload/src/blocks/Code/Component.tsx +21 -0
- package/templates/marketing/payload/src/blocks/Code/CopyButton.tsx +33 -0
- package/templates/marketing/payload/src/blocks/Code/config.ts +33 -0
- package/templates/marketing/payload/src/blocks/Content/Component.tsx +41 -0
- package/templates/marketing/payload/src/blocks/Content/config.ts +105 -0
- package/templates/marketing/payload/src/blocks/FAQAccordion/Component.tsx +90 -0
- package/templates/marketing/payload/src/blocks/FAQAccordion/config.ts +75 -0
- package/templates/marketing/payload/src/blocks/FeatureGrid/Component.tsx +108 -0
- package/templates/marketing/payload/src/blocks/FeatureGrid/config.ts +109 -0
- package/templates/marketing/payload/src/blocks/FeatureShowcase/Component.tsx +107 -0
- package/templates/marketing/payload/src/blocks/FeatureShowcase/config.ts +111 -0
- package/templates/marketing/payload/src/blocks/FinalCTA/Component.tsx +117 -0
- package/templates/marketing/payload/src/blocks/FinalCTA/config.ts +50 -0
- package/templates/marketing/payload/src/blocks/Form/Checkbox/index.tsx +45 -0
- package/templates/marketing/payload/src/blocks/Form/Component.tsx +170 -0
- package/templates/marketing/payload/src/blocks/Form/Country/index.tsx +65 -0
- package/templates/marketing/payload/src/blocks/Form/Country/options.ts +982 -0
- package/templates/marketing/payload/src/blocks/Form/Email/index.tsx +38 -0
- package/templates/marketing/payload/src/blocks/Form/Error/index.tsx +13 -0
- package/templates/marketing/payload/src/blocks/Form/Message/index.tsx +13 -0
- package/templates/marketing/payload/src/blocks/Form/Number/index.tsx +36 -0
- package/templates/marketing/payload/src/blocks/Form/Select/index.tsx +63 -0
- package/templates/marketing/payload/src/blocks/Form/State/index.tsx +64 -0
- package/templates/marketing/payload/src/blocks/Form/State/options.ts +52 -0
- package/templates/marketing/payload/src/blocks/Form/Text/index.tsx +32 -0
- package/templates/marketing/payload/src/blocks/Form/Textarea/index.tsx +40 -0
- package/templates/marketing/payload/src/blocks/Form/Width/index.tsx +13 -0
- package/templates/marketing/payload/src/blocks/Form/config.ts +77 -0
- package/templates/marketing/payload/src/blocks/Form/fields.tsx +21 -0
- package/templates/marketing/payload/src/blocks/HowItWorks/Component.tsx +59 -0
- package/templates/marketing/payload/src/blocks/HowItWorks/config.ts +88 -0
- package/templates/marketing/payload/src/blocks/IndustryTabs/Component.tsx +132 -0
- package/templates/marketing/payload/src/blocks/IndustryTabs/config.ts +77 -0
- package/templates/marketing/payload/src/blocks/LogoBanner/Component.tsx +95 -0
- package/templates/marketing/payload/src/blocks/LogoBanner/config.ts +48 -0
- package/templates/marketing/payload/src/blocks/MediaBlock/Component.tsx +67 -0
- package/templates/marketing/payload/src/blocks/MediaBlock/config.ts +14 -0
- package/templates/marketing/payload/src/blocks/Personas/Component.tsx +69 -0
- package/templates/marketing/payload/src/blocks/Personas/config.ts +96 -0
- package/templates/marketing/payload/src/blocks/PricingTable/ComparisonTable.tsx +250 -0
- package/templates/marketing/payload/src/blocks/PricingTable/Component.tsx +443 -0
- package/templates/marketing/payload/src/blocks/PricingTable/config.ts +142 -0
- package/templates/marketing/payload/src/blocks/ProofBanner/Component.tsx +65 -0
- package/templates/marketing/payload/src/blocks/ProofBanner/config.ts +42 -0
- package/templates/marketing/payload/src/blocks/RelatedPosts/Component.tsx +32 -0
- package/templates/marketing/payload/src/blocks/RenderBlocks.tsx +92 -0
- package/templates/marketing/payload/src/blocks/TestimonialsGrid/Component.tsx +107 -0
- package/templates/marketing/payload/src/blocks/TestimonialsGrid/config.ts +76 -0
- package/templates/marketing/payload/src/blocks/TrustColumns/Component.tsx +83 -0
- package/templates/marketing/payload/src/blocks/TrustColumns/config.ts +70 -0
- package/templates/marketing/payload/src/collections/Categories.ts +28 -0
- package/templates/marketing/payload/src/collections/FAQs/index.ts +100 -0
- package/templates/marketing/payload/src/collections/Media.ts +160 -0
- package/templates/marketing/payload/src/collections/Pages/hooks/revalidatePage.ts +43 -0
- package/templates/marketing/payload/src/collections/Pages/index.ts +168 -0
- package/templates/marketing/payload/src/collections/Posts/hooks/populateAuthors.ts +41 -0
- package/templates/marketing/payload/src/collections/Posts/hooks/revalidatePost.ts +44 -0
- package/templates/marketing/payload/src/collections/Posts/index.ts +259 -0
- package/templates/marketing/payload/src/collections/Users/index.ts +26 -0
- package/templates/marketing/payload/src/components/AdminBar/index.scss +7 -0
- package/templates/marketing/payload/src/components/AdminBar/index.tsx +89 -0
- package/templates/marketing/payload/src/components/Analytics/CTATracker.tsx +33 -0
- package/templates/marketing/payload/src/components/Analytics/FeatureSectionTracker.tsx +47 -0
- package/templates/marketing/payload/src/components/Analytics/PricingViewTracker.tsx +46 -0
- package/templates/marketing/payload/src/components/Analytics/index.tsx +3 -0
- package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/index.tsx +89 -0
- package/templates/marketing/payload/src/components/BeforeDashboard/index.tsx +69 -0
- package/templates/marketing/payload/src/components/BeforeLogin/index.tsx +14 -0
- package/templates/marketing/payload/src/components/BlogCTA/index.tsx +77 -0
- package/templates/marketing/payload/src/components/Card/index.tsx +85 -0
- package/templates/marketing/payload/src/components/CollectionArchive/index.tsx +32 -0
- package/templates/marketing/payload/src/components/JsonLd/index.tsx +138 -0
- package/templates/marketing/payload/src/components/Link/index.tsx +66 -0
- package/templates/marketing/payload/src/components/LivePreviewListener/index.tsx +10 -0
- package/templates/marketing/payload/src/components/Logo/Logo.tsx +46 -0
- package/templates/marketing/payload/src/components/Media/ImageMedia/index.tsx +80 -0
- package/templates/marketing/payload/src/components/Media/VideoMedia/index.tsx +47 -0
- package/templates/marketing/payload/src/components/Media/index.tsx +26 -0
- package/templates/marketing/payload/src/components/Media/types.ts +22 -0
- package/templates/marketing/payload/src/components/PageRange/index.tsx +57 -0
- package/templates/marketing/payload/src/components/Pagination/index.tsx +101 -0
- package/templates/marketing/payload/src/components/PayloadRedirects/index.tsx +48 -0
- package/templates/marketing/payload/src/components/RichText/index.tsx +152 -0
- package/templates/marketing/payload/src/components/TableOfContents/index.tsx +128 -0
- package/templates/marketing/payload/src/components/ui/accordion.tsx +64 -0
- package/templates/marketing/payload/src/components/ui/button.tsx +52 -0
- package/templates/marketing/payload/src/components/ui/card.tsx +48 -0
- package/templates/marketing/payload/src/components/ui/checkbox.tsx +27 -0
- package/templates/marketing/payload/src/components/ui/input.tsx +22 -0
- package/templates/marketing/payload/src/components/ui/label.tsx +19 -0
- package/templates/marketing/payload/src/components/ui/pagination.tsx +92 -0
- package/templates/marketing/payload/src/components/ui/select.tsx +144 -0
- package/templates/marketing/payload/src/components/ui/textarea.tsx +21 -0
- package/templates/marketing/payload/src/endpoints/seed/contact-form.ts +111 -0
- package/templates/marketing/payload/src/endpoints/seed/contact-page.ts +56 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/about.ts +281 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/faqs.ts +224 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/automation.ts +229 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/custom-fields.ts +229 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/dashboard.ts +228 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/index.ts +6 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/monetization.ts +230 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/seo.ts +229 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/templates.ts +218 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/home.ts +555 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/index.ts +767 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/posts.ts +623 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/pricing.ts +251 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/privacy.ts +457 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/richtext-helper.ts +88 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/terms.ts +478 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/use-cases/b2b-vendor-hubs.ts +229 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/use-cases/communities.ts +230 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/use-cases/index.ts +4 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/use-cases/local-services.ts +230 -0
- package/templates/marketing/payload/src/endpoints/seed/directoryhub/use-cases/marketplaces.ts +230 -0
- package/templates/marketing/payload/src/endpoints/seed/home-static.ts +691 -0
- package/templates/marketing/payload/src/endpoints/seed/home.ts +675 -0
- package/templates/marketing/payload/src/endpoints/seed/image-1.ts +67 -0
- package/templates/marketing/payload/src/endpoints/seed/image-2.ts +67 -0
- package/templates/marketing/payload/src/endpoints/seed/image-3.ts +67 -0
- package/templates/marketing/payload/src/endpoints/seed/image-hero-1.ts +5 -0
- package/templates/marketing/payload/src/endpoints/seed/image-hero1.webp +0 -0
- package/templates/marketing/payload/src/endpoints/seed/image-post1.webp +0 -0
- package/templates/marketing/payload/src/endpoints/seed/image-post2.webp +0 -0
- package/templates/marketing/payload/src/endpoints/seed/image-post3.webp +0 -0
- package/templates/marketing/payload/src/endpoints/seed/index.ts +335 -0
- package/templates/marketing/payload/src/endpoints/seed/post-1.ts +315 -0
- package/templates/marketing/payload/src/endpoints/seed/post-2.ts +232 -0
- package/templates/marketing/payload/src/endpoints/seed/post-3.ts +268 -0
- package/templates/marketing/payload/src/fields/defaultLexical.ts +73 -0
- package/templates/marketing/payload/src/fields/link.ts +139 -0
- package/templates/marketing/payload/src/fields/linkGroup.ts +28 -0
- package/templates/marketing/payload/src/heros/HighImpact/index.tsx +56 -0
- package/templates/marketing/payload/src/heros/LowImpact/index.tsx +48 -0
- package/templates/marketing/payload/src/heros/MediumImpact/index.tsx +50 -0
- package/templates/marketing/payload/src/heros/PostHero/index.tsx +73 -0
- package/templates/marketing/payload/src/heros/ProductShowcase/AnimatedMockup.tsx +241 -0
- package/templates/marketing/payload/src/heros/ProductShowcase/index.tsx +108 -0
- package/templates/marketing/payload/src/heros/{RenderHero.tsx.hbs → RenderHero.tsx} +9 -9
- package/templates/marketing/payload/src/heros/config.ts +121 -0
- package/templates/marketing/payload/src/hooks/populatePublishedAt.ts +15 -0
- package/templates/marketing/payload/src/hooks/{revalidateRedirects.ts.hbs → revalidateRedirects.ts} +3 -3
- package/templates/marketing/payload/src/lib/convex.ts +13 -0
- package/templates/marketing/payload/src/lib/docs-source.ts +138 -0
- package/templates/marketing/payload/src/lib/mdx.tsx +191 -0
- package/templates/marketing/payload/src/payload.config.ts.hbs +95 -145
- package/templates/marketing/payload/src/plugins/index.ts +107 -0
- package/templates/marketing/payload/src/providers/HeaderTheme/index.tsx +34 -0
- package/templates/marketing/payload/src/providers/PostHogProvider.tsx +33 -0
- package/templates/marketing/payload/src/providers/Theme/InitTheme/{index.tsx.hbs → index.tsx} +11 -10
- package/templates/marketing/payload/src/providers/Theme/ThemeSelector/index.tsx +133 -0
- package/templates/marketing/payload/src/providers/Theme/ThemeSelector/types.ts +7 -0
- package/templates/marketing/payload/src/providers/Theme/index.tsx +60 -0
- package/templates/marketing/payload/src/providers/Theme/shared.ts +17 -0
- package/templates/marketing/payload/src/providers/Theme/{types.ts.hbs → types.ts} +3 -3
- package/templates/marketing/payload/src/providers/index.tsx +17 -0
- package/templates/marketing/payload/src/search/Component.tsx +42 -0
- package/templates/marketing/payload/src/search/beforeSync.ts +56 -0
- package/templates/marketing/payload/src/search/fieldOverrides.ts +61 -0
- package/templates/marketing/payload/src/utilities/deepMerge.ts +35 -0
- package/templates/marketing/payload/src/utilities/extractHeadings.ts +78 -0
- package/templates/marketing/payload/src/utilities/formatAuthors.ts +24 -0
- package/templates/marketing/payload/src/utilities/formatDateTime.ts +20 -0
- package/templates/marketing/payload/src/utilities/generateMeta.ts +93 -0
- package/templates/marketing/payload/src/utilities/generatePreviewPath.ts +33 -0
- package/templates/marketing/payload/src/utilities/getDocument.ts +32 -0
- package/templates/marketing/payload/src/utilities/getGlobals.ts +26 -0
- package/templates/marketing/payload/src/utilities/getMeUser.ts +43 -0
- package/templates/marketing/payload/src/utilities/getMediaUrl.ts +24 -0
- package/templates/marketing/payload/src/utilities/getRedirects.ts +26 -0
- package/templates/marketing/payload/src/utilities/getURL.ts +26 -0
- package/templates/marketing/payload/src/utilities/mergeOpenGraph.ts +26 -0
- package/templates/marketing/payload/src/utilities/toKebabCase.ts +5 -0
- package/templates/marketing/payload/src/utilities/ui.ts +12 -0
- package/templates/marketing/payload/src/utilities/useClickableCard.ts +108 -0
- package/templates/marketing/payload/src/utilities/useDebounce.ts +17 -0
- package/templates/packages/ui/package.json.hbs +4 -0
- package/templates/packages/ui/src/components/button.tsx.hbs +53 -0
- package/templates/packages/ui/src/components/card.tsx.hbs +76 -0
- package/templates/packages/ui/src/components/separator.tsx.hbs +26 -0
- package/templates/{marketing/payload/src/app/globals.css.hbs → packages/ui/src/styles.css.hbs} +39 -1
- package/templates/marketing/payload/src/Footer/config.ts.hbs +0 -178
- package/templates/marketing/payload/src/Footer/index.ts.hbs +0 -1
- package/templates/marketing/payload/src/Header/RowLabel.tsx.hbs +0 -21
- package/templates/marketing/payload/src/Header/config.ts.hbs +0 -208
- package/templates/marketing/payload/src/Header/index.ts.hbs +0 -1
- package/templates/marketing/payload/src/access/index.ts.hbs +0 -3
- package/templates/marketing/payload/src/app/(frontend)/layout.tsx.hbs +0 -19
- package/templates/marketing/payload/src/app/(frontend)/next/seed/route.ts.hbs +0 -31
- package/templates/marketing/payload/src/app/(frontend)/page.tsx.hbs +0 -83
- package/templates/marketing/payload/src/app/(payload)/admin/[[...segments]]/not-found.tsx.hbs +0 -24
- package/templates/marketing/payload/src/app/(payload)/admin/[[...segments]]/page.tsx.hbs +0 -24
- package/templates/marketing/payload/src/app/(payload)/admin/importMap.js.hbs +0 -1
- package/templates/marketing/payload/src/app/(payload)/custom.scss.hbs +0 -1
- package/templates/marketing/payload/src/app/(payload)/layout.tsx.hbs +0 -31
- package/templates/marketing/payload/src/app/layout.tsx.hbs +0 -10
- package/templates/marketing/payload/src/blocks/Benefits.ts.hbs +0 -34
- package/templates/marketing/payload/src/blocks/CTA.ts.hbs +0 -39
- package/templates/marketing/payload/src/blocks/Content.ts.hbs +0 -9
- package/templates/marketing/payload/src/blocks/FAQ.ts.hbs +0 -18
- package/templates/marketing/payload/src/blocks/Features.ts.hbs +0 -32
- package/templates/marketing/payload/src/blocks/Hero.ts.hbs +0 -40
- package/templates/marketing/payload/src/blocks/LogoBanner.ts.hbs +0 -17
- package/templates/marketing/payload/src/blocks/Pricing.ts.hbs +0 -37
- package/templates/marketing/payload/src/blocks/Testimonials.ts.hbs +0 -21
- package/templates/marketing/payload/src/blocks/index.ts.hbs +0 -9
- package/templates/marketing/payload/src/collections/Categories/index.ts.hbs +0 -28
- package/templates/marketing/payload/src/collections/FAQs/index.ts.hbs +0 -100
- package/templates/marketing/payload/src/collections/Media.ts.hbs +0 -164
- package/templates/marketing/payload/src/collections/Pages/hooks/revalidatePage.ts.hbs +0 -43
- package/templates/marketing/payload/src/collections/Pages/index.ts.hbs +0 -142
- package/templates/marketing/payload/src/collections/Posts/hooks/populateAuthors.ts.hbs +0 -41
- package/templates/marketing/payload/src/collections/Posts/hooks/revalidatePost.ts.hbs +0 -44
- package/templates/marketing/payload/src/collections/Posts/index.ts.hbs +0 -244
- package/templates/marketing/payload/src/collections/Users/index.ts.hbs +0 -26
- package/templates/marketing/payload/src/collections/index.ts.hbs +0 -6
- package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/index.tsx.hbs +0 -89
- package/templates/marketing/payload/src/components/BeforeDashboard/index.tsx.hbs +0 -69
- package/templates/marketing/payload/src/components/BeforeLogin/index.tsx.hbs +0 -14
- package/templates/marketing/payload/src/components/Link/index.tsx.hbs +0 -79
- package/templates/marketing/payload/src/components/Media/index.tsx.hbs +0 -67
- package/templates/marketing/payload/src/components/RichText/index.tsx.hbs +0 -44
- package/templates/marketing/payload/src/endpoints/seed/home.ts.hbs +0 -76
- package/templates/marketing/payload/src/endpoints/seed/image-1.ts.hbs +0 -5
- package/templates/marketing/payload/src/endpoints/seed/image-2.ts.hbs +0 -5
- package/templates/marketing/payload/src/endpoints/seed/image-hero.ts.hbs +0 -5
- package/templates/marketing/payload/src/endpoints/seed/index.ts.hbs +0 -235
- package/templates/marketing/payload/src/endpoints/seed/post-1.ts.hbs +0 -252
- package/templates/marketing/payload/src/fields/defaultLexical.ts.hbs +0 -73
- package/templates/marketing/payload/src/fields/link.ts.hbs +0 -139
- package/templates/marketing/payload/src/fields/linkGroup.ts.hbs +0 -28
- package/templates/marketing/payload/src/globals/index.ts.hbs +0 -2
- package/templates/marketing/payload/src/heros/HighImpact/index.tsx.hbs +0 -53
- package/templates/marketing/payload/src/heros/LowImpact/index.tsx.hbs +0 -48
- package/templates/marketing/payload/src/heros/MediumImpact/index.tsx.hbs +0 -46
- package/templates/marketing/payload/src/heros/PostHero/index.tsx.hbs +0 -68
- package/templates/marketing/payload/src/heros/ProductShowcase/index.tsx.hbs +0 -88
- package/templates/marketing/payload/src/heros/config.ts.hbs +0 -112
- package/templates/marketing/payload/src/heros/index.ts.hbs +0 -7
- package/templates/marketing/payload/src/hooks/index.ts.hbs +0 -2
- package/templates/marketing/payload/src/hooks/populatePublishedAt.ts.hbs +0 -15
- package/templates/marketing/payload/src/providers/HeaderTheme/index.tsx.hbs +0 -34
- package/templates/marketing/payload/src/providers/Theme/index.tsx.hbs +0 -60
- package/templates/marketing/payload/src/providers/Theme/shared.ts.hbs +0 -17
- package/templates/marketing/payload/src/providers/index.tsx.hbs +0 -18
- package/templates/marketing/payload/src/utilities/deepMerge.ts.hbs +0 -35
- package/templates/marketing/payload/src/utilities/formatAuthors.ts.hbs +0 -24
- package/templates/marketing/payload/src/utilities/formatDateTime.ts.hbs +0 -13
- package/templates/marketing/payload/src/utilities/generateMeta.ts.hbs +0 -87
- package/templates/marketing/payload/src/utilities/generatePreviewPath.ts.hbs +0 -33
- package/templates/marketing/payload/src/utilities/getURL.ts.hbs +0 -26
- package/templates/marketing/payload/src/utilities/index.ts.hbs +0 -8
- package/templates/marketing/payload/src/utilities/mergeOpenGraph.ts.hbs +0 -26
- /package/templates/marketing/payload/src/access/{anyone.ts.hbs → anyone.ts} +0 -0
- /package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/{index.scss.hbs → index.scss} +0 -0
- /package/templates/marketing/payload/src/components/BeforeDashboard/{index.scss.hbs → index.scss} +0 -0
- /package/templates/marketing/payload/src/fields/{index.ts.hbs → index.ts} +0 -0
- /package/templates/marketing/payload/src/utilities/{canUseDOM.ts.hbs → canUseDOM.ts} +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Metadata } from "next"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/utilities/ui"
|
|
4
|
+
import { GeistMono } from "geist/font/mono"
|
|
5
|
+
import { Inter } from "next/font/google"
|
|
6
|
+
import type React from "react"
|
|
7
|
+
|
|
8
|
+
import { Footer } from "@/Footer/Component"
|
|
9
|
+
import { Header } from "@/Header/Component"
|
|
10
|
+
import { AdminBar } from "@/components/AdminBar"
|
|
11
|
+
import { JsonLdSchemas } from "@/components/JsonLd"
|
|
12
|
+
import { Providers } from "@/providers"
|
|
13
|
+
import { InitTheme } from "@/providers/Theme/InitTheme"
|
|
14
|
+
import { mergeOpenGraph } from "@/utilities/mergeOpenGraph"
|
|
15
|
+
import { draftMode } from "next/headers"
|
|
16
|
+
|
|
17
|
+
import { getServerSideURL } from "@/utilities/getURL"
|
|
18
|
+
import "./globals.css"
|
|
19
|
+
|
|
20
|
+
const inter = Inter({
|
|
21
|
+
subsets: ["latin"],
|
|
22
|
+
variable: "--font-inter",
|
|
23
|
+
display: "swap",
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
27
|
+
const { isEnabled } = await draftMode()
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<html className={cn(inter.variable, GeistMono.variable)} lang="en" suppressHydrationWarning>
|
|
31
|
+
<head>
|
|
32
|
+
<InitTheme />
|
|
33
|
+
{/* Favicon */}
|
|
34
|
+
<link href="/favicon.ico" rel="icon" sizes="32x32" />
|
|
35
|
+
<link href="/favicon.svg" rel="icon" type="image/svg+xml" />
|
|
36
|
+
|
|
37
|
+
{/* Preconnect to external resources for performance */}
|
|
38
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
39
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
|
40
|
+
{/* Preconnect to PostHog proxy for faster analytics initialization */}
|
|
41
|
+
<link rel="preconnect" href="/ingest" />
|
|
42
|
+
|
|
43
|
+
{/* DNS prefetch for common external services */}
|
|
44
|
+
<link rel="dns-prefetch" href="https://www.google-analytics.com" />
|
|
45
|
+
<link rel="dns-prefetch" href="https://www.googletagmanager.com" />
|
|
46
|
+
|
|
47
|
+
{/* JSON-LD Structured Data */}
|
|
48
|
+
<JsonLdSchemas />
|
|
49
|
+
</head>
|
|
50
|
+
<body className="font-sans antialiased" suppressHydrationWarning>
|
|
51
|
+
<Providers>
|
|
52
|
+
<AdminBar
|
|
53
|
+
adminBarProps={{
|
|
54
|
+
preview: isEnabled,
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
<Header />
|
|
59
|
+
<main id="main-content" className="flex-1">
|
|
60
|
+
{children}
|
|
61
|
+
</main>
|
|
62
|
+
<Footer />
|
|
63
|
+
</Providers>
|
|
64
|
+
</body>
|
|
65
|
+
</html>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const metadata: Metadata = {
|
|
70
|
+
metadataBase: new URL(getServerSideURL()),
|
|
71
|
+
title: {
|
|
72
|
+
default: "DirectoryHub - Build Directories That Generate Real Business",
|
|
73
|
+
template: "%s | DirectoryHub",
|
|
74
|
+
},
|
|
75
|
+
description:
|
|
76
|
+
"Launch a profitable directory business in minutes. The no-code platform to build, manage, and monetize niche directory websites.",
|
|
77
|
+
keywords: [
|
|
78
|
+
"directory builder",
|
|
79
|
+
"no-code directory",
|
|
80
|
+
"directory website",
|
|
81
|
+
"niche directory",
|
|
82
|
+
"business directory software",
|
|
83
|
+
"directory platform",
|
|
84
|
+
"monetize directory",
|
|
85
|
+
],
|
|
86
|
+
authors: [{ name: "DirectoryHub", url: getServerSideURL() }],
|
|
87
|
+
creator: "DirectoryHub",
|
|
88
|
+
publisher: "DirectoryHub",
|
|
89
|
+
robots: {
|
|
90
|
+
index: true,
|
|
91
|
+
follow: true,
|
|
92
|
+
googleBot: {
|
|
93
|
+
index: true,
|
|
94
|
+
follow: true,
|
|
95
|
+
"max-video-preview": -1,
|
|
96
|
+
"max-image-preview": "large",
|
|
97
|
+
"max-snippet": -1,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
openGraph: mergeOpenGraph(),
|
|
101
|
+
twitter: {
|
|
102
|
+
card: "summary_large_image",
|
|
103
|
+
creator: "@directoryhub",
|
|
104
|
+
site: "@directoryhub",
|
|
105
|
+
title: "DirectoryHub - Build Directories That Generate Real Business",
|
|
106
|
+
description:
|
|
107
|
+
"Launch a profitable directory business in minutes. The no-code platform to build, manage, and monetize niche directory websites.",
|
|
108
|
+
},
|
|
109
|
+
verification: {
|
|
110
|
+
// Add your verification codes here when available
|
|
111
|
+
// google: 'your-google-verification-code',
|
|
112
|
+
// yandex: 'your-yandex-verification-code',
|
|
113
|
+
},
|
|
114
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { CollectionSlug, PayloadRequest } from "payload"
|
|
2
|
+
import { getPayload } from "payload"
|
|
3
|
+
|
|
4
|
+
import { draftMode } from "next/headers"
|
|
5
|
+
import { redirect } from "next/navigation"
|
|
6
|
+
import type { NextRequest } from "next/server"
|
|
7
|
+
|
|
8
|
+
import configPromise from "@payload-config"
|
|
9
|
+
|
|
10
|
+
export async function GET(req: NextRequest): Promise<Response> {
|
|
11
|
+
const payload = await getPayload({ config: configPromise })
|
|
12
|
+
|
|
13
|
+
const { searchParams } = new URL(req.url)
|
|
14
|
+
|
|
15
|
+
const path = searchParams.get("path")
|
|
16
|
+
const collection = searchParams.get("collection") as CollectionSlug
|
|
17
|
+
const slug = searchParams.get("slug")
|
|
18
|
+
const previewSecret = searchParams.get("previewSecret")
|
|
19
|
+
|
|
20
|
+
if (previewSecret !== process.env.PREVIEW_SECRET) {
|
|
21
|
+
return new Response("You are not allowed to preview this page", { status: 403 })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!path || !collection || !slug) {
|
|
25
|
+
return new Response("Insufficient search params", { status: 404 })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!path.startsWith("/")) {
|
|
29
|
+
return new Response("This endpoint can only be used for relative previews", { status: 500 })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let user
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
user = await payload.auth({
|
|
36
|
+
req: req as unknown as PayloadRequest,
|
|
37
|
+
headers: req.headers,
|
|
38
|
+
})
|
|
39
|
+
} catch (error) {
|
|
40
|
+
payload.logger.error({ err: error }, "Error verifying token for live preview")
|
|
41
|
+
return new Response("You are not allowed to preview this page", { status: 403 })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const draft = await draftMode()
|
|
45
|
+
|
|
46
|
+
if (!user) {
|
|
47
|
+
draft.disable()
|
|
48
|
+
return new Response("You are not allowed to preview this page", { status: 403 })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// You can add additional checks here to see if the user is allowed to preview this page
|
|
52
|
+
|
|
53
|
+
draft.enable()
|
|
54
|
+
|
|
55
|
+
redirect(path)
|
|
56
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { seed } from "@/endpoints/seed"
|
|
2
|
+
import config from "@payload-config"
|
|
3
|
+
import { headers } from "next/headers"
|
|
4
|
+
import { createLocalReq, getPayload } from "payload"
|
|
5
|
+
|
|
6
|
+
export const maxDuration = 60 // This function can run for a maximum of 60 seconds
|
|
7
|
+
|
|
8
|
+
export async function POST(): Promise<Response> {
|
|
9
|
+
const payload = await getPayload({ config })
|
|
10
|
+
const requestHeaders = await headers()
|
|
11
|
+
|
|
12
|
+
// Authenticate by passing request headers
|
|
13
|
+
const { user } = await payload.auth({ headers: requestHeaders })
|
|
14
|
+
|
|
15
|
+
if (!user) {
|
|
16
|
+
return new Response("Action forbidden.", { status: 403 })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Create a Payload request object to pass to the Local API for transactions
|
|
21
|
+
// At this point you should pass in a user, locale, and any other context you need for the Local API
|
|
22
|
+
const payloadReq = await createLocalReq({ user }, payload)
|
|
23
|
+
|
|
24
|
+
await seed({ payload, req: payloadReq })
|
|
25
|
+
|
|
26
|
+
return Response.json({ success: true })
|
|
27
|
+
} catch (e) {
|
|
28
|
+
payload.logger.error({ err: e, message: "Error seeding data" })
|
|
29
|
+
return new Response("Error seeding data.", { status: 500 })
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Link from "next/link"
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button"
|
|
4
|
+
|
|
5
|
+
export default function NotFound() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="container py-28">
|
|
8
|
+
<div className="prose max-w-none">
|
|
9
|
+
<h1 style={{ marginBottom: 0 }}>404</h1>
|
|
10
|
+
<p className="mb-4">This page could not be found.</p>
|
|
11
|
+
</div>
|
|
12
|
+
<Button asChild variant="default">
|
|
13
|
+
<Link href="/">Go home</Link>
|
|
14
|
+
</Button>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Card, type CardPostData } from "@/components/Card"
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
5
|
+
import { Input } from "@/components/ui/input"
|
|
6
|
+
import type { Category } from "@/payload-types"
|
|
7
|
+
import { useHeaderTheme } from "@/providers/HeaderTheme"
|
|
8
|
+
import { Search, X } from "lucide-react"
|
|
9
|
+
import { useEffect, useMemo, useState } from "react"
|
|
10
|
+
|
|
11
|
+
interface BlogPageClientProps {
|
|
12
|
+
initialPosts: CardPostData[]
|
|
13
|
+
categories: Category[]
|
|
14
|
+
totalPosts: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const BlogPageClient: React.FC<BlogPageClientProps> = ({
|
|
18
|
+
initialPosts,
|
|
19
|
+
categories,
|
|
20
|
+
totalPosts,
|
|
21
|
+
}) => {
|
|
22
|
+
const { setHeaderTheme } = useHeaderTheme()
|
|
23
|
+
const [searchQuery, setSearchQuery] = useState("")
|
|
24
|
+
const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
setHeaderTheme("light")
|
|
28
|
+
}, [setHeaderTheme])
|
|
29
|
+
|
|
30
|
+
// Filter posts based on search query and selected category
|
|
31
|
+
const filteredPosts = useMemo(() => {
|
|
32
|
+
return initialPosts.filter((post) => {
|
|
33
|
+
// Search filter
|
|
34
|
+
const matchesSearch =
|
|
35
|
+
searchQuery === "" ||
|
|
36
|
+
post.title?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
37
|
+
post.meta?.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
|
38
|
+
|
|
39
|
+
// Category filter
|
|
40
|
+
const matchesCategory =
|
|
41
|
+
!selectedCategory ||
|
|
42
|
+
(Array.isArray(post.categories) &&
|
|
43
|
+
post.categories.some((cat) => {
|
|
44
|
+
if (typeof cat === "object" && cat !== null) {
|
|
45
|
+
return String(cat.id) === selectedCategory
|
|
46
|
+
}
|
|
47
|
+
return String(cat) === selectedCategory
|
|
48
|
+
}))
|
|
49
|
+
|
|
50
|
+
return matchesSearch && matchesCategory
|
|
51
|
+
})
|
|
52
|
+
}, [initialPosts, searchQuery, selectedCategory])
|
|
53
|
+
|
|
54
|
+
// Get featured post (first post when no filters)
|
|
55
|
+
const featuredPost = searchQuery === "" && !selectedCategory ? filteredPosts[0] : null
|
|
56
|
+
const regularPosts = featuredPost ? filteredPosts.slice(1) : filteredPosts
|
|
57
|
+
|
|
58
|
+
const clearFilters = () => {
|
|
59
|
+
setSearchQuery("")
|
|
60
|
+
setSelectedCategory(null)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const hasActiveFilters = searchQuery !== "" || selectedCategory !== null
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="pt-24 pb-24">
|
|
67
|
+
{/* Hero Section */}
|
|
68
|
+
<div className="container mb-12">
|
|
69
|
+
<div className="max-w-4xl">
|
|
70
|
+
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight mb-4">
|
|
71
|
+
Blog & Resources
|
|
72
|
+
</h1>
|
|
73
|
+
<p className="text-lg md:text-xl text-muted-foreground mb-8">
|
|
74
|
+
Learn how to build, grow, and monetize directory websites. Strategies, tutorials, and
|
|
75
|
+
success stories to help you succeed.
|
|
76
|
+
</p>
|
|
77
|
+
|
|
78
|
+
{/* Search Bar */}
|
|
79
|
+
<div className="relative max-w-xl">
|
|
80
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
|
|
81
|
+
<Input
|
|
82
|
+
type="text"
|
|
83
|
+
placeholder="Search articles..."
|
|
84
|
+
value={searchQuery}
|
|
85
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
86
|
+
className="pl-10 pr-10 h-12 text-base"
|
|
87
|
+
/>
|
|
88
|
+
{searchQuery && (
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
onClick={() => setSearchQuery("")}
|
|
92
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
93
|
+
aria-label="Clear search"
|
|
94
|
+
>
|
|
95
|
+
<X className="h-5 w-5" aria-hidden="true" />
|
|
96
|
+
</button>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Category Filters */}
|
|
103
|
+
<div className="container mb-8">
|
|
104
|
+
<div className="flex flex-wrap gap-2">
|
|
105
|
+
<Button
|
|
106
|
+
variant={selectedCategory === null ? "default" : "outline"}
|
|
107
|
+
size="sm"
|
|
108
|
+
onClick={() => setSelectedCategory(null)}
|
|
109
|
+
>
|
|
110
|
+
All Posts
|
|
111
|
+
</Button>
|
|
112
|
+
{categories.map((category) => (
|
|
113
|
+
<Button
|
|
114
|
+
type="button"
|
|
115
|
+
key={category.id}
|
|
116
|
+
variant={selectedCategory === String(category.id) ? "default" : "outline"}
|
|
117
|
+
size="sm"
|
|
118
|
+
onClick={() => setSelectedCategory(String(category.id))}
|
|
119
|
+
>
|
|
120
|
+
{category.title}
|
|
121
|
+
</Button>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Results Count & Clear Filters */}
|
|
127
|
+
<div className="container mb-8">
|
|
128
|
+
<div className="flex items-center justify-between">
|
|
129
|
+
<p className="text-sm text-muted-foreground">
|
|
130
|
+
{hasActiveFilters ? (
|
|
131
|
+
<>
|
|
132
|
+
Showing {filteredPosts.length} of {totalPosts} articles
|
|
133
|
+
</>
|
|
134
|
+
) : (
|
|
135
|
+
<>{totalPosts} articles</>
|
|
136
|
+
)}
|
|
137
|
+
</p>
|
|
138
|
+
{hasActiveFilters && (
|
|
139
|
+
<Button variant="ghost" size="sm" onClick={clearFilters}>
|
|
140
|
+
Clear filters
|
|
141
|
+
</Button>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{/* Featured Post */}
|
|
147
|
+
{featuredPost && (
|
|
148
|
+
<div className="container mb-12">
|
|
149
|
+
<div className="relative">
|
|
150
|
+
<span className="absolute -top-3 left-4 bg-primary text-primary-foreground text-xs font-medium px-2 py-1 rounded z-10">
|
|
151
|
+
Featured
|
|
152
|
+
</span>
|
|
153
|
+
<Card
|
|
154
|
+
doc={featuredPost}
|
|
155
|
+
relationTo="posts"
|
|
156
|
+
showCategories
|
|
157
|
+
className="lg:grid lg:grid-cols-2 lg:gap-8"
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{/* Posts Grid */}
|
|
164
|
+
<div className="container">
|
|
165
|
+
{regularPosts.length > 0 ? (
|
|
166
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
|
167
|
+
{regularPosts.map((post, index) => (
|
|
168
|
+
<Card
|
|
169
|
+
key={post.slug || index}
|
|
170
|
+
doc={post}
|
|
171
|
+
relationTo="posts"
|
|
172
|
+
showCategories
|
|
173
|
+
className="h-full"
|
|
174
|
+
/>
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
) : (
|
|
178
|
+
<div className="text-center py-16">
|
|
179
|
+
<p className="text-lg text-muted-foreground mb-4">
|
|
180
|
+
No articles found matching your criteria.
|
|
181
|
+
</p>
|
|
182
|
+
<Button variant="outline" onClick={clearFilters}>
|
|
183
|
+
Clear filters
|
|
184
|
+
</Button>
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import type { HeadingItem } from "@/utilities/extractHeadings"
|
|
4
|
+
import type { DefaultTypedEditorState } from "@payloadcms/richtext-lexical"
|
|
5
|
+
|
|
6
|
+
import RichText from "@/components/RichText"
|
|
7
|
+
import { TableOfContents } from "@/components/TableOfContents"
|
|
8
|
+
import { slugify } from "@/utilities/extractHeadings"
|
|
9
|
+
import { useEffect, useRef } from "react"
|
|
10
|
+
|
|
11
|
+
interface BlogPostContentProps {
|
|
12
|
+
content: DefaultTypedEditorState
|
|
13
|
+
headings: HeadingItem[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function BlogPostContent({ content, headings }: BlogPostContentProps) {
|
|
17
|
+
const contentRef = useRef<HTMLDivElement>(null)
|
|
18
|
+
|
|
19
|
+
// Add IDs to headings after mount for TOC linking
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!contentRef.current) return
|
|
22
|
+
|
|
23
|
+
const headingElements = contentRef.current.querySelectorAll("h1, h2, h3, h4, h5, h6")
|
|
24
|
+
for (const heading of headingElements) {
|
|
25
|
+
const text = heading.textContent || ""
|
|
26
|
+
const id = slugify(text)
|
|
27
|
+
if (!heading.id) {
|
|
28
|
+
heading.id = id
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}, [])
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="container py-12">
|
|
35
|
+
<div className="flex flex-col lg:flex-row gap-8 lg:gap-12">
|
|
36
|
+
{/* Sticky TOC Sidebar - Hidden on mobile */}
|
|
37
|
+
<aside className="hidden lg:block lg:w-64 xl:w-72 flex-shrink-0">
|
|
38
|
+
<div className="sticky top-24">
|
|
39
|
+
<TableOfContents
|
|
40
|
+
headings={headings}
|
|
41
|
+
signUpCta={{
|
|
42
|
+
title: "Experience DirectoryHub",
|
|
43
|
+
description: "Start building your directory today",
|
|
44
|
+
buttonText: "Sign up for free",
|
|
45
|
+
buttonLink: "https://app.directoryhub.app/sign-up",
|
|
46
|
+
imageSrc: "/media/hero-dashboard-500x500.webp",
|
|
47
|
+
imageAlt: "DirectoryHub dashboard feature",
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
</aside>
|
|
52
|
+
|
|
53
|
+
{/* Main Content */}
|
|
54
|
+
<main className="flex-1 min-w-0" ref={contentRef}>
|
|
55
|
+
{content && (
|
|
56
|
+
<RichText
|
|
57
|
+
className="max-w-none prose-headings:scroll-mt-24"
|
|
58
|
+
data={content}
|
|
59
|
+
enableGutter={false}
|
|
60
|
+
enableProse={true}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
</main>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import { useHeaderTheme } from "@/providers/HeaderTheme"
|
|
3
|
+
import React, { useEffect } from "react"
|
|
4
|
+
|
|
5
|
+
const PageClient: React.FC = () => {
|
|
6
|
+
/* Force the header to be dark mode while we have an image behind it */
|
|
7
|
+
const { setHeaderTheme } = useHeaderTheme()
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
setHeaderTheme("dark")
|
|
11
|
+
}, [setHeaderTheme])
|
|
12
|
+
return <React.Fragment />
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default PageClient
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Metadata } from "next"
|
|
2
|
+
|
|
3
|
+
import { RelatedPosts } from "@/blocks/RelatedPosts/Component"
|
|
4
|
+
import { BlogCTA } from "@/components/BlogCTA"
|
|
5
|
+
import { PayloadRedirects } from "@/components/PayloadRedirects"
|
|
6
|
+
import configPromise from "@payload-config"
|
|
7
|
+
import { draftMode } from "next/headers"
|
|
8
|
+
import { getPayload } from "payload"
|
|
9
|
+
import { cache } from "react"
|
|
10
|
+
|
|
11
|
+
import { LivePreviewListener } from "@/components/LivePreviewListener"
|
|
12
|
+
import { PostHero } from "@/heros/PostHero"
|
|
13
|
+
import { extractHeadingsFromLexical } from "@/utilities/extractHeadings"
|
|
14
|
+
import { generateMeta } from "@/utilities/generateMeta"
|
|
15
|
+
import { BlogPostContent } from "./BlogPostContent"
|
|
16
|
+
import PageClient from "./page.client"
|
|
17
|
+
|
|
18
|
+
export async function generateStaticParams() {
|
|
19
|
+
const payload = await getPayload({ config: configPromise })
|
|
20
|
+
const posts = await payload.find({
|
|
21
|
+
collection: "posts",
|
|
22
|
+
draft: false,
|
|
23
|
+
limit: 1000,
|
|
24
|
+
overrideAccess: false,
|
|
25
|
+
pagination: false,
|
|
26
|
+
select: {
|
|
27
|
+
slug: true,
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const params = posts.docs.map(({ slug }) => {
|
|
32
|
+
return { slug }
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return params
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type Args = {
|
|
39
|
+
params: Promise<{
|
|
40
|
+
slug?: string
|
|
41
|
+
}>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default async function Post({ params: paramsPromise }: Args) {
|
|
45
|
+
const { isEnabled: draft } = await draftMode()
|
|
46
|
+
const { slug = "" } = await paramsPromise
|
|
47
|
+
// Decode to support slugs with special characters
|
|
48
|
+
const decodedSlug = decodeURIComponent(slug)
|
|
49
|
+
const url = `/posts/${decodedSlug}`
|
|
50
|
+
const post = await queryPostBySlug({ slug: decodedSlug })
|
|
51
|
+
|
|
52
|
+
if (!post) return <PayloadRedirects url={url} />
|
|
53
|
+
|
|
54
|
+
// Extract headings for table of contents
|
|
55
|
+
const headings = extractHeadingsFromLexical(post.content)
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<article className="pb-0">
|
|
59
|
+
<PageClient />
|
|
60
|
+
|
|
61
|
+
{/* Allows redirects for valid pages too */}
|
|
62
|
+
<PayloadRedirects disableNotFound url={url} />
|
|
63
|
+
|
|
64
|
+
{draft && <LivePreviewListener />}
|
|
65
|
+
|
|
66
|
+
{/* Hero Section */}
|
|
67
|
+
<PostHero post={post} />
|
|
68
|
+
|
|
69
|
+
{/* Two-column layout with TOC and content */}
|
|
70
|
+
<BlogPostContent content={post.content} headings={headings} />
|
|
71
|
+
|
|
72
|
+
{/* Related Posts */}
|
|
73
|
+
{post.relatedPosts && post.relatedPosts.length > 0 && (
|
|
74
|
+
<div className="container py-12 border-t border-border">
|
|
75
|
+
<h2 className="text-2xl font-bold mb-8">Related Articles</h2>
|
|
76
|
+
<RelatedPosts
|
|
77
|
+
className="max-w-none"
|
|
78
|
+
docs={post.relatedPosts.filter((post) => typeof post === "object")}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
{/* Bottom CTA */}
|
|
84
|
+
<BlogCTA />
|
|
85
|
+
</article>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function generateMetadata({ params: paramsPromise }: Args): Promise<Metadata> {
|
|
90
|
+
const { slug = "" } = await paramsPromise
|
|
91
|
+
// Decode to support slugs with special characters
|
|
92
|
+
const decodedSlug = decodeURIComponent(slug)
|
|
93
|
+
const post = await queryPostBySlug({ slug: decodedSlug })
|
|
94
|
+
|
|
95
|
+
return generateMeta({ doc: post })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const queryPostBySlug = cache(async ({ slug }: { slug: string }) => {
|
|
99
|
+
const { isEnabled: draft } = await draftMode()
|
|
100
|
+
|
|
101
|
+
const payload = await getPayload({ config: configPromise })
|
|
102
|
+
|
|
103
|
+
const result = await payload.find({
|
|
104
|
+
collection: "posts",
|
|
105
|
+
depth: 2, // Populate relationships in rich text content
|
|
106
|
+
draft,
|
|
107
|
+
limit: 1,
|
|
108
|
+
overrideAccess: draft,
|
|
109
|
+
pagination: false,
|
|
110
|
+
where: {
|
|
111
|
+
slug: {
|
|
112
|
+
equals: slug,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return result.docs?.[0] || null
|
|
118
|
+
})
|
package/templates/marketing/payload/src/app/(frontend)/posts/page/[pageNumber]/page.client.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import { useHeaderTheme } from "@/providers/HeaderTheme"
|
|
3
|
+
import React, { useEffect } from "react"
|
|
4
|
+
|
|
5
|
+
const PageClient: React.FC = () => {
|
|
6
|
+
/* Force the header to be dark mode while we have an image behind it */
|
|
7
|
+
const { setHeaderTheme } = useHeaderTheme()
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
setHeaderTheme("light")
|
|
11
|
+
}, [setHeaderTheme])
|
|
12
|
+
return <React.Fragment />
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default PageClient
|