Skip to content

Commit

Permalink
EmbedLeaderboard
Browse files Browse the repository at this point in the history
  • Loading branch information
steven-tey committed Dec 24, 2024
1 parent b484548 commit 9d57df0
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 1 deletion.
40 changes: 40 additions & 0 deletions apps/web/app/api/embed/leaderboard/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { withEmbedToken } from "@/lib/embed/auth";
import { LeaderboardPartnerSchema } from "@/lib/zod/schemas/partners";
import { prisma } from "@dub/prisma";
import { NextResponse } from "next/server";
import z from "node_modules/zod/lib";

// GET /api/embed/sales – get sales for a link from an embed token
export const GET = withEmbedToken(async ({ program, searchParams }) => {
const programEnrollments = await prisma.programEnrollment.findMany({
where: {
programId: program.id,
},
orderBy: [
{
link: {
saleAmount: "desc",
},
},
{
link: {
leads: "desc",
},
},
{
link: {
clicks: "desc",
},
},
],
select: {
partner: true,
link: true,
},
take: 10,
});

return NextResponse.json(
z.array(LeaderboardPartnerSchema).parse(programEnrollments),
);
});
7 changes: 6 additions & 1 deletion apps/web/app/app.dub.co/embed/inline/page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { cn, getPrettyUrl } from "@dub/utils";
import { CSSProperties, useState } from "react";
import { EmbedActivity } from "../activity";
import { EmbedLeaderboard } from "../leaderboard";
import { EmbedPayouts } from "../payouts";
import { EmbedSales } from "../sales";
import { LinkToken } from "../token";
Expand Down Expand Up @@ -113,7 +114,11 @@ export function EmbedInlinePageClient({
}}
className="w-full rounded-lg"
/>
<EmbedSales salesCount={link.sales} />
{selectedTab === "Leaderboard" ? (
<EmbedLeaderboard />
) : (
<EmbedSales salesCount={link.sales} />
)}
</div>
<LinkToken />
</div>
Expand Down
86 changes: 86 additions & 0 deletions apps/web/app/app.dub.co/embed/leaderboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import z from "@/lib/zod";
import { LeaderboardPartnerSchema } from "@/lib/zod/schemas/partners";
import { AnimatedEmptyState } from "@/ui/shared/animated-empty-state";
import { Crown, Table, Users, useTable } from "@dub/ui";
import { currencyFormatter, fetcher } from "@dub/utils";
import { cn } from "@dub/utils/src/functions";
import useSWR from "swr";

export function EmbedLeaderboard() {
const { data: partners, isLoading } = useSWR<
z.infer<typeof LeaderboardPartnerSchema>[]
>("/api/embed/leaderboard", fetcher, {
keepPreviousData: true,
});

const { table, ...tableProps } = useTable({
data: partners || [],
loading: isLoading,
columns: [
{
id: "position",
header: "Position",
cell: ({ row }) => {
return (
<div className="flex items-center justify-start gap-2">
{row.index + 1}
{row.index <= 2 && (
<Crown
className={cn("size-4", {
"text-amber-400": row.index === 0,
"text-neutral-400": row.index === 1,
"text-yellow-900": row.index === 2,
})}
/>
)}
</div>
);
},
},
{
id: "name",
header: "Name",
cell: ({ row }) => {
return row.original.partner.name;
},
},
{
id: "sales",
header: "Sales",
cell: ({ row }) => {
return currencyFormatter(row.original.link?.saleAmount / 100 ?? 0, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
},
},
],
emptyState: (
<AnimatedEmptyState
title="No partners found"
description="No partners have been added to this program yet."
cardContent={() => (
<>
<Users className="size-4 text-neutral-700" />
<div className="h-2.5 w-24 min-w-0 rounded-sm bg-neutral-200" />
</>
)}
className="border-none md:min-h-fit"
/>
),
thClassName: "border-l-0",
tdClassName: "border-l-0",
resourceName: (plural) => `partner${plural ? "s" : ""}`,
});

return (
<div className="relative my-4 rounded-md border border-neutral-200">
<Table
{...tableProps}
table={table}
containerClassName="border-none max-h-[16.5rem] overflow-auto"
/>
<div className="pointer-events-none absolute -bottom-px left-0 h-16 w-full rounded-b-lg bg-gradient-to-t from-white sm:bottom-0" />
</div>
);
}
21 changes: 21 additions & 0 deletions apps/web/lib/zod/schemas/partners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { COUNTRY_CODES } from "@dub/utils";
import { z } from "zod";
import { CustomerSchema } from "./customers";
import { LinkSchema } from "./links";
import { getPaginationQuerySchema } from "./misc";
import { ProgramEnrollmentSchema } from "./programs";
import { parseDateSchema } from "./utils";
Expand Down Expand Up @@ -66,6 +67,26 @@ export const EnrolledPartnerSchema = PartnerSchema.omit({
earnings: z.number(),
});

export const LeaderboardPartnerSchema = z.object({
partner: z.object({
id: z.string(),
name: z.string().transform((name) => {
const parts = name.trim().split(/\s+/);
if (parts.length < 2) return name; // Return original if single word
const firstName = parts[0];
const lastInitial = parts[parts.length - 1][0];
return `${firstName} ${lastInitial}.`;
}),
}),
link: LinkSchema.pick({
shortLink: true,
clicks: true,
leads: true,
sales: true,
saleAmount: true,
}),
});

export const SaleSchema = z.object({
id: z.string(),
amount: z.number(),
Expand Down
27 changes: 27 additions & 0 deletions packages/ui/src/icons/nucleo/crown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { SVGProps } from "react";

export function Crown(props: SVGProps<SVGSVGElement>) {
return (
<svg
height="18"
width="18"
viewBox="0 0 18 18"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g fill="currentColor">
<circle cx="9" cy="2.25" fill="currentColor" r="1" />
<circle cx="2" cy="5" fill="currentColor" r="1" />
<circle cx="16" cy="5" fill="currentColor" r="1" />
<path
d="M15.426,6.882c-.244-.168-.566-.176-.819-.021l-2.609,1.605-2.357-3.858c-.272-.446-1.008-.446-1.28,0l-2.357,3.858-2.609-1.605c-.253-.155-.574-.147-.819,.021-.245,.169-.367,.466-.311,.758l.845,4.437c.157,.825,.88,1.423,1.719,1.423H13.172c.839,0,1.562-.598,1.719-1.423l.845-4.437c.056-.292-.066-.589-.311-.758Z"
fill="currentColor"
/>
<path
d="M14,14.5H4c-.414,0-.75,.336-.75,.75s.336,.75,.75,.75H14c.414,0,.75-.336,.75-.75s-.336-.75-.75-.75Z"
fill="currentColor"
/>
</g>
</svg>
);
}
1 change: 1 addition & 0 deletions packages/ui/src/icons/nucleo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export * from "./connected-dots4";
export * from "./connections3";
export * from "./credit-card";
export * from "./crosshairs3";
export * from "./crown";
export * from "./cube";
export * from "./cube-settings";
export * from "./cube-settings-fill";
Expand Down

0 comments on commit 9d57df0

Please sign in to comment.