Skip to content

feat(web): admin UI for email campaigns - #368

Open
williamchiii wants to merge 3 commits into
devfrom
wchi/email-campaigns-ui
Open

feat(web): admin UI for email campaigns#368
williamchiii wants to merge 3 commits into
devfrom
wchi/email-campaigns-ui

Conversation

@williamchiii

Copy link
Copy Markdown
Collaborator

Description

Admin UI for email campaigns, built against the seven endpoints from #365. Organizers
can now send campaigns themselves instead of asking the tech team to run a script.

Two-pane Figma layout: the list stays visible and shifts left while an inline panel
expands beside it, rather than an overlay.

  • List with status badges, dates, and failure reasons
  • Create and edit in a side panel, internal fields separated from outgoing ones
  • Send behind a confirmation naming the recipient groups
  • Schedule, reschedule, unschedule; delete behind a confirmation
  • Sent, sending, and failed campaigns open read-only with the reason

Verified against real AWS SES. A campaign scheduled through this UI was swept 35
seconds after its slot and delivered to ahe full path ran
outside an emulator.

Subject and format are added because the API requires both and neither is in the Figma.
Format has no default: picking wrong sendarkup.

Menu placed its own className after thescarding any passed
in. It now merges, and takes an optional popoverClassName.

Type of Change

  • New feature (non-breaking change)

Checklist

  • My code follows the project's style guidelines
  • I have commented on complex parts o
  • I have updated documentation if necessary
  • I have updated or added tests to co
  • I have updated the OpenAPI YAML or other API schema files if applicable

Additional Notes

  • No tests: no existing component test pattern for this kind of module, and the logic
    is thin over the API. Typecheck and lin baseline.
  • Recipient type is a union matching the enum, so an unresolvable group is a compile
    error rather than a runtime 400.
  • The picker's minValue ignores the time, so past datetimes are validated separately.
    The API accepts a past scheduledAt an campaign.
  • Known gaps: recipient chips aren't colour-coded (MultiSelect hardcodes chip styles),
    no recipient count in the confirmation and the list doesn't
    refetch on panel open, so a since-failed campaign can look scheduled until next fetch.
  • Independent of fix: clear scheduled_at on unschedule, return on template failures, add SES config LocalStack (testing) #367; both branch from dev

Builds the campaign list and create/edit panel against the seven
endpoints shipped in #365. Layout follows the Figma two-pane design:
the list stays visible and shifts left while an inline panel expands
beside it, rather than an overlay.

- Typed hooks for list, get, create, update, send, and delete
- Recipient groups are a union type, so a group the API cannot
  resolve is a compile error rather than a runtime 400
- Format has no default; picking wrong sends raw tags or unrendered
  markup, so it must be a deliberate choice
- Subject and format are not in the Figma but the API requires both

Send, delete, scheduling, and delivery stats are not wired yet.
Adds the Email Campaigns sidebar entry — the page was only reachable
by typing the URL.

Sent, sending, and failed campaigns now open read-only with the reason
shown, mirroring the API's canEditCampaign. Previously the form accepted
edits and only failed on save with a generic "check the fields" hint that
pointed at the wrong problem. Failed campaigns also surface last_error,
and save errors now show the API's own message.

- Every control disabled; MultiSelect wrapped since it takes no disabled prop
- Format pill drops its blue fill when loc
Completes the campaign lifecycle in the UI. Sending is a split button per the
Figma: the label sends immediately behind a confirmation naming the recipient
groups, the chevron holds scheduling, and Save for later sits beside it.

Scheduled campaigns get an amber banner with Reschedule and Unschedule, and the
card kebab carries the same actions plus Delete. Both destructive paths confirm
first; unschedule does not, since it is reversible.

The date picker's minValue only guards the calendar popover and ignores the time
entirely, so a past datetime could reach the API. The API accepts it and the
sweep then fails the campaign for missing its window, which is how a send ends up
dead with no way back. Validate the combined value instead and disable Schedule.

- Errors render inside the modals; the panel behind them is not visible
- Only currently scheduled campaigns prefill a time. scheduled_at survives
  unscheduling, so a draft can carry a stale timestamp
- Menu merges its className instead of discarding it, and takes popoverClassName
- Menu items highlight on hover, not focus: react-aria keeps the last item
  focused after the pointer leaves, so a focus highlight looks stuck
- Kebab drops variant="icon", whose pressed tint persists while the menu is open

Stats panel is not included; it needs SES delivery events that do not exist yet.
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
core Ready Ready Preview Aug 30, 2026 6:14am
xii-front-page Ready Ready Preview Aug 30, 2026 6:14am

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant