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 RPC | Purpose | Fallback if deferred |
|---|---|---|
InviteToOrg | Send invite email, create invite record, optionally create placeholder | No invite flow |
ListInvites | Show pending/accepted/revoked invites | No invite visibility |
RevokeInvite | Cancel pending invite | No invite management |
SetMemberRole | Change member’s role | No role changes |
RemoveMember | Remove member from org | No member removal |
UpdateOrg | Rename org | No 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 action | RPC | Key request fields | Notes |
|---|---|---|---|
| List members | ListOrgMembers | org_id, role filter, pagination | Admin/instructor only. Returns display_name, status, role, joined_at. |
| Get org info | GetMe | (none) | Org name, owner, is_personal from session orgs |
New RPCs (require server implementation)
| UI action | RPC | Key request fields | Notes |
|---|---|---|---|
| Send invite | InviteToOrg | org_id, email, role | Creates invite record + sends email. Creates placeholder user if email not registered. |
| List invites | ListInvites | org_id, status filter, pagination | Shows pending/accepted/revoked invites. |
| Revoke invite | RevokeInvite | invite_id | Sets revoked_at. Pending only. |
| Change role | SetMemberRole | org_id, user_id, new_role | Admin only. Cannot change own role. Cannot change org owner role. |
| Remove member | RemoveMember | org_id, user_id | Admin only. Cannot remove self. Cannot remove org owner. |
| Rename org | UpdateOrg | org_id, name | Admin only. |
Field editability
| Field | Editable by | Via RPC |
|---|---|---|
| Org name | Admin | UpdateOrg |
| Member role | Admin | SetMemberRole |
| Org owner | Not editable (ownership transfer not in MVP) | - |
| is_personal | Not 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
UpdateOrgexists, 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
| Column | Content | Actions |
|---|---|---|
| Name | display_name | - |
| Status | Active/Placeholder badge | - |
| Role | Role badge (Phase 2: inline role dropdown) | Phase 2: SetMemberRole on change |
| Joined | Formatted 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
| Column | Content | Actions |
|---|---|---|
| Invite email | - | |
| Role | Assigned role badge | - |
| Sent | Created date | - |
| Status | Pending / Accepted / Revoked | - |
| Actions | Phase 2: Revoke button (pending only) | Phase 2: RevokeInvite + confirm |
Invite flow:
- Admin enters email + selects role
- “Send Invite” →
InviteToOrg(org_id, email, role) - Server creates invite record, sends email with invite link, optionally creates placeholder user
- Invite appears in pending list
- 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_atInviteDto— id, email, role, status, created_at, accepted_at, revoked_atInviteToOrgRequest— org_id, email, roleSetMemberRoleRequest— org_id, user_id, roleRemoveMemberRequest— org_id, user_idUpdateOrgRequest— org_id, nameListInvitesRequest— 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):
- Org settings page is accessible only to admin users; nav link hidden for non-admins; component redirects unauthorized users to
/events. - Org profile section shows name and type (Personal/Team). Personal orgs show explanatory note.
- Member list shows all org members with display name, status badge (Active/Placeholder), role badge (color-coded), and join date.
- Role filter dropdown narrows member list by role (All / Admin / Instructor / Member / Read-only).
- Current user’s row shows “(You)” label via user_id matching.
- “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
| # | Question | Status |
|---|---|---|
| 1 | Which 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. |
| 2 | Should 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. |
| 3 | Ownership transfer (change org owner)? | Deferred. Not MVP. |
| 4 | Bulk invite (CSV upload)? | Deferred. Not MVP. |
| 5 | Invite expiration policy? | Open. invites table has expires_at. Need to decide default expiry (7 days? 30 days?) and whether dashboard shows expired invites. |