2
0

build: add pnpm

This commit is contained in:
Baptiste Arnaud
2022-08-08 08:21:36 +02:00
parent 8c3b5058f1
commit ee338f62dc
183 changed files with 19442 additions and 18364 deletions

View File

@@ -1,5 +1,11 @@
import { FlexProps, Flex, Box, Divider, Text } from '@chakra-ui/react'
import { useColorModeValue } from '@chakra-ui/system'
import {
FlexProps,
Flex,
Box,
Divider,
Text,
useColorModeValue,
} from '@chakra-ui/react'
import React from 'react'
export const DividerWithText = (props: FlexProps) => {

View File

@@ -11,7 +11,7 @@ import { useUser } from 'contexts/UserContext'
import { Answer, Typebot } from 'models'
import React, { useEffect, useRef, useState } from 'react'
import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
import { sendRequest } from 'utils'
import { getViewerUrl, sendRequest } from 'utils'
import confetti from 'canvas-confetti'
import { useToast } from 'components/shared/hooks/useToast'
@@ -119,6 +119,7 @@ export const OnboardingModal = ({ totalTypebots }: Props) => {
<ModalBody>
{typebot && (
<TypebotViewer
apiHost={getViewerUrl({ isBuilder: true })}
typebot={parseTypebotToPublicTypebot(typebot)}
predefinedVariables={{
Name: user?.name?.split(' ')[0] ?? undefined,

View File

@@ -1,5 +1,4 @@
import { Flex, HStack, StackProps } from '@chakra-ui/layout'
import { CloseButton } from '@chakra-ui/react'
import { CloseButton, Flex, HStack, StackProps } from '@chakra-ui/react'
import React, { useEffect, useState } from 'react'
type VerifyEmailBannerProps = { id: string } & StackProps

View File

@@ -18,6 +18,7 @@ import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
import { Log } from 'db'
import React, { useMemo, useState } from 'react'
import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
import { getViewerUrl } from 'utils'
export const PreviewDrawer = () => {
const { typebot } = useTypebot()
@@ -100,6 +101,7 @@ export const PreviewDrawer = () => {
pointerEvents={isResizing ? 'none' : 'auto'}
>
<TypebotViewer
apiHost={getViewerUrl({ isBuilder: true })}
typebot={publicTypebot}
onNewGroupVisible={setPreviewingEdge}
onNewLog={handleNewLog}

View File

@@ -1,10 +1,11 @@
import { chakra, Fade, Button } from '@chakra-ui/react'
import { Cell as CellProps } from '@tanstack/react-table'
import { Cell as CellProps, flexRender } from '@tanstack/react-table'
import { ExpandIcon } from 'assets/icons'
import { memo } from 'react'
import { TableData } from 'services/typebots/results'
type Props = {
cell: CellProps<any>
cell: CellProps<TableData, unknown>
size: number
isExpandButtonVisible: boolean
cellIndex: number
@@ -34,7 +35,7 @@ const Cell = ({
maxWidth: size,
}}
>
{cell.renderCell()}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
<chakra.span
pos="absolute"
top="0"

View File

@@ -1,10 +1,10 @@
import { Box, BoxProps, chakra } from '@chakra-ui/react'
import { HeaderGroup } from '@tanstack/react-table'
import { flexRender, HeaderGroup } from '@tanstack/react-table'
import React from 'react'
import { TableData } from 'services/typebots/results'
type Props = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
headerGroup: HeaderGroup<any>
headerGroup: HeaderGroup<TableData>
}
export const HeaderRow = ({ headerGroup }: Props) => {
@@ -28,7 +28,9 @@ export const HeaderRow = ({ headerGroup }: Props) => {
maxWidth: header.getSize(),
}}
>
{header.isPlaceholder ? null : header.renderHeader()}
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getCanResize() && (
<ResizeHandle
onMouseDown={header.getResizeHandler()}

View File

@@ -18,7 +18,7 @@ import {
convertResultsToTableData,
getAllResults,
deleteResults as deleteFetchResults,
} from 'services/typebots'
} from 'services/typebots/results'
type ResultsActionButtonsProps = {
selectedResultsId: string[]
@@ -93,7 +93,9 @@ export const ResultsActionButtons = ({
const dataToUnparse = isSelectAll
? await getAllTableData()
: tableData.filter((data) => selectedResultsId.includes(data.id))
: tableData.filter((data) =>
selectedResultsId.includes(data.id.plainText)
)
const csvData = new Blob(
[
unparse({

View File

@@ -13,10 +13,10 @@ import { ResultHeaderCell, ResultsTablePreferences } from 'models'
import React, { useEffect, useRef, useState } from 'react'
import { LoadingRows } from './LoadingRows'
import {
createTable,
useTableInstance,
useReactTable,
getCoreRowModel,
ColumnOrderState,
ColumnDef,
} from '@tanstack/react-table'
import { BlockIcon } from 'components/editor/BlocksSideBar/BlockIcon'
import { ColumnSettingsButton } from './ColumnsSettingsButton'
@@ -25,21 +25,11 @@ import { useDebounce } from 'use-debounce'
import { ResultsActionButtons } from './ResultsActionButtons'
import { Row } from './Row'
import { HeaderRow } from './HeaderRow'
type RowType = {
id: string
[key: string]:
| {
plainText: string
element?: JSX.Element | undefined
}
| string
}
const table = createTable().setRowType<RowType>()
import { CellValueType, TableData } from 'services/typebots/results'
type ResultsTableProps = {
resultHeader: ResultHeaderCell[]
data: RowType[]
data: TableData[]
hasMore?: boolean
preferences?: ResultsTablePreferences
onScrollToBottom: () => void
@@ -92,18 +82,18 @@ export const ResultsTable = ({
const bottomElement = useRef<HTMLDivElement | null>(null)
const tableWrapper = useRef<HTMLDivElement | null>(null)
const columns = React.useMemo(
const columns = React.useMemo<ColumnDef<TableData>[]>(
() => [
table.createDisplayColumn({
{
id: 'select',
enableResizing: false,
maxSize: 40,
header: ({ instance }) => (
header: ({ table }) => (
<IndeterminateCheckbox
{...{
checked: instance.getIsAllRowsSelected(),
indeterminate: instance.getIsSomeRowsSelected(),
onChange: instance.getToggleAllRowsSelectedHandler(),
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler(),
}}
/>
),
@@ -118,26 +108,24 @@ export const ResultsTable = ({
/>
</div>
),
}),
...resultHeader.map((header) =>
table.createDataColumn(header.label, {
id: header.id,
size: header.isLong ? 400 : 200,
cell: (info) => {
const value = info.getValue()
if (!value) return
if (typeof value === 'string') return ''
return value.element || value.plainText || ''
},
header: () => (
<HStack overflow="hidden" data-testid={`${header.label} header`}>
<HeaderIcon header={header} />
<Text>{header.label}</Text>
</HStack>
),
})
),
table.createDisplayColumn({
},
...resultHeader.map<ColumnDef<TableData>>((header) => ({
id: header.id,
accessorKey: header.label,
size: header.isLong ? 400 : 200,
header: () => (
<HStack overflow="hidden" data-testid={`${header.label} header`}>
<HeaderIcon header={header} />
<Text>{header.label}</Text>
</HStack>
),
cell: (info) => {
const value = info.getValue() as CellValueType | undefined
if (!value) return
return value.element || value.plainText || ''
},
})),
{
id: 'logs',
enableResizing: false,
maxSize: 110,
@@ -152,13 +140,13 @@ export const ResultsTable = ({
See logs
</Button>
),
}),
},
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[resultHeader]
)
const instance = useTableInstance(table, {
const instance = useReactTable({
data,
columns,
state: {
@@ -167,7 +155,7 @@ export const ResultsTable = ({
columnOrder: columnsOrder,
columnSizing: columnsWidth,
},
getRowId: (row) => row.id,
getRowId: (row) => row.id.plainText,
columnResizeMode: 'onChange',
onRowSelectionChange: setRowSelection,
onColumnVisibilityChange: setColumnsVisibility,

View File

@@ -1,10 +1,10 @@
import React, { useState } from 'react'
import { Row as RowProps } from '@tanstack/react-table'
import Cell from './Cell'
import { TableData } from 'services/typebots/results'
type Props = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
row: RowProps<any>
row: RowProps<TableData>
isSelected: boolean
bottomElement?: React.MutableRefObject<HTMLDivElement | null>
onExpandButtonClick: () => void

View File

@@ -16,11 +16,10 @@ import { useWorkspace } from 'contexts/WorkspaceContext'
import React from 'react'
import { parseDefaultPublicId } from 'services/typebots'
import { isFreePlan } from 'services/workspace'
import { isDefined, isNotDefined } from 'utils'
import { getViewerUrl, isDefined, isNotDefined } from 'utils'
import { CustomDomainsDropdown } from './customDomain/CustomDomainsDropdown'
import { EditableUrl } from './EditableUrl'
import { integrationsList } from './integrations/EmbedButton'
import { env } from 'utils'
export const ShareContent = () => {
const { workspace } = useWorkspace()
@@ -59,7 +58,9 @@ export const ShareContent = () => {
</Heading>
{typebot && (
<EditableUrl
hostname={env('VIEWER_URL') ?? 'https://typebot.io'}
hostname={
getViewerUrl({ isBuilder: true }) ?? 'https://typebot.io'
}
pathname={publicId}
onPathnameChange={handlePublicIdChange}
/>

View File

@@ -1,11 +1,11 @@
import { FlexProps } from '@chakra-ui/layout'
import prettier from 'prettier/standalone'
import parserHtml from 'prettier/parser-html'
import { BubbleParams } from 'typebot-js'
import { parseInitBubbleCode, typebotJsHtml } from '../params'
import { useTypebot } from 'contexts/TypebotContext'
import { CodeEditor } from 'components/shared/CodeEditor'
import { env, isEmpty } from 'utils'
import { env, getViewerUrl } from 'utils'
import { FlexProps } from '@chakra-ui/react'
type ChatEmbedCodeProps = {
withStarterVariables?: boolean
@@ -21,9 +21,7 @@ export const ChatEmbedCode = ({
const snippet = prettier.format(
createSnippet({
url: `${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
button,
proactiveMessage,

View File

@@ -5,7 +5,7 @@ import { parseInitContainerCode, typebotJsHtml } from '../params'
import { IframeParams } from 'typebot-js'
import { useTypebot } from 'contexts/TypebotContext'
import { CodeEditor } from 'components/shared/CodeEditor'
import { env, isEmpty } from 'utils'
import { env, getViewerUrl } from 'utils'
type ContainerEmbedCodeProps = {
widthLabel: string
@@ -23,9 +23,7 @@ export const ContainerEmbedCode = ({
const snippet = prettier.format(
parseSnippet({
url: `${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
heightLabel,
widthLabel,

View File

@@ -1,7 +1,7 @@
import { FlexProps } from '@chakra-ui/react'
import { CodeEditor } from 'components/shared/CodeEditor'
import { useTypebot } from 'contexts/TypebotContext'
import { env, isEmpty } from 'utils'
import { env, getViewerUrl } from 'utils'
type Props = {
widthLabel: string
@@ -14,9 +14,7 @@ export const IframeEmbedCode = ({
}: Props & FlexProps) => {
const { typebot } = useTypebot()
const src = `${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`
const code = `<iframe src="${src}" width="${widthLabel}" height="${heightLabel}" />`

View File

@@ -1,10 +1,10 @@
import { FlexProps } from '@chakra-ui/layout'
import { FlexProps } from '@chakra-ui/react'
import { CodeEditor } from 'components/shared/CodeEditor'
import { useTypebot } from 'contexts/TypebotContext'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { PopupParams } from 'typebot-js'
import { env, isEmpty } from 'utils'
import { env, getViewerUrl } from 'utils'
import { parseInitPopupCode, typebotJsHtml } from '../params'
type PopupEmbedCodeProps = {
@@ -18,9 +18,7 @@ export const PopupEmbedCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const snippet = prettier.format(
createSnippet({
url: `${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
delay,
}),

View File

@@ -10,7 +10,7 @@ import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { CodeEditor } from 'components/shared/CodeEditor'
import { useTypebot } from 'contexts/TypebotContext'
import { env, isEmpty } from 'utils'
import { env, getViewerUrl } from 'utils'
type StandardReactDivProps = { widthLabel: string; heightLabel: string }
export const StandardReactDiv = ({
@@ -21,9 +21,7 @@ export const StandardReactDiv = ({
const snippet = prettier.format(
parseContainerSnippet({
url: `${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
heightLabel,
widthLabel,
@@ -73,9 +71,7 @@ export const PopupReactCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const snippet = prettier.format(
parsePopupSnippet({
url: `${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
delay,
}),
@@ -124,9 +120,7 @@ export const ChatReactCode = ({
const snippet = prettier.format(
parseBubbleSnippet({
url: `${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
button,
proactiveMessage,

View File

@@ -18,7 +18,7 @@ import {
import { useToast } from 'components/shared/hooks/useToast'
import { useEffect, useRef, useState } from 'react'
import { createCustomDomain } from 'services/user'
import { env, isEmpty } from 'utils'
import { env, getViewerUrl } from 'utils'
const hostnameRegex =
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/
@@ -118,9 +118,8 @@ export const CustomDomainModal = ({
<Stack>
<Text fontWeight="bold">Value</Text>
<Text>
{isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')}
{env('VIEWER_INTERNAL_URL') ??
getViewerUrl({ isBuilder: true })}
</Text>
</Stack>
</HStack>

View File

@@ -11,7 +11,7 @@ import { PopupEmbedSettings } from 'components/share/codeSnippets/Popup/EmbedSet
import { CodeEditor } from 'components/shared/CodeEditor'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { env, isEmpty } from 'utils'
import { env, getViewerUrl } from 'utils'
import { ModalProps } from '../../EmbedButton'
type GtmInstructionsProps = {
@@ -41,9 +41,7 @@ const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
const jsCode = parseInitContainerCode({
url: `${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${publicId}`,
})
const headCode = `${typebotJsHtml}

View File

@@ -16,7 +16,7 @@ import {
} from '@chakra-ui/react'
import { CopyButton } from 'components/shared/buttons/CopyButton'
import { PublishFirstInfo } from 'components/shared/Info'
import { env, isEmpty } from 'utils'
import { env, getViewerUrl } from 'utils'
import { ModalProps } from '../EmbedButton'
export const NotionModal = ({
@@ -46,17 +46,15 @@ export const NotionModal = ({
pr="4.5rem"
type={'text'}
defaultValue={`${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ??
getViewerUrl({ isBuilder: true })
}/${publicId}`}
/>
<InputRightElement width="4.5rem">
<CopyButton
textToCopy={`${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ??
getViewerUrl({ isBuilder: true })
}/${publicId}`}
/>
</InputRightElement>

View File

@@ -14,7 +14,7 @@ import { BubbleParams } from 'typebot-js'
import { ModalProps } from '../../EmbedButton'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { env, isEmpty } from 'utils'
import { env, getViewerUrl } from 'utils'
type ShopifyInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
@@ -46,9 +46,7 @@ const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
const jsCode = parseInitContainerCode({
url: `${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${publicId}`,
})
const headCode = prettier.format(

View File

@@ -17,7 +17,7 @@ import {
import { ExternalLinkIcon } from 'assets/icons'
import { CopyButton } from 'components/shared/buttons/CopyButton'
import { PublishFirstInfo } from 'components/shared/Info'
import { env, isEmpty } from 'utils'
import { env, getViewerUrl } from 'utils'
import { ModalProps } from '../EmbedButton'
export const WordpressModal = ({
@@ -55,17 +55,15 @@ export const WordpressModal = ({
pr="4.5rem"
type={'text'}
defaultValue={`${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ??
getViewerUrl({ isBuilder: true })
}/${publicId}`}
/>
<InputRightElement width="4.5rem">
<CopyButton
textToCopy={`${
isEmpty(env('VIEWER_INTERNAL_URL'))
? env('VIEWER_URL')
: env('VIEWER_INTERNAL_URL')
env('VIEWER_INTERNAL_URL') ??
getViewerUrl({ isBuilder: true })
}/${publicId}`}
/>
</InputRightElement>

View File

@@ -1,5 +1,6 @@
import { Box, BoxProps, HStack } from '@chakra-ui/react'
import { EditorState, EditorView, basicSetup } from '@codemirror/basic-setup'
import { EditorView, basicSetup } from 'codemirror'
import { EditorState } from '@codemirror/state'
import { json, jsonParseLinter } from '@codemirror/lang-json'
import { css } from '@codemirror/lang-css'
import { javascript } from '@codemirror/lang-javascript'
@@ -43,7 +44,7 @@ export const CodeEditor = ({
setPlainTextValue(value)
onChange && onChange(value)
},
env('E2E_TEST') === 'enabled' ? 0 : debounceTimeout
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
)
useEffect(

View File

@@ -1,4 +1,4 @@
import { useEventListener } from '@chakra-ui/hooks'
import { useEventListener } from '@chakra-ui/react'
import assert from 'assert'
import {
useGraph,

View File

@@ -1,4 +1,4 @@
import { chakra } from '@chakra-ui/system'
import { chakra } from '@chakra-ui/react'
import { colors } from 'libs/theme'
import { Edge as EdgeProps } from 'models'
import React from 'react'

View File

@@ -3,7 +3,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'
import {
Plate,
selectEditor,
serializeHtml,
TEditor,
TElement,
Value,
@@ -16,6 +15,7 @@ import { parseHtmlStringToPlainText } from 'services/utils'
import { defaultTextBubbleContent, TextBubbleContent, Variable } from 'models'
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
import { ReactEditor } from 'slate-react'
import { serializeHtml } from '@udecode/plate-serializer-html'
type Props = {
initialValue: TElement[]
@@ -98,7 +98,7 @@ export const TextBubbleEditor = ({ initialValue, onClose }: Props) => {
setValue(val)
setIsVariableDropdownOpen(false)
}
const handleKeyDown = (e: React.KeyboardEvent) => {
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.shiftKey) return
if (e.key === 'Enter') closeEditor()
}

View File

@@ -1,4 +1,4 @@
import { StackProps, HStack, Button } from '@chakra-ui/react'
import { StackProps, HStack, Button, IconButton } from '@chakra-ui/react'
import {
MARK_BOLD,
MARK_ITALIC,
@@ -7,7 +7,13 @@ import {
import { getPluginType, PlateEditor, Value } from '@udecode/plate-core'
import { LinkToolbarButton } from '@udecode/plate-ui-link'
import { MarkToolbarButton } from '@udecode/plate-ui-toolbar'
import { BoldIcon, ItalicIcon, UnderlineIcon, LinkIcon } from 'assets/icons'
import {
BoldIcon,
ItalicIcon,
UnderlineIcon,
LinkIcon,
UserIcon,
} from 'assets/icons'
type Props = {
editor: PlateEditor<Value>
@@ -33,9 +39,12 @@ export const ToolBar = ({
borderBottomWidth={1}
{...props}
>
<Button size="sm" onMouseDown={handleVariablesButtonMouseDown}>
Variables
</Button>
<IconButton
aria-label="Insert variable"
size="sm"
onMouseDown={handleVariablesButtonMouseDown}
icon={<UserIcon />}
/>
<span data-testid="bold-button">
<MarkToolbarButton
type={getPluginType(editor, MARK_BOLD)}

View File

@@ -1,22 +1,12 @@
import { ChangeEvent, useState } from 'react'
import {
Button,
Flex,
HStack,
Stack,
Text,
Input as ClassicInput,
SimpleGrid,
GridItem,
} from '@chakra-ui/react'
import { useState } from 'react'
import { Button, Flex, HStack, Stack, Text } from '@chakra-ui/react'
import { SearchContextManager } from '@giphy/react-components'
import { UploadButton } from '../buttons/UploadButton'
import { GiphySearch } from './GiphySearch'
import { useTypebot } from 'contexts/TypebotContext'
import { BaseEmoji, emojiIndex } from 'emoji-mart'
import { emojis } from './emojis'
import { Input } from '../Textbox/Input'
import { env, isEmpty } from 'utils'
import { EmojiSearchableList } from './emoji/EmojiSearchableList'
type Props = {
url?: string
@@ -101,7 +91,7 @@ const BodyContent = ({
case 'giphy':
return <GiphyContent onNewUrl={onSubmit} />
case 'emoji':
return <EmojiContent onEmojiSelected={onSubmit} />
return <EmojiSearchableList onEmojiSelected={onSubmit} />
}
}
@@ -133,55 +123,6 @@ const EmbedLinkContent = ({ initialUrl, onNewUrl }: ContentProps) => (
</Stack>
)
const EmojiContent = ({
onEmojiSelected,
}: {
onEmojiSelected: (emoji: string) => void
}) => {
const [searchValue, setSearchValue] = useState('')
const [filteredEmojis, setFilteredEmojis] = useState<string[]>(emojis)
const handleEmojiClick = (emoji: string) => () => onEmojiSelected(emoji)
const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value)
setFilteredEmojis(
emojiIndex.search(e.target.value)?.map((o) => (o as BaseEmoji).native) ??
emojis
)
}
return (
<Stack>
<ClassicInput
placeholder="Search..."
value={searchValue}
onChange={handleSearchChange}
/>
<SimpleGrid
maxH="350px"
overflowY="scroll"
overflowX="hidden"
spacing={0}
columns={7}
>
{filteredEmojis.map((emoji) => (
<GridItem key={emoji}>
<Button
onClick={handleEmojiClick(emoji)}
variant="ghost"
size="sm"
fontSize="xl"
>
{emoji}
</Button>
</GridItem>
))}
</SimpleGrid>
</Stack>
)
}
const GiphyContent = ({ onNewUrl }: ContentProps) => {
if (isEmpty(env('GIPHY_API_KEY')))
return <Text>NEXT_PUBLIC_GIPHY_API_KEY is missing in environment</Text>

View File

@@ -0,0 +1,199 @@
import emojis from './emojiList.json'
import emojiTagsData from 'emojilib'
import {
Stack,
SimpleGrid,
GridItem,
Button,
Input as ClassicInput,
Text,
} from '@chakra-ui/react'
import { useState, ChangeEvent, useEffect, useRef } from 'react'
const emojiTags = emojiTagsData as Record<string, string[]>
const people = emojis['Smileys & Emotion'].concat(emojis['People & Body'])
const nature = emojis['Animals & Nature']
const food = emojis['Food & Drink']
const activities = emojis['Activities']
const travel = emojis['Travel & Places']
const objects = emojis['Objects']
const symbols = emojis['Symbols']
const flags = emojis['Flags']
export const EmojiSearchableList = ({
onEmojiSelected,
}: {
onEmojiSelected: (emoji: string) => void
}) => {
const scrollContainer = useRef<HTMLDivElement>(null)
const bottomElement = useRef<HTMLDivElement>(null)
const [isSearching, setIsSearching] = useState(false)
const [filteredPeople, setFilteredPeople] = useState(people)
const [filteredAnimals, setFilteredAnimals] = useState(nature)
const [filteredFood, setFilteredFood] = useState(food)
const [filteredTravel, setFilteredTravel] = useState(travel)
const [filteredActivities, setFilteredActivities] = useState(activities)
const [filteredObjects, setFilteredObjects] = useState(objects)
const [filteredSymbols, setFilteredSymbols] = useState(symbols)
const [filteredFlags, setFilteredFlags] = useState(flags)
const [totalDisplayedCategories, setTotalDisplayedCategories] = useState(1)
useEffect(() => {
if (!bottomElement.current) return
const observer = new IntersectionObserver(handleObserver, {
root: scrollContainer.current,
})
if (bottomElement.current) observer.observe(bottomElement.current)
return () => {
observer.disconnect()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bottomElement.current])
const handleObserver = (entities: IntersectionObserverEntry[]) => {
const target = entities[0]
if (target.isIntersecting) setTotalDisplayedCategories((c) => c + 1)
}
const handleSearchChange = async (e: ChangeEvent<HTMLInputElement>) => {
const searchValue = e.target.value
if (searchValue.length <= 2 && isSearching) return resetEmojiList()
setIsSearching(true)
setTotalDisplayedCategories(8)
const byTag = (emoji: string) =>
emojiTags[emoji].find((tag) => tag.includes(searchValue))
setFilteredPeople(people.filter(byTag))
setFilteredAnimals(nature.filter(byTag))
setFilteredFood(food.filter(byTag))
setFilteredTravel(travel.filter(byTag))
setFilteredActivities(activities.filter(byTag))
setFilteredObjects(objects.filter(byTag))
setFilteredSymbols(symbols.filter(byTag))
setFilteredFlags(flags.filter(byTag))
}
const resetEmojiList = () => {
setTotalDisplayedCategories(1)
setIsSearching(false)
setFilteredPeople(people)
setFilteredAnimals(nature)
setFilteredFood(food)
setFilteredTravel(travel)
setFilteredActivities(activities)
setFilteredObjects(objects)
setFilteredSymbols(symbols)
setFilteredFlags(flags)
}
return (
<Stack>
<ClassicInput placeholder="Search..." onChange={handleSearchChange} />
<Stack ref={scrollContainer} overflow="scroll" maxH="350px" spacing={4}>
{filteredPeople.length > 0 && (
<Stack>
<Text fontSize="sm" pl="2">
People
</Text>
<EmojiGrid emojis={filteredPeople} onEmojiClick={onEmojiSelected} />
</Stack>
)}
{filteredAnimals.length > 0 && totalDisplayedCategories >= 2 && (
<Stack>
<Text fontSize="sm" pl="2">
Animals & Nature
</Text>
<EmojiGrid
emojis={filteredAnimals}
onEmojiClick={onEmojiSelected}
/>
</Stack>
)}
{filteredFood.length > 0 && totalDisplayedCategories >= 3 && (
<Stack>
<Text fontSize="sm" pl="2">
Food & Drink
</Text>
<EmojiGrid emojis={filteredFood} onEmojiClick={onEmojiSelected} />
</Stack>
)}
{filteredTravel.length > 0 && totalDisplayedCategories >= 4 && (
<Stack>
<Text fontSize="sm" pl="2">
Travel & Places
</Text>
<EmojiGrid emojis={filteredTravel} onEmojiClick={onEmojiSelected} />
</Stack>
)}
{filteredActivities.length > 0 && totalDisplayedCategories >= 5 && (
<Stack>
<Text fontSize="sm" pl="2">
Activities
</Text>
<EmojiGrid
emojis={filteredActivities}
onEmojiClick={onEmojiSelected}
/>
</Stack>
)}
{filteredObjects.length > 0 && totalDisplayedCategories >= 6 && (
<Stack>
<Text fontSize="sm" pl="2">
Objects
</Text>
<EmojiGrid
emojis={filteredObjects}
onEmojiClick={onEmojiSelected}
/>
</Stack>
)}
{filteredSymbols.length > 0 && totalDisplayedCategories >= 7 && (
<Stack>
<Text fontSize="sm" pl="2">
Symbols
</Text>
<EmojiGrid
emojis={filteredSymbols}
onEmojiClick={onEmojiSelected}
/>
</Stack>
)}
{filteredFlags.length > 0 && totalDisplayedCategories >= 8 && (
<Stack>
<Text fontSize="sm" pl="2">
Flags
</Text>
<EmojiGrid emojis={filteredFlags} onEmojiClick={onEmojiSelected} />
</Stack>
)}
<div ref={bottomElement} />
</Stack>
</Stack>
)
}
const EmojiGrid = ({
emojis,
onEmojiClick,
}: {
emojis: string[]
onEmojiClick: (emoji: string) => void
}) => {
const handleClick = (emoji: string) => () => onEmojiClick(emoji)
return (
<SimpleGrid spacing={0} columns={7}>
{emojis.map((emoji) => (
<GridItem
as={Button}
onClick={handleClick(emoji)}
variant="ghost"
size="sm"
fontSize="xl"
key={emoji}
>
{emoji}
</GridItem>
))}
</SimpleGrid>
)
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,7 @@ export const SearchableDropdown = ({
const debounced = useDebouncedCallback(
// eslint-disable-next-line @typescript-eslint/no-empty-function
onValueChange ? onValueChange : () => {},
env('E2E_TEST') === 'enabled' ? 0 : debounceTimeout
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
)
const [filteredItems, setFilteredItems] = useState([
...items

View File

@@ -23,7 +23,7 @@ export const SmartNumberInput = ({
const [currentValue, setCurrentValue] = useState(value?.toString() ?? '')
const debounced = useDebouncedCallback(
onValueChange,
env('E2E_TEST') === 'enabled' ? 0 : debounceTimeout
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
)
useEffect(

View File

@@ -36,7 +36,7 @@ export const TextBox = ({
(value) => {
onChange(value)
},
env('E2E_TEST') === 'enabled' ? 0 : debounceTimeout
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
)
useEffect(() => {

View File

@@ -1,5 +1,9 @@
import { Editable, EditablePreview, EditableInput } from '@chakra-ui/editable'
import { Tooltip } from '@chakra-ui/tooltip'
import {
Editable,
EditablePreview,
EditableInput,
Tooltip,
} from '@chakra-ui/react'
import React, { useEffect, useState } from 'react'
type EditableProps = {

View File

@@ -47,7 +47,7 @@ export const VariableSearchInput = ({
const variable = variables.find((v) => v.name === value)
if (variable) onSelectVariable(variable)
},
env('E2E_TEST') === 'enabled' ? 0 : debounceTimeout
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
)
const [filteredItems, setFilteredItems] = useState<Variable[]>(
variables ?? []

View File

@@ -1,4 +1,4 @@
import { useToast as useChakraToast, UseToastOptions } from '@chakra-ui/toast'
import { useToast as useChakraToast, UseToastOptions } from '@chakra-ui/react'
export const useToast = () => {
const toast = useChakraToast()

View File

@@ -17,7 +17,7 @@ import { useToast } from 'components/shared/hooks/useToast'
import { Typebot } from 'models'
import React, { useEffect, useState } from 'react'
import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
import { sendRequest } from 'utils'
import { getViewerUrl, sendRequest } from 'utils'
import { TemplateProps, templates } from './data'
type Props = {
@@ -71,6 +71,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
</Heading>
{typebot && (
<TypebotViewer
apiHost={getViewerUrl({ isBuilder: true })}
typebot={parseTypebotToPublicTypebot(typebot)}
key={typebot.id}
/>