Team Members Interface
The Members UI provides work-level team management, allowing work owners and managers to invite collaborators, assign roles, update permissions, and remove members. Access control is enforced through a role-based permission system.
Component Hierarchy
MembersPage
|
+-- Header section
| +-- Title & subtitle
| +-- "Invite Member" button (if canManageMembers)
|
+-- MembersList
| +-- Owner row (always first, with "Creator" badge)
| +-- MemberRow (for each member)
| +-- Avatar (first letter)
| +-- Username & email
| +-- Role selector (Select) or role badge
| +-- Remove button (trash icon)
| +-- Confirm Remove Dialog
|
+-- InviteMemberDialog
+-- Email input
+-- Role selector (Select)
+-- Role description panel
+-- Submit / Cancel buttons
Key Components
MembersPage
File: apps/web/src/components/works/detail/members/MembersPage.tsx
The top-level container that manages the members list state and coordinates the invite dialog.
interface MembersPageProps {
work: Work;
members: WorkMember[];
owner: WorkOwner;
}
State Management:
membersstate is initialized frominitialMembersprop and updated locally on add/remove/updateinviteDialogOpencontrols the invite modal visibilitycanInviteis derived fromcanManageMembers(work.userRole)
Callback Handlers:
handleMemberAdded(member)-- appends the new member to the listhandleMemberRemoved(memberId)-- filters the member from the listhandleMemberUpdated(updated)-- replaces the member entry in the list
MembersList
File: apps/web/src/components/works/detail/members/MembersList.tsx
Renders the owner row followed by all member rows in a bordered card with dividers.
interface MembersListProps {
work: Work;
members: WorkMember[];
owner: WorkOwner;
onMemberRemoved: (memberId: string) => void;
onMemberUpdated: (member: WorkMember) => void;
}
The owner is always displayed at the top with a highlighted background and a "Creator" badge pill. If there are no members, a centered empty state message is shown.
MemberRow
File: apps/web/src/components/works/detail/members/MemberRow.tsx
Renders a single member with inline role management and removal capabilities.
interface MemberRowProps {
workId: string;
member: WorkMember;
canManage: boolean;
onRemoved: () => void;
onUpdated: (member: WorkMember) => void;
}
Role Change Flow:
- Manager selects a new role from the inline
Selectdropdown updateMemberRole(workId, memberId, newRole)server action is called- On success,
onUpdatedcallback propagates the change toMembersPage - Toast notification confirms the update
Member Removal Flow:
- Manager clicks the trash icon button
- A confirmation
Dialogopens with the member's username - On confirm,
removeMember(workId, memberId)server action is called - On success,
onRemovedcallback removes the member from the list - Toast notification confirms removal
InviteMemberDialog
File: apps/web/src/components/works/detail/members/InviteMemberDialog.tsx
A modal form for inviting new members by email with a role assignment.
interface InviteMemberDialogProps {
workId: string;
open: boolean;
onOpenChange: (open: boolean) => void;
onMemberAdded: (member: WorkMember) => void;
}
Form Fields:
| Field | Type | Validation |
|---|---|---|
| Input | Required, email format | |
| Role | Select | Default: VIEWER |
Available Roles:
| Role | Label | Description |
|---|---|---|
VIEWER | Viewer | Can view work content and settings (read-only) |
EDITOR | Editor | Can edit items, categories, and content |
MANAGER | Manager | Can manage members, settings, and all work features |
The dialog displays a contextual description panel below the role selector that updates dynamically based on the selected role.
Submission Flow:
- User enters email and selects role
- Form validation checks for non-empty email
inviteMember(workId, email, role)server action is called- On success: toast notification, form reset, dialog closes,
onMemberAddedcallback fires - On failure: inline error message displayed below the email field
Role-Based Permissions
The canManageMembers utility from @/lib/permissions determines whether the current user can manage team members:
| User Role | Can Invite | Can Change Roles | Can Remove Members | Sees Role Selector |
|---|---|---|---|---|
| Owner | Yes | Yes | Yes | Yes (Select) |
| Manager | Yes | Yes | Yes | Yes (Select) |
| Editor | No | No | No | No (badge only) |
| Viewer | No | No | No | No (badge only) |
When canManage is false, the role is displayed as a static badge rather than an interactive selector, and the remove button is hidden.
State Management Patterns
MembersPage (state owner)
|
|-- members: WorkMember[] // local state, initialized from server
|-- inviteDialogOpen: boolean // dialog visibility
|
+-- MembersList (stateless display)
| |
| +-- MemberRow (local state for isUpdating, isRemoving, confirmRemoveOpen)
|
+-- InviteMemberDialog (local form state: email, role, isSubmitting, error)
All server mutations go through Next.js server actions. Optimistic updates are applied locally to avoid full-page refreshes, maintaining a responsive feel.
Related API Endpoints
| Action | Server Action Function | HTTP Method |
|---|---|---|
| Invite member | inviteMember(workId, email, role) | POST |
| Update role | updateMemberRole(workId, memberId, role) | PATCH |
| Remove member | removeMember(workId, memberId) | DELETE |
Internationalization
All strings use next-intl with the namespace dashboard.workDetail.members:
title,subtitle-- page headerinviteMember-- invite button labelroles.creator,roles.viewer,roles.editor,roles.manager-- role labelsroleDescriptions.viewer,roleDescriptions.editor,roleDescriptions.manager-- role descriptionsinvite.*-- invite dialog labels and messagesconfirmRemove.*-- removal confirmation dialognoMembers-- empty state message
Cross-References
- Deployment UI -- members need appropriate roles to trigger deployments
- Items Management UI -- editors and above can manage work items
- Schedule UI -- schedule management permissions