Skip to content

Conversation

@visz11
Copy link

@visz11 visz11 commented Nov 10, 2025

CodeAnt-AI Description

Add team table with hierarchy, counts, config status, and actions

What Changed

  • New team list UI that shows hierarchical teams with expand/collapse and keeps the parent app informed of the current team count
  • Team rows show a description placeholder when empty, a Managers list with “+N in subteams” when applicable, and a Members column that lists counts for Managers, Members, and Subteams
  • Config Status column displays present/not-present indicators for Communication, DORA, Issue, and Git
  • Actions available per team: Edit opens the team settings, Add Member opens the members tab in team settings, Delete requires confirmation and is blocked for top-level teams that have subteams (error shown)
  • Loading indicator shown while team data is fetching or a delete is in progress; successful deletes refresh the team list and show a success message; failures show an error message

Impact

✅ Fewer accidental deletions of parent teams
✅ Clearer member and manager counts
✅ Faster access to edit teams and add members

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced a team management grid with expandable rows displaying team details
    • Teams can be edited, deleted (with confirmation safeguards), and members added directly from the interface
    • Team information now visible includes name, description, managers, member counts, and setup status
    • Deletion protection prevents removal of teams containing sub-teams
    • Loading indicators display during data operations

@codeant-ai
Copy link

codeant-ai bot commented Nov 10, 2025

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link

coderabbitai bot commented Nov 10, 2025

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.

Walkthrough

A new React component TeamView is added to TeamTable.tsx, rendering a data grid of teams with expandable rows. The component fetches team data, displays team metadata across multiple columns, and provides actions for editing, deletion with confirmation, and member management. Deletion is prevented if sub-teams exist and triggers API calls with query invalidation.

Changes

Cohort / File(s) Summary
New TeamView Component
TeamTable.tsx
Introduces TeamView React component with expandable data grid rendering team information. Includes columns for name, description, managers, members, subteams, setup status, and actions. Implements team fetching via useFetchTeams, delete with confirmation and API call, edit/add-member navigation, and query invalidation on state changes. Manages loading states and prevents deletion if sub-teams exist.

Sequence Diagram

sequenceDiagram
    participant Parent as Parent Component
    participant TeamView as TeamView
    participant API as API
    participant QueryCache as Query Cache

    Parent->>TeamView: Mount with props
    TeamView->>API: useFetchTeams()
    API-->>TeamView: Team data
    TeamView->>TeamView: Render data grid
    TeamView->>Parent: setTeamLength(count)

    rect rgb(200, 220, 255)
    Note over TeamView: User Action: Delete Team
    TeamView->>TeamView: Show confirmation
    activate TeamView
    TeamView->>API: DELETE /team/:id
    API-->>TeamView: Success/Error
    TeamView->>QueryCache: Invalidate queries
    QueryCache-->>TeamView: Refresh data
    TeamView->>Parent: setTeamLength(updated)
    deactivate TeamView
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Areas requiring attention:
    • Delete confirmation logic and prevention when sub-teams exist
    • API DELETE call error handling and query invalidation strategy
    • Column renderer implementations for data formatting and edge cases
    • Expand/collapse row functionality and state management
    • Integration between useFetchTeams hook and component lifecycle

Poem

🐰 A grid of teams now stands so tall,
With rows that expand to show their all—
Managers, members, status shown,
Delete with care (sub-teams groan),
One component to organize them all! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'TeamTable changes' is vague and generic, failing to communicate the specific purpose of the changes (introducing a new TeamView component with data grid functionality). Consider using a more descriptive title like 'Add TeamView component with expandable team data grid' to clearly convey the main change.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch TeamTable-changes

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.

@gemini-code-assist
Copy link

Summary of Changes

Hello @visz11, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new TeamView component, which provides a comprehensive and interactive table for managing teams. It displays detailed information about each team, including managers, member counts, and configuration statuses, and offers actions like editing, deleting, and adding members, enhancing the overall team management interface.

Highlights

  • New TeamView Component: Introduced a new TeamView component responsible for displaying and managing team-related data in a tabular format.
  • Data Grid Implementation: Implemented a robust data grid using @tanstack/react-table to present team information, including features like expandable rows for sub-teams.
  • Detailed Team Information Display: The table now shows comprehensive details for each team, such as team name, description, assigned managers (with subteam manager counts), member and subteam counts, and configuration statuses (Communication, DORA, Issue, Git).
  • Team Management Actions: Provided action buttons within the table for editing team details, safely deleting teams (with a check to prevent deletion of teams with subteams), and navigating to add new members.
  • API Integration for Deletion: Integrated API calls for team deletion, complete with loading states, toast notifications for success/failure, and invalidation of relevant react-query caches to ensure data consistency.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@visz11
Copy link
Author

visz11 commented Nov 10, 2025

/refacto-visz

@codeant-ai codeant-ai bot added the size:L This PR changes 100-499 lines, ignoring generated files label Nov 10, 2025
@refacto-visz
Copy link

refacto-visz bot commented Nov 10, 2025

TeamTable changes

TL;DR: Adds a comprehensive team management table component with hierarchical team display, member management, and configuration status tracking.


Refacto PR Summary

Implements a complete team management interface using React Table with expandable rows, member counting, and configuration status visualization.
This PR introduces a new TeamTable.tsx component that provides a hierarchical view of teams and subteams with expand/collapse functionality. The component displays team descriptions, manager assignments, member counts, and setup states for Communication, DORA, Issue tracking, and Git integrations. Users can edit teams, delete teams (with subteam validation), and add members through dedicated action buttons. The table integrates with the existing API client and query system for real-time data management.

Change Highlights

Click to expand
  • TeamTable.tsx: Complete team management table with hierarchical display and CRUD operations
  • Column definitions: Team name with expand/collapse, description, managers, member counts, config status, and actions
  • Setup states visualization: Present/NotPresent icons for Communication, DORA, Issue, and Git configurations
  • Action handlers: Edit navigation, delete with subteam validation, and add member functionality
  • Query integration: Real-time data fetching with cache invalidation on team operations
  • Responsive design: Flexible layout with proper spacing and visual hierarchy

Sequence Diagram

sequenceDiagram
    participant U as User
    participant TT as TeamTable
    participant API as API Client
    participant QC as Query Client
    participant DB as Database
    
    U->>TT: Load team view
    TT->>API: useFetchTeams(team)
    API->>DB: GET teams data
    DB-->>API: Teams with subteams
    API-->>TT: Teams data
    TT->>TT: Build hierarchical rows
    TT-->>U: Display team table
    
    U->>TT: Click delete team
    TT->>TT: Validate subteams
    TT->>API: DELETE /v1/team/{orgId}/{name}
    API->>DB: Remove team
    DB-->>API: Success response
    API-->>TT: Deletion confirmed
    TT->>QC: Invalidate queries
    QC->>TT: Refresh data
    TT-->>U: Updated table view
Loading

Testing Guide

Click to expand
  1. Hierarchical display: Load teams page, verify parent teams show expand/collapse icons and subteams indent properly
  2. Team operations: Test edit button navigation to team settings, verify add member button opens correct tab
  3. Delete validation: Attempt to delete team with subteams, confirm error message prevents deletion
  4. Configuration status: Verify Present/NotPresent icons display correctly for Communication, DORA, Issue, and Git states
  5. Member counting: Check manager and member counts display accurately, including subteam aggregation

@refacto-visz
Copy link

refacto-visz bot commented Nov 10, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new TeamTable.tsx component for displaying team information in a data grid. The implementation is mostly solid, but there are several areas for improvement regarding TypeScript best practices, React performance optimizations, code clarity, accessibility, and security. Key issues include widespread use of the any type, incorrect usage of useMemo, repetitive code blocks, an accessibility issue with an aria-label, and a security concern with storing tokens in localStorage. I've provided specific suggestions to address these points.

Comment on lines +34 to +40
interface TeamViewProps {
team: string;
setTeamLength: any;
appState?: any;
teamLength?: number;
plan?: string;
}

Choose a reason for hiding this comment

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

high

Throughout the component, the any type is used for props (setTeamLength, appState), state (rows), and generic type arguments (createColumnHelper<any>). This practice negates the benefits of TypeScript, such as type safety and improved developer experience. It's highly recommended to define and use specific interfaces for your data structures.

For example:

  • setTeamLength could be (length: number) => void.
  • A Team interface should be defined for the data from useFetchTeams and used for rows and createColumnHelper<Team>.

headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `bearer ${localStorage.getItem('token')}`,

Choose a reason for hiding this comment

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

high

Storing sensitive information like authentication tokens in localStorage is a security risk as it's vulnerable to Cross-Site Scripting (XSS) attacks. An attacker could steal the token and impersonate the user. Consider using httpOnly cookies for storing tokens, which are not accessible via JavaScript.

Comment on lines +65 to +281
const columns = useMemo(
() => [
columnHelper.accessor('name', {
cell: (info) => {
return (
<Box pl={`${info.row.depth * 3}rem`}>
{info.row.getCanExpand() ? (
<>
<Button
variant={'unstyled'}
minWidth={0}
onClick={info.row.getToggleExpandedHandler()}
>
{info.row.getIsExpanded() ? (
<AiOutlineMinusSquare
size={18}
color={colors.text.primary}
/>
) : (
<AiOutlinePlusSquare
size={18}
color={colors.text.primary}
/>
)}
</Button>
</>
) : (
''
)}{' '}
{info.getValue()}
</Box>
);
},
header: 'Team Name',
}),

columnHelper.accessor('description', {
cell: (info) => {
if (!info.getValue()) {
return <Box color="orange.600">No Description Added</Box>;
} else {
return info.getValue();
}
},
header: 'Description',
size: 360,
}),

columnHelper.accessor('managers', {
cell: (info) => {
let count = 0;
info.row.original.subTeams.forEach((item: any) => {
count += item.managers.length;
});
return (
<Flex flexWrap="wrap">
{info?.getValue()?.length > 0 ? (
<Box>
{info?.getValue()?.map((item: string, idx: number) => (
<Text mr={2} key={`manager-${idx}`}>
{item}
{info.getValue().length - 1 !== idx && ','}
</Text>
))}
{info.row.original.subTeams.length > 0 ? (
<Text>+{count} in subteams</Text>
) : (
''
)}
</Box>
) : (
<Box>
<Text color={'gray.400'} fontFamily={'heading'}>
No Manager Assigned
</Text>
{info.row.original.subTeams.length > 0 ? (
<Text>+{count} in subteams</Text>
) : (
''
)}
</Box>
)}
</Flex>
);
},
header: 'Managers',
}),
columnHelper.accessor('memberCount', {
cell: (info) => {
const rowData = info.row.original as any;
const managersCount = rowData?.managers
? rowData?.managers.length
: 0;
const membersCount = rowData?.memberCount ? rowData?.memberCount : 0;
const subTeamsCount = rowData?.subTeams ? rowData.subTeams.length : 0;


return (
<Flex
flexDirection="column"
gap="2"
width="100px"
alignItems="flex-start"
>
<Text>{managersCount} Managers</Text>
<Text>{membersCount} Members</Text>
<Text>{subTeamsCount} Subteams</Text>
</Flex>
);
},
header: 'Members',
size: 50,
meta: {
isNumeric: true,
},
}),
columnHelper.accessor('setupStates', {
cell: (info) => (
<UnorderedList listStyleType={'none'}>
<ListItem display={'flex'} gap={1}>
{info.getValue().Communication ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Communication
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Dora ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
DORA
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Issue ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Issue
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Git ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Git
</ListItem>
</UnorderedList>
),
header: 'Config Status',
}),

columnHelper.accessor('actions', {
cell: (info) => (
<Flex>
<Tooltip label="Edit" hasArrow>
<IconButton
aria-label="edit"
bg="transparent"
size="sm"
color="text.secondary"
icon={<MdOutlineEdit size={16} />}
position={'static'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigate(`/settings/teams/${info.row.original?.name}`);
}}
/>
</Tooltip>

<ConfirmButton
buttonText="Delete"
showIcon
bodyText={`Do you really want to delete this team - ${info.row.original?.name}?`}
headerText="Are you sure?"
onSuccessAction={() => {
if (info.row.original.subTeams.length >= 1) {
newToast({
message:
'Cannot delete top level team if sub teams available.',
status: 'error',
});
} else {
handleDelete(info.row.original?.name);
}
}}
/>

<Tooltip label="Add Member" hasArrow>
<IconButton
aria-label="edit"
size="sm"
bg="transparent"
color="primary"
icon={<MdPersonAddAlt size={16} />}
position={'static'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigate(`/settings/teams/${info.row.original?.name}`, {
state: { tab: 1 },
});
}}
/>
</Tooltip>
</Flex>
),
header: 'Actions',
}),
],
[teamsData?.dto]
);

Choose a reason for hiding this comment

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

medium

The useMemo hook for columns is not being used effectively, which can lead to performance issues.

  1. The dependency array [teamsData?.dto] is incorrect. The column definitions don't depend on the data itself, causing unnecessary recalculations when data changes.
  2. The handleDelete function, used within the 'Actions' column, is defined after this useMemo block and is not wrapped in useCallback. This means it's recreated on every render, which would break memoization.

To fix this, you should:

  1. Move the handleDelete function definition to be before the columns useMemo block.
  2. Wrap handleDelete in React.useCallback with its dependencies (e.g., [queryClient, newToast]).
  3. Update the useMemo dependency array for columns to include its actual dependencies, like [colors, navigate, newToast, handleDelete].

Comment on lines +114 to +149
cell: (info) => {
let count = 0;
info.row.original.subTeams.forEach((item: any) => {
count += item.managers.length;
});
return (
<Flex flexWrap="wrap">
{info?.getValue()?.length > 0 ? (
<Box>
{info?.getValue()?.map((item: string, idx: number) => (
<Text mr={2} key={`manager-${idx}`}>
{item}
{info.getValue().length - 1 !== idx && ','}
</Text>
))}
{info.row.original.subTeams.length > 0 ? (
<Text>+{count} in subteams</Text>
) : (
''
)}
</Box>
) : (
<Box>
<Text color={'gray.400'} fontFamily={'heading'}>
No Manager Assigned
</Text>
{info.row.original.subTeams.length > 0 ? (
<Text>+{count} in subteams</Text>
) : (
''
)}
</Box>
)}
</Flex>
);
},

Choose a reason for hiding this comment

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

medium

The logic for rendering managers is complex and likely has a rendering bug (managers will appear on separate lines because <Text> is a block element by default). It can be greatly simplified by using Array.prototype.join for the managers list and calculating the sub-team manager count with reduce. This also removes duplicated code.

        cell: (info) => {
          const managers = info.getValue() ?? [];
          const subTeamManagerCount = info.row.original.subTeams.reduce(
            (acc: number, team: any) => acc + team.managers.length,
            0
          );

          return (
            <Flex flexWrap="wrap" alignItems="baseline">
              {managers.length > 0 ? (
                <Text>{managers.join(', ')}</Text>
              ) : (
                <Text color="gray.400" fontFamily="heading">
                  No Manager Assigned
                </Text>
              )}
              {subTeamManagerCount > 0 && (
                <Text ml={2}>+{subTeamManagerCount} in subteams</Text>
              )}
            </Flex>
          );
        },

Comment on lines +182 to +217
cell: (info) => (
<UnorderedList listStyleType={'none'}>
<ListItem display={'flex'} gap={1}>
{info.getValue().Communication ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Communication
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Dora ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
DORA
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Issue ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Issue
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Git ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Git
</ListItem>
</UnorderedList>
),

Choose a reason for hiding this comment

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

medium

The code for rendering setup states is very repetitive. This can be made more concise and easier to maintain by iterating over an array of status configurations.

        cell: (info) => {
          const statuses = [
            { key: 'Communication', label: 'Communication' },
            { key: 'Dora', label: 'DORA' },
            { key: 'Issue', label: 'Issue' },
            { key: 'Git', label: 'Git' },
          ];
          const setupValue = info.getValue();

          return (
            <UnorderedList listStyleType="none">
              {statuses.map((status) => (
                <ListItem display="flex" gap={1} key={status.key}>
                  <Image src={setupValue[status.key] ? Present : NotPresent} />
                  {status.label}
                </ListItem>
              ))}
            </UnorderedList>
          );
        },


<Tooltip label="Add Member" hasArrow>
<IconButton
aria-label="edit"

Choose a reason for hiding this comment

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

medium

The aria-label for this IconButton is "edit", which is incorrect for an "Add Member" action. This is misleading for users of assistive technologies. It should be updated to accurately describe the button's function.

                aria-label="add member"

Comment on lines +6 to +12
ListIcon,
ListItem,
Text,
Tooltip,
UnorderedList,
useTheme,
useToast,
Copy link

Choose a reason for hiding this comment

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

Suggestion: Remove the unused imports ListIcon and useToast from the Chakra import to avoid dead code. [maintainability]

Severity Level: Minor ⚠️

Suggested change
ListIcon,
ListItem,
Text,
Tooltip,
UnorderedList,
useTheme,
useToast,
ListItem,
Text,
Tooltip,
UnorderedList,
useTheme,
Why it matters? ⭐

The import block in TeamTable.tsx (shown in the current file) includes ListIcon and useToast, neither of which are referenced anywhere else in the file. The file uses useToastHook (from @devd-client/devd/components) and newToast; there are no usages of Chakra's useToast or ListIcon. Removing them will eliminate dead imports and any linter warnings without changing runtime behavior.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** TeamTable.tsx
**Line:** 6:12
**Comment:**
	*Maintainability: Remove the unused imports `ListIcon` and `useToast` from the Chakra import to avoid dead code.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +186 to +212
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Communication
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Dora ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
DORA
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Issue ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Issue
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Git ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
Copy link

Choose a reason for hiding this comment

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

Suggestion: Add descriptive alt attributes to the Image elements in the setup states cell for accessibility. [best practice]

Severity Level: Minor ⚠️

Suggested change
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Communication
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Dora ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
DORA
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Issue ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Issue
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Git ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
<Image src={Present} alt="Communication configured" />
) : (
<Image src={NotPresent} alt="Communication not configured" />
)}
Communication
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Dora ? (
<Image src={Present} alt="DORA configured" />
) : (
<Image src={NotPresent} alt="DORA not configured" />
)}
DORA
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Issue ? (
<Image src={Present} alt="Issue configured" />
) : (
<Image src={NotPresent} alt="Issue not configured" />
)}
Issue
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Git ? (
<Image src={Present} alt="Git configured" />
) : (
<Image src={NotPresent} alt="Git not configured" />
Why it matters? ⭐

The setup state icons are rendered with elements that currently lack alt attributes. Adding descriptive alt text (e.g., "Communication configured" / "Communication not configured") improves accessibility and removes potential JSX/ESLint warnings about missing alt on images. This change doesn't alter business logic and directly improves usability for assistive technologies.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** TeamTable.tsx
**Line:** 186:212
**Comment:**
	*Best Practice: Add descriptive `alt` attributes to the `Image` elements in the setup states cell for accessibility.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

@codeant-ai
Copy link

codeant-ai bot commented Nov 10, 2025

CodeAnt AI finished reviewing your PR.

@refacto-visz
Copy link

refacto-visz bot commented Nov 10, 2025

Code Review: TeamTable Security & Architecture Issues

👍 Well Done
Comprehensive Error Handling

Delete operations include proper error handling with user feedback and loading state management

Efficient Data Memoization

Smart useMemo implementation prevents unnecessary row recalculations on re-renders

📁 Selected files for review (1)
  • TeamTable.tsx
🎯 Custom Instructions
✅ Applied Instructions
Organization Guidelines
  • Keep pull requests small and focused (prefer < 400 lines of code).
  • All CI/CD checks, linting, and unit tests must pass before merge.
  • Use feature flags for new functionality and include a clear rollback plan.
  • Follow the company security checklist:
    • No hard-coded secrets or credentials.
    • Validate all external inputs.
    • Use parameterized queries for DB access.

Scope: All files

📝 Additional Comments
TeamTable.tsx (4)
Unused Import Statement

Import 'useToast' is declared but never used in the component logic. The component uses 'useToastHook' instead for toast notifications. This creates unnecessary dependency loading and code clutter.

Standards:

  • Algorithm-Correctness-Import-Usage
  • Logic-Verification-Dependency-Management
Unused Import Statement

Import 'ListIcon' is declared but never used in the component. The component uses Image components with Present/NotPresent SVGs for status indicators instead. This creates unnecessary dependency loading.

Standards:

  • Algorithm-Correctness-Import-Usage
  • Logic-Verification-Dependency-Management
Sensitive Data Exposure

Error messages may expose sensitive system information through err.message without sanitization. Stack traces or internal error details could reveal system architecture to attackers. Information disclosure enables reconnaissance for targeted attacks.

Standards:

  • CWE-209
  • OWASP-A09
  • NIST-SSDF-RV.1
Inefficient Array Access

Multiple calls to info.getValue() within map iteration creates redundant function calls and array length calculations. Each iteration triggers getValue() twice - once for the item and once for length comparison. Cache the array value to eliminate repeated function calls and improve rendering performance.

Standards:

  • ISO-IEC-25010-Performance-Efficiency-Time-Behavior
  • Optimization-Pattern-Function-Call-Reduction

Comment on lines +285 to +291
apiClient(`/v1/team/${localStorage.getItem('orgId')}/${name}`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `bearer ${localStorage.getItem('token')}`,
},
Copy link

Choose a reason for hiding this comment

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

Hardcoded Secrets Storage

Authentication token retrieved from localStorage without validation or encryption violates organization guideline requiring no hard-coded secrets or credentials. Attackers with XSS access can steal tokens enabling account takeover and unauthorized API access.

    const secureAuth = useSecureAuth();
    const { orgId, token } = await secureAuth.getValidatedCredentials();
    
    if (!orgId || !token) {
      throw new Error('Authentication required');
    }
    
    apiClient(`/v1/team/${orgId}/${name}`, {
      method: 'DELETE',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        authorization: `bearer ${token}`,
Commitable Suggestion
Suggested change
apiClient(`/v1/team/${localStorage.getItem('orgId')}/${name}`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `bearer ${localStorage.getItem('token')}`,
},
const secureAuth = useSecureAuth();
const { orgId, token } = await secureAuth.getValidatedCredentials();
if (!orgId || !token) {
throw new Error('Authentication required');
}
apiClient(`/v1/team/${orgId}/${name}`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `bearer ${token}`,
Standards
  • CWE-522
  • OWASP-A02
  • Org-Guideline-No hard-coded secrets or credentials


const handleDelete = (name: string) => {
setLoading(true);
apiClient(`/v1/team/${localStorage.getItem('orgId')}/${name}`, {
Copy link

Choose a reason for hiding this comment

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

Missing Input Validation

URL constructed with unsanitized parameters from localStorage and function parameter violates organization guideline to validate all external inputs. Path traversal or injection attacks possible through manipulated orgId or name values enabling unauthorized resource access.

    const sanitizeInput = (input: string | null): string => {
      if (!input || typeof input !== 'string') {
        throw new Error('Invalid input');
      }
      return input.replace(/[^a-zA-Z0-9_-]/g, '');
    };
    
    const sanitizedOrgId = sanitizeInput(localStorage.getItem('orgId'));
    const sanitizedName = sanitizeInput(name);
    
    apiClient(`/v1/team/${sanitizedOrgId}/${sanitizedName}`, {
Commitable Suggestion
Suggested change
apiClient(`/v1/team/${localStorage.getItem('orgId')}/${name}`, {
const sanitizeInput = (input: string | null): string => {
if (!input || typeof input !== 'string') {
throw new Error('Invalid input');
}
return input.replace(/[^a-zA-Z0-9_-]/g, '');
};
const sanitizedOrgId = sanitizeInput(localStorage.getItem('orgId'));
const sanitizedName = sanitizeInput(name);
apiClient(`/v1/team/${sanitizedOrgId}/${sanitizedName}`, {
Standards
  • CWE-20
  • OWASP-A03
  • Org-Guideline-Validate all external inputs

plan?: string;
}

const TeamView: React.FC<TeamViewProps> = ({
Copy link

Choose a reason for hiding this comment

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

God Component Violation

Single component handles multiple unrelated responsibilities violating SRP. TeamView manages data fetching, UI rendering, delete operations, navigation, toast notifications, and column definitions. This creates tight coupling and makes changes risky across multiple domains.

const useTeamOperations = () => {
  const queryClient = useQueryClient();
  const [newToast] = useToastHook();
  const [loading, setLoading] = React.useState(false);
  
  const handleDelete = (name: string) => {
    // Move delete logic here
  };
  
  return { handleDelete, loading };
};

const TeamView: React.FC<TeamViewProps> = ({ team, setTeamLength, appState, teamLength, plan }) => {
  const { handleDelete, loading } = useTeamOperations();
  const columns = useTeamColumns(handleDelete);
Commitable Suggestion
Suggested change
const TeamView: React.FC<TeamViewProps> = ({
const useTeamOperations = () => {
const queryClient = useQueryClient();
const [newToast] = useToastHook();
const [loading, setLoading] = React.useState(false);
const handleDelete = (name: string) => {
// Move delete logic here
};
return { handleDelete, loading };
};
const TeamView: React.FC<TeamViewProps> = ({ team, setTeamLength, appState, teamLength, plan }) => {
const { handleDelete, loading } = useTeamOperations();
const columns = useTeamColumns(handleDelete);
Standards
  • SOLID-SRP
  • Clean-Code-Class-Organization
  • Refactoring-Extract-Class

Comment on lines +115 to +118
let count = 0;
info.row.original.subTeams.forEach((item: any) => {
count += item.managers.length;
});
Copy link

Choose a reason for hiding this comment

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

Inconsistent Variable Usage

Logic error in manager count calculation: variable 'count' is calculated but never used in the display logic. The forEach loop accumulates manager counts from subteams but this calculated value doesn't affect the rendered output. This creates misleading code that suggests functionality that doesn't exist.

          const subTeamManagerCount = info.row.original.subTeams.reduce((acc: number, item: any) => acc + item.managers.length, 0);
Commitable Suggestion
Suggested change
let count = 0;
info.row.original.subTeams.forEach((item: any) => {
count += item.managers.length;
});
const subTeamManagerCount = info.row.original.subTeams.reduce((acc: number, item: any) => acc + item.managers.length, 0);
Standards
  • Algorithm-Correctness-Variable-Usage
  • Logic-Verification-Data-Flow
  • Business-Rule-Display-Consistency

Comment on lines +115 to +118
let count = 0;
info.row.original.subTeams.forEach((item: any) => {
count += item.managers.length;
});
Copy link

Choose a reason for hiding this comment

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

N+1 Query Pattern

Nested iteration over subTeams array within cell renderer creates O(n*m) complexity where n is team count and m is average subteams per team. This computation executes on every render causing performance degradation with large team hierarchies. Consider pre-computing manager counts during data transformation.

          const subTeamManagerCount = info.row.original.subTeamManagerCount || 0;
Commitable Suggestion
Suggested change
let count = 0;
info.row.original.subTeams.forEach((item: any) => {
count += item.managers.length;
});
const subTeamManagerCount = info.row.original.subTeamManagerCount || 0;
Standards
  • ISO-IEC-25010-Performance-Efficiency-Time-Behavior
  • Algorithmic-Complexity-Nested-Loop-Optimization

Comment on lines +129 to +133
{info.row.original.subTeams.length > 0 ? (
<Text>+{count} in subteams</Text>
) : (
''
)}
Copy link

Choose a reason for hiding this comment

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

Duplicate Computation Pattern

Duplicate conditional logic for subteams count display creates redundant DOM evaluations and increases render complexity. The same subTeams.length check and count display logic appears twice within the same cell renderer. Consolidate logic to reduce computational overhead and improve maintainability.

Standards
  • ISO-IEC-25010-Performance-Efficiency-Resource-Utilization
  • Optimization-Pattern-Code-Deduplication

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: 4

🧹 Nitpick comments (3)
TeamTable.tsx (3)

57-57: Add proper type definitions for better type safety.

Both rows and columnHelper use any types. Consider creating a Team interface that describes the structure of team data (with properties like name, description, managers, memberCount, subTeams, setupStates) and use it instead of any.

Also applies to: 63-63


160-161: Remove excessive whitespace.

Lines 160-161 contain unnecessary blank lines that should be removed for better code consistency.


280-280: Consider adding handleDelete to dependency array.

The columns useMemo uses handleDelete but doesn't include it in the dependency array. While this works because handleDelete is recreated on every render, it's better practice to either:

  1. Include handleDelete in the dependency array and wrap it with useCallback, or
  2. Move handleDelete inside the useMemo if it's only used in column definitions

This ensures the columns are recreated when handleDelete changes and follows React best practices.

Example of wrapping with useCallback:

const handleDelete = useCallback(async (name: string) => {
  // ... existing implementation
}, [queryClient, newToast]);

Then update the dependency array:

   ],
-  [teamsData?.dto]
+  [teamsData?.dto, handleDelete, colors, navigate, newToast]
 );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e184110 and 1864418.

📒 Files selected for processing (1)
  • TeamTable.tsx (1 hunks)
🔇 Additional comments (3)
TeamTable.tsx (3)

101-111: Good handling of missing data.

The fallback message for missing descriptions provides clear feedback to users.


246-255: Good validation logic for team deletion.

The check to prevent deletion of teams with sub-teams is a good safeguard against data integrity issues. The user feedback via toast is clear and helpful.


317-333: LGTM!

The render logic correctly handles loading states and passes appropriate props to the DataGrid component.

Comment on lines +34 to +40
interface TeamViewProps {
team: string;
setTeamLength: any;
appState?: any;
teamLength?: number;
plan?: 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 | 🟠 Major

Improve type safety for the interface.

Multiple properties are typed as any, which defeats TypeScript's type checking and can lead to runtime errors. Consider defining proper types:

  • setTeamLength should be typed as (length: number) => void
  • appState should have a proper type definition or interface
  • Consider creating a proper type for team data instead of using any in column helper

Apply this diff to improve the setTeamLength type:

 interface TeamViewProps {
   team: string;
-  setTeamLength: any;
+  setTeamLength: (length: number) => void;
   appState?: any;
   teamLength?: number;
   plan?: string;
 }
📝 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
interface TeamViewProps {
team: string;
setTeamLength: any;
appState?: any;
teamLength?: number;
plan?: string;
}
interface TeamViewProps {
team: string;
setTeamLength: (length: number) => void;
appState?: any;
teamLength?: number;
plan?: string;
}
🤖 Prompt for AI Agents
In TeamTable.tsx around lines 34 to 40, the TeamViewProps interface uses several
any types; update setTeamLength to have the signature (length: number) => void,
replace appState: any with a specific interface/type (create AppState or import
existing type) and avoid any in other places by creating a Team type (e.g., Team
or TeamRow) and use it for props and column helpers; update any column helper
usages to reference the Team type so TypeScript can enforce correct field types.

Comment on lines +181 to +219
columnHelper.accessor('setupStates', {
cell: (info) => (
<UnorderedList listStyleType={'none'}>
<ListItem display={'flex'} gap={1}>
{info.getValue().Communication ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Communication
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Dora ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
DORA
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Issue ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Issue
</ListItem>
<ListItem display={'flex'} gap={1}>
{info.getValue().Git ? (
<Image src={Present}></Image>
) : (
<Image src={NotPresent}></Image>
)}
Git
</ListItem>
</UnorderedList>
),
header: 'Config Status',
}),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add alt text to images for accessibility.

The Image components are missing alt attributes, which is an accessibility issue that prevents screen readers from describing the setup status icons to visually impaired users.

Apply this diff to add alt attributes:

           <ListItem display={'flex'} gap={1}>
             {info.getValue().Communication ? (
-              <Image src={Present}></Image>
+              <Image src={Present} alt="Communication setup completed"></Image>
             ) : (
-              <Image src={NotPresent}></Image>
+              <Image src={NotPresent} alt="Communication setup not completed"></Image>
             )}
             Communication
           </ListItem>
           <ListItem display={'flex'} gap={1}>
             {info.getValue().Dora ? (
-              <Image src={Present}></Image>
+              <Image src={Present} alt="DORA setup completed"></Image>
             ) : (
-              <Image src={NotPresent}></Image>
+              <Image src={NotPresent} alt="DORA setup not completed"></Image>
             )}
             DORA
           </ListItem>
           <ListItem display={'flex'} gap={1}>
             {info.getValue().Issue ? (
-              <Image src={Present}></Image>
+              <Image src={Present} alt="Issue setup completed"></Image>
             ) : (
-              <Image src={NotPresent}></Image>
+              <Image src={NotPresent} alt="Issue setup not completed"></Image>
             )}
             Issue
           </ListItem>
           <ListItem display={'flex'} gap={1}>
             {info.getValue().Git ? (
-              <Image src={Present}></Image>
+              <Image src={Present} alt="Git setup completed"></Image>
             ) : (
-              <Image src={NotPresent}></Image>
+              <Image src={NotPresent} alt="Git setup not completed"></Image>
             )}
             Git
           </ListItem>

Additionally, consider refactoring this repetitive code using an array map to reduce duplication:

{['Communication', 'Dora', 'Issue', 'Git'].map((setupType) => (
  <ListItem key={setupType} display={'flex'} gap={1}>
    {info.getValue()[setupType] ? (
      <Image src={Present} alt={`${setupType} setup completed`} />
    ) : (
      <Image src={NotPresent} alt={`${setupType} setup not completed`} />
    )}
    {setupType === 'Dora' ? 'DORA' : setupType}
  </ListItem>
))}
🤖 Prompt for AI Agents
In TeamTable.tsx around lines 181 to 219, the Image components lack alt
attributes which is an accessibility issue; add descriptive alt text to every
<Image> (e.g., "Communication setup completed" / "Communication setup not
completed", etc.) so screen readers can convey status, and to reduce duplication
refactor the repeated ListItem blocks into a single map over an array like
['Communication','Dora','Issue','Git'] that renders each ListItem with the
correct alt text and uses 'DORA' for the display label when the key is 'Dora'.

Comment on lines +258 to +274
<Tooltip label="Add Member" hasArrow>
<IconButton
aria-label="edit"
size="sm"
bg="transparent"
color="primary"
icon={<MdPersonAddAlt size={16} />}
position={'static'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigate(`/settings/teams/${info.row.original?.name}`, {
state: { tab: 1 },
});
}}
/>
</Tooltip>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix incorrect aria-label.

The "Add Member" button has aria-label="edit" which misrepresents its function to screen reader users.

Apply this diff:

           <Tooltip label="Add Member" hasArrow>
             <IconButton
-              aria-label="edit"
+              aria-label="add member"
               size="sm"
               bg="transparent"
               color="primary"
               icon={<MdPersonAddAlt size={16} />}
               position={'static'}
               onClick={(e) => {
                 e.preventDefault();
                 e.stopPropagation();
                 navigate(`/settings/teams/${info.row.original?.name}`, {
                   state: { tab: 1 },
                 });
               }}
             />
           </Tooltip>
📝 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
<Tooltip label="Add Member" hasArrow>
<IconButton
aria-label="edit"
size="sm"
bg="transparent"
color="primary"
icon={<MdPersonAddAlt size={16} />}
position={'static'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigate(`/settings/teams/${info.row.original?.name}`, {
state: { tab: 1 },
});
}}
/>
</Tooltip>
<Tooltip label="Add Member" hasArrow>
<IconButton
aria-label="add member"
size="sm"
bg="transparent"
color="primary"
icon={<MdPersonAddAlt size={16} />}
position={'static'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigate(`/settings/teams/${info.row.original?.name}`, {
state: { tab: 1 },
});
}}
/>
</Tooltip>
🤖 Prompt for AI Agents
In TeamTable.tsx around lines 258 to 274, the IconButton for "Add Member"
incorrectly uses aria-label="edit"; change the aria-label to a descriptive,
accurate value such as "add member" (or "add team member") so screen readers
convey the correct action, and ensure the label is lower-case or title-cased
consistently with the app's accessibility conventions.

Comment on lines +283 to +315
const handleDelete = (name: string) => {
setLoading(true);
apiClient(`/v1/team/${localStorage.getItem('orgId')}/${name}`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `bearer ${localStorage.getItem('token')}`,
},
})
.then((res: any) => {
Promise.all([
queryClient.invalidateQueries([QUERY_KEYS.teams]),
queryClient.invalidateQueries([TEAM_DETAILS_QUERY_KEYS.teamList]),
queryClient.invalidateQueries(['teamMenu']),
]);

newToast({
message: `"${name}" Team has been deleted successfully.`,
status: 'success',
});
})
.catch((err: any) => {
newToast({
message:
err?.message ?? `Some error occurred while deleting ${name}.`,
status: 'error',
});
})
.finally(() => {
setLoading(false);
});
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Await query invalidation to ensure data consistency.

The Promise.all on lines 294-298 invalidates multiple queries but is not awaited, which means the success toast may appear before the UI is fully refreshed. This could lead to temporary inconsistent state where the deleted team still appears briefly.

Apply this diff:

     .then((res: any) => {
-      Promise.all([
+      await Promise.all([
         queryClient.invalidateQueries([QUERY_KEYS.teams]),
         queryClient.invalidateQueries([TEAM_DETAILS_QUERY_KEYS.teamList]),
         queryClient.invalidateQueries(['teamMenu']),
       ]);

       newToast({
         message: `"${name}" Team has been deleted successfully.`,
         status: 'success',
       });
     })

And make the function async:

-  const handleDelete = (name: string) => {
+  const handleDelete = async (name: string) => {

Additionally, consider validating that the token and orgId exist before making the API call to provide better error handling if they're missing.


🏁 Script executed:

#!/bin/bash
# Locate TeamTable.tsx and verify the handleDelete function
fd -t f "TeamTable.tsx" --exec cat {}

Length of output: 9768


🏁 Script executed:

#!/bin/bash
# Search for how queryClient.invalidateQueries is used elsewhere in the codebase
rg -n "invalidateQueries" -A2 -B2 --type=tsx --type=ts

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check for async handleDelete patterns and Promise.all usage with await
rg -n "handleDelete|Promise\.all" -A3 -B1 --type=tsx --type=ts | head -100

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for queryClient and invalidateQueries patterns without type flags
rg -n "invalidateQueries" -A2 -B2 | head -50

Length of output: 381


🏁 Script executed:

#!/bin/bash
# Search for other promise patterns and async handlers in the codebase
rg -n "\.then\(" -A3 -B1 | head -80

Length of output: 284


🏁 Script executed:

#!/bin/bash
# Check React Query package to understand invalidateQueries behavior
find . -name "package*.json" -type f -exec grep -l "react-query\|@tanstack/react-query" {} \;

Length of output: 0


🌐 Web query:

React Query invalidateQueries return type promise behavior

💡 Result:

Short answer: invalidateQueries returns a Promise — it resolves when the (background) refetches it triggered are finished (or immediately if no refetch needed). So you can await it (e.g. return await queryClient.invalidateQueries(...) from an onSuccess) to keep a mutation pending until invalidation/refetch completes. [1][2][3]

Notes:

  • You can control whether active/inactive queries refetch with options (refetchActive/refetchInactive) and whether the Promise throws on refetch errors (throwOnError). [2]
  • Common pattern: await Promise.all([...invalidateQueries(...)]) when invalidating multiple keys. [1][3]

Sources:
[1] TanStack Query — Invalidations from Mutations (example using await).
[2] TanStack Query — QueryClient reference (invalidateQueries/refetch and Promise behavior/options).
[3] tkdodo / dev.to — explanation and examples showing invalidateQueries returns a Promise.


Await query invalidation to ensure data consistency.

The invalidateQueries calls return Promises that resolve when background refetches complete. Currently, Promise.all on lines 294-298 is not awaited, allowing the success toast to display before queries are actually invalidated and refetched. This creates a race condition where the deleted team may briefly reappear in the UI.

Apply this diff:

     .then((res: any) => {
-      Promise.all([
+      await Promise.all([
         queryClient.invalidateQueries([QUERY_KEYS.teams]),
         queryClient.invalidateQueries([TEAM_DETAILS_QUERY_KEYS.teamList]),
         queryClient.invalidateQueries(['teamMenu']),
       ]);

       newToast({
         message: `"${name}" Team has been deleted successfully.`,
         status: 'success',
       });
     })

And make the function async:

-  const handleDelete = (name: string) => {
+  const handleDelete = async (name: string) => {

Additionally, validate that token and orgId exist before making the API call to catch missing credentials early.

📝 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 handleDelete = (name: string) => {
setLoading(true);
apiClient(`/v1/team/${localStorage.getItem('orgId')}/${name}`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `bearer ${localStorage.getItem('token')}`,
},
})
.then((res: any) => {
Promise.all([
queryClient.invalidateQueries([QUERY_KEYS.teams]),
queryClient.invalidateQueries([TEAM_DETAILS_QUERY_KEYS.teamList]),
queryClient.invalidateQueries(['teamMenu']),
]);
newToast({
message: `"${name}" Team has been deleted successfully.`,
status: 'success',
});
})
.catch((err: any) => {
newToast({
message:
err?.message ?? `Some error occurred while deleting ${name}.`,
status: 'error',
});
})
.finally(() => {
setLoading(false);
});
};
const handleDelete = async (name: string) => {
setLoading(true);
apiClient(`/v1/team/${localStorage.getItem('orgId')}/${name}`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `bearer ${localStorage.getItem('token')}`,
},
})
.then((res: any) => {
await Promise.all([
queryClient.invalidateQueries([QUERY_KEYS.teams]),
queryClient.invalidateQueries([TEAM_DETAILS_QUERY_KEYS.teamList]),
queryClient.invalidateQueries(['teamMenu']),
]);
newToast({
message: `"${name}" Team has been deleted successfully.`,
status: 'success',
});
})
.catch((err: any) => {
newToast({
message:
err?.message ?? `Some error occurred while deleting ${name}.`,
status: 'error',
});
})
.finally(() => {
setLoading(false);
});
};
🤖 Prompt for AI Agents
In TeamTable.tsx around lines 283 to 315, the delete handler should be made
async and must validate token and orgId from localStorage before calling the
API; if either is missing, return early with an error toast and
setLoading(false). Replace the current promise chain with an async/await flow:
await the apiClient DELETE call inside try, then await Promise.all of the three
queryClient.invalidateQueries calls so invalidations/refetches finish before
showing the success toast; handle errors in catch (show error toast) and always
setLoading(false) in finally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants