+
Skip to content

Conversation

devkiran
Copy link
Collaborator

@devkiran devkiran commented Aug 2, 2025

Summary by CodeRabbit

  • New Features

    • Improved email change process for partner profiles, including email verification when updating email addresses.
    • Users are now prompted to check their email for verification if their profile email is changed.
  • Enhancements

    • Streamlined email confirmation workflow for both user and partner profiles.
    • Conditional navigation after confirming email change, directing users to the appropriate page based on profile type.
  • Bug Fixes

    • Improved handling of duplicate email addresses during profile updates, providing clearer error messages.
  • Refactor

    • Consolidated email change logic for better maintainability and reliability.
    • Added safeguards preventing Stripe account deletion if payouts exist during profile updates.

Extracts email change confirmation logic into a shared utility, updates both user and partner profile flows to use it, and ensures email verification is required when changing emails. Adds support for partner profile email changes, improves error handling, and updates related UI feedback and email templates.
Copy link
Contributor

vercel bot commented Aug 2, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
dub ✅ Ready (Inspect) Visit Preview Aug 2, 2025 4:01pm

Copy link
Contributor

coderabbitai bot commented Aug 2, 2025

Walkthrough

This change refactors and centralizes the email change confirmation logic for both user and partner profiles. It introduces a new helper function for handling email change confirmation, updates the profile update flow to support email verification, adjusts client-side components to handle new verification states, and modifies related API and email template logic accordingly.

Changes

Cohort / File(s) Change Summary
Partner Profile Update Flow
apps/web/lib/actions/partners/update-partner-profile.ts, apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
Refactors Stripe account deletion logic into a helper, enforces explicit email uniqueness checks, integrates new email confirmation logic, and updates the client to handle a new needsEmailVerification flag in the UI.
Email Change Confirmation Logic
apps/web/lib/auth/confirm-email-change.ts, apps/web/app/api/user/route.ts
Introduces a new confirmEmailChange helper to handle email change confirmation, rate limiting, and email sending. The user API route is refactored to delegate all email change logic to this helper.
Email Change Confirmation Flow (UI & Routing)
apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx, apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx
Updates the email confirmation page to support partner profiles, changes redirect logic based on profile type, and adjusts backend logic to handle both user and partner email updates, with improved error handling and promise management.
Email Template
packages/email/src/templates/confirm-email-change.tsx
Updates the default confirmation URL in the email template to include a specific token path segment.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WebApp
    participant Server
    participant EmailService
    participant Redis/DB

    User->>WebApp: Update profile (change email)
    WebApp->>Server: Call updatePartnerProfileAction
    Server->>Redis/DB: Check email uniqueness
    alt Email changed
        Server->>Redis/DB: Store verification token and request
        Server->>EmailService: Send confirmation email
        Server-->>WebApp: Return { needsEmailVerification: true }
        WebApp-->>User: Show "Check your email to verify" toast
    else No email change
        Server-->>WebApp: Return { needsEmailVerification: false }
        WebApp-->>User: Show "Profile updated" toast
    end

    User->>WebApp: Click confirmation link in email
    WebApp->>Server: Validate token and update email
    Server->>Redis/DB: Delete verification token/request
    Server-->>WebApp: Confirmation result
    WebApp-->>User: Redirect to profile/settings page
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Allow updating partner email #2602: Adds support for editing partner email and updates the profile page client and server action to handle email changes with validation and restrictions, closely related to the current PR's changes in partner email update handling.
  • Dynamic host handling for email change confirmation links #2672: Refactors email confirmation URL handling in the user PATCH route, overlapping with the current PR's consolidation of email change confirmation logic and partner-specific handling.

Suggested reviewers

  • devkiran

Poem

A bunny hops with nimble feet,
Email flows are now complete!
Profiles updated, toasts appear,
Confirm your mail, the path is clear.
With tokens safe and logic neat,
🥕 This code is now a treat!
— A rabbit, signing off with cheer.

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch partner-confirm-email

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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/email/src/templates/confirm-email-change.tsx (1)

20-20: Consider using a realistic token length in the default URL.

The default token appears to be 30 characters, but the actual tokens generated by randomBytes(32).toString("hex") are 64 characters long. Consider updating for consistency:

- confirmUrl = "https://dub.co/auth/confirm-email-change/d03324452e1ac9352954315f3ffc",
+ confirmUrl = "https://dub.co/auth/confirm-email-change/d03324452e1ac9352954315f3ffcab123456789abcdef0123456789abcdef01",
apps/web/lib/auth/confirm-email-change.ts (1)

55-56: Consider more generic Redis key naming.

The Redis key uses user even for partner profiles. Consider a more generic naming scheme for clarity:

- `email-change-request:user:${identifier}`,
+ `email-change-request:${identifier}`,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d39de5b and aed057f.

📒 Files selected for processing (7)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (1 hunks)
  • apps/web/app/api/user/route.ts (2 hunks)
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx (2 hunks)
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx (4 hunks)
  • apps/web/lib/actions/partners/update-partner-profile.ts (5 hunks)
  • apps/web/lib/auth/confirm-email-change.ts (1 hunks)
  • packages/email/src/templates/confirm-email-change.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: in apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward conditi...
Learnt from: TWilson023
PR: dubinc/dub#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/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx
  • apps/web/lib/actions/partners/update-partner-profile.ts
📚 Learning: in the dub codebase, server-side validation errors for password fields are handled via toast notific...
Learnt from: TWilson023
PR: dubinc/dub#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)/profile/page-client.tsx
  • apps/web/app/api/user/route.ts
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx
📚 Learning: in apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition ob...
Learnt from: TWilson023
PR: dubinc/dub#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/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
📚 Learning: in the partnerprogramsummary email template (packages/email/src/templates/partner-program-summary.ts...
Learnt from: devkiran
PR: dubinc/dub#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/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
📚 Learning: in the singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in th...
Learnt from: devkiran
PR: dubinc/dub#2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
  • packages/email/src/templates/confirm-email-change.tsx
📚 Learning: in the dub codebase, multiple payout records can now share the same stripetransferid because payouts...
Learnt from: devkiran
PR: dubinc/dub#2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.693Z
Learning: In the Dub codebase, multiple payout records can now share the same stripeTransferId because payouts are grouped by partner and processed as single Stripe transfers. This is why the @unique constraint was removed from the stripeTransferId field in the Payout model - a single transfer can include multiple payouts for the same partner.

Applied to files:

  • apps/web/lib/actions/partners/update-partner-profile.ts
⏰ 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 (21)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (1)

208-216: LGTM! Clear user feedback for email verification flow.

The conditional toast messaging properly handles the new email verification workflow. When needsEmailVerification is true, users receive clear guidance to check their email, otherwise they get standard confirmation.

apps/web/app/api/user/route.ts (1)

106-110: Excellent refactoring! Email confirmation logic properly centralized.

The replacement of manual rate limiting, token generation, and email sending with a single call to confirmEmailChange improves maintainability while preserving all security measures. The function parameters correctly map the session user data.

apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx (1)

27-27: LGTM! Proper redirect handling for different profile types.

The conditional redirect correctly sends partner profiles to /profile and user profiles to /account/settings, maintaining the appropriate user experience flow.

apps/web/lib/auth/confirm-email-change.ts (3)

23-33: LGTM! Appropriate rate limiting for email change requests.

The rate limit of 3 requests per day with proper error handling prevents abuse while allowing legitimate retries.


35-52: LGTM! Secure token generation and storage.

The implementation properly:

  • Cleans up existing tokens before creating new ones
  • Uses cryptographically secure random token generation
  • Applies proper token hashing before storage
  • Sets reasonable 15-minute expiration

67-79: LGTM! Proper domain selection and non-blocking email sending.

The conditional domain selection correctly routes partner profiles to PARTNERS_DOMAIN and users to APP_DOMAIN. Using waitUntil for email sending prevents blocking the response while ensuring email delivery.

apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx (7)

9-9: LGTM!

The User type import is appropriately added to support type safety for user profile updates in the refactored email confirmation flow.


83-84: LGTM!

Appropriately extracts partnerId from the session to support partner profile email change functionality.


89-93: LGTM!

The Redis data structure appropriately supports both profile types with the optional isPartnerProfile flag, and the dynamic identifier correctly handles both user and partner scenarios.


105-142: Well-structured conditional email update logic.

The implementation correctly handles both partner and user profile email updates with appropriate error handling and clear separation of concerns. The error message for missing partner profiles is helpful and actionable.


145-163: Excellent use of Promise.allSettled for resilient cleanup operations.

The change to Promise.allSettled ensures that cleanup operations (token deletion, subscription updates, email notifications) don't fail the entire flow if individual operations encounter issues. The conditional subscription management correctly applies only to user profiles.


166-181: LGTM!

The isPartnerProfile prop correctly passes profile type information to the client component, and the deleteRequest function appropriately uses Promise.allSettled to ensure robust cleanup of both database and Redis records.


85-87: Confirm partner token prefix in generation logic

I reviewed the use of tokenFound.identifier.startsWith("pn_") in apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx, but couldn’t locate where identifiers are generated with a "pn_" prefix. Please ensure that wherever partner‐profile tokens are created, their identifier is consistently prefixed with "pn_".

  • Review the token generation code responsible for setting tokenFound.identifier (e.g., in your auth or token‐service modules)
  • Verify that partner tokens always use the "pn_" convention at creation time
apps/web/lib/actions/partners/update-partner-profile.ts (8)

3-3: LGTM!

The new imports appropriately support the email verification flow (confirmEmailChange) and type safety for the refactored Stripe account deletion logic (Partner).

Also applies to: 13-13


55-55: Improved code organization and clarity.

The renaming to newEmail enhances readability, and the early Stripe account validation prevents unnecessary operations. The simplified image upload logic and clear variable initialization improve code maintainability.

Also applies to: 63-72


95-117: Robust email change validation and verification flow.

The implementation correctly validates email uniqueness and provides helpful error messaging with account merging guidance. The integration with the confirmEmailChange helper maintains consistency across user and partner profiles.


145-147: LGTM!

The return value appropriately communicates email verification status to the client, enabling proper UI feedback for the email change flow.


155-182: Well-structured function extraction with comprehensive change detection.

The deleteStripeAccountIfRequired function appropriately encapsulates Stripe account management logic with clear parameter types and efficient change detection for all relevant fields.


184-199: Critical business logic for Stripe compliance.

The payout validation correctly prevents changes to compliance-critical fields after payouts have been processed, ensuring regulatory compliance. The error message appropriately directs users to support for resolution.


201-215: LGTM!

The Stripe account deletion logic correctly handles account removal and maintains database consistency by clearing related fields only after successful deletion.


155-216: Excellent refactoring that improves code organization and robustness.

The extracted deleteStripeAccountIfRequired function enhances maintainability and properly handles Stripe compliance requirements. The comprehensive validation and cleanup logic significantly improves the reliability of partner profile updates.

@devkiran
Copy link
Collaborator Author

devkiran commented Aug 2, 2025

@coderabbitai full review

Copy link
Contributor

coderabbitai bot commented Aug 2, 2025

✅ Actions performed

Full review triggered.

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

♻️ Duplicate comments (1)
apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx (1)

9-13: Remove async from React client component declaration.

React client components cannot be async functions. The async keyword should be removed from the component function declaration.

-export default async function ConfirmEmailChangePageClient({
+export default function ConfirmEmailChangePageClient({
  isPartnerProfile,
}: {
  isPartnerProfile: boolean;
}) {
🧹 Nitpick comments (2)
packages/email/src/templates/confirm-email-change.tsx (1)

20-20: Consider using a more generic placeholder token.

The hardcoded token d03324452e1ac9352954315f3ffc in the default URL seems arbitrary. Consider using a more generic placeholder like {token} or example-token to make it clearer this is a template parameter.

-  confirmUrl = "https://dub.co/auth/confirm-email-change/d03324452e1ac9352954315f3ffc",
+  confirmUrl = "https://dub.co/auth/confirm-email-change/{token}",
apps/web/lib/actions/partners/update-partner-profile.ts (1)

97-119: Well-implemented email verification with helpful error messaging.

The explicit email uniqueness check and centralized confirmation process are well-structured. The error message helpfully includes a merge link for existing accounts.

Consider wrapping the email confirmation in a try-catch for better error handling:

+        try {
           await confirmEmailChange({
             email: partner.email!,
             newEmail,
             identifier: partner.id,
             isPartnerProfile: true,
             hostName: PARTNERS_DOMAIN,
           });
+        } catch (error) {
+          throw new Error(`Failed to send email verification: ${error.message}`);
+        }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d39de5b and aa90253.

📒 Files selected for processing (7)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (1 hunks)
  • apps/web/app/api/user/route.ts (2 hunks)
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx (2 hunks)
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx (4 hunks)
  • apps/web/lib/actions/partners/update-partner-profile.ts (5 hunks)
  • apps/web/lib/auth/confirm-email-change.ts (1 hunks)
  • packages/email/src/templates/confirm-email-change.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: in apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward conditi...
Learnt from: TWilson023
PR: dubinc/dub#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/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx
  • apps/web/lib/actions/partners/update-partner-profile.ts
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx
📚 Learning: in the dub codebase, server-side validation errors for password fields are handled via toast notific...
Learnt from: TWilson023
PR: dubinc/dub#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)/profile/page-client.tsx
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx
  • apps/web/app/api/user/route.ts
📚 Learning: in apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition ob...
Learnt from: TWilson023
PR: dubinc/dub#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/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
📚 Learning: in the partnerprogramsummary email template (packages/email/src/templates/partner-program-summary.ts...
Learnt from: devkiran
PR: dubinc/dub#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/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
📚 Learning: in apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calcul...
Learnt from: TWilson023
PR: dubinc/dub#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/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx
📚 Learning: in the dub codebase, multiple payout records can now share the same stripetransferid because payouts...
Learnt from: devkiran
PR: dubinc/dub#2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.693Z
Learning: In the Dub codebase, multiple payout records can now share the same stripeTransferId because payouts are grouped by partner and processed as single Stripe transfers. This is why the @unique constraint was removed from the stripeTransferId field in the Payout model - a single transfer can include multiple payouts for the same partner.

Applied to files:

  • apps/web/lib/actions/partners/update-partner-profile.ts
📚 Learning: in the dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it confi...
Learnt from: devkiran
PR: dubinc/dub#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.

Applied to files:

  • apps/web/lib/actions/partners/update-partner-profile.ts
📚 Learning: in the dub codebase, prefer keeping semantically different functions separate even if they currently...
Learnt from: TWilson023
PR: dubinc/dub#2484
File: apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/usage/get-recommended-plan.ts:11-12
Timestamp: 2025-06-25T18:08:53.931Z
Learning: In the Dub codebase, prefer keeping semantically different functions separate even if they currently have identical implementations. This maintains readability and allows for easy future divergence when business logic changes. For example, `hasConversions` and `hasPartners` functions should remain separate despite having the same current logic, as they serve different conceptual purposes and may need different feature checks in the future.

Applied to files:

  • apps/web/lib/actions/partners/update-partner-profile.ts
🔇 Additional comments (14)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (1)

208-216: LGTM! Clean implementation of email verification feedback.

The conditional toast logic properly handles the new needsEmailVerification flag, providing appropriate user feedback for email verification requirements versus immediate profile updates.

apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx (1)

27-27: LGTM! Conditional redirect logic is well implemented.

The conditional redirect based on isPartnerProfile properly handles the post-verification flow, directing partner profiles to /profile and regular users to /account/settings.

apps/web/app/api/user/route.ts (1)

115-120: Excellent refactor! Email confirmation logic is now centralized.

The replacement of inline email verification logic with the confirmEmailChange helper function improves maintainability and reusability. The parameter mapping is correct, and the hostname resolution logic properly handles different domains.

apps/web/lib/auth/confirm-email-change.ts (1)

11-79: Excellent implementation of centralized email confirmation logic.

This helper function effectively consolidates email change verification with solid security practices:

  • Appropriate rate limiting (3 requests/day)
  • Secure token generation using crypto.randomBytes
  • Proper token hashing before database storage
  • Reasonable 15-minute expiration window
  • Non-blocking email dispatch with waitUntil
  • Clean error handling with custom API errors

The Redis caching strategy and database token management are well implemented, and the partner profile support integrates seamlessly.

apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx (5)

9-9: LGTM: Import addition supports new functionality.

The User type import is necessary for the new user profile update logic.


93-93: LGTM: Redis key update supports dynamic identifier.

The Redis key now correctly uses the dynamic identifier, supporting both user and partner profiles.


105-142: LGTM: Well-structured conditional email update logic.

The code properly handles both partner and user email updates with appropriate error handling and efficient database operations. The error message for missing partner profiles is helpful and includes the correct URL.


145-145: Excellent: Promise.allSettled improves reliability.

Using Promise.allSettled prevents one failing operation (e.g., email service) from blocking others, which is particularly important for cleanup operations like token deletion and email notifications.

Also applies to: 172-172


167-167: LGTM: Clear prop passing for client component.

The isPartnerProfile prop correctly conveys the profile type to the client component, enabling appropriate redirect behavior.

apps/web/lib/actions/partners/update-partner-profile.ts (5)

3-3: LGTM: Import additions support new functionality.

The new imports are necessary for the centralized email confirmation system and helper function type safety.

Also applies to: 12-12, 14-14


56-56: LGTM: Clear variable naming and initialization.

The variable renaming and initialization improve code clarity. The emailChanged check efficiently avoids unnecessary verification when the email hasn't actually changed.

Also applies to: 70-72


65-68: Excellent: Improved separation of concerns.

Extracting the Stripe account deletion logic into a helper function improves maintainability and makes the main function flow clearer.


75-79: LGTM: Cleaner image upload logic.

The simplified conditional logic with null initialization is more readable and maintainable.


147-149: LGTM: Clear return structure for client handling.

The needsEmailVerification flag provides clean communication to the client about when email verification UI should be shown.

@steven-tey
Copy link
Collaborator

@CodeRabbit final review please

Copy link
Contributor

coderabbitai bot commented Aug 2, 2025

@steven-tey I'll conduct a final review of the PR changes for email verification in partner profiles.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@steven-tey
Copy link
Collaborator

@CodeRabbit please do a final review like your life depends on it

Copy link
Contributor

coderabbitai bot commented Aug 2, 2025

🧩 Analysis chain

@steven-tey I've conducted a thorough final review of the email verification implementation for partner profiles. Here are my findings and critical recommendations:

🔒 Security Issues

CRITICAL: Token Collision Risk

  • The verification token generation uses crypto.randomBytes(32).toString("hex") which is secure, but the identifier pattern pn_${partnerId} vs ${userId} could create conflicts if partner IDs and user IDs overlap in the database.

Rate Limiting Gap

  • Rate limiting is only applied to confirmEmailChange but not to the token verification endpoint. An attacker could potentially brute force tokens.

🐛 Logic Issues

Race Condition in Email Uniqueness Check

// In update-partner-profile.ts - this check happens AFTER the update
const existingPartner = await prisma.partner.findFirst({
  where: { email: newEmail }
});

This should happen BEFORE the database update to prevent duplicate emails.

Incomplete Error Handling

  • In the confirmation page, Promise.allSettled is used but errors from individual operations aren't properly handled or logged.

Missing Validation

  • No validation that the new email is different from the current email before triggering verification flow.

🔧 Code Quality Issues

Inconsistent Error Messages

  • Some error messages reference "user" even in partner profile context.

Magic Numbers

  • Hard-coded expiration times (15 minutes) should be constants.

Potential Memory Leak

  • Redis keys might not get cleaned up if verification fails.

✅ Recommendations

  1. Fix Token Identifier Collision:
// Use a more distinct prefix
const identifier = isPartnerProfile ? `partner_${partnerId}` : `user_${userId}`;
  1. Add Rate Limiting to Verification:
// Add rate limiting to the token verification endpoint
await rateLimit(identifier, 10); // 10 attempts per hour
  1. Pre-validate Email Uniqueness:
// Check before updating
if (newEmail !== partner.email) {
  const existing = await prisma.partner.findFirst({
    where: { email: newEmail }
  });
  if (existing) throw new DubApiError({
    code: "conflict",
    message: "Email already in use"
  });
}
  1. Add Email Validation:
if (newEmail === currentEmail) {
  throw new DubApiError({
    code: "unprocessable_entity", 
    message: "New email must be different from current email"
  });
}

🎯 Overall Assessment

The implementation follows a solid architecture pattern but has several security and logic gaps that need addressing before production deployment. The email verification flow works correctly for the happy path, but edge cases and error scenarios need better handling.

Priority: Address the token collision and race condition issues immediately - these could cause data integrity problems.


🏁 Script executed:

#!/bin/bash
# Let me examine all the changed files to conduct a thorough review

# First, get the list of changed files in this PR
echo "=== Changed files in this PR ==="
git diff --name-only HEAD~$(git rev-list --count HEAD ^origin/main)

echo -e "\n=== Examining partner profile page client ==="
cat "apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx"

echo -e "\n=== Examining API user route ==="
cat "apps/web/app/api/user/route.ts"

echo -e "\n=== Examining confirm email change page client ==="
cat "apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx"

echo -e "\n=== Examining confirm email change page server ==="
cat "apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx"

Length of output: 27971


🏁 Script executed:

#!/bin/bash
# Continue examining the remaining files

echo "=== Examining partner profile update action ==="
cat "apps/web/lib/actions/partners/update-partner-profile.ts"

echo -e "\n=== Examining confirm email change helper ==="
cat "apps/web/lib/auth/confirm-email-change.ts"

echo -e "\n=== Examining email template ==="
cat "packages/email/src/templates/confirm-email-change.tsx"

Length of output: 10201


🏁 Script executed:

#!/bin/bash
# Let me examine the schema and types to understand the data structure better

echo "=== Looking for partner profile schema ==="
fd -e ts -e tsx | xargs grep -l "updatePartnerProfileSchema" | head -5

echo -e "\n=== Looking for Partner type definition ==="
fd -e ts | xargs grep -l "type Partner" | head -3

echo -e "\n=== Checking for existing rate limiting implementation ==="
fd -e ts | xargs grep -l "DubApiError.*rate" | head -3

Length of output: 653


🏁 Script executed:

#!/bin/bash
# Examine the schema definition and related types

echo "=== Partner profile schema ==="
rg -A 20 "updatePartnerProfileSchema" --type ts

echo -e "\n=== Partner type and related interfaces ==="
rg -A 10 -B 2 "interface.*Partner|type.*Partner" --type ts

echo -e "\n=== Rate limiting implementation ==="
rg -A 10 -B 2 "rate.*limit" --type ts | head -30

Length of output: 48061


🏁 Script executed:

#!/bin/bash
# Check for potential security issues and error handling patterns

echo "=== Token generation and validation patterns ==="
rg -A 5 -B 2 "crypto\.|randomBytes|hash" --type ts

echo -e "\n=== Error handling patterns ==="
rg -A 3 -B 1 "DubApiError" --type ts | head -20

echo -e "\n=== Redis key patterns ==="
rg -A 2 -B 1 "redis.*set|redis.*get|redis.*del" --type ts | head -15

Length of output: 65096


🏁 Script executed:

#!/bin/bash
# Examine the database schema and Prisma models

echo "=== Partner model in Prisma schema ==="
rg -A 15 -B 2 "model Partner" apps/web/lib/prisma/schema.prisma

echo -e "\n=== VerificationToken model ==="
rg -A 10 -B 2 "model VerificationToken" apps/web/lib/prisma/schema.prisma

echo -e "\n=== User model relevant fields ==="
rg -A 20 -B 2 "model User" apps/web/lib/prisma/schema.prisma

Length of output: 677


@steven-tey I've conducted an exhaustive final review of the email verification implementation for partner profiles. Here's my detailed analysis with critical findings:

🚨 CRITICAL SECURITY VULNERABILITIES

1. Token Identifier Collision Risk (P0 - URGENT)

Location: apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx:47

// CURRENT - VULNERABLE
const identifier = tokenFound.identifier.startsWith("pn_") 
  ? partnerId 
  : userId;

Issue: Partner IDs and User IDs could overlap, causing verification tokens to conflict.

Fix:

// SECURE IMPLEMENTATION
const identifier = tokenFound.identifier.startsWith("partner_") 
  ? partnerId 
  : userId;

// And in confirmEmailChange function:
const tokenIdentifier = isPartnerProfile ? `partner_${identifier}` : `user_${identifier}`;

2. Race Condition in Email Uniqueness Check (P0 - URGENT)

Location: apps/web/lib/actions/partners/update-partner-profile.ts:89-95

// CURRENT - VULNERABLE 
const updatedPartner = await prisma.partner.update({ ... });

// Email uniqueness check happens AFTER update
if (emailChanged) {
  const partnerWithEmail = await prisma.partner.findFirst({
    where: { email: newEmail }
  });
}

Fix:

// SECURE IMPLEMENTATION
if (emailChanged) {
  // Check BEFORE updating
  const existingPartner = await prisma.partner.findFirst({
    where: { 
      email: newEmail,
      id: { not: partner.id } // Exclude current partner
    }
  });
  
  if (existingPartner) {
    throw new Error(`Email ${newEmail} is already in use...`);
  }
}

const updatedPartner = await prisma.partner.update({ ... });

3. Missing Rate Limiting on Token Verification (P1 - HIGH)

Location: Token verification endpoint lacks brute force protection

Fix:

// Add to confirm-email-change page
import { ratelimit } from "@/lib/upstash";

const VerifyEmailChange = async ({ params: { token } }: PageProps) => {
  // Add rate limiting
  const { success } = await ratelimit(10, "1 h").limit(`verify-token:${token}`);
  if (!success) {
    return <EmptyState title="Too Many Attempts" />;
  }
  // ... rest of verification logic
};

🐛 LOGIC & DATA INTEGRITY ISSUES

4. Missing Email Validation (P1 - HIGH)

Location: apps/web/lib/actions/partners/update-partner-profile.ts:74

// ADD THIS VALIDATION
if (newEmail === partner.email) {
  throw new Error("New email must be different from current email");
}

5. Incomplete Error Handling (P2 - MEDIUM)

Location: apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx:108

// CURRENT - Silent failures
waitUntil(
  Promise.allSettled([
    deleteRequest(tokenFound),
    // ... other operations
  ])
);

// IMPROVED - With error logging
waitUntil(
  Promise.allSettled([
    deleteRequest(tokenFound),
    // ... other operations
  ]).then(results => {
    results.forEach((result, index) => {
      if (result.status === 'rejected') {
        console.error(`Operation ${index} failed:`, result.reason);
      }
    });
  })
);

6. Redis Key Cleanup Issues (P2 - MEDIUM)

Location: Redis keys may not get cleaned up properly on verification failure

Fix:

// Add cleanup in error cases
try {
  // verification logic
} catch (error) {
  // Ensure cleanup
  await redis.del(`email-change-request:user:${identifier}`);
  throw error;
}

🔧 CODE QUALITY IMPROVEMENTS

7. Magic Numbers (P3 - LOW)

// CURRENT
const expiresIn = 15 * 60 * 1000;

// IMPROVED
const EMAIL_VERIFICATION_EXPIRY = 15 * 60 * 1000; // 15 minutes

8. Inconsistent Error Messages (P3 - LOW)

Some error messages reference "user" even in partner profile context. Standardize messaging.

📊 SECURITY ASSESSMENT SUMMARY

Issue Severity Impact Effort
Token Collision CRITICAL Data corruption LOW
Race Condition CRITICAL Duplicate emails LOW
Missing Rate Limiting HIGH Brute force attacks MEDIUM
Missing Validation HIGH UX confusion LOW

RECOMMENDATIONS

IMMEDIATE (Before Merge):

  1. Fix token identifier collision (5 min fix)
  2. Fix race condition in email check (10 min fix)
  3. Add email validation (2 min fix)

NEXT SPRINT:

  1. Add rate limiting to verification endpoint
  2. Improve error handling and logging
  3. Add comprehensive tests for edge cases

The core architecture is solid, but these security gaps need immediate attention. The token collision and race condition issues are particularly concerning as they could lead to data integrity problems in production.

✅ Actions performed

Full review triggered.

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

♻️ Duplicate comments (4)
apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx (1)

9-13: Remove async from React client component declaration.

React client components cannot be async functions. The async keyword should be removed from the component function declaration.

-export default async function ConfirmEmailChangePageClient({
+export default function ConfirmEmailChangePageClient({
  isPartnerProfile,
}: {
  isPartnerProfile: boolean;
}) {
apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx (1)

83-87: Add explicit session-context validation for token identifiers.

The code determines the identifier based on token prefix but lacks validation that the identifier matches the current session context. This could lead to security issues if tokens are misused across users or partners.

Add validation immediately after computing the identifier:

 const identifier = tokenFound.identifier.startsWith("pn_")
   ? partnerId
   : userId;

+// Validate that the token identifier truly belongs to this session
+if (tokenFound.identifier.startsWith("pn_")) {
+  if (!partnerId) {
+    throw new Error("Partner token used but no partner ID in session");
+  }
+  if (tokenFound.identifier !== partnerId) {
+    throw new Error("Partner token identifier mismatch with session");
+  }
+} else {
+  if (tokenFound.identifier !== userId) {
+    throw new Error("User token identifier mismatch with session");
+  }
+}
apps/web/lib/actions/partners/update-partner-profile.ts (2)

164-180: Fix null-safe string comparisons.

The string comparisons are not null-safe and could throw runtime errors when partner fields are null but input fields are defined, or vice versa.

Apply null-safe comparisons:

 const countryChanged =
-  partner.country?.toLowerCase() !== input.country?.toLowerCase();
+  (partner.country?.toLowerCase() || null) !== (input.country?.toLowerCase() || null);

 const companyNameChanged =
-  partner.companyName?.toLowerCase() !== input.companyName?.toLowerCase();
+  (partner.companyName?.toLowerCase() || null) !== (input.companyName?.toLowerCase() || null);

181-218: Consider transaction safety for Stripe deletion.

The logic correctly prevents changes when payouts exist, but the Stripe account deletion and database update aren't atomic, which could lead to inconsistency.

Wrap in a transaction for atomicity:

 if (partner.stripeConnectId) {
   const response = await stripe.accounts.del(partner.stripeConnectId);

   if (response.deleted) {
+    await prisma.$transaction(async (tx) => {
-      await prisma.partner.update({
+      await tx.partner.update({
         where: {
           id: partner.id,
         },
         data: {
           stripeConnectId: null,
           payoutsEnabledAt: null,
         },
       });
+    });
   }
 }
🧹 Nitpick comments (3)
packages/email/src/templates/confirm-email-change.tsx (1)

20-20: Use a generic placeholder instead of a specific token.

The default value contains what appears to be a specific token. Default values should use generic placeholders to avoid confusion and potential security issues.

-  confirmUrl = "https://dub.co/auth/confirm-email-change/d03324452e1ac9352954315f3ffc",
+  confirmUrl = "https://dub.co/auth/confirm-email-change/[token]",
apps/web/lib/auth/confirm-email-change.ts (1)

57-57: Consider more descriptive Redis key naming.

The Redis key uses user: prefix for both user and partner profiles, which could be confusing. Consider using a more generic prefix or distinguishing between user and partner contexts.

-`email-change-request:user:${identifier}`,
+`email-change-request:${isPartnerProfile ? 'partner' : 'user'}:${identifier}`,
apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx (1)

144-164: Consider adding error handling for failed cleanup operations.

Using Promise.allSettled is good practice to prevent one failed operation from blocking others. However, consider adding error logging to track failed operations:

  waitUntil(
-   Promise.allSettled([
+   Promise.allSettled([
      deleteRequest(tokenFound),
      ...(user?.subscribed
        ? [
            unsubscribe({ email: data.email }),
            subscribe({ email: data.newEmail }),
          ]
        : []),
      sendEmail({
        subject: "Your email address has been changed",
        email: data.email,
        react: EmailUpdated({
          oldEmail: data.email,
          newEmail: data.newEmail,
        }),
      }),
-   ]),
+   ]).then((results) => {
+     results.forEach((result, index) => {
+       if (result.status === 'rejected') {
+         console.error(`Email change cleanup operation ${index} failed:`, result.reason);
+       }
+     });
+   }),
  );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d39de5b and aa90253.

📒 Files selected for processing (7)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (1 hunks)
  • apps/web/app/api/user/route.ts (2 hunks)
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx (2 hunks)
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx (4 hunks)
  • apps/web/lib/actions/partners/update-partner-profile.ts (5 hunks)
  • apps/web/lib/auth/confirm-email-change.ts (1 hunks)
  • packages/email/src/templates/confirm-email-change.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: in the dub codebase, server-side validation errors for password fields are handled via toast notific...
Learnt from: TWilson023
PR: dubinc/dub#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/api/user/route.ts
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx
📚 Learning: in apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward conditi...
Learnt from: TWilson023
PR: dubinc/dub#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/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx
  • apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx
  • apps/web/lib/actions/partners/update-partner-profile.ts
📚 Learning: in apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition ob...
Learnt from: TWilson023
PR: dubinc/dub#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/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
📚 Learning: in the singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in th...
Learnt from: devkiran
PR: dubinc/dub#2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
📚 Learning: in the partnerprogramsummary email template (packages/email/src/templates/partner-program-summary.ts...
Learnt from: devkiran
PR: dubinc/dub#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/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
📚 Learning: in apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calcul...
Learnt from: TWilson023
PR: dubinc/dub#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/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx
📚 Learning: in the dub codebase, multiple payout records can now share the same stripetransferid because payouts...
Learnt from: devkiran
PR: dubinc/dub#2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.693Z
Learning: In the Dub codebase, multiple payout records can now share the same stripeTransferId because payouts are grouped by partner and processed as single Stripe transfers. This is why the @unique constraint was removed from the stripeTransferId field in the Payout model - a single transfer can include multiple payouts for the same partner.

Applied to files:

  • apps/web/lib/actions/partners/update-partner-profile.ts
📚 Learning: in the dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it confi...
Learnt from: devkiran
PR: dubinc/dub#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.

Applied to files:

  • apps/web/lib/actions/partners/update-partner-profile.ts
📚 Learning: in the dub codebase, prefer keeping semantically different functions separate even if they currently...
Learnt from: TWilson023
PR: dubinc/dub#2484
File: apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/usage/get-recommended-plan.ts:11-12
Timestamp: 2025-06-25T18:08:53.931Z
Learning: In the Dub codebase, prefer keeping semantically different functions separate even if they currently have identical implementations. This maintains readability and allows for easy future divergence when business logic changes. For example, `hasConversions` and `hasPartners` functions should remain separate despite having the same current logic, as they serve different conceptual purposes and may need different feature checks in the future.

Applied to files:

  • apps/web/lib/actions/partners/update-partner-profile.ts
🔇 Additional comments (14)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (1)

208-216: LGTM! Proper user feedback for email verification flow.

The conditional toast notification correctly handles both scenarios - prompting users to verify their email when needed and confirming successful profile updates otherwise. This provides clear user guidance for the email verification process.

apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page-client.tsx (1)

27-27: LGTM! Proper conditional redirect logic.

The conditional redirect correctly routes users to the appropriate page based on whether they're updating a partner profile or user profile.

apps/web/app/api/user/route.ts (1)

115-120: LGTM! Good refactoring to centralize email change logic.

The delegation to confirmEmailChange helper function improves code organization and maintainability. The email uniqueness check is correctly performed before initiating the verification process, which prevents potential race conditions.

apps/web/lib/auth/confirm-email-change.ts (2)

24-34: LGTM! Appropriate rate limiting implementation.

The rate limiting of 3 requests per day per identifier is reasonable and helps prevent abuse of the email change verification system.


11-79: Ensure Global Uniqueness of User and Partner Identifiers

I wasn’t able to locate any clear separation or prefixing of user vs. partner IDs in the codebase. To avoid token‐collision risks, please manually verify that:

  • User and partner IDs are generated from distinct namespaces, have different prefixes, or are otherwise globally unique.
  • Your Prisma schema (or other ORM/database layer) enforces separate ID strategies or non‐overlapping ranges for both models.
  • Any in‐application ID‐generation utilities (UUID, NanoID, etc.) are scoped per entity type if you’re not relying solely on the database’s defaults.
apps/web/app/app.dub.co/(auth)/auth/confirm-email-change/[token]/page.tsx (4)

9-9: LGTM!

The addition of the User import is necessary for the type annotations in the updated email confirmation logic.


89-93: LGTM!

The Redis key construction and data structure updates correctly support the unified email confirmation flow for both users and partners.


105-142: Well-structured email update logic.

The conditional branching between partner and user email updates is implemented correctly:

  • Proper validation for partner profile requirements
  • Preserved subscription handling for user email changes
  • Clear error messaging for missing partner context

166-181: LGTM!

The component correctly passes the profile type context to the client component, and the deleteRequest function improvement with Promise.allSettled maintains consistency with the main cleanup logic.

apps/web/lib/actions/partners/update-partner-profile.ts (5)

3-14: LGTM!

The import additions correctly support the refactored email verification flow and new helper function requirements.


56-79: Well-structured refactoring improvements.

The changes improve code organization and clarity:

  • Renaming email to newEmail provides better semantic meaning
  • Extracting Stripe account deletion logic improves modularity
  • Simplified image upload logic with conditional execution is cleaner
  • Email change tracking variables support the new verification workflow

96-119: Proper email verification flow implementation.

The email verification logic is correctly implemented:

  • Email uniqueness check prevents conflicts before starting verification
  • Centralized confirmEmailChange helper maintains consistency
  • Clear error messaging with helpful merge suggestion
  • Proper flag return for client-side handling

The flow correctly checks uniqueness before triggering verification, with the actual email update happening in the confirmation step.


147-149: LGTM!

The return value correctly provides the email verification state to the client for appropriate UI feedback.


157-163: LGTM!

The helper function has a clear signature with well-typed parameters that promote good separation of concerns.

});

const token = randomBytes(32).toString("hex");
const expiresIn = 15 * 60 * 1000;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace magic number with named constant.

The 15-minute expiration time should be defined as a named constant for better maintainability and clarity.

+const EMAIL_VERIFICATION_EXPIRY_MS = 15 * 60 * 1000; // 15 minutes
+
 const token = randomBytes(32).toString("hex");
-const expiresIn = 15 * 60 * 1000;
+const expiresIn = EMAIL_VERIFICATION_EXPIRY_MS;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const expiresIn = 15 * 60 * 1000;
// Define a named constant for the 15-minute expiry
const EMAIL_VERIFICATION_EXPIRY_MS = 15 * 60 * 1000; // 15 minutes
const token = randomBytes(32).toString("hex");
const expiresIn = EMAIL_VERIFICATION_EXPIRY_MS;
🤖 Prompt for AI Agents
In apps/web/lib/auth/confirm-email-change.ts at line 44, replace the magic
number 15 * 60 * 1000 with a named constant. Define a descriptive constant like
EMAIL_CHANGE_TOKEN_EXPIRATION_MS at the top of the file or in a relevant
constants file, assign it the value 15 * 60 * 1000, and then use this constant
in place of the magic number for better clarity and maintainability.

@steven-tey steven-tey merged commit 54cd0c2 into main Aug 2, 2025
8 checks passed
@steven-tey steven-tey deleted the partner-confirm-email branch August 2, 2025 23:22
@coderabbitai coderabbitai bot mentioned this pull request Sep 24, 2025
@coderabbitai coderabbitai bot mentioned this pull request Oct 4, 2025
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.

2 participants

点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载