♻️ Add shared eslint config
This commit is contained in:
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
pnpm lint
|
@ -1,43 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
ignorePatterns: ['node_modules'],
|
root: true,
|
||||||
env: {
|
extends: ['custom'],
|
||||||
browser: true,
|
|
||||||
es6: true,
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:react/recommended',
|
|
||||||
'next/core-web-vitals',
|
|
||||||
],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
|
||||||
sourceType: 'module', // Allows for the use of imports
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true, // Allows for the parsing of JSX
|
|
||||||
},
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: ['react', '@typescript-eslint'],
|
|
||||||
rules: {
|
|
||||||
'react/no-unescaped-entities': [0],
|
|
||||||
'react/display-name': [0],
|
|
||||||
'no-restricted-imports': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
patterns: [
|
|
||||||
'*/src/*',
|
|
||||||
'src/*',
|
|
||||||
'*/src',
|
|
||||||
'@/features/*/*',
|
|
||||||
'!@/features/blocks/*',
|
|
||||||
'!@/features/*/api',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -106,20 +106,16 @@
|
|||||||
"@types/qs": "6.9.7",
|
"@types/qs": "6.9.7",
|
||||||
"@types/react": "18.0.25",
|
"@types/react": "18.0.25",
|
||||||
"@types/tinycolor2": "1.4.3",
|
"@types/tinycolor2": "1.4.3",
|
||||||
"@typescript-eslint/eslint-plugin": "5.43.0",
|
|
||||||
"@typescript-eslint/parser": "5.43.0",
|
|
||||||
"configs": "workspace:*",
|
|
||||||
"db": "workspace:*",
|
"db": "workspace:*",
|
||||||
"dotenv": "16.0.3",
|
"dotenv": "16.0.3",
|
||||||
"eslint": "8.27.0",
|
|
||||||
"eslint-config-next": "13.0.3",
|
|
||||||
"eslint-plugin-react": "7.31.10",
|
|
||||||
"models": "workspace:*",
|
"models": "workspace:*",
|
||||||
"next-transpile-modules": "10.0.0",
|
"next-transpile-modules": "10.0.0",
|
||||||
"superjson": "^1.11.0",
|
"superjson": "^1.11.0",
|
||||||
"tsconfig": "workspace:*",
|
"tsconfig": "workspace:*",
|
||||||
"typescript": "4.8.4",
|
"typescript": "4.8.4",
|
||||||
"utils": "workspace:*",
|
"utils": "workspace:*",
|
||||||
"zod": "3.19.1"
|
"zod": "3.19.1",
|
||||||
|
"eslint": "8.28.0",
|
||||||
|
"eslint-config-custom": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { PlaywrightTestConfig } from '@playwright/test'
|
import { PlaywrightTestConfig } from '@playwright/test'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { playwrightBaseConfig } from 'configs/playwright'
|
import { playwrightBaseConfig } from 'utils/playwright/baseConfig'
|
||||||
|
|
||||||
const config: PlaywrightTestConfig = {
|
const config: PlaywrightTestConfig = {
|
||||||
...playwrightBaseConfig,
|
...playwrightBaseConfig,
|
||||||
|
@ -36,7 +36,6 @@ export const SearchableDropdown = ({
|
|||||||
const { onOpen, onClose, isOpen } = useDisclosure()
|
const { onOpen, onClose, isOpen } = useDisclosure()
|
||||||
const [inputValue, setInputValue] = useState(selectedItem ?? '')
|
const [inputValue, setInputValue] = useState(selectedItem ?? '')
|
||||||
const debounced = useDebouncedCallback(
|
const debounced = useDebouncedCallback(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
onValueChange ? onValueChange : () => {},
|
onValueChange ? onValueChange : () => {},
|
||||||
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
@ -209,7 +209,7 @@ export const VariableSearchInput = ({
|
|||||||
leftIcon={<PlusIcon />}
|
leftIcon={<PlusIcon />}
|
||||||
bgColor={keyboardFocusIndex === 0 ? 'gray.200' : 'transparent'}
|
bgColor={keyboardFocusIndex === 0 ? 'gray.200' : 'transparent'}
|
||||||
>
|
>
|
||||||
Create "{inputValue}"
|
Create "{inputValue}"
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{filteredItems.length > 0 && (
|
{filteredItems.length > 0 && (
|
||||||
|
@ -24,7 +24,6 @@ const userContext = createContext<{
|
|||||||
currentWorkspaceId?: string
|
currentWorkspaceId?: string
|
||||||
updateUser: (newUser: Partial<User>) => void
|
updateUser: (newUser: Partial<User>) => void
|
||||||
saveUser: (newUser?: Partial<User>) => Promise<void>
|
saveUser: (newUser?: Partial<User>) => Promise<void>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ export const SignInPage = ({ type }: Props) => {
|
|||||||
</Heading>
|
</Heading>
|
||||||
{type === 'signin' ? (
|
{type === 'signin' ? (
|
||||||
<Text>
|
<Text>
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
<TextLink href="/register">Sign up for free</TextLink>
|
<TextLink href="/register">Sign up for free</TextLink>
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
|
@ -52,8 +52,8 @@ export const UsageContent = ({ workspace }: Props) => {
|
|||||||
p="3"
|
p="3"
|
||||||
label={
|
label={
|
||||||
<Text>
|
<Text>
|
||||||
Your typebots are popular! You will soon reach your plan's
|
Your typebots are popular! You will soon reach your
|
||||||
chats limit. 🚀
|
plan's chats limit. 🚀
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Make sure to <strong>update your plan</strong> to increase
|
Make sure to <strong>update your plan</strong> to increase
|
||||||
@ -111,8 +111,8 @@ export const UsageContent = ({ workspace }: Props) => {
|
|||||||
p="3"
|
p="3"
|
||||||
label={
|
label={
|
||||||
<Text>
|
<Text>
|
||||||
Your typebots are popular! You will soon reach your plan's
|
Your typebots are popular! You will soon reach your
|
||||||
storage limit. 🚀
|
plan's storage limit. 🚀
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Make sure to <strong>update your plan</strong> in order to
|
Make sure to <strong>update your plan</strong> in order to
|
||||||
|
@ -90,7 +90,7 @@ export const ChangePlanForm = () => {
|
|||||||
<Text color="gray.500">
|
<Text color="gray.500">
|
||||||
Need custom limits? Specific features?{' '}
|
Need custom limits? Specific features?{' '}
|
||||||
<TextLink href={'https://typebot.io/enterprise-lead-form'} isExternal>
|
<TextLink href={'https://typebot.io/enterprise-lead-form'} isExternal>
|
||||||
Let's chat!
|
Let's chat!
|
||||||
</TextLink>
|
</TextLink>
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -162,14 +162,20 @@ const ActionOptions = ({
|
|||||||
onOptionsChange({ ...options, cellsToExtract } as GoogleSheetsOptions)
|
onOptionsChange({ ...options, cellsToExtract } as GoogleSheetsOptions)
|
||||||
|
|
||||||
const UpdatingCellItem = useMemo(
|
const UpdatingCellItem = useMemo(
|
||||||
() => (props: TableListItemProps<Cell>) =>
|
() =>
|
||||||
<CellWithValueStack {...props} columns={sheet?.columns ?? []} />,
|
function Component(props: TableListItemProps<Cell>) {
|
||||||
|
return <CellWithValueStack {...props} columns={sheet?.columns ?? []} />
|
||||||
|
},
|
||||||
[sheet?.columns]
|
[sheet?.columns]
|
||||||
)
|
)
|
||||||
|
|
||||||
const ExtractingCellItem = useMemo(
|
const ExtractingCellItem = useMemo(
|
||||||
() => (props: TableListItemProps<ExtractingCell>) =>
|
() =>
|
||||||
<CellWithVariableIdStack {...props} columns={sheet?.columns ?? []} />,
|
function Component(props: TableListItemProps<ExtractingCell>) {
|
||||||
|
return (
|
||||||
|
<CellWithVariableIdStack {...props} columns={sheet?.columns ?? []} />
|
||||||
|
)
|
||||||
|
},
|
||||||
[sheet?.columns]
|
[sheet?.columns]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -143,8 +143,10 @@ export const WebhookSettings = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ResponseMappingInputs = useMemo(
|
const ResponseMappingInputs = useMemo(
|
||||||
() => (props: TableListItemProps<ResponseVariableMapping>) =>
|
() =>
|
||||||
<DataVariableInputs {...props} dataItems={responseKeys} />,
|
function Component(props: TableListItemProps<ResponseVariableMapping>) {
|
||||||
|
return <DataVariableInputs {...props} dataItems={responseKeys} />
|
||||||
|
},
|
||||||
[responseKeys]
|
[responseKeys]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export const getDeepKeys = (obj: any): string[] => {
|
export const getDeepKeys = (obj: any): string[] => {
|
||||||
let keys: string[] = []
|
let keys: string[] = []
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
|
@ -36,7 +36,7 @@ export const AnnoucementModal = ({ isOpen, onClose }: Props) => {
|
|||||||
<Modal isOpen={isOpen} onClose={handleCloseClick} size="2xl">
|
<Modal isOpen={isOpen} onClose={handleCloseClick} size="2xl">
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModalHeader>What's new in Typebot 2.0?</ModalHeader>
|
<ModalHeader>What's new in Typebot 2.0?</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody as={Stack} spacing="6" pb="10">
|
<ModalBody as={Stack} spacing="6" pb="10">
|
||||||
<Text>Typebo 2.0 has been launched February the 15th 🎉.</Text>
|
<Text>Typebo 2.0 has been launched February the 15th 🎉.</Text>
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { TypebotViewer } from 'bot-engine'
|
import { TypebotViewer } from 'bot-engine'
|
||||||
import { useUser } from '@/features/account'
|
import { useUser } from '@/features/account'
|
||||||
import { Answer, Typebot } from 'models'
|
import { AnswerInput, Typebot } from 'models'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { getViewerUrl, sendRequest } from 'utils'
|
import { getViewerUrl, sendRequest } from 'utils'
|
||||||
import confetti from 'canvas-confetti'
|
import confetti from 'canvas-confetti'
|
||||||
@ -79,7 +79,7 @@ export const OnboardingModal = ({ totalTypebots }: Props) => {
|
|||||||
setTypebot(data as Typebot)
|
setTypebot(data as Typebot)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNewAnswer = async (answer: Answer) => {
|
const handleNewAnswer = async (answer: AnswerInput) => {
|
||||||
const isName = answer.variableId === 'cl126f4hf000i2e6d8zvzc3t1'
|
const isName = answer.variableId === 'cl126f4hf000i2e6d8zvzc3t1'
|
||||||
const isCompany = answer.variableId === 'cl126jqww000w2e6dq9yv4ifq'
|
const isCompany = answer.variableId === 'cl126jqww000w2e6dq9yv4ifq'
|
||||||
const isCategories = answer.variableId === 'cl126mo3t001b2e6dvyi16bkd'
|
const isCategories = answer.variableId === 'cl126mo3t001b2e6dvyi16bkd'
|
||||||
|
@ -11,7 +11,7 @@ enum ActionType {
|
|||||||
Flush = 'FLUSH',
|
Flush = 'FLUSH',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Actions<T> {
|
export interface Actions<T extends { updatedAt: string } | undefined> {
|
||||||
set: (
|
set: (
|
||||||
newPresent: T | ((current: T) => T),
|
newPresent: T | ((current: T) => T),
|
||||||
options?: { updateDate: boolean }
|
options?: { updateDate: boolean }
|
||||||
@ -24,13 +24,13 @@ export interface Actions<T> {
|
|||||||
presentRef: React.MutableRefObject<T>
|
presentRef: React.MutableRefObject<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Action<T> {
|
interface Action<T extends { updatedAt: string } | undefined> {
|
||||||
type: ActionType
|
type: ActionType
|
||||||
newPresent?: T
|
newPresent?: T
|
||||||
updateDate?: boolean
|
updateDate?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State<T> {
|
export interface State<T extends { updatedAt: string } | undefined> {
|
||||||
past: T[]
|
past: T[]
|
||||||
present: T
|
present: T
|
||||||
future: T[]
|
future: T[]
|
||||||
@ -42,7 +42,10 @@ const initialState = {
|
|||||||
future: [],
|
future: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducer = <T>(state: State<T>, action: Action<T>) => {
|
const reducer = <T extends { updatedAt: string } | undefined>(
|
||||||
|
state: State<T>,
|
||||||
|
action: Action<T>
|
||||||
|
) => {
|
||||||
const { past, present, future } = state
|
const { past, present, future } = state
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@ -98,8 +101,6 @@ const reducer = <T>(state: State<T>, action: Action<T>) => {
|
|||||||
past: [...past, present].filter(isDefined),
|
past: [...past, present].filter(isDefined),
|
||||||
present: {
|
present: {
|
||||||
...newPresent,
|
...newPresent,
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
|
||||||
updatedAt: updateDate ? new Date() : newPresent.updatedAt,
|
updatedAt: updateDate ? new Date() : newPresent.updatedAt,
|
||||||
},
|
},
|
||||||
future: [],
|
future: [],
|
||||||
@ -111,11 +112,13 @@ const reducer = <T>(state: State<T>, action: Action<T>) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const useUndo = <T>(initialPresent: T): [State<T>, Actions<T>] => {
|
const useUndo = <T extends { updatedAt: string } | undefined>(
|
||||||
|
initialPresent: T
|
||||||
|
): [State<T>, Actions<T>] => {
|
||||||
const [state, dispatch] = useReducer(reducer, {
|
const [state, dispatch] = useReducer(reducer, {
|
||||||
...initialState,
|
...initialState,
|
||||||
present: initialPresent,
|
present: initialPresent,
|
||||||
}) as [State<T>, React.Dispatch<Action<T>>]
|
})
|
||||||
const presentRef = useRef<T>(initialPresent)
|
const presentRef = useRef<T>(initialPresent)
|
||||||
|
|
||||||
const canUndo = state.past.length !== 0
|
const canUndo = state.past.length !== 0
|
||||||
@ -136,7 +139,7 @@ const useUndo = <T>(initialPresent: T): [State<T>, Actions<T>] => {
|
|||||||
const set = useCallback(
|
const set = useCallback(
|
||||||
(newPresent: T | ((current: T) => T), options = { updateDate: true }) => {
|
(newPresent: T | ((current: T) => T), options = { updateDate: true }) => {
|
||||||
const updatedTypebot =
|
const updatedTypebot =
|
||||||
'id' in newPresent
|
newPresent && 'id' in newPresent
|
||||||
? newPresent
|
? newPresent
|
||||||
: (newPresent as (current: T) => T)(presentRef.current)
|
: (newPresent as (current: T) => T)(presentRef.current)
|
||||||
presentRef.current = updatedTypebot
|
presentRef.current = updatedTypebot
|
||||||
@ -153,7 +156,10 @@ const useUndo = <T>(initialPresent: T): [State<T>, Actions<T>] => {
|
|||||||
dispatch({ type: ActionType.Flush })
|
dispatch({ type: ActionType.Flush })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return [state, { set, undo, redo, flush, canUndo, canRedo, presentRef }]
|
return [
|
||||||
|
state as State<T>,
|
||||||
|
{ set, undo, redo, flush, canUndo, canRedo, presentRef },
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useUndo
|
export default useUndo
|
||||||
|
@ -16,7 +16,6 @@ const editorContext = createContext<{
|
|||||||
setRightPanel: Dispatch<SetStateAction<RightPanel | undefined>>
|
setRightPanel: Dispatch<SetStateAction<RightPanel | undefined>>
|
||||||
startPreviewAtGroup: string | undefined
|
startPreviewAtGroup: string | undefined
|
||||||
setStartPreviewAtGroup: Dispatch<SetStateAction<string | undefined>>
|
setStartPreviewAtGroup: Dispatch<SetStateAction<string | undefined>>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
|
@ -88,7 +88,6 @@ const typebotContext = createContext<
|
|||||||
ItemsActions &
|
ItemsActions &
|
||||||
VariablesActions &
|
VariablesActions &
|
||||||
EdgesActions
|
EdgesActions
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
>({})
|
>({})
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ const typebotDndContext = createContext<{
|
|||||||
setDraggedTypebot: Dispatch<SetStateAction<TypebotInDashboard | undefined>>
|
setDraggedTypebot: Dispatch<SetStateAction<TypebotInDashboard | undefined>>
|
||||||
mouseOverFolderId?: string | null
|
mouseOverFolderId?: string | null
|
||||||
setMouseOverFolderId: Dispatch<SetStateAction<string | undefined | null>>
|
setMouseOverFolderId: Dispatch<SetStateAction<string | undefined | null>>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
|
@ -47,197 +47,196 @@ export const GroupNode = ({ group, groupIndex }: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const DraggableGroupNode = memo(
|
const NonMemoizedDraggableGroupNode = ({
|
||||||
({
|
group,
|
||||||
group,
|
groupIndex,
|
||||||
groupIndex,
|
onGroupDrag,
|
||||||
onGroupDrag,
|
}: Props & { onGroupDrag: (newCoord: Coordinates) => void }) => {
|
||||||
}: Props & { onGroupDrag: (newCoord: Coordinates) => void }) => {
|
const {
|
||||||
const {
|
connectingIds,
|
||||||
connectingIds,
|
setConnectingIds,
|
||||||
setConnectingIds,
|
previewingEdge,
|
||||||
previewingEdge,
|
isReadOnly,
|
||||||
isReadOnly,
|
focusedGroupId,
|
||||||
focusedGroupId,
|
setFocusedGroupId,
|
||||||
setFocusedGroupId,
|
graphPosition,
|
||||||
graphPosition,
|
} = useGraph()
|
||||||
} = useGraph()
|
const { typebot, updateGroup } = useTypebot()
|
||||||
const { typebot, updateGroup } = useTypebot()
|
const { setMouseOverGroup, mouseOverGroup } = useBlockDnd()
|
||||||
const { setMouseOverGroup, mouseOverGroup } = useBlockDnd()
|
const { setRightPanel, setStartPreviewAtGroup } = useEditor()
|
||||||
const { setRightPanel, setStartPreviewAtGroup } = useEditor()
|
|
||||||
|
|
||||||
const [isMouseDown, setIsMouseDown] = useState(false)
|
const [isMouseDown, setIsMouseDown] = useState(false)
|
||||||
const [isConnecting, setIsConnecting] = useState(false)
|
const [isConnecting, setIsConnecting] = useState(false)
|
||||||
const [currentCoordinates, setCurrentCoordinates] = useState(
|
const [currentCoordinates, setCurrentCoordinates] = useState(
|
||||||
group.graphCoordinates
|
group.graphCoordinates
|
||||||
|
)
|
||||||
|
const [groupTitle, setGroupTitle] = useState(group.title)
|
||||||
|
|
||||||
|
// When the group is moved from external action (e.g. undo/redo), update the current coordinates
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentCoordinates({
|
||||||
|
x: group.graphCoordinates.x,
|
||||||
|
y: group.graphCoordinates.y,
|
||||||
|
})
|
||||||
|
}, [group.graphCoordinates.x, group.graphCoordinates.y])
|
||||||
|
|
||||||
|
// Same for group title
|
||||||
|
useEffect(() => {
|
||||||
|
setGroupTitle(group.title)
|
||||||
|
}, [group.title])
|
||||||
|
|
||||||
|
const isPreviewing =
|
||||||
|
previewingEdge?.from.groupId === group.id ||
|
||||||
|
(previewingEdge?.to.groupId === group.id &&
|
||||||
|
isNotDefined(previewingEdge.to.blockId))
|
||||||
|
const isStartGroup =
|
||||||
|
isDefined(group.blocks[0]) && group.blocks[0].type === 'start'
|
||||||
|
|
||||||
|
const groupRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
const [debouncedGroupPosition] = useDebounce(currentCoordinates, 100)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!currentCoordinates || isReadOnly) return
|
||||||
|
if (
|
||||||
|
currentCoordinates?.x === group.graphCoordinates.x &&
|
||||||
|
currentCoordinates.y === group.graphCoordinates.y
|
||||||
)
|
)
|
||||||
const [groupTitle, setGroupTitle] = useState(group.title)
|
return
|
||||||
|
updateGroup(groupIndex, { graphCoordinates: currentCoordinates })
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [debouncedGroupPosition])
|
||||||
|
|
||||||
// When the group is moved from external action (e.g. undo/redo), update the current coordinates
|
useEffect(() => {
|
||||||
useEffect(() => {
|
setIsConnecting(
|
||||||
setCurrentCoordinates({
|
connectingIds?.target?.groupId === group.id &&
|
||||||
x: group.graphCoordinates.x,
|
isNotDefined(connectingIds.target?.blockId)
|
||||||
y: group.graphCoordinates.y,
|
|
||||||
})
|
|
||||||
}, [group.graphCoordinates.x, group.graphCoordinates.y])
|
|
||||||
|
|
||||||
// Same for group title
|
|
||||||
useEffect(() => {
|
|
||||||
setGroupTitle(group.title)
|
|
||||||
}, [group.title])
|
|
||||||
|
|
||||||
const isPreviewing =
|
|
||||||
previewingEdge?.from.groupId === group.id ||
|
|
||||||
(previewingEdge?.to.groupId === group.id &&
|
|
||||||
isNotDefined(previewingEdge.to.blockId))
|
|
||||||
const isStartGroup =
|
|
||||||
isDefined(group.blocks[0]) && group.blocks[0].type === 'start'
|
|
||||||
|
|
||||||
const groupRef = useRef<HTMLDivElement | null>(null)
|
|
||||||
const [debouncedGroupPosition] = useDebounce(currentCoordinates, 100)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!currentCoordinates || isReadOnly) return
|
|
||||||
if (
|
|
||||||
currentCoordinates?.x === group.graphCoordinates.x &&
|
|
||||||
currentCoordinates.y === group.graphCoordinates.y
|
|
||||||
)
|
|
||||||
return
|
|
||||||
updateGroup(groupIndex, { graphCoordinates: currentCoordinates })
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [debouncedGroupPosition])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsConnecting(
|
|
||||||
connectingIds?.target?.groupId === group.id &&
|
|
||||||
isNotDefined(connectingIds.target?.blockId)
|
|
||||||
)
|
|
||||||
}, [connectingIds, group.id])
|
|
||||||
|
|
||||||
const handleTitleSubmit = (title: string) =>
|
|
||||||
title.length > 0 ? updateGroup(groupIndex, { title }) : undefined
|
|
||||||
|
|
||||||
const handleMouseDown = (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
if (isReadOnly) return
|
|
||||||
if (mouseOverGroup?.id !== group.id && !isStartGroup)
|
|
||||||
setMouseOverGroup({ id: group.id, ref: groupRef })
|
|
||||||
if (connectingIds)
|
|
||||||
setConnectingIds({ ...connectingIds, target: { groupId: group.id } })
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
if (isReadOnly) return
|
|
||||||
setMouseOverGroup(undefined)
|
|
||||||
if (connectingIds)
|
|
||||||
setConnectingIds({ ...connectingIds, target: undefined })
|
|
||||||
}
|
|
||||||
|
|
||||||
const startPreviewAtThisGroup = () => {
|
|
||||||
setStartPreviewAtGroup(group.id)
|
|
||||||
setRightPanel(RightPanel.PREVIEW)
|
|
||||||
}
|
|
||||||
|
|
||||||
useDrag(
|
|
||||||
({ first, last, offset: [offsetX, offsetY], event, target }) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
if ((target as HTMLElement).classList.contains('prevent-group-drag'))
|
|
||||||
return
|
|
||||||
if (first) {
|
|
||||||
setFocusedGroupId(group.id)
|
|
||||||
setIsMouseDown(true)
|
|
||||||
}
|
|
||||||
if (last) {
|
|
||||||
setIsMouseDown(false)
|
|
||||||
}
|
|
||||||
const newCoord = {
|
|
||||||
x: offsetX / graphPosition.scale,
|
|
||||||
y: offsetY / graphPosition.scale,
|
|
||||||
}
|
|
||||||
setCurrentCoordinates(newCoord)
|
|
||||||
onGroupDrag(newCoord)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: groupRef,
|
|
||||||
pointer: { keys: false },
|
|
||||||
from: () => [
|
|
||||||
currentCoordinates.x * graphPosition.scale,
|
|
||||||
currentCoordinates.y * graphPosition.scale,
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
}, [connectingIds, group.id])
|
||||||
|
|
||||||
return (
|
const handleTitleSubmit = (title: string) =>
|
||||||
<ContextMenu<HTMLDivElement>
|
title.length > 0 ? updateGroup(groupIndex, { title }) : undefined
|
||||||
renderMenu={() => <GroupNodeContextMenu groupIndex={groupIndex} />}
|
|
||||||
isDisabled={isReadOnly || isStartGroup}
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
>
|
e.stopPropagation()
|
||||||
{(ref, isOpened) => (
|
|
||||||
<Stack
|
|
||||||
ref={setMultipleRefs([ref, groupRef])}
|
|
||||||
data-testid="group"
|
|
||||||
p="4"
|
|
||||||
rounded="xl"
|
|
||||||
bgColor="#ffffff"
|
|
||||||
borderWidth="2px"
|
|
||||||
borderColor={
|
|
||||||
isConnecting || isOpened || isPreviewing ? 'blue.400' : '#ffffff'
|
|
||||||
}
|
|
||||||
w="300px"
|
|
||||||
transition="border 300ms, box-shadow 200ms"
|
|
||||||
pos="absolute"
|
|
||||||
style={{
|
|
||||||
transform: `translate(${currentCoordinates?.x ?? 0}px, ${
|
|
||||||
currentCoordinates?.y ?? 0
|
|
||||||
}px)`,
|
|
||||||
}}
|
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
cursor={isMouseDown ? 'grabbing' : 'pointer'}
|
|
||||||
shadow="md"
|
|
||||||
_hover={{ shadow: 'lg' }}
|
|
||||||
zIndex={focusedGroupId === group.id ? 10 : 1}
|
|
||||||
>
|
|
||||||
<Editable
|
|
||||||
value={groupTitle}
|
|
||||||
onChange={setGroupTitle}
|
|
||||||
onSubmit={handleTitleSubmit}
|
|
||||||
fontWeight="semibold"
|
|
||||||
pointerEvents={isReadOnly || isStartGroup ? 'none' : 'auto'}
|
|
||||||
pr="8"
|
|
||||||
>
|
|
||||||
<EditablePreview
|
|
||||||
_hover={{ bgColor: 'gray.200' }}
|
|
||||||
px="1"
|
|
||||||
userSelect={'none'}
|
|
||||||
/>
|
|
||||||
<EditableInput minW="0" px="1" className="prevent-group-drag" />
|
|
||||||
</Editable>
|
|
||||||
{typebot && (
|
|
||||||
<BlockNodesList
|
|
||||||
groupId={group.id}
|
|
||||||
blocks={group.blocks}
|
|
||||||
groupIndex={groupIndex}
|
|
||||||
groupRef={ref}
|
|
||||||
isStartGroup={isStartGroup}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<IconButton
|
|
||||||
icon={<PlayIcon />}
|
|
||||||
aria-label={'Preview bot from this group'}
|
|
||||||
pos="absolute"
|
|
||||||
right={2}
|
|
||||||
top={0}
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={startPreviewAtThisGroup}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</ContextMenu>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
if (isReadOnly) return
|
||||||
|
if (mouseOverGroup?.id !== group.id && !isStartGroup)
|
||||||
|
setMouseOverGroup({ id: group.id, ref: groupRef })
|
||||||
|
if (connectingIds)
|
||||||
|
setConnectingIds({ ...connectingIds, target: { groupId: group.id } })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
if (isReadOnly) return
|
||||||
|
setMouseOverGroup(undefined)
|
||||||
|
if (connectingIds) setConnectingIds({ ...connectingIds, target: undefined })
|
||||||
|
}
|
||||||
|
|
||||||
|
const startPreviewAtThisGroup = () => {
|
||||||
|
setStartPreviewAtGroup(group.id)
|
||||||
|
setRightPanel(RightPanel.PREVIEW)
|
||||||
|
}
|
||||||
|
|
||||||
|
useDrag(
|
||||||
|
({ first, last, offset: [offsetX, offsetY], event, target }) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
if ((target as HTMLElement).classList.contains('prevent-group-drag'))
|
||||||
|
return
|
||||||
|
if (first) {
|
||||||
|
setFocusedGroupId(group.id)
|
||||||
|
setIsMouseDown(true)
|
||||||
|
}
|
||||||
|
if (last) {
|
||||||
|
setIsMouseDown(false)
|
||||||
|
}
|
||||||
|
const newCoord = {
|
||||||
|
x: offsetX / graphPosition.scale,
|
||||||
|
y: offsetY / graphPosition.scale,
|
||||||
|
}
|
||||||
|
setCurrentCoordinates(newCoord)
|
||||||
|
onGroupDrag(newCoord)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: groupRef,
|
||||||
|
pointer: { keys: false },
|
||||||
|
from: () => [
|
||||||
|
currentCoordinates.x * graphPosition.scale,
|
||||||
|
currentCoordinates.y * graphPosition.scale,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu<HTMLDivElement>
|
||||||
|
renderMenu={() => <GroupNodeContextMenu groupIndex={groupIndex} />}
|
||||||
|
isDisabled={isReadOnly || isStartGroup}
|
||||||
|
>
|
||||||
|
{(ref, isOpened) => (
|
||||||
|
<Stack
|
||||||
|
ref={setMultipleRefs([ref, groupRef])}
|
||||||
|
data-testid="group"
|
||||||
|
p="4"
|
||||||
|
rounded="xl"
|
||||||
|
bgColor="#ffffff"
|
||||||
|
borderWidth="2px"
|
||||||
|
borderColor={
|
||||||
|
isConnecting || isOpened || isPreviewing ? 'blue.400' : '#ffffff'
|
||||||
|
}
|
||||||
|
w="300px"
|
||||||
|
transition="border 300ms, box-shadow 200ms"
|
||||||
|
pos="absolute"
|
||||||
|
style={{
|
||||||
|
transform: `translate(${currentCoordinates?.x ?? 0}px, ${
|
||||||
|
currentCoordinates?.y ?? 0
|
||||||
|
}px)`,
|
||||||
|
}}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
cursor={isMouseDown ? 'grabbing' : 'pointer'}
|
||||||
|
shadow="md"
|
||||||
|
_hover={{ shadow: 'lg' }}
|
||||||
|
zIndex={focusedGroupId === group.id ? 10 : 1}
|
||||||
|
>
|
||||||
|
<Editable
|
||||||
|
value={groupTitle}
|
||||||
|
onChange={setGroupTitle}
|
||||||
|
onSubmit={handleTitleSubmit}
|
||||||
|
fontWeight="semibold"
|
||||||
|
pointerEvents={isReadOnly || isStartGroup ? 'none' : 'auto'}
|
||||||
|
pr="8"
|
||||||
|
>
|
||||||
|
<EditablePreview
|
||||||
|
_hover={{ bgColor: 'gray.200' }}
|
||||||
|
px="1"
|
||||||
|
userSelect={'none'}
|
||||||
|
/>
|
||||||
|
<EditableInput minW="0" px="1" className="prevent-group-drag" />
|
||||||
|
</Editable>
|
||||||
|
{typebot && (
|
||||||
|
<BlockNodesList
|
||||||
|
groupId={group.id}
|
||||||
|
blocks={group.blocks}
|
||||||
|
groupIndex={groupIndex}
|
||||||
|
groupRef={ref}
|
||||||
|
isStartGroup={isStartGroup}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
icon={<PlayIcon />}
|
||||||
|
aria-label={'Preview bot from this group'}
|
||||||
|
pos="absolute"
|
||||||
|
right={2}
|
||||||
|
top={0}
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={startPreviewAtThisGroup}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</ContextMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DraggableGroupNode = memo(NonMemoizedDraggableGroupNode)
|
||||||
|
@ -27,7 +27,6 @@ const graphDndContext = createContext<{
|
|||||||
setMouseOverGroup: Dispatch<SetStateAction<NodeInfo | undefined>>
|
setMouseOverGroup: Dispatch<SetStateAction<NodeInfo | undefined>>
|
||||||
mouseOverBlock?: NodeInfo
|
mouseOverBlock?: NodeInfo
|
||||||
setMouseOverBlock: Dispatch<SetStateAction<NodeInfo | undefined>>
|
setMouseOverBlock: Dispatch<SetStateAction<NodeInfo | undefined>>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
|
@ -74,7 +74,6 @@ const graphContext = createContext<{
|
|||||||
isReadOnly: boolean
|
isReadOnly: boolean
|
||||||
focusedGroupId?: string
|
focusedGroupId?: string
|
||||||
setFocusedGroupId: Dispatch<SetStateAction<string | undefined>>
|
setFocusedGroupId: Dispatch<SetStateAction<string | undefined>>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({
|
}>({
|
||||||
graphPosition: graphPositionDefaultValue,
|
graphPosition: graphPositionDefaultValue,
|
||||||
|
@ -12,7 +12,6 @@ import { GroupsCoordinates, Coordinates } from './GraphProvider'
|
|||||||
const groupsCoordinatesContext = createContext<{
|
const groupsCoordinatesContext = createContext<{
|
||||||
groupsCoordinates: GroupsCoordinates
|
groupsCoordinates: GroupsCoordinates
|
||||||
updateGroupCoordinates: (groupId: string, newCoord: Coordinates) => void
|
updateGroupCoordinates: (groupId: string, newCoord: Coordinates) => void
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ const resultsContext = createContext<{
|
|||||||
onDeleteResults: (totalResultsDeleted: number) => void
|
onDeleteResults: (totalResultsDeleted: number) => void
|
||||||
fetchNextPage: () => void
|
fetchNextPage: () => void
|
||||||
refetchResults: () => void
|
refetchResults: () => void
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { ToolIcon, EyeIcon, EyeOffIcon, GripIcon } from '@/components/icons'
|
import { ToolIcon, EyeIcon, EyeOffIcon, GripIcon } from '@/components/icons'
|
||||||
import { ResultHeaderCell } from 'models'
|
import { ResultHeaderCell } from 'models'
|
||||||
import React, { forwardRef, useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { isNotDefined } from 'utils'
|
import { isNotDefined } from 'utils'
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
@ -128,7 +128,7 @@ export const ColumnSettingsButton = ({
|
|||||||
</SortableContext>
|
</SortableContext>
|
||||||
<Portal>
|
<Portal>
|
||||||
<DragOverlay dropAnimation={{ duration: 0 }}>
|
<DragOverlay dropAnimation={{ duration: 0 }}>
|
||||||
{draggingColumnId ? <SortableColumnOverlay /> : null}
|
{draggingColumnId ? <Flex /> : null}
|
||||||
</DragOverlay>
|
</DragOverlay>
|
||||||
</Portal>
|
</Portal>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
@ -210,9 +210,3 @@ const SortableColumns = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SortableColumnOverlay = forwardRef(
|
|
||||||
(_, ref: React.LegacyRef<HTMLDivElement>) => {
|
|
||||||
return <HStack ref={ref}></HStack>
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Checkbox, Flex } from '@chakra-ui/react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const TableCheckBox = (
|
||||||
|
{ indeterminate, checked, ...rest }: any,
|
||||||
|
ref: React.LegacyRef<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const defaultRef = React.useRef()
|
||||||
|
const resolvedRef: any = ref || defaultRef
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex justify="center" data-testid="checkbox">
|
||||||
|
<Checkbox
|
||||||
|
ref={resolvedRef}
|
||||||
|
{...rest}
|
||||||
|
isIndeterminate={indeterminate}
|
||||||
|
isChecked={checked}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IndeterminateCheckbox = React.forwardRef(TableCheckBox)
|
@ -2,7 +2,6 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
chakra,
|
chakra,
|
||||||
Checkbox,
|
|
||||||
Flex,
|
Flex,
|
||||||
HStack,
|
HStack,
|
||||||
Stack,
|
Stack,
|
||||||
@ -26,6 +25,7 @@ import { Row } from './Row'
|
|||||||
import { HeaderRow } from './HeaderRow'
|
import { HeaderRow } from './HeaderRow'
|
||||||
import { CellValueType, TableData } from '../../types'
|
import { CellValueType, TableData } from '../../types'
|
||||||
import { HeaderIcon } from '../../utils'
|
import { HeaderIcon } from '../../utils'
|
||||||
|
import { IndeterminateCheckbox } from './IndeterminateCheckbox'
|
||||||
|
|
||||||
type ResultsTableProps = {
|
type ResultsTableProps = {
|
||||||
resultHeader: ResultHeaderCell[]
|
resultHeader: ResultHeaderCell[]
|
||||||
@ -238,23 +238,3 @@ export const ResultsTable = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const IndeterminateCheckbox = React.forwardRef(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
({ indeterminate, checked, ...rest }: any, ref) => {
|
|
||||||
const defaultRef = React.useRef()
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const resolvedRef: any = ref || defaultRef
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex justify="center" data-testid="checkbox">
|
|
||||||
<Checkbox
|
|
||||||
ref={resolvedRef}
|
|
||||||
{...rest}
|
|
||||||
isIndeterminate={indeterminate}
|
|
||||||
isChecked={checked}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@ -30,7 +30,6 @@ const workspaceContext = createContext<{
|
|||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
deleteCurrentWorkspace: () => Promise<void>
|
deleteCurrentWorkspace: () => Promise<void>
|
||||||
refreshWorkspace: (expectedUpdates: Partial<Workspace>) => void
|
refreshWorkspace: (expectedUpdates: Partial<Workspace>) => void
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ const DeleteWorkspaceButton = ({
|
|||||||
message={
|
message={
|
||||||
<Text>
|
<Text>
|
||||||
Are you sure you want to delete {workspaceName} workspace? All its
|
Are you sure you want to delete {workspaceName} workspace? All its
|
||||||
folders, typebots and results will be deleted forever.'
|
folders, typebots and results will be deleted forever.
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
confirmButtonLabel="Delete"
|
confirmButtonLabel="Delete"
|
||||||
|
@ -7,8 +7,7 @@ export const useAutoSave = <T>(
|
|||||||
item,
|
item,
|
||||||
debounceTimeout,
|
debounceTimeout,
|
||||||
}: {
|
}: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
handler: () => Promise<void>
|
||||||
handler: () => Promise<any>
|
|
||||||
item?: T
|
item?: T
|
||||||
debounceTimeout: number
|
debounceTimeout: number
|
||||||
},
|
},
|
||||||
|
@ -106,5 +106,4 @@ const components = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export const customTheme: any = extendTheme({ colors, fonts, components })
|
export const customTheme: any = extendTheme({ colors, fonts, components })
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import NextErrorComponent from 'next/error'
|
import NextErrorComponent, { ErrorProps } from 'next/error'
|
||||||
|
|
||||||
import * as Sentry from '@sentry/nextjs'
|
import * as Sentry from '@sentry/nextjs'
|
||||||
import { NextPageContext } from 'next'
|
import { NextPageContext } from 'next'
|
||||||
@ -24,14 +24,14 @@ const MyError = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
MyError.getInitialProps = async (context: NextPageContext) => {
|
MyError.getInitialProps = async (context: NextPageContext) => {
|
||||||
const errorInitialProps = await NextErrorComponent.getInitialProps(context)
|
const errorInitialProps = (await NextErrorComponent.getInitialProps(
|
||||||
|
context
|
||||||
|
)) as ErrorProps & { hasGetInitialPropsRun: boolean }
|
||||||
|
|
||||||
const { res, err, asPath } = context
|
const { res, err, asPath } = context
|
||||||
|
|
||||||
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when
|
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when
|
||||||
// getInitialProps has run
|
// getInitialProps has run
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
errorInitialProps.hasGetInitialPropsRun = true
|
errorInitialProps.hasGetInitialPropsRun = true
|
||||||
|
|
||||||
// Returning early because we don't want to log 404 errors to Sentry.
|
// Returning early because we don't want to log 404 errors to Sentry.
|
||||||
|
@ -18,8 +18,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
return res.status(404).send("Couldn't find credentials in database")
|
return res.status(404).send("Couldn't find credentials in database")
|
||||||
const response = await drive({
|
const response = await drive({
|
||||||
version: 'v3',
|
version: 'v3',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
auth: auth.client,
|
auth: auth.client,
|
||||||
}).files.list({
|
}).files.list({
|
||||||
q: "mimeType='application/vnd.google-apps.spreadsheet'",
|
q: "mimeType='application/vnd.google-apps.spreadsheet'",
|
||||||
|
@ -128,5 +128,4 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
return methodNotAllowed(res)
|
return methodNotAllowed(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export default withSentry(cors(webhookHandler as any))
|
export default withSentry(cors(webhookHandler as any))
|
||||||
|
@ -76,9 +76,9 @@ export const readFile = (file: File): Promise<string> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const timeSince = (date: string) => {
|
export const timeSince = (date: string) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
const seconds = Math.floor(
|
||||||
//@ts-ignore
|
(new Date().getTime() - new Date(date).getTime()) / 1000
|
||||||
const seconds = Math.floor((new Date() - new Date(date)) / 1000)
|
)
|
||||||
|
|
||||||
let interval = seconds / 31536000
|
let interval = seconds / 31536000
|
||||||
|
|
||||||
|
@ -6,6 +6,5 @@
|
|||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
ignorePatterns: ['node_modules'],
|
root: true,
|
||||||
env: {
|
extends: ['custom'],
|
||||||
browser: true,
|
|
||||||
es6: true,
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:react/recommended',
|
|
||||||
'next/core-web-vitals',
|
|
||||||
],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
|
||||||
sourceType: 'module', // Allows for the use of imports
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true, // Allows for the parsing of JSX
|
|
||||||
},
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: ['react', '@typescript-eslint'],
|
|
||||||
rules: {
|
|
||||||
'react/no-unescaped-entities': [0],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ export const EasyEmbed = () => {
|
|||||||
>
|
>
|
||||||
Embedding your typebot in your applications is a walk in the park.
|
Embedding your typebot in your applications is a walk in the park.
|
||||||
Typebot gives you several step-by-step platform-specific
|
Typebot gives you several step-by-step platform-specific
|
||||||
instructions. Your typebot will always feel "native".
|
instructions. Your typebot will always feel "native".
|
||||||
</Text>
|
</Text>
|
||||||
<Flex data-aos="fade">
|
<Flex data-aos="fade">
|
||||||
<Button
|
<Button
|
||||||
|
@ -67,8 +67,8 @@ export const RealTimeResults = () => {
|
|||||||
data-aos="fade"
|
data-aos="fade"
|
||||||
>
|
>
|
||||||
One of the main advantage of a chat application is that you collect
|
One of the main advantage of a chat application is that you collect
|
||||||
the user's responses on each question.{' '}
|
the user's responses on each question.{' '}
|
||||||
<strong>You won't loose any valuable data.</strong>
|
<strong>You won't loose any valuable data.</strong>
|
||||||
</Text>
|
</Text>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Button
|
<Button
|
||||||
|
@ -10,7 +10,7 @@ export const Testimonials = () => {
|
|||||||
<Flex as="section" justify="center">
|
<Flex as="section" justify="center">
|
||||||
<VStack spacing={12} pt={'52'} px="4">
|
<VStack spacing={12} pt={'52'} px="4">
|
||||||
<Heading textAlign={'center'} data-aos="fade">
|
<Heading textAlign={'center'} data-aos="fade">
|
||||||
They've tried, they never looked back. 💙
|
They've tried, they never looked back. 💙
|
||||||
</Heading>
|
</Heading>
|
||||||
<Stack
|
<Stack
|
||||||
direction={{ base: 'column', xl: 'row' }}
|
direction={{ base: 'column', xl: 'row' }}
|
||||||
|
@ -92,7 +92,6 @@ const components = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export const theme: any = extendTheme({
|
export const theme: any = extendTheme({
|
||||||
fonts,
|
fonts,
|
||||||
components,
|
components,
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
"dev": "ENVSH_ENV=.env.local bash ../../env.sh next dev -p 3002",
|
"dev": "ENVSH_ENV=.env.local bash ../../env.sh next dev -p 3002",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
"lint": "next lint",
|
||||||
"analyze": "cross-env ANALYZE=true next build"
|
"analyze": "cross-env ANALYZE=true next build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -30,16 +31,14 @@
|
|||||||
"@types/aos": "3.0.4",
|
"@types/aos": "3.0.4",
|
||||||
"@types/node": "18.11.9",
|
"@types/node": "18.11.9",
|
||||||
"@types/react": "18.0.25",
|
"@types/react": "18.0.25",
|
||||||
"@typescript-eslint/eslint-plugin": "5.43.0",
|
|
||||||
"@typescript-eslint/parser": "5.43.0",
|
|
||||||
"autoprefixer": "10.4.13",
|
"autoprefixer": "10.4.13",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.27.0",
|
"eslint": "8.28.0",
|
||||||
"eslint-config-next": "13.0.3",
|
"eslint-config-custom": "workspace:*",
|
||||||
"eslint-plugin-react": "7.31.10",
|
|
||||||
"next-transpile-modules": "10.0.0",
|
"next-transpile-modules": "10.0.0",
|
||||||
"postcss": "8.4.19",
|
"postcss": "8.4.19",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.7.1",
|
||||||
"typescript": "4.8.4"
|
"typescript": "4.8.4",
|
||||||
|
"tsconfig": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,12 @@ const AboutPage = () => {
|
|||||||
textAlign="justify"
|
textAlign="justify"
|
||||||
>
|
>
|
||||||
<Flex w="full">
|
<Flex w="full">
|
||||||
<Heading as="h1">Typebot's story</Heading>
|
<Heading as="h1">Typebot's story</Heading>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
Typebot's team is composed of only me, Baptiste Arnaud, a Software
|
Typebot's team is composed of only me, Baptiste Arnaud, a
|
||||||
Engineer based in France.
|
Software Engineer based in France.
|
||||||
</Text>
|
</Text>
|
||||||
<Flex w="full" justify="center">
|
<Flex w="full" justify="center">
|
||||||
<Box as="figure" maxW="200px">
|
<Box as="figure" maxW="200px">
|
||||||
@ -35,13 +35,13 @@ const AboutPage = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
I'm passionate about great product UX and, during the first COVID
|
I'm passionate about great product UX and, during the first COVID
|
||||||
lockdown, I decided to create my own Typeform alternative.
|
lockdown, I decided to create my own Typeform alternative.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
Typebot was launched in July 2020. It is completely independent,
|
Typebot was launched in July 2020. It is completely independent,
|
||||||
self-funded, and bootstrapped. At the current stage, I'm not
|
self-funded, and bootstrapped. At the current stage, I'm not
|
||||||
interested in raising funds or taking investments.
|
interested in raising funds or taking investments.
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
@ -55,15 +55,16 @@ const AboutPage = () => {
|
|||||||
With Typebot, I want to create the best bot-building experience. My
|
With Typebot, I want to create the best bot-building experience. My
|
||||||
goal is to empower you as a user and help you build great user
|
goal is to empower you as a user and help you build great user
|
||||||
experiences. Also, privacy comes first. While using Typebot, you
|
experiences. Also, privacy comes first. While using Typebot, you
|
||||||
aren't tracked by some third-party analytics tool.
|
aren't tracked by some third-party analytics tool.
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
I'm working hard on making a living from Typebot with a simple
|
I'm working hard on making a living from Typebot with a simple
|
||||||
business model: <br />
|
business model: <br />
|
||||||
<br /> You can use the tool for free but your forms will contain a
|
<br /> You can use the tool for free but your forms will contain a
|
||||||
"Made with Typebot" small badge that potentially gets people to know
|
"Made with Typebot" small badge that potentially gets people
|
||||||
about the product. If you want to remove it and also have access to
|
to know about the product. If you want to remove it and also have
|
||||||
other advanced features, you have to subscribe for $39 per month.
|
access to other advanced features, you have to subscribe for $39 per
|
||||||
|
month.
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
If you have any questions, feel free to reach out to me at{' '}
|
If you have any questions, feel free to reach out to me at{' '}
|
||||||
|
@ -56,7 +56,7 @@ const Pricing = () => {
|
|||||||
<VStack>
|
<VStack>
|
||||||
<Heading fontSize="6xl">Plans fit for you</Heading>
|
<Heading fontSize="6xl">Plans fit for you</Heading>
|
||||||
<Text maxW="900px" fontSize="xl" textAlign="center">
|
<Text maxW="900px" fontSize="xl" textAlign="center">
|
||||||
Whether you're a{' '}
|
Whether you're a{' '}
|
||||||
<Text as="span" color="orange.200" fontWeight="bold">
|
<Text as="span" color="orange.200" fontWeight="bold">
|
||||||
solo business owner
|
solo business owner
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
@ -100,7 +100,7 @@ const Pricing = () => {
|
|||||||
<Text fontSize="lg">
|
<Text fontSize="lg">
|
||||||
Need custom limits? Specific features?{' '}
|
Need custom limits? Specific features?{' '}
|
||||||
<TextLink href={'https://typebot.io/enterprise-lead-form'}>
|
<TextLink href={'https://typebot.io/enterprise-lead-form'}>
|
||||||
Let's chat!
|
Let's chat!
|
||||||
</TextLink>
|
</TextLink>
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@ -155,9 +155,9 @@ const Faq = () => {
|
|||||||
<AccordionPanel pb={4}>
|
<AccordionPanel pb={4}>
|
||||||
You will receive an email notification once you reached 80% of this
|
You will receive an email notification once you reached 80% of this
|
||||||
limit. Then, once you reach 100%, your users will still be able to
|
limit. Then, once you reach 100%, your users will still be able to
|
||||||
chat with your bot but their uploads won't be stored anymore. You will
|
chat with your bot but their uploads won't be stored anymore. You
|
||||||
need to upgrade the limit or free up some space to continue collecting
|
will need to upgrade the limit or free up some space to continue
|
||||||
your users' files.
|
collecting your users' files.
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem>
|
<AccordionItem>
|
||||||
@ -189,7 +189,7 @@ const Faq = () => {
|
|||||||
<TextLink href="mailto:baptiste@typebot.io">
|
<TextLink href="mailto:baptiste@typebot.io">
|
||||||
shoot me an email
|
shoot me an email
|
||||||
</TextLink>{' '}
|
</TextLink>{' '}
|
||||||
and we'll figure things out 😀
|
and we'll figure things out 😀
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
@ -1,22 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "tsconfig/nextjs.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"incremental": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"composite": true
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
ignorePatterns: ['node_modules'],
|
root: true,
|
||||||
env: {
|
extends: ['custom'],
|
||||||
browser: true,
|
|
||||||
es6: true,
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:react/recommended',
|
|
||||||
'next/core-web-vitals',
|
|
||||||
],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
|
||||||
sourceType: 'module', // Allows for the use of imports
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true, // Allows for the parsing of JSX
|
|
||||||
},
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: ['react', '@typescript-eslint'],
|
|
||||||
rules: {
|
|
||||||
'react/no-unescaped-entities': [0],
|
|
||||||
'react/display-name': [0],
|
|
||||||
'no-restricted-imports': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
patterns: [
|
|
||||||
'*/src/*',
|
|
||||||
'src/*',
|
|
||||||
'*/src',
|
|
||||||
'@/features/*/*',
|
|
||||||
'!@/features/blocks/*',
|
|
||||||
'!@/features/*/api',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -39,21 +39,17 @@
|
|||||||
"@types/qs": "6.9.7",
|
"@types/qs": "6.9.7",
|
||||||
"@types/react": "18.0.25",
|
"@types/react": "18.0.25",
|
||||||
"@types/sanitize-html": "2.6.2",
|
"@types/sanitize-html": "2.6.2",
|
||||||
"@typescript-eslint/eslint-plugin": "5.43.0",
|
|
||||||
"@typescript-eslint/parser": "5.43.0",
|
|
||||||
"dotenv": "16.0.3",
|
"dotenv": "16.0.3",
|
||||||
"emails": "workspace:*",
|
"emails": "workspace:*",
|
||||||
"eslint": "8.27.0",
|
"eslint": "8.28.0",
|
||||||
"eslint-config-next": "13.0.3",
|
"eslint-config-custom": "workspace:*",
|
||||||
"eslint-plugin-react": "7.31.10",
|
|
||||||
"eslint-plugin-react-hooks": "4.6.0",
|
|
||||||
"google-auth-library": "8.7.0",
|
"google-auth-library": "8.7.0",
|
||||||
"models": "workspace:*",
|
"models": "workspace:*",
|
||||||
"next-transpile-modules": "10.0.0",
|
"next-transpile-modules": "10.0.0",
|
||||||
"node-fetch": "^3.3.0",
|
"node-fetch": "^3.3.0",
|
||||||
"papaparse": "5.3.2",
|
"papaparse": "5.3.2",
|
||||||
|
"tsconfig": "workspace:*",
|
||||||
"typescript": "4.8.4",
|
"typescript": "4.8.4",
|
||||||
"utils": "workspace:*",
|
"utils": "workspace:*"
|
||||||
"configs": "workspace:*"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { PlaywrightTestConfig } from '@playwright/test'
|
import { PlaywrightTestConfig } from '@playwright/test'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { playwrightBaseConfig } from 'configs/playwright'
|
import { playwrightBaseConfig } from 'utils/playwright/baseConfig'
|
||||||
|
|
||||||
const config: PlaywrightTestConfig = {
|
const config: PlaywrightTestConfig = {
|
||||||
...playwrightBaseConfig,
|
...playwrightBaseConfig,
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { TypebotViewer } from 'bot-engine'
|
import { TypebotViewer } from 'bot-engine'
|
||||||
import { Answer, PublicTypebot, Typebot, VariableWithValue } from 'models'
|
import {
|
||||||
|
Answer,
|
||||||
|
AnswerInput,
|
||||||
|
PublicTypebot,
|
||||||
|
Typebot,
|
||||||
|
VariableWithValue,
|
||||||
|
} from 'models'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { isDefined, isNotDefined } from 'utils'
|
import { isDefined, isNotDefined } from 'utils'
|
||||||
@ -106,7 +112,7 @@ export const TypebotPage = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleNewAnswer = async (
|
const handleNewAnswer = async (
|
||||||
answer: Answer & { uploadedFiles: boolean }
|
answer: AnswerInput & { uploadedFiles: boolean }
|
||||||
) => {
|
) => {
|
||||||
if (!resultId) return setError(new Error('Error: result was not created'))
|
if (!resultId) return setError(new Error('Error: result was not created'))
|
||||||
if (publishedTypebot.settings.general.isResultSavingEnabled !== false) {
|
if (publishedTypebot.settings.general.isResultSavingEnabled !== false) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Answer } from 'models'
|
import { Answer, AnswerInput } from 'models'
|
||||||
import { sendRequest } from 'utils'
|
import { sendRequest } from 'utils'
|
||||||
|
|
||||||
export const upsertAnswerQuery = async (
|
export const upsertAnswerQuery = async (
|
||||||
answer: Answer & { resultId: string } & { uploadedFiles?: boolean }
|
answer: AnswerInput & { resultId: string } & { uploadedFiles?: boolean }
|
||||||
) =>
|
) =>
|
||||||
sendRequest<Answer>({
|
sendRequest<Answer>({
|
||||||
url: `/api/typebots/t/results/r/answers`,
|
url: `/api/typebots/t/results/r/answers`,
|
||||||
|
@ -151,22 +151,30 @@ test('Should correctly parse metadata', async ({ page }) => {
|
|||||||
).toBe(customMetadata.title)
|
).toBe(customMetadata.title)
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate(
|
await page.evaluate(
|
||||||
() => (document.querySelector('meta[name="description"]') as any).content
|
() =>
|
||||||
|
(document.querySelector('meta[name="description"]') as HTMLMetaElement)
|
||||||
|
.content
|
||||||
)
|
)
|
||||||
).toBe(customMetadata.description)
|
).toBe(customMetadata.description)
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate(
|
await page.evaluate(
|
||||||
() => (document.querySelector('meta[property="og:image"]') as any).content
|
() =>
|
||||||
|
(document.querySelector('meta[property="og:image"]') as HTMLMetaElement)
|
||||||
|
.content
|
||||||
)
|
)
|
||||||
).toBe(customMetadata.imageUrl)
|
).toBe(customMetadata.imageUrl)
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate(() =>
|
await page.evaluate(() =>
|
||||||
(document.querySelector('link[rel="icon"]') as any).getAttribute('href')
|
(
|
||||||
|
document.querySelector('link[rel="icon"]') as HTMLLinkElement
|
||||||
|
).getAttribute('href')
|
||||||
)
|
)
|
||||||
).toBe(customMetadata.favIconUrl)
|
).toBe(customMetadata.favIconUrl)
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate(
|
await page.evaluate(
|
||||||
() => (document.querySelector('meta[name="author"]') as any).content
|
() =>
|
||||||
|
(document.querySelector('meta[name="author"]') as HTMLMetaElement)
|
||||||
|
.content
|
||||||
)
|
)
|
||||||
).toBe('John Doe')
|
).toBe('John Doe')
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import NextErrorComponent from 'next/error'
|
import NextErrorComponent, { ErrorProps } from 'next/error'
|
||||||
|
|
||||||
import * as Sentry from '@sentry/nextjs'
|
import * as Sentry from '@sentry/nextjs'
|
||||||
import { NextPageContext } from 'next'
|
import { NextPageContext } from 'next'
|
||||||
@ -24,14 +24,14 @@ const MyError = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
MyError.getInitialProps = async (context: NextPageContext) => {
|
MyError.getInitialProps = async (context: NextPageContext) => {
|
||||||
const errorInitialProps = await NextErrorComponent.getInitialProps(context)
|
const errorInitialProps = (await NextErrorComponent.getInitialProps(
|
||||||
|
context
|
||||||
|
)) as ErrorProps & { hasGetInitialPropsRun: boolean }
|
||||||
|
|
||||||
const { res, err, asPath } = context
|
const { res, err, asPath } = context
|
||||||
|
|
||||||
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when
|
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when
|
||||||
// getInitialProps has run
|
// getInitialProps has run
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
errorInitialProps.hasGetInitialPropsRun = true
|
errorInitialProps.hasGetInitialPropsRun = true
|
||||||
|
|
||||||
// Returning early because we don't want to log 404 errors to Sentry.
|
// Returning early because we don't want to log 404 errors to Sentry.
|
||||||
|
@ -84,18 +84,19 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
}`,
|
}`,
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
if (typeof err === 'object' && err && 'raw' in err) {
|
||||||
const error = err as any
|
const error = (err as { raw: Stripe.StripeRawError }).raw
|
||||||
return 'raw' in error
|
res.status(error.statusCode ?? 500).send({
|
||||||
? res.status(error.raw.statusCode).send({
|
error: {
|
||||||
error: {
|
name: `${error.type} ${error.param}`,
|
||||||
name: `${error.raw.type} ${error.raw.param}`,
|
message: error.message,
|
||||||
message: error.raw.message,
|
},
|
||||||
},
|
})
|
||||||
})
|
} else {
|
||||||
: res.status(500).send({
|
res.status(500).send({
|
||||||
error,
|
err,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return methodNotAllowed(res)
|
return methodNotAllowed(res)
|
||||||
|
@ -25,6 +25,7 @@ import { saveErrorLog, saveSuccessLog } from '@/features/logs/api'
|
|||||||
import { parseSampleResult } from '@/features/webhook/api'
|
import { parseSampleResult } from '@/features/webhook/api'
|
||||||
|
|
||||||
const cors = initMiddleware(Cors())
|
const cors = initMiddleware(Cors())
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
await cors(req, res)
|
await cors(req, res)
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
@ -34,11 +35,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
const { resultValues, variables } = (
|
const { resultValues, variables } = (
|
||||||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||||
) as {
|
) as {
|
||||||
resultValues:
|
resultValues: ResultValues | undefined
|
||||||
| (Omit<ResultValues, 'createdAt'> & {
|
|
||||||
createdAt: string
|
|
||||||
})
|
|
||||||
| undefined
|
|
||||||
variables: Variable[]
|
variables: Variable[]
|
||||||
}
|
}
|
||||||
const typebot = (await prisma.typebot.findUnique({
|
const typebot = (await prisma.typebot.findUnique({
|
||||||
@ -87,9 +84,7 @@ export const executeWebhook =
|
|||||||
webhook: Webhook,
|
webhook: Webhook,
|
||||||
variables: Variable[],
|
variables: Variable[],
|
||||||
groupId: string,
|
groupId: string,
|
||||||
resultValues?: Omit<ResultValues, 'createdAt'> & {
|
resultValues?: ResultValues,
|
||||||
createdAt: string
|
|
||||||
},
|
|
||||||
resultId?: string
|
resultId?: string
|
||||||
): Promise<WebhookResponse> => {
|
): Promise<WebhookResponse> => {
|
||||||
if (!webhook.url || !webhook.method)
|
if (!webhook.url || !webhook.method)
|
||||||
@ -199,9 +194,7 @@ const getBodyContent =
|
|||||||
groupId,
|
groupId,
|
||||||
}: {
|
}: {
|
||||||
body?: string | null
|
body?: string | null
|
||||||
resultValues?: Omit<ResultValues, 'createdAt'> & {
|
resultValues?: ResultValues
|
||||||
createdAt: string
|
|
||||||
}
|
|
||||||
groupId: string
|
groupId: string
|
||||||
}): Promise<string | undefined> => {
|
}): Promise<string | undefined> => {
|
||||||
if (!body) return
|
if (!body) return
|
||||||
@ -228,7 +221,6 @@ const convertKeyValueTableToObject = (
|
|||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const safeJsonParse = (json: string): { data: any; isJson: boolean } => {
|
const safeJsonParse = (json: string): { data: any; isJson: boolean } => {
|
||||||
try {
|
try {
|
||||||
return { data: JSON.parse(json), isJson: true }
|
return { data: JSON.parse(json), isJson: true }
|
||||||
|
@ -16,6 +16,7 @@ import Cors from 'cors'
|
|||||||
import { executeWebhook } from '../../executeWebhook'
|
import { executeWebhook } from '../../executeWebhook'
|
||||||
|
|
||||||
const cors = initMiddleware(Cors())
|
const cors = initMiddleware(Cors())
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
await cors(req, res)
|
await cors(req, res)
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
@ -26,11 +27,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
const { resultValues, variables } = (
|
const { resultValues, variables } = (
|
||||||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||||
) as {
|
) as {
|
||||||
resultValues:
|
resultValues: ResultValues
|
||||||
| (Omit<ResultValues, 'createdAt'> & {
|
|
||||||
createdAt: string
|
|
||||||
})
|
|
||||||
| undefined
|
|
||||||
variables: Variable[]
|
variables: Variable[]
|
||||||
}
|
}
|
||||||
const typebot = (await prisma.typebot.findUnique({
|
const typebot = (await prisma.typebot.findUnique({
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import { WorkspaceRole } from 'db'
|
|
||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { InputBlockType, PublicTypebot } from 'models'
|
import { InputBlockType, PublicTypebot } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { badRequest, generatePresignedUrl, methodNotAllowed } from 'utils/api'
|
import { badRequest, generatePresignedUrl, methodNotAllowed } from 'utils/api'
|
||||||
import { byId, getStorageLimit, isDefined, env } from 'utils'
|
import { byId } from 'utils'
|
||||||
import {
|
|
||||||
sendAlmostReachedStorageLimitEmail,
|
|
||||||
sendReachedStorageLimitEmail,
|
|
||||||
} from 'emails'
|
|
||||||
|
|
||||||
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
|
// const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
|
||||||
|
|
||||||
const handler = async (
|
const handler = async (
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
@ -57,112 +52,111 @@ const handler = async (
|
|||||||
return methodNotAllowed(res)
|
return methodNotAllowed(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// const checkStorageLimit = async (typebotId: string) => {
|
||||||
const checkStorageLimit = async (typebotId: string) => {
|
// const typebot = await prisma.typebot.findFirst({
|
||||||
const typebot = await prisma.typebot.findFirst({
|
// where: { id: typebotId },
|
||||||
where: { id: typebotId },
|
// include: {
|
||||||
include: {
|
// workspace: {
|
||||||
workspace: {
|
// select: {
|
||||||
select: {
|
// id: true,
|
||||||
id: true,
|
// additionalStorageIndex: true,
|
||||||
additionalStorageIndex: true,
|
// plan: true,
|
||||||
plan: true,
|
// storageLimitFirstEmailSentAt: true,
|
||||||
storageLimitFirstEmailSentAt: true,
|
// storageLimitSecondEmailSentAt: true,
|
||||||
storageLimitSecondEmailSentAt: true,
|
// customStorageLimit: true,
|
||||||
customStorageLimit: true,
|
// },
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
},
|
// })
|
||||||
})
|
// if (!typebot?.workspace) throw new Error('Workspace not found')
|
||||||
if (!typebot?.workspace) throw new Error('Workspace not found')
|
// const { workspace } = typebot
|
||||||
const { workspace } = typebot
|
// const {
|
||||||
const {
|
// _sum: { storageUsed: totalStorageUsed },
|
||||||
_sum: { storageUsed: totalStorageUsed },
|
// } = await prisma.answer.aggregate({
|
||||||
} = await prisma.answer.aggregate({
|
// where: {
|
||||||
where: {
|
// storageUsed: { gt: 0 },
|
||||||
storageUsed: { gt: 0 },
|
// result: {
|
||||||
result: {
|
// typebot: {
|
||||||
typebot: {
|
// workspace: {
|
||||||
workspace: {
|
// id: typebot?.workspaceId,
|
||||||
id: typebot?.workspaceId,
|
// },
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
},
|
// _sum: { storageUsed: true },
|
||||||
_sum: { storageUsed: true },
|
// })
|
||||||
})
|
// if (!totalStorageUsed) return false
|
||||||
if (!totalStorageUsed) return false
|
// const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null
|
||||||
const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null
|
// const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null
|
||||||
const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null
|
// const storageLimit = getStorageLimit(typebot.workspace)
|
||||||
const storageLimit = getStorageLimit(typebot.workspace)
|
// const storageLimitBytes = storageLimit * 1024 * 1024 * 1024
|
||||||
const storageLimitBytes = storageLimit * 1024 * 1024 * 1024
|
// if (
|
||||||
if (
|
// totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
||||||
totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
// !hasSentFirstEmail &&
|
||||||
!hasSentFirstEmail &&
|
// env('E2E_TEST') !== 'true'
|
||||||
env('E2E_TEST') !== 'true'
|
// )
|
||||||
)
|
// await sendAlmostReachStorageLimitNotification({
|
||||||
await sendAlmostReachStorageLimitNotification({
|
// workspaceId: workspace.id,
|
||||||
workspaceId: workspace.id,
|
// storageLimit,
|
||||||
storageLimit,
|
// })
|
||||||
})
|
// if (
|
||||||
if (
|
// totalStorageUsed >= storageLimitBytes &&
|
||||||
totalStorageUsed >= storageLimitBytes &&
|
// !hasSentSecondEmail &&
|
||||||
!hasSentSecondEmail &&
|
// env('E2E_TEST') !== 'true'
|
||||||
env('E2E_TEST') !== 'true'
|
// )
|
||||||
)
|
// await sendReachStorageLimitNotification({
|
||||||
await sendReachStorageLimitNotification({
|
// workspaceId: workspace.id,
|
||||||
workspaceId: workspace.id,
|
// storageLimit,
|
||||||
storageLimit,
|
// })
|
||||||
})
|
// return totalStorageUsed >= storageLimitBytes
|
||||||
return totalStorageUsed >= storageLimitBytes
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
const sendAlmostReachStorageLimitNotification = async ({
|
// const sendAlmostReachStorageLimitNotification = async ({
|
||||||
workspaceId,
|
// workspaceId,
|
||||||
storageLimit,
|
// storageLimit,
|
||||||
}: {
|
// }: {
|
||||||
workspaceId: string
|
// workspaceId: string
|
||||||
storageLimit: number
|
// storageLimit: number
|
||||||
}) => {
|
// }) => {
|
||||||
const members = await prisma.memberInWorkspace.findMany({
|
// const members = await prisma.memberInWorkspace.findMany({
|
||||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
// where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||||
include: { user: { select: { email: true } } },
|
// include: { user: { select: { email: true } } },
|
||||||
})
|
// })
|
||||||
|
|
||||||
await sendAlmostReachedStorageLimitEmail({
|
// await sendAlmostReachedStorageLimitEmail({
|
||||||
to: members.map((member) => member.user.email).filter(isDefined),
|
// to: members.map((member) => member.user.email).filter(isDefined),
|
||||||
storageLimit,
|
// storageLimit,
|
||||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||||
})
|
// })
|
||||||
|
|
||||||
await prisma.workspace.update({
|
// await prisma.workspace.update({
|
||||||
where: { id: workspaceId },
|
// where: { id: workspaceId },
|
||||||
data: { storageLimitFirstEmailSentAt: new Date() },
|
// data: { storageLimitFirstEmailSentAt: new Date() },
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
const sendReachStorageLimitNotification = async ({
|
// const sendReachStorageLimitNotification = async ({
|
||||||
workspaceId,
|
// workspaceId,
|
||||||
storageLimit,
|
// storageLimit,
|
||||||
}: {
|
// }: {
|
||||||
workspaceId: string
|
// workspaceId: string
|
||||||
storageLimit: number
|
// storageLimit: number
|
||||||
}) => {
|
// }) => {
|
||||||
const members = await prisma.memberInWorkspace.findMany({
|
// const members = await prisma.memberInWorkspace.findMany({
|
||||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
// where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||||
include: { user: { select: { email: true } } },
|
// include: { user: { select: { email: true } } },
|
||||||
})
|
// })
|
||||||
|
|
||||||
await sendReachedStorageLimitEmail({
|
// await sendReachedStorageLimitEmail({
|
||||||
to: members.map((member) => member.user.email).filter(isDefined),
|
// to: members.map((member) => member.user.email).filter(isDefined),
|
||||||
storageLimit,
|
// storageLimit,
|
||||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||||
})
|
// })
|
||||||
|
|
||||||
await prisma.workspace.update({
|
// await prisma.workspace.update({
|
||||||
where: { id: workspaceId },
|
// where: { id: workspaceId },
|
||||||
data: { storageLimitSecondEmailSentAt: new Date() },
|
// data: { storageLimitSecondEmailSentAt: new Date() },
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
export default withSentry(handler)
|
export default withSentry(handler)
|
||||||
|
@ -31,7 +31,7 @@ const defaultTransportOptions = {
|
|||||||
|
|
||||||
const defaultFrom = {
|
const defaultFrom = {
|
||||||
name: process.env.SMTP_FROM?.split(' <')[0].replace(/"/g, ''),
|
name: process.env.SMTP_FROM?.split(' <')[0].replace(/"/g, ''),
|
||||||
email: process.env.SMTP_FROM?.match(/\<(.*)\>/)?.pop(),
|
email: process.env.SMTP_FROM?.match(/<(.*)>/)?.pop(),
|
||||||
}
|
}
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
@ -54,9 +54,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
} = (
|
} = (
|
||||||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||||
) as SendEmailOptions & {
|
) as SendEmailOptions & {
|
||||||
resultValues: Omit<ResultValues, 'createdAt'> & {
|
resultValues: ResultValues
|
||||||
createdAt: string
|
|
||||||
}
|
|
||||||
fileUrls?: string
|
fileUrls?: string
|
||||||
}
|
}
|
||||||
const { name: replyToName } = parseEmailRecipient(replyTo)
|
const { name: replyToName } = parseEmailRecipient(replyTo)
|
||||||
@ -166,9 +164,7 @@ const getEmailBody = async ({
|
|||||||
resultValues,
|
resultValues,
|
||||||
}: {
|
}: {
|
||||||
typebotId: string
|
typebotId: string
|
||||||
resultValues: Omit<ResultValues, 'createdAt'> & {
|
resultValues: ResultValues
|
||||||
createdAt: string
|
|
||||||
}
|
|
||||||
} & Pick<SendEmailOptions, 'isCustomBody' | 'isBodyCode' | 'body'>): Promise<
|
} & Pick<SendEmailOptions, 'isCustomBody' | 'isBodyCode' | 'body'>): Promise<
|
||||||
{ html?: string; text?: string } | undefined
|
{ html?: string; text?: string } | undefined
|
||||||
> => {
|
> => {
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
import { authenticateUser } from '@/features/auth/api'
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { Workspace, WorkspaceRole } from 'db'
|
|
||||||
import {
|
|
||||||
sendAlmostReachedChatsLimitEmail,
|
|
||||||
sendReachedChatsLimitEmail,
|
|
||||||
} from 'emails'
|
|
||||||
import { ResultWithAnswers } from 'models'
|
import { ResultWithAnswers } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { env, getChatsLimit, isDefined } from 'utils'
|
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
|
|
||||||
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
|
// const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
@ -63,106 +57,105 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
methodNotAllowed(res)
|
methodNotAllowed(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// const checkChatsUsage = async (
|
||||||
const checkChatsUsage = async (
|
// workspace: Pick<
|
||||||
workspace: Pick<
|
// Workspace,
|
||||||
Workspace,
|
// | 'id'
|
||||||
| 'id'
|
// | 'plan'
|
||||||
| 'plan'
|
// | 'additionalChatsIndex'
|
||||||
| 'additionalChatsIndex'
|
// | 'chatsLimitFirstEmailSentAt'
|
||||||
| 'chatsLimitFirstEmailSentAt'
|
// | 'chatsLimitSecondEmailSentAt'
|
||||||
| 'chatsLimitSecondEmailSentAt'
|
// | 'customChatsLimit'
|
||||||
| 'customChatsLimit'
|
// >
|
||||||
>
|
// ) => {
|
||||||
) => {
|
// const chatsLimit = getChatsLimit(workspace)
|
||||||
const chatsLimit = getChatsLimit(workspace)
|
// if (chatsLimit === -1) return
|
||||||
if (chatsLimit === -1) return
|
// const now = new Date()
|
||||||
const now = new Date()
|
// const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||||
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
// const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||||
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
// const firstDayOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
|
||||||
const firstDayOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
|
// const chatsCount = await prisma.result.count({
|
||||||
const chatsCount = await prisma.result.count({
|
// where: {
|
||||||
where: {
|
// typebot: { workspaceId: workspace.id },
|
||||||
typebot: { workspaceId: workspace.id },
|
// hasStarted: true,
|
||||||
hasStarted: true,
|
// createdAt: { gte: firstDayOfMonth, lte: lastDayOfMonth },
|
||||||
createdAt: { gte: firstDayOfMonth, lte: lastDayOfMonth },
|
// },
|
||||||
},
|
// })
|
||||||
})
|
// const hasSentFirstEmail =
|
||||||
const hasSentFirstEmail =
|
// workspace.chatsLimitFirstEmailSentAt !== null &&
|
||||||
workspace.chatsLimitFirstEmailSentAt !== null &&
|
// workspace.chatsLimitFirstEmailSentAt < firstDayOfNextMonth &&
|
||||||
workspace.chatsLimitFirstEmailSentAt < firstDayOfNextMonth &&
|
// workspace.chatsLimitFirstEmailSentAt > firstDayOfMonth
|
||||||
workspace.chatsLimitFirstEmailSentAt > firstDayOfMonth
|
// const hasSentSecondEmail =
|
||||||
const hasSentSecondEmail =
|
// workspace.chatsLimitSecondEmailSentAt !== null &&
|
||||||
workspace.chatsLimitSecondEmailSentAt !== null &&
|
// workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth &&
|
||||||
workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth &&
|
// workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth
|
||||||
workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth
|
// if (
|
||||||
if (
|
// chatsCount >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
||||||
chatsCount >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
// !hasSentFirstEmail &&
|
||||||
!hasSentFirstEmail &&
|
// env('E2E_TEST') !== 'true'
|
||||||
env('E2E_TEST') !== 'true'
|
// )
|
||||||
)
|
// await sendAlmostReachChatsLimitNotification({
|
||||||
await sendAlmostReachChatsLimitNotification({
|
// workspaceId: workspace.id,
|
||||||
workspaceId: workspace.id,
|
// chatsLimit,
|
||||||
chatsLimit,
|
// })
|
||||||
})
|
// if (
|
||||||
if (
|
// chatsCount >= chatsLimit &&
|
||||||
chatsCount >= chatsLimit &&
|
// !hasSentSecondEmail &&
|
||||||
!hasSentSecondEmail &&
|
// env('E2E_TEST') !== 'true'
|
||||||
env('E2E_TEST') !== 'true'
|
// )
|
||||||
)
|
// await sendReachedAlertNotification({
|
||||||
await sendReachedAlertNotification({
|
// workspaceId: workspace.id,
|
||||||
workspaceId: workspace.id,
|
// chatsLimit,
|
||||||
chatsLimit,
|
// })
|
||||||
})
|
// return chatsCount >= chatsLimit
|
||||||
return chatsCount >= chatsLimit
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
const sendAlmostReachChatsLimitNotification = async ({
|
// const sendAlmostReachChatsLimitNotification = async ({
|
||||||
workspaceId,
|
// workspaceId,
|
||||||
chatsLimit,
|
// chatsLimit,
|
||||||
}: {
|
// }: {
|
||||||
workspaceId: string
|
// workspaceId: string
|
||||||
chatsLimit: number
|
// chatsLimit: number
|
||||||
}) => {
|
// }) => {
|
||||||
const members = await prisma.memberInWorkspace.findMany({
|
// const members = await prisma.memberInWorkspace.findMany({
|
||||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
// where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||||
include: { user: { select: { email: true } } },
|
// include: { user: { select: { email: true } } },
|
||||||
})
|
// })
|
||||||
|
|
||||||
await sendAlmostReachedChatsLimitEmail({
|
// await sendAlmostReachedChatsLimitEmail({
|
||||||
to: members.map((member) => member.user.email).filter(isDefined),
|
// to: members.map((member) => member.user.email).filter(isDefined),
|
||||||
chatsLimit,
|
// chatsLimit,
|
||||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||||
})
|
// })
|
||||||
|
|
||||||
await prisma.workspace.update({
|
// await prisma.workspace.update({
|
||||||
where: { id: workspaceId },
|
// where: { id: workspaceId },
|
||||||
data: { chatsLimitFirstEmailSentAt: new Date() },
|
// data: { chatsLimitFirstEmailSentAt: new Date() },
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
const sendReachedAlertNotification = async ({
|
// const sendReachedAlertNotification = async ({
|
||||||
workspaceId,
|
// workspaceId,
|
||||||
chatsLimit,
|
// chatsLimit,
|
||||||
}: {
|
// }: {
|
||||||
workspaceId: string
|
// workspaceId: string
|
||||||
chatsLimit: number
|
// chatsLimit: number
|
||||||
}) => {
|
// }) => {
|
||||||
const members = await prisma.memberInWorkspace.findMany({
|
// const members = await prisma.memberInWorkspace.findMany({
|
||||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
// where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||||
include: { user: { select: { email: true } } },
|
// include: { user: { select: { email: true } } },
|
||||||
})
|
// })
|
||||||
|
|
||||||
await sendReachedChatsLimitEmail({
|
// await sendReachedChatsLimitEmail({
|
||||||
to: members.map((member) => member.user.email).filter(isDefined),
|
// to: members.map((member) => member.user.email).filter(isDefined),
|
||||||
chatsLimit,
|
// chatsLimit,
|
||||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||||
})
|
// })
|
||||||
|
|
||||||
await prisma.workspace.update({
|
// await prisma.workspace.update({
|
||||||
where: { id: workspaceId },
|
// where: { id: workspaceId },
|
||||||
data: { chatsLimitSecondEmailSentAt: new Date() },
|
// data: { chatsLimitSecondEmailSentAt: new Date() },
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
export default handler
|
export default handler
|
||||||
|
@ -1,26 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "tsconfig/nextjs.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"incremental": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
},
|
}
|
||||||
"composite": true,
|
|
||||||
"downlevelIteration": true
|
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,13 @@
|
|||||||
"test:viewer": "cd apps/viewer && pnpm test",
|
"test:viewer": "cd apps/viewer && pnpm test",
|
||||||
"db:migrate": "cd packages/db && pnpm migration:deploy",
|
"db:migrate": "cd packages/db && pnpm migration:deploy",
|
||||||
"build:ci": "turbo run build --filter=builder... --filter=viewer... && ENVSH_ENV=./apps/builder/.env.docker ENVSH_OUTPUT=./apps/builder/public/__env.js bash env.sh && ENVSH_ENV=./apps/viewer/.env.docker ENVSH_OUTPUT=./apps/viewer/public/__env.js bash env.sh",
|
"build:ci": "turbo run build --filter=builder... --filter=viewer... && ENVSH_ENV=./apps/builder/.env.docker ENVSH_OUTPUT=./apps/builder/public/__env.js bash env.sh && ENVSH_ENV=./apps/viewer/.env.docker ENVSH_OUTPUT=./apps/viewer/public/__env.js bash env.sh",
|
||||||
"generate-change-log": "pnpx gitmoji-changelog"
|
"generate-change-log": "pnpx gitmoji-changelog",
|
||||||
|
"lint": "turbo run lint",
|
||||||
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cz-emoji": "1.3.2-canary.2",
|
"cz-emoji": "1.3.2-canary.2",
|
||||||
|
"husky": "^8.0.2",
|
||||||
"turbo": "1.6.3"
|
"turbo": "1.6.3"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
@ -31,5 +34,5 @@
|
|||||||
"path": "node_modules/cz-emoji"
|
"path": "node_modules/cz-emoji"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@7.16.1"
|
"packageManager": "pnpm@7.17.0"
|
||||||
}
|
}
|
||||||
|
@ -1,48 +1,8 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
ignorePatterns: ['node_modules'],
|
root: true,
|
||||||
env: {
|
extends: ['custom'],
|
||||||
browser: true,
|
|
||||||
es6: true,
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:react/recommended',
|
|
||||||
'next/core-web-vitals',
|
|
||||||
'prettier',
|
|
||||||
],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
|
||||||
sourceType: 'module', // Allows for the use of imports
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true, // Allows for the parsing of JSX
|
|
||||||
},
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: ['prettier', 'react', '@typescript-eslint'],
|
|
||||||
ignorePatterns: 'dist',
|
|
||||||
rules: {
|
rules: {
|
||||||
'react/no-unescaped-entities': [0],
|
'@next/next/no-img-element': 'off',
|
||||||
'prettier/prettier': 'error',
|
'@next/next/no-html-link-for-pages': 'off',
|
||||||
'react/display-name': [0],
|
|
||||||
'@next/next/no-img-element': [0],
|
|
||||||
'no-restricted-imports': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
patterns: [
|
|
||||||
'*/src/*',
|
|
||||||
'src/*',
|
|
||||||
'*/src',
|
|
||||||
'@/features/*/*',
|
|
||||||
'@/index',
|
|
||||||
'!@/features/blocks/*',
|
|
||||||
'!@/features/*/api',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
"module": "dist/index.mjs",
|
"module": "dist/index.mjs",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup",
|
"build": "pnpm tsc --noEmit && tsup",
|
||||||
"dev": "tsup --watch",
|
"dev": "tsup --watch",
|
||||||
"lint": "eslint --fix -c ./.eslintrc.js \"./src/**/*.ts*\""
|
"lint": "eslint \"src/**/*.ts*\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@stripe/react-stripe-js": "1.15.0",
|
"@stripe/react-stripe-js": "1.15.0",
|
||||||
@ -28,16 +28,11 @@
|
|||||||
"@types/react-phone-number-input": "3.0.14",
|
"@types/react-phone-number-input": "3.0.14",
|
||||||
"@types/react-scroll": "1.8.5",
|
"@types/react-scroll": "1.8.5",
|
||||||
"@types/react-transition-group": "4.4.5",
|
"@types/react-transition-group": "4.4.5",
|
||||||
"@typescript-eslint/eslint-plugin": "5.43.0",
|
|
||||||
"@typescript-eslint/parser": "5.43.0",
|
|
||||||
"autoprefixer": "10.4.13",
|
"autoprefixer": "10.4.13",
|
||||||
"baptistearno-tsup": "^0.1.0",
|
"tsup": "6.5.0",
|
||||||
"db": "workspace:*",
|
"db": "workspace:*",
|
||||||
"eslint": "8.27.0",
|
"eslint": "8.28.0",
|
||||||
"eslint-config-next": "13.0.3",
|
"eslint-config-custom": "workspace:*",
|
||||||
"eslint-config-prettier": "8.5.0",
|
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
|
||||||
"eslint-plugin-react": "7.31.10",
|
|
||||||
"models": "workspace:*",
|
"models": "workspace:*",
|
||||||
"postcss": "8.4.19",
|
"postcss": "8.4.19",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.7.1",
|
||||||
@ -46,7 +41,8 @@
|
|||||||
"tailwindcss": "3.2.4",
|
"tailwindcss": "3.2.4",
|
||||||
"typescript": "4.8.4",
|
"typescript": "4.8.4",
|
||||||
"utils": "workspace:*",
|
"utils": "workspace:*",
|
||||||
"typebot-js": "workspace:*"
|
"typebot-js": "workspace:*",
|
||||||
|
"tsconfig": "workspace:*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"db": "workspace:*",
|
"db": "workspace:*",
|
||||||
|
@ -13,59 +13,60 @@ import { ResizeObserver } from 'resize-observer'
|
|||||||
|
|
||||||
type Props = { hostAvatarSrc?: string; keepShowing: boolean }
|
type Props = { hostAvatarSrc?: string; keepShowing: boolean }
|
||||||
|
|
||||||
export const AvatarSideContainer = forwardRef(
|
export const AvatarSideContainer = forwardRef(function AvatarSideContainer(
|
||||||
({ hostAvatarSrc, keepShowing }: Props, ref: ForwardedRef<unknown>) => {
|
{ hostAvatarSrc, keepShowing }: Props,
|
||||||
const { document } = useFrame()
|
ref: ForwardedRef<unknown>
|
||||||
const [show, setShow] = useState(false)
|
) {
|
||||||
const [avatarTopOffset, setAvatarTopOffset] = useState(0)
|
const { document } = useFrame()
|
||||||
|
const [show, setShow] = useState(false)
|
||||||
|
const [avatarTopOffset, setAvatarTopOffset] = useState(0)
|
||||||
|
|
||||||
const refreshTopOffset = () => {
|
const refreshTopOffset = () => {
|
||||||
if (!scrollingSideGroupRef.current || !avatarContainer.current) return
|
if (!scrollingSideGroupRef.current || !avatarContainer.current) return
|
||||||
const { height } = scrollingSideGroupRef.current.getBoundingClientRect()
|
const { height } = scrollingSideGroupRef.current.getBoundingClientRect()
|
||||||
const { height: avatarHeight } =
|
const { height: avatarHeight } =
|
||||||
avatarContainer.current.getBoundingClientRect()
|
avatarContainer.current.getBoundingClientRect()
|
||||||
setAvatarTopOffset(height - avatarHeight)
|
setAvatarTopOffset(height - avatarHeight)
|
||||||
}
|
|
||||||
const scrollingSideGroupRef = useRef<HTMLDivElement>(null)
|
|
||||||
const avatarContainer = useRef<HTMLDivElement>(null)
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
refreshTopOffset,
|
|
||||||
}))
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!document) return
|
|
||||||
setShow(true)
|
|
||||||
const resizeObserver = new ResizeObserver(refreshTopOffset)
|
|
||||||
resizeObserver.observe(document.body)
|
|
||||||
return () => {
|
|
||||||
resizeObserver.disconnect()
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="flex w-6 xs:w-10 mr-2 mb-2 flex-shrink-0 items-center relative typebot-avatar-container "
|
|
||||||
ref={scrollingSideGroupRef}
|
|
||||||
>
|
|
||||||
<CSSTransition
|
|
||||||
classNames="bubble"
|
|
||||||
timeout={500}
|
|
||||||
in={show && keepShowing}
|
|
||||||
unmountOnExit
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="absolute w-6 xs:w-10 h-6 xs:h-10 mb-4 xs:mb-2 flex items-center top-0"
|
|
||||||
ref={avatarContainer}
|
|
||||||
style={{
|
|
||||||
top: `${avatarTopOffset}px`,
|
|
||||||
transition: 'top 350ms ease-out, opacity 500ms',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Avatar avatarSrc={hostAvatarSrc} />
|
|
||||||
</div>
|
|
||||||
</CSSTransition>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
const scrollingSideGroupRef = useRef<HTMLDivElement>(null)
|
||||||
|
const avatarContainer = useRef<HTMLDivElement>(null)
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
refreshTopOffset,
|
||||||
|
}))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!document) return
|
||||||
|
setShow(true)
|
||||||
|
const resizeObserver = new ResizeObserver(refreshTopOffset)
|
||||||
|
resizeObserver.observe(document.body)
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex w-6 xs:w-10 mr-2 mb-2 flex-shrink-0 items-center relative typebot-avatar-container "
|
||||||
|
ref={scrollingSideGroupRef}
|
||||||
|
>
|
||||||
|
<CSSTransition
|
||||||
|
classNames="bubble"
|
||||||
|
timeout={500}
|
||||||
|
in={show && keepShowing}
|
||||||
|
unmountOnExit
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute w-6 xs:w-10 h-6 xs:h-10 mb-4 xs:mb-2 flex items-center top-0"
|
||||||
|
ref={avatarContainer}
|
||||||
|
style={{
|
||||||
|
top: `${avatarTopOffset}px`,
|
||||||
|
transition: 'top 350ms ease-out, opacity 500ms',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar avatarSrc={hostAvatarSrc} />
|
||||||
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@ -53,7 +53,7 @@ export const InputChatBlock = ({
|
|||||||
blockId: block.id,
|
blockId: block.id,
|
||||||
groupId: block.groupId,
|
groupId: block.groupId,
|
||||||
content: value,
|
content: value,
|
||||||
variableId: variableId ?? null,
|
variableId,
|
||||||
uploadedFiles: block.type === InputBlockType.FILE,
|
uploadedFiles: block.type === InputBlockType.FILE,
|
||||||
})
|
})
|
||||||
if (!isEditting) onTransitionEnd({ label, value, itemId }, isRetry)
|
if (!isEditting) onTransitionEnd({ label, value, itemId }, isRetry)
|
||||||
|
@ -246,7 +246,6 @@ const ChatChunks = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [isSkipped, setIsSkipped] = useState(false)
|
const [isSkipped, setIsSkipped] = useState(false)
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const avatarSideContainerRef = useRef<any>()
|
const avatarSideContainerRef = useRef<any>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -8,6 +8,7 @@ import { ConversationContainer } from './ConversationContainer'
|
|||||||
import { AnswersProvider } from '../providers/AnswersProvider'
|
import { AnswersProvider } from '../providers/AnswersProvider'
|
||||||
import {
|
import {
|
||||||
Answer,
|
Answer,
|
||||||
|
AnswerInput,
|
||||||
BackgroundType,
|
BackgroundType,
|
||||||
Edge,
|
Edge,
|
||||||
PublicTypebot,
|
PublicTypebot,
|
||||||
@ -27,7 +28,9 @@ export type TypebotViewerProps = {
|
|||||||
startGroupId?: string
|
startGroupId?: string
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
onNewGroupVisible?: (edge: Edge) => void
|
onNewGroupVisible?: (edge: Edge) => void
|
||||||
onNewAnswer?: (answer: Answer & { uploadedFiles: boolean }) => Promise<void>
|
onNewAnswer?: (
|
||||||
|
answer: AnswerInput & { uploadedFiles: boolean }
|
||||||
|
) => Promise<void>
|
||||||
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
||||||
onCompleted?: () => void
|
onCompleted?: () => void
|
||||||
onVariablesUpdated?: (variables: VariableWithValue[]) => void
|
onVariablesUpdated?: (variables: VariableWithValue[]) => void
|
||||||
@ -58,7 +61,7 @@ export const TypebotViewer = ({
|
|||||||
const handleNewGroupVisible = (edge: Edge) =>
|
const handleNewGroupVisible = (edge: Edge) =>
|
||||||
onNewGroupVisible && onNewGroupVisible(edge)
|
onNewGroupVisible && onNewGroupVisible(edge)
|
||||||
|
|
||||||
const handleNewAnswer = (answer: Answer & { uploadedFiles: boolean }) =>
|
const handleNewAnswer = (answer: AnswerInput & { uploadedFiles: boolean }) =>
|
||||||
onNewAnswer && onNewAnswer(answer)
|
onNewAnswer && onNewAnswer(answer)
|
||||||
|
|
||||||
const handleNewLog = (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) =>
|
const handleNewLog = (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) =>
|
||||||
|
@ -5,21 +5,19 @@ type ShortTextInputProps = {
|
|||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'>
|
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'>
|
||||||
|
|
||||||
export const ShortTextInput = React.forwardRef(
|
export const ShortTextInput = React.forwardRef(function ShortTextInput(
|
||||||
(
|
{ onChange, ...props }: ShortTextInputProps,
|
||||||
{ onChange, ...props }: ShortTextInputProps,
|
ref: React.ForwardedRef<HTMLInputElement>
|
||||||
ref: React.ForwardedRef<HTMLInputElement>
|
) {
|
||||||
) => {
|
return (
|
||||||
return (
|
<input
|
||||||
<input
|
ref={ref}
|
||||||
ref={ref}
|
className="focus:outline-none bg-transparent px-4 py-4 flex-1 w-full text-input"
|
||||||
className="focus:outline-none bg-transparent px-4 py-4 flex-1 w-full text-input"
|
type="text"
|
||||||
type="text"
|
style={{ fontSize: '16px' }}
|
||||||
style={{ fontSize: '16px' }}
|
autoFocus={!isMobile}
|
||||||
autoFocus={!isMobile}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
onChange={(e) => onChange(e.target.value)}
|
{...props}
|
||||||
{...props}
|
/>
|
||||||
/>
|
)
|
||||||
)
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@ -5,11 +5,11 @@ type TextareaProps = {
|
|||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
} & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange'>
|
} & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange'>
|
||||||
|
|
||||||
export const Textarea = React.forwardRef(
|
export const Textarea = React.forwardRef(function Textarea(
|
||||||
(
|
{ onChange, ...props }: TextareaProps,
|
||||||
{ onChange, ...props }: TextareaProps,
|
ref: React.ForwardedRef<HTMLTextAreaElement>
|
||||||
ref: React.ForwardedRef<HTMLTextAreaElement>
|
) {
|
||||||
) => (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="focus:outline-none bg-transparent px-4 py-4 flex-1 w-full text-input"
|
className="focus:outline-none bg-transparent px-4 py-4 flex-1 w-full text-input"
|
||||||
@ -22,4 +22,4 @@ export const Textarea = React.forwardRef(
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
|
@ -9,6 +9,7 @@ import { parseVariables } from '@/features/variables'
|
|||||||
import { useChat } from '@/providers/ChatProvider'
|
import { useChat } from '@/providers/ChatProvider'
|
||||||
import { useTypebot } from '@/providers/TypebotProvider'
|
import { useTypebot } from '@/providers/TypebotProvider'
|
||||||
import { createPaymentIntentQuery } from '../../queries/createPaymentIntentQuery'
|
import { createPaymentIntentQuery } from '../../queries/createPaymentIntentQuery'
|
||||||
|
import { Stripe } from '@stripe/stripe-js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
options: PaymentInputOptions
|
options: PaymentInputOptions
|
||||||
@ -23,8 +24,7 @@ export const StripePaymentForm = ({ options, onSuccess }: Props) => {
|
|||||||
onNewLog,
|
onNewLog,
|
||||||
} = useTypebot()
|
} = useTypebot()
|
||||||
const { window: frameWindow, document: frameDocument } = useFrame()
|
const { window: frameWindow, document: frameDocument } = useFrame()
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const [stripe, setStripe] = useState<Stripe | null>(null)
|
||||||
const [stripe, setStripe] = useState<any>(null)
|
|
||||||
const [clientSecret, setClientSecret] = useState('')
|
const [clientSecret, setClientSecret] = useState('')
|
||||||
const [amountLabel, setAmountLabel] = useState('')
|
const [amountLabel, setAmountLabel] = useState('')
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ export const PhoneInput = ({
|
|||||||
hasGuestAvatar,
|
hasGuestAvatar,
|
||||||
}: PhoneInputProps) => {
|
}: PhoneInputProps) => {
|
||||||
const [inputValue, setInputValue] = useState(defaultValue ?? '')
|
const [inputValue, setInputValue] = useState(defaultValue ?? '')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const inputRef = useRef<any>(null)
|
const inputRef = useRef<any>(null)
|
||||||
|
|
||||||
const handleChange = (inputValue: Value | undefined) =>
|
const handleChange = (inputValue: Value | undefined) =>
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
import { GoogleAnalyticsOptions } from 'models'
|
import { GoogleAnalyticsOptions } from 'models'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
declare const gtag: (
|
||||||
declare const gtag: any
|
type: string,
|
||||||
|
action: string | undefined,
|
||||||
|
options: {
|
||||||
|
event_category: string | undefined
|
||||||
|
event_label: string | undefined
|
||||||
|
value: number | undefined
|
||||||
|
}
|
||||||
|
) => void
|
||||||
|
|
||||||
const initGoogleAnalytics = (id: string): Promise<void> =>
|
const initGoogleAnalytics = (id: string): Promise<void> =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import { safeStringify } from '@/features/variables'
|
import { safeStringify } from '@/features/variables'
|
||||||
import {
|
import {
|
||||||
Answer,
|
Answer,
|
||||||
|
AnswerInput,
|
||||||
ResultValues,
|
ResultValues,
|
||||||
VariableWithUnknowValue,
|
VariableWithUnknowValue,
|
||||||
VariableWithValue,
|
VariableWithValue,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import React, { createContext, ReactNode, useContext, useState } from 'react'
|
import { createContext, ReactNode, useContext, useState } from 'react'
|
||||||
|
|
||||||
const answersContext = createContext<{
|
const answersContext = createContext<{
|
||||||
resultId?: string
|
resultId?: string
|
||||||
resultValues: ResultValues
|
resultValues: ResultValues
|
||||||
addAnswer: (
|
addAnswer: (
|
||||||
answer: Answer & { uploadedFiles: boolean }
|
answer: AnswerInput & { uploadedFiles: boolean }
|
||||||
) => Promise<void> | undefined
|
) => Promise<void> | undefined
|
||||||
updateVariables: (variables: VariableWithUnknowValue[]) => void
|
updateVariables: (variables: VariableWithUnknowValue[]) => void
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ export const AnswersProvider = ({
|
|||||||
}: {
|
}: {
|
||||||
resultId?: string
|
resultId?: string
|
||||||
onNewAnswer: (
|
onNewAnswer: (
|
||||||
answer: Answer & { uploadedFiles: boolean }
|
answer: AnswerInput & { uploadedFiles: boolean }
|
||||||
) => Promise<void> | undefined
|
) => Promise<void> | undefined
|
||||||
onVariablesUpdated?: (variables: VariableWithValue[]) => void
|
onVariablesUpdated?: (variables: VariableWithValue[]) => void
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@ -34,10 +34,10 @@ export const AnswersProvider = ({
|
|||||||
const [resultValues, setResultValues] = useState<ResultValues>({
|
const [resultValues, setResultValues] = useState<ResultValues>({
|
||||||
answers: [],
|
answers: [],
|
||||||
variables: [],
|
variables: [],
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const addAnswer = (answer: Answer & { uploadedFiles: boolean }) => {
|
const addAnswer = (answer: AnswerInput & { uploadedFiles: boolean }) => {
|
||||||
setResultValues((resultValues) => ({
|
setResultValues((resultValues) => ({
|
||||||
...resultValues,
|
...resultValues,
|
||||||
answers: [...resultValues.answers, answer],
|
answers: [...resultValues.answers, answer],
|
||||||
|
@ -2,7 +2,6 @@ import React, { createContext, ReactNode, useContext } from 'react'
|
|||||||
|
|
||||||
const chatContext = createContext<{
|
const chatContext = createContext<{
|
||||||
scroll: () => void
|
scroll: () => void
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
|
@ -39,7 +39,6 @@ const typebotContext = createContext<{
|
|||||||
edgeId: string
|
edgeId: string
|
||||||
}) => void
|
}) => void
|
||||||
onNewLog: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
onNewLog: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
|
@ -1,24 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "tsconfig/react-library.json",
|
||||||
|
"include": ["src/**/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ES2017", "DOM"],
|
|
||||||
"target": "es5",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"jsx": "react",
|
|
||||||
"module": "ESNext",
|
|
||||||
"declaration": true,
|
|
||||||
"declarationDir": "types",
|
|
||||||
"sourceMap": true,
|
|
||||||
"outDir": "dist",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"emitDeclarationOnly": true,
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
},
|
}
|
||||||
"downlevelIteration": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from 'baptistearno-tsup'
|
import { defineConfig } from 'tsup'
|
||||||
|
|
||||||
export default defineConfig((options) => ({
|
export default defineConfig((options) => ({
|
||||||
entry: ['src/index.ts'],
|
entry: ['src/index.ts'],
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "configs",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"license": "AGPL-3.0-or-later",
|
|
||||||
"private": true,
|
|
||||||
"devDependencies": {
|
|
||||||
"@playwright/test": "1.27.1",
|
|
||||||
"@types/node": "18.11.9",
|
|
||||||
"dotenv": "16.0.3",
|
|
||||||
"utils": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { playwrightBaseConfig } from './baseConfig'
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"esModuleInterop": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,9 +19,9 @@
|
|||||||
"@prisma/client": "4.6.1"
|
"@prisma/client": "4.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prisma": "4.6.1",
|
|
||||||
"typescript": "4.8.4",
|
|
||||||
"dotenv-cli": "6.0.0",
|
"dotenv-cli": "6.0.0",
|
||||||
"tsconfig": "workspace:*"
|
"prisma": "4.6.1",
|
||||||
|
"tsconfig": "workspace:*",
|
||||||
|
"typescript": "4.8.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
packages/emails/.eslintrc.js
Normal file
4
packages/emails/.eslintrc.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['custom'],
|
||||||
|
}
|
@ -2,12 +2,13 @@
|
|||||||
"name": "emails",
|
"name": "emails",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./index.ts",
|
"main": "./src/index.ts",
|
||||||
"types": "./index.ts",
|
"types": "./src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preview": "concurrently \"pnpm run watch\" \"sleep 5 && pnpm run serve\" -n \"watch,serve\" -c \"bgBlue.bold,bgMagenta.bold\"",
|
"preview": "concurrently \"pnpm run watch\" \"sleep 5 && pnpm run serve\" -n \"watch,serve\" -c \"bgBlue.bold,bgMagenta.bold\"",
|
||||||
"watch": "tsx watch ./preview.tsx --clear-screen=false",
|
"watch": "tsx watch ./preview.tsx --clear-screen=false",
|
||||||
"serve": "http-server dist -a localhost -p 3223 -o"
|
"serve": "http-server dist -a localhost -p 3223 -o",
|
||||||
|
"lint": "eslint \"src/**/*.ts*\""
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Baptiste Arnaud",
|
"author": "Baptiste Arnaud",
|
||||||
@ -22,7 +23,10 @@
|
|||||||
"nodemailer": "6.8.0",
|
"nodemailer": "6.8.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"tsx": "3.12.1",
|
"tsx": "3.12.1",
|
||||||
"utils": "workspace:*"
|
"utils": "workspace:*",
|
||||||
|
"eslint": "8.28.0",
|
||||||
|
"eslint-config-custom": "workspace:*",
|
||||||
|
"tsconfig": "workspace:*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@faire/mjml-react": "2.1.4",
|
"@faire/mjml-react": "2.1.4",
|
||||||
|
@ -42,15 +42,15 @@ export const AlmostReachedChatsLimitEmail = ({
|
|||||||
</MjmlSection>
|
</MjmlSection>
|
||||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||||
<MjmlColumn>
|
<MjmlColumn>
|
||||||
<Text>Your bots are chatting a lot. That's amazing. 💙</Text>
|
<Text>Your bots are chatting a lot. That's amazing. 💙</Text>
|
||||||
<Text>
|
<Text>
|
||||||
This means you've almost reached your monthly chats limit. You
|
This means you've almost reached your monthly chats limit.
|
||||||
currently reached 80% of {readableChatsLimit} chats.
|
You currently reached 80% of {readableChatsLimit} chats.
|
||||||
</Text>
|
</Text>
|
||||||
<Text>This limit will be reset on {readableResetDate}.</Text>
|
<Text>This limit will be reset on {readableResetDate}.</Text>
|
||||||
<Text fontWeight={800}>
|
<Text fontWeight={800}>
|
||||||
Your bots won't start the chat if you reach the limit before this
|
Your bots won't start the chat if you reach the limit before
|
||||||
date❗
|
this date❗
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
If you need more monthly responses, you will need to upgrade your
|
If you need more monthly responses, you will need to upgrade your
|
@ -33,17 +33,18 @@ export const AlmostReachedStorageLimitEmail = ({
|
|||||||
</MjmlSection>
|
</MjmlSection>
|
||||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||||
<MjmlColumn>
|
<MjmlColumn>
|
||||||
<Text>Your bots are working a lot. That's amazing. 🤖</Text>
|
<Text>Your bots are working a lot. That's amazing. 🤖</Text>
|
||||||
<Text>
|
<Text>
|
||||||
This means you've almost reached your storage limit. You currently
|
This means you've almost reached your storage limit. You
|
||||||
reached 80% of your {readableStorageLimit} storage limit.
|
currently reached 80% of your {readableStorageLimit} storage
|
||||||
|
limit.
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontWeight={800}>
|
<Text fontWeight={800}>
|
||||||
Your bots won't collect new files once you reach the limit❗
|
Your bots won't collect new files once you reach the limit❗
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
To make sure it won't happen, you need to upgrade your plan or
|
To make sure it won't happen, you need to upgrade your plan
|
||||||
delete existing results to free up space.
|
or delete existing results to free up space.
|
||||||
</Text>
|
</Text>
|
||||||
<MjmlSpacer height="24px" />
|
<MjmlSpacer height="24px" />
|
||||||
<Button link={url}>Upgrade workspace</Button>
|
<Button link={url}>Upgrade workspace</Button>
|
@ -42,7 +42,7 @@ export const GuestInvitationEmail = ({
|
|||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
From now on you will see this typebot in your dashboard under his
|
From now on you will see this typebot in your dashboard under his
|
||||||
workspace "{workspaceName}" 👍
|
workspace "{workspaceName}" 👍
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
Make sure to log in as <i>{guestEmail}</i>.
|
Make sure to log in as <i>{guestEmail}</i>.
|
@ -43,15 +43,15 @@ export const ReachedChatsLimitEmail = ({
|
|||||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||||
<MjmlColumn>
|
<MjmlColumn>
|
||||||
<Text>
|
<Text>
|
||||||
It just happened, you've reached your monthly {readableChatsLimit}{' '}
|
It just happened, you've reached your monthly{' '}
|
||||||
chats limit 😮
|
{readableChatsLimit} chats limit 😮
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontWeight={800}>
|
<Text fontWeight={800}>
|
||||||
It means your bots are closed until {readableResetDate}❗
|
It means your bots are closed until {readableResetDate}❗
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
If you'd like to continue chatting with your users this month,
|
If you'd like to continue chatting with your users this
|
||||||
then you need to upgrade your plan. 🚀
|
month, then you need to upgrade your plan. 🚀
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<MjmlSpacer height="24px" />
|
<MjmlSpacer height="24px" />
|
@ -34,14 +34,14 @@ export const ReachedStorageLimitEmail = ({
|
|||||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||||
<MjmlColumn>
|
<MjmlColumn>
|
||||||
<Text>
|
<Text>
|
||||||
It just happened, you've reached your {readableStorageLimit}{' '}
|
It just happened, you've reached your {readableStorageLimit}{' '}
|
||||||
storage limit 😮
|
storage limit 😮
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontWeight={800}>
|
<Text fontWeight={800}>
|
||||||
It means your bots won't collect new files from your users❗
|
It means your bots won't collect new files from your users❗
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
If you'd like to continue collecting files, then you need to
|
If you'd like to continue collecting files, then you need to
|
||||||
upgrade your plan or remove existing results to free up space. 🚀
|
upgrade your plan or remove existing results to free up space. 🚀
|
||||||
</Text>
|
</Text>
|
||||||
|
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"extends": "tsconfig/react-library.json",
|
||||||
"jsx": "react",
|
"include": ["src/**/*"],
|
||||||
"esModuleInterop": true
|
"exclude": ["dist", "node_modules"]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user