# Phase 2: Multi-Page Editor & Dynamic Routing

## Goal
Transform the editor from a single-page editor (Homepage only) into a multi-page editor that uses URL parameters to load and save data for different pages.

## Important Context: The Two-Table System
> [!CAUTION]
> **DO NOT modify the Neon database schema.** All required tables are already created.

To avoid confusion, you must strictly follow our two-table architecture for managing pages. Do not mix these up or create new tables:

1. **`custom_pages` (The Directory):** This table only stores page metadata (Title, Slug, showing in header/footer). It **does not** store the heavy drag-and-drop JSON canvas data.
2. **`homepage_content` (The Canvas Storage):** Despite its name, this table stores the massive JSON canvas data for **every single page** (not just the homepage). We do this to reuse the complex Puck saving logic without duplicating tables.
   * **How it works:** To store data for a custom page like `/about-us`, we save it into the `homepage_content` table by dynamically modifying the `site_id` column to be `${SITE_ID}-about-us`. For the homepage, it remains just `${SITE_ID}`.

In this phase, you will primarily interact with `homepage_content` to load and save the JSON data based on the URL path.

## Requirements

### 1. The Page Switcher Dropdown
* Add a select dropdown menu in the editor header (above the Puck canvas).
* The dropdown must show the following pages in this exact order:
  1. **Homepage** (`path=/`)
  2. **About Us** (`path=/about-us`) — every website will always have this page.
  3. **Privacy Policy** (`path=/privacy-policy`) — every website will always have this page.
  4. Any additional pages fetched dynamically from the **`custom_pages`** table for the current `site_id`.
* **CRITICAL RULE:** Do not hardcode only the static pages. After the static 3, always fetch and append custom pages from the `custom_pages` table so that newly created pages automatically appear in the dropdown.
* When a user selects a page, update the browser URL to include a query parameter: `/page_editor_admin?path=/{page-slug}`.

### 2. Dynamic Database Loading
* The editor component must read the `?path=` query parameter from the URL.
* It must fetch the specific JSON data for that exact path from the `homepage_content` table.
* **Blank Pages:** If no data exists yet for the selected path (it is a blank page), the editor should load an empty Puck canvas.
* **Global Layout:** Even if the page is completely blank, the Global Header and Global Footer (from Phase 1) must still be fully visible.

### 3. Dynamic Saving
* When the user clicks "Publish" or "Draft" inside the Puck editor, the save function must read the current `?path=` parameter.
* It must save the JSON data to the `homepage_content` table specifically for that path.
* It must **never** accidentally overwrite the homepage or other pages.

## Bug Prevention: The "Stuck Content" Cache Issue
> [!WARNING]
> **CRITICAL CACHE BUG PREVENTION**
> When the `?path=` URL parameter changes (e.g., switching from Homepage to About Us), Next.js might try to use cached data, resulting in the canvas showing the old page's content on the new URL.
>
> **How to fix this:** Pass the current `path` as the React `key` prop on the `EditorClient` component inside `page.tsx`. When the `key` changes, React fully unmounts and remounts the component with fresh data from the server. This is safer and simpler than using a `useEffect` to manually fetch data client-side.
>
> **Next.js 15 breaking change (Dynamic APIs):** `searchParams` is now a Promise and MUST be unwrapped using `await`. Failing to `await searchParams` will crash the component and cause a fallback to `/` (the homepage).
>
> ```tsx
> // app/page_editor_admin/page.tsx
> export default async function EditorPage({ searchParams }: { searchParams: Promise<{ path?: string }> }) {
>   const params = await searchParams; // Next.js 15 Requirement!
>   const path = params.path || '/';
>   
>   return <EditorClient key={path} initialData={...} currentPath={path} ... />;
> }
> ```
>
> Combined with `export const dynamic = 'force-dynamic'` in `page.tsx`, this guarantees the server always re-fetches from the DB and Puck always starts clean on each page switch.

## Implementation Pattern (Critical Details for AI Agents)

### 1. Verify Static Page Paths Against Actual Routes Before Implementing

> [!CAUTION]
> **Always check the actual Next.js `app/` folder structure before hardcoding static page paths in the dropdown.** The doc lists static pages as About Us and Privacy Policy — but the doc may have the wrong slug. The source of truth is the folder name in `app/`.
>
> **Example:** The doc originally said `path=/about` but the real Next.js route was `app/about-us/` → correct path is `/about-us`. Using the wrong path means the editor saves data to the wrong `siteId` key and the live page never receives the published content.
>
> **Rule:** Always run a quick check of `app/` folder names and match them to the dropdown slugs before writing any code.

### 2. `pathToSiteId()` — The Core Conversion Helper

Add this function to `lib/homepage-content.ts`. It is the single source of truth for converting a URL path to the correct `siteId` key:

```ts
const SITE_ID = 'your-site-id.com'; // Replace with your actual site_id value

export function pathToSiteId(path: string = '/'): string {
  const normalized = path.trim();
  if (!normalized || normalized === '/') return SITE_ID; // Homepage — unchanged
  // Strip leading slash, prefix with SITE_ID + dash
  return `${SITE_ID}-${normalized.replace(/^\//, '').replace(/\//g, '-')}`;
}
```

**Path convention:**
| URL Path | `siteId` in DB |
|---|---|
| `/` | `your-site.com` |
| `/about-us` | `your-site.com-about-us` |
| `/privacy-policy` | `your-site.com-privacy-policy` |
| `/custom-slug` | `your-site.com-custom-slug` |

Update both `getHomepageContent(path = '/')` and `saveHomepageContent(..., path = '/')` to accept an optional `path` parameter and call `pathToSiteId(path)` internally. **Default to `'/'` so all existing call sites remain backward-compatible and the homepage data is never touched.**

> [!CAUTION]
> **CRITICAL FALLBACK FIX:**
> Make sure to update the fallback logic inside `getHomepageContent()` to ONLY return `DEFAULT_PUCK_DATA` when `path === '/'`. If you return `DEFAULT_PUCK_DATA` for empty non-homepage routes (like `/about-us`), the live fallback routes will falsely think they have real CMS data and will render the Homepage content!
> 
> ```ts
> const isHomepage = path === '/';
> const fallbackData = isHomepage ? DEFAULT_PUCK_DATA : null;
> const puckData = (dbContent?.puckData as Data) || fallbackData;
> ```

### 3. `custom_pages` Table Is NOT in `shared/schema.ts`

> [!IMPORTANT]
> The `custom_pages` table exists in the Neon database but is **not defined in `shared/schema.ts`**. Do NOT add it there (no schema changes allowed). Instead, define it **inline** inside a new `lib/custom-pages.ts` helper file:
>
> ```ts
> import { pgTable, varchar, boolean, timestamp } from 'drizzle-orm/pg-core';
>
> // Inline table definition — table exists in Neon, do not migrate
> const customPages = pgTable('custom_pages', {
>   id: varchar('id', { length: 255 }).primaryKey(),
>   siteId: varchar('site_id', { length: 255 }).notNull(),
>   title: varchar('title', { length: 255 }).notNull(),
>   slug: varchar('slug', { length: 255 }).notNull(),
>   showInHeader: boolean('show_in_header').default(false),
>   showInFooter: boolean('show_in_footer').default(false),
>   createdAt: timestamp('created_at').defaultNow(),
> });
> ```
>
> Fetch custom pages in a `getCustomPages()` async function and return an empty array (not an error) if the table is empty or the DB is temporarily unavailable.

### 4. Blank Canvas Behaviour — Homepage vs All Other Pages

When no saved data exists in the DB for a page, the canvas default state must be:
- **Homepage (`path=/`):** Pre-populate with all default content blocks (your site's Hero, Featured Articles, Category sections, Newsletter, etc.) + SiteHeader + SiteFooter.
- **Any other page (`path=/about-us`, etc.):** Show `SiteHeader` + **a `Spacer` block** + `SiteFooter`.

> [!CAUTION]
> **CRITICAL — Blank Page Drop Zone Bug**
> `SiteHeader` and `SiteFooter` both render as `<div style={{ display: 'none' }} />` in the Puck canvas. Their visuals come from `overrides.iframe` (CanvasHeader / CanvasFooter), not from the content array.
>
> **If you put only SiteHeader + SiteFooter in the content array for a blank page, the Puck drop zone between them is 0px tall. Drag-and-drop will not work at all.**
>
> **Fix:** Always include a `Spacer` block between `SiteHeader` and `SiteFooter` on blank non-homepage pages. This gives the canvas visible, droppable height. The admin can delete the Spacer once they start adding real content blocks.

```ts
const isHomepage = currentPath === '/';
content: [
  { type: 'SiteHeader', props: { ... } },
  ...(isHomepage ? [
    { type: 'YourHeroBlock', props: { ... } },
    // ... other homepage-specific blocks for your site
  ] : [
    // Spacer is REQUIRED here — without it drag-and-drop is broken on blank pages.
    // The admin deletes it once they add real content blocks.
    { type: 'Spacer', props: { id: 'Spacer-blank-placeholder', height: 500 } },
  ]),
  { type: 'SiteFooter', props: { ... } },
]
```

### 5. Page Wrapper Padding (CRITICAL LAYOUT RULE)

> [!WARNING]
> **Do NOT put hardcoded top/bottom padding (e.g., `pt-24 pb-20`) on the `<main>` wrapper in your dynamic page routes (e.g., `app/[slug]/page.tsx` or `app/about-us/page.tsx`).**
> 
> Because the site header is `sticky` (not `fixed`), it already pushes content down naturally in the document flow. If you add `pt-24` to the `<main>` wrapper, it will create a massive 96px artificial gap between the header and the first component. 
> 
> **Correct Wrapper:**
> ```tsx
> <main className="bg-white min-h-screen">
>   <Render config={puckConfig} data={pageData.puckData} />
> </main>
> ```
> 
> Full-width components (like Hero sections) need to touch the header natively. Any required spacing for smaller components (like a `Breadcrumb` sitting at the top of a page) must be handled by that specific component's own configuration in `lib/puck/config.tsx` (e.g., adding `pt-6` to the Breadcrumb wrapper).

### 6. The API Route Must Accept `path` in Both GET and POST

Update `app/api/homepage-content/route.ts`:
- **GET:** Read `?path=` from URL search params. Default to `'/'`.
- **POST:** Read `path` from the JSON request body. Default to `'/'`.
- **On Publish:** Call `revalidatePath(path)` — not just `revalidatePath('/')` — so the correct live page cache is busted, not just the homepage.

```ts
// GET
const path = searchParams.get('path') ?? '/';

// POST
const path: string = body.path ?? '/';
if (mode === 'publish') revalidatePath(path); // ← not always '/'
```

### 6. Live Pages Must Have the CMS Fallback Pattern (Do Not Skip)

> [!IMPORTANT]
> After Phase 2, the editor can save data for `/about-us` and `/privacy-policy`. But if the live `app/about-us/page.tsx` and `app/privacy-policy/page.tsx` are still hardcoded static components, publishing from the editor has no effect on the live site.
>
> You MUST add the CMS fallback pattern to each of these pages:
> 1. Try to fetch Puck data from `getHomepageContent('/about-us')`.
> 2. If `puckData` exists → render with Puck's `<Render config={puckConfig} data={...} />`.
> 3. If no data (DB empty or unavailable) → fall through and render the existing hardcoded content as normal.
>
> This is a zero-risk pattern. The hardcoded page is 100% preserved until the admin publishes CMS content for that page.

```tsx
// app/about-us/page.tsx (same pattern for privacy-policy)
export const dynamic = 'force-dynamic';

export default async function AboutUsPage() {
  try {
    const pageData = await getHomepageContent('/about-us');
    if (pageData?.puckData) {
      return (
        <main className="bg-white min-h-screen pt-24 md:pt-32 pb-20">
          <Render config={puckConfig} data={pageData.puckData as any} />
        </main>
      );
    }
  } catch {
    // DB unavailable — fall through to hardcoded content
  }

  // Existing hardcoded content (unchanged) ...
  return <main>...</main>;
}
```

### 7. Hardcoded Content Is NOT Pre-Seeded in Phase 2

> [!NOTE]
> The current hardcoded content in `about-us/page.tsx` and `privacy-policy/page.tsx` is **not** migrated to the DB in Phase 2. The hardcoded content acts as a safe fallback until the admin either:
> - Rebuilds the page in the CMS editor and publishes it, OR
> - Phase 4 (Database Seeding) is implemented to migrate the content automatically.
>
> Do NOT attempt to serialize the existing React JSX into Puck JSON manually. Phase 3 must be completed first (to have the right Puck component types available) before Phase 4 can seed the content.

## Testing Steps

### Editor Tests
1. Open `/page_editor_admin?path=/`. Ensure the homepage content loads.
2. Use the dropdown to switch to the "About Us" page (`?path=/about-us`).
3. **Verify Bug Prevention:** Ensure the canvas goes blank (shows only Header + Footer). It must **NOT** show the Homepage content.
4. Drag a new text block onto the About Us canvas and type "This is the About Us page."
5. Click Publish.
6. Refresh the page (`F5`). Ensure "This is the About Us page" is still there.
7. Switch back to the Homepage using the dropdown. Ensure the Homepage content returns, and the About Us text disappears.

### Live Site Tests
8. Check the live `/about-us` frontend route and ensure it now shows the Puck-rendered content (not the old hardcoded page).
9. Visit `/about-us` before any CMS publish — confirm it shows the original hardcoded content (fallback working).
10. Confirm `revalidatePath('/about-us')` is called on publish — NOT `revalidatePath('/')` — otherwise the live page cache won't bust correctly.

---

## ? Phase 2 Completion Verification

Run this checklist to confirm Phase 2 is fully implemented:

- [ ] `npx tsc --noEmit` passes with 0 errors
- [ ] Page Switcher dropdown visible in editor toolbar with all pages listed
- [ ] Switching pages via dropdown loads the correct canvas content
- [ ] `?path=/slug` URL param correctly loads the corresponding page data
- [ ] Publishing a page saves content to DB with correct `site_id` key
- [ ] Live `/about-us` (or any custom page slug) renders Puck content correctly
- [ ] `revalidatePath` is called with the correct page path on publish (not just `/`)
- [ ] Cache busting works � publish in CMS ? refresh live page ? new content appears

> **Phase 2 is complete when ALL boxes above are checked.**
