# Advertising Landing And Dashboard Redesign Plan

## Decisions

- Move campaign creation out of the landing page into a dedicated `/{locale}/advertise/new` route.
- Move campaign editing out of the large dashboard modal into a dedicated `/{locale}/advertise/dashboard/[campaignId]/edit` route.
- Preserve current auth, ownership, same-origin, upload, checkout, and PATCH behavior. This is a UI/UX redesign, not an ads backend rewrite.

## Current Problems To Fix

- `src/app/[locale]/advertise/page.tsx` uses a page-level `lg:grid-cols-[1fr_440px]` layout with `<AdvertiseForm />` in the right column. The form is taller than the hero content, leaving awkward empty space under the left column.
- `src/components/ads/advertise-form.tsx` mixes all creation fields, package selection, UTM settings, legal acceptance, and preview into one tall card.
- `src/components/ads/ad-dashboard.tsx` renders dense campaign cards with eight metrics, a chart, progress, destination metadata, and an edit modal trigger in one long block.
- `src/components/ads/ad-campaign-edit-dialog.tsx` is too large for a dialog now that it supports creative text, destination URL, UTM fields, image replacement, preview, upload, validation, and saving.

## Implementation Plan

### 1. Redesign The Public Landing Page

Modify `src/app/[locale]/advertise/page.tsx`.

- Remove the page-level form column and remove `<AdvertiseForm />` from `/advertise`.
- Keep `SiteHeader`, `SiteFooter`, `JsonLd`, `petdex-cloud`, semantic Tailwind tokens, and localized metadata.
- Keep support for `searchParams.checkout` so existing Stripe return URLs still show a status message.
- Replace the current two-column hero with a marketing-first layout:
  - Full-width hero with eyebrow, professional headline, supporting copy, and two CTAs.
  - Primary CTA links to `withLocale("/advertise/new", locale)`.
  - Secondary CTA links to `withLocale("/advertise/dashboard", locale)`.
  - Add a compact sponsor-card/feed mockup or campaign-delivery visual in the hero instead of the full form.
- Add a polished value-prop strip using existing copy where possible:
  - Audience fit.
  - Native sponsored placement.
  - Guaranteed viewable impressions.
  - Transparent metrics.
- Add a pricing/package section using `AD_PACKAGES` and `formatUsd`, but keep it compact and explanatory rather than acting as the full purchase UI.
- Keep acceptable-use and legal policy sections, but present them as reassuring cards near the bottom.
- Use local helper components in `page.tsx` unless the file becomes unwieldy. Prefer minimal extraction over introducing many one-off files.

### 2. Add A Dedicated Create Campaign Page

Add `src/app/[locale]/advertise/new/page.tsx`.

- Server page with Next 16 promise `params` and `searchParams`.
- Use `SiteHeader`, `SiteFooter`, and the same `petdex-cloud` shell.
- Add metadata via `generateMetadata`, likely using new `advertise.create.metadata` keys and `buildLocaleAlternates("/advertise/new")`.
- Render a focused page header explaining that this is the campaign setup flow.
- Render `<AdvertiseForm />` in a centered `max-w-5xl` container.
- If `checkout=cancelled`, show a localized warning/status card above the form.
- Optionally keep `checkout=success` handling here too for resilience, but the dashboard should be the preferred post-payment surface.
- Add two or three small helper cards beside or below the form for image requirements, review policy, and billing expectations.

Modify `src/components/ads/advertise-form.tsx`.

- Preserve the existing submit flow:
  - Image required.
  - 10 MB max.
  - PNG/JPEG/WebP accepted.
  - Upload via `/api/ads/image/presign`.
  - Create campaign via `/api/ads`.
  - Start checkout via `/api/ads/checkout`.
  - Clerk sign-in behavior stays intact.
- Redesign the form card for a full-page create route:
  - Desktop layout: `lg:grid-cols-[minmax(0,1fr)_minmax(300px,380px)]`.
  - Left column: company/contact, title/description, destination URL, upload, UTM, legal acceptance, submit.
  - Right column: sticky preview panel, package selector, and pricing trust copy.
  - Mobile layout: single column with preview/package content after the core creative fields.
- Keep the shared `<AdCard disableNavigation showImagePlaceholder={!imagePreviewUrl} />` preview.
- Do not introduce server actions or alter API contracts.

Modify `src/app/api/ads/checkout/route.ts` only if implementing improved return URLs.

- Preferred UX: `success_url` to localized `/advertise/dashboard?checkout=success` and `cancel_url` to localized `/advertise/new?checkout=cancelled`.
- If this is changed, add corresponding status handling on the dashboard and create page.
- Do not change checkout ownership, rate limiting, Stripe metadata, or price calculation.

### 3. Redesign The Dashboard

Modify `src/app/[locale]/advertise/dashboard/page.tsx`.

- Keep Clerk `auth()` gating and `getUserAdCampaigns(userId)` unchanged.
- Accept `searchParams` so `checkout=success` can show a localized payment confirmation banner if checkout return URLs are updated.
- Pass `locale` into dashboard UI so links can use `withLocale`.
- Keep `robots: { index: false, follow: false }`.

Modify `src/components/ads/ad-dashboard.tsx`.

- Remove the `AdCampaignEditDialog` import and replace edit actions with localized links to `/advertise/dashboard/${campaign.id}/edit`.
- Compute dashboard summary stats from the existing `campaigns` array only:
  - Total campaigns.
  - Active campaigns.
  - Total impressions served.
  - Total clicks.
  - Average/overall CTR derived from clicks and served impressions.
- Add a summary card grid inspired by `src/app/[locale]/admin/insights/page.tsx`.
- Redesign campaign cards into clearer zones:
  - Top row: title, company, status badge, primary edit action.
  - Visual row: image thumbnail or compact creative preview, destination URL, created/activated dates.
  - Delivery row: progress bar with served/remaining copy.
  - Metrics row: fewer primary metrics visible first, using existing package, served, clicks, CTR, spend, and avg time values.
  - Analytics chart remains available but should sit in a contained section rather than making the entire card feel like one long form.
- Improve empty state with a stronger CTA to `withLocale("/advertise/new", locale)` and a short explanation that campaigns appear after checkout/payment activation.
- Keep deleted campaign messaging and do not expose edit links for deleted campaigns.
- Keep formatting helpers local unless they become shared.

### 4. Add A Dedicated Edit Campaign Page

Add `src/app/[locale]/advertise/dashboard/[campaignId]/edit/page.tsx`.

- Use Next 16 promise route params:
  - `params: Promise<{ locale: string; campaignId: string }>`.
- Auth-gate with Clerk `auth()`.
- Redirect unauthenticated users to localized `/advertise`.
- Load exactly one campaign for the current user.
- Return `notFound()` for missing or unauthorized campaigns.
- If the campaign is deleted or otherwise not editable, show a safe non-editable state or `notFound()`. Prefer a user-facing non-editable card if the campaign belongs to the user.
- Render `SiteHeader`, `SiteFooter`, a page header, a back link to the dashboard, and the editor component.
- Add metadata with `robots: { index: false, follow: false }`.

Modify `src/lib/ads/queries.ts`.

- Add a focused query such as `getOwnedAdCampaignForEditing(id: string, userId: string)`.
- Select only fields needed by the editor:
  - `id`, `companyName`, `title`, `description`, `imageUrl`, `destinationUrl`, UTM fields, `status`, `deletedAt`, `createdAt`, `activatedAt`, `packageViews`, `viewsServed`, `priceCents`.
- Enforce ownership in the query by matching both `id` and `userId`.
- Do not change `updateOwnedAdCampaignCreative`.

Add `src/components/ads/ad-campaign-editor.tsx`.

- Reuse the logic currently in `AdCampaignEditDialog` without the modal/open state.
- Preserve:
  - Title max length 80.
  - Description max length 180.
  - HTTPS destination URL submitted to the existing API.
  - UTM editing and clearing behavior.
  - Optional replacement image upload.
  - 10 MB image limit.
  - PNG/JPEG/WebP accept list.
  - PATCH `/api/ads/${campaign.id}`.
  - `mapError` handling for current API error strings.
  - Shared `<AdCard ad={previewAd} disableNavigation />` preview.
- Page layout:
  - Desktop: editor fields left, sticky preview/status panel right.
  - Mobile: single column, no sticky behavior.
  - Add a success state after save and a clear “Back to dashboard” action.
  - Call `router.refresh()` after successful save so server-rendered data stays current.
- Keep helper functions (`buildPatch`, `nullableText`, `uploadImage`, `Field`) in this component unless shared extraction becomes clearly beneficial.

Remove or retire `src/components/ads/ad-campaign-edit-dialog.tsx`.

- First search imports after dashboard is updated.
- If unused, delete it and replace its test with an editor-focused test.
- If another import exists, leave it temporarily but stop exposing it from the dashboard.

### 5. Polish Shared Ad Presentation

Modify `src/components/ads/ad-card.tsx`.

- Keep behavior unchanged.
- Keep it as the single sponsored-card preview for create and edit flows.
- Replace straightforward hardcoded border/hover colors with semantic tokens where safe:
  - `border-border-base` instead of `border-black/10` where equivalent.
  - `hover:bg-surface` or `hover:bg-surface-elev` instead of `hover:bg-white dark:hover:bg-stone-800`.
  - Existing text tokens stay.
- Avoid changing event tracking semantics, link attributes, or image rendering behavior.

### 6. Update I18n

Modify all message files:

- `src/i18n/messages/en.json`
- `src/i18n/messages/es.json`
- `src/i18n/messages/zh.json`

Add or update keys under `advertise`:

- Landing CTAs and redesigned sections.
- `advertise.create` metadata, page title/body, helper cards, and checkout-cancel status.
- Dashboard summary labels and checkout-success banner.
- Dashboard action labels for edit links.
- Full-page editor labels, success message, back link, and non-editable state.

Reuse existing keys where they still fit, especially `advertise.form`, `advertise.card`, and most of `advertise.dashboard.edit`.

Run `bun run i18n:check` after edits.

### 7. Tests

Update existing source-level tests:

- Keep `src/components/ads/advertise-form.test.ts`, adjusting expected snippets only if layout changes affect the exact source text.
- Replace `src/components/ads/ad-campaign-edit-dialog.test.ts` with `src/components/ads/ad-campaign-editor.test.ts` if the dialog is removed.
- The editor test should verify:
  - It imports `@/components/ads/ad-card`.
  - It renders `<AdCard`.
  - It passes `disableNavigation`.
  - It does not use `FeedAdSlot`.

Add a small query test only if the existing test setup already supports this DB layer without new infrastructure. Otherwise verify through build and manual mock-dev flow.

## Verification

Run automated checks:

- `bun run check`
- `bun run i18n:check`
- `bun test src/components/ads/advertise-form.test.ts`
- `bun test src/components/ads/ad-campaign-editor.test.ts`
- `bun run build`

Manual verification with `bun run dev:mock`:

- Visit `/en/advertise`, `/es/advertise`, and `/zh/advertise`.
- Confirm the landing page is balanced on desktop and mobile, with no tall form column or empty left-side gap.
- Visit `/en/advertise/new`.
- Confirm create form preview, package selector, image upload, legal checkbox, sign-in state, and submit button still behave correctly.
- Confirm checkout-cancel messaging if `?checkout=cancelled` is present.
- Visit `/en/advertise/dashboard`.
- Confirm summary cards, campaign cards, progress, analytics tabs, destination URL, deleted-state messaging, and edit links are usable.
- Visit `/en/advertise/dashboard/[campaignId]/edit` with a mock campaign.
- Confirm ownership-gated loading, live preview updates, replacement image preview, unchanged-field error, save success, and back-to-dashboard flow.
- Check responsive behavior at mobile, tablet, and desktop widths.

## Risks And Guardrails

- Do not change identity rules: campaign creation and editing must continue to use Clerk auth and server ownership checks.
- Do not accept identity, price, package views, or campaign ownership from client request bodies.
- Do not change same-origin checks or rate limits.
- Do not invent new metrics; dashboard summaries must derive from existing campaign fields.
- Avoid placing sticky preview panels inside containers with `overflow-clip` or `overflow-hidden`, because sticky positioning can fail there.
- Keep locale-aware links consistent with `withLocale` for all newly added ad routes.
- Keep changes scoped to advertising UI, related i18n, and directly required query/test updates.
