17 — Campaigns List (Screen 2, `/app/testimonial-campaigns`)
Cursor-ready plan: campaign management table with status controls, trigger/channel visibility, and quick actions (create, pause, duplicate, archive).
17 — Campaigns List (Screen 2)
Section titled “17 — Campaigns List (Screen 2)”Source: 02-Implementation-Blueprint.md — Screen 2 - Campaigns List (/app/testimonial-campaigns).
Product alignment: 01-Post-Purchase-Video-Testimonial-Collector-Plan.md — campaign-driven post-purchase request automation.
This document is a build spec only. No code changes are implied until a task references this file.
Related: 07-email-sms-request-delivery-pipeline.md (campaigns power sending), 16-templates-screen-4.md (message templates), Screen 3 plan (campaign create/edit) should be separate.
0) Goal (one sentence)
Section titled “0) Goal (one sentence)”Merchants can view and manage all testimonial campaigns in one table, quickly understand each campaign’s trigger/scope/channel, and run operational actions (create, pause/resume, duplicate, archive).
1) Route and file
Section titled “1) Route and file”| Item | Value |
|---|---|
| Suggested file | app/routes/app.testimonial-campaigns.jsx |
| URL | /app/testimonial-campaigns |
Auth: authenticate.admin(request); queries/actions always scoped by shopId.
2) Data model dependencies
Section titled “2) Data model dependencies”Uses TestimonialCampaign (§5.2) and optionally TestimonialCampaignProduct (§5.3).
Required list columns from blueprint:
- Name
- Trigger event (
order_paid,order_fulfilled) - Delay days
- Audience scope (all products / collection / product list)
- Channel (
email,sms,both) - Status (
active,paused) - Last sent at
2.1 Suggested additions
Section titled “2.1 Suggested additions”- Add
archivedAt DateTime?or keepstatus='archived'only. - Add
lastSentAt DateTime?on campaign if not present; update from sender pipeline (07) whenever a request for that campaign is sent.
3) Loader behavior
Section titled “3) Loader behavior”3.1 Query
Section titled “3.1 Query”where: { shopId }- Include lightweight aggregates:
- count of linked products in
TestimonialCampaignProduct - optional requests sent in last 30 days for quick health signal
- count of linked products in
- Sort default:
updatedAt desc
3.2 URL filters (optional v1)
Section titled “3.2 URL filters (optional v1)”status=active|paused|archived|alltrigger=order_paid|order_fulfilled|allchannel=email|sms|both|allqsearch on campaign name
4) UI (Polaris)
Section titled “4) UI (Polaris)”4.1 Header
Section titled “4.1 Header”- Title: Campaigns
- Primary action: Create campaign
- Secondary: optional help link (“How campaign delays work”)
4.2 Campaign table
Section titled “4.2 Campaign table”Use IndexTable for sorting + row actions.
Columns:
- Campaign name
- Trigger event
- Delay days
- Audience scope (computed label: all / collections / products)
- Channel
- Status badge
- Last sent at
- Actions
4.3 Row actions
Section titled “4.3 Row actions”- Edit ->
/app/testimonial-campaigns/:id(Screen 3) - Pause / Resume
- Duplicate
- Archive
Use ActionList in overflow menu to keep table clean.
5) Actions contract
Section titled “5) Actions contract”Implement action intents on same route:
intent=pause_campaignintent=resume_campaignintent=duplicate_campaignintent=archive_campaign
All intents validate:
- campaign exists
- campaign belongs to current shop
- status transition is legal
5.1 Transition rules
Section titled “5.1 Transition rules”active->pausedorarchivedpaused->activeorarchivedarchived-> no resume (or allow restore as explicit v2 action; decide and document)
5.2 Duplicate behavior
Section titled “5.2 Duplicate behavior”When duplicating:
- copy all campaign fields except ids/timestamps
- name suffix:
" (Copy)" - status default:
paused(recommended to avoid accidental sends) - clone related
TestimonialCampaignProductrows - do not clone runtime metrics (
lastSentAt, sent counters)
6) Audience scope display logic
Section titled “6) Audience scope display logic”Campaign table needs clear human-readable scope:
allProducts=true->All products- else if product rows count > 0 ->
N products - else if collection targeting is implemented later ->
N collections - else ->
No target setwarning badge
If collection targeting is not implemented yet, keep placeholder label but do not fake data.
7) Status and safety UX
Section titled “7) Status and safety UX”- Show Paused campaigns with warning color.
- If campaign has invalid config (missing template/channel mismatch), show row-level warning icon and disable resume until fixed.
- Confirm modals:
- Archive confirmation required.
- Duplicate confirmation optional.
8) Integration with pipeline (07)
Section titled “8) Integration with pipeline (07)”Pipeline campaign lookup must only include status='active'.
When request sender dispatches for a campaign:
- update
campaign.lastSentAt = now()(if field exists)
This ensures the list’s “Last sent at” is operationally useful.
9) Testing checklist
Section titled “9) Testing checklist”- Table shows only shop-owned campaigns.
- Pause/resume updates status and is reflected immediately.
- Duplicate copies targeting rows and starts paused.
- Archive removes campaign from default “active/paused” view.
- “Last sent at” updates after sender worker run.
- Deep link filters (
status,trigger,channel) persist across reload.
10) Implementation order (for Cursor)
Section titled “10) Implementation order (for Cursor)”- Build loader query + table data mapping.
- Build
IndexTable+ create button + row actions. - Implement pause/resume/archive actions.
- Implement duplicate action with relation cloning.
- Add optional filters and badges.
- Wire
lastSentAtupdate in sender pipeline.
11) References
Section titled “11) References”02-Implementation-Blueprint.md— Screen 2,§5.2,§5.307-email-sms-request-delivery-pipeline.md— campaign execution behavior16-templates-screen-4.md— template dependencies
12) Note on numbering
Section titled “12) Note on numbering”This folder already includes 05 through 16 plans. This file is 17-....