11"use client" ;
22
33import { 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" ;
615import { usePlausible } from "next-plausible" ;
716import Image from "next/image" ;
817import { 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+ }
0 commit comments