-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Partner rewind #3210
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
Partner rewind #3210
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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: 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 addingsetStatusto the dependency array.For completeness and to satisfy exhaustive-deps lint rules, include
setStatusin 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 missingidandyearfields.The SQL query selects
pr.idandpr.year(lines 27-28), andPartnerRewindSchemaexpects 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
YearInReviewmodel 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 withas: "video"has limited browser support and may not effectively preload.webmfiles. Consider usingas: "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 buttonis ambiguous. Consider clarifying it to match the pattern used on line 95:- {/* > mobile close button */} + {/* Desktop close button (sm and up) */} <Buttonapps/web/app/(ee)/api/partner-profile/rewind/image/route.tsx (1)
58-58: Suppressing TypeScript errors is fragile.The
@ts-ignorecomment suppresses type checking for the SVG element. If possible, use proper typing or@ts-expect-errorwith 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/utilsdirectly.The
/srcsuffix 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
isPausedtofalse(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
📒 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.tsxapps/web/ui/layout/main-nav.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page-client.tsxapps/web/ui/partners/rewind/partner-rewind-card.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/page.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsxapps/web/ui/layout/sidebar/card-stack.tsxpackages/email/src/templates/dub-partner-rewind.tsxapps/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.tsapps/web/ui/partners/rewind/partner-rewind-card.tsxapps/web/ui/layout/sidebar/card-stack.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/intro.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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) toh-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-introanimation 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
defaultPartnerIdavailability 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 WHENguards, 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
rewindIdcan 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
getPartnerRewindfunction. 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 ifstepsarray is empty.If
steps.lengthis 0,Math.min(currentStepIndex, -1)evaluates to-1, and accessingsteps[-1].idwill throw. Consider adding a guard or ensuring the parent component never renders this with an emptystepsarray.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
StepSlidecomponent is well-structured with proper accessibility handling (theinertattribute 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
PartnerRewindSchemais 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
NewsArticleinterface 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.
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/conclusion.tsx
Show resolved
Hide resolved
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/rewind.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
Show resolved
Hide resolved
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
Show resolved
Hide resolved
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 (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 theMath.minguard 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-errorover@ts-ignore.Using
@ts-ignoresuppresses all TypeScript errors on the next line, even unintended ones.@ts-expect-erroris 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
📒 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. Theinertattribute 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.
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 (2)
packages/prisma/schema/misc.prisma (1)
19-35: Consider adding an index onyearfor 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
yearalone (e.g., admin dashboards showing all partner rewinds for a specific year), adding a standalone index onyearcould 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
📒 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_YEARandREWIND_ASSETS_PATHcontain 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.
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
🧹 Nitpick comments (4)
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx (3)
54-55: ReuseimageUrlvariable 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
useCallbackis 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
partnerUserto retrievepartnerId, but this value appears to be identical touser.defaultPartnerId(used in the composite key). Unless there's a specific security reason to verify the relationship, you could passuser.defaultPartnerIddirectly togetPartnerRewind.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
📒 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.tsxapps/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 byrewindId. SincePartnerRewind.idis globally unique (generated viacuid()), the missingyearfilter is not a data correctness issue—the ID alone correctly identifies the record.The duplication is still worth addressing for maintainability. Consider:
- Extract the shared percentile calculation logic into a reusable function
- Verify whether the architectural limitation ("couldn't get that to work with dynamic client + conditional Prisma.sql") can be resolved
- If not resolvable, document why the duplication is necessary
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
Show resolved
Hide resolved
apps/web/app/(ee)/partners.dub.co/(dashboard)/rewind/2025/share-rewind-modal.tsx
Show resolved
Hide resolved
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: 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
PartnerRewindBanneralready falls back toProgramMarketplaceBannerwhen 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
📒 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
PartnerRewindBanneris correct. The component appropriately replaces the marketplace banner for the seasonal rewind feature.Also applies to: 24-24
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.