Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

White labeled embed UI #1300

Open
wants to merge 117 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
6596e39
create public token
devkiran Sep 30, 2024
a7df990
add events and analytics
devkiran Sep 30, 2024
816c8fe
update
devkiran Sep 30, 2024
71d595c
Merge branch 'main' into referrals-tokens
devkiran Oct 1, 2024
8a8c4b2
rearrange
devkiran Oct 1, 2024
9bc3e7d
wip
devkiran Oct 1, 2024
9db82ae
wip
devkiran Oct 1, 2024
04fb198
wip
devkiran Oct 3, 2024
52d7d3d
Merge branch 'main' into referrals-tokens
devkiran Oct 3, 2024
1215a43
wip
devkiran Oct 3, 2024
abf4fda
display events
devkiran Oct 3, 2024
9dcc14e
display stats
devkiran Oct 3, 2024
29982cd
remove actions
devkiran Oct 3, 2024
6f2ac07
use public token
devkiran Oct 3, 2024
6adccbb
Merge branch 'main' into referrals-tokens
devkiran Oct 4, 2024
4457813
run prettier
devkiran Oct 4, 2024
7dc9700
revert
devkiran Oct 4, 2024
2d01ee0
wip iframe
devkiran Oct 4, 2024
9631595
add sample iframe
devkiran Oct 4, 2024
f741094
Merge branch 'main' into referrals-tokens
devkiran Oct 7, 2024
abf0909
move the hero section
devkiran Oct 7, 2024
aca0fbc
move the files to /embed
devkiran Oct 7, 2024
19cdefa
move to @dub/blocks
devkiran Oct 7, 2024
06b00e0
format
devkiran Oct 7, 2024
4b0c3c5
use APP_DOMAIN
devkiran Oct 7, 2024
f96d704
remove
devkiran Oct 7, 2024
803cc31
Merge branch 'main' into referrals-tokens
steven-tey Oct 7, 2024
3f61d3e
Merge branch 'main' into referrals-tokens
devkiran Oct 8, 2024
826b3e9
update the routes
devkiran Oct 8, 2024
44fa6b5
revert flag change
devkiran Oct 8, 2024
fa1874c
Merge branch 'partner-invite' into referrals-tokens
devkiran Nov 14, 2024
2930d66
fix build
devkiran Nov 14, 2024
cbecccd
rename to createReferralPublicToken
devkiran Nov 14, 2024
f2a80b1
wip new UI for embed
devkiran Nov 14, 2024
b12a156
display sales and earnings
devkiran Nov 14, 2024
470ef50
some cleanup
devkiran Nov 14, 2024
a8d1c47
Merge branch 'partner-invite' into referrals-tokens
devkiran Nov 14, 2024
483f5b7
Add a cron job to remove expired public tokens
devkiran Nov 14, 2024
eadb8fc
If an active public token is present for a link, return it instead of…
devkiran Nov 14, 2024
2ebc49b
Merge branch 'referrals-tokens' of https://github.com/dubinc/dub into…
devkiran Nov 14, 2024
faca4a6
Add rate limit create referral token endpoint
devkiran Nov 14, 2024
9c86ed1
remove unused
devkiran Nov 15, 2024
4f8d4e6
Merge branch 'partner-invite' into referrals-tokens
devkiran Nov 15, 2024
36d488d
update routes
devkiran Nov 15, 2024
6dd91e1
use brandColor
devkiran Nov 15, 2024
6ed0e3f
Merge branch 'main' into referrals-tokens
devkiran Nov 18, 2024
ad9fc76
Move `packages/blocks` to `packages/ui`
devkiran Nov 18, 2024
d7bae88
fix types
devkiran Nov 18, 2024
7735b65
update import
devkiran Nov 18, 2024
1e24452
update endpoints
devkiran Nov 18, 2024
71b2058
wip request invite form
devkiran Nov 18, 2024
0716c0c
/api/referrals/invite
devkiran Nov 18, 2024
16ece51
use the api
devkiran Nov 18, 2024
5810f4a
Merge branch 'main' into referrals-tokens
devkiran Nov 19, 2024
328cc8e
Merge branch 'main' into referrals-tokens
devkiran Nov 19, 2024
76ddece
move HeroBackground
devkiran Nov 19, 2024
feb1f7f
Pull out the common components to `@/ui/partners`
devkiran Nov 19, 2024
efba8e1
Add a "powered by Dub" badge
devkiran Nov 19, 2024
0aa91d6
small fix
devkiran Nov 19, 2024
c849e53
add RequestPartnerInviteForm
devkiran Nov 19, 2024
e94fa92
add onTokenExpired
devkiran Nov 19, 2024
9a87aa1
Merge branch 'main' into referrals-tokens
devkiran Nov 19, 2024
181c072
update error mesage
devkiran Nov 19, 2024
0f136e4
remove useReferralEvents
devkiran Nov 19, 2024
81f4da7
reuse invitePartner
devkiran Nov 19, 2024
99381dc
small fix
devkiran Nov 19, 2024
05b6798
Merge branch 'main' into referrals-tokens
devkiran Nov 20, 2024
6348f81
Update invite-partner.ts
devkiran Nov 20, 2024
3580c9e
Merge branch 'main' into referrals-tokens
devkiran Nov 20, 2024
1c37949
Merge branch 'main' into referrals-tokens
steven-tey Nov 20, 2024
3380e5c
Merge branch 'main' into referrals-tokens
steven-tey Nov 20, 2024
ff1e15e
Merge branch 'main' into referrals-tokens
steven-tey Nov 21, 2024
2c72084
wip widget
devkiran Nov 21, 2024
1d3c179
widget ui
devkiran Nov 21, 2024
5f0d863
move files
devkiran Nov 21, 2024
d7e1eba
demo html
devkiran Nov 21, 2024
0114458
Merge branch 'main' into referrals-tokens
steven-tey Nov 21, 2024
880905c
Delete generate-button.tsx
steven-tey Nov 21, 2024
326f076
update createEmbedToken
steven-tey Nov 22, 2024
982af85
Merge branch 'main' into referrals-tokens
steven-tey Nov 22, 2024
70fd542
update to use APP_DOMAIN_WITH_NGROK
steven-tey Nov 22, 2024
345a5c5
Merge branch 'referrals-tokens' of https://github.com/dubinc/dub into…
steven-tey Nov 22, 2024
a6d297e
withEmbedToken
steven-tey Nov 22, 2024
2a379b3
withEmbedToken
steven-tey Nov 22, 2024
c6d34d3
createOrRetrieveEmbedToken
steven-tey Nov 22, 2024
c241791
fix brandcolor
steven-tey Nov 22, 2024
3f2bdde
Merge branch 'referrals-tokens' into widgets
steven-tey Nov 22, 2024
0b0dfc7
Merge pull request #1743 from dubinc/widgets
steven-tey Nov 22, 2024
730e523
small fixes
steven-tey Nov 22, 2024
56e25da
add <DubDashboard />
devkiran Nov 22, 2024
8a10863
add <DubWidget />
devkiran Nov 22, 2024
c5cabda
move to shared
devkiran Nov 22, 2024
5a9344d
update the widget
devkiran Nov 22, 2024
1c8dc63
ui fixes
devkiran Nov 22, 2024
2879060
format
devkiran Nov 22, 2024
595fadf
update options
devkiran Nov 22, 2024
3fdae22
use client
steven-tey Nov 22, 2024
6127672
WIP embed
TWilson023 Nov 22, 2024
d6c851f
Make embed work for <script> and React
TWilson023 Nov 22, 2024
840089a
Clean up styles
TWilson023 Nov 22, 2024
aaf52aa
Add toggleWidget
TWilson023 Nov 22, 2024
dca1fdf
Update next.config.js
steven-tey Nov 22, 2024
53020b8
Add placement options
TWilson023 Nov 22, 2024
45febed
Merge branch 'referrals-tokens' of github.com:dubinc/dub into referra…
TWilson023 Nov 22, 2024
68d9b5c
Update index.html
TWilson023 Nov 22, 2024
3a0ca66
add refer button
steven-tey Nov 22, 2024
69a4471
update widget
steven-tey Nov 22, 2024
f3a4696
Merge branch 'main' into referrals-tokens
steven-tey Nov 22, 2024
c0e9ee3
small widget updates
steven-tey Nov 22, 2024
40a8574
Merge branch 'referrals-tokens' of https://github.com/dubinc/dub into…
steven-tey Nov 22, 2024
2ae6530
Merge branch 'main' into referrals-tokens
steven-tey Nov 23, 2024
7646a5c
small update
devkiran Nov 23, 2024
5fd2b83
add clipboard-write permission
devkiran Nov 23, 2024
637ed3f
Merge branch 'main' into referrals-tokens
devkiran Nov 23, 2024
2007ab3
add example back
devkiran Nov 23, 2024
dc88eab
update inline dashboard
devkiran Nov 23, 2024
8378e8d
move to embed-core
devkiran Nov 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions apps/web/app/api/analytics/client/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { getAnalytics } from "@/lib/analytics/get-analytics";
import { calculateEarnings } from "@/lib/api/sales/commission";
import { withEmbedToken } from "@/lib/auth/embed-token";
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 = withEmbedToken(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,
linkId: link.id,
});

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);
});
32 changes: 32 additions & 0 deletions apps/web/app/api/cron/cleanup-expired-public-tokens/route.ts
Original file line number Diff line number Diff line change
@@ -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: `Referrals public token cleanup failed - ${error.message}`,
type: "errors",
});

return handleAndReturnErrorResponse(error);
}
}
42 changes: 42 additions & 0 deletions apps/web/app/api/events/client/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { getEvents } from "@/lib/analytics/get-events";
import { calculateEarnings } from "@/lib/api/sales/commission";
import { withEmbedToken } from "@/lib/auth/embed-token";
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 = withEmbedToken(async ({ searchParams, program, link }) => {
const parsedParams = eventsQuerySchema
.omit({
linkId: true,
externalId: true,
domain: true,
root: true,
key: true,
tagId: true,
})
.parse(searchParams);

// TODO:
// Replace with sales data

const response = await getEvents({
...parsedParams,
linkId: link.id,
});

return NextResponse.json(
response.map((item: any) => {
return {
...item,
...(parsedParams.event === "sales" && {
earnings: calculateEarnings({
program,
sales: item.sales ?? 0,
saleAmount: item.sale?.amount ?? 0,
}),
}),
};
}),
);
});
48 changes: 48 additions & 0 deletions apps/web/app/api/referrals/invite/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { invitePartner } from "@/lib/api/partners/invite-partner";
import { parseRequestBody } from "@/lib/api/utils";
import { withEmbedToken } from "@/lib/auth/embed-token";
import { requestPartnerInviteSchema } from "@/lib/dots/schemas";
import { prisma } from "@/lib/prisma";
import { NextResponse } from "next/server";

// GET /api/referrals/invite - check if a partner is already invited to a program
export const GET = withEmbedToken(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 = withEmbedToken(
async ({ req, link, program, workspace }) => {
const { email } = requestPartnerInviteSchema.parse(
await parseRequestBody(req),
);

const programInvited = await invitePartner({
email,
program,
link,
workspace,
});

return NextResponse.json(programInvited);
},
);
17 changes: 17 additions & 0 deletions apps/web/app/api/referrals/link/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { withEmbedToken } from "@/lib/auth/embed-token";
import { NextResponse } from "next/server";

// GET /api/referrals/link - get the link for the given affiliate
export const GET = withEmbedToken(async ({ link }) => {
const { id, url, shortLink, clicks, leads, sales, saleAmount } = link;

return NextResponse.json({
id,
url,
shortLink,
clicks,
leads,
sales,
saleAmount,
});
});
8 changes: 8 additions & 0 deletions apps/web/app/api/referrals/program/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { withEmbedToken } from "@/lib/auth/embed-token";
import { ProgramSchema } from "@/lib/zod/schemas/programs";
import { NextResponse } from "next/server";

// GET /api/referrals/program - get the program for the given affiliate
export const GET = withEmbedToken(async ({ program }) => {
return NextResponse.json(ProgramSchema.parse(program));
});
23 changes: 23 additions & 0 deletions apps/web/app/api/referrals/sales/count/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
import { withEmbedToken } from "@/lib/auth/embed-token";
import { prisma } from "@/lib/prisma";
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 = withEmbedToken(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 });
});
30 changes: 30 additions & 0 deletions apps/web/app/api/referrals/sales/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { withEmbedToken } from "@/lib/auth/embed-token";
import { prisma } from "@/lib/prisma";
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 = withEmbedToken(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));
});
23 changes: 23 additions & 0 deletions apps/web/app/api/tokens/embed/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getLinkOrThrow } from "@/lib/api/links/get-link-or-throw";
import { parseRequestBody } from "@/lib/api/utils";
import { withWorkspace } from "@/lib/auth";
import { createOrRetrieveEmbedToken } from "@/lib/referrals/create-or-retrieve-embed-token";
import {
createReferralTokenSchema,
referralTokenSchema,
} from "@/lib/zod/schemas/referrals";
import { NextResponse } from "next/server";

// POST /api/tokens/embed - create a new embed token for the given link
export const POST = withWorkspace(async ({ workspace, req }) => {
const { linkId } = createReferralTokenSchema.parse(
await parseRequestBody(req),
);
await getLinkOrThrow({ linkId, workspaceId: workspace.id });

const token = await createOrRetrieveEmbedToken(linkId);

return NextResponse.json(referralTokenSchema.parse(token), {
status: 201,
});
});
27 changes: 27 additions & 0 deletions apps/web/app/api/workspaces/[idOrSlug]/embed-token/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { DubApiError } from "@/lib/api/errors";
import { withWorkspace } from "@/lib/auth";
import { APP_DOMAIN_WITH_NGROK } from "@dub/utils";
import { NextResponse } from "next/server";

// GET /api/workspaces/[idOrSlug]/embed-token - create a new public embed token for the workspace
export const POST = withWorkspace(async ({ workspace }) => {
const { referralLinkId } = workspace;

if (!referralLinkId) {
throw new DubApiError({
code: "bad_request",
message: "Referral link not found for this workspace.",
});
}

const token = await fetch(`${APP_DOMAIN_WITH_NGROK}/api/tokens/embed`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.DUB_API_KEY}`,
},
body: JSON.stringify({ linkId: referralLinkId }),
}).then((res) => res.json());

return NextResponse.json(token);
});
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ 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,
CursorRays,
Globe,
InfoTooltip,
LinkBroken,
PaginationControls,
Popover,
ToggleGroup,
TooltipContent,
usePagination,
useRouterStuff,
} from "@dub/ui";
import { CursorRays, LinkBroken } from "@dub/ui/src/icons";
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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
MaxWidthWrapper,
Popover,
TokenAvatar,
Tooltip,
TooltipContent,
} from "@dub/ui";
import {
CircleWarning,
Expand All @@ -25,7 +27,6 @@ import {
OfficeBuilding,
ShieldCheck,
} from "@dub/ui/src/icons";
import { Tooltip, TooltipContent } from "@dub/ui/src/tooltip";
import {
cn,
formatDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, SetStateAction, useState } from "react";
import { TagCard } from "./tag-card";
import { TagCardPlaceholder } from "./tag-card-placeholder";
Expand Down
Loading
Loading