Conversation
Introduce a Landing Page Builder module: update Prisma schema to add LandingPage and LandingPageVersion models, enums (LandingPageStatus, LandingPageCategory), and a relation from Store to landingPages (with indexes and unique constraint on storeId+slug). Add TypeScript core types and utilities (src/lib/landing-pages/types.ts) describing template, field and section systems and default theme colors. Register several code-defined templates in src/lib/landing-pages/templates/* (black-friday-mega-sale, discount-sale-promo, ecommerce-product-launch, lead-generation-classic, local-business-showcase). This enables code-managed templates with DB-stored vendor overrides and immutable published version snapshots.
Introduce a landing page template system: adds a template engine (resolveLandingPage, sanitiseCss, merge logic, utilities) that merges template defaults with vendor customData and sanitises custom CSS; a template registry with O(1) lookups and listing APIs; and a new 'restaurant-menu-promo' template definition. These files centralise template definitions, provide helper functions (getTemplate, getTemplates, getTemplateListItems, validateTemplateId, getDefaultSectionOrder, nextVersion) and ship a sample premium restaurant template with sections, fields and default data.
Introduce a landing pages API route and a Prisma-backed service for managing landing pages. The API (GET/POST/HEAD) supports listing with pagination and filters, creating pages (with slug sanitization and validation), and returning available templates/categories. The service implements multi-tenant-safe DB operations (list, get, getBySlug, create, update, publish/unpublish with version snapshots, duplicate, soft delete, and increment views), includes template validation, slug-uniqueness checks, default section ordering, and error handling.
Introduce new API routes for landing pages: POST /api/landing-pages/:id/duplicate to clone pages, POST/DELETE /api/landing-pages/:id/publish to publish/unpublish, and CRUD for /api/landing-pages/:id (GET, PATCH with slug sanitization, DELETE soft-delete). Add a dedicated /api/landing-pages/templates GET route with category/summary query options and caching headers. Update the main landing-pages route HEAD handler signature and expose template helper aliases in template-registry.ts (getAllTemplates, getTemplatesByCategory, getTemplateSummaries). Routes use requireStoreId for auth and include error handling for not-found and server errors; publish uses prisma to resolve store slug.
Introduce a new dashboard page for managing landing pages. The page performs a server-side session check and requires a storeId (redirecting to /login or /dashboard as needed), fetches up to 50 landing pages via listLandingPages, and renders the UI using SidebarProvider, AppSidebar, SiteHeader and LandingPageListClient (providing initialPages). Also configures sidebar/header CSS custom properties.
Create a new page at src/app/dashboard/landing-pages/new/page.tsx that provides a template chooser for creating landing pages. The page enforces authentication via getServerSession (redirecting to /login if unauthenticated), retrieves available templates from the template registry, and renders the TemplateGalleryClient inside the app sidebar and header layout (SidebarProvider, AppSidebar, SidebarInset, SiteHeader). Also sets page metadata title to "Choose a Template".
Add server route for editing landing pages and two client components for managing pages and templates. Creates src/app/dashboard/landing-pages/[id]/edit/page.tsx which validates session/store, loads the page and template, resolves defaults and renders LandingPageEditorClient. Adds LandingPageListClient (listing, duplicate, publish/unpublish, delete) and TemplateGalleryClient (search, categories, create flow) UI components. Update types/services: include storeId on LandingPageDetail and return storeId from getLandingPage and getLandingPageBySlug to keep type/service data consistent.
Introduce a client-side React component for editing landing pages (src/components/landing-pages/landing-page-editor-client.tsx). Implements field and section editors, a theme color panel, SEO and custom CSS editors, and a live preview area. Uses Next.js client hooks (useRouter, useTransition) and local state to mirror the resolved page, supports toggling hidden sections, patching custom data, and saving/publishing via /api/landing-pages endpoints. Integrates existing UI primitives (Tabs, Accordion, Input, Textarea, Switch, Badge) and iconography for a full editor UX.
Add a server-rendered public landing page at /lp/[storeSlug]/[pageSlug]. The route finds the store and its PUBLISHED landing page (via Prisma and landing-page service), resolves template and vendor customData, and renders the page with LandingPageRenderer (no client JS required). It also increments page views asynchronously (fire-and-forget), provides generateMetadata (seo title/description and OG image), and forces dynamic rendering for fresh data per request.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Introduce a server-only LandingPageRenderer component that renders a fully-resolved landing page to static HTML with inline CSS and no client JS. It builds CSS variables from themeColors, injects optional sanitized custom CSS, and renders sections via a SectionSwitch (Hero and Generic fallbacks). Includes simple helpers for safe text rendering and a fallback field-list renderer for unknown section types to keep output cacheable and predictable.
Import Prisma and LandingPageStatus and tighten Prisma typings across landing page operations. Cast status in listLandingPages, replace spread-based update data with a Prisma.LandingPageUncheckedUpdateInput built only from provided fields, and cast JSON fields (customData, themeColors) to Prisma.InputJsonValue. Map null themeColors to Prisma.DbNull when publishing/duplicating. These changes prevent passing undefined/incorrect types to Prisma and ensure proper storage of JSON and NULL values.
Remove the inline eslint-disable-next-line comment that suppressed the react/no-danger rule on the <style dangerouslySetInnerHTML> line. No functional changes; this restores linting so the rule can report any potential issues.
There was a problem hiding this comment.
Pull request overview
Introduces a new “Landing Pages” module: code-defined template registry + DB-backed landing page instances, with dashboard UI and API routes to create/edit/publish pages and a public render route.
Changes:
- Added Prisma models/enums for landing pages + versions, and a service layer for CRUD/publish/duplicate.
- Added landing page template system (types, registry, resolver engine) with several starter templates.
- Added dashboard pages/components and API routes for listing/creating/editing/publishing, plus a public
/lp/[storeSlug]/[pageSlug]route.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
src/lib/services/landing-page-service.ts |
DB service for listing/getting/creating/updating/publishing/duplicating/deleting landing pages + view increment. |
src/lib/landing-pages/types.ts |
Core type system for templates/sections/fields and API DTOs for landing pages. |
src/lib/landing-pages/templates/restaurant-menu-promo.ts |
Adds a RESTAURANT template definition. |
src/lib/landing-pages/templates/local-business-showcase.ts |
Adds a LOCAL_BUSINESS template definition. |
src/lib/landing-pages/templates/lead-generation-classic.ts |
Adds a LEAD_GENERATION template definition. |
src/lib/landing-pages/templates/ecommerce-product-launch.ts |
Adds a PRODUCT_LAUNCH template definition. |
src/lib/landing-pages/templates/discount-sale-promo.ts |
Adds a DISCOUNT_SALE template definition. |
src/lib/landing-pages/templates/black-friday-mega-sale.ts |
Adds a BLACK_FRIDAY template definition. |
src/lib/landing-pages/template-registry.ts |
Central in-memory registry/indexes for all templates and lightweight summaries. |
src/lib/landing-pages/template-engine.ts |
Resolver that merges template defaults + DB overrides and sanitises custom CSS. |
src/components/landing-pages/template-gallery-client.tsx |
Client UI to browse templates and create a new landing page. |
src/components/landing-pages/landing-page-list-client.tsx |
Client UI for listing pages + duplicate/publish/delete actions. |
src/components/landing-pages/landing-page-editor-client.tsx |
Client UI for editing section fields, theme colors, and SEO settings. |
src/app/lp/[storeSlug]/[pageSlug]/page.tsx |
Public render route for published landing pages + metadata and view increment. |
src/app/dashboard/landing-pages/page.tsx |
Dashboard landing pages index page wiring server data to client list UI. |
src/app/dashboard/landing-pages/new/page.tsx |
Dashboard “choose template” page. |
src/app/dashboard/landing-pages/[id]/edit/page.tsx |
Dashboard edit page that resolves template + page state for the editor. |
src/app/api/landing-pages/templates/route.ts |
Public API endpoint to list templates (optionally by category / summary). |
src/app/api/landing-pages/route.ts |
Authenticated API to list/create landing pages (plus template discovery helper). |
src/app/api/landing-pages/[id]/route.ts |
Authenticated API to get/update/delete a single landing page. |
src/app/api/landing-pages/[id]/publish/route.ts |
Authenticated API to publish/unpublish a landing page. |
src/app/api/landing-pages/[id]/duplicate/route.ts |
Authenticated API to duplicate a landing page. |
prisma/schema.prisma |
Adds landing page enums/models and Store relation. |
| export async function incrementViews(pageId: string): Promise<void> { | ||
| await prisma.landingPage.updateMany({ | ||
| where: { id: pageId, deletedAt: null }, |
There was a problem hiding this comment.
incrementViews contradicts the module contract (“Every method MUST receive and use storeId”) and updates by id only. This weakens tenant isolation and makes it easy to increment views for any page id; change it to accept storeId and include it in the where clause (and update the caller).
| export async function incrementViews(pageId: string): Promise<void> { | |
| await prisma.landingPage.updateMany({ | |
| where: { id: pageId, deletedAt: null }, | |
| export async function incrementViews( | |
| storeId: string, | |
| pageId: string | |
| ): Promise<void> { | |
| await prisma.landingPage.updateMany({ | |
| where: { id: pageId, storeId, deletedAt: null }, |
|
|
||
| // Create a version snapshot | ||
| await prisma.landingPageVersion.create({ | ||
| data: { | ||
| pageId, | ||
| version: nextVersion(p.currentVersion), | ||
| title: p.title, | ||
| customData: (p.customData ?? {}) as Prisma.InputJsonValue, | ||
| sectionOrder: p.sectionOrder ?? [], | ||
| hiddenSections: p.hiddenSections ?? [], | ||
| themeColors: p.themeColors !== null ? (p.themeColors as Prisma.InputJsonValue) : Prisma.DbNull, | ||
| customCss: p.customCss, | ||
| }, | ||
| }); | ||
|
|
||
| await prisma.landingPage.update({ | ||
| where: { id: pageId }, | ||
| data: { | ||
| status: "PUBLISHED", | ||
| publishedAt: new Date(), | ||
| publishedUrl, | ||
| currentVersion: nextVersion(p.currentVersion), | ||
| }, |
There was a problem hiding this comment.
Publishing is two separate DB writes (create version + update page) without a transaction. If the update fails after the version snapshot is created, you’ll leave an orphaned/newer version while the page remains in the prior state; wrap the version create + page update in a single prisma.$transaction and compute the next version once.
| // Create a version snapshot | |
| await prisma.landingPageVersion.create({ | |
| data: { | |
| pageId, | |
| version: nextVersion(p.currentVersion), | |
| title: p.title, | |
| customData: (p.customData ?? {}) as Prisma.InputJsonValue, | |
| sectionOrder: p.sectionOrder ?? [], | |
| hiddenSections: p.hiddenSections ?? [], | |
| themeColors: p.themeColors !== null ? (p.themeColors as Prisma.InputJsonValue) : Prisma.DbNull, | |
| customCss: p.customCss, | |
| }, | |
| }); | |
| await prisma.landingPage.update({ | |
| where: { id: pageId }, | |
| data: { | |
| status: "PUBLISHED", | |
| publishedAt: new Date(), | |
| publishedUrl, | |
| currentVersion: nextVersion(p.currentVersion), | |
| }, | |
| const newVersion = nextVersion(p.currentVersion); | |
| await prisma.$transaction(async (tx) => { | |
| // Create a version snapshot | |
| await tx.landingPageVersion.create({ | |
| data: { | |
| pageId, | |
| version: newVersion, | |
| title: p.title, | |
| customData: (p.customData ?? {}) as Prisma.InputJsonValue, | |
| sectionOrder: p.sectionOrder ?? [], | |
| hiddenSections: p.hiddenSections ?? [], | |
| themeColors: | |
| p.themeColors !== null | |
| ? (p.themeColors as Prisma.InputJsonValue) | |
| : Prisma.DbNull, | |
| customCss: p.customCss, | |
| }, | |
| }); | |
| await tx.landingPage.update({ | |
| where: { id: pageId }, | |
| data: { | |
| status: "PUBLISHED", | |
| publishedAt: new Date(), | |
| publishedUrl, | |
| currentVersion: newVersion, | |
| }, | |
| }); |
| import { Layers, ArrowRight, Star } from "lucide-react"; | ||
| import type { TemplateListItem, LandingPageCategory } from "@/lib/landing-pages/types"; | ||
|
|
There was a problem hiding this comment.
Unused type import: LandingPageCategory is imported but never used. This will fail linting under @typescript-eslint/no-unused-vars; remove it or use it for activeCategory typing.
| export async function GET(request: NextRequest) { | ||
| try { | ||
| let storeId: string; | ||
| try { | ||
| storeId = await requireStoreId(); | ||
| } catch { | ||
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | ||
| } | ||
|
|
||
| const { searchParams } = new URL(request.url); | ||
| const status = searchParams.get("status") ?? undefined; | ||
| const limit = Math.min(parseInt(searchParams.get("limit") ?? "20", 10), 100); | ||
| const cursor = searchParams.get("cursor") ?? undefined; | ||
|
|
||
| const result = await listLandingPages(storeId, { status, limit, cursor }); | ||
|
|
||
| return NextResponse.json(result); | ||
| } catch (error) { | ||
| console.error("[landing-pages] GET error:", error); | ||
| return NextResponse.json({ error: "Internal server error" }, { status: 500 }); | ||
| } | ||
| } |
There was a problem hiding this comment.
This PR introduces a new multi-tenant CRUD surface (/api/landing-pages, publish/duplicate, etc.) but there are no accompanying Vitest API tests (the repo already tests other API routes under src/test/api). Adding coverage for store isolation (cannot access another store’s pages), slug uniqueness/conflict handling, and publish/unpublish flows would help prevent regressions.
| import { prisma } from "@/lib/prisma"; | ||
| import { getLandingPageBySlug, incrementViews } from "@/lib/services/landing-page-service"; | ||
| import { resolveLandingPage } from "@/lib/landing-pages/template-engine"; | ||
| import { getTemplate } from "@/lib/landing-pages/template-registry"; | ||
| import { LandingPageRenderer } from "@/components/landing-pages/landing-page-renderer"; | ||
|
|
There was a problem hiding this comment.
LandingPageRenderer is imported from @/components/landing-pages/landing-page-renderer, but that file/component doesn’t exist in the repo (only editor/list/gallery exist under src/components/landing-pages). This will fail the build with a module-not-found error; either add the missing renderer component or update the import to an existing module.
| const page = await updateLandingPage(storeId, id, body); | ||
| return NextResponse.json(page); | ||
| } catch (error) { | ||
| if (error instanceof Error && error.message === "Landing page not found") { | ||
| return NextResponse.json({ error: "Not found" }, { status: 404 }); | ||
| } | ||
| if (error instanceof Error && error.message.includes("already taken")) { | ||
| return NextResponse.json({ error: error.message }, { status: 409 }); | ||
| } | ||
| console.error("[landing-pages/:id] PATCH error:", error); | ||
| return NextResponse.json({ error: "Internal server error" }, { status: 500 }); | ||
| } |
There was a problem hiding this comment.
PATCH can also hit Prisma unique constraint errors when changing slug, but the catch block only checks for the service’s “already taken” message. Handle Prisma unique constraint violations here as well so slug conflicts reliably return 409 instead of 500.
| @@ -0,0 +1,28 @@ | |||
| /** | |||
| * GET /api/templates — list all available landing page templates | |||
There was a problem hiding this comment.
The header comment says “GET /api/templates”, but the route is mounted at /api/landing-pages/templates. Update the comment to match the actual path to avoid misleading API consumers.
| * GET /api/templates — list all available landing page templates | |
| * GET /api/landing-pages/templates — list all available landing page templates |
| const { searchParams } = new URL(request.url); | ||
| const status = searchParams.get("status") ?? undefined; | ||
| const limit = Math.min(parseInt(searchParams.get("limit") ?? "20", 10), 100); | ||
| const cursor = searchParams.get("cursor") ?? undefined; | ||
|
|
||
| const result = await listLandingPages(storeId, { status, limit, cursor }); |
There was a problem hiding this comment.
Query parsing can produce invalid values: parseInt() can return NaN, and status is accepted as any string (invalid enum values will throw inside Prisma and become a 500). Validate/coerce limit (default to 20 on NaN) and validate status against the LandingPageStatus enum, returning 400 for invalid input.
| return ( | ||
| <div className={isHidden ? "opacity-50 pointer-events-none" : ""}> | ||
| <div className="flex items-center justify-between mb-3"> | ||
| <div> | ||
| <p className="text-sm font-medium">{section.label}</p> | ||
| {section.description && ( | ||
| <p className="text-xs text-muted-foreground mt-0.5">{section.description}</p> | ||
| )} | ||
| </div> | ||
| {!section.isRequired && ( | ||
| <div className="flex items-center gap-2"> | ||
| <Label className="text-xs text-muted-foreground"> | ||
| {isHidden ? "Hidden" : "Visible"} | ||
| </Label> | ||
| <Switch | ||
| checked={!isHidden} | ||
| onCheckedChange={onToggleHidden} | ||
| /> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
When a section is hidden you apply pointer-events-none to the entire SectionEditor wrapper, which also disables the visibility toggle switch. That makes hidden sections impossible to re-enable; only disable the field inputs (or conditionally disable inputs) while keeping the header/toggle interactive.
| const page = await createLandingPage(storeId, { ...body, slug }); | ||
|
|
||
| return NextResponse.json(page, { status: 201 }); | ||
| } catch (error) { | ||
| if (error instanceof Error && error.message.includes("already exists")) { | ||
| return NextResponse.json({ error: error.message }, { status: 409 }); | ||
| } | ||
| if (error instanceof Error && error.message.includes("does not exist")) { | ||
| return NextResponse.json({ error: error.message }, { status: 422 }); | ||
| } | ||
| console.error("[landing-pages] POST error:", error); | ||
| return NextResponse.json({ error: "Internal server error" }, { status: 500 }); | ||
| } |
There was a problem hiding this comment.
Create/update error handling relies on custom Error.message strings, but Prisma unique constraint violations (e.g. concurrent creates/slug updates) will throw a Prisma error that won’t match includes("already exists"). Handle Prisma unique constraint errors (e.g. P2002) and return 409 so races don’t degrade into 500s.
Add two new landing page templates (eid-mega-sale, pohela-boishakh-sale) and register them for use. Implement a server-rendered preview page for landing pages with auth and store checks (src/app/dashboard/landing-pages/[id]/preview/page.tsx). Expose a "Landing Pages" entry in the app sidebar navigation. Simplify LandingPageRenderer by removing the CSS var builder and inline css injection. These changes enable building and previewing seasonal campaign templates in the dashboard.
Introduce nine new Bangladesh-focused landing page templates under src/lib/landing-pages/templates: affiliate-product-review-bd, beauty-cosmetics-bd, digital-agency-bd, fcommerce-fashion-store, flash-sale-daraz-style, grocery-delivery-bd, mobile-accessories-bd, online-course-bd, and webinar-workshop-bd. Each template includes metadata, default theme colors, localized Bangla copy, sections (hero, CTAs, testimonials, product showcases, etc.), and sensible defaultData to speed up site generation; some templates are marked as premium.
Register a set of Bangladesh-specific landing page templates and clean up/refine the Eid Mega Sale template. Updated template-registry to import and include 12 BD templates (eid-mega-sale, pohela-boishakh-sale, fcommerce-fashion-store, flash-sale-daraz-style, grocery-delivery-bd, mobile-accessories-bd, online-course-bd, beauty-cosmetics-bd, affiliate-product-review-bd, webinar-workshop-bd, digital-agency-bd, influencer-collab-bd). Significant changes to eid-mega-sale.ts: removed corrupted/commented content, improved Bengali copy and defaults, reorganized and renamed sections/fields (hero, countdown, product_showcase, features, testimonials, CTA, footer), updated tags and thumbnail, and adjusted default theme colors to a festive palette. Added a new influencer-collab-bd.ts template defining an influencer promo page (hero, product showcase, discount CTA, video, social proof, testimonials, final CTA) targeted for Bangladesh.
Introduce a client-side ImageUploadField (drag/drop, URL paste, validation, uploads to /api/media/upload) and integrate it into the landing page editor by adding a storeId prop and handling image fields. Update the edit page to pass storeId to the client editor. Completely refactor LandingPageRenderer into a server-only, production-ready renderer with multiple section renderers (hero, product showcase, countdown, testimonials, pricing, FAQ, features, CTA, etc.), inline styles, helpers and accessibility improvements; use standard <img> tags for portability. Also update landing page templates to align with the new renderer and editor changes.
Delete generated Playwright test report artifacts under lp-test-report (index.html, trace assets, and numerous .zip data files) to remove bulky, committed binary outputs. Update .gitignore to ignore lp-test-report and e2e/results, and clean up duplicate/obsolete Playwright-related ignore entries and temporary agent_work patterns to prevent re-adding generated test artifacts.
HeroSection now also reads show_trust_bar/trust_bar_text (merged with existing show_urgency_bar/urgency_bar_text) so ecommerce-product-launch templates can reuse the same rendering logic. Also extracts price_tag for inline price labels used by online-course-bd hero. These changes broaden supported field names and enable additional hero features without changing rendering logic.
Introduce several UI and data-driven enhancements: center-align hero CTAs, add an inline price tag in the HeroSection, and support new product fields in ProductShowcaseSection (delivery_note, discount_code/discount_note, ingredients_list). Strip leading checkmark emojis from feature items to avoid duplicate icons, render an ingredients block, a promo code panel, and a delivery note under the CTA. Improve SocialProofSection theming by adapting text and background colors when a custom dark background is used to preserve contrast. These changes add template-driven content support and polishing for better UX and accessibility.
Add a new "countdown" section to the ecommerce product launch template to provide an urgency timer (fields, defaults and defaultData included; defaults contain Bengali copy and an ISO-style end_date). Update SocialProofSection rendering to strip leading checkmark emojis from badge text to avoid duplicated checkmarks and adjust pill styles for dark backgrounds (background, border and text color now respect hasDarkBg).
Remove a duplicated landingPages field/comment from the Store model in prisma/schema.prisma to avoid schema duplication. Add a missing return in ProductShowcaseSection so the JSX section actually renders. Extend end_date values for black-friday-mega-sale and discount-sale-promo templates to 2028 to keep the promotions active longer.
If a section has bgColor, render an inline heading block with white h2 text and a semi-transparent white subheadline (using HEADING and SUBTEXT styles) instead of the SectionHeading component to ensure contrast on colored backgrounds. Preserve existing SectionHeading for sections without bgColor. (Modified src/components/landing-pages/landing-page-renderer.tsx)
Convert newsletter and contact sections from visual-only markup to real <form> elements with named inputs and submit buttons (action="#", method="POST"). Add minor button/input style tweaks (cursor, border). Add a flat-field FAQ fallback that reads faq_1_question/answer (and question_N/answer_N) up to 20 entries when structured faq items are absent. Add an optional isDark prop to SectionHeading to support inverted text color and use it for dark StatsSection headers.
Introduce a React client component (CountdownTimerClient) that replaces the previous inline script-based countdown. The new component uses useEffect + setInterval to compute and render live days/hours/minutes/seconds (with data-testid attributes) so it works correctly in editor previews, SSR and hydration. LandingPageRenderer now imports and renders the client timer instead of embedding a dangerouslySetInnerHTML script/static markup. Added Playwright e2e tests to verify visibility and live ticking across templates. Also bumped default end_date values in black-friday-mega-sale and discount-sale-promo templates to future dates.
Add helpers to convert relative URLs to absolute ones and to rewrite image sources and inline background-image URLs in the exported HTML. The new makeAbsoluteUrl handles http(s), data:, protocol-relative (//) and prepends window.location.origin for relative paths; fixImageUrlsInHtml updates <img src> and style attributes. downloadAsHtml now runs this fixer before generating the final HTML for download.
Introduce a SectionColorOverride panel in the editor to allow quick per-section overrides for background, primary/CTA, and text colors (stored on section.data as _bg, _primary, _text). The panel includes color pickers, mono inputs, individual resets and a global "reset all to theme defaults" action, and uses theme defaults as placeholders. Integrate the panel into the section editor (landing-page-editor-client.tsx). Update the renderer (landing-page-renderer.tsx) to read these overrides and, when present, wrap section output in a container that injects CSS custom properties (e.g. --lp-background, --lp-primary, --lp-text and related aliases) so overrides cascade into existing var(--lp-*) usages. This preserves existing fallback rendering for unknown section types while enabling per-section styling without changing global theme values.
This pull request introduces a comprehensive Landing Page Builder module, including database schema changes, new API endpoints, and dashboard pages for managing landing pages. The changes enable stores to create, edit, publish, duplicate, track, and list landing pages based on customizable templates, with full versioning and analytics support.
Landing Page Builder: Database and Model Layer
LandingPageandLandingPageVersionfor storing landing pages, their status, content, appearance, SEO, analytics, and immutable version snapshots. Introduced enums for landing page status and category. Also, established a relation betweenStoreandLandingPage. [1] [2]Landing Page API Endpoints
src/app/api/landing-pages/route.ts,src/app/api/landing-pages/[id]/route.ts)src/app/api/landing-pages/[id]/publish/route.ts,src/app/api/landing-pages/[id]/duplicate/route.ts)src/app/api/landing-pages/[id]/track/route.ts)src/app/api/landing-pages/templates/route.ts)[1] src/app/api/landing-pages/[id]/route.tsR1-R92, src/app/api/landing-pages/[id]/publish/route.tsR1-R64, src/app/api/landing-pages/[id]/duplicate/route.tsR1-R29, src/app/api/landing-pages/[id]/track/route.tsR1-R29, [2]
Dashboard Pages for Landing Page Management
src/app/dashboard/landing-pages/page.tsx)src/app/dashboard/landing-pages/new/page.tsx)src/app/dashboard/landing-pages/[id]/edit/page.tsx)src/app/dashboard/landing-pages/[id]/preview/page.tsx)[1] [2] src/app/dashboard/landing-pages/[id]/edit/page.tsxR1-R64, src/app/dashboard/landing-pages/[id]/preview/page.tsxR1-R64)