From 2a40686b184d0502e1f2b4f73203dc953adeb393 Mon Sep 17 00:00:00 2001 From: Marcus Farrell Date: Thu, 9 Jul 2026 21:57:07 -0700 Subject: [PATCH 1/4] Duplicate partners links --- .gitignore | 4 +- .../[partnerId]/shared-platforms/route.ts | 107 +----------- .../[partnerId]/shared-platforms/route.ts | 33 ++++ .../applications/rejected/page-client.tsx | 2 +- .../partners/get-shared-partner-platforms.ts | 158 ++++++++++++++++++ .../lib/swr/use-partner-shared-platforms.ts | 29 ++++ apps/web/lib/types.ts | 5 + apps/web/lib/zod/schemas/partners.ts | 11 ++ apps/web/ui/partners/partner-about.tsx | 3 + .../ui/partners/partner-application-sheet.tsx | 2 +- .../partner-platform-shared-partners.tsx | 113 +++++++++++-- .../ui/partners/partner-platform-summary.tsx | 36 +++- 12 files changed, 379 insertions(+), 124 deletions(-) create mode 100644 apps/web/app/(ee)/api/partners/[partnerId]/shared-platforms/route.ts create mode 100644 apps/web/lib/api/partners/get-shared-partner-platforms.ts create mode 100644 apps/web/lib/swr/use-partner-shared-platforms.ts diff --git a/.gitignore b/.gitignore index 898bef49661..e7da5ca8e56 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,6 @@ packages/stripe-app/.build/* playwright-report/ **/playwright/.auth/ test-results/ -blob-report/ \ No newline at end of file +blob-report/ +.claude/launch.json +.claude/skills/verify/SKILL.md diff --git a/apps/web/app/(ee)/api/admin/partners/[partnerId]/shared-platforms/route.ts b/apps/web/app/(ee)/api/admin/partners/[partnerId]/shared-platforms/route.ts index 7db13bb4e38..792d76d3f52 100644 --- a/apps/web/app/(ee)/api/admin/partners/[partnerId]/shared-platforms/route.ts +++ b/apps/web/app/(ee)/api/admin/partners/[partnerId]/shared-platforms/route.ts @@ -1,8 +1,6 @@ +import { getSharedPartnerPlatforms } from "@/lib/api/partners/get-shared-partner-platforms"; import { withAdmin } from "@/lib/auth"; -import { prisma } from "@/lib/prisma"; import { partnerSharedPlatformSchema } from "@/lib/zod/schemas/partners"; -import { getDomainWithoutWWW } from "@dub/utils"; -import { Prisma } from "@prisma/client"; import { NextResponse } from "next/server"; import * as z from "zod/v4"; @@ -10,110 +8,11 @@ import * as z from "zod/v4"; export const GET = withAdmin(async ({ params }) => { const { partnerId } = params; - const partner = await prisma.partner.findUnique({ - where: { id: partnerId }, - include: { - platforms: true, - }, - }); + const sharedPlatforms = await getSharedPartnerPlatforms({ partnerId }); - if (!partner) { + if (sharedPlatforms === null) { return new Response("Partner not found.", { status: 404 }); } - const verifiedPlatforms = partner.platforms.filter( - (platform) => platform.verifiedAt !== null, - ); - - // a partner has at most one platform per type, so there is at most one website - const websitePlatform = verifiedPlatforms.find( - (platform) => platform.type === "website", - ); - - const websiteDomain = websitePlatform - ? getDomainWithoutWWW(websitePlatform.identifier) ?? null - : null; - - const matchConditions: Prisma.PartnerPlatformWhereInput[] = []; - - for (const platform of verifiedPlatforms) { - if (platform.type === "website") { - // website identifiers are full URLs with inconsistent normalization, - // so match on the domain instead of the exact identifier - if (websiteDomain) { - matchConditions.push({ - identifier: { - contains: websiteDomain, - }, - type: platform.type, - }); - } - continue; - } - - matchConditions.push({ - identifier: platform.identifier, - type: platform.type, - }); - } - - if (matchConditions.length === 0) { - return NextResponse.json([]); - } - - const sharedPlatformMatches = await prisma.partnerPlatform.findMany({ - where: { - OR: matchConditions, - partnerId: { - not: partner.id, - }, - verifiedAt: { - not: null, - }, - }, - include: { - partner: { - select: { - id: true, - name: true, - email: true, - image: true, - }, - }, - }, - orderBy: { - createdAt: "asc", - }, - take: 10, - }); - - const sharedPlatforms = verifiedPlatforms - .map((platform) => { - let platformMatches = sharedPlatformMatches.filter( - (match) => match.type === platform.type, - ); - - // "contains" matches on the domain need exact verification - // (e.g. contains "example.com" would also match "notexample.com") - if (platform.type === "website") { - platformMatches = platformMatches.filter( - (match) => - websiteDomain !== null && - getDomainWithoutWWW(match.identifier) === websiteDomain, - ); - } - - return { - type: platform.type, - identifier: platform.identifier, - partners: platformMatches.map((match) => ({ - id: match.partner.id, - name: match.partner.name, - email: match.partner.email, - image: match.partner.image, - })), - }; - }) - .filter((platform) => platform.partners.length > 0); return NextResponse.json( z.array(partnerSharedPlatformSchema).parse(sharedPlatforms), diff --git a/apps/web/app/(ee)/api/partners/[partnerId]/shared-platforms/route.ts b/apps/web/app/(ee)/api/partners/[partnerId]/shared-platforms/route.ts new file mode 100644 index 00000000000..8c2aaaab324 --- /dev/null +++ b/apps/web/app/(ee)/api/partners/[partnerId]/shared-platforms/route.ts @@ -0,0 +1,33 @@ +import { getSharedPartnerPlatforms } from "@/lib/api/partners/get-shared-partner-platforms"; +import { getDefaultProgramIdOrThrow } from "@/lib/api/programs/get-default-program-id-or-throw"; +import { getProgramEnrollmentOrThrow } from "@/lib/api/programs/get-program-enrollment-or-throw"; +import { withWorkspace } from "@/lib/auth"; +import { partnerProgramSharedPlatformSchema } from "@/lib/zod/schemas/partners"; +import { NextResponse } from "next/server"; +import * as z from "zod/v4"; + +// GET /api/partners/:partnerId/shared-platforms – other partners in this program with the same verified platform identifiers +export const GET = withWorkspace( + async ({ workspace, params }) => { + const { partnerId } = params; + const programId = getDefaultProgramIdOrThrow(workspace); + + await getProgramEnrollmentOrThrow({ + partnerId, + programId, + include: {}, + }); + + const sharedPlatforms = await getSharedPartnerPlatforms({ + partnerId, + programId, + }); + + return NextResponse.json( + z.array(partnerProgramSharedPlatformSchema).parse(sharedPlatforms ?? []), + ); + }, + { + requiredPlan: ["business", "advanced", "enterprise"], + }, +); diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/rejected/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/rejected/page-client.tsx index 175b81aad82..fb30b326bed 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/rejected/page-client.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/rejected/page-client.tsx @@ -88,7 +88,7 @@ export function ProgramPartnersRejectedApplicationsPageClient() { status: "rejected", sortBy, sortOrder, - includePartnerPlatformPropss: true, + includePartnerPlatforms: true, }, { exclude: ["partnerId"] }, )}`, diff --git a/apps/web/lib/api/partners/get-shared-partner-platforms.ts b/apps/web/lib/api/partners/get-shared-partner-platforms.ts new file mode 100644 index 00000000000..0b180caae41 --- /dev/null +++ b/apps/web/lib/api/partners/get-shared-partner-platforms.ts @@ -0,0 +1,158 @@ +import { prisma } from "@/lib/prisma"; +import { getDomainWithoutWWW } from "@dub/utils"; +import { + Partner, + PartnerPlatform, + Prisma, + ProgramEnrollmentStatus, +} from "@prisma/client"; + +type SharedPlatformMatch = PartnerPlatform & { + partner: Pick & { + // only selected when programId is provided + programs?: { status: ProgramEnrollmentStatus }[]; + }; +}; + +// Finds other partners with the same verified platform identifiers. +// When programId is provided, only matches partners enrolled in that program +// and includes each matched partner's enrollment status. +export async function getSharedPartnerPlatforms({ + partnerId, + programId, +}: { + partnerId: string; + programId?: string; +}) { + const partner = await prisma.partner.findUnique({ + where: { id: partnerId }, + include: { + platforms: true, + }, + }); + + if (!partner) { + return null; + } + + const verifiedPlatforms = partner.platforms.filter( + (platform) => platform.verifiedAt !== null, + ); + + // a partner has at most one platform per type, so there is at most one website + const websitePlatform = verifiedPlatforms.find( + (platform) => platform.type === "website", + ); + + const websiteDomain = websitePlatform + ? getDomainWithoutWWW(websitePlatform.identifier) ?? null + : null; + + const matchConditions: Prisma.PartnerPlatformWhereInput[] = []; + + for (const platform of verifiedPlatforms) { + if (platform.type === "website") { + // website identifiers are full URLs with inconsistent normalization, + // so match on the domain instead of the exact identifier + if (websiteDomain) { + matchConditions.push({ + identifier: { + contains: websiteDomain, + }, + type: platform.type, + }); + } + continue; + } + + matchConditions.push({ + identifier: platform.identifier, + type: platform.type, + }); + } + + if (matchConditions.length === 0) { + return []; + } + + const sharedPlatformMatches = (await prisma.partnerPlatform.findMany({ + where: { + OR: matchConditions, + partnerId: { + not: partner.id, + }, + verifiedAt: { + not: null, + }, + ...(programId && { + partner: { + programs: { + some: { + programId, + }, + }, + }, + }), + }, + include: { + partner: { + select: { + id: true, + name: true, + email: true, + image: true, + ...(programId && { + programs: { + where: { + programId, + }, + select: { + status: true, + }, + }, + }), + }, + }, + }, + orderBy: { + createdAt: "asc", + }, + take: 10, + })) as SharedPlatformMatch[]; + + const sharedPlatforms = verifiedPlatforms + .map((platform) => { + let platformMatches = sharedPlatformMatches.filter( + (match) => match.type === platform.type, + ); + + // "contains" matches on the domain need exact verification + // (e.g. contains "example.com" would also match "notexample.com") + if (platform.type === "website") { + platformMatches = platformMatches.filter( + (match) => + websiteDomain !== null && + getDomainWithoutWWW(match.identifier) === websiteDomain, + ); + } + + return { + type: platform.type, + identifier: platform.identifier, + partners: platformMatches.map((match) => ({ + id: match.partner.id, + name: match.partner.name, + email: match.partner.email, + image: match.partner.image, + // programs is filtered to the given programId, and the `some` + // condition above guarantees a matching enrollment exists + ...(programId && { + status: match.partner.programs?.[0]?.status, + }), + })), + }; + }) + .filter((platform) => platform.partners.length > 0); + + return sharedPlatforms; +} diff --git a/apps/web/lib/swr/use-partner-shared-platforms.ts b/apps/web/lib/swr/use-partner-shared-platforms.ts new file mode 100644 index 00000000000..23b8ccac6df --- /dev/null +++ b/apps/web/lib/swr/use-partner-shared-platforms.ts @@ -0,0 +1,29 @@ +import { PartnerProgramSharedPlatformProps } from "@/lib/types"; +import { fetcher } from "@dub/utils"; +import useSWR from "swr"; +import useWorkspace from "./use-workspace"; + +export function usePartnerSharedPlatforms({ + partnerId, + enabled = true, +}: { + partnerId: string | null | undefined; + enabled?: boolean; +}) { + const { id: workspaceId } = useWorkspace(); + + const { data, isLoading, error } = useSWR< + PartnerProgramSharedPlatformProps[] + >( + enabled && partnerId && workspaceId + ? `/api/partners/${partnerId}/shared-platforms?workspaceId=${workspaceId}` + : null, + fetcher, + ); + + return { + sharedPlatforms: data, + isLoading, + error, + }; +} diff --git a/apps/web/lib/types.ts b/apps/web/lib/types.ts index 3e34281ea53..1ea6c7a56bd 100644 --- a/apps/web/lib/types.ts +++ b/apps/web/lib/types.ts @@ -146,6 +146,7 @@ import { EnrolledPartnerSchema, EnrolledPartnerSchemaExtended, partnerPlatformSchema, + partnerProgramSharedPlatformSchema, PartnerRewindSchema, PartnerSchema, partnerSharedPlatformSchema, @@ -516,6 +517,10 @@ export type PartnerSharedPlatformProps = z.infer< typeof partnerSharedPlatformSchema >; +export type PartnerProgramSharedPlatformProps = z.infer< + typeof partnerProgramSharedPlatformSchema +>; + export type PartnerProps = z.infer & { role: PartnerRole; userId: string; diff --git a/apps/web/lib/zod/schemas/partners.ts b/apps/web/lib/zod/schemas/partners.ts index b7ffc40429c..6f5116ceee9 100644 --- a/apps/web/lib/zod/schemas/partners.ts +++ b/apps/web/lib/zod/schemas/partners.ts @@ -1073,3 +1073,14 @@ export const partnerSharedPlatformSchema = z.object({ }), ), }); + +// program-scoped variant: matched partners are enrolled in the program, +// so each one carries its enrollment status +export const partnerProgramSharedPlatformSchema = + partnerSharedPlatformSchema.extend({ + partners: z.array( + partnerSharedPlatformSchema.shape.partners.element.extend({ + status: z.enum(ProgramEnrollmentStatus), + }), + ), + }); diff --git a/apps/web/ui/partners/partner-about.tsx b/apps/web/ui/partners/partner-about.tsx index 2af8482a711..d6a4e3ce376 100644 --- a/apps/web/ui/partners/partner-about.tsx +++ b/apps/web/ui/partners/partner-about.tsx @@ -13,6 +13,7 @@ import { Icon, InfoTooltip } from "@dub/ui"; export function PartnerAbout({ partner, error, + showSharedPlatforms, }: { partner?: Pick< EnrolledPartnerExtendedProps, @@ -25,6 +26,7 @@ export function PartnerAbout({ | "platforms" >; error?: any; + showSharedPlatforms?: boolean; }) { return partner ? ( <> @@ -49,6 +51,7 @@ export function PartnerAbout({ platforms={partner.platforms} partnerId={partner.id} className="gap-y-2" + showSharedPlatforms={showSharedPlatforms} /> diff --git a/apps/web/ui/partners/partner-application-sheet.tsx b/apps/web/ui/partners/partner-application-sheet.tsx index f3a4225f4b8..749189a0da8 100644 --- a/apps/web/ui/partners/partner-application-sheet.tsx +++ b/apps/web/ui/partners/partner-application-sheet.tsx @@ -174,7 +174,7 @@ function PartnerApplicationAbout({ preferredApplicationId={partner.applicationId ?? null} />
- + ); } diff --git a/apps/web/ui/partners/partner-platform-shared-partners.tsx b/apps/web/ui/partners/partner-platform-shared-partners.tsx index bf8328545dd..fed6916fa84 100644 --- a/apps/web/ui/partners/partner-platform-shared-partners.tsx +++ b/apps/web/ui/partners/partner-platform-shared-partners.tsx @@ -1,15 +1,33 @@ -import { PartnerSharedPlatformProps } from "@/lib/types"; +import useWorkspace from "@/lib/swr/use-workspace"; +import { + PartnerProgramSharedPlatformProps, + PartnerSharedPlatformProps, +} from "@/lib/types"; +import { ArrowUpRight2, StatusBadge } from "@dub/ui"; import { Duplicate } from "@dub/ui/icons"; import { cn, pluralize } from "@dub/utils"; +import { ProgramEnrollmentStatus } from "@prisma/client"; import { ChevronDown } from "lucide-react"; import { useId, useState } from "react"; import { PartnerAvatar } from "./partner-avatar"; +import { PartnerStatusBadges } from "./partner-status-badges"; -export function PartnerPlatformSharedPartners({ - sharedPartners, -}: { - sharedPartners: PartnerSharedPlatformProps["partners"]; -}) { +type PartnerPlatformSharedPartnersProps = + | { + // admin: partners across the whole network, linking to the network page + variant?: "admin"; + sharedPartners: PartnerSharedPlatformProps["partners"]; + } + | { + // program: partners enrolled in the program, linking to their status-specific view + variant: "program"; + sharedPartners: PartnerProgramSharedPlatformProps["partners"]; + }; + +export function PartnerPlatformSharedPartners( + props: PartnerPlatformSharedPartnersProps, +) { + const { sharedPartners } = props; const [isExpanded, setIsExpanded] = useState(false); const panelId = useId(); @@ -38,10 +56,23 @@ export function PartnerPlatformSharedPartners({ {isExpanded && ( -
- {sharedPartners.map((partner) => ( - - ))} +
+ {props.variant === "program" + ? props.sharedPartners.map((partner) => ( + + )) + : props.sharedPartners.map((partner) => ( + + ))}
)}
@@ -72,3 +103,65 @@ function SharedPartner({ ); } + +const getPartnerDetailsUrl = ({ + workspaceSlug, + partnerId, + status, +}: { + workspaceSlug: string; + partnerId: string; + status: ProgramEnrollmentStatus; +}) => { + switch (status) { + case "pending": + return `/${workspaceSlug}/program/partners/applications?partnerId=${partnerId}`; + case "rejected": + return `/${workspaceSlug}/program/partners/applications/rejected?partnerId=${partnerId}`; + default: + return `/${workspaceSlug}/program/partners/${partnerId}`; + } +}; + +function ProgramSharedPartner({ + partner, +}: { + partner: PartnerProgramSharedPlatformProps["partners"][number]; +}) { + const { slug: workspaceSlug } = useWorkspace(); + const badge = PartnerStatusBadges[partner.status]; + + return ( + +
+ + + {partner.name} + + {partner.status !== "approved" && badge && ( + + {badge.label} + + )} +
+
+ View + +
+
+ ); +} diff --git a/apps/web/ui/partners/partner-platform-summary.tsx b/apps/web/ui/partners/partner-platform-summary.tsx index 4b2e983e93a..01277463b99 100644 --- a/apps/web/ui/partners/partner-platform-summary.tsx +++ b/apps/web/ui/partners/partner-platform-summary.tsx @@ -1,4 +1,5 @@ import { PARTNER_PLATFORM_FIELDS } from "@/lib/partners/partner-platforms"; +import { usePartnerSharedPlatforms } from "@/lib/swr/use-partner-shared-platforms"; import { PartnerPlatformProps, PartnerSharedPlatformProps } from "@/lib/types"; import { AnimatedSizeContainer, useCurrentSubdomain } from "@dub/ui"; import { cn, fetcher } from "@dub/utils"; @@ -12,21 +13,32 @@ export function PartnerPlatformSummary({ platforms, partnerId, className, + showSharedPlatforms, }: { platforms: PartnerPlatformProps[] | undefined; partnerId: string; className?: string; + showSharedPlatforms?: boolean; }) { const { subdomain } = useCurrentSubdomain(); const { mutate } = useSWRConfig(); - const { data: sharedPlatforms } = useSWR( - subdomain === "admin" && partnerId + const isAdmin = subdomain === "admin"; + + const { data: adminSharedPlatforms } = useSWR( + isAdmin && partnerId ? `/api/admin/partners/${partnerId}/shared-platforms` : null, fetcher, ); + const { sharedPlatforms: programSharedPlatforms } = usePartnerSharedPlatforms( + { + partnerId, + enabled: Boolean(showSharedPlatforms) && !isAdmin, + }, + ); + const [verifyingPlatforms, setVerifyingPlatforms] = useState< Partial> >({}); @@ -130,9 +142,13 @@ export function PartnerPlatformSummary({ info, identifier, }) => { - const sharedPlatform = sharedPlatforms?.find( - (platform) => platform.type === type, - ); + const adminSharedPlatform = isAdmin + ? adminSharedPlatforms?.find((platform) => platform.type === type) + : undefined; + + const programSharedPlatform = !isAdmin + ? programSharedPlatforms?.find((platform) => platform.type === type) + : undefined; return ( @@ -151,9 +167,15 @@ export function PartnerPlatformSummary({ /> - {sharedPlatform && ( + {adminSharedPlatform && ( + + )} + {programSharedPlatform && ( )} From 62671bc837ab88b41b72219771772f31089f4733 Mon Sep 17 00:00:00 2001 From: Pedro Ladeira Date: Fri, 10 Jul 2026 15:57:52 -0300 Subject: [PATCH 2/4] code improvements --- .gitignore | 2 -- .../[partnerId]/shared-platforms/route.ts | 10 +++++- .../partners/get-shared-partner-platforms.ts | 36 ++++++++++++------- .../lib/swr/use-partner-shared-platforms.ts | 29 --------------- .../ui/partners/partner-platform-summary.tsx | 21 +++++++---- 5 files changed, 47 insertions(+), 51 deletions(-) delete mode 100644 apps/web/lib/swr/use-partner-shared-platforms.ts diff --git a/.gitignore b/.gitignore index e7da5ca8e56..fac243e4528 100644 --- a/.gitignore +++ b/.gitignore @@ -51,5 +51,3 @@ playwright-report/ **/playwright/.auth/ test-results/ blob-report/ -.claude/launch.json -.claude/skills/verify/SKILL.md diff --git a/apps/web/app/(ee)/api/partners/[partnerId]/shared-platforms/route.ts b/apps/web/app/(ee)/api/partners/[partnerId]/shared-platforms/route.ts index 8c2aaaab324..2489d104525 100644 --- a/apps/web/app/(ee)/api/partners/[partnerId]/shared-platforms/route.ts +++ b/apps/web/app/(ee)/api/partners/[partnerId]/shared-platforms/route.ts @@ -1,3 +1,4 @@ +import { DubApiError } from "@/lib/api/errors"; import { getSharedPartnerPlatforms } from "@/lib/api/partners/get-shared-partner-platforms"; import { getDefaultProgramIdOrThrow } from "@/lib/api/programs/get-default-program-id-or-throw"; import { getProgramEnrollmentOrThrow } from "@/lib/api/programs/get-program-enrollment-or-throw"; @@ -23,8 +24,15 @@ export const GET = withWorkspace( programId, }); + if (sharedPlatforms === null) { + throw new DubApiError({ + code: "not_found", + message: "Partner not found.", + }); + } + return NextResponse.json( - z.array(partnerProgramSharedPlatformSchema).parse(sharedPlatforms ?? []), + z.array(partnerProgramSharedPlatformSchema).parse(sharedPlatforms), ); }, { diff --git a/apps/web/lib/api/partners/get-shared-partner-platforms.ts b/apps/web/lib/api/partners/get-shared-partner-platforms.ts index 0b180caae41..6862bd5c233 100644 --- a/apps/web/lib/api/partners/get-shared-partner-platforms.ts +++ b/apps/web/lib/api/partners/get-shared-partner-platforms.ts @@ -117,7 +117,7 @@ export async function getSharedPartnerPlatforms({ orderBy: { createdAt: "asc", }, - take: 10, + take: 100, })) as SharedPlatformMatch[]; const sharedPlatforms = verifiedPlatforms @@ -136,20 +136,32 @@ export async function getSharedPartnerPlatforms({ ); } + const partners = platformMatches + .flatMap((match) => { + const status = programId + ? match.partner.programs?.[0]?.status + : undefined; + + if (programId && !status) { + return []; + } + + return [ + { + id: match.partner.id, + name: match.partner.name, + email: match.partner.email, + image: match.partner.image, + ...(status && { status }), + }, + ]; + }) + .slice(0, 10); + return { type: platform.type, identifier: platform.identifier, - partners: platformMatches.map((match) => ({ - id: match.partner.id, - name: match.partner.name, - email: match.partner.email, - image: match.partner.image, - // programs is filtered to the given programId, and the `some` - // condition above guarantees a matching enrollment exists - ...(programId && { - status: match.partner.programs?.[0]?.status, - }), - })), + partners, }; }) .filter((platform) => platform.partners.length > 0); diff --git a/apps/web/lib/swr/use-partner-shared-platforms.ts b/apps/web/lib/swr/use-partner-shared-platforms.ts deleted file mode 100644 index 23b8ccac6df..00000000000 --- a/apps/web/lib/swr/use-partner-shared-platforms.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { PartnerProgramSharedPlatformProps } from "@/lib/types"; -import { fetcher } from "@dub/utils"; -import useSWR from "swr"; -import useWorkspace from "./use-workspace"; - -export function usePartnerSharedPlatforms({ - partnerId, - enabled = true, -}: { - partnerId: string | null | undefined; - enabled?: boolean; -}) { - const { id: workspaceId } = useWorkspace(); - - const { data, isLoading, error } = useSWR< - PartnerProgramSharedPlatformProps[] - >( - enabled && partnerId && workspaceId - ? `/api/partners/${partnerId}/shared-platforms?workspaceId=${workspaceId}` - : null, - fetcher, - ); - - return { - sharedPlatforms: data, - isLoading, - error, - }; -} diff --git a/apps/web/ui/partners/partner-platform-summary.tsx b/apps/web/ui/partners/partner-platform-summary.tsx index 01277463b99..8009cd8e593 100644 --- a/apps/web/ui/partners/partner-platform-summary.tsx +++ b/apps/web/ui/partners/partner-platform-summary.tsx @@ -1,6 +1,10 @@ import { PARTNER_PLATFORM_FIELDS } from "@/lib/partners/partner-platforms"; -import { usePartnerSharedPlatforms } from "@/lib/swr/use-partner-shared-platforms"; -import { PartnerPlatformProps, PartnerSharedPlatformProps } from "@/lib/types"; +import useWorkspace from "@/lib/swr/use-workspace"; +import { + PartnerPlatformProps, + PartnerProgramSharedPlatformProps, + PartnerSharedPlatformProps, +} from "@/lib/types"; import { AnimatedSizeContainer, useCurrentSubdomain } from "@dub/ui"; import { cn, fetcher } from "@dub/utils"; import { Fragment, useMemo, useState } from "react"; @@ -21,6 +25,7 @@ export function PartnerPlatformSummary({ showSharedPlatforms?: boolean; }) { const { subdomain } = useCurrentSubdomain(); + const { id: workspaceId } = useWorkspace(); const { mutate } = useSWRConfig(); const isAdmin = subdomain === "admin"; @@ -32,11 +37,13 @@ export function PartnerPlatformSummary({ fetcher, ); - const { sharedPlatforms: programSharedPlatforms } = usePartnerSharedPlatforms( - { - partnerId, - enabled: Boolean(showSharedPlatforms) && !isAdmin, - }, + const { data: programSharedPlatforms } = useSWR< + PartnerProgramSharedPlatformProps[] + >( + showSharedPlatforms && !isAdmin && partnerId && workspaceId + ? `/api/partners/${partnerId}/shared-platforms?workspaceId=${workspaceId}` + : null, + fetcher, ); const [verifyingPlatforms, setVerifyingPlatforms] = useState< From be0bb5618af9fd40d3857809beb3ca7654a77763 Mon Sep 17 00:00:00 2001 From: Pedro Ladeira Date: Fri, 10 Jul 2026 16:28:49 -0300 Subject: [PATCH 3/4] code improvements --- .../partners/get-shared-partner-platforms.ts | 84 ++++++++++--------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/apps/web/lib/api/partners/get-shared-partner-platforms.ts b/apps/web/lib/api/partners/get-shared-partner-platforms.ts index 6862bd5c233..588454980d5 100644 --- a/apps/web/lib/api/partners/get-shared-partner-platforms.ts +++ b/apps/web/lib/api/partners/get-shared-partner-platforms.ts @@ -75,50 +75,58 @@ export async function getSharedPartnerPlatforms({ return []; } - const sharedPlatformMatches = (await prisma.partnerPlatform.findMany({ - where: { - OR: matchConditions, - partnerId: { - not: partner.id, - }, - verifiedAt: { - not: null, - }, + const partnerInclude = { + select: { + id: true, + name: true, + email: true, + image: true, ...(programId && { - partner: { - programs: { - some: { - programId, - }, + programs: { + where: { + programId, + }, + select: { + status: true, }, }, }), }, - include: { - partner: { - select: { - id: true, - name: true, - email: true, - image: true, - ...(programId && { - programs: { - where: { - programId, - }, - select: { - status: true, - }, + } satisfies Prisma.PartnerPlatformInclude["partner"]; + + const sharedPlatformMatches = ( + await Promise.all( + matchConditions.map((condition) => + prisma.partnerPlatform.findMany({ + where: { + ...condition, + partnerId: { + not: partner.id, }, - }), - }, - }, - }, - orderBy: { - createdAt: "asc", - }, - take: 100, - })) as SharedPlatformMatch[]; + verifiedAt: { + not: null, + }, + ...(programId && { + partner: { + programs: { + some: { + programId, + }, + }, + }, + }), + }, + include: { + partner: partnerInclude, + }, + orderBy: { + createdAt: "asc", + }, + take: condition.type === "website" ? 100 : 10, + }), + ), + ) + ).flat() as SharedPlatformMatch[]; const sharedPlatforms = verifiedPlatforms .map((platform) => { From 234121c9cb0ee7ebd34cd6896df36062ef19e628 Mon Sep 17 00:00:00 2001 From: Pedro Ladeira Date: Fri, 10 Jul 2026 16:59:08 -0300 Subject: [PATCH 4/4] code improvements --- .../partners/get-shared-partner-platforms.ts | 99 ++++++++++++------- 1 file changed, 62 insertions(+), 37 deletions(-) diff --git a/apps/web/lib/api/partners/get-shared-partner-platforms.ts b/apps/web/lib/api/partners/get-shared-partner-platforms.ts index 588454980d5..58b7f6f53d2 100644 --- a/apps/web/lib/api/partners/get-shared-partner-platforms.ts +++ b/apps/web/lib/api/partners/get-shared-partner-platforms.ts @@ -48,30 +48,31 @@ export async function getSharedPartnerPlatforms({ ? getDomainWithoutWWW(websitePlatform.identifier) ?? null : null; - const matchConditions: Prisma.PartnerPlatformWhereInput[] = []; + const exactMatchConditions: Prisma.PartnerPlatformWhereInput[] = []; + let websiteMatchCondition: Prisma.PartnerPlatformWhereInput | null = null; for (const platform of verifiedPlatforms) { if (platform.type === "website") { // website identifiers are full URLs with inconsistent normalization, // so match on the domain instead of the exact identifier if (websiteDomain) { - matchConditions.push({ + websiteMatchCondition = { identifier: { contains: websiteDomain, }, type: platform.type, - }); + }; } continue; } - matchConditions.push({ + exactMatchConditions.push({ identifier: platform.identifier, type: platform.type, }); } - if (matchConditions.length === 0) { + if (exactMatchConditions.length === 0 && !websiteMatchCondition) { return []; } @@ -94,39 +95,63 @@ export async function getSharedPartnerPlatforms({ }, } satisfies Prisma.PartnerPlatformInclude["partner"]; - const sharedPlatformMatches = ( - await Promise.all( - matchConditions.map((condition) => - prisma.partnerPlatform.findMany({ - where: { - ...condition, - partnerId: { - not: partner.id, - }, - verifiedAt: { - not: null, - }, - ...(programId && { - partner: { - programs: { - some: { - programId, - }, - }, - }, - }), - }, - include: { - partner: partnerInclude, - }, - orderBy: { - createdAt: "asc", + const baseWhere = { + partnerId: { + not: partner.id, + }, + verifiedAt: { + not: null, + }, + ...(programId && { + partner: { + programs: { + some: { + programId, }, - take: condition.type === "website" ? 100 : 10, - }), - ), - ) - ).flat() as SharedPlatformMatch[]; + }, + }, + }), + } satisfies Prisma.PartnerPlatformWhereInput; + + const matchQueries: Promise[] = []; + + if (exactMatchConditions.length > 0) { + matchQueries.push( + prisma.partnerPlatform.findMany({ + where: { + OR: exactMatchConditions, + ...baseWhere, + }, + include: { + partner: partnerInclude, + }, + orderBy: { + createdAt: "asc", + }, + take: 10 * exactMatchConditions.length, + }) as Promise, + ); + } + + if (websiteMatchCondition) { + matchQueries.push( + prisma.partnerPlatform.findMany({ + where: { + ...websiteMatchCondition, + ...baseWhere, + }, + include: { + partner: partnerInclude, + }, + orderBy: { + createdAt: "asc", + }, + take: 100, + }) as Promise, + ); + } + + const sharedPlatformMatches = (await Promise.all(matchQueries)).flat(); const sharedPlatforms = verifiedPlatforms .map((platform) => {