Skip to content

Commit

Permalink
Merge branch 'main' into deprecate-post-customers
Browse files Browse the repository at this point in the history
  • Loading branch information
steven-tey authored Jan 19, 2025
2 parents 9331d2f + 1643ed8 commit 728a1bd
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 106 deletions.
38 changes: 26 additions & 12 deletions apps/web/ui/links/short-link-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ export const ShortLinkInput = forwardRef<HTMLInputElement, ShortLinkInputProps>(
setGeneratingRandomKey(false);
};

const [isFocused, setIsFocused] = useState(false);

useEffect(() => {
// generate a random key if:
// - there is a domain
// - there is no key
// - the input is not focused
if (domain && !key && !isFocused) {
generateRandomKey();
}
}, [domain, key, isFocused]);

const runKeyChecks = async (value: string) => {
const res = await fetch(
`/api/links/exists?domain=${domain}&key=${value}&workspaceId=${workspaceId}`,
Expand All @@ -116,6 +128,18 @@ export const ShortLinkInput = forwardRef<HTMLInputElement, ShortLinkInputProps>(
}
};

const [debouncedKey] = useDebounce(key, 500);

useEffect(() => {
// only run key checks if:
// - there is a key
// - there is a workspace
// - it's not an existing link
if (debouncedKey && workspaceId && !existingLink) {
runKeyChecks(debouncedKey);
}
}, [debouncedKey, workspaceId, existingLink]);

const [generatedKeys, setGeneratedKeys] = useState<string[]>(
existingLink && key ? [key] : [],
);
Expand All @@ -141,7 +165,6 @@ export const ShortLinkInput = forwardRef<HTMLInputElement, ShortLinkInputProps>(
onFinish: (_, completion) => {
setGeneratedKeys((prev) => [...prev, completion]);
mutateWorkspace();
runKeyChecks(completion);
posthog.capture("ai_key_generated", {
key: completion,
url: data.url,
Expand Down Expand Up @@ -262,17 +285,8 @@ export const ShortLinkInput = forwardRef<HTMLInputElement, ShortLinkInputProps>(
"Only letters, numbers, '-', '_', '/', and emojis are allowed.",
);
}}
onBlur={(e) => {
// if the key is changed, check if key exists
if (e.target.value) {
// no need to check if the link key didn't change (for editing existing links)
if (existingLinkProps?.key === e.target.value) {
return;
} else {
runKeyChecks(e.target.value);
}
}
}}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
disabled={lockKey}
autoComplete="off"
autoCapitalize="none"
Expand Down
59 changes: 27 additions & 32 deletions apps/web/ui/modals/link-builder/conversion-tracking-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import {
InfoTooltip,
SimpleTooltipContent,
Switch,
Tooltip,
TooltipContent,
useKeyboardShortcut,
} from "@dub/ui";
import { PropsWithChildren } from "react";
import { useFormContext } from "react-hook-form";
import { LinkFormData } from ".";

Expand All @@ -16,7 +15,7 @@ const isNew =
new Date().getTime() - new Date("2025-01-13").getTime() < 30 * 86_400_000;

export function ConversionTrackingToggle() {
const { plan } = useWorkspace();
const { slug, plan } = useWorkspace();
const { watch, setValue } = useFormContext<LinkFormData>();

const conversionsEnabled = !!plan && plan !== "free" && plan !== "pro";
Expand Down Expand Up @@ -50,35 +49,31 @@ export function ConversionTrackingToggle() {
/>
</span>
</div>
<DisabledTooltip disabled={!conversionsEnabled}>
<Switch
checked={trackConversion}
fn={(checked) =>
setValue("trackConversion", checked, {
shouldDirty: true,
})
}
disabled={!conversionsEnabled}
thumbIcon={
conversionsEnabled ? undefined : (
<CrownSmall className="size-full text-neutral-500" />
)
}
/>
</DisabledTooltip>
<Switch
checked={trackConversion}
fn={(checked) =>
setValue("trackConversion", checked, {
shouldDirty: true,
})
}
disabledTooltip={
conversionsEnabled ? undefined : (
<TooltipContent
title="Conversion tracking is only available on Business plans and above."
cta="Upgrade to Business"
href={
slug ? `/${slug}/upgrade?exit=close` : "https://dub.co/pricing"
}
target="_blank"
/>
)
}
thumbIcon={
conversionsEnabled ? undefined : (
<CrownSmall className="size-full text-neutral-500" />
)
}
/>
</label>
);
}

function DisabledTooltip({
children,
disabled,
}: PropsWithChildren<{ disabled: boolean }>) {
return disabled ? (
<Tooltip content="Requires a Dub Business Plan to enable">
<div>{children}</div>
</Tooltip>
) : (
children
);
}
82 changes: 63 additions & 19 deletions apps/web/ui/modals/link-builder/link-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { ProBadgeTooltip } from "@/ui/shared/pro-badge-tooltip";
import useWorkspace from "@/lib/swr/use-workspace";
import {
Button,
FileUpload,
Icon,
InfoTooltip,
ShimmerDots,
SimpleTooltipContent,
Switch,
TooltipContent,
useKeyboardShortcut,
useMediaQuery,
} from "@dub/ui";
import {
CrownSmall,
Facebook,
GlobePointer,
LinkedIn,
Expand Down Expand Up @@ -67,6 +71,7 @@ const tabComponents: Record<Tab, ComponentType<OGPreviewProps>> = {
};

export function LinkPreview() {
const { slug, plan } = useWorkspace();
const { watch, setValue } = useFormContext<LinkFormData>();
const { proxy, title, description, image, url, password } = watch();

Expand All @@ -84,7 +89,7 @@ export function LinkPreview() {
const [selectedTab, setSelectedTab] = useState<Tab>("default");

const onImageChange = (image: string) => {
setValue("image", image);
setValue("image", image, { shouldDirty: true });
setValue("proxy", true);
};

Expand All @@ -95,25 +100,44 @@ export function LinkPreview() {
<OGModal />
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<h2 className="text-sm font-medium text-gray-700">Link Preview</h2>
<ProBadgeTooltip
<h2 className="text-sm font-medium text-gray-700">
Custom Link Preview
</h2>
<InfoTooltip
content={
<SimpleTooltipContent
title="Customize how your links look when shared on social media to improve click-through rates."
title="Customize how your links look when shared on social media to improve click-through rates. When enabled, the preview settings below will be shown publicly (instead of the URL's original metatags)."
cta="Learn more."
href="https://dub.co/help/article/custom-social-media-cards"
href="https://dub.co/help/article/custom-link-previews"
/>
}
/>
</div>
<Button
type="button"
variant="outline"
icon={
<Pen2 className={cn("mx-px size-4", proxy && "text-blue-500")} />

<Switch
checked={proxy}
fn={(checked) => setValue("proxy", checked, { shouldDirty: true })}
disabledTooltip={
!url ? (
"Enter a URL to enable custom link previews."
) : !plan || plan === "free" ? (
<TooltipContent
title="Custom Link Previews are only available on the Pro plan and above."
cta="Upgrade to Pro"
href={
slug
? `/${slug}/upgrade?exit=close`
: "https://dub.co/pricing"
}
target="_blank"
/>
) : undefined
}
thumbIcon={
!plan || plan === "free" ? (
<CrownSmall className="size-full text-neutral-500" />
) : undefined
}
className="h-7 w-fit px-1"
onClick={() => setShowOGModal(true)}
/>
</div>
<div className="mt-2.5 grid grid-cols-4 gap-2">
Expand All @@ -138,7 +162,14 @@ export function LinkPreview() {
);
})}
</div>
<div className="mt-2">
<div className="relative mt-2">
<Button
type="button"
variant="secondary"
icon={<Pen2 className="mx-px size-4" />}
className="absolute right-2 top-2 z-10 h-8 w-fit px-1.5"
onClick={() => setShowOGModal(true)}
/>
<OGPreview
title={title}
description={description}
Expand Down Expand Up @@ -268,6 +299,7 @@ export const ImagePreview = ({
};

function DefaultOGPreview({ title, description, children }: OGPreviewProps) {
const { plan } = useWorkspace();
const { setValue } = useFormContext<LinkFormData>();

return (
Expand All @@ -281,7 +313,9 @@ function DefaultOGPreview({ title, description, children }: OGPreviewProps) {
maxRows={2}
onChange={(e) => {
setValue("title", e.currentTarget.value, { shouldDirty: true });
setValue("proxy", true, { shouldDirty: true });
if (plan && plan !== "free") {
setValue("proxy", true, { shouldDirty: true });
}
}}
/>
<ReactTextareaAutosize
Expand All @@ -292,7 +326,9 @@ function DefaultOGPreview({ title, description, children }: OGPreviewProps) {
setValue("description", e.currentTarget.value, {
shouldDirty: true,
});
setValue("proxy", true, { shouldDirty: true });
if (plan && plan !== "free") {
setValue("proxy", true, { shouldDirty: true });
}
}}
/>
</div>
Expand All @@ -305,6 +341,7 @@ function FacebookOGPreview({
hostname,
children,
}: OGPreviewProps) {
const { plan } = useWorkspace();
const { setValue } = useFormContext<LinkFormData>();

return (
Expand All @@ -323,7 +360,9 @@ function FacebookOGPreview({
setValue("title", e.currentTarget.value, {
shouldDirty: true,
});
setValue("proxy", true, { shouldDirty: true });
if (plan && plan !== "free") {
setValue("proxy", true, { shouldDirty: true });
}
}}
/>
<ReactTextareaAutosize
Expand All @@ -334,7 +373,9 @@ function FacebookOGPreview({
setValue("description", e.currentTarget.value, {
shouldDirty: true,
});
setValue("proxy", true, { shouldDirty: true });
if (plan && plan !== "free") {
setValue("proxy", true, { shouldDirty: true });
}
}}
/>
</div>
Expand All @@ -345,6 +386,7 @@ function FacebookOGPreview({
}

function LinkedInOGPreview({ title, hostname, children }: OGPreviewProps) {
const { plan } = useWorkspace();
const { setValue } = useFormContext<LinkFormData>();

return (
Expand All @@ -364,7 +406,9 @@ function LinkedInOGPreview({ title, hostname, children }: OGPreviewProps) {
setValue("title", e.currentTarget.value, {
shouldDirty: true,
});
setValue("proxy", true, { shouldDirty: true });
if (plan && plan !== "free") {
setValue("proxy", true, { shouldDirty: true });
}
}}
/>
<p className="text-xs text-[#00000099]">{hostname || "domain.com"}</p>
Expand Down
Loading

0 comments on commit 728a1bd

Please sign in to comment.