Skip to content

feat: domain-authorized promo codes + WithPromoCode audience#914

Closed
caseylocker wants to merge 30 commits intomasterfrom
feat/domain-authorized-promo-codes
Closed

feat: domain-authorized promo codes + WithPromoCode audience#914
caseylocker wants to merge 30 commits intomasterfrom
feat/domain-authorized-promo-codes

Conversation

@caseylocker
Copy link
Copy Markdown

@caseylocker caseylocker commented May 5, 2026

ref: https://app.clickup.com/t/86b952pgc

Implements sds/promo-codes-for-early-registration-access-summit-admin.md.

Parent SDS (summit-api, merged in #525): sds/promo-codes-for-early-registration-access.md.
Companion SDS (registration-lite): sds/promo-codes-for-early-registration-access-registration-lite.md — separate ticket.

What changed

Domain-authorized promo codes + auto-apply

  • New DomainAuthorizedPCForm and DomainAuthorizedDiscountPCForm composites (thin passthroughs over GenericBasePCForm / DiscountBasePCForm — per-class field rendering is handled by the orchestrator and BasePCForm via class-name conditionals).
  • Three new field leaves under src/components/forms/promocode-form/forms/domain-authorized/:
    • AllowedEmailDomainsRow — TagInput with client-side validateAllowedEmailDomainEntry validation, inline error, visible caption.
    • AutoApplyCheckbox — third checkbox in the Description-row column for DA classes.
    • MaxPerAccountInput — third column in the Quantity row for DA classes.
  • Shared domain-authorized/utils.js with fireChange synthesizer and isDomainAuthorizedClass helper. The helper is the single source of truth for the boolean across three sync points: validate() and render() in the orchestrator + the conditional in BasePCForm.
  • auto_apply checkbox added to MEMBER_/SPEAKER_ fragments as a standalone row underneath the member/speaker selector. Asymmetric with domain-authorized rendering, but forced by the mockup — documented in the SDS.
  • validateAllowedEmailDomainEntry() mirrors summit-api's AllowedEmailDomainsArray rule (three accepted formats: @domain.tld, .tld, user@example.com).
  • getPromocode() expand string includes allowed_email_domains.
  • DEFAULT_ENTITY extended with the three new fields.

Ticket-type audience

  • New WithPromoCode value in the ticket-type audience_ddl (form editor + list-page filter).

Reducer fix (surfaced by Codex review during Task 13 Step 5)

  • DOMAIN_AUTHORIZED_DISCOUNT_CODE added to the discount_classes list in promocode-reducer.js so apply_to_all_tix is derived from ticket_types_rules.length on load (matching every other discount-code subtype). Without this, reloading a saved per-ticket domain-authorized discount code would render the all-ticket UI.

i18n

  • New edit_promocode.captions.* keys (3 entries) parallel to the existing info.* tooltip tree. Documented as a coupled translation pair — captions are short inline helpers, tooltips are longer explanations.

Testing

Automated

  • Unit: validator (8 valid + 11 invalid cases), reducer defaults, apply_to_all_tix derivation.
  • Component: 4 leaf test files — utils (7 cases), AllowedEmailDomainsRow (6), AutoApplyCheckbox (4), MaxPerAccountInput (4).
  • Integration (promocode-form.integration.test.js):
    • Class switching across DA + 4 fragment-owned auto_apply classes.
    • DOMAIN_AUTHORIZED layout positions (3 ordinal-position assertions).
    • validate() drift between helper and domain enforcement (3 cases observing onSubmit mock).
    • 12-class regression matrix in two it.each blocks (4 MEMBER/SPEAKER expecting fragment-owned #auto_apply; 8 SPONSOR/SUMMIT/PRE_PAID/SPEAKERS expecting no #auto_apply).
  • 62 suites / 526 tests passing.

Verification gates

  • yarn lint: 0 new errors over the 2536-error / 17869-warning baseline (+24 warnings on new files, all repo-style camelcase / prop-types / destructuring conventions).
  • yarn build-dev: webpack 5.106.1 compiled successfully.
  • Codex second-model review (two passes — original Task 13 + post-reflow Task 14): 1 blocker fixed before merge (the reducer discount_classes entry); follow-up items deferred as documented in spec.

Manual QA

Verified on https://localhost:8080 against api.dev.fnopen.com + idp.dev.fnopen.com with a logged-in admin session:

  1. ✅ DA edit page matches the authoritative mockup (both DOMAIN_AUTHORIZED_PROMO_CODE and DOMAIN_AUTHORIZED_DISCOUNT_CODE): Allowed Email Domains row above Description, Auto-apply third checkbox in Description-row right column, Max Per Account third column in Quantity row, all three captions visible.
  2. ✅ Regression spot-check across MEMBER, SPEAKERS, SPONSOR, SUMMIT, PRE_PAID classes — DA fields absent; member/speaker fragment-owned auto_apply standalone row preserved.
  3. ✅ Description-row visual at default desktop widths — clean, no overlap.
  4. onCreate validation flow: malformed acme.com (no @) shows inline error + chip not added; @acme.com, .edu, user@example.com all accepted as chips.
    4b. Skipped — validate()-path silent-block is observed by JSDOM tests; pre-existing UX gap (no DOM error display) is documented in spec as out-of-scope.

Architecture notes

  • Auto-apply trait pattern (frontend mirror of AutoApplyPromoCodeTrait): auto_apply is rendered only where the trait participates — domain-authorized classes (via the orchestrator's .checkboxes-div conditional) and member/speaker fragments. Not hoisted to GenericBasePCForm.
  • Single-source-of-truth helper: isDomainAuthorizedClass centralizes the BOOLEAN derivation across three sync points. Per-class render routing remains 12 explicit branches — adding a hypothetical third DOMAIN_AUTHORIZED_* variant in the future requires the helper + i18n keys + a new render branch.
  • WithPromoCode audience: extends the existing ticket-type audience ENUM. Visibility (audience) and applicability (allowed_ticket_types) stay separate, per the parent SDS.
  • No MUI introduced. Summit-admin's MUI migration is a separate initiative; this PR follows the file's existing Bootstrap-3 + abc-checkbox + react-bootstrap idiom.

Files

  • 5 created (1 reducer test dir is new), 9 modified for Tasks 1–12 + Step-5 fix.
  • Layout reflow added 4 leaf source files + 4 leaf test files; modified base-pc-form.js, index.js, forms/index.js, en.json, promocode-form.integration.test.js; deleted domain-authorized-base-pc-form.js + its test.

Companion SDS update: separate docs(sds) commits in the fn-skills vault repo (record the layout-reflow approach addendum + new Deviations section). Not in this PR.

Summary by CodeRabbit

  • New Features

    • Added domain-authorized promo codes that restrict usage to specific email domains
    • Added quantity-per-account limits for promo code redemptions
    • Added auto-apply option for eligible promo codes
    • Added "WithPromoCode" audience filter for ticket type management
    • Expanded sponsor-related data in promo code API requests
  • Tests

    • Comprehensive test coverage for new domain-authorized promo code functionality

…toggle test

Direct wiring of onChange={handleChange} fails under React 16 event pooling
— target is nullified before test assertions read it. Both quantity and
auto_apply wrappers now synthesize plain objects through a single fireChange
helper (accepts `extra` spread for checkbox's `checked` field). Adds negated
auto_apply test to cover the false→true→false round-trip (6 tests total).
TagInput maps entries via .tag/.id internally; passing {value,label}
caused undefined.toLowerCase() crash on sort when editing an existing
code with pre-populated allowed_email_domains.

- domainsAsTags: { tag: d } instead of { value: d, label: d }
- normalizeTagValues: extract .tag first, then .value/.label fallback
- filter out empty strings (invalid domain guard)
- add regression test for non-empty initial domains
The RECEIVE_PROMOCODE reducer derives the UI-only apply_to_all_tix flag
from ticket_types_rules.length for every class that DiscountBasePCForm
can render, because summit-api's discount-code serializer never returns
apply_to_all_tix. DOMAIN_AUTHORIZED_DISCOUNT_CODE was missing from the
list, so reloading a saved per-ticket domain-authorized discount code
inherited DEFAULT_ENTITY.apply_to_all_tix=true and rendered the
all-ticket Amount/Rate inputs instead of the per-ticket rules table.

Surfaced by Codex second-model review against
sds/promo-codes-for-early-registration-access-summit-admin.md Truth #3
and #13 (discount variant composes DiscountBasePCForm; existing flows
unchanged).

Adds reducer tests covering rules populated, rules empty, and the
access-only DOMAIN_AUTHORIZED_PROMO_CODE variant (which is not a
discount class and must not participate in the derivation).
Symmetry with the text-default test above; reviewer suggestion.
Adds three integration tests observing the onSubmit mock to confirm
validate()-driven save blocking for domain-authorized promo codes.
Also polyfills scrollIntoView on HTMLElement.prototype (jsdom gap) and
re-adds the screen import (used by the new getByRole calls).
Append two it.each blocks covering MEMBER/SPEAKER (assert #auto_apply
present) and SPONSOR/SUMMIT/PRE_PAID/SPEAKERS (assert no #auto_apply),
confirming none render the DomainAuthorized layout reflow fields.
Also add speakers fixture to baseEntity so SPEAKERS_* classes mount
without crashing on entity.speakers destructuring.
Sponsor branch ends at :478; SummitPCForm + PRE_PAID_PROMO_CODE branch
is :494-505; SummitDiscountPCForm + PRE_PAID_DISCOUNT_CODE is :507-518.
The original :480-504 range straddled two branches.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR adds domain-authorized promo code support with new form components (AllowedEmailDomainsRow, AutoApplyCheckbox, MaxPerAccountInput) for managing email domain restrictions and per-account limits, validation utilities for email domain entries, reducer updates to handle new fields, and a new ticket-type audience option (WithPromoCode). The feature includes comprehensive integration and unit tests.

Changes

Domain-Authorized Promo Code Support

Layer / File(s) Summary
API & Reducer Defaults
src/actions/promocode-actions.js, src/reducers/promocodes/promocode-reducer.js
API getPromocode now expands sponsor.sponsorship and sponsor.sponsorship.type. Reducer DEFAULT_ENTITY gains allowed_email_domains: [], quantity_per_account: 0, and auto_apply: false; normalization coerces null/missing allowed_email_domains to [] during RECEIVE_PROMOCODE.
Validation Utility
src/utils/methods.js, src/utils/__tests__/validate-allowed-email-domain-entry.test.js
New validateAllowedEmailDomainEntry(entry) function validates email domains and TLDs against three regexes (domain, TLD, full email format); comprehensive test coverage for valid/invalid formats.
Domain-Authorized Component Utilities
src/components/forms/promocode-form/forms/domain-authorized/utils.js, src/components/forms/promocode-form/forms/domain-authorized/__tests__/utils.test.js
Exports fireChange helper (synthetic event creation with type/extra defaults) and isDomainAuthorizedClass predicate; both tested for correctness.
Domain-Authorized Form Components
src/components/forms/promocode-form/forms/domain-authorized/AutoApplyCheckbox.jsx, src/components/forms/promocode-form/forms/domain-authorized/MaxPerAccountInput.jsx, src/components/forms/promocode-form/forms/domain-authorized/AllowedEmailDomainsRow.jsx
Three new React components: AutoApplyCheckbox (toggled auto-apply flag), MaxPerAccountInput (numeric quantity limit), AllowedEmailDomainsRow (TagInput for domain list with validation, duplicate prevention, and error feedback).
Component Unit Tests
src/components/forms/promocode-form/forms/domain-authorized/__tests__/auto-apply-checkbox.test.jsx, src/components/forms/promocode-form/forms/domain-authorized/__tests__/max-per-account-input.test.jsx, src/components/forms/promocode-form/forms/domain-authorized/__tests__/allowed-email-domains-row.test.jsx
Unit tests for each domain-authorized component covering rendering, event handling, validation, and error display.
Form Base Layer
src/components/forms/promocode-form/forms/base-pc-form.js, src/components/forms/promocode-form/forms/member-base-pc-form.js, src/components/forms/promocode-form/forms/speaker-base-pc-form.js
BasePCForm conditionally renders MaxPerAccountInput when domain-authorized; MemberBasePCForm and SpeakerBasePCForm add auto_apply checkbox with translated labels.
Form Variant Exports
src/components/forms/promocode-form/forms/index.js
Simplified SummitPCForm/SummitDiscountPCForm rendering; added new exports DomainAuthorizedPCForm and DomainAuthorizedDiscountPCForm.
Main Form Integration
src/components/forms/promocode-form/index.js
Imports domain-authorized components and validation; enhanced validate() to check allowed_email_domains entries for domain-authorized classes; render() conditionally displays AllowedEmailDomainsRow, AutoApplyCheckbox, and domain-authorized form variants based on entity.class_name.
Reducer Tests & Derivation
src/reducers/promocodes/__tests__/promocode-reducer.test.js
Tests for DEFAULT_ENTITY field defaults, allowed_email_domains array coercion, and apply_to_all_tix derivation logic for DOMAIN_AUTHORIZED_DISCOUNT_CODE; validation of ticket_types_rules impact on the flag.
Form Integration Test
src/components/forms/promocode-form/__tests__/promocode-form.integration.test.js
Comprehensive integration test suite mocking TagInput and form dependencies; verifies field visibility/omission for domain-authorized vs. standard classes, layout positioning rules, allowed_email_domains entry validation (valid @domain, .tld, rejected malformed), and regression checks for non-affected classes.
Ticket Type Updates
src/components/forms/ticket-type-form.js, src/pages/tickets/ticket-type-list-page.js
Added "WithPromoCode" audience option with translated label and info icon; updated audience dropdown in ticket-type list page filter.
Internationalization
src/i18n/en.json
Added new i18n keys for promo code fields (allowed_email_domains, quantity_per_account, auto_apply) with nested placeholders, info, captions, and errors.allowed_email_domains_format; added ticket-type audience labels (audience_with_promo_code, info_audience_with_promo_code).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Suggested reviewers

  • smarcet

Poem

🐰 A promo code with domain might,
Limits email with a snappy flight,
Per-account caps and auto-apply blessed,
Domain-authorized, put to the test!
From form to validation, all so tight! 🎫✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title concisely summarizes the main changes: domain-authorized promo codes and a new WithPromoCode ticket audience, both of which are central to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/domain-authorized-promo-codes

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@caseylocker
Copy link
Copy Markdown
Author

Branch name violates CI rule (must start with feature/, not feat/). Reopening from renamed branch feature/domain-authorized-promo-codes — same commits, same content.

@caseylocker caseylocker closed this May 5, 2026
@caseylocker caseylocker deleted the feat/domain-authorized-promo-codes branch May 5, 2026 18:43
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