Skip to content

feat(dashboard): team members page with add and remove - #352

Merged
harshtandiya merged 4 commits into
developfrom
version-2-dashboard-team-members
Aug 19, 2026
Merged

feat(dashboard): team members page with add and remove#352
harshtandiya merged 4 commits into
developfrom
version-2-dashboard-team-members

Conversation

@harshtandiya

@harshtandiya harshtandiya commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

What changed

Fourth in the /manage stack. #351 has merged, so this sits directly on develop.
Fills in the sidebar's Members item, which fell through to the work-in-progress
placeholder until now.

  • /manage/team/members — the team as a subtle table: name, email, role, and an
    ellipsis menu holding the one destructive action. Owner sorts first and carries no
    menu, matching the controller lock. A legend above it spells out what each of the
    five roles can actually do.
  • remove_member — disables the membership rather than deleting it, so
    upsert_membership re-enables the same row if the person is invited back.
  • invite_members + an Add members dialog — rows of email and role, a pasted
    comma-separated list explodes into rows, and the next row opens as you type.
  • Pending invitations sit in the same table, address standing in for the name and
    (Invited) against the role.

Worth knowing

Invite-by-email existed and had never been called. 00-teams/04 wired the
user_invitation hook, the buzz_team/buzz_team_role custom fields and
on_invitation_accepted, but the only caller was EventTicket.send_user_invitation,
and that call site is commented out. This is the first path that actually sends one.

invite_members does not just forward to core. invite_by_email skips any address
that ever accepted a buzz invitation, filtered by app_name with no team filter — so
inviting a colleague to a second team would silently no-op and report success. Existing
users go through upsert_membership instead; only strangers get an email.

Guarded at invite time, not accept time. Core's validate_role gates on the Frappe
role, not team membership, so a Manager could otherwise create an invitation that throws
when the recipient clicks the link. can_manage_members runs before anything is sent.
The three refusal tests were mutation-checked: if False: breaks exactly those.

Accepting a Manager/Admin/Frontdesk invitation makes the user a System User. Event Manager and Frontdesk Manager both carry desk_access, and set_system_user() flips
user_type on any save. Pre-existing and deliberate — 00-teams/01 deferred an Event Manager fixture because desk_access: 0 would revoke Desk from every current organizer
— but this PR makes it one click away, so it is worth naming. Not addressed here.

Not in scope

Role changes, revoking an invitation (core's cancel_invitation would slot into the same
menu), leaving a team, and the team profile/settings surface — all spec 13, all separate.

Demo

https://cap.so/s/rb0k446j8jz7scj

image image image

Testing

32 new backend tests across test_teams.py and test_invitations.py, plus a Playwright
spec for the route and the owner row. Verified in a browser with five members and pending
invitations: remove, paste, add, and every toast variant.

Rebased onto develop after #351 squash-merged: 7 commits to 4, the three #351 commits
dropped. No content change — pre-commit clean on all 15 files, typecheck clean, 70 unit
tests, 32 backend tests green.

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds the team-members management page and its supporting invitation and removal APIs.

  • Lists active members and pending invitations with role details.
  • Adds guarded member removal and batch invitation flows.
  • Adds dashboard routing, shared response types, backend coverage, and a members-page browser test.

Confidence Score: 4/5

The PR does not yet appear safe to merge because an older overview request can still replace the currently selected team's roster.

The shared team-overview resource accepts overlapping reloads without tying each result to its initiating team, so switching teams can leave the members page displaying the previous team's data.

Files Needing Attention: dashboard/src/data/teams.ts, dashboard/src/pages/manage/teams/TeamMembers.vue

Important Files Changed

Filename Overview
dashboard/src/data/teams.ts Adds member-management resources while retaining the outstanding shared overview-resource race across team switches.
dashboard/src/pages/manage/teams/TeamMembers.vue Implements the members table, management controls, and refresh behavior, but still renders overview data that can belong to a previously selected team.
dashboard/src/components/dashboard/teams/AddMembersDialog.vue Implements validated multi-row invitations, pasted-address expansion, role selection, and outcome reporting.
buzz/api/teams/invitations.py Adds authorization and role validation before routing existing users to membership upsert and new users to email invitations.
buzz/api/teams/services.py Extends team overviews with pending invitations and adds guarded soft removal of memberships.
e2e/tests/team-members.spec.ts Covers route rendering and the protected owner row without introducing a blocking issue.

Reviews (6): Last reviewed commit: "feat(dashboard): add members and show wh..." | Re-trigger Greptile

const teamId = computed(() => route.params.teamId as string);
const isAdding = ref(false);

watch(teamId, (team) => team && teamOverview.fetch({ team }), { immediate: true });

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.

P1 Stale responses replace current team

If two team overview requests overlap, the older request can finish last and overwrite the shared teamOverview.data, causing the members page to display the previous team's roster and pending invitations under the newly selected team.

Prompt To Fix With AI
This is a comment left during a code review.
Path: dashboard/src/pages/manage/teams/TeamMembers.vue
Line: 29

Comment:
**Stale responses replace current team**

If two team overview requests overlap, the older request can finish last and overwrite the shared `teamOverview.data`, causing the members page to display the previous team's roster and pending invitations under the newly selected team.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-overview branch from 699cfc5 to a570feb Compare August 17, 2026 10:44
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-members branch 2 times, most recently from a58f3ab to c6ad7db Compare August 17, 2026 13:30
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-overview branch from a570feb to 50e0c6c Compare August 17, 2026 13:30
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-members branch from c6ad7db to 4ca3238 Compare August 19, 2026 06:14
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-overview branch from 50e0c6c to 34cff5a Compare August 19, 2026 06:14
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

UI Demo Check — a screenshot or demo is attached.

@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-overview branch from 34cff5a to d870159 Compare August 19, 2026 07:20
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-members branch from 4ca3238 to 5ec71cd Compare August 19, 2026 07:20
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-overview branch from d870159 to afff9d3 Compare August 19, 2026 07:40
Base automatically changed from version-2-dashboard-team-overview to develop August 19, 2026 08:54
harshtandiya and others added 4 commits August 19, 2026 14:26
remove_member disables the Buzz Team Membership rather than deleting it, so
upsert_membership re-enables the same row if they are ever invited back and the
history survives. Owner/Admin only, the rule the Desk add-members flow already
uses; the Owner row refuses to be disabled in the membership controller, so
ownership needs no second check here.

Event Manager holds no write permission on the membership doctype, so the write
goes through ignore_permissions with the explicit guard above it standing in for
the check — the same shape as BuzzTeam.add_members.

members_of now sorts Owners first so the members page can lock the top row
without asking a second question. A team may hold several Owners, hence a role
bucket rather than a single row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sidebar has pointed here since the manager shell landed; the route fell
through to the work-in-progress placeholder. The page reuses get_team_overview
rather than adding a read endpoint — it already carries the members.

A subtle table rather than a card list: shared grid for the header and the rows
so the columns line up without a table element, role as plain text, and an
ellipsis menu holding the one destructive action. The owner sorts first and
carries no menu at all, matching the controller lock. Nobody can remove
themselves either — leaving a team is its own flow.

Removing a row takes it out of flow while it fades, so the rows below start
closing the gap immediately instead of snapping up after the fetch. Entry is
left alone; the list is read, not watched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The plumbing has been in place since 00-teams/04 — the user_invitation hook,
the buzz_team/buzz_team_role custom fields, on_invitation_accepted — but
nothing ever called it, so no team has ever sent an invitation.

invite_members routes each address by whether it already has a User: a
colleague joins straight away through upsert_membership, only a stranger gets
an emailed invitation. That split is not a nicety. Core's invite_by_email skips
any address that ever accepted a buzz invitation, without regard to which team
it was for, so inviting a colleague to a second team would silently do nothing
and report success.

Guarded at invite time with can_manage_members rather than leaning on the
accept-time check in the hook, which would let a Manager send an invitation
that throws when the recipient clicks it. Roles are validated against the
membership doctype's own options so the two cannot drift, and Owner is refused
outright.

get_team_overview now carries the team's pending invitations, kept apart from
members: these people cannot do anything on the team yet, and folding them in
would inflate the member count the overview page renders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dialog takes rows of email and role rather than a chip field: the role is
per person, and a dropdown hanging off a chip is a fiddly place to set it. A
pasted comma-separated list still explodes into rows, and typing in the last
row opens the next one, so neither the bulk case nor the single case needs an
extra click.

Rows carry an id of their own — keyed by position, a splice in the middle hands
Vue the wrong node and the caret lands in the wrong input.

The form is novalidate on purpose. The action button sits outside it in the
dialog's actions slot, so the browser's own validation bubble would only ever
fire on the Enter path, leaving two different behaviours for one action. One
rule instead: bad addresses mark their own row and cost the user that row
rather than the whole batch.

Pending invitations join the members table with the address standing in for the
name and "(Invited)" against the role. No actions menu — there is nothing to
remove until they accept, and invitations expire on their own.

The toast names people rather than counting them, and says out loud that an
invited person will not appear in the table until they accept. Nothing new
happening is reported as info, not success.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-members branch from 5ec71cd to 86ae417 Compare August 19, 2026 09:04
@harshtandiya
harshtandiya merged commit c0dadda into develop Aug 19, 2026
13 of 18 checks passed
@harshtandiya
harshtandiya deleted the version-2-dashboard-team-members branch August 19, 2026 14:12
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