-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Public folder analytics dashboards #3177
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
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded@steven-tey has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 19 minutes and 18 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (2)
WalkthroughThis PR extends analytics dashboards to support folder-based access alongside existing link-based dashboards. It introduces folder routing in dashboard API endpoints, adds folder-specific permissions validation, updates Prisma schema relations, and expands UI components (modals, filters, toggles) to accommodate both link and folder contexts. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/api/analytics/dashboard/route.ts (1)
125-143: Potential TypeError:linkmay be undefined in folder-only requests.When a folder dashboard is accessed without
domainandkeyparameters,linkremains undefined (see line 63 - it's only set when the folder has links AND domain/key are provided). The rate limiting code at line 135 unconditionally accesseslink.id, which will throw aTypeErrorin production.Apply this diff to handle folder-only requests:
// Rate limit in production if (process.env.NODE_ENV !== "development") { const ip = ipAddress(req); + const rateLimitKey = link?.id ?? folder?.id; + if (!rateLimitKey) { + throw new DubApiError({ + code: "bad_request", + message: "Unable to determine rate limit context", + }); + } // for demo links, we rate limit at: // - 15 requests per 10 seconds if groupBy is "count" // - 15 request per minute if groupBy is not "count" // for non-demo links, we rate limit at 10 requests per 10 seconds const { success } = await ratelimit( demoLink ? 15 : 10, !demoLink || groupBy === "count" ? "10 s" : "1 m", - ).limit(`analytics-dashboard:${link.id}:${ip}:${groupBy}`); + ).limit(`analytics-dashboard:${rateLimitKey}:${ip}:${groupBy}`);
🧹 Nitpick comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx (1)
88-94: Consider handling loading and error states for the share button.The
ShareDashboardButtonis only rendered when bothfolderIdandfolderexist. SinceuseFoldercan returnloadinganderrorstates, the button will be hidden during loading or after a fetch error, which could be confusing.Consider:
- Showing a loading state/skeleton while folder is loading
- Handling the error case (perhaps still allowing sharing with just the folderId, or showing an error message)
const { folderId } = useCurrentFolderId(); - const { folder } = useFolder({ folderId }); + const { folder, loading, error } = useFolder({ folderId }); return ( <> <LinkBuilder /> - {folderId && folder && <ShareDashboardButton folderId={folderId} />} + {folderId && !error && <ShareDashboardButton folderId={folderId} loading={loading} />} <div className="hidden sm:block"> <CreateLinkButton className="h-9" /> </div>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
apps/web/app/api/analytics/dashboard/route.ts(3 hunks)apps/web/app/api/dashboards/route.ts(2 hunks)apps/web/app/api/folders/[folderId]/dashboard/route.ts(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx(4 hunks)apps/web/app/app.dub.co/(share)/share/[dashboardId]/page.tsx(3 hunks)apps/web/lib/fetchers/get-dashboard.ts(1 hunks)apps/web/lib/swr/use-folder-link-count.ts(1 hunks)apps/web/lib/swr/use-folder.ts(2 hunks)apps/web/lib/zod/schemas/dashboard.ts(1 hunks)apps/web/ui/analytics/analytics-provider.tsx(5 hunks)apps/web/ui/analytics/index.tsx(1 hunks)apps/web/ui/analytics/share-button.tsx(1 hunks)apps/web/ui/analytics/toggle.tsx(2 hunks)apps/web/ui/analytics/use-analytics-filters.tsx(3 hunks)apps/web/ui/analytics/use-analytics-query.tsx(2 hunks)apps/web/ui/folders/folder-actions.tsx(6 hunks)apps/web/ui/folders/folder-icon.tsx(1 hunks)apps/web/ui/modals/share-dashboard-modal.tsx(10 hunks)packages/prisma/schema/dashboard.prisma(1 hunks)packages/prisma/schema/folder.prisma(1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
📚 Learning: 2025-06-16T19:21:23.506Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.
Applied to files:
apps/web/ui/analytics/use-analytics-query.tsxapps/web/ui/analytics/toggle.tsxapps/web/ui/analytics/use-analytics-filters.tsxapps/web/ui/analytics/analytics-provider.tsxapps/web/app/app.dub.co/(share)/share/[dashboardId]/page.tsx
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsxapps/web/ui/modals/share-dashboard-modal.tsxapps/web/app/app.dub.co/(share)/share/[dashboardId]/page.tsx
📚 Learning: 2025-09-24T16:10:37.349Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/ui/partners/partner-about.tsx:11-11
Timestamp: 2025-09-24T16:10:37.349Z
Learning: In the Dub codebase, the team prefers to import Icon as a runtime value from "dub/ui" and uses Icon as both a type and variable name in component props, even when this creates shadowing. This is their established pattern and should not be suggested for refactoring.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsxapps/web/ui/folders/folder-actions.tsxapps/web/ui/modals/share-dashboard-modal.tsx
📚 Learning: 2025-09-24T15:47:40.293Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx:152-157
Timestamp: 2025-09-24T15:47:40.293Z
Learning: The Button component from dub/ui automatically adds type="button" when an onClick prop is passed, preventing accidental form submissions without requiring explicit type specification. The implementation uses: type={onClick ? "button" : type}
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx
📚 Learning: 2025-09-24T15:47:40.293Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx:152-157
Timestamp: 2025-09-24T15:47:40.293Z
Learning: The Button component from dub/ui automatically adds type="button" when an onClick prop is passed, preventing accidental form submissions without requiring explicit type specification.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.
Applied to files:
apps/web/app/api/analytics/dashboard/route.ts
📚 Learning: 2025-10-17T08:18:19.278Z
Learnt from: devkiran
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-10-17T08:18:19.278Z
Learning: In the apps/web codebase, `@/lib/zod` should only be used for places that need OpenAPI extended zod schema. All other places should import from the standard `zod` package directly using `import { z } from "zod"`.
Applied to files:
apps/web/lib/zod/schemas/dashboard.tsapps/web/app/api/dashboards/route.ts
📚 Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.
Applied to files:
apps/web/ui/modals/share-dashboard-modal.tsx
🧬 Code graph analysis (15)
apps/web/ui/analytics/use-analytics-query.tsx (2)
apps/web/lib/analytics/constants.ts (1)
DUB_LINKS_ANALYTICS_INTERVAL(13-13)apps/web/lib/analytics/types.ts (1)
EventType(31-31)
apps/web/app/api/folders/[folderId]/dashboard/route.ts (5)
apps/web/app/api/dashboards/route.ts (1)
GET(16-27)apps/web/lib/auth/workspace.ts (1)
withWorkspace(55-488)apps/web/lib/folder/permissions.ts (1)
verifyFolderAccess(19-73)packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/zod/schemas/dashboard.ts (1)
dashboardSchema(4-15)
apps/web/ui/analytics/toggle.tsx (2)
apps/web/ui/folders/folder-icon.tsx (1)
FolderIcon(66-108)packages/utils/src/constants/misc.ts (1)
GOOGLE_FAVICON_URL(26-27)
apps/web/ui/analytics/use-analytics-filters.tsx (1)
packages/utils/src/constants/misc.ts (1)
GOOGLE_FAVICON_URL(26-27)
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx (3)
apps/web/lib/swr/use-folder.ts (1)
useFolder(6-42)apps/web/ui/modals/link-builder/index.tsx (1)
LinkBuilder(57-59)apps/web/ui/modals/share-dashboard-modal.tsx (1)
useShareDashboardModal(465-485)
apps/web/lib/swr/use-folder-link-count.ts (2)
apps/web/lib/swr/use-links-count.ts (1)
useLinksCount(10-53)apps/web/lib/types.ts (1)
FolderLinkCount(556-559)
apps/web/app/api/analytics/dashboard/route.ts (5)
apps/web/lib/api/errors.ts (1)
DubApiError(58-75)packages/prisma/index.ts (1)
prisma(3-9)packages/utils/src/constants/dub-domains.ts (1)
DUB_DEMO_LINKS(157-212)apps/web/lib/api/utils/assert-valid-date-range-for-plan.ts (1)
assertValidDateRangeForPlan(4-21)apps/web/lib/types.ts (1)
PlanProps(206-206)
apps/web/lib/zod/schemas/dashboard.ts (1)
apps/web/lib/zod/schemas/links.ts (1)
domainKeySchema(287-303)
apps/web/ui/analytics/index.tsx (1)
apps/web/ui/analytics/top-links.tsx (1)
TopLinks(56-259)
apps/web/ui/analytics/share-button.tsx (2)
apps/web/ui/analytics/analytics-provider.tsx (1)
AnalyticsContext(56-99)apps/web/ui/modals/share-dashboard-modal.tsx (1)
useShareDashboardModal(465-485)
apps/web/ui/folders/folder-actions.tsx (1)
apps/web/lib/folder/constants.ts (1)
unsortedLinks(44-49)
apps/web/ui/modals/share-dashboard-modal.tsx (5)
apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(7-48)apps/web/lib/swr/use-folder.ts (1)
useFolder(6-42)apps/web/lib/types.ts (1)
Folder(523-523)apps/web/lib/swr/use-folder-link-count.ts (1)
useFolderLinkCount(5-30)apps/web/ui/folders/folder-icon.tsx (1)
FolderIcon(66-108)
apps/web/ui/analytics/analytics-provider.tsx (3)
apps/web/lib/types.ts (1)
PlanProps(206-206)apps/web/ui/analytics/use-analytics-query.tsx (1)
useAnalyticsQuery(13-125)apps/web/lib/analytics/constants.ts (2)
DUB_PARTNERS_ANALYTICS_INTERVAL(14-14)DUB_LINKS_ANALYTICS_INTERVAL(13-13)
apps/web/app/api/dashboards/route.ts (6)
apps/web/lib/zod/schemas/dashboard.ts (2)
createDashboardQuerySchema(17-21)dashboardSchema(4-15)apps/web/lib/plan-capabilities.ts (1)
getPlanCapabilities(4-22)apps/web/lib/api/links/get-link-or-throw.ts (1)
getLinkOrThrow(21-101)apps/web/lib/folder/permissions.ts (1)
verifyFolderAccess(19-73)packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/api/create-id.ts (1)
createId(68-73)
apps/web/app/app.dub.co/(share)/share/[dashboardId]/page.tsx (1)
apps/web/lib/fetchers/get-dashboard.ts (1)
getDashboard(4-34)
🔇 Additional comments (28)
apps/web/ui/folders/folder-icon.tsx (1)
73-79: LGTM! Clean handling of optional folder prop.The conditional check
folder ? determineFolderIcon(folder) : "write"safely handles the optional prop and provides a sensible default.apps/web/app/app.dub.co/(share)/share/[dashboardId]/page.tsx (2)
72-84: LGTM! Conditional props spreading is well-structured.The non-null assertions on
data.folder!are safe given the early return at line 38 ensures at least one oflinkorfolderexists when this code is reached.
19-22: Code logic correctly handles dashboard with both link and folder.The condition
!data?.link && !data?.folderappropriately returns early only when both are missing. The Prisma schema allows bothlinkIdandfolderIdto be set on the same Dashboard, and the code correctly prioritizeslinkoverfoldervia ternary operators when both exist. The non-null assertions ondata.folderare safe because they only execute after the condition passes. No changes needed.apps/web/ui/folders/folder-actions.tsx (3)
60-61: LGTM! ShareDashboardModal integration.The hook is correctly initialized with the folder ID.
75-92: LGTM! Keyboard shortcut for sharing analytics.The "s" shortcut is properly guarded with the same conditions as the UI button (
!unsortedLinks && canUpdateFolder).
186-198: LGTM! Share Analytics action.The button is properly guarded with permission checks and uses appropriate iconography.
apps/web/lib/fetchers/get-dashboard.ts (1)
21-26: LGTM! Clean extension of dashboard query.The folder selection follows the established pattern and selects only the necessary fields.
apps/web/ui/analytics/index.tsx (1)
67-67: LGTM! Correct visibility logic for folder dashboards.Changing from
!dashboardPropsto!dashboardProps?.keyensuresTopLinksrenders for folder-based dashboards (which havefolderIdbut nokey), while still hiding it for link-specific dashboards.apps/web/ui/analytics/use-analytics-query.tsx (1)
17-17: LGTM! Clean fallback chain for folder context.The
defaultFolderIdparameter follows the established pattern used bydefaultKeyand provides a sensible fallback whenfolderIdis not present in search params. The resolution order (search params → default → undefined) is clear and appropriate.Also applies to: 23-23, 38-39
packages/prisma/schema/folder.prisma (1)
31-31: LGTM! Proper bidirectional relationship.The optional
dashboardrelation correctly establishes the Folder side of the one-to-one relationship with Dashboard, complementing thefolderrelation added to the Dashboard model.apps/web/lib/swr/use-folder.ts (1)
26-26: LGTM! Useful error exposure.Exposing the
errorfield from SWR enables consuming components to handle folder fetch errors appropriately. This aligns with the broader folder-aware dashboard flows introduced in this PR.Also applies to: 40-40
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx (1)
102-120: LGTM! Clean integration of folder dashboard sharing.The
ShareDashboardButtoncomponent properly integrates folder-based dashboard sharing with responsive behavior usinguseMediaQuery.apps/web/ui/analytics/toggle.tsx (2)
201-221: LGTM! Preserved existing link dashboard display.The link branch maintains the existing display logic for domain/key-based dashboards.
194-200: No action needed – type safety is enforced by the discriminated union.The
AnalyticsDashboardPropstype uses a discriminated union that guaranteesfolderNameis always defined (as a required string) wheneverfolderIdis present. The current code is type-safe and renders correctly without a fallback.packages/prisma/schema/dashboard.prisma (1)
7-8: LGTM! Proper one-to-one relationship with appropriate constraints.The folder relation correctly establishes the Dashboard side of the bidirectional relationship. The
@uniqueconstraint onfolderIdand cascade options (onUpdate: Cascade, onDelete: Cascade) ensure proper one-to-one semantics and referential integrity.apps/web/app/api/folders/[folderId]/dashboard/route.ts (1)
8-32: LGTM! Follows established patterns with proper permission checks.The endpoint correctly implements folder dashboard retrieval with appropriate access verification. Note that
folders.readpermission is checked both in thewithWorkspacewrapper (line 30) and withinverifyFolderAccess(line 16), but this redundancy is harmless and provides defense in depth.apps/web/lib/swr/use-folder-link-count.ts (1)
5-10: LGTM! Useful conditional fetching enhancement.The optional
enabledparameter (defaulting totrue) allows consumers to conditionally disable the link count fetch while preserving backward compatibility. This aligns with theenabledparameter pattern used inuseLinksCount.Also applies to: 13-13
apps/web/lib/zod/schemas/dashboard.ts (1)
17-21: Well-structured union schema for dashboard creation.The discriminated union pattern using
domainKeySchema.or(z.object({ folderId: z.string() }))cleanly supports both link-based and folder-based dashboard creation paths.apps/web/ui/analytics/share-button.tsx (1)
7-21: LGTM!The conditional rendering logic correctly handles both link-based and folder-based sharing scenarios with proper permission checks. The early return for
partnerPagesimplifies the component flow.apps/web/app/api/dashboards/route.ts (1)
92-97: Verify the intended scope of publicStats update for folder dashboards.This
updateManysetspublicStats = truefor all links in the folder. This is a potentially broad update that could affect many links. Ensure this is the intended behavior and consider documenting this side effect for users.apps/web/ui/analytics/analytics-provider.tsx (2)
36-54: Well-designed discriminated union type for dashboard props.The
AnalyticsDashboardPropstype correctly usesneverconstraints to enforce mutual exclusivity between link-based (domain,key,url) and folder-based (folderId,folderName) variants. This provides strong type safety at the component boundary.
203-219: LGTM!The
useAnalyticsQueryintegration properly supports folder-based filtering by acceptingdefaultFolderIdfrom dashboard props and returningfolderIdfor context propagation.apps/web/ui/modals/share-dashboard-modal.tsx (4)
33-48: LGTM!The discriminated union types for
ShareDashboardModalInnerPropsandShareDashboardModalPropsare well-structured, ensuring type safety when switching between link-based and folder-based dashboard sharing.
78-88: Folder-first priority in dashboard fetching.The SWR key logic correctly prioritizes
folder.idoverlink.id, ensuring folder dashboards are fetched when a folder context exists.
408-463: Well-implemented FolderCard component.The component properly handles loading states, error states, and the "mega" folder edge case. The use of
useFolderLinkCountwith properenabledconditional prevents unnecessary API calls.
267-276: Good refactor to Controller for form switches.Using
Controllerfrom react-hook-form for theshowConversionsanddoIndexswitches is appropriate since these are controlled components that need explicit state management via thefnprop.apps/web/ui/analytics/use-analytics-filters.tsx (2)
372-391: Domain filter extraction and favicon reuse look goodCentralizing
DomainFilterItemand reusingGOOGLE_FAVICON_URLfrom@dub/utilskeeps the behavior identical to the prior inline implementation while removing duplication. The options mapping andgetFilterOptionTotalusage remain consistent with the existingdomainsshape, so this refactor looks safe.
427-430: Double‑check dashboard filter set for link vs. folder dashboardsWith the new branch:
dashboardProps && dashboardProps.key→ noDomainorLinkfilters.dashboardProps && !dashboardProps.key(likely folder dashboards) → onlyDomainFilterItem+LinkFilterItem, without thefolderId/tagIds/Link typefilters that exist in the non‑dashboard folder view.This may be exactly what you want (folder context implied by the dashboard, so you only expose per‑link/domain refinement), but it does mean:
- Any active
folderIdfilter coming from the URL has no explicit filter config in this branch.- Folder dashboards won’t expose the Folder selector itself, unlike the generic analytics view.
Can you confirm this UX is intentional for both public and internal folder dashboards, and that
Filter.Selectbehaves correctly whenactiveFilterscontainsfolderIdbut there is no corresponding visible filter item?Also applies to: 527-528
There was a problem hiding this 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
🧹 Nitpick comments (1)
apps/web/app/api/analytics/dashboard/route.ts (1)
139-139: Optional chaining is defensively safe.The expression
folder?.id || link?.iduses optional chaining, though based on the control flow, one of these values should always be defined (folder errors at line 56-61 if missing dashboard; link errors at line 99-104 if missing dashboard, or is set from demoLink at line 74-77).This defensive approach is acceptable, though you could replace it with non-null assertions if you prefer to enforce the invariant.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/api/analytics/dashboard/route.ts(3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
📚 Learning: 2025-06-18T20:26:25.177Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.
Applied to files:
apps/web/app/api/analytics/dashboard/route.ts
🧬 Code graph analysis (1)
apps/web/app/api/analytics/dashboard/route.ts (4)
apps/web/lib/api/errors.ts (1)
DubApiError(58-75)packages/utils/src/constants/dub-domains.ts (1)
DUB_DEMO_LINKS(157-212)apps/web/lib/api/utils/assert-valid-date-range-for-plan.ts (1)
assertValidDateRangeForPlan(4-21)apps/web/lib/exceeded-limit-error.ts (1)
exceededLimitError(4-29)
⏰ 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). (1)
- GitHub Check: build
🔇 Additional comments (5)
apps/web/app/api/analytics/dashboard/route.ts (5)
21-29: LGTM! Parameter validation correctly handles both contexts.The validation logic properly requires either domain/key (for link-based analytics) or folderId (for folder-based analytics), with a clear error message for missing parameters.
66-108: Link branch logic is correct.The code properly handles both demo links (public, no workspace) and regular links (with dashboard validation). Demo links intentionally skip workspace-related checks.
Note: The previous review issues regarding missing
idandcreatedAtin the project select have been correctly fixed (lines 89, 93).
110-116: Date range validation correctly resolved.The validation now properly uses
workspace?.createdAtfordataAvailableFrom. The previous review issue about missingcreatedAtin project selects has been correctly fixed (lines 49, 93 addcreatedAt: true).
118-127: LGTM! Usage limit check properly handles demo links.The conditional check ensures demo links (where
workspaceis undefined) skip usage validation while enforcing limits for workspace-backed analytics.
149-155: Response construction is correct.The conditional spreads properly include
workspaceId,folderId, andlinkIdbased on context. The previous review issue about missingworkspace.idhas been correctly fixed (lines 45, 89 addid: trueto project selects).
There was a problem hiding this 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/web/ui/folders/folder-actions.tsx (1)
169-184: Button nested inside Link (previously flagged).This accessibility issue was previously identified and deferred. Nesting a
<button>inside an<a>creates invalid HTML and can cause accessibility problems. However, consistent with your incremental approach, this refactoring remains out of scope for the folder analytics feature.Based on learnings: TWilson023 prefers to keep refactoring separate from feature PRs when the code pattern was existing and is being preserved during restructuring.
🧹 Nitpick comments (2)
apps/web/ui/folders/simple-folder-card.tsx (1)
1-34: Solid, type-safe folder card; only minor optional polishThe component looks correct and nicely reuses
FolderIconwith a responsive, truncating layout. One optional improvement would be to pull the props definition into a named type or interface (e.g.type SimpleFolderCardProps = { folder: FolderSummary; className?: string }) and reuse it where needed for consistency. Also, double‑check that importingcnfrom"@dub/utils/src"matches how other components in this repo import it (some projects standardize on"@dub/utils").apps/web/app/app.dub.co/(share)/share/[dashboardId]/page.tsx (1)
7-7: Preferimport type { Metadata }for type-only usage
Metadatais only used as a TypeScript return type ingenerateMetadata, so importing it as a type avoids an unnecessary runtime import and better matches Next’s recommended pattern.You can simplify to:
-import { Metadata } from "next"; +import type { Metadata } from "next";Please confirm this matches the current Next 15+ docs and your project’s TS import conventions.
Also applies to: 15-15
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/app/api/og/analytics/route.tsx(4 hunks)apps/web/app/app.dub.co/(share)/share/[dashboardId]/page.tsx(3 hunks)apps/web/lib/fetchers/get-dashboard.ts(1 hunks)apps/web/ui/folders/folder-actions.tsx(6 hunks)apps/web/ui/folders/simple-folder-card.tsx(1 hunks)apps/web/ui/modals/set-default-folder-modal.tsx(3 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
📚 Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.
Applied to files:
apps/web/ui/modals/set-default-folder-modal.tsx
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/app/app.dub.co/(share)/share/[dashboardId]/page.tsxapps/web/ui/folders/folder-actions.tsx
📚 Learning: 2025-06-16T19:21:23.506Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.
Applied to files:
apps/web/app/app.dub.co/(share)/share/[dashboardId]/page.tsx
📚 Learning: 2025-09-24T15:50:16.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
Applied to files:
apps/web/ui/folders/folder-actions.tsx
📚 Learning: 2025-11-19T17:26:51.932Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Applied to files:
apps/web/ui/folders/folder-actions.tsx
📚 Learning: 2025-09-24T16:10:37.349Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/ui/partners/partner-about.tsx:11-11
Timestamp: 2025-09-24T16:10:37.349Z
Learning: In the Dub codebase, the team prefers to import Icon as a runtime value from "dub/ui" and uses Icon as both a type and variable name in component props, even when this creates shadowing. This is their established pattern and should not be suggested for refactoring.
Applied to files:
apps/web/ui/folders/folder-actions.tsx
🧬 Code graph analysis (5)
apps/web/ui/folders/simple-folder-card.tsx (2)
apps/web/lib/types.ts (1)
FolderSummary(539-542)apps/web/ui/folders/folder-icon.tsx (1)
FolderIcon(66-108)
apps/web/ui/modals/set-default-folder-modal.tsx (1)
apps/web/ui/folders/simple-folder-card.tsx (1)
SimpleFolderCard(5-34)
apps/web/app/app.dub.co/(share)/share/[dashboardId]/page.tsx (1)
apps/web/lib/fetchers/get-dashboard.ts (1)
getDashboard(4-35)
apps/web/ui/folders/folder-actions.tsx (2)
apps/web/ui/modals/share-dashboard-modal.tsx (1)
useShareDashboardModal(465-485)apps/web/lib/folder/constants.ts (1)
unsortedLinks(44-49)
apps/web/app/api/og/analytics/route.tsx (3)
apps/web/lib/types.ts (1)
Folder(523-523)apps/web/lib/analytics/utils/get-start-end-dates.ts (1)
getStartEndDates(4-46)packages/utils/src/constants/misc.ts (1)
GOOGLE_FAVICON_URL(26-27)
⏰ 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). (1)
- GitHub Check: build
🔇 Additional comments (12)
apps/web/ui/modals/set-default-folder-modal.tsx (3)
15-15: Good reuse of SimpleFolderCard for consistent folder visualsImporting and using
SimpleFolderCardhere keeps the folder preview consistent with other surfaces (icon, name, description) and avoids duplicating layout logic. No issues from a typing or composition standpoint.
56-79: Modal body refactor improves clarity and scannabilityThe split header/body plus the descriptive copy, help link, and scrollable
SimpleFolderCardarea make the default-folder action much clearer. The constrainedmax-hwithoverflow-y-autois a nice touch to keep the modal from growing too tall. Just ensure the help-center URL remains stable long term, since it’s now a more prominent part of the UX.
81-102: Footer actions and Cancel behavior look correctAdding an explicit secondary “Cancel” button and aligning button sizing (
h-8 w-fit px-3) make the footer clearer and more consistent. Closing the modal on Cancel without mutating state matches the expected “no change on dismiss” pattern from prior learnings, and the primary button still correctly wires throughtoast.promiseandisPending.apps/web/ui/folders/folder-actions.tsx (3)
11-11: LGTM: ShareDashboardModal integration follows established patterns.The modal hook initialization and rendering align with existing modal implementations in this component (RenameFolderModal, DeleteFolderModal, DefaultFolderModal).
Also applies to: 24-24, 60-62, 130-130
76-76: LGTM: Keyboard shortcut properly implemented.The 's' shortcut is correctly guarded with permission checks and doesn't conflict with existing shortcuts.
Also applies to: 89-92
136-165: LGTM: Popover restructure with proper permission guards.The reorganized action groups are well-structured:
- Edit/Delete section correctly restricted to non-unsorted folders with appropriate disabled states
- Share Analytics properly guarded with
canUpdateFoldercheck- Copy Folder ID visual feedback (CircleCheck/Copy toggle) enhances UX
- All permission checks and conditional rendering logic are correct
Also applies to: 186-198, 201-234
apps/web/lib/fetchers/get-dashboard.ts (1)
16-16: Prisma select additions forlinkandfolderlook correct and are safely additiveIncluding
link.idand selectingfolder.id/folder.namealigns with the new dashboard consumers and doesn’t change existing behavior, since you’re only expanding the selected shape.Also applies to: 22-27
apps/web/app/api/og/analytics/route.tsx (5)
2-2: LGTM!The import of
FolderandLinktypes from@dub/prisma/clientprovides proper type safety for the data fetching logic.
20-27: LGTM with a minor note.The validation ensures at least one identifier is provided. Note that this logic allows both
linkIdandfolderIdto be present simultaneously. If mutual exclusivity is desired, consider adding a check for that scenario.
111-148: LGTM!The conditional rendering logic correctly distinguishes between folder and link contexts:
- Folder context: displays a blue folder icon with the folder name
- Link context: displays the link's favicon and formatted domain/key
The implementation is clear and handles both cases appropriately.
167-167: LGTM!The label now correctly reflects the 30-day interval configured on line 75, improving consistency between the UI and the actual data being displayed.
78-89: Verification of Tinybird API endpoint version requires external access.The review request cannot be fully verified from codebase analysis alone. The code shows the v3_timeseries endpoint is currently in use with proper error handling and response structure expectations (array with
clicksproperty). However, confirming endpoint stability, parameter compatibility, and production readiness requires access to Tinybird API documentation or testing credentials, which are not available in the repository.The parameters (
event,workspaceId,folderId,linkId,start,end,granularity) are correctly included in the query, and the response is defensively handled with.catch(() => [])fallback.
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.