Skip to content

fix(dashboard): paginate members and hide invite from non-admins - #416

Open
duyetbot wants to merge 1 commit into
mainfrom
fix/members-pagination-admin-408
Open

fix(dashboard): paginate members and hide invite from non-admins#416
duyetbot wants to merge 1 commit into
mainfrom
fix/members-pagination-admin-408

Conversation

@duyetbot

@duyetbot duyetbot commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes the members dashboard bugs in #408:

  • Members and invitations now use pageSize: 50 with Clerk infinite + fetchNext, and a Load more button when more pages remain. The UI no longer claims “15 members” while rendering only the first 10.
  • InviteMemberForm renders only when isAdmin. Non-admins still see the lists (without manage actions).
  • Switching org via ?org= now setActive then window.location.reload(), matching the sidebar org switcher so ProjectScopeProvider refetches for the new org.

Test plan

  • As an org admin, open Members — invite form is visible
  • As a non-admin member, open Members — invite form is hidden; list still loads
  • Org with >10 members shows all fetched members; with >50, Load more appears and appends
  • Pending invitations paginate the same way
  • Visit /dashboard/settings/organizations/members/?org=<other-org-id> — page reloads onto that org and sidebar projects match

Closes #408

Summary by Sourcery

Paginate organization members and invitations in the dashboard and align org switching behavior with the sidebar while restricting invite controls to admins.

New Features:

  • Add a "Load more" control to members and pending invitations lists in the organizations dashboard.

Bug Fixes:

  • Ensure members and invitations lists correctly reflect total counts by fetching additional pages with Clerk infinite pagination.
  • Hide the member invite form for non-admin users while keeping read-only access to member and invitation lists.
  • Reload the dashboard after switching organizations via the org query parameter so project-scoped data is refreshed.

Enhancements:

  • Increase Clerk memberships and invitations page size to reduce pagination churn and better match the displayed totals.

Members and invitations used pageSize 10 with no fetchNext, so a 15-member
org showed "15 members" but only the first page. InviteMemberForm rendered
for everyone; Clerk rejected non-admin submits. Switching org via ?org=
called setActive without reload, so the sidebar stayed on the previous org.

Raise pageSize to 50, append via Clerk infinite + Load more, gate the
invite form on isAdmin, and reload after setActive like the sidebar.

Fixes #408

Co-Authored-By: Duyet Le <me@duyet.net>
Co-Authored-By: duyetbot <bot@duyet.net>
@sourcery-ai

sourcery-ai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Reviewer's Guide

Paginated organization members and invitations using Clerk’s infinite pagination, added a "Load more" control, gated the invite form to admins only, and aligned org switching via the ?org= query param with the sidebar behavior by reloading after setActive.

Sequence diagram for members pagination with Load more

sequenceDiagram
  actor User
  participant MembersContent
  participant MembersList
  participant MemberListCard
  participant ClerkUseOrganization

  User->>MemberListCard: click Button_onClick
  MemberListCard->>MembersList: onLoadMore()
  MembersList->>MembersContent: onLoadMore()
  MembersContent->>ClerkUseOrganization: memberships.fetchNext()
  ClerkUseOrganization-->>MembersContent: memberships.data (next page)
  MembersContent-->>MembersList: memberships.data (appended)
  MembersList-->>MemberListCard: data, hasMore, isLoadingMore
  MemberListCard-->>User: updated table and Load more state
Loading

Sequence diagram for org switching via org query param

sequenceDiagram
  actor Browser
  participant MembersContent
  participant ClerkSetActive
  participant Window

  Browser->>MembersContent: initial_render_with_orgParam
  MembersContent->>MembersContent: React.useEffect [orgParam and isOrgLoaded]
  MembersContent->>ClerkSetActive: setActive(organization)
  ClerkSetActive-->>MembersContent: setActive_resolved
  MembersContent->>Window: window.location.reload()
  Window-->>Browser: trigger_full_page_reload
  Browser->>MembersContent: rerender_with_new_active_org
Loading

File-Level Changes

Change Details Files
Enable Clerk infinite pagination for members and invitations and wire it into the UI.
  • Increase memberships and invitations pageSize from 10 to 50 and enable infinite mode in useOrganization options.
  • Pass hasNextPage, isFetching, and fetchNext from memberships to MembersList.
  • Pass hasNextPage, isFetching, and fetchNext from invitations to the pending invitations list.
packages/dashboard/src/components/dashboard/organizations/members/members-content.tsx
Add a reusable "Load more" button to member list cards that integrates with pagination state.
  • Extend MemberListCardProps with hasMore, isLoadingMore, and onLoadMore props.
  • Render a centered secondary Button labeled "Load more" when hasMore and onLoadMore are provided.
  • Disable the button and show "Loading…" while isLoadingMore is true.
packages/dashboard/src/components/dashboard/organizations/members/_member-list-card.tsx
Propagate pagination controls through members and pending invitations list components.
  • Extend MembersListProps and _PendingInvitationsListProps with hasMore, isLoadingMore, and onLoadMore.
  • Pass pagination props from MembersList and _PendingInvitationsList to MemberListCard.
packages/dashboard/src/components/dashboard/organizations/members/_members-list.tsx
packages/dashboard/src/components/dashboard/organizations/members/_pending-invitations-list.tsx
Restrict the invite member form to admins and refine org switching via query param to trigger a full reload after setActive.
  • Render InviteMemberForm only when isAdmin is true so non-admins see lists without invite controls.
  • Update the org switching effect to also require isOrgLoaded before acting on ?org=.
  • Call setActive for the org param and then reload the page with window.location.reload to match sidebar org switcher behavior.
  • Include isOrgLoaded and organization.id in the effect dependency array.
packages/dashboard/src/components/dashboard/src/components/dashboard/organizations/members/members-content.tsx

Assessment against linked issues

Issue Objective Addressed Explanation
#408 Ensure the members and invitations lists correctly paginate beyond the initial page (previously 10 items), so the UI-rendered list matches the reported counts (e.g., 15 members shows all 15), using larger page size and fetchNext/load-more behavior.
#408 Restrict rendering of the InviteMemberForm to admin users only, so non-admins cannot see or submit the invite form.
#408 When switching organizations via the ?org= query parameter on the members page, trigger a reload or refetch after setActive so ProjectScopeProvider and the sidebar reflect the newly active organization.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@duyetbot, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 117 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e42d6192-78de-4f65-95e0-e8e423c40863

📥 Commits

Reviewing files that changed from the base of the PR and between 39c28e4 and da621c9.

📒 Files selected for processing (4)
  • packages/dashboard/src/components/dashboard/organizations/members/_member-list-card.tsx
  • packages/dashboard/src/components/dashboard/organizations/members/_members-list.tsx
  • packages/dashboard/src/components/dashboard/organizations/members/_pending-invitations-list.tsx
  • packages/dashboard/src/components/dashboard/organizations/members/members-content.tsx

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

❤️ Share

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The useEffect that calls setActive and then window.location.reload() may be heavy-handed for org switching; consider using router navigation or a scoped state reset to avoid a full-page reload while still re-triggering ProjectScopeProvider.
  • In the pagination logic, isLoadingMore is inferred as Boolean(isFetching && !isLoading); if Clerk exposes a more specific isFetchingNextPage (or equivalent), using that would avoid potential UI flicker when initial loads and background refetches overlap.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `useEffect` that calls `setActive` and then `window.location.reload()` may be heavy-handed for org switching; consider using router navigation or a scoped state reset to avoid a full-page reload while still re-triggering `ProjectScopeProvider`.
- In the pagination logic, `isLoadingMore` is inferred as `Boolean(isFetching && !isLoading)`; if Clerk exposes a more specific `isFetchingNextPage` (or equivalent), using that would avoid potential UI flicker when initial loads and background refetches overlap.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@duyetbot

Copy link
Copy Markdown
Collaborator Author

Sourcery's two review-body nits are left as-is:

setActive + window.location.reload() — this matches the sidebar org switcher. ProjectScopeProvider reads the active org only on mount, so a client-only setActive leaves project-scoped dashboard data on the previous org. A full reload is the smallest way to remount that provider; a scoped state reset would need a provider API that does not exist yet.

isLoadingMore via isFetching && !isLoading — Clerk's PaginatedResources (memberships/invitations from useOrganization) exposes isFetching / isLoading / hasNextPage / fetchNext, not React Query's isFetchingNextPage. The boolean is the Clerk equivalent: initial load uses isLoading, subsequent pages use isFetching while isLoading is already false.

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.

Members list silently stops at 10; invite form shown to non-admins

2 participants