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

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ export const PATCH = withReferralsEmbedToken(
.pick({ url: true, key: true })
.parse(await parseRequestBody(req));

if (programEnrollment.status === "banned") {
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.`,
});
}
Comment on lines +18 to 25
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.


Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/(ee)/api/embed/referrals/links/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ export const POST = withReferralsEmbedToken(
.pick({ url: true, key: true })
.parse(await parseRequestBody(req));

if (programEnrollment.status === "banned") {
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.`,
});
}
Comment on lines +27 to 32
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.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const PATCH = withPartnerProfile(
},
);

if (status === "banned") {
if (["banned", "deactivated"].includes(status)) {
throw new DubApiError({
code: "forbidden",
message: "You are banned from this program.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const POST = withPartnerProfile(
includeGroup: true,
});

if (status === "banned") {
if (["banned", "deactivated", "rejected"].includes(status)) {
throw new DubApiError({
code: "forbidden",
message: "You are banned from this program.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export function PartnerMessagesProgramPageClient() {
onClick={() => setIsRightPanelOpen((o) => !o)}
/>
</div>
{["rejected", "banned"].includes(programEnrollment?.status ?? "") ||
{["banned", "rejected"].includes(programEnrollment?.status ?? "") ||
programEnrollment?.program?.messagingEnabledAt === null ? (
<div className="flex size-full flex-col items-center justify-center px-4">
<MsgsDotted className="size-10 text-neutral-700" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

import useProgramEnrollment from "@/lib/swr/use-program-enrollment";
import LayoutLoader from "@/ui/layout/layout-loader";
import { redirect, useParams, usePathname } from "next/navigation";
import { redirect, useParams } from "next/navigation";
import { UnapprovedProgramPage } from "./unapproved-program-page";

export function ProgramEnrollmentAuth({
children,
}: {
children: React.ReactNode;
}) {
const pathname = usePathname();
const { programSlug } = useParams();
const { programEnrollment, error, loading } = useProgramEnrollment();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ export function ProgramLinksPageClient() {
links && maxPartnerLinks && links.length >= maxPartnerLinks;
const hasAdditionalLinks = additionalLinks && additionalLinks.length > 0;

const canCreateNewLink =
!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.


useKeyboardShortcut("c", () => setShowPartnerLinkModal(true), {
enabled: canCreateNewLink ?? false,
Expand All @@ -81,13 +78,11 @@ export function ProgramLinksPageClient() {
onClick={() => setShowPartnerLinkModal(true)}
disabled={!canCreateNewLink}
disabledTooltip={
programEnrollment?.status === "banned"
? "You are banned from this program."
: hasLinksLimitReached
? `You have reached the limit of ${maxPartnerLinks} referral links.`
: !hasAdditionalLinks
? `${program?.name ?? "This"} program does not allow partners to create new links.`
: undefined
hasLinksLimitReached
? `You have reached the limit of ${maxPartnerLinks} referral links.`
: !hasAdditionalLinks
? `${program?.name ?? "This"} program does not allow partners to create new links.`
: undefined
}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,18 @@ const states: Record<
</>
),
}),
rejected: () => ({
title: "Application rejected",
description: "Your application has been rejected.",
}),
banned: () => ({
title: "Program unavailable",
description: "You have been banned from this program.",
}),
deactivated: () => ({
title: "Partnership deactivated",
description: "Your partnership has been deactivated.",
}),
rejected: () => ({
title: "Application rejected",
description: "Your application has been rejected.",
}),
};

export function UnapprovedProgramPage({
Expand Down Expand Up @@ -86,14 +90,18 @@ export function UnapprovedProgramPage({
</h2>
<p className="text-content-subtle [&_strong]:text-content-default mt-2 max-w-sm text-balance text-sm font-medium [&_strong]:font-semibold">
{description}{" "}
{["rejected", "banned"].includes(programEnrollment.status) && (
<Link
href={`/messages/${programEnrollment.program.slug}`}
className="text-neutral-500 underline decoration-dotted underline-offset-2 transition-colors hover:text-neutral-800"
>
Reach out to the {programEnrollment.program.name} team to
appeal.
</Link>
{["banned", "deactivated", "rejected"].includes(
programEnrollment.status,
) && (
<>
<Link
href={`/messages/${programEnrollment.program.slug}`}
className="text-neutral-500 underline decoration-dotted underline-offset-2 transition-colors hover:text-neutral-800"
>
Reach out to the {programEnrollment.program.name} team
</Link>{" "}
if you have any questions.
</>
)}
</p>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ import useWorkspace from "@/lib/swr/use-workspace";
import { EnrolledPartnerProps } from "@/lib/types";
import { PageContent } from "@/ui/layout/page-content";
import { PageWidthWrapper } from "@/ui/layout/page-width-wrapper";
import { useBanPartnerModal } from "@/ui/partners/ban-partner-modal";
import { useArchivePartnerModal } from "@/ui/modals/archive-partner-modal";
import { useBanPartnerModal } from "@/ui/modals/ban-partner-modal";
import { useDeactivatePartnerModal } from "@/ui/modals/deactivate-partner-modal";
import { useReactivatePartnerModal } from "@/ui/modals/reactivate-partner-modal";
import { useUnbanPartnerModal } from "@/ui/modals/unban-partner-modal";
import { usePartnerAdvancedSettingsModal } from "@/ui/partners/partner-advanced-settings-modal";
import { PartnerInfoCards } from "@/ui/partners/partner-info-cards";
import { useUnbanPartnerModal } from "@/ui/partners/unban-partner-modal";
import { ThreeDots } from "@/ui/shared/icons";
import { Button, MenuItem, Popover, useKeyboardShortcut } from "@dub/ui";
import {
BoxArchive,
ChevronRight,
CircleXmark,
InvoiceDollar,
Msgs,
PenWriting,
UserCheck,
UserDelete,
Users,
} from "@dub/ui/icons";
import { LockOpen } from "lucide-react";
import Link from "next/link";
import { useParams } from "next/navigation";
import { ReactNode, useState } from "react";
Expand Down Expand Up @@ -110,13 +116,28 @@ function PageControls({ partner }: { partner: EnrolledPartnerProps }) {
const { UnbanPartnerModal, setShowUnbanPartnerModal } = useUnbanPartnerModal({
partner,
});
const { DeactivatePartnerModal, setShowDeactivatePartnerModal } =
useDeactivatePartnerModal({
partner,
});
const { ReactivatePartnerModal, setShowReactivatePartnerModal } =
useReactivatePartnerModal({
partner,
});
const { ArchivePartnerModal, setShowArchivePartnerModal } =
useArchivePartnerModal({
partner,
});

return (
<>
{createCommissionSheet}
<PartnerAdvancedSettingsModal />
<BanPartnerModal />
<UnbanPartnerModal />
<DeactivatePartnerModal />
<ReactivatePartnerModal />
<ArchivePartnerModal />

<Button
variant="primary"
Expand Down Expand Up @@ -170,18 +191,40 @@ function PageControls({ partner }: { partner: EnrolledPartnerProps }) {
>
Advanced settings
</MenuItem>
{partner.status !== "banned" ? (
{!["banned", "deactivated"].includes(partner.status) && (
<MenuItem
icon={UserDelete}
variant="danger"
icon={BoxArchive}
onClick={() => {
setShowBanPartnerModal(true);
setShowArchivePartnerModal(true);
setIsOpen(false);
}}
>
Ban partner
{partner.status === "archived" ? "Unarchive" : "Archive"}{" "}
partner
</MenuItem>
) : (
)}
{partner.status === "deactivated" ? (
<MenuItem
icon={LockOpen}
onClick={() => {
setShowReactivatePartnerModal(true);
setIsOpen(false);
}}
>
Reactivate partner
</MenuItem>
) : partner.status !== "banned" ? (
<MenuItem
icon={CircleXmark}
onClick={() => {
setShowDeactivatePartnerModal(true);
setIsOpen(false);
}}
>
Deactivate partner
</MenuItem>
) : null}
{partner.status === "banned" ? (
<MenuItem
icon={UserCheck}
onClick={() => {
Expand All @@ -191,6 +234,17 @@ function PageControls({ partner }: { partner: EnrolledPartnerProps }) {
>
Unban partner
</MenuItem>
) : (
<MenuItem
icon={UserDelete}
variant="danger"
onClick={() => {
setShowBanPartnerModal(true);
setIsOpen(false);
}}
>
Ban partner
</MenuItem>
)}
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import usePartner from "@/lib/swr/use-partner";
import useWorkspace from "@/lib/swr/use-workspace";
import { EnrolledPartnerProps } from "@/lib/types";
import { useAddPartnerLinkModal } from "@/ui/partners/add-partner-link-modal";
import { useAddPartnerLinkModal } from "@/ui/modals/add-partner-link-modal";
import { Button, CopyButton, LoadingSpinner, Table, useTable } from "@dub/ui";
import { cn, currencyFormatter, getPrettyUrl, nFormatter } from "@dub/utils";
import Link from "next/link";
Expand Down
Loading