+
Skip to content

Tolt importer #2600

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

Merged
merged 64 commits into from
Jul 7, 2025
Merged

Tolt importer #2600

merged 64 commits into from
Jul 7, 2025

Conversation

devkiran
Copy link
Collaborator

@devkiran devkiran commented Jul 2, 2025

Summary by CodeRabbit

  • New Features

    • Added support for importing partners, links, referrals, commissions, and Stripe customer synchronization from Tolt, including a new import option and modal in the dashboard.
    • Enabled authentication and storage of Tolt API credentials.
    • Added automated partner cleanup for unused partners after import.
    • Introduced dynamic import source selection supporting Rewardful and Tolt in program setup and rewards forms.
    • Added new modals and UI components for Tolt token input and program import details.
  • Improvements

    • Enhanced import progress visibility and error handling with user notifications.
    • Updated email notifications to support Tolt as a provider.
    • Generalized import UI and forms to support multiple import sources dynamically.
  • Bug Fixes

    • Improved error logging for API errors.
  • Developer Experience

    • Introduced comprehensive data validation, typing, and API integration for Tolt.
    • Improved partner link creation with customizable keys.
  • Tests

    • Added integration tests for referral embed token generation API endpoint.

Copy link

vercel bot commented Jul 2, 2025

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

Name Status Preview Updated (UTC)
dub ✅ Ready (Inspect) Visit Preview Jul 7, 2025 3:27pm

Copy link
Contributor

coderabbitai bot commented Jul 2, 2025

"""

Walkthrough

This update introduces comprehensive support for importing affiliate program data from Tolt. It adds backend modules for Tolt API integration, data import, credential management, partner cleanup, and Stripe customer synchronization. The UI is extended with a new modal and import option for Tolt. Supporting types, schemas, and email template updates are included for end-to-end Tolt import functionality.

Changes

Files/Paths Change Summary
apps/web/app/(ee)/api/cron/import/tolt/route.ts New API route for handling Tolt import/update actions, signature verification, and action dispatch.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx Added Tolt import option, modal hook, and UI integration for importing partners from Tolt.
apps/web/lib/actions/partners/set-tolt-token.ts
apps/web/lib/actions/partners/start-tolt-import.ts
New server actions for setting Tolt credentials and starting Tolt import.
apps/web/lib/api/errors.ts Simplified error logging in handleApiError.
apps/web/lib/tolt/api.ts New ToltApi class for interacting with Tolt API endpoints and parsing responses.
apps/web/lib/tolt/import-affiliates.ts
apps/web/lib/tolt/import-links.ts
apps/web/lib/tolt/import-referrals.ts
apps/web/lib/tolt/import-commissions.ts
apps/web/lib/tolt/update-stripe-customers.ts
apps/web/lib/tolt/cleanup-partners.ts
New modules for importing affiliates, links, referrals, commissions, updating Stripe customers, and cleaning up partners from Tolt.
apps/web/lib/tolt/importer.ts New ToltImporter class for credential management, import queuing, and partner ID tracking.
apps/web/lib/tolt/schemas.ts New Zod schemas for Tolt program, affiliate, link, customer, and commission entities.
apps/web/lib/tolt/types.ts New TypeScript types and interfaces for Tolt entities and config.
apps/web/ui/modals/import-tolt-modal.tsx New modal component and hook for Tolt import, with step-based UI and actions.
apps/web/ui/modals/modal-provider.tsx Integrated Tolt import modal into modal context and provider.
packages/email/src/templates/campaign-imported.tsx Expanded provider prop in CampaignImported email template to accept "Tolt".
apps/web/lib/api/partners/create-partner-link.ts Extended PartnerLinkArgs type and generatePartnerLink to accept optional custom key property.
apps/web/tests/embed-tokens/referrals.test.ts Added integration tests for POST /api/tokens/embed/referrals endpoint covering various scenarios.
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/partners/form.tsx Generalized import UI to support both Rewardful and Tolt import sources dynamically.
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx Refactored rewards import form to support multiple import sources with conditional subforms.
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-rewardful-form.tsx Added new React component for importing Rewardful campaigns with token and campaign selection UI.
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-tolt-form.tsx Added new React component for importing Tolt programs via API token and program ID input.
apps/web/lib/actions/partners/create-program.ts Extended program creation to queue Tolt import actions if Tolt data is provided.
apps/web/lib/partners/constants.ts Added PROGRAM_IMPORT_SOURCES constant listing Rewardful and Tolt import sources with metadata.
apps/web/lib/zod/schemas/program-onboarding.ts Added optional tolt property to program reward schema for Tolt program data with masked token.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WebApp
    participant ImportToltModal
    participant ServerActions
    participant ToltApi
    participant DB
    participant Queue

    User->>WebApp: Clicks "Import from Tolt"
    WebApp->>ImportToltModal: Show modal
    ImportToltModal->>User: Collect API key & Program ID
    User->>ImportToltModal: Submit credentials
    ImportToltModal->>ServerActions: setToltTokenAction
    ServerActions->>ToltApi: Fetch program details
    ToltApi->>ServerActions: Return program data
    ServerActions->>DB: Store credentials
    ServerActions->>ImportToltModal: Return program info
    ImportToltModal->>User: Display program info
    User->>ImportToltModal: Start import
    ImportToltModal->>ServerActions: startToltImportAction
    ServerActions->>Queue: Queue "import-affiliates" action
    Note right of Queue: Import pipeline begins (affiliates, links, referrals, etc.)
Loading

Possibly related PRs

Suggested reviewers

  • steven-tey

Poem

A Tolt import hops into view,
With modals and queues, and APIs too!
Affiliates, links, and partners align,
Stripe IDs updated, all working fine.
From schemas to types, the groundwork is set—
This bunny’s delighted, it’s Tolt we now get!
🐇✨
"""


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between dbcde7a and 989a362.

📒 Files selected for processing (1)
  • apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-rewardful-form.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-rewardful-form.tsx
⏰ 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
✨ Finishing Touches
  • 📝 Generate Docstrings

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

🧹 Nitpick comments (8)
apps/web/lib/api/errors.ts (1)

125-125: Consider the impact on error log clarity.

Removing the "API error occurred" prefix reduces context in error logs, which may make debugging more challenging. Developers won't immediately know that logged errors originated from API handlers.

If cleaner logs are desired, consider using a structured logging approach instead of removing contextual information entirely.

apps/web/lib/actions/partners/start-tolt-import.ts (1)

33-39: Consider validating credential freshness.

The code checks if credentials exist but doesn't verify they're still valid. Tolt API tokens might expire or become invalid, which could cause the queued import to fail.

Consider adding a quick validation call to the Tolt API before queuing the import task.

apps/web/app/(ee)/api/cron/import/tolt/route.ts (1)

31-47: Consider adding exhaustiveness check for the switch statement.

While the schema validation should prevent invalid actions, consider adding a TypeScript exhaustiveness check for better type safety:

    switch (action) {
      case "import-affiliates":
        await importAffiliates(payload);
        break;
      case "import-links":
        await importLinks(payload);
        break;
      case "import-referrals":
        await importReferrals(payload);
        break;
      case "import-commissions":
        await importCommissions(payload);
        break;
      case "update-stripe-customers":
        await updateStripeCustomers(payload);
        break;
+     default:
+       // Exhaustiveness check
+       const _exhaustive: never = action;
+       throw new Error(`Unknown action: ${_exhaustive}`);
    }
apps/web/lib/tolt/update-stripe-customers.ts (1)

118-121: Use appropriate log level for expected scenarios.

Finding no Stripe customer might be expected behavior rather than an error.

  if (stripeCustomers.data.length === 0) {
-   console.error(`Stripe search returned no customer for ${customer.email}`);
+   console.log(`No Stripe customer found for ${customer.email}`);
    return null;
  }
apps/web/lib/tolt/import-commissions.ts (3)

97-97: Use optional chaining for safer property access.

The static analysis correctly identifies that this can be simplified with optional chaining.

-  if (workspaceUser && workspaceUser.user.email) {
+  if (workspaceUser?.user.email) {

144-165: Remove commented dead code.

This large block of commented code should be removed to improve code cleanliness and maintainability.

-  // here, we also check for commissions that have already been recorded on Dub
-  // e.g. during the transition period
-  // since we don't have the Stripe invoiceId from Rewardful, we use the referral's Stripe customer ID
-  // and check for commissions that were created with the same amount and within a +-1 hour window
-  // const chargedAt = new Date(sale.created_at);
-  // const trackedCommission = await prisma.commission.findFirst({
-  //   where: {
-  //     programId,
-  //     type: "sale",
-  //     customer: {
-  //       stripeCustomerId: sale.referral.stripe_customer_id,
-  //     },
-  //     amount: Number(sale.amount),
-  //     createdAt: {
-  //       gte: new Date(chargedAt.getTime() - 60 * 60 * 1000), // 1 hour before
-  //       lte: new Date(chargedAt.getTime() + 60 * 60 * 1000), // 1 hour after
-  //     },
-  //   },
-  // });
-
-  // if (trackedCommission) {
-  //   console.log(
-  //     `Commission ${trackedCommission.id} was already recorded on Dub, skipping...`,
-  //   );
-  //   return;
-  // }

230-230: Address TODO comments about currency handling.

The hardcoded "usd" currency should be resolved. Consider adding currency to the Tolt schema or fetching it from the program/workspace configuration.

Would you like me to help implement proper currency handling by either:

  1. Adding currency field to the Tolt schemas
  2. Fetching currency from program/workspace settings
  3. Creating a currency mapping from Tolt program data

Also applies to: 245-245

apps/web/lib/tolt/api.ts (1)

16-16: Consider increasing PAGE_LIMIT for better batch processing performance.

A page limit of 10 items seems quite small for batch import operations. This could lead to excessive API calls and slower import times, especially for large datasets.

Consider making this configurable or increasing the default:

-const PAGE_LIMIT = 10;
+const PAGE_LIMIT = 100; // or make it configurable
+// const PAGE_LIMIT = process.env.TOLT_PAGE_LIMIT ? parseInt(process.env.TOLT_PAGE_LIMIT) : 100;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 26d76fa and 33cb5d0.

📒 Files selected for processing (17)
  • apps/web/app/(ee)/api/cron/import/tolt/route.ts (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx (3 hunks)
  • apps/web/lib/actions/partners/set-tolt-token.ts (1 hunks)
  • apps/web/lib/actions/partners/start-tolt-import.ts (1 hunks)
  • apps/web/lib/api/errors.ts (1 hunks)
  • apps/web/lib/tolt/api.ts (1 hunks)
  • apps/web/lib/tolt/import-affiliates.ts (1 hunks)
  • apps/web/lib/tolt/import-commissions.ts (1 hunks)
  • apps/web/lib/tolt/import-links.ts (1 hunks)
  • apps/web/lib/tolt/import-referrals.ts (1 hunks)
  • apps/web/lib/tolt/importer.ts (1 hunks)
  • apps/web/lib/tolt/schemas.ts (1 hunks)
  • apps/web/lib/tolt/types.ts (1 hunks)
  • apps/web/lib/tolt/update-stripe-customers.ts (1 hunks)
  • apps/web/ui/modals/import-tolt-modal.tsx (1 hunks)
  • apps/web/ui/modals/modal-provider.tsx (6 hunks)
  • packages/email/src/templates/campaign-imported.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
apps/web/lib/tolt/import-links.ts (1)
Learnt from: devkiran
PR: dubinc/dub#2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.
apps/web/lib/tolt/schemas.ts (1)
Learnt from: devkiran
PR: dubinc/dub#2433
File: apps/web/app/api/workspaces/[idOrSlug]/billing/payment-methods/route.ts:74-76
Timestamp: 2025-05-29T09:47:33.583Z
Learning: When Zod enum validation is used (e.g., `z.enum(PAYMENT_METHOD_TYPES)`), it ensures the validated value is one of the valid enum values, making subsequent type casts to the corresponding TypeScript types safe without additional validation.
apps/web/lib/tolt/import-referrals.ts (1)
Learnt from: devkiran
PR: dubinc/dub#2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.
🧬 Code Graph Analysis (9)
apps/web/lib/actions/partners/set-tolt-token.ts (4)
apps/web/lib/actions/safe-action.ts (1)
  • authActionClient (33-82)
apps/web/lib/tolt/api.ts (1)
  • ToltApi (18-157)
apps/web/lib/tolt/types.ts (1)
  • ToltProgram (22-24)
apps/web/lib/tolt/importer.ts (1)
  • toltImporter (54-54)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx (2)
apps/web/ui/modals/import-tolt-modal.tsx (1)
  • useImportToltModal (290-309)
apps/web/ui/modals/import-rewardful-modal.tsx (1)
  • useImportRewardfulModal (338-358)
apps/web/lib/actions/partners/start-tolt-import.ts (4)
apps/web/lib/actions/safe-action.ts (1)
  • authActionClient (33-82)
apps/web/lib/api/programs/get-default-program-id-or-throw.ts (1)
  • getDefaultProgramIdOrThrow (4-17)
apps/web/lib/api/programs/get-program-or-throw.ts (1)
  • getProgramOrThrow (10-65)
apps/web/lib/tolt/importer.ts (1)
  • toltImporter (54-54)
apps/web/ui/modals/import-tolt-modal.tsx (3)
apps/web/lib/tolt/types.ts (1)
  • ToltProgram (22-24)
apps/web/lib/actions/partners/set-tolt-token.ts (1)
  • setToltTokenAction (15-49)
apps/web/lib/actions/partners/start-tolt-import.ts (1)
  • startToltImportAction (13-45)
apps/web/ui/modals/modal-provider.tsx (1)
apps/web/ui/modals/import-tolt-modal.tsx (1)
  • useImportToltModal (290-309)
apps/web/lib/tolt/types.ts (1)
apps/web/lib/tolt/schemas.ts (5)
  • ToltProgramSchema (3-8)
  • ToltAffiliateSchema (10-19)
  • ToltLinkSchema (21-26)
  • ToltCustomerSchema (28-39)
  • ToltCommissionSchema (41-58)
apps/web/lib/tolt/import-commissions.ts (7)
apps/web/lib/tolt/importer.ts (2)
  • toltImporter (54-54)
  • MAX_BATCHES (7-7)
apps/web/lib/tolt/api.ts (1)
  • ToltApi (18-157)
packages/email/src/index.ts (1)
  • sendEmail (6-29)
packages/email/src/templates/campaign-imported.tsx (1)
  • CampaignImported (17-65)
apps/web/lib/tolt/types.ts (1)
  • ToltCommission (32-32)
packages/prisma/client.ts (1)
  • CommissionStatus (4-4)
apps/web/lib/api/partners/sync-total-commissions.ts (1)
  • syncTotalCommissions (4-35)
apps/web/lib/tolt/importer.ts (1)
apps/web/lib/tolt/types.ts (1)
  • ToltConfig (10-14)
apps/web/lib/tolt/api.ts (2)
apps/web/lib/tolt/types.ts (5)
  • ToltListResponse (16-20)
  • ToltAffiliate (26-26)
  • ToltLink (28-28)
  • ToltCustomer (30-30)
  • ToltCommission (32-32)
apps/web/lib/tolt/schemas.ts (5)
  • ToltProgramSchema (3-8)
  • ToltAffiliateSchema (10-19)
  • ToltLinkSchema (21-26)
  • ToltCustomerSchema (28-39)
  • ToltCommissionSchema (41-58)
🪛 Biome (1.9.4)
apps/web/lib/tolt/import-commissions.ts

[error] 97-97: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (23)
packages/email/src/templates/campaign-imported.tsx (1)

28-28: LGTM! Clean type expansion for Tolt support.

The union type properly extends the email template to support both Rewardful and Tolt providers. The existing template logic already handles the provider dynamically, so this change is all that's needed.

apps/web/lib/actions/partners/start-tolt-import.ts (1)

13-45: Well-structured action with good validation.

The action properly uses authentication middleware, validates program requirements (domain and URL), and follows the established error handling patterns. The logic flow is clear and appropriate.

apps/web/lib/actions/partners/set-tolt-token.ts (1)

15-49: Excellent implementation with robust validation.

The action properly:

  • Enforces workspace-level permissions via partnersEnabled check
  • Validates credentials by actually calling the Tolt API
  • Provides clear error messages for different failure scenarios
  • Stores credentials only after successful validation
  • Returns program data for UI feedback

The try/catch error handling is particularly well done, preserving the original error message while providing a fallback.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx (1)

6-6: LGTM! Consistent implementation following established patterns.

The Tolt import integration properly mirrors the existing Rewardful implementation:

  • Hook usage and modal rendering follow the same pattern
  • UI styling and structure are consistent
  • Router navigation with query parameters matches the established approach
  • Icon and text formatting align with existing import options

This maintains good code consistency and user experience.

Also applies to: 17-17, 25-25, 53-69

apps/web/app/(ee)/api/cron/import/tolt/route.ts (1)

1-53: LGTM! Well-structured cron endpoint with proper authentication and error handling.

The implementation correctly uses Qstash signature verification, validates input with Zod, and dispatches to appropriate import handlers.

apps/web/ui/modals/modal-provider.tsx (1)

30-30: Perfect integration following the established modal pattern.

The ImportToltModal is correctly integrated into the modal provider system, maintaining consistency with other modals.

Also applies to: 46-46, 57-57, 111-111, 203-203, 216-216

apps/web/lib/tolt/import-affiliates.ts (1)

1-77: Well-implemented affiliate import with proper batching and error handling.

The function correctly handles pagination, filters active affiliates, and manages the import queue.

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

1-32: Clean and well-structured type definitions.

Excellent use of Zod inference for type safety and proper generic typing for list responses.

apps/web/lib/tolt/importer.ts (3)

7-9: LGTM! Well-defined constants.

Good practice to define these constants at the module level for reuse across the Tolt import system.


11-17: Excellent use of Zod enum for type safety.

The importSteps enum provides both runtime validation and compile-time type safety across the import workflow.


19-52: Solid implementation of ToltImporter class.

The class provides clean abstractions for:

  • Credential management with Redis caching
  • Proper error handling for missing credentials
  • Type-safe task queuing with QStash

The 24-hour cache expiry is appropriate for import credentials.

apps/web/lib/tolt/import-links.ts (3)

53-57: Efficient partner filtering with Map lookup.

Good performance optimization using Map for O(1) partner email lookup instead of array.find().


64-76: Proper bulk link creation with required fields.

The link mapping correctly includes all required fields and uses fallback generation for missing keys with nanoid().


83-87: Smart conditional queuing logic.

Correctly queues either continuation of link import or progression to referral import based on pagination state.

apps/web/lib/tolt/schemas.ts (3)

3-8: Clean program schema definition.

Simple and focused schema capturing essential program metadata.


10-19: Comprehensive affiliate schema with proper nullable fields.

Good handling of optional fields like company_name and country_code with nullable(), and proper default for status enum.


41-58: Well-structured commission schema with smart field omission.

Excellent use of omit() to prevent redundancy in nested objects while maintaining type safety. The monetary fields are correctly typed as strings (cents) with descriptive comments.

apps/web/ui/modals/import-tolt-modal.tsx (3)

22-40: Good modal state management with URL integration.

Clean integration with search params to control modal visibility, providing better UX for bookmarkable states.


164-173: Helpful external documentation links.

Excellent UX providing direct links to Tolt settings where users can find their credentials.


282-282: Smart import validation logic.

Good business logic preventing imports when there are no affiliates to process.

apps/web/lib/tolt/import-commissions.ts (2)

218-285: Comprehensive commission creation with proper stat updates.

Excellent parallel processing of commission creation, sale recording, and stats updates. The status mapping and deduplication logic are well-implemented.


167-174: Verify stripeCustomerId vs email usage

I wasn’t able to find a stripeCustomerId field in the Prisma schema—please confirm that:

• Under model Customer in packages/prisma/schema/schema.prisma there is a stripeCustomerId field of type String? (or similar) intended to store the Stripe customer ID (cus_…).
• The code is deliberately using customer.email here as a fallback only if you don’t have a true Stripe ID—otherwise you should be passing the actual cus_… identifier.

If this is intentional, consider adding a comment to clarify the fallback behavior; otherwise update the query to use the proper Stripe ID field.

apps/web/lib/tolt/api.ts (1)

128-134: Good handling of the unusual API response structure.

The implementation correctly handles the nested data.data structure from the Tolt customers endpoint, and the comment clearly explains this API quirk for future maintainers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/web/lib/tolt/api.ts (1)

82-86: Avoid sending empty string for optional pagination parameter.

This issue was previously flagged but hasn't been addressed. Using startingAfter || "" sends an empty string when the parameter is undefined, which might not be the intended behavior for the API.

Apply conditional parameter appending:

    const searchParams = new URLSearchParams();
    searchParams.append("program_id", programId);
    searchParams.append("expand[]", "program");
-   searchParams.append("starting_after", startingAfter || "");
+   if (startingAfter) {
+     searchParams.append("starting_after", startingAfter);
+   }
    searchParams.append("limit", PAGE_LIMIT.toString());

This same pattern should be applied to all list methods (lines 105, 125, 148).

🧹 Nitpick comments (2)
apps/web/lib/tolt/api.ts (2)

26-43: Improve error handling robustness.

The current error handling assumes the error response is always JSON with a message property. This could fail if the API returns different error formats (e.g., plain text, HTML, or different JSON structure).

  private async fetch<T>(url: string): Promise<T> {
    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${this.token}`,
      },
    });

    if (!response.ok) {
-     const error = await response.json();
-
-     console.error("Tolt API Error:", error);
-     throw new Error(error.message || "Unknown error from Tolt API.");
+     let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
+     
+     try {
+       const error = await response.json();
+       errorMessage = error.message || error.error || errorMessage;
+     } catch {
+       // If JSON parsing fails, use the HTTP status message
+     }
+     
+     console.error("Tolt API Error:", errorMessage);
+     throw new Error(errorMessage);
    }

    const data = await response.json();

    return data as T;
  }

128-134: Document the API inconsistency workaround.

The nested data.data structure is handled correctly, but this API inconsistency makes the code fragile to future API changes.

Consider adding more detailed documentation about this API quirk:

-   // This might be an issue with the Tolt response, the response is within data.data for this endpoint
+   // WORKAROUND: The Tolt /customers endpoint returns a nested structure { data: { data: [...] } }
+   // unlike other endpoints that return { data: [...] }. This may change in future API versions.
    const { data } = await this.fetch<{ data: { data: ToltCustomer[] } }>(
      `${this.baseUrl}/customers?${searchParams.toString()}`,
    );
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 33cb5d0 and 8f926c0.

📒 Files selected for processing (3)
  • apps/web/lib/tolt/api.ts (1 hunks)
  • apps/web/lib/tolt/import-affiliates.ts (1 hunks)
  • apps/web/lib/tolt/import-links.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/lib/tolt/import-affiliates.ts
  • apps/web/lib/tolt/import-links.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/lib/tolt/api.ts (2)
apps/web/lib/tolt/types.ts (5)
  • ToltListResponse (16-20)
  • ToltAffiliate (26-26)
  • ToltLink (28-28)
  • ToltCustomer (30-30)
  • ToltCommission (32-32)
apps/web/lib/tolt/schemas.ts (5)
  • ToltProgramSchema (3-8)
  • ToltAffiliateSchema (10-19)
  • ToltLinkSchema (21-26)
  • ToltCustomerSchema (28-39)
  • ToltCommissionSchema (41-58)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (1)
apps/web/lib/tolt/api.ts (1)

45-73: Well-implemented workaround with proper error handling.

The approach of using the partners endpoint to fetch program details is well-documented and handles edge cases appropriately. Good use of Zod validation and error handling.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
apps/web/lib/tolt/update-stripe-customers.ts (1)

108-115: Security vulnerability exists: Email not properly escaped in Stripe search query.

This is a duplicate of a previous review comment. The email is directly interpolated into the search query without proper escaping, which could cause issues with special characters or potential injection attacks.

Apply this fix to properly escape the email:

  const stripeCustomers = await stripe.customers.search(
    {
-     query: `email:'${customer.email}'`,
+     query: `email:"${customer.email.replace(/["\\]/g, '\\$&')}"`,
    },
    {
      stripeAccount: workspace.stripeConnectId!,
    },
  );
apps/web/lib/tolt/import-commissions.ts (1)

8-8: Import inconsistency: Using rewardful MAX_BATCHES instead of tolt.

This is a duplicate of a previous review comment. You're importing MAX_BATCHES from the rewardful importer instead of the tolt importer, which could lead to inconsistent behavior.

-import { MAX_BATCHES } from "../rewardful/importer";
+import { MAX_BATCHES } from "./importer";
🧹 Nitpick comments (3)
apps/web/lib/tolt/import-commissions.ts (3)

106-106: Use optional chain for safer property access.

The static analysis correctly identifies that this can be simplified with optional chaining.

-  if (workspaceUser && workspaceUser.user.email) {
+  if (workspaceUser?.user?.email) {

155-176: Consider removing commented code if not needed.

This large block of commented code appears to be from a previous implementation (possibly Rewardful). If it's no longer needed for the Tolt implementation, consider removing it to improve code cleanliness.

If this code might be useful for reference or future implementation, consider adding a comment explaining its purpose.


235-235: Consider making currency configurable.

The currency is hardcoded to "usd" with TODO comments indicating this should be dynamic. While this is acceptable for the initial implementation, consider adding this to the backlog for future enhancement.

Consider creating a tracking issue for dynamic currency support.

Also applies to: 250-250

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8f926c0 and bad2606.

📒 Files selected for processing (5)
  • apps/web/lib/tolt/import-affiliates.ts (1 hunks)
  • apps/web/lib/tolt/import-commissions.ts (1 hunks)
  • apps/web/lib/tolt/import-referrals.ts (1 hunks)
  • apps/web/lib/tolt/schemas.ts (1 hunks)
  • apps/web/lib/tolt/update-stripe-customers.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/web/lib/tolt/import-referrals.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/lib/tolt/import-affiliates.ts
  • apps/web/lib/tolt/schemas.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/lib/tolt/update-stripe-customers.ts (3)
apps/web/lib/stripe/index.ts (2)
  • stripe (3-9)
  • stripeAppClient (12-22)
apps/web/lib/tolt/importer.ts (2)
  • MAX_BATCHES (7-7)
  • toltImporter (54-54)
packages/utils/src/functions/log.ts (1)
  • log (9-45)
🪛 Biome (1.9.4)
apps/web/lib/tolt/import-commissions.ts

[error] 106-106: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (18)
apps/web/lib/tolt/update-stripe-customers.ts (6)

1-11: LGTM! Clean imports and setup.

The imports are well-organized and the Stripe client initialization correctly handles environment-specific configuration.


22-42: LGTM! Proper workspace validation.

Good defensive programming by checking for the presence of stripeConnectId before proceeding with Stripe operations.


47-88: LGTM! Well-implemented batch processing with proper pagination.

The batch processing logic correctly handles:

  • Pagination using cursor-based approach
  • Rate limiting with 2-second delays between batches
  • Proper exit conditions

The implementation ensures efficient processing while respecting API rate limits.


90-98: LGTM! Proper continuation of processing when more data exists.

The queuing mechanism correctly preserves the cursor position to continue processing from where it left off.


117-133: LGTM! Appropriate error handling for Stripe search results.

The function correctly handles both no results and multiple results cases. Logging multiple customers as an error is appropriate since each email should ideally map to a single Stripe customer.


137-144: LGTM! Clean database update operation.

The database update correctly sets the Stripe customer ID when exactly one customer is found.

apps/web/lib/tolt/import-commissions.ts (12)

16-22: LGTM! Clean status mapping.

The status mapping from Tolt to Dub commission statuses is clear and comprehensive.


31-45: LGTM! Proper setup and credential retrieval.

The function correctly retrieves program details and credentials from the centralized importer.


51-76: LGTM! Well-implemented batch processing pattern.

The batch processing follows the same pattern as the Stripe customer update function, with proper pagination and rate limiting.


78-86: LGTM! Proper continuation logic.

The queuing mechanism correctly handles continuation when more batches need to be processed.


88-88: LGTM! Proper cleanup of credentials.

Credentials are correctly deleted after processing is complete.


107-117: LGTM! Proper notification email sending.

The email notification correctly uses the updated template with Tolt provider support.


119-122: LGTM! Proper queuing of follow-up task.

The function correctly queues the Stripe customer update task after commission import completion.


137-149: LGTM! Proper duplicate prevention.

The function correctly checks for existing commissions by invoice ID to prevent duplicates.


178-204: LGTM! Comprehensive validation of required data.

The function properly validates that all required relationships (customer, link, click, partner) exist before proceeding with commission creation.


206-220: LGTM! Proper lead event retrieval and validation.

The function correctly retrieves and validates lead event data, with proper schema parsing.


223-284: LGTM! Comprehensive commission creation with proper side effects.

The function correctly creates the commission record and handles all necessary side effects:

  • Commission record creation
  • Sale event recording
  • Link statistics updates
  • Customer statistics updates

The use of Promise.all ensures these operations happen concurrently for better performance.


286-289: LGTM! Proper commission synchronization.

The function correctly syncs total commissions for the partner after creating the commission.

@steven-tey
Copy link
Collaborator

@CodeRabbit final review

Copy link
Contributor

coderabbitai bot commented Jul 7, 2025

Tip

For best results, initiate chat on the files or code changes.

@steven-tey Here's my final review summary of the Tolt importer implementation:

🎯 Critical Issues That Must Be Addressed

1. Database Transaction Safety

Risk Level: HIGH

// Current approach in import-commissions.ts - NOT atomic
await Promise.all([
  prisma.commission.create(/* ... */),
  recordSaleWithTimestamp(/* ... */),
  prisma.link.update(/* ... */),
  prisma.customer.update(/* ... */),
]);

// Should be:
await prisma.$transaction(async (tx) => {
  // All operations here
});

Impact: Partial imports if any operation fails mid-process.

2. Production Logging ⚠️

Risk Level: MEDIUM

  • 15+ console.log statements throughout codebase
  • Should use structured logging for production monitoring

🚀 Strengths & Architecture Excellence

Security Implementation

  • QStash signature verification prevents unauthorized access
  • Proper input validation with Zod schemas
  • API credentials expire after 24 hours
  • Password-type inputs for API keys

Performance & Scalability

  • Excellent batching strategy (MAX_BATCHES = 5)
  • Cursor-based pagination prevents memory issues
  • Rate limiting (2-second delays) respects API limits
  • Efficient concurrent processing with Promise.allSettled

Code Quality

  • Strong TypeScript usage throughout
  • Clean separation of concerns across modules
  • Comprehensive error handling
  • Well-structured UI with progressive disclosure

📊 Overall Assessment

Architecture Grade: A-

  • Well-designed modular structure
  • Follows Next.js best practices
  • Comprehensive validation pipeline
  • Good user experience design

Production Readiness: B+

  • Core functionality is solid
  • Security measures are appropriate
  • Missing critical database safety measures

🎯 Final Recommendation

MERGE WITH CONDITIONS:

Before Production:

  1. MUST FIX: Add database transactions to all import modules
  2. SHOULD FIX: Replace console.log with proper logging
  3. NICE TO HAVE: Add retry logic for API failures

Estimated effort to address critical issues: 2-3 hours

This is a high-quality implementation that demonstrates strong engineering practices. The architecture is sound, security is well-implemented, and the user experience is excellent. With the transaction fixes, this will be production-ready.

Confidence Level: High - The foundation is solid, just needs transaction safety to be bulletproof. 🐰✨

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/web/tests/embed-tokens/referrals.test.ts (1)

83-97: Robust validation testing for required parameters.

The test properly validates that at least one of partnerId, tenantId, or partner must be provided. The error message assertion could be more specific to avoid brittleness if the exact wording changes.

Consider using a more flexible error message assertion:

-        message: expect.stringContaining(
-          "You must provide either partnerId, tenantId, or partner",
-        ),
+        message: expect.stringMatching(/partnerId|tenantId|partner/),
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1678238 and ce9e90e.

📒 Files selected for processing (1)
  • apps/web/tests/embed-tokens/referrals.test.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/tests/embed-tokens/referrals.test.ts (2)
apps/web/tests/utils/resource.ts (1)
  • E2E_PARTNER (64-67)
apps/web/tests/utils/helpers.ts (2)
  • randomEmail (23-29)
  • randomId (4-4)
⏰ 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 (10)
apps/web/tests/embed-tokens/referrals.test.ts (10)

1-10: Well-structured test setup with good helper usage.

The imports and test setup are clean and appropriate. The use of helper functions for generating random data helps avoid test conflicts, and the expected token response structure provides good validation.


12-16: Good use of sequential testing for stateful operations.

The sequential test execution is appropriate here since you're creating a partner in one test and using it in subsequent tests. The integration harness pattern provides a clean way to handle HTTP requests.


18-28: Solid baseline test using existing partner.

Good starting point that validates the endpoint works with an existing partner. The test structure and assertions are clear.


30-52: Comprehensive partner creation test with good state management.

The test covers creating a new partner with detailed properties and properly stores the tenantId for use in subsequent tests. The partner object includes all relevant fields for a thorough test.


54-65: Effective test of tenantId lookup functionality.

This test validates that the endpoint can find partners by tenantId, which is important for the referral flow. The dependency on the previous test's state is well-documented.


67-81: Good minimal case testing.

Testing with only the required email field ensures the endpoint handles minimal partner creation correctly.


99-114: Proper error handling for non-existent partners.

Good coverage of the 404 case with appropriate error message and code validation.


116-131: Consistent error handling for non-existent tenants.

The test properly validates the same error response for non-existent tenantId, maintaining consistency with the partnerId error case.


133-149: Appropriate email validation testing.

The test validates that invalid email addresses are rejected with proper error messages.


151-167: Complete validation coverage for required fields.

Good final test that ensures email is required when creating a partner. The test suite provides comprehensive coverage of both success and failure scenarios.

devkiran added 5 commits July 7, 2025 15:06
…nt; update Form component to use new type for selectedSource.
…ic import source selection; refactor affiliate count handling in Tolt and Rewardful forms to use 'affiliates' instead of 'total_affiliates'. Update UI components to reflect these changes.
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

🧹 Nitpick comments (3)
apps/web/lib/actions/partners/create-program.ts (1)

156-162: Apply optional chaining for cleaner code.

The static analysis tool correctly suggests using optional chaining to simplify the condition.

-  if (tolt && tolt.id) {
+  if (tolt?.id) {
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-tolt-form.tsx (1)

180-180: Consider safer property access.

While the current logic ensures affiliates exists when this renders, using optional chaining would be more defensive.

-              {toltProgram.affiliates?.toLocaleString() || "0"}
+              {(toltProgram.affiliates ?? 0).toLocaleString()}
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx (1)

99-109: Use optional chaining for cleaner code.

The logic is correct, but the static analysis tool correctly suggests using optional chaining for more concise code.

Apply this diff to use optional chaining:

-      if (rewardful && rewardful.id) {
+      if (rewardful?.id) {
         setSelectedSource(PROGRAM_IMPORT_SOURCES[0]);
-      } else if (tolt && tolt.id) {
+      } else if (tolt?.id) {
         setSelectedSource(PROGRAM_IMPORT_SOURCES[1]);
       }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ce9e90e and 06b7354.

📒 Files selected for processing (11)
  • apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/partners/form.tsx (5 hunks)
  • apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx (5 hunks)
  • apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-rewardful-form.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-tolt-form.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx (3 hunks)
  • apps/web/lib/actions/partners/create-program.ts (3 hunks)
  • apps/web/lib/partners/constants.ts (1 hunks)
  • apps/web/lib/tolt/api.ts (1 hunks)
  • apps/web/lib/tolt/types.ts (1 hunks)
  • apps/web/lib/zod/schemas/program-onboarding.ts (2 hunks)
  • apps/web/ui/modals/import-tolt-modal.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • apps/web/lib/partners/constants.ts
  • apps/web/ui/modals/import-tolt-modal.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx
  • apps/web/lib/tolt/types.ts
  • apps/web/lib/tolt/api.ts
🧰 Additional context used
🧠 Learnings (2)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/partners/form.tsx (2)
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.
Learnt from: TWilson023
PR: dubinc/dub#2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.
apps/web/lib/actions/partners/create-program.ts (1)
Learnt from: devkiran
PR: dubinc/dub#2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.
🧬 Code Graph Analysis (4)
apps/web/lib/zod/schemas/program-onboarding.ts (1)
apps/web/lib/tolt/schemas.ts (1)
  • ToltProgramSchema (3-8)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/partners/form.tsx (1)
apps/web/lib/partners/constants.ts (1)
  • PROGRAM_IMPORT_SOURCES (64-77)
apps/web/lib/actions/partners/create-program.ts (1)
apps/web/lib/tolt/importer.ts (1)
  • toltImporter (90-90)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-rewardful-form.tsx (1)
apps/web/lib/types.ts (1)
  • ProgramData (476-476)
🪛 Biome (1.9.4)
apps/web/lib/actions/partners/create-program.ts

[error] 157-157: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx

[error] 102-102: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 104-104: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ 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 (11)
apps/web/lib/zod/schemas/program-onboarding.ts (1)

1-1: LGTM: Well-structured schema extension.

The Tolt schema integration follows the established pattern used for Rewardful, properly extending the base schema with the required maskedToken and affiliates fields while maintaining nullish optionality.

Also applies to: 33-36

apps/web/lib/actions/partners/create-program.ts (1)

8-8: LGTM: Consistent import integration pattern.

The Tolt importer integration follows the same reliable pattern established for Rewardful, maintaining consistency in the codebase.

Also applies to: 46-46

apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/partners/form.tsx (2)

4-4: LGTM: Excellent refactoring for multiple import sources.

The generalization to support both Rewardful and Tolt import sources is well-implemented, using the PROGRAM_IMPORT_SOURCES constant for maintainable, dynamic rendering logic.

Also applies to: 41-41, 68-73


77-77: LGTM: Clean dynamic UI rendering.

The conditional rendering based on importSource properly displays the appropriate import metadata and affiliate counts for either Rewardful or Tolt sources.

Also applies to: 87-89, 97-97

apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-tolt-form.tsx (2)

21-47: LGTM: Well-designed two-step import flow.

The component implements a clean two-step flow with proper state management. The useEffect correctly synchronizes form state with component state when data is already present.


98-104: LGTM: Proper security handling for API tokens.

Using type="password" for the API token input appropriately masks sensitive credentials in the UI.

apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-rewardful-form.tsx (2)

27-71: LGTM: Solid form implementation with proper state management.

The component effectively handles token validation, campaign fetching, and form state synchronization with good separation of concerns.


66-70: LGTM: Robust commission formatting logic.

The formatCommission callback properly handles both percentage and dollar amount formatting with appropriate decimal precision.

apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx (3)

5-5: LGTM! Good modularization of import functionality.

The new imports support the multi-source import feature well. Moving the import logic to dedicated components (ImportRewardfulForm and ImportToltForm) improves code organization and maintainability.

Also applies to: 10-15, 20-20, 28-29


63-64: Clean state management for multi-source imports.

The type definition properly derives from the constant, and the state management correctly handles both import sources. Good job ensuring data is cleared when switching program types.

Also applies to: 69-71, 81-86, 91-91


141-143: Correct validation logic for multi-source imports.

The button state logic properly ensures at least one valid import source is selected before allowing submission.

Also applies to: 147-148

@steven-tey
Copy link
Collaborator

@CodeRabbit final review

Copy link
Contributor

coderabbitai bot commented Jul 7, 2025

Tip

For best results, initiate chat on the files or code changes.

@steven-tey, here's my final verdict on the Tolt importer PR:

🎯 Final Decision: APPROVE WITH CONDITIONS

Critical Blockers Before Production:

  1. Add database transactions to import modules (2-3 hour fix)
  2. Replace console.log with structured logging (1-2 hour fix)

Overall Assessment:

  • Architecture: A- - Excellent design patterns and separation of concerns
  • Security: A - Proper authentication, validation, and credential management
  • Performance: A - Smart batching, pagination, and rate limiting
  • Code Quality: A- - Strong TypeScript usage and error handling

Recommendation:

✅ MERGE - This is a high-quality implementation with solid foundations. The critical issues are straightforward fixes that don't affect the core architecture.

Risk Level: Low (after transaction fixes)
Confidence: High

The feature is well-architected and production-ready pending the transaction safety fixes. Great work on this comprehensive integration! 🐰✨

@steven-tey steven-tey merged commit b65b856 into main Jul 7, 2025
8 checks passed
@steven-tey steven-tey deleted the tolt branch July 7, 2025 16:09
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浏览器服务,不要输入任何密码和下载