✨ Add Google Tag Manager (#185)
This commit is contained in:
@ -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
|
||||
@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
@ -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'> & {
|
||||
@ -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
|
||||
}, [])
|
||||
|
||||
|
@ -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([
|
||||
{
|
||||
@ -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)
|
||||
@ -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
apps/viewer/src/lib/google-tag-manager.ts
Normal file
23
apps/viewer/src/lib/google-tag-manager.ts
Normal file
@ -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
|
||||
}
|
Reference in New Issue
Block a user