@@ -21,6 +21,7 @@ import {
2121} from "@webstudio-is/icons" ;
2222import { CollapsibleDomainSection } from "./collapsible-domain-section" ;
2323import {
24+ Fragment ,
2425 startTransition ,
2526 useEffect ,
2627 useOptimistic ,
@@ -29,17 +30,18 @@ import {
2930 type ReactNode ,
3031} from "react" ;
3132import { Entri } from "./entri" ;
32- import { nativeClient } from "~/shared/trpc/trpc-client" ;
33+ import { nativeClient , trpcClient } from "~/shared/trpc/trpc-client" ;
3334import { useStore } from "@nanostores/react" ;
3435import { $publisherHost } from "~/shared/nano-states" ;
3536import { extractCname } from "./cname" ;
3637import { useEffectEvent } from "~/shared/hook-utils/effect-event" ;
37- import DomainCheckbox from "./domain-checkbox" ;
38+ import { DomainCheckbox } from "./domain-checkbox" ;
3839import { CopyToClipboard } from "~/builder/shared/copy-to-clipboard" ;
3940import { RelativeTime } from "~/builder/shared/relative-time" ;
4041
4142export type Domain = Project [ "domainsVirtual" ] [ number ] ;
42- type DomainStatus = Project [ "domainsVirtual" ] [ number ] [ "status" ] ;
43+
44+ type DomainStatus = Domain [ "status" ] ;
4345
4446const InputEllipsis = styled ( InputField , {
4547 "&>input" : {
@@ -164,6 +166,119 @@ const StatusIcon = (props: { projectDomain: Domain; isLoading: boolean }) => {
164166 ) ;
165167} ;
166168
169+ const DomainConfig = ( {
170+ projectDomain,
171+ onUpdateStatus,
172+ } : {
173+ projectDomain : Domain ;
174+ onUpdateStatus : ( ) => void ;
175+ } ) => {
176+ const { load : findDomainRegistrar , data : registrar } =
177+ trpcClient . domain . findDomainRegistrar . useQuery ( ) ;
178+ const cname = extractCname ( projectDomain . domain ) ;
179+ useEffect ( ( ) => {
180+ if ( cname === "@" ) {
181+ findDomainRegistrar ( { domain : projectDomain . domain } ) ;
182+ }
183+ } , [ projectDomain . domain , cname ] ) ;
184+ const publisherHost = useStore ( $publisherHost ) ;
185+
186+ const cnameRecord = {
187+ // use alias for domain root when supported to avoid conflicting
188+ // with MX, NS etc records
189+ type : cname === "@" && registrar ?. alias ? "ALIAS" : "CNAME" ,
190+ host : cname ,
191+ value : `${ projectDomain . cname } .customers.${ publisherHost } ` ,
192+ ttl : 300 ,
193+ } as const ;
194+
195+ const txtRecord = {
196+ type : "TXT" ,
197+ host : cname === "@" ? "_webstudio_is" : `_webstudio_is.${ cname } ` ,
198+ value : projectDomain . expectedTxtRecord ,
199+ ttl : 300 ,
200+ } as const ;
201+
202+ const dnsRecords = [ cnameRecord , txtRecord ] ;
203+
204+ return (
205+ < >
206+ < Text color = "subtle" >
207+ < strong > To verify your domain:</ strong >
208+ < br />
209+ Visit the admin console of your domain registrar (the website you
210+ purchased your domain from) and create one < strong > CNAME</ strong > record
211+ and one < strong > TXT</ strong > record with the values shown below:
212+ </ Text >
213+
214+ < Grid
215+ gap = { 2 }
216+ css = { { gridTemplateColumns : `${ theme . spacing [ 18 ] } 1fr 1fr` } }
217+ >
218+ < Text color = "subtle" variant = "titles" >
219+ TYPE
220+ </ Text >
221+ < Text color = "subtle" variant = "titles" >
222+ NAME
223+ </ Text >
224+ < Text color = "subtle" variant = "titles" >
225+ VALUE
226+ </ Text >
227+
228+ { dnsRecords . map ( ( record , index ) => (
229+ < Fragment key = { index } >
230+ < InputEllipsis readOnly value = { record . type } />
231+ < InputEllipsis
232+ readOnly
233+ value = { record . host }
234+ suffix = {
235+ < CopyToClipboard text = { record . host } >
236+ < NestedInputButton type = "button" >
237+ < CopyIcon />
238+ </ NestedInputButton >
239+ </ CopyToClipboard >
240+ }
241+ />
242+ < InputEllipsis
243+ readOnly
244+ value = { record . value }
245+ suffix = {
246+ < CopyToClipboard text = { record . value } >
247+ < NestedInputButton type = "button" >
248+ < CopyIcon />
249+ </ NestedInputButton >
250+ </ CopyToClipboard >
251+ }
252+ />
253+ </ Fragment >
254+ ) ) }
255+ </ Grid >
256+
257+ < Grid
258+ gap = { 2 }
259+ align = { "center" }
260+ css = { {
261+ gridTemplateColumns : `1fr auto 1fr` ,
262+ } }
263+ >
264+ < Separator css = { { alignSelf : "unset" } } />
265+ < Text color = "main" > OR</ Text >
266+ < Separator css = { { alignSelf : "unset" } } />
267+ </ Grid >
268+
269+ < Entri
270+ dnsRecords = { dnsRecords }
271+ domain = { projectDomain . domain }
272+ onClose = { ( ) => {
273+ // Sometimes Entri modal dialog hangs even if it's successful,
274+ // until they fix that, we'll just refresh the status here on every onClose event
275+ onUpdateStatus ( ) ;
276+ } }
277+ />
278+ </ >
279+ ) ;
280+ } ;
281+
167282const DomainItem = ( props : {
168283 initiallyOpen : boolean ;
169284 projectDomain : Domain ;
@@ -272,33 +387,8 @@ const DomainItem = (props: {
272387 } ) ;
273388 } , [ status , handleVerify , handleUpdateStatus , isStatusLoading ] ) ;
274389
275- const publisherHost = useStore ( $publisherHost ) ;
276- const cnameEntryName = extractCname ( props . projectDomain . domain ) ;
277- const cnameEntryValue = `${ props . projectDomain . cname } .customers.${ publisherHost } ` ;
278-
279- const txtEntryName =
280- cnameEntryName === "@"
281- ? "_webstudio_is"
282- : `_webstudio_is.${ cnameEntryName } ` ;
283-
284390 const domainStatus = getStatus ( props . projectDomain ) ;
285391
286- const cnameRecord = {
287- type : "CNAME" ,
288- host : cnameEntryName ,
289- value : cnameEntryValue ,
290- ttl : 300 ,
291- } as const ;
292-
293- const txtRecord = {
294- type : "TXT" ,
295- host : txtEntryName ,
296- value : props . projectDomain . expectedTxtRecord ,
297- ttl : 300 ,
298- } as const ;
299-
300- const dnsRecords = [ cnameRecord , txtRecord ] ;
301-
302392 const { isVerifiedActive, text } = getStatusText ( {
303393 projectDomain : props . projectDomain ,
304394 isLoading : false ,
@@ -413,98 +503,9 @@ const DomainItem = (props: {
413503 ) }
414504 </ Grid >
415505
416- < Text color = "subtle" >
417- < strong > To verify your domain:</ strong >
418- < br />
419- Visit the admin console of your domain registrar (the website you
420- purchased your domain from) and create one < strong > CNAME</ strong > { " " }
421- record and one < strong > TXT</ strong > record with the values shown
422- below:
423- </ Text >
424-
425- < Grid
426- gap = { 2 }
427- css = { {
428- gridTemplateColumns : `${ theme . spacing [ 18 ] } 1fr 1fr` ,
429- } }
430- >
431- < Text color = "subtle" variant = { "titles" } >
432- TYPE
433- </ Text >
434- < Text color = "subtle" variant = { "titles" } >
435- NAME
436- </ Text >
437- < Text color = "subtle" variant = { "titles" } >
438- VALUE
439- </ Text >
440-
441- < InputEllipsis readOnly value = "CNAME" />
442- < InputEllipsis
443- readOnly
444- value = { cnameRecord . host }
445- suffix = {
446- < CopyToClipboard text = { cnameRecord . host } >
447- < NestedInputButton type = "button" >
448- < CopyIcon />
449- </ NestedInputButton >
450- </ CopyToClipboard >
451- }
452- />
453- < InputEllipsis
454- readOnly
455- value = { cnameRecord . value }
456- suffix = {
457- < CopyToClipboard text = { cnameRecord . value } >
458- < NestedInputButton type = "button" >
459- < CopyIcon />
460- </ NestedInputButton >
461- </ CopyToClipboard >
462- }
463- />
464-
465- < InputEllipsis readOnly value = "TXT" />
466- < InputEllipsis
467- readOnly
468- value = { txtRecord . host }
469- suffix = {
470- < CopyToClipboard text = { txtRecord . host } >
471- < NestedInputButton type = "button" >
472- < CopyIcon />
473- </ NestedInputButton >
474- </ CopyToClipboard >
475- }
476- />
477- < InputEllipsis
478- readOnly
479- value = { txtRecord . value }
480- suffix = {
481- < CopyToClipboard text = { txtRecord . value } >
482- < NestedInputButton type = "button" >
483- < CopyIcon />
484- </ NestedInputButton >
485- </ CopyToClipboard >
486- }
487- />
488- </ Grid >
489-
490- < Grid
491- gap = { 2 }
492- align = { "center" }
493- css = { {
494- gridTemplateColumns : `1fr auto 1fr` ,
495- } }
496- >
497- < Separator css = { { alignSelf : "unset" } } />
498- < Text color = "main" > OR</ Text >
499- < Separator css = { { alignSelf : "unset" } } />
500- </ Grid >
501-
502- < Entri
503- dnsRecords = { dnsRecords }
504- domain = { props . projectDomain . domain }
505- onClose = { ( ) => {
506- // Sometimes Entri modal dialog hangs even if it's successful,
507- // until they fix that, we'll just refresh the status here on every onClose event
506+ < DomainConfig
507+ projectDomain = { props . projectDomain }
508+ onUpdateStatus = { ( ) => {
508509 if ( status === "UNVERIFIED" ) {
509510 startTransition ( async ( ) => {
510511 await handleVerify ( ) ;
0 commit comments