Skip to content

feat(#170): add remove member functionality#193

Merged
iamitprakash merged 19 commits intoRealDevSquad:developfrom
Hariom01010:feat(team)/remove-member
Sep 20, 2025
Merged

feat(#170): add remove member functionality#193
iamitprakash merged 19 commits intoRealDevSquad:developfrom
Hariom01010:feat(team)/remove-member

Conversation

@Hariom01010
Copy link
Contributor

@Hariom01010 Hariom01010 commented Aug 28, 2025

Date: 29 Aug 2025

Developer Name: @Hariom01010


Issue Ticket Number

Closes #170

Description

Add remove member from team functionality.

  • 3 dots under actions in member tab is only shown to admins.
  • The remove from team option is not shown if member is owner, poc or themselves
  • Confirmation dialog is shown when removing member.

Documentation Updated?

  • Yes
  • No

Under Feature Flag

  • Yes
  • No

Database Changes

  • Yes
  • No

Breaking Changes

  • Yes
  • No

Development Tested?

  • Yes
  • No

Screenshots

Screenshot 1

Normal Users:

image

Admins:

admins.mp4

Test Coverage

Screenshot 1

Additional Notes

NOTE: the PR is built upon the leave-team thus showing 297 changes. Once that PR is merged, this will only show the changes made in this PR

Description by Korbit AI

What change is being made?

Add remove member functionality to Teams: introduce new API endpoints (removeFromTeam, getUserRoles), new enums and types for team roles and user roles, UI components and dialogs for removing members and leaving teams, and integrate role checks and removal flows into the team members list and team header.

Why are these changes being made?

Enable proper team membership management by allowing admins and authorized users to remove members and view member roles, improving workflow and activity tracking for team changes. If applicable, note any design decisions or trade-offs (e.g., using a shared LeaveTeamDialog for removal flows) in a brief, non-technical summary.

Is this description stale? Ask me to generate a new description by commenting /korbit-generate-pr-description

@Hariom01010 Hariom01010 self-assigned this Aug 28, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 28, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

  • New Features

    • Leave Team action with confirmation dialog, success/error toasts, and redirect.
    • Admins can remove team members via a confirmation dialog; safeguards prevent removing the owner, POC, or yourself.
  • Improvements

    • Team header now shows a Leave Team button for non-owners/POCs.
    • Activity feed includes “Member left,” “Member removed,” and “Tasks reassigned to team” events with icons and descriptions.
    • Permission-aware UI based on user roles; action visibility reflects current user’s role.
    • Enhanced loading states for auth and role data to provide smoother skeletons.

Walkthrough

Adds team member removal and self-leave functionality: new TeamsApi methods (removeFromTeam, getUserRoles), team role enum/types, UI components for confirmation dialogs and buttons, role-based gating in team members and header, and activity-to-UI mappings for new activity types with query invalidations and navigation updates.

Changes

Cohort / File(s) Summary
Teams API
api/teams/teams.api.ts
Adds removeFromTeam (DELETE /v1/teams/{teamId}/members/{memberId}) returning void; adds getUserRoles (GET /v1/teams/{teamId}/users/{userId}/roles) returning UserRole; imports UserRole type.
Team Enums & Types
api/teams/teams.enum.ts, api/teams/teams.type.ts
Introduces TeamRoles enum (OWNER, ADMIN, MEMBER). Extends TeamActivityActions with MEMBER_LEFT_TEAM and TASKS_REASSIGNED_TO_TEAM; adds corresponding activity types; adds UserTeamRole and UserRole; updates TeamActivity union.
Leave Team UI Components
components/leave-team-button.tsx, modules/teams/components/leave-team-dialog.tsx
New LeaveTeamButton using LeaveTeamDialog to confirm leaving; invokes removeFromTeam, invalidates teams/tasks queries, shows toasts, navigates to dashboard; dialog component exposes controlled open state, submit/cancel actions.
Teams Members UI & Logic
modules/teams/team-members.tsx
Dynamically fetches user roles via getUserRoles to compute admin; adds member removal via removeFromTeam with confirmation dialog; guards against removing creator/POC/self; invalidates related queries; toasts on success/error; loading states updated.
Teams Header UI
modules/teams/components/teams-layout-header.tsx
Loads auth, computes owner/POC; conditionally shows LeaveTeamButton for non-owner/non-POC users; preserves create-todo button; loading guard includes auth.
Activity UI Mapping
lib/team-utils.ts
Maps MEMBER_LEFT_TEAM, MEMBER_REMOVED_FROM_TEAM, TASKS_REASSIGNED_TO_TEAM to icons/titles/descriptions; imports UserMinus icon.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant H as Teams Header
  participant LTB as LeaveTeamButton
  participant LTD as LeaveTeamDialog
  participant API as TeamsApi.removeFromTeam
  participant QC as QueryClient
  participant R as Router
  participant T as Toast

  U->>H: Open team view
  H->>LTB: Render (if not owner/POC)
  U->>LTB: Click "Leave Team"
  LTB->>LTD: Open confirm dialog
  U->>LTD: Confirm
  LTD->>API: DELETE /teams/{teamId}/members/{userId}
  API-->>LTD: 204/OK
  LTD->>QC: Invalidate team/teams/tasks queries
  LTD->>T: Show success
  LTD->>R: Navigate /dashboard
  API-->>LTD: Error
  LTD->>T: Show error
Loading
sequenceDiagram
  autonumber
  actor A as Admin User
  participant TM as Team Members List
  participant LTD as LeaveTeamDialog
  participant API as TeamsApi.removeFromTeam
  participant QC as QueryClient
  participant T as Toast

  A->>TM: Open members
  TM->>TM: Compute isAdmin via getUserRoles
  A->>TM: Click "Remove from team" (eligible member)
  TM->>LTD: Open confirm dialog
  A->>LTD: Confirm
  LTD->>API: DELETE /teams/{teamId}/members/{memberId}
  API-->>LTD: 204/OK
  LTD->>QC: Invalidate team/teams/tasks
  LTD->>T: Show success
  API-->>LTD: Error
  LTD->>T: Show error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
Add leave button for users; prevent owner/POC from leaving (#189)
Allow admins to remove normal users (#189) Admin gating implemented, but target member role check isn’t explicit; removal allowed for any non-owner/POC member.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Add TASKS_REASSIGNED_TO_TEAM activity type and UI mapping (api/teams/teams.type.ts, lib/team-utils.ts) Issue #189 doesn’t mention task reassignment; unclear linkage to leave/remove requirement.
Map MEMBER_REMOVED_FROM_TEAM in UI without corresponding type change in this diff (lib/team-utils.ts) The mapping addition isn’t explicitly required by #189; presence/consistency with types is unclear from linked objectives.

Possibly related PRs

Suggested labels

feature task

Suggested reviewers

  • yesyash
  • iamitprakash

Poem

Hop hop, I tidy teams with care,
A click—poof! I’m out of there.
Admin paws nudge members free,
With gentle toasts and query tea.
Roles aligned, dialogs bright—
Carrot-commit, shipped just right! 🥕✨

✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot added the feature task A big ticket item that needs to come up as a feature label Aug 28, 2025
Copy link

@korbit-ai korbit-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by Korbit AI

Korbit automatically attempts to detect when you fix issues in new commits.
Category Issue Status
Error Handling Loss of Error Context in Mutation Error Handler ▹ view
Logging Missing Removed Member Information in Activity Log ▹ view
Functionality Insecure Role Validation ▹ view ✅ Fix detected
Functionality Missing Loading State ▹ view ✅ Fix detected
Readability Complex Conditional Logic in JSX ▹ view ✅ Fix detected
Logging Missing actor in bulk task reassignment log ▹ view
Design Cache invalidation logic needs abstraction ▹ view
Error Handling Unhandled Submit Error State ▹ view
Performance Inefficient Query Invalidation Pattern ▹ view ✅ Fix detected
Design Inconsistent Container Usage ▹ view ✅ Fix detected
Files scanned
File Path Reviewed
api/teams/teams.enum.ts
modules/teams/components/leave-team-dialog.tsx
modules/teams/components/teams-layout-header.tsx
api/teams/teams.api.ts
components/leave-team-button.tsx
api/teams/teams.type.ts
lib/team-utils.ts
modules/teams/team-members.tsx

Explore our documentation to understand the languages and file types we support and the files we ignore.

Check out our docs on how you can make Korbit work best for you and your team.

Loving Korbit!? Share us on LinkedIn Reddit and X

Comment on lines +21 to +31
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: TeamsApi.getTeamById.key({ teamId }) })
queryClient.invalidateQueries({
queryKey: TeamsApi.getTeamById.key({ teamId, member: true }),
})
queryClient.invalidateQueries({
queryKey: TeamsApi.getTeams.key,
})
queryClient.invalidateQueries({
queryKey: TasksApi.getTasks.key(),
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache invalidation logic needs abstraction category Design

Tell me more
What is the issue?

The cache invalidation logic in the onSuccess callback is not abstracted into a separate function, making it hard to maintain and reuse.

Why this matters

Multiple cache invalidation calls scattered in the component increase complexity and make it difficult to maintain consistency across similar operations. If cache invalidation rules change, multiple components would need updates.

Suggested change ∙ Feature Preview

Extract cache invalidation into a separate utility function:

const invalidateTeamCaches = (queryClient: QueryClient, teamId: string) => {
  queryClient.invalidateQueries({ queryKey: TeamsApi.getTeamById.key({ teamId }) })
  queryClient.invalidateQueries({ queryKey: TeamsApi.getTeamById.key({ teamId, member: true }) })
  queryClient.invalidateQueries({ queryKey: TeamsApi.getTeams.key })
  queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key() })
}
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

Comment on lines +35 to +37
onError: () => {
toast.error('Failed to leave team')
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loss of Error Context in Mutation Error Handler category Error Handling

Tell me more
What is the issue?

The error callback in the mutation only displays a generic error message without including the actual error details.

Why this matters

Without the actual error information, debugging issues becomes more difficult as developers won't know the specific reason for the failure.

Suggested change ∙ Feature Preview
onError: (error: Error) => {
      toast.error(`Failed to leave team: ${error.message}`)
      console.error('Leave team error:', error)
    },
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

Comment on lines +39 to +41
if (isAuthLoading) {
return null
}

This comment was marked as resolved.

},
enabled: !!userId,
})
const isAdmin = userRole?.roles.find((role) => role.role_name == TeamRoles.ADMIN)

This comment was marked as resolved.

Comment on lines +107 to +115
queryClient.invalidateQueries({
queryKey: TeamsApi.getTeamById.key({ teamId, member: true }),
})
queryClient.invalidateQueries({
queryKey: TeamsApi.getTeams.key,
})
queryClient.invalidateQueries({
queryKey: TasksApi.getTasks.key(),
})

This comment was marked as resolved.

Comment on lines +189 to +221
{member.id !== user.id &&
member.id !== data?.created_by &&
member.id !== data?.poc_id ? (
<LeaveTeamDialog
title="Remove Member"
description="Are you sure you want to remove this member from the team? They will lose access to all tasks and team information."
buttonText="Remove Member"
open={activeDialogMemberId === member.id}
onOpenChange={(open) => {
if (open) {
setActiveDialogMemberId(member.id)
} else {
setActiveDialogMemberId(null)
}
}}
onSubmit={() => {
removeMemberMutation.mutate({
teamId,
memberId: member.id,
})
}}
>
<DropdownMenuItem
onSelect={(e) => {
e.preventDefault()
setActiveDialogMemberId(member.id)
}}
>
Remove from team
</DropdownMenuItem>
</LeaveTeamDialog>
) : (
<></>

This comment was marked as resolved.

Comment on lines +78 to +82
case TeamActivityActions.MEMBER_REMOVED_FROM_TEAM:
return {
icon: UserMinus,
title: 'Member removed from team',
description: `${activity.performed_by_name} removed a member from team ${activity.team_name}`,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Removed Member Information in Activity Log category Logging

Tell me more
What is the issue?

The description for MEMBER_REMOVED_FROM_TEAM doesn't include the name of the removed member, making it impossible to track who was removed from the team.

Why this matters

Without knowing which member was removed, the activity log loses its audit value and administrators cannot effectively track membership changes.

Suggested change ∙ Feature Preview

Modify the description to include the removed member's name using the expected activity.removed_member_name property:

case TeamActivityActions.MEMBER_REMOVED_FROM_TEAM:
      return {
        icon: UserMinus,
        title: 'Member removed from team',
        description: `${activity.performed_by_name} removed ${activity.removed_member_name} from team ${activity.team_name}`,
        date,
      }
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

return {
icon: Plus,
title: 'Tasks reassigned to team',
description: `${activity.task_count} tasks assigned to team ${activity.team_name}`,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing actor in bulk task reassignment log category Logging

Tell me more
What is the issue?

The log message for TASKS_REASSIGNED_TO_TEAM doesn't indicate who performed the reassignment action.

Why this matters

Without knowing who performed the task reassignment, it becomes difficult to audit and trace responsibility for bulk task modifications in the system.

Suggested change ∙ Feature Preview
description: `${activity.performed_by_name} reassigned ${activity.task_count} tasks to team ${activity.team_name}`,
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

Comment on lines 36 to 48
return (
<div className="flex items-center justify-between pt-6 pb-8">
<h2 className="text-2xl font-bold">{team?.name}</h2>
<CreateTodoButton
defaultData={{
assignee: { label: team?.name ?? '', value: teamId, type: USER_TYPE_ENUM.TEAM },
}}
/>
<div>
<CreateTodoButton
defaultData={{
assignee: { label: team?.name ?? '', value: teamId, type: USER_TYPE_ENUM.TEAM },
}}
/>
{!isOwnerOrPOC ? <LeaveTeamButton teamId={teamId} /> : null}
</div>
</div>
)

This comment was marked as resolved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
modules/teams/team-members.tsx (2)

128-128: Search placeholder claims “role” filtering but code doesn’t support it.

Either implement role-based filtering or update the placeholder. Quick fix below updates the copy.

-          placeholder="Search by name, role or tasks count"
+          placeholder="Search by name or tasks count"

If you want role search, I can add it once member roles are included in TeamDto.users[].

Also applies to: 84-89


151-153: Skeleton colSpan should match column count (avoids table layout jitter).

When isAdmin is false, header has 4 columns but skeleton spans 5.

-                    <TableCell colSpan={5}>
+                    <TableCell colSpan={isAdmin ? 5 : 4}>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3c75372 and 032f31f.

📒 Files selected for processing (8)
  • api/teams/teams.api.ts (2 hunks)
  • api/teams/teams.enum.ts (1 hunks)
  • api/teams/teams.type.ts (4 hunks)
  • components/leave-team-button.tsx (1 hunks)
  • lib/team-utils.ts (2 hunks)
  • modules/teams/components/leave-team-dialog.tsx (1 hunks)
  • modules/teams/components/teams-layout-header.tsx (3 hunks)
  • modules/teams/team-members.tsx (6 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Hariom01010
PR: Real-Dev-Squad/todo-frontend#189
File: modules/teams/team-members.tsx:63-63
Timestamp: 2025-08-26T08:14:08.375Z
Learning: In modules/teams/team-members.tsx, the LeaveTeamDialog components use a shared boolean state (showLeaveTeamDialog) which causes all dialog instances across all rows to open simultaneously when any "Remove from team" option is clicked. This requires per-row state management using selectedMemberId to track which specific member's dialog should be open.
📚 Learning: 2025-08-26T08:14:08.375Z
Learnt from: Hariom01010
PR: Real-Dev-Squad/todo-frontend#189
File: modules/teams/team-members.tsx:63-63
Timestamp: 2025-08-26T08:14:08.375Z
Learning: In modules/teams/team-members.tsx, the LeaveTeamDialog components use a shared boolean state (showLeaveTeamDialog) which causes all dialog instances across all rows to open simultaneously when any "Remove from team" option is clicked. This requires per-row state management using selectedMemberId to track which specific member's dialog should be open.

Applied to files:

  • modules/teams/components/leave-team-dialog.tsx
  • modules/teams/components/teams-layout-header.tsx
  • components/leave-team-button.tsx
  • modules/teams/team-members.tsx
📚 Learning: 2025-08-26T07:14:25.788Z
Learnt from: Hariom01010
PR: Real-Dev-Squad/todo-frontend#189
File: modules/teams/components/teams-layout-header.tsx:44-51
Timestamp: 2025-08-26T07:14:25.788Z
Learning: In the todo-frontend application, teams are only visible to people who are part of the team. Non-members cannot access team pages, so membership checks in components like teams-layout-header are unnecessary since access control is handled at a higher level.

Applied to files:

  • modules/teams/components/teams-layout-header.tsx
  • modules/teams/team-members.tsx
🧬 Code graph analysis (6)
modules/teams/components/leave-team-dialog.tsx (2)
components/ui/alert-dialog.tsx (7)
  • AlertDialog (124-124)
  • AlertDialogTrigger (127-127)
  • AlertDialogContent (128-128)
  • AlertDialogHeader (129-129)
  • AlertDialogTitle (131-131)
  • AlertDialogDescription (132-132)
  • AlertDialogFooter (130-130)
components/ui/button.tsx (1)
  • Button (59-59)
modules/teams/components/teams-layout-header.tsx (2)
hooks/useAuth.ts (1)
  • useAuth (13-36)
components/leave-team-button.tsx (1)
  • LeaveTeamButton (13-64)
components/leave-team-button.tsx (4)
hooks/useAuth.ts (1)
  • useAuth (13-36)
api/teams/teams.api.ts (1)
  • TeamsApi (14-101)
api/tasks/tasks.api.ts (1)
  • TasksApi (17-77)
modules/teams/components/leave-team-dialog.tsx (1)
  • LeaveTeamDialog (23-59)
api/teams/teams.api.ts (2)
lib/api-client.ts (1)
  • apiClient (6-10)
api/teams/teams.type.ts (1)
  • UserRole (142-146)
modules/teams/team-members.tsx (4)
hooks/useAuth.ts (1)
  • useAuth (13-36)
api/teams/teams.api.ts (1)
  • TeamsApi (14-101)
api/tasks/tasks.api.ts (1)
  • TasksApi (17-77)
modules/teams/components/leave-team-dialog.tsx (1)
  • LeaveTeamDialog (23-59)
api/teams/teams.type.ts (1)
components/teams/TeamActivity.tsx (1)
  • TeamActivity (58-65)
🪛 GitHub Actions: CI
lib/team-utils.ts

[error] 78-78: Type error: Property 'MEMBER_REMOVED_FROM_TEAM' does not exist on type 'typeof TeamActivityActions'.

🔇 Additional comments (8)
api/teams/teams.enum.ts (1)

1-5: TeamRoles enum looks good and scoped.

Clear, string-valued roles; exported for reuse. No issues.

modules/teams/components/leave-team-dialog.tsx (1)

33-35: Controlled AlertDialog is correctly scoped per member via activeDialogMemberId; no changes needed.

modules/teams/components/teams-layout-header.tsx (1)

24-27: Good: guards and role-based gating for leave action.

Auth + team loading handled; owner/POC protected from leaving. Looks correct.

api/teams/teams.api.ts (2)

73-78: removeFromTeam endpoint wiring looks correct.

DELETE path and arg names are clear. Void return is appropriate.


79-89: getUserRoles addition is consistent with types and client config.

Query key and GET path are fine; return type matches UserRole.

components/leave-team-button.tsx (1)

51-55: No changes needed: DEFAULT_USER.id is an empty string (falsy), so the existing if (user?.id) guard correctly prevents using a bogus ID.

modules/teams/team-members.tsx (1)

189-219: Per-row dialog state fix looks good; prevents all dialogs opening at once.

activeDialogMemberId gating on member.id correctly scopes the dialog open state to the selected row.

api/teams/teams.type.ts (1)

134-146: Confirm backend contract for UserRole/UserTeamRole.

Verify that role_name aligns with TeamRoles enum values and that scope is always 'TEAM'; otherwise add a union or zod parser.

I can add a zod schema and runtime parsing if the backend returns wider shapes.

Comment on lines +73 to +78
removeFromTeam: {
key: ({ teamId }: { teamId: string }) => ['TeamsApi.removeFromTeam', teamId],
fn: async ({ teamId, memberId }: { teamId: string; memberId: string }): Promise<void> => {
await apiClient.delete(`/v1/teams/${teamId}/members/${memberId}`)
},
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Optional: include memberId in the key for better cache scoping.

If you later use mutation keys or logs based on keys, scoping by memberId can help.

Apply:

-    key: ({ teamId }: { teamId: string }) => ['TeamsApi.removeFromTeam', teamId],
+    key: ({ teamId, memberId }: { teamId: string; memberId?: string }) => [
+      'TeamsApi.removeFromTeam',
+      teamId,
+      memberId,
+    ],

Call sites don’t rely on this today, so safe to skip if unnecessary.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
removeFromTeam: {
key: ({ teamId }: { teamId: string }) => ['TeamsApi.removeFromTeam', teamId],
fn: async ({ teamId, memberId }: { teamId: string; memberId: string }): Promise<void> => {
await apiClient.delete(`/v1/teams/${teamId}/members/${memberId}`)
},
},
removeFromTeam: {
key: ({ teamId, memberId }: { teamId: string; memberId?: string }) => [
'TeamsApi.removeFromTeam',
teamId,
memberId,
],
fn: async ({ teamId, memberId }: { teamId: string; memberId: string }): Promise<void> => {
await apiClient.delete(`/v1/teams/${teamId}/members/${memberId}`)
},
},
🤖 Prompt for AI Agents
In api/teams/teams.api.ts around lines 73 to 78 the cache/mutation key for
removeFromTeam is currently only scoped by teamId which loses granularity for
per-member operations; update the key to include memberId (e.g.
['TeamsApi.removeFromTeam', teamId, memberId]) and adjust the key function
signature to accept memberId ({ teamId, memberId }: { teamId: string; memberId:
string }) so callers that generate the key pass both values; no other behavior
changes required.

Comment on lines +114 to +117
action: TeamActivityActions.TASKS_REASSIGNED_TO_TEAM
performed_by_name: string
task_count: string
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use a numeric type for task_count.

Counts should be numbers to avoid downstream parsing/pluralization issues.

-export type TaskReassignActivity = BaseActivity & {
+export type TaskReassignActivity = BaseActivity & {
   action: TeamActivityActions.TASKS_REASSIGNED_TO_TEAM
   performed_by_name: string
-  task_count: string
+  task_count: number
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
action: TeamActivityActions.TASKS_REASSIGNED_TO_TEAM
performed_by_name: string
task_count: string
}
export type TaskReassignActivity = BaseActivity & {
action: TeamActivityActions.TASKS_REASSIGNED_TO_TEAM
performed_by_name: string
task_count: number
}
🤖 Prompt for AI Agents
In api/teams/teams.type.ts around lines 114 to 117, the task_count field is
declared as a string which can cause downstream parsing and pluralization bugs;
change its type to a numeric type (number) so counters are handled as numbers
throughout the codebase, update any related interfaces/usages/tests to expect a
number and ensure any serialization/parsing converts incoming string values to
numbers before assigning to task_count.

Comment on lines +29 to +31
queryClient.invalidateQueries({
queryKey: TasksApi.getTasks.key(),
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Invalidate all task queries using a prefix key (current key risks missing variants).

Using TasksApi.getTasks.key() expands to ['TasksApi.getTasks', undefined], which may not match queries with params. Invalidate via a stable prefix instead.

-      queryClient.invalidateQueries({
-        queryKey: TasksApi.getTasks.key(),
-      })
+      queryClient.invalidateQueries({ queryKey: ['TasksApi.getTasks'] })
🤖 Prompt for AI Agents
In components/leave-team-button.tsx around lines 29-31, the call to
queryClient.invalidateQueries currently passes TasksApi.getTasks.key() which
expands to ['TasksApi.getTasks', undefined] and can miss queries with params;
change the invalidate to target the stable prefix instead (e.g.,
invalidateQueries with the prefix key ['TasksApi.getTasks'] or use a predicate
that checks query.queryKey[0] === 'TasksApi.getTasks') so all task query
variants are invalidated.

Comment on lines +32 to +36
toast.success('Leave Team Successfully')
router.push('/dashboard')
},
onError: () => {
toast.error('Failed to leave team')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Polish toast copy; align with UX tense/casing.

Minor, but improves UX consistency.

-      toast.success('Leave Team Successfully')
+      toast.success('Left team successfully.')
-      toast.error('Failed to leave team')
+      toast.error('Failed to leave team.')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
toast.success('Leave Team Successfully')
router.push('/dashboard')
},
onError: () => {
toast.error('Failed to leave team')
toast.success('Left team successfully.')
router.push('/dashboard')
},
onError: () => {
toast.error('Failed to leave team.')
🤖 Prompt for AI Agents
In components/leave-team-button.tsx around lines 32 to 36, the success and error
toast messages use inconsistent tense/casing; update them to match UX guidelines
by using past tense and sentence case (e.g., change success to "Left team
successfully" or "Successfully left team" and keep error as "Failed to leave
team" or "Could not leave team" to match tone), and ensure both messages follow
the same casing and tense conventions used across the app.

Comment on lines +43 to +57
return (
<LeaveTeamDialog
title="Leave Team"
description="Are you sure you want to leave this team? You will lose access to its tasks and members."
buttonText="Leave Team"
open={showLeaveTeamDialog}
onOpenChange={setShowLeaveTeamDialog}
onSubmit={() => {
if (user?.id) {
leaveTeamMutation.mutate({ teamId, memberId: user.id })
} else {
toast.error('User ID not found')
}
}}
>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

Prevent double-submit; disable confirm while mutation is pending.

Dialog closes immediately and confirm remains clickable until onClick runs; add a disabled/loading affordance.

Apply in this file:

     <LeaveTeamDialog
       title="Leave Team"
       description="Are you sure you want to leave this team? You will lose access to its tasks and members."
-      buttonText="Leave Team"
+      buttonText={leaveTeamMutation.isPending ? 'Leaving…' : 'Leave Team'}
       open={showLeaveTeamDialog}
-      onOpenChange={setShowLeaveTeamDialog}
+      onOpenChange={setShowLeaveTeamDialog}
+      confirmDisabled={leaveTeamMutation.isPending}

And extend the dialog component to support confirmDisabled (and optionally confirmVariant="destructive"):

 // modules/teams/components/leave-team-dialog.tsx
 export const LeaveTeamDialog = ({
   title,
   description,
   buttonText,
   open,
   onSubmit,
   onOpenChange,
   children,
+  confirmDisabled,
+  confirmVariant = 'default',
 }: LeaveTeamDialogProps) => {
 ...
-          <Button
-            variant="default"
+          <Button
+            variant={confirmVariant}
             type="submit"
+            disabled={confirmDisabled}
             onClick={() => {
               onSubmit()
               onOpenChange(false)
             }}
           >

If you prefer, I can open a follow-up PR to wire this across all usages.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In components/leave-team-button.tsx around lines 43–57, the confirm button
allows double-submit and the dialog closes immediately; wire the mutation
loading state into the dialog and prevent closing while pending. Change the
submit flow to use leaveTeamMutation.mutateAsync (or mutate with
onSuccess/onError) so you only close the dialog on success, and pass a new prop
confirmDisabled={leaveTeamMutation.isLoading} (and confirmVariant="destructive")
into LeaveTeamDialog so the confirm button is disabled/indicates loading while
the mutation is pending.

Comment on lines +45 to +54
<Button
variant="default"
type="submit"
onClick={() => {
onSubmit()
onOpenChange(false)
}}
>
{buttonText}
</Button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make the confirm button type="button" (not submit) to avoid unintended form submits.

This dialog isn’t inside a form; submit type can trigger parent form submissions.

Apply:

-          <Button
-            variant="default"
-            type="submit"
+          <Button
+            variant="default"
+            type="button"
             onClick={() => {
               onSubmit()
               onOpenChange(false)
             }}
           >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="default"
type="submit"
onClick={() => {
onSubmit()
onOpenChange(false)
}}
>
{buttonText}
</Button>
<Button
variant="default"
type="button"
onClick={() => {
onSubmit()
onOpenChange(false)
}}
>
{buttonText}
</Button>
🤖 Prompt for AI Agents
In modules/teams/components/leave-team-dialog.tsx around lines 45 to 54 the
confirm Button is declared with type="submit", which can cause unintended parent
form submissions; change the button to type="button" and keep the onClick
handler that calls onSubmit() and onOpenChange(false) so it behaves as a normal
dialog action without triggering form submits.

Comment on lines +45 to +46
{!isOwnerOrPOC ? <LeaveTeamButton teamId={teamId} /> : null}
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Minor JSX simplification.

Use logical && instead of ternary with null.

Apply:

-        {!isOwnerOrPOC ? <LeaveTeamButton teamId={teamId} /> : null}
+        {!isOwnerOrPOC && <LeaveTeamButton teamId={teamId} />}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{!isOwnerOrPOC ? <LeaveTeamButton teamId={teamId} /> : null}
</div>
{!isOwnerOrPOC && <LeaveTeamButton teamId={teamId} />}
</div>
🤖 Prompt for AI Agents
In modules/teams/components/teams-layout-header.tsx around lines 45 to 46,
replace the ternary rendering `{!isOwnerOrPOC ? <LeaveTeamButton teamId={teamId}
/> : null}` with the simpler logical AND form `!isOwnerOrPOC && <LeaveTeamButton
teamId={teamId} />`; keep props and surrounding JSX unchanged.

},
enabled: !!userId,
})
const isAdmin = userRole?.roles.find((role) => role.role_name == TeamRoles.ADMIN)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Compute admin as a boolean and use strict equality.

Avoid relying on truthiness of an object from find; prefer some with ===.

-  const isAdmin = userRole?.roles.find((role) => role.role_name == TeamRoles.ADMIN)
+  const isAdmin = !!userRole?.roles.some((role) => role.role_name === TeamRoles.ADMIN)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isAdmin = userRole?.roles.find((role) => role.role_name == TeamRoles.ADMIN)
const isAdmin = !!userRole?.roles.some((role) => role.role_name === TeamRoles.ADMIN)
🤖 Prompt for AI Agents
In modules/teams/team-members.tsx around line 77, the code currently assigns
isAdmin from find which returns an object (truthiness-based) and uses loose
equality; change it to compute isAdmin as a boolean using Array.prototype.some
and strict equality (===) against TeamRoles.ADMIN so isAdmin is true/false
reliably and comparisons use ===.

Comment on lines +113 to +115
queryClient.invalidateQueries({
queryKey: TasksApi.getTasks.key(),
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Invalidate all task queries using a prefix key (current key risks missing variants).

Same rationale as in LeaveTeamButton.

-      queryClient.invalidateQueries({
-        queryKey: TasksApi.getTasks.key(),
-      })
+      queryClient.invalidateQueries({ queryKey: ['TasksApi.getTasks'] })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
queryClient.invalidateQueries({
queryKey: TasksApi.getTasks.key(),
})
// Invalidate all variants of the getTasks query by prefixing the key
- queryClient.invalidateQueries({
- queryKey: TasksApi.getTasks.key(),
queryClient.invalidateQueries({ queryKey: ['TasksApi.getTasks'] })
🤖 Prompt for AI Agents
In modules/teams/team-members.tsx around lines 113–115, the current
invalidateQueries call uses the exact task query key and may miss variant
queries; change it to invalidate all task variants by passing exact: false (e.g.
queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key(), exact: false
})) so every task-related query that shares the key prefix is invalidated.

@AnujChhikara
Copy link
Contributor

could you please stack it over #192 as it contains changes of that PR too

const { data: userRole, isLoading: isUserRoleLoading } = useQuery({
queryKey: TeamsApi.getUserRoles.key({ teamId, userId: userId }),
queryFn: () => {
return TeamsApi.getUserRoles.fn({ teamId, userId })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a potential type safety issue here. The userId variable could be undefined, but TeamsApi.getUserRoles.fn() expects a string parameter. This would cause a runtime error if the function is called with undefined.

Consider adding a null check before making the API call:

return userId 
  ? TeamsApi.getUserRoles.fn({ teamId, userId }) 
  : Promise.reject('No user ID available')

This ensures the function only executes with valid parameters and provides a clear error message when the user ID is missing.

Suggested change
return TeamsApi.getUserRoles.fn({ teamId, userId })
return userId ? TeamsApi.getUserRoles.fn({ teamId, userId }) : Promise.reject('No user ID available')

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

<DropdownMenuContent>
<DropdownMenuItem>Change Role</DropdownMenuItem>
<DropdownMenuItem>Remove from team</DropdownMenuItem>
{member.id !== user.id &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a potential null reference issue on line 189 where user.id is accessed directly. If user is undefined during loading states, this will cause a runtime error. Consider using the optional chaining operator to safely access the id property:

{member.id !== user?.id &&

This ensures the comparison only happens when user is defined, preventing the application from crashing during loading states.

Suggested change
{member.id !== user.id &&
{member.id !== user?.id &&

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

},
enabled: !!userId,
})
const isAdmin = userRole?.roles.find((role) => role.role_name == TeamRoles.ADMIN)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comparison for admin role detection is using loose equality (==) which can lead to unexpected behavior due to type coercion. For enum comparisons, strict equality (===) is recommended to ensure type safety:

const isAdmin = userRole?.roles.find((role) => role.role_name === TeamRoles.ADMIN)

This ensures the comparison only succeeds when both the value and type match exactly, preventing potential bugs that could arise from type coercion.

Suggested change
const isAdmin = userRole?.roles.find((role) => role.role_name == TeamRoles.ADMIN)
const isAdmin = userRole?.roles.find((role) => role.role_name === TeamRoles.ADMIN)

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@Hariom01010
Copy link
Contributor Author

Hariom01010 commented Sep 3, 2025

could you please stack it over #192 as it contains changes of that PR too
@AnujChhikara this PR is already stacked over PR #192. Once PR #192 gets merged, this PR will start showing changes only specific to this PR

- add shimmer for isAuthLoading state
- moved the condition check in onSubmit in leave team button to a different function for
  better readability
@AnujChhikara
Copy link
Contributor

could you please stack it over #192 as it contains changes of that PR too
@AnujChhikara this PR is already stacked over PR #192. Once PR #192 gets merged, this PR will start showing changes only specific to this PR

@Hariom01010
Copy link
Contributor Author

could you please stack it over #192 as it contains changes of that PR too
@AnujChhikara this PR is already stacked over PR #192. Once PR #192 gets merged, this PR will start showing changes only specific to this PR

You’re welcome to review this PR if you’d like, but if it’s too much effort to compare changes with #192, we can hold off until #192 is merged.

@AnujChhikara
Copy link
Contributor

could you please stack it over #192 as it contains changes of that PR too
@AnujChhikara this PR is already stacked over PR #192. Once PR #192 gets merged, this PR will start showing changes only specific to this PR

You’re welcome to review this PR if you’d like, but if it’s too much effort to compare changes with #192, we can hold off until #192 is merged.

you can read about stack pr from here https://blog.logrocket.com/using-stacked-pull-requests-in-github/

@iamitprakash iamitprakash merged commit 8df4d65 into RealDevSquad:develop Sep 20, 2025
3 checks passed
@Hariom01010 Hariom01010 mentioned this pull request Sep 20, 2025
10 tasks
iamitprakash added a commit that referenced this pull request Sep 20, 2025
feat(#170): add remove member functionality (#193)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature task A big ticket item that needs to come up as a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: allow users to leave a team

4 participants