Skip to content

Commit fda8c61

Browse files
authored
Merge branch 'main' into group-move
2 parents 6b3a4eb + 5243ad0 commit fda8c61

File tree

25 files changed

+312
-199
lines changed

25 files changed

+312
-199
lines changed

apps/web/app/(ee)/api/customers/[id]/activity/route.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,9 @@ export const GET = withWorkspace(async ({ workspace, params }) => {
4646
link = decodeLinkIfCaseSensitive(link);
4747
}
4848

49-
// Find the time to lead of the customer
50-
const timeToLead =
51-
customer.clickedAt && customer.createdAt
52-
? customer.createdAt.getTime() - customer.clickedAt.getTime()
53-
: null;
54-
55-
const timeToSale =
56-
customer.firstSaleAt && customer.createdAt
57-
? customer.firstSaleAt.getTime() - customer.createdAt.getTime()
58-
: null;
59-
6049
return NextResponse.json(
6150
customerActivityResponseSchema.parse({
6251
...customer,
63-
timeToLead,
64-
timeToSale,
6552
events,
6653
link,
6754
}),

apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { generateRandomName } from "@/lib/names";
1111
import { PartnerProfileCustomerSchema } from "@/lib/zod/schemas/partner-profile";
1212
import { prisma } from "@dub/prisma";
13+
import { CommissionType } from "@dub/prisma/client";
1314
import { NextResponse } from "next/server";
1415
import * as z from "zod/v4";
1516

@@ -41,6 +42,19 @@ export const GET = withPartnerProfile(async ({ partner, params }) => {
4142
where: {
4243
id: customerId,
4344
},
45+
include: {
46+
// find the first sale commission for this customer and partner
47+
commissions: {
48+
where: {
49+
partnerId: partner.id,
50+
type: CommissionType.sale,
51+
},
52+
take: 1,
53+
orderBy: {
54+
createdAt: "asc",
55+
},
56+
},
57+
},
4458
});
4559

4660
if (!customer || customer?.projectId !== program.workspaceId) {
@@ -65,23 +79,16 @@ export const GET = withPartnerProfile(async ({ partner, params }) => {
6579
// get the first partner link that this customer interacted with
6680
const firstLinkId = events[events.length - 1].link_id;
6781
const link = links.find((link) => link.id === firstLinkId);
68-
69-
const timeToLead =
70-
customer.clickedAt && customer.createdAt
71-
? customer.createdAt.getTime() - customer.clickedAt.getTime()
72-
: null;
73-
74-
const timeToSale =
75-
customer.firstSaleAt && customer.createdAt
76-
? customer.firstSaleAt.getTime() - customer.createdAt.getTime()
77-
: null;
82+
const firstSaleAt =
83+
customer.commissions[0]?.createdAt ?? customer.firstSaleAt;
7884

7985
return NextResponse.json(
8086
PartnerProfileCustomerSchema.extend({
8187
...(customerDataSharingEnabledAt && { name: z.string().nullish() }),
8288
}).parse({
8389
...transformCustomer({
8490
...customer,
91+
firstSaleAt,
8592
email: customer.email
8693
? customerDataSharingEnabledAt
8794
? customer.email
@@ -90,8 +97,6 @@ export const GET = withPartnerProfile(async ({ partner, params }) => {
9097
}),
9198
activity: {
9299
...customer,
93-
timeToLead,
94-
timeToSale,
95100
events,
96101
link,
97102
},

apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/route.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
getPartnerCustomersQuerySchema,
1313
} from "@/lib/zod/schemas/partner-profile";
1414
import { prisma, sanitizeFullTextSearch } from "@dub/prisma";
15+
import { CommissionType } from "@dub/prisma/client";
1516
import { NextResponse } from "next/server";
1617
import * as z from "zod/v4";
1718

@@ -61,6 +62,16 @@ export const GET = withPartnerProfile(
6162
},
6263
include: {
6364
link: true,
65+
commissions: {
66+
where: {
67+
partnerId: partner.id,
68+
type: CommissionType.sale,
69+
},
70+
take: 1,
71+
orderBy: {
72+
createdAt: "asc",
73+
},
74+
},
6475
},
6576
orderBy: {
6677
[sortBy]: sortOrder,
@@ -71,16 +82,15 @@ export const GET = withPartnerProfile(
7182

7283
// Map customers with their data
7384
const customersWithData = customers.map((customer) => {
74-
const timeToLead =
75-
customer.clickedAt && customer.createdAt
76-
? customer.createdAt.getTime() - customer.clickedAt.getTime()
77-
: null;
85+
const firstSaleAt =
86+
customer.commissions[0]?.createdAt ?? customer.firstSaleAt;
7887

7988
return PartnerProfileCustomerSchema.extend({
8089
...(customerDataSharingEnabledAt && { name: z.string().nullish() }),
8190
}).parse({
8291
...transformCustomer({
8392
...customer,
93+
firstSaleAt,
8494
email: customer.email
8595
? customerDataSharingEnabledAt
8696
? customer.email
@@ -89,8 +99,6 @@ export const GET = withPartnerProfile(
8999
}),
90100
activity: {
91101
...customer,
92-
timeToLead,
93-
timeToSale: null,
94102
events: [],
95103
link: customer.link,
96104
},

apps/web/app/(ee)/api/partners/[partnerId]/cross-program-summary/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const GET = withWorkspace(
3232
});
3333

3434
// approved and archived statuses
35-
const trustedPrograms = programEnrollments
35+
const activePrograms = programEnrollments
3636
.filter((enrollment) =>
3737
ACTIVE_ENROLLMENT_STATUSES.includes(enrollment.status),
3838
)
@@ -45,8 +45,8 @@ export const GET = withWorkspace(
4545

4646
return NextResponse.json(
4747
partnerCrossProgramSummarySchema.parse({
48-
totalPrograms: trustedPrograms + bannedPrograms,
49-
trustedPrograms,
48+
totalPrograms: activePrograms + bannedPrograms,
49+
activePrograms,
5050
bannedPrograms,
5151
}),
5252
);

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -278,12 +278,7 @@ export function ProgramCustomersPageClient() {
278278
onPaginationChange: setPagination,
279279
columnVisibility,
280280
onColumnVisibilityChange: setColumnVisibility,
281-
sortableColumns: [
282-
"saleAmount",
283-
"createdAt",
284-
"firstSaleAt",
285-
"subscriptionCanceledAt",
286-
],
281+
sortableColumns: ["saleAmount", "createdAt"],
287282
sortBy,
288283
sortOrder,
289284
onSortChange: ({ sortBy, sortOrder }) =>

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import { CustomerSalesTable } from "@/ui/customers/customer-sales-table";
1010
import { PageContent } from "@/ui/layout/page-content";
1111
import { PageWidthWrapper } from "@/ui/layout/page-width-wrapper";
1212
import { ProgramRewardList } from "@/ui/partners/program-reward-list";
13-
import { ChevronRight, MoneyBill2, Tooltip, UserCheck } from "@dub/ui";
13+
import { ChevronRight, MoneyBill2, Tooltip, User } from "@dub/ui";
1414
import { cn, fetcher, formatDate } from "@dub/utils";
1515
import { addMonths, isBefore } from "date-fns";
1616
import { AlertCircle } from "lucide-react";
17+
import Link from "next/link";
1718
import { redirect, useParams } from "next/navigation";
1819
import { memo, useMemo } from "react";
1920
import useSWR from "swr";
@@ -30,8 +31,7 @@ export function ProgramCustomerPageClient() {
3031
});
3132

3233
const rewardPeriodEndDate = useMemo(() => {
33-
if (!programEnrollment?.rewards || !customer?.activity?.firstSaleAt)
34-
return null;
34+
if (!programEnrollment?.rewards || !customer?.firstSaleAt) return null;
3535
const saleReward = programEnrollment?.rewards.find(
3636
(r) => r.event === "sale",
3737
);
@@ -42,7 +42,7 @@ export function ProgramCustomerPageClient() {
4242
return null;
4343

4444
// add the max duration to the first sale date
45-
return addMonths(customer.activity.firstSaleAt, saleReward.maxDuration);
45+
return addMonths(customer.firstSaleAt, saleReward.maxDuration);
4646
}, [programEnrollment, customer]);
4747

4848
if ((!customer && !isLoading) || !showDetailedAnalytics) {
@@ -53,17 +53,17 @@ export function ProgramCustomerPageClient() {
5353
<PageContent
5454
title={
5555
<div className="flex items-center gap-1.5">
56-
<div
57-
// href={`/programs/${programSlug}/customers`}
58-
// aria-label="Back to customers"
59-
// title="Back to customers"
56+
<Link
57+
href={`/programs/${programSlug}/customers`}
58+
aria-label="Back to customers"
59+
title="Back to customers"
6060
className={cn(
6161
"bg-bg-subtle flex size-8 shrink-0 items-center justify-center rounded-lg",
62-
// "hover:bg-bg-emphasis transition-[transform,background-color] duration-150 active:scale-95",
62+
"hover:bg-bg-emphasis transition-[transform,background-color] duration-150 active:scale-95",
6363
)}
6464
>
65-
<UserCheck className="size-4" />
66-
</div>
65+
<User className="size-4" />
66+
</Link>
6767
<ChevronRight className="text-content-muted size-2.5 shrink-0 [&_*]:stroke-2" />
6868
<div>
6969
{customer ? (

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { CustomerTabs } from "@/ui/customers/customer-tabs";
1111
import { PageContent } from "@/ui/layout/page-content";
1212
import { PageWidthWrapper } from "@/ui/layout/page-width-wrapper";
1313
import { Button } from "@dub/ui";
14-
import { ChevronRight, UserCheck } from "@dub/ui/icons";
14+
import { ChevronRight, User } from "@dub/ui/icons";
1515
import Link from "next/link";
1616
import { redirect, useParams } from "next/navigation";
1717
import { ReactNode } from "react";
@@ -43,7 +43,7 @@ export default function CustomerLayout({ children }: { children: ReactNode }) {
4343
title="Back to customers"
4444
className="bg-bg-subtle hover:bg-bg-emphasis flex size-8 shrink-0 items-center justify-center rounded-lg transition-[transform,background-color] duration-150 active:scale-95"
4545
>
46-
<UserCheck className="size-4" />
46+
<User className="size-4" />
4747
</Link>
4848
<ChevronRight className="text-content-muted size-2.5 shrink-0 [&_*]:stroke-2" />
4949
<div>

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/resources/program-help-and-support.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export function ProgramHelpAndSupportContent({
5252
control,
5353
register,
5454
handleSubmit,
55+
getValues,
5556
formState: { isDirty, isValid, isSubmitting },
5657
} = useForm<FormData>({
5758
mode: "onBlur",
@@ -67,6 +68,15 @@ export function ProgramHelpAndSupportContent({
6768
onSuccess: async () => {
6869
toast.success("Communication settings updated successfully.");
6970
await mutate(`/api/programs/${program?.id}?workspaceId=${workspaceId}`);
71+
72+
// Notify other tabs (e.g., the application builder) that program data changed
73+
try {
74+
const channel = new BroadcastChannel("program-terms-updated");
75+
channel.postMessage({ termsUrl: getValues("termsUrl") || null });
76+
channel.close();
77+
} catch {
78+
// BroadcastChannel not supported, fall back to revalidateOnFocus
79+
}
7080
},
7181
onError: ({ error }) => {
7282
toast.error(

apps/web/lib/zod/schemas/customer-activity.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ import * as z from "zod/v4";
22
import { LinkSchema } from "./links";
33

44
export const customerActivityResponseSchema = z.object({
5-
saleAmount: z.number(),
6-
timeToLead: z.number().nullable(),
7-
timeToSale: z.number().nullable(),
8-
firstSaleAt: z.date().nullable(),
9-
subscriptionCanceledAt: z.date().nullable(),
105
events: z.array(z.any()), // we've already parsed the events in get-customer-events.ts
116
link: LinkSchema.pick({
127
id: true,

apps/web/lib/zod/schemas/partners.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,6 @@ export const partnerPayoutSettingsSchema = z.object({
852852

853853
export const partnerCrossProgramSummarySchema = z.object({
854854
totalPrograms: z.number(),
855-
trustedPrograms: z.number(),
855+
activePrograms: z.number(),
856856
bannedPrograms: z.number(),
857857
});

0 commit comments

Comments
 (0)