Skip to content

Add /links prefix to analytics, events, customers, Program Events page - #4101

Merged
steven-tey merged 3 commits into
mainfrom
add-links-prefix
Jul 1, 2026
Merged

Add /links prefix to analytics, events, customers, Program Events page#4101
steven-tey merged 3 commits into
mainfrom
add-links-prefix

Conversation

@steven-tey

@steven-tey steven-tey commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added a dedicated Program → Events page with Suspense loading and an Events title/link for consistent navigation.
    • Introduced product-aware analytics/events behavior across the UI, so navigation reflects whether you’re in Program or Short Links.
  • Bug Fixes

    • Fixed redirects and sidebar navigation so Analytics, Events, and Customers open under the correct product path.
    • Prevented analytics/events data requests from running until the correct product context is available, improving stability and page behavior.

@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview Jul 1, 2026 6:08am

Request Review

@steven-tey

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds product-aware analytics routing, query construction, navigation, and events-page wiring, including a new program events page and updated links for the short-links and program dashboards.

Changes

Product-scoped routing and analytics

Layer / File(s) Summary
Current product hook
packages/ui/src/hooks/use-current-product.ts, packages/ui/src/hooks/index.ts
New hook derives "links" or "program" from the pathname and is re-exported from the hooks barrel.
Analytics query and toggle links
apps/web/ui/analytics/use-analytics-query.tsx, apps/web/ui/analytics/toggle.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/program-analytics-shell.tsx
useAnalyticsQuery conditionally appends programId, AnalyticsToggle reads the current product for link paths, and ProgramAnalyticsShell updates its query string and events view link parameters.
Sidebar navigation and middleware redirects
apps/web/ui/layout/sidebar/app-sidebar-nav.tsx, apps/web/lib/middleware/utils/app-redirect.ts
The sidebar marks analytics active for related events routes, fixes Short Links hrefs to use /links/, and middleware redirects /[slug] and related routes through getWorkspaceProduct(slug).
Events data fetching
apps/web/ui/analytics/events/events-table.tsx, apps/web/ui/analytics/events/events-tabs.tsx
EventsTable and EventsTabs read the current product and only build SWR keys when it is available.
Events pages
apps/web/app/(ee)/admin.dub.co/(dashboard)/events/page.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/events/page.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/events/page.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/events/title-link.tsx
The admin and links events pages update the AnalyticsClient import path, and a new program events page with a title-link component renders AnalyticsClient, EventsProvider, and Events under the program route.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers

pepeladeira

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a /links prefix for analytics, events, customers, and the Program Events page.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-links-prefix

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/lib/middleware/utils/app-redirect.ts (1)

27-40: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the RESERVED_SLUGS guard here.
APP_REDIRECTS only covers a few exceptions; the generic workspace redirect still matches every other single-segment reserved path and turns it into /<slug>/<product> via getWorkspaceProduct, which falls back to links for unknown slugs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/middleware/utils/app-redirect.ts` around lines 27 - 40, Restore
the RESERVED_SLUGS guard in appRedirect so reserved single-segment paths do not
fall through to the generic workspace redirect logic. Update appRedirect to
check the slug before calling getWorkspaceProduct, and short-circuit for any
reserved slug that is not explicitly handled by APP_REDIRECTS. Keep the fix
local to appRedirect and use the existing RESERVED_SLUGS and getWorkspaceProduct
symbols to place the guard in the current matching flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/events/page.tsx:
- Around line 9-21: The page title is nesting a second heading because
PageContent already renders the title inside an h1. Update
ProgramEventsPageTitleLink so it returns plain text or a span-style breadcrumb
label instead of its own h1, and keep WorkspaceProgramEventsPage passing that
component into PageContent as the single page heading.

In `@apps/web/ui/analytics/use-analytics-query.tsx`:
- Around line 92-93: The query builder in useAnalyticsQuery is allowing
programId to be included even when defaultProgramId is undefined, which results
in a literal programId=undefined in the request. Update the object spread near
the URLSearchParams call so it only adds programId when both product ===
"program" and defaultProgramId is defined, using the existing
useCurrentProduct/defaultProgramId flow to guard the param before serialization.

In `@apps/web/ui/layout/sidebar/app-sidebar-nav.tsx`:
- Around line 335-341: The pathname checks in app-sidebar-nav are still using
the old analytics/events routes, so the queryString omission never triggers.
Update the ternary comparisons in the sidebar item definitions for Analytics and
Events to match the new /links/ paths used in their href values, keeping the
logic in the same sidebar nav builder where pathname and queryString are
assembled.

In `@packages/ui/src/hooks/use-current-product.ts`:
- Around line 1-18: The `useCurrentProduct` hook is leaving a stale product
value when `pathname` no longer points to a `/links/` or `/program/` route, so
update `useCurrentProduct` to always return `null` for non-matching paths
instead of preserving prior state. Replace the current `useEffect`/`useState`
approach with a synchronous computed value (for example via `useMemo`) based on
`usePathname`, and ensure the logic explicitly handles both `"links"` and
`"program"` while resetting everything else to `null` so consumers like
`useAnalyticsQuery`, `AnalyticsToggle`, and `program-analytics-shell.tsx` always
get the correct product.

---

Outside diff comments:
In `@apps/web/lib/middleware/utils/app-redirect.ts`:
- Around line 27-40: Restore the RESERVED_SLUGS guard in appRedirect so reserved
single-segment paths do not fall through to the generic workspace redirect
logic. Update appRedirect to check the slug before calling getWorkspaceProduct,
and short-circuit for any reserved slug that is not explicitly handled by
APP_REDIRECTS. Keep the fix local to appRedirect and use the existing
RESERVED_SLUGS and getWorkspaceProduct symbols to place the guard in the current
matching flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4cdc26de-d0c4-4282-812c-d065cc4c3efc

📥 Commits

Reviewing files that changed from the base of the PR and between b794078 and 6202a4b.

📒 Files selected for processing (19)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/events/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/[customerId]/earnings/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/[customerId]/earnings/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/[customerId]/layout.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/[customerId]/sales/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/[customerId]/sales/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/events/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/program-analytics-shell.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/events/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/events/title-link.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/links/analytics/client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/links/analytics/page.tsx
  • apps/web/lib/middleware/utils/app-redirect.ts
  • apps/web/ui/analytics/toggle.tsx
  • apps/web/ui/analytics/use-analytics-query.tsx
  • apps/web/ui/layout/sidebar/app-sidebar-nav.tsx
  • packages/ui/src/hooks/index.ts
  • packages/ui/src/hooks/use-current-product.ts

Comment thread apps/web/ui/analytics/use-analytics-query.tsx
Comment thread apps/web/ui/layout/sidebar/app-sidebar-nav.tsx
Comment thread packages/ui/src/hooks/use-current-product.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
apps/web/ui/analytics/use-analytics-query.tsx (1)

92-98: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Unresolved: guard against programId: undefined.

defaultProgramId may still be undefined while workspace data loads; the spread should also check for its presence to avoid serializing programId=undefined into the request.

🐛 Proposed fix
-      ...(product === "program" && { programId: defaultProgramId }),
+      ...(product === "program" && defaultProgramId && { programId: defaultProgramId }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/ui/analytics/use-analytics-query.tsx` around lines 92 - 98, The
query builder in useAnalyticsQuery should not serialize programId when
defaultProgramId is still undefined. Update the object spread that adds
programId in the useMemo logic to also guard on defaultProgramId being present,
using the existing product check in use-analytics-query.tsx so the request never
includes programId=undefined while workspace data is loading.
packages/ui/src/hooks/use-current-product.ts (1)

1-18: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Stale/delayed product state — unresolved from prior review.

Same issue flagged previously: no else branch resets product to null when navigating to non-/links//program routes, so product retains a stale value. Additionally, using useEffect+useState instead of computing synchronously means product is null on the very first render even when the pathname already matches /links or /program (confirmed usePathname returns the current path synchronously on render), delaying correct behavior by one render cycle.

This propagates downstream: useAnalyticsQuery's programId injection and program-analytics-shell.tsx's initial totalEvents SWR fetch (which depends on queryString from useAnalyticsQuery) will run without programId on first mount, and AnalyticsToggle's "View Events"/"View Analytics" links can point to the wrong product segment after navigating away from a /links/ or /program/ page.

🐛 Proposed fix
-import { usePathname } from "next/navigation";
-import { useEffect, useState } from "react";
-
-export function useCurrentProduct() {
-  const pathname = usePathname();
-  const [product, setProduct] = useState<"links" | "program" | null>(null);
-
-  useEffect(() => {
-    const productParam = pathname.split("/")[2];
-    if (productParam === "links") {
-      setProduct("links");
-    } else if (productParam === "program") {
-      setProduct("program");
-    }
-  }, [pathname]);
-
-  return { product };
-}
+import { usePathname } from "next/navigation";
+import { useMemo } from "react";
+
+export function useCurrentProduct() {
+  const pathname = usePathname();
+
+  const product = useMemo<"links" | "program" | null>(() => {
+    const productParam = pathname.split("/")[2];
+    if (productParam === "links") return "links";
+    if (productParam === "program") return "program";
+    return null;
+  }, [pathname]);
+
+  return { product };
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/hooks/use-current-product.ts` around lines 1 - 18, The
useCurrentProduct hook is leaving stale product state and delaying the correct
value by one render because it relies on useEffect plus useState without
resetting to null for non-matching routes. Update useCurrentProduct to derive
product synchronously from usePathname on each render (or ensure the effect
always sets null in the fallback case), so pathname values for /links and
/program are reflected immediately and other routes clear the product state. Use
the useCurrentProduct symbol to locate the hook and keep its return shape
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@apps/web/ui/analytics/use-analytics-query.tsx`:
- Around line 92-98: The query builder in useAnalyticsQuery should not serialize
programId when defaultProgramId is still undefined. Update the object spread
that adds programId in the useMemo logic to also guard on defaultProgramId being
present, using the existing product check in use-analytics-query.tsx so the
request never includes programId=undefined while workspace data is loading.

In `@packages/ui/src/hooks/use-current-product.ts`:
- Around line 1-18: The useCurrentProduct hook is leaving stale product state
and delaying the correct value by one render because it relies on useEffect plus
useState without resetting to null for non-matching routes. Update
useCurrentProduct to derive product synchronously from usePathname on each
render (or ensure the effect always sets null in the fallback case), so pathname
values for /links and /program are reflected immediately and other routes clear
the product state. Use the useCurrentProduct symbol to locate the hook and keep
its return shape unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c4dffde5-eb88-4ade-abe0-5c1b4b774bb1

📥 Commits

Reviewing files that changed from the base of the PR and between b794078 and bde3a9f.

📒 Files selected for processing (21)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/events/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/[customerId]/earnings/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/[customerId]/earnings/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/[customerId]/layout.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/[customerId]/sales/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/[customerId]/sales/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/customers/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/links/events/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/program-analytics-shell.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/events/page.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/events/title-link.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/links/analytics/client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/links/analytics/page.tsx
  • apps/web/lib/middleware/utils/app-redirect.ts
  • apps/web/ui/analytics/events/events-table.tsx
  • apps/web/ui/analytics/events/events-tabs.tsx
  • apps/web/ui/analytics/toggle.tsx
  • apps/web/ui/analytics/use-analytics-query.tsx
  • apps/web/ui/layout/sidebar/app-sidebar-nav.tsx
  • packages/ui/src/hooks/index.ts
  • packages/ui/src/hooks/use-current-product.ts

@steven-tey
steven-tey merged commit dcb59bb into main Jul 1, 2026
11 checks passed
@steven-tey
steven-tey deleted the add-links-prefix branch July 1, 2026 16:50
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.

1 participant