diff --git a/apps/web/app/api/analytics/client/route.ts b/apps/web/app/api/analytics/client/route.ts
index 9a5761133a..4a75223ff6 100644
--- a/apps/web/app/api/analytics/client/route.ts
+++ b/apps/web/app/api/analytics/client/route.ts
@@ -1,5 +1,5 @@
import { getAnalytics } from "@/lib/analytics/get-analytics";
-import { calculateEarnings } from "@/lib/api/sales/commission";
+import { calculateEarnings } from "@/lib/api/sales/calculate-earnings";
import { withEmbedToken } from "@/lib/embed/auth";
import { analyticsQuerySchema } from "@/lib/zod/schemas/analytics";
import { NextResponse } from "next/server";
diff --git a/apps/web/app/api/customers/[id]/route.ts b/apps/web/app/api/customers/[id]/route.ts
index b62a58604e..8e37f7fe90 100644
--- a/apps/web/app/api/customers/[id]/route.ts
+++ b/apps/web/app/api/customers/[id]/route.ts
@@ -1,4 +1,5 @@
import { getCustomerOrThrow } from "@/lib/api/customers/get-customer-or-throw";
+import { transformCustomer } from "@/lib/api/customers/transform-customer";
import { DubApiError } from "@/lib/api/errors";
import { parseRequestBody } from "@/lib/api/utils";
import { withWorkspace } from "@/lib/auth";
@@ -24,7 +25,7 @@ export const GET = withWorkspace(
},
);
- return NextResponse.json(CustomerSchema.parse(customer));
+ return NextResponse.json(CustomerSchema.parse(transformCustomer(customer)));
},
{
requiredAddOn: "conversion",
@@ -52,11 +53,22 @@ export const PATCH = withWorkspace(
},
data: { name, email, avatar, externalId },
include: {
- link: true,
+ link: {
+ include: {
+ programEnrollment: {
+ include: {
+ partner: true,
+ discount: true,
+ },
+ },
+ },
+ },
},
});
- return NextResponse.json(CustomerSchema.parse(updatedCustomer));
+ return NextResponse.json(
+ CustomerSchema.parse(transformCustomer(updatedCustomer)),
+ );
} catch (error) {
if (error.code === "P2002") {
throw new DubApiError({
diff --git a/apps/web/app/api/customers/route.ts b/apps/web/app/api/customers/route.ts
index 6d0b313d48..62cfbefe7a 100644
--- a/apps/web/app/api/customers/route.ts
+++ b/apps/web/app/api/customers/route.ts
@@ -1,3 +1,4 @@
+import { transformCustomer } from "@/lib/api/customers/transform-customer";
import { DubApiError } from "@/lib/api/errors";
import { parseRequestBody } from "@/lib/api/utils";
import { withWorkspace } from "@/lib/auth";
@@ -22,7 +23,9 @@ export const GET = withWorkspace(
},
});
- return NextResponse.json(CustomerSchema.array().parse(customers));
+ return NextResponse.json(
+ CustomerSchema.array().parse(customers.map(transformCustomer)),
+ );
},
{
requiredAddOn: "conversion",
@@ -48,14 +51,14 @@ export const POST = withWorkspace(
projectId: workspace.id,
projectConnectId: workspace.stripeConnectId,
},
- include: {
- link: true,
- },
});
- return NextResponse.json(CustomerSchema.parse(customer), {
- status: 201,
- });
+ return NextResponse.json(
+ CustomerSchema.parse(transformCustomer(customer)),
+ {
+ status: 201,
+ },
+ );
} catch (error) {
if (error.code === "P2002") {
throw new DubApiError({
diff --git a/apps/web/app/api/embed/analytics/route.ts b/apps/web/app/api/embed/analytics/route.ts
new file mode 100644
index 0000000000..c134280e18
--- /dev/null
+++ b/apps/web/app/api/embed/analytics/route.ts
@@ -0,0 +1,15 @@
+import { getAnalytics } from "@/lib/analytics/get-analytics";
+import { withEmbedToken } from "@/lib/embed/auth";
+import { NextResponse } from "next/server";
+
+// GET /api/embed/analytics – get timeseries analytics for a link from an embed token
+export const GET = withEmbedToken(async ({ link }) => {
+ const analytics = await getAnalytics({
+ event: "composite",
+ groupBy: "timeseries",
+ linkId: link.id,
+ interval: "1y",
+ });
+
+ return NextResponse.json(analytics);
+});
diff --git a/apps/web/app/api/embed/leaderboard/route.ts b/apps/web/app/api/embed/leaderboard/route.ts
new file mode 100644
index 0000000000..2c8a4ec360
--- /dev/null
+++ b/apps/web/app/api/embed/leaderboard/route.ts
@@ -0,0 +1,40 @@
+import { withEmbedToken } from "@/lib/embed/auth";
+import { LeaderboardPartnerSchema } from "@/lib/zod/schemas/partners";
+import { prisma } from "@dub/prisma";
+import { NextResponse } from "next/server";
+import z from "node_modules/zod/lib";
+
+// GET /api/embed/sales – get sales for a link from an embed token
+export const GET = withEmbedToken(async ({ program, searchParams }) => {
+ const programEnrollments = await prisma.programEnrollment.findMany({
+ where: {
+ programId: program.id,
+ },
+ orderBy: [
+ {
+ link: {
+ saleAmount: "desc",
+ },
+ },
+ {
+ link: {
+ leads: "desc",
+ },
+ },
+ {
+ link: {
+ clicks: "desc",
+ },
+ },
+ ],
+ select: {
+ partner: true,
+ link: true,
+ },
+ take: 20,
+ });
+
+ return NextResponse.json(
+ z.array(LeaderboardPartnerSchema).parse(programEnrollments),
+ );
+});
diff --git a/apps/web/app/api/embed/sales/route.ts b/apps/web/app/api/embed/sales/route.ts
index 720b209116..7ea7b25dd9 100644
--- a/apps/web/app/api/embed/sales/route.ts
+++ b/apps/web/app/api/embed/sales/route.ts
@@ -1,11 +1,16 @@
import { withEmbedToken } from "@/lib/embed/auth";
+import { SALES_PAGE_SIZE } from "@/lib/partners/constants";
import z from "@/lib/zod";
import { PartnerSaleResponseSchema } from "@/lib/zod/schemas/partners";
import { prisma } from "@dub/prisma";
import { NextResponse } from "next/server";
// GET /api/embed/sales – get sales for a link from an embed token
-export const GET = withEmbedToken(async ({ link }) => {
+export const GET = withEmbedToken(async ({ link, searchParams }) => {
+ const { page } = z
+ .object({ page: z.coerce.number().optional().default(1) })
+ .parse(searchParams);
+
const sales = await prisma.sale.findMany({
where: {
linkId: link.id,
@@ -25,7 +30,8 @@ export const GET = withEmbedToken(async ({ link }) => {
},
},
},
- take: 3,
+ take: SALES_PAGE_SIZE,
+ skip: (page - 1) * SALES_PAGE_SIZE,
orderBy: {
createdAt: "desc",
},
diff --git a/apps/web/app/api/events/client/route.ts b/apps/web/app/api/events/client/route.ts
index 1a8b002662..3c54b57b35 100644
--- a/apps/web/app/api/events/client/route.ts
+++ b/apps/web/app/api/events/client/route.ts
@@ -1,5 +1,5 @@
import { getEvents } from "@/lib/analytics/get-events";
-import { calculateEarnings } from "@/lib/api/sales/commission";
+import { calculateEarnings } from "@/lib/api/sales/calculate-earnings";
import { withEmbedToken } from "@/lib/embed/auth";
import { eventsQuerySchema } from "@/lib/zod/schemas/analytics";
import { NextResponse } from "next/server";
diff --git a/apps/web/app/api/partners/[partnerId]/programs/[programId]/analytics/route.ts b/apps/web/app/api/partners/[partnerId]/programs/[programId]/analytics/route.ts
index eca9c653c4..ad5d0f29cf 100644
--- a/apps/web/app/api/partners/[partnerId]/programs/[programId]/analytics/route.ts
+++ b/apps/web/app/api/partners/[partnerId]/programs/[programId]/analytics/route.ts
@@ -1,6 +1,6 @@
import { getAnalytics } from "@/lib/analytics/get-analytics";
import { getProgramEnrollmentOrThrow } from "@/lib/api/programs/get-program-enrollment-or-throw";
-import { calculateEarnings } from "@/lib/api/sales/commission";
+import { calculateEarnings } from "@/lib/api/sales/calculate-earnings";
import { withPartner } from "@/lib/auth/partner";
import { analyticsQuerySchema } from "@/lib/zod/schemas/analytics";
import { NextResponse } from "next/server";
diff --git a/apps/web/app/api/programs/[programId]/analytics/route.ts b/apps/web/app/api/programs/[programId]/analytics/route.ts
index 4573687441..5f7ef7b7a4 100644
--- a/apps/web/app/api/programs/[programId]/analytics/route.ts
+++ b/apps/web/app/api/programs/[programId]/analytics/route.ts
@@ -1,5 +1,5 @@
import { getAnalytics } from "@/lib/analytics/get-analytics";
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { analyticsQuerySchema } from "@/lib/zod/schemas/analytics";
import { NextResponse } from "next/server";
diff --git a/apps/web/app/api/programs/[programId]/customers/count/route.ts b/apps/web/app/api/programs/[programId]/customers/count/route.ts
index 562fb07916..51cc81a51b 100644
--- a/apps/web/app/api/programs/[programId]/customers/count/route.ts
+++ b/apps/web/app/api/programs/[programId]/customers/count/route.ts
@@ -1,4 +1,4 @@
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { prisma } from "@dub/prisma";
import { NextResponse } from "next/server";
diff --git a/apps/web/app/api/programs/[programId]/customers/route.ts b/apps/web/app/api/programs/[programId]/customers/route.ts
index eb8afa56cc..7b76f76b2e 100644
--- a/apps/web/app/api/programs/[programId]/customers/route.ts
+++ b/apps/web/app/api/programs/[programId]/customers/route.ts
@@ -1,4 +1,4 @@
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import {
CustomerSchema,
diff --git a/apps/web/app/api/programs/[programId]/invites/count/route.ts b/apps/web/app/api/programs/[programId]/invites/count/route.ts
index e513fd366d..83857988ab 100644
--- a/apps/web/app/api/programs/[programId]/invites/count/route.ts
+++ b/apps/web/app/api/programs/[programId]/invites/count/route.ts
@@ -1,4 +1,4 @@
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { prisma } from "@dub/prisma";
import { NextResponse } from "next/server";
diff --git a/apps/web/app/api/programs/[programId]/invites/route.ts b/apps/web/app/api/programs/[programId]/invites/route.ts
index 744c2d34cd..ff5fd21a41 100644
--- a/apps/web/app/api/programs/[programId]/invites/route.ts
+++ b/apps/web/app/api/programs/[programId]/invites/route.ts
@@ -1,4 +1,4 @@
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { partnerInvitesQuerySchema } from "@/lib/zod/schemas/partners";
import { ProgramInviteSchema } from "@/lib/zod/schemas/programs";
diff --git a/apps/web/app/api/programs/[programId]/metrics/route.ts b/apps/web/app/api/programs/[programId]/metrics/route.ts
index 650ca65975..f5b25205d3 100644
--- a/apps/web/app/api/programs/[programId]/metrics/route.ts
+++ b/apps/web/app/api/programs/[programId]/metrics/route.ts
@@ -1,5 +1,5 @@
import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { getProgramMetricsQuerySchema } from "@/lib/zod/schemas/programs";
import { prisma } from "@dub/prisma";
diff --git a/apps/web/app/api/programs/[programId]/partners/[partnerId]/route.ts b/apps/web/app/api/programs/[programId]/partners/[partnerId]/route.ts
index 09665f0b36..27ed0ad8f2 100644
--- a/apps/web/app/api/programs/[programId]/partners/[partnerId]/route.ts
+++ b/apps/web/app/api/programs/[programId]/partners/[partnerId]/route.ts
@@ -1,4 +1,4 @@
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { PartnerSchema } from "@/lib/zod/schemas/partners";
import { prisma } from "@dub/prisma";
diff --git a/apps/web/app/api/programs/[programId]/partners/count/route.ts b/apps/web/app/api/programs/[programId]/partners/count/route.ts
index aef45f857f..812c9b795e 100644
--- a/apps/web/app/api/programs/[programId]/partners/count/route.ts
+++ b/apps/web/app/api/programs/[programId]/partners/count/route.ts
@@ -1,4 +1,4 @@
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { partnersCountQuerySchema } from "@/lib/zod/schemas/partners";
import { prisma } from "@dub/prisma";
diff --git a/apps/web/app/api/programs/[programId]/partners/route.ts b/apps/web/app/api/programs/[programId]/partners/route.ts
index 97d3a9bef4..6e5a57a19e 100644
--- a/apps/web/app/api/programs/[programId]/partners/route.ts
+++ b/apps/web/app/api/programs/[programId]/partners/route.ts
@@ -1,4 +1,4 @@
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import {
EnrolledPartnerSchema,
diff --git a/apps/web/app/api/programs/[programId]/payouts/count/route.ts b/apps/web/app/api/programs/[programId]/payouts/count/route.ts
index 3ba527ac35..1b925d91df 100644
--- a/apps/web/app/api/programs/[programId]/payouts/count/route.ts
+++ b/apps/web/app/api/programs/[programId]/payouts/count/route.ts
@@ -1,5 +1,5 @@
import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { MIN_PAYOUT_AMOUNT } from "@/lib/partners/constants";
import { payoutsCountQuerySchema } from "@/lib/zod/schemas/payouts";
diff --git a/apps/web/app/api/programs/[programId]/payouts/route.ts b/apps/web/app/api/programs/[programId]/payouts/route.ts
index ff30a5265f..e083086d80 100644
--- a/apps/web/app/api/programs/[programId]/payouts/route.ts
+++ b/apps/web/app/api/programs/[programId]/payouts/route.ts
@@ -1,5 +1,5 @@
import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import {
PayoutResponseSchema,
diff --git a/apps/web/app/api/programs/[programId]/route.ts b/apps/web/app/api/programs/[programId]/route.ts
index 186b9ba364..c7424579e5 100644
--- a/apps/web/app/api/programs/[programId]/route.ts
+++ b/apps/web/app/api/programs/[programId]/route.ts
@@ -1,4 +1,4 @@
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { NextResponse } from "next/server";
diff --git a/apps/web/app/api/programs/[programId]/sales/amount/route.ts b/apps/web/app/api/programs/[programId]/sales/amount/route.ts
index ab787d7909..bf1a453efe 100644
--- a/apps/web/app/api/programs/[programId]/sales/amount/route.ts
+++ b/apps/web/app/api/programs/[programId]/sales/amount/route.ts
@@ -1,5 +1,5 @@
import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { getSalesAmountQuerySchema } from "@/lib/zod/schemas/partners";
import { prisma } from "@dub/prisma";
diff --git a/apps/web/app/api/programs/[programId]/sales/count/route.ts b/apps/web/app/api/programs/[programId]/sales/count/route.ts
index b4180d7dae..9b7882ed6e 100644
--- a/apps/web/app/api/programs/[programId]/sales/count/route.ts
+++ b/apps/web/app/api/programs/[programId]/sales/count/route.ts
@@ -1,5 +1,5 @@
import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { getSalesCountQuerySchema } from "@/lib/zod/schemas/partners";
import { prisma } from "@dub/prisma";
diff --git a/apps/web/app/api/programs/[programId]/sales/route.ts b/apps/web/app/api/programs/[programId]/sales/route.ts
index 4c0854f4d2..2ecf16fdbc 100644
--- a/apps/web/app/api/programs/[programId]/sales/route.ts
+++ b/apps/web/app/api/programs/[programId]/sales/route.ts
@@ -1,5 +1,5 @@
import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
-import { getProgramOrThrow } from "@/lib/api/programs/get-program";
+import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import {
getSalesQuerySchema,
diff --git a/apps/web/app/api/stripe/integration/webhook/checkout-session-completed.ts b/apps/web/app/api/stripe/integration/webhook/checkout-session-completed.ts
index 828e5e4c33..0e8cab9fd6 100644
--- a/apps/web/app/api/stripe/integration/webhook/checkout-session-completed.ts
+++ b/apps/web/app/api/stripe/integration/webhook/checkout-session-completed.ts
@@ -1,5 +1,5 @@
import { notifyPartnerSale } from "@/lib/api/partners/notify-partner-sale";
-import { createSaleData } from "@/lib/api/sales/sale";
+import { createSaleData } from "@/lib/api/sales/create-sale-data";
import { getLeadEvent, recordSale } from "@/lib/tinybird";
import { redis } from "@/lib/upstash";
import { sendWorkspaceWebhook } from "@/lib/webhook/publish";
@@ -129,32 +129,36 @@ export async function checkoutSessionCompleted(event: Stripe.Event) {
// for program links
if (link.programId) {
- const { program, partner } =
+ const { program, partnerId, commissionAmount } =
await prisma.programEnrollment.findUniqueOrThrow({
where: {
linkId: link.id,
},
select: {
program: true,
- partner: {
- select: {
- id: true,
- },
- },
+ partnerId: true,
+ commissionAmount: true,
},
});
const saleRecord = createSaleData({
- customerId: saleData.customer_id,
- linkId: saleData.link_id,
- clickId: saleData.click_id,
- invoiceId: saleData.invoice_id,
- eventId: saleData.event_id,
- paymentProcessor: saleData.payment_processor,
- amount: saleData.amount,
- currency: saleData.currency,
- partnerId: partner.id,
program,
+ partner: {
+ id: partnerId,
+ commissionAmount,
+ },
+ customer: {
+ id: saleData.customer_id,
+ linkId: saleData.link_id,
+ clickId: saleData.click_id,
+ },
+ sale: {
+ amount: saleData.amount,
+ currency: saleData.currency,
+ invoiceId: saleData.invoice_id,
+ eventId: saleData.event_id,
+ paymentProcessor: saleData.payment_processor,
+ },
metadata: {
...leadEvent.data[0],
stripeMetadata: charge,
@@ -168,7 +172,7 @@ export async function checkoutSessionCompleted(event: Stripe.Event) {
waitUntil(
notifyPartnerSale({
partner: {
- id: partner.id,
+ id: partnerId,
referralLink: link.shortLink,
},
program,
diff --git a/apps/web/app/api/stripe/integration/webhook/invoice-paid.ts b/apps/web/app/api/stripe/integration/webhook/invoice-paid.ts
index d2809e6f18..8f3b12eb21 100644
--- a/apps/web/app/api/stripe/integration/webhook/invoice-paid.ts
+++ b/apps/web/app/api/stripe/integration/webhook/invoice-paid.ts
@@ -1,5 +1,5 @@
import { notifyPartnerSale } from "@/lib/api/partners/notify-partner-sale";
-import { createSaleData } from "@/lib/api/sales/sale";
+import { createSaleData } from "@/lib/api/sales/create-sale-data";
import { getLeadEvent, recordSale } from "@/lib/tinybird";
import { redis } from "@/lib/upstash";
import { sendWorkspaceWebhook } from "@/lib/webhook/publish";
@@ -111,32 +111,36 @@ export async function invoicePaid(event: Stripe.Event) {
// for program links
if (link.programId) {
- const { program, partner } =
+ const { program, partnerId, commissionAmount } =
await prisma.programEnrollment.findUniqueOrThrow({
where: {
linkId: link.id,
},
select: {
program: true,
- partner: {
- select: {
- id: true,
- },
- },
+ partnerId: true,
+ commissionAmount: true,
},
});
const saleRecord = createSaleData({
- customerId: saleData.customer_id,
- linkId: saleData.link_id,
- clickId: saleData.click_id,
- invoiceId: saleData.invoice_id,
- eventId: saleData.event_id,
- paymentProcessor: saleData.payment_processor,
- amount: saleData.amount,
- currency: saleData.currency,
- partnerId: partner.id,
program,
+ partner: {
+ id: partnerId,
+ commissionAmount,
+ },
+ customer: {
+ id: saleData.customer_id,
+ linkId: saleData.link_id,
+ clickId: saleData.click_id,
+ },
+ sale: {
+ invoiceId: saleData.invoice_id,
+ eventId: saleData.event_id,
+ paymentProcessor: saleData.payment_processor,
+ amount: saleData.amount,
+ currency: saleData.currency,
+ },
metadata: {
...leadEvent.data[0],
stripeMetadata: invoice,
@@ -150,7 +154,7 @@ export async function invoicePaid(event: Stripe.Event) {
waitUntil(
notifyPartnerSale({
partner: {
- id: partner.id,
+ id: partnerId,
referralLink: link.shortLink,
},
program,
diff --git a/apps/web/app/api/track/sale/route.ts b/apps/web/app/api/track/sale/route.ts
index e1f8fd9b79..3c9fd83b6c 100644
--- a/apps/web/app/api/track/sale/route.ts
+++ b/apps/web/app/api/track/sale/route.ts
@@ -1,6 +1,6 @@
import { DubApiError } from "@/lib/api/errors";
import { notifyPartnerSale } from "@/lib/api/partners/notify-partner-sale";
-import { createSaleData } from "@/lib/api/sales/sale";
+import { createSaleData } from "@/lib/api/sales/create-sale-data";
import { parseRequestBody } from "@/lib/api/utils";
import { withWorkspaceEdge } from "@/lib/auth/workspace-edge";
import { getLeadEvent, recordSale } from "@/lib/tinybird";
@@ -121,32 +121,36 @@ export const POST = withWorkspaceEdge(
// for program links
if (link.programId) {
- const { program, partner } =
+ const { program, partnerId, commissionAmount } =
await prismaEdge.programEnrollment.findUniqueOrThrow({
where: {
linkId: link.id,
},
select: {
program: true,
- partner: {
- select: {
- id: true,
- },
- },
+ partnerId: true,
+ commissionAmount: true,
},
});
const saleRecord = createSaleData({
- customerId: customer.id,
- linkId: link.id,
- clickId: clickData.click_id,
- invoiceId,
- eventId,
- paymentProcessor,
- amount,
- currency,
- partnerId: partner.id,
program,
+ partner: {
+ id: partnerId,
+ commissionAmount,
+ },
+ customer: {
+ id: customer.id,
+ linkId: link.id,
+ clickId: clickData.click_id,
+ },
+ sale: {
+ amount,
+ currency,
+ invoiceId,
+ eventId,
+ paymentProcessor,
+ },
metadata: clickData,
});
@@ -156,7 +160,7 @@ export const POST = withWorkspaceEdge(
}),
notifyPartnerSale({
partner: {
- id: partner.id,
+ id: partnerId,
referralLink: link.shortLink,
},
program,
diff --git a/apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts b/apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts
index e1a0b05063..f88e00c313 100644
--- a/apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts
+++ b/apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts
@@ -46,6 +46,8 @@ export const POST = withWorkspace(async ({ req, workspace, session }) => {
});
return NextResponse.json({ url });
} else {
+ // const customer = await getDubCustomer(session.user.id);
+
// For both new users and users with canceled subscriptions
const stripeSession = await stripe.checkout.sessions.create({
...(workspace.stripeId
@@ -64,7 +66,18 @@ export const POST = withWorkspace(async ({ req, workspace, session }) => {
success_url: `${APP_DOMAIN}/${workspace.slug}?${onboarding ? "onboarded" : "upgraded"}=true&plan=${plan}&period=${period}`,
cancel_url: baseUrl,
line_items: [{ price: prices.data[0].id, quantity: 1 }],
- allow_promotion_codes: true,
+ ...(false
+ ? {
+ discounts: [
+ {
+ coupon:
+ process.env.NODE_ENV === "production"
+ ? "pEVpzGQE"
+ : "k8v8KtqG",
+ },
+ ],
+ }
+ : { allow_promotion_codes: true }),
automatic_tax: {
enabled: true,
},
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/overview-chart.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/overview-chart.tsx
index 5571167f26..8fdeaa5375 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/overview-chart.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/overview-chart.tsx
@@ -1,3 +1,4 @@
+import { formatDateTooltip } from "@/lib/analytics/format-date-tooltip";
import { IntervalOptions } from "@/lib/analytics/types";
import useProgramAnalytics from "@/lib/swr/use-program-analytics";
import useProgramMetrics from "@/lib/swr/use-program-metrics";
@@ -11,7 +12,7 @@ import {
YAxis,
} from "@dub/ui/charts";
import { LoadingSpinner } from "@dub/ui/icons";
-import { currencyFormatter, formatDate } from "@dub/utils";
+import { currencyFormatter } from "@dub/utils";
import NumberFlow from "@number-flow/react";
import { LinearGradient } from "@visx/gradient";
import { useId, useMemo } from "react";
@@ -97,7 +98,7 @@ export function OverviewChart() {
return (
<>
- {formatDate(d.date)}
+ {formatDateTooltip(d.date, { interval, start, end })}
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/page-client.tsx
index ee9340f6f1..2f0d21a068 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/page-client.tsx
@@ -48,6 +48,7 @@ export default function ProgramOverviewPageClient() {
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/sales/sale-table.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/sales/sale-table.tsx
index ee4baf60d8..398d98b7b1 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/sales/sale-table.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/sales/sale-table.tsx
@@ -24,6 +24,7 @@ import {
DICEBEAR_AVATAR_URL,
fetcher,
formatDate,
+ formatDateTime,
} from "@dub/utils";
import { useParams } from "next/navigation";
import { memo } from "react";
@@ -73,7 +74,13 @@ const SaleTableBusinessInner = memo(
{
id: "createdAt",
header: "Date",
- accessorFn: (d) => formatDate(d.createdAt, { month: "short" }),
+ cell: ({ row }) => (
+
+ {formatDate(row.original.createdAt, {
+ month: "short",
+ })}
+
+ ),
},
{
header: "Customer",
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/settings/program-settings.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/settings/program-settings.tsx
index 6376994a5a..adaadb0ab9 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/settings/program-settings.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/settings/program-settings.tsx
@@ -8,7 +8,7 @@ import { EmbedDocsSheet } from "@/ui/partners/embed-docs-sheet";
import { ProgramCommissionDescription } from "@/ui/partners/program-commission-description";
import { AnimatedSizeContainer, Button } from "@dub/ui";
import { CircleCheckFill, Code, LoadingSpinner } from "@dub/ui/icons";
-import { cn, pluralize } from "@dub/utils";
+import { cn, INFINITY_NUMBER, pluralize } from "@dub/utils";
import { useAction } from "next-safe-action/hooks";
import { useState } from "react";
import {
@@ -52,11 +52,10 @@ export function ProgramSettings() {
type FormData = Pick<
ProgramProps,
- | "recurringCommission"
- | "recurringDuration"
- | "isLifetimeRecurring"
- | "commissionType"
| "commissionAmount"
+ | "commissionType"
+ | "commissionDuration"
+ | "commissionInterval"
>;
function ProgramSettingsForm({ program }: { program: ProgramProps }) {
@@ -66,10 +65,7 @@ function ProgramSettingsForm({ program }: { program: ProgramProps }) {
const form = useForm
({
mode: "onBlur",
defaultValues: {
- recurringCommission: program.recurringCommission,
- recurringDuration: program.recurringDuration,
- isLifetimeRecurring: program.isLifetimeRecurring,
- commissionType: program.commissionType,
+ ...program,
commissionAmount:
program.commissionType === "flat"
? program.commissionAmount / 100
@@ -87,15 +83,15 @@ function ProgramSettingsForm({ program }: { program: ProgramProps }) {
} = form;
const [
- recurringCommission,
- recurringDuration,
- isLifetimeRecurring,
+ commissionAmount,
commissionType,
+ commissionDuration,
+ commissionInterval,
] = watch([
- "recurringCommission",
- "recurringDuration",
- "isLifetimeRecurring",
+ "commissionAmount",
"commissionType",
+ "commissionDuration",
+ "commissionInterval",
]);
const { executeAsync } = useAction(updateProgramAction, {
@@ -133,7 +129,7 @@ function ProgramSettingsForm({ program }: { program: ProgramProps }) {
Program