Skip to content

Commit

Permalink
Merge branch 'main' into referrals-tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
steven-tey authored Nov 22, 2024
2 parents 326f076 + a36340c commit 982af85
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,21 @@ export function PartnerTable() {
);
},
},
// TODO: Add these once we support hiding/showing columns
// {
// header: "Clicks",
// accessorFn: (d) =>
// d.status !== "pending"
// ? nFormatter(d.link?.clicks, { full: true })
// : "-",
// },
// {
// header: "Leads",
// accessorFn: (d) =>
// d.status !== "pending"
// ? nFormatter(d.link?.leads, { full: true })
// : "-",
// },
{
header: "Sales",
accessorFn: (d) =>
Expand Down
25 changes: 14 additions & 11 deletions apps/web/lib/actions/partners/create-partner-payout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { formatDate } from "@dub/utils";
import { waitUntil } from "@vercel/functions";
import { sendEmail } from "emails";
import PartnerPayoutSent from "emails/partner-payout-sent";
import { v5 as uuidv5 } from "uuid";
import { z } from "zod";
import { createOrgTransfer } from "../../dots/create-org-transfer";
import { createTransfer } from "../../dots/create-transfer";
Expand Down Expand Up @@ -47,17 +48,19 @@ export const createPartnerPayoutAction = authActionClient
throw new Error("Partner is not properly enrolled in this program");
}

const [transfer, orgTransfer] = await Promise.all([
createTransfer({
amount: payout.amount,
dotsAppId: workspace.dotsAppId,
dotsUserId: programEnrollment.dotsUserId,
}),
createOrgTransfer({
amount: payout.fee,
dotsAppId: workspace.dotsAppId,
}),
]);
const transfer = await createTransfer({
amount: payout.amount,
dotsAppId: workspace.dotsAppId,
dotsUserId: programEnrollment.dotsUserId,
idempotencyKey: uuidv5(payout.id, uuidv5.URL), // pass a unique idempotency key for each payout to avoid duplicate payouts
});

// we're splitting this out of the Promise.all() to avoid a race condition
// e.g. if the transfer fails, we shouldn't proceed with creating the org transfer
const orgTransfer = await createOrgTransfer({
amount: payout.fee,
dotsAppId: workspace.dotsAppId,
});

await Promise.all([
prisma.payout.update({
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/dots/create-transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@ export const createTransfer = async ({
amount,
dotsAppId,
dotsUserId,
idempotencyKey,
}: {
amount: number;
dotsAppId: string;
dotsUserId: string;
idempotencyKey: string;
}) => {
console.log(`Creating a transfer of ${amount} cents`);
console.log(
`Creating a transfer of ${amount} cents, with idempotency key ${idempotencyKey}`,
);

return await dotsFetch("/transfers", {
method: "POST",
dotsAppId,
body: {
user_id: dotsUserId,
amount: -amount, // negative means transfer from Business to Partner
idempotency_key: idempotencyKey,
},
});
};
2 changes: 2 additions & 0 deletions apps/web/lib/zod/schemas/programs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export const ProgramEnrollmentSchema = z.object({
link: LinkSchema.pick({
id: true,
shortLink: true,
domain: true,
key: true,
url: true,
clicks: true,
leads: true,
Expand Down
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"unique-names-generator": "^4.7.1",
"unsplash-js": "^7.0.18",
"use-debounce": "^10.0.4",
"uuid": "^11.0.3",
"vaul": "^0.6.8",
"zod": "^3.22.4",
"zod-error": "^1.5.0",
Expand Down
52 changes: 30 additions & 22 deletions apps/web/ui/partners/partner-details-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ import {
StatusBadge,
Table,
ToggleGroup,
useCopyToClipboard,
useRouterStuff,
useTable,
useTablePagination,
} from "@dub/ui";
import { Copy, GreekTemple } from "@dub/ui/src/icons";
import { GreekTemple, LinesY } from "@dub/ui/src/icons";
import {
cn,
COUNTRIES,
currencyFormatter,
DICEBEAR_AVATAR_URL,
formatDate,
getPrettyUrl,
nFormatter,
} from "@dub/utils";
import { ChevronLeft, Link2 } from "lucide-react";
import { ChevronLeft } from "lucide-react";
import { useAction } from "next-safe-action/hooks";
import Link from "next/link";
import { useRouter } from "next/navigation";
Expand All @@ -49,13 +49,13 @@ function PartnerDetailsSheetContent({
partner,
setIsOpen,
}: PartnerDetailsSheetProps) {
const { slug } = useWorkspace();

const badge = PartnerStatusBadges[partner.status];

const saleAmount = (partner.link?.saleAmount ?? 0) / 100;
const earnings = (partner.earnings ?? 0) / 100;

const [, copyToClipboard] = useCopyToClipboard();

const [selectedTab, setSelectedTab] = useState<"overview" | "payouts">(
"overview",
);
Expand Down Expand Up @@ -97,28 +97,19 @@ function PartnerDetailsSheetContent({
</div>
<div className="flex min-w-[40%] shrink grow basis-1/2 flex-wrap items-center justify-end gap-2">
{partner.link && (
<button
type="button"
title="Copy link"
onClick={() => {
if (!partner.link) return;
toast.promise(copyToClipboard(partner.link.shortLink), {
success: "Copied to clipboard",
});
}}
className="group flex min-w-0 items-center gap-1 overflow-hidden rounded-full bg-neutral-100 px-2 py-1 text-xs text-neutral-700 transition-colors duration-100 hover:bg-neutral-200/70 active:bg-neutral-200"
<a
href={`/${slug}/analytics?domain=${partner.link.domain}&key=${partner.link.key}`}
target="_blank"
className="group flex min-w-0 items-center gap-1.5 overflow-hidden rounded-full bg-neutral-100 px-2.5 py-1 text-xs text-neutral-700 transition-colors duration-100 hover:bg-neutral-200/70 active:bg-neutral-200"
>
<div className="relative size-3 shrink-0 text-neutral-600">
<Link2 className="absolute left-0 top-0 size-3 transition-[opacity,transform] duration-150 group-hover:-translate-y-2 group-hover:opacity-0" />
<Copy className="absolute left-0 top-0 size-3 translate-y-2 opacity-0 transition-[opacity,transform] duration-150 group-hover:translate-y-0 group-hover:opacity-100" />
</div>
<LinesY className="size-3.5" />
<span className="truncate">
{getPrettyUrl(partner.link.shortLink)}
</span>
</button>
</a>
)}
{partner.country && (
<div className="flex min-w-20 items-center gap-2 rounded-full bg-neutral-100 px-2 py-1 text-xs text-neutral-700">
<div className="flex min-w-20 items-center gap-2 rounded-full bg-neutral-100 px-2.5 py-1 text-xs text-neutral-700">
<img
alt=""
src={`https://flag.vercel.app/m/${partner.country}.svg`}
Expand All @@ -133,6 +124,24 @@ function PartnerDetailsSheetContent({
{/* Stats */}
<div className="mt-6 flex divide-x divide-neutral-200">
{[
[
"Clicks",
!partner.link
? "-"
: nFormatter(partner.link?.clicks, { full: true }),
],
[
"Leads",
!partner.link
? "-"
: nFormatter(partner.link?.leads, { full: true }),
],
[
"Sales",
!partner.link
? "-"
: nFormatter(partner.link?.sales, { full: true }),
],
[
"Revenue",
!partner.link
Expand All @@ -142,7 +151,6 @@ function PartnerDetailsSheetContent({
maximumFractionDigits: 2,
}),
],
["Sales", !partner.link ? "-" : partner.link?.sales],
[
"Earnings",
currencyFormatter(earnings, {
Expand Down
3 changes: 3 additions & 0 deletions apps/web/ui/partners/payout-details-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ function PayoutDetailsSheetContent({
error: error ? "Failed to load sales" : undefined,
} as any);

const { queryParams } = useRouterStuff();

const { executeAsync, isExecuting } = useAction(createPartnerPayoutAction, {
onSuccess: async () => {
await mutate(
Expand All @@ -170,6 +172,7 @@ function PayoutDetailsSheetContent({
);
toast.success("Successfully confirmed payout!");
setIsOpen(false);
queryParams({ del: "payoutId" });
},
onError({ error }) {
toast.error(error.serverError);
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 982af85

Please sign in to comment.