Org Settings

Summary

Org management page in the dashboard. Read-only member listing with role filter and org profile display. Admin-only access. Invite flow, role management, and member removal deferred to Phase 2 (requires server RPCs that don’t exist yet).

Shared contracts

This spec consumes the following from dashboard-shared:

  • Data table: member list with role filter
  • Status badges: role badges (Admin/Instructor/RO/Member), user status (Active/Placeholder)
  • User picker: search users for invite flow (optional, see invite design)
  • Confirm dialog: remove member, revoke invite
  • Role-gating: admin-only for all actions on this page
  • Route ownership: /org

Requirements

Phase 1 (this item — shipped, uses existing ListOrgMembers only):

  • Member listing with role badges and status indicators
  • Role filter dropdown (All / Admin / Instructor / Member / Read-only)
  • Org profile display (name, type)
  • Admin-only access (nav link hidden, component-level redirect for non-admins)
  • “(You)” label on current user’s row via user_id matching

Phase 2 (deferred — requires server RPCs that don’t exist yet):

  • Invite new members by email with role assignment (InviteToOrg, ListInvites)
  • Change member roles (SetMemberRole)
  • Remove members (RemoveMember)
  • Org profile editing (UpdateOrg)
  • Revoke pending invites (RevokeInvite)

Server API gaps (blocking)

This feature requires server RPCs that do not exist yet. See dashboard-shared section 3 for the full gap matrix.

Missing RPCPurposeFallback if deferred
InviteToOrgSend invite email, create invite record, optionally create placeholderNo invite flow
ListInvitesShow pending/accepted/revoked invitesNo invite visibility
RevokeInviteCancel pending inviteNo invite management
SetMemberRoleChange member’s roleNo role changes
RemoveMemberRemove member from orgNo member removal
UpdateOrgRename orgNo org editing

MVP scoping decision: Option 3 (read-only only) was shipped. Uses existing ListOrgMembers RPC. Dashboard shows member list with roles, status badges, and role filter. No mutations. Invite flow deferred to Phase 2 when server RPCs are implemented.

API-to-UI mapping

Existing RPCs

UI actionRPCKey request fieldsNotes
List membersListOrgMembersorg_id, role filter, paginationAdmin/instructor only. Returns display_name, status, role, joined_at.
Get org infoGetMe(none)Org name, owner, is_personal from session orgs

New RPCs (require server implementation)

UI actionRPCKey request fieldsNotes
Send inviteInviteToOrgorg_id, email, roleCreates invite record + sends email. Creates placeholder user if email not registered.
List invitesListInvitesorg_id, status filter, paginationShows pending/accepted/revoked invites.
Revoke inviteRevokeInviteinvite_idSets revoked_at. Pending only.
Change roleSetMemberRoleorg_id, user_id, new_roleAdmin only. Cannot change own role. Cannot change org owner role.
Remove memberRemoveMemberorg_id, user_idAdmin only. Cannot remove self. Cannot remove org owner.
Rename orgUpdateOrgorg_id, nameAdmin only.

Field editability

FieldEditable byVia RPC
Org nameAdminUpdateOrg
Member roleAdminSetMemberRole
Org ownerNot editable (ownership transfer not in MVP)-
is_personalNot editable (system-managed)-

Components

OrgSettings (/org)

Access: Admin only. Non-admin users redirected away (nav link hidden + component-level check).

Layout (single page with sections):

Section 1: Org Profile

  • Org name (editable text input + “Save” button if UpdateOrg exists, read-only label otherwise)
  • Owner display name (read-only)
  • Created date (read-only)
  • Note: Personal orgs show “This is your personal org” and no editing controls

Section 2: Members

  • Header: “Members” + member count + “Invite Member” button
  • Filter: Role dropdown (All / Admin / Instructor / RO / Member)
  • Member table
ColumnContentActions
Namedisplay_name-
StatusActive/Placeholder badge-
RoleRole badge (Phase 2: inline role dropdown)Phase 2: SetMemberRole on change
JoinedFormatted date-
Actions(Phase 2: Remove button)Phase 2: RemoveMember + confirm

Phase 1: Member table is read-only (no role changes or removals). Rows show “(Owner)” and “(You)” labels for context. Phase 2: Role change via inline dropdown, remove via X button + confirm dialog. Cannot modify org owner or self.

Section 3: Invitations (requires ListInvites + InviteToOrg)

  • Header: “Pending Invitations” + count
  • Invite form: email input + role dropdown (default: Member) + “Send Invite” button
  • Pending invites table
ColumnContentActions
EmailInvite email-
RoleAssigned role badge-
SentCreated date-
StatusPending / Accepted / Revoked-
ActionsPhase 2: Revoke button (pending only)Phase 2: RevokeInvite + confirm

Invite flow:

  1. Admin enters email + selects role
  2. “Send Invite” → InviteToOrg(org_id, email, role)
  3. Server creates invite record, sends email with invite link, optionally creates placeholder user
  4. Invite appears in pending list
  5. When recipient accepts (via AcceptInvite), status changes to Accepted, they appear in member list

Validation:

  • Email required, valid format
  • Role required, must be one of admin/instructor/ro/member
  • Org name: 1-200 chars (if UpdateOrg implemented)

New API module

apps/dashboard/src/api/user.rs (shared with roster-builder):

DTOs (in addition to roster-builder’s DTOs):

  • OrgMemberDto — id, display_name, status, role, joined_at
  • InviteDto — id, email, role, status, created_at, accepted_at, revoked_at
  • InviteToOrgRequest — org_id, email, role
  • SetMemberRoleRequest — org_id, user_id, role
  • RemoveMemberRequest — org_id, user_id
  • UpdateOrgRequest — org_id, name
  • ListInvitesRequest — org_id, status filter, pagination

Methods (new RPCs, require server implementation):

  • invite_to_org(token, req), list_invites(token, req), revoke_invite(token, invite_id), set_member_role(token, req), remove_member(token, req), update_org(token, req)

Test plan

  • Member list: Navigate to /org → members table shows all org members with roles and status badges
  • Role filter: Select “Instructor” → only instructors shown Phase 1:
  • Member list: Navigate to /org → members table shows all org members with roles and status badges
  • Role filter: Select “Instructor” → only instructors shown
  • Self/owner labels: Own row shows “(You)”, owner row shows “(Owner)”
  • Send invite (requires InviteToOrg): Enter email + select role → send → invite appears in pending list
  • Authorization: Non-admin user navigated to /org → redirected to /events (or shown access denied)
  • Personal org: Personal org settings show read-only info, no member management

Phase 2:

  • Change role (requires SetMemberRole): Change member from Member to Instructor → badge updates → server confirms
  • Remove member (requires RemoveMember): Click remove → confirm → member disappears from list
  • Revoke invite (requires RevokeInvite): Click revoke on pending invite → confirm → status changes to Revoked

Acceptance criteria

Phase 1 (this item — shipped):

  1. Org settings page is accessible only to admin users; nav link hidden for non-admins; component redirects unauthorized users to /events.
  2. Org profile section shows name and type (Personal/Team). Personal orgs show explanatory note.
  3. Member list shows all org members with display name, status badge (Active/Placeholder), role badge (color-coded), and join date.
  4. Role filter dropdown narrows member list by role (All / Admin / Instructor / Member / Read-only).
  5. Current user’s row shows “(You)” label via user_id matching.
  6. “Showing first 200” note when pagination indicates more members exist.

Phase 2 (deferred — requires org-management-api): 8. Admin can change member roles via inline dropdown (except own role and org owner’s role). 9. Admin can remove members via confirm dialog (except self and org owner). 10. Admin can revoke pending invites via confirm dialog. 11. Org name is editable (admin only) with save button.

Open questions

#QuestionStatus
1Which service owns the new invite/role/member RPCs — UserService or a new OrgService?Open. UserService already has ListOrgMembers. Adding more org operations there keeps it together, but it’s getting large.
2Should invite create a placeholder user immediately, or only on acceptance?Open. Current AcceptInvite handles placeholder merge. Creating placeholder at invite time lets the placeholder appear in roster before acceptance.
3Ownership transfer (change org owner)?Deferred. Not MVP.
4Bulk invite (CSV upload)?Deferred. Not MVP.
5Invite expiration policy?Open. invites table has expires_at. Need to decide default expiry (7 days? 30 days?) and whether dashboard shows expired invites.