✨ Introducing The Forge (#1072)
The Forge allows anyone to easily create their own Typebot Block. Closes #380
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
import { Flex, HStack, Tooltip, useColorModeValue } from '@chakra-ui/react'
|
||||
import { useBlockDnd } from '@/features/graph/providers/GraphDndProvider'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { HStack } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import { BlockIcon } from './BlockIcon'
|
||||
import { isFreePlan } from '@/features/billing/helpers/isFreePlan'
|
||||
import { Plan } from '@typebot.io/prisma'
|
||||
@ -13,6 +12,9 @@ import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/const
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
import { BlockV6 } from '@typebot.io/schemas'
|
||||
import { enabledBlocks } from '@typebot.io/forge-repository'
|
||||
import { BlockCardLayout } from './BlockCardLayout'
|
||||
import { ForgedBlockCard } from '@/features/forge/ForgedBlockCard'
|
||||
|
||||
type Props = {
|
||||
type: BlockV6['type']
|
||||
@ -28,6 +30,14 @@ export const BlockCard = (
|
||||
const { t } = useTranslate()
|
||||
const { workspace } = useWorkspace()
|
||||
|
||||
if (enabledBlocks.includes(props.type as (typeof enabledBlocks)[number])) {
|
||||
return (
|
||||
<ForgedBlockCard
|
||||
type={props.type as (typeof enabledBlocks)[number]}
|
||||
onMouseDown={props.onMouseDown}
|
||||
/>
|
||||
)
|
||||
}
|
||||
switch (props.type) {
|
||||
case BubbleBlockType.EMBED:
|
||||
return (
|
||||
@ -111,37 +121,3 @@ export const BlockCard = (
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const BlockCardLayout = ({ type, onMouseDown, tooltip, children }: Props) => {
|
||||
const { draggedBlockType } = useBlockDnd()
|
||||
const [isMouseDown, setIsMouseDown] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setIsMouseDown(draggedBlockType === type)
|
||||
}, [draggedBlockType, type])
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent) => onMouseDown(e, type)
|
||||
|
||||
return (
|
||||
<Tooltip label={tooltip}>
|
||||
<Flex pos="relative">
|
||||
<HStack
|
||||
borderWidth="1px"
|
||||
borderColor={useColorModeValue('gray.200', 'gray.800')}
|
||||
rounded="lg"
|
||||
flex="1"
|
||||
cursor={'grab'}
|
||||
opacity={isMouseDown ? '0.4' : '1'}
|
||||
onMouseDown={handleMouseDown}
|
||||
bgColor={useColorModeValue('gray.50', 'gray.850')}
|
||||
px="4"
|
||||
py="2"
|
||||
_hover={useColorModeValue({ shadow: 'md' }, { bgColor: 'gray.800' })}
|
||||
transition="box-shadow 200ms, background-color 200ms"
|
||||
>
|
||||
{!isMouseDown ? children : null}
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
import { useBlockDnd } from '@/features/graph/providers/GraphDndProvider'
|
||||
import { Tooltip, Flex, HStack, useColorModeValue } from '@chakra-ui/react'
|
||||
import { BlockV6 } from '@typebot.io/schemas'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
type Props = {
|
||||
type: BlockV6['type']
|
||||
tooltip?: string
|
||||
isDisabled?: boolean
|
||||
children: React.ReactNode
|
||||
onMouseDown: (e: React.MouseEvent, type: BlockV6['type']) => void
|
||||
}
|
||||
|
||||
export const BlockCardLayout = ({
|
||||
type,
|
||||
onMouseDown,
|
||||
tooltip,
|
||||
children,
|
||||
}: Props) => {
|
||||
const { draggedBlockType } = useBlockDnd()
|
||||
const [isMouseDown, setIsMouseDown] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setIsMouseDown(draggedBlockType === type)
|
||||
}, [draggedBlockType, type])
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent) => onMouseDown(e, type)
|
||||
|
||||
return (
|
||||
<Tooltip label={tooltip}>
|
||||
<Flex pos="relative">
|
||||
<HStack
|
||||
borderWidth="1px"
|
||||
borderColor={useColorModeValue('gray.200', 'gray.800')}
|
||||
rounded="lg"
|
||||
flex="1"
|
||||
cursor={'grab'}
|
||||
opacity={isMouseDown ? '0.4' : '1'}
|
||||
onMouseDown={handleMouseDown}
|
||||
bgColor={useColorModeValue('gray.50', 'gray.850')}
|
||||
px="4"
|
||||
py="2"
|
||||
_hover={useColorModeValue({ shadow: 'md' }, { bgColor: 'gray.800' })}
|
||||
transition="box-shadow 200ms, background-color 200ms"
|
||||
>
|
||||
{!isMouseDown ? children : null}
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
import { IconProps, useColorModeValue } from '@chakra-ui/react'
|
||||
import { useColorModeValue } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import { FlagIcon, SendEmailIcon, WebhookIcon } from '@/components/icons'
|
||||
import { WaitIcon } from '@/features/blocks/logic/wait/components/WaitIcon'
|
||||
import { ScriptIcon } from '@/features/blocks/logic/script/components/ScriptIcon'
|
||||
import { JumpIcon } from '@/features/blocks/logic/jump/components/JumpIcon'
|
||||
import { OpenAILogo } from '@/features/blocks/integrations/openai/components/OpenAILogo'
|
||||
import { AudioBubbleIcon } from '@/features/blocks/bubbles/audio/components/AudioBubbleIcon'
|
||||
import { EmbedBubbleIcon } from '@/features/blocks/bubbles/embed/components/EmbedBubbleIcon'
|
||||
import { ImageBubbleIcon } from '@/features/blocks/bubbles/image/components/ImageBubbleIcon'
|
||||
@ -39,10 +38,12 @@ import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/const
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
import { Block } from '@typebot.io/schemas'
|
||||
import { OpenAILogo } from '@/features/blocks/integrations/openai/components/OpenAILogo'
|
||||
import { ForgedBlockIcon } from '@/features/forge/ForgedBlockIcon'
|
||||
|
||||
type BlockIconProps = { type: Block['type'] } & IconProps
|
||||
type BlockIconProps = { type: Block['type']; mt?: string }
|
||||
|
||||
export const BlockIcon = ({ type, ...props }: BlockIconProps): JSX.Element => {
|
||||
export const BlockIcon = ({ type, mt }: BlockIconProps): JSX.Element => {
|
||||
const blue = useColorModeValue('blue.500', 'blue.300')
|
||||
const orange = useColorModeValue('orange.500', 'orange.300')
|
||||
const purple = useColorModeValue('purple.500', 'purple.300')
|
||||
@ -50,76 +51,78 @@ export const BlockIcon = ({ type, ...props }: BlockIconProps): JSX.Element => {
|
||||
|
||||
switch (type) {
|
||||
case BubbleBlockType.TEXT:
|
||||
return <TextBubbleIcon color={blue} {...props} />
|
||||
return <TextBubbleIcon color={blue} mt={mt} />
|
||||
case BubbleBlockType.IMAGE:
|
||||
return <ImageBubbleIcon color={blue} {...props} />
|
||||
return <ImageBubbleIcon color={blue} mt={mt} />
|
||||
case BubbleBlockType.VIDEO:
|
||||
return <VideoBubbleIcon color={blue} {...props} />
|
||||
return <VideoBubbleIcon color={blue} mt={mt} />
|
||||
case BubbleBlockType.EMBED:
|
||||
return <EmbedBubbleIcon color={blue} {...props} />
|
||||
return <EmbedBubbleIcon color={blue} mt={mt} />
|
||||
case BubbleBlockType.AUDIO:
|
||||
return <AudioBubbleIcon color={blue} {...props} />
|
||||
return <AudioBubbleIcon color={blue} mt={mt} />
|
||||
case InputBlockType.TEXT:
|
||||
return <TextInputIcon color={orange} {...props} />
|
||||
return <TextInputIcon color={orange} mt={mt} />
|
||||
case InputBlockType.NUMBER:
|
||||
return <NumberInputIcon color={orange} {...props} />
|
||||
return <NumberInputIcon color={orange} mt={mt} />
|
||||
case InputBlockType.EMAIL:
|
||||
return <EmailInputIcon color={orange} {...props} />
|
||||
return <EmailInputIcon color={orange} mt={mt} />
|
||||
case InputBlockType.URL:
|
||||
return <UrlInputIcon color={orange} {...props} />
|
||||
return <UrlInputIcon color={orange} mt={mt} />
|
||||
case InputBlockType.DATE:
|
||||
return <DateInputIcon color={orange} {...props} />
|
||||
return <DateInputIcon color={orange} mt={mt} />
|
||||
case InputBlockType.PHONE:
|
||||
return <PhoneInputIcon color={orange} {...props} />
|
||||
return <PhoneInputIcon color={orange} mt={mt} />
|
||||
case InputBlockType.CHOICE:
|
||||
return <ButtonsInputIcon color={orange} {...props} />
|
||||
return <ButtonsInputIcon color={orange} mt={mt} />
|
||||
case InputBlockType.PICTURE_CHOICE:
|
||||
return <PictureChoiceIcon color={orange} {...props} />
|
||||
return <PictureChoiceIcon color={orange} mt={mt} />
|
||||
case InputBlockType.PAYMENT:
|
||||
return <PaymentInputIcon color={orange} {...props} />
|
||||
return <PaymentInputIcon color={orange} mt={mt} />
|
||||
case InputBlockType.RATING:
|
||||
return <RatingInputIcon color={orange} {...props} />
|
||||
return <RatingInputIcon color={orange} mt={mt} />
|
||||
case InputBlockType.FILE:
|
||||
return <FileInputIcon color={orange} {...props} />
|
||||
return <FileInputIcon color={orange} mt={mt} />
|
||||
case LogicBlockType.SET_VARIABLE:
|
||||
return <SetVariableIcon color={purple} {...props} />
|
||||
return <SetVariableIcon color={purple} mt={mt} />
|
||||
case LogicBlockType.CONDITION:
|
||||
return <ConditionIcon color={purple} {...props} />
|
||||
return <ConditionIcon color={purple} mt={mt} />
|
||||
case LogicBlockType.REDIRECT:
|
||||
return <RedirectIcon color={purple} {...props} />
|
||||
return <RedirectIcon color={purple} mt={mt} />
|
||||
case LogicBlockType.SCRIPT:
|
||||
return <ScriptIcon {...props} />
|
||||
return <ScriptIcon mt={mt} />
|
||||
case LogicBlockType.WAIT:
|
||||
return <WaitIcon color={purple} {...props} />
|
||||
return <WaitIcon color={purple} mt={mt} />
|
||||
case LogicBlockType.JUMP:
|
||||
return <JumpIcon color={purple} {...props} />
|
||||
return <JumpIcon color={purple} mt={mt} />
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return <TypebotLinkIcon color={purple} {...props} />
|
||||
return <TypebotLinkIcon color={purple} mt={mt} />
|
||||
case LogicBlockType.AB_TEST:
|
||||
return <AbTestIcon color={purple} {...props} />
|
||||
return <AbTestIcon color={purple} mt={mt} />
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
return <GoogleSheetsLogo {...props} />
|
||||
return <GoogleSheetsLogo mt={mt} />
|
||||
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
||||
return <GoogleAnalyticsLogo {...props} />
|
||||
return <GoogleAnalyticsLogo mt={mt} />
|
||||
case IntegrationBlockType.WEBHOOK:
|
||||
return <WebhookIcon {...props} />
|
||||
return <WebhookIcon mt={mt} />
|
||||
case IntegrationBlockType.ZAPIER:
|
||||
return <ZapierLogo {...props} />
|
||||
return <ZapierLogo mt={mt} />
|
||||
case IntegrationBlockType.MAKE_COM:
|
||||
return <MakeComLogo {...props} />
|
||||
return <MakeComLogo mt={mt} />
|
||||
case IntegrationBlockType.PABBLY_CONNECT:
|
||||
return <PabblyConnectLogo {...props} />
|
||||
return <PabblyConnectLogo mt={mt} />
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return <SendEmailIcon {...props} />
|
||||
return <SendEmailIcon mt={mt} />
|
||||
case IntegrationBlockType.CHATWOOT:
|
||||
return <ChatwootLogo {...props} />
|
||||
case IntegrationBlockType.OPEN_AI:
|
||||
return <OpenAILogo fill={openAIColor} {...props} />
|
||||
return <ChatwootLogo mt={mt} />
|
||||
case IntegrationBlockType.PIXEL:
|
||||
return <PixelLogo {...props} />
|
||||
return <PixelLogo mt={mt} />
|
||||
case IntegrationBlockType.ZEMANTIC_AI:
|
||||
return <ZemanticAiLogo {...props} />
|
||||
return <ZemanticAiLogo mt={mt} />
|
||||
case 'start':
|
||||
return <FlagIcon {...props} />
|
||||
return <FlagIcon mt={mt} />
|
||||
case IntegrationBlockType.OPEN_AI:
|
||||
return <OpenAILogo mt={mt} fill={openAIColor} />
|
||||
default:
|
||||
return <ForgedBlockIcon type={type} mt={mt} />
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/const
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
import { Block } from '@typebot.io/schemas'
|
||||
import { ForgedBlockLabel } from '@/features/forge/ForgedBlockLabel'
|
||||
|
||||
type Props = { type: Block['type'] }
|
||||
|
||||
@ -98,5 +99,7 @@ export const BlockLabel = ({ type }: Props): JSX.Element => {
|
||||
return (
|
||||
<Text fontSize="sm">{t('editor.sidebarBlock.zemanticAi.label')}</Text>
|
||||
)
|
||||
default:
|
||||
return <ForgedBlockLabel type={type} />
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
Stack,
|
||||
Text,
|
||||
@ -22,6 +23,13 @@ import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/const
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
import { BlockV6 } from '@typebot.io/schemas'
|
||||
import { enabledBlocks } from '@typebot.io/forge-repository'
|
||||
|
||||
// Integration blocks migrated to forged blocks
|
||||
const legacyIntegrationBlocks = [
|
||||
IntegrationBlockType.OPEN_AI,
|
||||
IntegrationBlockType.ZEMANTIC_AI,
|
||||
]
|
||||
|
||||
export const BlocksSideBar = () => {
|
||||
const { t } = useTranslate()
|
||||
@ -160,9 +168,16 @@ export const BlocksSideBar = () => {
|
||||
{t('editor.sidebarBlocks.blockType.integrations.heading')}
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="3">
|
||||
{Object.values(IntegrationBlockType).map((type) => (
|
||||
<BlockCard key={type} type={type} onMouseDown={handleMouseDown} />
|
||||
))}
|
||||
{Object.values(IntegrationBlockType)
|
||||
.concat(enabledBlocks as any)
|
||||
.filter((type) => !legacyIntegrationBlocks.includes(type))
|
||||
.map((type) => (
|
||||
<BlockCard
|
||||
key={type}
|
||||
type={type}
|
||||
onMouseDown={handleMouseDown}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
|
||||
|
Reference in New Issue
Block a user