Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BA-1777: multiple profiles member list #135

Merged
merged 15 commits into from
Dec 4, 2024

Conversation

hpmoreira05
Copy link
Contributor

@hpmoreira05 hpmoreira05 commented Nov 13, 2024

As a user, on the BaseApp Profile,I would like to I want to have a page where I can see the members of a profile, In order to be able to manage the members.

Original Story:
Acceptance Criteria
Context
This story will be about creating the option to access the members page, creating the structure of the members page and also creating the members modal and the relationship with the profiles

Business Rules - Members List - 5

Given a user has navigated to the Main Profile Settings Page for any profile, then they should see the "Members" option in the side menu.

After clicking the Members option, the user should be able to see a list of the members for the active profile with the following properties

Show a count of the total members (active and pending invite)

Pending invite members should be displayed in a light gray font with a pending status.

The members list will have its scrolling behavior inside its component, this means the page will not scroll if you do it inside the member list component.

Current behavior

Expected behavior

Code Snippet

Design Link: https://www.figma.com/design/XRD6wSl1m8Kz6XUcAy5CLp/BaseApp---WEB?node-id=3660-64822&node-type=frame&t=MBvMfObBLgXLqFQv-0

Template: https://bitbucket.org/silverlogic/baseapp-frontend-template/pull-requests/113
BA BE Package: silverlogic/baseapp-backend#182

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced MemberItem component for displaying member information.
    • Added MembersList component for paginated member display.
    • Implemented MemberListItem component for rendering individual member details in a list.
    • New GraphQL queries and fragments for user members and member items.
  • Enhancements

    • Styled components for improved layout and visual representation of member items.
    • TypeScript interfaces added for better type safety and integration.
  • Constants

    • Added constants for member loading configurations and status representations.
  • Utility

    • New utility function to capitalize the first letter of strings.
  • Documentation

    • Updated schema with new types and structures for enhanced functionality.
  • Version Update

    • Updated package version from 0.0.22 to 0.0.23.

Copy link

changeset-bot bot commented Nov 13, 2024

⚠️ No Changeset found

Latest commit: 15a17cb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

coderabbitai bot commented Nov 13, 2024

Walkthrough

The changes in this pull request introduce a new member management system within a React application. Key components include MemberItem, MembersList, and MemberListItem, which display member information and support pagination. New styled components enhance the UI, while TypeScript interfaces improve type safety. GraphQL queries and fragments are added to manage data fetching and structure. Additionally, constants for member loading and statuses are defined, and the package.json file is updated to specify dependency versions, ensuring a more predictable development environment.

Changes

File Path Change Summary
packages/components/modules/profiles/Members/MemberItem/index.tsx New React component MemberItem created to display member information.
packages/components/modules/profiles/Members/MemberItem/styled.tsx New styled components MemberItemContainer and MemberPersonalInformation defined.
packages/components/modules/profiles/Members/MemberItem/types.ts New TypeScript interfaces MemberPersonalInformationProps and MemberItemProps added.
packages/components/modules/profiles/Members/MembersList/index.tsx New component MembersList introduced for displaying a paginated list of members.
packages/components/modules/profiles/Members/constants.ts New constants NUMBER_OF_MEMBERS_TO_LOAD_NEXT, NUMBER_OF_MEMBERS_ON_FIRST_LOAD, and enum MemberStatuses added.
packages/components/modules/profiles/Members/index.tsx New exports for Members and MembersSuspended components created.
packages/components/modules/profiles/Members/types.ts New interfaces MemberListProps, UserMembersSuspendedProps, and UserMembersProps added.
packages/components/modules/profiles/Members/utils.ts New utility function capitalizeFirstLetter added.
packages/components/modules/profiles/graphql/queries/UserMembersList.ts New GraphQL query UserMembersListPaginationQuery and fragment UserMembersListFragment added.
packages/components/modules/profiles/MemberListItem/index.tsx New component MemberListItem created to render member information within a list.
packages/components/modules/profiles/MemberListItem/types.ts New TypeScript interface MemberListItemProps defined.
packages/components/modules/profiles/graphql/queries/MemberItem.ts New GraphQL fragment MemberItemFragment added for member details.
packages/components/package.json Updated dependencies and devDependencies to specific versions for better control.
packages/components/schema.graphql Significant updates to the GraphQL schema, including new types, interfaces, and modifications to existing types.
packages/design-system/components/inputs/TextField/index.tsx Simplified logic for determining isMobile in the TextField component.

Possibly related PRs

  • BA-1831: Action overlay #141: The changes in this PR involve the creation of an overlay for chat cards, which is conceptually similar to the MemberItem component's functionality of displaying member information. Both involve enhancing user interaction through overlays, although they target different components (chat cards vs. member items).

Suggested reviewers

  • Ronan-Fernandes
  • anicioalexandre

Poem

In the meadow where members play,
A new list blooms, bright as the day.
With roles and statuses, all in line,
Each little detail, simply divine.
So hop along, let’s cheer and sing,
For the joy that new changes bring! 🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 eslint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/components/modules/profiles/Members/MemberListItem/index.tsx

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: Cannot read config file: /packages/components/.eslintrc.js
Error: Cannot find module '@baseapp-frontend/config/.eslintrc.js'
Require stack:

  • /packages/components/.eslintrc.js
  • /node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/cli-engine.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/eslint/eslint.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/eslint/index.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/bin/eslint.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1248:15)
    at Module._load (node:internal/modules/cjs/loader:1074:27)
    at TracingChannel.traceSync (node:diagnostics_channel:315:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
    at Module.require (node:internal/modules/cjs/loader:1339:12)
    at require (node:internal/modules/helpers:135:16)
    at Object. (/packages/components/.eslintrc.js:1:18)
    at Module._compile (node:internal/modules/cjs/loader:1546:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1691:10)
    at Module.load (node:internal/modules/cjs/loader:1317:32)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 Outside diff range and nitpick comments (17)
packages/components/modules/profiles/Members/utils.ts (1)

1-2: LGTM! Consider adding input validation for robustness.

The implementation correctly capitalizes the first letter while converting the rest to lowercase. However, consider adding input validation for more robust error handling.

Consider this enhanced version:

-export const capitalizeFirstLetter = (string: string) =>
-  string.charAt(0).toUpperCase() + string.slice(1).toLowerCase()
+export const capitalizeFirstLetter = (text: string): string => {
+  if (!text) return '';
+  return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
+}

The improvements include:

  • More descriptive parameter name
  • Explicit return type
  • Null/empty string handling
  • Block syntax for better readability
packages/components/modules/profiles/Members/constants.ts (2)

1-2: Add documentation for pagination constants

Consider adding JSDoc comments to explain the rationale behind these pagination values and their usage in the member list.

+/** Number of additional members to load when clicking "Load More" */
 export const NUMBER_OF_MEMBERS_TO_LOAD_NEXT = 5
+/** Initial number of members to display when the list first loads */
 export const NUMBER_OF_MEMBERS_ON_FIRST_LOAD = 10

3-7: LGTM! Consider adding enum documentation

The enum values are well-structured and follow consistent naming conventions. The inclusion of an 'inactive' status shows good forward thinking.

Consider adding JSDoc documentation to explain the enum's purpose and usage:

+/**
+ * Represents the possible states of a profile member
+ * - ACTIVE: Member has accepted the invitation and has full access
+ * - PENDING: Member has been invited but hasn't accepted yet
+ * - INACTIVE: Member's access has been revoked or they've left
+ */
 export enum MemberStatuses {
   active = 'ACTIVE',
   pending = 'PENDING',
   inactive = 'INACTIVE',
 }
packages/components/modules/profiles/Members/MemberItem/types.ts (2)

9-11: Consider removing the Hungarian notation prefix.

The interface name uses the 'I' prefix (Hungarian notation), which is not a common convention in modern TypeScript. Consider renaming IMemberPersonalInformation to MemberPersonalInformation to align with contemporary TypeScript naming conventions.

-export interface IMemberPersonalInformation extends BoxProps {
+export interface MemberPersonalInformation extends BoxProps {
   isActive: boolean
 }

1-20: Consider type composition for better reusability.

The types are well-structured, but consider extracting common member-related types (like the role and status) into a shared types file if they're used across multiple components. This would improve maintainability and reusability.

packages/components/modules/profiles/Members/MemberItem/styled.tsx (1)

5-11: Consider adding semantic HTML and accessibility attributes.

While the layout implementation is clean, consider enhancing accessibility by:

  1. Using semantic HTML through the component prop
  2. Adding appropriate ARIA attributes
 export const MemberItemContainer = styled(Box)(({ theme }) => ({
+  // Forward component prop to render as a semantic list item
+  component: 'li',
   display: 'flex',
   gap: theme.spacing(1.5),
   alignItems: 'center',
   justifyContent: 'space-between',
   padding: theme.spacing(1.5, 0),
+  // Add role for accessibility
+  role: 'listitem',
 }))
packages/design-system/components/inputs/TextField/index.tsx (1)

12-12: Nice optimization of the media query implementation!

The direct usage of the theme callback in useMediaQuery is more efficient than separate useTheme + useMediaQuery calls while maintaining the same behavior.

Consider adding a brief comment explaining the breakpoint behavior for future maintainers:

+ // Switches to small size on viewports smaller than 'sm' breakpoint (600px by default)
const isMobile = useMediaQuery<Theme>((theme) => theme.breakpoints.down('sm'))
packages/components/modules/profiles/Members/types.ts (2)

8-14: Add JSDoc documentation and consider prop refinements.

While the interface is well-structured, consider these improvements:

  1. Add JSDoc documentation to describe the purpose and usage of each prop
  2. Consider adding validation for membersContainerHeight (e.g., minimum height)
  3. The LoadingStateProps seems redundant since LoadingState already uses these props

Consider this improvement:

+/**
+ * Props for the MemberList component
+ * @property MemberItem - Component to render individual member items
+ * @property meRef - GraphQL fragment reference for user data
+ * @property LoadingState - Component to render loading state
+ * @property LoadingStateProps - Props for loading state
+ * @property membersContainerHeight - Optional height for the members container (in pixels)
+ */
 export interface MemberListProps {
   MemberItem: FC<MemberItemProps>
   meRef: UserMembersListFragment$key
   LoadingState: FC<LoadingStateProps>
-  LoadingStateProps: LoadingStateProps
+  LoadingStateProps: LoadingStateProps
+  membersContainerHeight?: number & { __brand: 'positive' }  // Type refinement for positive numbers
-  membersContainerHeight?: number
 }

26-26: LGTM! Consider adding documentation.

Good use of TypeScript's utility types to create a variant of MemberListProps without the GraphQL reference.

Add documentation to clarify the purpose:

+/** Props for UserMembers component, excluding GraphQL reference */
 export interface UserMembersProps extends Omit<MemberListProps, 'meRef'> {}
packages/components/modules/profiles/graphql/queries/UserMembersList.ts (1)

16-17: Consider implementing cursor caching for better UX

To optimize the pagination experience:

  1. Consider implementing cursor caching to maintain list position during navigation
  2. Monitor the default page size of 10 in production - adjust based on actual usage patterns
  3. Implement virtual scrolling if the total member count tends to be large

This will help maintain smooth performance as the member list grows.

Also applies to: 22-24

packages/components/modules/profiles/Members/MemberItem/index.tsx (2)

15-22: Consider adding prop validation

The props structure looks good, but consider adding runtime validation for critical props like member and status. This would help catch issues earlier in development.

+import PropTypes from 'prop-types'

 const MemberItem: FC<MemberItemProps> = ({
   member,
   memberRole,
   status,
   avatarProps = {},
   avatarWidth = 40,
   avatarHeight = 40,
 }) => {
   // ... component implementation
 }

+MemberItem.propTypes = {
+  member: PropTypes.object.isRequired,
+  memberRole: PropTypes.string.isRequired,
+  status: PropTypes.oneOf(Object.values(MemberStatuses)).isRequired,
+  avatarProps: PropTypes.object,
+  avatarWidth: PropTypes.number,
+  avatarHeight: PropTypes.number,
+}

23-24: Consider adding error boundary or fallback UI

The null check is good, but consider adding a fallback UI or error boundary to handle cases where member data might be invalid or incomplete.

-if (!member) return null
+if (!member) return <Typography color="error">Invalid member data</Typography>
packages/components/modules/profiles/Members/index.tsx (2)

16-26: Consider using a more predictable key pattern.

While using the array index as a key is acceptable for static lists, consider using a more predictable pattern for better React reconciliation.

-      key={index} // eslint-disable-line react/no-array-index-key
+      key={`member-skeleton-${index}`} // More descriptive and still deterministic

66-73: Consider adding an error boundary.

Since this component uses Suspense for loading states, consider wrapping it with an error boundary to handle potential rendering errors gracefully.

// In a separate ErrorBoundary component
class MembersErrorBoundary extends React.Component<{children: React.ReactNode}> {
  // Implementation of error boundary
}

// Usage in this component
<MembersErrorBoundary>
  <Suspense fallback={<InitialLoadingState />}>
    <Members {...props} />
  </Suspense>
</MembersErrorBoundary>
packages/components/modules/profiles/Members/MembersList/index.tsx (2)

14-24: Enhance type safety for component props.

Consider improving type safety by:

  1. Explicitly defining the type for meRef instead of implicitly typing it
  2. Creating an interface for LoadingStateProps to better type the accepted properties
-  LoadingStateProps = {},
+  LoadingStateProps: Partial<DefaultLoadingState['props']> = {},

84-88: Clarify member count calculation.

The addition of 1 to the total count (for the owner) could be more explicit:

- {(data.profile?.members?.totalCount ?? 0) + 1} members
+ const totalMembers = (data.profile?.members?.totalCount ?? 0) + 1; // Including owner
+ {totalMembers} {totalMembers === 1 ? 'member' : 'members'}
packages/components/package.json (1)

24-109: Consider using caret ranges consistently

While most dependencies use caret ranges (^), some don't. Consider using them consistently across all dependencies for better maintenance, particularly for:

  • react and react-dom peer dependencies
  • dev dependencies like css-loader
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 08bfce4 and 970fff1.

⛔ Files ignored due to path filters (4)
  • packages/components/__generated__/UserMembersListFragment.graphql.ts is excluded by !**/__generated__/**
  • packages/components/__generated__/UserMembersListPaginationQuery.graphql.ts is excluded by !**/__generated__/**
  • packages/components/__generated__/userMembersListPaginationRefetchable.graphql.ts is excluded by !**/__generated__/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • packages/components/modules/profiles/Members/MemberItem/index.tsx (1 hunks)
  • packages/components/modules/profiles/Members/MemberItem/styled.tsx (1 hunks)
  • packages/components/modules/profiles/Members/MemberItem/types.ts (1 hunks)
  • packages/components/modules/profiles/Members/MembersList/index.tsx (1 hunks)
  • packages/components/modules/profiles/Members/constants.ts (1 hunks)
  • packages/components/modules/profiles/Members/index.tsx (1 hunks)
  • packages/components/modules/profiles/Members/styled.tsx (1 hunks)
  • packages/components/modules/profiles/Members/types.ts (1 hunks)
  • packages/components/modules/profiles/Members/utils.ts (1 hunks)
  • packages/components/modules/profiles/graphql/queries/UserMembersList.ts (1 hunks)
  • packages/components/modules/profiles/index.ts (1 hunks)
  • packages/components/package.json (1 hunks)
  • packages/components/schema.graphql (57 hunks)
  • packages/design-system/components/inputs/TextField/index.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/components/schema.graphql
🔇 Additional comments (21)
packages/components/modules/profiles/index.ts (1)

6-6: LGTM! Export follows consistent patterns.

The export statement is well-placed and follows the established pattern for component exports in this module.

Let's verify the Members component exists at the specified path:

✅ Verification successful

✅ Export statement is correctly implemented

The verification confirms that:

  • The Members component exists at packages/components/modules/profiles/Members/index.tsx
  • It properly exports a default export as MembersSuspended
  • The export statement in the barrel file correctly references this component
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the Members component exists and exports correctly

# Check if the Members component file exists
fd --type f "^index\.(tsx?|jsx?)$" --parent-directory "Members" packages/components/modules/profiles

# Verify the component exports a default export
ast-grep --pattern 'export default $_' packages/components/modules/profiles/Members/index.ts*

Length of output: 573


Script:

#!/bin/bash
# Let's verify the Members component existence and structure
# First, check if the file exists
fd "^Members/index\.tsx?$" packages/components/modules/profiles

# Then examine its content to understand the component structure
rg -A 5 "^export default" packages/components/modules/profiles/Members/index.tsx

Length of output: 180

packages/components/modules/profiles/Members/styled.tsx (2)

1-1: LGTM! Clean import statement.

The imports are minimal and correctly sourced from Material-UI.


3-7: LGTM! Well-structured skeleton component.

The MemberItemSkeleton component follows Material-UI best practices:

  • Uses theme spacing for consistent border radius
  • Appropriate height for a list item
  • Responsive width handling

Let's verify the consistency of skeleton usage across member-related components:

✅ Verification successful

Let me gather more information about the member-related components and their skeleton implementations.


Skeleton implementation is consistent across components

The verification shows that both MemberItemSkeleton and ProfileMenuItemSkeleton follow identical implementation patterns:

  • Same height (52px)
  • Same responsive width (100%)
  • Same theme-based border radius (theme.spacing(0.75))

This consistency in skeleton styling maintains a uniform loading state appearance throughout the application.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent skeleton implementation across member components
# Expected: Find related member components and their loading states

# Search for member-related components
rg -t tsx "MemberItem|MemberList" --glob "packages/components/modules/profiles/**"

# Check for other skeleton implementations
ast-grep --pattern 'styled(Skeleton)' 

Length of output: 444


Script:

#!/bin/bash
# Search for member-related components with proper file extension
rg -t typescript -t javascriptreact -t typescriptreact "MemberItem|MemberList" --glob "packages/components/modules/profiles/**" -A 5

# Check the implementation of ProfileMenuItemSkeleton for consistency
cat packages/components/modules/profiles/ProfilePopover/ProfilesList/LoadingState/styled.tsx

Length of output: 460

packages/components/modules/profiles/Members/MemberItem/types.ts (2)

1-7: LGTM! Well-organized imports with proper type safety.

The imports are properly structured, utilizing both Material-UI types and generated GraphQL fragment types for type safety.


13-20: Verify null handling and document special cases.

The interface looks well-structured, but there are a few points to consider:

  1. The member prop allows null/undefined values. Ensure proper null checks are implemented in the component.
  2. The special case of 'owner' in memberRole should be documented for clarity.
✅ Verification successful

Null handling is properly implemented

The implementation in MemberItem/index.tsx and its usage in MembersList/index.tsx show proper null handling:

  1. MemberItem component has an explicit null check: if (!member) return null
  2. The parent MembersList component safely accesses nested properties using optional chaining: member?.user?.profile
  3. The 'owner' role case is used consistently in the codebase for the profile owner
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify null handling in MemberItem component implementation
# Expected: Find null checks for the member prop

# Search for the component implementation
ast-grep --pattern 'const MemberItem = ({ member, memberRole }: MemberItemProps) => {
  $$$
}'

# Search for null checks
rg -A 5 'member(\s+)===(\s+)null|member(\s+)\?|member(\s+)&&' 

Length of output: 160


Script:

#!/bin/bash
# Let's try a broader search for the MemberItem component implementation
fd "MemberItem" --type f --exec cat {}

# Also search for any usage of the member prop with different patterns
rg "member[^A-Za-z]" -A 5

Length of output: 8931

packages/components/modules/profiles/Members/MemberItem/styled.tsx (2)

1-3: LGTM! Clean and focused imports.

The imports are well-organized and specific to the needs of the file.


13-21: Enhance visual feedback and accessibility.

The current implementation has two potential improvements:

  1. The opacity value of 0.6 might not provide sufficient contrast for accessibility. Consider using a higher value or a different visual treatment.
  2. Adding a transition effect would make the opacity change smoother.
 export const MemberPersonalInformation = styled(Box, {
   shouldForwardProp: (prop) => prop !== 'isActive',
 })<IMemberPersonalInformation>(({ isActive, theme }) => ({
-  opacity: isActive ? 1 : 0.6,
+  opacity: isActive ? 1 : 0.75, // Increased for better contrast
+  transition: theme.transitions.create('opacity'), // Smooth transition
   display: 'flex',
   gap: theme.spacing(1.5),
   alignItems: 'center',
   justifyContent: 'space-between',
 }))
packages/components/modules/profiles/Members/types.ts (1)

1-6: LGTM! Well-organized imports with clear separation of concerns.

The imports are properly structured, separating React types, design system components, and local/generated types.

packages/components/modules/profiles/graphql/queries/UserMembersList.ts (2)

22-41: LGTM! Well-implemented pagination setup

The pagination implementation follows Relay best practices:

  • Proper use of @connection directive with a unique key
  • Includes necessary pageInfo fields
  • Correct cursor-based pagination structure
  • Appropriate filtering setup

3-10: Verify valid values for orderByStatus parameter

The query accepts an orderByStatus parameter, but we should verify the allowed values to prevent runtime errors.

✅ Verification successful

The orderByStatus parameter is correctly implemented

The verification shows that:

  • The orderByStatus parameter is properly defined in the schema as a String type
  • It has a default value of "custom" in the fragment
  • It's used consistently across the codebase for member list ordering
  • The parameter is properly connected to Relay's @connection directive with appropriate filters

The implementation follows GraphQL and Relay best practices, and no issues were found with the parameter usage.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for GraphQL schema definitions or usage of orderByStatus
rg -t graphql "enum.*Status|orderByStatus.*=|orderByStatus.*:.*String" -A 5

Length of output: 109


Script:

#!/bin/bash
# Search for GraphQL schema definitions or usage of orderByStatus in all file types
rg "enum.*Status|orderByStatus.*=|orderByStatus.*:.*String" -A 5

# Also search for any GraphQL schema files
fd "\.(graphql|gql)$"

Length of output: 6257

packages/components/modules/profiles/Members/MemberItem/index.tsx (2)

53-53: LGTM!

The component export follows React conventions.


1-14: Consider organizing imports for better maintainability

Consider organizing imports into the following groups:

  1. React/External libraries
  2. Material-UI components
  3. Internal components/utilities
  4. Types/constants
+// External libraries
 import { FC } from 'react'
+import { readInlineData } from 'react-relay'
 
+// Design system
 import { AvatarWithPlaceholder } from '@baseapp-frontend/design-system'
 
+// Material UI
 import { Box, Button, Typography } from '@mui/material'
-import { readInlineData } from 'react-relay'
 
+// Types and fragments
 import { ProfileItemFragment$key } from '../../../../__generated__/ProfileItemFragment.graphql'
 import { ProfileItemFragment } from '../../graphql/queries/ProfileItem'
+
+// Constants and utilities
 import { MemberStatuses } from '../constants'
 import { capitalizeFirstLetter } from '../utils'
 import { MemberItemContainer, MemberPersonalInformation } from './styled'
 import { MemberItemProps } from './types'

Let's verify the relative import paths are correct:

✅ Verification successful

Based on the search results, I can now generate the final response:

Import paths are correctly structured and files exist

The verification confirms that all imported files exist in the codebase:

  • ProfileItemFragment.graphql.ts exists at packages/components/__generated__/ProfileItemFragment.graphql.ts
  • styled.tsx exists at packages/components/modules/profiles/Members/MemberItem/styled.tsx
  • types.ts exists at packages/components/modules/profiles/Members/MemberItem/types.ts
  • The relative paths in the imports correctly reflect the project structure

While the suggested import organization would improve readability, all current imports are valid and functional.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify import paths exist
# Expected: All imported files should exist

# Check for ProfileItemFragment
fd -e ts -e tsx "ProfileItemFragment.graphql.ts" 

# Check for styled components
fd -e ts -e tsx "styled.tsx" --full-path ".*profiles/Members/MemberItem/"

# Check for types
fd -e ts -e tsx "types.ts" --full-path ".*profiles/Members/MemberItem/"

Length of output: 495


Script:

#!/bin/bash
# Let's try a different approach to verify the imports

# Check for all files in the profiles/Members directory
fd -e ts -e tsx . --full-path ".*/profiles/Members"

# Search for styled components file
fd "styled.tsx"

# Search for types file
fd "types.ts"

# Verify the graphql query path
fd "ProfileItem" --type f

# Check the actual content structure
find . -type d | grep -i "profiles"

Length of output: 11122

packages/components/modules/profiles/Members/index.tsx (3)

1-14: LGTM! Well-organized imports following best practices.

The imports are properly structured with clear separation between external and internal dependencies. The use of specific imports from React and proper typing from GraphQL generated files is commendable.


50-75: Verify the default height against design specifications.

The default membersContainerHeight of 400px should be verified against the design specifications mentioned in the PR objectives. Additionally, consider extracting this value to a constants file for better maintainability.

+ // In constants.ts
+ export const DEFAULT_MEMBERS_CONTAINER_HEIGHT = 400
+
 // In this file
- membersContainerHeight = 400,
+ membersContainerHeight = DEFAULT_MEMBERS_CONTAINER_HEIGHT,

34-37: Verify the 'custom' order implementation.

The orderByStatus: 'custom' parameter suggests a specific ordering implementation. Please ensure this matches the backend implementation.

✅ Verification successful

Let me search for more context about the ordering implementation.


Let me search for more specific implementation details about the member ordering.


Let me try again with correct file type flags.


Based on the gathered information, I can now provide a final response:

The 'custom' ordering parameter is properly implemented

The orderByStatus: 'custom' parameter is correctly used in the GraphQL query and is properly integrated with Relay's pagination system. The implementation shows:

  1. It's a default value in the GraphQL schema
  2. It's used consistently in the member listing query
  3. It's properly handled by Relay's connection configuration with appropriate filters

The parameter is used alongside standard pagination parameters (count, cursor) and is properly typed in the generated GraphQL types.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the implementation of custom ordering
ast-grep --pattern 'enum $_OrderByStatus {
  $$$
  custom
  $$$
}'

# Search for references to custom ordering
rg -A 5 "orderByStatus.*custom" 

Length of output: 2775


Script:

#!/bin/bash
# Search for enum definitions related to member status or ordering
ast-grep --pattern 'enum $_ {
  $$$
}'

# Search for any member status or ordering related types/interfaces
rg -A 5 "type.*Member|interface.*Member|enum.*Member"

# Look for any schema definitions that might contain the ordering logic
fd "schema.ts|schema.graphql" --exec cat {}

Length of output: 44716


Script:

#!/bin/bash
# Search for member ordering related code
rg -A 5 "orderByStatus" --type ts --type tsx

# Look for any constants or configurations related to member ordering
rg -A 5 "NUMBER_OF_MEMBERS_ON_FIRST_LOAD|custom.*order" --type ts --type tsx

Length of output: 179


Script:

#!/bin/bash
# Search for member ordering related code
rg -A 5 "orderByStatus"

# Look for any constants or configurations related to member ordering
rg -A 5 "NUMBER_OF_MEMBERS_ON_FIRST_LOAD"

# Search for any member-related constants
fd "constants.ts" --exec cat {}

Length of output: 20206

packages/components/modules/profiles/Members/MembersList/index.tsx (3)

1-13: LGTM! Well-organized imports.

The imports are properly organized with clear separation between external and internal dependencies.


89-102: LGTM! Well-implemented virtualization.

The Virtuoso configuration is properly set up with:

  • Appropriate overscan for smooth scrolling
  • Efficient endReached callback
  • Good use of virtualization for performance

1-107: Verify handling of pending invitations.

The implementation aligns well with the PR objectives, but let's verify the handling of pending invitations:

✅ Verification successful

Pending invitations are properly handled

The implementation correctly handles pending invitations:

  • MemberStatuses enum includes pending = 'PENDING' status
  • MemberItem component displays pending status by capitalizing it in a button when status !== MemberStatuses.active
  • The main list component properly renders all members regardless of their status, with special handling for active members (showing divider)
  • The virtualized list ensures efficient rendering of both active and pending members

The code meets the requirements for handling pending invitations without any issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for pending invitation handling in the codebase
rg -l "MemberStatuses.pending" --type ts

Length of output: 40


Script:

#!/bin/bash
# Let's first check the MemberStatuses enum definition
rg -A 5 "export enum MemberStatuses" --type ts

# Also check for any other status-related handling
rg -A 5 "status.*=.*pending" --type ts
rg -A 5 "status.*===.*pending" --type ts

# Let's see the constants file mentioned in the import
cat "packages/components/modules/profiles/Members/constants.ts"

Length of output: 784


Script:

#!/bin/bash
# Let's check how MemberItem handles different statuses
ast-grep --pattern 'interface $_ {
  $$$
  status: $_
  $$$
}'

# Also check MemberItem implementation
fd "MemberItem" --type f --exec cat {}

Length of output: 879


Script:

#!/bin/bash
# Let's check the actual implementation of MemberItem component
cat packages/components/modules/profiles/Members/MemberItem/index.tsx

# And check if there are any specific UI elements for pending status
rg -A 5 "pending" packages/components/modules/profiles/Members/MemberItem/

Length of output: 1870

packages/components/package.json (3)

47-48: Verify React 18.3.1 compatibility

The update to React 18.3.1 is significant. Ensure all components are compatible with this version, particularly:

  • Concurrent features usage
  • Strict mode compliance
  • Effect cleanup implementations
#!/bin/bash
# Description: Check for React compatibility issues

# Search for potential compatibility issues
echo "Checking for legacy lifecycle methods..."
rg "componentWillMount|componentWillReceiveProps|componentWillUpdate"

echo "Checking for strict mode issues..."
rg "findDOMNode|UNSAFE_"

echo "Checking effect cleanup patterns..."
ast-grep --pattern 'useEffect(() => { $$$ }, $$$)'

51-109: Verify testing infrastructure compatibility

The comprehensive update of testing dependencies requires verification:

  • Jest 29.7.0 configuration compatibility
  • Cypress 13.x integration
  • Storybook 8.x setup
#!/bin/bash
# Description: Verify testing configuration compatibility

# Check Jest configuration
echo "Checking Jest configuration..."
rg "jest.config"

# Check Cypress configuration
echo "Checking Cypress configuration..."
rg "cypress.config"

# Check Storybook configuration
echo "Checking Storybook configuration..."
rg "\.storybook.*\.(js|ts)"

24-40: Verify compatibility of major version updates

Several critical dependencies have significant version updates that warrant careful verification:

  • @tanstack/react-query v5: Major version update that may have breaking changes
  • @mui/lab and @mui/material: Ensure UI components remain consistent
  • Latest versions of form handling libraries (react-hook-form, zod)
✅ Verification successful

Dependencies update appears safe to proceed

Based on the codebase analysis:

  1. React Query usage is primarily in authentication flows and follows standard patterns (useQuery/useMutation). The v5 upgrade should be manageable as the usage is well-contained.

  2. MUI components are extensively used but mainly for basic components (Button, TextField, Dialog, etc.) which are stable across versions. The @mui/lab usage is minimal (only 2 files).

  3. Form handling libraries (react-hook-form and zod) are used consistently with resolvers pattern, and the version updates are minor/patch.

Recommendations:

  • Run the test suite after the update
  • Pay special attention to authentication flows during QA
  • Review MUI component styling, especially in the design-system package
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for breaking changes in major dependency updates

# Check changelogs for breaking changes
echo "Checking @tanstack/react-query changelog for breaking changes..."
curl -s "https://raw.githubusercontent.com/TanStack/query/main/CHANGELOG.md" | head -n 50

echo "Checking MUI changelog for breaking changes..."
curl -s "https://raw.githubusercontent.com/mui/material-ui/master/CHANGELOG.md" | head -n 50

# Search for existing query usage patterns that might be affected
echo "Checking usage of react-query hooks..."
rg -A 2 "useQuery|useMutation|useInfiniteQuery"

Length of output: 3003


Script:

#!/bin/bash
# Let's check for actual usage of react-query and MUI components to assess impact

# Check for react-query usage patterns
echo "Checking react-query usage patterns..."
rg -l "from '@tanstack/react-query'" 

# Check for specific react-query hooks usage
echo -e "\nChecking specific query hooks..."
rg -A 2 "useQuery\(|useMutation\(|useInfiniteQuery\("

# Check MUI component usage
echo -e "\nChecking MUI component usage..."
rg -l "from '@mui/material'"
rg -l "from '@mui/lab'"

# Check for specific form handling patterns
echo -e "\nChecking form handling patterns..."
rg -l "from 'react-hook-form'"
rg -l "from '@hookform/resolvers/zod'"

Length of output: 17819

Comment on lines +16 to +24
export interface UserMembersSuspendedProps {
MemberItem?: FC<MemberItemProps>
LoadingState?: FC<LoadingStateProps>
LoadingStateProps?: LoadingStateProps
title?: string
subtitle?: string
InitialLoadingState?: FC
membersContainerHeight?: number
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety for InitialLoadingState and string props.

The interface could benefit from stronger typing:

  1. InitialLoadingState should specify its props type
  2. String props could benefit from more specific types or validation

Consider these improvements:

+/** Props for the suspended state of UserMembers component */
 export interface UserMembersSuspendedProps {
   MemberItem?: FC<MemberItemProps>
   LoadingState?: FC<LoadingStateProps>
   LoadingStateProps?: LoadingStateProps
-  title?: string
-  subtitle?: string
+  title?: NonEmptyString  // Custom type to ensure non-empty string
+  subtitle?: NonEmptyString
-  InitialLoadingState?: FC
+  InitialLoadingState?: FC<{ isLoading: boolean }>  // Specify expected props
   membersContainerHeight?: number
 }

+// Add this type to ensure non-empty strings
+type NonEmptyString = string & { __brand: 'nonEmpty' }

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

orderByStatus: { type: "String", defaultValue: "custom" }
) {
profile {
...ProfileItemFragment
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Import missing ProfileItemFragment

The fragment references ProfileItemFragment but there's no corresponding import statement.

Add the following import at the top of the file:

import { graphql } from 'react-relay'
+import { ProfileItemFragment } from '../fragments/ProfileItemFragment'

Also applies to: 30-30

Comment on lines 42 to 48
<Box>
<Button variant="soft" color="inherit">
{status === MemberStatuses.active
? capitalizeFirstLetter(memberRole)
: capitalizeFirstLetter(status)}
</Button>
</Box>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance button accessibility

The button showing member role/status could benefit from improved accessibility and visual feedback.

 <Box>
   <Button
     variant="soft"
     color="inherit"
+    aria-label={`Member ${status === MemberStatuses.active ? 'role' : 'status'}`}
+    title={status === MemberStatuses.active
+      ? `Member role: ${capitalizeFirstLetter(memberRole)}`
+      : `Status: ${capitalizeFirstLetter(status)}`}
+    disabled={status !== MemberStatuses.active}
   >
     {status === MemberStatuses.active
       ? capitalizeFirstLetter(memberRole)
       : capitalizeFirstLetter(status)}
   </Button>
 </Box>
📝 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
<Box>
<Button variant="soft" color="inherit">
{status === MemberStatuses.active
? capitalizeFirstLetter(memberRole)
: capitalizeFirstLetter(status)}
</Button>
</Box>
<Box>
<Button
variant="soft"
color="inherit"
aria-label={`Member ${status === MemberStatuses.active ? 'role' : 'status'}`}
title={status === MemberStatuses.active
? `Member role: ${capitalizeFirstLetter(memberRole)}`
: `Status: ${capitalizeFirstLetter(status)}`}
disabled={status !== MemberStatuses.active}
>
{status === MemberStatuses.active
? capitalizeFirstLetter(memberRole)
: capitalizeFirstLetter(status)}
</Button>
</Box>

Comment on lines 26 to 30
const members = useMemo(
() =>
data?.profile?.members?.edges.filter((edge) => edge?.node).map((edge) => edge?.node) || [],
[data?.profile?.members?.edges],
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety and null handling in data processing.

The current implementation could benefit from:

  1. Explicit typing for the edge and node structure
  2. More explicit null checks in the filter condition
-  const members = useMemo(
-    () =>
-      data?.profile?.members?.edges.filter((edge) => edge?.node).map((edge) => edge?.node) || [],
-    [data?.profile?.members?.edges],
-  )
+  const members = useMemo(
+    () =>
+      data?.profile?.members?.edges
+        .filter((edge): edge is NonNullable<typeof edge> => 
+          edge !== null && edge.node !== null)
+        .map((edge) => edge.node) || [],
+    [data?.profile?.members?.edges],
+  )
📝 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 members = useMemo(
() =>
data?.profile?.members?.edges.filter((edge) => edge?.node).map((edge) => edge?.node) || [],
[data?.profile?.members?.edges],
)
const members = useMemo(
() =>
data?.profile?.members?.edges
.filter((edge): edge is NonNullable<typeof edge> =>
edge !== null && edge.node !== null)
.map((edge) => edge.node) || [],
[data?.profile?.members?.edges],
)

Comment on lines 44 to 69
const renderMemberItem = (member: any, index: number) => {
if (!member) return null
if (
member.status === MemberStatuses.active &&
members[index - 1]?.status !== MemberStatuses.active
) {
return (
<>
<Divider />
<MemberItem member={data.profile} memberRole="owner" status={MemberStatuses.active} />
<MemberItem
member={member?.user?.profile}
memberRole={member?.role}
status={member?.status}
/>
</>
)
}
return (
<MemberItem
member={member?.user?.profile}
memberRole={member?.role}
status={member?.status}
/>
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor member rendering logic for better maintainability.

Several improvements could be made to the renderMemberItem function:

  1. Replace any type with proper interface for member parameter
  2. Extract the owner member rendering logic to a separate component
  3. Simplify the conditional rendering logic
- const renderMemberItem = (member: any, index: number) => {
+ interface MemberNode {
+   user?: {
+     profile: Profile;
+   };
+   role: string;
+   status: MemberStatuses;
+ }
+ 
+ const renderMemberItem = (member: MemberNode, index: number) => {

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

Comment on lines 52 to 53
<Divider />
<MemberItem member={data.profile} memberRole="owner" status={MemberStatuses.active} />
Copy link
Contributor

Choose a reason for hiding this comment

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

You can add an if statement inside the return to avoid repeating the following component:

<MemberItem
            member={member?.user?.profile}
            memberRole={member?.role}
            status={member?.status}
          />

Comment on lines 16 to 26
const DefaultInitialLoadingState: FC = () => (
<>
{Array.from({ length: NUMBER_OF_MEMBERS_ON_FIRST_LOAD }).map((_, index) => (
<MemberItemSkeleton
key={index} // eslint-disable-line react/no-array-index-key
variant="rectangular"
sx={{ mb: 0.5 }}
/>
))}
</>
)
Copy link
Contributor

Choose a reason for hiding this comment

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

The BA packages avoid having two different FCs in the same file. I suggest you to create a component file for the loading state.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@hpmoreira05 let's move that to modules/profiles/Members/DefaultInitialLoadingState

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would make a simpler loading state, maybe just using the component, and wait for the designers to have a skeleton, maybe on the next versions

@@ -1,26 +1,42 @@
"""Exposes a URL that specifies the behaviour of this scalar."""
Copy link
Contributor

Choose a reason for hiding this comment

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

All comments were updated. I think that's an IDE configuration issue. I guess you didn't change this in propose, so I would revert these unneeded changes.

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

🧹 Outside diff range and nitpick comments (6)
packages/components/modules/profiles/Members/index.tsx (2)

40-40: Consider making orderByStatus configurable.

The 'custom' value is hardcoded, which might limit the component's reusability.

Consider adding it to the component props with 'custom' as the default value:

+ type OrderByStatus = 'custom' | 'status' | 'name';
+ interface UserMembersProps {
+   orderByStatus?: OrderByStatus;
+   // ... existing props
+ }

  const data = useLazyLoadQuery<IUserMembersListPaginationQuery>(UserMembersListPaginationQuery, {
    profileId: currentProfile?.id || '',
    count: NUMBER_OF_MEMBERS_ON_FIRST_LOAD,
-   orderByStatus: 'custom',
+   orderByStatus: orderByStatus || 'custom',
  })

62-62: Consider moving hardcoded values to constants.

The default membersContainerHeight value (400) should be defined in the constants file for better maintainability.

Move this value to the constants file:

// constants.ts
export const NUMBER_OF_MEMBERS_ON_FIRST_LOAD = ...
+ export const DEFAULT_MEMBERS_CONTAINER_HEIGHT = 400

// index.tsx
+ import { DEFAULT_MEMBERS_CONTAINER_HEIGHT } from './constants'

const MembersSuspended: FC<UserMembersSuspendedProps> = ({
  // ...
-  membersContainerHeight = 400,
+  membersContainerHeight = DEFAULT_MEMBERS_CONTAINER_HEIGHT,
})
packages/components/modules/profiles/Members/MembersList/index.tsx (2)

14-20: Add JSDoc documentation for the component props.

Consider adding comprehensive documentation for the component's props to improve maintainability and developer experience.

+/**
+ * MembersList component displays a paginated list of members with virtualization
+ * @param {Object} props
+ * @param {any} props.userRef - Relay reference to the user data
+ * @param {FC} props.MemberItem - Component to render individual member items
+ * @param {FC} props.LoadingState - Component to render loading state
+ * @param {Object} props.LoadingStateProps - Props to pass to LoadingState component
+ * @param {number} props.membersContainerHeight - Height of the members container
+ */
const MembersList: FC<MemberListProps> = ({

97-101: Improve null safety in member count display.

The current implementation could benefit from more explicit null handling.

-      {data.members?.totalCount && (
+      {(data.members?.totalCount ?? 0) > 0 && (
         <Typography variant="subtitle2" mb={4}>
-          {(data.members?.totalCount ?? 0) + 1} members
+          {`${(data.members?.totalCount ?? 0) + 1} ${
+            data.members?.totalCount === 0 ? 'member' : 'members'
+          }`}
         </Typography>
       )}
packages/components/modules/profiles/graphql/queries/UserMembersList.ts (2)

3-16: Ensure consistent default value for orderByStatus

In the UserMembersListPaginationQuery, the $orderByStatus variable does not have a default value, whereas in the UserMembersListFragment, it defaults to "custom". To maintain consistency and prevent potential issues, consider adding a default value to $orderByStatus in the query.

Apply this diff to set the default value:

 query UserMembersListPaginationQuery(
     $count: Int = 10
     $cursor: String
-    $orderByStatus: String
+    $orderByStatus: String = "custom"
     $profileId: ID!
   ) {

28-28: Align connection key with fragment name for consistency

The connection key in the @connection directive is "UserMembersFragment_members", but the fragment is named UserMembersListFragment. For clarity and to avoid potential caching issues, it's advisable to match the connection key with the fragment name.

Apply this diff to update the connection key:

       @connection(
-        key: "UserMembersFragment_members",
+        key: "UserMembersListFragment_members",
         filters: ["orderByStatus"]
       ) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 970fff1 and 8d564dc.

⛔ Files ignored due to path filters (3)
  • packages/components/__generated__/UserMembersListFragment.graphql.ts is excluded by !**/__generated__/**
  • packages/components/__generated__/UserMembersListPaginationQuery.graphql.ts is excluded by !**/__generated__/**
  • packages/components/__generated__/userMembersListPaginationRefetchable.graphql.ts is excluded by !**/__generated__/**
📒 Files selected for processing (4)
  • packages/components/modules/profiles/Members/MembersList/index.tsx (1 hunks)
  • packages/components/modules/profiles/Members/index.tsx (1 hunks)
  • packages/components/modules/profiles/Members/types.ts (1 hunks)
  • packages/components/modules/profiles/graphql/queries/UserMembersList.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/components/modules/profiles/Members/types.ts
🔇 Additional comments (4)
packages/components/modules/profiles/Members/index.tsx (3)

1-16: LGTM! Well-organized imports.

The imports are properly structured and follow good practices by separating external and internal dependencies.


17-27: Move DefaultInitialLoadingState to a separate file.

Per the team's convention and previous review comments, functional components should be in separate files.

Consider moving this component to packages/components/modules/profiles/Members/LoadingState/index.tsx.


71-78: LGTM! Good use of Suspense.

The component properly implements Suspense for handling loading states and provides a customizable InitialLoadingState.

packages/components/modules/profiles/graphql/queries/UserMembersList.ts (1)

18-48: Fragment definition is well-structured

The UserMembersListFragment is properly defined with appropriate use of pagination, argument definitions, and relay directives. The inclusion of member details and nested fragments is correctly implemented.

Comment on lines 37 to 41
const data = useLazyLoadQuery<IUserMembersListPaginationQuery>(UserMembersListPaginationQuery, {
profileId: currentProfile?.id || '',
count: NUMBER_OF_MEMBERS_ON_FIRST_LOAD,
orderByStatus: 'custom',
})
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

🛠️ Refactor suggestion

Empty string is not a valid profileId input

Looking at the generated query file, the profileId parameter is defined as ID! (non-nullable ID type) in the GraphQL schema. Using an empty string as a fallback could lead to runtime errors. The component already has a null check for the query response (if (!data.profile) return null), but it should prevent the query from being executed with invalid input.

  • packages/components/modules/profiles/Members/index.tsx: Add guard clause before query execution
🔗 Analysis chain

Consider handling undefined currentProfile case.

The query is executed with an empty string when currentProfile is undefined, which might cause unnecessary GraphQL requests.

Consider adding a guard clause:

-  const data = useLazyLoadQuery<IUserMembersListPaginationQuery>(UserMembersListPaginationQuery, {
-    profileId: currentProfile?.id || '',
-    count: NUMBER_OF_MEMBERS_ON_FIRST_LOAD,
-    orderByStatus: 'custom',
-  })
+  if (!currentProfile?.id) return null;
+  
+  const data = useLazyLoadQuery<IUserMembersListPaginationQuery>(UserMembersListPaginationQuery, {
+    profileId: currentProfile.id,
+    count: NUMBER_OF_MEMBERS_ON_FIRST_LOAD,
+    orderByStatus: 'custom',
+  })

The previous attempt didn't yield results, possibly due to the query being defined in a different format or location. Let's search for the query file and examine how it's used in the codebase to make an informed decision about the empty profileId handling.


Let's examine the generated query file to understand if empty string is a valid input, and check the component's implementation for existing error handling to make a more informed decision about the suggestion.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the GraphQL query handles empty profileId
ast-grep --pattern 'query UserMembersListPaginationQuery($profileId: String!) {
  $$$
}'

Length of output: 90


Script:

#!/bin/bash
# Let's first locate the GraphQL query file
fd -e graphql -e ts -e tsx UserMembersListPaginationQuery

# Then examine the query definition and its usage
rg -l "UserMembersListPaginationQuery" --type ts --type tsx

# Also check how currentProfile is used in the component
rg -A 5 "const.*currentProfile.*useCurrentProfile" packages/components/modules/profiles/Members/index.tsx

Length of output: 603


Script:

#!/bin/bash
# Let's check the generated query file to understand the schema requirements
cat packages/components/__generated__/UserMembersListPaginationQuery.graphql.ts

# Also check if there's any error handling or null checks after the query
rg -A 10 "useLazyLoadQuery.*UserMembersListPaginationQuery" packages/components/modules/profiles/Members/index.tsx

# Check if there are any other components using similar pattern
rg "profileId:.*\?\." --type ts --type tsx

Length of output: 10810

Comment on lines 43 to 82
const renderMemberItem = (member: any, index: number) => {
if (!member) return null
if (
(member.status === MemberStatuses.active &&
members[index - 1]?.status !== MemberStatuses.active) ||
(member.status === MemberStatuses.active && !members[index - 1]?.status)
) {
return (
<>
<Divider />
<MemberItem member={data} memberRole="owner" status={MemberStatuses.active} />
<MemberItem
member={member?.user?.profile}
memberRole={member?.role}
status={member?.status}
/>
</>
)
}
if (member.status !== MemberStatuses.active && !members[index + 1]?.status) {
return (
<>
<MemberItem
member={member?.user?.profile}
memberRole={member?.role}
status={member?.status}
/>
<Divider />
<MemberItem member={data} memberRole="owner" status={MemberStatuses.active} />
</>
)
}
return (
<MemberItem
member={member?.user?.profile}
memberRole={member?.role}
status={member?.status}
/>
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify the member rendering logic for better maintainability.

The current implementation has complex conditional logic and repeated code. Consider refactoring to:

  1. Extract the owner member rendering into a separate component
  2. Simplify the conditions for showing dividers
  3. Use an early return pattern
-  const renderMemberItem = (member: any, index: number) => {
+  const OwnerMember = () => (
+    <MemberItem member={data} memberRole="owner" status={MemberStatuses.active} />
+  )
+
+  const renderMemberItem = (member: any, index: number) => {
     if (!member) return null
-    if (
-      (member.status === MemberStatuses.active &&
-        members[index - 1]?.status !== MemberStatuses.active) ||
-      (member.status === MemberStatuses.active && !members[index - 1]?.status)
-    ) {
-      return (
-        <>
-          <Divider />
-          <MemberItem member={data} memberRole="owner" status={MemberStatuses.active} />
-          <MemberItem
-            member={member?.user?.profile}
-            memberRole={member?.role}
-            status={member?.status}
-          />
-        </>
-      )
-    }
-    if (member.status !== MemberStatuses.active && !members[index + 1]?.status) {
-      return (
-        <>
-          <MemberItem
-            member={member?.user?.profile}
-            memberRole={member?.role}
-            status={member?.status}
-          />
-          <Divider />
-          <MemberItem member={data} memberRole="owner" status={MemberStatuses.active} />
-        </>
-      )
-    }
+
+    const showOwnerBefore = member.status === MemberStatuses.active && 
+      (!members[index - 1]?.status || members[index - 1]?.status !== MemberStatuses.active)
+
+    const showOwnerAfter = member.status !== MemberStatuses.active && 
+      !members[index + 1]?.status
+
+    if (!showOwnerBefore && !showOwnerAfter) {
+      return (
+        <MemberItem
+          member={member?.user?.profile}
+          memberRole={member?.role}
+          status={member?.status}
+        />
+      )
+    }
+
     return (
-      <MemberItem
-        member={member?.user?.profile}
-        memberRole={member?.role}
-        status={member?.status}
-      />
+      <>
+        {showOwnerBefore && (
+          <>
+            <Divider />
+            <OwnerMember />
+          </>
+        )}
+        <MemberItem
+          member={member?.user?.profile}
+          memberRole={member?.role}
+          status={member?.status}
+        />
+        {showOwnerAfter && (
+          <>
+            <Divider />
+            <OwnerMember />
+          </>
+        )}
+      </>
     )
   }
📝 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 renderMemberItem = (member: any, index: number) => {
if (!member) return null
if (
(member.status === MemberStatuses.active &&
members[index - 1]?.status !== MemberStatuses.active) ||
(member.status === MemberStatuses.active && !members[index - 1]?.status)
) {
return (
<>
<Divider />
<MemberItem member={data} memberRole="owner" status={MemberStatuses.active} />
<MemberItem
member={member?.user?.profile}
memberRole={member?.role}
status={member?.status}
/>
</>
)
}
if (member.status !== MemberStatuses.active && !members[index + 1]?.status) {
return (
<>
<MemberItem
member={member?.user?.profile}
memberRole={member?.role}
status={member?.status}
/>
<Divider />
<MemberItem member={data} memberRole="owner" status={MemberStatuses.active} />
</>
)
}
return (
<MemberItem
member={member?.user?.profile}
memberRole={member?.role}
status={member?.status}
/>
)
}
const OwnerMember = () => (
<MemberItem member={data} memberRole="owner" status={MemberStatuses.active} />
)
const renderMemberItem = (member: any, index: number) => {
if (!member) return null
const showOwnerBefore = member.status === MemberStatuses.active &&
(!members[index - 1]?.status || members[index - 1]?.status !== MemberStatuses.active)
const showOwnerAfter = member.status !== MemberStatuses.active &&
!members[index + 1]?.status
if (!showOwnerBefore && !showOwnerAfter) {
return (
<MemberItem
member={member?.user?.profile}
memberRole={member?.role}
status={member?.status}
/>
)
}
return (
<>
{showOwnerBefore && (
<>
<Divider />
<OwnerMember />
</>
)}
<MemberItem
member={member?.user?.profile}
memberRole={member?.role}
status={member?.status}
/>
{showOwnerAfter && (
<>
<Divider />
<OwnerMember />
</>
)}
</>
)
}

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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8d564dc and d18d6fe.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • packages/components/modules/profiles/index.ts (1 hunks)
  • packages/components/package.json (1 hunks)
  • packages/components/schema.graphql (60 hunks)
  • packages/design-system/components/inputs/TextField/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/components/modules/profiles/index.ts
  • packages/components/schema.graphql
  • packages/design-system/components/inputs/TextField/index.tsx
🔇 Additional comments (1)
packages/components/package.json (1)

52-112: Verify compatibility of development dependencies.

The large number of development dependencies with caret (^) versions could lead to inconsistent development environments across the team. Consider using a package.json lock file or pinning versions.

"@tailwindcss/typography": "^0.5.13",
"@testing-library/cypress": "^10.0.2",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.0",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Version mismatch between React and Testing Library.

@testing-library/react v16.0.0 is significantly older than your React v18.3.1. This version mismatch could lead to compatibility issues or missing features in your tests.

-    "@testing-library/react": "^16.0.0",
+    "@testing-library/react": "^14.2.1"
📝 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
"@testing-library/react": "^16.0.0",
"@testing-library/react": "^14.2.1",

"graphql": "^16.8.1",
"js-cookie": "^3.0.5",
"luxon": "^3.4.4",
"next": "14.3.0-canary.24",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid using Next.js canary versions in production.

The Next.js version 14.3.0-canary.24 is a pre-release version that may contain unstable features. Consider using a stable version instead.

-    "next": "14.3.0-canary.24",
+    "next": "14.3.0"

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

Comment on lines 24 to 41
"@hookform/resolvers": "^3.6.0",
"@mui/lab": "^5.0.0-alpha.170",
"@mui/material": "^5.15.19",
"@mui/system": "^5.15.19",
"@storybook/react": "^8.2.8",
"@tanstack/react-query": "^5.45.1",
"framer-motion": "^11.5.4",
"graphql": "catalog:graphql",
"js-cookie": "catalog:",
"luxon": "catalog:",
"next": "catalog:",
"react-hook-form": "catalog:",
"react-relay": "catalog:graphql",
"react-virtuoso": "catalog:",
"relay-runtime": "catalog:graphql",
"graphql": "^16.8.1",
"js-cookie": "^3.0.5",
"luxon": "^3.4.4",
"next": "14.3.0-canary.24",
"react-hook-form": "^7.51.5",
"react-relay": "^16.2.0",
"react-virtuoso": "^4.7.11",
"relay-runtime": "^16.2.0",
"use-long-press": "^3.2.0",
"zod": "catalog:",
"zustand": "catalog:"
"zod": "^3.23.8",
"zustand": "^4.5.2"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider pinning critical dependencies.

Using caret (^) for critical dependencies like MUI, React Query, and GraphQL could lead to unexpected breaking changes. Consider pinning these versions to exact numbers for better stability.

Example changes:

-    "@mui/material": "^5.15.19",
-    "@mui/system": "^5.15.19",
-    "@tanstack/react-query": "^5.45.1",
-    "graphql": "^16.8.1",
+    "@mui/material": "5.15.19",
+    "@mui/system": "5.15.19",
+    "@tanstack/react-query": "5.45.1",
+    "graphql": "16.8.1",
📝 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
"@hookform/resolvers": "^3.6.0",
"@mui/lab": "^5.0.0-alpha.170",
"@mui/material": "^5.15.19",
"@mui/system": "^5.15.19",
"@storybook/react": "^8.2.8",
"@tanstack/react-query": "^5.45.1",
"framer-motion": "^11.5.4",
"graphql": "catalog:graphql",
"js-cookie": "catalog:",
"luxon": "catalog:",
"next": "catalog:",
"react-hook-form": "catalog:",
"react-relay": "catalog:graphql",
"react-virtuoso": "catalog:",
"relay-runtime": "catalog:graphql",
"graphql": "^16.8.1",
"js-cookie": "^3.0.5",
"luxon": "^3.4.4",
"next": "14.3.0-canary.24",
"react-hook-form": "^7.51.5",
"react-relay": "^16.2.0",
"react-virtuoso": "^4.7.11",
"relay-runtime": "^16.2.0",
"use-long-press": "^3.2.0",
"zod": "catalog:",
"zustand": "catalog:"
"zod": "^3.23.8",
"zustand": "^4.5.2"
"@hookform/resolvers": "^3.6.0",
"@mui/lab": "^5.0.0-alpha.170",
"@mui/material": "5.15.19",
"@mui/system": "5.15.19",
"@storybook/react": "^8.2.8",
"@tanstack/react-query": "5.45.1",
"framer-motion": "^11.5.4",
"graphql": "16.8.1",
"js-cookie": "^3.0.5",
"luxon": "^3.4.4",
"next": "14.3.0-canary.24",
"react-hook-form": "^7.51.5",
"react-relay": "^16.2.0",
"react-virtuoso": "^4.7.11",
"relay-runtime": "^16.2.0",
"use-long-press": "^3.2.0",
"zod": "^3.23.8",
"zustand": "^4.5.2"

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

🧹 Outside diff range and nitpick comments (2)
packages/components/modules/profiles/Members/MemberItem/index.tsx (2)

15-24: Consider explicit typing for default avatarProps

While the current implementation works, it would be more maintainable to explicitly type the default value.

-  avatarProps = {},
+  avatarProps: Partial<AvatarProps> = {},

36-40: Add fallbacks for missing data and improve URL display

Consider handling missing data gracefully and formatting the URL path for better readability.

 <Box>
-  <Typography variant="subtitle2">{memberProfile.name}</Typography>
-  <Typography variant="caption">{memberProfile?.urlPath?.path}</Typography>
+  <Typography variant="subtitle2">{memberProfile.name || 'Unnamed Member'}</Typography>
+  <Typography variant="caption">
+    {memberProfile?.urlPath?.path
+      ? `@${memberProfile.urlPath.path}`
+      : 'No profile URL'}
+  </Typography>
 </Box>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d18d6fe and 2476ed2.

📒 Files selected for processing (1)
  • packages/components/modules/profiles/Members/MemberItem/index.tsx (1 hunks)
🔇 Additional comments (4)
packages/components/modules/profiles/Members/MemberItem/index.tsx (4)

1-14: LGTM! Well-organized imports and type declarations.

The imports are logically grouped and all necessary dependencies are properly imported.


28-35: Improve avatar placeholder handling

Instead of using an empty string as fallback for missing image URL, consider using the member's initials.

 <AvatarWithPlaceholder
   width={avatarWidth}
   height={avatarHeight}
-  src={memberProfile?.image?.url ?? ''}
+  src={memberProfile?.image?.url}
+  fallback={memberProfile?.name?.[0]?.toUpperCase()}
   alt="Profile avatar"
   color="secondary"
   {...avatarProps}
 />

42-48: Enhance button accessibility

The button showing member role/status needs improved accessibility and visual feedback.

 <Box>
   <Button
     variant="soft"
     color="inherit"
+    aria-label={`Member ${status === MemberStatuses.active ? 'role' : 'status'}`}
+    title={status === MemberStatuses.active
+      ? `Member role: ${capitalizeFirstLetter(memberRole)}`
+      : `Status: ${capitalizeFirstLetter(status)}`}
+    disabled={status !== MemberStatuses.active}
   >
     {status === MemberStatuses.active
       ? capitalizeFirstLetter(memberRole)
       : capitalizeFirstLetter(status)}
   </Button>
 </Box>

15-51: Verify integration with member list requirements

Let's ensure this component properly integrates with the scrollable member list requirement mentioned in the PR objectives.

✅ Verification successful

MemberItem component properly integrates with scrollable list requirements

The MemberItem component is correctly implemented for integration with the parent MembersList component, which uses react-virtuoso for efficient scrolling and virtualization. The MembersList component properly handles:

  • Virtual scrolling with configurable container height
  • Dynamic member rendering through itemContent prop
  • Proper pagination with load-more functionality
  • Consistent styling and layout of member items
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the parent MembersList component implements proper scrolling containment
ast-grep --pattern 'const MembersList = $_: FC<$_> => {
  $$$
  return (
    <$_Container $$$>
      $$$
      style={$_}
      $$$
    </$_>
  )
}'

Length of output: 150


Script:

#!/bin/bash
# Let's try a broader search for MembersList component and its container implementation
rg -l "MembersList" --type tsx

# Then let's look for any styled components or containers that might handle scrolling
rg -l "styled\(.+Container\)" --type tsx

Length of output: 129


Script:

#!/bin/bash
# Search for MembersList component without file type restriction
rg -l "MembersList"

# Look for styled containers in the same directory as MemberItem
fd -e ts -e tsx -e js -e jsx . packages/components/modules/profiles/Members/

Length of output: 1180


Script:

#!/bin/bash
# Let's check the MembersList implementation and its container styling
cat packages/components/modules/profiles/Members/MembersList/index.tsx
cat packages/components/modules/profiles/Members/styled.tsx

Length of output: 3670

Copy link
Collaborator

@anicioalexandre anicioalexandre left a comment

Choose a reason for hiding this comment

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

that's a very good PR! congrats 🎉

ProfileRoles,
} from '../../../../__generated__/UserMembersListFragment.graphql'

export interface IMemberPersonalInformation extends BoxProps {
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's avoid adding the I prefix

if (
(member.status === MemberStatuses.active &&
members[index - 1]?.status !== MemberStatuses.active) ||
(member.status === MemberStatuses.active && !members[index - 1]?.status)
Copy link
Collaborator

Choose a reason for hiding this comment

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

could we move some of these conditions to a constant? just so we can read better what the rule is, something like:

    const isActiveMember = member.status === MemberStatuses.active
    const isPreviousMemberInactive = members[index - 1]?.status !== MemberStatuses.active
    const isPreviousMemberUndefined = !members[index - 1]?.status

    const isFirstActiveMember = (isActiveMember && isPreviousMemberInactive) || (isActiveMember && isPreviousMemberUndefined)

Comment on lines 16 to 26
const DefaultInitialLoadingState: FC = () => (
<>
{Array.from({ length: NUMBER_OF_MEMBERS_ON_FIRST_LOAD }).map((_, index) => (
<MemberItemSkeleton
key={index} // eslint-disable-line react/no-array-index-key
variant="rectangular"
sx={{ mb: 0.5 }}
/>
))}
</>
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@hpmoreira05 let's move that to modules/profiles/Members/DefaultInitialLoadingState


return (
<>
{data.members?.totalCount && (
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we could simplify that a little bit

Suggested change
{data.members?.totalCount && (
<Typography variant="subtitle2" mb={4}>
{data.members?.totalCount ?? 1} members
</Typography>


const MembersList: FC<MemberListProps> = ({
userRef,
MemberItem = DefaultMemberItem,
Copy link
Collaborator

Choose a reason for hiding this comment

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

we're missing MemberItemProps here

Comment on lines 16 to 26
const DefaultInitialLoadingState: FC = () => (
<>
{Array.from({ length: NUMBER_OF_MEMBERS_ON_FIRST_LOAD }).map((_, index) => (
<MemberItemSkeleton
key={index} // eslint-disable-line react/no-array-index-key
variant="rectangular"
sx={{ mb: 0.5 }}
/>
))}
</>
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would make a simpler loading state, maybe just using the component, and wait for the designers to have a skeleton, maybe on the next versions

</MemberPersonalInformation>

<Box>
<Button variant="soft" color="inherit">
Copy link
Collaborator

@anicioalexandre anicioalexandre Nov 29, 2024

Choose a reason for hiding this comment

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

let's remove the pointer-events here since it should not be interactive sx={{pointerEvents: "none"}}

)
}

const renderMemberItem = (member: any, index: number) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

we can avoid the any by typing it like this:

// on Members/types.ts
type Members = NonNullable<UserMembersListFragment$data['members']>
type MembersEdges = Members['edges']
export type MemberNode = NonNullable<MembersEdges[number]>['node']

// on MembersList/index.tsx
const renderMemberItem = (member: MemberNode, index: number) => {

avatarWidth = 40,
avatarHeight = 40,
}) => {
const memberProfile = useFragment<ProfileItemFragment$key>(ProfileItemFragment, member)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think ideally this fragment should be related to members (aka ProfileUserRole on the backend) instead of profile fragment, we would use a MembersItemFragment. So the list query would be something like this:

export const UserMembersListFragment = graphql`
  fragment UserMembersListFragment on Profile
  @refetchable(queryName: "userMembersListPaginationRefetchable")
  @argumentDefinitions(
    count: { type: "Int", defaultValue: 10 }
    cursor: { type: "String" }
    orderByStatus: { type: "String", defaultValue: "custom" }
  ) {
    members(first: $count, after: $cursor, orderByStatus: $orderByStatus)
      @connection(key: "UserMembersFragment_members", filters: ["orderByStatus"]) {
      totalCount
      edges {
        node {
          ...MemberItemFragment
        }
      }
      pageInfo {
        endCursor
        hasNextPage
      }
    }
  }
`

them the members fragment something like:

export const MemberItemFragment = graphql`
fragment MemberItemFragment on Member // need to create this interface
@argumentDefinitions(avatarSize: { type: "Int", defaultValue: 100 }) {
  id
  user {
    profile {
      ...ProfileItemFragment
    }
  }
  role
  status
}
`

@nossila what do you think? I think we mentioned that on our last sidebar

Copy link
Collaborator

Choose a reason for hiding this comment

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

then if we do that, we avoid needing to pass over the member?.role and member?.status since we would define those fields on the MemberItem fragment

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

🧹 Outside diff range and nitpick comments (2)
packages/components/schema.graphql (1)

925-925: Verify the implementation of the orderByStatus argument

The members field in the Profile type now accepts an orderByStatus argument. Make sure this argument is implemented correctly and that it aligns with the ordering conventions used elsewhere in the schema. Consider if a more generic orderBy parameter might enhance flexibility.

packages/components/modules/profiles/Members/MemberListItem/index.tsx (1)

18-75: Simplify conditional logic for better readability

The conditional statements determining isFirstActiveMember and isLastMemberInactive can be streamlined to enhance code clarity.

Consider simplifying the isFirstActiveMember condition:

- const isFirstActiveMember =
-   (isActiveMember && isPreviousMemberInactive) || (isActiveMember && isPreviousMemberUndefined)
+ const isFirstActiveMember = isActiveMember && (!prevMemberFragment?.status || prevMemberFragment.status !== MemberStatuses.active)

Similarly, refine isLastMemberInactive for consistency:

- const isLastMemberInactive = !isActiveMember && isNextMemberUndefined
+ const isLastInactiveMember = !isActiveMember && (!nextMemberFragment?.status || nextMemberFragment.status === MemberStatuses.active)

These changes will make the conditions more concise and easier to understand.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4852c83 and 68de6e0.

⛔ Files ignored due to path filters (4)
  • packages/components/__generated__/MemberItemFragment.graphql.ts is excluded by !**/__generated__/**
  • packages/components/__generated__/UserMembersListFragment.graphql.ts is excluded by !**/__generated__/**
  • packages/components/__generated__/UserMembersListPaginationQuery.graphql.ts is excluded by !**/__generated__/**
  • packages/components/__generated__/userMembersListPaginationRefetchable.graphql.ts is excluded by !**/__generated__/**
📒 Files selected for processing (11)
  • packages/components/modules/profiles/Members/MemberItem/index.tsx (1 hunks)
  • packages/components/modules/profiles/Members/MemberItem/styled.tsx (1 hunks)
  • packages/components/modules/profiles/Members/MemberItem/types.ts (1 hunks)
  • packages/components/modules/profiles/Members/MemberListItem/index.tsx (1 hunks)
  • packages/components/modules/profiles/Members/MemberListItem/types.ts (1 hunks)
  • packages/components/modules/profiles/Members/MembersList/index.tsx (1 hunks)
  • packages/components/modules/profiles/Members/index.tsx (1 hunks)
  • packages/components/modules/profiles/Members/types.ts (1 hunks)
  • packages/components/modules/profiles/graphql/queries/MemberItem.ts (1 hunks)
  • packages/components/modules/profiles/graphql/queries/UserMembersList.ts (1 hunks)
  • packages/components/schema.graphql (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/components/modules/profiles/Members/MemberItem/types.ts
  • packages/components/modules/profiles/Members/MemberItem/styled.tsx
  • packages/components/modules/profiles/Members/index.tsx
  • packages/components/modules/profiles/graphql/queries/UserMembersList.ts
  • packages/components/modules/profiles/Members/types.ts
  • packages/components/modules/profiles/Members/MemberItem/index.tsx
🔇 Additional comments (8)
packages/components/schema.graphql (3)

700-700: Ensure consistent usage of the level argument in notifications fields

The notifications fields now include a level argument of type NotificationsNotificationLevelChoices in both the NotificationsInterface and User types. Verify that this change is consistently applied and that all relevant queries and mutations are updated accordingly.

Also applies to: 1400-1400


735-749: Confirm the introduction of the new notification levels enumeration

The NotificationsNotificationLevelChoices enumeration has been introduced, replacing BaseappNotificationsNotificationLevelChoices. Ensure that all related types and fields, such as Notification and notifications, are using the new enumeration correctly.

Use the script provided earlier to confirm that the old enumeration is no longer in use.


598-598: Verify the replacement of the level field enumeration

The level field in the Notification type now uses NotificationsNotificationLevelChoices instead of BaseappNotificationsNotificationLevelChoices. Ensure that all references to the old enumeration have been updated to the new one to maintain consistency.

Run the following script to check for any remaining references to the old enumeration:

packages/components/modules/profiles/graphql/queries/MemberItem.ts (1)

1-14: Fragment definition is correct and well-structured

The MemberItemFragment is properly defined on the ProfileUserRole type. It includes all necessary fields such as id, user, role, and status, and correctly incorporates the ProfileItemFragment spread within the profile field.

packages/components/modules/profiles/Members/MemberListItem/types.ts (1)

1-14: Type definitions are appropriately declared

The MemberListItemProps interface is well-defined, with all required properties accurately typed. The imports for MemberItemFragment$key, UserMembersListFragment$data, and MemberItemProps are correctly referenced, ensuring strong type safety.

packages/components/modules/profiles/Members/MembersList/index.tsx (3)

70-71: Ensure accurate member count display

The total number of members is calculated by adding 1 to account for the owner. Verifying that data.members?.totalCount is defined before adding improves reliability.

Consider updating the member count display:

- {(data.members?.totalCount ?? 0) + 1} members
+ {data.members?.totalCount ? data.members.totalCount + 1 : 1} members

This change ensures that the count is accurate even if totalCount is undefined.


28-31: 🛠️ Refactor suggestion

Enhance type safety and explicit null handling

The current implementation of the members array could benefit from improved type safety and explicit null checks.

Consider updating the members derivation:

const members = useMemo(() => {
  return (
    data?.members?.edges
      ?.filter((edge): edge is NonNullable<typeof edge> => edge !== null && edge.node !== null)
      .map((edge) => edge.node) || []
  );
}, [data?.members?.edges]);

This change explicitly checks for null values and refines the type, which improves reliability and reduces potential runtime errors.


45-54: 🛠️ Refactor suggestion

Specify the type for member in renderMemberItem function

The renderMemberItem function currently uses member: any, which weakens type safety.

Define a specific type for MemberNode and use it in the function signature:

type MemberNode = NonNullable<UserMembersListFragment$data['members']>['edges'][number]['node'];

const renderMemberItem = (member: MemberNode, index: number) => (
  // function body
);

This ensures that the member parameter is strongly typed, enhancing code robustness.

Comment on lines 514 to 518
interface MemberInterface {
"""The ID of the object"""
id: ID!
member: ProfileUserRole
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential recursive type definition in MemberInterface

The MemberInterface includes a member field of type ProfileUserRole, which is the type that implements this interface. This creates a circular reference that may lead to infinite recursion or issues with schema parsing and code generation tools.

Consider refactoring the member field to prevent the recursive self-reference. You might rename the field or change its type to avoid the circular dependency.

Comment on lines 1048 to 1054
type ProfileUserRole implements MemberInterface {
"""The ID of the object"""
id: ID!
user: User!
role: ProfileRoles
status: ProfileRoleStatus
member: ProfileUserRole
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid recursive self-reference in ProfileUserRole type

The ProfileUserRole type includes a member field of type ProfileUserRole, causing a recursive type definition. This can lead to infinite loops or errors in schema parsing tools.

Consider modifying the member field to reference a different type or remove it if it's not necessary. This will prevent potential recursion issues and improve schema reliability.

prevMember,
nextMember,
MemberItemComponent,
memberItemComponentProps,
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's use capital letter for these kind of props

<Virtuoso
style={{ height: membersContainerHeight }}
data={members}
overscan={NUMBER_OF_MEMBERS_TO_LOAD_NEXT}
Copy link
Collaborator

Choose a reason for hiding this comment

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

lets not overscan for now, on large lists can cause Maximum call stack size exceeded error

Copy link

sonarqubecloud bot commented Dec 4, 2024

Quality Gate Failed Quality Gate failed

Failed conditions
22.0% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

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

🧹 Outside diff range and nitpick comments (3)
packages/components/schema.graphql (1)

919-929: LGTM: Members field enhanced with ordering capability

The addition of the orderBy parameter to the members field aligns with the PR objectives for member management and follows GraphQL conventions. This change enables flexible sorting of members in the UI.

Consider documenting the supported ordering options in the schema using a dedicated enum type (e.g., MemberOrderBy) to:

  1. Make valid sorting options self-documenting
  2. Provide better type safety
  3. Enable better tooling support (autocomplete, validation)
packages/components/modules/profiles/Members/MemberListItem/index.tsx (2)

22-28: Consider extracting complex conditions into helper functions.

The position and status calculations are complex but could be more readable if extracted into helper functions.

+  const isActiveMember = (member: typeof memberFragment) => member.status === MemberStatuses.active
+  const isPreviousMemberInactive = (prev: typeof prevMemberFragment) => 
+    !prev?.status || prev.status !== MemberStatuses.active
+
-  const isActiveMember = memberFragment.status === MemberStatuses.active
-  const isPreviousMemberInactive = prevMemberFragment?.status !== MemberStatuses.active
-  const isPreviousMemberUndefined = !prevMemberFragment?.status
-  const isFirstActiveMember =
-    (isActiveMember && isPreviousMemberInactive) || (isActiveMember && isPreviousMemberUndefined)
+  const isFirstActiveMember = isActiveMember(memberFragment) && 
+    isPreviousMemberInactive(prevMemberFragment)

10-80: Consider component composition for better maintainability.

The component currently handles multiple responsibilities:

  1. Data management with fragments
  2. Position/status logic
  3. Rendering different layouts

Consider splitting these concerns into smaller, more focused components:

  • A container component for data management
  • A pure component for rendering
  • A hook for position/status logic

This would improve testability and maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c44d2b0 and 15a17cb.

📒 Files selected for processing (2)
  • packages/components/modules/profiles/Members/MemberListItem/index.tsx (1 hunks)
  • packages/components/schema.graphql (5 hunks)
🔇 Additional comments (5)
packages/components/schema.graphql (1)

592-592: LGTM: Notification level enum renamed consistently

The renaming of BaseappNotificationsNotificationLevelChoices to NotificationsNotificationLevelChoices is applied consistently across the schema while maintaining the same enum values. This change improves naming clarity by removing the redundant "Baseapp" prefix.

Also applies to: 694-694, 729-742

packages/components/modules/profiles/Members/MemberListItem/index.tsx (4)

1-9: LGTM! Well-organized imports.

The imports are logically grouped and all are being used within the component.


16-16: Use PascalCase for component props.

As mentioned in a previous review, component props should use PascalCase naming convention.

-  memberItemComponentProps,
+  MemberItemComponentProps,

18-20: Verify fragment usage across the codebase.

The fragment usage looks correct, but let's verify that the MemberItemFragment is consistently used across the codebase.

✅ Verification successful

The MemberItemFragment usage is consistent across the codebase

The fragment is correctly defined in graphql/queries/MemberItem.ts and consistently used with useFragment hook in the component. All related type definitions and imports are properly set up, and the fragment is correctly spread in the parent queries.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent usage of MemberItemFragment
# Expected: All usages should follow the same pattern with useFragment

# Search for MemberItemFragment usage
rg "MemberItemFragment" -A 2

Length of output: 8547


40-41: Ensure props spreading doesn't override critical props.

The spread operator {...memberItemComponentProps} is placed after specific props, which means it could potentially override critical values like member, memberRole, and status.

Also applies to: 46-47, 59-60, 66-67, 77-78

Comment on lines +38 to +39
memberRole="owner"
status={MemberStatuses.active}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider extracting hardcoded values to constants.

The "owner" role is hardcoded in multiple places. Consider moving it to the constants file for better maintainability.

// In ../constants.ts
+ export const MemberRoles = {
+   OWNER: 'owner',
+ } as const;

// In this file
- memberRole="owner"
+ memberRole={MemberRoles.OWNER}

Also applies to: 64-65

@anicioalexandre anicioalexandre changed the title Ba 1777 fe multiple profiles member list BA-1777: multiple profiles member list Dec 4, 2024
@anicioalexandre anicioalexandre merged commit f889c17 into master Dec 4, 2024
8 of 9 checks passed
@anicioalexandre anicioalexandre deleted the BA-1777-fe-multiple-profiles-member-list branch December 4, 2024 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants