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

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Oct 2, 2025

Summary by CodeRabbit

  • New Features

    • Added partner deactivation/reactivation flows with dedicated modals and updated action menus (archive/unarchive, deactivate/reactivate, ban/unban).
    • Introduced and surfaced a new “Deactivated” status across dashboards, badges, and program cards with clearer user messaging.
    • Expanded link creation/editing restrictions to include deactivated/rejected statuses, with contextual error messages.
  • Style

    • Refreshed archive/ban/unban modals with improved layouts, icons, and action bars for a clearer, more consistent UI.

@vercel
Copy link
Contributor

vercel bot commented Oct 2, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Oct 2, 2025 3:49am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 2, 2025

Walkthrough

Expands enrollment status handling across APIs and UI to include “deactivated” (and sometimes “rejected”). Adds partner deactivate/reactivate flows with new server actions, modals, and menu options. Updates audit-log schema, status badges, and Prisma enum. Minor import/path and param handling adjustments. UI texts and tooltips updated accordingly.

Changes

Cohort / File(s) Summary
API routes: broaden forbidden statuses
apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts, apps/web/app/(ee)/api/embed/referrals/links/route.ts, apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/[linkId]/route.ts, apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts
POST/PATCH now forbid additional enrollment statuses: include “deactivated” and/or “rejected”; error messages use dynamic status.
Partners dashboard: enrolled program views
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx, .../programs/[programSlug]/(enrolled)/auth.tsx, .../programs/[programSlug]/(enrolled)/links/page-client.tsx, .../programs/[programSlug]/(enrolled)/unapproved-program-page.tsx, apps/web/ui/partners/program-card.tsx
Minor logic/UI tweaks: status arrays reordered; removed unused usePathname; link creation gating simplified; added “deactivated” state and unified copy for banned/deactivated/rejected messaging.
Program partners admin pages
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/layout.tsx, .../[partnerId]/links/page-client.tsx, .../partners/partners-table.tsx
Introduces archive/deactivate/reactivate/unban modals; updates actions menu with new status-aware options and icons; adjusts import paths for add-partner-link modal.
Server actions: partner status transitions
apps/web/lib/actions/partners/deactivate-partner.ts, apps/web/lib/actions/partners/reactivate-partner.ts
New actions to deactivate/reactivate partners; validate input, transact enrollment/links updates, expire caches, and record audit logs via waitUntil.
Modals: add/refresh partner status flows
apps/web/ui/modals/deactivate-partner-modal.tsx, apps/web/ui/modals/reactivate-partner-modal.tsx, apps/web/ui/modals/archive-partner-modal.tsx, apps/web/ui/modals/ban-partner-modal.tsx, apps/web/ui/modals/unban-partner-modal.tsx
New deactivate/reactivate/ban modals; archive/unban modals restyled; all wired to actions, toasts, and SWR cache updates; confirmation UX enforced where applicable.
Schemas, auth, and enums
apps/web/lib/zod/schemas/partners.ts, apps/web/lib/api/audit-logs/schemas.ts, apps/web/lib/auth/partner.ts, packages/prisma/schema/program.prisma
Adds deactivatePartnerSchema; audit action enum adds partner.deactivated/reactivated and reorders entries; withPartnerProfile awaits params; ProgramEnrollmentStatus enum adds “deactivated”.
Status badges
apps/web/ui/partners/partner-status-badges.ts
Adds deactivated badge; enriches declined styling; updates icons (CircleXmark, EnvelopeAlert).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as DeactivatePartnerModal
  participant Action as deactivatePartnerAction
  participant DB as Prisma (TX)
  participant BG as waitUntil
  participant Cache as linkCache
  participant Logs as audit.log

  User->>UI: Confirm "deactivate partner"
  UI->>Action: workspaceId, partnerId
  Action->>DB: Verify enrollment (must not already be deactivated)
  DB-->>Action: Enrollment OK
  Action->>DB: TX: expire links + set enrollment.status=deactivated
  DB-->>Action: TX committed
  Action->>BG: enqueue post-tasks
  BG->>Cache: expireMany(partner links)
  BG->>Logs: record "partner.deactivated"
  UI-->>User: Success toast and refresh
Loading
sequenceDiagram
  autonumber
  actor User
  participant UI as ReactivatePartnerModal
  participant Action as reactivatePartnerAction
  participant DB as Prisma (TX)
  participant BG as waitUntil
  participant Cache as linkCache
  participant Logs as audit.log

  User->>UI: Click Reactivate
  UI->>Action: workspaceId, partnerId
  Action->>DB: Require enrollment.status=deactivated
  DB-->>Action: Enrollment fetched
  Action->>DB: TX: unexpire links + set status=approved
  DB-->>Action: TX committed
  Action->>BG: enqueue post-tasks
  BG->>Cache: expireMany(partner links)
  BG->>Logs: record "partner.reactivated"
  UI-->>User: Success toast and refresh
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • devkiran
  • TWilson023

Poem

A whisk of paws, I flip the switch—deactivate, reactivate, click!
New badges gleam, enums grow,
Menus sprout where actions flow.
Logs record each hop and track,
Caches poof—then hopping back.
Carrot cheers for cleaner states—thump-thump, ship that update! 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the core new functionality of adding partner deactivation support across the codebase, reflecting the primary feature introduced in the pull request without unnecessary detail or noise. It is specific, concise, and clearly indicates the main change for reviewers scanning the history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch deactivated-partner

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Caution

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

⚠️ Outside diff range comments (2)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/[linkId]/route.ts (1)

31-36: Update error message to reflect both banned and deactivated states.

The status check now includes both "banned" and "deactivated", but the error message only mentions "You are banned from this program." This is misleading for deactivated partners, as these states are semantically different (banned typically implies a permanent/punitive action, while deactivated suggests a temporary suspension).

Consider updating the error message to be more accurate:

-    if (["banned", "deactivated"].includes(status)) {
-      throw new DubApiError({
-        code: "forbidden",
-        message: "You are banned from this program.",
-      });
-    }
+    if (["banned", "deactivated"].includes(status)) {
+      throw new DubApiError({
+        code: "forbidden",
+        message: status === "banned" 
+          ? "You are banned from this program."
+          : "Your account has been deactivated for this program.",
+      });
+    }
apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts (1)

39-44: Error message doesn't match expanded status check.

The status check now includes ["banned", "deactivated", "rejected"], but the error message hardcoded on line 42 only mentions "You are banned from this program." This will mislead users who are deactivated or rejected.

Apply this diff to provide accurate feedback:

-    if (["banned", "deactivated", "rejected"].includes(status)) {
+    const forbiddenStatuses = ["banned", "deactivated", "rejected"];
+    if (forbiddenStatuses.includes(status)) {
+      const messages = {
+        banned: "You are banned from this program.",
+        deactivated: "Your enrollment in this program has been deactivated.",
+        rejected: "Your enrollment in this program has been rejected.",
+      };
       throw new DubApiError({
         code: "forbidden",
-        message: "You are banned from this program.",
+        message: messages[status] || "You cannot create links for this program.",
       });
     }

Alternatively, use a generic message that covers all cases:

     if (["banned", "deactivated", "rejected"].includes(status)) {
       throw new DubApiError({
         code: "forbidden",
-        message: "You are banned from this program.",
+        message: "You cannot create links for this program due to your enrollment status.",
       });
     }
🧹 Nitpick comments (4)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/unapproved-program-page.tsx (3)

35-38: Consider enriching the deactivation message.

The description "Your partnership has been deactivated." is accurate but minimal. Consider adding context about next steps or how the user can get more information, similar to how the contact link is shown below (lines 97-103).

For example:

  deactivated: () => ({
    title: "Partnership deactivated",
-    description: "Your partnership has been deactivated.",
+    description: "Your partnership with this program has been deactivated by the program administrator.",
  }),

39-42: Reintroduction of rejected status looks good.

The "rejected" status has been properly reintroduced with clear messaging. Like the "deactivated" status, you might consider enriching the description to provide more context.

Optional enhancement:

  rejected: () => ({
    title: "Application rejected",
-    description: "Your application has been rejected.",
+    description: "Your application to join this program has been rejected.",
  }),

93-105: Contact link logic updated correctly for new status.

The expansion to include "deactivated" alongside "banned" and "rejected" is appropriate and consistent with the PR's objective. The updated messaging ("if you have any questions") is more neutral and suitable for all three statuses.

Optional: Extract status array to constant.

The array literal is recreated on every render. Consider extracting it to a constant for better performance:

+const CONTACT_STATUSES = ["banned", "deactivated", "rejected"] as const;
+
 export function UnapprovedProgramPage({
   programEnrollment,
 }: {
   programEnrollment: ProgramEnrollmentProps;
 }) {
   // ... rest of component
   
-  {["banned", "deactivated", "rejected"].includes(
-    programEnrollment.status,
-  ) && (
+  {CONTACT_STATUSES.includes(programEnrollment.status) && (
apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (1)

36-42: Consider updating pre-existing error message.

The error message says "before creating a link" but this is a PATCH endpoint for updating links. While this is a pre-existing issue not introduced in this PR, consider updating it for consistency.

Apply this diff if addressing the pre-existing issue:

       throw new DubApiError({
         code: "bad_request",
         message:
-          "This program needs a domain and URL set before creating a link.",
+          "This program needs a domain and URL set before updating a link.",
       });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b492ae and 47db85e.

📒 Files selected for processing (21)
  • apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (1 hunks)
  • apps/web/app/(ee)/api/embed/referrals/links/route.ts (1 hunks)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/[linkId]/route.ts (1 hunks)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/auth.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/unapproved-program-page.tsx (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/layout.tsx (4 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (8 hunks)
  • apps/web/lib/actions/partners/deactivate-partner.ts (1 hunks)
  • apps/web/lib/actions/partners/reactivate-partner.ts (1 hunks)
  • apps/web/lib/api/audit-logs/schemas.ts (1 hunks)
  • apps/web/lib/auth/partner.ts (1 hunks)
  • apps/web/lib/zod/schemas/partners.ts (1 hunks)
  • apps/web/ui/modals/deactivate-partner-modal.tsx (1 hunks)
  • apps/web/ui/modals/reactivate-partner-modal.tsx (1 hunks)
  • apps/web/ui/partners/partner-status-badges.ts (1 hunks)
  • apps/web/ui/partners/program-card.tsx (2 hunks)
  • packages/prisma/schema/program.prisma (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
PR: dubinc/dub#2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
📚 Learning: 2025-09-24T16:10:37.349Z
Learnt from: TWilson023
PR: dubinc/dub#2872
File: apps/web/ui/partners/partner-about.tsx:11-11
Timestamp: 2025-09-24T16:10:37.349Z
Learning: In the Dub codebase, the team prefers to import Icon as a runtime value from "dub/ui" and uses Icon as both a type and variable name in component props, even when this creates shadowing. This is their established pattern and should not be suggested for refactoring.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
🧬 Code graph analysis (8)
apps/web/lib/actions/partners/deactivate-partner.ts (5)
apps/web/lib/actions/safe-action.ts (1)
  • authActionClient (33-82)
apps/web/lib/zod/schemas/partners.ts (1)
  • deactivatePartnerSchema (755-758)
apps/web/lib/api/programs/get-program-enrollment-or-throw.ts (1)
  • getProgramEnrollmentOrThrow (6-97)
apps/web/lib/api/links/cache.ts (1)
  • linkCache (113-113)
apps/web/lib/api/audit-logs/record-audit-log.ts (1)
  • recordAuditLog (47-73)
apps/web/app/(ee)/api/embed/referrals/links/route.ts (1)
apps/web/lib/api/errors.ts (1)
  • DubApiError (75-92)
apps/web/ui/modals/deactivate-partner-modal.tsx (3)
apps/web/lib/types.ts (1)
  • PartnerProps (432-432)
apps/web/lib/actions/partners/deactivate-partner.ts (1)
  • deactivatePartnerAction (14-86)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (1)
apps/web/lib/api/errors.ts (1)
  • DubApiError (75-92)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (2)
apps/web/ui/modals/deactivate-partner-modal.tsx (1)
  • useDeactivatePartnerModal (142-167)
apps/web/ui/modals/reactivate-partner-modal.tsx (1)
  • useReactivatePartnerModal (88-113)
apps/web/lib/actions/partners/reactivate-partner.ts (4)
apps/web/lib/actions/safe-action.ts (1)
  • authActionClient (33-82)
apps/web/lib/zod/schemas/partners.ts (1)
  • deactivatePartnerSchema (755-758)
apps/web/lib/api/links/cache.ts (1)
  • linkCache (113-113)
apps/web/lib/api/audit-logs/record-audit-log.ts (1)
  • recordAuditLog (47-73)
apps/web/ui/modals/reactivate-partner-modal.tsx (3)
apps/web/lib/types.ts (1)
  • PartnerProps (432-432)
apps/web/lib/actions/partners/reactivate-partner.ts (1)
  • reactivatePartnerAction (12-87)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/layout.tsx (3)
apps/web/ui/modals/deactivate-partner-modal.tsx (1)
  • useDeactivatePartnerModal (142-167)
apps/web/ui/modals/reactivate-partner-modal.tsx (1)
  • useReactivatePartnerModal (88-113)
packages/ui/src/menu-item.tsx (1)
  • MenuItem (43-86)
⏰ 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 (12)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/auth.tsx (2)

5-5: LGTM! Clean removal of unused import.

The removal of usePathname is appropriate as it's no longer used in the component logic.


20-29: Deactivated status handling is correct.
UnapprovedProgramPage explicitly supports “deactivated” with its own title, description, and contact link, and auth.tsx’s catch-all renders it appropriately. No changes required.

apps/web/ui/partners/program-card.tsx (2)

82-93: Consider if the generic messaging is appropriate for all non-pending statuses.

The refactored messaging now uses a generic "if you have any questions" for all statuses (banned, rejected, deactivated), removing the previous "to appeal" language. While this works well for "deactivated," users who are "banned" or "rejected" might still need the ability to appeal their status. Consider whether different messaging should be used for different statuses, or if the generic approach is intentional.

If appeal functionality is important for banned/rejected users, you may want to restore status-specific messaging:

-              {` ${statusDescription} `}
-              <Link
-                href={`/messages/${program.slug}`}
-                className="text-neutral-400 underline decoration-dotted underline-offset-2 hover:text-neutral-700"
-              >
-                Reach out to the {program.name} team
-              </Link>{" "}
-              if you have any questions.
+              {` ${statusDescription} `}
+              <Link
+                href={`/messages/${program.slug}`}
+                className="text-neutral-400 underline decoration-dotted underline-offset-2 hover:text-neutral-700"
+              >
+                Reach out to the {program.name} team
+              </Link>{" "}
+              {status === "deactivated" ? "if you have any questions." : "to appeal."}

39-44: No badge needed for “deactivated” status: program-card.tsx renders all non-approved statuses (including “deactivated”) via the statusDescriptions map, so ProgramEnrollmentStatusBadges intentionally excludes it.

Likely an incorrect or invalid review comment.

apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx (1)

81-85: Simplified tooltip logic is cleaner.

The removal of ban-specific tooltip messaging improves code clarity and aligns with moving status validation to the server layer. The remaining tooltip conditions clearly communicate the two client-side constraints: link limit and program permissions.

Note: Ensure that when a banned/deactivated partner attempts link creation (if the button is enabled), the error response from the server provides clear feedback about their enrollment status.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsx (1)

6-6: LGTM!

The import path update aligns with the modal system reorganization mentioned in the PR.

apps/web/lib/api/audit-logs/schemas.ts (1)

58-61: LGTM!

The reordering groups related partner lifecycle actions together, and the new partner.deactivated and partner.reactivated entries properly support the expanded status workflow.

apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (1)

140-140: LGTM!

The status array reordering is functionally equivalent since .includes() performs membership checking regardless of order. This change is cosmetic.

apps/web/lib/auth/partner.ts (1)

30-32: LGTM!

The async params handling aligns with Next.js patterns and includes a safe fallback to an empty object. The handler continues to receive a concrete Record<string, string>.

apps/web/ui/partners/partner-status-badges.ts (1)

38-47: LGTM!

The styling updates clearly differentiate partner statuses:

  • declined now uses amber (invitation-related)
  • deactivated uses neutral (manually disabled, distinct from banned)

Icon choices are semantically appropriate for each status.

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

1-114: LGTM!

The modal implementation follows established patterns:

  • Proper error and loading state handling
  • Cache invalidation after successful action
  • Clear UI with partner information and descriptive text
  • Memoized component for optimization
apps/web/lib/zod/schemas/partners.ts (1)

755-758: LGTM!

The deactivatePartnerSchema follows the established pattern for partner action schemas and includes the necessary fields for partner deactivation/reactivation operations.

Comment on lines +18 to 25
if (
["banned", "deactivated", "rejected"].includes(programEnrollment.status)
) {
throw new DubApiError({
code: "forbidden",
message: "You are banned from this program hence cannot create links.",
message: `You are ${programEnrollment.status} from this program hence cannot create links.`,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix misleading error message.

The error message says "cannot create links" but this is a PATCH endpoint for updating links. The message should reflect the actual operation being blocked.

Apply this diff to fix the error message:

     throw new DubApiError({
       code: "forbidden",
-      message: `You are ${programEnrollment.status} from this program hence cannot create links.`,
+      message: `You are ${programEnrollment.status} from this program hence cannot update links.`,
     });
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts around lines 18
to 25, the error message incorrectly says "cannot create links" for a PATCH
(update) endpoint; update the thrown DubApiError message to reflect the blocked
operation (e.g., "You are {status} from this program hence cannot update
links.") so it correctly indicates the update action being prevented.

Comment on lines +27 to 32
if (["banned", "deactivated"].includes(programEnrollment.status)) {
throw new DubApiError({
code: "forbidden",
message: "You are banned from this program hence cannot create links.",
message: `You are ${programEnrollment.status} from this program hence cannot create links.`,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Fix grammar and verify status completeness.

The expanded status check and dynamic error message are correct improvements. However, there are two concerns:

  1. Grammar issue: "You are deactivated from this program" is awkward. Consider:

    • "You have been deactivated from this program and cannot create links."
    • Or: "Your enrollment status is '{status}' and you cannot create links."
  2. Status completeness: The AI summary mentions "rejected" status is blocked in other PATCH routes. Verify whether "rejected" should also be included here for consistency.

Apply this diff to improve the error message grammar:

-        message: `You are ${programEnrollment.status} from this program hence cannot create links.`,
+        message: `You have been ${programEnrollment.status} from this program and cannot create links.`,

Run the following script to verify if "rejected" status is blocked elsewhere and should be included here:


🏁 Script executed:

#!/bin/bash
# Description: Check if "rejected" status is blocked in other partner API routes

# Search for status checks in partner-related API routes
rg -n --type=ts -C3 '\["banned",.*"deactivated".*"rejected"\]' apps/web/app

# Also check for any other status validation patterns
rg -n --type=ts -C3 'programEnrollment\.status.*rejected' apps/web/app

Length of output: 2523


Include "rejected" status and improve error message grammar.

  • Add "rejected" to the blocked statuses for consistency with other routes.
  • Change the message to:
    You have been ${programEnrollment.status} from this program and cannot create links.
-    if (["banned", "deactivated"].includes(programEnrollment.status)) {
+    if (["banned", "deactivated", "rejected"].includes(programEnrollment.status)) {
       throw new DubApiError({
         code: "forbidden",
-        message: `You are ${programEnrollment.status} from this program hence cannot create links.`,
+        message: `You have been ${programEnrollment.status} from this program and cannot create links.`,
       });
     }
📝 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
if (["banned", "deactivated"].includes(programEnrollment.status)) {
throw new DubApiError({
code: "forbidden",
message: "You are banned from this program hence cannot create links.",
message: `You are ${programEnrollment.status} from this program hence cannot create links.`,
});
}
if (["banned", "deactivated", "rejected"].includes(programEnrollment.status)) {
throw new DubApiError({
code: "forbidden",
message: `You have been ${programEnrollment.status} from this program and cannot create links.`,
});
}
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/embed/referrals/links/route.ts around lines 27 to 32,
the blocked-status check should include "rejected" and the error message grammar
should be corrected; update the array to include "rejected" alongside "banned"
and "deactivated", and change the DubApiError message to: "You have been
${programEnrollment.status} from this program and cannot create links." so it
matches other routes and reads correctly.

!hasLinksLimitReached &&
hasAdditionalLinks &&
programEnrollment?.status !== "banned";
const canCreateNewLink = !hasLinksLimitReached && hasAdditionalLinks;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that enrollment status validation exists at the server layer.

The removal of the programEnrollment?.status !== "banned" check from canCreateNewLink means the UI will no longer prevent banned/deactivated partners from attempting to create links. While moving validation to the server is a security best practice, ensure that:

  1. The server action or API endpoint that creates partner links validates enrollment status
  2. Error handling provides clear feedback when a banned/deactivated partner attempts link creation
  3. This aligns with the PR objective of adding the "deactivating partner feature"

Run the following script to verify server-side status validation:


🏁 Script executed:

#!/bin/bash
# Description: Find server actions or API routes that handle partner link creation
# and verify they validate enrollment status

# Search for partner link creation endpoints/actions
echo "=== Searching for partner link creation server actions/APIs ==="
rg -n --type=ts --type=tsx -C5 'createPartnerLink|create.*link' -g '!node_modules' -g '!*.test.*' -g '!*.spec.*'

echo -e "\n=== Searching for enrollment status validation in server code ==="
rg -n --type=ts --type=tsx -C3 'enrollment.*status|status.*banned|status.*deactivated' -g '!node_modules' -g '!*.test.*' -g '!*.spec.*' -g '**/app/**/route.ts' -g '**/lib/actions/**' -g '**/app/**/actions.ts'

Length of output: 186


🏁 Script executed:

#!/bin/bash
echo "=== Searching for createPartnerLink definitions ==="
rg -n 'createPartnerLink' --glob '*.ts' --glob '*.tsx' --glob '!node_modules/*' --glob '!*.test.*' --glob '!*.spec.*'

echo -e "\n=== Searching for Next.js POST route handlers ==="
rg -n "export\s+async\s+function\s+POST" -g 'route.ts' --glob '!node_modules/*'

echo -e "\n=== Searching for enrollment status validation ==="
rg -nE 'status.*(banned|deactivated)' --glob '*.ts' --glob '*.tsx' --glob '!node_modules/*' --glob '!*.test.*' --glob '!*.spec.*'

Length of output: 7432


🏁 Script executed:

#!/bin/bash
echo "=== Displaying first 200 lines of partner links route ==="
sed -n '1,200p' 'apps/web/app/(ee)/api/partners/links/route.ts'

echo -e "\n=== Searching for status checks in this route file ==="
rg -n 'status' 'apps/web/app/(ee)/api/partners/links/route.ts'

Length of output: 5120


Enforce enrollment status in link-creation API
The POST handler in apps/web/app/(ee)/api/partners/links/route.ts doesn’t check programEnrollment.status (eg. “banned”/“deactivated”), so banned partners can still create links. Add a status guard before processing:

if (partner.status === "banned" || partner.status === "deactivated") {
  throw new DubApiError({ code: "forbidden", message: "Partner is deactivated." });
}
🤖 Prompt for AI Agents
In
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
around line 59 and the POST handler file
apps/web/app/(ee)/api/partners/links/route.ts, the API does not enforce partner
enrollment status allowing banned/deactivated partners to create links; add a
status guard in the POST handler that checks partner.programEnrollment.status
(or partner.status) and, if it equals "banned" or "deactivated", throw a
DubApiError with code "forbidden" and message "Partner is deactivated." before
any further processing so creation is blocked for those statuses.

Comment on lines +67 to +83
await Promise.allSettled([
// TODO send email to partner
linkCache.expireMany(links),
recordAuditLog({
workspaceId: workspace.id,
programId,
action: "partner.unbanned",
description: `Partner ${partnerId} unbanned`,
actor: user,
targets: [
{
type: "partner",
id: partnerId,
metadata: programEnrollment.partner,
},
],
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the correct audit-log event for reactivation

This action is logging partner.unbanned, which mislabels the event and breaks any consumers expecting a distinct “reactivated” audit entry. Please switch the action/description to the new reactivation event so audit trails stay accurate.

-          recordAuditLog({
+          recordAuditLog({
             workspaceId: workspace.id,
             programId,
-            action: "partner.unbanned",
-            description: `Partner ${partnerId} unbanned`,
+            action: "partner.reactivated",
+            description: `Partner ${partnerId} reactivated`,
             actor: user,
             targets: [
📝 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
await Promise.allSettled([
// TODO send email to partner
linkCache.expireMany(links),
recordAuditLog({
workspaceId: workspace.id,
programId,
action: "partner.unbanned",
description: `Partner ${partnerId} unbanned`,
actor: user,
targets: [
{
type: "partner",
id: partnerId,
metadata: programEnrollment.partner,
},
],
}),
await Promise.allSettled([
// TODO send email to partner
linkCache.expireMany(links),
recordAuditLog({
workspaceId: workspace.id,
programId,
action: "partner.reactivated",
description: `Partner ${partnerId} reactivated`,
actor: user,
targets: [
{
type: "partner",
id: partnerId,
metadata: programEnrollment.partner,
},
],
}),
🤖 Prompt for AI Agents
In apps/web/lib/actions/partners/reactivate-partner.ts around lines 67 to 83,
the audit log call uses action "partner.unbanned" and description "Partner
${partnerId} unbanned" which mislabels the operation; change the action to the
designated reactivation event (e.g., "partner.reactivated") and update the
description to reflect reactivation (e.g., "Partner ${partnerId} reactivated")
so the audit entry correctly represents the operation and downstream consumers
receive the correct event.

Comment on lines 46 to 135
const { executeAsync, isPending } = useAction(deactivatePartnerAction, {
onSuccess: async () => {
toast.success("Partner deactivated successfully!");
setShowDeactivatePartnerModal(false);
mutatePrefix("/api/partners");
},
onError({ error }) {
toast.error(error.serverError);
},
});

const onSubmit = useCallback(async () => {
if (!workspaceId || !partner.id) {
return;
}

await executeAsync({
workspaceId,
partnerId: partner.id,
});
}, [executeAsync, partner.id, workspaceId]);

const isDisabled = useMemo(() => {
return (
!workspaceId || !partner.id || confirm !== "confirm deactivate partner"
);
}, [workspaceId, partner.id, confirm]);

return (
<Modal
showModal={showDeactivatePartnerModal}
setShowModal={setShowDeactivatePartnerModal}
>
<div className="flex flex-col items-center justify-center space-y-3 border-b border-neutral-200 px-4 py-8 sm:px-10">
<img
src={partner.image || `${OG_AVATAR_URL}${partner.name}`}
alt={partner.name}
className="size-12 rounded-full"
/>

<div className="flex flex-col text-center">
<h3 className="text-lg font-semibold leading-7">{partner.name}</h3>
<p className="text-sm font-medium leading-5 text-neutral-500">
{partner.email}
</p>
</div>

<p className="text-balance text-center text-sm font-normal leading-5 text-neutral-600">
This will deactivate the partner and disable all their active links.
Their commissions and payouts will remain intact. You can reactivate
them later if needed.{" "}
<span className="font-semibold">
Are you sure you want to continue?
</span>
</p>
</div>

<form
onSubmit={handleSubmit(onSubmit)}
className="grid gap-4 bg-neutral-50 px-4 py-8 text-left sm:rounded-b-2xl sm:px-12"
>
<div>
<label htmlFor="name" className="flex items-center space-x-2">
<h2 className="text-sm font-medium text-neutral-900">
To verify, type <strong>confirm deactivate partner</strong> below
</h2>
</label>
<div className="relative mt-2 rounded-md shadow-sm">
<input
className={cn(
"block w-full rounded-md border-neutral-300 text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm",
errors.confirm && "border-red-600",
)}
placeholder="confirm deactivate partner"
type="text"
autoComplete="off"
{...register("confirm", {
required: true,
})}
/>
</div>
</div>

<Button
type="submit"
className="w-full"
variant="danger"
text="Confirm deactivate"
disabled={isDisabled}
loading={isPending}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reset the confirmation field after closing

Because the Modal keeps this form mounted while hidden, the typed “confirm deactivate partner” string persists after you close the dialog. Re‑opening the modal leaves the input prefilled, so the destructive action can be triggered without retyping the guard phrase. Please call reset() (or manually clear confirm) on success/close so every attempt requires a fresh confirmation.

   const {
     register,
     handleSubmit,
     watch,
+    reset,
     formState: { errors },
   } = useForm<DeactivatePartnerFormData>({
       ...
   const { executeAsync, isPending } = useAction(deactivatePartnerAction, {
     onSuccess: async () => {
       toast.success("Partner deactivated successfully!");
       setShowDeactivatePartnerModal(false);
+      reset();
       mutatePrefix("/api/partners");
     },
📝 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 { executeAsync, isPending } = useAction(deactivatePartnerAction, {
onSuccess: async () => {
toast.success("Partner deactivated successfully!");
setShowDeactivatePartnerModal(false);
mutatePrefix("/api/partners");
},
onError({ error }) {
toast.error(error.serverError);
},
});
const onSubmit = useCallback(async () => {
if (!workspaceId || !partner.id) {
return;
}
await executeAsync({
workspaceId,
partnerId: partner.id,
});
}, [executeAsync, partner.id, workspaceId]);
const isDisabled = useMemo(() => {
return (
!workspaceId || !partner.id || confirm !== "confirm deactivate partner"
);
}, [workspaceId, partner.id, confirm]);
return (
<Modal
showModal={showDeactivatePartnerModal}
setShowModal={setShowDeactivatePartnerModal}
>
<div className="flex flex-col items-center justify-center space-y-3 border-b border-neutral-200 px-4 py-8 sm:px-10">
<img
src={partner.image || `${OG_AVATAR_URL}${partner.name}`}
alt={partner.name}
className="size-12 rounded-full"
/>
<div className="flex flex-col text-center">
<h3 className="text-lg font-semibold leading-7">{partner.name}</h3>
<p className="text-sm font-medium leading-5 text-neutral-500">
{partner.email}
</p>
</div>
<p className="text-balance text-center text-sm font-normal leading-5 text-neutral-600">
This will deactivate the partner and disable all their active links.
Their commissions and payouts will remain intact. You can reactivate
them later if needed.{" "}
<span className="font-semibold">
Are you sure you want to continue?
</span>
</p>
</div>
<form
onSubmit={handleSubmit(onSubmit)}
className="grid gap-4 bg-neutral-50 px-4 py-8 text-left sm:rounded-b-2xl sm:px-12"
>
<div>
<label htmlFor="name" className="flex items-center space-x-2">
<h2 className="text-sm font-medium text-neutral-900">
To verify, type <strong>confirm deactivate partner</strong> below
</h2>
</label>
<div className="relative mt-2 rounded-md shadow-sm">
<input
className={cn(
"block w-full rounded-md border-neutral-300 text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm",
errors.confirm && "border-red-600",
)}
placeholder="confirm deactivate partner"
type="text"
autoComplete="off"
{...register("confirm", {
required: true,
})}
/>
</div>
</div>
<Button
type="submit"
className="w-full"
variant="danger"
text="Confirm deactivate"
disabled={isDisabled}
loading={isPending}
const {
register,
handleSubmit,
watch,
reset,
formState: { errors },
} = useForm<DeactivatePartnerFormData>({
defaultValues: {
confirm: "",
},
});
const { executeAsync, isPending } = useAction(deactivatePartnerAction, {
onSuccess: async () => {
toast.success("Partner deactivated successfully!");
setShowDeactivatePartnerModal(false);
reset();
mutatePrefix("/api/partners");
},
onError({ error }) {
toast.error(error.serverError);
},
});
🤖 Prompt for AI Agents
In apps/web/ui/modals/deactivate-partner-modal.tsx around lines 46-135 the
confirmation input value persists because the modal stays mounted; after a
successful deactivation or when the modal is closed you must clear the form
state — call reset() from react-hook-form (or set the confirm field to empty)
immediately after setShowDeactivatePartnerModal(false) in the onSuccess handler
and also in any handler that closes the modal so the next open starts with a
blank confirmation field.

@@ -0,0 +1,113 @@
import { reactivatePartnerAction } from "@/lib/actions/partners/reactivate-partner";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify audit log action name consistency.

The referenced reactivatePartnerAction (apps/web/lib/actions/partners/reactivate-partner.ts:73) logs the action as "partner.unbanned" instead of "partner.reactivated". This is inconsistent with the new audit schema entry "partner.reactivated" added in apps/web/lib/api/audit-logs/schemas.ts.

Please verify the correct audit log action name:


🏁 Script executed:

#!/bin/bash
# Description: Check audit log references in reactivate-partner action

# Find the audit log call in reactivate-partner action
rg -A 5 "recordAuditLog" apps/web/lib/actions/partners/reactivate-partner.ts

# Verify the schema includes both entries
rg '"partner\.(reactivated|unbanned)"' apps/web/lib/api/audit-logs/schemas.ts

Length of output: 636


Fix audit log action name in reactivate-partner action
In apps/web/lib/actions/partners/reactivate-partner.ts, change the recordAuditLog call’s action value from "partner.unbanned" to "partner.reactivated" to align with the audit schema.

🤖 Prompt for AI Agents
In apps/web/lib/actions/partners/reactivate-partner.ts (locate the
recordAuditLog call near the partner reactivation flow), the audit log action is
currently set to "partner.unbanned"; update that value to "partner.reactivated"
to match the audit schema; ensure the rest of the recordAuditLog payload and
callsite remain unchanged and run tests/lint to confirm no other references need
updating.

rejected // program rejected the partner
invited // partner who has been invited
declined // partner declined the invite
deactivated // partner is deactivated by the program
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

LGTM! Verify that the migration file exists.

The new deactivated status is logically positioned between declined and banned, expanding the partner lifecycle management capabilities.

Please ensure that a Prisma migration file has been generated and committed for this schema change:


🏁 Script executed:

#!/bin/bash
# Description: Verify that a migration file exists for the deactivated status

# Check for recent migration files that might contain the deactivated status
fd -e sql . packages/prisma/migrations --exec head -20 {} \; | rg -i "deactivated"

Length of output: 113


🏁 Script executed:

#!/bin/bash
# Locate all migrations directories
echo "Migrations directories:"
fd -t d migrations

# Search for 'deactivated' in all SQL files under any migrations directory
echo "Searching for 'deactivated' in migration SQL files:"
find . -type f -path '*/migrations/*/*.sql' -exec grep -Hn "deactivated" {} +

Length of output: 104


Missing Prisma migration for the new deactivated status
I found no SQL migration containing “deactivated”; run npx prisma migrate dev --name add-deactivated-status and commit the generated files under packages/prisma/migrations.

🤖 Prompt for AI Agents
In packages/prisma/schema/program.prisma around line 7, the new enum/value
comment "deactivated" was added but there is no corresponding Prisma migration;
run `npx prisma migrate dev --name add-deactivated-status` from the repo root
(or from packages/prisma if your setup requires) to generate the SQL migration
and updated Prisma client, then commit the new migration folder/files under
packages/prisma/migrations (and any updated prisma client artifacts) to the
repo.

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

Caution

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

⚠️ Outside diff range comments (1)
apps/web/ui/modals/unban-partner-modal.tsx (1)

46-144: Reset the confirmation field whenever the modal closes.

Because this modal stays mounted while hidden, the typed confirm unban … string persists after closing. Reopening keeps the guard prefilled, so the destructive action can be confirmed without retyping. Import reset from useForm and call it when the modal closes or succeeds (e.g., useEffect on showUnbanPartnerModal or inside both the success handler and Cancel click) to reinstate the empty confirmation state.

♻️ Duplicate comments (1)
apps/web/ui/modals/deactivate-partner-modal.tsx (1)

46-145: Reset the confirmation guard on close.

The confirmation input stays populated after closing, so reopening the modal leaves confirm deactivate partner already filled and the action can fire without retyping. Please invoke reset() (or manually clear the field) whenever the modal closes/succeeds—e.g., by pulling reset from useForm and calling it inside the success handler and any close path (Cancel button, useEffect on showDeactivatePartnerModal, etc.).

🧹 Nitpick comments (2)
apps/web/ui/modals/ban-partner-modal.tsx (2)

65-67: Silent early return could mask issues.

The early return when workspaceId or partner.id is missing provides no user feedback. While this should never occur in normal flows (the submit button is also disabled), consider logging an error or showing a toast to aid debugging if this state is somehow reached.

Apply this diff to add defensive logging:

     async (data: BanPartnerFormData) => {
       if (!workspaceId || !partner.id) {
+        console.error("Missing workspaceId or partner.id in ban partner form");
+        toast.error("Unable to ban partner. Please refresh and try again.");
         return;
       }

192-200: Remove unnecessary dependency from useCallback.

Including showBanPartnerModal in the dependency array causes the callback to regenerate on every modal open/close, even though the modal component receives it as a prop and will always get the current value. Removing it improves memoization efficiency.

Apply this diff:

   const BanPartnerModalCallback = useCallback(() => {
     return (
       <BanPartnerModal
         showBanPartnerModal={showBanPartnerModal}
         setShowBanPartnerModal={setShowBanPartnerModal}
         partner={partner}
       />
     );
-  }, [showBanPartnerModal, setShowBanPartnerModal, partner]);
+  }, [setShowBanPartnerModal, 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 47db85e and fb43ceb.

📒 Files selected for processing (7)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/layout.tsx (4 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (5 hunks)
  • apps/web/ui/modals/archive-partner-modal.tsx (2 hunks)
  • apps/web/ui/modals/ban-partner-modal.tsx (1 hunks)
  • apps/web/ui/modals/deactivate-partner-modal.tsx (1 hunks)
  • apps/web/ui/modals/reactivate-partner-modal.tsx (1 hunks)
  • apps/web/ui/modals/unban-partner-modal.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-24T16:10:37.349Z
Learnt from: TWilson023
PR: dubinc/dub#2872
File: apps/web/ui/partners/partner-about.tsx:11-11
Timestamp: 2025-09-24T16:10:37.349Z
Learning: In the Dub codebase, the team prefers to import Icon as a runtime value from "dub/ui" and uses Icon as both a type and variable name in component props, even when this creates shadowing. This is their established pattern and should not be suggested for refactoring.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
🧬 Code graph analysis (7)
apps/web/ui/modals/unban-partner-modal.tsx (1)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (4)
apps/web/ui/modals/deactivate-partner-modal.tsx (1)
  • useDeactivatePartnerModal (151-176)
apps/web/ui/modals/reactivate-partner-modal.tsx (1)
  • useReactivatePartnerModal (104-129)
packages/ui/src/popover.tsx (1)
  • Popover (25-102)
packages/ui/src/menu-item.tsx (1)
  • MenuItem (43-86)
apps/web/ui/modals/reactivate-partner-modal.tsx (3)
apps/web/lib/types.ts (1)
  • PartnerProps (432-432)
apps/web/lib/actions/partners/reactivate-partner.ts (1)
  • reactivatePartnerAction (12-87)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
apps/web/ui/modals/ban-partner-modal.tsx (4)
apps/web/lib/zod/schemas/partners.ts (2)
  • banPartnerSchema (729-738)
  • BAN_PARTNER_REASONS (67-74)
apps/web/lib/types.ts (1)
  • PartnerProps (432-432)
apps/web/lib/actions/partners/ban-partner.ts (1)
  • banPartnerAction (20-158)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
apps/web/ui/modals/archive-partner-modal.tsx (1)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/layout.tsx (4)
apps/web/ui/modals/deactivate-partner-modal.tsx (1)
  • useDeactivatePartnerModal (151-176)
apps/web/ui/modals/reactivate-partner-modal.tsx (1)
  • useReactivatePartnerModal (104-129)
apps/web/ui/modals/archive-partner-modal.tsx (1)
  • useArchivePartnerModal (108-132)
packages/ui/src/menu-item.tsx (1)
  • MenuItem (43-86)
apps/web/ui/modals/deactivate-partner-modal.tsx (3)
apps/web/lib/types.ts (1)
  • PartnerProps (432-432)
apps/web/lib/actions/partners/deactivate-partner.ts (1)
  • deactivatePartnerAction (14-86)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
⏰ 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/ui/modals/archive-partner-modal.tsx (5)

1-15: LGTM!

Imports are clean and all dependencies are properly utilized throughout the component.


17-54: LGTM!

The component logic correctly handles both archive and unarchive flows:

  • Proper status-based action text/description
  • Validation guards before action execution
  • Appropriate cache invalidation on success
  • Clean error handling

61-87: LGTM!

The UI refactor improves visual hierarchy:

  • Partner details are now in a distinct card with appropriate borders and background
  • Avatar styling with white background ensures proper display
  • Action description is appropriately positioned outside the info card
  • Consistent use of neutral color palette

89-103: LGTM!

The action bar is well-structured:

  • Proper loading state handling during async operations
  • Dynamic button text matches the action context
  • Consistent styling with the modal's neutral theme

108-132: LGTM!

The custom hook follows React best practices:

  • Proper memoization with useCallback and useMemo
  • Correct dependency arrays
  • Clean API surface for consumers
  • Prevents unnecessary re-renders
apps/web/ui/modals/ban-partner-modal.tsx (5)

1-21: LGTM!

All imports are properly scoped and necessary for the modal's functionality.


23-25: LGTM!

Type definition correctly extends the Zod schema with the additional confirmation field.


82-182: LGTM!

The modal UI is well-structured with proper validation feedback, destructive action warnings, and confirmation requirements. The form handling and state management are implemented correctly.


185-209: LGTM!

The hook implementation follows a solid pattern for modal state management and provides a clean API for consumers. The memoization ensures stable references for the returned values.


96-96: No encoding needed for avatar URL fallback
partner.name is intentionally concatenated unencoded—this matches the established codebase pattern for OG_AVATAR_URL.

Likely an incorrect or invalid review comment.

@steven-tey steven-tey merged commit cbb8bb1 into main Oct 2, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the deactivated-partner branch October 2, 2025 03:53
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