Campaigns — Dashboard UI
Related: Workflow & Permissions | API Routes | Dashboard pages
Page Map
/campaigns → Campaigns Hub (list)
/campaigns/new → Creation Wizard
/campaigns/[id] → Campaign Detail
/campaigns/[id]?tab=content → Content tab
/campaigns/[id]?tab=audience → Audience tab
/campaigns/[id]?tab=performance → Performance tab
/campaigns/[id]?tab=keywords → Keywords tab (Google Ads only)
/campaigns/[id]?tab=ads → Ads tab (Meta Ads + LinkedIn Ads)
/campaigns/[id]?tab=optimizations → Optimizations tab (paid_ads, seo_outreach, review_generation)
/campaigns/[id]?tab=sequence → Sequence tab (email/review only)
/campaigns/[id]?tab=settings → Settings tab
/campaigns/analytics → Cross-campaign Analytics1. Campaigns Hub (/campaigns)
The entry point for the Campaigns section.
Layout:
-
Quick stats header (4 cards):
- Active campaigns (count)
- Total ad spend (paid_ads campaigns, current month)
- Avg email open rate (email_marketing campaigns)
- Top performing campaign (by primary metric)
-
Filter bar: type, channel, status, goal, date range, linked deliverable period
-
Campaign list: Cards or table rows, each showing:
- Type icon + channel badges
- Campaign name + goal label
- Date range
- Status badge (colour-coded to workflow stage)
- Primary metric for the type (CTR for paid ads, open rate for email, engagement rate for social)
- Quick-action: “View” / “Continue setup”
-
“New Campaign” button — opens the creation wizard
-
“Import from Platform” button — opens the import flow (see below)
Import from Platform flow
Triggered when the DM team has campaigns already running on Google Ads / Meta Ads / LinkedIn Ads and wants to bring them into Leadmetrics.
- Select connected channel (dropdown listing connected ad accounts, e.g. “Google Ads — Acme Corp”)
- Leadmetrics calls
GET /campaigns/import?channel=google_ads&connectedChannelId=...and shows the list of campaigns from the platform - Each row: campaign name, platform status (enabled / paused), budget, date range, “Already imported” badge if applicable
- DM selects one or more campaigns and clicks “Import Selected”
- Calls
POST /campaigns/import— creates Campaign + CampaignExternalMapping records and triggers initial metrics sync - Imported campaigns appear in the hub list immediately with
source: importedbadge
2. Campaign Creation Wizard (/campaigns/new)
A multi-step wizard. State is persisted so the user can navigate back between steps.
Step 1 — Type & Goal
- Select campaign type (radio cards with icon and description for each of the 5 types)
- Select goal (radio cards: awareness, traffic, leads, sales, retention, reviews)
- Select channels (multi-select checkboxes, filtered to channels connected for the tenant)
- For
paid_adstype: show a secondary option — “Link to existing platform campaign” — which lets the DM enter or look up theexternalCampaignIdafter creation (viaPOST /:id/link-external)
Step 2 — Details
- Campaign name
- Budget (optional; shown for
paid_ads, hidden forseo_outreach) - Currency selector (shown when budget is entered)
- Start date / end date (date range picker)
- Link to Deliverable Period (optional dropdown — “Standalone” or select from active periods)
Step 3 — Campaign Brief
- “Generate Brief” button → calls
POST /:id/generate-brief - Loading state while
campaign-brief-writerruns - Editable rich-text output displaying:
- Objectives + KPI targets
- Messaging pillars
- Audience breakdown
- Recommended formats per channel
- Budget allocation (if budget provided)
- DM can edit before proceeding
Step 4 — Audience
- “Generate Audience Suggestions” button → calls
POST /:id/generate-audience - Displays AI-suggested segments as cards (name, rationale, estimated size, recommended budget split)
- DM can accept, reject, or manually add segments
- Platform audience sync status per segment (if
platformAudienceIdis set)
Step 5 — Content
- List of content pieces to generate for this campaign (based on type + channels):
- e.g. for
paid_adson Google: “Generate RSA Ad Copy” → triggersgoogle-ads-writer - e.g. for
email_marketing: “Generate Email Sequence” → triggersemail-writer
- e.g. for
- Each item shows: deliverable type, agent to run, status (not started / queued / done)
- DM can trigger each piece independently or “Generate All”
- Can also link existing Activities from the deliverables plan
Step 6 — Review & Launch
- Summary of all setup: type, goal, channels, brief excerpt, audience count, content count
- Status timeline widget (current:
draft) - “Submit for DM Review” button → transitions to
dm_review - Option: “Send for Client Approval” (skips internal review, transitions directly to
client_review) — admin-only
3. Campaign Detail Page (/campaigns/[id])
Tabbed layout. Tabs visible to DM team. Client reviewer sees a restricted view (see below).
Overview Tab
- Status timeline (horizontal stepper showing current stage in the lifecycle)
- Campaign brief (collapsible)
- Goal progress widget (target KPI vs current metric)
- Quick stats: impressions, clicks, conversions, spend (last 7 days vs previous period)
- Recent AI insights callout (latest recommendation from
ads-analystormeta-ads-insights)
Content Tab
- List of all
Activityrecords whereActivity.campaignId === id - Columns: type, status, created date, agent used, quick preview of output
- For each item: “Edit”, “Approve”, “Regenerate” actions
- “Add Content” button → triggers new activity creation (calls
POST /:id/content) - Filter by status (pending / awaiting_approval / approved / done)
Audience Tab
- Segment cards: name, type badge, estimated size, filters summary, platform sync status
- “Sync to Platform” button per segment (pushes to Google / Meta audience manager via connected channel)
- “Generate Audience Suggestions” button (re-runs
audience-analyst) - “Add Segment” manual form
Performance Tab
- Date range picker (last 7 / 30 / 90 days, custom)
- Channel selector (if campaign spans multiple channels, each shown separately)
- Sync status bar: shows
CampaignExternalMapping.lastSyncedAtper platform + “Sync Now” button (callsPOST /:id/metrics/sync). If no mapping exists, shows a “Link to platform campaign” prompt instead of charts. - Line charts: impressions, clicks, conversions over time (sourced from
CampaignMetricsstructured rows) - Cost metrics (paid ads): spend, CPC, ROAS
- Email metrics: open rate, CTR, unsubscribes per sequence step
- Social metrics: reach, engagement rate
- AI Insights panel (right sidebar or collapsible section):
- Latest recommendations from insights workers
- Each recommendation: description, suggested action, confidence level
- “Apply” button → queues the optimisation for double-approval workflow
For seo_outreach campaigns, the Performance tab additionally shows:
- Backlink health summary: live count, dead count, nofollow count — colour-coded status bar
- Backlink table: domain, URL, DA score, status badge, anchor text, acquired date, last checked date — with a “Re-check” button per row that triggers a fresh HTTP health check
- Outreach funnel per sequence step: sent → opened → clicked — bar chart showing drop-off
- Reply rate tracker (manual entry or email provider webhook): prospects contacted → replied → agreed to publish → live
For review_generation campaigns, the Performance tab additionally shows:
- Review velocity chart:
ReviewMetrics.newReviewCountweek-over-week per platform — line chart - Rating trend: average star rating over time per platform
- Platform distribution: donut chart showing review count split across Google, Trustpilot, G2, etc.
- Drip funnel: enrolled contacts → step 1 sent → step 2 sent → review link clicked — showing conversion drop-off per step
- Negative theme alerts: if
ReviewMetrics.keyThemescontains flagged negative themes, shown as a warning banner with the extracted theme text
Keywords Tab (Google Ads paid_ads campaigns only)
Shown only when CampaignExternalMapping.platform === "google_ads" exists for the campaign.
Three sub-views via segmented control:
Keywords
- Table: keyword text, match type badge (Broad / Phrase / Exact), status, bid (editable inline), quality score (colour-coded 1–10), impressions, clicks, CTR, avg CPC, conversions
- Filter by: ad group, status, match type
- Inline bid edit: click a bid cell, enter new value, saves on blur → calls
PATCH /:id/keywords/:keywordId - Inline status toggle: pause / enable per keyword
- “Sync Keywords” button → calls
POST /:id/keywords/sync - Ad group selector dropdown (filters the table to a specific ad group)
Search Terms
- Table: search term, match type, impressions, clicks, spend, conversions, CTR, avg CPC, classification badge (colour-coded: green = add as keyword, red = negative, amber = watch, grey = irrelevant, white = unclassified)
- Filter by: classification, date range, ad group
- “Sync Search Terms” button with date range picker → calls
POST /:id/search-terms/sync - “Classify with AI” button → calls
POST /:id/search-terms/classify— shown when unclassified terms exist - Per-row: DM can click the classification badge to override → dropdown to select decision → calls
PATCH /:id/search-terms/:id/classification - Bulk selection: select multiple terms → “Mark as Negative” / “Add as Keyword” bulk action
- “Push Negatives to Google Ads” button (sticky bottom bar, appears when dm_reviewed negative terms exist) → match type selector → calls
POST /:id/search-terms/push-negatives
Negative Keywords
- Table: keyword, match type, level (Campaign / Ad Group), source badge (
manualorfrom search terms), status, date added - ”+ Add Negative Keyword” button → inline form (keyword text, match type, campaign/ad group level) → calls
POST /:id/negative-keywords - Per-row delete icon → confirmation modal → calls
DELETE /:id/negative-keywords/:id - “Sync from Google Ads” button → calls
POST /:id/keywords/sync(also syncs negatives)
Ads Tab (Meta Ads and/or LinkedIn Ads paid_ads campaigns only)
Shown when a CampaignExternalMapping with platform === "meta_ads" or "linkedin_ads" exists for the campaign. If both platforms are linked, a platform switcher (tab or dropdown) toggles between Meta view and LinkedIn view.
Meta Ads View
- Sync status bar —
MetaAdSet.lastSyncedAt+ “Sync Ads” button → callsPOST /:id/metrics/sync?scope=ads - Ad Sets panel (top section):
- Table of
MetaAdSetrecords: name, status badge, daily budget, reach, frequency, CTR, ROAS - Frequency shown with colour coding: green < 3.0, amber 3–5, red > 5 (cold audience thresholds)
- Inline: expand an ad set row to see placement breakdown and targeting summary
- “Pause” / “Resume” toggle per ad set (calls
PATCH /:id/optimizations/:id/applyafter DM approval flow)
- Table of
- Ads grid (bottom section, filters by selected ad set):
- Card grid view:
creativePreviewUrlthumbnail, ad name, format badge, status badge - Per card footer: impressions, CTR, ROAS, frequency
- Fatigue flag banner on fatigued cards (red border + “Refresh Creative” button)
- “Refresh Creative” → enqueues
meta-ads-writerand creates acreative_refreshCampaignOptimizationRecommendation - Click a card to see full metrics breakdown + headline copy
- Card grid view:
LinkedIn Ads View
- Sync status bar —
LinkedInAd.lastSyncedAt+ “Sync Ads” button - Ads table:
- Columns: creative name, format badge, status, impressions, clicks, CTR, CPC, conversions, frequency, Lead Gen Form completions (if applicable)
- Frequency colour-coded: green < 2.0, amber 2–3, red > 3 (B2B audience threshold)
- Fatigue flag icon (⚠) inline on rows where
fatigueFlag = true - Row action: “Refresh Creative” (creates
creative_refreshrecommendation)
- Demographic Breakdown panel (collapsible, below the table):
- Dimension selector: Job Function | Seniority | Industry | Company Size | Job Title | Company
- Bar chart showing CTR and conversion rate per dimension value, sorted descending
- Top-performing segments highlighted green; lowest-performing highlighted amber
- “Tighten Audience” button next to top-performing segment → creates an
audience_tightenCampaignOptimizationRecommendationwith the selected segment as context - Sync date shown; “Sync Demographics” button calls the demographic breakdown endpoint
Optimizations Tab (paid_ads, seo_outreach, and review_generation campaigns)
Shown for paid_ads, seo_outreach, and review_generation campaigns. This is the HITL hub for the weekly automated optimization scan and any manually triggered recommendations.
Layout:
- Platform filter (all / Meta Ads / LinkedIn Ads / Google Ads)
- Status filter (pending / dm_reviewing / client_review / applied / skipped)
- “Run Optimization Scan” button → calls
POST /:id/optimizations/trigger— triggers a fresh threshold check and enqueues optimizer workers
Recommendation cards (ordered by priority: high → medium → low):
Each card shows:
- Priority badge (🔴 High / 🟡 Medium / ⚪ Low)
- Platform badge (Meta Ads / LinkedIn Ads)
- Recommendation type label (e.g. “Creative Refresh”, “Budget Shift”, “Audience Tighten”)
- Title (AI-generated, e.g. “Refresh fatigued ad — Summer Promo V1”)
- Rationale (AI explanation, expandable)
- Estimated impact (e.g. “~15% CTR recovery expected”)
- Suggested action summary (e.g. “Pause ad ID 123456 and request new creative variant”)
- Status badge
- Created date
Actions per card (role-gated):
| Status | DM actions | Client actions |
|---|---|---|
pending | ”Approve” → dm_approved | “Skip” → skipped | — |
dm_approved (client approval needed) | View only | ”Approve” → client_approved | “Reject” → dm_reviewing |
dm_approved (DM can apply directly) | “Apply Now” → applied | — |
client_approved | ”Push to Platform” → applied | View only |
applied | View only (shows applied date) | View only |
skipped | ”Reopen” → pending | — |
- “Approve” / “Skip” open a confirmation drawer with optional note field
- For
creative_refreshtype: the card also links to the draft creative Activity generated by the writer worker — DM can review and edit the new ad copy before approving
Sequence Tab (email_marketing + review_generation only)
- List of sequences for this campaign
- Each sequence: name, trigger type, status, step count
- Expand a sequence to see step list:
- Step order, delay, subject, body preview
- Per-step metrics: sent count, open rate, click rate
- “Edit Step” inline
- “Pause / Resume” step
- “Create Sequence” button (for email campaigns)
- “Generate Review Drip” button (for review_generation campaigns — calls
POST /:id/review-sequence)
Settings Tab
- Campaign name (editable)
- Budget + currency (editable)
- Start / end dates
- Connected channels (shows which tenant channels are linked)
- Deliverable period link (dropdown to change or unlink)
- Auto-pilot toggle (paid_ads only; shows confirmation modal explaining double-approval requirement)
- Danger zone: “Archive Campaign”
4. Cross-Campaign Analytics (/campaigns/analytics)
Aggregate view across all campaigns.
- Summary cards: total campaigns, total spend, total leads generated, avg ROAS
- Campaign comparison table: sortable by any metric column
- Channel breakdown chart: spend / conversions grouped by channel
- Funnel view: impressions → clicks → leads → conversions (stacked bar or sankey)
- Time series: compare multiple campaigns on the same chart
- Date range filter + campaign type filter
5. Client Approval View
When the campaign status === "client_review", client users (role: reviewer) see a read-only campaign review page:
- Campaign name, type, goal, channels, date range
- Budget (if applicable)
- Campaign brief (full text)
- Content list (previews of ad copy, emails, social posts — read-only)
- Audience segments (name + estimated size only — no filter detail)
- “Approve” button → calls
POST /:id/approvewith{ approved: true } - “Request Changes” button → calls
POST /:id/approvewith{ approved: false, note }— opens a text field for feedback before submitting
This reuses the same approval pattern as blog and social post client reviews.
Files to Create
| File | Purpose |
|---|---|
apps/dashboard/src/app/(dashboard)/campaigns/page.tsx | Campaigns Hub |
apps/dashboard/src/app/(dashboard)/campaigns/new/page.tsx | Creation Wizard |
apps/dashboard/src/app/(dashboard)/campaigns/[id]/page.tsx | Campaign Detail (tabbed) |
apps/dashboard/src/app/(dashboard)/campaigns/analytics/page.tsx | Cross-campaign Analytics |