-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
⚡ (customDomain) Add configuration modal for domain verification
Closes #742
- Loading branch information
1 parent
21ad061
commit 322c48c
Showing
11 changed files
with
447 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
131 changes: 131 additions & 0 deletions
131
apps/builder/src/features/customDomains/api/verifyCustomDomain.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { authenticatedProcedure } from '@/helpers/server/trpc' | ||
import { z } from 'zod' | ||
import { DomainConfigResponse, DomainVerificationResponse } from '../types' | ||
import { | ||
DomainResponse, | ||
DomainVerificationStatus, | ||
domainResponseSchema, | ||
domainVerificationStatusSchema, | ||
} from '@typebot.io/schemas/features/customDomains' | ||
import prisma from '@/lib/prisma' | ||
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden' | ||
import { TRPCError } from '@trpc/server' | ||
import { env } from '@typebot.io/env' | ||
|
||
export const verifyCustomDomain = authenticatedProcedure | ||
.meta({ | ||
openapi: { | ||
method: 'GET', | ||
path: '/custom-domains/{name}/verify', | ||
protect: true, | ||
summary: 'Verify domain config', | ||
tags: ['Custom domains'], | ||
}, | ||
}) | ||
.input( | ||
z.object({ | ||
workspaceId: z.string(), | ||
name: z.string(), | ||
}) | ||
) | ||
.output( | ||
z.object({ | ||
status: domainVerificationStatusSchema, | ||
domainJson: domainResponseSchema, | ||
}) | ||
) | ||
.query(async ({ input: { workspaceId, name }, ctx: { user } }) => { | ||
const workspace = await prisma.workspace.findFirst({ | ||
where: { id: workspaceId }, | ||
select: { | ||
members: { | ||
select: { | ||
userId: true, | ||
role: true, | ||
}, | ||
}, | ||
}, | ||
}) | ||
|
||
if (!workspace || isWriteWorkspaceForbidden(workspace, user)) | ||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' }) | ||
|
||
let status: DomainVerificationStatus = 'Valid Configuration' | ||
|
||
const [domainJson, configJson] = await Promise.all([ | ||
getDomainResponse(name), | ||
getConfigResponse(name), | ||
]) | ||
|
||
if (domainJson?.error?.code === 'not_found') { | ||
status = 'Domain Not Found' | ||
} else if (domainJson.error) { | ||
throw new TRPCError({ | ||
code: 'INTERNAL_SERVER_ERROR', | ||
message: domainJson.error.message, | ||
}) | ||
} else if (!domainJson.verified) { | ||
status = 'Pending Verification' | ||
const verificationJson = await verifyDomain(name) | ||
|
||
if (verificationJson && verificationJson.verified) { | ||
status = 'Valid Configuration' | ||
} | ||
} else if (configJson.misconfigured) { | ||
status = 'Invalid Configuration' | ||
} else { | ||
status = 'Valid Configuration' | ||
} | ||
|
||
return { | ||
status, | ||
domainJson, | ||
} | ||
}) | ||
|
||
const getDomainResponse = async ( | ||
domain: string | ||
): Promise<DomainResponse & { error: { code: string; message: string } }> => { | ||
return await fetch( | ||
`https://api.vercel.com/v9/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${domain}?teamId=${env.VERCEL_TEAM_ID}`, | ||
{ | ||
method: 'GET', | ||
headers: { | ||
Authorization: `Bearer ${env.VERCEL_TOKEN}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
} | ||
).then((res) => { | ||
return res.json() | ||
}) | ||
} | ||
|
||
const getConfigResponse = async ( | ||
domain: string | ||
): Promise<DomainConfigResponse> => { | ||
return await fetch( | ||
`https://api.vercel.com/v6/domains/${domain}/config?teamId=${env.VERCEL_TEAM_ID}`, | ||
{ | ||
method: 'GET', | ||
headers: { | ||
Authorization: `Bearer ${env.VERCEL_TOKEN}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
} | ||
).then((res) => res.json()) | ||
} | ||
|
||
const verifyDomain = async ( | ||
domain: string | ||
): Promise<DomainVerificationResponse> => { | ||
return await fetch( | ||
`https://api.vercel.com/v9/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${domain}/verify?teamId=${env.VERCEL_TEAM_ID}`, | ||
{ | ||
method: 'POST', | ||
headers: { | ||
Authorization: `Bearer ${env.VERCEL_TOKEN}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
} | ||
).then((res) => res.json()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
187 changes: 187 additions & 0 deletions
187
apps/builder/src/features/customDomains/components/CustomDomainConfigModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import { | ||
Modal, | ||
ModalOverlay, | ||
ModalContent, | ||
ModalHeader, | ||
ModalCloseButton, | ||
ModalBody, | ||
HStack, | ||
ModalFooter, | ||
Button, | ||
Text, | ||
Box, | ||
Code, | ||
Stack, | ||
Alert, | ||
AlertIcon, | ||
} from '@chakra-ui/react' | ||
import { XCircleIcon } from '@/components/icons' | ||
import { trpc } from '@/lib/trpc' | ||
|
||
type Props = { | ||
workspaceId: string | ||
isOpen: boolean | ||
domain: string | ||
onClose: () => void | ||
} | ||
|
||
export const CustomDomainConfigModal = ({ | ||
workspaceId, | ||
isOpen, | ||
onClose, | ||
domain, | ||
}: Props) => { | ||
const { data, error } = trpc.customDomains.verifyCustomDomain.useQuery({ | ||
name: domain, | ||
workspaceId, | ||
}) | ||
|
||
const { domainJson, status } = data ?? {} | ||
|
||
if (!status || status === 'Valid Configuration' || !domainJson) return null | ||
|
||
if ('error' in domainJson) return null | ||
|
||
const subdomain = getSubdomain(domainJson.name, domainJson.apexName) | ||
|
||
const recordType = subdomain ? 'CNAME' : 'A' | ||
|
||
const txtVerification = | ||
(status === 'Pending Verification' && | ||
domainJson.verification?.find((x) => x.type === 'TXT')) || | ||
null | ||
|
||
return ( | ||
<Modal isOpen={isOpen} onClose={onClose} size="xl"> | ||
<ModalOverlay /> | ||
<ModalContent> | ||
<ModalHeader> | ||
<HStack> | ||
<XCircleIcon stroke="red.500" /> | ||
<Text fontSize="lg" fontWeight="semibold"> | ||
{status} | ||
</Text> | ||
</HStack> | ||
</ModalHeader> | ||
<ModalCloseButton /> | ||
<ModalBody> | ||
{txtVerification ? ( | ||
<Stack spacing="4"> | ||
<Text> | ||
Please set the following <Code>TXT</Code> record on{' '} | ||
<Text as="span" fontWeight="bold"> | ||
{domainJson.apexName} | ||
</Text>{' '} | ||
to prove ownership of{' '} | ||
<Text as="span" fontWeight="bold"> | ||
{domainJson.name} | ||
</Text> | ||
: | ||
</Text> | ||
<HStack | ||
justifyContent="space-between" | ||
alignItems="flex-start" | ||
spacing="6" | ||
> | ||
<Stack> | ||
<Text fontWeight="bold">Type</Text> | ||
<Text fontSize="sm" fontFamily="mono"> | ||
{txtVerification.type} | ||
</Text> | ||
</Stack> | ||
<Stack> | ||
<Text fontWeight="bold">Name</Text> | ||
<Text fontSize="sm" fontFamily="mono"> | ||
{txtVerification.domain.slice( | ||
0, | ||
txtVerification.domain.length - | ||
domainJson.apexName.length - | ||
1 | ||
)} | ||
</Text> | ||
</Stack> | ||
<Stack> | ||
<Text fontWeight="bold">Value</Text> | ||
<Text fontSize="sm" fontFamily="mono"> | ||
<Box text-overflow="ellipsis" white-space="nowrap"> | ||
{txtVerification.value} | ||
</Box> | ||
</Text> | ||
</Stack> | ||
</HStack> | ||
<Alert status="warning"> | ||
<AlertIcon /> | ||
<Text> | ||
If you are using this domain for another site, setting this | ||
TXT record will transfer domain ownership away from that site | ||
and break it. Please exercise caution when setting this | ||
record. | ||
</Text> | ||
</Alert> | ||
</Stack> | ||
) : status === 'Unknown Error' ? ( | ||
<Text mb="5" fontSize="sm"> | ||
{error?.message} | ||
</Text> | ||
) : ( | ||
<Stack spacing={4}> | ||
<Text> | ||
To configure your{' '} | ||
{recordType === 'A' ? 'apex domain' : 'subdomain'} ( | ||
<Box as="span" fontWeight="bold"> | ||
{recordType === 'A' ? domainJson.apexName : domainJson.name} | ||
</Box> | ||
), set the following {recordType} record on your DNS provider to | ||
continue: | ||
</Text> | ||
<HStack justifyContent="space-between"> | ||
<Stack> | ||
<Text fontWeight="bold">Type</Text> | ||
<Text fontFamily="mono" fontSize="sm"> | ||
{recordType} | ||
</Text> | ||
</Stack> | ||
<Stack> | ||
<Text fontWeight="bold">Name</Text> | ||
<Text fontFamily="mono" fontSize="sm"> | ||
{recordType === 'A' ? '@' : subdomain ?? 'www'} | ||
</Text> | ||
</Stack> | ||
<Stack> | ||
<Text fontWeight="bold">Value</Text> | ||
<Text fontFamily="mono" fontSize="sm"> | ||
{recordType === 'A' | ||
? '76.76.21.21' | ||
: `cname.vercel-dns.com`} | ||
</Text> | ||
</Stack> | ||
<Stack> | ||
<Text fontWeight="bold">TTL</Text> | ||
<Text fontFamily="mono" fontSize="sm"> | ||
86400 | ||
</Text> | ||
</Stack> | ||
</HStack> | ||
<Alert fontSize="sm"> | ||
<AlertIcon /> | ||
<Text> | ||
Note: for TTL, if <Code>86400</Code> is not available, set the | ||
highest value possible. Also, domain propagation can take up | ||
to an hour. | ||
</Text> | ||
</Alert> | ||
</Stack> | ||
)} | ||
</ModalBody> | ||
<ModalFooter as={HStack}> | ||
<Button onClick={onClose}>Close</Button> | ||
</ModalFooter> | ||
</ModalContent> | ||
</Modal> | ||
) | ||
} | ||
|
||
const getSubdomain = (name: string, apexName: string) => { | ||
if (name === apexName) return null | ||
return name.slice(0, name.length - apexName.length - 1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
322c48c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
landing-page-v2 – ./apps/landing-page
landing-page-v2-git-main-typebot-io.vercel.app
get-typebot.com
home.typebot.io
www.typebot.io
landing-page-v2-typebot-io.vercel.app
www.get-typebot.com
322c48c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
builder-v2 – ./apps/builder
builder-v2-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app
app.typebot.io
322c48c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
docs – ./apps/docs
docs-git-main-typebot-io.vercel.app
docs.typebot.io
docs-typebot-io.vercel.app
322c48c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
viewer-v2 – ./apps/viewer
acordo-certo.com
app-liberado.pro
ask.pemantau.org
batepapo.digital
bot.contakit.com
bot.imovfast.com
bot.piccinato.co
chat.sifucrm.com
chat.syncwin.com
chatonlineja.com
clo.closeer.work
cockroach.cr8.ai
desafioem21d.com
digitando.online
faqs.nigerias.io
feiraodehoje.com
georgemarttt.com
go.chatbotcv.com
haymanevents.com
kw.wpwakanda.com
localamor.online
lojamundobox.com
my.skillbrow.com
myrentalhost.com
silvercop.com.br
silvercop.online
stan.vselise.com
typebot.aloe.bot
vidalimentar.com
voicehelp.cr8.ai
web.bjogador.com
webwhatsapp.work
whatisappweb.com
www.pantaflow.ai
zap.fundviser.in
analistamines.com
app.bouclidom.com
app.chatforms.net
appbotcontato.com
appmillion.online
averdadehoje.site
bot.cerograsa.com
bot.chatbotcv.com
bot.hostnation.de
bot.ketoolife.com
bot.maitempah.com
bot.phuonghub.com
bot.reviewamp.com
bot.reviewzer.com
bot.uluhub.com.br
chat.daftarjer.com
chat.hand-made.one
chat.tuanpakya.com
chat.webisharp.com
chatbotforthat.com
descobrindotudo.me
dicanatural.online
digitalhelp.com.au
draraquelnutri.com
drcarlosyoshi.site
goalsettingbot.com
viewer-v2-typebot-io.vercel.app
mdb.assessoria.fernanda.progenbr.com
mdb.assessoria.jbatista.progenbr.com
mdb.assessoria.mauricio.progenbr.com
mdb.evento.autocadastro.progenbr.com
form.shopmercedesbenzsouthorlando.com
mdb.evento.equipeinterna.progenbr.com
bot.studiotecnicoimmobiliaremerelli.it
mdb.assessoria.boaventura.progenbr.com
mdb.assessoria.jtrebesqui.progenbr.com
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
gabinete.baleia.formulario.progenbr.com
mdb.assessoria.carreirinha.progenbr.com
chrome-os-inquiry-system.itschromeos.com
mdb.assessoria.paulomarques.progenbr.com
viewer-v2-git-main-typebot-io.vercel.app
main-menu-for-itschromeos.itschromeos.com
mdb.assessoria.qrcode.ademir.progenbr.com
mdb.assessoria.qrcode.arthur.progenbr.com
mdb.assessoria.qrcode.danilo.progenbr.com
mdb.assessoria.qrcode.marcao.progenbr.com
mdb.assessoria.qrcode.marcio.progenbr.com
mdb.assessoria.qrcode.aloisio.progenbr.com
mdb.assessoria.qrcode.girotto.progenbr.com
mdb.assessoria.qrcode.marinho.progenbr.com
mdb.assessoria.qrcode.rodrigo.progenbr.com
mdb.assessoria.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.desideri.progenbr.com
mdb.assessoria.qrcode.fernanda.progenbr.com
mdb.assessoria.qrcode.jbatista.progenbr.com
mdb.assessoria.qrcode.mauricio.progenbr.com
mdb.assessoria.fernanda.regional.progenbr.com
mdb.assessoria.qrcode.boaventura.progenbr.com
mdb.assessoria.qrcode.jtrebesqui.progenbr.com
mdb.assessoria.qrcode.carreirinha.progenbr.com
mdb.assessoria.qrcode.paulomarques.progenbr.com
mdb.assessoria.qrcode.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.fernanda.regional.progenbr.com