Skip to content

New team setup UI - #6666

Open
RobertJoonas wants to merge 11 commits into
masterfrom
team-setup-form
Open

New team setup UI#6666
RobertJoonas wants to merge 11 commits into
masterfrom
team-setup-form

Conversation

@RobertJoonas

@RobertJoonas RobertJoonas commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Changes

This PR builds a new UI for the team setup page that is completely independent from TeamManagement code (which continues to work exactly the same way in team settings). The objective is to make the initial team setup UI cleaner in order to add team creation as an optional onboarding step.

The new UI is a simple form, where:

  • name update happens only after hitting "Create team" (no db write on defocus anymore)
  • the user can add multiple "email + role" rows before hitting submit
  • the number of rows is limited by team_member_limit. Hitting the plus icon to add rows while at limit will not add any more rows and will display a "not-allowed" cursor
  • invitations are sent only after hitting "Create team" (just like before, but there's no confusing extra "Invite" step anymore that might make it feel like the invitation was already sent)
  • a validation error on any input field will flash an error message without resetting the form state itself (e.g. some email is invalid or the team name is reserved/prohibited)
  • Any rows with an empty email are ignored

Note: The handrolled member-rows.js hook is quite a lot of code, but as a follow-up, I'm planning to introduce a Prima.Listbox component and use it here. So member-rows.js should eventually get rid of the hand-crafted keyboard navigation, focusing and blurring the dropdown, etc. We'll also be able to use Listbox in the TeamManagement LiveView to make the UX feel more snappy in high-latency scenarios.

Tests

  • Automated tests have been added

Changelog

  • This PR does not make a user-facing change

Documentation

  • Docs have been updated

Dark mode

  • The UI has been tested both in dark and light mode

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Preview environment👷🏼‍♀️🏗️
PR-6666

@RobertJoonas
RobertJoonas requested review from a team, apata and ukutaht September 9, 2026 22:40
details.querySelector('summary').focus()
},

// Wires up the WAI-ARIA listbox-button keyboard pattern for a role picker:

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.

issue, non-blocking: These handlers don't match the dashboard's top bar popovers from headless-ui (https://headlessui.com/react/popover). I think ideally, we should be consistent with that. Popover's handlers do less than this and that's good. Less to go wrong.

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.

This consistency sounds nice but it's difficult to achieve and the scope expands far beyond this PR. There's a split between LiveViews and the dashboard, and I just logically followed the LiveView example, aiming for consistency with other Prima components (just pushed a PR that creates this component in Prima: plausible/prima#25).

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.

Is the split justified though? From perspective of the user, it's all Plausible.

I understand the reluctance to rewrite this minor piece of UI again, but this extra complexity (over a headlessui popover) isn't justified for this component, at least I don't think so.

Who are these keyboard handlers for and what's our confidence that they serve the purpose?

All custom keyboard handlers risk messing with people that can't use a mouse to navigate, or see what's happening on the screen.

To them, the browser's default Home button behavior may be the escape from out of a messed up cookie banner that's preventing them from completing some essential task on a government website. Close your eyes, turn on screen reading, and try to set up an appointment to renew your passport. Another challenge: set up Plausible.

I'm not an expert on accessibility, but I do know that it takes expertise to build an accessible component.

Anyways, this is just me advising to take care here and in the future with custom keyboard handlers. It's a non-blocking comment because I can't prove that it's not perfectly accessible.

Comment thread assets/js/liveview/member-rows.js
Comment thread assets/js/liveview/member-rows.js
Comment on lines +156 to +164
# validate_exclusion/3 only runs its check when the field actually changed
# relative to the struct's current value, which would let a freshly
# auto-created team (whose name already equals the reserved default) keep
# that name simply by resubmitting it unchanged. Check unconditionally.
if get_field(changeset, :name) == Plausible.Teams.default_name() do
add_error(changeset, :name, "is reserved")
else
changeset
end

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.

suggestion, non-blocking: There's a force_change function in Ecto. We ended up using it for annotations for a similar situation where I originally had built a workaround.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants