Skip to content

Commit 428fe74

Browse files
authored
Dismissed network partners (dubinc#3721)
1 parent 9bd7672 commit 428fe74

17 files changed

Lines changed: 674 additions & 578 deletions

File tree

apps/web/app/(ee)/api/network/partners/count/route.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,21 @@ export const GET = withWorkspace(
116116
some: { programId, invitedAt: { not: null } },
117117
},
118118
},
119+
ignored: {
120+
programs: { none: { programId } },
121+
discoveredByPrograms: {
122+
some: { programId, ignoredAt: { not: null } },
123+
},
124+
},
119125
} as const;
120126

127+
const statusWhereForFacet =
128+
status && status in statusWheres
129+
? statusWheres[status as keyof typeof statusWheres]
130+
: statusWheres.discover;
131+
121132
if (groupBy === "status") {
122-
const [discover, invited, recruited] = await Promise.all([
133+
const [discover, invited, recruited, ignored] = await Promise.all([
123134
!status || status === "discover"
124135
? prisma.partner.count({
125136
where: {
@@ -144,18 +155,27 @@ export const GET = withWorkspace(
144155
},
145156
})
146157
: undefined,
158+
!status || status === "ignored"
159+
? prisma.partner.count({
160+
where: {
161+
...commonWhere,
162+
...statusWheres.ignored,
163+
},
164+
})
165+
: undefined,
147166
]);
148167

149168
return NextResponse.json({
150169
discover,
151170
invited,
152171
recruited,
172+
ignored,
153173
});
154174
} else if (groupBy === "country") {
155175
const countries = await prisma.partner.groupBy({
156176
by: ["country"],
157177
_count: true,
158-
where: { ...commonWhere, ...statusWheres[status || "discover"] },
178+
where: { ...commonWhere, ...statusWhereForFacet },
159179
orderBy: {
160180
_count: {
161181
country: "desc",
@@ -185,7 +205,7 @@ export const GET = withWorkspace(
185205
// Build partner where clause combining all filters
186206
const partnerWhere: Prisma.PartnerWhereInput = {
187207
...commonWhere,
188-
...statusWheres[status || "discover"],
208+
...statusWhereForFacet,
189209
platforms: {
190210
some: platformPlatformFilter,
191211
},
@@ -238,7 +258,7 @@ export const GET = withWorkspace(
238258
subscriberRanges.map(async (range) => {
239259
const where: Prisma.PartnerWhereInput = {
240260
...commonWhere,
241-
...statusWheres[status || "discover"],
261+
...statusWhereForFacet,
242262
platforms: {
243263
some: {
244264
verifiedAt: { not: null },

apps/web/app/(ee)/api/network/partners/route.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getConversionScore } from "@/lib/actions/partners/get-conversion-score";
21
import { DubApiError } from "@/lib/api/errors";
32
import { calculatePartnerRanking } from "@/lib/api/network/calculate-partner-ranking";
43
import { getDefaultProgramIdOrThrow } from "@/lib/api/programs/get-default-program-id-or-throw";
@@ -55,6 +54,51 @@ export const GET = withWorkspace(
5554
subscribers,
5655
} = getNetworkPartnersQuerySchema.parse(searchParams);
5756

57+
if (status !== "discover") {
58+
const partners = await prisma.discoveredPartner.findMany({
59+
where: {
60+
programId,
61+
...(status === "ignored" && { ignoredAt: { not: null } }),
62+
...(status === "invited" && { invitedAt: { not: null } }),
63+
...(status === "recruited" && {
64+
invitedAt: { not: null },
65+
programEnrollment: { status: "approved" },
66+
}),
67+
},
68+
orderBy: {
69+
...(status === "ignored" && { ignoredAt: "desc" }),
70+
...(status === "invited" && { invitedAt: "desc" }),
71+
...(status === "recruited" && {
72+
programEnrollment: { createdAt: "desc" },
73+
}),
74+
},
75+
include: {
76+
partner: {
77+
include: {
78+
platforms: true,
79+
},
80+
},
81+
programEnrollment: true,
82+
},
83+
take: pageSize,
84+
skip: (page ?? 1 - 1) * pageSize,
85+
});
86+
87+
return NextResponse.json(
88+
partners.map(({ partner, ...rest }) =>
89+
NetworkPartnerSchema.parse({
90+
...rest,
91+
...partner,
92+
categories: [],
93+
recruitedAt:
94+
rest.programEnrollment?.status === "approved"
95+
? new Date(rest.programEnrollment.createdAt)
96+
: null,
97+
}),
98+
),
99+
);
100+
}
101+
58102
const similarPrograms = program.similarPrograms.map((sp) => ({
59103
programId: sp.similarProgramId,
60104
similarityScore: sp.similarityScore,
@@ -79,7 +123,6 @@ export const GET = withWorkspace(
79123
z.array(NetworkPartnerSchema).parse(
80124
partners.map((partner) => ({
81125
...partner,
82-
conversionScore: getConversionScore(partner.conversionRate || 0),
83126
starredAt: partner.starredAt ? new Date(partner.starredAt) : null,
84127
ignoredAt: partner.ignoredAt ? new Date(partner.ignoredAt) : null,
85128
invitedAt: partner.invitedAt ? new Date(partner.invitedAt) : null,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { PageContent } from "@/ui/layout/page-content";
2+
import { PageWidthWrapper } from "@/ui/layout/page-width-wrapper";
3+
import { InvitesUsage } from "@/ui/partners/partner-network/invites-usage";
4+
import { ProgramPartnerNetworkPageClient } from "../page-client";
5+
6+
export default async function ProgramPartnerNetworkIgnored(props: {
7+
params: Promise<{ slug: string }>;
8+
}) {
9+
const params = await props.params;
10+
11+
return (
12+
<PageContent
13+
title="Ignored partners"
14+
titleBackHref={`/${params.slug}/program/network`}
15+
controls={<InvitesUsage />}
16+
>
17+
<PageWidthWrapper className="mb-10">
18+
<ProgramPartnerNetworkPageClient variant="ignored" />
19+
</PageWidthWrapper>
20+
</PageContent>
21+
);
22+
}

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/network-empty-state.tsx

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,17 @@ export function NetworkEmptyState({
77
isFiltered,
88
isStarred,
99
onClearAllFilters,
10+
variant = "default",
1011
}: {
1112
isFiltered: boolean;
1213
isStarred: boolean;
1314
onClearAllFilters: () => void;
15+
variant?: "default" | "ignored";
1416
}) {
1517
return (
1618
<AnimatedEmptyState
1719
title="No partners found"
18-
description={
19-
isFiltered || isStarred ? (
20-
<>
21-
Press{" "}
22-
<span className="text-content-default bg-bg-emphasis rounded-md px-1 py-0.5 text-xs font-semibold">
23-
Esc
24-
</span>{" "}
25-
to clear all filters.
26-
</>
27-
) : (
28-
"There are no partners for you to discover yet."
29-
)
30-
}
20+
description="Adjust your filters to refine your search results."
3121
className="border-none md:min-h-[400px]"
3222
cardClassName="py-3"
3323
cardCount={2}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"use client";
2+
3+
import useProgram from "@/lib/swr/use-program";
4+
import useWorkspace from "@/lib/swr/use-workspace";
5+
import { ThreeDots } from "@/ui/shared/icons";
6+
import { Button, Popover, UserXmark } from "@dub/ui";
7+
import { useRouter } from "next/navigation";
8+
import { useState } from "react";
9+
10+
export function NetworkMenu() {
11+
const router = useRouter();
12+
const { slug: workspaceSlug } = useWorkspace();
13+
const { program } = useProgram();
14+
const [isOpen, setIsOpen] = useState(false);
15+
16+
return (
17+
<Popover
18+
openPopover={isOpen}
19+
setOpenPopover={setIsOpen}
20+
content={
21+
<div className="grid w-full gap-px p-2 md:w-56">
22+
<button
23+
type="button"
24+
onClick={() => {
25+
router.push(`/${workspaceSlug}/program/network/ignored`);
26+
setIsOpen(false);
27+
}}
28+
className="w-full rounded-md p-2 hover:bg-neutral-100 active:bg-neutral-200"
29+
>
30+
<div className="flex items-center gap-2 text-left">
31+
<UserXmark className="size-4 shrink-0" />
32+
<span className="text-sm font-medium">View ignored partners</span>
33+
</div>
34+
</button>
35+
</div>
36+
}
37+
align="end"
38+
>
39+
<Button
40+
type="button"
41+
aria-label="Open partner network menu"
42+
className="w-fit whitespace-nowrap px-2"
43+
variant="secondary"
44+
disabled={!program}
45+
icon={<ThreeDots className="size-4 shrink-0" />}
46+
/>
47+
</Popover>
48+
);
49+
}

0 commit comments

Comments
 (0)