diff --git a/apps/web/ui/domains/domain-card.tsx b/apps/web/ui/domains/domain-card.tsx
index f34e4f0926..6cdd1ab7a4 100644
--- a/apps/web/ui/domains/domain-card.tsx
+++ b/apps/web/ui/domains/domain-card.tsx
@@ -33,9 +33,8 @@ import { motion } from "framer-motion";
import { Archive, ChevronDown, FolderInput, QrCode } from "lucide-react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
-import { useRef, useState } from "react";
+import { useMemo, useRef, useState } from "react";
import { toast } from "sonner";
-import useSWR from "swr";
import useSWRImmutable from "swr/immutable";
import { useAddEditDomainModal } from "../modals/add-edit-domain-modal";
import { useArchiveDomainModal } from "../modals/archive-domain-modal";
@@ -63,34 +62,32 @@ export default function DomainCard({ props }: { props: DomainProps }) {
}>(
workspaceId &&
isVisible &&
+ !props.verified &&
`/api/domains/${domain}/verify?workspaceId=${workspaceId}`,
fetcher,
);
- const { data: linkProps } = useSWRImmutable
(
- workspaceId &&
- isVisible &&
- `/api/links/info?${new URLSearchParams({ workspaceId, domain, key: "_root" }).toString()}`,
- fetcher,
- );
-
- const { data: totalEvents } = useSWR<{ clicks: number }>(
- workspaceId &&
- isVisible &&
- linkProps &&
- `/api/analytics?event=clicks&workspaceId=${workspaceId}&domain=${domain}&key=_root&interval=all_unfiltered`,
- fetcher,
- {
- dedupingInterval: 15000,
- },
- );
+ const verificationData = useMemo(() => {
+ if (props.verified) {
+ return {
+ status: "Valid Configuration",
+ response: null,
+ } as {
+ status: DomainVerificationStatusProps;
+ response: any;
+ };
+ }
+ return data;
+ }, [props.verified, data]);
const [showDetails, setShowDetails] = useState(false);
const [groupHover, setGroupHover] = useState(false);
const isInvalid =
- data &&
- !["Valid Configuration", "Pending Verification"].includes(data.status);
+ verificationData &&
+ !["Valid Configuration", "Pending Verification"].includes(
+ verificationData.status,
+ );
const searchParams = useSearchParams();
const tab = searchParams.get("tab") || "active";
@@ -125,40 +122,34 @@ export default function DomainCard({ props }: { props: DomainProps }) {
{/* Clicks */}
- {totalEvents ? (
-
-
-
-
- {nFormatter(totalEvents?.clicks)}
-
- clicks
-
-
-
-
- ) : (
-
- )}
+
+
+
+
+ {nFormatter(props.link?.clicks || 0)}
+ clicks
+
+
+
{/* Status */}
- {data ? (
+ {verificationData ? (
setShowDetails((s) => !s)
}
>
- {data.status === "Valid Configuration"
+ {verificationData.status === "Valid Configuration"
? "Active"
- : data.status === "Pending Verification"
+ : verificationData.status === "Pending Verification"
? "Pending"
: isDubProvisioned
? "Provisioning"
@@ -195,7 +186,7 @@ export default function DomainCard({ props }: { props: DomainProps }) {
)}
/>
{/* Error indicator */}
- {data && isInvalid && (
+ {verificationData && isInvalid && (
@@ -222,7 +213,7 @@ export default function DomainCard({ props }: { props: DomainProps }) {
)}
@@ -233,8 +224,8 @@ export default function DomainCard({ props }: { props: DomainProps }) {
animate={{ height: showDetails ? "auto" : 0 }}
className="overflow-hidden"
>
- {data ? (
- data.status === "Valid Configuration" ? (
+ {verificationData ? (
+ verificationData.status === "Valid Configuration" ? (
@@ -250,7 +241,7 @@ export default function DomainCard({ props }: { props: DomainProps }) {
) : (
-
+
)
) : (
diff --git a/apps/web/ui/layout/toolbar/onboarding/onboarding-button.tsx b/apps/web/ui/layout/toolbar/onboarding/onboarding-button.tsx
index 2511fa2e6e..82d4807b71 100644
--- a/apps/web/ui/layout/toolbar/onboarding/onboarding-button.tsx
+++ b/apps/web/ui/layout/toolbar/onboarding/onboarding-button.tsx
@@ -30,7 +30,9 @@ function OnboardingButtonInner({
}) {
const { slug } = useParams() as { slug: string };
- const { data: domainsCount, loading: domainsLoading } = useDomainsCount();
+ const { data: domainsCount, loading: domainsLoading } = useDomainsCount({
+ ignoreParams: true,
+ });
const { data: linksCount, loading: linksLoading } = useLinksCount({
ignoreParams: true,
});
diff --git a/apps/web/ui/links/link-title-column.tsx b/apps/web/ui/links/link-title-column.tsx
index 7d1aa5d354..3e6cc2d30b 100644
--- a/apps/web/ui/links/link-title-column.tsx
+++ b/apps/web/ui/links/link-title-column.tsx
@@ -10,6 +10,7 @@ import {
Switch,
Tooltip,
TooltipContent,
+ useInViewport,
} from "@dub/ui";
import {
Apple,
@@ -152,29 +153,38 @@ function UnverifiedTooltip({
}: PropsWithChildren<{ domain: string; _key: string }>) {
const { id: workspaceId, slug } = useWorkspace();
+ const ref = useRef(null);
+ const isVisible = useInViewport(ref);
+
const { data: { verified } = {} } = useSWR(
workspaceId &&
+ isVisible &&
!isDubDomain(domain) &&
`/api/domains/${domain}?workspaceId=${workspaceId}`,
fetcher,
+ { refreshInterval: 60000 },
);
- return !isDubDomain(domain) && verified === false ? (
-
- }
- >
-
- {linkConstructor({ domain, key: _key, pretty: true })}
-
-
- ) : (
- children
+ return (
+
+ {!isDubDomain(domain) && verified === false ? (
+
+ }
+ >
+
+ {linkConstructor({ domain, key: _key, pretty: true })}
+
+
+ ) : (
+ children
+ )}
+
);
}
diff --git a/apps/web/ui/links/use-available-domains.ts b/apps/web/ui/links/use-available-domains.ts
index 2100a31833..b1c2ab8e4a 100644
--- a/apps/web/ui/links/use-available-domains.ts
+++ b/apps/web/ui/links/use-available-domains.ts
@@ -22,9 +22,7 @@ export function useAvailableDomains(
allDomains,
allWorkspaceDomains,
loading,
- } = useDomains({
- query: { search: options.search },
- });
+ } = useDomains(options.search ? { opts: { search: options.search } } : {});
const domains = useMemo(() => {
if (options.onboarding) {
diff --git a/apps/web/ui/modals/delete-workspace-modal.tsx b/apps/web/ui/modals/delete-workspace-modal.tsx
index d575d5403f..fbee8ae389 100644
--- a/apps/web/ui/modals/delete-workspace-modal.tsx
+++ b/apps/web/ui/modals/delete-workspace-modal.tsx
@@ -1,6 +1,7 @@
import useWorkspace from "@/lib/swr/use-workspace";
import { Button, Logo, Modal, useMediaQuery } from "@dub/ui";
import { cn } from "@dub/utils";
+import { useSession } from "next-auth/react";
import { useParams, useRouter } from "next/navigation";
import {
Dispatch,
@@ -19,6 +20,7 @@ function DeleteWorkspaceModal({
showDeleteWorkspaceModal: boolean;
setShowDeleteWorkspaceModal: Dispatch>;
}) {
+ const { update } = useSession();
const router = useRouter();
const { slug } = useParams() as { slug: string };
const { id, isOwner } = useWorkspace();
@@ -35,7 +37,7 @@ function DeleteWorkspaceModal({
},
}).then(async (res) => {
if (res.ok) {
- await mutate("/api/workspaces");
+ await Promise.all([mutate("/api/workspaces"), update()]);
router.push("/");
resolve(null);
} else {
diff --git a/apps/web/ui/modals/link-builder/index.tsx b/apps/web/ui/modals/link-builder/index.tsx
index 92151d1a0d..dd4d2985af 100644
--- a/apps/web/ui/modals/link-builder/index.tsx
+++ b/apps/web/ui/modals/link-builder/index.tsx
@@ -60,6 +60,7 @@ import { TagSelect } from "./tag-select";
import { useTargetingModal } from "./targeting-modal";
import { useMetatags } from "./use-metatags";
import { useUTMModal } from "./utm-modal";
+import { WebhookSelect } from "./webhook-select";
export const LinkModalContext = createContext<{
workspaceId?: string;
@@ -111,6 +112,7 @@ function LinkBuilderInner({
plan,
nextPlan,
logo,
+ flags,
conversionEnabled,
} = useWorkspace();
@@ -473,6 +475,7 @@ function LinkBuilderInner({
+ {flags?.webhooks && }
{homepageDemo ? (
@@ -569,7 +572,6 @@ export function useLinkBuilder({
duplicateProps?: LinkWithTagsProps;
homepageDemo?: boolean;
} = {}) {
- const { flags } = useWorkspace();
const [showLinkBuilder, setShowLinkBuilder] = useState(false);
const LinkBuilderCallback = useCallback(() => {
diff --git a/apps/web/ui/modals/link-builder/webhook-select.tsx b/apps/web/ui/modals/link-builder/webhook-select.tsx
new file mode 100644
index 0000000000..0e07ea0332
--- /dev/null
+++ b/apps/web/ui/modals/link-builder/webhook-select.tsx
@@ -0,0 +1,103 @@
+import useWebhooks from "@/lib/swr/use-webhooks";
+import useWorkspace from "@/lib/swr/use-workspace";
+import { Button, Combobox, Globe, useKeyboardShortcut, Webhook } from "@dub/ui";
+import { cn } from "@dub/utils";
+import { useRouter } from "next/navigation";
+import { useMemo, useState } from "react";
+import { useFormContext } from "react-hook-form";
+import { LinkFormData } from ".";
+
+export function WebhookSelect() {
+ const [isOpen, setIsOpen] = useState(false);
+ const { watch, setValue } = useFormContext