Add Dashboard
This commit is contained in:
@ -1,5 +1,8 @@
|
|||||||
DATABASE_URL=postgresql://username:password@host:5450/typebot?schema=public
|
DATABASE_URL=postgresql://username:password@host:5450/typebot?schema=public
|
||||||
|
|
||||||
|
SECRET=secret
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
|
||||||
# Used for email auth and email notifications
|
# Used for email auth and email notifications
|
||||||
EMAIL_SERVER_USER=username
|
EMAIL_SERVER_USER=username
|
||||||
EMAIL_SERVER_PASSWORD=password
|
EMAIL_SERVER_PASSWORD=password
|
||||||
|
20
README.md
20
README.md
@ -33,26 +33,10 @@
|
|||||||
|
|
||||||
3. Copy `.env.example` to `.env`
|
3. Copy `.env.example` to `.env`
|
||||||
4. Configure environment variables in the `.env` file.
|
4. Configure environment variables in the `.env` file.
|
||||||
5. Setup the database
|
5. Run the applications
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yarn dev:setup
|
yarn dx
|
||||||
```
|
|
||||||
|
|
||||||
6. Run the applications
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn dev:builder
|
|
||||||
```
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn dev:viewer
|
|
||||||
```
|
|
||||||
|
|
||||||
7. Open [Prisma Studio](https://www.prisma.io/studio) to look at or modify the database content
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn db:inspect
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
@ -1 +1,72 @@
|
|||||||
import { IconProps, Icon } from '@chakra-ui/react'
|
import { IconProps, Icon } from '@chakra-ui/react'
|
||||||
|
|
||||||
|
const featherIconsBaseProps: IconProps = {
|
||||||
|
fill: 'none',
|
||||||
|
stroke: 'currentColor',
|
||||||
|
strokeWidth: '2px',
|
||||||
|
strokeLinecap: 'round',
|
||||||
|
strokeLinejoin: 'round',
|
||||||
|
}
|
||||||
|
export const SettingsIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const LogOutIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
|
||||||
|
<polyline points="16 17 21 12 16 7"></polyline>
|
||||||
|
<line x1="21" y1="12" x2="9" y2="12"></line>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ChevronLeftIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<polyline points="15 18 9 12 15 6"></polyline>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const PlusIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const FolderIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const MoreVerticalIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<circle cx="12" cy="12" r="1"></circle>
|
||||||
|
<circle cx="12" cy="5" r="1"></circle>
|
||||||
|
<circle cx="12" cy="19" r="1"></circle>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const GlobeIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<circle cx="12" cy="12" r="10"></circle>
|
||||||
|
<line x1="2" y1="12" x2="22" y2="12"></line>
|
||||||
|
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ToolIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const FolderPlusIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
|
||||||
|
<line x1="12" y1="11" x2="12" y2="17"></line>
|
||||||
|
<line x1="9" y1="14" x2="15" y2="14"></line>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { User } from '@typebot/prisma'
|
|
||||||
|
|
||||||
export type withAuthProps = {
|
|
||||||
user?: User
|
|
||||||
}
|
|
||||||
|
|
||||||
const withAuth =
|
const withAuth =
|
||||||
(WrappedComponent: ({ user }: withAuthProps) => JSX.Element) =>
|
(WrappedComponent: (props: any) => JSX.Element) =>
|
||||||
(props: JSX.IntrinsicAttributes & withAuthProps) => {
|
(props: JSX.IntrinsicAttributes) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { data: session, status } = useSession()
|
const { status } = useSession()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!router.isReady) return
|
if (!router.isReady) return
|
||||||
@ -19,9 +14,7 @@ const withAuth =
|
|||||||
if (status === 'unauthenticated') router.replace('/signin')
|
if (status === 'unauthenticated') router.replace('/signin')
|
||||||
}, [status, router])
|
}, [status, router])
|
||||||
|
|
||||||
return (
|
return <WrappedComponent {...props} />
|
||||||
<WrappedComponent user={session?.user as User | undefined} {...props} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuth
|
export default withAuth
|
||||||
|
28
apps/builder/components/MoreButton.tsx
Normal file
28
apps/builder/components/MoreButton.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import {
|
||||||
|
ButtonProps,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuList,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
import { MoreVerticalIcon } from 'assets/icons'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
type Props = { children: ReactNode } & ButtonProps
|
||||||
|
|
||||||
|
export const MoreButton = ({ children, ...props }: Props) => {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuButton
|
||||||
|
as={IconButton}
|
||||||
|
icon={<MoreVerticalIcon />}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="ghost"
|
||||||
|
size="lg"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<MenuList>{children}</MenuList>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}
|
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>
|
||||||
|
)
|
||||||
|
}
|
77
apps/builder/components/modals/ConfirmModal.tsx
Normal file
77
apps/builder/components/modals/ConfirmModal.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogBody,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
Button,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
|
||||||
|
type ConfirmDeleteModalProps = {
|
||||||
|
isOpen: boolean
|
||||||
|
onConfirm: () => Promise<unknown>
|
||||||
|
onClose: () => void
|
||||||
|
message: JSX.Element
|
||||||
|
title?: string
|
||||||
|
confirmButtonLabel: string
|
||||||
|
confirmButtonColor?: 'blue' | 'red'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConfirmModal = ({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
confirmButtonLabel,
|
||||||
|
onConfirm,
|
||||||
|
confirmButtonColor = 'red',
|
||||||
|
}: ConfirmDeleteModalProps) => {
|
||||||
|
const [confirmLoading, setConfirmLoading] = useState(false)
|
||||||
|
const cancelRef = useRef(null)
|
||||||
|
|
||||||
|
const onConfirmClick = async () => {
|
||||||
|
setConfirmLoading(true)
|
||||||
|
try {
|
||||||
|
await onConfirm()
|
||||||
|
} catch (e) {
|
||||||
|
setConfirmLoading(false)
|
||||||
|
return setConfirmLoading(false)
|
||||||
|
}
|
||||||
|
setConfirmLoading(false)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog
|
||||||
|
isOpen={isOpen}
|
||||||
|
leastDestructiveRef={cancelRef}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<AlertDialogOverlay>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||||
|
{title ?? 'Are you sure?'}
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
|
<AlertDialogBody>{message}</AlertDialogBody>
|
||||||
|
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<Button ref={cancelRef} onClick={onClose} colorScheme="gray">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme={confirmButtonColor}
|
||||||
|
onClick={onConfirmClick}
|
||||||
|
ml={3}
|
||||||
|
isLoading={confirmLoading}
|
||||||
|
>
|
||||||
|
{confirmButtonLabel}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogOverlay>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
30
apps/builder/cypress/plugins/database.ts
Normal file
30
apps/builder/cypress/plugins/database.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { PrismaClient } from '.prisma/client'
|
||||||
|
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
const teardownTestData = async () => prisma.user.deleteMany()
|
||||||
|
|
||||||
|
export const seedDb = async () => {
|
||||||
|
await teardownTestData()
|
||||||
|
await createUsers()
|
||||||
|
await createFolders()
|
||||||
|
return createTypebots()
|
||||||
|
}
|
||||||
|
|
||||||
|
const createUsers = () =>
|
||||||
|
prisma.user.createMany({
|
||||||
|
data: [
|
||||||
|
{ id: 'test1', email: 'test1@gmail.com', emailVerified: new Date() },
|
||||||
|
{ id: 'test2', email: 'test2@gmail.com', emailVerified: new Date() },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const createFolders = () =>
|
||||||
|
prisma.dashboardFolder.createMany({
|
||||||
|
data: [{ ownerId: 'test2', name: 'Folder #1', id: 'folder1' }],
|
||||||
|
})
|
||||||
|
|
||||||
|
const createTypebots = () =>
|
||||||
|
prisma.typebot.createMany({
|
||||||
|
data: [{ name: 'Typebot #1', ownerId: 'test2' }],
|
||||||
|
})
|
@ -3,6 +3,7 @@ import {
|
|||||||
FacebookSocialLogin,
|
FacebookSocialLogin,
|
||||||
GoogleSocialLogin,
|
GoogleSocialLogin,
|
||||||
} from 'cypress-social-logins/src/Plugins'
|
} from 'cypress-social-logins/src/Plugins'
|
||||||
|
import { seedDb } from './database'
|
||||||
/// <reference types="cypress" />
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,6 +15,7 @@ const handler = (on: any) => {
|
|||||||
GoogleSocialLogin: GoogleSocialLogin,
|
GoogleSocialLogin: GoogleSocialLogin,
|
||||||
FacebookSocialLogin: FacebookSocialLogin,
|
FacebookSocialLogin: FacebookSocialLogin,
|
||||||
GitHubSocialLogin: GitHubSocialLogin,
|
GitHubSocialLogin: GitHubSocialLogin,
|
||||||
|
seed: seedDb,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,37 +1,93 @@
|
|||||||
// ***********************************************
|
import { signIn, signOut } from 'next-auth/react'
|
||||||
// This example commands.js shows you how to
|
|
||||||
// create various custom commands and overwrite
|
|
||||||
// existing commands.
|
|
||||||
//
|
|
||||||
// For more comprehensive examples of custom
|
|
||||||
// commands please read more here:
|
|
||||||
// https://on.cypress.io/custom-commands
|
|
||||||
// ***********************************************
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a parent command --
|
|
||||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a child command --
|
|
||||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a dual command --
|
|
||||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This will overwrite an existing command --
|
|
||||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
|
||||||
import '@testing-library/cypress/add-commands'
|
|
||||||
|
|
||||||
Cypress.Commands.add('logOutByApi', () =>
|
Cypress.Commands.add('signOut', () => {
|
||||||
cy
|
cy.log(`🔐 Sign out`)
|
||||||
.request('GET', `${Cypress.env('SITE_NAME')}/api/auth/csrf/login`)
|
return cy.wrap(signOut({ redirect: false }), { log: false })
|
||||||
.its('body')
|
})
|
||||||
.then((result) => {
|
|
||||||
cy.request('POST', `${Cypress.env('SITE_NAME')}/api/auth/signout`, {
|
Cypress.Commands.add('signIn', (email: string) => {
|
||||||
csrfToken: result.csrfToken,
|
cy.log(`🔐 Sign in as ${email}`)
|
||||||
|
return cy.wrap(signIn('credentials', { redirect: false, email }), {
|
||||||
|
log: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add(
|
||||||
|
'mouseMoveBy',
|
||||||
|
{
|
||||||
|
prevSubject: 'element',
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
(
|
||||||
|
subject: JQuery<HTMLElement>,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
options?: { delay: number }
|
||||||
|
) => {
|
||||||
|
cy.wrap(subject, { log: false })
|
||||||
|
.then((subject) => {
|
||||||
|
const initialRect = subject.get(0).getBoundingClientRect()
|
||||||
|
const windowScroll = getDocumentScroll()
|
||||||
|
|
||||||
|
return [subject, initialRect, windowScroll] as const
|
||||||
})
|
})
|
||||||
})
|
.then(([subject, initialRect, initialWindowScroll]) => {
|
||||||
|
cy.wrap(subject)
|
||||||
|
.trigger('mousedown', { force: true })
|
||||||
|
.wait(options?.delay || 0, { log: Boolean(options?.delay) })
|
||||||
|
.trigger('mousemove', {
|
||||||
|
force: true,
|
||||||
|
clientX: Math.floor(
|
||||||
|
initialRect.left + initialRect.width / 2 + x / 2
|
||||||
|
),
|
||||||
|
clientY: Math.floor(
|
||||||
|
initialRect.top + initialRect.height / 2 + y / 2
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.trigger('mousemove', {
|
||||||
|
force: true,
|
||||||
|
clientX: Math.floor(initialRect.left + initialRect.width / 2 + x),
|
||||||
|
clientY: Math.floor(initialRect.top + initialRect.height / 2 + y),
|
||||||
|
})
|
||||||
|
// .wait(1000)
|
||||||
|
.trigger('mouseup', { force: true })
|
||||||
|
.wait(250)
|
||||||
|
.then((subject: any) => {
|
||||||
|
const finalRect = subject.get(0).getBoundingClientRect()
|
||||||
|
const windowScroll = getDocumentScroll()
|
||||||
|
const windowScrollDelta = {
|
||||||
|
x: windowScroll.x - initialWindowScroll.x,
|
||||||
|
y: windowScroll.y - initialWindowScroll.y,
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = {
|
||||||
|
x: Math.round(
|
||||||
|
finalRect.left - initialRect.left - windowScrollDelta.x
|
||||||
|
),
|
||||||
|
y: Math.round(
|
||||||
|
finalRect.top - initialRect.top - windowScrollDelta.y
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return [subject, { initialRect, finalRect, delta }] as const
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const getDocumentScroll = () => {
|
||||||
|
if (document.scrollingElement) {
|
||||||
|
const { scrollTop, scrollLeft } = document.scrollingElement
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: scrollTop,
|
||||||
|
y: scrollLeft,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,20 +13,33 @@
|
|||||||
// You can read more here:
|
// You can read more here:
|
||||||
// https://on.cypress.io/configuration
|
// https://on.cypress.io/configuration
|
||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
|
/// <reference types="cypress" />
|
||||||
// Import commands.js using ES2015 syntax:
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Cypress {
|
namespace Cypress {
|
||||||
interface Chainable {
|
interface Chainable {
|
||||||
/**
|
signOut(): Chainable<any>
|
||||||
* Log out using the NextAuth API.
|
signIn(email: string): Chainable<any>
|
||||||
* @example cy.logOutByApi()
|
mouseMoveBy(
|
||||||
*/
|
x: number,
|
||||||
logOutByApi(): Chainable<Response<any>>
|
y: number,
|
||||||
|
options?: { delay: number }
|
||||||
|
): Chainable<
|
||||||
|
[
|
||||||
|
Element,
|
||||||
|
{
|
||||||
|
initialRect: ClientRect
|
||||||
|
finalRect: ClientRect
|
||||||
|
delta: { x: number; y: number }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import '@testing-library/cypress/add-commands'
|
||||||
import './commands'
|
import './commands'
|
||||||
|
|
||||||
// Alternatively you can use CommonJS syntax:
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
describe('SignIn page', () => {
|
describe('SignIn page', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.logOutByApi()
|
cy.signOut()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can continue with Google', () => {
|
it('can continue with Google', () => {
|
||||||
cy.visit('/signin')
|
cy.visit('/signin')
|
||||||
const username = Cypress.env('GOOGLE_USER')
|
const username = Cypress.env('GOOGLE_USER')
|
||||||
@ -98,6 +99,6 @@ const exectueSocialLogin = (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
cy.visit('/typebots')
|
cy.visit('/typebots')
|
||||||
cy.findByText(`Hello ${username}`).should('exist')
|
cy.findByRole('button', { name: 'Create a folder' }).should('exist')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
49
apps/builder/cypress/tests/dashboard.ts
Normal file
49
apps/builder/cypress/tests/dashboard.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
describe('Dashboard page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.task('seed')
|
||||||
|
cy.signOut()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should navigate correctly', () => {
|
||||||
|
cy.signIn('test1@gmail.com')
|
||||||
|
cy.visit('/typebots')
|
||||||
|
createFolder('My folder #1')
|
||||||
|
cy.findByTestId('folder-button').click()
|
||||||
|
cy.findByRole('heading', { name: 'My folder #1' }).should('exist')
|
||||||
|
createFolder('My folder #2')
|
||||||
|
cy.findByTestId('folder-button').click()
|
||||||
|
cy.findByRole('heading', { name: 'My folder #2' }).should('exist')
|
||||||
|
cy.findByRole('link', { name: 'Back' }).click()
|
||||||
|
cy.findByRole('heading', { name: 'My folder #1' }).should('exist')
|
||||||
|
cy.findByRole('link', { name: 'Back' }).click()
|
||||||
|
cy.findByRole('button', { name: 'Show folder menu' }).click()
|
||||||
|
cy.findByRole('menuitem', { name: 'Delete' }).click()
|
||||||
|
cy.findByRole('button', { name: 'Delete' }).click()
|
||||||
|
cy.findByDisplayValue('My folder #2').should('exist')
|
||||||
|
cy.findByRole('button', { name: 'Show folder menu' }).click()
|
||||||
|
cy.findByRole('menuitem', { name: 'Delete' }).click()
|
||||||
|
cy.findByRole('button', { name: 'Delete' }).click()
|
||||||
|
cy.findByDisplayValue('My folder #2').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be droppable', () => {
|
||||||
|
cy.signIn('test2@gmail.com')
|
||||||
|
cy.visit('/typebots')
|
||||||
|
cy.findByTestId('typebot-button').mouseMoveBy(-100, 0, {
|
||||||
|
delay: 120,
|
||||||
|
})
|
||||||
|
cy.visit('/typebots/folders/folder1')
|
||||||
|
cy.findByTestId('typebot-button').mouseMoveBy(-300, -100, {
|
||||||
|
delay: 120,
|
||||||
|
})
|
||||||
|
cy.visit('/typebots')
|
||||||
|
cy.findByDisplayValue('Folder #1').should('exist')
|
||||||
|
cy.findByText('Typebot #1').should('exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const createFolder = (folderName: string) => {
|
||||||
|
cy.findByRole('button', { name: 'Create a folder' }).click({ force: true })
|
||||||
|
cy.findByText('New folder').click({ force: true })
|
||||||
|
cy.findByDisplayValue('New folder').type(`${folderName}{enter}`)
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
"target": "es5",
|
"target": "es5",
|
||||||
"isolatedModules": false,
|
"isolatedModules": false,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"noEmit": true
|
"noEmit": true,
|
||||||
|
"downlevelIteration": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
apps/builder/libs/prisma.ts
Normal file
15
apps/builder/libs/prisma.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { PrismaClient } from '@typebot/prisma'
|
||||||
|
|
||||||
|
declare const global: { prisma: PrismaClient }
|
||||||
|
let prisma: PrismaClient
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
prisma = new PrismaClient()
|
||||||
|
} else {
|
||||||
|
if (!global.prisma) {
|
||||||
|
global.prisma = new PrismaClient()
|
||||||
|
}
|
||||||
|
prisma = global.prisma
|
||||||
|
}
|
||||||
|
|
||||||
|
export default prisma
|
@ -9,17 +9,22 @@
|
|||||||
"cypress": "cypress open"
|
"cypress": "cypress open"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@chakra-ui/css-reset": "^1.1.1",
|
||||||
"@chakra-ui/react": "^1.7.2",
|
"@chakra-ui/react": "^1.7.2",
|
||||||
|
"@dnd-kit/core": "^4.0.3",
|
||||||
"@emotion/react": "^11",
|
"@emotion/react": "^11",
|
||||||
"@emotion/styled": "^11",
|
"@emotion/styled": "^11",
|
||||||
"@next-auth/prisma-adapter": "next",
|
"@next-auth/prisma-adapter": "next",
|
||||||
|
"focus-visible": "^5.2.0",
|
||||||
"framer-motion": "^4",
|
"framer-motion": "^4",
|
||||||
"next": "^12.0.4",
|
"next": "^12.0.4",
|
||||||
"next-auth": "beta",
|
"next-auth": "beta",
|
||||||
"nodemailer": "^6.7.1",
|
"nodemailer": "^6.7.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^17.0.2",
|
||||||
|
"swr": "^1.0.1",
|
||||||
|
"use-debounce": "^7.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/cypress": "^8.0.2",
|
"@testing-library/cypress": "^8.0.2",
|
||||||
|
@ -3,8 +3,9 @@ import { AppProps } from 'next/app'
|
|||||||
import { SessionProvider } from 'next-auth/react'
|
import { SessionProvider } from 'next-auth/react'
|
||||||
import { ChakraProvider } from '@chakra-ui/react'
|
import { ChakraProvider } from '@chakra-ui/react'
|
||||||
import { customTheme } from 'libs/chakra'
|
import { customTheme } from 'libs/chakra'
|
||||||
import 'assets/styles/routerProgressBar.css'
|
|
||||||
import { useRouterProgressBar } from 'libs/routerProgressBar'
|
import { useRouterProgressBar } from 'libs/routerProgressBar'
|
||||||
|
import 'assets/styles/routerProgressBar.css'
|
||||||
|
import 'focus-visible/dist/focus-visible'
|
||||||
|
|
||||||
const App = ({ Component, pageProps }: AppProps) => {
|
const App = ({ Component, pageProps }: AppProps) => {
|
||||||
useRouterProgressBar()
|
useRouterProgressBar()
|
||||||
|
@ -1,39 +1,87 @@
|
|||||||
import NextAuth from 'next-auth'
|
import NextAuth from 'next-auth'
|
||||||
import { PrismaAdapter } from '@next-auth/prisma-adapter'
|
import { PrismaAdapter } from '@next-auth/prisma-adapter'
|
||||||
import { PrismaClient } from '@typebot/prisma'
|
|
||||||
import EmailProvider from 'next-auth/providers/email'
|
import EmailProvider from 'next-auth/providers/email'
|
||||||
import GitHubProvider from 'next-auth/providers/github'
|
import GitHubProvider from 'next-auth/providers/github'
|
||||||
import GoogleProvider from 'next-auth/providers/google'
|
import GoogleProvider from 'next-auth/providers/google'
|
||||||
import FacebookProvider from 'next-auth/providers/facebook'
|
import FacebookProvider from 'next-auth/providers/facebook'
|
||||||
|
import CredentialsProvider from 'next-auth/providers/credentials'
|
||||||
|
import prisma from 'libs/prisma'
|
||||||
|
import { Provider } from 'next-auth/providers'
|
||||||
|
import { User } from '@typebot/prisma'
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
const providers: Provider[] = [
|
||||||
|
EmailProvider({
|
||||||
|
server: {
|
||||||
|
host: process.env.EMAIL_SERVER_HOST,
|
||||||
|
port: process.env.EMAIL_SERVER_PORT,
|
||||||
|
auth: {
|
||||||
|
user: process.env.EMAIL_SERVER_USER,
|
||||||
|
pass: process.env.EMAIL_SERVER_PASSWORD,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
from: process.env.EMAIL_FROM,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET)
|
||||||
|
providers.push(
|
||||||
|
GitHubProvider({
|
||||||
|
clientId: process.env.GITHUB_CLIENT_ID,
|
||||||
|
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET)
|
||||||
|
providers.push(
|
||||||
|
GoogleProvider({
|
||||||
|
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||||
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
if (process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET)
|
||||||
|
providers.push(
|
||||||
|
FacebookProvider({
|
||||||
|
clientId: process.env.FACEBOOK_CLIENT_ID,
|
||||||
|
clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production')
|
||||||
|
providers.push(
|
||||||
|
CredentialsProvider({
|
||||||
|
name: 'Credentials',
|
||||||
|
credentials: {
|
||||||
|
email: {
|
||||||
|
label: 'Email',
|
||||||
|
type: 'email',
|
||||||
|
placeholder: 'email@email.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async authorize(credentials) {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { email: credentials?.email },
|
||||||
|
})
|
||||||
|
return user
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
export default NextAuth({
|
export default NextAuth({
|
||||||
adapter: PrismaAdapter(prisma),
|
adapter: PrismaAdapter(prisma),
|
||||||
secret: process.env.SECRET,
|
secret: process.env.SECRET,
|
||||||
providers: [
|
providers,
|
||||||
EmailProvider({
|
session: {
|
||||||
server: {
|
strategy: process.env.NODE_ENV === 'production' ? 'database' : 'jwt',
|
||||||
host: process.env.EMAIL_SERVER_HOST,
|
},
|
||||||
port: process.env.EMAIL_SERVER_PORT,
|
callbacks: {
|
||||||
auth: {
|
jwt: async ({ token, user }) => {
|
||||||
user: process.env.EMAIL_SERVER_USER,
|
user && (token.user = user)
|
||||||
pass: process.env.EMAIL_SERVER_PASSWORD,
|
return token
|
||||||
},
|
},
|
||||||
},
|
session: async ({ session, token }) => {
|
||||||
from: process.env.EMAIL_FROM,
|
if (token.user) session.user = token.user as User
|
||||||
}),
|
return session
|
||||||
GitHubProvider({
|
},
|
||||||
clientId: process.env.GITHUB_CLIENT_ID,
|
},
|
||||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
||||||
}),
|
|
||||||
GoogleProvider({
|
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID ?? '',
|
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? '',
|
|
||||||
}),
|
|
||||||
FacebookProvider({
|
|
||||||
clientId: process.env.FACEBOOK_CLIENT_ID ?? '',
|
|
||||||
clientSecret: process.env.FACEBOOK_CLIENT_SECRET ?? '',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
36
apps/builder/pages/api/folders.ts
Normal file
36
apps/builder/pages/api/folders.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { DashboardFolder, User } from '@typebot/prisma'
|
||||||
|
import prisma from 'libs/prisma'
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { getSession } from 'next-auth/react'
|
||||||
|
import { methodNotAllowed } from 'services/api/utils'
|
||||||
|
|
||||||
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const session = await getSession({ req })
|
||||||
|
|
||||||
|
if (!session?.user)
|
||||||
|
return res.status(401).json({ message: 'Not authenticated' })
|
||||||
|
|
||||||
|
const user = session.user as User
|
||||||
|
const parentFolderId = req.query.parentId
|
||||||
|
? req.query.parentId.toString()
|
||||||
|
: null
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
const folders = await prisma.dashboardFolder.findMany({
|
||||||
|
where: {
|
||||||
|
ownerId: user.id,
|
||||||
|
parentFolderId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return res.send({ folders })
|
||||||
|
}
|
||||||
|
if (req.method === 'POST') {
|
||||||
|
const data = JSON.parse(req.body) as Pick<DashboardFolder, 'parentFolderId'>
|
||||||
|
const folder = await prisma.dashboardFolder.create({
|
||||||
|
data: { ...data, ownerId: user.id, name: 'New folder' },
|
||||||
|
})
|
||||||
|
return res.send(folder)
|
||||||
|
}
|
||||||
|
return methodNotAllowed(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handler
|
37
apps/builder/pages/api/folders/[id].ts
Normal file
37
apps/builder/pages/api/folders/[id].ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { DashboardFolder } from '@typebot/prisma'
|
||||||
|
import prisma from 'libs/prisma'
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { getSession } from 'next-auth/react'
|
||||||
|
import { methodNotAllowed } from 'services/api/utils'
|
||||||
|
|
||||||
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const session = await getSession({ req })
|
||||||
|
|
||||||
|
if (!session?.user)
|
||||||
|
return res.status(401).json({ message: 'Not authenticated' })
|
||||||
|
|
||||||
|
const id = req.query.id.toString()
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
const folder = await prisma.dashboardFolder.findUnique({
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
return res.send({ folder })
|
||||||
|
}
|
||||||
|
if (req.method === 'DELETE') {
|
||||||
|
const folders = await prisma.dashboardFolder.delete({
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
return res.send({ folders })
|
||||||
|
}
|
||||||
|
if (req.method === 'PATCH') {
|
||||||
|
const data = JSON.parse(req.body) as Partial<DashboardFolder>
|
||||||
|
const folders = await prisma.dashboardFolder.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
return res.send({ typebots: folders })
|
||||||
|
}
|
||||||
|
return methodNotAllowed(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handler
|
34
apps/builder/pages/api/typebots.ts
Normal file
34
apps/builder/pages/api/typebots.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Typebot, User } from '@typebot/prisma'
|
||||||
|
import prisma from 'libs/prisma'
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { getSession } from 'next-auth/react'
|
||||||
|
import { methodNotAllowed } from 'services/api/utils'
|
||||||
|
|
||||||
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const session = await getSession({ req })
|
||||||
|
|
||||||
|
if (!session?.user)
|
||||||
|
return res.status(401).json({ message: 'Not authenticated' })
|
||||||
|
|
||||||
|
const user = session.user as User
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
const folderId = req.query.folderId ? req.query.folderId.toString() : null
|
||||||
|
const typebots = await prisma.typebot.findMany({
|
||||||
|
where: {
|
||||||
|
ownerId: user.id,
|
||||||
|
folderId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return res.send({ typebots })
|
||||||
|
}
|
||||||
|
if (req.method === 'POST') {
|
||||||
|
const data = JSON.parse(req.body) as Typebot
|
||||||
|
const typebot = await prisma.typebot.create({
|
||||||
|
data: { ...data, ownerId: user.id },
|
||||||
|
})
|
||||||
|
return res.send(typebot)
|
||||||
|
}
|
||||||
|
return methodNotAllowed(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handler
|
31
apps/builder/pages/api/typebots/[id].ts
Normal file
31
apps/builder/pages/api/typebots/[id].ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Typebot } from '.prisma/client'
|
||||||
|
import prisma from 'libs/prisma'
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { getSession } from 'next-auth/react'
|
||||||
|
import { methodNotAllowed } from 'services/api/utils'
|
||||||
|
|
||||||
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const session = await getSession({ req })
|
||||||
|
|
||||||
|
if (!session?.user)
|
||||||
|
return res.status(401).json({ message: 'Not authenticated' })
|
||||||
|
|
||||||
|
const id = req.query.id.toString()
|
||||||
|
if (req.method === 'DELETE') {
|
||||||
|
const typebots = await prisma.typebot.delete({
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
return res.send({ typebots })
|
||||||
|
}
|
||||||
|
if (req.method === 'PATCH') {
|
||||||
|
const data = JSON.parse(req.body) as Partial<Typebot>
|
||||||
|
const typebots = await prisma.typebot.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
return res.send({ typebots })
|
||||||
|
}
|
||||||
|
return methodNotAllowed(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handler
|
18
apps/builder/pages/typebots.tsx
Normal file
18
apps/builder/pages/typebots.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import withAuth from 'components/HOC/withUser'
|
||||||
|
import React from 'react'
|
||||||
|
import { Stack } from '@chakra-ui/layout'
|
||||||
|
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
import { FolderContent } from 'components/dashboard/FolderContent'
|
||||||
|
|
||||||
|
const DashboardPage = () => {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Seo title="My typebots" />
|
||||||
|
<DashboardHeader />
|
||||||
|
<FolderContent folder={null} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withAuth(DashboardPage)
|
43
apps/builder/pages/typebots/create.tsx
Normal file
43
apps/builder/pages/typebots/create.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { Button, Stack, useToast } from '@chakra-ui/react'
|
||||||
|
import { useUser } from 'services/user'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||||
|
import { createTypebot } from 'services/typebots'
|
||||||
|
|
||||||
|
const TemplatesPage = () => {
|
||||||
|
const user = useUser()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
const toast = useToast({
|
||||||
|
position: 'top-right',
|
||||||
|
status: 'error',
|
||||||
|
title: 'An error occured',
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleCreateSubmit = async () => {
|
||||||
|
if (!user) return
|
||||||
|
setIsLoading(true)
|
||||||
|
const { error, data } = await createTypebot({
|
||||||
|
folderId: router.query.folderId?.toString() ?? null,
|
||||||
|
})
|
||||||
|
if (error) toast({ description: error.message })
|
||||||
|
if (data) router.push(`/typebots/${data.id}`)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Seo title="Templates" />
|
||||||
|
<DashboardHeader />
|
||||||
|
<Button ml={4} onClick={() => handleCreateSubmit()} isLoading={isLoading}>
|
||||||
|
Start from scratch
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TemplatesPage
|
44
apps/builder/pages/typebots/folders/[id].tsx
Normal file
44
apps/builder/pages/typebots/folders/[id].tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import withAuth from 'components/HOC/withUser'
|
||||||
|
import React from 'react'
|
||||||
|
import { Flex, Stack } from '@chakra-ui/layout'
|
||||||
|
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
import { FolderContent } from 'components/dashboard/FolderContent'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useFolderContent } from 'services/folders'
|
||||||
|
import { Spinner, useToast } from '@chakra-ui/react'
|
||||||
|
|
||||||
|
const FolderPage = () => {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const toast = useToast({
|
||||||
|
position: 'top-right',
|
||||||
|
status: 'error',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { folder } = useFolderContent({
|
||||||
|
folderId: router.query.id?.toString(),
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: "Couldn't fetch folder content",
|
||||||
|
description: error.message,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Seo title="My typebots" />
|
||||||
|
<DashboardHeader />
|
||||||
|
{!folder ? (
|
||||||
|
<Flex flex="1">
|
||||||
|
<Spinner mx="auto" />
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<FolderContent folder={folder} />
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withAuth(FolderPage)
|
@ -1,9 +0,0 @@
|
|||||||
import withAuth, { withAuthProps } from 'components/HOC/withUser'
|
|
||||||
import { Text } from '@chakra-ui/react'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
const TypebotsPage = ({ user }: withAuthProps) => {
|
|
||||||
return <Text data-testid="authenticated">Hello {user?.email}</Text>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withAuth(TypebotsPage)
|
|
BIN
apps/builder/public/favicon.png
Normal file
BIN
apps/builder/public/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 558 B |
4
apps/builder/services/api/utils.ts
Normal file
4
apps/builder/services/api/utils.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
export const methodNotAllowed = (res: NextApiResponse) =>
|
||||||
|
res.status(405).json({ message: 'Method Not Allowed' })
|
69
apps/builder/services/folders.ts
Normal file
69
apps/builder/services/folders.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { DashboardFolder } from '.prisma/client'
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import { fetcher, sendRequest } from './utils'
|
||||||
|
|
||||||
|
export const useFolders = ({
|
||||||
|
parentId,
|
||||||
|
onError,
|
||||||
|
}: {
|
||||||
|
parentId?: string
|
||||||
|
onError: (error: Error) => void
|
||||||
|
}) => {
|
||||||
|
const params = new URLSearchParams(
|
||||||
|
parentId ? { parentId: parentId.toString() } : undefined
|
||||||
|
)
|
||||||
|
const { data, error, mutate } = useSWR<{ folders: DashboardFolder[] }, Error>(
|
||||||
|
`/api/folders?${params}`,
|
||||||
|
fetcher
|
||||||
|
)
|
||||||
|
if (error) onError(error)
|
||||||
|
return {
|
||||||
|
folders: data?.folders,
|
||||||
|
isLoading: !error && !data,
|
||||||
|
mutate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useFolderContent = ({
|
||||||
|
folderId,
|
||||||
|
onError,
|
||||||
|
}: {
|
||||||
|
folderId?: string
|
||||||
|
onError: (error: Error) => void
|
||||||
|
}) => {
|
||||||
|
const { data, error, mutate } = useSWR<{ folder: DashboardFolder }, Error>(
|
||||||
|
`/api/folders/${folderId}`,
|
||||||
|
fetcher
|
||||||
|
)
|
||||||
|
if (error) onError(error)
|
||||||
|
return {
|
||||||
|
folder: data?.folder,
|
||||||
|
isLoading: !error && !data,
|
||||||
|
mutate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createFolder = async (
|
||||||
|
folder: Pick<DashboardFolder, 'parentFolderId'>
|
||||||
|
) =>
|
||||||
|
sendRequest<DashboardFolder>({
|
||||||
|
url: `/api/folders`,
|
||||||
|
method: 'POST',
|
||||||
|
body: folder,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const deleteFolder = async (id: string) =>
|
||||||
|
sendRequest({
|
||||||
|
url: `/api/folders/${id}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateFolder = async (
|
||||||
|
id: string,
|
||||||
|
folder: Partial<DashboardFolder>
|
||||||
|
) =>
|
||||||
|
sendRequest({
|
||||||
|
url: `/api/folders/${id}`,
|
||||||
|
method: 'PATCH',
|
||||||
|
body: folder,
|
||||||
|
})
|
69
apps/builder/services/typebots.ts
Normal file
69
apps/builder/services/typebots.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { Typebot } from '@typebot/prisma'
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import { fetcher, sendRequest } from './utils'
|
||||||
|
|
||||||
|
export const useTypebots = ({
|
||||||
|
folderId,
|
||||||
|
onError,
|
||||||
|
}: {
|
||||||
|
folderId?: string
|
||||||
|
onError: (error: Error) => void
|
||||||
|
}) => {
|
||||||
|
const params = new URLSearchParams(
|
||||||
|
folderId ? { folderId: folderId.toString() } : undefined
|
||||||
|
)
|
||||||
|
const { data, error, mutate } = useSWR<{ typebots: Typebot[] }, Error>(
|
||||||
|
`/api/typebots?${params}`,
|
||||||
|
fetcher
|
||||||
|
)
|
||||||
|
if (error) onError(error)
|
||||||
|
return {
|
||||||
|
typebots: data?.typebots,
|
||||||
|
isLoading: !error && !data,
|
||||||
|
mutate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createTypebot = async ({
|
||||||
|
folderId,
|
||||||
|
}: Pick<Typebot, 'folderId'>) => {
|
||||||
|
const typebot = {
|
||||||
|
folderId,
|
||||||
|
name: 'My typebot',
|
||||||
|
}
|
||||||
|
return sendRequest<Typebot>({
|
||||||
|
url: `/api/typebots`,
|
||||||
|
method: 'POST',
|
||||||
|
body: typebot,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const duplicateTypebot = async ({
|
||||||
|
folderId,
|
||||||
|
ownerId,
|
||||||
|
name,
|
||||||
|
}: Typebot) => {
|
||||||
|
const typebot = {
|
||||||
|
folderId,
|
||||||
|
ownerId,
|
||||||
|
name: `${name} copy`,
|
||||||
|
}
|
||||||
|
return sendRequest<Typebot>({
|
||||||
|
url: `/api/typebots`,
|
||||||
|
method: 'POST',
|
||||||
|
body: typebot,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteTypebot = async (id: string) =>
|
||||||
|
sendRequest({
|
||||||
|
url: `/api/typebots/${id}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateTypebot = async (id: string, typebot: Partial<Typebot>) =>
|
||||||
|
sendRequest({
|
||||||
|
url: `/api/typebots/${id}`,
|
||||||
|
method: 'PATCH',
|
||||||
|
body: typebot,
|
||||||
|
})
|
7
apps/builder/services/user.ts
Normal file
7
apps/builder/services/user.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { User } from '@typebot/prisma'
|
||||||
|
import { useSession } from 'next-auth/react'
|
||||||
|
|
||||||
|
export const useUser = (): User | undefined => {
|
||||||
|
const { data } = useSession()
|
||||||
|
return data?.user as User | undefined
|
||||||
|
}
|
32
apps/builder/services/utils.ts
Normal file
32
apps/builder/services/utils.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export const fetcher = async (input: RequestInfo, init?: RequestInit) => {
|
||||||
|
const res = await fetch(input, init)
|
||||||
|
return res.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isMobile =
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
window.matchMedia('only screen and (max-width: 760px)').matches
|
||||||
|
|
||||||
|
export const sendRequest = async <ResponseData>({
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
}: {
|
||||||
|
url: string
|
||||||
|
method: string
|
||||||
|
body?: Record<string, unknown>
|
||||||
|
}): Promise<{ data?: ResponseData; error?: Error }> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method,
|
||||||
|
mode: 'cors',
|
||||||
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
|
})
|
||||||
|
if (!response.ok) throw new Error(response.statusText)
|
||||||
|
const data = await response.json()
|
||||||
|
return { data }
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return { error: e as Error }
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": false,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "viewer",
|
"name": "viewer",
|
||||||
"packageManager": "yarn@3.1.0",
|
"packageManager": "yarn@3.1.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev -p 3001",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
|
@ -7,5 +7,8 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/postgresql/data
|
- db_data:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: ""
|
||||||
|
POSTGRES_HOST_AUTH_METHOD: trust
|
||||||
volumes:
|
volumes:
|
||||||
db_data:
|
db_data:
|
||||||
|
12
package.json
12
package.json
@ -7,15 +7,17 @@
|
|||||||
"apps/*"
|
"apps/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"db:up": "docker-compose up -d",
|
"db:up": "docker-compose up -d && yarn workspace @typebot/prisma prisma db push",
|
||||||
"db:setup": "yarn workspace @typebot/prisma dev",
|
|
||||||
"db:nuke": "docker-compose down --volumes --remove-orphans",
|
"db:nuke": "docker-compose down --volumes --remove-orphans",
|
||||||
"db:inspect": "dotenv -e .env yarn workspace @typebot/prisma prisma studio",
|
"dev": "concurrently -n builder,viewer \"yarn dev:builder\" \"yarn dev:viewer\"",
|
||||||
"dev:setup": "dotenv -e .env run-s db:up db:setup",
|
|
||||||
"dev:builder": "yarn workspace builder dev",
|
"dev:builder": "yarn workspace builder dev",
|
||||||
"dev:viewer": "yarn workspace viewer dev"
|
"dev:viewer": "yarn workspace viewer dev",
|
||||||
|
"dx": "run-s db:up dev",
|
||||||
|
"cypress:builder": "yarn workspace builder cypress",
|
||||||
|
"cypress:viewer": "yarn workspace builder cypress"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"concurrently": "^6.4.0",
|
||||||
"dotenv-cli": "^4.1.0",
|
"dotenv-cli": "^4.1.0",
|
||||||
"npm-run-all": "^4.1.5"
|
"npm-run-all": "^4.1.5"
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,14 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dotenv-cli": "^4.1.0",
|
"dotenv-cli": "^4.1.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prisma": "^3.5.0",
|
"prisma": "latest",
|
||||||
"ts-node": "^10.4.0",
|
"ts-node": "^10.4.0",
|
||||||
"typescript": "^4.5.2"
|
"typescript": "^4.5.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^3.5.0"
|
"@prisma/client": "latest"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prisma": "dotenv -e ../../.env prisma",
|
"prisma": "dotenv -e ../../.env prisma"
|
||||||
"dev": "run-s migrate generate build",
|
|
||||||
"build": "dotenv -e ../../.env tsc --build",
|
|
||||||
"migrate": "dotenv -e ../../.env prisma migrate dev",
|
|
||||||
"push": "dotenv -e ../../.env prisma db push",
|
|
||||||
"generate": "dotenv -e ../../.env prisma generate"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "Plan" AS ENUM ('FREE', 'PRO');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "plan" "Plan" NOT NULL DEFAULT E'FREE';
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DashboardFolder" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"ownerId" TEXT NOT NULL,
|
||||||
|
"parentFolderId" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "DashboardFolder_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Typebot" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"ownerId" TEXT NOT NULL,
|
||||||
|
"publishedTypebotId" TEXT,
|
||||||
|
"folderId" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "Typebot_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PublicTypebot" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"typebotId" TEXT NOT NULL,
|
||||||
|
"steps" JSONB[],
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "PublicTypebot_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Result" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"typebotId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Result_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "PublicTypebot_typebotId_key" ON "PublicTypebot"("typebotId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DashboardFolder" ADD CONSTRAINT "DashboardFolder_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DashboardFolder" ADD CONSTRAINT "DashboardFolder_parentFolderId_fkey" FOREIGN KEY ("parentFolderId") REFERENCES "DashboardFolder"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Typebot" ADD CONSTRAINT "Typebot_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Typebot" ADD CONSTRAINT "Typebot_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "DashboardFolder"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "PublicTypebot" ADD CONSTRAINT "PublicTypebot_typebotId_fkey" FOREIGN KEY ("typebotId") REFERENCES "Typebot"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Result" ADD CONSTRAINT "Result_typebotId_fkey" FOREIGN KEY ("typebotId") REFERENCES "Typebot"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
87
packages/prisma/prisma/schema.draft.prisma
Normal file
87
packages/prisma/prisma/schema.draft.prisma
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
datasource db {
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
provider = "postgresql"
|
||||||
|
}
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
email String @unique
|
||||||
|
name String?
|
||||||
|
avatarUrl String?
|
||||||
|
redeemedCoupon Boolean?
|
||||||
|
oAuthCredentials Json?
|
||||||
|
referralId String?
|
||||||
|
domains String[]
|
||||||
|
onboarding_data Json?
|
||||||
|
settings Json
|
||||||
|
typebots Typebot[] @relation("Owner")
|
||||||
|
sharedTypebots Typebot[] @relation("Collaborators")
|
||||||
|
dashboardFolders DashboardFolder[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model DashboardFolder {
|
||||||
|
id BigInt @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
name String
|
||||||
|
owner User @relation(fields: [ownerId], references: [id])
|
||||||
|
ownerId String
|
||||||
|
parentFolderId BigInt
|
||||||
|
parentFolder DashboardFolder @relation("ParentChild", fields: [parentFolderId], references: [id])
|
||||||
|
childrenFolder DashboardFolder[] @relation("ParentChild")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Typebot {
|
||||||
|
id BigInt @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @default(now())
|
||||||
|
steps Json[]
|
||||||
|
publishedTypebotId BigInt @unique
|
||||||
|
publishedTypebot PublicTypebot @relation(fields: [publishedTypebotId], references: [id])
|
||||||
|
connectors Json[]
|
||||||
|
name String
|
||||||
|
ownerId String
|
||||||
|
owner User @relation("Owner", fields: [ownerId], references: [id])
|
||||||
|
conditions Json
|
||||||
|
startConditions Json
|
||||||
|
theme Json
|
||||||
|
settings Json
|
||||||
|
collaborators User[] @relation("Collaborators")
|
||||||
|
customDomains String[]
|
||||||
|
shareSettings Json
|
||||||
|
variables Json
|
||||||
|
checkedConversionRules String[]
|
||||||
|
results Result[]
|
||||||
|
httpRequests Json[]
|
||||||
|
credentials Json[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model PublicTypebot {
|
||||||
|
id BigInt @id @default(autoincrement())
|
||||||
|
typebot Typebot?
|
||||||
|
steps Json[]
|
||||||
|
name String
|
||||||
|
conditions Json
|
||||||
|
startConditions Json
|
||||||
|
theme Json
|
||||||
|
settings Json
|
||||||
|
connectors Json
|
||||||
|
customDomains String[]
|
||||||
|
shareSettings Json
|
||||||
|
variables Json
|
||||||
|
}
|
||||||
|
|
||||||
|
model Result {
|
||||||
|
id BigInt @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @default(now())
|
||||||
|
typebotId BigInt
|
||||||
|
typebot Typebot @relation(fields: [typebotId], references: [id])
|
||||||
|
variables Json[]
|
||||||
|
isCompleted Boolean
|
||||||
|
answers Json[]
|
||||||
|
}
|
@ -4,24 +4,24 @@ datasource db {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
}
|
}
|
||||||
|
|
||||||
model Account {
|
model Account {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String
|
userId String
|
||||||
type String
|
type String
|
||||||
provider String
|
provider String
|
||||||
providerAccountId String
|
providerAccountId String
|
||||||
refresh_token String?
|
refresh_token String?
|
||||||
access_token String?
|
access_token String?
|
||||||
expires_at Int?
|
expires_at Int?
|
||||||
token_type String?
|
token_type String?
|
||||||
scope String?
|
scope String?
|
||||||
id_token String?
|
id_token String?
|
||||||
session_state String?
|
session_state String?
|
||||||
oauth_token_secret String?
|
oauth_token_secret String?
|
||||||
oauth_token String?
|
oauth_token String?
|
||||||
refresh_token_expires_in Int?
|
refresh_token_expires_in Int?
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
@ -38,13 +38,21 @@ model Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String?
|
name String?
|
||||||
email String? @unique
|
email String? @unique
|
||||||
emailVerified DateTime?
|
emailVerified DateTime?
|
||||||
image String?
|
image String?
|
||||||
accounts Account[]
|
accounts Account[]
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
|
typebots Typebot[]
|
||||||
|
folders DashboardFolder[]
|
||||||
|
plan Plan @default(FREE)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Plan {
|
||||||
|
FREE
|
||||||
|
PRO
|
||||||
}
|
}
|
||||||
|
|
||||||
model VerificationToken {
|
model VerificationToken {
|
||||||
@ -53,4 +61,46 @@ model VerificationToken {
|
|||||||
expires DateTime
|
expires DateTime
|
||||||
|
|
||||||
@@unique([identifier, token])
|
@@unique([identifier, token])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model DashboardFolder {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
name String
|
||||||
|
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
||||||
|
ownerId String
|
||||||
|
parentFolderId String?
|
||||||
|
parentFolder DashboardFolder? @relation("ParentChild", fields: [parentFolderId], references: [id])
|
||||||
|
childrenFolder DashboardFolder[] @relation("ParentChild")
|
||||||
|
typebots Typebot[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Typebot {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @default(now())
|
||||||
|
name String
|
||||||
|
ownerId String
|
||||||
|
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
||||||
|
publishedTypebotId String?
|
||||||
|
publishedTypebot PublicTypebot?
|
||||||
|
results Result[]
|
||||||
|
folderId String?
|
||||||
|
folder DashboardFolder? @relation(fields: [folderId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
model PublicTypebot {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
typebotId String @unique
|
||||||
|
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade)
|
||||||
|
steps Json[]
|
||||||
|
name String
|
||||||
|
}
|
||||||
|
|
||||||
|
model Result {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @default(now())
|
||||||
|
typebotId String
|
||||||
|
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
228
yarn.lock
228
yarn.lock
@ -287,7 +287,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@chakra-ui/css-reset@npm:1.1.1":
|
"@chakra-ui/css-reset@npm:1.1.1, @chakra-ui/css-reset@npm:^1.1.1":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "@chakra-ui/css-reset@npm:1.1.1"
|
resolution: "@chakra-ui/css-reset@npm:1.1.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1010,6 +1010,42 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/accessibility@npm:^3.0.0":
|
||||||
|
version: 3.0.0
|
||||||
|
resolution: "@dnd-kit/accessibility@npm:3.0.0"
|
||||||
|
dependencies:
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
checksum: feac6afea07ece1568df38973d7a1a1b994782a6df119983250c3849ca2a3818a7bcc67fe4c5d22b0a1cfde866081644d677d2188abed507b3eccc255da79320
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/core@npm:^4.0.3":
|
||||||
|
version: 4.0.3
|
||||||
|
resolution: "@dnd-kit/core@npm:4.0.3"
|
||||||
|
dependencies:
|
||||||
|
"@dnd-kit/accessibility": ^3.0.0
|
||||||
|
"@dnd-kit/utilities": ^3.0.1
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
react-dom: ">=16.8.0"
|
||||||
|
checksum: 828609317504f32bf37238840e5d2ae545efaaa779d696fdad1606d2ae22ae5668a43466db007efde89c8c1ef04ca66b2c61b6e98465f9487ec37c8e3d11a1b2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/utilities@npm:^3.0.1":
|
||||||
|
version: 3.0.1
|
||||||
|
resolution: "@dnd-kit/utilities@npm:3.0.1"
|
||||||
|
dependencies:
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
checksum: 01ffe77bf98707e2e0e949b4425caf6a237ad4fc07fe15f9f0e4ab7c710d81ba2190915549c5302386cb55517e7be4bea6560dfb7eba0a516fa46719f837ec57
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@emotion/babel-plugin@npm:^11.3.0":
|
"@emotion/babel-plugin@npm:^11.3.0":
|
||||||
version: 11.3.0
|
version: 11.3.0
|
||||||
resolution: "@emotion/babel-plugin@npm:11.3.0"
|
resolution: "@emotion/babel-plugin@npm:11.3.0"
|
||||||
@ -1529,31 +1565,31 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@prisma/client@npm:^3.5.0":
|
"@prisma/client@npm:latest":
|
||||||
version: 3.5.0
|
version: 3.6.0
|
||||||
resolution: "@prisma/client@npm:3.5.0"
|
resolution: "@prisma/client@npm:3.6.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/engines-version": 3.5.0-38.78a5df6def6943431f4c022e1428dbc3e833cf8e
|
"@prisma/engines-version": 3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
prisma: "*"
|
prisma: "*"
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
prisma:
|
prisma:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 515880b22c4df43185cefb17fe34968c9ba51cf4fead39e2a9bd9da7ba58a3303194ca0d81fcee27e9a14caa017d3f92cffdcdb4e90fa912ba1ca9508991503d
|
checksum: 83835117d60d100cf0933f60933603f5b85833414ad4dae9ea6b18f99190030317de459bbb573e928f764b476733676722fd90637292defae887dd974a2914a1
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@prisma/engines-version@npm:3.5.0-38.78a5df6def6943431f4c022e1428dbc3e833cf8e":
|
"@prisma/engines-version@npm:3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727":
|
||||||
version: 3.5.0-38.78a5df6def6943431f4c022e1428dbc3e833cf8e
|
version: 3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727
|
||||||
resolution: "@prisma/engines-version@npm:3.5.0-38.78a5df6def6943431f4c022e1428dbc3e833cf8e"
|
resolution: "@prisma/engines-version@npm:3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727"
|
||||||
checksum: 2919f44abc369ec48c8b2c0169293934ba7e2cff6e2bf29a86731b9e1a8c6bad8a3ffc41e604187ea75cfbb04b138bad7e4fe8ec7b557b437e0e2264ef9806df
|
checksum: 40adea282d62b7d8d03696f3f9f1c6da271671955f5305d3750d07f80eff7bcfad89430e97215183d027895487c4ab36373fc8f59df1a537eb2ab18458168993
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@prisma/engines@npm:3.5.0-38.78a5df6def6943431f4c022e1428dbc3e833cf8e":
|
"@prisma/engines@npm:3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727":
|
||||||
version: 3.5.0-38.78a5df6def6943431f4c022e1428dbc3e833cf8e
|
version: 3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727
|
||||||
resolution: "@prisma/engines@npm:3.5.0-38.78a5df6def6943431f4c022e1428dbc3e833cf8e"
|
resolution: "@prisma/engines@npm:3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727"
|
||||||
checksum: 2a31da94fef3c497d66ae77174c1a6627e7aae63039b01cbd4f2dbc4b44198dd4932af48a013566bb54ed173dd948d75b8d47bfb4f139551cc685f2e2cf2f0ad
|
checksum: 3eb1e40dbd6ca8e8bd727048393b9fd993b415bcc1efca2f6bac5b0a60da8e73c2dd35602c4083b29fc31fd17a2cc9ba0522afe1b6499a68663ecdf5f833cdb3
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -1689,10 +1725,10 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@typebot/prisma@workspace:packages/prisma"
|
resolution: "@typebot/prisma@workspace:packages/prisma"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/client": ^3.5.0
|
"@prisma/client": latest
|
||||||
dotenv-cli: ^4.1.0
|
dotenv-cli: ^4.1.0
|
||||||
npm-run-all: ^4.1.5
|
npm-run-all: ^4.1.5
|
||||||
prisma: ^3.5.0
|
prisma: latest
|
||||||
ts-node: ^10.4.0
|
ts-node: ^10.4.0
|
||||||
typescript: ^4.5.2
|
typescript: ^4.5.2
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
@ -2663,7 +2699,9 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "builder@workspace:apps/builder"
|
resolution: "builder@workspace:apps/builder"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@chakra-ui/css-reset": ^1.1.1
|
||||||
"@chakra-ui/react": ^1.7.2
|
"@chakra-ui/react": ^1.7.2
|
||||||
|
"@dnd-kit/core": ^4.0.3
|
||||||
"@emotion/react": ^11
|
"@emotion/react": ^11
|
||||||
"@emotion/styled": ^11
|
"@emotion/styled": ^11
|
||||||
"@next-auth/prisma-adapter": next
|
"@next-auth/prisma-adapter": next
|
||||||
@ -2681,6 +2719,7 @@ __metadata:
|
|||||||
eslint-config-prettier: ^8.3.0
|
eslint-config-prettier: ^8.3.0
|
||||||
eslint-plugin-cypress: ^2.12.1
|
eslint-plugin-cypress: ^2.12.1
|
||||||
eslint-plugin-prettier: ^4.0.0
|
eslint-plugin-prettier: ^4.0.0
|
||||||
|
focus-visible: ^5.2.0
|
||||||
framer-motion: ^4
|
framer-motion: ^4
|
||||||
next: ^12.0.4
|
next: ^12.0.4
|
||||||
next-auth: beta
|
next-auth: beta
|
||||||
@ -2689,7 +2728,9 @@ __metadata:
|
|||||||
prettier: ^2.4.1
|
prettier: ^2.4.1
|
||||||
react: ^17.0.2
|
react: ^17.0.2
|
||||||
react-dom: ^17.0.2
|
react-dom: ^17.0.2
|
||||||
|
swr: ^1.0.1
|
||||||
typescript: ^4.5.2
|
typescript: ^4.5.2
|
||||||
|
use-debounce: ^7.0.1
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
@ -2899,6 +2940,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"cliui@npm:^7.0.2":
|
||||||
|
version: 7.0.4
|
||||||
|
resolution: "cliui@npm:7.0.4"
|
||||||
|
dependencies:
|
||||||
|
string-width: ^4.2.0
|
||||||
|
strip-ansi: ^6.0.0
|
||||||
|
wrap-ansi: ^7.0.0
|
||||||
|
checksum: ce2e8f578a4813806788ac399b9e866297740eecd4ad1823c27fd344d78b22c5f8597d548adbcc46f0573e43e21e751f39446c5a5e804a12aace402b7a315d7f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"code-point-at@npm:^1.0.0":
|
"code-point-at@npm:^1.0.0":
|
||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
resolution: "code-point-at@npm:1.1.0"
|
resolution: "code-point-at@npm:1.1.0"
|
||||||
@ -3015,6 +3067,24 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"concurrently@npm:^6.4.0":
|
||||||
|
version: 6.4.0
|
||||||
|
resolution: "concurrently@npm:6.4.0"
|
||||||
|
dependencies:
|
||||||
|
chalk: ^4.1.0
|
||||||
|
date-fns: ^2.16.1
|
||||||
|
lodash: ^4.17.21
|
||||||
|
rxjs: ^6.6.3
|
||||||
|
spawn-command: ^0.0.2-1
|
||||||
|
supports-color: ^8.1.0
|
||||||
|
tree-kill: ^1.2.2
|
||||||
|
yargs: ^16.2.0
|
||||||
|
bin:
|
||||||
|
concurrently: bin/concurrently.js
|
||||||
|
checksum: 902864cc853176cac406246fa367a1b24ebfcbea9ba43c164f9cb4e2c9c1a5f9d8be05ce98fe7ef13329ffd5cc340a052007a9c870863e2068425d95b3bf89d7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"console-control-strings@npm:^1.0.0, console-control-strings@npm:~1.1.0":
|
"console-control-strings@npm:^1.0.0, console-control-strings@npm:~1.1.0":
|
||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
resolution: "console-control-strings@npm:1.1.0"
|
resolution: "console-control-strings@npm:1.1.0"
|
||||||
@ -3316,6 +3386,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"date-fns@npm:^2.16.1":
|
||||||
|
version: 2.27.0
|
||||||
|
resolution: "date-fns@npm:2.27.0"
|
||||||
|
checksum: db62036b3816eb0322c9532b353beac7f660a91e1a55dbd21c14893c621ebb8509f21c66ba287844dabd34dee0207edd54a9537bce6bb7cab9383dedc6b8bc90
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"dayjs@npm:^1.10.4":
|
"dayjs@npm:^1.10.4":
|
||||||
version: 1.10.7
|
version: 1.10.7
|
||||||
resolution: "dayjs@npm:1.10.7"
|
resolution: "dayjs@npm:1.10.7"
|
||||||
@ -3390,6 +3467,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"dequal@npm:2.0.2":
|
||||||
|
version: 2.0.2
|
||||||
|
resolution: "dequal@npm:2.0.2"
|
||||||
|
checksum: 86c7a2c59f7b0797ed397c74b5fcdb744e48fc19440b70ad6ac59f57550a96b0faef3f1cfd5760ec5e6d3f7cb101f634f1f80db4e727b1dc8389bf62d977c0a0
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"des.js@npm:^1.0.0":
|
"des.js@npm:^1.0.0":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "des.js@npm:1.0.1"
|
resolution: "des.js@npm:1.0.1"
|
||||||
@ -4260,6 +4344,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"focus-visible@npm:^5.2.0":
|
||||||
|
version: 5.2.0
|
||||||
|
resolution: "focus-visible@npm:5.2.0"
|
||||||
|
checksum: 876f646ef453680d3d34e9f9b23961527ffd5ccaed8690f423d4fbfa37ff023d98a490972bc1387850e37ec2e44958c81f6096ef95b67462e5c4b5404cf1dbb9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"foreach@npm:^2.0.5":
|
"foreach@npm:^2.0.5":
|
||||||
version: 2.0.5
|
version: 2.0.5
|
||||||
resolution: "foreach@npm:2.0.5"
|
resolution: "foreach@npm:2.0.5"
|
||||||
@ -4391,6 +4482,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"get-caller-file@npm:^2.0.5":
|
||||||
|
version: 2.0.5
|
||||||
|
resolution: "get-caller-file@npm:2.0.5"
|
||||||
|
checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1":
|
"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "get-intrinsic@npm:1.1.1"
|
resolution: "get-intrinsic@npm:1.1.1"
|
||||||
@ -6618,15 +6716,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"prisma@npm:^3.5.0":
|
"prisma@npm:latest":
|
||||||
version: 3.5.0
|
version: 3.6.0
|
||||||
resolution: "prisma@npm:3.5.0"
|
resolution: "prisma@npm:3.6.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/engines": 3.5.0-38.78a5df6def6943431f4c022e1428dbc3e833cf8e
|
"@prisma/engines": 3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727
|
||||||
bin:
|
bin:
|
||||||
prisma: build/index.js
|
prisma: build/index.js
|
||||||
prisma2: build/index.js
|
prisma2: build/index.js
|
||||||
checksum: 5ac2558dc29a8c325d991e59de2208bf6756bddb4fa2d3d97cd17aa9daa94611515d3ab284777adcda2c7b918b0624316e7081bae5d1c01b449738b8ebf81882
|
checksum: 365718b1e0ed8491aedb79bda39c500e8fddae0d62f57ed0ab3786c6709bd79d43b61c3603847b165b5a3de1e983b64388902a9ed55d4d5cb5878d67af23da8b
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -7040,6 +7138,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"require-directory@npm:^2.1.1":
|
||||||
|
version: 2.1.1
|
||||||
|
resolution: "require-directory@npm:2.1.1"
|
||||||
|
checksum: fb47e70bf0001fdeabdc0429d431863e9475e7e43ea5f94ad86503d918423c1543361cc5166d713eaa7029dd7a3d34775af04764bebff99ef413111a5af18c80
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"require-from-string@npm:^2.0.2":
|
"require-from-string@npm:^2.0.2":
|
||||||
version: 2.0.2
|
version: 2.0.2
|
||||||
resolution: "require-from-string@npm:2.0.2"
|
resolution: "require-from-string@npm:2.0.2"
|
||||||
@ -7166,6 +7271,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"rxjs@npm:^6.6.3":
|
||||||
|
version: 6.6.7
|
||||||
|
resolution: "rxjs@npm:6.6.7"
|
||||||
|
dependencies:
|
||||||
|
tslib: ^1.9.0
|
||||||
|
checksum: bc334edef1bb8bbf56590b0b25734ba0deaf8825b703256a93714308ea36dff8a11d25533671adf8e104e5e8f256aa6fdfe39b2e248cdbd7a5f90c260acbbd1b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"rxjs@npm:^7.4.0":
|
"rxjs@npm:^7.4.0":
|
||||||
version: 7.4.0
|
version: 7.4.0
|
||||||
resolution: "rxjs@npm:7.4.0"
|
resolution: "rxjs@npm:7.4.0"
|
||||||
@ -7412,6 +7526,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"spawn-command@npm:^0.0.2-1":
|
||||||
|
version: 0.0.2
|
||||||
|
resolution: "spawn-command@npm:0.0.2"
|
||||||
|
checksum: e35c5d28177b4d461d33c88cc11f6f3a5079e2b132c11e1746453bbb7a0c0b8a634f07541a2a234fa4758239d88203b758def509161b651e81958894c0b4b64b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"spdx-correct@npm:^3.0.0":
|
"spdx-correct@npm:^3.0.0":
|
||||||
version: 3.1.1
|
version: 3.1.1
|
||||||
resolution: "spdx-correct@npm:3.1.1"
|
resolution: "spdx-correct@npm:3.1.1"
|
||||||
@ -7735,7 +7856,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1":
|
"supports-color@npm:^8.0.0, supports-color@npm:^8.1.0, supports-color@npm:^8.1.1":
|
||||||
version: 8.1.1
|
version: 8.1.1
|
||||||
resolution: "supports-color@npm:8.1.1"
|
resolution: "supports-color@npm:8.1.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -7744,6 +7865,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"swr@npm:^1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "swr@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
dequal: 2.0.2
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.11.0 || ^17.0.0
|
||||||
|
checksum: 8aaa10c4c65cb9b46a143a52ac2728111fc8af96e83781df1f7b7d56aa027ef720b7feb230658616e479f224f684d4cbc5d2ca3265c40f95a3140dbdba801061
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"table@npm:^6.0.9":
|
"table@npm:^6.0.9":
|
||||||
version: 6.7.3
|
version: 6.7.3
|
||||||
resolution: "table@npm:6.7.3"
|
resolution: "table@npm:6.7.3"
|
||||||
@ -7873,6 +8005,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"tree-kill@npm:^1.2.2":
|
||||||
|
version: 1.2.2
|
||||||
|
resolution: "tree-kill@npm:1.2.2"
|
||||||
|
bin:
|
||||||
|
tree-kill: cli.js
|
||||||
|
checksum: 49117f5f410d19c84b0464d29afb9642c863bc5ba40fcb9a245d474c6d5cc64d1b177a6e6713129eb346b40aebb9d4631d967517f9fbe8251c35b21b13cd96c7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ts-node@npm:^10.4.0":
|
"ts-node@npm:^10.4.0":
|
||||||
version: 10.4.0
|
version: 10.4.0
|
||||||
resolution: "ts-node@npm:10.4.0"
|
resolution: "ts-node@npm:10.4.0"
|
||||||
@ -7921,14 +8062,14 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tslib@npm:^1.0.0, tslib@npm:^1.8.1, tslib@npm:^1.9.3":
|
"tslib@npm:^1.0.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3":
|
||||||
version: 1.14.1
|
version: 1.14.1
|
||||||
resolution: "tslib@npm:1.14.1"
|
resolution: "tslib@npm:1.14.1"
|
||||||
checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd
|
checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tslib@npm:^2.0.3, tslib@npm:^2.1.0":
|
"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0":
|
||||||
version: 2.3.1
|
version: 2.3.1
|
||||||
resolution: "tslib@npm:2.3.1"
|
resolution: "tslib@npm:2.3.1"
|
||||||
checksum: de17a98d4614481f7fcb5cd53ffc1aaf8654313be0291e1bfaee4b4bb31a20494b7d218ff2e15017883e8ea9626599b3b0e0229c18383ba9dce89da2adf15cb9
|
checksum: de17a98d4614481f7fcb5cd53ffc1aaf8654313be0291e1bfaee4b4bb31a20494b7d218ff2e15017883e8ea9626599b3b0e0229c18383ba9dce89da2adf15cb9
|
||||||
@ -8010,6 +8151,7 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "typebot-os@workspace:."
|
resolution: "typebot-os@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
|
concurrently: ^6.4.0
|
||||||
dotenv-cli: ^4.1.0
|
dotenv-cli: ^4.1.0
|
||||||
npm-run-all: ^4.1.5
|
npm-run-all: ^4.1.5
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
@ -8125,6 +8267,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"use-debounce@npm:^7.0.1":
|
||||||
|
version: 7.0.1
|
||||||
|
resolution: "use-debounce@npm:7.0.1"
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
checksum: c5c1c34ccef6c11fe25fecef983a07f5a7260fce77b58f0f648f6ae08693a22a9077b5362b96154896e9ea756e6aaea303eb44550a293456cecb6f15cf87d73b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"use-sidecar@npm:^1.0.1, use-sidecar@npm:^1.0.5":
|
"use-sidecar@npm:^1.0.1, use-sidecar@npm:^1.0.5":
|
||||||
version: 1.0.5
|
version: 1.0.5
|
||||||
resolution: "use-sidecar@npm:1.0.5"
|
resolution: "use-sidecar@npm:1.0.5"
|
||||||
@ -8380,6 +8531,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"y18n@npm:^5.0.5":
|
||||||
|
version: 5.0.8
|
||||||
|
resolution: "y18n@npm:5.0.8"
|
||||||
|
checksum: 54f0fb95621ee60898a38c572c515659e51cc9d9f787fb109cef6fde4befbe1c4602dc999d30110feee37456ad0f1660fa2edcfde6a9a740f86a290999550d30
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"yallist@npm:^4.0.0":
|
"yallist@npm:^4.0.0":
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
resolution: "yallist@npm:4.0.0"
|
resolution: "yallist@npm:4.0.0"
|
||||||
@ -8394,6 +8552,28 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"yargs-parser@npm:^20.2.2":
|
||||||
|
version: 20.2.9
|
||||||
|
resolution: "yargs-parser@npm:20.2.9"
|
||||||
|
checksum: 8bb69015f2b0ff9e17b2c8e6bfe224ab463dd00ca211eece72a4cd8a906224d2703fb8a326d36fdd0e68701e201b2a60ed7cf81ce0fd9b3799f9fe7745977ae3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"yargs@npm:^16.2.0":
|
||||||
|
version: 16.2.0
|
||||||
|
resolution: "yargs@npm:16.2.0"
|
||||||
|
dependencies:
|
||||||
|
cliui: ^7.0.2
|
||||||
|
escalade: ^3.1.1
|
||||||
|
get-caller-file: ^2.0.5
|
||||||
|
require-directory: ^2.1.1
|
||||||
|
string-width: ^4.2.0
|
||||||
|
y18n: ^5.0.5
|
||||||
|
yargs-parser: ^20.2.2
|
||||||
|
checksum: b14afbb51e3251a204d81937c86a7e9d4bdbf9a2bcee38226c900d00f522969ab675703bee2a6f99f8e20103f608382936034e64d921b74df82b63c07c5e8f59
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"yauzl@npm:^2.10.0":
|
"yauzl@npm:^2.10.0":
|
||||||
version: 2.10.0
|
version: 2.10.0
|
||||||
resolution: "yauzl@npm:2.10.0"
|
resolution: "yauzl@npm:2.10.0"
|
||||||
|
Reference in New Issue
Block a user