Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@TWilson023
Copy link
Collaborator

@TWilson023 TWilson023 commented Dec 9, 2025

Summary by CodeRabbit

  • New Features

    • Partner Rewind ’25: full multi-step animated flow (Intro → Rewind → Conclusion) with videos, preloading, and share/download cards.
    • Dashboard banner/card and sidebar placements with dismiss controls.
    • Share modal and dynamic Open Graph image generation for rewind steps.
    • New email template for Partner Rewind and update to available email templates.
    • Client hook and API endpoint to fetch partner rewind data.
  • Chores

    • Database model and types added to store and expose rewind metrics; utility script to populate rewind data.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Dec 9, 2025

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

Project Deployment Review Updated (UTC)
dub Ready Ready Preview Dec 17, 2025 5:47am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 9, 2025

Walkthrough

Adds a Partner Rewind 2025 feature: DB model and import script, API endpoints (JSON + OG image), SWR hook, Zod schemas/types, UI pages/components (intro, rewind, conclusion, banner, card), share modal, Tailwind animation, and an email template.

Changes

Cohort / File(s) Summary
API routes
apps/web/app/(ee)/api/partner-profile/rewind/route.ts, apps/web/app/api/og/partner-rewind/route.tsx
New GET JSON API for partner rewind and an Edge GET route that generates dynamic OG images (fonts, percentile computation, ImageResponse).
Database schema
packages/prisma/schema/misc.prisma, packages/prisma/schema/partner.prisma
Added PartnerRewind model with unique(partnerId, year); added partnerRewinds relation to Partner; fixed YearInReview index syntax.
Data query & import
apps/web/lib/api/partners/get-partner-rewind.ts, apps/web/scripts/dub-partner-rewind.ts
New DB query (prisma.$queryRaw) computing per-year percentiles and returning PartnerRewindProps; script aggregates partner metrics and bulk-inserts PartnerRewind rows.
Types & schemas
apps/web/lib/zod/schemas/partners.ts, apps/web/lib/types.ts
Added PartnerRewindSchema and expanded EnrolledPartnerSchema with many aggregated metrics; exported PartnerRewindProps type.
Client data hook
apps/web/lib/swr/use-partner-rewind.ts
SWR hook to fetch /api/partner-profile/rewind (session-driven), returns partnerRewind, error, loading, mutate; treats 404 as absent.
Rewind page & client flow
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsx, .../page-client.tsx
New server page that authenticates, preloads assets, fetches rewind data and renders client component; client handles intro → rewind → conclusion flow with animated transitions.
Rewind UI components
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/intro.tsx, rewind.tsx, conclusion.tsx, share-rewind-modal.tsx
New Intro, Rewind (steps, auto-advance, controls), Conclusion components and a ShareRewindModal with image fetch/copy/download and a useShareRewindModal hook.
Banner / Card / status hook
apps/web/ui/partners/rewind/partner-rewind-banner.tsx, partner-rewind-card.tsx, use-partner-rewind-status.tsx
New PartnerRewindBanner and PartnerRewindCard integrated into dashboard/sidebar; status persisted in local storage via usePartnerRewindStatus ("banner"
UI constants & integration
apps/web/ui/partners/rewind/constants.ts, apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx, apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx
Added REWIND_YEAR, REWIND_ASSETS_PATH, REWIND_STEPS; swapped marketplace promo components to rewind variants in dashboard and sidebar.
Email template & map
packages/email/src/templates/dub-partner-rewind.tsx, apps/web/lib/email/email-templates-map.ts
New DubPartnerRewind email template and registration in EMAIL_TEMPLATES_MAP (ordering updated).
Config & layout tweaks
apps/web/tailwind.config.ts, apps/web/ui/layout/main-nav.tsx
Added partner-rewind-intro animation keyframes/alias; changed main-nav top container to h-screen at base breakpoint.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Client (Browser)
  participant Server as Next Server Page
  participant API as Next API (/api/partner-profile/rewind)
  participant EdgeOG as Edge Route (/api/og/partner-rewind)
  participant DB as Prisma / Database
  participant Scripts as Import Script

  Browser->>Server: Request /partners/.../rewind/2025 page
  Server->>DB: getPartnerRewind(partnerId) (prisma.$queryRaw)
  DB-->>Server: PartnerRewind data (with percentiles)
  Server-->>Browser: Render page + preload assets + client props

  Browser->>API: GET /api/partner-profile/rewind (SWR)
  API->>DB: getPartnerRewind(partnerId)
  DB-->>API: PartnerRewind JSON
  API-->>Browser: JSON response

  Browser->>EdgeOG: GET /api/og/partner-rewind?rewindId&step
  EdgeOG->>DB: getPartnerRewind(rewindId)
  DB-->>EdgeOG: PartnerRewind data
  EdgeOG-->>Browser: ImageResponse (OG PNG)

  Note over Scripts,DB: Offline import
  Scripts->>DB: createMany(partnerRewind payloads)
  DB-->>Scripts: insertion result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review SQL percentile subqueries in get-partner-rewind.ts and Edge OG route for correctness and performance.
  • Validate Zod schema changes for backward compatibility with existing consumers.
  • Test client-side timing, AnimatePresence/motion transitions, and share modal clipboard/download behavior across browsers.

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

🐰
I hopped through tables, fonts, and frames,
Counted clicks and built percentiles' games.
Banners, videos, and an OG view—
Rewind ’25, carrot-curious and new! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Partner rewind' is too vague and generic, using non-descriptive language that doesn't clearly convey the scope or purpose of the substantial changeset. Expand the title to be more specific about what is being implemented, such as 'Add partner rewind feature with visualization and email template' or 'Implement 2025 partner rewind dashboard and metrics tracking'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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 partner-rewind

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.

Base automatically changed from program-marketplace-promo to main December 10, 2025 17:12
@TWilson023 TWilson023 marked this pull request as ready for review December 16, 2025 01:13
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: 11

🧹 Nitpick comments (13)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx (1)

23-24: Consider removing commented code.

The commented-out ProgramMarketplaceBanner can be safely removed if it's no longer needed, or kept temporarily if you plan to revert/compare implementations during development.

apps/web/scripts/dub-partner-rewind.ts (1)

77-77: Add error handling for the async entry point.

The script invokes main() without handling potential rejections, which could result in silent failures or unhandled promise rejections.

-main();
+main()
+  .then(() => {
+    console.log("Partner rewind generation complete");
+    process.exit(0);
+  })
+  .catch((error) => {
+    console.error("Failed to generate partner rewinds:", error);
+    process.exit(1);
+  });
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/conclusion.tsx (1)

17-19: Consider adding setStatus to the dependency array.

For completeness and to satisfy exhaustive-deps lint rules, include setStatus in the dependency array. While it's likely stable, this ensures correctness if the hook implementation changes.

   useEffect(() => {
     if (status !== "card") setStatus("card");
-  }, [status]);
+  }, [status, setStatus]);
apps/web/lib/api/partners/get-partner-rewind.ts (1)

14-24: Type definition is missing id and year fields.

The SQL query selects pr.id and pr.year (lines 27-28), and PartnerRewindSchema expects them, but the local type definition doesn't include these fields. While the code works due to the spread operator, the type is inaccurate.

   const rewinds = await prisma.$queryRaw<
     {
+      id: string;
+      year: number;
       totalClicks: number;
       totalLeads: number;
       totalRevenue: number;
       totalEarnings: number;
       clicksPercentile: any; // Decimal
       leadsPercentile: any; // Decimal
       revenuePercentile: any; // Decimal
       earningsPercentile: any; // Decimal
     }[]
   >`
packages/prisma/schema/misc.prisma (1)

19-35: Consider adding a unique constraint on (partnerId, year).

The model allows multiple rewind records for the same partner in the same year. If only one rewind per partner per year is intended (as suggested by the data population script logic), consider adding a unique constraint to enforce this at the database level:

   @@index([partnerId])
+  @@unique([partnerId, year])
 }

This would prevent accidental duplicate records and align with the YearInReview model pattern if it has similar constraints.

apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsx (1)

38-41: Video preloading may not work as expected.

The preload() function with as: "video" has limited browser support and may not effectively preload .webm files. Consider using as: "fetch" instead, or verify that the target browsers support video preloading.

   REWIND_STEPS.forEach((step) => {
-    preload(`${REWIND_ASSETS_PATH}/${step.video}`, { as: "video" });
+    preload(`${REWIND_ASSETS_PATH}/${step.video}`, { as: "fetch" });
   });

Alternatively, if preloading videos isn't critical for the user experience, you could remove this preload entirely and rely on the browser's native loading behavior.

apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx (1)

54-58: Duplicate image URL construction.

The image URL is already stored in imageUrl (line 26). Use the variable instead of reconstructing the URL string:

         <img
-          src={`/api/partner-profile/rewind/image?${new URLSearchParams({ rewindId, step }).toString()}`}
+          src={imageUrl}
           alt="share rewind image"
           className="relative size-full"
         />
apps/web/ui/partners/rewind/partner-rewind-banner.tsx (1)

85-91: Clarify the comment.

The comment > mobile close button is ambiguous. Consider clarifying it to match the pattern used on line 95:

-              {/* > mobile close button */}
+              {/* Desktop close button (sm and up) */}
               <Button
apps/web/app/(ee)/api/partner-profile/rewind/image/route.tsx (1)

58-58: Suppressing TypeScript errors is fragile.

The @ts-ignore comment suppresses type checking for the SVG element. If possible, use proper typing or @ts-expect-error with a description of the expected error, which will fail if the error is resolved in a future update.

-        {/* @ts-ignore */}
+        {/* @ts-expect-error - ImageResponse JSX doesn't fully type SVG elements */}
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx (3)

8-8: Consider importing from @dub/utils directly.

The /src suffix is unconventional. Typically, package exports are accessed from the root.

-import { cn } from "@dub/utils/src";
+import { cn } from "@dub/utils";

12-16: Use path alias for consistency.

The deep relative import can be simplified using the @/ alias, consistent with line 1.

-import {
-  REWIND_ASSETS_PATH,
-  REWIND_STEPS,
-} from "../../../../../../ui/partners/rewind/constants";
+import {
+  REWIND_ASSETS_PATH,
+  REWIND_STEPS,
+} from "@/ui/partners/rewind/constants";

126-136: Consider keeping playback paused when navigating backwards.

The left button sets isPaused to false (line 129), resuming auto-advance. Users manually navigating might expect playback to stay paused. Consider keeping the pause state or making this behavior intentional.

apps/web/ui/layout/sidebar/card-stack.tsx (1)

283-338: Prefer camelCase for SVG attributes in React.

While kebab-case SVG attributes work in React 19, the React convention is to use camelCase (strokeDasharray, strokeLinecap, fillRule, clipRule) for consistency with JSX standards.

Apply this diff to align with React conventions:

       <path
         fillRule="evenodd"
         clipRule="evenodd"
         d="M12 1H15V12.9332C15.0001 12.9465 15.0002 12.9598 15.0003 12.9731C15.0003 12.982 15.0003 12.991 15.0003 13C15.0003 13.0223 15.0002 13.0445 15 13.0668V20H12V18.7455C10.8662 19.5362 9.48733 20 8.00016 20C4.13408 20 1 16.866 1 13C1 9.13401 4.13408 6 8.00016 6C9.48733 6 10.8662 6.46375 12 7.25452V1ZM8 16.9998C10.2091 16.9998 12 15.209 12 12.9999C12 10.7908 10.2091 9 8 9C5.79086 9 4 10.7908 4 12.9999C4 15.209 5.79086 16.9998 8 16.9998Z"
         stroke="currentColor"
-        stroke-dasharray="63"
-        stroke-linecap="round"
+        strokeDasharray="63"
+        strokeLinecap="round"
       >
         <animate
           attributeName="stroke-dashoffset"
           dur="2500ms"
           values="63;0;0;0;63"
           fill="freeze"
         />
       </path>
       <path
         fillRule="evenodd"
         clipRule="evenodd"
         d="M17 6H20V13V13C20 14.0608 20.4215 15.0782 21.1716 15.8283C21.9217 16.5784 22.9391 16.9998 24 16.9998C25.0609 16.9998 26.0783 16.5784 26.8284 15.8283C27.5785 15.0782 28 14.0608 28 13C28 13 28 13 28 13V6H31V13H31.0003C31.0003 13.9192 30.8192 14.8295 30.4675 15.6788C30.1157 16.5281 29.6 17.2997 28.95 17.9497C28.3 18.5997 27.5283 19.1154 26.679 19.4671C25.8297 19.8189 24.9194 20 24.0002 20C23.0809 20 22.1706 19.8189 21.3213 19.4671C20.472 19.1154 19.7003 18.5997 19.0503 17.9497C18.4003 17.2997 17.8846 16.5281 17.5329 15.6788C17.1811 14.8295 17 13.9192 17 13V13V6Z"
         stroke="currentColor"
-        stroke-dasharray="69"
-        stroke-linecap="round"
+        strokeDasharray="69"
+        strokeLinecap="round"
       >
         <animate
           attributeName="stroke-dashoffset"
           dur="2500ms"
           values="69;0;0;0;69"
           fill="freeze"
         />
       </path>
       <path
         fillRule="evenodd"
         clipRule="evenodd"
         d="M33 1H36V7.25474C37.1339 6.46383 38.5128 6 40.0002 6C43.8662 6 47.0003 9.13401 47.0003 13C47.0003 16.866 43.8662 20 40.0002 20C36.1341 20 33 16.866 33 13V1ZM40 16.9998C42.2091 16.9998 44 15.209 44 12.9999C44 10.7908 42.2091 9 40 9C37.7909 9 36 10.7908 36 12.9999C36 15.209 37.7909 16.9998 40 16.9998Z"
         stroke="currentColor"
-        stroke-dasharray="60"
-        stroke-linecap="round"
+        strokeDasharray="60"
+        strokeLinecap="round"
       >
         <animate
           attributeName="stroke-dashoffset"
           dur="2500ms"
           values="-60;0;0;0;-60"
           fill="freeze"
         />
       </path>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1117445 and b59942b.

📒 Files selected for processing (25)
  • apps/web/app/(ee)/api/partner-profile/rewind/image/route.tsx (1 hunks)
  • apps/web/app/(ee)/api/partner-profile/rewind/route.ts (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/conclusion.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/intro.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page-client.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx (1 hunks)
  • apps/web/lib/api/partners/get-partner-rewind.ts (1 hunks)
  • apps/web/lib/swr/use-partner-rewind.ts (1 hunks)
  • apps/web/lib/types.ts (2 hunks)
  • apps/web/lib/zod/schemas/partners.ts (1 hunks)
  • apps/web/scripts/dub-partner-rewind.ts (1 hunks)
  • apps/web/tailwind.config.ts (2 hunks)
  • apps/web/ui/layout/main-nav.tsx (1 hunks)
  • apps/web/ui/layout/sidebar/card-stack.tsx (1 hunks)
  • apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (2 hunks)
  • apps/web/ui/partners/rewind/constants.ts (1 hunks)
  • apps/web/ui/partners/rewind/partner-rewind-banner.tsx (1 hunks)
  • apps/web/ui/partners/rewind/partner-rewind-card.tsx (1 hunks)
  • apps/web/ui/partners/rewind/use-partner-rewind-status.tsx (1 hunks)
  • packages/email/src/templates/dub-partner-rewind.tsx (1 hunks)
  • packages/prisma/schema/misc.prisma (1 hunks)
  • packages/prisma/schema/partner.prisma (1 hunks)
🧰 Additional context used
🧠 Learnings (18)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.
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: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/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx
  • apps/web/ui/layout/main-nav.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page-client.tsx
  • apps/web/ui/partners/rewind/partner-rewind-card.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
  • apps/web/ui/layout/sidebar/card-stack.tsx
  • packages/email/src/templates/dub-partner-rewind.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx
📚 Learning: 2025-12-08T09:44:28.429Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.

Applied to files:

  • packages/prisma/schema/partner.prisma
📚 Learning: 2025-07-09T20:52:56.592Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2614
File: apps/web/ui/partners/design/previews/lander-preview.tsx:181-181
Timestamp: 2025-07-09T20:52:56.592Z
Learning: In apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calculation `3 - i * -0.15` is intentionally designed to create negative delays that offset each dot's animation cycle. This pattern works correctly for the intended ellipsis effect and should not be changed to positive incremental delays.

Applied to files:

  • apps/web/tailwind.config.ts
  • apps/web/ui/partners/rewind/partner-rewind-card.tsx
  • apps/web/ui/layout/sidebar/card-stack.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/intro.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/ui/partners/rewind/constants.ts
📚 Learning: 2025-09-18T16:33:17.719Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2858
File: apps/web/ui/partners/partner-application-tabs.tsx:1-1
Timestamp: 2025-09-18T16:33:17.719Z
Learning: When a React component in Next.js App Router uses non-serializable props (like setState functions), adding "use client" directive can cause serialization warnings. If the component is only imported by Client Components, it's better to omit the "use client" directive to avoid these warnings while still getting client-side execution through promotion.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page-client.tsx
📚 Learning: 2025-08-25T21:03:24.285Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/bounty-card.tsx:1-1
Timestamp: 2025-08-25T21:03:24.285Z
Learning: In Next.js App Router, Server Components that use hooks can work without "use client" directive if they are only imported by Client Components, as they get "promoted" to run on the client side within the Client Component boundary.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page-client.tsx
  • apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx
📚 Learning: 2025-08-18T02:31:22.282Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2756
File: apps/web/ui/webhooks/webhook-header.tsx:20-20
Timestamp: 2025-08-18T02:31:22.282Z
Learning: The Next.js redirect() function can be used in both Server Components and Client Components, as well as Route Handlers and Server Actions. It is not server-only as previously thought.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page-client.tsx
📚 Learning: 2025-08-18T02:31:22.282Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2756
File: apps/web/ui/webhooks/webhook-header.tsx:20-20
Timestamp: 2025-08-18T02:31:22.282Z
Learning: The Next.js redirect() function can be used in both Server Components and Client Components, as well as Route Handlers and Server Actions, according to the official Next.js documentation. It is not server-only.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page-client.tsx
📚 Learning: 2025-11-24T16:36:36.196Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:36:36.196Z
Learning: Applies to packages/hubspot-app/app/cards/**/*.{js,jsx,ts,tsx} : Only components exported from the `hubspot/ui-extensions` npm package can be used in card components

Applied to files:

  • apps/web/ui/partners/rewind/partner-rewind-card.tsx
  • apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx
📚 Learning: 2025-11-24T16:36:36.196Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:36:36.196Z
Learning: Applies to packages/hubspot-app/app/cards/**/*.{js,jsx,ts,tsx} : Card components cannot use `window.fetch`; must use `hubspot.fetch` function from `hubspot/ui-extensions` npm package and add URLs to `config.permittedUrls.fetch` in the app component's hsmeta.json

Applied to files:

  • apps/web/ui/partners/rewind/partner-rewind-card.tsx
📚 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/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/conclusion.tsx
📚 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/lib/swr/use-partner-rewind.ts
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/ui/layout/sidebar/partners-sidebar-nav.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/layout/sidebar/partners-sidebar-nav.tsx
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/lib/zod/schemas/partners.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/types.ts
🧬 Code graph analysis (13)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx (1)
apps/web/ui/partners/rewind/partner-rewind-banner.tsx (1)
  • PartnerRewindBanner (11-107)
apps/web/app/(ee)/api/partner-profile/rewind/route.ts (3)
apps/web/lib/auth/partner.ts (1)
  • withPartnerProfile (47-175)
apps/web/lib/api/partners/get-partner-rewind.ts (1)
  • getPartnerRewind (9-64)
apps/web/lib/api/errors.ts (1)
  • DubApiError (58-75)
apps/web/lib/api/partners/get-partner-rewind.ts (4)
apps/web/lib/types.ts (1)
  • PartnerRewindProps (459-459)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/ui/partners/rewind/constants.ts (2)
  • REWIND_YEAR (1-1)
  • REWIND_EARNINGS_MINIMUM (6-6)
apps/web/lib/zod/schemas/partners.ts (1)
  • PartnerRewindSchema (345-356)
apps/web/ui/partners/rewind/partner-rewind-card.tsx (3)
apps/web/lib/swr/use-partner-rewind.ts (1)
  • usePartnerRewind (6-31)
apps/web/ui/partners/rewind/use-partner-rewind-status.tsx (1)
  • usePartnerRewindStatus (6-19)
packages/ui/src/button.tsx (1)
  • buttonVariants (7-28)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsx (5)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/api/partners/get-partner-rewind.ts (1)
  • getPartnerRewind (9-64)
apps/web/ui/partners/rewind/constants.ts (2)
  • REWIND_ASSETS_PATH (3-4)
  • REWIND_STEPS (8-48)
apps/web/ui/layout/page-content/index.tsx (1)
  • PageContent (10-39)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page-client.tsx (1)
  • PartnerRewind2025PageClient (11-48)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx (1)
apps/web/ui/partners/rewind/constants.ts (1)
  • REWIND_STEPS (8-48)
apps/web/lib/swr/use-partner-rewind.ts (1)
apps/web/lib/types.ts (1)
  • PartnerRewindProps (459-459)
apps/web/scripts/dub-partner-rewind.ts (2)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/ui/partners/rewind/constants.ts (1)
  • REWIND_EARNINGS_MINIMUM (6-6)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/conclusion.tsx (2)
apps/web/ui/partners/rewind/use-partner-rewind-status.tsx (1)
  • usePartnerRewindStatus (6-19)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx (1)
  • navButtonClassName (20-21)
packages/email/src/templates/dub-partner-rewind.tsx (1)
packages/email/src/react-email.d.ts (11)
  • Html (4-4)
  • Head (5-5)
  • Preview (18-18)
  • Tailwind (19-19)
  • Body (6-6)
  • Container (7-7)
  • Section (8-8)
  • Img (13-13)
  • Heading (16-16)
  • Text (15-15)
  • Link (14-14)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/intro.tsx (1)
packages/ui/src/button.tsx (1)
  • Button (158-158)
apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (1)
apps/web/ui/partners/rewind/partner-rewind-card.tsx (1)
  • PartnerRewindCard (11-71)
apps/web/lib/types.ts (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • PartnerRewindSchema (345-356)
⏰ 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 (24)
packages/prisma/schema/partner.prisma (1)

83-83: LGTM!

The one-to-many relation to PartnerRewind is correctly defined and aligns with the broader Partner Rewind feature set introduced in this PR.

apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (1)

10-10: LGTM!

The component swap from ProgramMarketplaceCard to PartnerRewindCard is clean and aligns with the Partner Rewind feature introduction.

Also applies to: 357-357

apps/web/lib/types.ts (1)

97-97: LGTM!

The new PartnerRewindProps type follows the established pattern of inferring types from Zod schemas and integrates cleanly with the existing type definitions.

Also applies to: 459-460

apps/web/app/(ee)/api/partner-profile/rewind/route.ts (1)

1-19: LGTM!

The API route implementation follows established patterns with proper authentication via withPartnerProfile, appropriate error handling with DubApiError, and clean data fetching logic.

apps/web/ui/layout/main-nav.tsx (1)

82-82: Verify mobile layout with h-screen applied at all breakpoints.

The height class changed from md:h-screen (desktop-only) to h-screen (all breakpoints). Ensure this doesn't cause layout issues on mobile devices, especially with the bottom navigation or when the keyboard is visible.

apps/web/tailwind.config.ts (1)

35-35: LGTM!

The new partner-rewind-intro animation is properly defined with appropriate keyframes and timing. The animation scales and translates elements to create an intro effect for the Partner Rewind feature.

Also applies to: 100-105

apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/intro.tsx (1)

4-46: LGTM!

The component is well-structured with clean animation orchestration using CSS custom properties. The staggered fade-in animations create a polished entrance effect.

apps/web/ui/partners/rewind/use-partner-rewind-status.tsx (1)

6-19: LGTM!

The hook correctly validates the stored value and falls back to "banner" for invalid values. The explicit return type provides good type safety for consumers.

apps/web/lib/swr/use-partner-rewind.ts (1)

6-31: LGTM!

The hook correctly gates the fetch on defaultPartnerId availability and uses appropriate SWR configuration. The 404 retry exclusion is a good pattern for expected "not found" responses.

apps/web/ui/partners/rewind/partner-rewind-card.tsx (1)

11-71: LGTM!

The component correctly uses AnimatePresence for smooth transitions, properly gates rendering on data availability and status, and hides itself on the rewind page to avoid redundancy. The empty alt="" for the decorative image follows accessibility best practices.

apps/web/lib/api/partners/get-partner-rewind.ts (1)

26-52: LGTM!

The percentile calculations correctly handle zero values with CASE WHEN guards, and the parameterized query via Prisma's tagged template literal is safe from SQL injection. The formula correctly computes the percentage of peers outperformed.

apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsx (1)

15-66: LGTM!

The authentication flow is well-structured with proper null checks and redirects. The server component correctly fetches partner data and rewind information before rendering the client component.

apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page-client.tsx (1)

11-48: LGTM!

The state machine approach for managing the intro → rewind → conclusion flow is clean and easy to follow. The animation transitions are well-configured with consistent enter/exit effects.

apps/web/ui/partners/rewind/partner-rewind-banner.tsx (1)

11-106: LGTM!

The banner implementation is well-structured with proper conditional rendering, responsive design, and smooth animations. The early return pattern for missing data is appropriate.

apps/web/ui/partners/rewind/constants.ts (1)

1-48: LGTM!

The constants are well-organized with clear naming. Using numeric separators (10_00) with the dollar comment improves readability for currency values in cents.

apps/web/app/(ee)/api/partner-profile/rewind/image/route.tsx (2)

17-44: Verify that unauthenticated access is intentional.

The endpoint doesn't require authentication—anyone with a valid rewindId can generate the OG image. This may be intentional for social sharing, but verify this is the desired behavior since it exposes partner metrics (earnings, revenue, etc.) without authorization.


152-205: Code duplication is acceptable given the edge runtime constraint.

The comment at lines 152-153 acknowledges the duplication with the main getPartnerRewind function. The edge runtime limitation is a valid reason, though consider extracting the shared SQL query string to a constant if this pattern recurs.

apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx (4)

62-65: Potential runtime error if steps array is empty.

If steps.length is 0, Math.min(currentStepIndex, -1) evaluates to -1, and accessing steps[-1].id will throw. Consider adding a guard or ensuring the parent component never renders this with an empty steps array.

   const { ShareRewindModal, setShowShareRewindModal } = useShareRewindModal({
     rewindId: partnerRewind.id,
-    step: steps[Math.min(currentStepIndex, steps.length - 1)].id,
+    step: steps.length > 0 ? steps[Math.min(currentStepIndex, steps.length - 1)].id : undefined,
   });

70-101: LGTM!

Step navigation buttons with animated progress indicators are well-implemented. The animation duration correctly syncs with STEP_DELAY_MS.


104-123: LGTM!

Animation transitions with blur and scale effects provide a polished user experience. The mode="wait" ensures clean step transitions.


165-254: LGTM!

The StepSlide component is well-structured with proper accessibility handling (the inert attribute for the hidden badge) and appropriate video autoplay attributes. The currency conversion from cents to dollars is a standard pattern.

apps/web/lib/zod/schemas/partners.ts (1)

345-356: LGTM!

The PartnerRewindSchema is well-structured with appropriate defaults. Consider adding .describe() calls for API documentation consistency with other schemas in this file, but this is optional.

apps/web/ui/layout/sidebar/card-stack.tsx (2)

1-18: LGTM! Clear interface and constants.

The NewsArticle interface is well-defined with appropriate string fields, and the visual constants are clearly named.


222-281: LGTM! Solid implementation of drag-to-dismiss UI.

The NewsCard implementation handles drag interactions well with proper animation cleanup, responsive behavior, and accessible image rendering. The CSS custom property technique for smooth drag transforms is elegant.

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

🧹 Nitpick comments (2)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx (2)

148-160: Simplify redundant boundary check.

Line 152 already ensures currentStepIndex < steps.length - 1, making the Math.min guard on line 153 redundant.

Apply this diff to simplify:

         onClick={() => {
           setIsPaused(false);
           if (currentStepIndex < steps.length - 1)
-            setCurrentStepIndex((c) => Math.min(c + 1, steps.length - 1));
+            setCurrentStepIndex((c) => c + 1);
           else onComplete();
         }}

195-214: Prefer @ts-expect-error over @ts-ignore.

Using @ts-ignore suppresses all TypeScript errors on the next line, even unintended ones. @ts-expect-error is safer because it requires an actual type error to exist and will fail if the error is resolved in a future TypeScript version.

Apply this diff:

             format={{
               ...(valueType === "currency" && {
                 style: "currency",
                 currency: "USD",
-                // @ts-ignore – trailingZeroDisplay is a valid option but TS is outdated
+                // @ts-expect-error – trailingZeroDisplay is valid but not in current TS types
                 trailingZeroDisplay: "stripIfInteger",
               }),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ef0399 and ae4fcb3.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.
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-07-09T20:52:56.592Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2614
File: apps/web/ui/partners/design/previews/lander-preview.tsx:181-181
Timestamp: 2025-07-09T20:52:56.592Z
Learning: In apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calculation `3 - i * -0.15` is intentionally designed to create negative delays that offset each dot's animation cycle. This pattern works correctly for the intended ellipsis effect and should not be changed to positive incremental delays.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.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/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx
🧬 Code graph analysis (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx (3)
apps/web/lib/types.ts (1)
  • PartnerRewindProps (459-459)
apps/web/ui/partners/rewind/constants.ts (2)
  • REWIND_STEPS (8-48)
  • REWIND_ASSETS_PATH (3-4)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx (1)
  • useShareRewindModal (115-135)
⏰ 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 (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx (1)

216-233: No action needed. The inert attribute is well-established and widely supported across modern browsers since April 2023. Your codebase already uses this attribute extensively in 20+ places with the same boolean conditional pattern (inert={condition}), confirming this approach is standard practice in your project.

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

🧹 Nitpick comments (2)
packages/prisma/schema/misc.prisma (1)

19-35: Consider adding an index on year for potential future queries.

The model structure is well-designed with an appropriate unique constraint on [partnerId, year]. The current API usage (filtering by both partnerId and year) is efficiently supported by this constraint's index.

However, if you anticipate future queries that filter by year alone (e.g., admin dashboards showing all partner rewinds for a specific year), adding a standalone index on year could improve performance.

Apply this diff to add the index:

  @@unique([partnerId, year])
+ @@index(year)
apps/web/lib/api/partners/get-partner-rewind.ts (1)

23-49: Consider optimizing percentile calculations for better performance.

The current implementation uses multiple subqueries (8 total: 4 percentiles × 2 subqueries each), which could result in slow query execution as the number of partners grows.

Consider refactoring to use PostgreSQL window functions or a CTE to compute all percentiles in a single table scan, which would be more efficient.

Example approach using window functions:

WITH ranked AS (
  SELECT 
    *,
    PERCENT_RANK() OVER (PARTITION BY year ORDER BY totalClicks) * 100 AS clicksPercentile,
    PERCENT_RANK() OVER (PARTITION BY year ORDER BY totalLeads) * 100 AS leadsPercentile,
    PERCENT_RANK() OVER (PARTITION BY year ORDER BY totalRevenue) * 100 AS revenuePercentile,
    PERCENT_RANK() OVER (PARTITION BY year ORDER BY totalEarnings) * 100 AS earningsPercentile
  FROM PartnerRewind
  WHERE year = ${REWIND_YEAR}
)
SELECT * FROM ranked WHERE partnerId = ${partnerId}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1749618 and 87421c7.

📒 Files selected for processing (4)
  • apps/web/lib/api/partners/get-partner-rewind.ts (1 hunks)
  • apps/web/scripts/dub-partner-rewind.ts (1 hunks)
  • apps/web/ui/partners/rewind/constants.ts (1 hunks)
  • packages/prisma/schema/misc.prisma (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/scripts/dub-partner-rewind.ts
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.
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-06T15:48:45.956Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: packages/prisma/schema/workspace.prisma:21-36
Timestamp: 2025-10-06T15:48:45.956Z
Learning: In the Dub repository (dubinc/dub), Prisma schema changes are not managed with separate migration files. Do not flag missing Prisma migration files when schema changes are made to files like `packages/prisma/schema/workspace.prisma` or other schema files.

Applied to files:

  • packages/prisma/schema/misc.prisma
📚 Learning: 2025-09-24T16:13:00.387Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: packages/prisma/schema/partner.prisma:151-153
Timestamp: 2025-09-24T16:13:00.387Z
Learning: In the Dub codebase, Prisma schemas use single-column indexes without brackets (e.g., `@index(partnerId)`) and multi-column indexes with brackets (e.g., `@index([programId, partnerId])`). This syntax pattern is consistently used throughout their schema files and works correctly with their Prisma version.

Applied to files:

  • packages/prisma/schema/misc.prisma
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/ui/partners/rewind/constants.ts
🧬 Code graph analysis (1)
apps/web/lib/api/partners/get-partner-rewind.ts (4)
apps/web/lib/types.ts (1)
  • PartnerRewindProps (459-459)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/ui/partners/rewind/constants.ts (1)
  • REWIND_YEAR (1-1)
apps/web/lib/zod/schemas/partners.ts (1)
  • PartnerRewindSchema (345-356)
⏰ 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 (3)
apps/web/ui/partners/rewind/constants.ts (1)

1-46: LGTM! Well-structured constants for the 2025 rewind feature.

The constants are clearly defined and organized. The REWIND_STEPS structure provides good metadata for the UI flow.

Note: Both REWIND_YEAR and REWIND_ASSETS_PATH contain year-specific values that will need updating for future rewind campaigns.

packages/prisma/schema/misc.prisma (1)

16-17: LGTM! Consistent with codebase conventions.

The change from @@index([workspaceId]) to @@index(workspaceId) aligns with the single-column index syntax used throughout the codebase.

apps/web/lib/api/partners/get-partner-rewind.ts (1)

51-59: LGTM! Result processing is clean.

The null check and Zod validation provide appropriate data handling. The conversion of Decimal percentiles to numbers ensures compatibility with the expected schema types.

Note: The PartnerRewindSchema.parse() can throw validation errors, which will bubble up to the caller. This appears intentional for this internal API function.

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: 3

🧹 Nitpick comments (4)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx (3)

54-55: Reuse imageUrl variable instead of reconstructing.

The URL is constructed twice—once at line 26 and again here. Reuse the variable to avoid duplication.

Apply this diff:

           <img
-            src={`/api/og/partner-rewind?${new URLSearchParams({ rewindId, step }).toString()}`}
+            src={imageUrl}
             alt="share rewind image"

96-96: Provide a more descriptive fallback filename.

If no matching step is found, the filename becomes "rewind-.png". Consider using a more descriptive fallback.

Apply this diff:

-            link.download = `rewind-${slugify(REWIND_STEPS.find((s) => s.id === step)?.label || "").toLowerCase()}.png`;
+            link.download = `rewind-${slugify(REWIND_STEPS.find((s) => s.id === step)?.label || "image").toLowerCase()}.png`;

118-126: Consider simplifying the component callback pattern.

Wrapping a component in useCallback is unconventional. You can simplify by directly memoizing the JSX in the return value.

Alternative approach:

 export function useShareRewindModal(props: ShareRewindModalInnerProps) {
   const [showShareRewindModal, setShowShareRewindModal] = useState(false);
 
-  const ShareRewindModalCallback = useCallback(() => {
-    return (
+  return useMemo(
+    () => ({
+      setShowShareRewindModal,
+      ShareRewindModal: () => (
         <ShareRewindModal
           showModal={showShareRewindModal}
           setShowModal={setShowShareRewindModal}
           {...props}
         />
-    );
-  }, [showShareRewindModal, setShowShareRewindModal, props]);
-
-  return useMemo(
-    () => ({
-      setShowShareRewindModal,
-      ShareRewindModal: ShareRewindModalCallback,
+      ),
     }),
-    [setShowShareRewindModal, ShareRewindModalCallback],
+    [showShareRewindModal, props],
   );
 }
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsx (1)

20-30: Consider simplifying the partnerId resolution.

The code queries partnerUser to retrieve partnerId, but this value appears to be identical to user.defaultPartnerId (used in the composite key). Unless there's a specific security reason to verify the relationship, you could pass user.defaultPartnerId directly to getPartnerRewind.

If the query serves as an authorization check to ensure the user has access to the partner, consider adding a comment to clarify this intent:

+ // Verify user has access to the default partner
  const partnerUser = await prisma.partnerUser.findUnique({
    select: { partnerId: true },

Otherwise, simplify to:

- const partnerUser = await prisma.partnerUser.findUnique({
-   select: { partnerId: true },
-   where: {
-     userId_partnerId: {
-       userId: user.id,
-       partnerId: user.defaultPartnerId,
-     },
-   },
- });
- 
- if (!partnerUser) redirect("/");
- 
  const partnerRewind = await getPartnerRewind({
-   partnerId: partnerUser.partnerId,
+   partnerId: user.defaultPartnerId,
  });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87421c7 and 942d69e.

📒 Files selected for processing (4)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx (1 hunks)
  • apps/web/app/api/og/partner-rewind/route.tsx (1 hunks)
  • apps/web/ui/partners/rewind/constants.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/ui/partners/rewind/constants.ts
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.
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/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-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/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.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/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
📚 Learning: 2025-06-04T15:09:51.562Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2471
File: apps/web/ui/auth/reset-password-form.tsx:52-65
Timestamp: 2025-06-04T15:09:51.562Z
Learning: In the Dub codebase, server-side validation errors for password fields are handled via toast notifications rather than using react-hook-form's setError method to display field-specific errors. This architectural pattern separates client-side validation feedback (inline) from server-side error handling (toast).

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.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/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
📚 Learning: 2025-10-08T21:33:23.553Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2936
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/analytics/add-hostname-modal.tsx:28-34
Timestamp: 2025-10-08T21:33:23.553Z
Learning: In the dub/ui Button component, when the `disabledTooltip` prop is set to a non-undefined value (e.g., a string), the button is automatically disabled. Therefore, it's not necessary to also add the same condition to the `disabled` prop—setting `disabledTooltip={permissionsError || undefined}` is sufficient to disable the button when there's a permissions error.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
🧬 Code graph analysis (3)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx (2)
packages/ui/src/button.tsx (1)
  • Button (158-158)
apps/web/ui/partners/rewind/constants.ts (1)
  • REWIND_STEPS (6-46)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsx (5)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/api/partners/get-partner-rewind.ts (1)
  • getPartnerRewind (6-60)
apps/web/ui/partners/rewind/constants.ts (2)
  • REWIND_ASSETS_PATH (3-4)
  • REWIND_STEPS (6-46)
apps/web/ui/layout/page-content/index.tsx (1)
  • PageContent (10-39)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page-client.tsx (1)
  • PartnerRewind2025PageClient (11-48)
apps/web/app/api/og/partner-rewind/route.tsx (3)
apps/web/ui/partners/rewind/constants.ts (2)
  • REWIND_STEPS (6-46)
  • REWIND_ASSETS_PATH (3-4)
apps/web/lib/api/partners/get-partner-rewind.ts (1)
  • getPartnerRewind (6-60)
apps/web/lib/zod/schemas/partners.ts (1)
  • PartnerRewindSchema (345-356)
⏰ 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/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsx (2)

38-41: LGTM! Good use of asset preloading.

Preloading critical assets improves perceived performance and user experience, especially for media-heavy features like this rewind animation.


68-78: LGTM! Decorative gradient implementation.

The complex radial gradient composition creates an effective visual background for the rewind feature.

apps/web/app/api/og/partner-rewind/route.tsx (3)

27-35: LGTM! Proper parameter validation.

The route correctly validates required query parameters and returns appropriate HTTP 400 responses when they're missing.


44-49: LGTM! Correct value computation and formatting.

The code properly handles both currency (dividing by 100 to convert cents to dollars) and numeric value types, with appropriate floor rounding for display.


151-202: Code duplication with getPartnerRewind is a valid maintenance concern, but remove the incorrect year filter claim.

While the SQL logic is duplicated, the two functions serve different purposes: the original gets rewind data by partnerId + year, whereas the route handler gets it by rewindId. Since PartnerRewind.id is globally unique (generated via cuid()), the missing year filter is not a data correctness issue—the ID alone correctly identifies the record.

The duplication is still worth addressing for maintainability. Consider:

  1. Extract the shared percentile calculation logic into a reusable function
  2. Verify whether the architectural limitation ("couldn't get that to work with dynamic client + conditional Prisma.sql") can be resolved
  3. If not resolvable, document why the duplication is necessary

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: 0

🧹 Nitpick comments (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx (1)

23-23: Consider removing the commented code and TODO.

Since PartnerRewindBanner already falls back to ProgramMarketplaceBanner when there's no rewind data (as shown in the component implementation), the commented code and TODO may be unnecessary. The fallback behavior handles the "after rewind is over" scenario automatically.

If a manual revert is truly needed, consider documenting why the automatic fallback isn't sufficient.

Apply this diff to remove the commented code:

-      {/* <ProgramMarketplaceBanner /> // TODO: Add back after rewind is over  */}
       <PartnerRewindBanner />
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa8f531 and cf579f8.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx (2 hunks)
  • apps/web/ui/partners/rewind/partner-rewind-banner.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/ui/partners/rewind/partner-rewind-banner.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.
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: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/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx
🧬 Code graph analysis (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx (1)
apps/web/ui/partners/rewind/partner-rewind-banner.tsx (1)
  • PartnerRewindBanner (12-110)
⏰ 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 (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/page-client.tsx (1)

6-6: LGTM! Clean component swap for the rewind feature.

The import and usage of PartnerRewindBanner is correct. The component appropriately replaces the marketplace banner for the seasonal rewind feature.

Also applies to: 24-24

@steven-tey steven-tey merged commit 1ae36c6 into main Dec 17, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the partner-rewind branch December 17, 2025 15:57
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.

3 participants