🦴 Add theme page backbone
This commit is contained in:
@ -161,3 +161,27 @@ export const TrashIcon = (props: IconProps) => (
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const LayoutIcon = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="3" y1="9" x2="21" y2="9"></line>
|
||||
<line x1="9" y1="21" x2="9" y2="9"></line>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const CodeIcon = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||
<polyline points="16 18 22 12 16 6"></polyline>
|
||||
<polyline points="8 6 2 12 8 18"></polyline>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const PencilIcon = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||
<path d="M12 19l7-7 3 3-7 7-3-3z"></path>
|
||||
<path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"></path>
|
||||
<path d="M2 2l7.586 7.586"></path>
|
||||
<circle cx="11" cy="11" r="2"></circle>
|
||||
</Icon>
|
||||
)
|
||||
|
@ -2,7 +2,6 @@ import { Flex } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import Graph from './graph/Graph'
|
||||
import { DndContext } from 'contexts/DndContext'
|
||||
import { headerHeight } from 'components/shared/TypebotHeader/TypebotHeader'
|
||||
import { StepTypesList } from './StepTypesList'
|
||||
import { PreviewDrawer } from './preview/PreviewDrawer'
|
||||
import { RightPanel, useEditor } from 'contexts/EditorContext'
|
||||
@ -10,13 +9,7 @@ import { RightPanel, useEditor } from 'contexts/EditorContext'
|
||||
export const Board = () => {
|
||||
const { rightPanel } = useEditor()
|
||||
return (
|
||||
<Flex
|
||||
flex="1"
|
||||
pos="relative"
|
||||
bgColor="gray.50"
|
||||
h={`calc(100vh - ${headerHeight}px)`}
|
||||
marginTop={`${headerHeight}px`}
|
||||
>
|
||||
<Flex flex="1" pos="relative" bgColor="gray.50" h="full">
|
||||
<DndContext>
|
||||
<StepTypesList />
|
||||
<Graph />
|
||||
|
@ -44,7 +44,6 @@ export const TextEditor = ({ initialValue, ids, onClose }: TextEditorProps) => {
|
||||
}, [debouncedValue])
|
||||
|
||||
const save = (value: unknown[]) => {
|
||||
console.log('SAVE', value)
|
||||
if (value.length === 0) return
|
||||
const html = serializeHtml(editor, {
|
||||
nodes: value,
|
||||
|
@ -22,6 +22,7 @@ export const CreateBotButton = ({
|
||||
onClick={handleClick}
|
||||
paddingX={6}
|
||||
whiteSpace={'normal'}
|
||||
colorScheme="blue"
|
||||
{...props}
|
||||
>
|
||||
<VStack spacing="6">
|
||||
|
@ -29,7 +29,7 @@ export const TypebotHeader = () => {
|
||||
borderBottomWidth="1px"
|
||||
justify="center"
|
||||
align="center"
|
||||
pos="fixed"
|
||||
pos="relative"
|
||||
h={`${headerHeight}px`}
|
||||
zIndex={2}
|
||||
bgColor="white"
|
||||
@ -37,10 +37,7 @@ export const TypebotHeader = () => {
|
||||
<HStack>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href={{
|
||||
pathname: `/typebots/${typebot?.id}/edit`,
|
||||
query: { ...router.query, typebotId: [] },
|
||||
}}
|
||||
href={`/typebots/${typebot?.id}/edit`}
|
||||
colorScheme={router.pathname.includes('/edit') ? 'blue' : 'gray'}
|
||||
variant={router.pathname.includes('/edit') ? 'outline' : 'ghost'}
|
||||
>
|
||||
@ -48,21 +45,15 @@ export const TypebotHeader = () => {
|
||||
</Button>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href={{
|
||||
pathname: `/typebots/${typebot?.id}/design`,
|
||||
query: { ...router.query, typebotId: [] },
|
||||
}}
|
||||
colorScheme={router.pathname.endsWith('share') ? 'blue' : 'gray'}
|
||||
variant={router.pathname.endsWith('share') ? 'outline' : 'ghost'}
|
||||
href={`/typebots/${typebot?.id}/theme`}
|
||||
colorScheme={router.pathname.endsWith('theme') ? 'blue' : 'gray'}
|
||||
variant={router.pathname.endsWith('theme') ? 'outline' : 'ghost'}
|
||||
>
|
||||
Theme
|
||||
</Button>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href={{
|
||||
pathname: `/typebots/${typebot?.id}/design`,
|
||||
query: { ...router.query, typebotId: [] },
|
||||
}}
|
||||
href={`/typebots/${typebot?.id}/design`}
|
||||
colorScheme={router.pathname.endsWith('share') ? 'blue' : 'gray'}
|
||||
variant={router.pathname.endsWith('share') ? 'outline' : 'ghost'}
|
||||
>
|
||||
@ -70,10 +61,7 @@ export const TypebotHeader = () => {
|
||||
</Button>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href={{
|
||||
pathname: `/typebots/${typebot?.id}/share`,
|
||||
query: { ...router.query, typebotId: [] },
|
||||
}}
|
||||
href={`/typebots/${typebot?.id}/share`}
|
||||
colorScheme={router.pathname.endsWith('share') ? 'blue' : 'gray'}
|
||||
variant={router.pathname.endsWith('share') ? 'outline' : 'ghost'}
|
||||
>
|
||||
@ -81,10 +69,7 @@ export const TypebotHeader = () => {
|
||||
</Button>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href={{
|
||||
pathname: `/typebots/${typebot?.id}/results/responses`,
|
||||
query: { ...router.query, typebotId: [] },
|
||||
}}
|
||||
href={`/typebots/${typebot?.id}/results/responses`}
|
||||
colorScheme={router.pathname.includes('results') ? 'blue' : 'gray'}
|
||||
variant={router.pathname.includes('results') ? 'outline' : 'ghost'}
|
||||
>
|
||||
|
@ -0,0 +1,38 @@
|
||||
import { Flex, Text } from '@chakra-ui/react'
|
||||
import { Background, BackgroundType } from 'bot-engine'
|
||||
import React from 'react'
|
||||
import { ColorPicker } from '../ColorPicker'
|
||||
|
||||
type BackgroundContentProps = {
|
||||
background: Background
|
||||
onBackgroundContentChange: (content: string) => void
|
||||
}
|
||||
|
||||
export const BackgroundContent = ({
|
||||
background,
|
||||
onBackgroundContentChange,
|
||||
}: BackgroundContentProps) => {
|
||||
const handleContentChange = (content: string) =>
|
||||
onBackgroundContentChange(content)
|
||||
|
||||
switch (background.type) {
|
||||
case BackgroundType.COLOR:
|
||||
return (
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>Background color:</Text>
|
||||
<ColorPicker
|
||||
initialColor={background.content}
|
||||
onColorChange={handleContentChange}
|
||||
/>
|
||||
</Flex>
|
||||
)
|
||||
case BackgroundType.IMAGE:
|
||||
return (
|
||||
<Flex>
|
||||
<Text>Image</Text>
|
||||
</Flex>
|
||||
)
|
||||
default:
|
||||
return <></>
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import { Stack, Text } from '@chakra-ui/react'
|
||||
import { Background, BackgroundType } from 'bot-engine'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { BackgroundContent } from './BackgroundContent'
|
||||
import { BackgroundTypeRadioButtons } from './BackgroundTypeRadioButtons'
|
||||
|
||||
type Props = {
|
||||
initialBackground?: Background
|
||||
onBackgroundChange: (newBackground: Background) => void
|
||||
}
|
||||
export const BackgroundSelector = ({
|
||||
initialBackground,
|
||||
onBackgroundChange,
|
||||
}: Props) => {
|
||||
const [currentBackground, setCurrentBackground] = useState<Background>(
|
||||
initialBackground ?? { type: BackgroundType.NONE, content: '' }
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (deepEqual(currentBackground, initialBackground)) return
|
||||
onBackgroundChange(currentBackground)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentBackground])
|
||||
|
||||
const handleBackgroundTypeChange = (type: BackgroundType) =>
|
||||
setCurrentBackground({ ...currentBackground, type })
|
||||
|
||||
const handleBackgroundContentChange = (content: string) =>
|
||||
setCurrentBackground({ ...currentBackground, content })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Text>Background</Text>
|
||||
<BackgroundTypeRadioButtons
|
||||
backgroundType={currentBackground.type}
|
||||
onBackgroundTypeChange={handleBackgroundTypeChange}
|
||||
/>
|
||||
<BackgroundContent
|
||||
background={currentBackground}
|
||||
onBackgroundContentChange={handleBackgroundContentChange}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
HStack,
|
||||
useRadio,
|
||||
useRadioGroup,
|
||||
UseRadioProps,
|
||||
} from '@chakra-ui/react'
|
||||
import { BackgroundType } from 'bot-engine'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
type Props = {
|
||||
backgroundType: BackgroundType
|
||||
onBackgroundTypeChange: (type: BackgroundType) => void
|
||||
}
|
||||
export const BackgroundTypeRadioButtons = ({
|
||||
backgroundType,
|
||||
onBackgroundTypeChange,
|
||||
}: Props) => {
|
||||
const options = ['Color', 'Image', 'None']
|
||||
|
||||
const { getRootProps, getRadioProps } = useRadioGroup({
|
||||
name: 'background-type',
|
||||
defaultValue: backgroundType,
|
||||
onChange: (nextVal: string) =>
|
||||
onBackgroundTypeChange(nextVal as BackgroundType),
|
||||
})
|
||||
|
||||
const group = getRootProps()
|
||||
|
||||
return (
|
||||
<HStack {...group}>
|
||||
{options.map((value) => {
|
||||
const radio = getRadioProps({ value })
|
||||
return (
|
||||
<RadioCard key={value} {...radio}>
|
||||
{value}
|
||||
</RadioCard>
|
||||
)
|
||||
})}
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
||||
export const RadioCard = (props: UseRadioProps & { children: ReactNode }) => {
|
||||
const { getInputProps, getCheckboxProps } = useRadio(props)
|
||||
|
||||
const input = getInputProps()
|
||||
const checkbox = getCheckboxProps()
|
||||
|
||||
return (
|
||||
<Box as="label" flex="1">
|
||||
<input {...input} />
|
||||
<Flex
|
||||
{...checkbox}
|
||||
cursor="pointer"
|
||||
borderWidth="1px"
|
||||
borderRadius="md"
|
||||
_checked={{
|
||||
bg: 'orange.400',
|
||||
color: 'white',
|
||||
borderColor: 'orange.400',
|
||||
}}
|
||||
px={5}
|
||||
py={2}
|
||||
transition="background-color 150ms, color 150ms, border 150ms"
|
||||
justifyContent="center"
|
||||
>
|
||||
{props.children}
|
||||
</Flex>
|
||||
</Box>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { BackgroundSelector } from './BackgroundSelector'
|
99
apps/builder/components/theme/GeneralContent/ColorPicker.tsx
Normal file
99
apps/builder/components/theme/GeneralContent/ColorPicker.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverArrow,
|
||||
PopoverCloseButton,
|
||||
PopoverHeader,
|
||||
Center,
|
||||
PopoverBody,
|
||||
SimpleGrid,
|
||||
Input,
|
||||
Button,
|
||||
} from '@chakra-ui/react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
const colorsSelection: `#${string}`[] = [
|
||||
'#264653',
|
||||
'#e9c46a',
|
||||
'#2a9d8f',
|
||||
'#7209b7',
|
||||
'#023e8a',
|
||||
'#ffe8d6',
|
||||
'#d8f3dc',
|
||||
'#4ea8de',
|
||||
'#ffb4a2',
|
||||
]
|
||||
|
||||
type Props = {
|
||||
initialColor: string
|
||||
onColorChange: (color: string) => void
|
||||
}
|
||||
|
||||
export const ColorPicker = ({ initialColor, onColorChange }: Props) => {
|
||||
const [color, setColor] = useState(initialColor)
|
||||
|
||||
useEffect(() => {
|
||||
onColorChange(color)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [color])
|
||||
|
||||
return (
|
||||
<Popover variant="picker">
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
aria-label={color}
|
||||
background={color}
|
||||
height="22px"
|
||||
width="22px"
|
||||
padding={0}
|
||||
borderRadius={3}
|
||||
borderWidth={1}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent width="170px">
|
||||
<PopoverArrow bg={color} />
|
||||
<PopoverCloseButton color="white" />
|
||||
<PopoverHeader
|
||||
height="100px"
|
||||
backgroundColor={color}
|
||||
borderTopLeftRadius={5}
|
||||
borderTopRightRadius={5}
|
||||
color={color === '#ffffff' ? 'gray.800' : 'white'}
|
||||
>
|
||||
<Center height="100%">{color}</Center>
|
||||
</PopoverHeader>
|
||||
<PopoverBody height="120px">
|
||||
<SimpleGrid columns={5} spacing={2}>
|
||||
{colorsSelection.map((c) => (
|
||||
<Button
|
||||
key={c}
|
||||
aria-label={c}
|
||||
background={c}
|
||||
height="22px"
|
||||
width="22px"
|
||||
padding={0}
|
||||
minWidth="unset"
|
||||
borderRadius={3}
|
||||
_hover={{ background: c }}
|
||||
onClick={() => {
|
||||
setColor(c)
|
||||
}}
|
||||
></Button>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
<Input
|
||||
borderRadius={3}
|
||||
marginTop={3}
|
||||
placeholder="red.100"
|
||||
size="sm"
|
||||
value={color}
|
||||
onChange={(e) => {
|
||||
setColor(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Text, Flex } from '@chakra-ui/react'
|
||||
import { SearchableDropdown } from './SearchableDropdown'
|
||||
|
||||
type FontSelectorProps = {
|
||||
activeFont?: string
|
||||
onSelectFont: (font: string) => void
|
||||
}
|
||||
|
||||
export const FontSelector = ({
|
||||
activeFont,
|
||||
onSelectFont,
|
||||
}: FontSelectorProps) => {
|
||||
const [currentFont, setCurrentFont] = useState(activeFont)
|
||||
const [googleFonts, setGoogleFonts] = useState<string[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
fetchPopularFonts().then(setGoogleFonts)
|
||||
}, [])
|
||||
|
||||
const fetchPopularFonts = async () => {
|
||||
const response = await fetch(
|
||||
`https://www.googleapis.com/webfonts/v1/webfonts?key=AIzaSyD2YAiipBLNYg058Wm-sPE-e2dPDn_zX8w&sort=popularity`
|
||||
)
|
||||
return (await response.json()).items.map(
|
||||
(item: { family: string }) => item.family
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>Font</Text>
|
||||
<SearchableDropdown
|
||||
selectedItem={activeFont}
|
||||
items={googleFonts}
|
||||
onSelectItem={(nextFont) => {
|
||||
if (nextFont !== currentFont) {
|
||||
setCurrentFont(nextFont)
|
||||
onSelectFont(nextFont)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
import {
|
||||
useDisclosure,
|
||||
useOutsideClick,
|
||||
Flex,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
Input,
|
||||
PopoverContent,
|
||||
Button,
|
||||
Text,
|
||||
} from '@chakra-ui/react'
|
||||
import { useState, useRef, useEffect, ChangeEvent } from 'react'
|
||||
|
||||
export const SearchableDropdown = ({
|
||||
selectedItem,
|
||||
items,
|
||||
onSelectItem,
|
||||
}: {
|
||||
selectedItem?: string
|
||||
items: string[]
|
||||
onSelectItem: (value: string) => void
|
||||
}) => {
|
||||
const { onOpen, onClose, isOpen } = useDisclosure()
|
||||
const [inputValue, setInputValue] = useState(selectedItem)
|
||||
const [filteredItems, setFilteredItems] = useState([
|
||||
...items
|
||||
.filter((item) =>
|
||||
item.toLowerCase().includes((selectedItem ?? '').toLowerCase())
|
||||
)
|
||||
.slice(0, 50),
|
||||
])
|
||||
const dropdownRef = useRef(null)
|
||||
const inputRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (filteredItems.length > 0) return
|
||||
setFilteredItems([
|
||||
...items
|
||||
.filter((item) =>
|
||||
item.toLowerCase().includes((selectedItem ?? '').toLowerCase())
|
||||
)
|
||||
.slice(0, 50),
|
||||
])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [items])
|
||||
|
||||
useOutsideClick({
|
||||
ref: dropdownRef,
|
||||
handler: onClose,
|
||||
})
|
||||
|
||||
const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(e.target.value)
|
||||
if (e.target.value === '') {
|
||||
setFilteredItems([...items.slice(0, 50)])
|
||||
return
|
||||
}
|
||||
setFilteredItems([
|
||||
...items
|
||||
.filter((item) =>
|
||||
item.toLowerCase().includes((inputValue ?? '').toLowerCase())
|
||||
)
|
||||
.slice(0, 50),
|
||||
])
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex ref={dropdownRef}>
|
||||
<Popover isOpen={isOpen} initialFocusRef={inputRef}>
|
||||
<PopoverTrigger>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={onInputChange}
|
||||
onClick={onOpen}
|
||||
w="300px"
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent maxH="35vh" overflowY="scroll" spacing="0" w="300px">
|
||||
{filteredItems.length > 0 ? (
|
||||
<>
|
||||
{filteredItems.map((item, idx) => {
|
||||
return (
|
||||
<Button
|
||||
minH="40px"
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
setInputValue(item)
|
||||
onSelectItem(item)
|
||||
onClose()
|
||||
}}
|
||||
fontSize="16px"
|
||||
fontWeight="normal"
|
||||
rounded="none"
|
||||
colorScheme="gray"
|
||||
variant="ghost"
|
||||
justifyContent="flex-start"
|
||||
>
|
||||
{item}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<Text p={4}>Not found.</Text>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { FontSelector } from './FontSelector'
|
@ -0,0 +1,60 @@
|
||||
import {
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
HStack,
|
||||
Heading,
|
||||
AccordionIcon,
|
||||
AccordionPanel,
|
||||
Stack,
|
||||
} from '@chakra-ui/react'
|
||||
import { PencilIcon } from 'assets/icons'
|
||||
import { Background } from 'bot-engine'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import React from 'react'
|
||||
import { BackgroundSelector } from './BackgroundSelector'
|
||||
import { FontSelector } from './FontSelector'
|
||||
|
||||
export const GeneralContent = () => {
|
||||
const { typebot, updateTheme } = useTypebot()
|
||||
|
||||
const handleSelectFont = (font: string) => {
|
||||
if (!typebot) return
|
||||
updateTheme({
|
||||
...typebot.theme,
|
||||
general: { ...typebot.theme.general, font },
|
||||
})
|
||||
}
|
||||
|
||||
const handleBackgroundChange = (background: Background) => {
|
||||
if (!typebot) return
|
||||
updateTheme({
|
||||
...typebot.theme,
|
||||
general: { ...typebot.theme.general, background },
|
||||
})
|
||||
}
|
||||
return (
|
||||
<AccordionItem>
|
||||
<AccordionButton py={6}>
|
||||
<HStack flex="1" pl={2}>
|
||||
<PencilIcon />
|
||||
<Heading fontSize="lg" color="gray.700">
|
||||
General
|
||||
</Heading>
|
||||
</HStack>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
{typebot && (
|
||||
<AccordionPanel as={Stack} pb={4} spacing={6}>
|
||||
<FontSelector
|
||||
activeFont={typebot.theme.general.font}
|
||||
onSelectFont={handleSelectFont}
|
||||
/>
|
||||
<BackgroundSelector
|
||||
initialBackground={typebot.theme.general.background}
|
||||
onBackgroundChange={handleBackgroundChange}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
)}
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
1
apps/builder/components/theme/GeneralContent/index.ts
Normal file
1
apps/builder/components/theme/GeneralContent/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { GeneralContent } from './GeneralContent'
|
78
apps/builder/components/theme/SideMenu.tsx
Normal file
78
apps/builder/components/theme/SideMenu.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Heading,
|
||||
HStack,
|
||||
Stack,
|
||||
} from '@chakra-ui/react'
|
||||
import { ChatIcon, CodeIcon, LayoutIcon } from 'assets/icons'
|
||||
import React from 'react'
|
||||
import { GeneralContent } from './GeneralContent'
|
||||
|
||||
export const SideMenu = () => {
|
||||
return (
|
||||
<Stack flex="1" maxW="400px" borderRightWidth={1} pt={10} spacing={10}>
|
||||
<Heading fontSize="xl" textAlign="center">
|
||||
Customize the theme
|
||||
</Heading>
|
||||
<Accordion allowMultiple allowToggle>
|
||||
<GeneralContent />
|
||||
|
||||
<AccordionItem>
|
||||
<AccordionButton py={6}>
|
||||
<HStack flex="1" pl={2}>
|
||||
<LayoutIcon />
|
||||
<Heading fontSize="lg" color="gray.700">
|
||||
Layout
|
||||
</Heading>
|
||||
</HStack>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
|
||||
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
||||
aliquip ex ea commodo consequat.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton py={6}>
|
||||
<HStack flex="1" pl={2}>
|
||||
<ChatIcon />
|
||||
<Heading fontSize="lg" color="gray.700">
|
||||
Chat
|
||||
</Heading>
|
||||
</HStack>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
|
||||
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
||||
aliquip ex ea commodo consequat.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton py={6}>
|
||||
<HStack flex="1" pl={2}>
|
||||
<CodeIcon />
|
||||
<Heading fontSize="lg" color="gray.700">
|
||||
Custom CSS
|
||||
</Heading>
|
||||
</HStack>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
|
||||
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
||||
aliquip ex ea commodo consequat.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
)
|
||||
}
|
22
apps/builder/components/theme/ThemeContent.tsx
Normal file
22
apps/builder/components/theme/ThemeContent.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { Flex } from '@chakra-ui/react'
|
||||
import { TypebotViewer } from 'bot-engine'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import React, { useMemo } from 'react'
|
||||
import { parseTypebotToPublicTypebot } from 'services/typebots'
|
||||
import { SideMenu } from './SideMenu'
|
||||
|
||||
export const ThemeContent = () => {
|
||||
const { typebot } = useTypebot()
|
||||
const publicTypebot = useMemo(
|
||||
() => (typebot ? parseTypebotToPublicTypebot(typebot) : undefined),
|
||||
[typebot]
|
||||
)
|
||||
return (
|
||||
<Flex h="full" w="full">
|
||||
<SideMenu />
|
||||
<Flex flex="1">
|
||||
{publicTypebot && <TypebotViewer typebot={publicTypebot} />}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { useToast } from '@chakra-ui/react'
|
||||
import { Block, Step, StepType, Target, Typebot } from 'bot-engine'
|
||||
import { Block, Step, StepType, Target, Theme, Typebot } from 'bot-engine'
|
||||
import { useRouter } from 'next/router'
|
||||
import {
|
||||
createContext,
|
||||
@ -46,6 +46,7 @@ const typebotContext = createContext<{
|
||||
target?: Target
|
||||
}) => void
|
||||
undo: () => void
|
||||
updateTheme: (theme: Theme) => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
@ -264,6 +265,11 @@ export const TypebotContext = ({
|
||||
setLocalTypebot({ ...localTypebot, blocks })
|
||||
}
|
||||
|
||||
const updateTheme = (theme: Theme) => {
|
||||
if (!localTypebot) return
|
||||
setLocalTypebot({ ...localTypebot, theme })
|
||||
}
|
||||
|
||||
return (
|
||||
<typebotContext.Provider
|
||||
value={{
|
||||
@ -279,6 +285,7 @@ export const TypebotContext = ({
|
||||
save: saveTypebot,
|
||||
removeBlock,
|
||||
undo,
|
||||
updateTheme,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { StartBlock, StepType } from 'bot-engine'
|
||||
import { BackgroundType, StartBlock, StepType, Theme } from 'bot-engine'
|
||||
import { Typebot, User } from 'db'
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
@ -38,8 +38,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
},
|
||||
],
|
||||
}
|
||||
const theme: Theme = {
|
||||
general: {
|
||||
font: 'Open Sans',
|
||||
background: { type: BackgroundType.NONE, content: '#ffffff' },
|
||||
},
|
||||
}
|
||||
const typebot = await prisma.typebot.create({
|
||||
data: { ...data, ownerId: user.id, startBlock },
|
||||
data: { ...data, ownerId: user.id, startBlock, theme },
|
||||
})
|
||||
return res.send(typebot)
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ const TypebotEditPage = () => {
|
||||
<EditorContext>
|
||||
<KBarProvider actions={actions}>
|
||||
<KBar />
|
||||
<Flex overflow="hidden" h="100vh">
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<GraphProvider>
|
||||
<Board />
|
||||
|
23
apps/builder/pages/typebots/[id]/theme.tsx
Normal file
23
apps/builder/pages/typebots/[id]/theme.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { Flex } from '@chakra-ui/layout'
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||
import { ThemeContent } from 'components/theme/ThemeContent'
|
||||
import { TypebotContext } from 'contexts/TypebotContext'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
|
||||
const ThemePage = () => {
|
||||
const { query } = useRouter()
|
||||
return (
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Theme" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<ThemeContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(ThemePage)
|
@ -5,6 +5,7 @@ import { useRouter } from 'next/router'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||
import { createTypebot } from 'services/typebots'
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
|
||||
const TemplatesPage = () => {
|
||||
const user = useUser()
|
||||
@ -25,7 +26,7 @@ const TemplatesPage = () => {
|
||||
folderId: router.query.folderId?.toString() ?? null,
|
||||
})
|
||||
if (error) toast({ description: error.message })
|
||||
if (data) router.push(`/typebots/${data.id}`)
|
||||
if (data) router.push(`/typebots/${data.id}/edit`)
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
@ -40,4 +41,4 @@ const TemplatesPage = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default TemplatesPage
|
||||
export default withAuth(TemplatesPage)
|
||||
|
@ -155,4 +155,5 @@ export const parseTypebotToPublicTypebot = (
|
||||
name: typebot.name,
|
||||
startBlock: typebot.startBlock,
|
||||
typebotId: typebot.id,
|
||||
theme: typebot.theme,
|
||||
})
|
||||
|
@ -11,22 +11,22 @@
|
||||
"react-transition-group": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-node-resolve": "^13.1.1",
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-scroll": "^1.8.3",
|
||||
"@types/react-transition-group": "^4.4.4",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.5",
|
||||
"tailwindcss": "^3.0.7",
|
||||
"typescript": "^4.5.4",
|
||||
"rollup": "^2.61.1",
|
||||
"rollup-plugin-dts": "^4.0.1",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-node-resolve": "^13.1.1",
|
||||
"@rollup/plugin-typescript": "^8.3.0"
|
||||
"tailwindcss": "^3.0.7",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2"
|
||||
|
@ -16,11 +16,13 @@ export default [
|
||||
file: packageJson.main,
|
||||
format: 'cjs',
|
||||
sourcemap: true,
|
||||
inlineDynamicImports: true,
|
||||
},
|
||||
{
|
||||
file: packageJson.module,
|
||||
format: 'esm',
|
||||
sourcemap: true,
|
||||
inlineDynamicImports: true,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
|
@ -4,10 +4,9 @@
|
||||
|
||||
:root {
|
||||
--typebot-container-bg-image: none;
|
||||
--typebot-container-bg-color: #f7f8ff;
|
||||
--typebot-container-font-family: 'Inter';
|
||||
--typebot-container-bg-color: transparent;
|
||||
--typebot-container-font-family: 'Open Sans';
|
||||
--typebot-chat-view-max-width: 700px;
|
||||
--typebot-chat-view-bg-color: #ffffff;
|
||||
--typebot-chat-view-color: #303235;
|
||||
|
||||
--typebot-button-active-bg-color: #0042da;
|
||||
@ -318,7 +317,6 @@ textarea {
|
||||
|
||||
.typebot-chat-view {
|
||||
max-width: var(--typebot-chat-view-max-width);
|
||||
background-color: var(--typebot-chat-view-bg-color);
|
||||
}
|
||||
|
||||
.typebot-button.active {
|
||||
|
@ -3,6 +3,8 @@ import { PublicTypebot } from '..'
|
||||
|
||||
import { Block } from '..'
|
||||
import { ChatBlock } from './ChatBlock/ChatBlock'
|
||||
import { useFrame } from 'react-frame-component'
|
||||
import { setCssVariablesValue } from '../services/theme'
|
||||
|
||||
export const ConversationContainer = ({
|
||||
typebot,
|
||||
@ -11,6 +13,7 @@ export const ConversationContainer = ({
|
||||
typebot: PublicTypebot
|
||||
onNewBlockVisisble: (blockId: string) => void
|
||||
}) => {
|
||||
const { document: frameDocument } = useFrame()
|
||||
const [displayedBlocks, setDisplayedBlocks] = useState<Block[]>([])
|
||||
|
||||
const [isConversationEnded, setIsConversationEnded] = useState(false)
|
||||
@ -28,6 +31,10 @@ export const ConversationContainer = ({
|
||||
if (firstBlockId) displayNextBlock(firstBlockId)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setCssVariablesValue(typebot.theme, frameDocument.body.style)
|
||||
}, [typebot.theme, frameDocument])
|
||||
|
||||
return (
|
||||
<div
|
||||
className="overflow-y-scroll w-full lg:w-3/4 min-h-full rounded lg:px-5 px-3 pt-10 relative scrollable-container typebot-chat-view"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { PublicTypebot } from '../models'
|
||||
import React, { useMemo } from 'react'
|
||||
import { BackgroundType, PublicTypebot } from '../models'
|
||||
import { TypebotContext } from '../contexts/TypebotContext'
|
||||
import Frame from 'react-frame-component'
|
||||
//@ts-ignore
|
||||
@ -9,25 +9,47 @@ import { ResultContext } from '../contexts/ResultsContext'
|
||||
|
||||
export type TypebotViewerProps = {
|
||||
typebot: PublicTypebot
|
||||
onNewBlockVisisble: (blockId: string) => void
|
||||
onNewBlockVisisble?: (blockId: string) => void
|
||||
}
|
||||
export const TypebotViewer = ({
|
||||
typebot,
|
||||
onNewBlockVisisble,
|
||||
}: TypebotViewerProps) => {
|
||||
const containerBgColor = useMemo(
|
||||
() =>
|
||||
typebot.theme.general.background.type === BackgroundType.COLOR
|
||||
? typebot.theme.general.background.content
|
||||
: 'transparent',
|
||||
[typebot.theme.general.background]
|
||||
)
|
||||
const handleNewBlockVisible = (blockId: string) => {
|
||||
if (onNewBlockVisisble) onNewBlockVisisble(blockId)
|
||||
}
|
||||
|
||||
return (
|
||||
<Frame
|
||||
id="typebot-iframe"
|
||||
head={<style>{style}</style>}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `@import url('https://fonts.googleapis.com/css2?family=${typebot.theme.general.font}:wght@300;400;600&display=swap');`,
|
||||
}}
|
||||
/>
|
||||
<TypebotContext typebot={typebot}>
|
||||
<ResultContext typebotId={typebot.id}>
|
||||
<div className="flex text-base overflow-hidden bg-cover h-screen w-screen typebot-container flex-col items-center">
|
||||
<div
|
||||
className="flex text-base overflow-hidden bg-cover h-screen w-screen flex-col items-center typebot-container"
|
||||
style={{
|
||||
// We set this as inline style to avoid color flashing (due to SSR)
|
||||
backgroundColor: containerBgColor,
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full h-full justify-center">
|
||||
<ConversationContainer
|
||||
typebot={typebot}
|
||||
onNewBlockVisisble={onNewBlockVisisble}
|
||||
onNewBlockVisisble={handleNewBlockVisible}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { PublicTypebot as PublicTypebotFromPrisma } from 'db'
|
||||
import { Block, StartBlock } from '.'
|
||||
import { Block, StartBlock, Theme } from '.'
|
||||
|
||||
export type PublicTypebot = Omit<
|
||||
PublicTypebotFromPrisma,
|
||||
'blocks' | 'startBlock'
|
||||
'blocks' | 'startBlock' | 'theme'
|
||||
> & {
|
||||
blocks: Block[]
|
||||
startBlock: StartBlock
|
||||
theme: Theme
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { Typebot as TypebotFromPrisma } from 'db'
|
||||
|
||||
export type Typebot = Omit<TypebotFromPrisma, 'blocks' | 'startBlock'> & {
|
||||
export type Typebot = Omit<
|
||||
TypebotFromPrisma,
|
||||
'blocks' | 'startBlock' | 'theme'
|
||||
> & {
|
||||
blocks: Block[]
|
||||
startBlock: StartBlock
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
export type StartBlock = {
|
||||
@ -53,11 +57,20 @@ export type TextInputStep = StepBase & {
|
||||
type: StepType.TEXT_INPUT
|
||||
}
|
||||
|
||||
export type Button = {
|
||||
id: string
|
||||
content: string
|
||||
target: {
|
||||
type: 'block' | 'step'
|
||||
id: string
|
||||
export type Theme = {
|
||||
general: {
|
||||
font: string
|
||||
background: Background
|
||||
}
|
||||
}
|
||||
|
||||
export enum BackgroundType {
|
||||
COLOR = 'Color',
|
||||
IMAGE = 'Image',
|
||||
NONE = 'None',
|
||||
}
|
||||
|
||||
export type Background = {
|
||||
type: BackgroundType
|
||||
content: string
|
||||
}
|
||||
|
25
packages/bot-engine/src/services/theme.ts
Normal file
25
packages/bot-engine/src/services/theme.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { BackgroundType, Theme } from '../models'
|
||||
|
||||
const cssVariableNames = {
|
||||
container: {
|
||||
bg: {
|
||||
image: '--typebot-container-bg-image',
|
||||
color: '--typebot-container-bg-color',
|
||||
},
|
||||
fontFamily: '--typebot-container-font-family',
|
||||
},
|
||||
}
|
||||
|
||||
export const setCssVariablesValue = (
|
||||
theme: Theme,
|
||||
documentStyle: CSSStyleDeclaration
|
||||
) => {
|
||||
const { background, font } = theme.general
|
||||
documentStyle.setProperty(
|
||||
background.type === BackgroundType.IMAGE
|
||||
? cssVariableNames.container.bg.image
|
||||
: cssVariableNames.container.bg.color,
|
||||
background.content
|
||||
)
|
||||
documentStyle.setProperty(cssVariableNames.container.fontFamily, font)
|
||||
}
|
@ -4,16 +4,17 @@
|
||||
"main": "./index.tsx",
|
||||
"types": "./index.tsx",
|
||||
"devDependencies": {
|
||||
"prisma": "latest",
|
||||
"prisma": "^3.7.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "latest"
|
||||
"@prisma/client": "^3.7.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "yarn prisma db push && BROWSER=none yarn prisma studio",
|
||||
"build": "prisma generate && prisma migrate deploy",
|
||||
"migration:push": "dotenv -e ../../.env yarn prisma db push",
|
||||
"migration:create": "dotenv -e ../../.env yarn prisma migrate dev",
|
||||
"migration:reset": "dotenv -e ../../.env yarn prisma migrate reset"
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `theme` to the `PublicTypebot` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `theme` to the `Typebot` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "PublicTypebot" ADD COLUMN "theme" JSONB NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Typebot" ADD COLUMN "theme" JSONB NOT NULL;
|
@ -89,6 +89,7 @@ model Typebot {
|
||||
folder DashboardFolder? @relation(fields: [folderId], references: [id])
|
||||
blocks Json[]
|
||||
startBlock Json
|
||||
theme Json
|
||||
}
|
||||
|
||||
model PublicTypebot {
|
||||
@ -98,6 +99,7 @@ model PublicTypebot {
|
||||
name String
|
||||
blocks Json[]
|
||||
startBlock Json
|
||||
theme Json
|
||||
}
|
||||
|
||||
model Result {
|
||||
|
36
yarn.lock
36
yarn.lock
@ -1180,22 +1180,22 @@
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7"
|
||||
integrity sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==
|
||||
|
||||
"@prisma/client@latest":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.6.0.tgz#68a60cd4c73a369b11f72e173e86fd6789939293"
|
||||
integrity sha512-ycSGY9EZGROtje0iCNsgC5Zqi/ttX2sO7BNMYaLsUMiTlf3F69ZPH+08pRo0hrDfkZzyimXYqeXJlaoYDH1w7A==
|
||||
"@prisma/client@^3.7.0":
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.7.0.tgz#9cafc105f12635c95e9b7e7b18e8fbf52cf3f18a"
|
||||
integrity sha512-fUJMvBOX5C7JPc0e3CJD6Gbelbu4dMJB4ScYpiht8HMUnRShw20ULOipTopjNtl6ekHQJ4muI7pXlQxWS9nMbw==
|
||||
dependencies:
|
||||
"@prisma/engines-version" "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727"
|
||||
"@prisma/engines-version" "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
|
||||
|
||||
"@prisma/engines-version@3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727":
|
||||
version "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727.tgz#25aa447776849a774885866b998732b37ec4f4f5"
|
||||
integrity sha512-vtoO2ys6mSfc8ONTWdcYztKN3GBU1tcKBj0aXObyjzSuGwHFcM/pEA0xF+n1W4/0TAJgfoPX2khNEit6g0jtNA==
|
||||
"@prisma/engines-version@3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f":
|
||||
version "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f.tgz#055f36ac8b06c301332c14963cd0d6c795942c90"
|
||||
integrity sha512-+qx2b+HK7BKF4VCa0LZ/t1QCXsu6SmvhUQyJkOD2aPpmOzket4fEnSKQZSB0i5tl7rwCDsvAiSeK8o7rf+yvwg==
|
||||
|
||||
"@prisma/engines@3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727":
|
||||
version "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727.tgz#c68ede6aeffa9ef7743a32cfa6daf9172a4e15b3"
|
||||
integrity sha512-dRClHS7DsTVchDKzeG72OaEyeDskCv91pnZ72Fftn0mp4BkUvX2LvWup65hCNzwwQm5IDd6A88APldKDnMiEMA==
|
||||
"@prisma/engines@3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f":
|
||||
version "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f.tgz#12f28d5b78519fbd84c89a5bdff457ff5095e7a2"
|
||||
integrity sha512-W549ub5NlgexNhR8EFstA/UwAWq3Zq0w9aNkraqsozVCt2CsX+lK4TK7IW5OZVSnxHwRjrgEAt3r9yPy8nZQRg==
|
||||
|
||||
"@reach/alert@0.13.2":
|
||||
version "0.13.2"
|
||||
@ -5971,12 +5971,12 @@ pretty-format@^3.8.0:
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
|
||||
integrity sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=
|
||||
|
||||
prisma@latest:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.6.0.tgz#99532abc02e045e58c6133a19771bdeb28cecdbe"
|
||||
integrity sha512-6SqgHS/5Rq6HtHjsWsTxlj+ySamGyCLBUQfotc2lStOjPv52IQuDVpp58GieNqc9VnfuFyHUvTZw7aQB+G2fvQ==
|
||||
prisma@^3.7.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.7.0.tgz#9c73eeb2f16f767fdf523d0f4cc4c749734d62e2"
|
||||
integrity sha512-pzgc95msPLcCHqOli7Hnabu/GRfSGSUWl5s2P6N13T/rgMB+NNeKbxCmzQiZT2yLOeLEPivV6YrW1oeQIwJxcg==
|
||||
dependencies:
|
||||
"@prisma/engines" "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727"
|
||||
"@prisma/engines" "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
|
Reference in New Issue
Block a user