2
0

Introduce bot v2 in builder (#328)

Also, the new engine is the default for updated typebots for viewer

Closes #211
This commit is contained in:
Baptiste Arnaud
2023-02-21 15:25:14 +01:00
committed by GitHub
parent 527dc8a5b1
commit debdac12ff
208 changed files with 4462 additions and 5236 deletions

View File

@ -5,12 +5,12 @@ export const mockedUser: User = {
name: 'John Doe',
email: 'user@email.com',
company: null,
createdAt: new Date(),
createdAt: new Date('2022-01-01'),
emailVerified: null,
graphNavigation: 'TRACKPAD',
preferredAppAppearance: null,
image: 'https://avatars.githubusercontent.com/u/16015833?v=4',
lastActivityAt: new Date(),
lastActivityAt: new Date('2022-01-01'),
onboardingCategories: [],
updatedAt: new Date(),
updatedAt: new Date('2022-01-01'),
}

View File

@ -4,7 +4,6 @@ import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { BubbleBlockType, defaultAudioBubbleContent } from 'models'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'
import { typebotViewer } from 'utils/playwright/testHelpers'
const audioSampleUrl =
'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'
@ -34,7 +33,7 @@ test('should work as expected', async ({ page }) => {
RegExp(`/public/typebots/${typebotId}/blocks`, 'gm')
)
await page.getByRole('button', { name: 'Preview', exact: true }).click()
await expect(typebotViewer(page).locator('audio')).toHaveAttribute(
await expect(page.locator('audio')).toHaveAttribute(
'src',
RegExp(`/public/typebots/${typebotId}/blocks`, 'gm')
)

View File

@ -3,7 +3,6 @@ import { BubbleBlockType, defaultEmbedBubbleContent } from 'models'
import { createId } from '@paralleldrive/cuid2'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { typebotViewer } from 'utils/playwright/testHelpers'
const pdfSrc = 'https://www.orimi.com/pdf-test.pdf'
const siteSrc = 'https://app.cal.com/baptistearno/15min'
@ -47,9 +46,10 @@ test.describe.parallel('Embed bubble block', () => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator('iframe#embed-bubble-content')
).toHaveAttribute('src', siteSrc)
await expect(page.locator('iframe#embed-bubble-content')).toHaveAttribute(
'src',
siteSrc
)
})
})
})

View File

@ -3,7 +3,6 @@ import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { BubbleBlockType, defaultImageBubbleContent } from 'models'
import { createId } from '@paralleldrive/cuid2'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { getTestAsset } from '@/test/utils/playwright'
const unsplashImageSrc =
@ -117,10 +116,7 @@ test.describe.parallel('Image bubble block', () => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(typebotViewer(page).locator('img')).toHaveAttribute(
'src',
unsplashImageSrc
)
await expect(page.locator('img')).toHaveAttribute('src', unsplashImageSrc)
})
})
})

View File

@ -3,7 +3,6 @@ import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { BubbleBlockType, defaultTextBubbleContent } from 'models'
import { createId } from '@paralleldrive/cuid2'
import { typebotViewer } from 'utils/playwright/testHelpers'
test.describe('Text bubble block', () => {
test('rich text features should work', async ({ page }) => {
@ -51,17 +50,17 @@ test.describe('Text bubble block', () => {
await page.getByRole('menuitem', { name: 'Create test' }).click()
await page.click('text=Preview')
await expect(page.locator('span.slate-bold >> nth=0')).toHaveText(
'Bold text'
)
await expect(page.locator('span.slate-italic >> nth=0')).toHaveText(
'Italic text'
)
await expect(page.locator('span.slate-underline >> nth=0')).toHaveText(
'Underlined text'
)
await expect(
typebotViewer(page).locator('span.slate-bold >> nth=0')
).toHaveText('Bold text')
await expect(
typebotViewer(page).locator('span.slate-italic >> nth=0')
).toHaveText('Italic text')
await expect(
typebotViewer(page).locator('span.slate-underline >> nth=0')
).toHaveText('Underlined text')
await expect(
typebotViewer(page).locator('a[href="https://github.com"]')
page.locator('typebot-standard').locator('a[href="https://github.com"]')
).toHaveText('My super link')
})
})

View File

@ -7,7 +7,6 @@ import {
VideoBubbleContentType,
} from 'models'
import { createId } from '@paralleldrive/cuid2'
import { typebotViewer } from 'utils/playwright/testHelpers'
const videoSrc =
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4'
@ -57,9 +56,10 @@ test.describe.parallel('Video bubble block', () => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator('video > source')
).toHaveAttribute('src', videoSrc)
await expect(page.locator('video > source').nth(1)).toHaveAttribute(
'src',
videoSrc
)
})
test('should display youtube video correctly', async ({ page }) => {
@ -80,7 +80,7 @@ test.describe.parallel('Video bubble block', () => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(typebotViewer(page).locator('iframe')).toHaveAttribute(
await expect(page.locator('iframe').nth(1)).toHaveAttribute(
'src',
'https://www.youtube.com/embed/dQw4w9WgXcQ'
)
@ -104,7 +104,7 @@ test.describe.parallel('Video bubble block', () => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(typebotViewer(page).locator('iframe')).toHaveAttribute(
await expect(page.locator('iframe').nth(1)).toHaveAttribute(
'src',
'https://player.vimeo.com/video/649301125'
)

View File

@ -6,7 +6,6 @@ import {
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { defaultChoiceInputOptions, InputBlockType, ItemType } from 'models'
import { createId } from '@paralleldrive/cuid2'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { getTestAsset } from '@/test/utils/playwright'
test.describe.parallel('Buttons input block', () => {
@ -42,10 +41,10 @@ test.describe.parallel('Buttons input block', () => {
await expect(page.locator('text=Item 2')).toBeHidden()
await page.click('text=Preview')
const item3Button = typebotViewer(page).locator('button >> text=Item 3')
const item3Button = page.locator('button >> text=Item 3')
await item3Button.click()
await expect(item3Button).toBeHidden()
await expect(typebotViewer(page).locator('text=Item 3')).toBeVisible()
await expect(page.getByTestId('guest-bubble')).toHaveText('Item 3')
await page.click('button[aria-label="Close"]')
await page.click('[data-testid="block2-icon"]')
@ -64,13 +63,11 @@ test.describe.parallel('Buttons input block', () => {
await page.click('text=Preview')
await typebotViewer(page).locator('button >> text="Item 3"').click()
await typebotViewer(page).locator('button >> text="Item 1"').click()
await typebotViewer(page).locator('text=Go').click()
await page.locator('button >> text="Item 3"').click()
await page.locator('button >> text="Item 1"').click()
await page.locator('text=Go').click()
await expect(
typebotViewer(page).locator('text="Item 3, Item 1"')
).toBeVisible()
await expect(page.locator('text="Item 3, Item 1"')).toBeVisible()
})
})
@ -85,18 +82,18 @@ test('Variable buttons should work', async ({ page }) => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await typebotViewer(page).locator('text=Variable item').click()
await expect(typebotViewer(page).locator('text=Variable item')).toBeVisible()
await expect(typebotViewer(page).locator('text=Ok great!')).toBeVisible()
await page.getByRole('button', { name: 'Variable item' }).click()
await expect(page.getByTestId('guest-bubble')).toHaveText('Variable item')
await expect(page.locator('text=Ok great!')).toBeVisible()
await page.click('text="Item 1"')
await page.fill('input[value="Item 1"]', '{{Item 2}}')
await page.click('[data-testid="block1-icon"]')
await page.click('text=Multiple choice?')
await page.click('text="Restart"')
await typebotViewer(page).locator('text="Variable item" >> nth=0').click()
await typebotViewer(page).locator('text="Variable item" >> nth=1').click()
await typebotViewer(page).locator('text="Send"').click()
await page.getByTestId('button').first().click()
await page.getByTestId('button').nth(1).click()
await page.locator('text="Send"').click()
await expect(
typebotViewer(page).locator('text="Variable item, Variable item"')
page.locator('text="Variable item, Variable item"')
).toBeVisible()
})

View File

@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { defaultDateInputOptions, InputBlockType } from 'models'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { createId } from '@paralleldrive/cuid2'
test.describe('Date input block', () => {
@ -21,15 +20,14 @@ test.describe('Date input block', () => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator('[data-testid="from-date"]')
).toHaveAttribute('type', 'date')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await typebotViewer(page)
.locator('[data-testid="from-date"]')
.fill('2021-01-01')
await typebotViewer(page).locator(`button`).click()
await expect(typebotViewer(page).locator('text="01/01/2021"')).toBeVisible()
await expect(page.locator('[data-testid="from-date"]')).toHaveAttribute(
'type',
'date'
)
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled()
await page.locator('[data-testid="from-date"]').fill('2021-01-01')
await page.getByRole('button', { name: 'Send' }).click()
await expect(page.locator('text="01/01/2021"')).toBeVisible()
await page.click(`text=Pick a date...`)
await page.click('text=Is range?')
@ -39,23 +37,19 @@ test.describe('Date input block', () => {
await page.fill('#button', 'Go')
await page.click('text=Restart')
await expect(page.locator(`[data-testid="from-date"]`)).toHaveAttribute(
'type',
'datetime-local'
)
await expect(page.locator(`[data-testid="to-date"]`)).toHaveAttribute(
'type',
'datetime-local'
)
await page.locator('[data-testid="from-date"]').fill('2021-01-01T11:00')
await page.locator('[data-testid="to-date"]').fill('2022-01-01T09:00')
await page.getByRole('button', { name: 'Go' }).click()
await expect(
typebotViewer(page).locator(`[data-testid="from-date"]`)
).toHaveAttribute('type', 'datetime-local')
await expect(
typebotViewer(page).locator(`[data-testid="to-date"]`)
).toHaveAttribute('type', 'datetime-local')
await typebotViewer(page)
.locator('[data-testid="from-date"]')
.fill('2021-01-01T11:00')
await typebotViewer(page)
.locator('[data-testid="to-date"]')
.fill('2022-01-01T09:00')
await typebotViewer(page).locator(`button`).click()
await expect(
typebotViewer(page).locator(
'text="01/01/2021, 11:00 AM to 01/01/2022, 09:00 AM"'
)
page.locator('text="01/01/2021, 11:00 AM to 01/01/2022, 09:00 AM"')
).toBeVisible()
})
})

View File

@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { defaultEmailInputOptions, InputBlockType } from 'models'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { createId } from '@paralleldrive/cuid2'
test.describe('Email input block', () => {
@ -22,11 +21,11 @@ test.describe('Email input block', () => {
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(
page.locator(
`input[placeholder="${defaultEmailInputOptions.labels.placeholder}"]`
)
).toHaveAttribute('type', 'email')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled()
await page.click(`text=${defaultEmailInputOptions.labels.placeholder}`)
await page.fill(
@ -41,19 +40,13 @@ test.describe('Email input block', () => {
)
await page.click('text=Restart')
await typebotViewer(page)
.locator(`input[placeholder="Your email..."]`)
.fill('test@test')
await typebotViewer(page).locator('text=Go').click()
await expect(
typebotViewer(page).locator('text=Try again bro')
).toBeVisible()
await typebotViewer(page)
await page.locator(`input[placeholder="Your email..."]`).fill('test@test')
await page.getByRole('button', { name: 'Go' }).click()
await expect(page.locator('text=Try again bro')).toBeVisible()
await page
.locator(`input[placeholder="Your email..."]`)
.fill('test@test.com')
await typebotViewer(page).locator('text=Go').click()
await expect(
typebotViewer(page).locator('text=test@test.com')
).toBeVisible()
await page.getByRole('button', { name: 'Go' }).click()
await expect(page.locator('text=test@test.com')).toBeVisible()
})
})

View File

@ -63,7 +63,7 @@ export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
<CodeEditor
lang="html"
onChange={handlePlaceholderLabelChange}
value={options.labels.placeholder}
defaultValue={options.labels.placeholder}
height={'100px'}
withVariableButton={false}
/>

View File

@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { defaultFileInputOptions, InputBlockType } from 'models'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { createId } from '@paralleldrive/cuid2'
import { freeWorkspaceId } from 'utils/playwright/databaseSetup'
import { getTestAsset } from '@/test/utils/playwright'
@ -24,14 +23,12 @@ test('options should work', async ({ page }) => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(`text=Click to upload`)
).toBeVisible()
await expect(typebotViewer(page).locator(`text="Skip"`)).toBeHidden()
await typebotViewer(page)
await expect(page.locator(`text=Click to upload`)).toBeVisible()
await expect(page.locator(`text="Skip"`)).toBeHidden()
await page
.locator(`input[type="file"]`)
.setInputFiles([getTestAsset('avatar.jpg')])
await expect(typebotViewer(page).locator(`text=File uploaded`)).toBeVisible()
await expect(page.locator(`text=File uploaded`)).toBeVisible()
await page.click('text="Collect file"')
await page.click('text="Required?"')
await page.click('text="Allow multiple files?"')
@ -41,20 +38,18 @@ test('options should work', async ({ page }) => {
await page.fill('[value="Skip"]', 'Pass')
await page.fill('input[value="10"]', '20')
await page.click('text="Restart"')
await expect(typebotViewer(page).locator(`text="Pass"`)).toBeVisible()
await expect(typebotViewer(page).locator(`text="Upload now!!"`)).toBeVisible()
await typebotViewer(page)
await expect(page.locator(`text="Pass"`)).toBeVisible()
await expect(page.locator(`text="Upload now!!"`)).toBeVisible()
await page
.locator(`input[type="file"]`)
.setInputFiles([
getTestAsset('avatar.jpg'),
getTestAsset('avatar.jpg'),
getTestAsset('avatar.jpg'),
])
await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible()
await typebotViewer(page).locator('text="Go"').click()
await expect(
typebotViewer(page).locator(`text="3 files uploaded"`)
).toBeVisible()
await expect(page.locator(`text="3"`)).toBeVisible()
await page.locator('text="Go"').click()
await expect(page.locator(`text="3 files uploaded"`)).toBeVisible()
})
test.describe('Free workspace', () => {

View File

@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { defaultNumberInputOptions, InputBlockType } from 'models'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { createId } from '@paralleldrive/cuid2'
test.describe('Number input block', () => {
@ -22,11 +21,11 @@ test.describe('Number input block', () => {
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(
page.locator(
`input[placeholder="${defaultNumberInputOptions.labels.placeholder}"]`
)
).toHaveAttribute('type', 'number')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled()
await page.click(`text=${defaultNumberInputOptions.labels.placeholder}`)
await page.fill('#placeholder', 'Your number...')
@ -37,15 +36,13 @@ test.describe('Number input block', () => {
await page.fill('[role="spinbutton"] >> nth=2', '10')
await page.click('text=Restart')
const input = typebotViewer(page).locator(
`input[placeholder="Your number..."]`
)
const input = page.locator(`input[placeholder="Your number..."]`)
await input.fill('-1')
await input.press('Enter')
await input.fill('150')
await input.press('Enter')
await input.fill('50')
await input.press('Enter')
await expect(typebotViewer(page).locator('text=50')).toBeVisible()
await expect(page.locator('text=50')).toBeVisible()
})
})

View File

@ -3,7 +3,6 @@ import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { defaultPaymentInputOptions, InputBlockType } from 'models'
import { createId } from '@paralleldrive/cuid2'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { stripePaymentForm } from '@/test/utils/selectorUtils'
test.describe('Payment input block', () => {
@ -59,9 +58,9 @@ test.describe('Payment input block', () => {
.locator(`[placeholder="MM / YY"]`)
.fill('12 / 25')
await stripePaymentForm(page).locator(`[placeholder="CVC"]`).fill('240')
await typebotViewer(page).locator(`text="Pay 30€"`).click()
await page.locator(`text="Pay 30€"`).click()
await expect(
typebotViewer(page).locator(`text="Your card has been declined."`)
page.locator(`text="Your card has been declined."`)
).toBeVisible()
await stripePaymentForm(page)
.locator(`[placeholder="1234 1234 1234 1234"]`)
@ -69,7 +68,7 @@ test.describe('Payment input block', () => {
const zipInput = stripePaymentForm(page).getByPlaceholder('90210')
const isZipInputVisible = await zipInput.isVisible()
if (isZipInputVisible) await zipInput.fill('12345')
await typebotViewer(page).locator(`text="Pay 30€"`).click()
await expect(typebotViewer(page).locator(`text="Success"`)).toBeVisible()
await page.locator(`text="Pay 30€"`).click()
await expect(page.locator(`text="Success"`)).toBeVisible()
})
})

View File

@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { defaultPhoneInputOptions, InputBlockType } from 'models'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { createId } from '@paralleldrive/cuid2'
test.describe('Phone input block', () => {
@ -22,11 +21,11 @@ test.describe('Phone input block', () => {
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(
page.locator(
`input[placeholder="${defaultPhoneInputOptions.labels.placeholder}"]`
)
).toHaveAttribute('type', 'tel')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled()
await page.click(`text=${defaultPhoneInputOptions.labels.placeholder}`)
await page.fill('#placeholder', '+33 XX XX XX XX')
@ -37,21 +36,14 @@ test.describe('Phone input block', () => {
)
await page.click('text=Restart')
await typebotViewer(page)
.locator(`input[placeholder="+33 XX XX XX XX"]`)
.fill('+33 6 73')
await expect(typebotViewer(page).locator(`img`)).toHaveAttribute(
'alt',
'France'
)
await typebotViewer(page).locator('button >> text="Go"').click()
await expect(
typebotViewer(page).locator('text=Try again bro')
).toBeVisible()
await typebotViewer(page)
await page.locator(`input[placeholder="+33 XX XX XX XX"]`).type('+33 6 73')
await expect(page.getByRole('combobox')).toHaveText(/🇫🇷.+/)
await page.locator('button >> text="Go"').click()
await expect(page.locator('text=Try again bro')).toBeVisible()
await page
.locator(`input[placeholder="+33 XX XX XX XX"]`)
.fill('+33 6 73 54 45 67')
await typebotViewer(page).locator('button >> text="Go"').click()
await expect(typebotViewer(page).locator('text=+33673544567')).toBeVisible()
await page.locator('button >> text="Go"').click()
await expect(page.locator('text=+33 6 73 54 45 67')).toBeVisible()
})
})

View File

@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { defaultRatingInputOptions, InputBlockType } from 'models'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { createId } from '@paralleldrive/cuid2'
const boxSvg = `<svg
@ -33,10 +32,10 @@ test('options should work', async ({ page }) => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(typebotViewer(page).locator(`text=Send`)).toBeHidden()
await typebotViewer(page).locator(`text=8`).click()
await typebotViewer(page).locator(`text=Send`).click()
await expect(typebotViewer(page).locator(`text=8`)).toBeVisible()
await expect(page.locator(`text=Send`)).toBeHidden()
await page.locator(`text=8`).click()
await page.locator(`text=Send`).click()
await expect(page.locator(`text=8`)).toBeVisible()
await page.click('text=Rate from 0 to 10')
await page.click('text="10"')
await page.click('text="5"')
@ -48,14 +47,10 @@ test('options should work', async ({ page }) => {
await page.fill('[placeholder="Not likely at all"]', 'Not likely at all')
await page.fill('[placeholder="Extremely likely"]', 'Extremely likely')
await page.click('text="Restart"')
await expect(typebotViewer(page).locator(`text=8`)).toBeHidden()
await expect(typebotViewer(page).locator(`text=4`)).toBeHidden()
await expect(
typebotViewer(page).locator(`text=Not likely at all`)
).toBeVisible()
await expect(
typebotViewer(page).locator(`text=Extremely likely`)
).toBeVisible()
await typebotViewer(page).locator(`svg >> nth=4`).click()
await expect(typebotViewer(page).locator(`text=5`)).toBeVisible()
await expect(page.locator(`text=8`)).toBeHidden()
await expect(page.locator(`text=4`)).toBeHidden()
await expect(page.locator(`text=Not likely at all`)).toBeVisible()
await expect(page.locator(`text=Extremely likely`)).toBeVisible()
await page.locator(`svg >> nth=4`).click()
await expect(page.locator(`text=5`)).toBeVisible()
})

View File

@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { defaultTextInputOptions, InputBlockType } from 'models'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { createId } from '@paralleldrive/cuid2'
test.describe.parallel('Text input block', () => {
@ -22,11 +21,11 @@ test.describe.parallel('Text input block', () => {
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(
page.locator(
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
)
).toHaveAttribute('type', 'text')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled()
await page.click(`text=${defaultTextInputOptions.labels.placeholder}`)
await page.fill('#placeholder', 'Your name...')
@ -35,8 +34,8 @@ test.describe.parallel('Text input block', () => {
await page.click('text=Restart')
await expect(
typebotViewer(page).locator(`textarea[placeholder="Your name..."]`)
page.locator(`textarea[placeholder="Your name..."]`)
).toBeVisible()
await expect(typebotViewer(page).locator(`text=Go`)).toBeVisible()
await expect(page.getByRole('button', { name: 'Go' })).toBeVisible()
})
})

View File

@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { defaultUrlInputOptions, InputBlockType } from 'models'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { createId } from '@paralleldrive/cuid2'
test.describe('Url input block', () => {
@ -22,11 +21,13 @@ test.describe('Url input block', () => {
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(
page.locator(
`input[placeholder="${defaultUrlInputOptions.labels.placeholder}"]`
)
).toHaveAttribute('type', 'url')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await expect(
page.locator('typebot-standard').locator(`button`)
).toBeDisabled()
await page.click(`text=${defaultUrlInputOptions.labels.placeholder}`)
await page.fill('#placeholder', 'Your URL...')
@ -38,19 +39,15 @@ test.describe('Url input block', () => {
)
await page.click('text=Restart')
await typebotViewer(page)
await page
.locator(`input[placeholder="Your URL..."]`)
.fill('https://https://test')
await typebotViewer(page).locator('button >> text="Go"').click()
await expect(
typebotViewer(page).locator('text=Try again bro')
).toBeVisible()
await typebotViewer(page)
await page.locator('button >> text="Go"').click()
await expect(page.locator('text=Try again bro')).toBeVisible()
await page
.locator(`input[placeholder="Your URL..."]`)
.fill('https://website.com')
await typebotViewer(page).locator('button >> text="Go"').click()
await expect(
typebotViewer(page).locator('text=https://website.com')
).toBeVisible()
await page.locator('button >> text="Go"').click()
await expect(page.locator('text=https://website.com')).toBeVisible()
})
})

View File

@ -36,7 +36,7 @@ test.describe('Chatwoot block', () => {
await page.getByLabel('Phone number').fill('+33654347543')
await page.getByRole('button', { name: 'Preview', exact: true }).click()
await expect(
page.getByText("Chatwoot won't open in preview mode").nth(0)
page.getByText('Chatwoot block is not supported in preview').nth(0)
).toBeVisible()
})
})

View File

@ -1,6 +1,5 @@
import test, { expect, Page } from '@playwright/test'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'
@ -33,25 +32,17 @@ test.describe.parallel('Google sheets integration', () => {
)
await page.click('text=Preview')
await typebotViewer(page)
await page
.locator('typebot-standard')
.locator('input[placeholder="Type your email..."]')
.fill('georges@gmail.com')
await Promise.all([
page.waitForResponse(
(resp) =>
resp
.request()
.url()
.includes(
'/api/integrations/google-sheets/spreadsheets/1k_pIDw3YHl9tlZusbBVSBRY0PeRPd2H6t4Nj7rwnOtM/sheets/0'
) &&
resp.status() === 200 &&
resp.request().method() === 'POST'
),
typebotViewer(page)
.locator('input[placeholder="Type your email..."]')
.press('Enter'),
])
await page
.locator('typebot-standard')
.locator('input[placeholder="Type your email..."]')
.press('Enter')
await expect(
page.getByText('Succesfully inserted row in CRM > Sheet1').nth(0)
).toBeVisible()
})
test('Update row should work', async ({ page }) => {
@ -82,25 +73,17 @@ test.describe.parallel('Google sheets integration', () => {
)
await page.click('text=Preview')
await typebotViewer(page)
await page
.locator('typebot-standard')
.locator('input[placeholder="Type your email..."]')
.fill('test@test.com')
await Promise.all([
page.waitForResponse(
(resp) =>
resp
.request()
.url()
.includes(
'/api/integrations/google-sheets/spreadsheets/1k_pIDw3YHl9tlZusbBVSBRY0PeRPd2H6t4Nj7rwnOtM/sheets/0'
) &&
resp.status() === 200 &&
resp.request().method() === 'POST'
),
typebotViewer(page)
.locator('input[placeholder="Type your email..."]')
.press('Enter'),
])
await page
.locator('typebot-standard')
.locator('input[placeholder="Type your email..."]')
.press('Enter')
await expect(
page.getByText('Succesfully updated row in CRM > Sheet1').nth(0)
).toBeVisible()
})
test('Get row should work', async ({ page }) => {
@ -143,15 +126,17 @@ test.describe.parallel('Google sheets integration', () => {
await createNewVar(page, 'Last name')
await page.click('text=Preview')
await typebotViewer(page)
await page
.locator('typebot-standard')
.locator('input[placeholder="Type your email..."]')
.fill('test2@test.com')
await typebotViewer(page)
await page
.locator('typebot-standard')
.locator('input[placeholder="Type your email..."]')
.press('Enter')
await expect(typebotViewer(page).locator('text=Your name is:')).toHaveText(
/John|Fred|Georges/
)
await expect(
page.locator('typebot-standard').locator('text=Your name is:')
).toHaveText(/John|Fred|Georges/)
})
})

View File

@ -181,7 +181,7 @@ export const SendEmailSettings = ({ options, onOptionsChange }: Props) => {
</Flex>
{options.isBodyCode ? (
<CodeEditor
value={options.body ?? ''}
defaultValue={options.body ?? ''}
onChange={handleBodyChange}
lang="html"
/>

View File

@ -1,6 +1,5 @@
import test, { expect } from '@playwright/test'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'
@ -64,7 +63,7 @@ test.describe('Send email block', () => {
await page.fill('[data-testid="body-input"]', 'Here is my email')
await page.click('text=Preview')
await typebotViewer(page).locator('text=Go').click()
await page.locator('typebot-standard').locator('text=Go').click()
await expect(
page.locator('text=Emails are not sent in preview mode >> nth=0')
).toBeVisible()

View File

@ -223,7 +223,7 @@ export const WebhookSettings = ({
/>
{(options.isCustomBody ?? true) && (
<CodeEditor
value={localWebhook.body ?? ''}
defaultValue={localWebhook.body ?? ''}
lang="json"
onChange={handleBodyChange}
debounceTimeout={0}
@ -262,7 +262,7 @@ export const WebhookSettings = ({
</Button>
)}
{testResponse && (
<CodeEditor isReadOnly lang="json" value={testResponse} />
<CodeEditor isReadOnly lang="json" defaultValue={testResponse} />
)}
{(testResponse || options?.responseVariableMapping.length > 0) && (
<Accordion allowMultiple>

View File

@ -1,5 +1,4 @@
import test, { expect } from '@playwright/test'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'
@ -51,30 +50,33 @@ test.describe('Condition block', () => {
await page.fill('input[placeholder="Type a value..."]', '20')
await page.click('text=Preview')
await typebotViewer(page)
await page
.locator('typebot-standard')
.locator('input[placeholder="Type a number..."]')
.fill('15')
await typebotViewer(page).locator('text=Send').click()
await page.locator('typebot-standard').locator('text=Send').click()
await expect(
typebotViewer(page).locator('text=You are younger than 20')
page.locator('typebot-standard').getByText('You are younger than 20')
).toBeVisible()
await page.click('text=Restart')
await typebotViewer(page)
await page
.locator('typebot-standard')
.locator('input[placeholder="Type a number..."]')
.fill('45')
await typebotViewer(page).locator('text=Send').click()
await page.locator('typebot-standard').locator('text=Send').click()
await expect(
typebotViewer(page).locator('text=You are older than 20')
page.locator('typebot-standard').getByText('You are older than 20')
).toBeVisible()
await page.click('text=Restart')
await typebotViewer(page)
await page
.locator('typebot-standard')
.locator('input[placeholder="Type a number..."]')
.fill('90')
await typebotViewer(page).locator('text=Send').click()
await page.locator('typebot-standard').locator('text=Send').click()
await expect(
typebotViewer(page).locator('text=You are older than 80')
page.locator('typebot-standard').getByText('You are older than 80')
).toBeVisible()
})
})

View File

@ -1,5 +1,4 @@
import test, { expect } from '@playwright/test'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'
@ -20,7 +19,7 @@ test.describe('Redirect block', () => {
await page.fill('input[placeholder="Type a URL..."]', 'google.com')
await page.click('text=Preview')
await typebotViewer(page).locator('text=Go to URL').click()
await page.locator('typebot-standard').locator('text=Go to URL').click()
await expect(page).toHaveURL('https://www.google.com')
await page.goBack()
@ -30,7 +29,7 @@ test.describe('Redirect block', () => {
await page.click('text=Preview')
const [newPage] = await Promise.all([
context.waitForEvent('page'),
typebotViewer(page).locator('text=Go to URL').click(),
page.locator('typebot-standard').locator('text=Go to URL').click(),
])
await newPage.waitForLoadState()
await expect(newPage).toHaveURL('https://www.google.com')

View File

@ -41,8 +41,8 @@ export const ScriptSettings = ({ options, onOptionsChange }: Props) => {
<Stack>
<Text>Code:</Text>
<CodeEditor
value={options.content ?? ''}
lang="js"
defaultValue={options.content ?? ''}
lang="javascript"
onChange={handleCodeChange}
/>
</Stack>

View File

@ -1,5 +1,4 @@
import test, { expect } from '@playwright/test'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'
@ -20,7 +19,7 @@ test.describe('Script block', () => {
)
await page.click('text=Preview')
await typebotViewer(page).locator('text=Trigger code').click()
await page.getByRole('button', { name: 'Trigger code' }).click()
await expect(page).toHaveURL('https://www.google.com')
})
})

View File

@ -51,9 +51,9 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
{options.isCode ?? false ? (
<CodeEditor
value={options.expressionToEvaluate ?? ''}
defaultValue={options.expressionToEvaluate ?? ''}
onChange={handleExpressionChange}
lang="js"
lang="javascript"
/>
) : (
<Textarea

View File

@ -1,5 +1,4 @@
import test, { expect } from '@playwright/test'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'
@ -42,18 +41,19 @@ test.describe('Set variable block', () => {
await page.fill('textarea', '1000 + {{Total}}')
await page.click('text=Preview')
await typebotViewer(page)
await page
.locator('typebot-standard')
.locator('input[placeholder="Type a number..."]')
.fill('365')
await typebotViewer(page).locator('text=Send').click()
await page.locator('typebot-standard').locator('text=Send').click()
await expect(
typebotViewer(page).locator('text=Multiplication: 365000')
page.locator('typebot-standard').locator('text=Multiplication: 365000')
).toBeVisible()
await expect(
typebotViewer(page).locator('text=Custom var: Custom value')
page.locator('typebot-standard').locator('text=Custom var: Custom value')
).toBeVisible()
await expect(
typebotViewer(page).locator('text=Addition: 366000')
page.locator('typebot-standard').locator('text=Addition: 366000')
).toBeVisible()
})
})

View File

@ -1,5 +1,4 @@
import test, { expect } from '@playwright/test'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'
@ -35,7 +34,9 @@ test('should be configurable', async ({ page }) => {
await page.click('text=Group #2')
await page.click('text=Preview')
await expect(typebotViewer(page).locator('text=Second block')).toBeVisible()
await expect(
page.locator('typebot-standard').locator('text=Second block')
).toBeVisible()
await page.click('[aria-label="Close"]')
await page.click('text=Jump to Group #2 in My link typebot 2')
@ -44,9 +45,11 @@ test('should be configurable', async ({ page }) => {
await page.click('button >> text=Start')
await page.click('text=Preview')
await typebotViewer(page).locator('input').fill('Hello there!')
await typebotViewer(page).locator('input').press('Enter')
await expect(typebotViewer(page).locator('text=Hello there!')).toBeVisible()
await page.locator('typebot-standard').locator('input').fill('Hello there!')
await page.locator('typebot-standard').locator('input').press('Enter')
await expect(
page.locator('typebot-standard').locator('text=Hello there!')
).toBeVisible()
await page.click('[aria-label="Close"]')
await page.click('text=Jump to Start in My link typebot 2')
@ -61,5 +64,7 @@ test('should be configurable', async ({ page }) => {
await page.click('button >> text=Hello')
await page.click('text=Preview')
await expect(typebotViewer(page).locator('text=Hello world')).toBeVisible()
await expect(
page.locator('typebot-standard').locator('text=Hello world')
).toBeVisible()
})

View File

@ -1,5 +1,4 @@
import test, { expect } from '@playwright/test'
import { typebotViewer } from 'utils/playwright/testHelpers'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'
@ -17,10 +16,14 @@ test.describe('Wait block', () => {
await page.getByRole('textbox', { name: 'Seconds to wait for:' }).fill('3')
await page.click('text=Preview')
await typebotViewer(page).locator('text=Wait now').click()
await page.getByRole('button', { name: 'Wait now' }).click()
await page.waitForTimeout(1000)
await expect(typebotViewer(page).locator('text="Hi there!"')).toBeHidden()
await expect(
page.locator('typebot-standard').locator('text="Hi there!"')
).toBeHidden()
await page.waitForTimeout(3000)
await expect(typebotViewer(page).locator('text="Hi there!"')).toBeVisible()
await expect(
page.locator('typebot-standard').locator('text="Hi there!"')
).toBeVisible()
})
})

View File

@ -50,6 +50,7 @@ export const parseNewTypebot = ({
return {
folderId,
name,
version: '3',
workspaceId,
groups: [startGroup],
edges: [],

View File

@ -1,64 +1,49 @@
import {
chakra,
Modal,
ModalBody,
ModalContent,
ModalOverlay,
useColorModeValue,
useDisclosure,
} from '@chakra-ui/react'
import { TypebotViewer } from 'bot-engine'
import { chakra, useColorModeValue } from '@chakra-ui/react'
import { Popup } from '@typebot.io/react'
import { useUser } from '@/features/account'
import { AnswerInput, Typebot } from 'models'
import { Typebot } from 'models'
import React, { useEffect, useRef, useState } from 'react'
import { getViewerUrl, sendRequest } from 'utils'
import { sendRequest } from 'utils'
import confetti from 'canvas-confetti'
import { useToast } from '@/hooks/useToast'
import { parseTypebotToPublicTypebot } from '@/features/publish'
import { useRouter } from 'next/router'
type Props = { totalTypebots: number }
export const OnboardingModal = ({ totalTypebots }: Props) => {
const { push } = useRouter()
const botPath = useColorModeValue(
'/bots/onboarding.json',
'/bots/onboarding-dark.json'
)
const { user, updateUser } = useUser()
const { isOpen, onOpen, onClose } = useDisclosure()
const [typebot, setTypebot] = useState<Typebot>()
const confettiCanvaContainer = useRef<HTMLCanvasElement | null>(null)
const confettiCanon = useRef<confetti.CreateTypes>()
const [chosenCategories, setChosenCategories] = useState<string[]>([])
const [openedOnce, setOpenedOnce] = useState(false)
const { showToast } = useToast()
useEffect(() => {
fetchTemplate()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const isNewUser =
user &&
new Date(user?.createdAt as unknown as string).toDateString() ===
new Date().toDateString() &&
totalTypebots === 0
useEffect(() => {
if (openedOnce) return
const isNewUser =
user &&
new Date(user?.createdAt as unknown as string).toDateString() ===
new Date().toDateString() &&
totalTypebots === 0
if (isNewUser) {
onOpen()
setOpenedOnce(true)
const fetchTemplate = async () => {
const { data, error } = await sendRequest(botPath)
if (error)
return showToast({ title: error.name, description: error.message })
setTypebot(data as Typebot)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user])
fetchTemplate()
}, [botPath, showToast])
useEffect(() => {
initConfettis()
return () => {
window.removeEventListener('message', handleIncomingMessage)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [confettiCanvaContainer.current])
}, [])
const initConfettis = () => {
if (!confettiCanvaContainer.current || confettiCanon.current) return
@ -66,48 +51,41 @@ export const OnboardingModal = ({ totalTypebots }: Props) => {
resize: true,
useWorker: true,
})
window.addEventListener('message', handleIncomingMessage)
}
const handleIncomingMessage = (message: MessageEvent) => {
if (message.data.from === 'typebot') {
if (message.data.action === 'shootConfettis' && confettiCanon.current)
shootConfettis(confettiCanon.current)
}
const handleBotEnd = () => {
setTimeout(() => {
push('/typebots/create', { query: { isFirstBot: true } })
}, 2000)
}
const fetchTemplate = async () => {
const { data, error } = await sendRequest(botPath)
if (error)
return showToast({ title: error.name, description: error.message })
setTypebot(data as Typebot)
}
const handleNewAnswer = async (answer: AnswerInput) => {
const isName = answer.variableId === 'cl126f4hf000i2e6d8zvzc3t1'
const isCompany = answer.variableId === 'cl126jqww000w2e6dq9yv4ifq'
const isCategories = answer.variableId === 'cl126mo3t001b2e6dvyi16bkd'
const isOtherCategories = answer.variableId === 'cl126q38p001q2e6d0hj23f6b'
if (isName) updateUser({ name: answer.content })
if (isCompany) updateUser({ company: answer.content })
const handleNewAnswer = async (answer: {
message: string
blockId: string
}) => {
const isName = answer.blockId === 'cl126820m000g2e6dfleq78bt'
const isCompany = answer.blockId === 'cl126jioz000v2e6dwrk1f2cb'
const isCategories = answer.blockId === 'cl126lb8v00142e6duv5qe08l'
const isOtherCategories = answer.blockId === 'cl126pv7n001o2e6dajltc4qz'
const answeredAllQuestions =
isOtherCategories || (isCategories && !answer.message.includes('Other'))
if (answeredAllQuestions && confettiCanon.current)
shootConfettis(confettiCanon.current)
if (isName) updateUser({ name: answer.message })
if (isCompany) updateUser({ company: answer.message })
if (isCategories) {
const onboardingCategories = answer.content.split(', ')
const onboardingCategories = answer.message.split(', ')
updateUser({ onboardingCategories })
setChosenCategories(onboardingCategories)
}
if (isOtherCategories)
updateUser({
onboardingCategories: [...chosenCategories, answer.content],
onboardingCategories: [...chosenCategories, answer.message],
})
}
return (
<Modal
size="3xl"
isOpen={isOpen}
onClose={onClose}
blockScrollOnMount={false}
>
<>
<chakra.canvas
ref={confettiCanvaContainer}
pos="fixed"
@ -118,23 +96,18 @@ export const OnboardingModal = ({ totalTypebots }: Props) => {
zIndex={9999}
pointerEvents="none"
/>
<ModalOverlay />
<ModalContent h="85vh">
<ModalBody p="10">
{typebot && (
<TypebotViewer
apiHost={getViewerUrl()}
typebot={parseTypebotToPublicTypebot(typebot)}
predefinedVariables={{
Name: user?.name?.split(' ')[0] ?? undefined,
}}
onNewAnswer={handleNewAnswer}
style={{ borderRadius: '0.25rem' }}
/>
)}
</ModalBody>
</ModalContent>
</Modal>
{typebot && (
<Popup
typebot={typebot}
prefilledVariables={{
Name: user?.name?.split(' ')[0] ?? undefined,
}}
defaultOpen={isNewUser}
onAnswer={handleNewAnswer}
onEnd={handleBotEnd}
/>
)}
</>
)
}

View File

@ -56,6 +56,7 @@ const duplicateTypebot = (
return {
typebot: {
...typebot,
version: '3',
id,
name: `${typebot.name} copy`,
publicId: null,

View File

@ -11,32 +11,25 @@ import {
UseToastOptions,
VStack,
} from '@chakra-ui/react'
import { TypebotViewer } from 'bot-engine'
import { useToast } from '@/hooks/useToast'
import { useEditor } from '../providers/EditorProvider'
import { useGraph } from '@/features/graph'
import { useTypebot } from '../providers/TypebotProvider'
import { Log } from 'db'
import React, { useMemo, useState } from 'react'
import { getViewerUrl } from 'utils'
import React, { useState } from 'react'
import { headerHeight } from '../constants'
import { parseTypebotToPublicTypebot } from '@/features/publish'
import { Standard } from '@typebot.io/react'
import { ChatReply } from 'models'
import { useToast } from '@/hooks/useToast'
export const PreviewDrawer = () => {
const isDark = useColorMode().colorMode === 'dark'
const { typebot } = useTypebot()
const { typebot, save, isSavingLoading } = useTypebot()
const { setRightPanel, startPreviewAtGroup } = useEditor()
const { setPreviewingEdge } = useGraph()
const { setPreviewingBlock } = useGraph()
const [isResizing, setIsResizing] = useState(false)
const [width, setWidth] = useState(500)
const [isResizeHandleVisible, setIsResizeHandleVisible] = useState(false)
const [restartKey, setRestartKey] = useState(0)
const publicTypebot = useMemo(
() => (typebot ? { ...parseTypebotToPublicTypebot(typebot) } : undefined),
[typebot]
)
const { showToast } = useToast()
const handleMouseDown = () => {
@ -54,15 +47,19 @@ export const PreviewDrawer = () => {
}
useEventListener('mouseup', handleMouseUp)
const handleRestartClick = () => setRestartKey((key) => key + 1)
const handleRestartClick = async () => {
await save()
setRestartKey((key) => key + 1)
}
const handleCloseClick = () => {
setPreviewingEdge(undefined)
setPreviewingBlock(undefined)
setRightPanel(undefined)
}
const handleNewLog = (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) =>
showToast(log as UseToastOptions)
const handleNewLogs = (logs: ChatReply['logs']) => {
logs?.forEach((log) => showToast(log as UseToastOptions))
}
return (
<Flex
@ -92,29 +89,25 @@ export const PreviewDrawer = () => {
<VStack w="full" spacing={4}>
<Flex justifyContent={'space-between'} w="full">
<Button onClick={handleRestartClick}>Restart</Button>
<Button onClick={handleRestartClick} isLoading={isSavingLoading}>
Restart
</Button>
<CloseButton onClick={handleCloseClick} />
</Flex>
{publicTypebot && (
<Flex
borderWidth={'1px'}
borderRadius={'lg'}
h="full"
w="full"
{typebot && (
<Standard
key={restartKey + (startPreviewAtGroup ?? '')}
pointerEvents={isResizing ? 'none' : 'auto'}
>
<TypebotViewer
apiHost={getViewerUrl()}
typebot={publicTypebot}
onNewGroupVisible={setPreviewingEdge}
onNewLog={handleNewLog}
startGroupId={startPreviewAtGroup}
isPreview
style={{ borderRadius: '10px' }}
/>
</Flex>
typebot={typebot}
startGroupId={startPreviewAtGroup}
onNewInputBlock={setPreviewingBlock}
onNewLogs={handleNewLogs}
style={{
borderWidth: '1px',
borderRadius: '0.25rem',
pointerEvents: isResizing ? 'none' : 'auto',
}}
/>
)}
</VStack>
</Flex>

View File

@ -20,7 +20,7 @@ import { useRouter } from 'next/router'
import React, { useState } from 'react'
import { isDefined, isNotDefined } from 'utils'
import { EditableTypebotName } from './EditableTypebotName'
import { getBubbleActions } from 'typebot-js'
import { open as openSupportBubble } from '@typebot.io/js'
import Link from 'next/link'
import { isCloudProdInstance } from '@/utils/helpers'
import { headerHeight } from '../../constants'
@ -70,7 +70,7 @@ export const TypebotHeader = () => {
const handleHelpClick = () => {
isCloudProdInstance
? getBubbleActions().open()
? openSupportBubble()
: window.open('https://docs.typebot.io', '_blank')
}

View File

@ -6,7 +6,6 @@ import {
importTypebotInDatabase,
} from 'utils/playwright/databaseActions'
import {
typebotViewer,
waitForSuccessfulDeleteRequest,
waitForSuccessfulPostRequest,
waitForSuccessfulPutRequest,
@ -200,16 +199,16 @@ test('Preview from group should work', async ({ page }) => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('[aria-label="Preview bot from this group"] >> nth=1')
await expect(
typebotViewer(page).locator('text="Hello this is group 1"')
page.locator('typebot-standard').locator('text="Hello this is group 1"')
).toBeVisible()
await page.click('[aria-label="Preview bot from this group"] >> nth=2')
await expect(
typebotViewer(page).locator('text="Hello this is group 2"')
page.locator('typebot-standard').locator('text="Hello this is group 2"')
).toBeVisible()
await page.click('[aria-label="Close"]')
await page.click('text="Preview"')
await expect(
typebotViewer(page).locator('text="Hello this is group 1"')
page.locator('typebot-standard').locator('text="Hello this is group 1"')
).toBeVisible()
})

View File

@ -1,5 +1,4 @@
import { DashboardFolder, WorkspaceRole } from 'db'
import { env } from 'utils'
import {
Flex,
Heading,
@ -11,7 +10,6 @@ import {
Wrap,
} from '@chakra-ui/react'
import { useTypebotDnd } from '../TypebotDndProvider'
import { useUser } from '@/features/account'
import React, { useState } from 'react'
import { BackButton } from './BackButton'
import { OnboardingModal } from '../../dashboard/components/OnboardingModal'
@ -26,14 +24,12 @@ import { CreateFolderButton } from './CreateFolderButton'
import { ButtonSkeleton, FolderButton } from './FolderButton'
import { TypebotButton } from './TypebotButton'
import { TypebotCardOverlay } from './TypebotButtonOverlay'
import { isCloudProdInstance } from '@/utils/helpers'
type Props = { folder: DashboardFolder | null }
const dragDistanceTolerance = 20
export const FolderContent = ({ folder }: Props) => {
const { user } = useUser()
const { workspace, currentRole } = useWorkspace()
const [isCreatingFolder, setIsCreatingFolder] = useState(false)
const {
@ -160,14 +156,7 @@ export const FolderContent = ({ folder }: Props) => {
return (
<Flex w="full" flex="1" justify="center">
{typebots &&
!isTypebotLoading &&
user &&
isCloudProdInstance &&
folder === null &&
env('E2E_TEST') !== 'true' && (
<OnboardingModal totalTypebots={typebots.length} />
)}
{typebots && <OnboardingModal totalTypebots={typebots.length} />}
<Stack w="1000px" spacing={6}>
<Skeleton isLoaded={folder?.name !== undefined}>
<Heading as="h1">{folder?.name}</Heading>

View File

@ -64,6 +64,7 @@ export const BlockNode = ({
setFocusedGroupId,
previewingEdge,
isReadOnly,
previewingBlock,
} = useGraph()
const { mouseOverBlock, setMouseOverBlock } = useBlockDnd()
const { typebot, updateBlock } = useTypebot()
@ -76,7 +77,10 @@ export const BlockNode = ({
)
const blockRef = useRef<HTMLDivElement | null>(null)
const isPreviewing = isConnecting || previewingEdge?.to.blockId === block.id
const isPreviewing =
isConnecting ||
previewingEdge?.to.blockId === block.id ||
previewingBlock?.id === block.id
const onDrag = (position: NodePosition) => {
if (block.type === 'start' || !onMouseDown) return

View File

@ -61,6 +61,7 @@ const NonMemoizedDraggableGroupNode = ({
connectingIds,
setConnectingIds,
previewingEdge,
previewingBlock,
isReadOnly,
focusedGroupId,
setFocusedGroupId,
@ -91,6 +92,7 @@ const NonMemoizedDraggableGroupNode = ({
}, [group.title])
const isPreviewing =
previewingBlock?.groupId === group.id ||
previewingEdge?.from.groupId === group.id ||
(previewingEdge?.to.groupId === group.id &&
isNotDefined(previewingEdge.to.blockId))

View File

@ -62,11 +62,18 @@ export type Endpoint = {
export type GroupsCoordinates = IdMap<Coordinates>
type PreviewingBlock = {
id: string
groupId: string
}
const graphContext = createContext<{
graphPosition: Position
setGraphPosition: Dispatch<SetStateAction<Position>>
connectingIds: ConnectingIds | null
setConnectingIds: Dispatch<SetStateAction<ConnectingIds | null>>
previewingBlock?: PreviewingBlock
setPreviewingBlock: Dispatch<SetStateAction<PreviewingBlock | undefined>>
previewingEdge?: Edge
setPreviewingEdge: Dispatch<SetStateAction<Edge | undefined>>
sourceEndpoints: IdMap<Endpoint>
@ -99,6 +106,7 @@ export const GraphProvider = ({
)
const [connectingIds, setConnectingIds] = useState<ConnectingIds | null>(null)
const [previewingEdge, setPreviewingEdge] = useState<Edge>()
const [previewingBlock, setPreviewingBlock] = useState<PreviewingBlock>()
const [sourceEndpoints, setSourceEndpoints] = useState<IdMap<Endpoint>>({})
const [targetEndpoints, setTargetEndpoints] = useState<IdMap<Endpoint>>({})
const [openedBlockId, setOpenedBlockId] = useState<string>()
@ -139,6 +147,8 @@ export const GraphProvider = ({
isReadOnly,
focusedGroupId,
setFocusedGroupId,
setPreviewingBlock,
previewingBlock,
}}
>
{children}

View File

@ -11,6 +11,7 @@ import {
MenuItem,
useDisclosure,
ButtonProps,
useColorModeValue,
} from '@chakra-ui/react'
import {
ChevronLeftIcon,
@ -27,6 +28,7 @@ import { ChangePlanModal, isFreePlan, LimitReached } from '@/features/billing'
import { timeSince } from '@/utils/helpers'
export const PublishButton = (props: ButtonProps) => {
const warningTextColor = useColorModeValue('red.300', 'red.600')
const { workspace } = useWorkspace()
const { push, query } = useRouter()
const { isOpen, onOpen, onClose } = useDisclosure()
@ -71,12 +73,17 @@ export const PublishButton = (props: ButtonProps) => {
type={LimitReached.FILE_INPUT}
/>
<Tooltip
borderRadius="md"
hasArrow
placement="bottom-end"
label={
<Stack>
<Text>There are non published changes.</Text>
{!publishedTypebot?.version ? (
<Text color={warningTextColor} fontWeight="semibold">
This will deploy your bot with an updated engine. Make sure to
test it properly in preview mode before publishing.
</Text>
) : (
<Text>There are non published changes.</Text>
)}
<Text fontStyle="italic">
Published version from{' '}
{publishedTypebot &&

View File

@ -23,6 +23,7 @@ import {
IframeModal,
WixModal,
} from './modals'
import { OtherModal } from './modals/OtherModal'
export type ModalProps = {
publicId: string
@ -139,7 +140,7 @@ export const integrationsList = [
<EmbedButton
logo={<OtherLogo height={100} width="70px" />}
label="Other"
Modal={JavascriptModal}
Modal={OtherModal}
{...props}
/>
),

View File

@ -0,0 +1,77 @@
import { AlertInfo } from '@/components/AlertInfo'
import { ChevronLeftIcon } from '@/components/icons'
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
HStack,
IconButton,
Heading,
ModalCloseButton,
ModalBody,
Stack,
ModalFooter,
} from '@chakra-ui/react'
import { capitalize } from 'utils'
import { EmbedTypeMenu } from './EmbedTypeMenu/EmbedTypeMenu'
type Props = {
selectedEmbedType: 'standard' | 'popup' | 'bubble' | undefined
titlePrefix: string
isOpen: boolean
isPublished: boolean
children: React.ReactNode
onClose: () => void
onSelectEmbedType: (type: 'standard' | 'popup' | 'bubble' | undefined) => void
}
export const EmbedModal = ({
selectedEmbedType,
isOpen,
isPublished,
titlePrefix,
children,
onSelectEmbedType,
onClose,
}: Props) => (
<Modal
isOpen={isOpen}
onClose={onClose}
size={!selectedEmbedType ? '2xl' : 'xl'}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{selectedEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => onSelectEmbedType(undefined)}
/>
)}
<Heading size="md">
{titlePrefix}{' '}
{selectedEmbedType && `- ${capitalize(selectedEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody as={Stack} spacing={4} pt={0}>
{!isPublished && (
<AlertInfo>You need to publish your bot first.</AlertInfo>
)}
{!selectedEmbedType ? (
<EmbedTypeMenu onSelectEmbedType={onSelectEmbedType} />
) : (
children
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)

View File

@ -0,0 +1,33 @@
import { MotionStack } from '@/components/MotionStack'
import { Stack, Button, StackProps, Text, ButtonProps } from '@chakra-ui/react'
import { BubbleIllustration } from './illustrations/BubbleIllustration'
type Props = StackProps & Pick<ButtonProps, 'isDisabled'>
export const BubbleMenuButton = (props: Props) => {
return (
<MotionStack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
whiteSpace={'normal'}
spacing="6"
flex="1"
height="250px"
animate="default"
whileHover="animateBubbles"
transition={{ staggerChildren: 0.1 }}
{...props}
>
<BubbleIllustration />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Bubble
</Text>
<Text textColor="gray.500">Embed in a chat bubble</Text>
</Stack>
</MotionStack>
)
}

View File

@ -0,0 +1,18 @@
import { HStack } from '@chakra-ui/react'
import { BubbleMenuButton } from './BubbleMenuButton'
import { PopupMenuButton } from './PopupMenuButton'
import { StandardMenuButton } from './StandardMenuButton'
type Props = {
onSelectEmbedType: (type: 'standard' | 'popup' | 'bubble') => void
}
export const EmbedTypeMenu = ({ onSelectEmbedType }: Props) => {
return (
<HStack spacing={4}>
<StandardMenuButton onClick={() => onSelectEmbedType('standard')} />
<PopupMenuButton onClick={() => onSelectEmbedType('popup')} />
<BubbleMenuButton onClick={() => onSelectEmbedType('bubble')} />
</HStack>
)
}

View File

@ -0,0 +1,35 @@
import { MotionStack } from '@/components/MotionStack'
import { Stack, Button, StackProps, Text, ButtonProps } from '@chakra-ui/react'
import { PopupIllustration } from './illustrations/PopupIllustration'
type Props = StackProps & Pick<ButtonProps, 'isDisabled'>
export const PopupMenuButton = (props: Props) => {
return (
<MotionStack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
whiteSpace={'normal'}
spacing="6"
height="250px"
flex="1"
animate="default"
whileHover="animateBubbles"
transition={{ staggerChildren: 0.1 }}
{...props}
>
<PopupIllustration />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Popup
</Text>
<Text textColor="gray.500">
Embed in a popup on top of your website
</Text>
</Stack>
</MotionStack>
)
}

View File

@ -0,0 +1,33 @@
import { MotionStack } from '@/components/MotionStack'
import { Stack, Button, StackProps, Text, ButtonProps } from '@chakra-ui/react'
import { StandardIllustration } from './illustrations/StandardIllustration'
type Props = StackProps & Pick<ButtonProps, 'isDisabled'>
export const StandardMenuButton = (props: Props) => {
return (
<MotionStack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
whiteSpace={'normal'}
spacing="6"
height="250px"
flex="1"
animate="default"
whileHover="animateBubbles"
transition={{ staggerChildren: 0.1 }}
{...props}
>
<StandardIllustration />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Standard
</Text>
<Text textColor="gray.500">Embed in a container on your site</Text>
</Stack>
</MotionStack>
)
}

View File

@ -0,0 +1,77 @@
import { colors } from '@/lib/theme'
import { useColorModeValue } from '@chakra-ui/react'
import { motion } from 'framer-motion'
import { animationVariants } from './animationVariants'
export const BubbleIllustration = () => {
const bubbleColor = useColorModeValue('white', colors.blue[100])
return (
<svg
width="100"
viewBox="0 0 500 500"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="500"
height="500"
rx="20"
fill={useColorModeValue(colors.gray['200'], colors.gray['900'])}
/>
<rect x="164" y="59" width="287" height="305" rx="10" fill="#0042DA" />
<motion.rect
x="227"
y="91"
width="156"
height="34"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="227"
y="134"
width="156"
height="65"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.circle
cx="198"
cy="228"
r="20"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="227"
y="208"
width="156"
height="40"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.circle
cx="412"
cy="277"
r="20"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="253"
y="257"
width="130"
height="40"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<circle cx="411" cy="430" r="40" fill="#0042DA" />
</svg>
)
}

View File

@ -0,0 +1,75 @@
import { colors } from '@/lib/theme'
import { useColorModeValue } from '@chakra-ui/react'
import { motion } from 'framer-motion'
import { animationVariants } from './animationVariants'
export const PopupIllustration = () => {
const bubbleColor = useColorModeValue('white', colors.blue[100])
return (
<svg
width="100"
viewBox="0 0 500 500"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="500"
height="500"
rx="20"
fill={useColorModeValue(colors.gray['400'], colors.gray['900'])}
/>
<rect x="105" y="77" width="290" height="352" rx="10" fill="#0042DA" />
<motion.rect
x="171"
y="117"
width="156"
height="34"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="171"
y="160"
width="156"
height="65"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.circle
cx="142"
cy="254"
r="20"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="171"
y="234"
width="156"
height="40"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.circle
cx="356"
cy="303"
r="20"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="197"
y="283"
width="130"
height="40"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
</svg>
)
}

View File

@ -0,0 +1,79 @@
import { colors } from '@/lib/theme'
import { useColorModeValue } from '@chakra-ui/react'
import { motion } from 'framer-motion'
import { animationVariants } from './animationVariants'
export const StandardIllustration = () => {
const gray = useColorModeValue(colors.gray[400], colors.gray[700])
const bubbleColor = useColorModeValue('white', colors.blue[100])
return (
<svg
viewBox="0 0 500 500"
width="100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="500"
height="500"
rx="20"
fill={useColorModeValue(colors.gray['200'], colors.gray['900'])}
/>
<rect x="49" y="49" width="108" height="109" rx="10" fill={gray} />
<rect x="188" y="74" width="263" height="25" rx="5" fill={gray} />
<rect x="188" y="111" width="263" height="25" rx="5" fill={gray} />
<rect x="49" y="189" width="402" height="262" rx="10" fill="#0042DA" />
<motion.rect
variants={animationVariants}
x="121"
y="217"
width="218"
height="34"
rx="10"
fill={bubbleColor}
/>
<motion.rect
variants={animationVariants}
x="121"
y="260"
width="218"
height="65"
rx="10"
fill={bubbleColor}
/>
<motion.circle
variants={animationVariants}
cx="93"
cy="354"
r="20"
fill={bubbleColor}
/>
<motion.rect
variants={animationVariants}
x="121"
y="334"
width="218"
height="40"
rx="10"
fill={bubbleColor}
/>
<motion.circle
variants={animationVariants}
cx="407"
cy="410"
r="20"
fill={bubbleColor}
/>
<motion.rect
variants={animationVariants}
x="250"
y="390"
width="130"
height="40"
rx="10"
fill={bubbleColor}
/>
</svg>
)
}

View File

@ -0,0 +1,8 @@
import { Variants } from 'framer-motion'
export const animationVariants: Variants = {
animateBubbles: {
opacity: [0, 1],
},
default: { opacity: 1 },
}

View File

@ -1,41 +0,0 @@
import prettier from 'prettier/standalone'
import parserHtml from 'prettier/parser-html'
import { BubbleParams } from 'typebot-js'
import { parseInitBubbleCode, typebotJsHtml } from '../params'
import { useTypebot } from '@/features/editor'
import { CodeEditor } from '@/components/CodeEditor'
import { env, getViewerUrl } from 'utils'
import { FlexProps } from '@chakra-ui/react'
type ChatEmbedCodeProps = {
withStarterVariables?: boolean
onCopied?: () => void
} & Pick<BubbleParams, 'button' | 'proactiveMessage'>
export const ChatEmbedCode = ({
proactiveMessage,
button,
}: ChatEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
createSnippet({
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
typebot?.publicId
}`,
button,
proactiveMessage,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
const createSnippet = (params: BubbleParams): string => {
const jsCode = parseInitBubbleCode(params)
return `${typebotJsHtml}
<script>${jsCode}</script>`
}

View File

@ -1,223 +0,0 @@
import {
StackProps,
Stack,
Heading,
HStack,
Input,
Flex,
FormControl,
FormLabel,
NumberInput,
NumberInputField,
Switch,
Text,
Image,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
} from '@chakra-ui/react'
import { useTypebot } from '@/features/editor'
import { useUser } from '@/features/account'
import { useState, useEffect } from 'react'
import { BubbleParams } from 'typebot-js'
import { ColorPicker } from '@/components/ColorPicker'
type ChatEmbedSettingsProps = {
onUpdateSettings: (
windowSettings: Pick<BubbleParams, 'button' | 'proactiveMessage'>
) => void
}
export const ChatEmbedSettings = ({
onUpdateSettings,
...props
}: ChatEmbedSettingsProps & StackProps) => {
const { user } = useUser()
const { typebot } = useTypebot()
const [proactiveMessageChecked, setProactiveMessageChecked] = useState(false)
const [isCustomIconChecked, setIsCustomIconChecked] = useState(false)
const [rememberProMessageChecked] = useState(true)
const [customIconInputValue, setCustomIconInputValue] = useState('')
const [inputValues, setInputValues] = useState({
messageDelay: '0',
messageContent: 'I have a question for you!',
avatarUrl: typebot?.theme.chat.hostAvatar?.url ?? user?.image ?? '',
})
const [bubbleColor, setBubbleColor] = useState(
typebot?.theme.chat.buttons.backgroundColor ?? '#0042DA'
)
const [bubbleIconColor, setIconBubbleColor] = useState(
typebot?.theme.chat.buttons.color ?? '#FFFFFF'
)
useEffect(() => {
if (proactiveMessageChecked) {
onUpdateSettings({
button: {
color: bubbleColor,
iconUrl: isCustomIconChecked ? customIconInputValue : undefined,
iconColor:
bubbleIconColor === '#FFFFFF' ? undefined : bubbleIconColor,
},
proactiveMessage: {
delay: parseInt(inputValues.messageDelay) * 1000,
textContent: inputValues.messageContent,
avatarUrl: inputValues.avatarUrl,
rememberClose: rememberProMessageChecked,
},
})
} else {
onUpdateSettings({
button: {
color: bubbleColor,
iconUrl: isCustomIconChecked ? customIconInputValue : undefined,
iconColor:
bubbleIconColor === '#FFFFFF' ? undefined : bubbleIconColor,
},
proactiveMessage: undefined,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
inputValues,
bubbleColor,
rememberProMessageChecked,
customIconInputValue,
bubbleIconColor,
proactiveMessageChecked,
isCustomIconChecked,
])
return (
<Stack {...props} spacing="4">
<Heading fontSize="md" fontWeight="semibold">
Chat bubble settings
</Heading>
<Flex justify="space-between" align="center">
<Text>Button color</Text>
<ColorPicker
initialColor={bubbleColor}
onColorChange={setBubbleColor}
/>
</Flex>
<HStack justify="space-between">
<Text>Icon color</Text>
<ColorPicker
initialColor={bubbleIconColor}
onColorChange={setIconBubbleColor}
/>
</HStack>
<HStack justifyContent="space-between">
<FormLabel htmlFor="custom-icon" mb="0" flexShrink={0}>
Custom button icon?
</FormLabel>
<Switch
id="custom-icon"
onChange={() => setIsCustomIconChecked(!isCustomIconChecked)}
isChecked={isCustomIconChecked}
/>
</HStack>
{isCustomIconChecked && (
<>
<HStack pl="4">
<Text>Url:</Text>
<Input
placeholder={'Paste image link (.png, .svg)'}
value={customIconInputValue}
onChange={(e) => setCustomIconInputValue(e.target.value)}
minW="0"
/>
</HStack>
</>
)}
<Flex alignItems="center">
<FormControl
display="flex"
alignItems="center"
w="full"
justifyContent="space-between"
>
<FormLabel htmlFor="fullscreen-option" mb="0">
Enable popup message?
</FormLabel>
<Switch
id="fullscreen-option"
onChange={() =>
setProactiveMessageChecked(!proactiveMessageChecked)
}
isChecked={proactiveMessageChecked}
/>
</FormControl>
</Flex>
{proactiveMessageChecked && (
<>
<Flex pl="4">
<HStack
bgColor="white"
shadow="md"
rounded="md"
p="3"
maxW="280px"
spacing={4}
>
{inputValues.avatarUrl && (
// eslint-disable-next-line jsx-a11y/alt-text
<Image src={inputValues.avatarUrl} w="40px" rounded="full" />
)}
<Text>{inputValues.messageContent}</Text>
</HStack>
</Flex>
<Flex justify="space-between" align="center" pl="4">
<Text>Appearance delay</Text>
<NumberInput
onChange={(messageDelay) =>
setInputValues({
...inputValues,
messageDelay,
})
}
value={inputValues.messageDelay}
min={0}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Flex>
<Flex justify="space-between" align="center" pl="4">
<Text>Avatar URL</Text>
<Input
type="text"
onChange={(e) =>
setInputValues({
...inputValues,
avatarUrl: e.target.value,
})
}
value={inputValues.avatarUrl}
placeholder={'Paste image link (.png, .jpg)'}
/>
</Flex>
<Flex justify="space-between" align="center" pl="4">
<Text>Message content</Text>
<Input
type="text"
onChange={(e) =>
setInputValues({
...inputValues,
messageContent: e.target.value,
})
}
value={inputValues.messageContent}
/>
</Flex>
</>
)}
</Stack>
)
}

View File

@ -1,59 +0,0 @@
import { FlexProps } from '@chakra-ui/react'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { parseInitContainerCode, typebotJsHtml } from '../params'
import { IframeParams } from 'typebot-js'
import { useTypebot } from '@/features/editor'
import { CodeEditor } from '@/components/CodeEditor'
import { env, getViewerUrl } from 'utils'
type ContainerEmbedCodeProps = {
widthLabel: string
heightLabel: string
withStarterVariables?: boolean
onCopied?: () => void
}
export const ContainerEmbedCode = ({
widthLabel,
heightLabel,
}: ContainerEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
parseSnippet({
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
typebot?.publicId
}`,
heightLabel,
widthLabel,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
type SnippetProps = IframeParams &
Pick<ContainerEmbedCodeProps, 'widthLabel' | 'heightLabel'>
const parseSnippet = ({
url,
customDomain,
backgroundColor,
hiddenVariables,
...embedProps
}: SnippetProps): string => {
const jsCode = parseInitContainerCode({
customDomain,
hiddenVariables,
backgroundColor,
url,
})
return `${typebotJsHtml}
<div id="typebot-container" style="width: ${embedProps.widthLabel}; height: ${embedProps.heightLabel};"></div>
<script>${jsCode}</script>`
}

View File

@ -1,120 +0,0 @@
import {
StackProps,
Stack,
Flex,
Heading,
FormControl,
FormLabel,
Switch,
Input,
HStack,
Text,
} from '@chakra-ui/react'
import { DropdownList } from '@/components/DropdownList'
import { useState, useEffect } from 'react'
type StandardEmbedWindowSettingsProps = {
onUpdateWindowSettings: (windowSettings: {
heightLabel: string
widthLabel: string
}) => void
}
export const StandardEmbedWindowSettings = ({
onUpdateWindowSettings,
...props
}: StandardEmbedWindowSettingsProps & StackProps) => {
const [fullscreen, setFullscreen] = useState(false)
const [inputValues, setInputValues] = useState({
widthValue: '100',
widthType: '%',
heightValue: '600',
heightType: 'px',
})
useEffect(() => {
onUpdateWindowSettings({
widthLabel: fullscreen
? '100%'
: inputValues.widthValue + inputValues.widthType,
heightLabel: fullscreen
? '100vh'
: inputValues.heightValue + inputValues.heightType,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputValues, fullscreen])
const handleWidthTypeSelect = (widthType: string) =>
setInputValues({ ...inputValues, widthType })
const handleHeightTypeSelect = (heightType: string) =>
setInputValues({ ...inputValues, heightType })
return (
<Stack {...props}>
<Flex alignItems="center" justifyContent="space-between">
<Heading fontSize="md" fontWeight="semibold" style={{ flexShrink: 0 }}>
Window settings
</Heading>
<FormControl
display="flex"
alignItems="center"
w="full"
justifyContent="flex-end"
>
<FormLabel htmlFor="fullscreen-option" mb="1">
Set to fullscreen?
</FormLabel>
<Switch
id="fullscreen-option"
onChange={() => setFullscreen(!fullscreen)}
isChecked={fullscreen}
/>
</FormControl>
</Flex>
{!fullscreen && (
<>
<Flex justify="space-between" align="center" mb="2">
<Text>Width</Text>
<HStack>
<Input
onChange={(e) =>
setInputValues({
...inputValues,
widthValue: e.target.value,
})
}
w="70px"
value={inputValues.widthValue}
/>
<DropdownList<string>
items={['px', '%']}
onItemSelect={handleWidthTypeSelect}
currentItem={inputValues.widthType}
/>
</HStack>
</Flex>
<Flex justify="space-between" align="center" mb="2">
<Text>Height</Text>
<HStack>
<Input
onChange={(e) =>
setInputValues({
...inputValues,
heightValue: e.target.value,
})
}
w="70px"
value={inputValues.heightValue}
/>
<DropdownList<string>
items={['px', '%']}
onItemSelect={handleHeightTypeSelect}
currentItem={inputValues.heightType}
/>
</HStack>
</Flex>
</>
)}
</Stack>
)
}

View File

@ -1,37 +0,0 @@
import { FlexProps } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { PopupParams } from 'typebot-js'
import { env, getViewerUrl } from 'utils'
import { parseInitPopupCode, typebotJsHtml } from '../params'
import { CodeEditor } from '@/components/CodeEditor'
type PopupEmbedCodeProps = {
delay?: number
withStarterVariables?: boolean
onCopied?: () => void
}
export const PopupEmbedCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
createSnippet({
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
typebot?.publicId
}`,
delay,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
const createSnippet = (params: PopupParams): string => {
const jsCode = parseInitPopupCode(params)
return `${typebotJsHtml}
<script>${jsCode}</script>`
}

View File

@ -1,66 +0,0 @@
import {
StackProps,
Stack,
Flex,
Heading,
NumberInput,
NumberInputField,
Switch,
HStack,
NumberIncrementStepper,
NumberDecrementStepper,
} from '@chakra-ui/react'
import { useState, useEffect } from 'react'
import { PopupParams } from 'typebot-js'
type PopupEmbedSettingsProps = {
onUpdateSettings: (windowSettings: Pick<PopupParams, 'delay'>) => void
}
export const PopupEmbedSettings = ({
onUpdateSettings,
...props
}: PopupEmbedSettingsProps & StackProps) => {
const [isEnabled, setIsEnabled] = useState(false)
const [inputValue, setInputValue] = useState(0)
useEffect(() => {
onUpdateSettings({
delay: isEnabled ? inputValue * 1000 : undefined,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputValue, isEnabled])
return (
<Stack {...props}>
<Flex alignItems="center" justifyContent="space-between">
<Heading fontSize="md" fontWeight="semibold">
Popup settings
</Heading>
</Flex>
<Flex justify="space-between" align="center" mb="2">
<HStack>
<p>Appearance delay</p>
<Switch
isChecked={isEnabled}
onChange={(e) => setIsEnabled(e.target.checked)}
/>
</HStack>
{isEnabled && (
<NumberInput
onChange={(_, val) => setInputValue(val)}
value={inputValue}
min={0}
>
<NumberInputField />
<NumberIncrementStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberIncrementStepper>
</NumberInput>
)}
</Flex>
</Stack>
)
}

View File

@ -1,161 +0,0 @@
import { FlexProps } from '@chakra-ui/react'
import React from 'react'
import { BubbleParams, IframeParams, PopupParams } from 'typebot-js'
import {
parseInitBubbleCode,
parseInitContainerCode,
parseInitPopupCode,
} from './params'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { useTypebot } from '@/features/editor'
import { env, getViewerUrl } from 'utils'
import { CodeEditor } from '@/components/CodeEditor'
type StandardReactDivProps = { widthLabel: string; heightLabel: string }
export const StandardReactDiv = ({
widthLabel,
heightLabel,
}: StandardReactDivProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
parseContainerSnippet({
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
typebot?.publicId
}`,
heightLabel,
widthLabel,
}),
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="js" isReadOnly />
}
type SnippetProps = IframeParams &
Pick<StandardReactDivProps, 'widthLabel' | 'heightLabel'>
const parseContainerSnippet = ({
url,
customDomain,
backgroundColor,
hiddenVariables,
...embedProps
}: SnippetProps): string => {
const jsCode = parseInitContainerCode({
url,
customDomain,
backgroundColor,
hiddenVariables,
})
return `import Typebot from "typebot-js";
const Component = () => {
useEffect(()=> {
${jsCode}
}, [])
return <div id="typebot-container" style={{width: "${embedProps.widthLabel}", height: "${embedProps.heightLabel}"}} />
}`
}
type PopupEmbedCodeProps = {
delay?: number
withStarterVariables?: boolean
}
export const PopupReactCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
parsePopupSnippet({
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
typebot?.publicId
}`,
delay,
}),
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="js" isReadOnly />
}
const parsePopupSnippet = ({
url,
customDomain,
backgroundColor,
hiddenVariables,
delay,
}: PopupParams): string => {
const jsCode = parseInitPopupCode({
url,
customDomain,
backgroundColor,
hiddenVariables,
delay,
})
return `import Typebot from "typebot-js";
const Component = () => {
useEffect(()=> {
${jsCode}
}, [])
return <></>;
}`
}
type ChatEmbedCodeProps = {
withStarterVariables?: boolean
} & Pick<BubbleParams, 'button' | 'proactiveMessage'>
export const ChatReactCode = ({
proactiveMessage,
button,
}: ChatEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
parseBubbleSnippet({
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
typebot?.publicId
}`,
button,
proactiveMessage,
}),
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="js" isReadOnly />
}
const parseBubbleSnippet = ({
url,
customDomain,
backgroundColor,
hiddenVariables,
proactiveMessage,
button,
}: BubbleParams): string => {
const jsCode = parseInitBubbleCode({
url,
customDomain,
backgroundColor,
hiddenVariables,
proactiveMessage,
button,
})
return `import Typebot from "typebot-js";
const Component = () => {
useEffect(()=> {
${jsCode}
}, [])
return <></>
}`
}

View File

@ -1,140 +0,0 @@
import {
BubbleParams,
ButtonParams,
IframeParams,
PopupParams,
ProactiveMessageParams,
} from 'typebot-js'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { isDefined } from 'utils'
const parseStringParam = (fieldName: string, fieldValue?: string) =>
fieldValue ? `${fieldName}: "${fieldValue}",` : ``
const parseNonStringParam = (
fieldName: string,
fieldValue?: number | boolean
) => (isDefined(fieldValue) ? `${fieldName}: ${fieldValue},` : ``)
const parseCustomDomain = (domain?: string): string =>
parseStringParam('customDomain', domain)
const parseHiddenVariables = (
variables: { [key: string]: string | undefined } | undefined
): string => (variables ? `hiddenVariables: ${JSON.stringify(variables)},` : ``)
const parseBackgroundColor = (bgColor?: string): string =>
parseStringParam('backgroundColor', bgColor)
const parseDelay = (delay?: number) => parseNonStringParam('delay', delay)
const parseButton = (button?: ButtonParams): string => {
if (!button) return ''
const iconUrlString = parseStringParam('iconUrl', button.iconUrl)
const buttonColorstring = parseStringParam('color', button.color)
const buttonIconColorString = parseStringParam('iconColor', button.iconColor)
return `button: {${iconUrlString}${buttonColorstring}${buttonIconColorString}},`
}
const parseProactiveMessage = (
proactiveMessage?: ProactiveMessageParams
): string => {
if (!proactiveMessage) return ``
const { avatarUrl, textContent, delay } = proactiveMessage
const avatarUrlString = parseStringParam('avatarUrl', avatarUrl)
const textContentString = parseStringParam('textContent', textContent)
const delayString = parseNonStringParam('delay', delay)
return `proactiveMessage: {${avatarUrlString}${textContentString}${delayString}},`
}
const parseIframeParams = ({
customDomain,
hiddenVariables,
backgroundColor,
}: Pick<
IframeParams,
'customDomain' | 'hiddenVariables' | 'backgroundColor'
>) => ({
customDomainString: parseCustomDomain(customDomain),
hiddenVariablesString: parseHiddenVariables(hiddenVariables),
bgColorString: parseBackgroundColor(backgroundColor),
})
const parsePopupParams = ({ delay }: Pick<PopupParams, 'delay'>) => ({
delayString: parseDelay(delay),
})
const parseBubbleParams = ({
button,
proactiveMessage,
}: Pick<BubbleParams, 'button' | 'proactiveMessage'>) => ({
proactiveMessageString: parseProactiveMessage(proactiveMessage),
buttonString: parseButton(button),
})
export const parseInitContainerCode = ({
url,
customDomain,
backgroundColor,
hiddenVariables,
}: IframeParams) => {
const { customDomainString, hiddenVariablesString, bgColorString } =
parseIframeParams({
customDomain,
hiddenVariables,
backgroundColor,
})
return prettier.format(
`Typebot.initContainer("typebot-container", {
url: "${url}",${bgColorString}${customDomainString}${hiddenVariablesString}
});`,
{ parser: 'babel', plugins: [parserBabel] }
)
}
export const parseInitPopupCode = ({
url,
customDomain,
hiddenVariables,
backgroundColor,
delay,
}: PopupParams) => {
const { customDomainString, hiddenVariablesString, bgColorString } =
parseIframeParams({
customDomain,
hiddenVariables,
backgroundColor,
})
const { delayString } = parsePopupParams({ delay })
return prettier.format(
`var typebotCommands = Typebot.initPopup({url: "${url}",${delayString}${bgColorString}${customDomainString}${hiddenVariablesString}});`,
{ parser: 'babel', plugins: [parserBabel] }
)
}
export const parseInitBubbleCode = ({
url,
customDomain,
hiddenVariables,
backgroundColor,
button,
proactiveMessage,
}: BubbleParams) => {
const { customDomainString, hiddenVariablesString, bgColorString } =
parseIframeParams({
customDomain,
hiddenVariables,
backgroundColor,
})
const { buttonString, proactiveMessageString } = parseBubbleParams({
button,
proactiveMessage,
})
return prettier.format(
`var typebotCommands = Typebot.initBubble({url: "${url}",${bgColorString}${customDomainString}${hiddenVariablesString}${proactiveMessageString}${buttonString}});`,
{ parser: 'babel', plugins: [parserBabel] }
)
}
export const typebotJsHtml = `<script src="https://unpkg.com/typebot-js@2.2"></script>`

View File

@ -1,155 +0,0 @@
import { HStack, Button, Text, Stack } from '@chakra-ui/react'
type ChooseEmbedTypeListProps = {
onSelectEmbedType: (type: 'standard' | 'popup' | 'bubble') => void
disabledTypes?: ('standard' | 'popup' | 'bubble')[]
}
export const ChooseEmbedTypeList = ({
onSelectEmbedType,
disabledTypes = [],
}: ChooseEmbedTypeListProps) => {
return (
<HStack mx="auto">
<Stack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
style={{ width: '225px', height: '270px' }}
onClick={() => onSelectEmbedType('standard')}
whiteSpace={'normal'}
spacing="6"
isDisabled={disabledTypes.includes('standard')}
>
<StandardEmbedSvg />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Standard
</Text>
<Text textColor="gray.500">Embed in a container on your site</Text>
</Stack>
</Stack>
<Stack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
style={{ width: '225px', height: '270px' }}
onClick={() => onSelectEmbedType('popup')}
whiteSpace={'normal'}
spacing="6"
isDisabled={disabledTypes.includes('popup')}
>
<PopupEmbedSvg />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Popup
</Text>
<Text textColor="gray.500">
Embed in a popup window on top of your website
</Text>
</Stack>
</Stack>
<Stack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
style={{ width: '225px', height: '270px' }}
onClick={() => onSelectEmbedType('bubble')}
whiteSpace={'normal'}
spacing="6"
isDisabled={disabledTypes.includes('bubble')}
>
<BubbleEmbedSvg />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Bubble
</Text>
<Text textColor="gray.500">
Embed in a chat bubble on the corner of your site
</Text>
</Stack>
</Stack>
</HStack>
)
}
const StandardEmbedSvg = () => (
<svg
width="100"
height="100"
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="100" height="100" rx="5" fill="#0042DA" />
<rect x="10" y="28" width="80" height="42" rx="6" fill="#FF8E20" />
<circle cx="18" cy="37" r="5" fill="white" />
<rect x="24" y="33" width="45" height="8" rx="4" fill="white" />
<circle cx="18" cy="61" r="5" fill="white" />
<rect x="24" y="57" width="45" height="8" rx="4" fill="white" />
<rect x="31" y="45" width="45" height="8" rx="4" fill="white" />
<circle cx="82" cy="49" r="5" fill="white" />
<rect x="10" y="9" width="80" height="1" rx="0.5" fill="white" />
<rect x="10" y="14" width="80" height="1" rx="0.5" fill="white" />
<rect x="10" y="19" width="80" height="1" rx="0.5" fill="white" />
<rect x="10" y="80" width="80" height="1" rx="0.5" fill="white" />
<rect x="10" y="85" width="80" height="1" rx="0.5" fill="white" />
<rect x="10" y="90" width="80" height="1" rx="0.5" fill="white" />
</svg>
)
const PopupEmbedSvg = () => (
<svg
width="100"
height="100"
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="100" height="100" rx="5" fill="#0042DA" />
<rect x="19" y="20" width="63" height="63" rx="6" fill="#FF8E20" />
<circle cx="25.7719" cy="33.7719" r="3.77193" fill="white" />
<rect x="31" y="30" width="27" height="8" rx="4" fill="white" />
<circle
r="3.77193"
transform="matrix(-1 0 0 1 75.2281 43.7719)"
fill="white"
/>
<rect
width="22"
height="8"
rx="4"
transform="matrix(-1 0 0 1 70 40)"
fill="white"
/>
<rect
x="31.0527"
y="52"
width="26.9473"
height="7.54386"
rx="3.77193"
fill="white"
/>
<circle cx="25.7719" cy="67.7719" r="3.77193" fill="white" />
<rect x="31" y="64" width="27" height="8" rx="4" fill="white" />
</svg>
)
const BubbleEmbedSvg = () => (
<svg
width="100"
height="100"
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="100" height="100" rx="5" fill="#0042DA" />
<circle cx="85.5" cy="85.5" r="7.5" fill="#FF8E20" />
</svg>
)

View File

@ -1,132 +0,0 @@
import { CodeEditor } from '@/components/CodeEditor'
import { OrderedList, ListItem, Tag } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { env, getViewerUrl } from 'utils'
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
import {
parseInitContainerCode,
typebotJsHtml,
} from '../../codeSnippets/params'
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
import { ModalProps } from '../../EmbedButton'
type GtmInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
publicId: string
}
export const GtmInstructions = ({ type, publicId }: GtmInstructionsProps) => {
switch (type) {
case 'standard': {
return <StandardInstructions publicId={publicId} />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
}
}
const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
const [windowSizes, setWindowSizes] = useState({
height: '100%',
width: '100%',
})
const jsCode = parseInitContainerCode({
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${publicId}`,
})
const headCode = `${typebotJsHtml}
<script>
${jsCode}
</script>`
const elementCode = `<div id="typebot-container" style="height: ${windowSizes.height}; width: ${windowSizes.width}"></div>`
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your GTM account dashboard, click on <Tag>Add a new tag</Tag>
</ListItem>
<ListItem>
Choose Custom <Tag>HTML tag</Tag> type
</ListItem>
<ListItem>
Paste the code below:
<CodeEditor value={headCode} mt={2} isReadOnly lang="html" />
</ListItem>
<ListItem>
On your webpage, you need to have an element on which the typebot will
go. It needs to have the id <Tag>typebot-container</Tag>:
<StandardEmbedWindowSettings
my={4}
onUpdateWindowSettings={(sizes) =>
setWindowSizes({
height: sizes.heightLabel,
width: sizes.widthLabel,
})
}
/>
<CodeEditor value={elementCode} mt={2} isReadOnly lang="html" />
</ListItem>
</OrderedList>
)
}
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your GTM account dashboard, click on <Tag>Add a new tag</Tag>
</ListItem>
<ListItem>
Choose Custom <Tag>HTML tag</Tag> type
</ListItem>
<ListItem>
Paste the code below:
<PopupEmbedSettings
my={4}
onUpdateSettings={(settings) => setInputValue(settings.delay)}
/>
<PopupEmbedCode delay={inputValue} />
</ListItem>
</OrderedList>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your GTM account dashboard, click on <Tag>Add a new tag</Tag>
</ListItem>
<ListItem>
Choose Custom <Tag>HTML tag</Tag> type
</ListItem>
<ListItem>
Paste the code below:
<ChatEmbedSettings
onUpdateSettings={(settings) => setInputValues({ ...settings })}
/>
<ChatEmbedCode my={4} {...inputValues} />
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,30 @@
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { EmbedModal } from '../../EmbedModal'
import { isDefined } from '@udecode/plate-common'
import { GtmInstructions } from './instructions/GtmInstructions'
export const GtmModal = ({
isOpen,
onClose,
isPublished,
publicId,
}: ModalProps) => {
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<EmbedModal
titlePrefix="GTM"
isOpen={isOpen}
onClose={onClose}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
{isDefined(selectedEmbedType) && (
<GtmInstructions type={selectedEmbedType} publicId={publicId} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1 @@
export * from './GtmModal'

View File

@ -1,70 +0,0 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
IconButton,
Heading,
HStack,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
import { capitalize } from 'utils'
import { GtmInstructions } from './GtmInstructions'
import { AlertInfo } from '@/components/AlertInfo'
export const GtmModal = ({
isOpen,
onClose,
isPublished,
publicId,
}: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{chosenEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => setChosenEmbedType(undefined)}
/>
)}
<Heading size="md">
Javascript {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
{!chosenEmbedType ? (
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
) : (
<GtmInstructions type={chosenEmbedType} publicId={publicId} />
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,45 @@
import { useTypebot } from '@/features/editor'
import { OrderedList, ListItem, Stack, Text, Code } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
import { JavascriptBubbleSnippet } from '../../Javascript/JavascriptBubbleSnippet'
export const GtmBubbleInstructions = () => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your GTM account dashboard, click on <Code>Add a new tag</Code>
</ListItem>
<ListItem>
Choose Custom <Code>HTML tag</Code> type
</ListItem>
<ListItem>
<Stack spacing={4}>
<BubbleSettings
theme={theme}
previewMessage={previewMessage}
defaultPreviewMessageAvatar={
typebot?.theme.chat.hostAvatar?.url ?? ''
}
onThemeChange={setTheme}
onPreviewMessageChange={setPreviewMessage}
/>
<Text>Paste the code below:</Text>
<JavascriptBubbleSnippet
theme={theme}
previewMessage={previewMessage}
/>
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,22 @@
import { GtmBubbleInstructions } from './GtmBubbleInstructions'
import { GtmPopupInstructions } from './GtmPopupInstructions'
import { GtmStandardInstructions } from './GtmStandardInstructions'
type GtmInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
publicId: string
}
export const GtmInstructions = ({ type, publicId }: GtmInstructionsProps) => {
switch (type) {
case 'standard': {
return <GtmStandardInstructions publicId={publicId} />
}
case 'popup': {
return <GtmPopupInstructions />
}
case 'bubble': {
return <GtmBubbleInstructions />
}
}
}

View File

@ -0,0 +1,30 @@
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { JavascriptPopupSnippet } from '../../Javascript/JavascriptPopupSnippet'
export const GtmPopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your GTM account dashboard, click on <Code>Add a new tag</Code>
</ListItem>
<ListItem>
Choose Custom <Code>HTML tag</Code> type
</ListItem>
<ListItem>
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) =>
setInputValue(settings.autoShowDelay)
}
/>
<Text>Paste the code below:</Text>
<JavascriptPopupSnippet autoShowDelay={inputValue} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,62 @@
import { CodeEditor } from '@/components/CodeEditor'
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
import { Typebot } from 'models'
import { useState } from 'react'
import { StandardSettings } from '../../../settings/StandardSettings'
import {
parseStandardElementCode,
parseStandardHeadCode,
} from '../../Javascript/JavascriptStandardSnippet'
export const GtmStandardInstructions = ({
publicId,
}: Pick<Typebot, 'publicId'>) => {
const [windowSizes, setWindowSizes] = useState<{
height: string
width?: string
}>({
height: '100%',
width: '100%',
})
const headCode = parseStandardHeadCode(publicId)
const elementCode = parseStandardElementCode(
windowSizes.width,
windowSizes.height
)
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your GTM account dashboard, click on <Code>Add a new tag</Code>
</ListItem>
<ListItem>
Choose Custom <Code>HTML tag</Code> type
</ListItem>
<ListItem>
<Stack spacing={4}>
<Text>Paste the code below:</Text>
<CodeEditor value={headCode} isReadOnly lang="html" />
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4}>
<StandardSettings
onUpdateWindowSettings={(sizes) =>
setWindowSizes({
height: sizes.heightLabel,
width: sizes.widthLabel,
})
}
/>
<Text>
On your web page, you need to have an element on which the typebot
will go:
</Text>
<CodeEditor value={elementCode} isReadOnly lang="html" />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -11,12 +11,15 @@ import {
Text,
} from '@chakra-ui/react'
import { useState } from 'react'
import { StandardEmbedWindowSettings } from '../codeSnippets/Container/EmbedSettings'
import { IframeEmbedCode } from '../codeSnippets/Iframe/EmbedCode'
import { ModalProps } from '../EmbedButton'
import { ModalProps } from '../../EmbedButton'
import { StandardSettings } from '../../settings/StandardSettings'
import { IframeSnippet } from './IframeSnippet'
export const IframeModal = ({ isPublished, isOpen, onClose }: ModalProps) => {
const [inputValues, setInputValues] = useState({
const [inputValues, setInputValues] = useState<{
heightLabel: string
widthLabel?: string
}>({
heightLabel: '100%',
widthLabel: '100%',
})
@ -27,17 +30,21 @@ export const IframeModal = ({ isPublished, isOpen, onClose }: ModalProps) => {
<ModalContent>
<ModalHeader>Iframe</ModalHeader>
<ModalCloseButton />
<ModalBody as={Stack} spacing={4}>
<ModalBody as={Stack} spacing={4} pt="0">
{!isPublished && (
<AlertInfo>You need to publish your bot first.</AlertInfo>
)}
<Text>Paste this anywhere in your HTML code:</Text>
<StandardEmbedWindowSettings
<StandardSettings
onUpdateWindowSettings={(settings) =>
setInputValues({ ...settings })
}
/>
<IframeEmbedCode {...inputValues} />
<Text>Paste this anywhere in your HTML code:</Text>
<IframeSnippet
widthLabel={inputValues.widthLabel ?? '100%'}
heightLabel={inputValues.heightLabel}
/>
</ModalBody>
<ModalFooter />
</ModalContent>

View File

@ -2,21 +2,24 @@ import { FlexProps } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor'
import { env, getViewerUrl } from 'utils'
import { CodeEditor } from '@/components/CodeEditor'
import prettier from 'prettier/standalone'
import parserHtml from 'prettier/parser-html'
type Props = {
widthLabel: string
heightLabel: string
onCopied?: () => void
}
export const IframeEmbedCode = ({
widthLabel,
heightLabel,
}: Props & FlexProps) => {
} & FlexProps
export const IframeSnippet = ({ widthLabel, heightLabel }: Props) => {
const { typebot } = useTypebot()
const src = `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
typebot?.publicId
}`
const code = `<iframe src="${src}" width="${widthLabel}" height="${heightLabel}" style="border: none"></iframe>`
const code = prettier.format(
`<iframe src="${src}" style="border: none; width='${widthLabel}'; height='${heightLabel}'"></iframe>`,
{ parser: 'html', plugins: [parserHtml] }
)
return <CodeEditor value={code} lang="html" isReadOnly />
}

View File

@ -0,0 +1,36 @@
import prettier from 'prettier/standalone'
import parserHtml from 'prettier/parser-html'
import { parseInitBubbleCode } from '../../snippetParsers'
import { useTypebot } from '@/features/editor'
import { CodeEditor } from '@/components/CodeEditor'
import { BubbleProps } from '@typebot.io/js'
import { isCloudProdInstance } from '@/utils/helpers'
import { env, getViewerUrl } from 'utils'
type Props = Pick<BubbleProps, 'theme' | 'previewMessage'>
export const JavascriptBubbleSnippet = ({ theme, previewMessage }: Props) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`<script type="module">${parseInitBubbleCode({
typebot: typebot?.publicId ?? '',
apiHost: isCloudProdInstance
? undefined
: env('VIEWER_INTERNAL_URL') ?? getViewerUrl(),
theme: {
...theme,
chatWindow: {
backgroundColor: typebot?.theme.general.background.content ?? '#fff',
},
},
previewMessage,
})}</script>`,
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}

View File

@ -1,89 +0,0 @@
import { Stack, Tag, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { ContainerEmbedCode } from '../../codeSnippets/Container/EmbedCode'
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
type JavascriptInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
}
export const JavascriptInstructions = ({
type,
}: JavascriptInstructionsProps) => {
switch (type) {
case 'standard': {
return <StandardInstructions />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
}
}
const StandardInstructions = () => {
const [inputValues, setInputValues] = useState({
heightLabel: '100%',
widthLabel: '100%',
})
return (
<Stack spacing={4}>
<Text>
Paste this anywhere in the <Tag>body</Tag>
</Text>
<StandardEmbedWindowSettings
onUpdateWindowSettings={(settings) => setInputValues({ ...settings })}
/>
<ContainerEmbedCode withStarterVariables={true} {...inputValues} mt={4} />
</Stack>
)
}
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<Stack spacing={4}>
<Text>
Paste this anywhere in the <Tag>body</Tag>
</Text>
<PopupEmbedSettings
mb={4}
onUpdateSettings={(settings) => setInputValue(settings.delay)}
/>
<PopupEmbedCode delay={inputValue} />
</Stack>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<Stack spacing={4}>
<Text>
Paste this anywhere in the <Tag>body</Tag>
</Text>
<ChatEmbedSettings
onUpdateSettings={(settings) => setInputValues({ ...settings })}
/>
<ChatEmbedCode {...inputValues} />
</Stack>
)
}

View File

@ -1,69 +1,29 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
IconButton,
Heading,
HStack,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
import { capitalize } from 'utils'
import { JavascriptInstructions } from './JavascriptInstructions'
import { AlertInfo } from '@/components/AlertInfo'
import { EmbedModal } from '../../EmbedModal'
import { isDefined } from '@udecode/plate-common'
import { JavascriptInstructions } from './instructions/JavascriptInstructions'
export const JavascriptModal = ({
isOpen,
onClose,
isPublished,
}: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
<EmbedModal
titlePrefix="Javascript"
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{chosenEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => setChosenEmbedType(undefined)}
/>
)}
<Heading size="md">
Javascript {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
{!chosenEmbedType ? (
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
) : (
<JavascriptInstructions type={chosenEmbedType} />
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
{isDefined(selectedEmbedType) && (
<JavascriptInstructions type={selectedEmbedType} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1,33 @@
import { useTypebot } from '@/features/editor'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { parseInitPopupCode } from '../../snippetParsers'
import { CodeEditor } from '@/components/CodeEditor'
import { PopupProps } from '@typebot.io/js'
import { isCloudProdInstance } from '@/utils/helpers'
import { env, getViewerUrl } from 'utils'
type Props = Pick<PopupProps, 'autoShowDelay'>
export const JavascriptPopupSnippet = ({ autoShowDelay }: Props) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
createSnippet({
typebot: typebot?.publicId ?? '',
apiHost: isCloudProdInstance
? undefined
: env('VIEWER_INTERNAL_URL') ?? getViewerUrl(),
autoShowDelay,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
const createSnippet = (params: PopupProps): string => {
const jsCode = parseInitPopupCode(params)
return `<script type="module">${jsCode}</script>`
}

View File

@ -0,0 +1,51 @@
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { parseInitStandardCode } from '../../snippetParsers'
import { useTypebot } from '@/features/editor'
import { CodeEditor } from '@/components/CodeEditor'
import { isCloudProdInstance } from '@/utils/helpers'
import { env, getViewerUrl } from 'utils'
type Props = {
widthLabel?: string
heightLabel?: string
}
export const JavascriptStandardSnippet = ({
widthLabel,
heightLabel,
}: Props) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`${parseStandardHeadCode(typebot?.publicId)}
${parseStandardElementCode(widthLabel, heightLabel)}`,
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
export const parseStandardHeadCode = (publicId?: string | null) =>
prettier.format(
`<script type="module">${parseInitStandardCode({
typebot: publicId ?? '',
apiHost: isCloudProdInstance
? undefined
: env('VIEWER_INTERNAL_URL') ?? getViewerUrl(),
})}</script>`,
{ parser: 'html', plugins: [parserHtml] }
)
export const parseStandardElementCode = (width?: string, height?: string) => {
if (!width && !height) return '<typebot-standard></typebot-standard>'
return prettier.format(
`<typebot-standard style="${width ? `width: ${width}; ` : ''}${
height ? `height: ${height}; ` : ''
}"></typebot-standard>`,
{ parser: 'html', plugins: [parserHtml] }
)
}

View File

@ -0,0 +1,43 @@
import { useTypebot } from '@/features/editor'
import { Stack, Code, Text } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { Typebot } from 'models'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { JavascriptBubbleSnippet } from '../JavascriptBubbleSnippet'
export const parseDefaultBubbleTheme = (typebot?: Typebot) => ({
button: {
backgroundColor: typebot?.theme.chat.buttons.backgroundColor,
iconColor: typebot?.theme.chat.buttons.color,
},
previewMessage: {
backgroundColor: typebot?.theme.general.background.content ?? 'white',
textColor: 'black',
},
})
export const JavascriptBubbleInstructions = () => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
return (
<Stack spacing={4}>
<BubbleSettings
theme={theme}
previewMessage={previewMessage}
defaultPreviewMessageAvatar={typebot?.theme.chat.hostAvatar?.url ?? ''}
onThemeChange={setTheme}
onPreviewMessageChange={setPreviewMessage}
/>
<Text>
Paste this anywhere in the <Code>{'<body>'}</Code>:
</Text>
<JavascriptBubbleSnippet theme={theme} previewMessage={previewMessage} />
</Stack>
)
}

View File

@ -0,0 +1,23 @@
import { JavascriptBubbleInstructions } from './JavascriptBubbleInstructions'
import { JavascriptPopupInstructions } from './JavascriptPopupInstructions'
import { JavascriptStandardInstructions } from './JavascriptStandardInstructions'
type JavascriptInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
}
export const JavascriptInstructions = ({
type,
}: JavascriptInstructionsProps) => {
switch (type) {
case 'standard': {
return <JavascriptStandardInstructions />
}
case 'popup': {
return <JavascriptPopupInstructions />
}
case 'bubble': {
return <JavascriptBubbleInstructions />
}
}
}

View File

@ -0,0 +1,20 @@
import { Stack, Code, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { JavascriptPopupSnippet } from '../JavascriptPopupSnippet'
export const JavascriptPopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) => setInputValue(settings.autoShowDelay)}
/>
<Text>
Paste this anywhere in the <Code>{'<body>'}</Code>:
</Text>
<JavascriptPopupSnippet autoShowDelay={inputValue} />
</Stack>
)
}

View File

@ -0,0 +1,26 @@
import { Stack, Code, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { StandardSettings } from '../../../settings/StandardSettings'
import { JavascriptStandardSnippet } from '../JavascriptStandardSnippet'
export const JavascriptStandardInstructions = () => {
const [inputValues, setInputValues] = useState<{
heightLabel: string
widthLabel?: string
}>({
heightLabel: '100%',
widthLabel: '100%',
})
return (
<Stack spacing={4}>
<StandardSettings
onUpdateWindowSettings={(settings) => setInputValues({ ...settings })}
/>
<Text>
Paste this anywhere in the <Code>{'<body>'}</Code>:
</Text>
<JavascriptStandardSnippet {...inputValues} />
</Stack>
)
}

View File

@ -10,11 +10,13 @@ import {
ModalBody,
OrderedList,
ListItem,
Tag,
Code,
InputGroup,
Input,
InputRightElement,
ModalFooter,
Text,
Stack,
} from '@chakra-ui/react'
import { env, getViewerUrl } from 'utils'
import { ModalProps } from '../EmbedButton'
@ -37,28 +39,30 @@ export const NotionModal = ({
{!isPublished && (
<AlertInfo mb="4">You need to publish your bot first.</AlertInfo>
)}
<OrderedList spacing={3}>
<OrderedList spacing={4}>
<ListItem>
Type <Tag>/embed</Tag>
Type <Code>/embed</Code>
</ListItem>
<ListItem>
Paste your typebot URL
<InputGroup size="md" mt={2}>
<Input
pr="4.5rem"
type={'text'}
defaultValue={`${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
}/${publicId}`}
/>
<InputRightElement width="4.5rem">
<CopyButton
textToCopy={`${
<Stack>
<Text>Paste your typebot URL</Text>
<InputGroup size="sm">
<Input
type={'text'}
defaultValue={`${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
}/${publicId}`}
/>
</InputRightElement>
</InputGroup>
<InputRightElement width="60px">
<CopyButton
size="sm"
textToCopy={`${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
}/${publicId}`}
/>
</InputRightElement>
</InputGroup>
</Stack>
</ListItem>
</OrderedList>
</ModalBody>

View File

@ -0,0 +1,25 @@
import React, { useState } from 'react'
import { isDefined } from '@udecode/plate-common'
import { EmbedModal } from '../EmbedModal'
import { JavascriptInstructions } from './Javascript/instructions/JavascriptInstructions'
import { ModalProps } from '../EmbedButton'
export const OtherModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<EmbedModal
titlePrefix="Other"
isOpen={isOpen}
onClose={onClose}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
{isDefined(selectedEmbedType) && (
<JavascriptInstructions type={selectedEmbedType} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1,11 @@
import { CodeEditor } from '@/components/CodeEditor'
export const InstallReactPackageSnippet = () => {
return (
<CodeEditor
value={`npm install @typebot.io/js @typebot.io/react`}
isReadOnly
lang="shell"
/>
)
}

View File

@ -0,0 +1,31 @@
import { CodeEditor } from '@/components/CodeEditor'
import { useTypebot } from '@/features/editor'
import { BubbleProps } from '@typebot.io/js'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { parseReactBubbleProps } from '../../snippetParsers'
export const ReactBubbleSnippet = ({
theme,
previewMessage,
}: Pick<BubbleProps, 'theme' | 'previewMessage'>) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`import { Bubble } from "@typebot.io/react";
const App = () => {
return <Bubble ${parseReactBubbleProps({
typebot: typebot?.publicId ?? '',
theme,
previewMessage,
})}/>
}`,
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="javascript" isReadOnly />
}

View File

@ -1,95 +0,0 @@
import { CodeEditor } from '@/components/CodeEditor'
import { Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
import {
StandardReactDiv,
PopupReactCode,
ChatReactCode,
} from '../../codeSnippets/ReactCode'
type Props = {
type: 'standard' | 'popup' | 'bubble'
}
export const ReactInstructions = ({ type }: Props) => {
switch (type) {
case 'standard': {
return <StandardInstructions />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
}
}
const StandardInstructions = () => {
const [inputValues, setInputValues] = useState({
heightLabel: '100%',
widthLabel: '100%',
})
return (
<Stack spacing={4}>
<InstallPackageInstruction />
<StandardEmbedWindowSettings
onUpdateWindowSettings={(settings) => setInputValues({ ...settings })}
/>
<Text>Insert the typebot container</Text>
<StandardReactDiv {...inputValues} />
</Stack>
)
}
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<Stack spacing={4}>
<InstallPackageInstruction />
<PopupEmbedSettings
onUpdateSettings={(settings) => setInputValue(settings.delay)}
/>
<Text>Initialize the typebot</Text>
<PopupReactCode withStarterVariables={true} delay={inputValue} />
</Stack>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<Stack spacing={4}>
<InstallPackageInstruction />
<ChatEmbedSettings
onUpdateSettings={(settings) => setInputValues({ ...settings })}
/>
<Text>Initialize the typebot</Text>
<ChatReactCode withStarterVariables={true} {...inputValues} mt={4} />
</Stack>
)
}
const InstallPackageInstruction = () => {
return (
<Stack>
<Text>Install the package:</Text>
<CodeEditor value={`npm install typebot-js`} isReadOnly />
</Stack>
)
}

View File

@ -1,65 +1,25 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
IconButton,
Heading,
HStack,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
import { capitalize } from 'utils'
import { ReactInstructions } from './ReactInstructions'
import { AlertInfo } from '@/components/AlertInfo'
import { EmbedModal } from '../../EmbedModal'
import { isDefined } from '@udecode/plate-common'
import { ReactInstructions } from './instructions/ReactInstructions'
export const ReactModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
<EmbedModal
titlePrefix="React"
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{chosenEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => setChosenEmbedType(undefined)}
/>
)}
<Heading size="md">
React {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
{!chosenEmbedType ? (
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
) : (
<ReactInstructions type={chosenEmbedType} />
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
{isDefined(selectedEmbedType) && (
<ReactInstructions type={selectedEmbedType} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1,29 @@
import { CodeEditor } from '@/components/CodeEditor'
import { useTypebot } from '@/features/editor'
import { PopupProps } from '@typebot.io/js'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { parseReactPopupProps } from '../../snippetParsers'
export const ReactPopupSnippet = ({
autoShowDelay,
}: Pick<PopupProps, 'autoShowDelay'>) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`import { Popup } from "@typebot.io/react";
const App = () => {
return <Popup ${parseReactPopupProps({
typebot: typebot?.publicId ?? '',
autoShowDelay,
})}/>;
}`,
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="javascript" isReadOnly />
}

View File

@ -0,0 +1,28 @@
import { CodeEditor } from '@/components/CodeEditor'
import { useTypebot } from '@/features/editor'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { parseReactBotProps } from '../../snippetParsers'
type ReactStandardSnippetProps = { widthLabel?: string; heightLabel: string }
export const ReactStandardSnippet = ({
widthLabel,
heightLabel,
}: ReactStandardSnippetProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`import { Standard } from "@typebot.io/react";
const App = () => {
return <Standard ${parseReactBotProps({
typebot: typebot?.publicId ?? '',
})} style={{width: "${widthLabel}", height: "${heightLabel}"}} />
}`,
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="javascript" isReadOnly />
}

View File

@ -0,0 +1,42 @@
import { useTypebot } from '@/features/editor'
import { ListItem, OrderedList, Stack, Text } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { InstallReactPackageSnippet } from '../InstallReactPackageSnippet'
import { ReactBubbleSnippet } from '../ReactBubbleSnippet'
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
export const ReactBubbleInstructions = () => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
<Stack spacing={4}>
<Text>Install the packages</Text>
<InstallReactPackageSnippet />
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4}>
<BubbleSettings
theme={theme}
previewMessage={previewMessage}
defaultPreviewMessageAvatar={
typebot?.theme.chat.hostAvatar?.url ?? ''
}
onThemeChange={setTheme}
onPreviewMessageChange={setPreviewMessage}
/>
<ReactBubbleSnippet theme={theme} previewMessage={previewMessage} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,21 @@
import { ReactBubbleInstructions } from './ReactBubbleInstructions'
import { ReactPopupInstructions } from './ReactPopupInstructions'
import { ReactStandardInstructions } from './ReactStandardInstructions'
type Props = {
type: 'standard' | 'popup' | 'bubble'
}
export const ReactInstructions = ({ type }: Props) => {
switch (type) {
case 'standard': {
return <ReactStandardInstructions />
}
case 'popup': {
return <ReactPopupInstructions />
}
case 'bubble': {
return <ReactBubbleInstructions />
}
}
}

View File

@ -0,0 +1,30 @@
import { ListItem, OrderedList, Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { InstallReactPackageSnippet } from '../InstallReactPackageSnippet'
import { ReactPopupSnippet } from '../ReactPopupSnippet'
export const ReactPopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
<Stack spacing={4}>
<Text>Install the packages</Text>
<InstallReactPackageSnippet />
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) =>
setInputValue(settings.autoShowDelay)
}
/>
<ReactPopupSnippet autoShowDelay={inputValue} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,36 @@
import { ListItem, OrderedList, Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { StandardSettings } from '../../../settings/StandardSettings'
import { InstallReactPackageSnippet } from '../InstallReactPackageSnippet'
import { ReactStandardSnippet } from '../ReactStandardSnippet'
export const ReactStandardInstructions = () => {
const [inputValues, setInputValues] = useState<{
widthLabel?: string
heightLabel: string
}>({
heightLabel: '100%',
widthLabel: '100%',
})
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
<Stack spacing={4}>
<Text>Install the packages</Text>
<InstallReactPackageSnippet />
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4}>
<StandardSettings
onUpdateWindowSettings={(settings) =>
setInputValues({ ...settings })
}
/>
<ReactStandardSnippet {...inputValues} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -1,146 +0,0 @@
import { OrderedList, ListItem, Tag } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { ModalProps } from '../../EmbedButton'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { env, getViewerUrl } from 'utils'
import { CodeEditor } from '@/components/CodeEditor'
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
import {
parseInitContainerCode,
typebotJsHtml,
} from '../../codeSnippets/params'
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
type ShopifyInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
publicId: string
}
export const ShopifyInstructions = ({
type,
publicId,
}: ShopifyInstructionsProps) => {
switch (type) {
case 'standard': {
return <StandardInstructions publicId={publicId} />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
}
}
const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
const [windowSizes, setWindowSizes] = useState({
height: '100%',
width: '100%',
})
const jsCode = parseInitContainerCode({
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${publicId}`,
})
const headCode = prettier.format(
`${typebotJsHtml}<script>${jsCode}</script>`,
{
parser: 'html',
plugins: [parserHtml],
}
)
const elementCode = prettier.format(
`<div id="typebot-container" style="height: ${windowSizes.height}; width: ${windowSizes.width}"></div>`,
{
parser: 'html',
plugins: [parserHtml],
}
)
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your shop dashboard in the <Tag>Themes</Tag> page, click on{' '}
<Tag>Actions {'>'} Edit code</Tag>
</ListItem>
<ListItem>
In <Tag>Layout {'>'} theme.liquid</Tag> file, paste this code just
before the closing <Tag>head</Tag> tag:
<CodeEditor value={headCode} mt={2} lang="html" isReadOnly />
</ListItem>
<ListItem>
Then, you can place an element on which the typebot will go in any file
in the <Tag>body</Tag> tags. It needs to have the id{' '}
<Tag>typebot-container</Tag>:
<StandardEmbedWindowSettings
my={4}
onUpdateWindowSettings={(sizes) =>
setWindowSizes({
height: sizes.heightLabel,
width: sizes.widthLabel,
})
}
/>
<CodeEditor value={elementCode} mt={2} lang="html" isReadOnly />
</ListItem>
</OrderedList>
)
}
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your shop dashboard in the <Tag>Themes</Tag> page, click on{' '}
<Tag>Actions {'>'} Edit code</Tag>
</ListItem>
<ListItem>
In <Tag>Layout {'>'} theme.liquid</Tag> file, paste this code just
before the closing <Tag>head</Tag> tag:
<PopupEmbedSettings
my="4"
onUpdateSettings={(settings) => setInputValue(settings.delay)}
/>
<PopupEmbedCode delay={inputValue} />
</ListItem>
</OrderedList>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your shop dashboard in the <Tag>Themes</Tag> page, click on{' '}
<Tag>Actions {'>'} Edit code</Tag>
</ListItem>
<ListItem>
In <Tag>Layout {'>'} theme.liquid</Tag> file, paste this code just
before the closing <Tag>head</Tag> tag:
<ChatEmbedSettings
my="4"
onUpdateSettings={(settings) => setInputValues({ ...settings })}
/>
<ChatEmbedCode mt={4} {...inputValues} />
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,30 @@
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { EmbedModal } from '../../EmbedModal'
import { isDefined } from '@udecode/plate-common'
import { ShopifyInstructions } from './instructions/ShopifyInstructions'
export const ShopifyModal = ({
isOpen,
onClose,
isPublished,
publicId,
}: ModalProps) => {
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<EmbedModal
titlePrefix="Shopify"
isOpen={isOpen}
onClose={onClose}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
{isDefined(selectedEmbedType) && (
<ShopifyInstructions type={selectedEmbedType} publicId={publicId} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1 @@
export * from './ShopifyModal'

View File

@ -1,70 +0,0 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
IconButton,
Heading,
HStack,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
import { capitalize } from 'utils'
import { ShopifyInstructions } from './ShopifyInstructions'
import { AlertInfo } from '@/components/AlertInfo'
export const ShopifyModal = ({
isOpen,
onClose,
isPublished,
publicId,
}: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{chosenEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => setChosenEmbedType(undefined)}
/>
)}
<Heading size="md">
Shopify {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
{!chosenEmbedType ? (
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
) : (
<ShopifyInstructions type={chosenEmbedType} publicId={publicId} />
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,47 @@
import { useTypebot } from '@/features/editor'
import { OrderedList, ListItem, Stack, Text, Code } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
import { JavascriptBubbleSnippet } from '../../Javascript/JavascriptBubbleSnippet'
export const ShopifyBubbleInstructions = () => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your shop dashboard in the <Code>Themes</Code> page, click on{' '}
<Code>Actions {'>'} Edit code</Code>
</ListItem>
<ListItem>
<Stack spacing={4}>
<BubbleSettings
previewMessage={previewMessage}
defaultPreviewMessageAvatar={
typebot?.theme.chat.hostAvatar?.url ?? ''
}
theme={theme}
onPreviewMessageChange={setPreviewMessage}
onThemeChange={setTheme}
/>
<Text>
In <Code>Layout {'>'} theme.liquid</Code> file, paste this code just
before the closing <Code>{'<head>'}</Code> tag:
</Text>
<JavascriptBubbleSnippet
theme={theme}
previewMessage={previewMessage}
/>
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,25 @@
import { ShopifyBubbleInstructions } from './ShopifyBubbleInstructions'
import { ShopifyPopupInstructions } from './ShopifyPopupInstructions'
import { ShopifyStandardInstructions } from './ShopifyStandardInstructions'
type ShopifyInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
publicId: string
}
export const ShopifyInstructions = ({
type,
publicId,
}: ShopifyInstructionsProps) => {
switch (type) {
case 'standard': {
return <ShopifyStandardInstructions publicId={publicId} />
}
case 'popup': {
return <ShopifyPopupInstructions />
}
case 'bubble': {
return <ShopifyBubbleInstructions />
}
}
}

View File

@ -0,0 +1,31 @@
import { OrderedList, ListItem, Stack, Text, Code } from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { JavascriptPopupSnippet } from '../../Javascript/JavascriptPopupSnippet'
export const ShopifyPopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your shop dashboard in the <Code>Themes</Code> page, click on{' '}
<Code>Actions {'>'} Edit code</Code>
</ListItem>
<ListItem>
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) =>
setInputValue(settings.autoShowDelay)
}
/>
<Text>
In <Code>Layout {'>'} theme.liquid</Code> file, paste this code just
before the closing <Code>{'<head>'}</Code> tag:
</Text>
<JavascriptPopupSnippet autoShowDelay={inputValue} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,65 @@
import { CodeEditor } from '@/components/CodeEditor'
import { OrderedList, ListItem, Stack, Text, Code } from '@chakra-ui/react'
import { useState } from 'react'
import { StandardSettings } from '../../../settings/StandardSettings'
import {
parseStandardElementCode,
parseStandardHeadCode,
} from '../../Javascript/JavascriptStandardSnippet'
type Props = {
publicId: string
}
export const ShopifyStandardInstructions = ({ publicId }: Props) => {
const [windowSizes, setWindowSizes] = useState<{
width?: string
height: string
}>({
height: '100%',
width: '100%',
})
const headCode = parseStandardHeadCode(publicId)
const elementCode = parseStandardElementCode(
windowSizes.width,
windowSizes.height
)
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your shop dashboard in the <Code>Themes</Code> page, click on{' '}
<Code>Actions {'>'} Edit code</Code>
</ListItem>
<ListItem>
<Stack spacing={4}>
<Text>
In <Code>Layout {'>'} theme.liquid</Code> file, paste this code just
before the closing <Code>{'<head>'}</Code> tag:
</Text>
<CodeEditor value={headCode} lang="html" isReadOnly />
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4}>
<StandardSettings
onUpdateWindowSettings={(sizes) =>
setWindowSizes({
height: sizes.heightLabel,
width: sizes.widthLabel,
})
}
/>
<Text>
Place an element on which the typebot will go in any file in the{' '}
<Code>{'<body>'}</Code>:
</Text>
<CodeEditor value={elementCode} lang="html" isReadOnly />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -1,91 +0,0 @@
import { OrderedList, ListItem, Tag } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { ContainerEmbedCode } from '../../codeSnippets/Container/EmbedCode'
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
type WebflowInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
}
export const WebflowInstructions = ({ type }: WebflowInstructionsProps) => {
switch (type) {
case 'standard': {
return <StandardInstructions />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
default:
return <></>
}
}
const StandardInstructions = () => (
<OrderedList spacing={2} mb={4}>
<ListItem>
Press <Tag>A</Tag> to open the <Tag>Add elements</Tag> panel
</ListItem>
<ListItem>
Add an <Tag>embed</Tag> element from the <Tag>components</Tag>
section and paste this code:
<ContainerEmbedCode widthLabel="100%" heightLabel="100%" my={4} />
</ListItem>
</OrderedList>
)
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
Press <Tag>A</Tag> to open the <Tag>Add elements</Tag> panel
</ListItem>
<ListItem>
Add an <Tag>embed</Tag> element from the <Tag>components</Tag>
section and paste this code:
<PopupEmbedSettings
onUpdateSettings={(settings) => setInputValue(settings.delay)}
my={4}
/>
<PopupEmbedCode delay={inputValue} mt={4} />
</ListItem>
</OrderedList>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
Press <Tag>A</Tag> to open the <Tag>Add elements</Tag> panel
</ListItem>
<ListItem>
Add an <Tag>embed</Tag> element from the <Tag>components</Tag>
section and paste this code:
<ChatEmbedSettings
onUpdateSettings={(settings) => setInputValues({ ...settings })}
my={4}
/>
<ChatEmbedCode withStarterVariables={true} {...inputValues} my={4} />
</ListItem>
</OrderedList>
)
}

Some files were not shown because too many files have changed in this diff Show More