CustomDomainConfig.tsx 6.2 KB

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