From 6596e3945d394d68e61c5b58a19477a1e7965296 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 30 Sep 2024 12:27:35 +0530
Subject: [PATCH 01/88] create public token
---
apps/web/app/api/referrals/tokens/route.ts | 47 ++++++++++++++++++++++
apps/web/lib/referrals/constants.ts | 4 ++
apps/web/lib/zod/schemas/referrals.ts | 10 +++++
apps/web/prisma/schema/link.prisma | 2 +
apps/web/prisma/schema/referral.prisma | 12 ++++++
5 files changed, 75 insertions(+)
create mode 100644 apps/web/app/api/referrals/tokens/route.ts
create mode 100644 apps/web/lib/zod/schemas/referrals.ts
create mode 100644 apps/web/prisma/schema/referral.prisma
diff --git a/apps/web/app/api/referrals/tokens/route.ts b/apps/web/app/api/referrals/tokens/route.ts
new file mode 100644
index 0000000000..8643ad9046
--- /dev/null
+++ b/apps/web/app/api/referrals/tokens/route.ts
@@ -0,0 +1,47 @@
+import { DubApiError } from "@/lib/api/errors";
+import { parseRequestBody } from "@/lib/api/utils";
+import { withWorkspace } from "@/lib/auth";
+import { prisma } from "@/lib/prisma";
+import {
+ REFERRAL_PUBLIC_TOKEN_EXPIRY,
+ REFERRAL_PUBLIC_TOKEN_LENGTH,
+} from "@/lib/referrals/constants";
+import {
+ createReferralTokenSchema,
+ referralTokenSchema,
+} from "@/lib/zod/schemas/referrals";
+import { nanoid } from "@dub/utils";
+import { NextResponse } from "next/server";
+
+// GET /api/referrals/tokens - create a new referral token for the given link
+export const POST = withWorkspace(async ({ workspace, req }) => {
+ const { linkId } = createReferralTokenSchema.parse(
+ await parseRequestBody(req),
+ );
+
+ const link = await prisma.link.findUniqueOrThrow({
+ where: {
+ id: linkId,
+ projectId: workspace.id,
+ },
+ });
+
+ if (!link.trackConversion) {
+ throw new DubApiError({
+ code: "forbidden",
+ message: "Conversion tracking is not enabled for this link.",
+ });
+ }
+
+ const referralToken = await prisma.referralPublicToken.create({
+ data: {
+ linkId,
+ expires: new Date(Date.now() + REFERRAL_PUBLIC_TOKEN_EXPIRY),
+ publicToken: nanoid(REFERRAL_PUBLIC_TOKEN_LENGTH),
+ },
+ });
+
+ return NextResponse.json(referralTokenSchema.parse(referralToken), {
+ status: 201,
+ });
+});
diff --git a/apps/web/lib/referrals/constants.ts b/apps/web/lib/referrals/constants.ts
index c5793bb0b2..7bda65072e 100644
--- a/apps/web/lib/referrals/constants.ts
+++ b/apps/web/lib/referrals/constants.ts
@@ -2,3 +2,7 @@ export const REFERRAL_SIGNUPS_MAX = 32;
export const REFERRAL_CLICKS_QUOTA_BONUS = 500;
export const REFERRAL_CLICKS_QUOTA_BONUS_MAX = 16000;
export const REFERRAL_REVENUE_SHARE = 0.2;
+
+// Referral public token
+export const REFERRAL_PUBLIC_TOKEN_LENGTH = 36;
+export const REFERRAL_PUBLIC_TOKEN_EXPIRY = 1000 * 60 * 60 * 2; // 2 hours
diff --git a/apps/web/lib/zod/schemas/referrals.ts b/apps/web/lib/zod/schemas/referrals.ts
new file mode 100644
index 0000000000..27bc116e1a
--- /dev/null
+++ b/apps/web/lib/zod/schemas/referrals.ts
@@ -0,0 +1,10 @@
+import z from "@/lib/zod";
+
+export const createReferralTokenSchema = z.object({
+ linkId: z.string().min(1),
+});
+
+export const referralTokenSchema = z.object({
+ publicToken: z.string(),
+ expires: z.date(),
+});
diff --git a/apps/web/prisma/schema/link.prisma b/apps/web/prisma/schema/link.prisma
index fd921135f6..ce45a28bed 100644
--- a/apps/web/prisma/schema/link.prisma
+++ b/apps/web/prisma/schema/link.prisma
@@ -62,6 +62,8 @@ model Link {
// Comments on the particular shortlink
comments String? @db.LongText
+ referralPublicTokens ReferralPublicToken[]
+
@@unique([domain, key])
@@unique([projectId, externalId])
@@index(projectId)
diff --git a/apps/web/prisma/schema/referral.prisma b/apps/web/prisma/schema/referral.prisma
new file mode 100644
index 0000000000..93aef82b67
--- /dev/null
+++ b/apps/web/prisma/schema/referral.prisma
@@ -0,0 +1,12 @@
+model ReferralPublicToken {
+ id String @id @default(cuid())
+ linkId String
+ publicToken String @unique
+ expires DateTime
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ link Link @relation(fields: [linkId], references: [id], onDelete: Cascade)
+
+ @@index([linkId])
+}
From a7df9906c0bab5a23226838fc52b7ef6b7aa63ea Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 30 Sep 2024 22:06:03 +0530
Subject: [PATCH 02/88] add events and analytics
---
apps/web/app/api/analytics/client/route.ts | 17 +++
apps/web/app/api/events/client/route.ts | 17 +++
apps/web/lib/referrals/auth.ts | 136 +++++++++++++++++++++
3 files changed, 170 insertions(+)
create mode 100644 apps/web/app/api/analytics/client/route.ts
create mode 100644 apps/web/app/api/events/client/route.ts
create mode 100644 apps/web/lib/referrals/auth.ts
diff --git a/apps/web/app/api/analytics/client/route.ts b/apps/web/app/api/analytics/client/route.ts
new file mode 100644
index 0000000000..3335215dd6
--- /dev/null
+++ b/apps/web/app/api/analytics/client/route.ts
@@ -0,0 +1,17 @@
+import { getAnalytics } from "@/lib/analytics/get-analytics";
+import { withAuth } from "@/lib/referrals/auth";
+import { analyticsQuerySchema } from "@/lib/zod/schemas/analytics";
+import { NextResponse } from "next/server";
+
+// GET /api/analytics/client - get analytics for the current link
+export const GET = withAuth(async ({ workspace, link, searchParams }) => {
+ const parsedParams = analyticsQuerySchema.parse(searchParams);
+
+ const response = await getAnalytics({
+ ...parsedParams,
+ linkId: link.id,
+ workspaceId: workspace.id,
+ });
+
+ return NextResponse.json(response);
+});
diff --git a/apps/web/app/api/events/client/route.ts b/apps/web/app/api/events/client/route.ts
new file mode 100644
index 0000000000..0a771e2642
--- /dev/null
+++ b/apps/web/app/api/events/client/route.ts
@@ -0,0 +1,17 @@
+import { getEvents } from "@/lib/analytics/get-events";
+import { withAuth } from "@/lib/referrals/auth";
+import { eventsQuerySchema } from "@/lib/zod/schemas/analytics";
+import { NextResponse } from "next/server";
+
+// GET /api/events/client - get events for the current link
+export const GET = withAuth(async ({ searchParams, workspace, link }) => {
+ const parsedParams = eventsQuerySchema.parse(searchParams);
+
+ const response = await getEvents({
+ ...parsedParams,
+ linkId: link.id,
+ workspaceId: workspace.id,
+ });
+
+ return NextResponse.json(response);
+});
diff --git a/apps/web/lib/referrals/auth.ts b/apps/web/lib/referrals/auth.ts
new file mode 100644
index 0000000000..8ac0d0c0ba
--- /dev/null
+++ b/apps/web/lib/referrals/auth.ts
@@ -0,0 +1,136 @@
+import { DubApiError, handleAndReturnErrorResponse } from "@/lib/api/errors";
+import { prisma } from "@/lib/prisma";
+import { ratelimit } from "@/lib/upstash";
+import { getSearchParams } from "@dub/utils";
+import { Link, Project } from "@prisma/client";
+import { AxiomRequest, withAxiom } from "next-axiom";
+
+interface WithAuthHandler {
+ ({
+ req,
+ params,
+ searchParams,
+ workspace,
+ link,
+ }: {
+ req: Request;
+ params: Record;
+ searchParams: Record;
+ workspace: Project;
+ link: Link;
+ }): Promise;
+}
+
+export const withAuth = (handler: WithAuthHandler) => {
+ return withAxiom(
+ async (
+ req: AxiomRequest,
+ { params = {} }: { params: Record | undefined },
+ ) => {
+ let headers = {};
+
+ try {
+ let link: Link | undefined = undefined;
+ let publicToken: string | undefined = undefined;
+
+ const rateLimit = 100;
+ const searchParams = getSearchParams(req.url);
+ const authorizationHeader = req.headers.get("Authorization");
+
+ if (!authorizationHeader) {
+ throw new DubApiError({
+ code: "unauthorized",
+ message: "Missing Authorization header.",
+ });
+ }
+
+ if (!authorizationHeader.includes("Bearer ")) {
+ throw new DubApiError({
+ code: "bad_request",
+ message:
+ "Misconfigured authorization header. Did you forget to add 'Bearer '? Learn more: https://d.to/auth",
+ });
+ }
+
+ publicToken = authorizationHeader.replace("Bearer ", "");
+
+ if (!publicToken) {
+ throw new DubApiError({
+ code: "unauthorized",
+ message: "Missing Authorization header.",
+ });
+ }
+
+ const referralToken =
+ await prisma.referralPublicToken.findUniqueOrThrow({
+ where: {
+ publicToken,
+ },
+ });
+
+ if (referralToken.expires < new Date()) {
+ throw new DubApiError({
+ code: "unauthorized",
+ message: "Public token expired.",
+ });
+ }
+
+ link = await prisma.link.findUniqueOrThrow({
+ where: {
+ id: referralToken.linkId,
+ },
+ });
+
+ if (!link.trackConversion) {
+ throw new DubApiError({
+ code: "forbidden",
+ message: "Conversion tracking is not enabled for this link.",
+ });
+ }
+
+ const workspace = await prisma.project.findUniqueOrThrow({
+ where: {
+ id: link.projectId!,
+ },
+ });
+
+ const { success, limit, reset, remaining } = await ratelimit(
+ rateLimit,
+ "1 m",
+ ).limit(publicToken);
+
+ if (!success) {
+ throw new DubApiError({
+ code: "rate_limit_exceeded",
+ message: "Too many requests.",
+ });
+ }
+
+ headers = {
+ "Retry-After": reset.toString(),
+ "X-RateLimit-Limit": limit.toString(),
+ "X-RateLimit-Remaining": remaining.toString(),
+ "X-RateLimit-Reset": reset.toString(),
+ };
+
+ if (!success) {
+ throw new DubApiError({
+ code: "rate_limit_exceeded",
+ message: "Too many requests.",
+ });
+ }
+
+ return await handler({
+ req,
+ params,
+ searchParams,
+ workspace,
+ link,
+ });
+ } catch (error) {
+ req.log.error(error);
+ return handleAndReturnErrorResponse(error, headers);
+ }
+ },
+ );
+};
From 816c8fe2ee5e1d99f6b303f4dc56f850937208e1 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 30 Sep 2024 22:24:10 +0530
Subject: [PATCH 03/88] update
---
apps/web/lib/referrals/auth.ts | 23 +++++++++++------------
1 file changed, 11 insertions(+), 12 deletions(-)
diff --git a/apps/web/lib/referrals/auth.ts b/apps/web/lib/referrals/auth.ts
index 8ac0d0c0ba..63c8924bae 100644
--- a/apps/web/lib/referrals/auth.ts
+++ b/apps/web/lib/referrals/auth.ts
@@ -61,12 +61,18 @@ export const withAuth = (handler: WithAuthHandler) => {
});
}
- const referralToken =
- await prisma.referralPublicToken.findUniqueOrThrow({
- where: {
- publicToken,
- },
+ const referralToken = await prisma.referralPublicToken.findUnique({
+ where: {
+ publicToken,
+ },
+ });
+
+ if (!referralToken) {
+ throw new DubApiError({
+ code: "unauthorized",
+ message: "Invalid public token.",
});
+ }
if (referralToken.expires < new Date()) {
throw new DubApiError({
@@ -99,13 +105,6 @@ export const withAuth = (handler: WithAuthHandler) => {
"1 m",
).limit(publicToken);
- if (!success) {
- throw new DubApiError({
- code: "rate_limit_exceeded",
- message: "Too many requests.",
- });
- }
-
headers = {
"Retry-After": reset.toString(),
"X-RateLimit-Limit": limit.toString(),
From 8a8c4b2d8f64b262f27d675d965cbb0b7dc206e1 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Tue, 1 Oct 2024 20:13:42 +0530
Subject: [PATCH 04/88] rearrange
---
apps/web/lib/referrals/auth.ts | 54 +++++++++++++++++-----------------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/apps/web/lib/referrals/auth.ts b/apps/web/lib/referrals/auth.ts
index 63c8924bae..cebd5ac7fc 100644
--- a/apps/web/lib/referrals/auth.ts
+++ b/apps/web/lib/referrals/auth.ts
@@ -31,7 +31,7 @@ export const withAuth = (handler: WithAuthHandler) => {
try {
let link: Link | undefined = undefined;
- let publicToken: string | undefined = undefined;
+ let tokenFromHeader: string | undefined = undefined;
const rateLimit = 100;
const searchParams = getSearchParams(req.url);
@@ -52,58 +52,39 @@ export const withAuth = (handler: WithAuthHandler) => {
});
}
- publicToken = authorizationHeader.replace("Bearer ", "");
+ tokenFromHeader = authorizationHeader.replace("Bearer ", "");
- if (!publicToken) {
+ if (!tokenFromHeader) {
throw new DubApiError({
code: "unauthorized",
message: "Missing Authorization header.",
});
}
- const referralToken = await prisma.referralPublicToken.findUnique({
+ const publicToken = await prisma.referralPublicToken.findUnique({
where: {
- publicToken,
+ publicToken: tokenFromHeader,
},
});
- if (!referralToken) {
+ if (!publicToken) {
throw new DubApiError({
code: "unauthorized",
message: "Invalid public token.",
});
}
- if (referralToken.expires < new Date()) {
+ if (publicToken.expires < new Date()) {
throw new DubApiError({
code: "unauthorized",
message: "Public token expired.",
});
}
- link = await prisma.link.findUniqueOrThrow({
- where: {
- id: referralToken.linkId,
- },
- });
-
- if (!link.trackConversion) {
- throw new DubApiError({
- code: "forbidden",
- message: "Conversion tracking is not enabled for this link.",
- });
- }
-
- const workspace = await prisma.project.findUniqueOrThrow({
- where: {
- id: link.projectId!,
- },
- });
-
const { success, limit, reset, remaining } = await ratelimit(
rateLimit,
"1 m",
- ).limit(publicToken);
+ ).limit(tokenFromHeader);
headers = {
"Retry-After": reset.toString(),
@@ -119,6 +100,25 @@ export const withAuth = (handler: WithAuthHandler) => {
});
}
+ link = await prisma.link.findUniqueOrThrow({
+ where: {
+ id: publicToken.linkId,
+ },
+ });
+
+ if (!link.trackConversion) {
+ throw new DubApiError({
+ code: "forbidden",
+ message: "Conversion tracking is not enabled for this link.",
+ });
+ }
+
+ const workspace = await prisma.project.findUniqueOrThrow({
+ where: {
+ id: link.projectId!,
+ },
+ });
+
return await handler({
req,
params,
From 9bc3e7d16cde391f76e5342ff9942cb57441d2cd Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Wed, 2 Oct 2024 00:12:15 +0530
Subject: [PATCH 05/88] wip
---
.../{activity-list.tsx => events.tsx} | 14 ++----
.../settings/referrals/placeholders.tsx | 50 +++++++++++++++++++
packages/blocks/package.json | 3 +-
packages/blocks/src/hooks/use-analytics.ts | 12 +++++
packages/blocks/src/hooks/use-events.ts | 12 +++++
packages/blocks/src/index.ts | 2 +
6 files changed, 83 insertions(+), 10 deletions(-)
rename apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/{activity-list.tsx => events.tsx} (97%)
create mode 100644 apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/placeholders.tsx
create mode 100644 packages/blocks/src/hooks/use-analytics.ts
create mode 100644 packages/blocks/src/hooks/use-events.ts
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/activity-list.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx
similarity index 97%
rename from apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/activity-list.tsx
rename to apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx
index a92c79b012..50f37bf588 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/activity-list.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx
@@ -20,15 +20,11 @@ import {
} from "dub/dist/commonjs/models/components";
import { useSearchParams } from "next/navigation";
-export function ActivityList({
- events,
- totalEvents,
- demo,
-}: {
- events: ConversionEvent[];
- totalEvents: number;
- demo?: boolean;
-}) {
+interface EventsProps {
+ //
+}
+
+export const Events = (props: EventsProps) => {
const searchParams = useSearchParams();
const event = (searchParams.get("event") || "clicks") as EventType;
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/placeholders.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/placeholders.tsx
new file mode 100644
index 0000000000..5ebaa1a852
--- /dev/null
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/placeholders.tsx
@@ -0,0 +1,50 @@
+import { randomValue } from "@dub/utils";
+import { subDays } from "date-fns";
+
+export const placeholderEvents = {
+ clicks: [...Array(10)].map(
+ (_, idx) =>
+ ({
+ timestamp: subDays(new Date(), idx).toISOString(),
+ click_id: "1",
+ link_id: "1",
+ domain: "refer.dub.co",
+ key: "",
+ url: "https://dub.co",
+ country: randomValue(["US", "GB", "CA", "AU", "DE", "FR", "ES", "IT"]),
+ }) as any,
+ ),
+
+ leads: [...Array(10)].map(
+ (_, idx) =>
+ ({
+ timestamp: subDays(new Date(), idx).toISOString(),
+ click_id: "1",
+ link_id: "1",
+ domain: "refer.dub.co",
+ key: "",
+ url: "https://dub.co",
+ country: randomValue(["US", "GB", "CA", "AU", "DE", "FR", "ES", "IT"]),
+ }) as any,
+ ),
+
+ sales: [...Array(10)].map(
+ (_, idx) =>
+ ({
+ timestamp: subDays(new Date(), idx).toISOString(),
+ click_id: "1",
+ link_id: "1",
+ domain: "refer.dub.co",
+ key: "",
+ url: "https://dub.co",
+ country: randomValue(["US", "GB", "CA", "AU", "DE", "FR", "ES", "IT"]),
+ event_name: [
+ "Subscription creation",
+ "Subscription paid",
+ "Plan upgraded",
+ ][idx % 3],
+ // TODO update to saleAmount
+ amount: [1100, 4900, 2400][idx % 3],
+ }) as any,
+ ),
+};
diff --git a/packages/blocks/package.json b/packages/blocks/package.json
index f366666dde..2872efa211 100644
--- a/packages/blocks/package.json
+++ b/packages/blocks/package.json
@@ -43,7 +43,8 @@
"@visx/scale": "^3.3.0",
"@visx/shape": "^2.12.2",
"class-variance-authority": "^0.7.0",
- "framer-motion": "^10.16.16"
+ "framer-motion": "^10.16.16",
+ "swr": "^2.1.5"
},
"author": "Steven Tey ",
"homepage": "https://dub.co",
diff --git a/packages/blocks/src/hooks/use-analytics.ts b/packages/blocks/src/hooks/use-analytics.ts
new file mode 100644
index 0000000000..9bd91503ed
--- /dev/null
+++ b/packages/blocks/src/hooks/use-analytics.ts
@@ -0,0 +1,12 @@
+import { fetcher } from "@dub/utils";
+import useSWR from "swr";
+
+export const useAnalytics = () => {
+ const { error, data, isLoading } = useSWR(`/api/analytics/client`, fetcher);
+
+ return {
+ analytics: data,
+ error,
+ isLoading,
+ };
+};
diff --git a/packages/blocks/src/hooks/use-events.ts b/packages/blocks/src/hooks/use-events.ts
new file mode 100644
index 0000000000..d299dea28a
--- /dev/null
+++ b/packages/blocks/src/hooks/use-events.ts
@@ -0,0 +1,12 @@
+import { fetcher } from "@dub/utils";
+import useSWR from "swr";
+
+export const useEvents = () => {
+ const { error, data, isLoading } = useSWR(`/api/events/client`, fetcher);
+
+ return {
+ events: data,
+ error,
+ isLoading,
+ };
+};
diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts
index 3a63f7417a..3a7736b51e 100644
--- a/packages/blocks/src/index.ts
+++ b/packages/blocks/src/index.ts
@@ -4,6 +4,8 @@ import "./styles.css";
export * from "./empty-state";
export * from "./event-list";
export * from "./gauge";
+export * from "./hooks/use-analytics";
+export * from "./hooks/use-events";
export * from "./mini-area-chart";
export * from "./pagination-controls";
export * from "./stats-card";
From 9db82ae9b5c872c08ab4252d4904a8cfab2dc6e5 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Wed, 2 Oct 2024 00:18:01 +0530
Subject: [PATCH 06/88] wip
---
.../[slug]/settings/referrals/page.tsx | 100 ++----------
.../[slug]/settings/referrals/stats.tsx | 143 ++++++------------
apps/web/lib/edge-config/get-feature-flags.ts | 2 +-
pnpm-lock.yaml | 41 +----
4 files changed, 60 insertions(+), 226 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
index 76e3825939..ee1e896eba 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
@@ -1,24 +1,18 @@
-import { getConversionEvents } from "@/lib/actions/get-conversion-events";
-import { getReferralLink } from "@/lib/actions/get-referral-link";
-import { getTotalEvents } from "@/lib/actions/get-total-events";
import { EventType } from "@/lib/analytics/types";
import {
REFERRAL_CLICKS_QUOTA_BONUS,
REFERRAL_CLICKS_QUOTA_BONUS_MAX,
REFERRAL_REVENUE_SHARE,
} from "@/lib/referrals/constants";
-import { EventListSkeleton } from "@dub/blocks";
import { Wordmark } from "@dub/ui";
import { Check } from "@dub/ui/src/icons";
-import { nFormatter, randomValue } from "@dub/utils";
-import { subDays } from "date-fns";
+import { nFormatter } from "@dub/utils";
import { Suspense } from "react";
-import { ActivityList } from "./activity-list";
import { EventTabs } from "./event-tabs";
+import { Events } from "./events";
import { HeroBackground } from "./hero-background";
import ReferralsPageClient from "./page-client";
import ReferralLink, { ReferralLinkSkeleton } from "./referral-link";
-import { Stats } from "./stats";
export const revalidate = 0;
@@ -93,96 +87,20 @@ export default function ReferralsPage({
-
-
-
+
+ {/*
+
+
*/}
+
+ {/* Events */}
);
}
-
-const placeholderEvents = {
- clicks: [...Array(10)].map(
- (_, idx) =>
- ({
- timestamp: subDays(new Date(), idx).toISOString(),
- click_id: "1",
- link_id: "1",
- domain: "refer.dub.co",
- key: "",
- url: "https://dub.co",
- country: randomValue(["US", "GB", "CA", "AU", "DE", "FR", "ES", "IT"]),
- }) as any,
- ),
- leads: [...Array(10)].map(
- (_, idx) =>
- ({
- timestamp: subDays(new Date(), idx).toISOString(),
- click_id: "1",
- link_id: "1",
- domain: "refer.dub.co",
- key: "",
- url: "https://dub.co",
- country: randomValue(["US", "GB", "CA", "AU", "DE", "FR", "ES", "IT"]),
- }) as any,
- ),
- sales: [...Array(10)].map(
- (_, idx) =>
- ({
- timestamp: subDays(new Date(), idx).toISOString(),
- click_id: "1",
- link_id: "1",
- domain: "refer.dub.co",
- key: "",
- url: "https://dub.co",
- country: randomValue(["US", "GB", "CA", "AU", "DE", "FR", "ES", "IT"]),
- event_name: [
- "Subscription creation",
- "Subscription paid",
- "Plan upgraded",
- ][idx % 3],
- // TODO update to saleAmount
- amount: [1100, 4900, 2400][idx % 3],
- }) as any,
- ),
-};
-
-async function ActivityListRSC({
- slug,
- event,
- page,
-}: {
- slug: string;
- event: EventType;
- page: number;
-}) {
- const link = await getReferralLink(slug);
- if (!link) {
- return (
-
- );
- }
-
- const [events, totalEvents] = await Promise.all([
- getConversionEvents({ linkId: link.id, event, page }),
- getTotalEvents(link.id),
- ]);
-
- return ;
-}
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
index 8715125d8b..01ff1387e6 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
@@ -1,4 +1,5 @@
-import { getReferralLink } from "@/lib/actions/get-referral-link";
+"use client";
+
import { getTotalEvents } from "@/lib/actions/get-total-events";
import { dub } from "@/lib/dub";
import {
@@ -11,113 +12,61 @@ import {
MiniAreaChart,
StatsCard,
StatsCardSkeleton,
+ useAnalytics,
} from "@dub/blocks";
import { CountingNumbers } from "@dub/ui";
import { User } from "@dub/ui/src/icons";
-import { nFormatter, randomValue } from "@dub/utils";
-import { subDays } from "date-fns";
import { AnalyticsTimeseries } from "dub/dist/commonjs/models/components";
-import { Suspense } from "react";
-export function Stats({ slug }: { slug: string }) {
- return (
-
- (
-
- ))}
- >
-
-
-
- );
+export function Stats() {
+ const { analytics, isLoading } = useAnalytics();
+
+ return ;
+
+ // return (
+ //
+ // {isLoading ? (
+ // [...Array(2)].map(() => )
+ // ) : (
+ //
+ // )}
+ //
+ // );
}
-async function StatsInner({ slug }: { slug: string }) {
- try {
- const link = await getReferralLink(slug);
- if (!link) {
- return (
- <>
- {
- const x = (idx - 7.5) / 4;
- const curve1 = 800 * Math.exp(-Math.pow(x + 0.5, 2));
- const curve2 = 600 * Math.exp(-Math.pow(x - 0.5, 2));
- return {
- date: subDays(new Date(), 15 - idx),
- value: Math.floor(
- 1500 + curve1 + curve2 + (Math.random() - 0.5) * 200,
- ),
- };
- })}
- />
- }
- >
- ${Math.floor(Math.random() * 50) + 50}
-
-
-
-
- 5
-
-
- }
- >
- {nFormatter(randomValue([1000, 1500, 2000, 2500, 3000]), {
- full: true,
- })}
-
- >
- );
- }
+const StatsInner = () => {
+ const { totalSales, sales, referredSignups, clicksQuotaBonus } =
+ await loadData(link.id);
- const { totalSales, sales, referredSignups, clicksQuotaBonus } =
- await loadData(link.id);
+ return (
+ <>
+ }
+ >
+
+ {(totalSales / 100) * REFERRAL_REVENUE_SHARE}
+
+
- return (
- <>
- }
- >
-
- {(totalSales / 100) * REFERRAL_REVENUE_SHARE}
-
-
-
-
-
- {referredSignups}
-
-
- }
- >
- {clicksQuotaBonus}
-
- >
- );
- } catch (e) {
- console.error("Failed to load referral stats", e);
- }
+
+
+
+ {referredSignups}
+
+
+ }
+ >
+ {clicksQuotaBonus}
+
+ >
+ );
return [...Array(2)].map(() => );
-}
+};
async function loadData(linkId: string) {
const [clicks, sales, totalEvents] = await Promise.all([
diff --git a/apps/web/lib/edge-config/get-feature-flags.ts b/apps/web/lib/edge-config/get-feature-flags.ts
index d649eb90b3..1b6a2fb840 100644
--- a/apps/web/lib/edge-config/get-feature-flags.ts
+++ b/apps/web/lib/edge-config/get-feature-flags.ts
@@ -17,7 +17,7 @@ export const getFeatureFlags = async ({
}
const workspaceFeatures: Record = {
- referrals: false,
+ referrals: true,
webhooks: false,
newlinkbuilder: false,
};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 770370ded3..e09addc7eb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -450,6 +450,9 @@ importers:
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
+ swr:
+ specifier: ^2.1.5
+ version: 2.1.5(react@18.2.0)
devDependencies:
'@dub/tailwind-config':
specifier: workspace:*
@@ -708,7 +711,7 @@ importers:
version: link:../tsconfig
tsup:
specifier: ^6.1.3
- version: 6.1.3(typescript@5.1.6)
+ version: 6.1.3(postcss@8.4.31)(typescript@5.1.6)
typescript:
specifier: ^5.1.6
version: 5.1.6
@@ -20237,42 +20240,6 @@ packages:
- ts-node
dev: true
- /tsup@6.1.3(typescript@5.1.6):
- resolution: {integrity: sha512-eRpBnbfpDFng+EJNTQ90N7QAf4HAGGC7O3buHIjroKWK7D1ibk9/YnR/3cS8HsMU5T+6Oi+cnF+yU5WmCnB//Q==}
- engines: {node: '>=14'}
- hasBin: true
- peerDependencies:
- '@swc/core': ^1
- postcss: ^8.4.12
- typescript: ^4.1.0
- peerDependenciesMeta:
- '@swc/core':
- optional: true
- postcss:
- optional: true
- typescript:
- optional: true
- dependencies:
- bundle-require: 3.1.2(esbuild@0.14.54)
- cac: 6.7.14
- chokidar: 3.5.3
- debug: 4.3.4
- esbuild: 0.14.54
- execa: 5.1.1
- globby: 11.1.0
- joycon: 3.1.1
- postcss-load-config: 3.1.4(postcss@8.4.38)
- resolve-from: 5.0.0
- rollup: 2.79.1
- source-map: 0.8.0-beta.0
- sucrase: 3.34.0
- tree-kill: 1.2.2
- typescript: 5.1.6
- transitivePeerDependencies:
- - supports-color
- - ts-node
- dev: true
-
/tsutils@3.21.0(typescript@4.9.5):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
From 04fb198fd6002a357b401b705d4f0d157ac8f0fc Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 3 Oct 2024 12:19:41 +0530
Subject: [PATCH 07/88] wip
---
.../[slug]/settings/referrals/events.tsx | 78 ++++++++++---------
1 file changed, 41 insertions(+), 37 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx
index 50f37bf588..60691e07af 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx
@@ -3,7 +3,7 @@
import { ConversionEvent } from "@/lib/actions/get-conversion-events";
import { EventType } from "@/lib/analytics/types";
import { REFERRAL_REVENUE_SHARE } from "@/lib/referrals/constants";
-import { EventList } from "@dub/blocks";
+import { EventList, useEvents } from "@dub/blocks";
import {
CaretUpFill,
ChartActivity2,
@@ -26,44 +26,48 @@ interface EventsProps {
export const Events = (props: EventsProps) => {
const searchParams = useSearchParams();
+ const { events, isLoading } = useEvents();
+
const event = (searchParams.get("event") || "clicks") as EventType;
- return (
-
-
{
- const Icon = {
- clicks: CursorRays,
- leads: UserCheck,
- sales: InvoiceDollar,
- }[event];
- return {
- icon: ,
- content: {
- clicks: ,
- leads: ,
- sales: ,
- }[event],
- right: e.timestamp ? (
-
- {timeAgo(new Date(e.timestamp), { withAgo: true })}
-
- ) : null,
- };
- })}
- totalEvents={totalEvents}
- emptyState={{
- icon: ChartActivity2,
- title: `${capitalize(event)} Activity`,
- description: `No referral ${event} have been recorded yet.`,
- learnMore: "https://d.to/conversions",
- }}
- />
- {demo && (
-
- )}
-
- );
+ return Events
;
+
+ // return (
+ //
+ //
{
+ // const Icon = {
+ // clicks: CursorRays,
+ // leads: UserCheck,
+ // sales: InvoiceDollar,
+ // }[event];
+ // return {
+ // icon: ,
+ // content: {
+ // clicks: ,
+ // leads: ,
+ // sales: ,
+ // }[event],
+ // right: e.timestamp ? (
+ //
+ // {timeAgo(new Date(e.timestamp), { withAgo: true })}
+ //
+ // ) : null,
+ // };
+ // })}
+ // totalEvents={totalEvents}
+ // emptyState={{
+ // icon: ChartActivity2,
+ // title: `${capitalize(event)} Activity`,
+ // description: `No referral ${event} have been recorded yet.`,
+ // learnMore: "https://d.to/conversions",
+ // }}
+ // />
+ // {demo && (
+ //
+ // )}
+ //
+ // );
}
function ClickDescription({ event }: { event: ClickEvent }) {
From 1215a43b0eba5f900d35d07d136b634deecb4a15 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 3 Oct 2024 15:07:47 +0530
Subject: [PATCH 08/88] wip
---
apps/web/app/api/analytics/client/route.ts | 1 +
.../[slug]/settings/referrals/events.tsx | 16 ++++---
apps/web/lib/referrals/auth.ts | 48 ++++++++++---------
3 files changed, 36 insertions(+), 29 deletions(-)
diff --git a/apps/web/app/api/analytics/client/route.ts b/apps/web/app/api/analytics/client/route.ts
index 3335215dd6..b6a2d17996 100644
--- a/apps/web/app/api/analytics/client/route.ts
+++ b/apps/web/app/api/analytics/client/route.ts
@@ -7,6 +7,7 @@ import { NextResponse } from "next/server";
export const GET = withAuth(async ({ workspace, link, searchParams }) => {
const parsedParams = analyticsQuerySchema.parse(searchParams);
+
const response = await getAnalytics({
...parsedParams,
linkId: link.id,
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx
index 60691e07af..ad6fb43a09 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx
@@ -28,6 +28,8 @@ export const Events = (props: EventsProps) => {
const searchParams = useSearchParams();
const { events, isLoading } = useEvents();
+ console.log("events", events);
+
const event = (searchParams.get("event") || "clicks") as EventType;
return Events
;
@@ -70,6 +72,13 @@ export const Events = (props: EventsProps) => {
// );
}
+const saleText = {
+ "Subscription creation": "upgraded their account",
+ "Subscription paid": "paid their subscription",
+ "Plan upgraded": "upgraded their plan",
+ default: "made a payment",
+};
+
function ClickDescription({ event }: { event: ClickEvent }) {
return (
<>
@@ -116,12 +125,7 @@ function LeadDescription({ event }: { event: LeadEvent }) {
);
}
-const saleText = {
- "Subscription creation": "upgraded their account",
- "Subscription paid": "paid their subscription",
- "Plan upgraded": "upgraded their plan",
- default: "made a payment",
-};
+
function SaleDescription({ event }: { event: SaleEvent }) {
return (
diff --git a/apps/web/lib/referrals/auth.ts b/apps/web/lib/referrals/auth.ts
index cebd5ac7fc..cd80149c6e 100644
--- a/apps/web/lib/referrals/auth.ts
+++ b/apps/web/lib/referrals/auth.ts
@@ -37,29 +37,31 @@ export const withAuth = (handler: WithAuthHandler) => {
const searchParams = getSearchParams(req.url);
const authorizationHeader = req.headers.get("Authorization");
- if (!authorizationHeader) {
- throw new DubApiError({
- code: "unauthorized",
- message: "Missing Authorization header.",
- });
- }
-
- if (!authorizationHeader.includes("Bearer ")) {
- throw new DubApiError({
- code: "bad_request",
- message:
- "Misconfigured authorization header. Did you forget to add 'Bearer '? Learn more: https://d.to/auth",
- });
- }
-
- tokenFromHeader = authorizationHeader.replace("Bearer ", "");
-
- if (!tokenFromHeader) {
- throw new DubApiError({
- code: "unauthorized",
- message: "Missing Authorization header.",
- });
- }
+ // if (!authorizationHeader) {
+ // throw new DubApiError({
+ // code: "unauthorized",
+ // message: "Missing Authorization header.",
+ // });
+ // }
+
+ // if (!authorizationHeader.includes("Bearer ")) {
+ // throw new DubApiError({
+ // code: "bad_request",
+ // message:
+ // "Misconfigured authorization header. Did you forget to add 'Bearer '? Learn more: https://d.to/auth",
+ // });
+ // }
+
+ // tokenFromHeader = authorizationHeader.replace("Bearer ", "");
+
+ // if (!tokenFromHeader) {
+ // throw new DubApiError({
+ // code: "unauthorized",
+ // message: "Missing Authorization header.",
+ // });
+ // }
+
+ tokenFromHeader = "Wu2HvXx2w99h1nFnXY2rnVE6532bVqLAoJht";
const publicToken = await prisma.referralPublicToken.findUnique({
where: {
From abf4fda38377b27e7a92fa86063ad295f32a2c53 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 3 Oct 2024 16:25:13 +0530
Subject: [PATCH 09/88] display events
---
apps/web/app/api/analytics/client/route.ts | 1 -
apps/web/app/api/analytics/route.ts | 6 +-
apps/web/app/api/events/route.ts | 6 +-
.../[slug]/settings/referrals/event-tabs.tsx | 1 +
.../[slug]/settings/referrals/events.tsx | 122 +++++++++---------
.../[slug]/settings/referrals/page.tsx | 8 +-
apps/web/lib/analytics/get-analytics.ts | 2 +
apps/web/lib/referrals/auth.ts | 2 +-
packages/blocks/src/hooks/use-events.ts | 21 ++-
packages/blocks/src/index.ts | 2 +-
10 files changed, 97 insertions(+), 74 deletions(-)
diff --git a/apps/web/app/api/analytics/client/route.ts b/apps/web/app/api/analytics/client/route.ts
index b6a2d17996..3335215dd6 100644
--- a/apps/web/app/api/analytics/client/route.ts
+++ b/apps/web/app/api/analytics/client/route.ts
@@ -7,7 +7,6 @@ import { NextResponse } from "next/server";
export const GET = withAuth(async ({ workspace, link, searchParams }) => {
const parsedParams = analyticsQuerySchema.parse(searchParams);
-
const response = await getAnalytics({
...parsedParams,
linkId: link.id,
diff --git a/apps/web/app/api/analytics/route.ts b/apps/web/app/api/analytics/route.ts
index 1f0ee4bd8d..6c7672c8c3 100644
--- a/apps/web/app/api/analytics/route.ts
+++ b/apps/web/app/api/analytics/route.ts
@@ -41,9 +41,9 @@ export const GET = withWorkspace(
} = parsedParams;
let link: Link | null = null;
- if (domain) {
- await getDomainOrThrow({ workspace, domain });
- }
+ // if (domain) {
+ // await getDomainOrThrow({ workspace, domain });
+ // }
if (linkId || externalId || (domain && key)) {
link = await getLinkOrThrow({
diff --git a/apps/web/app/api/events/route.ts b/apps/web/app/api/events/route.ts
index f543318ea9..807b184587 100644
--- a/apps/web/app/api/events/route.ts
+++ b/apps/web/app/api/events/route.ts
@@ -18,9 +18,9 @@ export const GET = withWorkspace(
parsedParams;
let link: Link | null = null;
- if (domain) {
- await getDomainOrThrow({ workspace, domain });
- }
+ // if (domain) {
+ // await getDomainOrThrow({ workspace, domain });
+ // }
if (linkId || externalId || (domain && key)) {
link = await getLinkOrThrow({
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/event-tabs.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/event-tabs.tsx
index dad47c15ce..1079bba6fc 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/event-tabs.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/event-tabs.tsx
@@ -5,6 +5,7 @@ import { ToggleGroup } from "@dub/ui/src/toggle-group";
export function EventTabs() {
const { queryParams, searchParams } = useRouterStuff();
+
return (
{
- const searchParams = useSearchParams();
- const { events, isLoading } = useEvents();
-
- console.log("events", events);
-
- const event = (searchParams.get("event") || "clicks") as EventType;
-
- return Events
;
-
- // return (
- //
- //
{
- // const Icon = {
- // clicks: CursorRays,
- // leads: UserCheck,
- // sales: InvoiceDollar,
- // }[event];
- // return {
- // icon: ,
- // content: {
- // clicks: ,
- // leads: ,
- // sales: ,
- // }[event],
- // right: e.timestamp ? (
- //
- // {timeAgo(new Date(e.timestamp), { withAgo: true })}
- //
- // ) : null,
- // };
- // })}
- // totalEvents={totalEvents}
- // emptyState={{
- // icon: ChartActivity2,
- // title: `${capitalize(event)} Activity`,
- // description: `No referral ${event} have been recorded yet.`,
- // learnMore: "https://d.to/conversions",
- // }}
- // />
- // {demo && (
- //
- // )}
- //
- // );
-}
+const iconMap: Record = {
+ clicks: CursorRays,
+ leads: UserCheck,
+ sales: InvoiceDollar,
+};
const saleText = {
"Subscription creation": "upgraded their account",
@@ -79,7 +36,56 @@ const saleText = {
default: "made a payment",
};
-function ClickDescription({ event }: { event: ClickEvent }) {
+export const Events = ({ event, page }: EventsProps) => {
+ const { events, isLoading } = useEvents({
+ event,
+ interval: "all",
+ page,
+ });
+
+ if (isLoading || !events) {
+ return ;
+ }
+
+ const Icon = iconMap[event];
+
+ return (
+
+
{
+ const content = {
+ clicks: ,
+ leads: ,
+ sales: ,
+ }[event];
+
+ return {
+ icon: ,
+ content,
+ right: e.timestamp ? (
+
+ {timeAgo(new Date(e.timestamp), { withAgo: true })}
+
+ ) : null,
+ };
+ })}
+ totalEvents={events?.length || 0}
+ emptyState={{
+ icon: ChartActivity2,
+ title: `${capitalize(event)} Activity`,
+ description: `No referral ${event} have been recorded yet.`,
+ learnMore: "https://d.to/conversions",
+ }}
+ />
+
+ {/* {demo && (
+
+ )} */}
+
+ );
+};
+
+const ClickDescription = ({ event }: { event: ClickEvent }) => {
return (
<>
Someone from{" "}
@@ -100,9 +106,9 @@ function ClickDescription({ event }: { event: ClickEvent }) {
clicked on your link
>
);
-}
+};
-function LeadDescription({ event }: { event: LeadEvent }) {
+const LeadDescription = ({ event }: { event: LeadEvent }) => {
return (
<>
Someone from{" "}
@@ -123,11 +129,9 @@ function LeadDescription({ event }: { event: LeadEvent }) {
signed up for an account
>
);
-}
-
-
+};
-function SaleDescription({ event }: { event: SaleEvent }) {
+const SaleDescription = ({ event }: { event: SaleEvent }) => {
return (
@@ -164,4 +168,4 @@ function SaleDescription({ event }: { event: SaleEvent }) {
)}
);
-}
+};
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
index ee1e896eba..74dbd0eaaa 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
@@ -21,10 +21,10 @@ export default function ReferralsPage({
searchParams,
}: {
params: { slug: string };
- searchParams: { event?: string; page?: string };
+ searchParams: { event?: EventType; page?: string };
}) {
- const event = (searchParams.event ?? "clicks") as EventType;
- const page = parseInt(searchParams.page ?? "1") || 1;
+ const event = searchParams.event || "clicks";
+ const page = searchParams.page || "1";
return (
@@ -98,7 +98,7 @@ export default function ReferralsPage({
Activity
-
+
diff --git a/apps/web/lib/analytics/get-analytics.ts b/apps/web/lib/analytics/get-analytics.ts
index 0357c1da75..c2e4600731 100644
--- a/apps/web/lib/analytics/get-analytics.ts
+++ b/apps/web/lib/analytics/get-analytics.ts
@@ -87,6 +87,8 @@ export const getAnalytics = async (params: AnalyticsFilters) => {
timezone,
});
+ console.log("getAnalytics", response);
+
if (groupBy === "count") {
// Return the count value for deprecated endpoints
if (isDeprecatedClicksEndpoint) {
diff --git a/apps/web/lib/referrals/auth.ts b/apps/web/lib/referrals/auth.ts
index cd80149c6e..f7a6448486 100644
--- a/apps/web/lib/referrals/auth.ts
+++ b/apps/web/lib/referrals/auth.ts
@@ -61,7 +61,7 @@ export const withAuth = (handler: WithAuthHandler) => {
// });
// }
- tokenFromHeader = "Wu2HvXx2w99h1nFnXY2rnVE6532bVqLAoJht";
+ tokenFromHeader = "i2yisemInUCWbWnEXB1WR4b3ROe2lLccYulj";
const publicToken = await prisma.referralPublicToken.findUnique({
where: {
diff --git a/packages/blocks/src/hooks/use-events.ts b/packages/blocks/src/hooks/use-events.ts
index d299dea28a..e33fbbe00e 100644
--- a/packages/blocks/src/hooks/use-events.ts
+++ b/packages/blocks/src/hooks/use-events.ts
@@ -1,8 +1,25 @@
import { fetcher } from "@dub/utils";
import useSWR from "swr";
-export const useEvents = () => {
- const { error, data, isLoading } = useSWR(`/api/events/client`, fetcher);
+const EVENT_TYPES = ["clicks", "leads", "sales"] as const;
+
+interface Props {
+ event: (typeof EVENT_TYPES)[number];
+ interval: string;
+ page: string;
+}
+
+export const useEvents = ({ event, interval, page }: Props) => {
+ const searchParams = new URLSearchParams();
+
+ searchParams.set("event", event);
+ searchParams.set("interval", interval);
+ searchParams.set("page", page);
+
+ const { error, data, isLoading } = useSWR<[]>(
+ `/api/events/client?${searchParams.toString()}`,
+ fetcher,
+ );
return {
events: data,
diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts
index 3a7736b51e..5137ee0e1f 100644
--- a/packages/blocks/src/index.ts
+++ b/packages/blocks/src/index.ts
@@ -8,4 +8,4 @@ export * from "./hooks/use-analytics";
export * from "./hooks/use-events";
export * from "./mini-area-chart";
export * from "./pagination-controls";
-export * from "./stats-card";
+export * from "./stats-card";
\ No newline at end of file
From 9dcc14ee2eb27bfc1910e2cae2c5fe5181d5d020 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 3 Oct 2024 17:51:32 +0530
Subject: [PATCH 10/88] display stats
---
apps/web/app/api/analytics/client/route.ts | 2 +-
apps/web/app/api/analytics/route.ts | 1 -
apps/web/app/api/events/route.ts | 1 -
.../[slug]/settings/referrals/page.tsx | 6 +-
.../[slug]/settings/referrals/stats.tsx | 115 ++++++++----------
apps/web/lib/analytics/get-analytics.ts | 2 -
packages/blocks/src/hooks/use-analytics.ts | 24 +++-
packages/blocks/src/hooks/use-events.ts | 19 ++-
packages/blocks/src/index.ts | 2 +-
packages/blocks/src/types.ts | 1 +
10 files changed, 89 insertions(+), 84 deletions(-)
create mode 100644 packages/blocks/src/types.ts
diff --git a/apps/web/app/api/analytics/client/route.ts b/apps/web/app/api/analytics/client/route.ts
index 3335215dd6..357d4f4a86 100644
--- a/apps/web/app/api/analytics/client/route.ts
+++ b/apps/web/app/api/analytics/client/route.ts
@@ -9,8 +9,8 @@ export const GET = withAuth(async ({ workspace, link, searchParams }) => {
const response = await getAnalytics({
...parsedParams,
- linkId: link.id,
workspaceId: workspace.id,
+ linkId: link.id,
});
return NextResponse.json(response);
diff --git a/apps/web/app/api/analytics/route.ts b/apps/web/app/api/analytics/route.ts
index 6c7672c8c3..012c7014c2 100644
--- a/apps/web/app/api/analytics/route.ts
+++ b/apps/web/app/api/analytics/route.ts
@@ -1,7 +1,6 @@
import { VALID_ANALYTICS_ENDPOINTS } from "@/lib/analytics/constants";
import { getAnalytics } from "@/lib/analytics/get-analytics";
import { validDateRangeForPlan } from "@/lib/analytics/utils";
-import { getDomainOrThrow } from "@/lib/api/domains/get-domain-or-throw";
import { getLinkOrThrow } from "@/lib/api/links/get-link-or-throw";
import { throwIfClicksUsageExceeded } from "@/lib/api/links/usage-checks";
import { withWorkspace } from "@/lib/auth";
diff --git a/apps/web/app/api/events/route.ts b/apps/web/app/api/events/route.ts
index 807b184587..5f1e38fb5c 100644
--- a/apps/web/app/api/events/route.ts
+++ b/apps/web/app/api/events/route.ts
@@ -1,6 +1,5 @@
import { getEvents } from "@/lib/analytics/get-events";
import { validDateRangeForPlan } from "@/lib/analytics/utils";
-import { getDomainOrThrow } from "@/lib/api/domains/get-domain-or-throw";
import { getLinkOrThrow } from "@/lib/api/links/get-link-or-throw";
import { throwIfClicksUsageExceeded } from "@/lib/api/links/usage-checks";
import { withWorkspace } from "@/lib/auth";
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
index 74dbd0eaaa..6f8534e57e 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
@@ -13,6 +13,7 @@ import { Events } from "./events";
import { HeroBackground } from "./hero-background";
import ReferralsPageClient from "./page-client";
import ReferralLink, { ReferralLinkSkeleton } from "./referral-link";
+import { Stats } from "./stats";
export const revalidate = 0;
@@ -88,9 +89,10 @@ export default function ReferralsPage({
- {/*
+ {/* Stats */}
+
-
*/}
+
{/* Events */}
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
index 01ff1387e6..50f919fc35 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
@@ -1,7 +1,5 @@
"use client";
-import { getTotalEvents } from "@/lib/actions/get-total-events";
-import { dub } from "@/lib/dub";
import {
REFERRAL_CLICKS_QUOTA_BONUS,
REFERRAL_CLICKS_QUOTA_BONUS_MAX,
@@ -16,33 +14,65 @@ import {
} from "@dub/blocks";
import { CountingNumbers } from "@dub/ui";
import { User } from "@dub/ui/src/icons";
-import { AnalyticsTimeseries } from "dub/dist/commonjs/models/components";
+import {
+ AnalyticsCount,
+ AnalyticsTimeseries,
+} from "dub/dist/commonjs/models/components";
-export function Stats() {
- const { analytics, isLoading } = useAnalytics();
+interface StatsInnerProps {
+ totalEvents: AnalyticsCount;
+ sales: AnalyticsTimeseries[];
+}
- return
;
+export const Stats = () => {
+ const { analytics: sales, isLoading: isLoadingSales } = useAnalytics({
+ event: "sales",
+ interval: "90d",
+ groupBy: "timeseries",
+ });
- // return (
- //
- // {isLoading ? (
- // [...Array(2)].map(() => )
- // ) : (
- //
- // )}
- //
- // );
-}
+ const { analytics: totalEvents, isLoading: isLoadingTotalEvents } =
+ useAnalytics({
+ event: "composite",
+ interval: "all_unfiltered",
+ groupBy: "count",
+ });
-const StatsInner = () => {
- const { totalSales, sales, referredSignups, clicksQuotaBonus } =
- await loadData(link.id);
+ const loading = isLoadingSales || isLoadingTotalEvents;
+
+ return (
+
+ {loading ? (
+ [...Array(2)].map(() => )
+ ) : (
+
+ )}
+
+ );
+};
+
+const StatsInner = ({ totalEvents, sales }: StatsInnerProps) => {
+ const totalLeads = Math.min(totalEvents.leads ?? 0, 32);
+ const totalSales = totalEvents.saleAmount ?? 0;
+
+ const clicksQuotaBonus = Math.min(
+ totalLeads * REFERRAL_CLICKS_QUOTA_BONUS,
+ REFERRAL_CLICKS_QUOTA_BONUS_MAX,
+ );
+
+ const salesData = sales.map((sale) => ({
+ date: new Date(sale.start),
+ value: sale.saleAmount ?? 0,
+ }));
return (
<>
}
+ graphic={
}
>
{(totalSales / 100) * REFERRAL_REVENUE_SHARE}
@@ -55,7 +85,7 @@ const StatsInner = () => {
- {referredSignups}
+ {totalLeads}
}
@@ -64,47 +94,4 @@ const StatsInner = () => {
>
);
-
- return [...Array(2)].map(() => );
};
-
-async function loadData(linkId: string) {
- const [clicks, sales, totalEvents] = await Promise.all([
- // Clicks timeseries
- dub.analytics.retrieve({
- linkId,
- event: "clicks",
- interval: "30d",
- groupBy: "timeseries",
- }) as Promise,
-
- // Sales timeseries
- dub.analytics.retrieve({
- linkId,
- event: "sales",
- interval: "30d",
- groupBy: "timeseries",
- }) as Promise,
-
- // Total events
- getTotalEvents(linkId),
- ]);
-
- return {
- totalClicks: totalEvents.clicks,
- clicks: clicks.map((d) => ({
- date: new Date(d.start),
- value: d.clicks,
- })),
- totalSales: totalEvents.saleAmount ?? 0,
- sales: sales.map((d) => ({
- date: new Date(d.start),
- value: d.saleAmount ?? 0,
- })),
- referredSignups: Math.min(totalEvents.leads ?? 0, 32),
- clicksQuotaBonus: Math.min(
- (totalEvents.leads ?? 0) * REFERRAL_CLICKS_QUOTA_BONUS,
- REFERRAL_CLICKS_QUOTA_BONUS_MAX,
- ),
- };
-}
diff --git a/apps/web/lib/analytics/get-analytics.ts b/apps/web/lib/analytics/get-analytics.ts
index c2e4600731..0357c1da75 100644
--- a/apps/web/lib/analytics/get-analytics.ts
+++ b/apps/web/lib/analytics/get-analytics.ts
@@ -87,8 +87,6 @@ export const getAnalytics = async (params: AnalyticsFilters) => {
timezone,
});
- console.log("getAnalytics", response);
-
if (groupBy === "count") {
// Return the count value for deprecated endpoints
if (isDeprecatedClicksEndpoint) {
diff --git a/packages/blocks/src/hooks/use-analytics.ts b/packages/blocks/src/hooks/use-analytics.ts
index 9bd91503ed..daa44ed860 100644
--- a/packages/blocks/src/hooks/use-analytics.ts
+++ b/packages/blocks/src/hooks/use-analytics.ts
@@ -1,8 +1,28 @@
import { fetcher } from "@dub/utils";
import useSWR from "swr";
+import { EventType } from "../types";
-export const useAnalytics = () => {
- const { error, data, isLoading } = useSWR(`/api/analytics/client`, fetcher);
+interface UseAnalyticsParams {
+ event: EventType;
+ interval: string;
+ groupBy: "timeseries" | "top_links" | "devices" | "count";
+}
+
+export const useAnalytics = ({
+ event,
+ interval,
+ groupBy,
+}: UseAnalyticsParams) => {
+ const searchParams = new URLSearchParams({
+ event,
+ interval,
+ groupBy,
+ });
+
+ const { error, data, isLoading } = useSWR(
+ `/api/analytics/client?${searchParams.toString()}`,
+ fetcher,
+ );
return {
analytics: data,
diff --git a/packages/blocks/src/hooks/use-events.ts b/packages/blocks/src/hooks/use-events.ts
index e33fbbe00e..5ea3bfbd96 100644
--- a/packages/blocks/src/hooks/use-events.ts
+++ b/packages/blocks/src/hooks/use-events.ts
@@ -1,20 +1,19 @@
import { fetcher } from "@dub/utils";
import useSWR from "swr";
+import { EventType } from "../types";
-const EVENT_TYPES = ["clicks", "leads", "sales"] as const;
-
-interface Props {
- event: (typeof EVENT_TYPES)[number];
+interface UseEventsParams {
+ event: EventType;
interval: string;
page: string;
}
-export const useEvents = ({ event, interval, page }: Props) => {
- const searchParams = new URLSearchParams();
-
- searchParams.set("event", event);
- searchParams.set("interval", interval);
- searchParams.set("page", page);
+export const useEvents = ({ event, interval, page }: UseEventsParams) => {
+ const searchParams = new URLSearchParams({
+ event,
+ interval,
+ page,
+ });
const { error, data, isLoading } = useSWR<[]>(
`/api/events/client?${searchParams.toString()}`,
diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts
index 5137ee0e1f..3a7736b51e 100644
--- a/packages/blocks/src/index.ts
+++ b/packages/blocks/src/index.ts
@@ -8,4 +8,4 @@ export * from "./hooks/use-analytics";
export * from "./hooks/use-events";
export * from "./mini-area-chart";
export * from "./pagination-controls";
-export * from "./stats-card";
\ No newline at end of file
+export * from "./stats-card";
diff --git a/packages/blocks/src/types.ts b/packages/blocks/src/types.ts
new file mode 100644
index 0000000000..510cddc5ce
--- /dev/null
+++ b/packages/blocks/src/types.ts
@@ -0,0 +1 @@
+export type EventType = "clicks" | "leads" | "sales" | "composite";
From 29982cd3680b7f202a2bb08b6dbd78d2e8a198d5 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 3 Oct 2024 17:58:20 +0530
Subject: [PATCH 11/88] remove actions
---
apps/web/lib/actions/get-conversion-events.ts | 28 -------------------
apps/web/lib/actions/get-total-events.ts | 12 --------
2 files changed, 40 deletions(-)
delete mode 100644 apps/web/lib/actions/get-conversion-events.ts
delete mode 100644 apps/web/lib/actions/get-total-events.ts
diff --git a/apps/web/lib/actions/get-conversion-events.ts b/apps/web/lib/actions/get-conversion-events.ts
deleted file mode 100644
index aaadd28577..0000000000
--- a/apps/web/lib/actions/get-conversion-events.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-"use server";
-
-import {
- ClickEvent,
- LeadEvent,
- SaleEvent,
-} from "dub/dist/commonjs/models/components";
-import { EventType } from "../analytics/types";
-import { dub } from "../dub";
-
-export type ConversionEvent = ClickEvent | LeadEvent | SaleEvent;
-
-export const getConversionEvents = async ({
- linkId,
- event,
- page,
-}: {
- linkId: string;
- event: EventType;
- page: number;
-}) => {
- return (await dub.events.list({
- linkId,
- event,
- interval: "all",
- page,
- })) as ConversionEvent[];
-};
diff --git a/apps/web/lib/actions/get-total-events.ts b/apps/web/lib/actions/get-total-events.ts
deleted file mode 100644
index 7d29ded170..0000000000
--- a/apps/web/lib/actions/get-total-events.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-"use server";
-
-import { dub } from "@/lib/dub";
-import { AnalyticsCount } from "dub/dist/commonjs/models/components";
-
-export const getTotalEvents = async (linkId: string) => {
- return (await dub.analytics.retrieve({
- linkId,
- event: "composite",
- interval: "all_unfiltered",
- })) as AnalyticsCount;
-};
From 6f2ac076f30a8d79fd86c350ec856964b936a281 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Fri, 4 Oct 2024 00:19:34 +0530
Subject: [PATCH 12/88] use public token
---
apps/web/app/api/referrals/tokens/route.ts | 32 +----
.../[idOrSlug]/referrals-token/route.ts | 26 ++++
.../[slug]/settings/referrals/page-client.tsx | 56 ++++++++-
.../[slug]/settings/referrals/page.tsx | 100 +--------------
.../settings/referrals/referral-link.tsx | 3 +-
.../[slug]/settings/referrals/referrals.tsx | 115 ++++++++++++++++++
.../[slug]/settings/referrals/stats.tsx | 2 +
apps/web/app/app.dub.co/embed/page.tsx | 3 +
apps/web/lib/middleware/app.ts | 4 +
apps/web/lib/referrals/auth.ts | 19 ++-
apps/web/lib/referrals/token.ts | 37 ++++++
packages/blocks/src/context.tsx | 27 ++++
packages/blocks/src/hooks/use-analytics.ts | 4 +
packages/blocks/src/hooks/use-events.ts | 4 +
packages/blocks/src/index.ts | 1 +
15 files changed, 295 insertions(+), 138 deletions(-)
create mode 100644 apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts
create mode 100644 apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
create mode 100644 apps/web/app/app.dub.co/embed/page.tsx
create mode 100644 apps/web/lib/referrals/token.ts
create mode 100644 packages/blocks/src/context.tsx
diff --git a/apps/web/app/api/referrals/tokens/route.ts b/apps/web/app/api/referrals/tokens/route.ts
index 8643ad9046..aeb2952b6d 100644
--- a/apps/web/app/api/referrals/tokens/route.ts
+++ b/apps/web/app/api/referrals/tokens/route.ts
@@ -1,16 +1,10 @@
-import { DubApiError } from "@/lib/api/errors";
import { parseRequestBody } from "@/lib/api/utils";
import { withWorkspace } from "@/lib/auth";
-import { prisma } from "@/lib/prisma";
-import {
- REFERRAL_PUBLIC_TOKEN_EXPIRY,
- REFERRAL_PUBLIC_TOKEN_LENGTH,
-} from "@/lib/referrals/constants";
+import { createPublicToken } from "@/lib/referrals/token";
import {
createReferralTokenSchema,
referralTokenSchema,
} from "@/lib/zod/schemas/referrals";
-import { nanoid } from "@dub/utils";
import { NextResponse } from "next/server";
// GET /api/referrals/tokens - create a new referral token for the given link
@@ -19,29 +13,9 @@ export const POST = withWorkspace(async ({ workspace, req }) => {
await parseRequestBody(req),
);
- const link = await prisma.link.findUniqueOrThrow({
- where: {
- id: linkId,
- projectId: workspace.id,
- },
- });
-
- if (!link.trackConversion) {
- throw new DubApiError({
- code: "forbidden",
- message: "Conversion tracking is not enabled for this link.",
- });
- }
-
- const referralToken = await prisma.referralPublicToken.create({
- data: {
- linkId,
- expires: new Date(Date.now() + REFERRAL_PUBLIC_TOKEN_EXPIRY),
- publicToken: nanoid(REFERRAL_PUBLIC_TOKEN_LENGTH),
- },
- });
+ const token = await createPublicToken({ linkId, workspaceId: workspace.id });
- return NextResponse.json(referralTokenSchema.parse(referralToken), {
+ return NextResponse.json(referralTokenSchema.parse(token), {
status: 201,
});
});
diff --git a/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts b/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts
new file mode 100644
index 0000000000..66a31c301b
--- /dev/null
+++ b/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts
@@ -0,0 +1,26 @@
+import { DubApiError } from "@/lib/api/errors";
+import { withWorkspace } from "@/lib/auth";
+import { createPublicToken } from "@/lib/referrals/token";
+import { referralTokenSchema } from "@/lib/zod/schemas/referrals";
+import { NextResponse } from "next/server";
+
+// GET /api/workspaces/[idOrSlug]/referrals-token - create a new referral token for the workspace
+export const POST = withWorkspace(async ({ workspace }) => {
+ const { referralLinkId, id } = workspace;
+
+ if (!referralLinkId) {
+ throw new DubApiError({
+ code: "bad_request",
+ message: "Referral link not found for this workspace.",
+ });
+ }
+
+ const token = await createPublicToken({
+ linkId: referralLinkId,
+ workspaceId: id,
+ });
+
+ return NextResponse.json(referralTokenSchema.parse(token), {
+ status: 201,
+ });
+});
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index acac0c8944..aa637d4dd2 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -1,19 +1,63 @@
"use client";
+import { EventType } from "@/lib/analytics/types";
import useWorkspace from "@/lib/swr/use-workspace";
import { redirect } from "next/navigation";
-import { ReactNode } from "react";
+import { useEffect, useState } from "react";
+import { toast } from "sonner";
+import { Referrals } from "./referrals";
+
+// TODO:
+// event and page should be part of iframe
+interface ReferralsPageClientProps {
+ event: EventType | undefined;
+ page: string | undefined;
+}
export default function ReferralsPageClient({
- children,
-}: {
- children: ReactNode;
-}) {
+ event,
+ page,
+}: ReferralsPageClientProps) {
const { slug, flags } = useWorkspace();
+ const [publicToken, setPublicToken] = useState(null);
+
+ // Get publicToken from server when component mounts
+ const createPublicToken = async () => {
+ const response = await fetch(`/api/workspaces/${slug}/referrals-token`, {
+ method: "POST",
+ });
+
+ if (!response.ok) {
+ throw toast.error("Failed to create public token");
+ }
+
+ const { publicToken } = (await response.json()) as {
+ publicToken: string;
+ };
+
+ setPublicToken(publicToken);
+ };
+
+ useEffect(() => {
+ createPublicToken();
+ }, []);
if (!flags?.referrals) {
redirect(`/${slug}/settings`);
}
- return <>{children}>;
+ if (!publicToken) {
+ return null;
+ }
+
+ return (
+ <>
+
+ >
+ );
}
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
index 6f8534e57e..3c0e349ab6 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
@@ -1,108 +1,16 @@
import { EventType } from "@/lib/analytics/types";
-import {
- REFERRAL_CLICKS_QUOTA_BONUS,
- REFERRAL_CLICKS_QUOTA_BONUS_MAX,
- REFERRAL_REVENUE_SHARE,
-} from "@/lib/referrals/constants";
-import { Wordmark } from "@dub/ui";
-import { Check } from "@dub/ui/src/icons";
-import { nFormatter } from "@dub/utils";
-import { Suspense } from "react";
-import { EventTabs } from "./event-tabs";
-import { Events } from "./events";
-import { HeroBackground } from "./hero-background";
import ReferralsPageClient from "./page-client";
-import ReferralLink, { ReferralLinkSkeleton } from "./referral-link";
-import { Stats } from "./stats";
-
-export const revalidate = 0;
export default function ReferralsPage({
- params: { slug },
searchParams,
}: {
- params: { slug: string };
searchParams: { event?: EventType; page?: string };
}) {
- const event = searchParams.event || "clicks";
- const page = searchParams.page || "1";
+ const { event, page } = searchParams;
return (
-
-
-
-
-
-
-
-
-
-
- Refer and earn
-
-
- {/* Benefits */}
-
- {[
- {
- title: `${nFormatter(REFERRAL_REVENUE_SHARE * 100)}% recurring revenue`,
- description: "per paying customer (up to 1 year)",
- },
- {
- title: `${nFormatter(REFERRAL_CLICKS_QUOTA_BONUS)} extra clicks quota per month`,
- description: `per signup (up to ${nFormatter(REFERRAL_CLICKS_QUOTA_BONUS_MAX, { full: true })} total)`,
- },
- ].map(({ title, description }) => (
-
-
-
-
- {title}
-
-
{description}
-
-
- ))}
-
-
- {/* Referral link + invite button or empty/error states */}
-
}>
-
-
-
-
-
- {/* Powered by Dub Conversions */}
-
-
-
- Powered by Dub Conversions
-
-
-
-
- {/* Stats */}
-
-
-
-
- {/* Events */}
-
-
-
+ <>
+
+ >
);
}
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referral-link.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referral-link.tsx
index 7be3b58c09..abc64d83dc 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referral-link.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referral-link.tsx
@@ -1,11 +1,10 @@
-import { getReferralLink } from "@/lib/actions/get-referral-link";
import { CopyButton } from "@dub/ui";
import { getPrettyUrl } from "@dub/utils";
import { GenerateButton } from "./generate-button";
import { InviteButton } from "./invite-button";
export default async function ReferralLink({ slug }: { slug: string }) {
- const { shortLink } = (await getReferralLink(slug)) || {};
+ const shortLink = "demo";
return (
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
new file mode 100644
index 0000000000..10d924c046
--- /dev/null
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
@@ -0,0 +1,115 @@
+"use client";
+
+import { EventType } from "@/lib/analytics/types";
+import {
+ REFERRAL_CLICKS_QUOTA_BONUS,
+ REFERRAL_CLICKS_QUOTA_BONUS_MAX,
+ REFERRAL_REVENUE_SHARE,
+} from "@/lib/referrals/constants";
+import { DubProvider } from "@dub/blocks";
+import { Wordmark } from "@dub/ui";
+import { Check } from "@dub/ui/src/icons";
+import { nFormatter } from "@dub/utils";
+import { Suspense } from "react";
+import { EventTabs } from "./event-tabs";
+import { Events } from "./events";
+import { HeroBackground } from "./hero-background";
+import ReferralLink, { ReferralLinkSkeleton } from "./referral-link";
+import { Stats } from "./stats";
+
+interface ReferralsProps {
+ slug: string;
+ event: EventType | undefined;
+ page: string | undefined;
+ publicToken: string | undefined | null;
+}
+
+export const Referrals = ({
+ slug,
+ event,
+ page,
+ publicToken,
+}: ReferralsProps) => {
+ if (!publicToken) {
+ return
;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ Refer and earn
+
+
+ {/* Benefits */}
+
+ {[
+ {
+ title: `${nFormatter(REFERRAL_REVENUE_SHARE * 100)}% recurring revenue`,
+ description: "per paying customer (up to 1 year)",
+ },
+ {
+ title: `${nFormatter(REFERRAL_CLICKS_QUOTA_BONUS)} extra clicks quota per month`,
+ description: `per signup (up to ${nFormatter(REFERRAL_CLICKS_QUOTA_BONUS_MAX, { full: true })} total)`,
+ },
+ ].map(({ title, description }) => (
+
+
+
+
+ {title}
+
+
{description}
+
+
+ ))}
+
+
+ {/* Referral link + invite button or empty/error states */}
+
}>
+
+
+
+
+
+ {/* Powered by Dub Conversions */}
+
+
+
+ Powered by Dub Conversions
+
+
+
+
+ {/* Stats */}
+
+
+
+
+ {/* Events */}
+
+
+
+ );
+};
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
index 50f919fc35..2925b93fca 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
@@ -40,6 +40,8 @@ export const Stats = () => {
const loading = isLoadingSales || isLoadingTotalEvents;
+ console.log({ sales, totalEvents });
+
return (
{loading ? (
diff --git a/apps/web/app/app.dub.co/embed/page.tsx b/apps/web/app/app.dub.co/embed/page.tsx
new file mode 100644
index 0000000000..23e123c025
--- /dev/null
+++ b/apps/web/app/app.dub.co/embed/page.tsx
@@ -0,0 +1,3 @@
+export default function EmbedPage() {
+ return
Embed Page
;
+}
diff --git a/apps/web/lib/middleware/app.ts b/apps/web/lib/middleware/app.ts
index 9b8daa6330..a173b09390 100644
--- a/apps/web/lib/middleware/app.ts
+++ b/apps/web/lib/middleware/app.ts
@@ -12,6 +12,10 @@ export default async function AppMiddleware(req: NextRequest) {
const user = await getUserViaToken(req);
const isWorkspaceInvite = req.nextUrl.searchParams.get("invite");
+ if (path.startsWith("/embed")) {
+ return NextResponse.rewrite(new URL(`/app.dub.co${fullPath}`, req.url));
+ }
+
// if there's no user and the path isn't /login or /register, redirect to /login
if (
!user &&
diff --git a/apps/web/lib/referrals/auth.ts b/apps/web/lib/referrals/auth.ts
index f7a6448486..cb19197e1f 100644
--- a/apps/web/lib/referrals/auth.ts
+++ b/apps/web/lib/referrals/auth.ts
@@ -33,9 +33,10 @@ export const withAuth = (handler: WithAuthHandler) => {
let link: Link | undefined = undefined;
let tokenFromHeader: string | undefined = undefined;
- const rateLimit = 100;
+ const rateLimit = 60;
const searchParams = getSearchParams(req.url);
- const authorizationHeader = req.headers.get("Authorization");
+
+ // const authorizationHeader = req.headers.get("Authorization");
// if (!authorizationHeader) {
// throw new DubApiError({
@@ -61,11 +62,19 @@ export const withAuth = (handler: WithAuthHandler) => {
// });
// }
- tokenFromHeader = "i2yisemInUCWbWnEXB1WR4b3ROe2lLccYulj";
+ // Read token from query params
+ const tokenFromQuery = searchParams["publicToken"];
+
+ if (!tokenFromQuery) {
+ throw new DubApiError({
+ code: "unauthorized",
+ message: "Missing public token.",
+ });
+ }
const publicToken = await prisma.referralPublicToken.findUnique({
where: {
- publicToken: tokenFromHeader,
+ publicToken: tokenFromQuery,
},
});
@@ -86,7 +95,7 @@ export const withAuth = (handler: WithAuthHandler) => {
const { success, limit, reset, remaining } = await ratelimit(
rateLimit,
"1 m",
- ).limit(tokenFromHeader);
+ ).limit(tokenFromQuery);
headers = {
"Retry-After": reset.toString(),
diff --git a/apps/web/lib/referrals/token.ts b/apps/web/lib/referrals/token.ts
new file mode 100644
index 0000000000..04e8638ec4
--- /dev/null
+++ b/apps/web/lib/referrals/token.ts
@@ -0,0 +1,37 @@
+import { prisma } from "@/lib/prisma";
+import { nanoid } from "@dub/utils";
+import { DubApiError } from "../api/errors";
+import {
+ REFERRAL_PUBLIC_TOKEN_EXPIRY,
+ REFERRAL_PUBLIC_TOKEN_LENGTH,
+} from "./constants";
+
+export const createPublicToken = async ({
+ linkId,
+ workspaceId,
+}: {
+ linkId: string;
+ workspaceId: string;
+}) => {
+ const link = await prisma.link.findUniqueOrThrow({
+ where: {
+ id: linkId,
+ projectId: workspaceId,
+ },
+ });
+
+ if (!link.trackConversion) {
+ throw new DubApiError({
+ code: "forbidden",
+ message: "Conversion tracking is not enabled for this link.",
+ });
+ }
+
+ return await prisma.referralPublicToken.create({
+ data: {
+ linkId,
+ expires: new Date(Date.now() + REFERRAL_PUBLIC_TOKEN_EXPIRY),
+ publicToken: nanoid(REFERRAL_PUBLIC_TOKEN_LENGTH),
+ },
+ });
+};
diff --git a/packages/blocks/src/context.tsx b/packages/blocks/src/context.tsx
new file mode 100644
index 0000000000..44b818ed41
--- /dev/null
+++ b/packages/blocks/src/context.tsx
@@ -0,0 +1,27 @@
+import React, { createContext, useContext } from "react";
+
+interface DubContextType {
+ publicToken: string;
+}
+
+const DubContext = createContext
(undefined);
+
+export const DubProvider: React.FC<
+ DubContextType & { children: React.ReactNode }
+> = ({ publicToken, children }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export const useDub = () => {
+ const context = useContext(DubContext);
+
+ if (context === undefined) {
+ throw new Error("useDub must be used within a DubProvider");
+ }
+
+ return context;
+};
diff --git a/packages/blocks/src/hooks/use-analytics.ts b/packages/blocks/src/hooks/use-analytics.ts
index daa44ed860..24086cda7a 100644
--- a/packages/blocks/src/hooks/use-analytics.ts
+++ b/packages/blocks/src/hooks/use-analytics.ts
@@ -1,5 +1,6 @@
import { fetcher } from "@dub/utils";
import useSWR from "swr";
+import { useDub } from "../context";
import { EventType } from "../types";
interface UseAnalyticsParams {
@@ -13,10 +14,13 @@ export const useAnalytics = ({
interval,
groupBy,
}: UseAnalyticsParams) => {
+ const { publicToken } = useDub();
+
const searchParams = new URLSearchParams({
event,
interval,
groupBy,
+ publicToken,
});
const { error, data, isLoading } = useSWR(
diff --git a/packages/blocks/src/hooks/use-events.ts b/packages/blocks/src/hooks/use-events.ts
index 5ea3bfbd96..b575a758c7 100644
--- a/packages/blocks/src/hooks/use-events.ts
+++ b/packages/blocks/src/hooks/use-events.ts
@@ -1,5 +1,6 @@
import { fetcher } from "@dub/utils";
import useSWR from "swr";
+import { useDub } from "../context";
import { EventType } from "../types";
interface UseEventsParams {
@@ -9,10 +10,13 @@ interface UseEventsParams {
}
export const useEvents = ({ event, interval, page }: UseEventsParams) => {
+ const { publicToken } = useDub();
+
const searchParams = new URLSearchParams({
event,
interval,
page,
+ publicToken,
});
const { error, data, isLoading } = useSWR<[]>(
diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts
index 3a7736b51e..cfba1d9737 100644
--- a/packages/blocks/src/index.ts
+++ b/packages/blocks/src/index.ts
@@ -1,6 +1,7 @@
// styles
import "./styles.css";
+export * from "./context";
export * from "./empty-state";
export * from "./event-list";
export * from "./gauge";
From 44578137ba5a039e3e71604f45473360f7d14741 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Fri, 4 Oct 2024 16:58:35 +0530
Subject: [PATCH 13/88] run prettier
---
.../[slug]/settings/referrals/page-client.tsx | 9 +++++----
apps/web/ui/modals/link-builder/index.tsx | 8 +-------
2 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index aa637d4dd2..cd84d350d7 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -3,8 +3,9 @@
import { EventType } from "@/lib/analytics/types";
import useWorkspace from "@/lib/swr/use-workspace";
import { redirect } from "next/navigation";
-import { useEffect, useState } from "react";
+import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
+import { ReferralLinkSkeleton } from "./referral-link";
import { Referrals } from "./referrals";
// TODO:
@@ -22,7 +23,7 @@ export default function ReferralsPageClient({
const [publicToken, setPublicToken] = useState(null);
// Get publicToken from server when component mounts
- const createPublicToken = async () => {
+ const createPublicToken = useCallback(async () => {
const response = await fetch(`/api/workspaces/${slug}/referrals-token`, {
method: "POST",
});
@@ -36,7 +37,7 @@ export default function ReferralsPageClient({
};
setPublicToken(publicToken);
- };
+ }, [slug]);
useEffect(() => {
createPublicToken();
@@ -47,7 +48,7 @@ export default function ReferralsPageClient({
}
if (!publicToken) {
- return null;
+ return ;
}
return (
diff --git a/apps/web/ui/modals/link-builder/index.tsx b/apps/web/ui/modals/link-builder/index.tsx
index cdd7d99e40..045039c7ba 100644
--- a/apps/web/ui/modals/link-builder/index.tsx
+++ b/apps/web/ui/modals/link-builder/index.tsx
@@ -169,13 +169,7 @@ function LinkBuilderInner({
isSubmitSuccessful ||
(props && !isDirty),
);
- }, [
- showLinkBuilder,
- isSubmitting,
- isSubmitSuccessful,
- props,
- isDirty,
- ]);
+ }, [showLinkBuilder, isSubmitting, isSubmitSuccessful, props, isDirty]);
const keyRef = useRef(null);
useEffect(() => {
From 7dc970018f44043e05b3d56ce9b57da0ceaf2cd7 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Fri, 4 Oct 2024 17:00:10 +0530
Subject: [PATCH 14/88] revert
---
apps/web/app/api/analytics/route.ts | 7 ++++---
apps/web/app/api/events/route.ts | 7 ++++---
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/apps/web/app/api/analytics/route.ts b/apps/web/app/api/analytics/route.ts
index 012c7014c2..1f0ee4bd8d 100644
--- a/apps/web/app/api/analytics/route.ts
+++ b/apps/web/app/api/analytics/route.ts
@@ -1,6 +1,7 @@
import { VALID_ANALYTICS_ENDPOINTS } from "@/lib/analytics/constants";
import { getAnalytics } from "@/lib/analytics/get-analytics";
import { validDateRangeForPlan } from "@/lib/analytics/utils";
+import { getDomainOrThrow } from "@/lib/api/domains/get-domain-or-throw";
import { getLinkOrThrow } from "@/lib/api/links/get-link-or-throw";
import { throwIfClicksUsageExceeded } from "@/lib/api/links/usage-checks";
import { withWorkspace } from "@/lib/auth";
@@ -40,9 +41,9 @@ export const GET = withWorkspace(
} = parsedParams;
let link: Link | null = null;
- // if (domain) {
- // await getDomainOrThrow({ workspace, domain });
- // }
+ if (domain) {
+ await getDomainOrThrow({ workspace, domain });
+ }
if (linkId || externalId || (domain && key)) {
link = await getLinkOrThrow({
diff --git a/apps/web/app/api/events/route.ts b/apps/web/app/api/events/route.ts
index 5f1e38fb5c..f543318ea9 100644
--- a/apps/web/app/api/events/route.ts
+++ b/apps/web/app/api/events/route.ts
@@ -1,5 +1,6 @@
import { getEvents } from "@/lib/analytics/get-events";
import { validDateRangeForPlan } from "@/lib/analytics/utils";
+import { getDomainOrThrow } from "@/lib/api/domains/get-domain-or-throw";
import { getLinkOrThrow } from "@/lib/api/links/get-link-or-throw";
import { throwIfClicksUsageExceeded } from "@/lib/api/links/usage-checks";
import { withWorkspace } from "@/lib/auth";
@@ -17,9 +18,9 @@ export const GET = withWorkspace(
parsedParams;
let link: Link | null = null;
- // if (domain) {
- // await getDomainOrThrow({ workspace, domain });
- // }
+ if (domain) {
+ await getDomainOrThrow({ workspace, domain });
+ }
if (linkId || externalId || (domain && key)) {
link = await getLinkOrThrow({
From 2d01ee012bf271a151b7279f1f98ddbf949c9486 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Fri, 4 Oct 2024 19:02:12 +0530
Subject: [PATCH 15/88] wip iframe
---
.../[slug]/settings/referrals/referrals.tsx | 9 ++++++++-
.../[slug]/settings/referrals/stats.tsx | 2 --
apps/web/app/app.dub.co/embed/page.tsx | 17 +++++++++++++++--
apps/web/lib/middleware/app.ts | 8 +++++++-
apps/web/lib/referrals/auth.ts | 19 +++++++++++++++----
packages/blocks/src/hooks/use-analytics.ts | 4 ----
packages/blocks/src/hooks/use-events.ts | 4 ----
7 files changed, 45 insertions(+), 18 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
index 10d924c046..e0feaeeb4b 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
@@ -31,7 +31,14 @@ export const Referrals = ({
publicToken,
}: ReferralsProps) => {
if (!publicToken) {
- return ;
+ return (
+
+
+ Unavailable
+
+
Sorry, the referral token is not found.
+
+ );
}
return (
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
index 2925b93fca..50f919fc35 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
@@ -40,8 +40,6 @@ export const Stats = () => {
const loading = isLoadingSales || isLoadingTotalEvents;
- console.log({ sales, totalEvents });
-
return (
{loading ? (
diff --git a/apps/web/app/app.dub.co/embed/page.tsx b/apps/web/app/app.dub.co/embed/page.tsx
index 23e123c025..2152305194 100644
--- a/apps/web/app/app.dub.co/embed/page.tsx
+++ b/apps/web/app/app.dub.co/embed/page.tsx
@@ -1,3 +1,16 @@
-export default function EmbedPage() {
- return
Embed Page
;
+import { EventType } from "@/lib/analytics/types";
+import { Referrals } from "../(dashboard)/[slug]/settings/referrals/referrals";
+
+export default async function EmbedPage({
+ searchParams,
+}: {
+ searchParams: { token: string; event?: EventType; page?: string };
+}) {
+ const { token, event, page } = searchParams;
+
+ return (
+ <>
+
+ >
+ );
}
diff --git a/apps/web/lib/middleware/app.ts b/apps/web/lib/middleware/app.ts
index a173b09390..ee21346047 100644
--- a/apps/web/lib/middleware/app.ts
+++ b/apps/web/lib/middleware/app.ts
@@ -13,7 +13,13 @@ export default async function AppMiddleware(req: NextRequest) {
const isWorkspaceInvite = req.nextUrl.searchParams.get("invite");
if (path.startsWith("/embed")) {
- return NextResponse.rewrite(new URL(`/app.dub.co${fullPath}`, req.url));
+ return NextResponse.rewrite(new URL(`/app.dub.co${fullPath}`, req.url), {
+ headers: {
+ // TODO: Need better cookie name
+ // Maybe move this to a API route level?
+ "Set-Cookie": `token=${req.nextUrl.searchParams.get("token")}; HttpOnly; Path=/`,
+ },
+ });
}
// if there's no user and the path isn't /login or /register, redirect to /login
diff --git a/apps/web/lib/referrals/auth.ts b/apps/web/lib/referrals/auth.ts
index cb19197e1f..b73cc9d551 100644
--- a/apps/web/lib/referrals/auth.ts
+++ b/apps/web/lib/referrals/auth.ts
@@ -4,6 +4,7 @@ import { ratelimit } from "@/lib/upstash";
import { getSearchParams } from "@dub/utils";
import { Link, Project } from "@prisma/client";
import { AxiomRequest, withAxiom } from "next-axiom";
+import { cookies } from "next/headers";
interface WithAuthHandler {
({
@@ -63,9 +64,19 @@ export const withAuth = (handler: WithAuthHandler) => {
// }
// Read token from query params
- const tokenFromQuery = searchParams["publicToken"];
+ // const tokenFromQuery = searchParams["publicToken"];
- if (!tokenFromQuery) {
+ // if (!tokenFromQuery) {
+ // throw new DubApiError({
+ // code: "unauthorized",
+ // message: "Missing public token.",
+ // });
+ // }
+
+ const cookieStore = cookies();
+ const tokenFromCookie = cookieStore.get("token")?.value;
+
+ if (!tokenFromCookie) {
throw new DubApiError({
code: "unauthorized",
message: "Missing public token.",
@@ -74,7 +85,7 @@ export const withAuth = (handler: WithAuthHandler) => {
const publicToken = await prisma.referralPublicToken.findUnique({
where: {
- publicToken: tokenFromQuery,
+ publicToken: tokenFromCookie,
},
});
@@ -95,7 +106,7 @@ export const withAuth = (handler: WithAuthHandler) => {
const { success, limit, reset, remaining } = await ratelimit(
rateLimit,
"1 m",
- ).limit(tokenFromQuery);
+ ).limit(tokenFromCookie);
headers = {
"Retry-After": reset.toString(),
diff --git a/packages/blocks/src/hooks/use-analytics.ts b/packages/blocks/src/hooks/use-analytics.ts
index 24086cda7a..daa44ed860 100644
--- a/packages/blocks/src/hooks/use-analytics.ts
+++ b/packages/blocks/src/hooks/use-analytics.ts
@@ -1,6 +1,5 @@
import { fetcher } from "@dub/utils";
import useSWR from "swr";
-import { useDub } from "../context";
import { EventType } from "../types";
interface UseAnalyticsParams {
@@ -14,13 +13,10 @@ export const useAnalytics = ({
interval,
groupBy,
}: UseAnalyticsParams) => {
- const { publicToken } = useDub();
-
const searchParams = new URLSearchParams({
event,
interval,
groupBy,
- publicToken,
});
const { error, data, isLoading } = useSWR(
diff --git a/packages/blocks/src/hooks/use-events.ts b/packages/blocks/src/hooks/use-events.ts
index b575a758c7..5ea3bfbd96 100644
--- a/packages/blocks/src/hooks/use-events.ts
+++ b/packages/blocks/src/hooks/use-events.ts
@@ -1,6 +1,5 @@
import { fetcher } from "@dub/utils";
import useSWR from "swr";
-import { useDub } from "../context";
import { EventType } from "../types";
interface UseEventsParams {
@@ -10,13 +9,10 @@ interface UseEventsParams {
}
export const useEvents = ({ event, interval, page }: UseEventsParams) => {
- const { publicToken } = useDub();
-
const searchParams = new URLSearchParams({
event,
interval,
page,
- publicToken,
});
const { error, data, isLoading } = useSWR<[]>(
From 9631595359b97c431034b667fb20c0dc046df578 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Fri, 4 Oct 2024 19:19:50 +0530
Subject: [PATCH 16/88] add sample iframe
---
.../[slug]/settings/referrals/page-client.tsx | 13 ++-----------
.../[slug]/settings/referrals/referrals.tsx | 15 +++++++++++++++
apps/web/next.config.js | 9 +++++++++
3 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index cd84d350d7..d908bc0c07 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -6,7 +6,7 @@ import { redirect } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { ReferralLinkSkeleton } from "./referral-link";
-import { Referrals } from "./referrals";
+import { ReferralsEmbed } from "./referrals";
// TODO:
// event and page should be part of iframe
@@ -51,14 +51,5 @@ export default function ReferralsPageClient({
return ;
}
- return (
- <>
-
- >
- );
+ return ;
}
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
index e0feaeeb4b..56da1b98e4 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
@@ -120,3 +120,18 @@ export const Referrals = ({
);
};
+
+export const ReferralsEmbed = ({ publicToken }: { publicToken: string }) => {
+ return (
+ <>
+
+ >
+ );
+};
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index 3e1a4860ab..4512003839 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -94,6 +94,15 @@ module.exports = withAxiom({
},
],
},
+ {
+ source: "/embed",
+ headers: [
+ {
+ key: "X-Frame-Options",
+ value: "ALLOW",
+ },
+ ],
+ },
];
},
async redirects() {
From abf09096bee676d671c3388420357d091710b21c Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 7 Oct 2024 16:28:27 +0530
Subject: [PATCH 17/88] move the hero section
---
.../[slug]/settings/referrals/page-client.tsx | 89 +++++++++++++++---
.../[slug]/settings/referrals/page.tsx | 11 +--
.../settings/referrals/referral-link.tsx | 3 +-
.../[slug]/settings/referrals/referrals.tsx | 92 ++-----------------
apps/web/lib/actions/get-referral-link.ts | 2 +
5 files changed, 90 insertions(+), 107 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index d908bc0c07..a9147c229b 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -1,24 +1,21 @@
"use client";
-import { EventType } from "@/lib/analytics/types";
+import {
+ REFERRAL_CLICKS_QUOTA_BONUS,
+ REFERRAL_CLICKS_QUOTA_BONUS_MAX,
+ REFERRAL_REVENUE_SHARE,
+} from "@/lib/referrals/constants";
import useWorkspace from "@/lib/swr/use-workspace";
+import { Check, Wordmark } from "@dub/ui";
+import { nFormatter } from "@dub/utils";
import { redirect } from "next/navigation";
-import { useCallback, useEffect, useState } from "react";
+import { Suspense, useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
-import { ReferralLinkSkeleton } from "./referral-link";
+import { HeroBackground } from "./hero-background";
+import ReferralLink, { ReferralLinkSkeleton } from "./referral-link";
import { ReferralsEmbed } from "./referrals";
-// TODO:
-// event and page should be part of iframe
-interface ReferralsPageClientProps {
- event: EventType | undefined;
- page: string | undefined;
-}
-
-export default function ReferralsPageClient({
- event,
- page,
-}: ReferralsPageClientProps) {
+export default function ReferralsPageClient() {
const { slug, flags } = useWorkspace();
const [publicToken, setPublicToken] = useState(null);
@@ -51,5 +48,67 @@ export default function ReferralsPageClient({
return ;
}
- return ;
+ return (
+
+
+
+
+
+
+
+
+
+ Refer and earn
+
+
+ {/* Benefits */}
+
+ {[
+ {
+ title: `${nFormatter(REFERRAL_REVENUE_SHARE * 100)}% recurring revenue`,
+ description: "per paying customer (up to 1 year)",
+ },
+ {
+ title: `${nFormatter(REFERRAL_CLICKS_QUOTA_BONUS)} extra clicks quota per month`,
+ description: `per signup (up to ${nFormatter(REFERRAL_CLICKS_QUOTA_BONUS_MAX, { full: true })} total)`,
+ },
+ ].map(({ title, description }) => (
+
+
+
+
+ {title}
+
+
{description}
+
+
+ ))}
+
+
+ {/* Referral link + invite button or empty/error states */}
+
}>
+
+
+
+
+
+ {/* Powered by Dub Conversions */}
+
+
+
+ Powered by Dub Conversions
+
+
+
+
;
+
+ );
}
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
index 3c0e349ab6..7eb9276d35 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
@@ -1,16 +1,9 @@
-import { EventType } from "@/lib/analytics/types";
import ReferralsPageClient from "./page-client";
-export default function ReferralsPage({
- searchParams,
-}: {
- searchParams: { event?: EventType; page?: string };
-}) {
- const { event, page } = searchParams;
-
+export default async function ReferralsPage() {
return (
<>
-
+
>
);
}
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referral-link.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referral-link.tsx
index abc64d83dc..7be3b58c09 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referral-link.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referral-link.tsx
@@ -1,10 +1,11 @@
+import { getReferralLink } from "@/lib/actions/get-referral-link";
import { CopyButton } from "@dub/ui";
import { getPrettyUrl } from "@dub/utils";
import { GenerateButton } from "./generate-button";
import { InviteButton } from "./invite-button";
export default async function ReferralLink({ slug }: { slug: string }) {
- const shortLink = "demo";
+ const { shortLink } = (await getReferralLink(slug)) || {};
return (
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
index 56da1b98e4..0b72e98c47 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
@@ -1,20 +1,9 @@
"use client";
import { EventType } from "@/lib/analytics/types";
-import {
- REFERRAL_CLICKS_QUOTA_BONUS,
- REFERRAL_CLICKS_QUOTA_BONUS_MAX,
- REFERRAL_REVENUE_SHARE,
-} from "@/lib/referrals/constants";
import { DubProvider } from "@dub/blocks";
-import { Wordmark } from "@dub/ui";
-import { Check } from "@dub/ui/src/icons";
-import { nFormatter } from "@dub/utils";
-import { Suspense } from "react";
import { EventTabs } from "./event-tabs";
import { Events } from "./events";
-import { HeroBackground } from "./hero-background";
-import ReferralLink, { ReferralLinkSkeleton } from "./referral-link";
import { Stats } from "./stats";
interface ReferralsProps {
@@ -43,79 +32,18 @@ export const Referrals = ({
return (
-
-
-
-
-
-
-
-
-
- Refer and earn
-
-
- {/* Benefits */}
-
- {[
- {
- title: `${nFormatter(REFERRAL_REVENUE_SHARE * 100)}% recurring revenue`,
- description: "per paying customer (up to 1 year)",
- },
- {
- title: `${nFormatter(REFERRAL_CLICKS_QUOTA_BONUS)} extra clicks quota per month`,
- description: `per signup (up to ${nFormatter(REFERRAL_CLICKS_QUOTA_BONUS_MAX, { full: true })} total)`,
- },
- ].map(({ title, description }) => (
-
-
-
-
- {title}
-
-
{description}
-
-
- ))}
-
-
- {/* Referral link + invite button or empty/error states */}
-
}>
-
-
-
-
-
- {/* Powered by Dub Conversions */}
-
-
-
- Powered by Dub Conversions
-
-
-
-
- {/* Stats */}
-
-
-
+ {/* Stats */}
+
+
+
- {/* Events */}
-
-
-
Activity
-
-
-
+ {/* Events */}
+
);
diff --git a/apps/web/lib/actions/get-referral-link.ts b/apps/web/lib/actions/get-referral-link.ts
index 5af75ebec7..19644c4339 100644
--- a/apps/web/lib/actions/get-referral-link.ts
+++ b/apps/web/lib/actions/get-referral-link.ts
@@ -9,9 +9,11 @@ export const getReferralLink = async (slug: string) => {
slug,
},
});
+
if (!workspace || !workspace.referralLinkId) {
return null;
}
+
return await dub.links.get({
linkId: workspace.referralLinkId,
});
From aca0fbc891f07af31b210c9ae50eec7d6716f6bd Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 7 Oct 2024 16:34:46 +0530
Subject: [PATCH 18/88] move the files to /embed
---
.../(dashboard)/[slug]/settings/referrals/constants.ts | 0
.../[slug]/settings/referrals/page-client.tsx | 2 +-
.../[slug]/settings/referrals => embed}/event-tabs.tsx | 0
.../[slug]/settings/referrals => embed}/events.tsx | 0
apps/web/app/app.dub.co/embed/page.tsx | 2 +-
.../[slug]/settings/referrals => embed}/placeholders.tsx | 0
.../[slug]/settings/referrals => embed}/referrals.tsx | 9 ++-------
.../[slug]/settings/referrals => embed}/stats.tsx | 0
8 files changed, 4 insertions(+), 9 deletions(-)
delete mode 100644 apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/constants.ts
rename apps/web/app/app.dub.co/{(dashboard)/[slug]/settings/referrals => embed}/event-tabs.tsx (100%)
rename apps/web/app/app.dub.co/{(dashboard)/[slug]/settings/referrals => embed}/events.tsx (100%)
rename apps/web/app/app.dub.co/{(dashboard)/[slug]/settings/referrals => embed}/placeholders.tsx (100%)
rename apps/web/app/app.dub.co/{(dashboard)/[slug]/settings/referrals => embed}/referrals.tsx (89%)
rename apps/web/app/app.dub.co/{(dashboard)/[slug]/settings/referrals => embed}/stats.tsx (100%)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/constants.ts b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/constants.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index a9147c229b..94d56952c9 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -13,7 +13,7 @@ import { Suspense, useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { HeroBackground } from "./hero-background";
import ReferralLink, { ReferralLinkSkeleton } from "./referral-link";
-import { ReferralsEmbed } from "./referrals";
+import { ReferralsEmbed } from "../../../../embed/referrals";
export default function ReferralsPageClient() {
const { slug, flags } = useWorkspace();
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/event-tabs.tsx b/apps/web/app/app.dub.co/embed/event-tabs.tsx
similarity index 100%
rename from apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/event-tabs.tsx
rename to apps/web/app/app.dub.co/embed/event-tabs.tsx
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx b/apps/web/app/app.dub.co/embed/events.tsx
similarity index 100%
rename from apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/events.tsx
rename to apps/web/app/app.dub.co/embed/events.tsx
diff --git a/apps/web/app/app.dub.co/embed/page.tsx b/apps/web/app/app.dub.co/embed/page.tsx
index 2152305194..ac800f5962 100644
--- a/apps/web/app/app.dub.co/embed/page.tsx
+++ b/apps/web/app/app.dub.co/embed/page.tsx
@@ -1,5 +1,5 @@
import { EventType } from "@/lib/analytics/types";
-import { Referrals } from "../(dashboard)/[slug]/settings/referrals/referrals";
+import { Referrals } from "./referrals";
export default async function EmbedPage({
searchParams,
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/placeholders.tsx b/apps/web/app/app.dub.co/embed/placeholders.tsx
similarity index 100%
rename from apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/placeholders.tsx
rename to apps/web/app/app.dub.co/embed/placeholders.tsx
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx b/apps/web/app/app.dub.co/embed/referrals.tsx
similarity index 89%
rename from apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
rename to apps/web/app/app.dub.co/embed/referrals.tsx
index 0b72e98c47..0869b901fe 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referrals.tsx
+++ b/apps/web/app/app.dub.co/embed/referrals.tsx
@@ -13,19 +13,14 @@ interface ReferralsProps {
publicToken: string | undefined | null;
}
-export const Referrals = ({
- slug,
- event,
- page,
- publicToken,
-}: ReferralsProps) => {
+export const Referrals = ({ event, page, publicToken }: ReferralsProps) => {
if (!publicToken) {
return (
Unavailable
-
Sorry, the referral token is not found.
+
The referral token is not found.
);
}
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx b/apps/web/app/app.dub.co/embed/stats.tsx
similarity index 100%
rename from apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/stats.tsx
rename to apps/web/app/app.dub.co/embed/stats.tsx
From 19cdefaecb5fc8577213cdfc6ea9624a13e2732e Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 7 Oct 2024 16:39:19 +0530
Subject: [PATCH 19/88] move to @dub/blocks
---
.../[slug]/settings/referrals/page-client.tsx | 4 ++--
apps/web/app/app.dub.co/embed/referrals.tsx | 15 ---------------
packages/blocks/src/index.ts | 1 +
packages/blocks/src/referrals-embed.tsx | 14 ++++++++++++++
4 files changed, 17 insertions(+), 17 deletions(-)
create mode 100644 packages/blocks/src/referrals-embed.tsx
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index 94d56952c9..ba12d164c5 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -6,6 +6,7 @@ import {
REFERRAL_REVENUE_SHARE,
} from "@/lib/referrals/constants";
import useWorkspace from "@/lib/swr/use-workspace";
+import { ReferralsEmbed } from "@dub/blocks";
import { Check, Wordmark } from "@dub/ui";
import { nFormatter } from "@dub/utils";
import { redirect } from "next/navigation";
@@ -13,7 +14,6 @@ import { Suspense, useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { HeroBackground } from "./hero-background";
import ReferralLink, { ReferralLinkSkeleton } from "./referral-link";
-import { ReferralsEmbed } from "../../../../embed/referrals";
export default function ReferralsPageClient() {
const { slug, flags } = useWorkspace();
@@ -91,7 +91,7 @@ export default function ReferralsPageClient() {
{/* Referral link + invite button or empty/error states */}
}>
-
+ {/* */}
diff --git a/apps/web/app/app.dub.co/embed/referrals.tsx b/apps/web/app/app.dub.co/embed/referrals.tsx
index 0869b901fe..4d912de946 100644
--- a/apps/web/app/app.dub.co/embed/referrals.tsx
+++ b/apps/web/app/app.dub.co/embed/referrals.tsx
@@ -43,18 +43,3 @@ export const Referrals = ({ event, page, publicToken }: ReferralsProps) => {
);
};
-
-export const ReferralsEmbed = ({ publicToken }: { publicToken: string }) => {
- return (
- <>
-
- >
- );
-};
diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts
index cfba1d9737..b77b774e74 100644
--- a/packages/blocks/src/index.ts
+++ b/packages/blocks/src/index.ts
@@ -9,4 +9,5 @@ export * from "./hooks/use-analytics";
export * from "./hooks/use-events";
export * from "./mini-area-chart";
export * from "./pagination-controls";
+export * from "./referrals-embed";
export * from "./stats-card";
diff --git a/packages/blocks/src/referrals-embed.tsx b/packages/blocks/src/referrals-embed.tsx
new file mode 100644
index 0000000000..3a9e20d8bb
--- /dev/null
+++ b/packages/blocks/src/referrals-embed.tsx
@@ -0,0 +1,14 @@
+export const ReferralsEmbed = ({ publicToken }: { publicToken: string }) => {
+ return (
+ <>
+
+ >
+ );
+};
From 06b00e0f8b2e1a3f4325fc2e7b35e449e84533d9 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 7 Oct 2024 17:11:58 +0530
Subject: [PATCH 20/88] format
---
.../(dashboard)/[slug]/settings/referrals/page-client.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index ba12d164c5..520bf7996e 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -13,7 +13,7 @@ import { redirect } from "next/navigation";
import { Suspense, useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { HeroBackground } from "./hero-background";
-import ReferralLink, { ReferralLinkSkeleton } from "./referral-link";
+import { ReferralLinkSkeleton } from "./referral-link";
export default function ReferralsPageClient() {
const { slug, flags } = useWorkspace();
From 4b0c3c5bacda76c53fdcb9a3147e2c09320cee9c Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 7 Oct 2024 17:20:43 +0530
Subject: [PATCH 21/88] use APP_DOMAIN
---
.../[slug]/settings/referrals/page-client.tsx | 7 ++++---
packages/blocks/src/referrals-embed.tsx | 20 +++++++++----------
2 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index 520bf7996e..4fd978c840 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -13,7 +13,7 @@ import { redirect } from "next/navigation";
import { Suspense, useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { HeroBackground } from "./hero-background";
-import { ReferralLinkSkeleton } from "./referral-link";
+import ReferralLink, { ReferralLinkSkeleton } from "./referral-link";
export default function ReferralsPageClient() {
const { slug, flags } = useWorkspace();
@@ -91,7 +91,7 @@ export default function ReferralsPageClient() {
{/* Referral link + invite button or empty/error states */}
}>
- {/* */}
+
@@ -108,7 +108,8 @@ export default function ReferralsPageClient() {
-
;
+ {/* Embed Dub */}
+
);
}
diff --git a/packages/blocks/src/referrals-embed.tsx b/packages/blocks/src/referrals-embed.tsx
index 3a9e20d8bb..718c57a0d4 100644
--- a/packages/blocks/src/referrals-embed.tsx
+++ b/packages/blocks/src/referrals-embed.tsx
@@ -1,14 +1,14 @@
+import { APP_DOMAIN } from "@dub/utils";
+
export const ReferralsEmbed = ({ publicToken }: { publicToken: string }) => {
return (
- <>
-
- >
+
);
};
From f96d704284c4a6b35f84ab8cfd70e04cd2edfb5e Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 7 Oct 2024 17:59:51 +0530
Subject: [PATCH 22/88] remove
---
apps/web/app/app.dub.co/embed/page.tsx | 2 +-
apps/web/app/app.dub.co/embed/referrals.tsx | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/apps/web/app/app.dub.co/embed/page.tsx b/apps/web/app/app.dub.co/embed/page.tsx
index ac800f5962..0bccef43b8 100644
--- a/apps/web/app/app.dub.co/embed/page.tsx
+++ b/apps/web/app/app.dub.co/embed/page.tsx
@@ -10,7 +10,7 @@ export default async function EmbedPage({
return (
<>
-
+
>
);
}
diff --git a/apps/web/app/app.dub.co/embed/referrals.tsx b/apps/web/app/app.dub.co/embed/referrals.tsx
index 4d912de946..182e3df5a3 100644
--- a/apps/web/app/app.dub.co/embed/referrals.tsx
+++ b/apps/web/app/app.dub.co/embed/referrals.tsx
@@ -7,7 +7,6 @@ import { Events } from "./events";
import { Stats } from "./stats";
interface ReferralsProps {
- slug: string;
event: EventType | undefined;
page: string | undefined;
publicToken: string | undefined | null;
From 826b3e92873ad85c227e5c76433e1a536b81b140 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Tue, 8 Oct 2024 18:14:50 +0530
Subject: [PATCH 23/88] update the routes
---
apps/web/app/app.dub.co/embed/referrals.tsx | 4 +-
apps/web/lib/middleware/app.ts | 7 ++--
apps/web/lib/referrals/auth.ts | 46 +++------------------
apps/web/lib/referrals/constants.ts | 7 ++--
apps/web/lib/referrals/token.ts | 10 ++---
apps/web/prisma/schema/link.prisma | 2 +-
apps/web/prisma/schema/referral.prisma | 12 ------
apps/web/prisma/schema/token.prisma | 15 ++++++-
8 files changed, 35 insertions(+), 68 deletions(-)
delete mode 100644 apps/web/prisma/schema/referral.prisma
diff --git a/apps/web/app/app.dub.co/embed/referrals.tsx b/apps/web/app/app.dub.co/embed/referrals.tsx
index 182e3df5a3..7eefb96e19 100644
--- a/apps/web/app/app.dub.co/embed/referrals.tsx
+++ b/apps/web/app/app.dub.co/embed/referrals.tsx
@@ -19,19 +19,17 @@ export const Referrals = ({ event, page, publicToken }: ReferralsProps) => {
Unavailable
- The referral token is not found.
+ The embed public token is not found.
);
}
return (
- {/* Stats */}
- {/* Events */}
Activity
diff --git a/apps/web/lib/middleware/app.ts b/apps/web/lib/middleware/app.ts
index ee21346047..812b7bb94c 100644
--- a/apps/web/lib/middleware/app.ts
+++ b/apps/web/lib/middleware/app.ts
@@ -1,5 +1,6 @@
import { parse } from "@/lib/middleware/utils";
import { NextRequest, NextResponse } from "next/server";
+import { EMBED_PUBLIC_TOKEN_COOKIE_NAME } from "../referrals/constants";
import NewLinkMiddleware from "./new-link";
import { getDefaultWorkspace } from "./utils/get-default-workspace";
import { getOnboardingStep } from "./utils/get-onboarding-step";
@@ -13,11 +14,11 @@ export default async function AppMiddleware(req: NextRequest) {
const isWorkspaceInvite = req.nextUrl.searchParams.get("invite");
if (path.startsWith("/embed")) {
+ const token = req.nextUrl.searchParams.get("token");
+
return NextResponse.rewrite(new URL(`/app.dub.co${fullPath}`, req.url), {
headers: {
- // TODO: Need better cookie name
- // Maybe move this to a API route level?
- "Set-Cookie": `token=${req.nextUrl.searchParams.get("token")}; HttpOnly; Path=/`,
+ "Set-Cookie": `${EMBED_PUBLIC_TOKEN_COOKIE_NAME}=${token}; HttpOnly; Path=/`,
},
});
}
diff --git a/apps/web/lib/referrals/auth.ts b/apps/web/lib/referrals/auth.ts
index b73cc9d551..cd1b89dbc4 100644
--- a/apps/web/lib/referrals/auth.ts
+++ b/apps/web/lib/referrals/auth.ts
@@ -5,6 +5,7 @@ import { getSearchParams } from "@dub/utils";
import { Link, Project } from "@prisma/client";
import { AxiomRequest, withAxiom } from "next-axiom";
import { cookies } from "next/headers";
+import { EMBED_PUBLIC_TOKEN_COOKIE_NAME } from "./constants";
interface WithAuthHandler {
({
@@ -32,58 +33,23 @@ export const withAuth = (handler: WithAuthHandler) => {
try {
let link: Link | undefined = undefined;
- let tokenFromHeader: string | undefined = undefined;
const rateLimit = 60;
const searchParams = getSearchParams(req.url);
- // const authorizationHeader = req.headers.get("Authorization");
-
- // if (!authorizationHeader) {
- // throw new DubApiError({
- // code: "unauthorized",
- // message: "Missing Authorization header.",
- // });
- // }
-
- // if (!authorizationHeader.includes("Bearer ")) {
- // throw new DubApiError({
- // code: "bad_request",
- // message:
- // "Misconfigured authorization header. Did you forget to add 'Bearer '? Learn more: https://d.to/auth",
- // });
- // }
-
- // tokenFromHeader = authorizationHeader.replace("Bearer ", "");
-
- // if (!tokenFromHeader) {
- // throw new DubApiError({
- // code: "unauthorized",
- // message: "Missing Authorization header.",
- // });
- // }
-
- // Read token from query params
- // const tokenFromQuery = searchParams["publicToken"];
-
- // if (!tokenFromQuery) {
- // throw new DubApiError({
- // code: "unauthorized",
- // message: "Missing public token.",
- // });
- // }
-
const cookieStore = cookies();
- const tokenFromCookie = cookieStore.get("token")?.value;
+ const tokenFromCookie = cookieStore.get(
+ EMBED_PUBLIC_TOKEN_COOKIE_NAME,
+ )?.value;
if (!tokenFromCookie) {
throw new DubApiError({
code: "unauthorized",
- message: "Missing public token.",
+ message: "Embed public token not found in the request.",
});
}
- const publicToken = await prisma.referralPublicToken.findUnique({
+ const publicToken = await prisma.embedPublicToken.findUnique({
where: {
publicToken: tokenFromCookie,
},
diff --git a/apps/web/lib/referrals/constants.ts b/apps/web/lib/referrals/constants.ts
index 7bda65072e..0a22f39907 100644
--- a/apps/web/lib/referrals/constants.ts
+++ b/apps/web/lib/referrals/constants.ts
@@ -3,6 +3,7 @@ export const REFERRAL_CLICKS_QUOTA_BONUS = 500;
export const REFERRAL_CLICKS_QUOTA_BONUS_MAX = 16000;
export const REFERRAL_REVENUE_SHARE = 0.2;
-// Referral public token
-export const REFERRAL_PUBLIC_TOKEN_LENGTH = 36;
-export const REFERRAL_PUBLIC_TOKEN_EXPIRY = 1000 * 60 * 60 * 2; // 2 hours
+// Embed public token
+export const EMBED_PUBLIC_TOKEN_LENGTH = 36;
+export const EMBED_PUBLIC_TOKEN_EXPIRY = 1000 * 60 * 60 * 2; // 2 hours
+export const EMBED_PUBLIC_TOKEN_COOKIE_NAME = "embed_public_token";
diff --git a/apps/web/lib/referrals/token.ts b/apps/web/lib/referrals/token.ts
index 04e8638ec4..76c9e1ae6b 100644
--- a/apps/web/lib/referrals/token.ts
+++ b/apps/web/lib/referrals/token.ts
@@ -2,8 +2,8 @@ import { prisma } from "@/lib/prisma";
import { nanoid } from "@dub/utils";
import { DubApiError } from "../api/errors";
import {
- REFERRAL_PUBLIC_TOKEN_EXPIRY,
- REFERRAL_PUBLIC_TOKEN_LENGTH,
+ EMBED_PUBLIC_TOKEN_EXPIRY,
+ EMBED_PUBLIC_TOKEN_LENGTH,
} from "./constants";
export const createPublicToken = async ({
@@ -27,11 +27,11 @@ export const createPublicToken = async ({
});
}
- return await prisma.referralPublicToken.create({
+ return await prisma.embedPublicToken.create({
data: {
linkId,
- expires: new Date(Date.now() + REFERRAL_PUBLIC_TOKEN_EXPIRY),
- publicToken: nanoid(REFERRAL_PUBLIC_TOKEN_LENGTH),
+ expires: new Date(Date.now() + EMBED_PUBLIC_TOKEN_EXPIRY),
+ publicToken: nanoid(EMBED_PUBLIC_TOKEN_LENGTH),
},
});
};
diff --git a/apps/web/prisma/schema/link.prisma b/apps/web/prisma/schema/link.prisma
index 769e71c694..0fc4081edf 100644
--- a/apps/web/prisma/schema/link.prisma
+++ b/apps/web/prisma/schema/link.prisma
@@ -63,7 +63,7 @@ model Link {
// Comments on the particular shortlink
comments String? @db.LongText
- referralPublicTokens ReferralPublicToken[]
+ embedPublicTokens EmbedPublicToken[]
@@unique([domain, key])
@@unique([projectId, externalId])
diff --git a/apps/web/prisma/schema/referral.prisma b/apps/web/prisma/schema/referral.prisma
deleted file mode 100644
index 93aef82b67..0000000000
--- a/apps/web/prisma/schema/referral.prisma
+++ /dev/null
@@ -1,12 +0,0 @@
-model ReferralPublicToken {
- id String @id @default(cuid())
- linkId String
- publicToken String @unique
- expires DateTime
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
-
- link Link @relation(fields: [linkId], references: [id], onDelete: Cascade)
-
- @@index([linkId])
-}
diff --git a/apps/web/prisma/schema/token.prisma b/apps/web/prisma/schema/token.prisma
index 745af51753..3f19527e5c 100644
--- a/apps/web/prisma/schema/token.prisma
+++ b/apps/web/prisma/schema/token.prisma
@@ -64,4 +64,17 @@ model PasswordResetToken {
expires DateTime
@@unique([identifier, token])
-}
\ No newline at end of file
+}
+
+model EmbedPublicToken {
+ id String @id @default(cuid())
+ linkId String
+ publicToken String @unique
+ expires DateTime
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ link Link @relation(fields: [linkId], references: [id], onDelete: Cascade)
+
+ @@index([linkId])
+}
From 44fa6b5b288311eab5dc3d635d1c9bdb56ed2618 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Tue, 8 Oct 2024 18:19:40 +0530
Subject: [PATCH 24/88] revert flag change
---
apps/web/lib/edge-config/get-feature-flags.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/lib/edge-config/get-feature-flags.ts b/apps/web/lib/edge-config/get-feature-flags.ts
index 473de471d2..b400c122db 100644
--- a/apps/web/lib/edge-config/get-feature-flags.ts
+++ b/apps/web/lib/edge-config/get-feature-flags.ts
@@ -17,7 +17,7 @@ export const getFeatureFlags = async ({
}
const workspaceFeatures: Record = {
- referrals: true,
+ referrals: false,
webhooks: false,
};
From 2930d66da50c80e31d62144e7a87f1f195773a6a Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 14 Nov 2024 17:58:13 +0530
Subject: [PATCH 25/88] fix build
---
.../(dashboard)/[slug]/settings/referrals/page-client.tsx | 2 +-
apps/web/app/app.dub.co/embed/stats.tsx | 5 +----
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index 4fd978c840..e9fcafd60f 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -53,7 +53,7 @@ export default function ReferralsPageClient() {
-
+
diff --git a/apps/web/app/app.dub.co/embed/stats.tsx b/apps/web/app/app.dub.co/embed/stats.tsx
index 50f919fc35..7e0d548ea1 100644
--- a/apps/web/app/app.dub.co/embed/stats.tsx
+++ b/apps/web/app/app.dub.co/embed/stats.tsx
@@ -14,10 +14,7 @@ import {
} from "@dub/blocks";
import { CountingNumbers } from "@dub/ui";
import { User } from "@dub/ui/src/icons";
-import {
- AnalyticsCount,
- AnalyticsTimeseries,
-} from "dub/dist/commonjs/models/components";
+import { AnalyticsCount, AnalyticsTimeseries } from "dub/models/components";
interface StatsInnerProps {
totalEvents: AnalyticsCount;
From cbecccd791f90c377111d2a1da35ce14bf69cc51 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 14 Nov 2024 18:10:39 +0530
Subject: [PATCH 26/88] rename to createReferralPublicToken
---
apps/web/app/api/referrals/tokens/route.ts | 7 +++++--
.../app/api/workspaces/[idOrSlug]/referrals-token/route.ts | 4 ++--
apps/web/lib/referrals/token.ts | 2 +-
3 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/apps/web/app/api/referrals/tokens/route.ts b/apps/web/app/api/referrals/tokens/route.ts
index aeb2952b6d..0a2412b24d 100644
--- a/apps/web/app/api/referrals/tokens/route.ts
+++ b/apps/web/app/api/referrals/tokens/route.ts
@@ -1,6 +1,6 @@
import { parseRequestBody } from "@/lib/api/utils";
import { withWorkspace } from "@/lib/auth";
-import { createPublicToken } from "@/lib/referrals/token";
+import { createReferralPublicToken } from "@/lib/referrals/token";
import {
createReferralTokenSchema,
referralTokenSchema,
@@ -13,7 +13,10 @@ export const POST = withWorkspace(async ({ workspace, req }) => {
await parseRequestBody(req),
);
- const token = await createPublicToken({ linkId, workspaceId: workspace.id });
+ const token = await createReferralPublicToken({
+ linkId,
+ workspaceId: workspace.id,
+ });
return NextResponse.json(referralTokenSchema.parse(token), {
status: 201,
diff --git a/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts b/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts
index 66a31c301b..17561d9086 100644
--- a/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts
+++ b/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts
@@ -1,6 +1,6 @@
import { DubApiError } from "@/lib/api/errors";
import { withWorkspace } from "@/lib/auth";
-import { createPublicToken } from "@/lib/referrals/token";
+import { createReferralPublicToken } from "@/lib/referrals/token";
import { referralTokenSchema } from "@/lib/zod/schemas/referrals";
import { NextResponse } from "next/server";
@@ -15,7 +15,7 @@ export const POST = withWorkspace(async ({ workspace }) => {
});
}
- const token = await createPublicToken({
+ const token = await createReferralPublicToken({
linkId: referralLinkId,
workspaceId: id,
});
diff --git a/apps/web/lib/referrals/token.ts b/apps/web/lib/referrals/token.ts
index 76c9e1ae6b..0a5e66d5bd 100644
--- a/apps/web/lib/referrals/token.ts
+++ b/apps/web/lib/referrals/token.ts
@@ -6,7 +6,7 @@ import {
EMBED_PUBLIC_TOKEN_LENGTH,
} from "./constants";
-export const createPublicToken = async ({
+export const createReferralPublicToken = async ({
linkId,
workspaceId,
}: {
From f2a80b1af560679fe10270c650eac51ef11fccbe Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 14 Nov 2024 19:51:46 +0530
Subject: [PATCH 27/88] wip new UI for embed
---
apps/web/app/api/analytics/client/route.ts | 41 ++-
apps/web/app/api/referrals/link/route.ts | 7 +
apps/web/app/api/referrals/program/route.ts | 7 +
apps/web/app/api/referrals/tokens/route.ts | 2 +-
.../[idOrSlug]/referrals-token/route.ts | 2 +-
.../settings/referrals/generate-button.tsx | 35 --
.../settings/referrals/hero-background.tsx | 34 --
.../settings/referrals/invite-button.tsx | 26 --
.../[slug]/settings/referrals/page-client.tsx | 78 +----
.../settings/referrals/referral-link.tsx | 36 --
apps/web/app/app.dub.co/embed/event-tabs.tsx | 20 --
apps/web/app/app.dub.co/embed/events.tsx | 167 ---------
.../app/app.dub.co/embed/hero-background.tsx | 49 +++
apps/web/app/app.dub.co/embed/page-client.tsx | 321 ++++++++++++++++++
apps/web/app/app.dub.co/embed/page.tsx | 13 +-
.../web/app/app.dub.co/embed/placeholders.tsx | 50 ---
apps/web/app/app.dub.co/embed/referrals.tsx | 42 ---
apps/web/app/app.dub.co/embed/stats.tsx | 94 -----
.../embed/use-referral-analytics.ts | 31 ++
.../app/app.dub.co/embed/use-referral-link.ts | 17 +
.../app.dub.co/embed/use-referral-program.ts | 17 +
.../web/lib/actions/generate-referral-link.ts | 50 ---
apps/web/lib/referrals/auth.ts | 11 +-
.../{token.ts => create-referral-token.ts} | 0
24 files changed, 504 insertions(+), 646 deletions(-)
create mode 100644 apps/web/app/api/referrals/link/route.ts
create mode 100644 apps/web/app/api/referrals/program/route.ts
delete mode 100644 apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/generate-button.tsx
delete mode 100644 apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/hero-background.tsx
delete mode 100644 apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/invite-button.tsx
delete mode 100644 apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/referral-link.tsx
delete mode 100644 apps/web/app/app.dub.co/embed/event-tabs.tsx
delete mode 100644 apps/web/app/app.dub.co/embed/events.tsx
create mode 100644 apps/web/app/app.dub.co/embed/hero-background.tsx
create mode 100644 apps/web/app/app.dub.co/embed/page-client.tsx
delete mode 100644 apps/web/app/app.dub.co/embed/placeholders.tsx
delete mode 100644 apps/web/app/app.dub.co/embed/referrals.tsx
delete mode 100644 apps/web/app/app.dub.co/embed/stats.tsx
create mode 100644 apps/web/app/app.dub.co/embed/use-referral-analytics.ts
create mode 100644 apps/web/app/app.dub.co/embed/use-referral-link.ts
create mode 100644 apps/web/app/app.dub.co/embed/use-referral-program.ts
delete mode 100644 apps/web/lib/actions/generate-referral-link.ts
rename apps/web/lib/referrals/{token.ts => create-referral-token.ts} (100%)
diff --git a/apps/web/app/api/analytics/client/route.ts b/apps/web/app/api/analytics/client/route.ts
index 357d4f4a86..59f01701ff 100644
--- a/apps/web/app/api/analytics/client/route.ts
+++ b/apps/web/app/api/analytics/client/route.ts
@@ -1,17 +1,50 @@
import { getAnalytics } from "@/lib/analytics/get-analytics";
+import { calculateEarnings } from "@/lib/api/sales/commission";
import { withAuth } from "@/lib/referrals/auth";
import { analyticsQuerySchema } from "@/lib/zod/schemas/analytics";
import { NextResponse } from "next/server";
// GET /api/analytics/client - get analytics for the current link
-export const GET = withAuth(async ({ workspace, link, searchParams }) => {
- const parsedParams = analyticsQuerySchema.parse(searchParams);
+export const GET = withAuth(async ({ link, searchParams, program }) => {
+ const parsedParams = analyticsQuerySchema
+ .pick({
+ event: true,
+ start: true,
+ end: true,
+ interval: true,
+ groupBy: true,
+ timezone: true,
+ })
+ .parse(searchParams);
const response = await getAnalytics({
...parsedParams,
- workspaceId: workspace.id,
linkId: link.id,
});
- return NextResponse.json(response);
+ let data;
+
+ if (response instanceof Array) {
+ data = response.map((item) => {
+ return {
+ ...item,
+ earnings: calculateEarnings({
+ program,
+ sales: item.sales ?? 0,
+ saleAmount: item.saleAmount ?? 0,
+ }),
+ };
+ });
+ } else {
+ data = {
+ ...response,
+ earnings: calculateEarnings({
+ program,
+ sales: response.sales,
+ saleAmount: response.saleAmount,
+ }),
+ };
+ }
+
+ return NextResponse.json(data);
});
diff --git a/apps/web/app/api/referrals/link/route.ts b/apps/web/app/api/referrals/link/route.ts
new file mode 100644
index 0000000000..3a3ae10206
--- /dev/null
+++ b/apps/web/app/api/referrals/link/route.ts
@@ -0,0 +1,7 @@
+import { withAuth } from "@/lib/referrals/auth";
+import { NextResponse } from "next/server";
+
+// GET /api/referrals/link - get the link for the given affiliate
+export const GET = withAuth(async ({ link }) => {
+ return NextResponse.json(link);
+});
diff --git a/apps/web/app/api/referrals/program/route.ts b/apps/web/app/api/referrals/program/route.ts
new file mode 100644
index 0000000000..a18da727ac
--- /dev/null
+++ b/apps/web/app/api/referrals/program/route.ts
@@ -0,0 +1,7 @@
+import { withAuth } from "@/lib/referrals/auth";
+import { NextResponse } from "next/server";
+
+// GET /api/referrals/program - get the program for the given affiliate
+export const GET = withAuth(async ({ program }) => {
+ return NextResponse.json(program);
+});
diff --git a/apps/web/app/api/referrals/tokens/route.ts b/apps/web/app/api/referrals/tokens/route.ts
index 0a2412b24d..4174a1c621 100644
--- a/apps/web/app/api/referrals/tokens/route.ts
+++ b/apps/web/app/api/referrals/tokens/route.ts
@@ -1,6 +1,6 @@
import { parseRequestBody } from "@/lib/api/utils";
import { withWorkspace } from "@/lib/auth";
-import { createReferralPublicToken } from "@/lib/referrals/token";
+import { createReferralPublicToken } from "@/lib/referrals/create-referral-token";
import {
createReferralTokenSchema,
referralTokenSchema,
diff --git a/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts b/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts
index 17561d9086..90e7f27ee7 100644
--- a/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts
+++ b/apps/web/app/api/workspaces/[idOrSlug]/referrals-token/route.ts
@@ -1,6 +1,6 @@
import { DubApiError } from "@/lib/api/errors";
import { withWorkspace } from "@/lib/auth";
-import { createReferralPublicToken } from "@/lib/referrals/token";
+import { createReferralPublicToken } from "@/lib/referrals/create-referral-token";
import { referralTokenSchema } from "@/lib/zod/schemas/referrals";
import { NextResponse } from "next/server";
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/generate-button.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/generate-button.tsx
deleted file mode 100644
index cc3c746ead..0000000000
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/generate-button.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-"use client";
-
-import { generateReferralLink } from "@/lib/actions/generate-referral-link";
-import useWorkspace from "@/lib/swr/use-workspace";
-import { Button } from "@dub/ui";
-import { useAction } from "next-safe-action/hooks";
-import { useRouter } from "next/navigation";
-import { toast } from "sonner";
-
-export function GenerateButton() {
- const router = useRouter();
- const { id: workspaceId } = useWorkspace();
-
- const { executeAsync, isExecuting, hasSucceeded } = useAction(
- generateReferralLink,
- {
- onSuccess: () => {
- router.refresh();
- toast.success("Referral link generated.");
- },
- onError: ({ error }) => {
- toast.error(error.serverError?.serverError);
- },
- },
- );
-
- return (
-
-
{/* */}
+
+
+
);
}
-
-function EarningsChart() {
- const id = useId();
- const { start, end, interval, color } = useContext(ProgramOverviewContext);
-
- const { data: { earnings: total } = {} } = useReferralAnalytics({
- interval,
- start,
- end,
- });
-
- const { data: timeseries, error } = useReferralAnalytics({
- groupBy: "timeseries",
- interval,
- start,
- end,
- });
-
- const data = useMemo(
- () =>
- timeseries?.map(({ start, earnings }) => ({
- date: new Date(start),
- values: { earnings: earnings / 100 },
- })),
- [timeseries],
- );
-
- return (
-
-
-
-
Earnings
-
- {total !== undefined ? (
-
- {currencyFormatter(total / 100, {
- minimumFractionDigits: 2,
- maximumFractionDigits: 2,
- })}
-
- ) : (
-
- )}
-
-
-
-
-
-
-
- {data ? (
-
d.values.earnings,
- colorClassName: color ? `text-[${color}]` : "text-violet-500",
- isActive: true,
- },
- ]}
- tooltipClassName="p-0"
- tooltipContent={(d) => {
- return (
- <>
-
- {formatDate(d.date)}
-
-
-
-
- {currencyFormatter(d.values.earnings, {
- minimumFractionDigits: 2,
- maximumFractionDigits: 2,
- })}
-
-
- >
- );
- }}
- >
-
- {(context) => (
-
- )}
-
-
-
-
-
-
- ) : (
-
- {error ? (
-
- Failed to load earnings data.
-
- ) : (
-
- )}
-
- )}
-
-
- );
-}
-
-function StatCard({
- title,
- event,
-}: {
- title: string;
- event: "clicks" | "leads" | "sales";
-}) {
- const { start, end, interval, color } = useContext(ProgramOverviewContext);
-
- const { data: total } = useReferralAnalytics({
- interval,
- start,
- end,
- });
-
- const { data: timeseries, error } = useReferralAnalytics({
- groupBy: "timeseries",
- interval,
- start,
- end,
- event,
- });
-
- return (
-
-
{title}
- {total !== undefined ? (
-
- {nFormatter(total[event])}
-
- ) : (
-
- )}
-
- {timeseries ? (
-
({
- date: new Date(d.start),
- value: d[event],
- }))}
- curve={false}
- color={color}
- />
- ) : (
-
- {error ? (
-
- Failed to load data.
-
- ) : (
-
- )}
-
- )}
-
-
- );
-}
diff --git a/apps/web/app/app.dub.co/embed/sale-table.tsx b/apps/web/app/app.dub.co/embed/sale-table.tsx
new file mode 100644
index 0000000000..f9afb3e6d0
--- /dev/null
+++ b/apps/web/app/app.dub.co/embed/sale-table.tsx
@@ -0,0 +1,151 @@
+"use client";
+
+import { IntervalOptions } from "@/lib/analytics/types";
+import { AnimatedEmptyState } from "@/ui/shared/animated-empty-state";
+import SimpleDateRangePicker from "@/ui/shared/simple-date-range-picker";
+import { Table, usePagination, useRouterStuff, useTable } from "@dub/ui";
+import { CircleDollar } from "@dub/ui/src/icons";
+import { currencyFormatter, formatDate } from "@dub/utils";
+import useReferralAnalytics from "./use-referral-analytics";
+import useReferralEvents from "./use-referral-events";
+
+export function SaleTable({ limit }: { limit?: number }) {
+ const { queryParams, searchParamsObj } = useRouterStuff();
+
+ const {
+ start,
+ end,
+ interval,
+ sortBy = "timestamp",
+ order = "desc",
+ } = searchParamsObj as {
+ start?: string;
+ end?: string;
+ interval?: IntervalOptions;
+ sortBy?: "timestamp";
+ order?: "asc" | "desc";
+ };
+
+ const { data: { sales: totalSaleEvents } = {} } = useReferralAnalytics({
+ interval,
+ start: start ? new Date(start) : undefined,
+ end: end ? new Date(end) : undefined,
+ });
+
+ const {
+ data: saleEvents,
+ loading,
+ error,
+ } = useReferralEvents({
+ event: "sales",
+ interval: interval as any,
+ start: start ? new Date(start) : undefined,
+ end: end ? new Date(end) : undefined,
+ order,
+ sortBy,
+ });
+
+ const { pagination, setPagination } = usePagination(limit);
+
+ const { table, ...tableProps } = useTable({
+ data: saleEvents?.slice(0, limit) || [],
+ loading,
+ error: error ? "Failed to fetch sales events." : undefined,
+ columns: [
+ {
+ id: "timestamp",
+ header: "Date",
+ accessorKey: "timestamp",
+ cell: ({ row }) => {
+ return formatDate(row.original.timestamp, { month: "short" });
+ },
+ },
+ {
+ id: "customer",
+ header: "Customer",
+ accessorKey: "customer",
+ cell: ({ row }) => {
+ return row.original.customer.email;
+ },
+ },
+ {
+ id: "saleAmount",
+ header: "Sale Amount",
+ accessorKey: "sale",
+ cell: ({ row }) => {
+ return currencyFormatter(row.original.sale.amount / 100, {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ });
+ },
+ },
+ {
+ id: "earnings",
+ header: "Earnings",
+ accessorKey: "earnings",
+ cell: ({ row }) => {
+ return currencyFormatter(row.original.earnings / 100, {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ });
+ },
+ },
+ ],
+ ...(!limit && {
+ pagination,
+ onPaginationChange: setPagination,
+ sortableColumns: ["timestamp"],
+ sortBy,
+ sortOrder: order,
+ onSortChange: ({ sortBy, sortOrder }) =>
+ queryParams({
+ set: {
+ ...(sortBy && { sort: sortBy }),
+ ...(sortOrder && { order: sortOrder }),
+ },
+ }),
+ }),
+ rowCount: totalSaleEvents,
+ emptyState: (
+
(
+ <>
+
+
+ >
+ )}
+ />
+ ),
+ resourceName: (plural) => `sale${plural ? "s" : ""}`,
+ });
+
+ return (
+
+ {!limit && (
+
+
+
+ )}
+ {loading || saleEvents?.length ? (
+
+ ) : (
+
(
+ <>
+
+
+ >
+ )}
+ />
+ )}
+
+ );
+}
diff --git a/apps/web/app/app.dub.co/embed/stat-card.tsx b/apps/web/app/app.dub.co/embed/stat-card.tsx
new file mode 100644
index 0000000000..184a685255
--- /dev/null
+++ b/apps/web/app/app.dub.co/embed/stat-card.tsx
@@ -0,0 +1,67 @@
+"use client";
+
+import { MiniAreaChart } from "@dub/blocks";
+import { LoadingSpinner } from "@dub/ui/src/icons";
+import { nFormatter } from "@dub/utils";
+import { useContext } from "react";
+import { ProgramOverviewContext } from "./context";
+import useReferralAnalytics from "./use-referral-analytics";
+
+export function StatCard({
+ title,
+ event,
+}: {
+ title: string;
+ event: "clicks" | "leads" | "sales";
+}) {
+ const { start, end, interval, color } = useContext(ProgramOverviewContext);
+
+ const { data: total } = useReferralAnalytics({
+ interval,
+ start,
+ end,
+ });
+
+ const { data: timeseries, error } = useReferralAnalytics({
+ groupBy: "timeseries",
+ interval,
+ start,
+ end,
+ event,
+ });
+
+ return (
+
+
{title}
+ {total !== undefined ? (
+
+ {nFormatter(total[event])}
+
+ ) : (
+
+ )}
+
+ {timeseries ? (
+
({
+ date: new Date(d.start),
+ value: d[event],
+ }))}
+ curve={false}
+ color={color}
+ />
+ ) : (
+
+ {error ? (
+
+ Failed to load data.
+
+ ) : (
+
+ )}
+
+ )}
+
+
+ );
+}
diff --git a/apps/web/app/app.dub.co/embed/use-referral-analytics.ts b/apps/web/app/app.dub.co/embed/use-referral-analytics.ts
index eb32a6cd84..da20e38c06 100644
--- a/apps/web/app/app.dub.co/embed/use-referral-analytics.ts
+++ b/apps/web/app/app.dub.co/embed/use-referral-analytics.ts
@@ -15,7 +15,7 @@ export default function useReferralAnalytics(params?: PartnerAnalyticsFilters) {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
});
- const { data, error, isLoading } = useSWR(
+ const { data, error } = useSWR(
`/api/analytics/client?${searchParams.toString()}`,
fetcher,
{
@@ -26,6 +26,6 @@ export default function useReferralAnalytics(params?: PartnerAnalyticsFilters) {
return {
data,
error,
- isLoading,
+ loading: !data && !error,
};
}
diff --git a/apps/web/app/app.dub.co/embed/use-referral-events.ts b/apps/web/app/app.dub.co/embed/use-referral-events.ts
new file mode 100644
index 0000000000..9d695a43df
--- /dev/null
+++ b/apps/web/app/app.dub.co/embed/use-referral-events.ts
@@ -0,0 +1,29 @@
+import { PartnerEventsFilters } from "@/lib/analytics/types";
+import { fetcher } from "@dub/utils";
+import useSWR from "swr";
+
+export default function useReferralEvents(params?: PartnerEventsFilters) {
+ const searchParams = new URLSearchParams({
+ event: params?.event ?? "sales",
+ ...(params?.start && params?.end
+ ? {
+ start: params.start.toISOString(),
+ end: params.end.toISOString(),
+ }
+ : { interval: params?.interval ?? "30d" }),
+ });
+
+ const { data, error } = useSWR(
+ `/api/events/client?${searchParams.toString()}`,
+ fetcher,
+ {
+ dedupingInterval: 60000,
+ },
+ );
+
+ return {
+ data,
+ error,
+ loading: !data && !error,
+ };
+}
From 470ef50ae9c812caf41d57a6287b92eadb5bbc07 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 14 Nov 2024 22:21:23 +0530
Subject: [PATCH 29/88] some cleanup
---
.../[slug]/settings/referrals/page-client.tsx | 6 +++++-
.../(dashboard)/[slug]/settings/referrals/page.tsx | 9 +++++++--
apps/web/app/app.dub.co/embed/page-client.tsx | 14 ++++----------
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index 8782824043..645c303ce8 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -39,5 +39,9 @@ export default function ReferralsPageClient() {
return Loading...
;
}
- return ;
+ return (
+ <>
+
+ >
+ );
}
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
index 7eb9276d35..42f13f9059 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page.tsx
@@ -1,9 +1,14 @@
+import LayoutLoader from "@/ui/layout/layout-loader";
+import { Suspense } from "react";
import ReferralsPageClient from "./page-client";
export default async function ReferralsPage() {
return (
- <>
+ }>
+
+ Referrals
+
- >
+
);
}
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/page-client.tsx
index 64f6cd43e4..4892468856 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/page-client.tsx
@@ -2,13 +2,7 @@
import { IntervalOptions } from "@/lib/analytics/types";
import { ProgramCommissionDescription } from "@/ui/partners/program-commission-description";
-import {
- Button,
- Check2,
- MaxWidthWrapper,
- useCopyToClipboard,
- useRouterStuff,
-} from "@dub/ui";
+import { Button, Check2, useCopyToClipboard, useRouterStuff } from "@dub/ui";
import { Copy, MoneyBill2 } from "@dub/ui/src/icons";
import { getPrettyUrl } from "@dub/utils";
import { ProgramOverviewContext } from "./context";
@@ -22,7 +16,7 @@ import { useReferralProgram } from "./use-referral-program";
export function ReferralsEmbedPageClient() {
const { program } = useReferralProgram();
const [copied, copyToClipboard] = useCopyToClipboard();
- const { getQueryString, searchParamsObj } = useRouterStuff();
+ const { searchParamsObj } = useRouterStuff();
const { link, isLoading: isLoadingLink } = useReferralLink();
const {
@@ -38,7 +32,7 @@ export function ReferralsEmbedPageClient() {
const color = "#8B5CF6"; // TODO: Read this from a program attribute
return (
-
+ <>
{program && }
@@ -109,6 +103,6 @@ export function ReferralsEmbedPageClient() {
-
+ >
);
}
From 483f5b7ad521a897c719104a344210f767a24d69 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 14 Nov 2024 22:37:21 +0530
Subject: [PATCH 30/88] Add a cron job to remove expired public tokens
---
.../cleanup-expired-public-tokens/route.ts | 32 +++++++++++++++++++
apps/web/vercel.json | 4 +++
2 files changed, 36 insertions(+)
create mode 100644 apps/web/app/api/cron/cleanup-expired-public-tokens/route.ts
diff --git a/apps/web/app/api/cron/cleanup-expired-public-tokens/route.ts b/apps/web/app/api/cron/cleanup-expired-public-tokens/route.ts
new file mode 100644
index 0000000000..dc12eb412b
--- /dev/null
+++ b/apps/web/app/api/cron/cleanup-expired-public-tokens/route.ts
@@ -0,0 +1,32 @@
+import { handleAndReturnErrorResponse } from "@/lib/api/errors";
+import { verifyVercelSignature } from "@/lib/cron/verify-vercel";
+import { prisma } from "@/lib/prisma";
+import { log } from "@dub/utils";
+import { NextResponse } from "next/server";
+
+export const dynamic = "force-dynamic";
+
+// Cron to remove the expired public embed tokens
+// Run every day (0 12 * * *)
+export async function GET(req: Request) {
+ try {
+ await verifyVercelSignature(req);
+
+ await prisma.embedPublicToken.deleteMany({
+ where: {
+ expires: {
+ lt: new Date(),
+ },
+ },
+ });
+
+ return NextResponse.json({ status: "OK" });
+ } catch (error) {
+ await log({
+ message: `Links and domain cleanup failed - ${error.message}`,
+ type: "errors",
+ });
+
+ return handleAndReturnErrorResponse(error);
+ }
+}
diff --git a/apps/web/vercel.json b/apps/web/vercel.json
index ba487b5a45..8f9ffe21e6 100644
--- a/apps/web/vercel.json
+++ b/apps/web/vercel.json
@@ -15,6 +15,10 @@
{
"path": "/api/cron/disposable-emails",
"schedule": "0 12 * * 1"
+ },
+ {
+ "path": "/api/cron/cleanup-expired-public-tokens",
+ "schedule": "0 12 * * *"
}
],
"functions": {
From eadb8fcb3499620a81d7189bf5eb94f56188d875 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 14 Nov 2024 22:52:25 +0530
Subject: [PATCH 31/88] If an active public token is present for a link, return
it instead of creating a new one
---
apps/web/lib/referrals/create-referral-token.ts | 10 ++++++++++
apps/web/prisma/schema/token.prisma | 2 +-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/apps/web/lib/referrals/create-referral-token.ts b/apps/web/lib/referrals/create-referral-token.ts
index 0a5e66d5bd..9d5563f396 100644
--- a/apps/web/lib/referrals/create-referral-token.ts
+++ b/apps/web/lib/referrals/create-referral-token.ts
@@ -27,6 +27,16 @@ export const createReferralPublicToken = async ({
});
}
+ const token = await prisma.embedPublicToken.findFirst({
+ where: {
+ linkId,
+ },
+ });
+
+ if (token) {
+ return token;
+ }
+
return await prisma.embedPublicToken.create({
data: {
linkId,
diff --git a/apps/web/prisma/schema/token.prisma b/apps/web/prisma/schema/token.prisma
index 3f19527e5c..5bb63bc5a9 100644
--- a/apps/web/prisma/schema/token.prisma
+++ b/apps/web/prisma/schema/token.prisma
@@ -68,7 +68,7 @@ model PasswordResetToken {
model EmbedPublicToken {
id String @id @default(cuid())
- linkId String
+ linkId String // TODO: Do we wanna make this unique? One token per link at a time?
publicToken String @unique
expires DateTime
createdAt DateTime @default(now())
From faca4a699539f02f5d9c632b81c345cc47e3f2f5 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 14 Nov 2024 22:55:43 +0530
Subject: [PATCH 32/88] Add rate limit create referral token endpoint
---
apps/web/lib/referrals/create-referral-token.ts | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/apps/web/lib/referrals/create-referral-token.ts b/apps/web/lib/referrals/create-referral-token.ts
index 9d5563f396..d9ca2649ac 100644
--- a/apps/web/lib/referrals/create-referral-token.ts
+++ b/apps/web/lib/referrals/create-referral-token.ts
@@ -1,6 +1,7 @@
import { prisma } from "@/lib/prisma";
import { nanoid } from "@dub/utils";
import { DubApiError } from "../api/errors";
+import { ratelimit } from "../upstash";
import {
EMBED_PUBLIC_TOKEN_EXPIRY,
EMBED_PUBLIC_TOKEN_LENGTH,
@@ -27,6 +28,15 @@ export const createReferralPublicToken = async ({
});
}
+ const { success } = await ratelimit(10, "1 m").limit(linkId);
+
+ if (!success) {
+ throw new DubApiError({
+ code: "rate_limit_exceeded",
+ message: "Too many requests.",
+ });
+ }
+
const token = await prisma.embedPublicToken.findFirst({
where: {
linkId,
From 9c86ed109b53462889bff0d90bf70d62be78b8e9 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Fri, 15 Nov 2024 09:57:37 +0530
Subject: [PATCH 33/88] remove unused
---
packages/blocks/src/event-list.tsx | 73 ----------------------
packages/blocks/src/hooks/use-analytics.ts | 32 ----------
packages/blocks/src/hooks/use-events.ts | 28 ---------
packages/blocks/src/index.ts | 4 --
packages/blocks/src/stats-card.tsx | 54 ----------------
5 files changed, 191 deletions(-)
delete mode 100644 packages/blocks/src/event-list.tsx
delete mode 100644 packages/blocks/src/hooks/use-analytics.ts
delete mode 100644 packages/blocks/src/hooks/use-events.ts
delete mode 100644 packages/blocks/src/stats-card.tsx
diff --git a/packages/blocks/src/event-list.tsx b/packages/blocks/src/event-list.tsx
deleted file mode 100644
index 0cae1ca3bb..0000000000
--- a/packages/blocks/src/event-list.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-"use client";
-
-import { usePagination } from "@dub/ui";
-import { PropsWithChildren, ReactNode } from "react";
-import { EmptyState, EmptyStateProps } from "./empty-state";
-import { PaginationControls } from "./pagination-controls";
-
-export type EventListProps = PropsWithChildren<{
- events: { icon: ReactNode; content: ReactNode; right?: ReactNode }[];
- totalEvents: number;
- emptyState: EmptyStateProps;
-}>;
-
-export function EventList({ events, totalEvents, emptyState }: EventListProps) {
- const { pagination, setPagination } = usePagination();
-
- return (
-
- {events.length === 0 && totalEvents === 0 ? (
-
-
-
- ) : (
- <>
-
- {events.map((event, index) => (
-
-
-
{event.icon}
-
{event.content}
-
- {event.right !== undefined && (
-
{event.right}
- )}
-
- ))}
-
-
-
`event${p ? "s" : ""}`}
- />
-
- >
- )}
-
- );
-}
-
-export function EventListSkeleton() {
- return (
-
-
- {[...Array(5)].map((_, index) => (
-
- ))}
-
-
- );
-}
diff --git a/packages/blocks/src/hooks/use-analytics.ts b/packages/blocks/src/hooks/use-analytics.ts
deleted file mode 100644
index daa44ed860..0000000000
--- a/packages/blocks/src/hooks/use-analytics.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { fetcher } from "@dub/utils";
-import useSWR from "swr";
-import { EventType } from "../types";
-
-interface UseAnalyticsParams {
- event: EventType;
- interval: string;
- groupBy: "timeseries" | "top_links" | "devices" | "count";
-}
-
-export const useAnalytics = ({
- event,
- interval,
- groupBy,
-}: UseAnalyticsParams) => {
- const searchParams = new URLSearchParams({
- event,
- interval,
- groupBy,
- });
-
- const { error, data, isLoading } = useSWR(
- `/api/analytics/client?${searchParams.toString()}`,
- fetcher,
- );
-
- return {
- analytics: data,
- error,
- isLoading,
- };
-};
diff --git a/packages/blocks/src/hooks/use-events.ts b/packages/blocks/src/hooks/use-events.ts
deleted file mode 100644
index 5ea3bfbd96..0000000000
--- a/packages/blocks/src/hooks/use-events.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { fetcher } from "@dub/utils";
-import useSWR from "swr";
-import { EventType } from "../types";
-
-interface UseEventsParams {
- event: EventType;
- interval: string;
- page: string;
-}
-
-export const useEvents = ({ event, interval, page }: UseEventsParams) => {
- const searchParams = new URLSearchParams({
- event,
- interval,
- page,
- });
-
- const { error, data, isLoading } = useSWR<[]>(
- `/api/events/client?${searchParams.toString()}`,
- fetcher,
- );
-
- return {
- events: data,
- error,
- isLoading,
- };
-};
diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts
index b77b774e74..a3de8ffcb7 100644
--- a/packages/blocks/src/index.ts
+++ b/packages/blocks/src/index.ts
@@ -3,11 +3,7 @@ import "./styles.css";
export * from "./context";
export * from "./empty-state";
-export * from "./event-list";
export * from "./gauge";
-export * from "./hooks/use-analytics";
-export * from "./hooks/use-events";
export * from "./mini-area-chart";
export * from "./pagination-controls";
export * from "./referrals-embed";
-export * from "./stats-card";
diff --git a/packages/blocks/src/stats-card.tsx b/packages/blocks/src/stats-card.tsx
deleted file mode 100644
index 517dfe21b7..0000000000
--- a/packages/blocks/src/stats-card.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { cn } from "@dub/utils";
-import { PropsWithChildren, ReactNode } from "react";
-
-const wrapperClassName =
- "flex justify-between gap-4 rounded-xl border border-gray-200 bg-white px-5 py-4 text-left overflow-hidden";
-
-export function StatsCard({
- label,
- demo,
- graphic,
- children,
-}: PropsWithChildren<{
- label: string;
- demo?: boolean;
- graphic?: ReactNode;
-}>) {
- return (
-
- {demo && (
-
- DEMO DATA
-
- )}
-
-
- {label}
-
- {children}
-
- {graphic && (
-
- {graphic}
-
- )}
-
- );
-}
-
-export function StatsCardSkeleton({ error }: { error?: boolean }) {
- return (
-
- {error ? (
-
- Failed to load data
-
- ) : (
-
-
-
-
- )}
-
- );
-}
From 36d488dad9c7228a03a28ab26a35769b119b46cb Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Fri, 15 Nov 2024 11:11:24 +0530
Subject: [PATCH 34/88] update routes
---
apps/web/app/api/referrals/link/route.ts | 12 +++++++++++-
apps/web/app/api/referrals/program/route.ts | 3 ++-
apps/web/app/app.dub.co/embed/page-client.tsx | 2 +-
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/apps/web/app/api/referrals/link/route.ts b/apps/web/app/api/referrals/link/route.ts
index 3a3ae10206..dd132f5ceb 100644
--- a/apps/web/app/api/referrals/link/route.ts
+++ b/apps/web/app/api/referrals/link/route.ts
@@ -3,5 +3,15 @@ import { NextResponse } from "next/server";
// GET /api/referrals/link - get the link for the given affiliate
export const GET = withAuth(async ({ link }) => {
- return NextResponse.json(link);
+ const { id, url, shortLink, clicks, leads, sales, saleAmount } = link;
+
+ return NextResponse.json({
+ id,
+ url,
+ shortLink,
+ clicks,
+ leads,
+ sales,
+ saleAmount,
+ });
});
diff --git a/apps/web/app/api/referrals/program/route.ts b/apps/web/app/api/referrals/program/route.ts
index a18da727ac..bb7238dd34 100644
--- a/apps/web/app/api/referrals/program/route.ts
+++ b/apps/web/app/api/referrals/program/route.ts
@@ -1,7 +1,8 @@
import { withAuth } from "@/lib/referrals/auth";
+import { ProgramSchema } from "@/lib/zod/schemas/partners";
import { NextResponse } from "next/server";
// GET /api/referrals/program - get the program for the given affiliate
export const GET = withAuth(async ({ program }) => {
- return NextResponse.json(program);
+ return NextResponse.json(ProgramSchema.parse(program));
});
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/page-client.tsx
index 4892468856..26f4a89596 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/page-client.tsx
@@ -15,8 +15,8 @@ import { useReferralProgram } from "./use-referral-program";
export function ReferralsEmbedPageClient() {
const { program } = useReferralProgram();
- const [copied, copyToClipboard] = useCopyToClipboard();
const { searchParamsObj } = useRouterStuff();
+ const [copied, copyToClipboard] = useCopyToClipboard();
const { link, isLoading: isLoadingLink } = useReferralLink();
const {
From 6dd91e15692138509529194a20c7f897a203c069 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Fri, 15 Nov 2024 22:59:45 +0530
Subject: [PATCH 35/88] use brandColor
---
apps/web/app/app.dub.co/embed/page-client.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/page-client.tsx
index 26f4a89596..5705dae949 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/page-client.tsx
@@ -29,7 +29,7 @@ export function ReferralsEmbedPageClient() {
interval?: IntervalOptions;
};
- const color = "#8B5CF6"; // TODO: Read this from a program attribute
+ const color = program?.brandColor || "#8B5CF6";
return (
<>
From ad9fc760279687d88de7080aafc73cf665ae1260 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 18 Nov 2024 14:20:39 +0530
Subject: [PATCH 36/88] Move `packages/blocks` to `packages/ui`
---
.../[slug]/settings/billing/usage-chart.tsx | 3 +-
.../[slug]/settings/domains/page-client.tsx | 2 +-
.../settings/library/tags/page-client.tsx | 9 +-
.../[slug]/settings/referrals/page-client.tsx | 2 +-
apps/web/app/app.dub.co/embed/stat-card.tsx | 2 +-
.../[partnerId]/[programId]/page-client.tsx | 2 +-
apps/web/package.json | 1 -
apps/web/ui/analytics/events/events-tabs.tsx | 8 +-
apps/web/ui/blocks/pagination-controls.tsx | 7 -
.../src => apps/web/ui/embed}/context.tsx | 0
.../web/ui/embed}/referrals-embed.tsx | 0
apps/web/ui/links/links-container.tsx | 2 +-
apps/web/ui/partners/program-card.tsx | 3 +-
apps/web/ui/shared/empty-state.tsx | 3 +-
packages/blocks/README.md | 11 --
packages/blocks/package.json | 66 -------
packages/blocks/postcss.config.js | 9 -
packages/blocks/src/gauge.tsx | 32 ----
packages/blocks/src/index.ts | 9 -
packages/blocks/src/styles.css | 3 -
packages/blocks/src/types.ts | 1 -
packages/blocks/tailwind.config.ts | 9 -
packages/blocks/tsconfig.json | 5 -
packages/blocks/tsup.config.ts | 15 --
packages/ui/package.json | 8 +-
packages/{blocks => ui}/src/empty-state.tsx | 0
packages/ui/src/index.tsx | 3 +
.../{blocks => ui}/src/mini-area-chart.tsx | 0
.../src/pagination-controls.tsx | 2 +-
pnpm-lock.yaml | 170 ++----------------
30 files changed, 50 insertions(+), 337 deletions(-)
delete mode 100644 apps/web/ui/blocks/pagination-controls.tsx
rename {packages/blocks/src => apps/web/ui/embed}/context.tsx (100%)
rename {packages/blocks/src => apps/web/ui/embed}/referrals-embed.tsx (100%)
delete mode 100644 packages/blocks/README.md
delete mode 100644 packages/blocks/package.json
delete mode 100644 packages/blocks/postcss.config.js
delete mode 100644 packages/blocks/src/gauge.tsx
delete mode 100644 packages/blocks/src/index.ts
delete mode 100644 packages/blocks/src/styles.css
delete mode 100644 packages/blocks/src/types.ts
delete mode 100644 packages/blocks/tailwind.config.ts
delete mode 100644 packages/blocks/tsconfig.json
delete mode 100644 packages/blocks/tsup.config.ts
rename packages/{blocks => ui}/src/empty-state.tsx (100%)
rename packages/{blocks => ui}/src/mini-area-chart.tsx (100%)
rename packages/{blocks => ui}/src/pagination-controls.tsx (97%)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/billing/usage-chart.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/billing/usage-chart.tsx
index 75af87ef57..0c92f5568c 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/billing/usage-chart.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/billing/usage-chart.tsx
@@ -3,8 +3,7 @@ import { Bars } from "@/ui/charts/bars";
import TimeSeriesChart from "@/ui/charts/time-series-chart";
import XAxis from "@/ui/charts/x-axis";
import YAxis from "@/ui/charts/y-axis";
-import { EmptyState } from "@dub/blocks/src/empty-state";
-import { LoadingSpinner } from "@dub/ui";
+import { EmptyState, LoadingSpinner } from "@dub/ui";
import { CircleDollar, CursorRays, Hyperlink } from "@dub/ui/src/icons";
import { formatDate, nFormatter } from "@dub/utils";
import { LinearGradient } from "@visx/gradient";
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/page-client.tsx
index 54733f2fc5..eddc554522 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/page-client.tsx
@@ -14,7 +14,6 @@ import { useRegisterDomainSuccessModal } from "@/ui/modals/register-domain-succe
import { AnimatedEmptyState } from "@/ui/shared/animated-empty-state";
import EmptyState from "@/ui/shared/empty-state";
import { SearchBoxPersisted } from "@/ui/shared/search-box";
-import { PaginationControls } from "@dub/blocks/src/pagination-controls";
import {
Badge,
Button,
@@ -24,6 +23,7 @@ import {
useRouterStuff,
} from "@dub/ui";
import { CursorRays, LinkBroken } from "@dub/ui/src/icons";
+import { PaginationControls } from "@dub/ui/src/pagination-controls";
import { ToggleGroup } from "@dub/ui/src/toggle-group";
import { InfoTooltip, TooltipContent } from "@dub/ui/src/tooltip";
import { capitalize, pluralize } from "@dub/utils";
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/library/tags/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/library/tags/page-client.tsx
index 41f6feceae..278117ed31 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/library/tags/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/library/tags/page-client.tsx
@@ -7,8 +7,13 @@ import { TAGS_MAX_PAGE_SIZE } from "@/lib/zod/schemas/tags";
import { useAddEditTagModal } from "@/ui/modals/add-edit-tag-modal";
import { AnimatedEmptyState } from "@/ui/shared/animated-empty-state";
import { SearchBoxPersisted } from "@/ui/shared/search-box";
-import { PaginationControls } from "@dub/blocks";
-import { CardList, Tag, usePagination, useRouterStuff } from "@dub/ui";
+import {
+ CardList,
+ PaginationControls,
+ Tag,
+ usePagination,
+ useRouterStuff,
+} from "@dub/ui";
import {
createContext,
Dispatch,
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index 645c303ce8..76fca3d872 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -1,7 +1,7 @@
"use client";
import useWorkspace from "@/lib/swr/use-workspace";
-import { ReferralsEmbed } from "@dub/blocks";
+import { ReferralsEmbed } from "@/ui/embed/referrals-embed";
import { redirect } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
diff --git a/apps/web/app/app.dub.co/embed/stat-card.tsx b/apps/web/app/app.dub.co/embed/stat-card.tsx
index 184a685255..c07c7e5dd7 100644
--- a/apps/web/app/app.dub.co/embed/stat-card.tsx
+++ b/apps/web/app/app.dub.co/embed/stat-card.tsx
@@ -1,6 +1,6 @@
"use client";
-import { MiniAreaChart } from "@dub/blocks";
+import { MiniAreaChart } from "@dub/ui";
import { LoadingSpinner } from "@dub/ui/src/icons";
import { nFormatter } from "@dub/utils";
import { useContext } from "react";
diff --git a/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx b/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx
index 2455c8679b..f81f70e25e 100644
--- a/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx
+++ b/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx
@@ -10,12 +10,12 @@ import XAxis from "@/ui/charts/x-axis";
import YAxis from "@/ui/charts/y-axis";
import { ProgramCommissionDescription } from "@/ui/partners/program-commission-description";
import SimpleDateRangePicker from "@/ui/shared/simple-date-range-picker";
-import { MiniAreaChart } from "@dub/blocks";
import {
Button,
buttonVariants,
Check2,
MaxWidthWrapper,
+ MiniAreaChart,
useCopyToClipboard,
useRouterStuff,
} from "@dub/ui";
diff --git a/apps/web/package.json b/apps/web/package.json
index 73aa71c2e7..73d3bfac53 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -16,7 +16,6 @@
"@boxyhq/saml-jackson": "1.23.9",
"@chronark/zod-bird": "^0.3.9",
"@dub/analytics": "^0.0.21",
- "@dub/blocks": "workspace:*",
"@dub/tailwind-config": "workspace:*",
"@dub/ui": "workspace:*",
"@dub/utils": "workspace:*",
diff --git a/apps/web/ui/analytics/events/events-tabs.tsx b/apps/web/ui/analytics/events/events-tabs.tsx
index 1d3736d9be..110eb94d1e 100644
--- a/apps/web/ui/analytics/events/events-tabs.tsx
+++ b/apps/web/ui/analytics/events/events-tabs.tsx
@@ -1,7 +1,11 @@
import { AnalyticsResponseOptions } from "@/lib/analytics/types";
import { editQueryString } from "@/lib/analytics/utils";
-import { MiniAreaChart } from "@dub/blocks";
-import { CountingNumbers, useMediaQuery, useRouterStuff } from "@dub/ui";
+import {
+ CountingNumbers,
+ MiniAreaChart,
+ useMediaQuery,
+ useRouterStuff,
+} from "@dub/ui";
import { capitalize, cn, fetcher } from "@dub/utils";
import { useCallback, useContext, useEffect } from "react";
import useSWR from "swr";
diff --git a/apps/web/ui/blocks/pagination-controls.tsx b/apps/web/ui/blocks/pagination-controls.tsx
deleted file mode 100644
index 0578aaee78..0000000000
--- a/apps/web/ui/blocks/pagination-controls.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export type PaginationState = {
- page: number;
- pageSize: number;
- total: number;
-};
-
-export function PaginationControls() {}
diff --git a/packages/blocks/src/context.tsx b/apps/web/ui/embed/context.tsx
similarity index 100%
rename from packages/blocks/src/context.tsx
rename to apps/web/ui/embed/context.tsx
diff --git a/packages/blocks/src/referrals-embed.tsx b/apps/web/ui/embed/referrals-embed.tsx
similarity index 100%
rename from packages/blocks/src/referrals-embed.tsx
rename to apps/web/ui/embed/referrals-embed.tsx
diff --git a/apps/web/ui/links/links-container.tsx b/apps/web/ui/links/links-container.tsx
index 29bc5b37d8..0c4bd876cf 100644
--- a/apps/web/ui/links/links-container.tsx
+++ b/apps/web/ui/links/links-container.tsx
@@ -3,9 +3,9 @@
import useLinks from "@/lib/swr/use-links";
import useLinksCount from "@/lib/swr/use-links-count";
import { ExpandedLinkProps, UserProps } from "@/lib/types";
-import { PaginationControls } from "@dub/blocks/src/pagination-controls";
import { CardList, MaxWidthWrapper, usePagination } from "@dub/ui";
import { CursorRays, Hyperlink, LoadingSpinner } from "@dub/ui/src/icons";
+import { PaginationControls } from "@dub/ui/src/pagination-controls";
import { cn } from "@dub/utils";
import { useSearchParams } from "next/navigation";
import {
diff --git a/apps/web/ui/partners/program-card.tsx b/apps/web/ui/partners/program-card.tsx
index db51d7c27e..9ee1e14a05 100644
--- a/apps/web/ui/partners/program-card.tsx
+++ b/apps/web/ui/partners/program-card.tsx
@@ -1,7 +1,6 @@
import usePartnerAnalytics from "@/lib/swr/use-partner-analytics";
import { ProgramEnrollmentProps, ProgramProps } from "@/lib/types";
-import { MiniAreaChart } from "@dub/blocks";
-import { BlurImage, StatusBadge } from "@dub/ui";
+import { BlurImage, MiniAreaChart, StatusBadge } from "@dub/ui";
import {
cn,
currencyFormatter,
diff --git a/apps/web/ui/shared/empty-state.tsx b/apps/web/ui/shared/empty-state.tsx
index bdb2e54a0d..e5f11a33ff 100644
--- a/apps/web/ui/shared/empty-state.tsx
+++ b/apps/web/ui/shared/empty-state.tsx
@@ -1,7 +1,6 @@
"use client";
-import { EmptyState as EmptyStateBlock } from "@dub/blocks";
-import { buttonVariants } from "@dub/ui";
+import { buttonVariants, EmptyState as EmptyStateBlock } from "@dub/ui";
import { cn } from "@dub/utils";
import Link from "next/link";
import { ComponentProps } from "react";
diff --git a/packages/blocks/README.md b/packages/blocks/README.md
deleted file mode 100644
index a22876d7b9..0000000000
--- a/packages/blocks/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# `@dub/blocks`
-
-`@dub/blocks` is a library of React components built by Dub.
-
-## Installation
-
-To install the package, run:
-
-```bash
-pnpm i @dub/blocks
-```
diff --git a/packages/blocks/package.json b/packages/blocks/package.json
deleted file mode 100644
index 2872efa211..0000000000
--- a/packages/blocks/package.json
+++ /dev/null
@@ -1,66 +0,0 @@
-{
- "name": "@dub/blocks",
- "description": "UI components built by Dub",
- "version": "0.0.1",
- "sideEffects": false,
- "main": "./dist/index.js",
- "module": "./dist/index.mjs",
- "types": "./dist/index.d.ts",
- "files": [
- "dist/**"
- ],
- "scripts": {
- "build": "tsup",
- "lint": "eslint src/",
- "dev": "tsup --watch",
- "check-types": "tsc --noEmit"
- },
- "peerDependencies": {
- "next": "14.2.0-canary.47",
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
- },
- "devDependencies": {
- "@dub/tailwind-config": "workspace:*",
- "@types/react": "^18.2.47",
- "@types/react-dom": "^18.2.14",
- "autoprefixer": "^10.4.16",
- "next": "14.2.0-canary.67",
- "postcss": "^8.4.31",
- "react": "^18.2.0",
- "tailwindcss": "^3.4.4",
- "tsconfig": "workspace:*",
- "tsup": "^6.1.3",
- "typescript": "^5.1.6"
- },
- "dependencies": {
- "@dub/ui": "workspace:*",
- "@dub/utils": "workspace:*",
- "@visx/curve": "^3.3.0",
- "@visx/gradient": "^3.3.0",
- "@visx/group": "^3.3.0",
- "@visx/responsive": "^2.10.0",
- "@visx/scale": "^3.3.0",
- "@visx/shape": "^2.12.2",
- "class-variance-authority": "^0.7.0",
- "framer-motion": "^10.16.16",
- "swr": "^2.1.5"
- },
- "author": "Steven Tey ",
- "homepage": "https://dub.co",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/dubinc/dub.git"
- },
- "bugs": {
- "url": "https://github.com/dubinc/dub/issues"
- },
- "keywords": [
- "dub",
- "dub.co",
- "blocks"
- ],
- "publishConfig": {
- "access": "public"
- }
-}
diff --git a/packages/blocks/postcss.config.js b/packages/blocks/postcss.config.js
deleted file mode 100644
index 07aa434b2b..0000000000
--- a/packages/blocks/postcss.config.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// If you want to use other PostCSS plugins, see the following:
-// https://tailwindcss.com/docs/using-with-preprocessors
-
-module.exports = {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-};
diff --git a/packages/blocks/src/gauge.tsx b/packages/blocks/src/gauge.tsx
deleted file mode 100644
index f024b86c13..0000000000
--- a/packages/blocks/src/gauge.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { PropsWithChildren } from "react";
-
-export type GaugeProps = PropsWithChildren<{
- value: number;
- min?: number;
- max: number;
-}>;
-
-export function Gauge({ value, min = 0, max, children }: GaugeProps) {
- const fraction = (value - min) / (max - min);
- const gradientStop = fraction * 50;
-
- return (
-
- );
-}
diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts
deleted file mode 100644
index a3de8ffcb7..0000000000
--- a/packages/blocks/src/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// styles
-import "./styles.css";
-
-export * from "./context";
-export * from "./empty-state";
-export * from "./gauge";
-export * from "./mini-area-chart";
-export * from "./pagination-controls";
-export * from "./referrals-embed";
diff --git a/packages/blocks/src/styles.css b/packages/blocks/src/styles.css
deleted file mode 100644
index b5c61c9567..0000000000
--- a/packages/blocks/src/styles.css
+++ /dev/null
@@ -1,3 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
diff --git a/packages/blocks/src/types.ts b/packages/blocks/src/types.ts
deleted file mode 100644
index 510cddc5ce..0000000000
--- a/packages/blocks/src/types.ts
+++ /dev/null
@@ -1 +0,0 @@
-export type EventType = "clicks" | "leads" | "sales" | "composite";
diff --git a/packages/blocks/tailwind.config.ts b/packages/blocks/tailwind.config.ts
deleted file mode 100644
index 5c39ec95b9..0000000000
--- a/packages/blocks/tailwind.config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// tailwind config is required for editor support
-import sharedConfig from "@dub/tailwind-config/tailwind.config.ts";
-import type { Config } from "tailwindcss";
-
-const config: Pick = {
- presets: [sharedConfig],
-};
-
-export default config;
diff --git a/packages/blocks/tsconfig.json b/packages/blocks/tsconfig.json
deleted file mode 100644
index cd6c94d6e8..0000000000
--- a/packages/blocks/tsconfig.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "extends": "tsconfig/react-library.json",
- "include": ["."],
- "exclude": ["dist", "build", "node_modules"]
-}
diff --git a/packages/blocks/tsup.config.ts b/packages/blocks/tsup.config.ts
deleted file mode 100644
index 78a41d1e80..0000000000
--- a/packages/blocks/tsup.config.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { defineConfig, Options } from "tsup";
-
-export default defineConfig((options: Options) => ({
- entry: ["src/**/*.tsx", "src/**/*.ts"],
- format: ["esm"],
- esbuildOptions(options) {
- options.banner = {
- js: '"use client"',
- };
- },
- dts: true,
- minify: true,
- external: ["react"],
- ...options,
-}));
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 49ca0b7354..16ce376984 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -68,7 +68,13 @@
"sonner": "^1.4.41",
"swr": "^2.1.5",
"use-debounce": "^8.0.4",
- "vaul": "^0.9.6"
+ "vaul": "^0.9.6",
+ "@visx/curve": "^3.3.0",
+ "@visx/gradient": "^3.3.0",
+ "@visx/group": "^3.3.0",
+ "@visx/responsive": "^2.10.0",
+ "@visx/scale": "^3.3.0",
+ "@visx/shape": "^2.12.2"
},
"author": "Steven Tey ",
"homepage": "https://dub.co",
diff --git a/packages/blocks/src/empty-state.tsx b/packages/ui/src/empty-state.tsx
similarity index 100%
rename from packages/blocks/src/empty-state.tsx
rename to packages/ui/src/empty-state.tsx
diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx
index 08b40281be..7f7d83e0f2 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 "./empty-state";
export * from "./file-upload";
export * from "./filter";
export * from "./form";
@@ -19,7 +20,9 @@ export * from "./grid";
export * from "./input";
export * from "./input-select";
export * from "./label";
+export * from "./mini-area-chart";
export * from "./modal";
+export * from "./pagination-controls";
export * from "./popover";
export * from "./radio-group";
export * from "./sheet";
diff --git a/packages/blocks/src/mini-area-chart.tsx b/packages/ui/src/mini-area-chart.tsx
similarity index 100%
rename from packages/blocks/src/mini-area-chart.tsx
rename to packages/ui/src/mini-area-chart.tsx
diff --git a/packages/blocks/src/pagination-controls.tsx b/packages/ui/src/pagination-controls.tsx
similarity index 97%
rename from packages/blocks/src/pagination-controls.tsx
rename to packages/ui/src/pagination-controls.tsx
index 43f6c1f7ef..d5d2aa76ce 100644
--- a/packages/blocks/src/pagination-controls.tsx
+++ b/packages/ui/src/pagination-controls.tsx
@@ -1,5 +1,5 @@
-import { PaginationState } from "@dub/ui";
import { cn, nFormatter } from "@dub/utils";
+import { PaginationState } from "@tanstack/react-table";
import { PropsWithChildren } from "react";
const buttonClassName = cn(
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e961035553..dbd697b2fb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -44,9 +44,6 @@ importers:
'@dub/analytics':
specifier: ^0.0.21
version: 0.0.21
- '@dub/blocks':
- specifier: workspace:*
- version: link:../../packages/blocks
'@dub/tailwind-config':
specifier: workspace:*
version: link:../../packages/tailwind-config
@@ -409,79 +406,6 @@ importers:
specifier: 1.5.0
version: 1.5.0(@types/node@18.11.9)
- packages/blocks:
- dependencies:
- '@dub/ui':
- specifier: workspace:*
- version: link:../ui
- '@dub/utils':
- specifier: workspace:*
- version: link:../utils
- '@visx/curve':
- specifier: ^3.3.0
- version: 3.3.0
- '@visx/gradient':
- specifier: ^3.3.0
- version: 3.3.0(react@18.2.0)
- '@visx/group':
- specifier: ^3.3.0
- version: 3.3.0(react@18.2.0)
- '@visx/responsive':
- specifier: ^2.10.0
- version: 2.10.0(react@18.2.0)
- '@visx/scale':
- specifier: ^3.3.0
- version: 3.3.0
- '@visx/shape':
- specifier: ^2.12.2
- version: 2.12.2(react@18.2.0)
- class-variance-authority:
- specifier: ^0.7.0
- version: 0.7.0
- framer-motion:
- specifier: ^10.16.16
- version: 10.18.0(react-dom@18.2.0)(react@18.2.0)
- react-dom:
- specifier: ^18.2.0
- version: 18.2.0(react@18.2.0)
- swr:
- specifier: ^2.1.5
- version: 2.1.5(react@18.2.0)
- devDependencies:
- '@dub/tailwind-config':
- specifier: workspace:*
- version: link:../tailwind-config
- '@types/react':
- specifier: ^18.2.47
- version: 18.2.48
- '@types/react-dom':
- specifier: ^18.2.14
- version: 18.2.14
- autoprefixer:
- specifier: ^10.4.16
- version: 10.4.16(postcss@8.4.38)
- next:
- specifier: 14.2.0-canary.67
- version: 14.2.0-canary.67(react-dom@18.2.0)(react@18.2.0)
- postcss:
- specifier: ^8.4.31
- version: 8.4.38
- react:
- specifier: ^18.2.0
- version: 18.2.0
- tailwindcss:
- specifier: ^3.4.4
- version: 3.4.4
- tsconfig:
- specifier: workspace:*
- version: link:../tsconfig
- tsup:
- specifier: ^6.1.3
- version: 6.1.3(postcss@8.4.38)(typescript@5.5.3)
- typescript:
- specifier: ^5.1.6
- version: 5.5.3
-
packages/cli:
dependencies:
'@badgateway/oauth2-client':
@@ -642,6 +566,24 @@ importers:
'@tanstack/react-table':
specifier: ^8.17.3
version: 8.17.3(react-dom@18.2.0)(react@18.2.0)
+ '@visx/curve':
+ specifier: ^3.3.0
+ version: 3.3.0
+ '@visx/gradient':
+ specifier: ^3.3.0
+ version: 3.3.0(react@18.2.0)
+ '@visx/group':
+ specifier: ^3.3.0
+ version: 3.3.0(react@18.2.0)
+ '@visx/responsive':
+ specifier: ^2.10.0
+ version: 2.10.0(react@18.2.0)
+ '@visx/scale':
+ specifier: ^3.3.0
+ version: 3.3.0
+ '@visx/shape':
+ specifier: ^2.12.2
+ version: 2.12.2(react@18.2.0)
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@@ -10937,22 +10879,6 @@ packages:
postcss-value-parser: 4.2.0
dev: true
- /autoprefixer@10.4.16(postcss@8.4.38):
- resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==}
- engines: {node: ^10 || ^12 || >=14}
- hasBin: true
- peerDependencies:
- postcss: ^8.1.0
- dependencies:
- browserslist: 4.23.3
- caniuse-lite: 1.0.30001651
- fraction.js: 4.3.6
- normalize-range: 0.1.2
- picocolors: 1.0.1
- postcss: 8.4.38
- postcss-value-parser: 4.2.0
- dev: true
-
/available-typed-arrays@1.0.5:
resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
engines: {node: '>= 0.4'}
@@ -18278,23 +18204,6 @@ packages:
yaml: 1.10.2
dev: true
- /postcss-load-config@3.1.4(postcss@8.4.38):
- resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
- engines: {node: '>= 10'}
- peerDependencies:
- postcss: '>=8.0.9'
- ts-node: '>=9.0.0'
- peerDependenciesMeta:
- postcss:
- optional: true
- ts-node:
- optional: true
- dependencies:
- lilconfig: 2.1.0
- postcss: 8.4.38
- yaml: 1.10.2
- dev: true
-
/postcss-load-config@3.1.4(ts-node@10.9.2):
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'}
@@ -20996,43 +20905,6 @@ packages:
- ts-node
dev: true
- /tsup@6.1.3(postcss@8.4.38)(typescript@5.5.3):
- resolution: {integrity: sha512-eRpBnbfpDFng+EJNTQ90N7QAf4HAGGC7O3buHIjroKWK7D1ibk9/YnR/3cS8HsMU5T+6Oi+cnF+yU5WmCnB//Q==}
- engines: {node: '>=14'}
- hasBin: true
- peerDependencies:
- '@swc/core': ^1
- postcss: ^8.4.12
- typescript: ^4.1.0
- peerDependenciesMeta:
- '@swc/core':
- optional: true
- postcss:
- optional: true
- typescript:
- optional: true
- dependencies:
- bundle-require: 3.1.2(esbuild@0.14.54)
- cac: 6.7.14
- chokidar: 3.5.3
- debug: 4.3.4
- esbuild: 0.14.54
- execa: 5.1.1
- globby: 11.1.0
- joycon: 3.1.1
- postcss: 8.4.38
- postcss-load-config: 3.1.4(postcss@8.4.38)
- resolve-from: 5.0.0
- rollup: 2.79.1
- source-map: 0.8.0-beta.0
- sucrase: 3.34.0
- tree-kill: 1.2.2
- typescript: 5.5.3
- transitivePeerDependencies:
- - supports-color
- - ts-node
- dev: true
-
/tsup@6.7.0(ts-node@10.9.2)(typescript@5.6.2):
resolution: {integrity: sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==}
engines: {node: '>=14.18'}
@@ -21425,12 +21297,6 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- /typescript@5.5.3:
- resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==}
- engines: {node: '>=14.17'}
- hasBin: true
- dev: true
-
/typescript@5.6.2:
resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
engines: {node: '>=14.17'}
From d7bae88957efa9ded5ba394a56061e7748be4f34 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 18 Nov 2024 14:23:59 +0530
Subject: [PATCH 37/88] fix types
---
apps/web/app/api/events/client/route.ts | 4 +++-
apps/web/app/api/referrals/program/route.ts | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/apps/web/app/api/events/client/route.ts b/apps/web/app/api/events/client/route.ts
index 4ecdb848b7..464108c44d 100644
--- a/apps/web/app/api/events/client/route.ts
+++ b/apps/web/app/api/events/client/route.ts
@@ -17,10 +17,12 @@ export const GET = withAuth(async ({ searchParams, program, link }) => {
})
.parse(searchParams);
+ // TODO:
+ // Replace with sales data
+
const response = await getEvents({
...parsedParams,
linkId: link.id,
- obfuscateData: true,
});
return NextResponse.json(
diff --git a/apps/web/app/api/referrals/program/route.ts b/apps/web/app/api/referrals/program/route.ts
index bb7238dd34..57a9237732 100644
--- a/apps/web/app/api/referrals/program/route.ts
+++ b/apps/web/app/api/referrals/program/route.ts
@@ -1,5 +1,5 @@
import { withAuth } from "@/lib/referrals/auth";
-import { ProgramSchema } from "@/lib/zod/schemas/partners";
+import { ProgramSchema } from "@/lib/zod/schemas/programs";
import { NextResponse } from "next/server";
// GET /api/referrals/program - get the program for the given affiliate
From 7735b655e9c2d247adf7cc3ad50ba3a350f2884b Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 18 Nov 2024 14:39:21 +0530
Subject: [PATCH 38/88] update import
---
.../[slug]/settings/domains/default-domains.tsx | 3 +--
.../[slug]/settings/domains/page-client.tsx | 10 ++++++----
.../integrations/[integrationSlug]/page-client.tsx | 3 ++-
.../[slug]/settings/library/utm/template-card.tsx | 2 +-
.../[slug]/settings/payouts/bank-account.tsx | 3 +--
.../(dashboard)/[slug]/settings/payouts/wallet.tsx | 3 +--
.../(dashboard)/[slug]/settings/tokens/page-client.tsx | 2 +-
.../[slug]/settings/webhooks/page-client.tsx | 3 +--
.../account/settings/security/update-password.tsx | 3 +--
.../onboarding/(steps)/plan/plan-selector.tsx | 3 +--
apps/web/app/not-found.tsx | 2 +-
.../[partnerId]/settings/payouts/page-client.tsx | 3 +--
apps/web/ui/analytics/bar-list.tsx | 8 ++++++--
apps/web/ui/analytics/events/filter-button.tsx | 2 +-
apps/web/ui/analytics/export-button.tsx | 2 +-
apps/web/ui/integrations/integration-card.tsx | 3 +--
apps/web/ui/layout/sidebar/usage.tsx | 3 +--
apps/web/ui/links/archived-links-hint.tsx | 3 +--
apps/web/ui/links/links-container.tsx | 8 ++++++--
apps/web/ui/modals/add-edit-token-modal.tsx | 4 ++--
apps/web/ui/modals/add-edit-utm-template.modal.tsx | 3 +--
apps/web/ui/modals/import-csv-modal/field-mapping.tsx | 3 +--
apps/web/ui/modals/link-builder/targeting-modal.tsx | 2 +-
apps/web/ui/webhooks/webhook-events.tsx | 10 ++++++++--
apps/web/ui/workspaces/create-workspace-button.tsx | 3 +--
25 files changed, 49 insertions(+), 45 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/default-domains.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/default-domains.tsx
index 1f48299913..1dd6ac1580 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/default-domains.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/default-domains.tsx
@@ -5,7 +5,7 @@ import useDefaultDomains from "@/lib/swr/use-default-domains";
import useWorkspace from "@/lib/swr/use-workspace";
import { DomainCardTitleColumn } from "@/ui/domains/domain-card-title-column";
import { UpgradeRequiredToast } from "@/ui/shared/upgrade-required-toast";
-import { Logo, Switch } from "@dub/ui";
+import { Logo, Switch, TooltipContent } from "@dub/ui";
import {
Amazon,
CalendarDays,
@@ -15,7 +15,6 @@ import {
GoogleEnhanced,
Spotify,
} from "@dub/ui/src/icons";
-import { TooltipContent } from "@dub/ui/src/tooltip";
import { DUB_DOMAINS } from "@dub/utils";
import Link from "next/link";
import { useEffect, useState } from "react";
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/page-client.tsx
index eddc554522..1795d92ebb 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/page-client.tsx
@@ -17,15 +17,17 @@ import { SearchBoxPersisted } from "@/ui/shared/search-box";
import {
Badge,
Button,
+ CursorRays,
Globe,
+ InfoTooltip,
+ LinkBroken,
+ PaginationControls,
Popover,
+ ToggleGroup,
+ TooltipContent,
usePagination,
useRouterStuff,
} from "@dub/ui";
-import { CursorRays, LinkBroken } from "@dub/ui/src/icons";
-import { PaginationControls } from "@dub/ui/src/pagination-controls";
-import { ToggleGroup } from "@dub/ui/src/toggle-group";
-import { InfoTooltip, TooltipContent } from "@dub/ui/src/tooltip";
import { capitalize, pluralize } from "@dub/utils";
import { ChevronDown, Crown } from "lucide-react";
import { useEffect, useState } from "react";
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/integrations/[integrationSlug]/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/integrations/[integrationSlug]/page-client.tsx
index b21fd0d58d..ede6de6341 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/integrations/[integrationSlug]/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/integrations/[integrationSlug]/page-client.tsx
@@ -17,6 +17,8 @@ import {
MaxWidthWrapper,
Popover,
TokenAvatar,
+ Tooltip,
+ TooltipContent,
} from "@dub/ui";
import {
CircleWarning,
@@ -25,7 +27,6 @@ import {
OfficeBuilding,
ShieldCheck,
} from "@dub/ui/src/icons";
-import { Tooltip, TooltipContent } from "@dub/ui/src/tooltip";
import {
cn,
formatDate,
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/library/utm/template-card.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/library/utm/template-card.tsx
index 054308383e..11256098dc 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/library/utm/template-card.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/library/utm/template-card.tsx
@@ -11,13 +11,13 @@ import {
Popover,
Tooltip,
useKeyboardShortcut,
+ UTM_PARAMETERS,
} from "@dub/ui";
import {
DiamondTurnRight,
LoadingSpinner,
PenWriting,
} from "@dub/ui/src/icons";
-import { UTM_PARAMETERS } from "@dub/ui/src/utm-builder";
import { cn, formatDate } from "@dub/utils";
import { Fragment, useContext, useState } from "react";
import { toast } from "sonner";
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/payouts/bank-account.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/payouts/bank-account.tsx
index 0bd10dd3d9..4190d822cb 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/payouts/bank-account.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/payouts/bank-account.tsx
@@ -1,8 +1,7 @@
"use client";
import useWorkspace from "@/lib/swr/use-workspace";
-import { StatusBadge, Tooltip } from "@dub/ui";
-import { SimpleTooltipContent } from "@dub/ui/src/tooltip";
+import { SimpleTooltipContent, StatusBadge, Tooltip } from "@dub/ui";
import { Fragment } from "react";
export const BankAccount = () => {
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/payouts/wallet.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/payouts/wallet.tsx
index 359c9a3e70..7c22d0fe6f 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/payouts/wallet.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/payouts/wallet.tsx
@@ -3,8 +3,7 @@
import useDotsApp from "@/lib/swr/use-dots-app";
import useWorkspace from "@/lib/swr/use-workspace";
import { useDepositFundsModal } from "@/ui/modals/deposit-funds-modal";
-import { Button } from "@dub/ui";
-import { SimpleTooltipContent } from "@dub/ui/src/tooltip";
+import { Button, SimpleTooltipContent } from "@dub/ui";
import { currencyFormatter } from "@dub/utils";
export const Wallet = () => {
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/tokens/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/tokens/page-client.tsx
index 6beda3581e..a52597348f 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/tokens/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/tokens/page-client.tsx
@@ -16,9 +16,9 @@ import {
LoadingSpinner,
Popover,
TokenAvatar,
+ Tooltip,
} from "@dub/ui";
import { Key } from "@dub/ui/src/icons";
-import { Tooltip } from "@dub/ui/src/tooltip";
import { fetcher, timeAgo } from "@dub/utils";
import { Edit3, MoreVertical } from "lucide-react";
import { useState } from "react";
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/page-client.tsx
index d2c91ebaf4..20121bf986 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/page-client.tsx
@@ -6,8 +6,7 @@ import useWorkspace from "@/lib/swr/use-workspace";
import EmptyState from "@/ui/shared/empty-state";
import WebhookCard from "@/ui/webhooks/webhook-card";
import WebhookPlaceholder from "@/ui/webhooks/webhook-placeholder";
-import { Button, TooltipContent } from "@dub/ui";
-import { InfoTooltip } from "@dub/ui/src/tooltip";
+import { Button, InfoTooltip, TooltipContent } from "@dub/ui";
import { Webhook } from "lucide-react";
import { redirect, useRouter } from "next/navigation";
diff --git a/apps/web/app/app.dub.co/(dashboard)/account/settings/security/update-password.tsx b/apps/web/app/app.dub.co/(dashboard)/account/settings/security/update-password.tsx
index 0c718b33f5..253d552608 100644
--- a/apps/web/app/app.dub.co/(dashboard)/account/settings/security/update-password.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/account/settings/security/update-password.tsx
@@ -2,8 +2,7 @@
import z from "@/lib/zod";
import { updatePasswordSchema } from "@/lib/zod/schemas/auth";
-import { Button, Input, Label } from "@dub/ui";
-import { Tooltip } from "@dub/ui/src/tooltip";
+import { Button, Input, Label, Tooltip } from "@dub/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
diff --git a/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/plan/plan-selector.tsx b/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/plan/plan-selector.tsx
index 612ea58461..12d415780e 100644
--- a/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/plan/plan-selector.tsx
+++ b/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/plan/plan-selector.tsx
@@ -3,8 +3,7 @@
import useWorkspace from "@/lib/swr/use-workspace";
import { PlanFeatures } from "@/ui/workspaces/plan-features";
import { UpgradePlanButton } from "@/ui/workspaces/upgrade-plan-button";
-import { Badge, CountingNumbers } from "@dub/ui";
-import { ToggleGroup } from "@dub/ui/src/toggle-group";
+import { Badge, CountingNumbers, ToggleGroup } from "@dub/ui";
import { PRO_PLAN, SELF_SERVE_PAID_PLANS } from "@dub/utils";
import { useEffect, useState } from "react";
diff --git a/apps/web/app/not-found.tsx b/apps/web/app/not-found.tsx
index cc37906fb6..f9499a7d71 100644
--- a/apps/web/app/not-found.tsx
+++ b/apps/web/app/not-found.tsx
@@ -1,5 +1,5 @@
import { NewBackground } from "@/ui/shared/new-background";
-import { Wordmark } from "@dub/ui/src/wordmark";
+import { Wordmark } from "@dub/ui";
import Link from "next/link";
export default function NotFound() {
diff --git a/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/settings/payouts/page-client.tsx b/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/settings/payouts/page-client.tsx
index 8c1b36cd78..1e2dede5c9 100644
--- a/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/settings/payouts/page-client.tsx
+++ b/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/settings/payouts/page-client.tsx
@@ -7,9 +7,8 @@ import LayoutLoader from "@/ui/layout/layout-loader";
import { usePayoutWithdrawSheet } from "@/ui/partners/payout-withdraw-sheet";
import { AnimatedEmptyState } from "@/ui/shared/animated-empty-state";
import { CheckCircleFill, X } from "@/ui/shared/icons";
-import { Button, Modal, Note } from "@dub/ui";
+import { Button, Modal, Note, SimpleTooltipContent, Tooltip } from "@dub/ui";
import { GiftFill, GreekTemple, MobilePhone } from "@dub/ui/src/icons";
-import { SimpleTooltipContent, Tooltip } from "@dub/ui/src/tooltip";
import {
cn,
currencyFormatter,
diff --git a/apps/web/ui/analytics/bar-list.tsx b/apps/web/ui/analytics/bar-list.tsx
index 0ba252243d..63091d8667 100644
--- a/apps/web/ui/analytics/bar-list.tsx
+++ b/apps/web/ui/analytics/bar-list.tsx
@@ -1,8 +1,12 @@
"use client";
import { LinkProps } from "@/lib/types";
-import { NumberTooltip, Tooltip, useMediaQuery } from "@dub/ui";
-import { LinkifyTooltipContent } from "@dub/ui/src/tooltip";
+import {
+ LinkifyTooltipContent,
+ NumberTooltip,
+ Tooltip,
+ useMediaQuery,
+} from "@dub/ui";
import { cn, getPrettyUrl, nFormatter } from "@dub/utils";
import { motion } from "framer-motion";
import { Search } from "lucide-react";
diff --git a/apps/web/ui/analytics/events/filter-button.tsx b/apps/web/ui/analytics/events/filter-button.tsx
index cc90bf48cc..1a6c7d437e 100644
--- a/apps/web/ui/analytics/events/filter-button.tsx
+++ b/apps/web/ui/analytics/events/filter-button.tsx
@@ -1,4 +1,4 @@
-import { useRouterStuff } from "@dub/ui/src/hooks/use-router-stuff";
+import { useRouterStuff } from "@dub/ui";
import { FilterBars } from "@dub/ui/src/icons";
import Link from "next/link";
diff --git a/apps/web/ui/analytics/export-button.tsx b/apps/web/ui/analytics/export-button.tsx
index c511310c5f..b4d8b6a7f5 100644
--- a/apps/web/ui/analytics/export-button.tsx
+++ b/apps/web/ui/analytics/export-button.tsx
@@ -1,4 +1,4 @@
-import { Button } from "@dub/ui/src/button";
+import { Button } from "@dub/ui";
import { Download } from "@dub/ui/src/icons";
import { Dispatch, SetStateAction, useContext, useState } from "react";
import { toast } from "sonner";
diff --git a/apps/web/ui/integrations/integration-card.tsx b/apps/web/ui/integrations/integration-card.tsx
index 34a666e4e5..99e9db65fc 100644
--- a/apps/web/ui/integrations/integration-card.tsx
+++ b/apps/web/ui/integrations/integration-card.tsx
@@ -3,14 +3,13 @@
import useIntegrations from "@/lib/swr/use-integrations";
import useWorkspace from "@/lib/swr/use-workspace";
import { InstalledIntegrationProps } from "@/lib/types";
-import { BlurImage, TokenAvatar } from "@dub/ui";
+import { BlurImage, TokenAvatar, Tooltip } from "@dub/ui";
import {
CircleWarning,
Download,
OfficeBuilding,
ShieldCheck,
} from "@dub/ui/src/icons";
-import { Tooltip } from "@dub/ui/src/tooltip";
import { pluralize } from "@dub/utils";
import Link from "next/link";
diff --git a/apps/web/ui/layout/sidebar/usage.tsx b/apps/web/ui/layout/sidebar/usage.tsx
index 8444d7e73c..0a950a6e37 100644
--- a/apps/web/ui/layout/sidebar/usage.tsx
+++ b/apps/web/ui/layout/sidebar/usage.tsx
@@ -2,8 +2,7 @@
import useWorkspace from "@/lib/swr/use-workspace";
import ManageSubscriptionButton from "@/ui/workspaces/manage-subscription-button";
-import { AnimatedSizeContainer, Icon } from "@dub/ui";
-import { buttonVariants } from "@dub/ui/src/button";
+import { AnimatedSizeContainer, Icon, buttonVariants } from "@dub/ui";
import { CursorRays, Hyperlink } from "@dub/ui/src/icons";
import { cn, getFirstAndLastDay, nFormatter } from "@dub/utils";
import { ChevronRight } from "lucide-react";
diff --git a/apps/web/ui/links/archived-links-hint.tsx b/apps/web/ui/links/archived-links-hint.tsx
index 10fe30de2a..6530150f13 100644
--- a/apps/web/ui/links/archived-links-hint.tsx
+++ b/apps/web/ui/links/archived-links-hint.tsx
@@ -1,7 +1,6 @@
import useLinksCount from "@/lib/swr/use-links-count";
-import { Button } from "@dub/ui";
+import { Button, Tooltip } from "@dub/ui";
import { BoxArchive } from "@dub/ui/src/icons";
-import { Tooltip } from "@dub/ui/src/tooltip";
import { pluralize } from "@dub/utils";
import { useSearchParams } from "next/navigation";
import { useContext } from "react";
diff --git a/apps/web/ui/links/links-container.tsx b/apps/web/ui/links/links-container.tsx
index 0c4bd876cf..8189c5c065 100644
--- a/apps/web/ui/links/links-container.tsx
+++ b/apps/web/ui/links/links-container.tsx
@@ -3,9 +3,13 @@
import useLinks from "@/lib/swr/use-links";
import useLinksCount from "@/lib/swr/use-links-count";
import { ExpandedLinkProps, UserProps } from "@/lib/types";
-import { CardList, MaxWidthWrapper, usePagination } from "@dub/ui";
+import {
+ CardList,
+ MaxWidthWrapper,
+ PaginationControls,
+ usePagination,
+} from "@dub/ui";
import { CursorRays, Hyperlink, LoadingSpinner } from "@dub/ui/src/icons";
-import { PaginationControls } from "@dub/ui/src/pagination-controls";
import { cn } from "@dub/utils";
import { useSearchParams } from "next/navigation";
import {
diff --git a/apps/web/ui/modals/add-edit-token-modal.tsx b/apps/web/ui/modals/add-edit-token-modal.tsx
index 005d45dce1..03b44cdc35 100644
--- a/apps/web/ui/modals/add-edit-token-modal.tsx
+++ b/apps/web/ui/modals/add-edit-token-modal.tsx
@@ -16,9 +16,9 @@ import {
Modal,
RadioGroup,
RadioGroupItem,
+ SimpleTooltipContent,
+ ToggleGroup,
} from "@dub/ui";
-import { ToggleGroup } from "@dub/ui/src/toggle-group";
-import { SimpleTooltipContent } from "@dub/ui/src/tooltip";
import { cn } from "@dub/utils";
import {
Dispatch,
diff --git a/apps/web/ui/modals/add-edit-utm-template.modal.tsx b/apps/web/ui/modals/add-edit-utm-template.modal.tsx
index 28a20d12f1..8c043d5313 100644
--- a/apps/web/ui/modals/add-edit-utm-template.modal.tsx
+++ b/apps/web/ui/modals/add-edit-utm-template.modal.tsx
@@ -1,7 +1,6 @@
import useWorkspace from "@/lib/swr/use-workspace";
import { UtmTemplateProps } from "@/lib/types";
-import { Button, Modal, useMediaQuery } from "@dub/ui";
-import { UTMBuilder } from "@dub/ui/src/utm-builder";
+import { Button, Modal, useMediaQuery, UTMBuilder } from "@dub/ui";
import posthog from "posthog-js";
import {
Dispatch,
diff --git a/apps/web/ui/modals/import-csv-modal/field-mapping.tsx b/apps/web/ui/modals/import-csv-modal/field-mapping.tsx
index 6f0bf43b30..456d06f4dd 100644
--- a/apps/web/ui/modals/import-csv-modal/field-mapping.tsx
+++ b/apps/web/ui/modals/import-csv-modal/field-mapping.tsx
@@ -1,7 +1,7 @@
"use client";
import { generateCsvMapping } from "@/lib/ai/generate-csv-mapping";
-import { Button, IconMenu, InfoTooltip, Popover } from "@dub/ui";
+import { Button, IconMenu, InfoTooltip, Popover, Tooltip } from "@dub/ui";
import {
ArrowRight,
Check,
@@ -9,7 +9,6 @@ import {
TableIcon,
Xmark,
} from "@dub/ui/src/icons";
-import { Tooltip } from "@dub/ui/src/tooltip";
import {
cn,
formatDate,
diff --git a/apps/web/ui/modals/link-builder/targeting-modal.tsx b/apps/web/ui/modals/link-builder/targeting-modal.tsx
index f41d2d98d1..701d786a0a 100644
--- a/apps/web/ui/modals/link-builder/targeting-modal.tsx
+++ b/apps/web/ui/modals/link-builder/targeting-modal.tsx
@@ -6,9 +6,9 @@ import {
SimpleTooltipContent,
Tooltip,
useKeyboardShortcut,
+ UTM_PARAMETERS,
} from "@dub/ui";
import { Crosshairs3, Trash } from "@dub/ui/src/icons";
-import { UTM_PARAMETERS } from "@dub/ui/src/utm-builder";
import {
cn,
constructURLFromUTMParams,
diff --git a/apps/web/ui/webhooks/webhook-events.tsx b/apps/web/ui/webhooks/webhook-events.tsx
index 914a9ca215..eee7cf82eb 100644
--- a/apps/web/ui/webhooks/webhook-events.tsx
+++ b/apps/web/ui/webhooks/webhook-events.tsx
@@ -1,9 +1,15 @@
"use client";
import { WebhookEventProps } from "@/lib/types";
-import { Button, Sheet, useCopyToClipboard, useMediaQuery } from "@dub/ui";
+import {
+ Button,
+ ButtonTooltip,
+ Sheet,
+ Tooltip,
+ useCopyToClipboard,
+ useMediaQuery,
+} from "@dub/ui";
import { CircleCheck, CircleHalfDottedClock, Copy } from "@dub/ui/src/icons";
-import { ButtonTooltip, Tooltip } from "@dub/ui/src/tooltip";
import { PropsWithChildren, useEffect, useState } from "react";
import { Highlighter } from "shiki";
import { toast } from "sonner";
diff --git a/apps/web/ui/workspaces/create-workspace-button.tsx b/apps/web/ui/workspaces/create-workspace-button.tsx
index 2a2d9a2273..989698c931 100644
--- a/apps/web/ui/workspaces/create-workspace-button.tsx
+++ b/apps/web/ui/workspaces/create-workspace-button.tsx
@@ -2,8 +2,7 @@
import useWorkspaces from "@/lib/swr/use-workspaces";
import { ModalContext } from "@/ui/modals/modal-provider";
-import { Button } from "@dub/ui";
-import { TooltipContent } from "@dub/ui/src/tooltip";
+import { Button, TooltipContent } from "@dub/ui";
import { FREE_WORKSPACES_LIMIT } from "@dub/utils";
import { useContext } from "react";
From 1e244527faf20cb552dc4fc81a45e6940d804da2 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 18 Nov 2024 20:37:41 +0530
Subject: [PATCH 39/88] update endpoints
---
.../app/api/referrals/sales/count/route.ts | 23 +++++
apps/web/app/api/referrals/sales/route.ts | 30 +++++++
.../[slug]/settings/referrals/page-client.tsx | 13 ++-
apps/web/app/app.dub.co/embed/page.tsx | 6 +-
apps/web/app/app.dub.co/embed/sale-table.tsx | 87 ++++++++++---------
5 files changed, 104 insertions(+), 55 deletions(-)
create mode 100644 apps/web/app/api/referrals/sales/count/route.ts
create mode 100644 apps/web/app/api/referrals/sales/route.ts
diff --git a/apps/web/app/api/referrals/sales/count/route.ts b/apps/web/app/api/referrals/sales/count/route.ts
new file mode 100644
index 0000000000..e212938170
--- /dev/null
+++ b/apps/web/app/api/referrals/sales/count/route.ts
@@ -0,0 +1,23 @@
+import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
+import { prisma } from "@/lib/prisma";
+import { withAuth } from "@/lib/referrals/auth";
+import { getPartnerSalesCountQuerySchema } from "@/lib/zod/schemas/partners";
+import { NextResponse } from "next/server";
+
+// GET /api/referrals/sales/count – get sales count for a link
+export const GET = withAuth(async ({ searchParams, link }) => {
+ const parsed = getPartnerSalesCountQuerySchema.parse(searchParams);
+ const { startDate, endDate } = getStartEndDates(parsed);
+
+ const salesCount = await prisma.sale.count({
+ where: {
+ linkId: link.id,
+ createdAt: {
+ gte: startDate.toISOString(),
+ lte: endDate.toISOString(),
+ },
+ },
+ });
+
+ return NextResponse.json({ count: salesCount });
+});
diff --git a/apps/web/app/api/referrals/sales/route.ts b/apps/web/app/api/referrals/sales/route.ts
new file mode 100644
index 0000000000..70f7cd954b
--- /dev/null
+++ b/apps/web/app/api/referrals/sales/route.ts
@@ -0,0 +1,30 @@
+import { prisma } from "@/lib/prisma";
+import { withAuth } from "@/lib/referrals/auth";
+import z from "@/lib/zod";
+import { PartnerSaleResponseSchema } from "@/lib/zod/schemas/partners";
+import { NextResponse } from "next/server";
+
+// GET /api/referrals/sales – get sales for a link
+export const GET = withAuth(async ({ link }) => {
+ const sales = await prisma.sale.findMany({
+ where: {
+ linkId: link.id,
+ },
+ select: {
+ id: true,
+ amount: true,
+ earnings: true,
+ currency: true,
+ status: true,
+ createdAt: true,
+ updatedAt: true,
+ customer: true,
+ },
+ take: 10,
+ orderBy: {
+ createdAt: "desc",
+ },
+ });
+
+ return NextResponse.json(z.array(PartnerSaleResponseSchema).parse(sales));
+});
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index 76fca3d872..c9000988af 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -2,6 +2,7 @@
import useWorkspace from "@/lib/swr/use-workspace";
import { ReferralsEmbed } from "@/ui/embed/referrals-embed";
+import { LoadingSpinner } from "@dub/ui";
import { redirect } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
@@ -35,13 +36,9 @@ export default function ReferralsPageClient() {
redirect(`/${slug}/settings`);
}
- if (!publicToken) {
- return Loading...
;
- }
-
- return (
- <>
-
- >
+ return publicToken ? (
+
+ ) : (
+
);
}
diff --git a/apps/web/app/app.dub.co/embed/page.tsx b/apps/web/app/app.dub.co/embed/page.tsx
index 450571379e..c920f89d35 100644
--- a/apps/web/app/app.dub.co/embed/page.tsx
+++ b/apps/web/app/app.dub.co/embed/page.tsx
@@ -1,9 +1,5 @@
import { ReferralsEmbedPageClient } from "./page-client";
export default async function ReferralsEmbedPage() {
- return (
- <>
-
- >
- );
+ return ;
}
diff --git a/apps/web/app/app.dub.co/embed/sale-table.tsx b/apps/web/app/app.dub.co/embed/sale-table.tsx
index f9afb3e6d0..66709ed9f8 100644
--- a/apps/web/app/app.dub.co/embed/sale-table.tsx
+++ b/apps/web/app/app.dub.co/embed/sale-table.tsx
@@ -1,55 +1,46 @@
"use client";
-import { IntervalOptions } from "@/lib/analytics/types";
+import { PartnerSaleResponse } from "@/lib/types";
+import { SaleStatusBadges } from "@/ui/partners/sale-status-badges";
import { AnimatedEmptyState } from "@/ui/shared/animated-empty-state";
import SimpleDateRangePicker from "@/ui/shared/simple-date-range-picker";
-import { Table, usePagination, useRouterStuff, useTable } from "@dub/ui";
+import {
+ StatusBadge,
+ Table,
+ usePagination,
+ useRouterStuff,
+ useTable,
+} from "@dub/ui";
import { CircleDollar } from "@dub/ui/src/icons";
-import { currencyFormatter, formatDate } from "@dub/utils";
-import useReferralAnalytics from "./use-referral-analytics";
-import useReferralEvents from "./use-referral-events";
+import { currencyFormatter, fetcher, formatDate } from "@dub/utils";
+import useSWR from "swr";
export function SaleTable({ limit }: { limit?: number }) {
- const { queryParams, searchParamsObj } = useRouterStuff();
+ const { pagination, setPagination } = usePagination(limit);
+ const { queryParams, searchParamsObj, getQueryString } = useRouterStuff();
const {
- start,
- end,
- interval,
- sortBy = "timestamp",
- order = "desc",
- } = searchParamsObj as {
- start?: string;
- end?: string;
- interval?: IntervalOptions;
+ data: sales,
+ isLoading,
+ error,
+ } = useSWR(
+ `/api/referrals/sales${getQueryString()}`,
+ fetcher,
+ );
+
+ const { data: salesCount } = useSWR<{ count: number }>(
+ `/api/referrals/sales/count${getQueryString()}`,
+ fetcher,
+ );
+
+ const { sortBy = "timestamp", order = "desc" } = searchParamsObj as {
sortBy?: "timestamp";
order?: "asc" | "desc";
};
- const { data: { sales: totalSaleEvents } = {} } = useReferralAnalytics({
- interval,
- start: start ? new Date(start) : undefined,
- end: end ? new Date(end) : undefined,
- });
-
- const {
- data: saleEvents,
- loading,
- error,
- } = useReferralEvents({
- event: "sales",
- interval: interval as any,
- start: start ? new Date(start) : undefined,
- end: end ? new Date(end) : undefined,
- order,
- sortBy,
- });
-
- const { pagination, setPagination } = usePagination(limit);
-
const { table, ...tableProps } = useTable({
- data: saleEvents?.slice(0, limit) || [],
- loading,
+ data: sales?.slice(0, limit) || [],
+ loading: isLoading,
error: error ? "Failed to fetch sales events." : undefined,
columns: [
{
@@ -57,7 +48,7 @@ export function SaleTable({ limit }: { limit?: number }) {
header: "Date",
accessorKey: "timestamp",
cell: ({ row }) => {
- return formatDate(row.original.timestamp, { month: "short" });
+ return formatDate(row.original.createdAt, { month: "short" });
},
},
{
@@ -73,7 +64,7 @@ export function SaleTable({ limit }: { limit?: number }) {
header: "Sale Amount",
accessorKey: "sale",
cell: ({ row }) => {
- return currencyFormatter(row.original.sale.amount / 100, {
+ return currencyFormatter(row.original.amount / 100, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
@@ -90,6 +81,18 @@ export function SaleTable({ limit }: { limit?: number }) {
});
},
},
+ {
+ header: "Status",
+ cell: ({ row }) => {
+ const badge = SaleStatusBadges[row.original.status];
+
+ return (
+
+ {badge.label}
+
+ );
+ },
+ },
],
...(!limit && {
pagination,
@@ -105,7 +108,7 @@ export function SaleTable({ limit }: { limit?: number }) {
},
}),
}),
- rowCount: totalSaleEvents,
+ rowCount: salesCount?.count || 0,
emptyState: (
)}
- {loading || saleEvents?.length ? (
+ {isLoading || sales?.length ? (
Date: Mon, 18 Nov 2024 20:49:17 +0530
Subject: [PATCH 40/88] wip request invite form
---
apps/web/app/api/referrals/invite/route.ts | 15 ++
.../embed/request-partner-invite-modal.tsx | 139 ++++++++++++++++++
apps/web/lib/dots/schemas.ts | 4 +
3 files changed, 158 insertions(+)
create mode 100644 apps/web/app/api/referrals/invite/route.ts
create mode 100644 apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx
diff --git a/apps/web/app/api/referrals/invite/route.ts b/apps/web/app/api/referrals/invite/route.ts
new file mode 100644
index 0000000000..bf7812ff37
--- /dev/null
+++ b/apps/web/app/api/referrals/invite/route.ts
@@ -0,0 +1,15 @@
+import { parseRequestBody } from "@/lib/api/utils";
+import { requestPartnerInviteSchema } from "@/lib/dots/schemas";
+import { withAuth } from "@/lib/referrals/auth";
+import { NextResponse } from "next/server";
+
+// POST /api/referrals/invite - invite a partner to dub partners
+export const POST = withAuth(async ({ req }) => {
+ const { email } = requestPartnerInviteSchema.parse(
+ await parseRequestBody(req),
+ );
+
+ // TODO: Invite the partner to program
+
+ return NextResponse.json({});
+});
diff --git a/apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx b/apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx
new file mode 100644
index 0000000000..0e6609a344
--- /dev/null
+++ b/apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx
@@ -0,0 +1,139 @@
+import { requestPartnerInviteSchema } from "@/lib/dots/schemas";
+import useWorkspace from "@/lib/swr/use-workspace";
+import z from "@/lib/zod";
+import { Button, Modal } from "@dub/ui";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useCallback, useMemo, useState } from "react";
+import { useForm } from "react-hook-form";
+
+type RequestPartnerInvite = z.infer;
+
+interface RequestPartnerInviteProps {
+ showModal: boolean;
+ setShowModal: (showModal: boolean) => void;
+}
+
+interface RequestPartnerInviteFormProps {
+ closeModal: () => void;
+}
+
+const RequestPartnerInvite = ({
+ showModal,
+ setShowModal,
+}: RequestPartnerInviteProps) => {
+ return (
+
+
+ Request invite to Dub Partners for receiving payouts
+
+
+ setShowModal(false)} />
+
+
+ );
+};
+
+const RequestPartnerInviteForm = ({
+ closeModal,
+}: RequestPartnerInviteFormProps) => {
+ const { id: workspaceId, mutate } = useWorkspace();
+
+ const {
+ register,
+ handleSubmit,
+ formState: { isValid, isSubmitting },
+ } = useForm({
+ resolver: zodResolver(requestPartnerInviteSchema),
+ });
+
+ // const { executeAsync, isExecuting } = useAction(requestPartnerInviteAction, {
+ // async onSuccess() {
+ // toast.success(
+ // "Bank account added successfully. Waiting for verification.",
+ // );
+ // mutate();
+ // closeModal();
+ // },
+ // onError({ error }) {
+ // toast.error(error.serverError?.serverError);
+ // },
+ // });
+
+ const onSubmit = async (data: RequestPartnerInvite) => {
+ // await executeAsync({ ...data, workspaceId: workspaceId! });
+ };
+
+ return (
+
+ );
+};
+
+export function useRequestPartnerInviteModal() {
+ const [showRequestPartnerInviteModal, setShowRequestPartnerInviteModal] =
+ useState(false);
+
+ const RequestPartnerInviteModal = useCallback(() => {
+ return (
+
+ );
+ }, [showRequestPartnerInviteModal, setShowRequestPartnerInviteModal]);
+
+ return useMemo(
+ () => ({
+ setShowRequestPartnerInviteModal,
+ RequestPartnerInviteModal,
+ }),
+ [setShowRequestPartnerInviteModal, RequestPartnerInviteModal],
+ );
+}
diff --git a/apps/web/lib/dots/schemas.ts b/apps/web/lib/dots/schemas.ts
index d1af9c56dd..f56c16d74a 100644
--- a/apps/web/lib/dots/schemas.ts
+++ b/apps/web/lib/dots/schemas.ts
@@ -121,3 +121,7 @@ export const dotsUserSchema = z.object({
payout_methods: z.array(payoutMethodSchema),
compliance: z.any(),
});
+
+export const requestPartnerInviteSchema = z.object({
+ email: z.string().email(),
+});
From 0716c0c0a60be59fd1fdb60a5d6be09077b330bd Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 18 Nov 2024 21:07:13 +0530
Subject: [PATCH 41/88] /api/referrals/invite
---
apps/web/app/api/referrals/invite/route.ts | 120 ++++++++++++++++++++-
apps/web/lib/referrals/auth.ts | 24 +++--
2 files changed, 129 insertions(+), 15 deletions(-)
diff --git a/apps/web/app/api/referrals/invite/route.ts b/apps/web/app/api/referrals/invite/route.ts
index bf7812ff37..1b3fbf3ec4 100644
--- a/apps/web/app/api/referrals/invite/route.ts
+++ b/apps/web/app/api/referrals/invite/route.ts
@@ -1,15 +1,127 @@
-import { parseRequestBody } from "@/lib/api/utils";
+import { createId, parseRequestBody } from "@/lib/api/utils";
import { requestPartnerInviteSchema } from "@/lib/dots/schemas";
+import { updateConfig } from "@/lib/edge-config";
+import { prisma } from "@/lib/prisma";
import { withAuth } from "@/lib/referrals/auth";
+import { recordLink } from "@/lib/tinybird";
+import { sendEmail } from "emails";
+import PartnerInvite from "emails/partner-invite";
import { NextResponse } from "next/server";
// POST /api/referrals/invite - invite a partner to dub partners
-export const POST = withAuth(async ({ req }) => {
+export const POST = withAuth(async ({ req, link, program, workspace }) => {
const { email } = requestPartnerInviteSchema.parse(
await parseRequestBody(req),
);
- // TODO: Invite the partner to program
+ // TODO:
+ // We're repating the same logic in the invite-partner action.
+ // We should move it to a shared location if it makes sense.
- return NextResponse.json({});
+ const [
+ programEnrollment,
+ programInvite,
+ linkInProgramEnrollment,
+ linkInProgramInvite,
+ tags,
+ ] = await Promise.all([
+ prisma.programEnrollment.findFirst({
+ where: {
+ programId: program.id,
+ partner: {
+ users: {
+ some: {
+ user: {
+ email,
+ },
+ },
+ },
+ },
+ },
+ }),
+
+ prisma.programInvite.findUnique({
+ where: {
+ email_programId: {
+ email,
+ programId: program.id,
+ },
+ },
+ }),
+
+ prisma.programEnrollment.findUnique({
+ where: {
+ linkId: link.id,
+ },
+ }),
+
+ prisma.programInvite.findUnique({
+ where: {
+ linkId: link.id,
+ },
+ }),
+
+ prisma.tag.findMany({
+ where: {
+ links: {
+ some: {
+ linkId: link.id,
+ },
+ },
+ },
+ }),
+ ]);
+
+ if (programEnrollment) {
+ throw new Error(`Partner ${email} already enrolled in this program.`);
+ }
+
+ if (programInvite) {
+ throw new Error(`Partner ${email} already invited to this program.`);
+ }
+
+ if (linkInProgramEnrollment || linkInProgramInvite) {
+ throw new Error("Link is already associated with another partner.");
+ }
+
+ const [result, _] = await Promise.all([
+ prisma.programInvite.create({
+ data: {
+ id: createId({ prefix: "pgi_" }),
+ email,
+ linkId: link.id,
+ programId: program.id,
+ },
+ }),
+
+ updateConfig({
+ key: "partnersPortal",
+ value: email,
+ }),
+
+ recordLink({
+ domain: link.domain,
+ key: link.key,
+ link_id: link.id,
+ created_at: link.createdAt,
+ url: link.url,
+ tag_ids: tags.map((t) => t.id) || [],
+ program_id: program.id,
+ workspace_id: workspace.id,
+ deleted: false,
+ }),
+ ]);
+
+ await sendEmail({
+ subject: `${program.name} invited you to join Dub Partners`,
+ email,
+ react: PartnerInvite({
+ email,
+ appName: `${process.env.NEXT_PUBLIC_APP_NAME}`,
+ programName: program.name,
+ programLogo: program.logo,
+ }),
+ });
+
+ return NextResponse.json(result);
});
diff --git a/apps/web/lib/referrals/auth.ts b/apps/web/lib/referrals/auth.ts
index dfb0254b34..6ee1ea4cfe 100644
--- a/apps/web/lib/referrals/auth.ts
+++ b/apps/web/lib/referrals/auth.ts
@@ -103,17 +103,19 @@ export const withAuth = (handler: WithAuthHandler) => {
});
}
- const workspace = await prisma.project.findUniqueOrThrow({
- where: {
- id: link.projectId!,
- },
- });
-
- const program = await prisma.program.findFirstOrThrow({
- where: {
- workspaceId: workspace.id,
- },
- });
+ const [workspace, program] = await Promise.all([
+ prisma.project.findUniqueOrThrow({
+ where: {
+ id: link.projectId!,
+ },
+ }),
+
+ prisma.program.findFirstOrThrow({
+ where: {
+ workspaceId: link.projectId!,
+ },
+ }),
+ ]);
return await handler({
req,
From 16ece51b48f47a689b84537fe8e7e176d0446acb Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Mon, 18 Nov 2024 21:09:32 +0530
Subject: [PATCH 42/88] use the api
---
.../embed/request-partner-invite-modal.tsx | 30 ++++++++-----------
1 file changed, 13 insertions(+), 17 deletions(-)
diff --git a/apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx b/apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx
index 0e6609a344..a34ba96ece 100644
--- a/apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx
+++ b/apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx
@@ -1,10 +1,10 @@
import { requestPartnerInviteSchema } from "@/lib/dots/schemas";
-import useWorkspace from "@/lib/swr/use-workspace";
import z from "@/lib/zod";
import { Button, Modal } from "@dub/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useCallback, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
+import { toast } from "sonner";
type RequestPartnerInvite = z.infer;
@@ -40,8 +40,6 @@ const RequestPartnerInvite = ({
const RequestPartnerInviteForm = ({
closeModal,
}: RequestPartnerInviteFormProps) => {
- const { id: workspaceId, mutate } = useWorkspace();
-
const {
register,
handleSubmit,
@@ -50,21 +48,19 @@ const RequestPartnerInviteForm = ({
resolver: zodResolver(requestPartnerInviteSchema),
});
- // const { executeAsync, isExecuting } = useAction(requestPartnerInviteAction, {
- // async onSuccess() {
- // toast.success(
- // "Bank account added successfully. Waiting for verification.",
- // );
- // mutate();
- // closeModal();
- // },
- // onError({ error }) {
- // toast.error(error.serverError?.serverError);
- // },
- // });
-
const onSubmit = async (data: RequestPartnerInvite) => {
- // await executeAsync({ ...data, workspaceId: workspaceId! });
+ const response = await fetch("/api/referrals/invite", {
+ method: "POST",
+ body: JSON.stringify(data),
+ });
+
+ if (response.ok) {
+ toast.success("Invite request sent successfully!");
+ closeModal();
+ }
+
+ const { error } = await response.json();
+ toast.error(error.message);
};
return (
From 76ddece0264c8071ba9ddbbf2e67d56efa324321 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Tue, 19 Nov 2024 16:00:19 +0530
Subject: [PATCH 43/88] move HeroBackground
---
apps/web/app/app.dub.co/embed/page-client.tsx | 2 +-
.../[programId]/hero-background.tsx | 49 -------------------
.../[partnerId]/[programId]/page-client.tsx | 2 +-
.../embed => ui/partners}/hero-background.tsx | 0
4 files changed, 2 insertions(+), 51 deletions(-)
delete mode 100644 apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/hero-background.tsx
rename apps/web/{app/app.dub.co/embed => ui/partners}/hero-background.tsx (100%)
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/page-client.tsx
index 5705dae949..b5be0121d2 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/page-client.tsx
@@ -1,13 +1,13 @@
"use client";
import { IntervalOptions } from "@/lib/analytics/types";
+import { HeroBackground } from "@/ui/partners/hero-background";
import { ProgramCommissionDescription } from "@/ui/partners/program-commission-description";
import { Button, Check2, useCopyToClipboard, useRouterStuff } from "@dub/ui";
import { Copy, MoneyBill2 } from "@dub/ui/src/icons";
import { getPrettyUrl } from "@dub/utils";
import { ProgramOverviewContext } from "./context";
import { EarningsChart } from "./earnings-chart";
-import { HeroBackground } from "./hero-background";
import { SaleTable } from "./sale-table";
import { StatCard } from "./stat-card";
import { useReferralLink } from "./use-referral-link";
diff --git a/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/hero-background.tsx b/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/hero-background.tsx
deleted file mode 100644
index 17bfe4e5b7..0000000000
--- a/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/hero-background.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { BlurImage } from "@dub/ui";
-import Image from "next/image";
-
-export function HeroBackground({
- logo,
- color,
-}: {
- logo?: string | null;
- color?: string | null;
-}) {
- const heroGradient = color
- ? `conic-gradient(from 45deg at 65% 70%, ${color} 0deg, ${color} 72deg, ${color} 144deg, ${color} 197.89deg, ${color} 260.96deg, ${color} 360deg)`
- : "conic-gradient(from 45deg at 65% 70%, #855AFC 0deg, #3A8BFD 72deg, #00FFF9 144deg, #5CFF80 197.89deg, #EAB308 260.96deg, #FF0000 360deg)";
-
- return (
-
-
-
-
-
- {logo && (
-
- )}
-
-
- );
-}
diff --git a/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx b/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx
index f81f70e25e..4a803c2dfc 100644
--- a/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx
+++ b/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx
@@ -8,6 +8,7 @@ import { ChartContext } from "@/ui/charts/chart-context";
import TimeSeriesChart from "@/ui/charts/time-series-chart";
import XAxis from "@/ui/charts/x-axis";
import YAxis from "@/ui/charts/y-axis";
+import { HeroBackground } from "@/ui/partners/hero-background";
import { ProgramCommissionDescription } from "@/ui/partners/program-commission-description";
import SimpleDateRangePicker from "@/ui/shared/simple-date-range-picker";
import {
@@ -31,7 +32,6 @@ import { LinearGradient } from "@visx/gradient";
import Link from "next/link";
import { useParams } from "next/navigation";
import { createContext, useContext, useId, useMemo } from "react";
-import { HeroBackground } from "./hero-background";
import { SaleTablePartner } from "./sales/sale-table";
const ProgramOverviewContext = createContext<{
diff --git a/apps/web/app/app.dub.co/embed/hero-background.tsx b/apps/web/ui/partners/hero-background.tsx
similarity index 100%
rename from apps/web/app/app.dub.co/embed/hero-background.tsx
rename to apps/web/ui/partners/hero-background.tsx
From feb1f7fd619b0afa9a0e616eb6edf6f01be77444 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Tue, 19 Nov 2024 16:57:47 +0530
Subject: [PATCH 44/88] Pull out the common components to `@/ui/partners`
---
apps/web/app/app.dub.co/embed/context.tsx | 11 -
apps/web/app/app.dub.co/embed/page-client.tsx | 79 ++++++-
apps/web/app/app.dub.co/embed/stat-card.tsx | 67 ------
.../[partnerId]/[programId]/page-client.tsx | 210 +++---------------
apps/web/lib/middleware/partners.ts | 4 +-
.../embed => ui/partners}/earnings-chart.tsx | 38 ++--
apps/web/ui/partners/stat-card.tsx | 90 ++++++++
7 files changed, 216 insertions(+), 283 deletions(-)
delete mode 100644 apps/web/app/app.dub.co/embed/context.tsx
delete mode 100644 apps/web/app/app.dub.co/embed/stat-card.tsx
rename apps/web/{app/app.dub.co/embed => ui/partners}/earnings-chart.tsx (89%)
create mode 100644 apps/web/ui/partners/stat-card.tsx
diff --git a/apps/web/app/app.dub.co/embed/context.tsx b/apps/web/app/app.dub.co/embed/context.tsx
deleted file mode 100644
index 6e3ebfd46b..0000000000
--- a/apps/web/app/app.dub.co/embed/context.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-"use client";
-
-import { IntervalOptions } from "@/lib/analytics/types";
-import { createContext } from "react";
-
-export const ProgramOverviewContext = createContext<{
- start?: Date;
- end?: Date;
- interval?: IntervalOptions;
- color?: string;
-}>({});
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/page-client.tsx
index b5be0121d2..8fc8f179f1 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/page-client.tsx
@@ -1,15 +1,19 @@
"use client";
import { IntervalOptions } from "@/lib/analytics/types";
+import {
+ EarningsChart,
+ ProgramOverviewContext,
+} from "@/ui/partners/earnings-chart";
import { HeroBackground } from "@/ui/partners/hero-background";
import { ProgramCommissionDescription } from "@/ui/partners/program-commission-description";
+import { StatCard } from "@/ui/partners/stat-card";
import { Button, Check2, useCopyToClipboard, useRouterStuff } from "@dub/ui";
import { Copy, MoneyBill2 } from "@dub/ui/src/icons";
import { getPrettyUrl } from "@dub/utils";
-import { ProgramOverviewContext } from "./context";
-import { EarningsChart } from "./earnings-chart";
+import { useContext } from "react";
import { SaleTable } from "./sale-table";
-import { StatCard } from "./stat-card";
+import useReferralAnalytics from "./use-referral-analytics";
import { useReferralLink } from "./use-referral-link";
import { useReferralProgram } from "./use-referral-program";
@@ -84,13 +88,13 @@ export function ReferralsEmbedPageClient() {
>
-
-
-
+
+
+
@@ -106,3 +110,64 @@ export function ReferralsEmbedPageClient() {
>
);
}
+
+const EarningsChartContainer = () => {
+ const { start, end, interval, color } = useContext(ProgramOverviewContext);
+
+ const { data: { earnings: total } = {} } = useReferralAnalytics({
+ interval,
+ start,
+ end,
+ });
+
+ const { data: timeseries, error } = useReferralAnalytics({
+ groupBy: "timeseries",
+ interval,
+ start,
+ end,
+ });
+
+ return (
+
+ );
+};
+
+function StatCardContainer({
+ title,
+ event,
+}: {
+ title: string;
+ event: "clicks" | "leads" | "sales";
+}) {
+ const { start, end, interval, color } = useContext(ProgramOverviewContext);
+
+ const { data: total } = useReferralAnalytics({
+ interval,
+ start,
+ end,
+ });
+
+ const { data: timeseries, error } = useReferralAnalytics({
+ groupBy: "timeseries",
+ interval,
+ start,
+ end,
+ event,
+ });
+
+ return (
+
+ );
+}
diff --git a/apps/web/app/app.dub.co/embed/stat-card.tsx b/apps/web/app/app.dub.co/embed/stat-card.tsx
deleted file mode 100644
index c07c7e5dd7..0000000000
--- a/apps/web/app/app.dub.co/embed/stat-card.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-"use client";
-
-import { MiniAreaChart } from "@dub/ui";
-import { LoadingSpinner } from "@dub/ui/src/icons";
-import { nFormatter } from "@dub/utils";
-import { useContext } from "react";
-import { ProgramOverviewContext } from "./context";
-import useReferralAnalytics from "./use-referral-analytics";
-
-export function StatCard({
- title,
- event,
-}: {
- title: string;
- event: "clicks" | "leads" | "sales";
-}) {
- const { start, end, interval, color } = useContext(ProgramOverviewContext);
-
- const { data: total } = useReferralAnalytics({
- interval,
- start,
- end,
- });
-
- const { data: timeseries, error } = useReferralAnalytics({
- groupBy: "timeseries",
- interval,
- start,
- end,
- event,
- });
-
- return (
-
-
{title}
- {total !== undefined ? (
-
- {nFormatter(total[event])}
-
- ) : (
-
- )}
-
- {timeseries ? (
-
({
- date: new Date(d.start),
- value: d[event],
- }))}
- curve={false}
- color={color}
- />
- ) : (
-
- {error ? (
-
- Failed to load data.
-
- ) : (
-
- )}
-
- )}
-
-
- );
-}
diff --git a/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx b/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx
index 4a803c2dfc..c728ad6493 100644
--- a/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx
+++ b/apps/web/app/partners.dub.co/(dashboard)/[partnerId]/[programId]/page-client.tsx
@@ -3,44 +3,28 @@
import { IntervalOptions } from "@/lib/analytics/types";
import usePartnerAnalytics from "@/lib/swr/use-partner-analytics";
import useProgramEnrollment from "@/lib/swr/use-program-enrollment";
-import Areas from "@/ui/charts/areas";
-import { ChartContext } from "@/ui/charts/chart-context";
-import TimeSeriesChart from "@/ui/charts/time-series-chart";
-import XAxis from "@/ui/charts/x-axis";
-import YAxis from "@/ui/charts/y-axis";
+import {
+ EarningsChart,
+ ProgramOverviewContext,
+} from "@/ui/partners/earnings-chart";
import { HeroBackground } from "@/ui/partners/hero-background";
import { ProgramCommissionDescription } from "@/ui/partners/program-commission-description";
-import SimpleDateRangePicker from "@/ui/shared/simple-date-range-picker";
+import { StatCard } from "@/ui/partners/stat-card";
import {
Button,
buttonVariants,
Check2,
MaxWidthWrapper,
- MiniAreaChart,
useCopyToClipboard,
useRouterStuff,
} from "@dub/ui";
-import { Copy, LoadingSpinner, MoneyBill2 } from "@dub/ui/src/icons";
-import {
- cn,
- currencyFormatter,
- formatDate,
- getPrettyUrl,
- nFormatter,
-} from "@dub/utils";
-import { LinearGradient } from "@visx/gradient";
+import { Copy, MoneyBill2 } from "@dub/ui/src/icons";
+import { cn, getPrettyUrl } from "@dub/utils";
import Link from "next/link";
import { useParams } from "next/navigation";
-import { createContext, useContext, useId, useMemo } from "react";
+import { useContext } from "react";
import { SaleTablePartner } from "./sales/sale-table";
-const ProgramOverviewContext = createContext<{
- start?: Date;
- end?: Date;
- interval?: IntervalOptions;
- color?: string;
-}>({});
-
export default function ProgramPageClient() {
const { getQueryString, searchParamsObj } = useRouterStuff();
const { partnerId, programId } = useParams();
@@ -117,13 +101,13 @@ export default function ProgramPageClient() {
>
-
-
-
+
+
+
@@ -149,9 +133,7 @@ export default function ProgramPageClient() {
);
}
-function EarningsChart() {
- const id = useId();
-
+function EarningsChartContainer() {
const { start, end, interval, color } = useContext(ProgramOverviewContext);
const { data: { earnings: total } = {} } = usePartnerAnalytics({
@@ -159,6 +141,7 @@ function EarningsChart() {
start,
end,
});
+
const { data: timeseries, error } = usePartnerAnalytics({
groupBy: "timeseries",
interval,
@@ -166,120 +149,17 @@ function EarningsChart() {
end,
});
- const data = useMemo(
- () =>
- timeseries?.map(({ start, earnings }) => ({
- date: new Date(start),
- values: { earnings: earnings / 100 },
- })),
- [timeseries],
- );
-
return (
-
-
-
-
Earnings
-
- {total !== undefined ? (
-
- {currencyFormatter(total / 100, {
- minimumFractionDigits: 2,
- maximumFractionDigits: 2,
- })}
-
- ) : (
-
- )}
-
-
-
-
-
-
-
- {data ? (
-
d.values.earnings,
- colorClassName: color ? `text-[${color}]` : "text-violet-500",
- isActive: true,
- },
- ]}
- tooltipClassName="p-0"
- tooltipContent={(d) => {
- return (
- <>
-
- {formatDate(d.date)}
-
-
-
-
- {currencyFormatter(d.values.earnings, {
- minimumFractionDigits: 2,
- maximumFractionDigits: 2,
- })}
-
-
- >
- );
- }}
- >
-
- {(context) => (
-
- )}
-
-
-
-
-
-
- ) : (
-
- {error ? (
-
- Failed to load earnings data.
-
- ) : (
-
- )}
-
- )}
-
-
+
);
}
-function StatCard({
+function StatCardContainer({
title,
event,
}: {
@@ -295,6 +175,7 @@ function StatCard({
start,
end,
});
+
const { data: timeseries, error } = usePartnerAnalytics({
groupBy: "timeseries",
interval,
@@ -304,45 +185,14 @@ function StatCard({
});
return (
-
-
{title}
- {total !== undefined ? (
-
- {nFormatter(total[event])}
- {event === "sales" && (
-
- ({currencyFormatter(total.saleAmount / 100)})
-
- )}
-
- ) : (
-
- )}
-
- {timeseries ? (
-
({
- date: new Date(d.start),
- value: d[event],
- }))}
- curve={false}
- color={color}
- />
- ) : (
-
- {error ? (
-
- Failed to load data.
-
- ) : (
-
- )}
-
- )}
-
-
+ />
);
}
diff --git a/apps/web/lib/middleware/partners.ts b/apps/web/lib/middleware/partners.ts
index 9c41dba4b8..77e76fc4cd 100644
--- a/apps/web/lib/middleware/partners.ts
+++ b/apps/web/lib/middleware/partners.ts
@@ -32,12 +32,14 @@ export default async function PartnersMiddleware(req: NextRequest) {
if (
user &&
partnersEnabled &&
- !["/account", "/pn_"].some((p) => path.startsWith(p))
+ !["/account", "/pn_", "/onboarding"].some((p) => path.startsWith(p))
) {
const defaultPartner = await getDefaultPartner(user);
+
if (!defaultPartner) {
return NextResponse.redirect(new URL("/onboarding", req.url));
}
+
return NextResponse.redirect(
new URL(`/${defaultPartner}${path}${searchParamsString}`, req.url),
);
diff --git a/apps/web/app/app.dub.co/embed/earnings-chart.tsx b/apps/web/ui/partners/earnings-chart.tsx
similarity index 89%
rename from apps/web/app/app.dub.co/embed/earnings-chart.tsx
rename to apps/web/ui/partners/earnings-chart.tsx
index b66be6e5a1..c7d28d8eb9 100644
--- a/apps/web/app/app.dub.co/embed/earnings-chart.tsx
+++ b/apps/web/ui/partners/earnings-chart.tsx
@@ -1,5 +1,6 @@
"use client";
+import { IntervalOptions } from "@/lib/analytics/types";
import Areas from "@/ui/charts/areas";
import { ChartContext } from "@/ui/charts/chart-context";
import TimeSeriesChart from "@/ui/charts/time-series-chart";
@@ -9,26 +10,29 @@ import SimpleDateRangePicker from "@/ui/shared/simple-date-range-picker";
import { LoadingSpinner } from "@dub/ui/src/icons";
import { cn, currencyFormatter, formatDate } from "@dub/utils";
import { LinearGradient } from "@visx/gradient";
-import { useContext, useId, useMemo } from "react";
-import { ProgramOverviewContext } from "./context";
-import useReferralAnalytics from "./use-referral-analytics";
+import { createContext, useId, useMemo } from "react";
-export function EarningsChart() {
- const id = useId();
- const { start, end, interval, color } = useContext(ProgramOverviewContext);
+export const ProgramOverviewContext = createContext<{
+ start?: Date;
+ end?: Date;
+ interval?: IntervalOptions;
+ color?: string;
+}>({});
- const { data: { earnings: total } = {} } = useReferralAnalytics({
- interval,
- start,
- end,
- });
+interface EarningsChartProps {
+ timeseries: any;
+ total: any;
+ color: any;
+ error: any;
+}
- const { data: timeseries, error } = useReferralAnalytics({
- groupBy: "timeseries",
- interval,
- start,
- end,
- });
+export function EarningsChart({
+ timeseries,
+ total,
+ color,
+ error,
+}: EarningsChartProps) {
+ const id = useId();
const data = useMemo(
() =>
diff --git a/apps/web/ui/partners/stat-card.tsx b/apps/web/ui/partners/stat-card.tsx
new file mode 100644
index 0000000000..295791fe99
--- /dev/null
+++ b/apps/web/ui/partners/stat-card.tsx
@@ -0,0 +1,90 @@
+import { MiniAreaChart } from "@dub/ui";
+import { LoadingSpinner } from "@dub/ui/src/icons";
+import { currencyFormatter, nFormatter } from "@dub/utils";
+import Link from "next/link";
+
+interface StatCardProps {
+ title: string;
+ event: "clicks" | "leads" | "sales";
+ timeseries?: any;
+ error?: any;
+ color?: string;
+ href?: string;
+ total?: {
+ clicks: number;
+ leads: number;
+ sales: number;
+ saleAmount: number;
+ };
+}
+
+export function StatCard({
+ title,
+ event,
+ total,
+ timeseries,
+ error,
+ color,
+ href,
+}: StatCardProps) {
+ return (
+
+ {title}
+ {total !== undefined ? (
+
+ {nFormatter(total[event])}
+ {event === "sales" && (
+
+ ({currencyFormatter(total.saleAmount / 100)})
+
+ )}
+
+ ) : (
+
+ )}
+
+ {timeseries ? (
+
({
+ date: new Date(d.start),
+ value: d[event],
+ }))}
+ curve={false}
+ color={color}
+ />
+ ) : (
+
+ {error ? (
+
+ Failed to load data.
+
+ ) : (
+
+ )}
+
+ )}
+
+
+ );
+}
+
+const Wrapper = ({
+ href,
+ children,
+ className,
+}: {
+ href?: string;
+ children: React.ReactNode;
+ className?: string;
+}) => {
+ return href ? (
+
+ {children}
+
+ ) : (
+
{children}
+ );
+};
From efba8e12d8175517b6903448768949342ba9be09 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Tue, 19 Nov 2024 18:08:51 +0530
Subject: [PATCH 45/88] Add a "powered by Dub" badge
---
apps/web/app/app.dub.co/embed/page-client.tsx | 80 ++++++++++++-------
1 file changed, 53 insertions(+), 27 deletions(-)
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/page-client.tsx
index 8fc8f179f1..86f63b8dcb 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/page-client.tsx
@@ -8,7 +8,13 @@ import {
import { HeroBackground } from "@/ui/partners/hero-background";
import { ProgramCommissionDescription } from "@/ui/partners/program-commission-description";
import { StatCard } from "@/ui/partners/stat-card";
-import { Button, Check2, useCopyToClipboard, useRouterStuff } from "@dub/ui";
+import {
+ Button,
+ Check2,
+ useCopyToClipboard,
+ useRouterStuff,
+ Wordmark,
+} from "@dub/ui";
import { Copy, MoneyBill2 } from "@dub/ui/src/icons";
import { getPrettyUrl } from "@dub/utils";
import { useContext } from "react";
@@ -39,10 +45,12 @@ export function ReferralsEmbedPageClient() {
<>
{program &&
}
+
Refer and earn
+
{program ? (
@@ -50,9 +58,11 @@ export function ReferralsEmbedPageClient() {
)}
+
Referral link
+
{!isLoadingLink && link ? (
-
-
-
-
+
+
-
-
-
-
-
-
-
-
- Recent sales
-
+
+
+
+
-
-
+
+
+
+ Recent sales
+
+
+
+
+
-
-
+
+
+
+
>
);
}
From 0aa91d6d5ec11c5932d7d19a9ca57fb23893d422 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Tue, 19 Nov 2024 18:15:24 +0530
Subject: [PATCH 46/88] small fix
---
apps/web/app/app.dub.co/embed/page-client.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/page-client.tsx
index 86f63b8dcb..ab2e38a30e 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/page-client.tsx
@@ -125,7 +125,7 @@ export function ReferralsEmbedPageClient() {
From c849e53cbe5f41e5124bb645baa2b5192f253183 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Tue, 19 Nov 2024 19:13:05 +0530
Subject: [PATCH 47/88] add RequestPartnerInviteForm
---
apps/web/app/api/referrals/invite/route.ts | 84 +++++------
apps/web/app/app.dub.co/embed/page-client.tsx | 17 ++-
.../embed/request-partner-invite-form.tsx | 61 ++++++++
.../embed/request-partner-invite-modal.tsx | 135 ------------------
4 files changed, 121 insertions(+), 176 deletions(-)
create mode 100644 apps/web/app/app.dub.co/embed/request-partner-invite-form.tsx
delete mode 100644 apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx
diff --git a/apps/web/app/api/referrals/invite/route.ts b/apps/web/app/api/referrals/invite/route.ts
index 1b3fbf3ec4..2c48617913 100644
--- a/apps/web/app/api/referrals/invite/route.ts
+++ b/apps/web/app/api/referrals/invite/route.ts
@@ -8,23 +8,41 @@ import { sendEmail } from "emails";
import PartnerInvite from "emails/partner-invite";
import { NextResponse } from "next/server";
+// GET /api/referrals/invite - check if a partner is already invited to a program
+export const GET = withAuth(async ({ link, program }) => {
+ const [programEnrollment, programInvite] = await Promise.all([
+ prisma.programEnrollment.findFirst({
+ where: {
+ programId: program.id,
+ linkId: link.id,
+ },
+ }),
+
+ prisma.programInvite.findFirst({
+ where: {
+ programId: program.id,
+ linkId: link.id,
+ },
+ }),
+ ]);
+
+ return NextResponse.json({
+ programEnrollment,
+ programInvite,
+ });
+});
+
// POST /api/referrals/invite - invite a partner to dub partners
export const POST = withAuth(async ({ req, link, program, workspace }) => {
- const { email } = requestPartnerInviteSchema.parse(
- await parseRequestBody(req),
- );
-
// TODO:
// We're repating the same logic in the invite-partner action.
// We should move it to a shared location if it makes sense.
- const [
- programEnrollment,
- programInvite,
- linkInProgramEnrollment,
- linkInProgramInvite,
- tags,
- ] = await Promise.all([
+ const { email } = requestPartnerInviteSchema.parse(
+ await parseRequestBody(req),
+ );
+
+ const [programEnrollment, programInvite, tags] = await Promise.all([
prisma.programEnrollment.findFirst({
where: {
programId: program.id,
@@ -49,18 +67,6 @@ export const POST = withAuth(async ({ req, link, program, workspace }) => {
},
}),
- prisma.programEnrollment.findUnique({
- where: {
- linkId: link.id,
- },
- }),
-
- prisma.programInvite.findUnique({
- where: {
- linkId: link.id,
- },
- }),
-
prisma.tag.findMany({
where: {
links: {
@@ -73,18 +79,14 @@ export const POST = withAuth(async ({ req, link, program, workspace }) => {
]);
if (programEnrollment) {
- throw new Error(`Partner ${email} already enrolled in this program.`);
+ throw new Error("You're already enrolled in this program.");
}
if (programInvite) {
- throw new Error(`Partner ${email} already invited to this program.`);
- }
-
- if (linkInProgramEnrollment || linkInProgramInvite) {
- throw new Error("Link is already associated with another partner.");
+ throw new Error("You've already been invited to this program.");
}
- const [result, _] = await Promise.all([
+ const [programInvited] = await Promise.all([
prisma.programInvite.create({
data: {
id: createId({ prefix: "pgi_" }),
@@ -110,18 +112,20 @@ export const POST = withAuth(async ({ req, link, program, workspace }) => {
workspace_id: workspace.id,
deleted: false,
}),
- ]);
- await sendEmail({
- subject: `${program.name} invited you to join Dub Partners`,
- email,
- react: PartnerInvite({
+ sendEmail({
+ subject: `${program.name} invited you to join Dub Partners`,
email,
- appName: `${process.env.NEXT_PUBLIC_APP_NAME}`,
- programName: program.name,
- programLogo: program.logo,
+ react: PartnerInvite({
+ email,
+ appName: `${process.env.NEXT_PUBLIC_APP_NAME}`,
+ program: {
+ name: program.name,
+ logo: program.logo,
+ },
+ }),
}),
- });
+ ]);
- return NextResponse.json(result);
+ return NextResponse.json(programInvited);
});
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/page-client.tsx
index ab2e38a30e..6cbfc932e5 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/page-client.tsx
@@ -16,8 +16,11 @@ import {
Wordmark,
} from "@dub/ui";
import { Copy, MoneyBill2 } from "@dub/ui/src/icons";
-import { getPrettyUrl } from "@dub/utils";
+import { fetcher, getPrettyUrl } from "@dub/utils";
+import { ProgramEnrollment, ProgramInvite } from "@prisma/client";
import { useContext } from "react";
+import useSWR from "swr";
+import { RequestPartnerInviteForm } from "./request-partner-invite-form";
import { SaleTable } from "./sale-table";
import useReferralAnalytics from "./use-referral-analytics";
import { useReferralLink } from "./use-referral-link";
@@ -39,6 +42,13 @@ export function ReferralsEmbedPageClient() {
interval?: IntervalOptions;
};
+ const { data, isLoading } = useSWR<{
+ programEnrollment: ProgramEnrollment;
+ programInvite: ProgramInvite;
+ }>(`/api/referrals/invite`, fetcher);
+
+ const hasEnrolled = data?.programEnrollment;
+ const hasInvited = data?.programInvite;
const color = program?.brandColor || "#8B5CF6";
return (
@@ -121,6 +131,11 @@ export function ReferralsEmbedPageClient() {
+ {/* TODO: UI needs tweaking */}
+ {!isLoading && !hasEnrolled && !hasInvited && (
+
+ )}
+
;
+
+export const RequestPartnerInviteForm = () => {
+ const {
+ register,
+ handleSubmit,
+ formState: { isSubmitting },
+ } = useForm({
+ resolver: zodResolver(requestPartnerInviteSchema),
+ });
+
+ const onSubmit = async (data: RequestPartnerInvite) => {
+ const response = await fetch("/api/referrals/invite", {
+ method: "POST",
+ body: JSON.stringify(data),
+ });
+
+ if (!response.ok) {
+ const { error } = await response.json();
+ toast.error(error.message);
+ return;
+ }
+
+ mutate("/api/referrals/invite");
+ toast.success(
+ "Invite request sent successfully! Check your email for the invite link.",
+ );
+ };
+
+ return (
+
+ );
+};
diff --git a/apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx b/apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx
deleted file mode 100644
index a34ba96ece..0000000000
--- a/apps/web/app/app.dub.co/embed/request-partner-invite-modal.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { requestPartnerInviteSchema } from "@/lib/dots/schemas";
-import z from "@/lib/zod";
-import { Button, Modal } from "@dub/ui";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useCallback, useMemo, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-
-type RequestPartnerInvite = z.infer;
-
-interface RequestPartnerInviteProps {
- showModal: boolean;
- setShowModal: (showModal: boolean) => void;
-}
-
-interface RequestPartnerInviteFormProps {
- closeModal: () => void;
-}
-
-const RequestPartnerInvite = ({
- showModal,
- setShowModal,
-}: RequestPartnerInviteProps) => {
- return (
-
-
- Request invite to Dub Partners for receiving payouts
-
-
- setShowModal(false)} />
-
-
- );
-};
-
-const RequestPartnerInviteForm = ({
- closeModal,
-}: RequestPartnerInviteFormProps) => {
- const {
- register,
- handleSubmit,
- formState: { isValid, isSubmitting },
- } = useForm({
- resolver: zodResolver(requestPartnerInviteSchema),
- });
-
- const onSubmit = async (data: RequestPartnerInvite) => {
- const response = await fetch("/api/referrals/invite", {
- method: "POST",
- body: JSON.stringify(data),
- });
-
- if (response.ok) {
- toast.success("Invite request sent successfully!");
- closeModal();
- }
-
- const { error } = await response.json();
- toast.error(error.message);
- };
-
- return (
-
- );
-};
-
-export function useRequestPartnerInviteModal() {
- const [showRequestPartnerInviteModal, setShowRequestPartnerInviteModal] =
- useState(false);
-
- const RequestPartnerInviteModal = useCallback(() => {
- return (
-
- );
- }, [showRequestPartnerInviteModal, setShowRequestPartnerInviteModal]);
-
- return useMemo(
- () => ({
- setShowRequestPartnerInviteModal,
- RequestPartnerInviteModal,
- }),
- [setShowRequestPartnerInviteModal, RequestPartnerInviteModal],
- );
-}
From e94fa9293e882da9a1301a795f6c9459ed8d4cbe Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Tue, 19 Nov 2024 22:57:18 +0530
Subject: [PATCH 48/88] add onTokenExpired
---
.../[slug]/settings/referrals/page-client.tsx | 5 +++-
apps/web/app/app.dub.co/embed/page-client.tsx | 17 ++++++++++-
apps/web/lib/referrals/auth.ts | 4 +++
.../lib/referrals/create-referral-token.ts | 16 +++++++++++
apps/web/ui/embed/referrals-embed.tsx | 28 ++++++++++++++++++-
5 files changed, 67 insertions(+), 3 deletions(-)
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index c9000988af..b0cb43eacd 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -37,7 +37,10 @@ export default function ReferralsPageClient() {
}
return publicToken ? (
-
+ createPublicToken()}
+ />
) : (
);
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/page-client.tsx
index 6cbfc932e5..b0e4ef2d16 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/page-client.tsx
@@ -18,6 +18,7 @@ import {
import { Copy, MoneyBill2 } from "@dub/ui/src/icons";
import { fetcher, getPrettyUrl } from "@dub/utils";
import { ProgramEnrollment, ProgramInvite } from "@prisma/client";
+import { notFound } from "next/navigation";
import { useContext } from "react";
import useSWR from "swr";
import { RequestPartnerInviteForm } from "./request-partner-invite-form";
@@ -30,16 +31,22 @@ export function ReferralsEmbedPageClient() {
const { program } = useReferralProgram();
const { searchParamsObj } = useRouterStuff();
const [copied, copyToClipboard] = useCopyToClipboard();
- const { link, isLoading: isLoadingLink } = useReferralLink();
+ const {
+ link,
+ isLoading: isLoadingLink,
+ error: linkError,
+ } = useReferralLink();
const {
start,
end,
interval = "30d",
+ token,
} = searchParamsObj as {
start?: string;
end?: string;
interval?: IntervalOptions;
+ token?: string;
};
const { data, isLoading } = useSWR<{
@@ -51,6 +58,14 @@ export function ReferralsEmbedPageClient() {
const hasInvited = data?.programInvite;
const color = program?.brandColor || "#8B5CF6";
+ if (!token) {
+ notFound();
+ }
+
+ if (linkError && linkError.status === 401) {
+ window.parent.postMessage("TOKEN_EXPIRED", "*");
+ }
+
return (
<>
diff --git a/apps/web/lib/referrals/auth.ts b/apps/web/lib/referrals/auth.ts
index 6ee1ea4cfe..c506ed2e2d 100644
--- a/apps/web/lib/referrals/auth.ts
+++ b/apps/web/lib/referrals/auth.ts
@@ -58,6 +58,8 @@ export const withAuth = (handler: WithAuthHandler) => {
});
if (!publicToken) {
+ cookieStore.delete(EMBED_PUBLIC_TOKEN_COOKIE_NAME);
+
throw new DubApiError({
code: "unauthorized",
message: "Invalid public token.",
@@ -65,6 +67,8 @@ export const withAuth = (handler: WithAuthHandler) => {
}
if (publicToken.expires < new Date()) {
+ cookieStore.delete(EMBED_PUBLIC_TOKEN_COOKIE_NAME);
+
throw new DubApiError({
code: "unauthorized",
message: "Public token expired.",
diff --git a/apps/web/lib/referrals/create-referral-token.ts b/apps/web/lib/referrals/create-referral-token.ts
index d9ca2649ac..c3c95368b8 100644
--- a/apps/web/lib/referrals/create-referral-token.ts
+++ b/apps/web/lib/referrals/create-referral-token.ts
@@ -1,5 +1,6 @@
import { prisma } from "@/lib/prisma";
import { nanoid } from "@dub/utils";
+import { waitUntil } from "@vercel/functions";
import { DubApiError } from "../api/errors";
import { ratelimit } from "../upstash";
import {
@@ -40,6 +41,9 @@ export const createReferralPublicToken = async ({
const token = await prisma.embedPublicToken.findFirst({
where: {
linkId,
+ expires: {
+ gt: new Date(),
+ },
},
});
@@ -47,6 +51,18 @@ export const createReferralPublicToken = async ({
return token;
}
+ // Remove all expired tokens for this link
+ waitUntil(
+ prisma.embedPublicToken.deleteMany({
+ where: {
+ linkId,
+ expires: {
+ lte: new Date(),
+ },
+ },
+ }),
+ );
+
return await prisma.embedPublicToken.create({
data: {
linkId,
diff --git a/apps/web/ui/embed/referrals-embed.tsx b/apps/web/ui/embed/referrals-embed.tsx
index 718c57a0d4..e35709cd26 100644
--- a/apps/web/ui/embed/referrals-embed.tsx
+++ b/apps/web/ui/embed/referrals-embed.tsx
@@ -1,6 +1,32 @@
import { APP_DOMAIN } from "@dub/utils";
+import { useEffect } from "react";
+
+interface ReferralsEmbedProps {
+ publicToken: string;
+ onTokenExpired?: () => void;
+}
+
+export const ReferralsEmbed = ({
+ publicToken,
+ onTokenExpired,
+}: ReferralsEmbedProps) => {
+ useEffect(() => {
+ const handleMessage = (event: MessageEvent) => {
+ // Verify the message is from our embed page
+ if (event.origin !== APP_DOMAIN) {
+ return;
+ }
+
+ if (event.data === "TOKEN_EXPIRED") {
+ onTokenExpired?.();
+ }
+ };
+
+ window.addEventListener("message", handleMessage);
+
+ return () => window.removeEventListener("message", handleMessage);
+ }, [onTokenExpired]);
-export const ReferralsEmbed = ({ publicToken }: { publicToken: string }) => {
return (
{/* TODO: UI needs tweaking */}
- {!isLoading && !hasEnrolled && !hasInvited && (
+ {!isLoading && !hasEnrolled && !hasInvited && salesCount?.count && (
)}
diff --git a/apps/web/lib/actions/invite-partner.ts b/apps/web/lib/actions/invite-partner.ts
index 1435e6cf94..ddb635a12c 100644
--- a/apps/web/lib/actions/invite-partner.ts
+++ b/apps/web/lib/actions/invite-partner.ts
@@ -1,14 +1,9 @@
"use server";
-import { prisma } from "@/lib/prisma";
-import { sendEmail } from "emails";
-import PartnerInvite from "emails/partner-invite";
import { z } from "zod";
import { getLinkOrThrow } from "../api/links/get-link-or-throw";
+import { invitePartner } from "../api/partners/invite-partner";
import { getProgramOrThrow } from "../api/programs/get-program";
-import { createId } from "../api/utils";
-import { updateConfig } from "../edge-config";
-import { recordLink } from "../tinybird";
import { authActionClient } from "./safe-action";
const invitePartnerSchema = z.object({
@@ -24,7 +19,7 @@ export const invitePartnerAction = authActionClient
const { workspace } = ctx;
const { email, linkId, programId } = parsedInput;
- const [program, link, tags] = await Promise.all([
+ const [program, link] = await Promise.all([
getProgramOrThrow({
workspaceId: workspace.id,
programId,
@@ -34,105 +29,16 @@ export const invitePartnerAction = authActionClient
workspaceId: workspace.id,
linkId,
}),
-
- prisma.tag.findMany({
- where: {
- links: {
- some: {
- linkId,
- },
- },
- },
- }),
]);
if (link.programId) {
throw new Error("Link is already associated with another partner.");
}
- const [programEnrollment, programInvite] = await Promise.all([
- prisma.programEnrollment.findFirst({
- where: {
- programId,
- partner: {
- users: {
- some: {
- user: {
- email,
- },
- },
- },
- },
- },
- }),
-
- prisma.programInvite.findUnique({
- where: {
- email_programId: {
- email,
- programId,
- },
- },
- }),
- ]);
-
- if (programEnrollment) {
- throw new Error(`Partner ${email} already enrolled in this program.`);
- }
-
- if (programInvite) {
- throw new Error(`Partner ${email} already invited to this program.`);
- }
-
- const [result, _] = await Promise.all([
- prisma.programInvite.create({
- data: {
- id: createId({ prefix: "pgi_" }),
- email,
- linkId,
- programId,
- },
- }),
-
- prisma.link.update({
- where: {
- id: linkId,
- },
- data: {
- programId,
- },
- }),
-
- updateConfig({
- key: "partnersPortal",
- value: email,
- }),
-
- recordLink({
- domain: link.domain,
- key: link.key,
- link_id: link.id,
- created_at: link.createdAt,
- url: link.url,
- tag_ids: tags.map((t) => t.id) || [],
- program_id: program.id,
- workspace_id: workspace.id,
- deleted: false,
- }),
- ]);
-
- await sendEmail({
- subject: `${program.name} invited you to join Dub Partners`,
+ return await invitePartner({
email,
- react: PartnerInvite({
- email,
- appName: `${process.env.NEXT_PUBLIC_APP_NAME}`,
- program: {
- name: program.name,
- logo: program.logo,
- },
- }),
+ program,
+ link,
+ workspace,
});
-
- return result;
});
diff --git a/apps/web/lib/api/partners/invite-partner.ts b/apps/web/lib/api/partners/invite-partner.ts
new file mode 100644
index 0000000000..9b1d33b150
--- /dev/null
+++ b/apps/web/lib/api/partners/invite-partner.ts
@@ -0,0 +1,114 @@
+import { prisma } from "@/lib/prisma";
+import { ProgramProps } from "@/lib/types";
+import { Link, Project } from "@prisma/client";
+import { sendEmail } from "emails";
+import PartnerInvite from "emails/partner-invite";
+import { createId } from "../utils";
+
+export const invitePartner = async ({
+ email,
+ program,
+ workspace,
+ link,
+}: {
+ email: string;
+ program: ProgramProps;
+ workspace: Project;
+ link: Link;
+}) => {
+ const [programEnrollment, programInvite] = await Promise.all([
+ prisma.programEnrollment.findFirst({
+ where: {
+ programId: program.id,
+ partner: {
+ users: {
+ some: {
+ user: {
+ email,
+ },
+ },
+ },
+ },
+ },
+ }),
+
+ prisma.programInvite.findUnique({
+ where: {
+ email_programId: {
+ email,
+ programId: program.id,
+ },
+ },
+ }),
+ ]);
+
+ if (programEnrollment) {
+ throw new Error(`Partner ${email} already enrolled in this program.`);
+ }
+
+ if (programInvite) {
+ throw new Error(`Partner ${email} already invited to this program.`);
+ }
+
+ const tags = await prisma.tag.findMany({
+ where: {
+ links: {
+ some: {
+ linkId: link.id,
+ },
+ },
+ },
+ });
+
+ const [programInvited] = await Promise.all([
+ prisma.programInvite.create({
+ data: {
+ id: createId({ prefix: "pgi_" }),
+ email,
+ linkId: link.id,
+ programId: program.id,
+ },
+ }),
+
+ prisma.link.update({
+ where: {
+ id: link.id,
+ },
+ data: {
+ programId: program.id,
+ },
+ }),
+
+ // updateConfig({
+ // key: "partnersPortal",
+ // value: email,
+ // }),
+
+ // recordLink({
+ // domain: link.domain,
+ // key: link.key,
+ // link_id: link.id,
+ // created_at: link.createdAt,
+ // url: link.url,
+ // tag_ids: tags.map((t) => t.id) || [],
+ // program_id: program.id,
+ // workspace_id: workspace.id,
+ // deleted: false,
+ // }),
+
+ sendEmail({
+ subject: `${program.name} invited you to join Dub Partners`,
+ email,
+ react: PartnerInvite({
+ email,
+ appName: `${process.env.NEXT_PUBLIC_APP_NAME}`,
+ program: {
+ name: program.name,
+ logo: program.logo,
+ },
+ }),
+ }),
+ ]);
+
+ return programInvited;
+};
From 99381dc43f323622aaffdf118a103a9021bce1ac Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Tue, 19 Nov 2024 23:45:39 +0530
Subject: [PATCH 52/88] small fix
---
apps/web/lib/api/partners/invite-partner.ts | 32 +++++++++++----------
1 file changed, 17 insertions(+), 15 deletions(-)
diff --git a/apps/web/lib/api/partners/invite-partner.ts b/apps/web/lib/api/partners/invite-partner.ts
index 9b1d33b150..c8d732828a 100644
--- a/apps/web/lib/api/partners/invite-partner.ts
+++ b/apps/web/lib/api/partners/invite-partner.ts
@@ -1,4 +1,6 @@
+import { updateConfig } from "@/lib/edge-config";
import { prisma } from "@/lib/prisma";
+import { recordLink } from "@/lib/tinybird";
import { ProgramProps } from "@/lib/types";
import { Link, Project } from "@prisma/client";
import { sendEmail } from "emails";
@@ -79,22 +81,22 @@ export const invitePartner = async ({
},
}),
- // updateConfig({
- // key: "partnersPortal",
- // value: email,
- // }),
+ updateConfig({
+ key: "partnersPortal",
+ value: email,
+ }),
- // recordLink({
- // domain: link.domain,
- // key: link.key,
- // link_id: link.id,
- // created_at: link.createdAt,
- // url: link.url,
- // tag_ids: tags.map((t) => t.id) || [],
- // program_id: program.id,
- // workspace_id: workspace.id,
- // deleted: false,
- // }),
+ recordLink({
+ domain: link.domain,
+ key: link.key,
+ link_id: link.id,
+ created_at: link.createdAt,
+ url: link.url,
+ tag_ids: tags.map((t) => t.id) || [],
+ program_id: program.id,
+ workspace_id: workspace.id,
+ deleted: false,
+ }),
sendEmail({
subject: `${program.name} invited you to join Dub Partners`,
From 6348f814d67999b87b9aab45c0ee9ffc8fa5f6f2 Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Wed, 20 Nov 2024 09:46:41 +0530
Subject: [PATCH 53/88] Update invite-partner.ts
---
apps/web/lib/api/partners/invite-partner.ts | 35 +++++++++++----------
1 file changed, 19 insertions(+), 16 deletions(-)
diff --git a/apps/web/lib/api/partners/invite-partner.ts b/apps/web/lib/api/partners/invite-partner.ts
index c8d732828a..b68b511498 100644
--- a/apps/web/lib/api/partners/invite-partner.ts
+++ b/apps/web/lib/api/partners/invite-partner.ts
@@ -72,6 +72,7 @@ export const invitePartner = async ({
},
}),
+ // update link to have programId
prisma.link.update({
where: {
id: link.id,
@@ -81,11 +82,7 @@ export const invitePartner = async ({
},
}),
- updateConfig({
- key: "partnersPortal",
- value: email,
- }),
-
+ // record link update in tinybird
recordLink({
domain: link.domain,
key: link.key,
@@ -98,19 +95,25 @@ export const invitePartner = async ({
deleted: false,
}),
- sendEmail({
- subject: `${program.name} invited you to join Dub Partners`,
- email,
- react: PartnerInvite({
- email,
- appName: `${process.env.NEXT_PUBLIC_APP_NAME}`,
- program: {
- name: program.name,
- logo: program.logo,
- },
- }),
+ // TODO: Remove this once we open up partners.dub.co to everyone
+ updateConfig({
+ key: "partnersPortal",
+ value: email,
}),
]);
+ await sendEmail({
+ subject: `${program.name} invited you to join Dub Partners`,
+ email,
+ react: PartnerInvite({
+ email,
+ appName: `${process.env.NEXT_PUBLIC_APP_NAME}`,
+ program: {
+ name: program.name,
+ logo: program.logo,
+ },
+ }),
+ });
+
return programInvited;
};
From 2c72084c073d548fdce3d08964ceea6a7a64e4ad Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 21 Nov 2024 21:00:35 +0530
Subject: [PATCH 54/88] wip widget
---
apps/web/app/app.dub.co/embed/page-client.tsx | 4 +-
apps/web/lib/middleware/app.ts | 22 +-
.../lib/referrals/retrieve-referral-token.ts | 9 +
apps/web/middleware.ts | 2 +-
apps/web/public/embed.js | 75 +++
apps/web/ui/embed/referrals-embed.tsx | 2 +-
packages/widgets/README.md | 11 +
packages/widgets/package.json | 91 +++
packages/widgets/postcss.config.js | 9 +
packages/widgets/src/embed.js | 26 +
packages/widgets/src/index.ts | 0
packages/widgets/tailwind.config.ts | 9 +
packages/widgets/tsconfig.json | 5 +
packages/widgets/tsup.config.ts | 15 +
pnpm-lock.yaml | 547 ++++++++++++++++--
15 files changed, 761 insertions(+), 66 deletions(-)
create mode 100644 apps/web/lib/referrals/retrieve-referral-token.ts
create mode 100644 apps/web/public/embed.js
create mode 100644 packages/widgets/README.md
create mode 100644 packages/widgets/package.json
create mode 100644 packages/widgets/postcss.config.js
create mode 100644 packages/widgets/src/embed.js
create mode 100644 packages/widgets/src/index.ts
create mode 100644 packages/widgets/tailwind.config.ts
create mode 100644 packages/widgets/tsconfig.json
create mode 100644 packages/widgets/tsup.config.ts
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/page-client.tsx
index 7b4a5ef84a..13b300d6a7 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/page-client.tsx
@@ -153,9 +153,9 @@ export function ReferralsEmbedPageClient() {
{/* TODO: UI needs tweaking */}
- {!isLoading && !hasEnrolled && !hasInvited && salesCount?.count && (
+ {/* {!isLoading && !hasEnrolled && !hasInvited && salesCount?.count && (
- )}
+ )} */}
+ Click Me
+
+ `;
+
+ // Load widget on click
+ button.addEventListener("click", () => {
+ if (!Dub.options.linkToken) {
+ console.error("Link token is required");
+ return;
+ }
+
+ // Create iframe container
+ const container = document.createElement("div");
+ container.id = "dub-widget-container";
+ container.style.position = "fixed";
+ container.style.bottom = "80px";
+ container.style.right = "20px";
+ container.style.width = "400px";
+ container.style.height = "600px";
+ container.style.zIndex = "9998";
+ container.style.boxShadow = "0 0 20px rgba(0,0,0,0.1)";
+ container.style.borderRadius = "10px";
+ container.style.overflow = "hidden";
+ container.style.padding = "10px";
+
+ // Create iframe
+ const iframe = document.createElement("iframe");
+ iframe.src = `http://localhost:8888/embed?token=${Dub.options.linkToken}`;
+ iframe.style.width = "100%";
+ iframe.style.height = "100%";
+ iframe.style.border = "none";
+ iframe.setAttribute("credentialssupport", "");
+ iframe.setAttribute("allow", "same-origin");
+ iframe.crossOrigin = "use-credentials";
+
+ container.appendChild(iframe);
+ document.body.appendChild(container);
+ });
+
+ document.body.appendChild(button);
+ }
+
+ // Initialize when DOM is ready
+ function init() {
+ setTimeout(createFloatingButton, 100);
+ console.log("Initialized");
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", init);
+ } else {
+ init();
+ }
+})();
diff --git a/apps/web/ui/embed/referrals-embed.tsx b/apps/web/ui/embed/referrals-embed.tsx
index e35709cd26..84959001f6 100644
--- a/apps/web/ui/embed/referrals-embed.tsx
+++ b/apps/web/ui/embed/referrals-embed.tsx
@@ -13,7 +13,7 @@ export const ReferralsEmbed = ({
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
// Verify the message is from our embed page
- if (event.origin !== APP_DOMAIN) {
+ if (event.origin !== APP_DOMAIN) {
return;
}
diff --git a/packages/widgets/README.md b/packages/widgets/README.md
new file mode 100644
index 0000000000..b5721d0597
--- /dev/null
+++ b/packages/widgets/README.md
@@ -0,0 +1,11 @@
+# `@dub/ui`
+
+`@dub/ui` is a library of React components that are used across Dub.co's web applications.
+
+## Installation
+
+To install the package, run:
+
+```bash
+pnpm i @dub/ui
+```
diff --git a/packages/widgets/package.json b/packages/widgets/package.json
new file mode 100644
index 0000000000..73f27dc578
--- /dev/null
+++ b/packages/widgets/package.json
@@ -0,0 +1,91 @@
+{
+ "name": "@dub/widgets",
+ "description": "UI widgets for Dub Partners.",
+ "version": "0.1.66",
+ "sideEffects": false,
+ "files": [
+ "dist/**"
+ ],
+ "scripts": {
+ "build": "tsup",
+ "lint": "eslint src/",
+ "check-types": "tsc --noEmit"
+ },
+ "peerDependencies": {
+ "next": "14.2.0-canary.47",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@dub/tailwind-config": "workspace:*",
+ "@dub/utils": "workspace:*",
+ "@types/js-cookie": "^3.0.6",
+ "@types/react": "^18.2.47",
+ "@types/react-dom": "^18.2.14",
+ "autoprefixer": "^10.4.16",
+ "next": "14.2.0-canary.67",
+ "postcss": "^8.4.31",
+ "react": "^18.2.0",
+ "tailwindcss": "^3.4.4",
+ "tsconfig": "workspace:*",
+ "tsup": "^6.1.3",
+ "typescript": "^5.1.6"
+ },
+ "dependencies": {
+ "@floating-ui/react": "^0.26.20",
+ "@internationalized/date": "^3.5.3",
+ "@radix-ui/react-accordion": "^1.0.1",
+ "@radix-ui/react-checkbox": "^1.0.4",
+ "@radix-ui/react-dialog": "1.0.5",
+ "@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-navigation-menu": "^1.1.2",
+ "@radix-ui/react-popover": "1.0.7",
+ "@radix-ui/react-radio-group": "^1.1.3",
+ "@radix-ui/react-slot": "1.0.1",
+ "@radix-ui/react-switch": "^1.0.1",
+ "@radix-ui/react-tooltip": "^1.0.7",
+ "@radix-ui/react-visually-hidden": "^1.1.0",
+ "@react-aria/datepicker": "^3.10.0",
+ "@react-stately/datepicker": "^3.9.3",
+ "@tanstack/react-table": "^8.17.3",
+ "class-variance-authority": "^0.7.0",
+ "cmdk": "^0.2.0",
+ "date-fns": "^3.6.0",
+ "embla-carousel-autoplay": "^8.1.7",
+ "embla-carousel-react": "^8.1.7",
+ "framer-motion": "^10.16.16",
+ "js-cookie": "^3.0.5",
+ "js-sha256": "^0.11.0",
+ "linkify-react": "^4.1.3",
+ "linkifyjs": "^4.1.3",
+ "lucide-react": "^0.367.0",
+ "react-day-picker": "^8.10.1",
+ "sonner": "^1.4.41",
+ "swr": "^2.1.5",
+ "use-debounce": "^8.0.4",
+ "vaul": "^0.9.6",
+ "@visx/curve": "^3.3.0",
+ "@visx/gradient": "^3.3.0",
+ "@visx/group": "^3.3.0",
+ "@visx/responsive": "^2.10.0",
+ "@visx/scale": "^3.3.0",
+ "@visx/shape": "^2.12.2"
+ },
+ "author": "Steven Tey ",
+ "homepage": "https://dub.co",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/steven-tey/dub.git"
+ },
+ "bugs": {
+ "url": "https://github.com/steven-tey/dub/issues"
+ },
+ "keywords": [
+ "dub",
+ "dub.co",
+ "ui"
+ ],
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/widgets/postcss.config.js b/packages/widgets/postcss.config.js
new file mode 100644
index 0000000000..07aa434b2b
--- /dev/null
+++ b/packages/widgets/postcss.config.js
@@ -0,0 +1,9 @@
+// If you want to use other PostCSS plugins, see the following:
+// https://tailwindcss.com/docs/using-with-preprocessors
+
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/packages/widgets/src/embed.js b/packages/widgets/src/embed.js
new file mode 100644
index 0000000000..6459729572
--- /dev/null
+++ b/packages/widgets/src/embed.js
@@ -0,0 +1,26 @@
+(function () {
+ // Add a floating button to the page
+ function createFloatingButton() {
+ const button = document.createElement("div");
+ button.id = "floating-widget-button";
+ button.innerHTML = `
+
+ 💬
+
+ `;
+
+ document.body.appendChild(button);
+ }
+
+ // Initialize when DOM is ready
+ function init() {
+ setTimeout(createFloatingButton, 100);
+ console.log("Initialized");
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", init);
+ } else {
+ init();
+ }
+})();
\ No newline at end of file
diff --git a/packages/widgets/src/index.ts b/packages/widgets/src/index.ts
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/widgets/tailwind.config.ts b/packages/widgets/tailwind.config.ts
new file mode 100644
index 0000000000..5c39ec95b9
--- /dev/null
+++ b/packages/widgets/tailwind.config.ts
@@ -0,0 +1,9 @@
+// tailwind config is required for editor support
+import sharedConfig from "@dub/tailwind-config/tailwind.config.ts";
+import type { Config } from "tailwindcss";
+
+const config: Pick = {
+ presets: [sharedConfig],
+};
+
+export default config;
diff --git a/packages/widgets/tsconfig.json b/packages/widgets/tsconfig.json
new file mode 100644
index 0000000000..cd6c94d6e8
--- /dev/null
+++ b/packages/widgets/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "tsconfig/react-library.json",
+ "include": ["."],
+ "exclude": ["dist", "build", "node_modules"]
+}
diff --git a/packages/widgets/tsup.config.ts b/packages/widgets/tsup.config.ts
new file mode 100644
index 0000000000..6a8a1e5cb0
--- /dev/null
+++ b/packages/widgets/tsup.config.ts
@@ -0,0 +1,15 @@
+import { defineConfig, Options } from "tsup";
+
+export default defineConfig((options: Options) => ({
+ entry: ["src/**/*.tsx"],
+ format: ["esm"],
+ esbuildOptions(options) {
+ options.banner = {
+ js: '"use client"',
+ };
+ },
+ dts: true,
+ minify: true,
+ external: ["react"],
+ ...options,
+}));
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3a0dee0ee4..eaf30e067c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -729,6 +729,166 @@ importers:
specifier: ^5.1.6
version: 5.1.6
+ packages/widgets:
+ dependencies:
+ '@floating-ui/react':
+ specifier: ^0.26.20
+ version: 0.26.20(react-dom@18.2.0)(react@18.2.0)
+ '@internationalized/date':
+ specifier: ^3.5.3
+ version: 3.5.4
+ '@radix-ui/react-accordion':
+ specifier: ^1.0.1
+ version: 1.0.1(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-checkbox':
+ specifier: ^1.0.4
+ version: 1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-dialog':
+ specifier: 1.0.5
+ version: 1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-label':
+ specifier: ^2.0.2
+ version: 2.0.2(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-navigation-menu':
+ specifier: ^1.1.2
+ version: 1.1.2(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-popover':
+ specifier: 1.0.7
+ version: 1.0.7(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-radio-group':
+ specifier: ^1.1.3
+ version: 1.1.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-slot':
+ specifier: 1.0.1
+ version: 1.0.1(react@18.2.0)
+ '@radix-ui/react-switch':
+ specifier: ^1.0.1
+ version: 1.0.1(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-tooltip':
+ specifier: ^1.0.7
+ version: 1.0.7(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-visually-hidden':
+ specifier: ^1.1.0
+ version: 1.1.0(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@react-aria/datepicker':
+ specifier: ^3.10.0
+ version: 3.10.1(react-dom@18.2.0)(react@18.2.0)
+ '@react-stately/datepicker':
+ specifier: ^3.9.3
+ version: 3.9.4(react@18.2.0)
+ '@tanstack/react-table':
+ specifier: ^8.17.3
+ version: 8.17.3(react-dom@18.2.0)(react@18.2.0)
+ '@visx/curve':
+ specifier: ^3.3.0
+ version: 3.3.0
+ '@visx/gradient':
+ specifier: ^3.3.0
+ version: 3.3.0(react@18.2.0)
+ '@visx/group':
+ specifier: ^3.3.0
+ version: 3.3.0(react@18.2.0)
+ '@visx/responsive':
+ specifier: ^2.10.0
+ version: 2.10.0(react@18.2.0)
+ '@visx/scale':
+ specifier: ^3.3.0
+ version: 3.3.0
+ '@visx/shape':
+ specifier: ^2.12.2
+ version: 2.12.2(react@18.2.0)
+ class-variance-authority:
+ specifier: ^0.7.0
+ version: 0.7.0
+ cmdk:
+ specifier: ^0.2.0
+ version: 0.2.0(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ date-fns:
+ specifier: ^3.6.0
+ version: 3.6.0
+ embla-carousel-autoplay:
+ specifier: ^8.1.7
+ version: 8.1.7(embla-carousel@8.1.7)
+ embla-carousel-react:
+ specifier: ^8.1.7
+ version: 8.1.7(react@18.2.0)
+ framer-motion:
+ specifier: ^10.16.16
+ version: 10.17.4(react-dom@18.2.0)(react@18.2.0)
+ js-cookie:
+ specifier: ^3.0.5
+ version: 3.0.5
+ js-sha256:
+ specifier: ^0.11.0
+ version: 0.11.0
+ linkify-react:
+ specifier: ^4.1.3
+ version: 4.1.3(linkifyjs@4.1.3)(react@18.2.0)
+ linkifyjs:
+ specifier: ^4.1.3
+ version: 4.1.3
+ lucide-react:
+ specifier: ^0.367.0
+ version: 0.367.0(react@18.2.0)
+ react-day-picker:
+ specifier: ^8.10.1
+ version: 8.10.1(date-fns@3.6.0)(react@18.2.0)
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.2.0(react@18.2.0)
+ sonner:
+ specifier: ^1.4.41
+ version: 1.4.41(react-dom@18.2.0)(react@18.2.0)
+ swr:
+ specifier: ^2.1.5
+ version: 2.1.5(react@18.2.0)
+ use-debounce:
+ specifier: ^8.0.4
+ version: 8.0.4(react@18.2.0)
+ vaul:
+ specifier: ^0.9.6
+ version: 0.9.6(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ devDependencies:
+ '@dub/tailwind-config':
+ specifier: workspace:*
+ version: link:../tailwind-config
+ '@dub/utils':
+ specifier: workspace:*
+ version: link:../utils
+ '@types/js-cookie':
+ specifier: ^3.0.6
+ version: 3.0.6
+ '@types/react':
+ specifier: ^18.2.47
+ version: 18.2.47
+ '@types/react-dom':
+ specifier: ^18.2.14
+ version: 18.2.14
+ autoprefixer:
+ specifier: ^10.4.16
+ version: 10.4.16(postcss@8.4.31)
+ next:
+ specifier: 14.2.0-canary.67
+ version: 14.2.0-canary.67(react-dom@18.2.0)(react@18.2.0)
+ postcss:
+ specifier: ^8.4.31
+ version: 8.4.31
+ react:
+ specifier: ^18.2.0
+ version: 18.2.0
+ tailwindcss:
+ specifier: ^3.4.4
+ version: 3.4.4
+ tsconfig:
+ specifier: workspace:*
+ version: link:../tsconfig
+ tsup:
+ specifier: ^6.1.3
+ version: 6.7.0(postcss@8.4.31)(typescript@5.6.2)
+ typescript:
+ specifier: ^5.1.6
+ version: 5.6.2
+
packages:
/@aashutoshrathi/word-wrap@1.2.6:
@@ -4655,7 +4815,7 @@ packages:
/@internationalized/date@3.5.4:
resolution: {integrity: sha512-qoVJVro+O0rBaw+8HPjUB1iH8Ihf8oziEnqMnvhJUSuVIrHOuZ6eNLHNvzXJKUvAtaDiqMnRlg8Z2mgh09BlUw==}
dependencies:
- '@swc/helpers': 0.5.11
+ '@swc/helpers': 0.5.12
dev: false
/@internationalized/message@3.1.4:
@@ -4978,14 +5138,6 @@ packages:
chalk: 4.1.2
dev: false
- /@jridgewell/gen-mapping@0.3.3:
- resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
- engines: {node: '>=6.0.0'}
- dependencies:
- '@jridgewell/set-array': 1.2.1
- '@jridgewell/sourcemap-codec': 1.4.15
- '@jridgewell/trace-mapping': 0.3.25
-
/@jridgewell/gen-mapping@0.3.5:
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@@ -5729,6 +5881,34 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-checkbox@1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-checkbox@1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==}
peerDependencies:
@@ -5833,6 +6013,30 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-slot': 1.0.2(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies:
@@ -6006,6 +6210,40 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-id': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-slot': 1.0.2(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.14
+ aria-hidden: 1.2.3
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-remove-scroll: 2.5.5(@types/react@18.2.47)(react@18.2.0)
+ dev: false
+
/@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
peerDependencies:
@@ -6049,6 +6287,20 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-direction@1.0.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-direction@1.0.1(@types/react@18.2.48)(react@18.2.0):
resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
peerDependencies:
@@ -6346,6 +6598,27 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-label@2.0.2(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-label@2.0.2(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==}
peerDependencies:
@@ -6440,7 +6713,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.23.1
+ '@babel/runtime': 7.23.9
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0)
@@ -6506,7 +6779,7 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.23.9
- '@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0)
+ '@floating-ui/react-dom': 2.1.1(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.48)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.48)(react@18.2.0)
@@ -6869,6 +7142,36 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-radio-group@1.1.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-direction': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-radio-group@1.1.3(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==}
peerDependencies:
@@ -6899,6 +7202,35 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-direction': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-id': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==}
peerDependencies:
@@ -7090,6 +7422,38 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-id': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-slot': 1.0.2(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.47)(react@18.2.0)
+ '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==}
peerDependencies:
@@ -7424,6 +7788,20 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-use-previous@1.0.1(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@types/react': 18.2.47
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-use-previous@1.0.1(@types/react@18.2.48)(react@18.2.0):
resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==}
peerDependencies:
@@ -7576,6 +7954,27 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.47
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
peerDependencies:
@@ -8362,7 +8761,7 @@ packages:
'@react-stately/utils': 3.10.1(react@18.2.0)
'@react-types/datepicker': 3.7.4(react@18.2.0)
'@react-types/shared': 3.23.1(react@18.2.0)
- '@swc/helpers': 0.5.11
+ '@swc/helpers': 0.5.12
react: 18.2.0
dev: false
@@ -9777,7 +10176,6 @@ packages:
'@types/prop-types': 15.7.8
'@types/scheduler': 0.16.4
csstype: 3.1.3
- dev: false
/@types/react@18.2.48:
resolution: {integrity: sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==}
@@ -10952,7 +11350,7 @@ packages:
peerDependencies:
postcss: ^8.1.0
dependencies:
- browserslist: 4.22.1
+ browserslist: 4.23.3
caniuse-lite: 1.0.30001651
fraction.js: 4.3.6
normalize-range: 0.1.2
@@ -10968,11 +11366,11 @@ packages:
peerDependencies:
postcss: ^8.1.0
dependencies:
- browserslist: 4.22.1
- caniuse-lite: 1.0.30001543
+ browserslist: 4.23.3
+ caniuse-lite: 1.0.30001651
fraction.js: 4.3.6
normalize-range: 0.1.2
- picocolors: 1.0.0
+ picocolors: 1.0.1
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
@@ -11268,16 +11666,6 @@ packages:
resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==}
dev: true
- /browserslist@4.22.1:
- resolution: {integrity: sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==}
- engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
- hasBin: true
- dependencies:
- caniuse-lite: 1.0.30001583
- electron-to-chromium: 1.4.540
- node-releases: 2.0.13
- update-browserslist-db: 1.0.13(browserslist@4.22.1)
-
/browserslist@4.23.3:
resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@@ -11446,13 +11834,6 @@ packages:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
dev: false
- /caniuse-lite@1.0.30001543:
- resolution: {integrity: sha512-qxdO8KPWPQ+Zk6bvNpPeQIOH47qZSYdFZd6dXQzb2KzhnSXju4Kd7H1PkSJx6NICSMgo/IhRZRhhfPTHYpJUCA==}
- dev: true
-
- /caniuse-lite@1.0.30001583:
- resolution: {integrity: sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==}
-
/caniuse-lite@1.0.30001651:
resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==}
@@ -11664,6 +12045,21 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /cmdk@0.2.0(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-JQpKvEOb86SnvMZbYaFKYhvzFntWBeSZdyii0rZPhKJj9uwJBxu4DaVYDrRN7r3mPop56oPhRw+JYWTKs66TYw==}
+ peerDependencies:
+ react: ^18.0.0
+ react-dom: ^18.0.0
+ dependencies:
+ '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ command-score: 0.1.2
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+ dev: false
+
/cmdk@0.2.0(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-JQpKvEOb86SnvMZbYaFKYhvzFntWBeSZdyii0rZPhKJj9uwJBxu4DaVYDrRN7r3mPop56oPhRw+JYWTKs66TYw==}
peerDependencies:
@@ -12005,7 +12401,6 @@ packages:
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
- dev: false
/csv-parse@5.5.6:
resolution: {integrity: sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A==}
@@ -12561,9 +12956,6 @@ packages:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false
- /electron-to-chromium@1.4.540:
- resolution: {integrity: sha512-aoCqgU6r9+o9/S7wkcSbmPRFi7OWZWiXS9rtjEd+Ouyu/Xyw5RSq2XN8s5Qp8IaFOLiRrhQCphCIjAxgG3eCAg==}
-
/electron-to-chromium@1.5.11:
resolution: {integrity: sha512-R1CccCDYqndR25CaXFd6hp/u9RaaMcftMkphmvuepXr5b1vfLkRml6aWVeBhXJ7rbevHkKEMJtz8XqPf7ffmew==}
@@ -13219,6 +13611,7 @@ packages:
/escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
+ dev: false
/escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
@@ -17344,6 +17737,7 @@ packages:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ dev: false
/nanoid@3.3.7:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
@@ -17647,9 +18041,6 @@ packages:
/node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
- /node-releases@2.0.13:
- resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
-
/node-releases@2.0.18:
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
@@ -18367,9 +18758,9 @@ packages:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
- nanoid: 3.3.6
- picocolors: 1.0.0
- source-map-js: 1.0.2
+ nanoid: 3.3.7
+ picocolors: 1.0.1
+ source-map-js: 1.2.0
/postcss@8.4.38:
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
@@ -20084,6 +20475,7 @@ packages:
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
+ dev: false
/source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
@@ -20433,7 +20825,7 @@ packages:
engines: {node: '>=8'}
hasBin: true
dependencies:
- '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/gen-mapping': 0.3.5
commander: 4.1.1
glob: 7.1.6
lines-and-columns: 1.2.4
@@ -20966,6 +21358,43 @@ packages:
- ts-node
dev: true
+ /tsup@6.7.0(postcss@8.4.31)(typescript@5.6.2):
+ resolution: {integrity: sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==}
+ engines: {node: '>=14.18'}
+ hasBin: true
+ peerDependencies:
+ '@swc/core': ^1
+ postcss: ^8.4.12
+ typescript: '>=4.1.0'
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ postcss:
+ optional: true
+ typescript:
+ optional: true
+ dependencies:
+ bundle-require: 4.2.1(esbuild@0.17.19)
+ cac: 6.7.14
+ chokidar: 3.5.3
+ debug: 4.3.4
+ esbuild: 0.17.19
+ execa: 5.1.1
+ globby: 11.1.0
+ joycon: 3.1.1
+ postcss: 8.4.31
+ postcss-load-config: 3.1.4(postcss@8.4.31)
+ resolve-from: 5.0.0
+ rollup: 3.29.5
+ source-map: 0.8.0-beta.0
+ sucrase: 3.34.0
+ tree-kill: 1.2.2
+ typescript: 5.6.2
+ transitivePeerDependencies:
+ - supports-color
+ - ts-node
+ dev: true
+
/tsup@6.7.0(ts-node@10.9.2)(typescript@5.6.2):
resolution: {integrity: sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==}
engines: {node: '>=14.18'}
@@ -21546,16 +21975,6 @@ packages:
content-type: 1.0.5
dev: false
- /update-browserslist-db@1.0.13(browserslist@4.22.1):
- resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
- hasBin: true
- peerDependencies:
- browserslist: '>= 4.21.0'
- dependencies:
- browserslist: 4.22.1
- escalade: 3.1.1
- picocolors: 1.0.1
-
/update-browserslist-db@1.1.0(browserslist@4.23.3):
resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==}
hasBin: true
@@ -21766,6 +22185,20 @@ packages:
- '@types/react-dom'
dev: false
+ /vaul@0.9.6(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-Ykk5FSu4ibeD6qfKQH/CkBRdSGWkxi35KMNei0z59kTPAlgzpE/Qf1gTx2sxih8Q05KBO/aFhcF/UkBW5iI1Ww==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ dependencies:
+ '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+ dev: false
+
/vaul@0.9.6(@types/react-dom@18.2.14)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Ykk5FSu4ibeD6qfKQH/CkBRdSGWkxi35KMNei0z59kTPAlgzpE/Qf1gTx2sxih8Q05KBO/aFhcF/UkBW5iI1Ww==}
peerDependencies:
@@ -22045,7 +22478,7 @@ packages:
'@webassemblyjs/wasm-parser': 1.11.6
acorn: 8.11.3
acorn-import-assertions: 1.9.0(acorn@8.11.3)
- browserslist: 4.22.1
+ browserslist: 4.23.3
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
es-module-lexer: 1.4.1
@@ -22406,7 +22839,7 @@ packages:
engines: {node: '>=10'}
dependencies:
cliui: 7.0.4
- escalade: 3.1.1
+ escalade: 3.1.2
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
From 1d3c1799369c1cca50e6bb801fb329460c33bdff Mon Sep 17 00:00:00 2001
From: Kiran K
Date: Thu, 21 Nov 2024 23:45:41 +0530
Subject: [PATCH 55/88] widget ui
---
.../[slug]/settings/referrals/page-client.tsx | 4 +-
.../embed/{ => dashboard}/page-client.tsx | 24 +---
.../app/app.dub.co/embed/dashboard/page.tsx | 5 +
.../request-partner-invite-form.tsx | 0
.../embed/{ => dashboard}/sale-table.tsx | 0
.../{ => dashboard}/use-referral-analytics.ts | 0
.../{ => dashboard}/use-referral-link.ts | 0
.../{ => dashboard}/use-referral-program.ts | 0
apps/web/app/app.dub.co/embed/page.tsx | 5 -
.../app.dub.co/embed/widget/page-client.tsx | 131 ++++++++++++++++++
apps/web/app/app.dub.co/embed/widget/page.tsx | 5 +
apps/web/next.config.js | 2 +-
apps/web/public/embed.js | 48 ++++---
apps/web/ui/embed/referrals-embed.tsx | 10 +-
packages/widgets/src/embed.js | 27 +---
15 files changed, 182 insertions(+), 79 deletions(-)
rename apps/web/app/app.dub.co/embed/{ => dashboard}/page-client.tsx (88%)
create mode 100644 apps/web/app/app.dub.co/embed/dashboard/page.tsx
rename apps/web/app/app.dub.co/embed/{ => dashboard}/request-partner-invite-form.tsx (100%)
rename apps/web/app/app.dub.co/embed/{ => dashboard}/sale-table.tsx (100%)
rename apps/web/app/app.dub.co/embed/{ => dashboard}/use-referral-analytics.ts (100%)
rename apps/web/app/app.dub.co/embed/{ => dashboard}/use-referral-link.ts (100%)
rename apps/web/app/app.dub.co/embed/{ => dashboard}/use-referral-program.ts (100%)
delete mode 100644 apps/web/app/app.dub.co/embed/page.tsx
create mode 100644 apps/web/app/app.dub.co/embed/widget/page-client.tsx
create mode 100644 apps/web/app/app.dub.co/embed/widget/page.tsx
diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
index b0cb43eacd..83b297d2de 100644
--- a/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
+++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/settings/referrals/page-client.tsx
@@ -1,7 +1,7 @@
"use client";
import useWorkspace from "@/lib/swr/use-workspace";
-import { ReferralsEmbed } from "@/ui/embed/referrals-embed";
+import { RewardDashboard } from "@/ui/embed/referrals-embed";
import { LoadingSpinner } from "@dub/ui";
import { redirect } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
@@ -37,7 +37,7 @@ export default function ReferralsPageClient() {
}
return publicToken ? (
- createPublicToken()}
/>
diff --git a/apps/web/app/app.dub.co/embed/page-client.tsx b/apps/web/app/app.dub.co/embed/dashboard/page-client.tsx
similarity index 88%
rename from apps/web/app/app.dub.co/embed/page-client.tsx
rename to apps/web/app/app.dub.co/embed/dashboard/page-client.tsx
index 13b300d6a7..99c22e3830 100644
--- a/apps/web/app/app.dub.co/embed/page-client.tsx
+++ b/apps/web/app/app.dub.co/embed/dashboard/page-client.tsx
@@ -16,18 +16,15 @@ import {
Wordmark,
} from "@dub/ui";
import { Copy, MoneyBill2 } from "@dub/ui/src/icons";
-import { fetcher, getPrettyUrl } from "@dub/utils";
-import { ProgramEnrollment, ProgramInvite } from "@prisma/client";
+import { getPrettyUrl } from "@dub/utils";
import { notFound } from "next/navigation";
import { useContext } from "react";
-import useSWR from "swr";
-import { RequestPartnerInviteForm } from "./request-partner-invite-form";
import { SaleTable } from "./sale-table";
import useReferralAnalytics from "./use-referral-analytics";
import { useReferralLink } from "./use-referral-link";
import { useReferralProgram } from "./use-referral-program";
-export function ReferralsEmbedPageClient() {
+export function RewardDashboardPageClient() {
const { program } = useReferralProgram();
const { searchParamsObj } = useRouterStuff();
const [copied, copyToClipboard] = useCopyToClipboard();
@@ -49,18 +46,6 @@ export function ReferralsEmbedPageClient() {
token?: string;
};
- const { data, isLoading } = useSWR<{
- programEnrollment: ProgramEnrollment;
- programInvite: ProgramInvite;
- }>(`/api/referrals/invite`, fetcher);
-
- const { data: salesCount } = useSWR<{ count: number }>(
- "/api/referrals/sales/count",
- fetcher,
- );
-
- const hasEnrolled = data?.programEnrollment;
- const hasInvited = data?.programInvite;
const color = program?.brandColor || "#8B5CF6";
if (!token) {
@@ -152,11 +137,6 @@ export function ReferralsEmbedPageClient() {
- {/* TODO: UI needs tweaking */}
- {/* {!isLoading && !hasEnrolled && !hasInvited && salesCount?.count && (
-
- )} */}
-
;
+}
diff --git a/apps/web/app/app.dub.co/embed/request-partner-invite-form.tsx b/apps/web/app/app.dub.co/embed/dashboard/request-partner-invite-form.tsx
similarity index 100%
rename from apps/web/app/app.dub.co/embed/request-partner-invite-form.tsx
rename to apps/web/app/app.dub.co/embed/dashboard/request-partner-invite-form.tsx
diff --git a/apps/web/app/app.dub.co/embed/sale-table.tsx b/apps/web/app/app.dub.co/embed/dashboard/sale-table.tsx
similarity index 100%
rename from apps/web/app/app.dub.co/embed/sale-table.tsx
rename to apps/web/app/app.dub.co/embed/dashboard/sale-table.tsx
diff --git a/apps/web/app/app.dub.co/embed/use-referral-analytics.ts b/apps/web/app/app.dub.co/embed/dashboard/use-referral-analytics.ts
similarity index 100%
rename from apps/web/app/app.dub.co/embed/use-referral-analytics.ts
rename to apps/web/app/app.dub.co/embed/dashboard/use-referral-analytics.ts
diff --git a/apps/web/app/app.dub.co/embed/use-referral-link.ts b/apps/web/app/app.dub.co/embed/dashboard/use-referral-link.ts
similarity index 100%
rename from apps/web/app/app.dub.co/embed/use-referral-link.ts
rename to apps/web/app/app.dub.co/embed/dashboard/use-referral-link.ts
diff --git a/apps/web/app/app.dub.co/embed/use-referral-program.ts b/apps/web/app/app.dub.co/embed/dashboard/use-referral-program.ts
similarity index 100%
rename from apps/web/app/app.dub.co/embed/use-referral-program.ts
rename to apps/web/app/app.dub.co/embed/dashboard/use-referral-program.ts
diff --git a/apps/web/app/app.dub.co/embed/page.tsx b/apps/web/app/app.dub.co/embed/page.tsx
deleted file mode 100644
index c920f89d35..0000000000
--- a/apps/web/app/app.dub.co/embed/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { ReferralsEmbedPageClient } from "./page-client";
-
-export default async function ReferralsEmbedPage() {
- return
;
-}
diff --git a/apps/web/app/app.dub.co/embed/widget/page-client.tsx b/apps/web/app/app.dub.co/embed/widget/page-client.tsx
new file mode 100644
index 0000000000..90532b74b1
--- /dev/null
+++ b/apps/web/app/app.dub.co/embed/widget/page-client.tsx
@@ -0,0 +1,131 @@
+"use client";
+
+import {
+ Button,
+ Check2,
+ Copy,
+ MoneyBill2,
+ ToggleGroup,
+ useCopyToClipboard,
+} from "@dub/ui";
+import { currencyFormatter, getPrettyUrl } from "@dub/utils";
+import { useState } from "react";
+import { useReferralLink } from "../dashboard/use-referral-link";
+import { useReferralProgram } from "../dashboard/use-referral-program";
+
+type Tab = "invite" | "rewards";
+
+export function EmbedWidgetPageClient() {
+ const [copied, copyToClipboard] = useCopyToClipboard();
+ const [selectedTab, setSelectedTab] = useState
("invite");
+
+ const { link, isLoading: isLoadingLink } = useReferralLink();
+ const { program, isLoading: isLoadingProgram } = useReferralProgram();
+
+ return (
+
+
+
+
+ Refer a friend and earn
+
+
+
+ Earn additional credits and cash when you refer a friend and they sign
+ up for {program?.name}
+
+
+
+
+
setSelectedTab(option)}
+ className="grid grid-cols-2 bg-gray-100"
+ optionClassName="w-full h-10"
+ indicatorClassName="rounded-md bg-white border-none shadow-sm"
+ />
+
+
+ {selectedTab === "invite" && (
+ <>
+
+ {!isLoadingLink && link ? (
+
+ ) : (
+
+ )}
+
+ ) : (
+
+ )
+ }
+ text={copied ? "Copied link" : "Copy link"}
+ onClick={() => copyToClipboard(getPrettyUrl(link?.shortLink))}
+ disabled={isLoadingLink}
+ />
+
+ >
+ )}
+
+ {selectedTab === "rewards" && (
+ <>
+
Activity
+ {isLoadingLink || !link ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+
+ Clicks
+
+ {link.clicks}
+
+
+
+ Signups
+
+ {link.leads}
+
+
+
+ Total earned
+
+ {currencyFormatter(link.saleAmount / 100, {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ })}
+
+
+
+ )}
+ >
+ )}
+
+
+
+ );
+}
diff --git a/apps/web/app/app.dub.co/embed/widget/page.tsx b/apps/web/app/app.dub.co/embed/widget/page.tsx
new file mode 100644
index 0000000000..c62d4b5722
--- /dev/null
+++ b/apps/web/app/app.dub.co/embed/widget/page.tsx
@@ -0,0 +1,5 @@
+import { EmbedWidgetPageClient } from "./page-client";
+
+export default async function EmbedWidgetPage() {
+ return ;
+}
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index 4512003839..97071443a6 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -95,7 +95,7 @@ module.exports = withAxiom({
],
},
{
- source: "/embed",
+ source: "/embed/:path*",
headers: [
{
key: "X-Frame-Options",
diff --git a/apps/web/public/embed.js b/apps/web/public/embed.js
index 4fe2f822e7..e8565c18ab 100644
--- a/apps/web/public/embed.js
+++ b/apps/web/public/embed.js
@@ -6,16 +6,30 @@
linkToken: null,
};
+ const buttonStyles = {
+ position: "fixed",
+ bottom: "20px",
+ right: "20px",
+ zIndex: "9999",
+ };
+
+ const containerStyles = {
+ width: "450px",
+ height: "600px",
+ boxShadow: "0 0 20px rgba(0,0,0,0.1)",
+ borderRadius: "10px",
+ overflow: "hidden",
+ position: "fixed",
+ bottom: "80px",
+ right: "20px",
+ zIndex: "9998",
+ };
+
// Add a floating button to the page
function createFloatingButton() {
const button = document.createElement("div");
button.id = "floating-widget-button";
-
- // Add styles for positioning and appearance
- button.style.position = "fixed";
- button.style.bottom = "20px";
- button.style.right = "20px";
- button.style.zIndex = "9999";
+ Object.assign(button.style, buttonStyles);
button.innerHTML = `
@@ -23,8 +37,15 @@
`;
- // Load widget on click
button.addEventListener("click", () => {
+ const existingContainer = document.getElementById("dub-widget-container");
+
+ if (existingContainer) {
+ // If the container exists, remove it (close the widget)
+ document.body.removeChild(existingContainer);
+ return;
+ }
+
if (!Dub.options.linkToken) {
console.error("Link token is required");
return;
@@ -33,20 +54,11 @@
// Create iframe container
const container = document.createElement("div");
container.id = "dub-widget-container";
- container.style.position = "fixed";
- container.style.bottom = "80px";
- container.style.right = "20px";
- container.style.width = "400px";
- container.style.height = "600px";
- container.style.zIndex = "9998";
- container.style.boxShadow = "0 0 20px rgba(0,0,0,0.1)";
- container.style.borderRadius = "10px";
- container.style.overflow = "hidden";
- container.style.padding = "10px";
+ Object.assign(container.style, containerStyles);
// Create iframe
const iframe = document.createElement("iframe");
- iframe.src = `http://localhost:8888/embed?token=${Dub.options.linkToken}`;
+ iframe.src = `http://localhost:8888/embed/widget?token=${Dub.options.linkToken}`;
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.style.border = "none";
diff --git a/apps/web/ui/embed/referrals-embed.tsx b/apps/web/ui/embed/referrals-embed.tsx
index 84959001f6..3e7ce38763 100644
--- a/apps/web/ui/embed/referrals-embed.tsx
+++ b/apps/web/ui/embed/referrals-embed.tsx
@@ -1,19 +1,19 @@
import { APP_DOMAIN } from "@dub/utils";
import { useEffect } from "react";
-interface ReferralsEmbedProps {
+interface RewardDashboardProps {
publicToken: string;
onTokenExpired?: () => void;
}
-export const ReferralsEmbed = ({
+export const RewardDashboard = ({
publicToken,
onTokenExpired,
-}: ReferralsEmbedProps) => {
+}: RewardDashboardProps) => {
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
// Verify the message is from our embed page
- if (event.origin !== APP_DOMAIN) {
+ if (event.origin !== APP_DOMAIN) {
return;
}
@@ -29,7 +29,7 @@ export const ReferralsEmbed = ({
return (