Skip to content

Commit

Permalink
feat(engine): ✨ Link typebot step
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Mar 9, 2022
1 parent 1bcc8ae commit 7e61ab1
Show file tree
Hide file tree
Showing 61 changed files with 1,272 additions and 245 deletions.
8 changes: 8 additions & 0 deletions apps/builder/assets/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,11 @@ export const AlignLeftTextIcon = (props: IconProps) => (
<line x1="17" y1="18" x2="3" y2="18"></line>
</Icon>
)

export const BoxIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
<line x1="12" y1="22.08" x2="12" y2="12"></line>
</Icon>
)
3 changes: 3 additions & 0 deletions apps/builder/components/editor/StepsSideBar/StepIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IconProps } from '@chakra-ui/react'
import {
BoxIcon,
CalendarIcon,
ChatIcon,
CheckSquareIcon,
Expand Down Expand Up @@ -60,6 +61,8 @@ export const StepIcon = ({ type, ...props }: StepIconProps) => {
return <ExternalLinkIcon color="purple.500" {...props} />
case LogicStepType.CODE:
return <CodeIcon color="purple.500" {...props} />
case LogicStepType.TYPEBOT_LINK:
return <BoxIcon color="purple.500" {...props} />
case IntegrationStepType.GOOGLE_SHEETS:
return <GoogleSheetsLogo {...props} />
case IntegrationStepType.GOOGLE_ANALYTICS:
Expand Down
6 changes: 6 additions & 0 deletions apps/builder/components/editor/StepsSideBar/StepTypeLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export const StepTypeLabel = ({ type }: Props) => {
<Text>Code</Text>
</Tooltip>
)
case LogicStepType.TYPEBOT_LINK:
return (
<Tooltip label="Link to another of your typebots">
<Text>Typebot</Text>
</Tooltip>
)
case IntegrationStepType.GOOGLE_SHEETS:
return (
<Tooltip label="Google Sheets">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
/* eslint-disable react/jsx-key */
import { Button, chakra, Checkbox, Flex, HStack, Text } from '@chakra-ui/react'
import { AlignLeftTextIcon } from 'assets/icons'
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
import { PublicTypebot } from 'models'
import React, { useEffect, useMemo, useRef } from 'react'
import { Hooks, useRowSelect, useTable } from 'react-table'
import { parseSubmissionsColumns } from 'services/publicTypebot'
import { LoadingRows } from './LoadingRows'

type SubmissionsTableProps = {
blocksAndVariables: Pick<PublicTypebot, 'blocks' | 'variables'>
data?: any
hasMore?: boolean
onNewSelection: (indices: number[]) => void
Expand All @@ -17,16 +18,16 @@ type SubmissionsTableProps = {
}

export const SubmissionsTable = ({
blocksAndVariables,
data,
hasMore,
onNewSelection,
onScrollToBottom,
onLogOpenIndex,
}: SubmissionsTableProps) => {
const { publishedTypebot } = useTypebot()
const columns: any = useMemo(
() => (publishedTypebot ? parseSubmissionsColumns(publishedTypebot) : []),
[publishedTypebot]
() => parseSubmissionsColumns(blocksAndVariables),
[blocksAndVariables]
)
const bottomElement = useRef<HTMLDivElement | null>(null)
const tableWrapper = useRef<HTMLDivElement | null>(null)
Expand Down
11 changes: 8 additions & 3 deletions apps/builder/components/shared/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { css } from '@codemirror/lang-css'
import { javascript } from '@codemirror/lang-javascript'
import { html } from '@codemirror/lang-html'
import { useEffect, useRef, useState } from 'react'
import { useDebounce } from 'use-debounce'

type Props = {
value: string
Expand All @@ -22,6 +23,10 @@ export const CodeEditor = ({
const editorContainer = useRef<HTMLDivElement | null>(null)
const editorView = useRef<EditorView | null>(null)
const [plainTextValue, setPlainTextValue] = useState(value)
const [debouncedValue] = useDebounce(
plainTextValue,
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : 1000
)

useEffect(() => {
if (!editorView.current || !isReadOnly) return
Expand All @@ -36,10 +41,10 @@ export const CodeEditor = ({
}, [value])

useEffect(() => {
if (!onChange || plainTextValue === value) return
onChange(plainTextValue)
if (!onChange || debouncedValue === value) return
onChange(debouncedValue)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [plainTextValue])
}, [debouncedValue])

useEffect(() => {
const editor = initEditor(value)
Expand Down
14 changes: 9 additions & 5 deletions apps/builder/components/shared/DebouncedInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useEffect,
useState,
} from 'react'
import { useDebounce } from 'use-debounce'

type Props = Omit<InputProps, 'onChange' | 'value'> & {
initialValue: string
Expand All @@ -18,16 +19,19 @@ export const DebouncedInput = forwardRef(
ref: ForwardedRef<HTMLInputElement>
) => {
const [currentValue, setCurrentValue] = useState(initialValue)
const [debouncedValue] = useDebounce(
currentValue,
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : 1000
)

useEffect(() => {
if (currentValue === initialValue) return
onChange(currentValue)
if (debouncedValue === initialValue) return
onChange(debouncedValue)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentValue])
}, [debouncedValue])

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
setCurrentValue(e.target.value)
}

return (
<Input
Expand Down
11 changes: 8 additions & 3 deletions apps/builder/components/shared/DebouncedTextarea.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Textarea, TextareaProps } from '@chakra-ui/react'
import { ChangeEvent, useEffect, useState } from 'react'
import { useDebounce } from 'use-debounce'

type Props = Omit<TextareaProps, 'onChange' | 'value'> & {
initialValue: string
Expand All @@ -12,12 +13,16 @@ export const DebouncedTextarea = ({
...props
}: Props) => {
const [currentValue, setCurrentValue] = useState(initialValue)
const [debouncedValue] = useDebounce(
currentValue,
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : 1000
)

useEffect(() => {
if (currentValue === initialValue) return
onChange(currentValue)
if (debouncedValue === initialValue) return
onChange(debouncedValue)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentValue])
}, [debouncedValue])

const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
setCurrentValue(e.target.value)
Expand Down
9 changes: 7 additions & 2 deletions apps/builder/components/shared/Graph/Edges/Edge.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Coordinates, useGraph } from 'contexts/GraphContext'
import React, { useLayoutEffect, useMemo, useState } from 'react'
import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react'
import {
getAnchorsPosition,
computeEdgePath,
Expand Down Expand Up @@ -31,6 +31,7 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
const [isMouseOver, setIsMouseOver] = useState(false)
const { isOpen, onOpen, onClose } = useDisclosure()
const [edgeMenuPosition, setEdgeMenuPosition] = useState({ x: 0, y: 0 })
const [refreshEdge, setRefreshEdge] = useState(false)

const isPreviewing = isMouseOver || previewingEdge?.id === edge.id

Expand All @@ -47,9 +48,13 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
getSourceEndpointId(edge)
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[sourceBlockCoordinates?.y, edge, sourceEndpoints]
[sourceBlockCoordinates?.y, edge, sourceEndpoints, refreshEdge]
)

useEffect(() => {
setTimeout(() => setRefreshEdge(true), 50)
}, [])

const [targetTop, setTargetTop] = useState(
getEndpointTopOffset(targetEndpoints, graphPosition.y, edge?.to.stepId)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ import { GoogleAnalyticsSettings } from './bodies/GoogleAnalyticsSettings'
import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody'
import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody'
import { RedirectSettings } from './bodies/RedirectSettings'
import { SendEmailSettings } from './bodies/SendEmailSettings/SendEmailSettings'
import { SendEmailSettings } from './bodies/SendEmailSettings'
import { SetVariableSettings } from './bodies/SetVariableSettings'
import { TypebotLinkSettingsForm } from './bodies/TypebotLinkSettingsForm'
import { WebhookSettings } from './bodies/WebhookSettings'
import { ZapierSettings } from './bodies/ZapierSettings'

Expand All @@ -43,7 +44,6 @@ type Props = {
webhook?: Webhook
onExpandClick: () => void
onStepChange: (updates: Partial<Step>) => void
onTestRequestClick: () => void
}

export const SettingsPopoverContent = ({ onExpandClick, ...props }: Props) => {
Expand Down Expand Up @@ -85,12 +85,10 @@ export const SettingsPopoverContent = ({ onExpandClick, ...props }: Props) => {
export const StepSettings = ({
step,
onStepChange,
onTestRequestClick,
}: {
step: Step
webhook?: Webhook
onStepChange: (step: Partial<Step>) => void
onTestRequestClick: () => void
}) => {
const handleOptionsChange = (options: StepOptions) => {
onStepChange({ options } as Partial<Step>)
Expand Down Expand Up @@ -186,6 +184,14 @@ export const StepSettings = ({
/>
)
}
case LogicStepType.TYPEBOT_LINK: {
return (
<TypebotLinkSettingsForm
options={step.options}
onOptionsChange={handleOptionsChange}
/>
)
}
case IntegrationStepType.GOOGLE_SHEETS: {
return (
<GoogleSheetsSettingsBody
Expand All @@ -208,11 +214,7 @@ export const StepSettings = ({
}
case IntegrationStepType.WEBHOOK: {
return (
<WebhookSettings
step={step}
onOptionsChange={handleOptionsChange}
onTestRequestClick={onTestRequestClick}
/>
<WebhookSettings step={step} onOptionsChange={handleOptionsChange} />
)
}
case IntegrationStepType.EMAIL: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ export const NumberInputSettingsBody = ({
onOptionsChange(removeUndefinedFields({ ...options, max }))
const handleStepChange = (step?: number) =>
onOptionsChange(removeUndefinedFields({ ...options, step }))
const handleVariableChange = (variable?: Variable) =>
const handleVariableChange = (variable?: Variable) => {
onOptionsChange({ ...options, variableId: variable?.id })
}

return (
<Stack spacing={4}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Input } from '@chakra-ui/react'
import { SearchableDropdown } from 'components/shared/SearchableDropdown'
import { Block } from 'models'
import { useMemo } from 'react'
import { byId } from 'utils'

type Props = {
blocks: Block[]
blockId?: string
onBlockIdSelected: (blockId: string) => void
isLoading?: boolean
}

export const BlocksDropdown = ({
blocks,
blockId,
onBlockIdSelected,
isLoading,
}: Props) => {
const currentBlock = useMemo(
() => blocks?.find(byId(blockId)),
[blockId, blocks]
)

const handleBlockSelect = (title: string) => {
const id = blocks?.find((b) => b.title === title)?.id
if (id) onBlockIdSelected(id)
}

if (isLoading) return <Input value="Loading..." isDisabled />
if (!blocks || blocks.length === 0)
return <Input value="No blocks found" isDisabled />
return (
<SearchableDropdown
selectedItem={currentBlock?.title}
items={(blocks ?? []).map((b) => b.title)}
onValueChange={handleBlockSelect}
placeholder={'Select a block'}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Stack } from '@chakra-ui/react'
import { useTypebot } from 'contexts/TypebotContext'
import { TypebotLinkOptions } from 'models'
import { byId } from 'utils'
import { BlocksDropdown } from './BlocksDropdown'
import { TypebotsDropdown } from './TypebotsDropdown'

type Props = {
options: TypebotLinkOptions
onOptionsChange: (options: TypebotLinkOptions) => void
}

export const TypebotLinkSettingsForm = ({
options,
onOptionsChange,
}: Props) => {
const { linkedTypebots, typebot } = useTypebot()

const handleTypebotIdChange = (typebotId: string) =>
onOptionsChange({ ...options, typebotId })
const handleBlockIdChange = (blockId: string) =>
onOptionsChange({ ...options, blockId })

return (
<Stack>
<TypebotsDropdown
typebotId={options.typebotId}
onSelectTypebotId={handleTypebotIdChange}
/>
<BlocksDropdown
blocks={
typebot && options.typebotId === typebot.id
? typebot.blocks
: linkedTypebots?.find(byId(options.typebotId))?.blocks ?? []
}
blockId={options.blockId}
onBlockIdSelected={handleBlockIdChange}
isLoading={
linkedTypebots === undefined &&
typebot &&
typebot.id !== options.typebotId
}
/>
</Stack>
)
}
Loading

2 comments on commit 7e61ab1

@vercel
Copy link

@vercel vercel bot commented on 7e61ab1 Mar 9, 2022

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

app.typebot.io
builder-v2-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app

Please sign in to comment.