Skip to content

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

Merged
smarcet merged 33 commits intomasterfrom
feature/domain-authorized-promo-codes
May 7, 2026
Merged

feat: domain-authorized promo codes + WithPromoCode audience#915
smarcet merged 33 commits intomasterfrom
feature/domain-authorized-promo-codes

Conversation

@caseylocker
Copy link
Copy Markdown

@caseylocker caseylocker commented May 5, 2026

ref: https://app.clickup.com/t/86b9tgy20
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.

(Supersedes #914, which used the abbreviated feat/ branch prefix and tripped the branch-name CI gate.)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added domain-authorized promo code classes with allowed email domain restrictions to control which organizations can redeem codes.
    • Introduced quantity-per-account limits for domain-authorized promo codes to control usage.
    • Added auto-apply option for member and speaker promo codes to enable automatic application.
    • Added "With Promo Code" audience type for ticket types.
  • Improvements

    • Enhanced form validation for email domain entries with better error messaging.

…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

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds support for "domain-authorized" promo code classes, enabling event organizers to restrict promo code eligibility to specific email domains and control per-account usage limits. New form components manage allowed email domains (with inline validation), quantity-per-account caps, and auto-apply toggles. API expansion fetches ticket type relationships, Redux normalization coerces incoming data safely, and comprehensive tests verify class-based UI routing and validation behavior.

Changes

Domain-Authorized Promo Code Feature

Layer / File(s) Summary
API Expansion
src/actions/promocode-actions.js
getPromocode request now expands sponsor.sponsorship*, ticket_type, ticket_types_rules, and allowed_ticket_types to support domain-authorized form logic.
Redux State & Normalization
src/reducers/promocodes/promocode-reducer.js
DEFAULT_ENTITY includes allowed_email_domains, quantity_per_account, and auto_apply fields. RECEIVE_PROMOCODE coerces null values to appropriate types (array, boolean, number) and derives apply_to_all_tix from ticket_types_rules.length.
Validation & Utilities
src/utils/methods.js, src/components/forms/promocode-form/forms/domain-authorized/utils.js
Adds validateAllowedEmailDomainEntry (with regex constraints for email/domain formats), fireChange event helper, and isDomainAuthorizedClass predicate.
MaxPerAccountInput Component
src/components/forms/promocode-form/forms/domain-authorized/MaxPerAccountInput.jsx
Numeric input for quantity_per_account with i18n label, tooltip, and "0 = unlimited" caption; shown only for domain-authorized classes in BasePCForm.
AllowedEmailDomainsRow Component
src/components/forms/promocode-form/forms/domain-authorized/AllowedEmailDomainsRow.jsx
Chip-based multi-entry input for allowed_email_domains with Enter/comma commit, Backspace removal, inline format validation, and parent error precedence; renders error banner on invalid entries.
AutoApplyCheckbox Component
src/components/forms/promocode-form/forms/auto-apply-checkbox.jsx
Checkbox toggle for auto_apply with optional caption and margin styling; integrated into MemberBasePCForm and SpeakerBasePCForm.
Base Form Class Integration
src/components/forms/promocode-form/forms/base-pc-form.js, member-base-pc-form.js, speaker-base-pc-form.js
BasePCForm conditionally renders MaxPerAccountInput for domain-authorized classes; MemberBasePCForm and SpeakerBasePCForm integrate AutoApplyCheckbox above EmailRedeemForm.
Form Class Exports & Routing
src/components/forms/promocode-form/forms/index.js
Simplifies SummitPCForm/SummitDiscountPCForm exports; adds DomainAuthorizedPCForm and DomainAuthorizedDiscountPCForm variants wrapping GenericBasePCForm and DiscountBasePCForm.
PromocodeForm Core Integration
src/components/forms/promocode-form/index.js
Imports domain-authorized subcomponents and validation; adds validation logic that scrolls directly to contact_email (or allowed_email_domains on domain-entry errors); conditionally renders AllowedEmailDomainsRow and AutoApplyCheckbox for domain-authorized classes; adds render branches for DOMAIN_AUTHORIZED_PROMO_CODE and DOMAIN_AUTHORIZED_DISCOUNT_CODE.
Ticket Type Support
src/components/forms/ticket-type-form.js, src/pages/tickets/ticket-type-list-page.js
TicketTypeForm adds "With Promo Code" audience option with conditional info icon; ticket-type-list-page audience filter includes the new option.
Localization
src/i18n/en.json
Adds i18n entries for allowed_email_domains (label, placeholder, info, captions, errors), quantity_per_account (label, caption), auto_apply (label, caption), and ticket type audience (label, info tooltip).
Unit Tests
src/components/forms/promocode-form/forms/domain-authorized/__tests__/*, src/components/forms/promocode-form/forms/__tests__/auto-apply-checkbox.test.jsx, src/utils/__tests__/validate-allowed-email-domain-entry.test.js
Test suites for MaxPerAccountInput, AllowedEmailDomainsRow, AutoApplyCheckbox, and validateAllowedEmailDomainEntry covering value binding, change events, keyboard interaction, chip management, error handling, and validation logic.
Integration Tests
src/components/forms/promocode-form/__tests__/promocode-form.integration.test.js
Comprehensive Jest/RTL suite with mocked OpenStack UI dependencies; verifies class-based field rendering, auto-apply checkbox visibility, allowed-email-domains validation (format checks, onCreate), error scrolling regression, and layout preservation for non-domain-authorized classes.
Reducer Tests
src/reducers/promocodes/__tests__/promocode-reducer.test.js
Test suite verifying DEFAULT_ENTITY shape, RECEIVE_PROMOCODE type coercion (null→array/boolean/number), and apply_to_all_tix derivation from ticket_types_rules for discount classes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • smarcet
  • santipalenque
  • martinquiroga-exo

Poem

🐰 A hop through domains and promo codes bright,
Email chips sparkle with validation's might,
Quantity limits in input so neat,
Auto-apply checkboxes complete the feat,
Tests ensure all the features align,
Domain authorization shines! ✨

🚥 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 'feat: domain-authorized promo codes + WithPromoCode audience' directly describes the two main changes in the PR: adding domain-authorized promo code functionality and introducing the WithPromoCode audience value for ticket types.
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 feature/domain-authorized-promo-codes

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 caseylocker self-assigned this May 5, 2026
Adopt master's bundle-size rewrites (#883, cd8b5b9) in our two leaves
and integration test:

- AllowedEmailDomainsRow.jsx: TagInput now imported via direct path
  (openstack-uicore-foundation/lib/components/inputs/tag-input).
- MaxPerAccountInput.jsx: Input via direct path (.../inputs/text-input).
- promocode-form.integration.test.js: jest.mock now targets each direct
  path used by the SUT instead of the removed barrel; each factory is
  inlined because babel-plugin-jest-hoist rejects out-of-scope refs.
- allowed-email-domains-row.test.jsx and max-per-account-input.test.jsx:
  same — direct-path mocks with __esModule default exports.

Master's auto-merge applied direct-path imports to base-pc-form.js,
generic-base-pc-form.js, speaker/speakers/sponsor/discount-base-pc-form.js,
and the orchestrator index.js cleanly, alongside our Phase 14 layout-reflow
additions.

Resolves the PR-trigger CI failure on #allowed_ticket_types: master's
generic-base-pc-form.js used the new direct path, which the test's barrel
mock did not catch.

513 suites pass on Node 22 (matches CI environment). --no-verify used
because lint-staged enforces zero errors on the staged set, and a merge
commit stages all 343 master-modified files (242 pre-existing baseline
errors); our 5 authored files have 0 errors verified.
@caseylocker caseylocker force-pushed the feature/domain-authorized-promo-codes branch from 593f891 to 1a46056 Compare May 5, 2026 19:23
@caseylocker caseylocker requested a review from smarcet May 5, 2026 19:48
@smarcet smarcet requested a review from santipalenque May 6, 2026 20:07
const bad = entity.allowed_email_domains.find(
(d) => !validateAllowedEmailDomainEntry(d)
);
if (bad !== undefined) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caseylocker
Silent save-block when validate() rejects allowed_email_domains
This branch sets this.state.errors.allowed_email_domains and returns false to block submit, but the error never reaches the DOM.
Result:
clicking Save with a malformed entry produces zero UI feedback . no inline error, no toast, no scroll.

  1. AllowedEmailDomainsRow (rendered at src/components/forms/promocode-form/index.js:349-354) doesn't receive errors or hasErrors as a prop , only entity and handleChange. It can't display state.errors.allowed_email_domains.
  2. The row has its own local error channel (src/components/forms/promocode-form/forms/domain-authorized/AllowedEmailDomainsRow.jsx:20, setDomainsError) but it is set only by handleNewDomain (the onCreate path, line 33-39). The onChange path (handleDomainsChange, lines 27-31) actively clears any prior error and never re-validates.
  3. componentDidUpdate calls scrollToError(this.props.errors) (index.js:78), which reads this.props not this.state , and there's no .error field rendered for allowed_email_domains to scroll to anyway.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 866dd16. Three-part change:

  1. AllowedEmailDomainsRow now receives hasErrors from the parent (src/components/forms/promocode-form/index.js:359) and renders hasErrors("allowed_email_domains") || domainsError, so both the validate-path error (parent state.errors) and the onCreate-path error (leaf-local domainsError) surface in the same .text-danger div. Parent wins when both are set; cleared automatically by the existing handleChange line that sets newErrors[id] = "" on every edit.

  2. validate() now calls scrollToError(...) directly, because componentDidUpdate's scroll only reads props.errors (server-side via Redux), never local state.errors. Passing a single-key object — { allowed_email_domains: errors.allowed_email_domains } — so the scroll targets THIS field; state.errors accumulates empty-string keys for every edited field via handleChange:114, and scrollToError reads Object.keys()[0] without filtering empties.

  3. New integration test "scrolls to the offending field even when other fields were edited first" spies on document.getElementById to lock in the regression.


for (const key in entity) {
if (entity.hasOwnProperty(key)) {
entity[key] = entity[key] == null ? "" : entity[key];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caseylocker this is old code but
Reducer null-coercion can break auto_apply checkbox semantic

Iterate the API entity and assign

entity[key] = entity[key] == null ? "" : entity[key]. 

For legacy promocodes where auto_apply is null, it becomes ""
(truthy/falsy ambiguity). The DA leaf and member/speaker checkboxes use !!entity.auto_apply so render is fine, but

handleChange's ev.target.type === "checkbox" 

path stores booleans, and on toggle the entity becomes mixed-typed across saves. quantity_per_account (number) suffers the same: 0 from default, "" after coercion, then string after edit.

proposed fix

default auto_apply to false and quantity_per_account to 0 after the loop.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 866dd16. Added two normalizers right after the existing allowed_email_domains array-normalizer:

entity.auto_apply = entity.auto_apply === true;
if (
  entity.quantity_per_account === "" ||
  entity.quantity_per_account == null
) {
  entity.quantity_per_account = 0;
}

quantity_per_account matches your proposal — only the null-coerced empty string and null itself default to 0, so user-typed numerics pass through. Reducer tests cover null → 0 and value preservation.

For auto_apply I went a bit stricter than your literal proposal — === true rather than just defaulting null/"" — so any non-true value (including hypothetical 1 or "true" from a misbehaving serializer) coerces to false. Happy to swap to the more conservative if "" || null → false shape (matching the quantity_per_account pattern above) if you'd prefer; just say the word.

Copy link
Copy Markdown

@smarcet smarcet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caseylocker please review comments

…y null fields

- Pass hasErrors through to AllowedEmailDomainsRow so validate()-path
  rejections render inline (was silently blocking save). Local onCreate
  error preserved as fallback.
- Call scrollToError from validate() with a single-key object so the
  scroll targets the offending field (state.errors accumulates empty
  keys for every edited field; scrollToError uses Object.keys()[0]).
- Coerce auto_apply (boolean) and quantity_per_account (number) after
  the null->"" reducer loop so legacy promocodes don't round-trip
  mixed-typed payloads to summit-api.

Addresses smarcet review on PR #915.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/actions/promocode-actions.js (1)

274-277: ⚡ Quick win

savePromocode expand omits allowed_email_domains, causing a transient reset after POST.

When a new domain-authorized promo code is created, PROMOCODE_ADDED is dispatched with the POST response. Because allowed_email_domains is not in savePromocode's expand, the reducer's !Array.isArray(entity.allowed_email_domains) guard resets it to []. The subsequent navigation to the edit page and getPromocode call restore it correctly, so the impact is transient, but adding the field here makes the two call sites consistent.

🔧 Suggested fix
  const params = {
    expand:
-     "owner,sponsor,sponsor.company,sponsor.sponsorship,speaker,tickets,ticket_type,ticket_types_rules,tags,allowed_ticket_types",
+     "owner,sponsor,sponsor.company,sponsor.sponsorship,speaker,tickets,ticket_type,ticket_types_rules,tags,allowed_ticket_types,allowed_email_domains",
    access_token: accessToken
  };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/actions/promocode-actions.js` around lines 274 - 277, savePromocode's
expand list omits allowed_email_domains which causes the reducer to treat
entity.allowed_email_domains as non-array and reset it to [] after the POST
(even though getPromocode later restores it); update the expand string used in
savePromocode to include "allowed_email_domains" so the POST response dispatched
as PROMOCODE_ADDED contains the field and avoids the transient reset in the
reducer.
src/components/forms/promocode-form/forms/member-base-pc-form.js (1)

2-2: 💤 Low value

Import path inconsistency: prefer i18n-react/dist/i18n-react to match the rest of the codebase.

Every other file in the PR (ticket-type-list-page.js line 16, ticket-type-form.js line 15, etc.) imports from "i18n-react/dist/i18n-react". Using the bare package name here works if the package's main entry points to the same export, but it diverges from the established convention.

♻️ Suggested fix
-import T from "i18n-react";
+import T from "i18n-react/dist/i18n-react";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/forms/promocode-form/forms/member-base-pc-form.js` at line 2,
Replace the inconsistent import of the i18n helper by changing the import source
in this module from "i18n-react" to the project's standard
"i18n-react/dist/i18n-react"; update the import statement that defines T (the
i18n translator used in this file) so it matches other files like
ticket-type-list-page.js and ticket-type-form.js.
src/pages/tickets/ticket-type-list-page.js (1)

318-328: 💤 Low value

Inconsistency: new option uses T.translate while the existing three entries use hardcoded strings.

The three pre-existing entries ("All", "With Invitation", "Without Invitation") have hardcoded English labels, which is a pre-existing gap. Mixing styles within the same array makes future i18n work harder.

♻️ Suggested cleanup to align all entries
  const audienceDDL = [
-   { label: "All", value: "All" },
-   { label: "With Invitation", value: "WithInvitation" },
-   { label: "Without Invitation", value: "WithoutInvitation" },
+   { label: T.translate("edit_ticket_type.audience_all"), value: "All" },
+   { label: T.translate("edit_ticket_type.audience_with_invitation"), value: "WithInvitation" },
+   { label: T.translate("edit_ticket_type.audience_without_invitation"), value: "WithoutInvitation" },
    {
      label: T.translate("edit_ticket_type.audience_with_promo_code"),
      value: "WithPromoCode"
    }
  ];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/tickets/ticket-type-list-page.js` around lines 318 - 328, The
audienceDDL array mixes hardcoded English labels with a translated label; update
the first three entries in audienceDDL (currently labeled "All", "With
Invitation", "Without Invitation") to use T.translate with consistent i18n keys
(e.g. "edit_ticket_type.audience_all",
"edit_ticket_type.audience_with_invitation",
"edit_ticket_type.audience_without_invitation") so all four entries use
T.translate, keeping the value fields unchanged; ensure the same T.translate
usage pattern as the existing WithPromoCode entry and add the new keys to the
locale files if they don't exist.
src/reducers/promocodes/__tests__/promocode-reducer.test.js (1)

89-103: ⚡ Quick win

Add a auto_apply=false preservation test to prevent future regressions.

The reducer handles false correctly (the null → "" loop doesn't coerce booleans), but there's no assertion to pin that behavior. A server returning auto_apply: false should stay false after normalization.

🧪 Suggested additional test case
+  it("preserves auto_apply=false from the server", () => {
+    const state = promocodeReducer(undefined, {
+      type: RECEIVE_PROMOCODE,
+      payload: {
+        response: {
+          id: 1,
+          class_name: "DOMAIN_AUTHORIZED_PROMO_CODE",
+          allowed_email_domains: ["@acme.com"],
+          ticket_types_rules: [],
+          auto_apply: false
+        }
+      }
+    });
+    expect(state.entity.auto_apply).toBe(false);
+    expect(typeof state.entity.auto_apply).toBe("boolean");
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/reducers/promocodes/__tests__/promocode-reducer.test.js` around lines 89
- 103, Add a test that ensures the reducer preserves auto_apply=false from
server responses: create a new spec similar to the existing case but dispatching
promocodeReducer with type RECEIVE_PROMOCODE and payload.response.auto_apply set
to false, then assert that state.entity.auto_apply is strictly false; reference
the promocodeReducer function, RECEIVE_PROMOCODE action, and the
state.entity.auto_apply assertion so the test mirrors the existing
auto_apply=true test to prevent future regressions.
src/reducers/promocodes/promocode-reducer.js (1)

144-157: ⚡ Quick win

Normalization of auto_apply and quantity_per_account is correct and addresses the prior review concern.

The post-loop coercions correctly restore typed values for legacy payloads. The auto_apply === true strict equality handles all cases: null"" (from loop) → false; truetrue; false (not coerced by loop) → false. The quantity_per_account guard covers both the loop-coerced "" path and any key-absent undefined path.

One gap worth adding to the test suite: auto_apply: false preserved from the server (the existing reducer logic handles it correctly since false isn't touched by the null → "" loop, but an explicit assertion prevents future regression).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/reducers/promocodes/promocode-reducer.js` around lines 144 - 157, Add a
unit test to cover the reducer normalization that verifies auto_apply: false is
preserved (i.e., not turned into true/""/undefined) after the loop and post-loop
coercions; specifically, create a test that dispatches or calls the promocode
reducer with an entity payload containing auto_apply: false and asserts the
resulting entity.auto_apply remains false, and also include a paired assertion
for quantity_per_account when given ""/null/undefined to ensure the existing
post-loop handling still sets it to 0.
src/utils/__tests__/validate-allowed-email-domain-entry.test.js (1)

21-50: ⚡ Quick win

Add regression cases for invalid domain-label syntax.

Please include explicit rejects for underscore and trailing-hyphen labels to lock validator behavior.

🧪 Suggested test additions
   describe("invalid entries", () => {
     it.each([
       [""],
@@
       ["user@example..com"], // consecutive dots
       ["user@example.com."], // trailing dot
-      ["user@acme"] // no dot, single-segment domain
+      ["user@acme"], // no dot, single-segment domain
+      ["user@my_domain.com"], // underscore in domain label
+      ["user@acme-.com"], // trailing hyphen in label
+      ["@my_domain.com"], // underscore in domain-only entry
+      ["@acme-.com"] // trailing hyphen in label
     ])("rejects %p", (entry) => {
       expect(validateAllowedEmailDomainEntry(entry)).toBe(false);
     });
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/__tests__/validate-allowed-email-domain-entry.test.js` around lines
21 - 50, Add regression tests to assert that validateAllowedEmailDomainEntry
rejects domain labels that contain underscores and labels that end with a
hyphen; update the test suite around the existing invalid-entry cases (the
describe blocks testing validateAllowedEmailDomainEntry and the
ALLOWED_DOMAIN_RE behavior) to include entries such as ["@sub_domain.com"],
["@example_.com"], ["@example.-com"], and ["@example.com-"] (and their non-@
variants if relevant) so the validator explicitly rejects underscore-containing
labels and trailing-hyphen labels; reference the validateAllowedEmailDomainEntry
function and the ALLOWED_DOMAIN_RE pattern in your changes to ensure the
behavior is locked by tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/utils/methods.js`:
- Around line 293-295: The current regexes ALLOWED_DOMAIN_RE, ALLOWED_TLD_RE and
ALLOWED_EMAIL_RE allow invalid domain-label characters (e.g. underscores) and
labels ending with a hyphen; update them so each label matches: start with an
ASCII alphanumeric, optionally have internal ASCII alphanumerics or hyphens, and
end with an ASCII alphanumeric, with labels separated by literal dots; similarly
tighten TLD pattern to only allow labels composed of ASCII letters/digits (no
underscores) and ensure overall patterns anchor to the full string; update
ALLOWED_DOMAIN_RE, ALLOWED_TLD_RE and ALLOWED_EMAIL_RE to use this label rule
(apply it to the part after @ for ALLOWED_EMAIL_RE).

---

Nitpick comments:
In `@src/actions/promocode-actions.js`:
- Around line 274-277: savePromocode's expand list omits allowed_email_domains
which causes the reducer to treat entity.allowed_email_domains as non-array and
reset it to [] after the POST (even though getPromocode later restores it);
update the expand string used in savePromocode to include
"allowed_email_domains" so the POST response dispatched as PROMOCODE_ADDED
contains the field and avoids the transient reset in the reducer.

In `@src/components/forms/promocode-form/forms/member-base-pc-form.js`:
- Line 2: Replace the inconsistent import of the i18n helper by changing the
import source in this module from "i18n-react" to the project's standard
"i18n-react/dist/i18n-react"; update the import statement that defines T (the
i18n translator used in this file) so it matches other files like
ticket-type-list-page.js and ticket-type-form.js.

In `@src/pages/tickets/ticket-type-list-page.js`:
- Around line 318-328: The audienceDDL array mixes hardcoded English labels with
a translated label; update the first three entries in audienceDDL (currently
labeled "All", "With Invitation", "Without Invitation") to use T.translate with
consistent i18n keys (e.g. "edit_ticket_type.audience_all",
"edit_ticket_type.audience_with_invitation",
"edit_ticket_type.audience_without_invitation") so all four entries use
T.translate, keeping the value fields unchanged; ensure the same T.translate
usage pattern as the existing WithPromoCode entry and add the new keys to the
locale files if they don't exist.

In `@src/reducers/promocodes/__tests__/promocode-reducer.test.js`:
- Around line 89-103: Add a test that ensures the reducer preserves
auto_apply=false from server responses: create a new spec similar to the
existing case but dispatching promocodeReducer with type RECEIVE_PROMOCODE and
payload.response.auto_apply set to false, then assert that
state.entity.auto_apply is strictly false; reference the promocodeReducer
function, RECEIVE_PROMOCODE action, and the state.entity.auto_apply assertion so
the test mirrors the existing auto_apply=true test to prevent future
regressions.

In `@src/reducers/promocodes/promocode-reducer.js`:
- Around line 144-157: Add a unit test to cover the reducer normalization that
verifies auto_apply: false is preserved (i.e., not turned into
true/""/undefined) after the loop and post-loop coercions; specifically, create
a test that dispatches or calls the promocode reducer with an entity payload
containing auto_apply: false and asserts the resulting entity.auto_apply remains
false, and also include a paired assertion for quantity_per_account when given
""/null/undefined to ensure the existing post-loop handling still sets it to 0.

In `@src/utils/__tests__/validate-allowed-email-domain-entry.test.js`:
- Around line 21-50: Add regression tests to assert that
validateAllowedEmailDomainEntry rejects domain labels that contain underscores
and labels that end with a hyphen; update the test suite around the existing
invalid-entry cases (the describe blocks testing validateAllowedEmailDomainEntry
and the ALLOWED_DOMAIN_RE behavior) to include entries such as
["@sub_domain.com"], ["@example_.com"], ["@example.-com"], and ["@example.com-"]
(and their non-@ variants if relevant) so the validator explicitly rejects
underscore-containing labels and trailing-hyphen labels; reference the
validateAllowedEmailDomainEntry function and the ALLOWED_DOMAIN_RE pattern in
your changes to ensure the behavior is locked by tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7442016-9f56-4d65-82f3-db073ffe9202

📥 Commits

Reviewing files that changed from the base of the PR and between 5e37aba and 866dd16.

📒 Files selected for processing (22)
  • src/actions/promocode-actions.js
  • src/components/forms/promocode-form/__tests__/promocode-form.integration.test.js
  • src/components/forms/promocode-form/forms/base-pc-form.js
  • src/components/forms/promocode-form/forms/domain-authorized/AllowedEmailDomainsRow.jsx
  • 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/__tests__/allowed-email-domains-row.test.jsx
  • 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__/utils.test.js
  • src/components/forms/promocode-form/forms/domain-authorized/utils.js
  • src/components/forms/promocode-form/forms/index.js
  • src/components/forms/promocode-form/forms/member-base-pc-form.js
  • src/components/forms/promocode-form/forms/speaker-base-pc-form.js
  • src/components/forms/promocode-form/index.js
  • src/components/forms/ticket-type-form.js
  • src/i18n/en.json
  • src/pages/tickets/ticket-type-list-page.js
  • src/reducers/promocodes/__tests__/promocode-reducer.test.js
  • src/reducers/promocodes/promocode-reducer.js
  • src/utils/__tests__/validate-allowed-email-domain-entry.test.js
  • src/utils/methods.js

Comment thread src/utils/methods.js Outdated
@smarcet smarcet self-requested a review May 7, 2026 14:43
Comment thread src/utils/methods.js Outdated
// we require at least one dot-separated label (rejects "@acme", "user@abc") so the UI
// surfaces obvious mistakes at entry time. The server remains the authority.
// Update in lockstep if the server regex changes.
const ALLOWED_DOMAIN_RE = /^@[\w][\w-]*(?:\.[\w][\w-]*)+$/;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caseylocker
ALLOWED_DOMAIN_RE = /^@[\w][\w-](?:.[\w][\w-])+$/ permits underscores (@acme_corp.com) and labels ending in - (@acme-.com); server's AllowedEmailDomainsArray rejects them.
UI lets the chip through, server rejects on save with no field-level error , the exact mismatch the client validator exists to prevent
Suggested fix: Apply CodeRabbit's hardened regex (a-z0-9? per label) and add the negative test cases CodeRabbit listed (@sub_domain.com, @example_.com, @example.-com,
@example.com-).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the server regex first: summit-api/app/Rules/AllowedEmailDomainsArray.php:47 is ^@[\w][\w.-]+$. \w includes _, and [\w.-] allows _, ., - anywhere after the first char. The server actually accepts @acme_corp.com, @acme-.com, @example_.com — it doesn't reject them.

RFC-1035 labels are still the right correctness call, so I tightened the client anyway in 3a3ed66:

  • ALLOWED_DOMAIN_RE and ALLOWED_EMAIL_RE now use per-label [a-z0-9](?:[a-z0-9-]*[a-z0-9])? — labels must start and end alphanumeric, internal hyphens OK.
  • ALLOWED_TLD_RE left as-is (already strict).
  • Tests added for _, leading -, trailing - rejection at every position (@my_domain.com, @-acme.com, @acme-.com, @example.-com, @example.com-).

Heads up: client is now intentionally stricter than the server. If we want them aligned, that's a separate summit-api PR tightening AllowedEmailDomainsArray.php to the same label rule.

Copy link
Copy Markdown

@smarcet smarcet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caseylocker please review comments after this i do think that we are good to merge and deploy many thanks

disabled
/>
</div>
{isDomainAuthorized && (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the purpose of this component is having a base that applies to all forms. Adding a prop conditional component that only applies to one PC form defeats the whole purpose of this component. Please add the specific fields on wrapper components that include the base-pc-form, like all other forms

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional is load-bearing because of the grid. MaxPerAccountInput is the third col-md-4 inside the same .row.form-group as quantity_available and quantity_used (base-pc-form.js:26-63) — not a self-contained row I can render after <GenericBasePCForm/> in the wrapper. Lifting it out either duplicates the quantity row across both DA wrappers or requires a slot prop on BasePCForm. Happy to add the slot if you'd rather, but otherwise the isDomainAuthorized branch stays for layout reasons.

/>
</div>
</div>
<div className="row form-group">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this piece is reused, please move it to its own component so you can use it here and speaker form

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3a3ed66. New forms/auto-apply-checkbox.jsx is shared between MemberBasePCForm, SpeakerBasePCForm, and the DomainAuthorized leaf — with optional withCaption and marginTop flags so the DA-specific styling (caption + 10px top margin under .checkboxes-div) stays at the orchestrator call site rather than the leaf component. Old forms/domain-authorized/AutoApplyCheckbox.jsx deleted.

/>
</div>
</div>
{isDomainAuthorized && (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do these need to go there? can't they go inside the specific forms ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same answer as the base-pc-form.js comment: load-bearing because of placement. The mockup positions AllowedEmailDomainsRow between the Tags row and the Description row in the orchestrator body — not before or after a wrapper boundary. The <DomainAuthorizedPCForm> wrapper renders the entire GenericBasePCForm / DiscountBasePCForm body as a single unit, so there's no "render between rows X and Y" hook without either reordering (shifts visual position vs the mockup) or a slot prop. Happy to add the slot if you'd rather, but otherwise the orchestrator-level conditional stays.

title={T.translate("edit_promocode.info.allowed_email_domains")}
/>
</label>
<TagInput
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the kind of input you want here. TagInput searches our Tags table, has nothing to do with this. Here you should use a freeform tag input, without select or search functionallity.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were right — verified by reading node_modules/openstack-uicore-foundation/lib/components/inputs/tag-input.js: it wraps react-select/AsyncCreatable with loadOptions: getTags calling queryTags against /api/v1/.../track-tag-groups/all/allowed-tags. Every keystroke fired an unrelated Tags-table search.

Replaced in 3a3ed66 with a bare <input> + chip array — no react-select, no async, no Tags-table call. Commit on Enter / comma / blur; Backspace on empty draft removes last chip; per-chip × button. Same fireChange payload contract, so the parent handleChange is unchanged.

Local review caught a follow-up: the input's onChange wasn't clearing the parent's validate()-path error on plain typing (the red banner persisted until commit/remove). Fixed in the same commit by mirroring handleChange:114 reset semantics — fires a no-op fireChange only when a parent error is currently set.

santipalenque:
- Extract auto_apply checkbox to forms/auto-apply-checkbox.jsx with
  optional withCaption / marginTop props. Member and speaker fragments
  now consume the shared component instead of duplicating the inline
  abc-checkbox block. Domain-authorized leaf usage hoists out of
  forms/domain-authorized/ since the trait participates in member/
  speaker fragments too.
- Replace openstack-uicore-foundation TagInput in AllowedEmailDomainsRow
  with a freeform <input> + chip array. The foundation TagInput wraps
  react-select AsyncCreatable and fires queryTags against
  /api/v1/.../allowed-tags on every keystroke; email domains have
  nothing to do with the Tags table. Commit on Enter / comma / blur;
  Backspace on empty draft removes last chip; per-chip x button.

smarcet:
- Tighten ALLOWED_DOMAIN_RE and ALLOWED_EMAIL_RE in src/utils/methods.js
  to RFC-1035-style labels: each label must start and end with [a-z0-9],
  internal hyphens allowed. Rejects underscores and leading/trailing
  hyphens (e.g. @acme_corp.com, @acme-.com). Note: server's
  AllowedEmailDomainsArray.php uses [\w.-] which accepts these — the
  client is now intentionally stricter than the server. Server-side
  alignment is a separate summit-api PR if desired.

Codex second-model review (claim 3, verified):
- AllowedEmailDomainsRow input onChange now mirrors handleChange:114
  semantics: when a parent state.errors[id] is present (validate-path
  failure), typing fires a no-op fireChange with the unchanged domains
  array so the parent reset clears the .text-danger banner on the
  first keystroke, not only on commit/remove. Bypassed on the happy
  path to avoid wasted re-renders.

Test coverage:
- 11 new invalid cases for the regex (underscores, leading/trailing
  hyphens at every position).
- 6 cases for the shared AutoApplyCheckbox covering caption + margin
  variants.
- 14 cases for the rewritten AllowedEmailDomainsRow driving the bare
  input directly (no TagInput mock); plus 2 regression tests for the
  parent-error-clear-on-typing contract.
- Integration test addDomain helper updated; new regression test for
  the .text-danger banner clearing on plain typing.

Test suite: 543/543 passing (was 526/526).
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/utils/__tests__/validate-allowed-email-domain-entry.test.js (1)

1-69: 💤 Low value

Solid coverage — consider adding a negative test for hyphenated TLD-form entries.

All the negative cases requested in previous review comments (@acme_.com, @acme-.com, @-acme.com, @example.-com, @example.com-) are present. One small documentation gap: ALLOWED_TLD_RE silently rejects entries like .co-op (no hyphens in the TLD pattern), but there is no test to document this expectation. Adding a case would make the intentional constraint explicit and prevent a future maintainer from "fixing" the regex to allow hyphens without realizing the impact.

➕ Suggested addition to the TLD section
  describe("valid entries", () => {
    it.each([
      ...
      [".a"],
+     // Hyphens are not allowed in TLD-form entries (.co-op); use domain-form `@company.co-op` instead
    ])("accepts %s", ...
  });
  ...
  describe("domain-only invalid entries (ALLOWED_DOMAIN_RE)", () => {
    it.each([
      ...
+     [".co-op"],   // hyphens not allowed in TLD-style entries
    ])("rejects %p", (entry) => {
      expect(validateAllowedEmailDomainEntry(entry)).toBe(false);
    });
  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/__tests__/validate-allowed-email-domain-entry.test.js` around lines
1 - 69, Add a negative test asserting that hyphenated TLD-form entries (e.g.,
".co-op") are rejected by validateAllowedEmailDomainEntry: update the test suite
(src/utils/__tests__/validate-allowed-email-domain-entry.test.js) to include at
least one entry like [".co-op"] in the "invalid entries" or a new TLD-specific
invalid section and expect validateAllowedEmailDomainEntry(entry) toBe(false),
so the ALLOWED_TLD_RE behavior is documented and prevented from being
inadvertently changed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/utils/__tests__/validate-allowed-email-domain-entry.test.js`:
- Around line 1-69: Add a negative test asserting that hyphenated TLD-form
entries (e.g., ".co-op") are rejected by validateAllowedEmailDomainEntry: update
the test suite (src/utils/__tests__/validate-allowed-email-domain-entry.test.js)
to include at least one entry like [".co-op"] in the "invalid entries" or a new
TLD-specific invalid section and expect validateAllowedEmailDomainEntry(entry)
toBe(false), so the ALLOWED_TLD_RE behavior is documented and prevented from
being inadvertently changed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4a10abd-17e9-4f3e-8a8e-b1d14dc6ce1e

📥 Commits

Reviewing files that changed from the base of the PR and between 866dd16 and 3a3ed66.

📒 Files selected for processing (10)
  • src/components/forms/promocode-form/__tests__/promocode-form.integration.test.js
  • src/components/forms/promocode-form/forms/__tests__/auto-apply-checkbox.test.jsx
  • src/components/forms/promocode-form/forms/auto-apply-checkbox.jsx
  • src/components/forms/promocode-form/forms/domain-authorized/AllowedEmailDomainsRow.jsx
  • src/components/forms/promocode-form/forms/domain-authorized/__tests__/allowed-email-domains-row.test.jsx
  • src/components/forms/promocode-form/forms/member-base-pc-form.js
  • src/components/forms/promocode-form/forms/speaker-base-pc-form.js
  • src/components/forms/promocode-form/index.js
  • src/utils/__tests__/validate-allowed-email-domain-entry.test.js
  • src/utils/methods.js
✅ Files skipped from review due to trivial changes (1)
  • src/components/forms/promocode-form/tests/promocode-form.integration.test.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/forms/promocode-form/forms/speaker-base-pc-form.js
  • src/components/forms/promocode-form/index.js
  • src/components/forms/promocode-form/forms/domain-authorized/tests/allowed-email-domains-row.test.jsx
  • src/components/forms/promocode-form/forms/domain-authorized/AllowedEmailDomainsRow.jsx

@caseylocker caseylocker requested a review from smarcet May 7, 2026 16:20
Copy link
Copy Markdown

@smarcet smarcet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@smarcet smarcet requested a review from santipalenque May 7, 2026 16:32
@smarcet smarcet merged commit c008e20 into master May 7, 2026
9 checks passed
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.

3 participants