Skip to content

Commit a5fa025

Browse files
.dub.link changes (dubinc#3871)
Co-authored-by: Steven Tey <stevensteel97@gmail.com>
1 parent 9b487f5 commit a5fa025

4 files changed

Lines changed: 518 additions & 113 deletions

File tree

apps/web/app/api/domains/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ export const POST = withWorkspace(
207207
slug: slug,
208208
projectId: workspace.id,
209209
primary: totalDomains === 0,
210+
...(slug.endsWith(".dub.link") && {
211+
verified: true,
212+
}),
210213
...(placeholder && { placeholder }),
211214
expiredUrl,
212215
notFoundUrl,
Lines changed: 161 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
"use client";
22

33
import { OnboardingStep } from "@/lib/onboarding/types";
4-
import { BoltFill, Button, Crown, Icon } from "@dub/ui";
5-
import { capitalize } from "@dub/utils";
4+
import {
5+
BoltFill,
6+
Button,
7+
CircleCheckFill,
8+
Crown,
9+
CursorRays,
10+
Flask,
11+
Globe,
12+
type Icon,
13+
} from "@dub/ui";
14+
import { capitalize, cn } from "@dub/utils";
615
import { usePlausible } from "next-plausible";
716
import Image from "next/image";
817
import { useSearchParams } from "next/navigation";
@@ -19,54 +28,88 @@ export function DefaultDomainSelector() {
1928
return (
2029
<>
2130
<div className="animate-fade-in mx-auto grid w-full gap-4 sm:max-w-[600px] sm:grid-cols-2">
31+
<DomainOption
32+
step="domain/custom"
33+
icon="https://assets.dub.co/icons/domain-sign.webp"
34+
bannerIcon={CircleCheckFill}
35+
bannerText="Recommended setup"
36+
bannerVariant="recommended"
37+
title="Connect a custom domain"
38+
description={
39+
product === "links"
40+
? "Already have a domain? Connect it to your Dub workspace."
41+
: undefined
42+
}
43+
features={
44+
product === "partners"
45+
? [
46+
{
47+
icon: CursorRays,
48+
text: (
49+
<>
50+
<a
51+
href="https://dub.co/blog/custom-domains"
52+
target="_blank"
53+
className="underline decoration-dotted underline-offset-2 transition-colors hover:text-neutral-700"
54+
>
55+
Higher click-through rates
56+
</a>
57+
</>
58+
),
59+
},
60+
{
61+
icon: Globe,
62+
text: (
63+
<>
64+
Requires a{" "}
65+
<a
66+
href="https://dub.co/help/article/choosing-a-custom-domain"
67+
target="_blank"
68+
className="underline decoration-dotted underline-offset-2 transition-colors hover:text-neutral-700"
69+
>
70+
dedicated domain
71+
</a>
72+
</>
73+
),
74+
},
75+
]
76+
: undefined
77+
}
78+
cta="Connect domain"
79+
/>
2280
{product === "partners" && (
2381
<DomainOption
2482
step="domain/subdomain"
2583
icon="https://assets.dub.co/icons/gift.webp"
84+
bannerIcon={BoltFill}
85+
bannerText="Instant setup"
2686
title={
2787
<>
28-
Free{" "}
29-
<span className="rounded border border-neutral-800/10 bg-neutral-100 px-1 py-0.5 font-mono text-xs">
30-
.dub.link
31-
</span>{" "}
32-
subdomain
33-
</>
34-
}
35-
description={
36-
<>
37-
Get a free custom domain like{" "}
38-
<span className="font-mono font-semibold text-neutral-900">
39-
{workspaceSlug && workspaceSlug.length < 8
40-
? workspaceSlug
41-
: "company"}
42-
.dub.link
43-
</span>{" "}
44-
for your links.
88+
Use <DomainChip>.dub.link</DomainChip> subdomain
4589
</>
4690
}
47-
cta="Claim .dub.link subdomain"
48-
bannerIcon={BoltFill}
49-
bannerText="Instant setup"
91+
features={[
92+
{
93+
icon: Flask,
94+
text: "Best for testing purposes",
95+
},
96+
{
97+
icon: BoltFill,
98+
text: "Instant subdomain setup",
99+
},
100+
]}
101+
cta="Use .dub.link subdomain"
50102
/>
51103
)}
52-
<DomainOption
53-
step="domain/custom"
54-
icon="https://assets.dub.co/icons/domain-sign.webp"
55-
title="Connect a custom domain"
56-
description="Already have a domain? Connect it to your Dub workspace."
57-
cta="Connect domain"
58-
/>
59104
{product === "links" && (
60105
<DomainOption
61106
step="domain/register"
62107
icon="https://assets.dub.co/icons/gift.webp"
108+
bannerIcon={Crown}
109+
bannerText="Paid plan required"
63110
title={
64111
<>
65-
Claim a free{" "}
66-
<span className="rounded border border-neutral-800/10 bg-neutral-100 px-1 py-0.5 font-mono text-xs">
67-
.link
68-
</span>{" "}
69-
domain
112+
Claim a free <DomainChip>.link</DomainChip> domain
70113
</>
71114
}
72115
description={
@@ -82,8 +125,6 @@ export function DefaultDomainSelector() {
82125
</>
83126
}
84127
cta="Claim .link domain"
85-
bannerIcon={Crown}
86-
bannerText="Paid plan required"
87128
/>
88129
)}
89130
</div>
@@ -101,60 +142,107 @@ function DomainOption({
101142
icon,
102143
title,
103144
description,
145+
features,
104146
cta,
105147
bannerIcon: BannerIcon,
106148
bannerText,
149+
bannerVariant = "default",
107150
}: {
108151
step: OnboardingStep;
109152
icon: string;
110153
title: ReactNode;
111-
description: ReactNode;
154+
description?: ReactNode;
155+
features?: {
156+
icon: Icon;
157+
text: ReactNode;
158+
}[];
112159
cta: string;
113160
bannerIcon?: Icon;
114161
bannerText?: string;
162+
bannerVariant?: "default" | "recommended";
115163
}) {
116164
const plausible = usePlausible();
117165
const { continueTo, isLoading, isSuccessful } = useOnboardingProgress();
118166
return (
119-
<div className="relative flex h-full flex-col items-center gap-6 rounded-xl border border-neutral-300 p-6 pt-12 transition-all">
120-
{BannerIcon && bannerText && (
121-
<div className="absolute inset-x-2 top-2 flex items-center justify-center gap-2 rounded-md border border-neutral-200 bg-neutral-100 px-2.5 py-1 text-xs font-medium text-neutral-600">
122-
<BannerIcon className="size-3" />
123-
{bannerText}
167+
<div className="relative flex h-full flex-col items-center rounded-xl border border-neutral-200 bg-white p-3 transition-all">
168+
<div className="relative flex h-52 w-full items-center justify-center rounded-xl bg-neutral-50">
169+
{BannerIcon && bannerText && (
170+
<div
171+
className={cn(
172+
"absolute inset-x-2 top-2 flex h-6 items-center justify-center gap-2 rounded-lg border text-xs font-semibold",
173+
bannerVariant === "recommended"
174+
? "border-green-200 bg-green-100 text-green-700"
175+
: "border-neutral-200 bg-white text-neutral-700",
176+
)}
177+
>
178+
<BannerIcon
179+
className={cn(
180+
"size-3.5",
181+
bannerVariant === "recommended"
182+
? "text-green-700"
183+
: "text-neutral-900",
184+
)}
185+
/>
186+
{bannerText}
187+
</div>
188+
)}
189+
<div className="relative mt-8 size-32">
190+
<Image
191+
src={icon}
192+
alt=""
193+
fill
194+
className="object-contain"
195+
fetchPriority="high"
196+
/>
124197
</div>
125-
)}
126-
<div className="relative size-36">
127-
<Image
128-
src={icon}
129-
alt=""
130-
fill
131-
className="object-contain"
132-
fetchPriority="high"
133-
/>
134-
</div>
135-
<div className="space-y-2 text-center">
136-
<span className="text-base font-semibold text-neutral-900">
137-
{title}
138-
</span>
139-
<p className="text-balance text-sm text-neutral-500">{description}</p>
140198
</div>
141-
<div className="flex w-full grow flex-col justify-end gap-2">
142-
<Button
143-
type="button"
144-
variant="primary"
145-
className="rounded-lg"
146-
onClick={() => {
147-
plausible("Selected Domain", {
148-
props: {
149-
domainType: capitalize(step.replace("domain/", "")),
150-
},
151-
});
152-
continueTo(step);
153-
}}
154-
loading={isLoading || isSuccessful}
155-
text={cta}
156-
/>
199+
<div className="flex w-full flex-col p-2">
200+
<div className="mt-2 space-y-3 text-center">
201+
<span className="text-base font-semibold text-neutral-900">
202+
{title}
203+
</span>
204+
{description && (
205+
<p className="text-balance text-sm leading-snug text-neutral-500">
206+
{description}
207+
</p>
208+
)}
209+
{features && (
210+
<div className="mx-auto inline-flex flex-col items-start space-y-2 text-left text-sm text-neutral-500">
211+
{features.map(({ icon: FeatureIcon, text }, idx) => (
212+
<div key={idx} className="flex items-center gap-2">
213+
<FeatureIcon className="size-4 shrink-0 text-neutral-500" />
214+
<div>{text}</div>
215+
</div>
216+
))}
217+
</div>
218+
)}
219+
</div>
220+
<div className="mt-6 flex w-full grow flex-col justify-end">
221+
<Button
222+
type="button"
223+
variant="primary"
224+
className="h-10 rounded-lg text-sm"
225+
onClick={() => {
226+
plausible("Selected Domain", {
227+
props: {
228+
domainType: capitalize(step.replace("domain/", "")),
229+
},
230+
});
231+
continueTo(step);
232+
}}
233+
loading={isLoading || isSuccessful}
234+
text={cta}
235+
/>
236+
</div>
157237
</div>
158238
</div>
159239
);
160240
}
241+
242+
function DomainChip({ children }: { children: ReactNode }) {
243+
return (
244+
<span className="rounded border border-neutral-800/10 bg-neutral-100 px-1 py-0.5 font-mono text-xs">
245+
{children}
246+
</span>
247+
);
248+
}

apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/domain/page.tsx

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,44 @@
11
import { StepPage } from "../step-page";
22
import { DefaultDomainSelector } from "./default-domain-selector";
33

4-
export default function Domain() {
4+
export default async function Domain({
5+
searchParams,
6+
}: {
7+
searchParams: Promise<{ product?: string }>;
8+
}) {
9+
const { product } = await searchParams;
10+
const isPartners = product !== "links";
11+
512
return (
613
<StepPage
714
title="Add a custom domain"
815
description={
9-
<>
10-
Make your links stand out and{" "}
11-
<a
12-
href="https://dub.co/blog/custom-domains"
13-
target="_blank"
14-
className="cursor-help font-medium underline decoration-dotted underline-offset-2 transition-colors hover:text-neutral-700"
15-
>
16-
boost click-through rates by 30%
17-
</a>
18-
</>
16+
isPartners ? (
17+
<>
18+
A{" "}
19+
<a
20+
href="https://dub.co/help/article/choosing-a-custom-domain"
21+
target="_blank"
22+
className="cursor-help font-medium underline decoration-dotted underline-offset-2 transition-colors hover:text-neutral-700"
23+
>
24+
dedicated domain
25+
</a>{" "}
26+
is required for Dub Partner programs, and can be changed at anytime.
27+
</>
28+
) : (
29+
<>
30+
Make your links stand out and{" "}
31+
<a
32+
href="https://dub.co/blog/custom-domains"
33+
target="_blank"
34+
className="cursor-help font-medium underline decoration-dotted underline-offset-2 transition-colors hover:text-neutral-700"
35+
>
36+
boost click-through rates by 30%
37+
</a>
38+
</>
39+
)
1940
}
20-
className="max-w-none"
41+
className="max-w-none [&>div:first-of-type]:max-w-[640px]"
2142
>
2243
<DefaultDomainSelector />
2344
</StepPage>

0 commit comments

Comments
 (0)