CustomDomainConfig.tsx173 lines · main
1import { SupportCategories } from '@supabase/shared-types/out/constants'
2import { useFlag, useParams } from 'common'
3import { AlertCircle } from 'lucide-react'
4import { Card, CardContent } from 'ui'
5import {
6 PageSection,
7 PageSectionContent,
8 PageSectionDescription,
9 PageSectionMeta,
10 PageSectionSummary,
11 PageSectionTitle,
12} from 'ui-patterns/PageSection'
13
14import { CustomDomainActivate } from './CustomDomainActivate'
15import { CustomDomainDelete } from './CustomDomainDelete'
16import { CustomDomainsConfigureHostname } from './CustomDomainsConfigureHostname'
17import { CustomDomainsShimmerLoader } from './CustomDomainsShimmerLoader'
18import { CustomDomainVerify } from './CustomDomainVerify'
19import { SupportLink } from '@/components/interfaces/Support/SupportLink'
20import { InlineLinkClassName } from '@/components/ui/InlineLink'
21import { UpgradeToPro } from '@/components/ui/UpgradeToPro'
22import {
23 useCustomDomainsQuery,
24 type CustomDomainsData,
25} from '@/data/custom-domains/custom-domains-query'
26import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query'
27import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
28
29export const CustomDomainConfig = () => {
30 const { ref } = useParams()
31 const { data: organization } = useSelectedOrganizationQuery()
32
33 const customDomainsDisabledDueToQuota = useFlag('customDomainsDisabledDueToQuota')
34
35 const plan = organization?.plan?.id
36
37 const { data: addons, isPending: isLoadingAddons } = useProjectAddonsQuery({ projectRef: ref })
38 const hasCustomDomainAddon = !!addons?.selected_addons.find((x) => x.type === 'custom_domain')
39
40 const {
41 data: customDomainData,
42 isPending: isCustomDomainsLoading,
43 isError,
44 isSuccess,
45 status: customDomainStatus,
46 } = useCustomDomainsQuery(
47 { projectRef: ref },
48 {
49 refetchInterval: (query) => {
50 const data = query.state.data
51 // while setting up the ssl certificate, we want to poll every 5 seconds
52 if (data?.customDomain?.ssl.status) {
53 return 10000 // 10 seconds
54 }
55
56 return false
57 },
58 }
59 )
60
61 const { status } = customDomainData || {}
62
63 return (
64 <PageSection id="custom-domains">
65 <PageSectionMeta>
66 <PageSectionSummary>
67 <PageSectionTitle>Custom domains</PageSectionTitle>
68 <PageSectionDescription>
69 Present a branded experience to your users
70 </PageSectionDescription>
71 </PageSectionSummary>
72 </PageSectionMeta>
73 <PageSectionContent>
74 {isLoadingAddons ? (
75 <Card>
76 <CardContent className="space-y-6">
77 <CustomDomainsShimmerLoader />
78 </CardContent>
79 </Card>
80 ) : !hasCustomDomainAddon ? (
81 <UpgradeToPro
82 primaryText={
83 customDomainsDisabledDueToQuota
84 ? 'New custom domains are temporarily disabled'
85 : 'Custom domains are a Pro Plan add-on'
86 }
87 secondaryText={
88 customDomainsDisabledDueToQuota
89 ? 'We are working with our upstream DNS provider before we are able to sign up new custom domains. Please check back in a few hours.'
90 : plan === 'free'
91 ? 'Paid Plans come with free vanity subdomains or Custom Domains for an additional $10/month per domain.'
92 : 'To configure a custom domain for your project, please enable the add-on. Each Custom Domain costs $10 per month.'
93 }
94 addon="customDomain"
95 source="customDomain"
96 featureProposition="enable custom domains"
97 disabled={customDomainsDisabledDueToQuota}
98 />
99 ) : isCustomDomainsLoading ? (
100 <Card>
101 <CardContent className="space-y-6">
102 <CustomDomainsShimmerLoader />
103 </CardContent>
104 </Card>
105 ) : isError ? (
106 <Card>
107 <CardContent className="space-y-6">
108 <div className="flex items-center justify-center space-x-2 py-8">
109 <AlertCircle size={16} strokeWidth={1.5} />
110 <p className="text-sm text-foreground-light">
111 Failed to retrieve custom domain configuration. Please try again later or{' '}
112 <SupportLink
113 queryParams={{ projectRef: ref, category: SupportCategories.SALES_ENQUIRY }}
114 className={InlineLinkClassName}
115 >
116 contact support
117 </SupportLink>
118 .
119 </p>
120 </div>
121 </CardContent>
122 </Card>
123 ) : status === '0_no_hostname_configured' ? (
124 <CustomDomainsConfigureHostname />
125 ) : (
126 <Card>
127 <CardContent className="p-0">
128 {isSuccess ? (
129 <div className="flex flex-col">
130 {(status === '1_not_started' ||
131 status === '2_initiated' ||
132 status === '3_challenge_verified') && <CustomDomainVerify />}
133
134 {customDomainData.status === '4_origin_setup_completed' && (
135 <CustomDomainActivate
136 projectRef={ref}
137 customDomain={customDomainData.customDomain}
138 />
139 )}
140
141 {customDomainData.status === '5_services_reconfigured' && (
142 <CustomDomainDelete
143 projectRef={ref}
144 customDomain={customDomainData.customDomain}
145 />
146 )}
147 </div>
148 ) : (
149 <CustomDomainConfigFallthrough
150 fetchStatus={customDomainStatus}
151 data={customDomainData}
152 />
153 )}
154 </CardContent>
155 </Card>
156 )}
157 </PageSectionContent>
158 </PageSection>
159 )
160}
161
162interface CustomDomainConfigFallthroughProps {
163 fetchStatus: 'error' | 'success' | 'pending'
164 data: CustomDomainsData | undefined
165}
166
167function CustomDomainConfigFallthrough({ fetchStatus, data }: CustomDomainConfigFallthroughProps) {
168 console.error(`Failing to display UI for custom domains:
169Fetch status: ${fetchStatus}
170Custom domain status: ${data?.status}`)
171
172 return null
173}