Skip to content

New table component#995

Merged
BilalG1 merged 12 commits intodevfrom
new-table-component
Nov 6, 2025
Merged

New table component#995
BilalG1 merged 12 commits intodevfrom
new-table-component

Conversation

@BilalG1
Copy link
Contributor

@BilalG1 BilalG1 commented Nov 3, 2025

Summary by CodeRabbit

  • New Features
    • Enhanced data table with cursor-based pagination, per-page caching and background prefetching for faster navigation
    • Pagination controls with page-size selector and Prev/Next navigation
    • Skeleton loading screens for smoother transitions
    • URL-synchronized query state with debounced search and shareable filters
    • Redesigned user management table with improved columns, actions (including copy-to-clipboard), filtering, sorting, and accessible controls

@vercel
Copy link

vercel bot commented Nov 3, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Nov 6, 2025 1:21am
stack-dashboard Ready Ready Preview Comment Nov 6, 2025 1:21am
stack-demo Ready Ready Preview Comment Nov 6, 2025 1:21am
stack-docs Ready Ready Preview Comment Nov 6, 2025 1:21am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Walkthrough

Adds a modular data-table system for the dashboard: cursor pagination caching and prefetching, URL query-state synchronization, stable-value hook, pagination UI, table rendering utilities and skeletons, and a large refactor of the user table to use these primitives; plus minor import/type-only import tweaks in team tables.

Changes

Cohort / File(s) Summary
Cursor pagination
apps/dashboard/src/components/data-table/common/cursor-pagination.tsx
New useCursorPaginationCache(initialPage?: number) hook managing per-page cursors, prefetched-cursor tracking, and async prefetch deduplication.
URL query sync
apps/dashboard/src/components/data-table/common/url-query-state.tsx
New useUrlQueryState hook: Yup-validated URL query ↔ component state sync with sanitization, custom serialization, and equality checks.
Stable value helper
apps/dashboard/src/components/data-table/common/stable-value.tsx
New useStableValue<T>(value, fingerprint) hook that returns a memoized value until the fingerprint changes.
Pagination UI
apps/dashboard/src/components/data-table/common/pagination.tsx
New PaginationControls component with page-size selector, previous/next buttons, and customizable page indicator label.
Table primitives & utils
apps/dashboard/src/components/data-table/common/table.tsx
Adds TableContent component, ColumnLayoutEntry/ColumnLayout/ColumnMeta types, DEFAULT_ROW_HEIGHT_PX, getRowHeightStyle, combineClassNames, and getColumnStyles utilities.
Table skeleton
apps/dashboard/src/components/data-table/common/table-skeleton.tsx
New generic TableSkeleton component for rendering header and body skeletons with configurable column layout and cell skeleton renderer.
User table refactor
apps/dashboard/src/components/data-table/user-table.tsx
Major refactor: modular components (header/body/skeleton), ExtendedServerUser type, extendUsers overloads, getCommonUserColumns, cursor-based pagination integration, URL-synced query state, prefetching, new cell components, and actions dropdown.
Team table minor edits
apps/dashboard/src/components/data-table/team-member-table.tsx, apps/dashboard/src/components/data-table/team-member-search-table.tsx
Type-only import for ExtendedServerUser and import path formatting change.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as UserTable UI
    participant QS as useUrlQueryState
    participant PC as useCursorPaginationCache
    participant Fetch as Data Fetch
    participant Router as Router

    User->>UI: interact (search, sort, change page/size)
    UI->>QS: setState(partial)
    QS->>QS: merge + sanitize + validate
    QS->>Router: router.replace (update URL)
    QS->>UI: updated state

    UI->>PC: readCursorForPage(page)
    alt cursor cached
        PC->>UI: cached cursor
    else cache miss
        UI->>Fetch: fetch page with cursor
        Fetch->>UI: return rows + nextCursor
        UI->>PC: recordPageCursor(page, cursor)
        UI->>PC: recordNextCursor(page, nextCursor)
    end

    UI->>PC: prefetchCursor(nextCursor, fetchTask)
    PC->>PC: dedupe & enqueue
    PC->>Fetch: run async prefetch

    UI->>UI: render TableContent or TableSkeleton
    UI->>User: display rows + PaginationControls
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Files warranting extra attention:
    • url-query-state.tsx — validation, serialization, sanitization, router replace behavior, and equality semantics.
    • table.tsx — generics, integration with @tanstack/react-table, empty-state and layout application.
    • user-table.tsx — data flow changes, cursor pagination integration, extendUsers overloads, and UI/action wiring.
    • cursor-pagination.tsx — prefetch deduplication and async task handling.

Possibly related PRs

  • Improved anonymous users #857 — Overlapping edits to apps/dashboard/src/components/data-table/user-table.tsx adding an anonymous-users toggle and related fetch logic; likely to conflict or require coordination.

Suggested reviewers

  • N2D4

Poem

🐰
Through rows and cursors I softly tread,
I stash the pages in my leafy bed,
Prefetch the next hop, keep values still,
A stable nibble, a table-sized thrill. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description only contains a template HTML comment referencing contribution guidelines with no actual content describing the changes, objectives, or technical details. Add a detailed description explaining what the PR accomplishes, why these changes were made, key components added/modified, and any migration notes or testing guidance needed.
Title check ❓ Inconclusive The title 'New table component' is vague and generic, using non-descriptive language that doesn't convey meaningful information about the specific changes in the changeset. Provide a more specific title that describes the primary change, such as 'Add modular data table components with cursor pagination and URL state sync' or 'Refactor user table with new pagination and filtering system'.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new-table-component

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

❤️ Share

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

@BilalG1 BilalG1 marked this pull request as ready for review November 3, 2025 21:15
@claude
Copy link
Contributor

claude bot commented Nov 3, 2025

Claude finished @BilalG1's task —— View job


I'll analyze this and get back to you.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR introduces a new table component system for the dashboard, replacing the existing user table implementation with a custom-built solution that provides better control and UX.

Key changes:

  • Replaced the old user table with a complete rewrite featuring custom pagination, URL state management, and improved filtering
  • Created 6 new reusable table utilities in common/ directory for pagination, skeleton loading, URL state sync, and cursor caching
  • Implemented cursor-based pagination with intelligent prefetching to improve performance
  • Added URL query parameter synchronization for shareable table states (search, filters, pagination)
  • Used Map and Set for cursor caching (follows custom instruction cd0e08f7)
  • Used runAsynchronouslyWithAlert for async user actions (follows custom instruction 5e671275)

Architecture improvements:

  • Better separation of concerns with reusable hooks and components
  • Type-safe column layout system with TypeScript generics
  • Stable value memoization to prevent unnecessary re-renders
  • Suspense-based loading with skeleton UI

Confidence Score: 4/5

  • This PR is safe to merge with careful testing of the new table behavior
  • The code is well-structured and follows best practices including custom instructions. The implementation uses Map/Set instead of plain objects, and properly uses runAsynchronouslyWithAlert for error handling. However, the large scope of changes (1325+ lines) and complete rewrite of the user table warrants thorough testing of pagination, filtering, and URL state management before merging.
  • apps/dashboard/src/components/data-table/user-table.tsx requires thorough testing of pagination and URL state edge cases

Important Files Changed

File Analysis

Filename Score Overview
apps/dashboard/src/components/data-table/common/cursor-pagination.tsx 5/5 Custom hook for managing cursor-based pagination cache using Map and Set refs
apps/dashboard/src/components/data-table/common/table.tsx 5/5 Core table component using TanStack Table with typed column layout system
apps/dashboard/src/components/data-table/common/url-query-state.tsx 5/5 Custom hook for syncing state with URL query parameters using yup validation
apps/dashboard/src/components/data-table/user-table.tsx 4/5 Complete rewrite: replaced old table with new custom implementation featuring URL state management, cursor pagination, and improved UI

Sequence Diagram

sequenceDiagram
    participant User
    participant UserTable
    participant URLQueryState
    participant CursorCache
    participant StackAdminApp
    participant TableContent
    
    User->>UserTable: Loads page
    UserTable->>URLQueryState: useUrlQueryState()
    URLQueryState->>URLQueryState: Parse URL params with yup
    URLQueryState-->>UserTable: Return query state
    
    UserTable->>CursorCache: useCursorPaginationCache()
    CursorCache-->>UserTable: Return cache methods
    
    UserTable->>UserTable: useEffect (reset cache on filter change)
    UserTable->>CursorCache: resetCache()
    
    UserTable->>StackAdminApp: useUsers(listOptions)
    StackAdminApp-->>UserTable: Return users + nextCursor
    
    UserTable->>UserTable: useStableValue() to memoize
    UserTable->>CursorCache: recordPageCursor()
    UserTable->>CursorCache: recordNextCursor()
    
    UserTable->>StackAdminApp: Prefetch next page
    StackAdminApp-->>CursorCache: Cache prefetched data
    
    UserTable->>TableContent: Render table with data
    TableContent-->>User: Display table
    
    User->>UserTable: Changes search filter
    UserTable->>URLQueryState: setState({search, page: 1})
    URLQueryState->>URLQueryState: router.replace() with new params
    Note over UserTable,CursorCache: Cache reset triggers
    
    User->>UserTable: Clicks next page
    UserTable->>CursorCache: readCursorForPage(page+1)
    CursorCache-->>UserTable: Return cached cursor
    UserTable->>URLQueryState: setState({page, cursor})
    URLQueryState->>URLQueryState: Update URL
    UserTable->>StackAdminApp: useUsers() with cached cursor
    StackAdminApp-->>UserTable: Return next page data
Loading

9 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@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

🧹 Nitpick comments (1)
apps/dashboard/src/components/data-table/common/cursor-pagination.tsx (1)

31-40: Wrap the prefetch task before passing to runAsynchronously.

If task throws synchronously (before returning a Promise), the exception will bubble out of prefetchCursor because we call task() before runAsynchronously can attach its safety net. Passing a thunk (() => task()) lets runAsynchronously handle both sync and async errors consistently.

-    runAsynchronously(task());
+    runAsynchronously(() => task());
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3538638 and 8f420cb.

📒 Files selected for processing (9)
  • apps/dashboard/src/components/data-table/common/cursor-pagination.tsx (1 hunks)
  • apps/dashboard/src/components/data-table/common/pagination.tsx (1 hunks)
  • apps/dashboard/src/components/data-table/common/stable-value.tsx (1 hunks)
  • apps/dashboard/src/components/data-table/common/table-skeleton.tsx (1 hunks)
  • apps/dashboard/src/components/data-table/common/table.tsx (1 hunks)
  • apps/dashboard/src/components/data-table/common/url-query-state.tsx (1 hunks)
  • apps/dashboard/src/components/data-table/team-member-search-table.tsx (1 hunks)
  • apps/dashboard/src/components/data-table/team-member-table.tsx (1 hunks)
  • apps/dashboard/src/components/data-table/user-table.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
apps/dashboard/src/components/data-table/common/pagination.tsx (1)
apps/dashboard/src/components/data-table/common/table.tsx (1)
  • combineClassNames (27-29)
apps/dashboard/src/components/data-table/common/cursor-pagination.tsx (1)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronously (343-366)
apps/dashboard/src/components/data-table/common/table-skeleton.tsx (1)
apps/dashboard/src/components/data-table/common/table.tsx (5)
  • ColumnLayout (15-15)
  • getRowHeightStyle (23-25)
  • DEFAULT_ROW_HEIGHT_PX (21-21)
  • combineClassNames (27-29)
  • getColumnStyles (31-40)
apps/dashboard/src/components/data-table/common/url-query-state.tsx (1)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
apps/dashboard/src/components/data-table/user-table.tsx (11)
apps/dashboard/src/components/data-table/common/table.tsx (3)
  • ColumnLayout (15-15)
  • ColumnMeta (17-19)
  • TableContent (49-119)
apps/dashboard/src/components/data-table/common/cursor-pagination.tsx (1)
  • useCursorPaginationCache (6-49)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (29-44)
apps/dashboard/src/components/data-table/common/stable-value.tsx (1)
  • useStableValue (5-12)
packages/stack-shared/src/utils/promises.tsx (2)
  • runAsynchronously (343-366)
  • runAsynchronouslyWithAlert (312-328)
apps/dashboard/src/components/data-table/common/pagination.tsx (1)
  • PaginationControls (26-76)
apps/dashboard/src/components/user-dialogs.tsx (2)
  • DeleteUserDialog (6-31)
  • ImpersonateUserDialog (34-57)
apps/dashboard/src/components/payments/create-checkout-dialog.tsx (1)
  • CreateCheckoutDialog (24-81)
packages/stack-shared/src/utils/strings.tsx (1)
  • deindent (235-238)
apps/dashboard/src/components/data-table/common/url-query-state.tsx (1)
  • useUrlQueryState (23-93)
packages/stack-shared/src/utils/dates.tsx (1)
  • fromNow (28-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Security Check

@BilalG1 BilalG1 requested a review from N2D4 November 5, 2025 17:04
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Nov 5, 2025
Copy link
Contributor

@N2D4 N2D4 left a comment

Choose a reason for hiding this comment

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

Can we have the email status just be an icon on the Email column like in the opld table to save some space? Otherwise LGTM!

@github-actions github-actions bot assigned BilalG1 and unassigned N2D4 Nov 5, 2025
Copy link
Contributor

@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

♻️ Duplicate comments (1)
apps/dashboard/src/components/data-table/user-table.tsx (1)

202-204: Critical: Pagination cache wiped on every render.

The cursorPaginationCache object reference changes on every render (even though the functions inside are useCallback'd), causing this effect to fire continuously and clear all stored cursors. This breaks back/forward pagination.

The fix (as noted in past reviews) is to destructure resetCache outside the effect:

   const cursorPaginationCache = useCursorPaginationCache();
+  const { resetCache } = cursorPaginationCache;
 
   useEffect(() => {
-    cursorPaginationCache.resetCache();
-  }, [cursorPaginationCache, query.search, query.includeAnonymous, query.pageSize, query.signedUpOrder]);
+    resetCache();
+  }, [resetCache, query.search, query.includeAnonymous, query.pageSize, query.signedUpOrder]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f420cb and 389090e.

📒 Files selected for processing (1)
  • apps/dashboard/src/components/data-table/user-table.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/dashboard/src/components/data-table/user-table.tsx (11)
packages/template/src/lib/stack-app/users/index.ts (1)
  • ServerUser (415-415)
apps/dashboard/src/components/data-table/common/table.tsx (3)
  • ColumnLayout (15-15)
  • ColumnMeta (17-19)
  • TableContent (49-119)
apps/dashboard/src/components/data-table/common/cursor-pagination.tsx (1)
  • useCursorPaginationCache (6-49)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (29-44)
apps/dashboard/src/components/data-table/common/stable-value.tsx (1)
  • useStableValue (5-12)
packages/stack-shared/src/utils/promises.tsx (2)
  • runAsynchronously (343-366)
  • runAsynchronouslyWithAlert (312-328)
apps/dashboard/src/components/data-table/common/table-skeleton.tsx (1)
  • TableSkeleton (21-68)
apps/dashboard/src/components/user-dialogs.tsx (2)
  • DeleteUserDialog (6-31)
  • ImpersonateUserDialog (34-57)
apps/dashboard/src/components/payments/create-checkout-dialog.tsx (1)
  • CreateCheckoutDialog (24-81)
apps/dashboard/src/components/data-table/common/url-query-state.tsx (1)
  • useUrlQueryState (23-93)
packages/stack-shared/src/utils/dates.tsx (1)
  • fromNow (28-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: build (22.x)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: all-good
  • GitHub Check: Vercel Agent Review
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: setup-tests
🔇 Additional comments (14)
apps/dashboard/src/components/data-table/user-table.tsx (14)

1-52: LGTM! Clean import structure.

The imports are well-organized with proper type-only imports where needed.


53-119: LGTM! Well-structured type definitions.

The type system is comprehensive and the responsive column layout using clamp() is a good design choice.


120-172: LGTM! Robust validation and transformation logic.

The schema validation with Yup and custom transforms properly handles edge cases and invalid inputs.


235-283: LGTM! Accessible and user-friendly header.

The search input with debouncing, clear button, and proper ARIA labels follows best practices.


285-420: LGTM! Solid pagination and data fetching logic.

The use of useStableValue with fingerprinting, proper cursor caching, and prefetching provides excellent UX. The empty state with reset functionality is well-designed.


422-519: LGTM! Complete skeleton implementation.

The skeleton properly mirrors the table structure and the exhaustive switch with a default error case is good defensive programming.


521-572: LGTM! Type-safe column definitions with accessibility.

The modular column construction and accessible sort toggle with dynamic ARIA labels demonstrate good practices.


574-639: LGTM! Comprehensive user actions with proper error handling.

The action menu provides appropriate admin functionality with error handling via runAsynchronouslyWithAlert. The impersonation flow requiring manual console paste is a reasonable security measure.


641-670: LGTM! Comprehensive fingerprinting for memoization.

The fingerprint captures all relevant user fields, and normalizeDateValue handles edge cases defensively.


672-715: LGTM! Clean URL state management.

The sanitization ensures valid states and the serialization keeps URLs minimal by excluding default values.


717-726: LGTM! Simple utility functions.

These helpers serve their purpose well, and the ID formatting doesn't affect copying since the full ID is used in the copy action.


728-746: LGTM! Type-safe user data transformation.

The overloads correctly handle different return types, and the auth type derivation logic is clear and comprehensive.


748-797: LGTM! Reusable column definitions.

The generic type parameter and modular cell components make this function highly reusable for other table contexts.


799-834: LGTM! Well-structured cell components.

The cell components are modular and handle edge cases defensively. The date formatting with tooltips and the auth method badge mapping are particularly well done.

Also applies to: 860-925

@BilalG1 BilalG1 merged commit 35a56f7 into dev Nov 6, 2025
22 checks passed
@BilalG1 BilalG1 deleted the new-table-component branch November 6, 2025 01:40
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.

2 participants