Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions apps/web/app/(ee)/api/cron/domains/transfer/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ export async function POST(req: Request) {
},
data: {
projectId: newWorkspaceId,
// reset all stats and folder
clicks: 0,
leads: 0,
sales: 0,
saleAmount: 0,
conversions: 0,
lastClicked: null,
lastLeadAt: null,
lastConversionAt: null,
folderId: null,
},
}),
Expand Down
55 changes: 11 additions & 44 deletions apps/web/app/api/domains/[domain]/transfer/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getAnalytics } from "@/lib/analytics/get-analytics";
import { getDomainOrThrow } from "@/lib/api/domains/get-domain-or-throw";
import { transformDomain } from "@/lib/api/domains/transform-domain";
import { DubApiError } from "@/lib/api/errors";
Expand Down Expand Up @@ -31,7 +30,7 @@ export const POST = withWorkspace(
if (newWorkspaceId === workspace.id) {
throw new DubApiError({
code: "bad_request",
message: "Please select another workspace to transfer the domain to.",
message: "You cannot transfer a domain to the same workspace.",
});
}

Expand Down Expand Up @@ -103,49 +102,17 @@ export const POST = withWorkspace(
});
}

const { clicks: totalLinkClicks } = await getAnalytics({
domain,
event: "clicks",
groupBy: "count",
workspaceId: workspace.id,
interval: "30d",
});

// Update the domain to use the new workspace
const [domainResponse] = await Promise.all([
prisma.domain.update({
where: { slug: domain, projectId: workspace.id },
data: {
projectId: newWorkspaceId,
primary: newWorkspace.domains.length === 0,
},
include: {
registeredDomain: true,
},
}),
prisma.project.update({
where: { id: workspace.id },
data: {
usage: {
set: Math.max(workspace.usage - totalLinkClicks, 0),
},
linksUsage: {
set: Math.max(workspace.linksUsage - linksCount, 0),
},
},
}),
prisma.project.update({
where: { id: newWorkspaceId },
data: {
usage: {
increment: totalLinkClicks,
},
linksUsage: {
increment: linksCount,
},
},
}),
]);
const domainResponse = await prisma.domain.update({
where: { slug: domain, projectId: workspace.id },
data: {
projectId: newWorkspaceId,
primary: newWorkspace.domains.length === 0,
},
include: {
registeredDomain: true,
},
});

await qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/domains/transfer`,
Expand Down
50 changes: 25 additions & 25 deletions apps/web/app/api/links/[linkId]/transfer/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getAnalytics } from "@/lib/analytics/get-analytics";
import { DubApiError } from "@/lib/api/errors";
import { linkCache } from "@/lib/api/links/cache";
import { getLinkOrThrow } from "@/lib/api/links/get-link-or-throw";
Expand All @@ -7,6 +6,7 @@ import { withWorkspace } from "@/lib/auth";
import { verifyFolderAccess } from "@/lib/folder/permissions";
import { recordLink } from "@/lib/tinybird";
import { prisma } from "@dub/prisma";
import { isDubDomain } from "@dub/utils";
import { waitUntil } from "@vercel/functions";
import { NextResponse } from "next/server";
import { z } from "zod";
Expand All @@ -26,6 +26,14 @@ export const POST = withWorkspace(
linkId: params.linkId,
});

if (!isDubDomain(link.domain)) {
throw new DubApiError({
code: "bad_request",
message:
"You can only transfer Dub default domain links to another workspace.",
});
}

if (link.folderId) {
await verifyFolderAccess({
workspace,
Expand All @@ -37,6 +45,13 @@ export const POST = withWorkspace(

const { newWorkspaceId } = transferLinkBodySchema.parse(await req.json());

if (newWorkspaceId === workspace.id) {
throw new DubApiError({
code: "bad_request",
message: "You cannot transfer a link to the same workspace.",
});
}

const newWorkspace = await prisma.project.findUnique({
where: { id: newWorkspaceId },
select: {
Expand Down Expand Up @@ -67,24 +82,24 @@ export const POST = withWorkspace(
});
}

const { clicks: linkClicks } = await getAnalytics({
event: "clicks",
groupBy: "count",
linkId: link.id,
interval: "30d",
});

const updatedLink = await prisma.link.update({
where: {
id: link.id,
},
data: {
projectId: newWorkspaceId,
// remove tags when transferring link
// reset all stats, tags, and folder when transferring link
clicks: 0,
leads: 0,
sales: 0,
saleAmount: 0,
conversions: 0,
lastClicked: null,
lastLeadAt: null,
lastConversionAt: null,
tags: {
deleteMany: {},
},
// remove folder when transferring link
folderId: null,
},
});
Expand All @@ -95,21 +110,6 @@ export const POST = withWorkspace(

recordLink(updatedLink),

// increment new workspace usage
prisma.project.update({
where: {
id: newWorkspaceId,
},
data: {
usage: {
increment: linkClicks,
},
linksUsage: {
increment: 1,
},
},
}),

// Remove the webhooks associated with the link
prisma.linkWebhook.deleteMany({
where: {
Expand Down
61 changes: 35 additions & 26 deletions apps/web/ui/modals/send-test-webhook-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ import { sendTestWebhookEvent } from "@/lib/actions/send-test-webhook";
import useWorkspace from "@/lib/swr/use-workspace";
import { WebhookProps, WebhookTrigger } from "@/lib/types";
import { WEBHOOK_TRIGGER_DESCRIPTIONS } from "@/lib/webhook/constants";
import {
Button,
InputSelect,
InputSelectItemProps,
Logo,
Modal,
} from "@dub/ui";
import { Button, Combobox, ComboboxOption, Modal } from "@dub/ui";
import { useAction } from "next-safe-action/hooks";
import {
Dispatch,
Expand All @@ -29,8 +23,9 @@ function SendTestWebhookModal({
webhook: WebhookProps | undefined;
}) {
const workspace = useWorkspace();
const [selectedTrigger, setSelectedTrigger] =
useState<InputSelectItemProps | null>(null);
const [selectedTrigger, setSelectedTrigger] = useState<ComboboxOption | null>(
null,
);

const { execute, isPending } = useAction(sendTestWebhookEvent, {
onSuccess: () => {
Expand All @@ -44,8 +39,8 @@ function SendTestWebhookModal({

const triggers = Object.entries(WEBHOOK_TRIGGER_DESCRIPTIONS).map(
([key, value]) => ({
id: key,
value: value,
value: key,
label: value,
}),
);

Expand All @@ -54,12 +49,10 @@ function SendTestWebhookModal({
showModal={showSendTestWebhookModal}
setShowModal={setShowSendTestWebhookModal}
>
<div className="flex flex-col items-center justify-center space-y-3 border-b border-neutral-200 px-4 py-4 pt-8 text-center sm:px-16">
<Logo />
<h3 className="text-lg font-medium">Send test webhook event</h3>
<p className="text-center text-sm text-neutral-500">
Choose a webhook event to send to your receiver endpoint
</p>
<div className="space-y-2 border-b border-neutral-200 p-4 sm:p-6">
<h3 className="text-lg font-medium leading-none">
Send test webhook event
</h3>
</div>
<form
onSubmit={async (e) => {
Expand All @@ -72,23 +65,39 @@ function SendTestWebhookModal({
execute({
workspaceId: workspace.id!,
webhookId: webhook.id,
trigger: selectedTrigger?.id as WebhookTrigger,
trigger: selectedTrigger?.value as WebhookTrigger,
});
}}
>
<div className="flex flex-col space-y-28 bg-neutral-50 px-4 py-8 text-left sm:space-y-3 sm:rounded-b-2xl sm:px-16">
<InputSelect
items={triggers}
selectedItem={selectedTrigger}
setSelectedItem={setSelectedTrigger}
inputAttrs={{
placeholder: "Select a webhook event",
}}
<div className="bg-neutral-50 p-4 sm:p-6">
<p className="text-sm text-neutral-800">
Choose a webhook event to send to your receiver endpoint
</p>

<div className="mt-4">
<Combobox
options={triggers}
selected={selectedTrigger}
setSelected={setSelectedTrigger}
placeholder="Select a webhook event"
matchTriggerWidth
caret
/>
</div>
</div>

<div className="flex items-center justify-end gap-2 border-t border-neutral-200 bg-neutral-50 px-4 py-5 sm:px-6">
<Button
onClick={() => setShowSendTestWebhookModal(false)}
variant="secondary"
text="Cancel"
className="h-8 w-fit px-3"
/>
<Button
disabled={!selectedTrigger}
text="Send test webhook"
loading={isPending}
className="h-8 w-fit px-3"
/>
</div>
</form>
Expand Down
Loading