CustomDomainActivate.tsx136 lines · main
1import { useState } from 'react'
2import { toast } from 'sonner'
3import { Button } from 'ui'
4import { Admonition } from 'ui-patterns/admonition'
5import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
6
7import { DocsButton } from '@/components/ui/DocsButton'
8import Panel from '@/components/ui/Panel'
9import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
10import { useCheckCNAMERecordMutation } from '@/data/custom-domains/check-cname-mutation'
11import { useCustomDomainActivateMutation } from '@/data/custom-domains/custom-domains-activate-mutation'
12import { useCustomDomainDeleteMutation } from '@/data/custom-domains/custom-domains-delete-mutation'
13import type { CustomDomainResponse } from '@/data/custom-domains/custom-domains-query'
14import { DOCS_URL } from '@/lib/constants'
15
16export type CustomDomainActivateProps = {
17 projectRef?: string
18 customDomain: CustomDomainResponse
19}
20
21export const CustomDomainActivate = ({ projectRef, customDomain }: CustomDomainActivateProps) => {
22 const [isActivateConfirmModalVisible, setIsActivateConfirmModalVisible] = useState(false)
23
24 const { data: settings } = useProjectSettingsV2Query({ projectRef })
25 const { mutate: checkCNAMERecord, isPending: isCheckingRecord } = useCheckCNAMERecordMutation()
26 const { mutate: activateCustomDomain, isPending: isActivating } = useCustomDomainActivateMutation(
27 {
28 onSuccess: () => {
29 toast.success(`Successfully activated custom domain`)
30 setIsActivateConfirmModalVisible(false)
31 },
32 }
33 )
34 const { mutate: deleteCustomDomain, isPending: isDeleting } = useCustomDomainDeleteMutation({
35 onSuccess: () => {
36 toast.success(
37 'Custom domain setup cancelled successfully. It may take a few seconds before your custom domain is fully removed, so you may need to refresh your browser.'
38 )
39 },
40 })
41
42 const endpoint = settings?.app_config?.endpoint
43
44 const onActivateCustomDomain = async () => {
45 if (!projectRef) return console.error('Project ref is required')
46 checkCNAMERecord(
47 { domain: customDomain.hostname },
48 { onSuccess: () => activateCustomDomain({ projectRef }) }
49 )
50 }
51
52 const onCancelCustomDomain = async () => {
53 if (!projectRef) return console.error('Project ref is required')
54 deleteCustomDomain({ projectRef })
55 }
56
57 return (
58 <>
59 <div className="flex flex-col items-start">
60 <Panel.Content>
61 <div className="flex flex-col gap-2">
62 <h4 className="text-foreground">Enable your custom domain</h4>
63 <p className="text-sm text-foreground-light">
64 Set up is almost complete. Press “Activate” below to enable{' '}
65 <code className="text-code-inline">{customDomain.hostname}</code> for this project.
66 </p>
67 <p className="text-sm text-foreground-light">
68 We recommend that you schedule a downtime window of 20 - 30 minutes for your
69 application, as you will need to update any services that need to know about your
70 custom domain (e.g client side code or OAuth providers).
71 </p>
72 </div>
73 <div className="mt-4">
74 <Admonition
75 type="note"
76 title="Retain your CNAME record for service continuity after activation"
77 >
78 <p>
79 Your custom domain CNAME record for{' '}
80 <code className="text-code-inline">{customDomain.hostname}</code> should resolve to{' '}
81 {endpoint ? (
82 <code className="text-code-inline">{endpoint}</code>
83 ) : (
84 "your project's API URL"
85 )}
86 . If you're using Cloudflare as your DNS provider, disable the proxy option.
87 </p>
88 </Admonition>
89 </div>
90 </Panel.Content>
91
92 <div className="w-full border-t border-muted" />
93
94 <Panel.Content className="w-full">
95 <div className="flex items-center justify-between">
96 <DocsButton href={`${DOCS_URL}/guides/platform/custom-domains`} />
97 <div className="flex items-center space-x-2">
98 <Button
99 type="default"
100 className="self-end"
101 onClick={onCancelCustomDomain}
102 loading={isDeleting}
103 >
104 Cancel
105 </Button>
106 <Button
107 disabled={isDeleting}
108 onClick={() => setIsActivateConfirmModalVisible(true)}
109 className="self-end"
110 >
111 Activate
112 </Button>
113 </div>
114 </div>
115 </Panel.Content>
116 </div>
117
118 <ConfirmationModal
119 size="small"
120 loading={isCheckingRecord || isActivating}
121 visible={isActivateConfirmModalVisible}
122 title="Activate custom domain"
123 confirmLabel="Activate"
124 confirmLabelLoading="Activating"
125 onCancel={() => setIsActivateConfirmModalVisible(false)}
126 onConfirm={onActivateCustomDomain}
127 >
128 <p className="text-sm">
129 Activating <code className="text-code-inline break-normal!">{customDomain.hostname}</code>{' '}
130 will make it visible to users in place of your project’s Briven domain. The Briven
131 domain will continue to work too.
132 </p>
133 </ConfirmationModal>
134 </>
135 )
136}