feat(dashboard): Persist pagination in list pages fixes NV-6997#9702
feat(dashboard): Persist pagination in list pages fixes NV-6997#9702
Conversation
Introduce a hook to manage and persist table page sizes, improving user experience by remembering their preferences across sessions. Co-authored-by: dima <dima@novu.co>
|
Cursor Agent can help with this pull request. Just |
✅ Deploy Preview for dashboard-v2-novu-staging ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
WalkthroughThis pull request introduces a persistent page size mechanism across multiple dashboard tables. A new hook 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
apps/dashboard/src/components/layouts/layout-list.tsx (1)
9-13: Move constant definition after all imports.The
LAYOUTS_TABLE_IDconstant is defined between import statements (lines 11-12), breaking import grouping conventions. Move it after all imports for cleaner organization.Additionally, this constant is duplicated in
use-layouts-url-state.tsx. Consider exporting it from a single location to avoid potential drift.Suggested organization
import { LayoutsFilter, LayoutsSortableColumn, LayoutsUrlState, useLayoutsUrlState, } from '@/components/layouts/hooks/use-layouts-url-state'; import { usePersistedPageSize } from '@/hooks/use-persisted-page-size'; - -const LAYOUTS_TABLE_ID = 'layouts-list'; - import { LayoutListBlank } from '@/components/layouts/layout-list-blank'; import { LayoutRow, LayoutRowSkeleton } from '@/components/layouts/layout-row'; +// ... other imports ... + +const LAYOUTS_TABLE_ID = 'layouts-list';Or better, export the constant from
use-layouts-url-state.tsxand import it here.apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx (1)
4-6: Consider consolidating theLAYOUTS_TABLE_IDconstant.This constant is also defined in
layout-list.tsx. Having the same constant in two files risks them drifting apart. Consider exporting it from this file solayout-list.tsxcan import it:-const LAYOUTS_TABLE_ID = 'layouts-list'; +export const LAYOUTS_TABLE_ID = 'layouts-list';Also applies to: 18-24
apps/dashboard/src/hooks/use-persisted-page-size.ts (1)
36-43: Consider simplifying the useEffect dependency.The
useEffectincludespageSizein its dependency array, but its purpose is primarily to sync state whentableIdchanges. IncludingpageSizecauses the effect to run on every page size change, even though the condition prevents unnecessary state updates.♻️ Optional: Remove pageSize from dependencies
useEffect(() => { const map = getPageSizeMap(); const stored = map[tableId]; - if (stored !== undefined && stored !== pageSize) { + if (stored !== undefined) { setPageSizeState(stored); } - }, [tableId, pageSize]); + }, [tableId]);This ensures the effect only runs when
tableIdchanges, which is the primary use case. WhensetPageSizeis called, it updates both state and storage synchronously, so no additional sync is needed.
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
apps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/components/contexts/hooks/use-contexts-url-state.tsapps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.tsapps/dashboard/src/components/topics/hooks/use-topics-url-state.tsapps/dashboard/src/hooks/use-logs-url-state.tsapps/dashboard/src/hooks/use-persisted-page-size.tsapps/dashboard/src/pages/workflows.tsx
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use descriptive variable names with auxiliary verbs (isLoading, hasError)
Add blank lines before return statements
Import motion components from 'motion/react' instead of 'motion-react'
**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use functional and declarative programming patterns; avoid classes
Prefer iteration and modularization over code duplication, minimize code duplication as possible
Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
Structure files: exported component, subcomponents, helpers, static content, types
Don't leave comments in code, unless they explain something complex and not trivial
Don't use nested ternaries
Favor named exports for components
Use TypeScript for all code; prefer interfaces over types
In front end code, use types over interfaces
Use functional components with TypeScript types
Use the "function" keyword for pure functions
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
Add blank lines before return statements
When importing "motion-react" package, import it from "motion/react"
Files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/contexts/hooks/use-contexts-url-state.tsapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.tsapps/dashboard/src/pages/workflows.tsxapps/dashboard/src/components/topics/hooks/use-topics-url-state.tsapps/dashboard/src/hooks/use-persisted-page-size.tsapps/dashboard/src/hooks/use-logs-url-state.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use functional and declarative programming patterns; avoid classes
Files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/contexts/hooks/use-contexts-url-state.tsapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.tsapps/dashboard/src/pages/workflows.tsxapps/dashboard/src/components/topics/hooks/use-topics-url-state.tsapps/dashboard/src/hooks/use-persisted-page-size.tsapps/dashboard/src/hooks/use-logs-url-state.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
Favor named exports for components
Files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/contexts/hooks/use-contexts-url-state.tsapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.tsapps/dashboard/src/pages/workflows.tsxapps/dashboard/src/components/topics/hooks/use-topics-url-state.tsapps/dashboard/src/hooks/use-persisted-page-size.tsapps/dashboard/src/hooks/use-logs-url-state.ts
apps/{dashboard,web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Prefer types over interfaces in frontend code
Files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/contexts/hooks/use-contexts-url-state.tsapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.tsapps/dashboard/src/pages/workflows.tsxapps/dashboard/src/components/topics/hooks/use-topics-url-state.tsapps/dashboard/src/hooks/use-persisted-page-size.tsapps/dashboard/src/hooks/use-logs-url-state.ts
apps/dashboard/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use lowercase with dashes for directories and files in dashboard (e.g., components/auth-wizard)
Files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/contexts/hooks/use-contexts-url-state.tsapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.tsapps/dashboard/src/pages/workflows.tsxapps/dashboard/src/components/topics/hooks/use-topics-url-state.tsapps/dashboard/src/hooks/use-persisted-page-size.tsapps/dashboard/src/hooks/use-logs-url-state.ts
apps/dashboard/**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
Favor named exports for components in dashboard
Files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/contexts/hooks/use-contexts-url-state.tsapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.tsapps/dashboard/src/pages/workflows.tsxapps/dashboard/src/components/topics/hooks/use-topics-url-state.tsapps/dashboard/src/hooks/use-persisted-page-size.tsapps/dashboard/src/hooks/use-logs-url-state.ts
apps/dashboard/**/*
📄 CodeRabbit inference engine (.cursor/rules/figma.mdc)
apps/dashboard/**/*: Use the assets endpoint from Figma Dev Mode MCP Server to serve image and SVG assets
If the Figma Dev Mode MCP Server returns a localhost source for an image or SVG, use that image or SVG source directly without modification
Do not import or add new icon packages; all assets should come from the Figma payload
Do not use or create placeholders if a localhost source is provided by the Figma Dev Mode MCP ServerUse lowercase with dashes for directory and file names (e.g., components/auth-wizard)
Files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/contexts/hooks/use-contexts-url-state.tsapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.tsapps/dashboard/src/pages/workflows.tsxapps/dashboard/src/components/topics/hooks/use-topics-url-state.tsapps/dashboard/src/hooks/use-persisted-page-size.tsapps/dashboard/src/hooks/use-logs-url-state.ts
apps/dashboard/**/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)
Favor named exports for components
Files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/contexts/hooks/use-contexts-url-state.tsapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.tsapps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
**
📄 CodeRabbit inference engine (.cursor/rules/novu.mdc)
Use lowercase with dashes for directories and files (e.g., components/auth-wizard)
Files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/contexts/hooks/use-contexts-url-state.tsapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.tsapps/dashboard/src/pages/workflows.tsxapps/dashboard/src/components/topics/hooks/use-topics-url-state.tsapps/dashboard/src/hooks/use-persisted-page-size.tsapps/dashboard/src/hooks/use-logs-url-state.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/novu.mdc)
**/*.tsx: Use declarative JSX
Wrap client components in Suspense with fallback
Use dynamic loading for non-critical components
Files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsxapps/dashboard/src/components/layouts/layout-list.tsxapps/dashboard/src/components/activity/activity-table.tsxapps/dashboard/src/pages/workflows.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-25T11:29:52.304Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:29:52.304Z
Learning: Applies to **/*.{ts,tsx} : Import motion components from 'motion/react' instead of 'motion-react'
Applied to files:
apps/dashboard/src/components/http-logs/logs-table.tsxapps/dashboard/src/components/activity/activity-table.tsx
📚 Learning: 2026-01-07T13:09:45.904Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: .cursor/rules/novu.mdc:0-0
Timestamp: 2026-01-07T13:09:45.904Z
Learning: Applies to **/*.{ts,tsx} : When importing "motion-react" package, import it from "motion/react"
Applied to files:
apps/dashboard/src/components/http-logs/logs-table.tsx
🧬 Code graph analysis (6)
apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts (1)
apps/dashboard/src/hooks/use-persisted-page-size.ts (2)
getPersistedPageSize(61-65)usePersistedPageSize(27-59)
apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx (1)
apps/dashboard/src/hooks/use-persisted-page-size.ts (1)
getPersistedPageSize(61-65)
apps/dashboard/src/components/activity/activity-table.tsx (1)
apps/dashboard/src/hooks/use-persisted-page-size.ts (1)
usePersistedPageSize(27-59)
apps/dashboard/src/pages/workflows.tsx (1)
apps/dashboard/src/hooks/use-persisted-page-size.ts (2)
getPersistedPageSize(61-65)usePersistedPageSize(27-59)
apps/dashboard/src/hooks/use-persisted-page-size.ts (1)
apps/dashboard/src/utils/local-storage.ts (2)
loadFromStorage(19-43)saveToStorage(5-17)
apps/dashboard/src/hooks/use-logs-url-state.ts (1)
apps/dashboard/src/hooks/use-persisted-page-size.ts (2)
usePersistedPageSize(27-59)getPersistedPageSize(61-65)
⏰ 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). (6)
- GitHub Check: E2E test API / Test E2E
- GitHub Check: Redirect rules - dashboard-v2-novu-staging
- GitHub Check: Header rules - dashboard-v2-novu-staging
- GitHub Check: Pages changed - dashboard-v2-novu-staging
- GitHub Check: Analyze (javascript)
- GitHub Check: Analyze (typescript)
🔇 Additional comments (14)
apps/dashboard/src/components/layouts/layout-list.tsx (1)
133-136: LGTM!The
usePersistedPageSizehook is correctly integrated. ThehandlePageSizeChangefunction properly persists the new page size before updating filters and correctly resets offset to 0 to avoid showing an empty page.Also applies to: 165-171
apps/dashboard/src/components/http-logs/logs-table.tsx (1)
30-43: LGTM!Clean refactor that properly delegates page size management to the
useLogsUrlStatehook. The component is now simpler and follows better separation of concerns.apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx (1)
23-23: LGTM on the persistence integration.The
getPersistedPageSizecall correctly provides the persisted page size as the default limit, falling back to 10 if no value is stored.apps/dashboard/src/pages/workflows.tsx (2)
43-44: LGTM!The persisted page size integration follows the established pattern correctly:
WORKFLOWS_TABLE_IDconstant for consistent identificationgetPersistedPageSizeat module level for defaultusePersistedPageSizehook in component for updatesAlso applies to: 51-51, 57-60
328-336: LGTM!The page size change handler correctly persists the new size and resets pagination by removing the offset parameter. This keeps the URL clean while ensuring users start from page 1 after changing page size.
apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts (2)
4-6: LGTM!The persisted page size integration is correctly implemented:
- Module-level
getPersistedPageSizefor default limit- Hook-level
usePersistedPageSizefor state management- Consistent
CONTEXTS_TABLE_IDidentifierAlso applies to: 32-32, 38-41
174-186: LGTM!The
handlePageSizeChangecorrectly handles cursor-based pagination by clearing bothafterandbeforecursors when page size changes, ensuring users return to the first page. The dependency array properly includessetPersistedPageSize.apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts (1)
5-10: LGTM!The persisted page size integration is well-implemented. The pattern correctly:
- Initializes the default limit from persisted storage
- Persists the page size before updating URL params
- Includes
setPersistedPageSizein the dependency arrayAlso applies to: 29-29, 59-62, 241-250
apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts (1)
4-7: LGTM!Consistent implementation with the subscribers hook. The page size persistence is correctly wired up with proper initialization, setter usage, and dependency tracking.
Also applies to: 34-34, 40-43, 184-198
apps/dashboard/src/hooks/use-logs-url-state.ts (1)
5-9: LGTM!The logs URL state hook correctly integrates persisted page sizes with a sensible default of 20 for logs. The
handlePageSizeChangeimplementation properly:
- Persists the new size
- Updates the URL limit parameter
- Resets pagination by deleting the page param
Also applies to: 25-25, 37-40, 66-69, 93-104
apps/dashboard/src/components/activity/activity-table.tsx (2)
19-19: LGTM!The persisted page size integration is correctly implemented with the
ACTIVITY_TABLE_IDconstant and theusePersistedPageSizehook.Also applies to: 25-26, 49-52
121-128: Feature-flag-aware page size change handling looks good.The branching logic correctly handles both cursor-based (new) and page-based (legacy) pagination when the page size changes, ensuring users are navigated to the first page in both cases.
apps/dashboard/src/hooks/use-persisted-page-size.ts (2)
1-15: LGTM!Clean setup with appropriate storage key constants and helper functions. The
PageSizeMaptype alias and helper functions follow the coding guidelines (types over interfaces, functional patterns).
61-65: LGTM!The
getPersistedPageSizehelper provides a clean synchronous API for initializing default values outside of React components or during module initialization.
What changed? Why was the change needed?
This PR introduces a new
usePersistedPageSizehook to store user-selected pagination page sizes inlocalStorage. This hook manages a single map (novu-page-sizes) where eachtableIdis mapped to its selected page size.The change was needed to persist user-selected pagination values across sessions for various paginated lists in the dashboard, improving user experience by remembering their preferences.
Affected components now using this persistence:
Relevant link: NV-6997
Screenshots
N/A
Expand for optional sections
Related enterprise PR
N/A
Special notes for your reviewer
N/A
Linear Issue: NV-6997