Skip to content

Commit 87c8cc5

Browse files
authored
Fix customer activity endpoint (#3401)
1 parent 6686f04 commit 87c8cc5

File tree

8 files changed

+46
-92
lines changed

8 files changed

+46
-92
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: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,6 @@ export const GET = withPartnerProfile(async ({ partner, params }) => {
8282
const firstSaleAt =
8383
customer.commissions[0]?.createdAt ?? customer.firstSaleAt;
8484

85-
// TODO: Calculate based on events (more accurate for multi-tenant setups where customers can interact with multiple partners)
86-
const timeToLead =
87-
customer.clickedAt && customer.createdAt
88-
? customer.createdAt.getTime() - customer.clickedAt.getTime()
89-
: null;
90-
91-
const timeToSale =
92-
firstSaleAt && customer.createdAt
93-
? firstSaleAt.getTime() - customer.createdAt.getTime()
94-
: null;
95-
9685
return NextResponse.json(
9786
PartnerProfileCustomerSchema.extend({
9887
...(customerDataSharingEnabledAt && { name: z.string().nullish() }),
@@ -108,9 +97,6 @@ export const GET = withPartnerProfile(async ({ partner, params }) => {
10897
}),
10998
activity: {
11099
...customer,
111-
firstSaleAt,
112-
timeToLead,
113-
timeToSale,
114100
events,
115101
link,
116102
},

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,6 @@ export const GET = withPartnerProfile(
8585
const firstSaleAt =
8686
customer.commissions[0]?.createdAt ?? customer.firstSaleAt;
8787

88-
// TODO: Calculate based on events (more accurate for multi-tenant setups where customers can interact with multiple partners)
89-
const timeToLead =
90-
customer.clickedAt && customer.createdAt
91-
? customer.createdAt.getTime() - customer.clickedAt.getTime()
92-
: null;
93-
9488
return PartnerProfileCustomerSchema.extend({
9589
...(customerDataSharingEnabledAt && { name: z.string().nullish() }),
9690
}).parse({
@@ -105,9 +99,6 @@ export const GET = withPartnerProfile(
10599
}),
106100
activity: {
107101
...customer,
108-
firstSaleAt,
109-
timeToLead,
110-
timeToSale: null,
111102
events: [],
112103
link: customer.link,
113104
},

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ export function ProgramCustomerPageClient() {
3131
});
3232

3333
const rewardPeriodEndDate = useMemo(() => {
34-
if (!programEnrollment?.rewards || !customer?.activity?.firstSaleAt)
35-
return null;
34+
if (!programEnrollment?.rewards || !customer?.firstSaleAt) return null;
3635
const saleReward = programEnrollment?.rewards.find(
3736
(r) => r.event === "sale",
3837
);
@@ -43,7 +42,7 @@ export function ProgramCustomerPageClient() {
4342
return null;
4443

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

4948
if ((!customer && !isLoading) || !showDetailedAnalytics) {

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/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/tests/utils/verify-commission.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ export const verifyCommission = async ({
1919
}: VerifyCommissionProps) => {
2020
let customerId: string | undefined;
2121

22-
// Pause for 1.5 seconds for data to be fully processed
23-
await new Promise((resolve) => setTimeout(resolve, 1500));
22+
// Pause for 3 seconds for data to be fully processed
23+
await new Promise((resolve) => setTimeout(resolve, 3000));
2424

2525
// Optional: resolve customer ID if customerExternalId is given
2626
if (customerExternalId) {
@@ -31,9 +31,6 @@ export const verifyCommission = async ({
3131

3232
expect(customers.length).toBeGreaterThan(0);
3333
customerId = customers[0].id;
34-
35-
// Small delay if necessary for async commission processing
36-
await new Promise((resolve) => setTimeout(resolve, 2000));
3734
}
3835

3936
const query: Record<string, string> = {};

apps/web/ui/customers/customer-stats.tsx

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import useCustomerActivity from "@/lib/swr/use-customer-activity";
21
import { CustomerEnriched } from "@/lib/types";
32
import { TimestampTooltip } from "@dub/ui";
43
import { ArrowUpRight2 } from "@dub/ui/icons";
@@ -17,17 +16,20 @@ import { CSSProperties, useMemo } from "react";
1716
export function CustomerStats({
1817
customer,
1918
}: {
20-
customer?: Pick<CustomerEnriched, "sales" | "saleAmount" | "createdAt">;
19+
customer?: Pick<
20+
CustomerEnriched,
21+
| "sales"
22+
| "saleAmount"
23+
| "createdAt"
24+
| "firstSaleAt"
25+
| "subscriptionCanceledAt"
26+
>;
2127
}) {
2228
const { slug: workspaceSlug, customerId } = useParams<{
2329
slug: string;
2430
customerId: string;
2531
}>();
2632

27-
const { customerActivity, isCustomerActivityLoading } = useCustomerActivity({
28-
customerId,
29-
});
30-
3133
const stats: {
3234
label: string;
3335
value?: string | React.ReactNode;
@@ -36,29 +38,27 @@ export function CustomerStats({
3638
() => [
3739
{
3840
label: "First sale date",
39-
value:
40-
isCustomerActivityLoading ? undefined : customerActivity?.firstSaleAt ? (
41-
<TimestampTooltip
42-
timestamp={customerActivity.firstSaleAt}
43-
side="right"
44-
rows={["local", "utc"]}
45-
>
46-
<span className="hover:text-content-emphasis underline decoration-dotted underline-offset-2">
47-
{formatDateTimeSmart(customerActivity.firstSaleAt)}
48-
</span>
49-
</TimestampTooltip>
50-
) : (
51-
"-"
52-
),
41+
value: !customer ? undefined : customer.firstSaleAt ? (
42+
<TimestampTooltip
43+
timestamp={customer.firstSaleAt}
44+
side="right"
45+
rows={["local", "utc"]}
46+
>
47+
<span className="hover:text-content-emphasis underline decoration-dotted underline-offset-2">
48+
{formatDateTimeSmart(customer.firstSaleAt)}
49+
</span>
50+
</TimestampTooltip>
51+
) : (
52+
"-"
53+
),
5354
},
5455
{
5556
label: "Time to sale",
56-
value:
57-
!customer || isCustomerActivityLoading
58-
? undefined
59-
: customerActivity?.firstSaleAt
60-
? formatDistance(customerActivity.firstSaleAt, customer.createdAt)
61-
: "-",
57+
value: !customer
58+
? undefined
59+
: customer.firstSaleAt
60+
? formatDistance(customer.firstSaleAt, customer.createdAt)
61+
: "-",
6262
},
6363
{
6464
label: "Lifetime value",
@@ -75,23 +75,22 @@ export function CustomerStats({
7575
},
7676
{
7777
label: "Subscription canceled",
78-
value:
79-
isCustomerActivityLoading ? undefined : customerActivity?.subscriptionCanceledAt ? (
80-
<TimestampTooltip
81-
timestamp={customerActivity.subscriptionCanceledAt}
82-
side="right"
83-
rows={["local", "utc"]}
84-
>
85-
<span className="hover:text-content-emphasis underline decoration-dotted underline-offset-2">
86-
{formatDateTimeSmart(customerActivity.subscriptionCanceledAt)}
87-
</span>
88-
</TimestampTooltip>
89-
) : (
90-
"-"
91-
),
78+
value: !customer ? undefined : customer.subscriptionCanceledAt ? (
79+
<TimestampTooltip
80+
timestamp={customer.subscriptionCanceledAt}
81+
side="right"
82+
rows={["local", "utc"]}
83+
>
84+
<span className="hover:text-content-emphasis underline decoration-dotted underline-offset-2">
85+
{formatDateTimeSmart(customer.subscriptionCanceledAt)}
86+
</span>
87+
</TimestampTooltip>
88+
) : (
89+
"-"
90+
),
9291
},
9392
],
94-
[workspaceSlug, customer, customerActivity, isCustomerActivityLoading],
93+
[workspaceSlug, customer],
9594
);
9695

9796
return (

0 commit comments

Comments
 (0)