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