Add Dashboard
This commit is contained in:
74
apps/builder/components/dashboard/DashboardHeader.tsx
Normal file
74
apps/builder/components/dashboard/DashboardHeader.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
Text,
|
||||
HStack,
|
||||
Flex,
|
||||
Avatar,
|
||||
SkeletonCircle,
|
||||
Skeleton,
|
||||
} from '@chakra-ui/react'
|
||||
import { TypebotLogo } from 'assets/logos'
|
||||
import { useUser } from 'services/user'
|
||||
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
|
||||
import { LogOutIcon, SettingsIcon } from 'assets/icons'
|
||||
import { signOut } from 'next-auth/react'
|
||||
|
||||
export const DashboardHeader = () => {
|
||||
const user = useUser()
|
||||
|
||||
const handleLogOut = () => {
|
||||
signOut()
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex w="full" borderBottomWidth="1px" justify="center">
|
||||
<Flex
|
||||
justify="space-between"
|
||||
alignItems="center"
|
||||
h="16"
|
||||
maxW="1000px"
|
||||
flex="1"
|
||||
>
|
||||
<NextChakraLink
|
||||
className="w-24"
|
||||
href="/typebots"
|
||||
data-testid="authenticated"
|
||||
>
|
||||
<TypebotLogo w="30px" />
|
||||
</NextChakraLink>
|
||||
<Menu>
|
||||
<MenuButton>
|
||||
<HStack>
|
||||
<Skeleton isLoaded={user !== undefined}>
|
||||
<Text>{user?.name}</Text>
|
||||
</Skeleton>
|
||||
<SkeletonCircle isLoaded={user !== undefined}>
|
||||
<Avatar
|
||||
boxSize="35px"
|
||||
name={user?.name ?? undefined}
|
||||
src={user?.image ?? undefined}
|
||||
/>
|
||||
</SkeletonCircle>
|
||||
</HStack>
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
as={NextChakraLink}
|
||||
href="/account"
|
||||
icon={<SettingsIcon />}
|
||||
>
|
||||
My account
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleLogOut} icon={<LogOutIcon />}>
|
||||
Log out
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
181
apps/builder/components/dashboard/FolderContent.tsx
Normal file
181
apps/builder/components/dashboard/FolderContent.tsx
Normal file
@ -0,0 +1,181 @@
|
||||
import { DashboardFolder, Typebot } from '.prisma/client'
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
Skeleton,
|
||||
Stack,
|
||||
useToast,
|
||||
Wrap,
|
||||
} from '@chakra-ui/react'
|
||||
import {
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
DragOverlay,
|
||||
DragStartEvent,
|
||||
MouseSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core'
|
||||
import { FolderPlusIcon } from 'assets/icons'
|
||||
import React, { useState } from 'react'
|
||||
import { createFolder, useFolders } from 'services/folders'
|
||||
import { updateTypebot, useTypebots } from 'services/typebots'
|
||||
import { BackButton } from './FolderContent/BackButton'
|
||||
import { CreateBotButton } from './FolderContent/CreateBotButton'
|
||||
import { ButtonSkeleton, FolderButton } from './FolderContent/FolderButton'
|
||||
import { TypebotButton } from './FolderContent/TypebotButton'
|
||||
import { TypebotCardOverlay } from './FolderContent/TypebotButtonOverlay'
|
||||
|
||||
type Props = { folder: DashboardFolder | null }
|
||||
|
||||
export const FolderContent = ({ folder }: Props) => {
|
||||
const [isCreatingFolder, setIsCreatingFolder] = useState(false)
|
||||
const [draggedTypebot, setDraggedTypebot] = useState<Typebot | undefined>()
|
||||
const sensors = useSensors(
|
||||
useSensor(MouseSensor, {
|
||||
activationConstraint: {
|
||||
delay: 100,
|
||||
tolerance: 300,
|
||||
},
|
||||
})
|
||||
)
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
})
|
||||
const {
|
||||
folders,
|
||||
isLoading: isFolderLoading,
|
||||
mutate: mutateFolders,
|
||||
} = useFolders({
|
||||
parentId: folder?.id,
|
||||
onError: (error) => {
|
||||
toast({ title: "Couldn't fetch folders", description: error.message })
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
typebots,
|
||||
isLoading: isTypebotLoading,
|
||||
mutate: mutateTypebots,
|
||||
} = useTypebots({
|
||||
folderId: folder?.id,
|
||||
onError: (error) => {
|
||||
toast({ title: "Couldn't fetch typebots", description: error.message })
|
||||
},
|
||||
})
|
||||
|
||||
const handleDragStart = (event: DragStartEvent) => {
|
||||
if (!typebots) return
|
||||
setDraggedTypebot(typebots.find((c) => c.id === event.active.id))
|
||||
}
|
||||
|
||||
const handleDragEnd = async (event: DragEndEvent) => {
|
||||
if (!typebots) return
|
||||
const { over } = event
|
||||
if (over?.id && draggedTypebot?.id)
|
||||
await moveTypebotToFolder(draggedTypebot.id, over.id)
|
||||
setDraggedTypebot(undefined)
|
||||
}
|
||||
|
||||
const moveTypebotToFolder = async (typebotId: string, folderId: string) => {
|
||||
if (!typebots) return
|
||||
const { error } = await updateTypebot(typebotId, {
|
||||
folderId: folderId === 'root' ? null : folderId,
|
||||
})
|
||||
if (error) toast({ description: error.message })
|
||||
mutateTypebots({ typebots: typebots.filter((t) => t.id !== typebotId) })
|
||||
}
|
||||
|
||||
const handleCreateFolder = async () => {
|
||||
if (!folders) return
|
||||
setIsCreatingFolder(true)
|
||||
const { error, data: newFolder } = await createFolder({
|
||||
parentFolderId: folder?.id ?? null,
|
||||
})
|
||||
setIsCreatingFolder(false)
|
||||
if (error)
|
||||
return toast({ title: 'An error occured', description: error.message })
|
||||
if (newFolder) mutateFolders({ folders: [...folders, newFolder] })
|
||||
}
|
||||
|
||||
const handleTypebotDeleted = (deletedId: string) => {
|
||||
if (!typebots) return
|
||||
mutateTypebots({ typebots: typebots.filter((t) => t.id !== deletedId) })
|
||||
}
|
||||
|
||||
const handleFolderDeleted = (deletedId: string) => {
|
||||
if (!folders) return
|
||||
mutateFolders({ folders: folders.filter((f) => f.id !== deletedId) })
|
||||
}
|
||||
|
||||
const handleFolderRenamed = (folderId: string, name: string) => {
|
||||
if (!folders) return
|
||||
mutateFolders({
|
||||
folders: folders.map((f) => (f.id === folderId ? { ...f, name } : f)),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex w="full" justify="center" align="center" pt={4}>
|
||||
<DndContext
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
sensors={sensors}
|
||||
>
|
||||
<Stack w="1000px" spacing={6}>
|
||||
<Skeleton isLoaded={folder?.name !== undefined}>
|
||||
<Heading as="h1">{folder?.name}</Heading>
|
||||
</Skeleton>
|
||||
<Stack>
|
||||
<HStack>
|
||||
{folder && <BackButton id={folder.parentFolderId} />}
|
||||
<Button
|
||||
colorScheme="gray"
|
||||
leftIcon={<FolderPlusIcon />}
|
||||
onClick={handleCreateFolder}
|
||||
isLoading={isCreatingFolder || isFolderLoading}
|
||||
>
|
||||
Create a folder
|
||||
</Button>
|
||||
</HStack>
|
||||
<Wrap spacing={4}>
|
||||
<CreateBotButton
|
||||
folderId={folder?.id}
|
||||
isLoading={isTypebotLoading}
|
||||
/>
|
||||
{isFolderLoading && <ButtonSkeleton />}
|
||||
{folders &&
|
||||
folders.map((folder) => (
|
||||
<FolderButton
|
||||
key={folder.id.toString()}
|
||||
folder={folder}
|
||||
onFolderDeleted={() => handleFolderDeleted(folder.id)}
|
||||
onFolderRenamed={(newName: string) =>
|
||||
handleFolderRenamed(folder.id, newName)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{isTypebotLoading && <ButtonSkeleton />}
|
||||
{typebots &&
|
||||
typebots.map((typebot) => (
|
||||
<TypebotButton
|
||||
key={typebot.id.toString()}
|
||||
typebot={typebot}
|
||||
onTypebotDeleted={() => handleTypebotDeleted(typebot.id)}
|
||||
/>
|
||||
))}
|
||||
<DragOverlay dropAnimation={null}>
|
||||
{draggedTypebot && (
|
||||
<TypebotCardOverlay typebot={draggedTypebot} />
|
||||
)}
|
||||
</DragOverlay>
|
||||
</Wrap>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DndContext>
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { Button } from '@chakra-ui/react'
|
||||
import { useDroppable } from '@dnd-kit/core'
|
||||
import { ChevronLeftIcon } from 'assets/icons'
|
||||
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
|
||||
import React from 'react'
|
||||
|
||||
export const BackButton = ({ id }: { id: string | null }) => {
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
id: id?.toString() ?? 'root',
|
||||
})
|
||||
return (
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href={id ? `/typebots/folders/${id}` : '/typebots'}
|
||||
leftIcon={<ChevronLeftIcon />}
|
||||
variant={'outline'}
|
||||
colorScheme={isOver ? 'blue' : 'gray'}
|
||||
borderWidth={isOver ? '3px' : '1px'}
|
||||
ref={setNodeRef}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
)
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { Button, ButtonProps, Text, VStack } from '@chakra-ui/react'
|
||||
import { PlusIcon } from 'assets/icons'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
|
||||
export const CreateBotButton = ({
|
||||
folderId,
|
||||
...props
|
||||
}: { folderId?: string } & ButtonProps) => {
|
||||
const router = useRouter()
|
||||
|
||||
const handleClick = () =>
|
||||
folderId
|
||||
? router.push(`/typebots/create?folderId=${folderId}`)
|
||||
: router.push('/typebots/create')
|
||||
|
||||
return (
|
||||
<Button
|
||||
mr={{ sm: 6 }}
|
||||
mb={6}
|
||||
style={{ width: '225px', height: '270px' }}
|
||||
onClick={handleClick}
|
||||
paddingX={6}
|
||||
whiteSpace={'normal'}
|
||||
{...props}
|
||||
>
|
||||
<VStack spacing="6">
|
||||
<PlusIcon fontSize="40px" />
|
||||
<Text
|
||||
fontSize={18}
|
||||
fontWeight="medium"
|
||||
maxW={40}
|
||||
textAlign="center"
|
||||
mt="6"
|
||||
>
|
||||
Create a typebot
|
||||
</Text>
|
||||
</VStack>
|
||||
</Button>
|
||||
)
|
||||
}
|
158
apps/builder/components/dashboard/FolderContent/FolderButton.tsx
Normal file
158
apps/builder/components/dashboard/FolderContent/FolderButton.tsx
Normal file
@ -0,0 +1,158 @@
|
||||
import { DashboardFolder } from '.prisma/client'
|
||||
import {
|
||||
Button,
|
||||
Editable,
|
||||
EditableInput,
|
||||
EditablePreview,
|
||||
MenuItem,
|
||||
useDisclosure,
|
||||
Text,
|
||||
VStack,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
useToast,
|
||||
SkeletonText,
|
||||
SkeletonCircle,
|
||||
WrapItem,
|
||||
} from '@chakra-ui/react'
|
||||
import { useDroppable } from '@dnd-kit/core'
|
||||
import { FolderIcon, MoreVerticalIcon } from 'assets/icons'
|
||||
import { ConfirmModal } from 'components/modals/ConfirmModal'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
import { deleteFolder, updateFolder } from 'services/folders'
|
||||
|
||||
export const FolderButton = ({
|
||||
folder,
|
||||
onFolderDeleted,
|
||||
onFolderRenamed,
|
||||
}: {
|
||||
folder: DashboardFolder
|
||||
onFolderDeleted: () => void
|
||||
onFolderRenamed: (newName: string) => void
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
id: folder.id.toString(),
|
||||
})
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
})
|
||||
|
||||
const onDeleteClick = async () => {
|
||||
const { error } = await deleteFolder(folder.id)
|
||||
return error
|
||||
? toast({
|
||||
title: "Couldn't delete the folder",
|
||||
description: error.message,
|
||||
})
|
||||
: onFolderDeleted()
|
||||
}
|
||||
|
||||
const onRenameSubmit = async (newName: string) => {
|
||||
if (newName === '' || newName === folder.name) return
|
||||
const { error } = await updateFolder(folder.id, { name: newName })
|
||||
return error
|
||||
? toast({ title: 'An error occured', description: error.message })
|
||||
: onFolderRenamed(newName)
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
router.push(`/typebots/folders/${folder.id}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
as={WrapItem}
|
||||
ref={setNodeRef}
|
||||
style={{ width: '225px', height: '270px' }}
|
||||
paddingX={6}
|
||||
whiteSpace={'normal'}
|
||||
pos="relative"
|
||||
cursor="pointer"
|
||||
variant="outline"
|
||||
colorScheme={isOver ? 'blue' : 'gray'}
|
||||
borderWidth={isOver ? '3px' : '1px'}
|
||||
justifyContent="center"
|
||||
onClick={handleClick}
|
||||
data-testid="folder-button"
|
||||
>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
icon={<MoreVerticalIcon />}
|
||||
aria-label="Show folder menu"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
colorScheme="blue"
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
pos="absolute"
|
||||
top="5"
|
||||
right="5"
|
||||
/>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
color="red"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onOpen()
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
<VStack spacing="4">
|
||||
<FolderIcon fontSize={50} color="blue.500" fill="blue.500" />
|
||||
<Editable
|
||||
defaultValue={folder.name}
|
||||
fontSize="18"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onSubmit={onRenameSubmit}
|
||||
>
|
||||
<EditablePreview _hover={{ bgColor: 'gray.300' }} px="2" />
|
||||
<EditableInput textAlign="center" />
|
||||
</Editable>
|
||||
</VStack>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
confirmButtonLabel={'Delete'}
|
||||
message={
|
||||
<Text>
|
||||
Are you sure you want to delete <strong>{folder.name}</strong>{' '}
|
||||
folder? (Everything inside will be move to your dashboard)
|
||||
</Text>
|
||||
}
|
||||
title={`Delete ${folder.name}?`}
|
||||
onConfirm={onDeleteClick}
|
||||
confirmButtonColor="red"
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export const ButtonSkeleton = () => (
|
||||
<Button
|
||||
as={VStack}
|
||||
mr={{ sm: 6 }}
|
||||
mb={6}
|
||||
style={{ width: '225px', height: '270px' }}
|
||||
paddingX={6}
|
||||
whiteSpace={'normal'}
|
||||
pos="relative"
|
||||
cursor="pointer"
|
||||
variant="outline"
|
||||
colorScheme={'gray'}
|
||||
>
|
||||
<VStack spacing="6" w="full">
|
||||
<SkeletonCircle boxSize="45px" />
|
||||
<SkeletonText noOfLines={2} w="full" />
|
||||
</VStack>
|
||||
</Button>
|
||||
)
|
@ -0,0 +1,133 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
MenuItem,
|
||||
Text,
|
||||
useDisclosure,
|
||||
useToast,
|
||||
VStack,
|
||||
WrapItem,
|
||||
} from '@chakra-ui/react'
|
||||
import { useDraggable } from '@dnd-kit/core'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Typebot } from '@typebot/prisma'
|
||||
import { isMobile } from 'services/utils'
|
||||
import { MoreButton } from 'components/MoreButton'
|
||||
import { ConfirmModal } from 'components/modals/ConfirmModal'
|
||||
import { GlobeIcon, ToolIcon } from 'assets/icons'
|
||||
import { deleteTypebot, duplicateTypebot } from 'services/typebots'
|
||||
|
||||
type ChatbotCardProps = {
|
||||
typebot: Typebot
|
||||
onTypebotDeleted: () => void
|
||||
}
|
||||
|
||||
export const TypebotButton = ({
|
||||
typebot,
|
||||
onTypebotDeleted,
|
||||
}: ChatbotCardProps) => {
|
||||
const router = useRouter()
|
||||
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
||||
id: typebot.id.toString(),
|
||||
})
|
||||
const {
|
||||
isOpen: isDeleteOpen,
|
||||
onOpen: onDeleteOpen,
|
||||
onClose: onDeleteClose,
|
||||
} = useDisclosure()
|
||||
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
})
|
||||
|
||||
const handleTypebotClick = () => {
|
||||
router.push(
|
||||
isMobile
|
||||
? `/typebots/${typebot.id}/results/responses`
|
||||
: `/typebots/${typebot.id}/edit`
|
||||
)
|
||||
}
|
||||
|
||||
const handleDeleteTypebotClick = async () => {
|
||||
const { error } = await deleteTypebot(typebot.id)
|
||||
if (error)
|
||||
return toast({
|
||||
title: "Couldn't delete typebot",
|
||||
description: error.message,
|
||||
})
|
||||
onTypebotDeleted()
|
||||
}
|
||||
|
||||
const handleDuplicateClick = async () => {
|
||||
const { data: createdTypebot, error } = await duplicateTypebot(typebot)
|
||||
if (error)
|
||||
return toast({
|
||||
title: "Couldn't duplicate typebot",
|
||||
description: error.message,
|
||||
})
|
||||
if (createdTypebot) router.push(`/typebots/${createdTypebot?.id}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
as={WrapItem}
|
||||
onClick={handleTypebotClick}
|
||||
display="flex"
|
||||
flexDir="column"
|
||||
variant="outline"
|
||||
colorScheme="gray"
|
||||
color="gray.800"
|
||||
w="225px"
|
||||
h="270px"
|
||||
mr={{ sm: 6 }}
|
||||
mb={6}
|
||||
rounded="lg"
|
||||
whiteSpace="normal"
|
||||
data-testid="typebot-button"
|
||||
opacity={isDragging ? 0.2 : 1}
|
||||
ref={setNodeRef}
|
||||
{...listeners}
|
||||
{...attributes}
|
||||
>
|
||||
<MoreButton pos="absolute" top="10px" right="10px">
|
||||
<MenuItem onClick={handleDuplicateClick}>Duplicate</MenuItem>
|
||||
<MenuItem color="red" onClick={onDeleteOpen}>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</MoreButton>
|
||||
<VStack spacing="4">
|
||||
<Flex
|
||||
boxSize="45px"
|
||||
rounded="full"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
bgColor={typebot.publishedTypebotId ? 'blue.500' : 'gray.400'}
|
||||
color="white"
|
||||
>
|
||||
{typebot.publishedTypebotId ? (
|
||||
<GlobeIcon fill="white" fontSize="20px" />
|
||||
) : (
|
||||
<ToolIcon fill="white" fontSize="20px" />
|
||||
)}
|
||||
</Flex>
|
||||
<Text>{typebot.name}</Text>
|
||||
</VStack>
|
||||
<ConfirmModal
|
||||
message={
|
||||
<Text>
|
||||
Are you sure you want to delete your Typebot "{typebot.name}
|
||||
".
|
||||
<br />
|
||||
All associated data will be lost.
|
||||
</Text>
|
||||
}
|
||||
confirmButtonLabel="Delete"
|
||||
onConfirm={handleDeleteTypebotClick}
|
||||
isOpen={isDeleteOpen}
|
||||
onClose={onDeleteClose}
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import { Button, Flex, Text, VStack } from '@chakra-ui/react'
|
||||
import { Typebot } from '.prisma/client'
|
||||
import { GlobeIcon, ToolIcon } from 'assets/icons'
|
||||
|
||||
type Props = {
|
||||
typebot: Typebot
|
||||
}
|
||||
|
||||
export const TypebotCardOverlay = ({ typebot }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className="sm:mr-6 mb-6 focus:outline-none rounded-lg "
|
||||
style={{ width: '225px', height: '270px' }}
|
||||
>
|
||||
<Button
|
||||
display="flex"
|
||||
flexDir="column"
|
||||
variant="outline"
|
||||
colorScheme="gray"
|
||||
w="full"
|
||||
h="full"
|
||||
whiteSpace="normal"
|
||||
>
|
||||
<VStack spacing={4}>
|
||||
<Flex
|
||||
boxSize="45px"
|
||||
rounded="full"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
bgColor={typebot.publishedTypebotId ? 'blue.500' : 'gray.400'}
|
||||
color="white"
|
||||
>
|
||||
{typebot.publishedTypebotId ? (
|
||||
<GlobeIcon fill="white" fontSize="20px" />
|
||||
) : (
|
||||
<ToolIcon fill="white" fontSize="20px" />
|
||||
)}
|
||||
</Flex>
|
||||
<Text>{typebot.name}</Text>
|
||||
</VStack>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user