Skip to content

Commit

Permalink
✨ Add Google Tag Manager (#185)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jorgelig authored Dec 20, 2022
1 parent 3394fa5 commit a898a7a
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 80 deletions.
39 changes: 19 additions & 20 deletions apps/builder/src/features/settings/components/MetadataForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const MetadataForm = ({
onMetadataChange({ ...metadata, favIconUrl })
const handleImageSubmit = (imageUrl: string) =>
onMetadataChange({ ...metadata, imageUrl })
const handleGoogleTagManagerIdChange = (googleTagManagerId: string) =>
onMetadataChange({ ...metadata, googleTagManagerId })
const handleHeadCodeChange = (customHeadCode: string) =>
onMetadataChange({ ...metadata, customHeadCode })

Expand Down Expand Up @@ -92,26 +94,23 @@ export const MetadataForm = ({
</PopoverContent>
</Popover>
</Stack>
<Stack>
<FormLabel mb="0" htmlFor="title">
Title:
</FormLabel>
<Input
id="title"
defaultValue={metadata.title ?? typebotName}
onChange={handleTitleChange}
/>
</Stack>
<Stack>
<FormLabel mb="0" htmlFor="description">
Description:
</FormLabel>
<Textarea
id="description"
defaultValue={metadata.description}
onChange={handleDescriptionChange}
/>
</Stack>
<Input
label="Title:"
defaultValue={metadata.title ?? typebotName}
onChange={handleTitleChange}
/>
<Textarea
defaultValue={metadata.description}
onChange={handleDescriptionChange}
label="Description:"
/>
<Input
defaultValue={metadata.googleTagManagerId}
placeholder="GTM-XXXXXX"
onChange={handleGoogleTagManagerIdChange}
label="Google Tag Manager ID:"
moreInfoTooltip="Do not include it if you are embedding your typebot in an existing website. GTM should be installed in the parent website instead."
/>
<Stack>
<HStack as={FormLabel} mb="0" htmlFor="head">
<Text>Custom head code:</Text>
Expand Down
11 changes: 4 additions & 7 deletions apps/builder/src/features/settings/settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,10 @@ test.describe.parallel('Settings page', () => {
await page.fill('input[placeholder="Paste the image link..."]', imageUrl)
await expect(websiteImg).toHaveAttribute('src', imageUrl)

// Title
await page.fill('input#title', 'Awesome typebot')

// Description
await page.fill('textarea#description', 'Lorem ipsum')

// Custom head code
await page.getByRole('textbox', { name: 'Title' }).fill('Awesome typebot')
await page
.getByRole('textbox', { name: 'Description' })
.fill('Lorem ipsum')
await page.fill(
'div[contenteditable=true]',
'<script>Lorem ipsum</script>'
Expand Down
6 changes: 6 additions & 0 deletions apps/docs/docs/editor/settings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ You can tweak `3000` (3s) to your liking.
In the Metadata section, you can customize how the preview card will look if you share your bot URL on social media for example.

You can also add some custom head code to add third-party scripts like a Facebook pixel for example.

### Google Tag Manager

Allows you to easily add a GTM container to your bot. To find your GTM container ID, go to your GTM dashboard and click on the container you want to use. The ID is displayed in the top right corner.

Note that you should not include it if you are embedding your typebot in an existing website. GTM should be installed in the parent website instead.
106 changes: 58 additions & 48 deletions apps/viewer/src/components/Seo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { gtmHeadSnippet } from '@/lib/google-tag-manager'
import { Metadata } from 'models'
import Head from 'next/head'
import Script from 'next/script'
import React from 'react'
import { isNotEmpty } from 'utils'

type SEOProps = {
url: string
Expand All @@ -11,55 +14,62 @@ type SEOProps = {
export const SEO = ({
url,
typebotName,
metadata: { title, description, favIconUrl, imageUrl },
metadata: { title, description, favIconUrl, imageUrl, googleTagManagerId },
}: SEOProps) => (
<Head>
<title>{title ?? typebotName}</title>
<meta name="robots" content="noindex" />
<link
rel="icon"
type="image/png"
href={favIconUrl ?? 'https://bot.typebot.io/favicon.png'}
/>
<meta name="title" content={title ?? typebotName} />
<meta
name="description"
content={
description ??
'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.'
}
/>
<>
<Head key="seo">
<title>{title ?? typebotName}</title>
<meta name="robots" content="noindex" />
<link
rel="icon"
type="image/png"
href={favIconUrl ?? 'https://bot.typebot.io/favicon.png'}
/>
<meta name="title" content={title ?? typebotName} />
<meta
name="description"
content={
description ??
'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.'
}
/>

<meta property="og:type" content="website" />
<meta property="og:url" content={url ?? 'https://bot.typebot.io'} />
<meta property="og:title" content={title ?? typebotName} />
<meta property="og:site_name" content={title ?? typebotName} />
<meta
property="og:description"
content={
description ??
'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.'
}
/>
<meta
property="og:image"
itemProp="image"
content={imageUrl ?? 'https://bot.typebot.io/site-preview.png'}
/>
<meta property="og:type" content="website" />
<meta property="og:url" content={url ?? 'https://bot.typebot.io'} />
<meta property="og:title" content={title ?? typebotName} />
<meta property="og:site_name" content={title ?? typebotName} />
<meta
property="og:description"
content={
description ??
'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.'
}
/>
<meta
property="og:image"
itemProp="image"
content={imageUrl ?? 'https://bot.typebot.io/site-preview.png'}
/>

<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={url ?? 'https://bot.typebot.io'} />
<meta property="twitter:title" content={title ?? typebotName} />
<meta
property="twitter:description"
content={
description ??
'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.'
}
/>
<meta
property="twitter:image"
content={imageUrl ?? 'https://bot.typebot.io/site-preview.png'}
/>
</Head>
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={url ?? 'https://bot.typebot.io'} />
<meta property="twitter:title" content={title ?? typebotName} />
<meta
property="twitter:description"
content={
description ??
'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.'
}
/>
<meta
property="twitter:image"
content={imageUrl ?? 'https://bot.typebot.io/site-preview.png'}
/>
</Head>
{isNotEmpty(googleTagManagerId) && (
<Script id="google-tag-manager">
{gtmHeadSnippet(googleTagManagerId)}
</Script>
)}
</>
)
5 changes: 4 additions & 1 deletion apps/viewer/src/components/TypebotPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { TypebotViewer } from 'bot-engine'
import { AnswerInput, PublicTypebot, Typebot, VariableWithValue } from 'models'
import { useRouter } from 'next/router'
import React, { useEffect, useState } from 'react'
import { isDefined, isNotDefined } from 'utils'
import { isDefined, isNotDefined, isNotEmpty } from 'utils'
import { SEO } from './Seo'
import { ErrorPage } from './ErrorPage'
import { createResultQuery, updateResultQuery } from '@/features/results'
import { upsertAnswerQuery } from '@/features/answers'
import { gtmBodyElement } from '@/lib/google-tag-manager'

export type TypebotPageProps = {
publishedTypebot: Omit<PublicTypebot, 'createdAt' | 'updatedAt'> & {
Expand Down Expand Up @@ -51,6 +52,8 @@ export const TypebotPage = ({
initializeResult().then()
if (isDefined(customHeadCode))
document.head.innerHTML = document.head.innerHTML + customHeadCode
const gtmId = publishedTypebot.settings.metadata.googleTagManagerId
if (isNotEmpty(gtmId)) document.body.prepend(gtmBodyElement(gtmId))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

Expand Down
18 changes: 14 additions & 4 deletions apps/viewer/src/features/settings/settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,14 @@ test('Show close message', async ({ page }) => {

test('Should correctly parse metadata', async ({ page }) => {
const typebotId = cuid()
const googleTagManagerId = 'GTM-M72NXKB'
const customMetadata: Metadata = {
description: 'My custom description',
title: 'Custom title',
favIconUrl: 'https://www.baptistearno.com/favicon.png',
imageUrl: 'https://www.baptistearno.com/images/site-preview.png',
customHeadCode: '<meta name="author" content="John Doe">',
googleTagManagerId,
}
await createTypebots([
{
Expand All @@ -146,6 +148,11 @@ test('Should correctly parse metadata', async ({ page }) => {
},
])
await page.goto(`/${typebotId}-public`)
await expect(
typebotViewer(page).locator(
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
)
).toBeVisible()
expect(
await page.evaluate(`document.querySelector('title').textContent`)
).toBe(customMetadata.title)
Expand Down Expand Up @@ -177,9 +184,12 @@ test('Should correctly parse metadata', async ({ page }) => {
.content
)
).toBe('John Doe')
await expect(
typebotViewer(page).locator(
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
expect(
await page.evaluate(
(googleTagManagerId) =>
document.querySelector(
`iframe[src="https://www.googletagmanager.com/ns.html?id=${googleTagManagerId}"]`
) as HTMLMetaElement
)
).toBeVisible()
).toBeDefined()
})
23 changes: 23 additions & 0 deletions apps/viewer/src/lib/google-tag-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const gtmHeadSnippet = (
googleTagManagerId: string
) => `<!-- Google Tag Manager -->
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${googleTagManagerId}');
<!-- End Google Tag Manager -->`

export const gtmBodyElement = (googleTagManagerId: string) => {
if (document.getElementById('gtm-noscript')) return ''
const noScriptElement = document.createElement('noscript')
noScriptElement.id = 'gtm-noscript'
const iframeElement = document.createElement('iframe')
iframeElement.src = `https://www.googletagmanager.com/ns.html?id=${googleTagManagerId}`
iframeElement.height = '0'
iframeElement.width = '0'
iframeElement.style.display = 'none'
iframeElement.style.visibility = 'hidden'
noScriptElement.appendChild(iframeElement)
return noScriptElement
}
1 change: 1 addition & 0 deletions packages/models/src/features/typebot/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const metadataSchema = z.object({
imageUrl: z.string().optional(),
favIconUrl: z.string().optional(),
customHeadCode: z.string().optional(),
googleTagManagerId: z.string().optional(),
})

export const settingsSchema = z.object({
Expand Down

5 comments on commit a898a7a

@vercel
Copy link

@vercel vercel bot commented on a898a7a Dec 20, 2022

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-alpha – ./apps/viewer

ns8.vn
yobot.me
247987.com
8jours.top
bot.aws.bj
bot.bbc.bj
finplex.be
sat.cr8.ai
bot.aipr.kr
docs.cr8.ai
minipost.uk
team.cr8.ai
svhm.mprs.in
video.cr8.ai
bot.krdfy.com
goldorayo.com
signup.cr8.ai
vhpage.cr8.ai
am.nigerias.io
an.nigerias.io
ar.nigerias.io
bot.enreso.org
carsalesenquiry.com
chatbot.repplai.com
demo.botscientis.us
forms.webisharp.com
kbsub.wpwakanda.com
live.botscientis.us
mentoria.omelhor.vc
nutrisamirbayde.com
order.maitempah.com
quest.wpwakanda.com
survey1.digienge.io
test.botscientis.us
typebot.stillio.com
wordsandimagery.com
bium.gratirabbit.com
bot.ansuraniphone.my
bot.cotemeuplano.com
bot.leadbooster.help
chat.hayurihijab.com
chatbee.agfunnel.com
click.sevenoways.com
connect.growthguy.in
kuiz.sistemniaga.com
offer.botscientis.us
sellmycarglasgow.com
talkbot.agfunnel.com
tenorioadvogados.com
uppity.wpwakanda.com
abutton.wpwakanda.com
acelera.maxbot.com.br
aidigitalmarketing.kr
bbutton.wpwakanda.com
bot.incusservices.com
bot.meuesocial.com.br
bot.ramonmatos.com.br
bot.truongnguyen.live
cdd.searchcube.com.sg
chat.missarkansas.org
chatbot.ownacademy.co
sbutton.wpwakanda.com
815639944.21000000.one
brigadeirosemdrama.com.br
chat.ertcrebateportal.com
chat.thisiscrushhouse.com
sellmyharleylouisiana.com
verfica.botmachine.com.br
configurator.bouclidom.com
help.atlasoutfittersk9.com
ted.meujalecobrasil.com.br
type.dericsoncalari.com.br
chatbot.berbelanjabiz.trade
designguide.techyscouts.com
presente.empresarias.com.mx
sell.sellthemotorhome.co.uk
anamnese.odontopavani.com.br
austin.channelautomation.com
bot.marketingplusmindset.com
piazzatorre.barrettamario.it
requests.swamprecordsgnv.com
type.cookieacademyonline.com
bot.brigadeirosemdrama.com.br
onboarding.libertydreamcare.ie
type.talitasouzamarques.com.br
agendamento.sergiolimajr.com.br
anamnese.clinicamegasjdr.com.br
bookings.littlepartymonkeys.com
bot.comercializadoraomicron.com
elevateyourmind.groovepages.com
yourfeedback.comebackreward.com
personal-trainer.barrettamario.it
preagendamento.sergiolimajr.com.br
studiotecnicoimmobiliaremerelli.it
download.thailandmicespecialist.com
register.thailandmicespecialist.com
viewer-v2-alpha-typebot-io.vercel.app
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
viewer-v2-alpha-git-main-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on a898a7a Dec 20, 2022

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

@vercel
Copy link

@vercel vercel bot commented on a898a7a Dec 20, 2022

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-typebot-io.vercel.app
builder-v2-git-main-typebot-io.vercel.app
app.typebot.io

@vercel
Copy link

@vercel vercel bot commented on a898a7a Dec 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on a898a7a Dec 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.