Skip to content

feat: show duplicate emails and stats summary in B2B seat assignment modal#3509

Merged
daniellefrappier18 merged 13 commits into
mainfrom
daniellef/b2b-admin-dialogs
Jun 25, 2026
Merged

feat: show duplicate emails and stats summary in B2B seat assignment modal#3509
daniellefrappier18 merged 13 commits into
mainfrom
daniellef/b2b-admin-dialogs

Conversation

@daniellefrappier18

@daniellefrappier18 daniellefrappier18 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

Description (What does it do?)

  • Adds duplicateEmails: string[] prop to AssignSeatsConfirmModal — duplicate addresses are now listed individually in the review step, not just counted
  • Adds stats cards to the confirm step (invitations, seats remaining after sending) and the over-capacity state (imported, seats available, over the limit)
  • Updates Dialog to accept React.ReactNode for the title prop, enabling icon badges alongside text in dialog headers
  • Updates extractEmailsFromCsvRows and parseEmailsForSubmit in ol-utilities to return duplicateEmails[] alongside duplicateCount

Screen Recording (if appropriate):

  • Desktop screenshots
  • Mobile width screenshots
AssignSeats.mov

Using NVDA

assign_seats_nvda.mp4

How can this be tested?

Confirm step (no issues):

  1. Upload a CSV with all valid, unique emails within seat capacity.
  2. Confirm the stats card shows the correct invitation count and seats remaining after sending.
  3. Click "Send N Invitations" — confirm the modal closes after submission.

Review step (duplicates):

  1. Upload a CSV with duplicate emails.
  2. Confirm the review step lists the duplicate addresses individually (not just a count).
  3. Advance to confirm and send — confirm only deduplicated emails are submitted.

Review step (all invalid):

  1. Upload a CSV with only invalid emails.
  2. Confirm that an inline error appears and no dialog opens

Review step (invalid + duplicates):

  1. Upload a CSV with both invalid and duplicate emails.
  2. Confirm both categories render correctly in the review step.

Over-capacity (CSV):

  1. Upload a CSV where valid email count exceeds available seats.
  2. Confirm the stats card shows Imported, Seats available, and Over the limit with correct values.

Over-capacity (manual entry):

  1. Add valid email count exceeds available seats.
  2. Confirm that an inline error displays and the Assign Seats button is disabled

Additional Context

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

OpenAPI Changes

No changes detected

View full changelog

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

- Deduplicate duplicateEmails for 3+ occurrences of the same address
- Drop redundant duplicateCount prop; derive from duplicateEmails.length
- Show "Sending…" on Send button while async confirm is in flight
- Fix pre-existing test failures from ambiguous queries and timing
@daniellefrappier18 daniellefrappier18 marked this pull request as ready for review June 23, 2026 16:57
Copilot AI review requested due to automatic review settings June 23, 2026 16:57

This comment was marked as outdated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Comment thread frontends/main/src/app-pages/ContractAdminPage/AssignSeatsConfirmModal.tsx Outdated
Comment thread frontends/main/src/app-pages/ContractAdminPage/AssignSeatsConfirmModal.tsx Outdated
Comment thread frontends/main/src/app-pages/ContractAdminPage/AssignSeatsConfirmModal.tsx Outdated
{overCapacity && (
<Alert severity="error">
You entered {validCount} {pluralize("email", validCount)}, but only{" "}
{availableSeats} unassigned {pluralize("seat", availableSeats)} are

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think if availableSeats is 1, it'll end up reading "but only 1 available seat are available.`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good catch. All set now

)
}

const copyToClipboard = (text: string) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This might just be a me/my browser problem, but attempting to copy duplicate emails when uploading a CSV w/ dupes doesn't seem to actually copy to the clipboard. It does switch the text to Copied!, but when I try and paste it looks like it just has whatever I previously copied.

That said, I also get some very unrelated looking errors, so I'd say if this isn't reproducible and everything looks shipshape for others, feel free to ignore this!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nope, not your browser - you found a real bug. But should be resolved now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looking good on my end! Thanks!

@dsubak dsubak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tested all the manual cases, seems pretty good!

I only noticed one hiccup with the clipboard copying, but as noted in the comment, I can't even be sure if this is my browser or not so feel free to ignore it if it can't be reproduced.

Comment on lines +78 to +81
let resolve!: () => void
const promise = new Promise<void>((res) => {
resolve = res
})

@ChristopherChudzicki ChristopherChudzicki Jun 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

BTW, Promise.withResolvers exists now

const { promise, resolve, reject } = Promise.withResolvers();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Promise.withResolvers() is cleaner and the ! assertion was a lie TypeScript was happy to accept.

@daniellefrappier18 daniellefrappier18 merged commit 3ea5436 into main Jun 25, 2026
13 checks passed
@daniellefrappier18 daniellefrappier18 deleted the daniellef/b2b-admin-dialogs branch June 25, 2026 13:08
This was referenced Jun 25, 2026
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.

4 participants