@@ -0,0 +1,44 @@
|
||||
import { IconProps, Icon } from '@chakra-ui/react'
|
||||
|
||||
export const PixelLogo = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 288 191" fill="none" {...props}>
|
||||
<path
|
||||
d="M31.06 125.96C31.06 136.94 33.47 145.37 36.62 150.47C40.75 157.15 46.91 159.98 53.19 159.98C61.29 159.98 68.7 157.97 82.98 138.22C94.42 122.39 107.9 100.17 116.97 86.24L132.33 62.64C143 46.25 155.35 28.03 169.51 15.68C181.07 5.6 193.54 0 206.09 0C227.16 0 247.23 12.21 262.59 35.11C279.4 60.19 287.56 91.78 287.56 124.38C287.56 143.76 283.74 158 277.24 169.25C270.96 180.13 258.72 191 238.13 191V159.98C255.76 159.98 260.16 143.78 260.16 125.24C260.16 98.82 254 69.5 240.43 48.55C230.8 33.69 218.32 24.61 204.59 24.61C189.74 24.61 177.79 35.81 164.36 55.78C157.22 66.39 149.89 79.32 141.66 93.91L132.6 109.96C114.4 142.23 109.79 149.58 100.69 161.71C84.74 182.95 71.12 191 53.19 191C31.92 191 18.47 181.79 10.14 167.91C3.34 156.6 0 141.76 0 124.85L31.06 125.96Z"
|
||||
fill="#0081FB"
|
||||
/>
|
||||
<path
|
||||
d="M24.4902 37.3C38.7302 15.35 59.2802 0 82.8502 0C96.5002 0 110.07 4.04 124.24 15.61C139.74 28.26 156.26 49.09 176.87 83.42L184.26 95.74C202.1 125.46 212.25 140.75 218.19 147.96C225.83 157.22 231.18 159.98 238.13 159.98C255.76 159.98 260.16 143.78 260.16 125.24L287.56 124.38C287.56 143.76 283.74 158 277.24 169.25C270.96 180.13 258.72 191 238.13 191C225.33 191 213.99 188.22 201.45 176.39C191.81 167.31 180.54 151.18 171.87 136.68L146.08 93.6C133.14 71.98 121.27 55.86 114.4 48.56C107.01 40.71 97.5102 31.23 82.3502 31.23C70.0802 31.23 59.6602 39.84 50.9402 53.01L24.4902 37.3Z"
|
||||
fill="url(#paint0_linear_1302_7)"
|
||||
/>
|
||||
<path
|
||||
d="M82.35 31.23C70.08 31.23 59.66 39.84 50.94 53.01C38.61 71.62 31.06 99.34 31.06 125.96C31.06 136.94 33.47 145.37 36.62 150.47L10.14 167.91C3.34 156.6 0 141.76 0 124.85C0 94.1 8.44 62.05 24.49 37.3C38.73 15.35 59.28 0 82.85 0L82.35 31.23Z"
|
||||
fill="url(#paint1_linear_1302_7)"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_1302_7"
|
||||
x1="61.0002"
|
||||
y1="117"
|
||||
x2="259"
|
||||
y2="127"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#0064E1" />
|
||||
<stop offset="0.4" stop-color="#0064E1" />
|
||||
<stop offset="0.83" stop-color="#0073EE" />
|
||||
<stop offset="1" stop-color="#0082FB" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_1302_7"
|
||||
x1="45"
|
||||
y1="139"
|
||||
x2="45"
|
||||
y2="66"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#0082FB" />
|
||||
<stop offset="1" stop-color="#0064E0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</Icon>
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { PixelBlock } from '@typebot.io/schemas'
|
||||
|
||||
type Props = {
|
||||
options: PixelBlock['options']
|
||||
}
|
||||
|
||||
export const PixelNodeBody = ({ options }: Props) => (
|
||||
<Text
|
||||
color={options.eventType || options.pixelId ? 'currentcolor' : 'gray.500'}
|
||||
noOfLines={1}
|
||||
>
|
||||
{options.eventType
|
||||
? `Track "${options.eventType}"`
|
||||
: options.pixelId
|
||||
? 'Init Pixel'
|
||||
: 'Configure...'}
|
||||
</Text>
|
||||
)
|
||||
@@ -0,0 +1,185 @@
|
||||
import { DropdownList } from '@/components/DropdownList'
|
||||
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
|
||||
import { TableList, TableListItemProps } from '@/components/TableList'
|
||||
import { TextLink } from '@/components/TextLink'
|
||||
import { TextInput } from '@/components/inputs'
|
||||
import { CodeEditor } from '@/components/inputs/CodeEditor'
|
||||
import { Select } from '@/components/inputs/Select'
|
||||
import { Stack, Text } from '@chakra-ui/react'
|
||||
import { isDefined, isEmpty } from '@typebot.io/lib'
|
||||
import {
|
||||
PixelBlock,
|
||||
pixelEventTypes,
|
||||
pixelObjectProperties,
|
||||
} from '@typebot.io/schemas'
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
const pixelReferenceUrl =
|
||||
'https://developers.facebook.com/docs/meta-pixel/reference#standard-events'
|
||||
|
||||
type Props = {
|
||||
options?: PixelBlock['options']
|
||||
onOptionsChange: (options: PixelBlock['options']) => void
|
||||
}
|
||||
|
||||
type Item = NonNullable<PixelBlock['options']['params']>[number]
|
||||
|
||||
export const PixelSettings = ({ options, onOptionsChange }: Props) => {
|
||||
const updatePixelId = (pixelId: string) =>
|
||||
onOptionsChange({
|
||||
...options,
|
||||
pixelId: isEmpty(pixelId) ? undefined : pixelId,
|
||||
})
|
||||
|
||||
const updateIsTrackingEventEnabled = (isChecked: boolean) =>
|
||||
onOptionsChange({
|
||||
...options,
|
||||
params: isChecked && !options?.params ? [] : undefined,
|
||||
})
|
||||
|
||||
const updateEventType = (
|
||||
_: string | undefined,
|
||||
eventType?: (typeof pixelEventTypes)[number] | 'Custom'
|
||||
) =>
|
||||
onOptionsChange({
|
||||
...options,
|
||||
params: [],
|
||||
eventType,
|
||||
})
|
||||
|
||||
const updateParams = (params: PixelBlock['options']['params']) =>
|
||||
onOptionsChange({
|
||||
...options,
|
||||
params,
|
||||
})
|
||||
|
||||
const updateEventName = (name: string) => {
|
||||
if (options?.eventType !== 'Custom') return
|
||||
onOptionsChange({
|
||||
...options,
|
||||
name: isEmpty(name) ? undefined : name,
|
||||
})
|
||||
}
|
||||
|
||||
const Item = useMemo(
|
||||
() =>
|
||||
function Component(props: TableListItemProps<Item>) {
|
||||
return <ParamItem {...props} eventType={options?.eventType} />
|
||||
},
|
||||
[options?.eventType]
|
||||
)
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<TextInput
|
||||
defaultValue={options?.pixelId ?? ''}
|
||||
onChange={updatePixelId}
|
||||
withVariableButton={false}
|
||||
placeholder='Pixel ID (e.g. "123456789")'
|
||||
/>
|
||||
<SwitchWithRelatedSettings
|
||||
label={'Track event'}
|
||||
initialValue={isDefined(options?.params)}
|
||||
onCheckChange={updateIsTrackingEventEnabled}
|
||||
>
|
||||
<Text fontSize="sm" color="gray.500">
|
||||
Read the{' '}
|
||||
<TextLink href={pixelReferenceUrl} isExternal>
|
||||
reference
|
||||
</TextLink>{' '}
|
||||
to better understand the available options.
|
||||
</Text>
|
||||
<Select
|
||||
items={['Custom', ...pixelEventTypes] as const}
|
||||
selectedItem={options?.eventType}
|
||||
placeholder="Select event type"
|
||||
onSelect={updateEventType}
|
||||
/>
|
||||
{options?.eventType === 'Custom' && (
|
||||
<TextInput
|
||||
defaultValue={options.name ?? ''}
|
||||
onChange={updateEventName}
|
||||
placeholder="Event name"
|
||||
/>
|
||||
)}
|
||||
{options?.eventType &&
|
||||
(options.eventType === 'Custom' ||
|
||||
pixelObjectProperties.filter((prop) =>
|
||||
prop.associatedEvents.includes(options.eventType)
|
||||
).length > 0) && (
|
||||
<TableList
|
||||
initialItems={options?.params ?? []}
|
||||
Item={Item}
|
||||
onItemsChange={updateParams}
|
||||
addLabel="Add parameter"
|
||||
/>
|
||||
)}
|
||||
</SwitchWithRelatedSettings>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
type ParamItemProps = {
|
||||
item: Item
|
||||
eventType: 'Custom' | (typeof pixelEventTypes)[number] | undefined
|
||||
onItemChange: (item: Item) => void
|
||||
}
|
||||
|
||||
const ParamItem = ({ item, eventType, onItemChange }: ParamItemProps) => {
|
||||
const possibleObjectProps =
|
||||
eventType && eventType !== 'Custom'
|
||||
? pixelObjectProperties.filter((prop) =>
|
||||
prop.associatedEvents.includes(eventType)
|
||||
)
|
||||
: []
|
||||
|
||||
const currentObject = possibleObjectProps.find(
|
||||
(prop) => prop.key === item.key
|
||||
)
|
||||
|
||||
const updateKey = (key: string) =>
|
||||
onItemChange({
|
||||
...item,
|
||||
key,
|
||||
})
|
||||
|
||||
const updateValue = (value: string) =>
|
||||
onItemChange({
|
||||
...item,
|
||||
value,
|
||||
})
|
||||
|
||||
if (!eventType) return null
|
||||
|
||||
return (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
{eventType === 'Custom' ? (
|
||||
<TextInput
|
||||
defaultValue={item.key}
|
||||
onChange={updateKey}
|
||||
placeholder="Key"
|
||||
/>
|
||||
) : (
|
||||
<DropdownList
|
||||
currentItem={item.key}
|
||||
items={possibleObjectProps.map((prop) => prop.key)}
|
||||
onItemSelect={updateKey}
|
||||
placeholder="Select key"
|
||||
/>
|
||||
)}
|
||||
{currentObject?.type === 'code' ? (
|
||||
<CodeEditor
|
||||
lang={'javascript'}
|
||||
defaultValue={item.value}
|
||||
onChange={updateValue}
|
||||
/>
|
||||
) : (
|
||||
<TextInput
|
||||
defaultValue={item.value}
|
||||
onChange={updateValue}
|
||||
placeholder="Value"
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import { createTypebots } from '@typebot.io/lib/playwright/databaseActions'
|
||||
import { parseDefaultGroupWithBlock } from '@typebot.io/lib/playwright/databaseHelpers'
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
|
||||
test.describe('Pixel block', () => {
|
||||
test('its configuration should work', async ({ page }) => {
|
||||
const typebotId = createId()
|
||||
await createTypebots([
|
||||
{
|
||||
id: typebotId,
|
||||
...parseDefaultGroupWithBlock({
|
||||
type: IntegrationBlockType.PIXEL,
|
||||
options: {},
|
||||
}),
|
||||
},
|
||||
])
|
||||
|
||||
await page.goto(`/typebots/${typebotId}/edit`)
|
||||
await page.click('text=Configure...')
|
||||
await page.getByPlaceholder('Pixel ID (e.g. "123456789")').fill('pixelid')
|
||||
await expect(page.getByText('Init Pixel')).toBeVisible()
|
||||
await page.getByText('Track event').click()
|
||||
await page.getByPlaceholder('Select event type').click()
|
||||
await page.getByRole('menuitem', { name: 'Lead' }).click()
|
||||
await expect(page.getByText('Track "Lead"')).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Add parameter' }).click()
|
||||
await page.getByRole('button', { name: 'Select key' }).click()
|
||||
await page.getByRole('menuitem', { name: 'currency' }).click()
|
||||
await page.getByPlaceholder('Value').fill('USD')
|
||||
await page.getByRole('button', { name: 'Preview' }).click()
|
||||
await expect(
|
||||
page.getByText('Pixel is not enabled in Preview mode').nth(1)
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user