Skip to content

Commit d401eb2

Browse files
committed
revert to draft
1 parent 6bb27d3 commit d401eb2

3 files changed

Lines changed: 111 additions & 35 deletions

File tree

apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/network/network-partner-application-sheet.tsx

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import {
1818
StatusBadge,
1919
Trophy,
2020
useKeyboardShortcut,
21-
useRouterStuff,
2221
User,
22+
useRouterStuff,
2323
Users,
2424
} from "@dub/ui";
2525
import { cn, COUNTRIES, currencyFormatter, OG_AVATAR_URL } from "@dub/utils";
@@ -30,6 +30,24 @@ import { NetworkPartnerChangeHistory } from "./network-partner-change-history";
3030

3131
type NetworkPartnerSheetTabId = "about" | "programs" | "duplicates";
3232

33+
function isDirectDraftToApprovedNetworkApproval(
34+
partner: AdminNetworkPartner,
35+
): boolean {
36+
if (partner.networkStatus !== "approved") {
37+
return false;
38+
}
39+
40+
const networkLogs = (partner.changeHistoryLog ?? []).filter(
41+
(entry) => entry.field === "networkStatus",
42+
);
43+
44+
return (
45+
networkLogs.length === 1 &&
46+
networkLogs[0].from === "draft" &&
47+
networkLogs[0].to === "approved"
48+
);
49+
}
50+
3351
export function NetworkPartnerApplicationSheet({
3452
isOpen,
3553
setIsOpen,
@@ -45,13 +63,16 @@ export function NetworkPartnerApplicationSheet({
4563
onNext?: () => void;
4664
onReview: (
4765
partner: AdminNetworkPartner,
48-
status: "approved" | "rejected",
66+
status: "approved" | "rejected" | "draft",
4967
) => Promise<void>;
5068
}) {
5169
const [currentTabId, setCurrentTabId] =
5270
useState<NetworkPartnerSheetTabId>("about");
5371
const { queryParams } = useRouterStuff();
5472

73+
const showRevertToDraftInsteadOfReject =
74+
isDirectDraftToApprovedNetworkApproval(partner);
75+
5576
const PartnerDetails = (
5677
<div className="rounded-lg border border-neutral-200 bg-neutral-100 p-3">
5778
<div className="flex items-center gap-4">
@@ -95,6 +116,21 @@ export function NetworkPartnerApplicationSheet({
95116
confirmShortcutOptions: { sheet: true, modal: true },
96117
});
97118

119+
const {
120+
setShowConfirmModal: setShowRevertToDraftConfirm,
121+
confirmModal: revertToDraftModal,
122+
} = useConfirmModal({
123+
title: "Revert network profile to draft",
124+
description: PartnerDetails,
125+
confirmText: "Revert to draft",
126+
confirmVariant: "danger",
127+
onConfirm: async () => {
128+
await onReview(partner, "draft");
129+
},
130+
confirmShortcut: "r",
131+
confirmShortcutOptions: { sheet: true, modal: true },
132+
});
133+
98134
useKeyboardShortcut(
99135
"ArrowRight",
100136
() => {
@@ -122,7 +158,14 @@ export function NetworkPartnerApplicationSheet({
122158

123159
useKeyboardShortcut("r", () => setShowRejectConfirm(true), {
124160
sheet: true,
125-
enabled: !["rejected", "trusted"].includes(partner.networkStatus),
161+
enabled:
162+
!showRevertToDraftInsteadOfReject &&
163+
!["rejected", "trusted"].includes(partner.networkStatus),
164+
});
165+
166+
useKeyboardShortcut("r", () => setShowRevertToDraftConfirm(true), {
167+
sheet: true,
168+
enabled: showRevertToDraftInsteadOfReject,
126169
});
127170

128171
return (
@@ -136,6 +179,7 @@ export function NetworkPartnerApplicationSheet({
136179
>
137180
{approveModal}
138181
{rejectModal}
182+
{revertToDraftModal}
139183

140184
<div className="flex size-full flex-col">
141185
<div className="flex h-16 shrink-0 items-center justify-between border-b border-neutral-200 px-6 py-4">
@@ -220,19 +264,30 @@ export function NetworkPartnerApplicationSheet({
220264

221265
<div className="shrink-0 border-t border-neutral-200 p-5">
222266
<div className="flex justify-end gap-2">
223-
<Button
224-
type="button"
225-
variant="secondary"
226-
text="Reject"
227-
className="w-fit shrink-0"
228-
shortcut="R"
229-
onClick={() => setShowRejectConfirm(true)}
230-
disabledTooltip={
231-
["rejected", "trusted"].includes(partner.networkStatus)
232-
? `Cannot reject a ${partner.networkStatus} partner.`
233-
: undefined
234-
}
235-
/>
267+
{showRevertToDraftInsteadOfReject ? (
268+
<Button
269+
type="button"
270+
variant="secondary"
271+
text="Revert to draft"
272+
className="w-fit shrink-0"
273+
shortcut="R"
274+
onClick={() => setShowRevertToDraftConfirm(true)}
275+
/>
276+
) : (
277+
<Button
278+
type="button"
279+
variant="secondary"
280+
text="Reject"
281+
className="w-fit shrink-0"
282+
shortcut="R"
283+
onClick={() => setShowRejectConfirm(true)}
284+
disabledTooltip={
285+
["rejected", "trusted"].includes(partner.networkStatus)
286+
? `Cannot reject a ${partner.networkStatus} partner.`
287+
: undefined
288+
}
289+
/>
290+
)}
236291
<Button
237292
type="button"
238293
variant="primary"
@@ -282,7 +337,12 @@ function NetworkPartnerSheetTabs({
282337
id: "programs",
283338
label: "Programs",
284339
icon: Trophy,
285-
badge: programsCount > 99 ? "99+" : programsCount,
340+
badge:
341+
programsCount === 0
342+
? undefined
343+
: programsCount > 99
344+
? "99+"
345+
: programsCount,
286346
},
287347
...(duplicatesCount > 0
288348
? [

apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/network/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export default function NetworkApplicationsPage() {
206206

207207
const handleReviewPartner = async (
208208
partner: AdminNetworkPartner,
209-
status: "approved" | "rejected",
209+
status: "approved" | "rejected" | "draft",
210210
) => {
211211
const currentIndex = partners.findIndex(({ id }) => id === partner.id);
212212
const fallbackPartnerId =
@@ -233,7 +233,9 @@ export default function NetworkApplicationsPage() {
233233
toast.success(
234234
status === "approved"
235235
? "Partner approved for the network."
236-
: "Partner rejected from the network.",
236+
: status === "draft"
237+
? "Network profile reverted to draft."
238+
: "Partner rejected from the network.",
237239
);
238240

239241
await mutate();

apps/web/app/(ee)/api/admin/partners/[partnerId]/network-status/route.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ import { NextResponse } from "next/server";
99
import * as z from "zod/v4";
1010

1111
const updateAdminNetworkStatusSchema = z.object({
12-
status: z.enum(["approved", "rejected"]),
12+
status: z.enum(["approved", "rejected", "draft"]),
1313
});
1414

1515
// PATCH /api/admin/partners/[partnerId]/network-status
1616
export const PATCH = withAdmin(
1717
async ({ params, req }) => {
1818
const { partnerId } = params;
19-
const { status } = updateAdminNetworkStatusSchema.parse(await req.json());
19+
const { status: updatedStatus } = updateAdminNetworkStatusSchema.parse(
20+
await req.json(),
21+
);
2022

2123
const existingPartner = await prisma.partner.findUnique({
2224
where: {
@@ -35,39 +37,49 @@ export const PATCH = withAdmin(
3537
return new Response("Partner not found.", { status: 404 });
3638
}
3739

38-
if (existingPartner.networkStatus === status) {
40+
if (existingPartner.networkStatus === updatedStatus) {
3941
return new Response("Partner is already in this status.", {
4042
status: 400,
4143
});
4244
}
4345

44-
if (existingPartner.networkStatus === "trusted" && status === "approved") {
46+
if (
47+
existingPartner.networkStatus === "trusted" &&
48+
updatedStatus === "approved"
49+
) {
4550
return new Response("Trusted partners cannot be approved.", {
4651
status: 400,
4752
});
4853
}
4954

50-
const partnerChangeHistoryLog = existingPartner.changeHistoryLog
55+
let partnerChangeHistoryLog = existingPartner.changeHistoryLog
5156
? partnerProfileChangeHistoryLogSchema.parse(
5257
existingPartner.changeHistoryLog,
5358
)
5459
: [];
5560

56-
partnerChangeHistoryLog.push({
57-
field: "networkStatus",
58-
from: existingPartner.networkStatus,
59-
to: status,
60-
changedAt: new Date(),
61-
});
61+
if (updatedStatus === "draft") {
62+
// if reverting back to draft, remove any pre-existing networkStatus change logs
63+
partnerChangeHistoryLog = partnerChangeHistoryLog.filter(
64+
(log) => log.field !== "networkStatus",
65+
);
66+
} else {
67+
partnerChangeHistoryLog.push({
68+
field: "networkStatus",
69+
from: existingPartner.networkStatus,
70+
to: updatedStatus,
71+
changedAt: new Date(),
72+
});
73+
}
6274

6375
const updatedPartner = await prisma.partner.update({
6476
where: {
6577
id: partnerId,
6678
},
6779
data: {
68-
networkStatus: status,
80+
networkStatus: updatedStatus,
6981
changeHistoryLog: partnerChangeHistoryLog,
70-
reviewedAt: new Date(),
82+
reviewedAt: updatedStatus === "draft" ? null : new Date(),
7183
},
7284
select: {
7385
id: true,
@@ -77,18 +89,20 @@ export const PATCH = withAdmin(
7789

7890
if (
7991
existingPartner.email &&
80-
(status === "approved" || status === "rejected")
92+
// only send notification emails if partner actually submitted their profile
93+
existingPartner.networkStatus === "submitted" &&
94+
(updatedStatus === "approved" || updatedStatus === "rejected")
8195
) {
8296
waitUntil(
8397
sendEmail({
8498
to: existingPartner.email,
8599
subject:
86-
status === "approved"
100+
updatedStatus === "approved"
87101
? "Your Dub Partner Network application was approved"
88102
: "Dub Partner Network application update",
89103
variant: "notifications",
90104
react:
91-
status === "approved"
105+
updatedStatus === "approved"
92106
? NetworkPartnerApplicationApproved({
93107
name: existingPartner.name,
94108
email: existingPartner.email,

0 commit comments

Comments
 (0)