From 3b3a62ee94580be820a03fd553c9c6d942bb072d Mon Sep 17 00:00:00 2001 From: Bharath Lakshman Kumar Date: Wed, 16 Apr 2025 15:27:15 +0530 Subject: [PATCH 01/12] fix: filter out HEAD requests from click tracking to prevent inflated metrics --- apps/web/lib/tinybird/record-click.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/web/lib/tinybird/record-click.ts b/apps/web/lib/tinybird/record-click.ts index 4b0863b4296..ef4c10566ed 100644 --- a/apps/web/lib/tinybird/record-click.ts +++ b/apps/web/lib/tinybird/record-click.ts @@ -63,6 +63,10 @@ export async function recordClick({ return null; } + if (req.method === "HEAD") { + return null; + } + const isBot = detectBot(req); // don't record clicks from bots From bb7512a637ca2fa926e04a9292bfc8875a8f65dc Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 17 Apr 2025 07:38:55 -0700 Subject: [PATCH 02/12] Revert link cache bug --- apps/web/lib/api/links/cache.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/lib/api/links/cache.ts b/apps/web/lib/api/links/cache.ts index 422eff1937c..f6b63dbd7fc 100644 --- a/apps/web/lib/api/links/cache.ts +++ b/apps/web/lib/api/links/cache.ts @@ -51,7 +51,9 @@ class LinkCache { } async get({ domain, key }: Pick) { - return await redis.get(this._createKey({ domain, key })); + // here we use linkcache:${domain}:${key} instead of this._createKey({ domain, key }) + // because the key can either be case-sensitive or case-insensitive depending on the domain + return await redis.get(`linkcache:${domain}:${key}`); } async delete({ domain, key }: Pick) { From e565deac59e299b3c06c94483e7c3dfff0985b6b Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 17 Apr 2025 07:48:08 -0700 Subject: [PATCH 03/12] fix build blocker --- apps/web/app/api/cron/import/csv/route.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/app/api/cron/import/csv/route.ts b/apps/web/app/api/cron/import/csv/route.ts index d6d16540d16..57e0b2f04ec 100644 --- a/apps/web/app/api/cron/import/csv/route.ts +++ b/apps/web/app/api/cron/import/csv/route.ts @@ -87,10 +87,11 @@ export async function POST(req: Request) { await redis.incrby(`${redisKey}:processed`, rows.length); if (rows.length === BATCH_SIZE) { - return await qstash.publishJSON({ + const response = await qstash.publishJSON({ url: `${APP_DOMAIN_WITH_NGROK}/api/cron/import/csv`, body: payload, }); + return NextResponse.json(response); } } From b069b5e1ee2410958481d512d64e2a9e4578b219 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 17 Apr 2025 08:21:35 -0700 Subject: [PATCH 04/12] temp fix hide profile --- .../customers/[customerId]/activity/route.ts | 7 +++++++ .../[programId]/customers/[customerId]/route.ts | 7 +++++++ .../(enrolled)/customers/[customerId]/page.tsx | 10 +++++++++- apps/web/lib/api/links/cache.ts | 3 ++- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/activity/route.ts b/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/activity/route.ts index 8e625d818b5..16790085889 100644 --- a/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/activity/route.ts +++ b/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/activity/route.ts @@ -16,6 +16,13 @@ export const GET = withPartnerProfile(async ({ partner, params }) => { programId: programId, }); + if (program.slug === "framer") { + throw new DubApiError({ + code: "forbidden", + message: "Framer program does not support customer activity", + }); + } + const customer = await prisma.customer.findUnique({ where: { id: customerId, diff --git a/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts b/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts index 8278f79f785..39b2e6ba44d 100644 --- a/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts +++ b/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts @@ -16,6 +16,13 @@ export const GET = withPartnerProfile(async ({ partner, params }) => { programId: programId, }); + if (program.slug === "framer") { + throw new DubApiError({ + code: "forbidden", + message: "Framer program does not support customer profile", + }); + } + const customer = await prisma.customer.findUnique({ where: { id: customerId, diff --git a/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page.tsx b/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page.tsx index 4982498f9dc..67a7a9e209d 100644 --- a/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page.tsx +++ b/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page.tsx @@ -1,8 +1,16 @@ import { PageContent } from "@/ui/layout/page-content"; import { MaxWidthWrapper } from "@dub/ui"; +import { redirect } from "next/navigation"; import { ProgramCustomerPageClient } from "./page-client"; -export default function ProgramCustomer() { +export default function ProgramCustomer({ + params, +}: { + params: { programSlug: string; customerId: string }; +}) { + if (params.programSlug === "framer") { + redirect("/programs/framer"); + } return ( diff --git a/apps/web/lib/api/links/cache.ts b/apps/web/lib/api/links/cache.ts index f6b63dbd7fc..97f73571f34 100644 --- a/apps/web/lib/api/links/cache.ts +++ b/apps/web/lib/api/links/cache.ts @@ -52,7 +52,8 @@ class LinkCache { async get({ domain, key }: Pick) { // here we use linkcache:${domain}:${key} instead of this._createKey({ domain, key }) - // because the key can either be case-sensitive or case-insensitive depending on the domain + // because the key can either be cached as case-sensitive or case-insensitive depending on the domain + // so we should get the original key from the cache return await redis.get(`linkcache:${domain}:${key}`); } From 3f55f714e5f5cfe919de1b0beb5d9b4412b2b31c Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Thu, 17 Apr 2025 11:44:07 -0400 Subject: [PATCH 05/12] `@dub/ui` updates --- packages/ui/src/dub-status-badge.tsx | 68 ++++++++++++++++++++ packages/ui/src/footer.tsx | 66 +------------------ packages/ui/src/icons/nucleo/index.ts | 2 + packages/ui/src/icons/nucleo/msgs.tsx | 32 +++++++++ packages/ui/src/icons/nucleo/shield-user.tsx | 42 ++++++++++++ packages/ui/src/index.tsx | 1 + 6 files changed, 148 insertions(+), 63 deletions(-) create mode 100644 packages/ui/src/dub-status-badge.tsx create mode 100644 packages/ui/src/icons/nucleo/msgs.tsx create mode 100644 packages/ui/src/icons/nucleo/shield-user.tsx diff --git a/packages/ui/src/dub-status-badge.tsx b/packages/ui/src/dub-status-badge.tsx new file mode 100644 index 00000000000..8967ed0259e --- /dev/null +++ b/packages/ui/src/dub-status-badge.tsx @@ -0,0 +1,68 @@ +import { cn } from "@dub/utils"; + +import { fetcher } from "@dub/utils"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import useSWR from "swr"; + +export function DubStatusBadge({ className }: { className?: string }) { + const { data } = useSWR<{ + ongoing_incidents: { + name: string; + current_worst_impact: + | "degraded_performance" + | "partial_outage" + | "full_outage"; + }[]; + }>("https://status.dub.co/api/v1/summary", fetcher); + + const [color, setColor] = useState("bg-neutral-200"); + const [status, setStatus] = useState("Loading status..."); + + useEffect(() => { + if (!data) return; + const { ongoing_incidents } = data; + if (ongoing_incidents.length > 0) { + const { current_worst_impact, name } = ongoing_incidents[0]; + const color = + current_worst_impact === "degraded_performance" + ? "bg-yellow-500" + : "bg-red-500"; + setStatus(name); + setColor(color); + } else { + setStatus("All systems operational"); + setColor("bg-green-500"); + } + }, [data]); + + return ( + +
+
+
+
+

+ {status} +

+ + ); +} diff --git a/packages/ui/src/footer.tsx b/packages/ui/src/footer.tsx index fe6bfb6360f..888644b1924 100644 --- a/packages/ui/src/footer.tsx +++ b/packages/ui/src/footer.tsx @@ -1,12 +1,11 @@ "use client"; -import { ALL_TOOLS, cn, createHref, fetcher } from "@dub/utils"; +import { ALL_TOOLS, cn, createHref } from "@dub/utils"; import Image from "next/image"; import Link from "next/link"; import { useParams } from "next/navigation"; -import { useEffect, useState } from "react"; -import useSWR from "swr"; import { COMPARE_PAGES, FEATURES_LIST, LEGAL_PAGES } from "./content"; +import { DubStatusBadge } from "./dub-status-badge"; import { Github, LinkedIn, ReferredVia, Twitter, YouTube } from "./icons"; import { MaxWidthWrapper } from "./max-width-wrapper"; import { NavWordmark } from "./nav-wordmark"; @@ -277,7 +276,7 @@ export function Footer({ {/* Bottom row (status, SOC2, copyright) */}
- + ); } - -function StatusBadge() { - const { data } = useSWR<{ - ongoing_incidents: { - name: string; - current_worst_impact: - | "degraded_performance" - | "partial_outage" - | "full_outage"; - }[]; - }>("https://status.dub.co/api/v1/summary", fetcher); - - const [color, setColor] = useState("bg-neutral-200"); - const [status, setStatus] = useState("Loading status..."); - - useEffect(() => { - if (!data) return; - const { ongoing_incidents } = data; - if (ongoing_incidents.length > 0) { - const { current_worst_impact, name } = ongoing_incidents[0]; - const color = - current_worst_impact === "degraded_performance" - ? "bg-yellow-500" - : "bg-red-500"; - setStatus(name); - setColor(color); - } else { - setStatus("All systems operational"); - setColor("bg-green-500"); - } - }, [data]); - - return ( - -
-
-
-
-

- {status} -

- - ); -} diff --git a/packages/ui/src/icons/nucleo/index.ts b/packages/ui/src/icons/nucleo/index.ts index 93ee87d798b..a574ad8571e 100644 --- a/packages/ui/src/icons/nucleo/index.ts +++ b/packages/ui/src/icons/nucleo/index.ts @@ -142,6 +142,7 @@ export * from "./mobile-phone"; export * from "./money-bill"; export * from "./money-bill2"; export * from "./money-bills2"; +export * from "./msgs"; export * from "./note"; export * from "./office-building"; export * from "./page2"; @@ -168,6 +169,7 @@ export * from "./shield-alert"; export * from "./shield-check"; export * from "./shield-keyhole"; export * from "./shield-slash"; +export * from "./shield-user"; export * from "./shuffle"; export * from "./sliders"; export * from "./sparkle3"; diff --git a/packages/ui/src/icons/nucleo/msgs.tsx b/packages/ui/src/icons/nucleo/msgs.tsx new file mode 100644 index 00000000000..6a1e1e92778 --- /dev/null +++ b/packages/ui/src/icons/nucleo/msgs.tsx @@ -0,0 +1,32 @@ +import { SVGProps } from "react"; + +export function Msgs(props: SVGProps) { + return ( + + + + + + + ); +} diff --git a/packages/ui/src/icons/nucleo/shield-user.tsx b/packages/ui/src/icons/nucleo/shield-user.tsx new file mode 100644 index 00000000000..9b753a8ea2e --- /dev/null +++ b/packages/ui/src/icons/nucleo/shield-user.tsx @@ -0,0 +1,42 @@ +import { SVGProps } from "react"; + +export function ShieldUser(props: SVGProps) { + return ( + + + + + + + + ); +} diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index 9e61a28e158..03fc87282ec 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -12,6 +12,7 @@ export * from "./carousel"; export * from "./checkbox"; export * from "./combobox"; export * from "./date-picker"; +export * from "./dub-status-badge"; export * from "./empty-state"; export * from "./file-upload"; export * from "./filter"; From 45d6dbb4022c559070bf0c0248553d65904289d3 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 17 Apr 2025 09:43:25 -0700 Subject: [PATCH 06/12] Add case-sensitivity E2E tests --- apps/web/lib/middleware/utils/parse.ts | 15 ++++++++++----- apps/web/tests/redirects/index.test.ts | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/web/lib/middleware/utils/parse.ts b/apps/web/lib/middleware/utils/parse.ts index 3aac8cf850d..e5c1b079080 100644 --- a/apps/web/lib/middleware/utils/parse.ts +++ b/apps/web/lib/middleware/utils/parse.ts @@ -3,16 +3,21 @@ import { NextRequest } from "next/server"; export const parse = (req: NextRequest) => { let domain = req.headers.get("host") as string; + // path is the path of the URL (e.g. dub.sh/stats/github -> /stats/github) + let path = req.nextUrl.pathname; + // remove www. from domain and convert to lowercase domain = domain.replace(/^www./, "").toLowerCase(); if (domain === "dub.localhost:8888" || domain.endsWith(".vercel.app")) { - // for local development and preview URLs - domain = SHORT_DOMAIN; + if (path === "cAsE") { + // special case for case-sensitive link test + domain = "dub-internal-test.com"; + } else { + // for local development and preview URLs + domain = SHORT_DOMAIN; + } } - // path is the path of the URL (e.g. dub.sh/stats/github -> /stats/github) - let path = req.nextUrl.pathname; - // fullPath is the full URL path (along with search params) const searchParams = req.nextUrl.searchParams.toString(); const searchParamsObj = Object.fromEntries(req.nextUrl.searchParams); diff --git a/apps/web/tests/redirects/index.test.ts b/apps/web/tests/redirects/index.test.ts index 9d4a90511bd..36e977c8170 100644 --- a/apps/web/tests/redirects/index.test.ts +++ b/apps/web/tests/redirects/index.test.ts @@ -108,6 +108,24 @@ describe.runIf(env.CI)("Link Redirects", async () => { expect(response.status).toBe(302); }); + test("with case-sensitive (correct) key", async () => { + const response = await fetch(`${h.baseUrl}/cAsE`, fetchOptions); + + expect(response.headers.get("location")).toBe( + "https://dub.co/changelog/case-insensitive-links", + ); + expect(response.headers.get("x-powered-by")).toBe(poweredBy); + expect(response.status).toBe(302); + }); + + test("with case-sensitive (incorrect) key", async () => { + const response = await fetch(`${h.baseUrl}/case`, fetchOptions); + + expect(response.headers.get("location")).toBe("https://dub.co"); + expect(response.headers.get("x-powered-by")).toBe(poweredBy); + expect(response.status).toBe(302); + }); + test("with password", async () => { const response = await fetch( `${h.baseUrl}/password/check?pw=dub`, From 635dbaec1ba02fa7c190154a4d9604774c70cfb9 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 17 Apr 2025 09:43:47 -0700 Subject: [PATCH 07/12] missed a spot --- apps/web/lib/middleware/utils/parse.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/lib/middleware/utils/parse.ts b/apps/web/lib/middleware/utils/parse.ts index e5c1b079080..1097b1043c9 100644 --- a/apps/web/lib/middleware/utils/parse.ts +++ b/apps/web/lib/middleware/utils/parse.ts @@ -9,7 +9,7 @@ export const parse = (req: NextRequest) => { // remove www. from domain and convert to lowercase domain = domain.replace(/^www./, "").toLowerCase(); if (domain === "dub.localhost:8888" || domain.endsWith(".vercel.app")) { - if (path === "cAsE") { + if (path === "/cAsE") { // special case for case-sensitive link test domain = "dub-internal-test.com"; } else { From a41680488d1af6c63a522848e70edcf42b9748ea Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 17 Apr 2025 10:00:53 -0700 Subject: [PATCH 08/12] fix test --- apps/web/tests/redirects/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/tests/redirects/index.test.ts b/apps/web/tests/redirects/index.test.ts index 36e977c8170..c1410944ed6 100644 --- a/apps/web/tests/redirects/index.test.ts +++ b/apps/web/tests/redirects/index.test.ts @@ -121,7 +121,7 @@ describe.runIf(env.CI)("Link Redirects", async () => { test("with case-sensitive (incorrect) key", async () => { const response = await fetch(`${h.baseUrl}/case`, fetchOptions); - expect(response.headers.get("location")).toBe("https://dub.co"); + expect(response.headers.get("location")).toBe("https://dub.co/"); expect(response.headers.get("x-powered-by")).toBe(poweredBy); expect(response.status).toBe(302); }); From c5a9308eebc8d5fd2edca28edae7bf4ea67a6d0d Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 17 Apr 2025 10:03:06 -0700 Subject: [PATCH 09/12] make tests more resilient --- apps/web/lib/middleware/utils/parse.ts | 2 +- apps/web/tests/redirects/index.test.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/web/lib/middleware/utils/parse.ts b/apps/web/lib/middleware/utils/parse.ts index 1097b1043c9..2cafc5b7441 100644 --- a/apps/web/lib/middleware/utils/parse.ts +++ b/apps/web/lib/middleware/utils/parse.ts @@ -9,7 +9,7 @@ export const parse = (req: NextRequest) => { // remove www. from domain and convert to lowercase domain = domain.replace(/^www./, "").toLowerCase(); if (domain === "dub.localhost:8888" || domain.endsWith(".vercel.app")) { - if (path === "/cAsE") { + if (path.toLowerCase() === "/case-sensitive-test") { // special case for case-sensitive link test domain = "dub-internal-test.com"; } else { diff --git a/apps/web/tests/redirects/index.test.ts b/apps/web/tests/redirects/index.test.ts index c1410944ed6..bc1dea07f7b 100644 --- a/apps/web/tests/redirects/index.test.ts +++ b/apps/web/tests/redirects/index.test.ts @@ -109,7 +109,10 @@ describe.runIf(env.CI)("Link Redirects", async () => { }); test("with case-sensitive (correct) key", async () => { - const response = await fetch(`${h.baseUrl}/cAsE`, fetchOptions); + const response = await fetch( + `${h.baseUrl}/cAsE-sensitive-test`, + fetchOptions, + ); expect(response.headers.get("location")).toBe( "https://dub.co/changelog/case-insensitive-links", @@ -119,7 +122,10 @@ describe.runIf(env.CI)("Link Redirects", async () => { }); test("with case-sensitive (incorrect) key", async () => { - const response = await fetch(`${h.baseUrl}/case`, fetchOptions); + const response = await fetch( + `${h.baseUrl}/case-sensitive-test`, + fetchOptions, + ); expect(response.headers.get("location")).toBe("https://dub.co/"); expect(response.headers.get("x-powered-by")).toBe(poweredBy); From b8ea02709e9d4840dbaf063442bd889ebaf71616 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 17 Apr 2025 10:05:30 -0700 Subject: [PATCH 10/12] add comment --- apps/web/lib/tinybird/record-click.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/lib/tinybird/record-click.ts b/apps/web/lib/tinybird/record-click.ts index ef4c10566ed..9c3de151bd6 100644 --- a/apps/web/lib/tinybird/record-click.ts +++ b/apps/web/lib/tinybird/record-click.ts @@ -63,6 +63,7 @@ export async function recordClick({ return null; } + // don't track HEAD requests to avoid non-user traffic from inflating click count if (req.method === "HEAD") { return null; } From 969ff14c6cd3825e61e392d526a8d6c7917f900d Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 17 Apr 2025 10:29:36 -0700 Subject: [PATCH 11/12] add customer avatars to partner earnings table --- .../[programId]/commissions/commission-table.tsx | 13 +++++-------- .../customers/[customerId]/page-client.tsx | 16 +++++++++++----- .../(enrolled)/earnings/earnings-table.tsx | 12 ++++++++++-- apps/web/lib/zod/schemas/partner-profile.ts | 1 - apps/web/ui/customers/customer-sales-table.tsx | 9 +++------ packages/ui/package.json | 2 +- 6 files changed, 30 insertions(+), 23 deletions(-) diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/commissions/commission-table.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/commissions/commission-table.tsx index 51a41a935e0..55a9ab331b8 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/commissions/commission-table.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/programs/[programId]/commissions/commission-table.tsx @@ -90,12 +90,8 @@ const CommissionTableInner = memo( }, { header: "Customer", - cell: ({ row }) => { - if (!row.original.customer) { - return "-"; - } - - return ( + cell: ({ row }) => + row.original.customer ? (
- ); - }, + ) : ( + "-" + ), meta: { filterParams: ({ row }) => row.original.customer diff --git a/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx b/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx index 6bfc78c5e57..bf61f6c2ad8 100644 --- a/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx +++ b/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx @@ -11,8 +11,8 @@ import { CustomerDetailsColumn } from "@/ui/customers/customer-details-column"; import { CustomerSalesTable } from "@/ui/customers/customer-sales-table"; import { ProgramRewardList } from "@/ui/partners/program-reward-list"; import { BackLink } from "@/ui/shared/back-link"; -import { MoneyBill2, Tooltip, User } from "@dub/ui"; -import { fetcher } from "@dub/utils"; +import { MoneyBill2, Tooltip } from "@dub/ui"; +import { fetcher, OG_AVATAR_URL } from "@dub/utils"; import { notFound, useParams } from "next/navigation"; import { memo } from "react"; import useSWR from "swr"; @@ -46,9 +46,15 @@ export function ProgramCustomerPageClient() {
Earnings
-
- -
+ {customer ? ( + {customer.email + ) : ( +
+ )}
{customer ? ( diff --git a/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx b/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx index 512a5b6cfb7..538e94c5cdb 100644 --- a/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx +++ b/apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx @@ -24,6 +24,7 @@ import { formatDateTimeSmart, getApexDomain, getPrettyUrl, + OG_AVATAR_URL, } from "@dub/utils"; import { Cell } from "@tanstack/react-table"; import Link from "next/link"; @@ -130,8 +131,15 @@ export function EarningsTablePartner({ limit }: { limit?: number }) { scroll={false} className="flex w-full items-center justify-between gap-2 px-4 py-2.5 transition-colors hover:bg-stone-100" > -
- {row.original.customer.email} +
+ {row.original.customer.email} +
+ {row.original.customer.email} +
diff --git a/apps/web/lib/zod/schemas/partner-profile.ts b/apps/web/lib/zod/schemas/partner-profile.ts index 478bcff1a1e..3d63f55a405 100644 --- a/apps/web/lib/zod/schemas/partner-profile.ts +++ b/apps/web/lib/zod/schemas/partner-profile.ts @@ -21,7 +21,6 @@ export const PartnerEarningsSchema = CommissionSchema.merge( email: z .string() .transform((email) => email.replace(/(?<=^.).+(?=.@)/, "********")), - avatar: z.string().nullable(), }) .nullable(), link: LinkSchema.pick({ diff --git a/apps/web/ui/customers/customer-sales-table.tsx b/apps/web/ui/customers/customer-sales-table.tsx index 4ee69161466..0a7d017124f 100644 --- a/apps/web/ui/customers/customer-sales-table.tsx +++ b/apps/web/ui/customers/customer-sales-table.tsx @@ -1,4 +1,4 @@ -import { PartnerEarningsResponse, SaleEvent } from "@/lib/types"; +import { CommissionResponse, SaleEvent } from "@/lib/types"; import { StatusBadge } from "@dub/ui"; import { currencyFormatter, formatDateTimeSmart } from "@dub/utils"; import { @@ -18,7 +18,7 @@ export function CustomerSalesTable({ sales?: | Pick[] | Pick< - PartnerEarningsResponse, + CommissionResponse, "createdAt" | "amount" | "earnings" | "status" >[]; totalSales?: number; @@ -27,10 +27,7 @@ export function CustomerSalesTable({ }) { const table = useReactTable< | Pick - | Pick< - PartnerEarningsResponse, - "createdAt" | "amount" | "earnings" | "status" - > + | Pick >({ data: sales || [], columns: [ diff --git a/packages/ui/package.json b/packages/ui/package.json index 8e25526634e..ec5aea02f0d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { "name": "@dub/ui", "description": "UI components for Dub.co", - "version": "0.2.31", + "version": "0.2.32", "sideEffects": false, "main": "./dist/index.js", "module": "./dist/index.mjs", From 96ea14da84ce1f17f167bfbbb18c93cb6684f68e Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 17 Apr 2025 11:04:54 -0700 Subject: [PATCH 12/12] Add referrer URL to customer activity list --- .../[programId]/earnings/count/route.ts | 2 +- apps/web/lib/zod/schemas/partner-profile.ts | 4 +- .../ui/customers/customer-activity-list.tsx | 56 ++++++++++++++----- .../ui/customers/customer-details-column.tsx | 12 ++-- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/apps/web/app/api/partner-profile/programs/[programId]/earnings/count/route.ts b/apps/web/app/api/partner-profile/programs/[programId]/earnings/count/route.ts index 02a855abaad..2011dbe5fc7 100644 --- a/apps/web/app/api/partner-profile/programs/[programId]/earnings/count/route.ts +++ b/apps/web/app/api/partner-profile/programs/[programId]/earnings/count/route.ts @@ -95,7 +95,7 @@ export const GET = withPartnerProfile( return { id: customerId, email: customer?.email - ? customer.email.replace(/(?<=^.).+(?=.@)/, "********") + ? customer.email.replace(/(?<=^.).+(?=.@)/, "****") : customer?.name || generateRandomName(), _count, }; diff --git a/apps/web/lib/zod/schemas/partner-profile.ts b/apps/web/lib/zod/schemas/partner-profile.ts index 3d63f55a405..391552322b1 100644 --- a/apps/web/lib/zod/schemas/partner-profile.ts +++ b/apps/web/lib/zod/schemas/partner-profile.ts @@ -20,7 +20,7 @@ export const PartnerEarningsSchema = CommissionSchema.merge( id: z.string(), email: z .string() - .transform((email) => email.replace(/(?<=^.).+(?=.@)/, "********")), + .transform((email) => email.replace(/(?<=^.).+(?=.@)/, "****")), }) .nullable(), link: LinkSchema.pick({ @@ -86,5 +86,5 @@ export const PartnerProfileCustomerSchema = CustomerEnrichedSchema.pick({ }).extend({ email: z .string() - .transform((email) => email.replace(/(?<=^.).+(?=.@)/, "********")), + .transform((email) => email.replace(/(?<=^.).+(?=.@)/, "****")), }); diff --git a/apps/web/ui/customers/customer-activity-list.tsx b/apps/web/ui/customers/customer-activity-list.tsx index c3c9ac4c1dc..0434803ab6d 100644 --- a/apps/web/ui/customers/customer-activity-list.tsx +++ b/apps/web/ui/customers/customer-activity-list.tsx @@ -1,5 +1,5 @@ import { CustomerActivityResponse } from "@/lib/types"; -import { LinkLogo } from "@dub/ui"; +import { DynamicTooltipWrapper, LinkLogo } from "@dub/ui"; import { CursorRays, MoneyBill2, UserCheck } from "@dub/ui/icons"; import { formatDateTimeSmart, getApexDomain, getPrettyUrl } from "@dub/utils"; import Link from "next/link"; @@ -10,17 +10,24 @@ const activityData = { icon: CursorRays, content: (event) => { const { slug, programSlug } = useParams(); + + const analyticsBaseUrl = programSlug + ? `/programs/${programSlug}/analytics` + : `/${slug}/analytics`; + const referer = !event.click?.referer || event.click.referer === "(direct)" ? "direct" : event.click.referer; + const refererUrl = event.click.refererUrl; + return ( Found{" "} via - + Referrer URL:{" "} + + {getPrettyUrl(refererUrl)} + +
+ ), + } + : undefined } - target="_blank" - className="flex items-center gap-2 rounded-md bg-neutral-100 px-1.5 py-1 font-mono text-xs leading-none transition-colors hover:bg-neutral-200/80" > - - {referer} - +
+ + + {referer} + +
+ ); }, diff --git a/apps/web/ui/customers/customer-details-column.tsx b/apps/web/ui/customers/customer-details-column.tsx index 02fed1891c7..441eee885dc 100644 --- a/apps/web/ui/customers/customer-details-column.tsx +++ b/apps/web/ui/customers/customer-details-column.tsx @@ -46,7 +46,6 @@ export function CustomerDetailsColumn({
{icon} @@ -173,7 +171,6 @@ export function CustomerDetailsColumn({ href={`/${programSlug ? `programs/${programSlug}` : slug}/analytics?domain=${link.domain}&key=${link.key}`} target="_blank" className="min-w-0 overflow-hidden truncate" - linkClassName="underline-offset-2 hover:text-neutral-950 hover:underline" > {getPrettyUrl(link.shortLink)} @@ -193,7 +190,6 @@ export function CustomerDetailsColumn({ href={`/${programSlug ? `programs/${programSlug}` : slug}/analytics?${key}=${encodeURIComponent(value)}`} target="_blank" className="truncate text-neutral-500" - linkClassName="underline-offset-2 hover:text-neutral-600 hover:underline" > {value} @@ -222,13 +218,15 @@ const ConditionalLink = ({ href, className, children, - linkClassName, ...rest -}: HTMLProps & { linkClassName?: string }) => { +}: HTMLProps) => { return href ? (
{children}