From 033f8f99ddb72c91b46cd37cfb012ea45e23bf1e Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Wed, 28 Jun 2023 09:52:03 +0200 Subject: [PATCH] :sparkles: Add Meta Pixel block Closes #582 --- apps/builder/src/components/TableList.tsx | 6 +- .../src/features/billing/billing.spec.ts | 4 +- .../pixel/components/PixelLogo.tsx | 44 +++++ .../pixel/components/PixelNodeBody.tsx | 20 ++ .../pixel/components/PixelSettings.tsx | 185 ++++++++++++++++++ .../blocks/integrations/pixel/pixel.spec.ts | 37 ++++ .../features/editor/components/BlockIcon.tsx | 3 + .../features/editor/components/BlockLabel.tsx | 2 + .../nodes/block/BlockNodeContent.tsx | 4 + .../nodes/block/SettingsPopoverContent.tsx | 9 + .../features/graph/helpers/getHelpDocUrl.ts | 2 + .../embeds/logos/FlutterFlowLogo.tsx | 4 +- .../src/features/templates/templates.spec.ts | 2 +- .../features/typebot/helpers/parseNewBlock.ts | 2 + .../docs/editor/blocks/integrations/ga.md | 13 ++ .../docs/editor/blocks/integrations/pixel.md | 22 +++ .../static/img/blocks/integrations/pixel.png | Bin 0 -> 127752 bytes .../executeGoogleAnalyticsBlock.ts | 16 +- .../integrations/pixel/executePixelBlock.ts | 25 +++ .../src/features/chat/api/sendMessage.ts | 77 +++++++- .../src/features/chat/helpers/executeGroup.ts | 6 +- .../chat/helpers/executeIntegration.ts | 3 + .../features/variables/deepParseVariable.ts | 34 +++- .../variables/parseGuessedTypeFromString.ts | 12 ++ .../src/features/variables/parseVariables.ts | 2 +- packages/embeds/js/src/components/Bot.tsx | 4 +- .../utils/executeGoogleAnalytics.ts | 2 - .../blocks/integrations/pixel/executePixel.ts | 8 + packages/embeds/js/src/lib/gtag.ts | 11 +- packages/embeds/js/src/lib/gtm.ts | 23 +++ packages/embeds/js/src/lib/pixel.ts | 41 ++++ .../js/src/utils/executeClientSideActions.ts | 8 + .../embeds/js/src/utils/injectStartProps.ts | 20 ++ .../features/blocks/integrations/enums.ts | 1 + .../features/blocks/integrations/index.ts | 2 + .../blocks/integrations/pixel/constants.ts | 134 +++++++++++++ .../blocks/integrations/pixel/schemas.ts | 51 +++++ packages/schemas/features/blocks/schemas.ts | 6 +- packages/schemas/features/chat.ts | 19 ++ 39 files changed, 826 insertions(+), 38 deletions(-) create mode 100644 apps/builder/src/features/blocks/integrations/pixel/components/PixelLogo.tsx create mode 100644 apps/builder/src/features/blocks/integrations/pixel/components/PixelNodeBody.tsx create mode 100644 apps/builder/src/features/blocks/integrations/pixel/components/PixelSettings.tsx create mode 100644 apps/builder/src/features/blocks/integrations/pixel/pixel.spec.ts create mode 100644 apps/docs/docs/editor/blocks/integrations/pixel.md create mode 100644 apps/docs/static/img/blocks/integrations/pixel.png create mode 100644 apps/viewer/src/features/blocks/integrations/pixel/executePixelBlock.ts create mode 100644 apps/viewer/src/features/variables/parseGuessedTypeFromString.ts create mode 100644 packages/embeds/js/src/features/blocks/integrations/pixel/executePixel.ts create mode 100644 packages/embeds/js/src/lib/gtm.ts create mode 100644 packages/embeds/js/src/lib/pixel.ts create mode 100644 packages/embeds/js/src/utils/injectStartProps.ts create mode 100644 packages/schemas/features/blocks/integrations/pixel/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/pixel/schemas.ts diff --git a/apps/builder/src/components/TableList.tsx b/apps/builder/src/components/TableList.tsx index a35abc7c0..478f6e657 100644 --- a/apps/builder/src/components/TableList.tsx +++ b/apps/builder/src/components/TableList.tsx @@ -9,7 +9,7 @@ import { } from '@chakra-ui/react' import { TrashIcon, PlusIcon } from '@/components/icons' import { createId } from '@paralleldrive/cuid2' -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' type ItemWithId = T & { id: string } @@ -40,6 +40,10 @@ export const TableList = ({ const [items, setItems] = useState(initialItems) const [showDeleteIndex, setShowDeleteIndex] = useState(null) + useEffect(() => { + if (items.length && initialItems.length === 0) setItems(initialItems) + }, [initialItems, items.length]) + const createItem = () => { const id = createId() const newItem = { id, ...newItemDefaultProps } as ItemWithId diff --git a/apps/builder/src/features/billing/billing.spec.ts b/apps/builder/src/features/billing/billing.spec.ts index 744d9b375..60adcd8de 100644 --- a/apps/builder/src/features/billing/billing.spec.ts +++ b/apps/builder/src/features/billing/billing.spec.ts @@ -225,8 +225,10 @@ test('should display invoices', async ({ page }) => { await page.click('text=Settings & Members') await page.click('text=Billing & Usage') await expect(page.locator('text="Invoices"')).toBeVisible() - await expect(page.locator('tr')).toHaveCount(2) + await expect(page.locator('tr')).toHaveCount(3) await expect(page.locator('text="$39.00"')).toBeVisible() + await expect(page.locator('text="$34.00"')).toBeVisible() + await expect(page.locator('text="$174.00"')).toBeVisible() }) test('custom plans should work', async ({ page }) => { diff --git a/apps/builder/src/features/blocks/integrations/pixel/components/PixelLogo.tsx b/apps/builder/src/features/blocks/integrations/pixel/components/PixelLogo.tsx new file mode 100644 index 000000000..2f6e7f1af --- /dev/null +++ b/apps/builder/src/features/blocks/integrations/pixel/components/PixelLogo.tsx @@ -0,0 +1,44 @@ +import { IconProps, Icon } from '@chakra-ui/react' + +export const PixelLogo = (props: IconProps) => ( + + + + + + + + + + + + + + + + + +) diff --git a/apps/builder/src/features/blocks/integrations/pixel/components/PixelNodeBody.tsx b/apps/builder/src/features/blocks/integrations/pixel/components/PixelNodeBody.tsx new file mode 100644 index 000000000..1b2ad8ec7 --- /dev/null +++ b/apps/builder/src/features/blocks/integrations/pixel/components/PixelNodeBody.tsx @@ -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) => ( + + {options.eventType + ? `Track "${options.eventType}"` + : options.pixelId + ? 'Init Pixel' + : 'Configure...'} + +) diff --git a/apps/builder/src/features/blocks/integrations/pixel/components/PixelSettings.tsx b/apps/builder/src/features/blocks/integrations/pixel/components/PixelSettings.tsx new file mode 100644 index 000000000..40535a135 --- /dev/null +++ b/apps/builder/src/features/blocks/integrations/pixel/components/PixelSettings.tsx @@ -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[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) { + return + }, + [options?.eventType] + ) + + return ( + + + + + Read the{' '} + + reference + {' '} + to better understand the available options. + +