diff --git a/apps/web/app/api/embed/leaderboard/route.ts b/apps/web/app/api/embed/leaderboard/route.ts new file mode 100644 index 0000000000..09b59449d2 --- /dev/null +++ b/apps/web/app/api/embed/leaderboard/route.ts @@ -0,0 +1,40 @@ +import { withEmbedToken } from "@/lib/embed/auth"; +import { LeaderboardPartnerSchema } from "@/lib/zod/schemas/partners"; +import { prisma } from "@dub/prisma"; +import { NextResponse } from "next/server"; +import z from "node_modules/zod/lib"; + +// GET /api/embed/sales – get sales for a link from an embed token +export const GET = withEmbedToken(async ({ program, searchParams }) => { + const programEnrollments = await prisma.programEnrollment.findMany({ + where: { + programId: program.id, + }, + orderBy: [ + { + link: { + saleAmount: "desc", + }, + }, + { + link: { + leads: "desc", + }, + }, + { + link: { + clicks: "desc", + }, + }, + ], + select: { + partner: true, + link: true, + }, + take: 10, + }); + + return NextResponse.json( + z.array(LeaderboardPartnerSchema).parse(programEnrollments), + ); +}); diff --git a/apps/web/app/api/embed/sales/route.ts b/apps/web/app/api/embed/sales/route.ts index 720b209116..7ea7b25dd9 100644 --- a/apps/web/app/api/embed/sales/route.ts +++ b/apps/web/app/api/embed/sales/route.ts @@ -1,11 +1,16 @@ import { withEmbedToken } from "@/lib/embed/auth"; +import { SALES_PAGE_SIZE } from "@/lib/partners/constants"; import z from "@/lib/zod"; import { PartnerSaleResponseSchema } from "@/lib/zod/schemas/partners"; import { prisma } from "@dub/prisma"; import { NextResponse } from "next/server"; // GET /api/embed/sales – get sales for a link from an embed token -export const GET = withEmbedToken(async ({ link }) => { +export const GET = withEmbedToken(async ({ link, searchParams }) => { + const { page } = z + .object({ page: z.coerce.number().optional().default(1) }) + .parse(searchParams); + const sales = await prisma.sale.findMany({ where: { linkId: link.id, @@ -25,7 +30,8 @@ export const GET = withEmbedToken(async ({ link }) => { }, }, }, - take: 3, + take: SALES_PAGE_SIZE, + skip: (page - 1) * SALES_PAGE_SIZE, orderBy: { createdAt: "desc", }, diff --git a/apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts b/apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts index e1a0b05063..95ae990915 100644 --- a/apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts +++ b/apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts @@ -1,4 +1,5 @@ import { withWorkspace } from "@/lib/auth"; +import { getDubCustomer } from "@/lib/dub"; import { stripe } from "@/lib/stripe"; import { APP_DOMAIN } from "@dub/utils"; import { NextResponse } from "next/server"; @@ -46,6 +47,10 @@ export const POST = withWorkspace(async ({ req, workspace, session }) => { }); return NextResponse.json({ url }); } else { + const customer = await getDubCustomer(session.user.id); + const isReferral = + customer?.link?.programId === "prog_d8pl69xXCv4AoHNT281pHQdo"; + // For both new users and users with canceled subscriptions const stripeSession = await stripe.checkout.sessions.create({ ...(workspace.stripeId @@ -64,7 +69,18 @@ export const POST = withWorkspace(async ({ req, workspace, session }) => { success_url: `${APP_DOMAIN}/${workspace.slug}?${onboarding ? "onboarded" : "upgraded"}=true&plan=${plan}&period=${period}`, cancel_url: baseUrl, line_items: [{ price: prices.data[0].id, quantity: 1 }], - allow_promotion_codes: true, + ...(isReferral + ? { + discounts: [ + { + coupon: + process.env.NODE_ENV === "production" + ? "pEVpzGQE" + : "k8v8KtqG", + }, + ], + } + : { allow_promotion_codes: true }), automatic_tax: { enabled: true, }, diff --git a/apps/web/app/app.dub.co/embed/activity.tsx b/apps/web/app/app.dub.co/embed/activity.tsx index 2e848c1336..0ca76ac4e8 100644 --- a/apps/web/app/app.dub.co/embed/activity.tsx +++ b/apps/web/app/app.dub.co/embed/activity.tsx @@ -1,30 +1,44 @@ -import { currencyFormatter, nFormatter } from "@dub/utils"; +import { InfoTooltip } from "@dub/ui"; +import { nFormatter } from "@dub/utils"; -export function Activity({ +export function EmbedActivity({ clicks, leads, - earnings, + sales, }: { clicks: number; leads: number; - earnings: number; + sales: number; }) { return ( -
+
{[ - { label: "Clicks", value: clicks }, - { label: "Signups", value: leads }, - { label: "Total earned", value: earnings }, - ].map(({ label, value }) => ( -
- {label} - - {label === "Total earned" - ? currencyFormatter(value / 100, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }) - : nFormatter(value)} + { + label: "Clicks", + value: clicks, + description: "Total number of unique clicks your link has received", + }, + { + label: "Leads", + value: leads, + description: "Total number of signups that came from your link", + }, + { + label: "Sales", + value: sales, + description: "Total number of leads that converted to a paid account", + }, + ].map(({ label, value, description }) => ( +
+ + {label} + + + + {nFormatter(value, { full: true })}
))} diff --git a/apps/web/app/app.dub.co/embed/inline/page-client.tsx b/apps/web/app/app.dub.co/embed/inline/page-client.tsx index b399f57418..a8ef5fc09f 100644 --- a/apps/web/app/app.dub.co/embed/inline/page-client.tsx +++ b/apps/web/app/app.dub.co/embed/inline/page-client.tsx @@ -1,45 +1,36 @@ "use client"; -import { PartnerSaleResponse } from "@/lib/types"; import { HeroBackground } from "@/ui/partners/hero-background"; import { ProgramCommissionDescription } from "@/ui/partners/program-commission-description"; import { Link, Program } from "@dub/prisma/client"; import { Button, - buttonVariants, Check, Copy, MoneyBill2, + ToggleGroup, useCopyToClipboard, Wordmark, } from "@dub/ui"; -import { cn, fetcher, getPrettyUrl } from "@dub/utils"; -import { CSSProperties } from "react"; -import useSWR from "swr"; -import { Activity } from "../activity"; -import { SalesList } from "../sales-list"; +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"; export function EmbedInlinePageClient({ program, link, - earnings, - hasPartnerProfile, }: { program: Program; link: Link; - earnings: number; - hasPartnerProfile: boolean; }) { const [copied, copyToClipboard] = useCopyToClipboard(); - const { data: sales, isLoading } = useSWR( - "/api/embed/sales", - fetcher, - { - keepPreviousData: true, - }, - ); + const tabs = ["Quickstart", "Sales", "Leaderboard", "FAQ"]; + const [selectedTab, setSelectedTab] = useState(tabs[0]); return (
- {program && ( - - )} + Refer and earn -
+
@@ -99,59 +88,39 @@ export function EmbedInlinePageClient({

Powered by

-
- <> -

Activity

- -
-

- Recent sales -

- - {!isLoading && - sales && - sales.length > 0 && - (hasPartnerProfile ? ( - - Withdraw earnings - - ) : ( - - Create partner account - - ))} -
- - +
+ + +
+
+ ({ + label: tab, + value: tab, + }))} + selected={selectedTab} + selectAction={(option) => { + setSelectedTab(option); + }} + className="w-full rounded-lg" + /> + {selectedTab === "Leaderboard" ? ( + + ) : ( + + )}
+
); diff --git a/apps/web/app/app.dub.co/embed/inline/page.tsx b/apps/web/app/app.dub.co/embed/inline/page.tsx index 4f12f6876d..d0042127d1 100644 --- a/apps/web/app/app.dub.co/embed/inline/page.tsx +++ b/apps/web/app/app.dub.co/embed/inline/page.tsx @@ -8,15 +8,7 @@ export default async function EmbedInlinePage({ }) { const { token } = searchParams; - const { link, program, hasPartnerProfile, earnings } = - await getEmbedData(token); + const { link, program } = await getEmbedData(token); - return ( - - ); + return ; } diff --git a/apps/web/app/app.dub.co/embed/leaderboard.tsx b/apps/web/app/app.dub.co/embed/leaderboard.tsx new file mode 100644 index 0000000000..aac14e4f51 --- /dev/null +++ b/apps/web/app/app.dub.co/embed/leaderboard.tsx @@ -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[] + >("/api/embed/leaderboard", fetcher, { + keepPreviousData: true, + }); + + const { table, ...tableProps } = useTable({ + data: partners || [], + loading: isLoading, + columns: [ + { + id: "position", + header: "Position", + cell: ({ row }) => { + return ( +
+ {row.index + 1} + {row.index <= 2 && ( + + )} +
+ ); + }, + }, + { + 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: ( + ( + <> + +
+ + )} + className="border-none md:min-h-fit" + /> + ), + thClassName: "border-l-0", + tdClassName: "border-l-0", + resourceName: (plural) => `partner${plural ? "s" : ""}`, + }); + + return ( +
+ +
+
+ ); +} diff --git a/apps/web/app/app.dub.co/embed/payouts.tsx b/apps/web/app/app.dub.co/embed/payouts.tsx new file mode 100644 index 0000000000..211b2553cf --- /dev/null +++ b/apps/web/app/app.dub.co/embed/payouts.tsx @@ -0,0 +1,36 @@ +import { InfoTooltip } from "@dub/ui"; +import { currencyFormatter } from "@dub/utils"; + +export function EmbedPayouts() { + return ( +
+
+

Payouts

+ +
+
+ {[ + { + label: "Upcoming", + value: 1830, + }, + { + label: "Total", + value: 18112, + }, + ].map(({ label, value }) => ( +
+ {label} + + {currencyFormatter(value / 100, { + style: "currency", + currency: "USD", + maximumFractionDigits: 2, + })} + +
+ ))} +
+
+ ); +} diff --git a/apps/web/app/app.dub.co/embed/sales-list.tsx b/apps/web/app/app.dub.co/embed/sales-list.tsx deleted file mode 100644 index 182fc129f6..0000000000 --- a/apps/web/app/app.dub.co/embed/sales-list.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { PartnerSaleResponse } from "@/lib/types"; -import { Gift, LoadingSpinner } from "@dub/ui"; -import { cn, currencyFormatter } from "@dub/utils"; - -export function SalesList({ - sales, - isLoading, - hasPartnerProfile, -}: { - sales: PartnerSaleResponse[] | undefined; - isLoading: boolean; - hasPartnerProfile: boolean; -}) { - return sales ? ( - sales.length ? ( -
-
- {sales.slice(0, hasPartnerProfile ? 3 : 1).map((sale, idx) => ( -
0 && "border-t", - )} - > -
- - {sale.customer.email} - -
-
- - {currencyFormatter(sale.earnings / 100, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - })} - -
-
- ))} -
- {!hasPartnerProfile && ( -

- To withdraw your earnings or view all of your sales, create a free - Dub partner account below. -

- )} -
- ) : ( - - ) - ) : isLoading ? ( -
- -
- ) : ( - - ); -} - -const EmptyState = () => { - return ( -
- -

- No sales yet. When you refer a friend and they make a purchase, they'll - show up here. -

-
- ); -}; diff --git a/apps/web/app/app.dub.co/embed/sales.tsx b/apps/web/app/app.dub.co/embed/sales.tsx new file mode 100644 index 0000000000..3bd314a827 --- /dev/null +++ b/apps/web/app/app.dub.co/embed/sales.tsx @@ -0,0 +1,82 @@ +import { SALES_PAGE_SIZE } from "@/lib/partners/constants"; +import { PartnerSaleResponse } from "@/lib/types"; +import { Gift, Table, usePagination, useTable } from "@dub/ui"; +import { currencyFormatter, fetcher, formatDate } from "@dub/utils"; +import useSWR from "swr"; + +export function EmbedSales({ salesCount }: { salesCount: number }) { + const { pagination, setPagination } = usePagination(SALES_PAGE_SIZE); + const { data: sales, isLoading } = useSWR( + `/api/embed/sales?page=${pagination.pageIndex}`, + fetcher, + { + keepPreviousData: true, + }, + ); + + const { table, ...tableProps } = useTable({ + data: sales || [], + loading: isLoading, + columns: [ + { + id: "customer", + header: "Customer", + cell: ({ row }) => { + return row.original.customer.email; + }, + }, + { + id: "createdAt", + header: "Date", + cell: ({ row }) => { + return formatDate(row.original.createdAt, { month: "short" }); + }, + }, + { + id: "saleAmount", + header: "Amount", + cell: ({ row }) => { + return currencyFormatter(row.original.amount / 100, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + }, + }, + { + id: "earnings", + header: "Earnings", + accessorKey: "earnings", + cell: ({ row }) => { + return currencyFormatter(row.original.earnings / 100, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + }, + }, + ], + pagination, + onPaginationChange: setPagination, + rowCount: salesCount, + emptyState: ( +
+ +

+ No sales yet. When you refer a friend and they make a purchase, + they'll show up here. +

+
+ ), + thClassName: "border-l-0", + tdClassName: "border-l-0", + resourceName: (plural) => `sale${plural ? "s" : ""}`, + }); + + return ( +
+ ); +} diff --git a/apps/web/app/app.dub.co/embed/token.tsx b/apps/web/app/app.dub.co/embed/token.tsx index 31fc5d207b..d94780024c 100644 --- a/apps/web/app/app.dub.co/embed/token.tsx +++ b/apps/web/app/app.dub.co/embed/token.tsx @@ -7,7 +7,8 @@ import useSWR from "swr"; export const LinkToken = () => { const { error } = useSWR<{ token: number }>("/api/embed/token", fetcher, { revalidateOnFocus: true, - refreshInterval: 50000, + dedupingInterval: 30000, + keepPreviousData: true, }); // Inform the parent if there's an error (Eg: token is expired) 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 index 86a64fc9ab..774782399a 100644 --- a/apps/web/app/app.dub.co/embed/widget/page-client.tsx +++ b/apps/web/app/app.dub.co/embed/widget/page-client.tsx @@ -1,6 +1,5 @@ "use client"; -import { PartnerSaleResponse } from "@/lib/types"; import { Link, Program } from "@dub/prisma/client"; import { AnimatedSizeContainer, @@ -19,12 +18,11 @@ import { QRCode, Twitter, } from "@dub/ui/icons"; -import { cn, fetcher, getPrettyUrl } from "@dub/utils"; +import { cn, getPrettyUrl } from "@dub/utils"; import { motion } from "framer-motion"; import { CSSProperties, useState } from "react"; -import useSWR from "swr"; -import { Activity } from "../activity"; -import { SalesList } from "../sales-list"; +import { EmbedActivity } from "../activity"; +import { EmbedSales } from "../sales"; import { LinkToken } from "../token"; import { useIframeVisibility } from "../use-iframe-visibility"; @@ -35,12 +33,10 @@ const heroAnimationDuration = 0.2; export function EmbedWidgetPageClient({ program, link, - earnings, hasPartnerProfile, }: { program: Program; link: Link; - earnings: number; hasPartnerProfile: boolean; }) { const [copied, copyToClipboard] = useCopyToClipboard(); @@ -48,14 +44,6 @@ export function EmbedWidgetPageClient({ const isIframeVisible = useIframeVisibility(); - const { data: sales, isLoading } = useSWR( - isIframeVisible && "/api/embed/sales", - fetcher, - { - keepPreviousData: true, - }, - ); - return (

- Activity + EmbedActivity

-

Recent sales

- - {!isLoading && - sales && - sales.length > 0 && - (hasPartnerProfile ? ( - - Withdraw earnings - - ) : ( - - Create partner account - - ))} + + {hasPartnerProfile ? ( + + Withdraw earnings + + ) : ( + + Create partner account + + )}
diff --git a/apps/web/app/app.dub.co/embed/widget/page.tsx b/apps/web/app/app.dub.co/embed/widget/page.tsx index 324e6dd91f..b2152d0804 100644 --- a/apps/web/app/app.dub.co/embed/widget/page.tsx +++ b/apps/web/app/app.dub.co/embed/widget/page.tsx @@ -8,14 +8,12 @@ export default async function EmbedWidgetPage({ }) { const { token } = searchParams; - const { link, program, hasPartnerProfile, earnings } = - await getEmbedData(token); + const { link, program, hasPartnerProfile } = await getEmbedData(token); return ( ); diff --git a/apps/web/lib/dub.ts b/apps/web/lib/dub.ts index 7f32903592..acb509885a 100644 --- a/apps/web/lib/dub.ts +++ b/apps/web/lib/dub.ts @@ -1,3 +1,14 @@ import { Dub } from "dub"; export const dub = new Dub(); + +// fetch Dub customer using their external ID (ID in our database) +export const getDubCustomer = async (userId: string) => { + try { + return await dub.customers.get({ + id: `ext_${userId}`, + }); + } catch (error) { + return null; + } +}; diff --git a/apps/web/lib/partners/constants.ts b/apps/web/lib/partners/constants.ts index c47af9e234..1a9059d10d 100644 --- a/apps/web/lib/partners/constants.ts +++ b/apps/web/lib/partners/constants.ts @@ -1,3 +1,4 @@ export const DUB_PARTNERS_PAYOUT_FEE = 0.03; export const MIN_PAYOUT_AMOUNT = 10000; // min payout is $100 export const SHEET_MAX_ITEMS = 6; +export const SALES_PAGE_SIZE = 4; diff --git a/apps/web/lib/zod/schemas/partners.ts b/apps/web/lib/zod/schemas/partners.ts index 2f8823fd38..ba9b9758a4 100644 --- a/apps/web/lib/zod/schemas/partners.ts +++ b/apps/web/lib/zod/schemas/partners.ts @@ -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"; @@ -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(), diff --git a/apps/web/package.json b/apps/web/package.json index 230033112f..00eeafbc0c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -64,7 +64,7 @@ "concurrently": "^8.0.1", "csv-parse": "^5.5.6", "date-fns": "^3.6.0", - "dub": "^0.46.1", + "dub": "^0.46.8", "framer-motion": "^10.16.16", "fuse.js": "^6.6.2", "geist": "^1.3.1", diff --git a/apps/web/ui/partners/embed-docs-sheet.tsx b/apps/web/ui/partners/embed-docs-sheet.tsx index 1a643649dc..936a1bd205 100644 --- a/apps/web/ui/partners/embed-docs-sheet.tsx +++ b/apps/web/ui/partners/embed-docs-sheet.tsx @@ -97,8 +97,7 @@ const App = () => { }, []); return `; const htmlSnippet = ` diff --git a/apps/web/ui/partners/program-commission-description.tsx b/apps/web/ui/partners/program-commission-description.tsx index 70d8330000..4d59fe0be0 100644 --- a/apps/web/ui/partners/program-commission-description.tsx +++ b/apps/web/ui/partners/program-commission-description.tsx @@ -49,7 +49,12 @@ export function ProgramCommissionDescription({ ) : ( "." - )} + )}{" "} + Referred users get{" "} + + 20% off for 3 months + + . ); } diff --git a/packages/embeds/core/src/core.ts b/packages/embeds/core/src/core.ts index 3f7aebdc90..81bcb41268 100644 --- a/packages/embeds/core/src/core.ts +++ b/packages/embeds/core/src/core.ts @@ -14,7 +14,7 @@ import { DubInitResult, DubOptions, IframeMessage } from "./types"; const INLINE_CONTAINER_STYLES = { position: "relative", maxWidth: "1024px", - height: "760px", + height: "960px", margin: "0 auto", }; @@ -86,15 +86,8 @@ class DubWidget { renderWidget() { console.debug("[Dub] Rendering widget."); - const { - token, - variant, - root, - containerStyles, - popupStyles, - onError, - onTokenExpired, - } = this.options; + const { token, variant, root, containerStyles, popupStyles, onError } = + this.options; const existingContainer = document.getElementById( `${this.prefix}${DUB_CONTAINER_ID}`, @@ -162,10 +155,6 @@ class DubWidget { }), ); } - - if (data?.code === "unauthorized") { - onTokenExpired?.(); - } }); (root ?? document.body).appendChild(container); diff --git a/packages/embeds/core/src/types.ts b/packages/embeds/core/src/types.ts index a98c32aa47..d716b0b5f3 100644 --- a/packages/embeds/core/src/types.ts +++ b/packages/embeds/core/src/types.ts @@ -35,9 +35,6 @@ export type DubOptions = { // An error occurred in the APIs onError?: (error: Error) => void; - // The token expired - onTokenExpired?: () => void; - // The placement of the floating button buttonPlacement?: DubFloatingButtonPlacement; diff --git a/packages/embeds/react/src/example/widget.tsx b/packages/embeds/react/src/example/widget.tsx index c4d0fc8f48..7a11383fd4 100644 --- a/packages/embeds/react/src/example/widget.tsx +++ b/packages/embeds/react/src/example/widget.tsx @@ -15,7 +15,7 @@ const Widget = () => { createToken(); }, []); - return ; + return ; }; ReactDom.render(, document.getElementById("root")); diff --git a/packages/ui/src/icons/nucleo/crown.tsx b/packages/ui/src/icons/nucleo/crown.tsx new file mode 100644 index 0000000000..7101399753 --- /dev/null +++ b/packages/ui/src/icons/nucleo/crown.tsx @@ -0,0 +1,27 @@ +import { SVGProps } from "react"; + +export function Crown(props: SVGProps) { + return ( + + + + + + + + + + ); +} diff --git a/packages/ui/src/icons/nucleo/index.ts b/packages/ui/src/icons/nucleo/index.ts index a67a0756c2..7aa5f82758 100644 --- a/packages/ui/src/icons/nucleo/index.ts +++ b/packages/ui/src/icons/nucleo/index.ts @@ -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"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b055203519..1dcbc975c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,8 +184,8 @@ importers: specifier: ^3.6.0 version: 3.6.0 dub: - specifier: ^0.46.1 - version: 0.46.1(react-dom@18.2.0)(react@18.2.0)(zod@3.22.4) + specifier: ^0.46.8 + version: 0.46.8(zod@3.22.4) framer-motion: specifier: ^10.16.16 version: 10.18.0(react-dom@18.2.0)(react@18.2.0) @@ -12560,15 +12560,11 @@ packages: zod: 3.23.8 dev: false - /dub@0.46.1(react-dom@18.2.0)(react@18.2.0)(zod@3.22.4): - resolution: {integrity: sha512-3VRmxY3oAl4w3PCpwIO6R+SQCumlIJKZR5RCF9lPhj5IeUAGVTZsms3bMwKmO+uwQoWZ79ymZHnMOmmZvUaEsg==} + /dub@0.46.8(zod@3.22.4): + resolution: {integrity: sha512-48r051aekxXnRvHtxwaJeKeUYi0MgX3jiEE9BbCdGkAaNOBU6+hfl2bJPYMK+MO+qBRAc0uYOeBvIxcKKgrBCQ==} peerDependencies: - react: ^18 || ^19 - react-dom: ^18 || ^19 zod: '>= 3' dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) zod: 3.22.4 dev: false