♻️ 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 = {
|
||||
ignorePatterns: ['node_modules'],
|
||||
env: {
|
||||
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',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
root: true,
|
||||
extends: ['custom'],
|
||||
}
|
||||
|
@ -106,20 +106,16 @@
|
||||
"@types/qs": "6.9.7",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.43.0",
|
||||
"@typescript-eslint/parser": "5.43.0",
|
||||
"configs": "workspace:*",
|
||||
"db": "workspace:*",
|
||||
"dotenv": "16.0.3",
|
||||
"eslint": "8.27.0",
|
||||
"eslint-config-next": "13.0.3",
|
||||
"eslint-plugin-react": "7.31.10",
|
||||
"models": "workspace:*",
|
||||
"next-transpile-modules": "10.0.0",
|
||||
"superjson": "^1.11.0",
|
||||
"tsconfig": "workspace:*",
|
||||
"typescript": "4.8.4",
|
||||
"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 path from 'path'
|
||||
import { playwrightBaseConfig } from 'configs/playwright'
|
||||
import { playwrightBaseConfig } from 'utils/playwright/baseConfig'
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
...playwrightBaseConfig,
|
||||
|
@ -36,7 +36,6 @@ export const SearchableDropdown = ({
|
||||
const { onOpen, onClose, isOpen } = useDisclosure()
|
||||
const [inputValue, setInputValue] = useState(selectedItem ?? '')
|
||||
const debounced = useDebouncedCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onValueChange ? onValueChange : () => {},
|
||||
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
||||
)
|
||||
|
@ -209,7 +209,7 @@ export const VariableSearchInput = ({
|
||||
leftIcon={<PlusIcon />}
|
||||
bgColor={keyboardFocusIndex === 0 ? 'gray.200' : 'transparent'}
|
||||
>
|
||||
Create "{inputValue}"
|
||||
Create "{inputValue}"
|
||||
</Button>
|
||||
)}
|
||||
{filteredItems.length > 0 && (
|
||||
|
@ -24,7 +24,6 @@ const userContext = createContext<{
|
||||
currentWorkspaceId?: string
|
||||
updateUser: (newUser: Partial<User>) => void
|
||||
saveUser: (newUser?: Partial<User>) => Promise<void>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
|
@ -24,7 +24,7 @@ export const SignInPage = ({ type }: Props) => {
|
||||
</Heading>
|
||||
{type === 'signin' ? (
|
||||
<Text>
|
||||
Don't have an account?{' '}
|
||||
Don't have an account?{' '}
|
||||
<TextLink href="/register">Sign up for free</TextLink>
|
||||
</Text>
|
||||
) : (
|
||||
|
@ -52,8 +52,8 @@ export const UsageContent = ({ workspace }: Props) => {
|
||||
p="3"
|
||||
label={
|
||||
<Text>
|
||||
Your typebots are popular! You will soon reach your plan's
|
||||
chats limit. 🚀
|
||||
Your typebots are popular! You will soon reach your
|
||||
plan's chats limit. 🚀
|
||||
<br />
|
||||
<br />
|
||||
Make sure to <strong>update your plan</strong> to increase
|
||||
@ -111,8 +111,8 @@ export const UsageContent = ({ workspace }: Props) => {
|
||||
p="3"
|
||||
label={
|
||||
<Text>
|
||||
Your typebots are popular! You will soon reach your plan's
|
||||
storage limit. 🚀
|
||||
Your typebots are popular! You will soon reach your
|
||||
plan's storage limit. 🚀
|
||||
<br />
|
||||
<br />
|
||||
Make sure to <strong>update your plan</strong> in order to
|
||||
|
@ -90,7 +90,7 @@ export const ChangePlanForm = () => {
|
||||
<Text color="gray.500">
|
||||
Need custom limits? Specific features?{' '}
|
||||
<TextLink href={'https://typebot.io/enterprise-lead-form'} isExternal>
|
||||
Let's chat!
|
||||
Let's chat!
|
||||
</TextLink>
|
||||
</Text>
|
||||
</Stack>
|
||||
|
@ -162,14 +162,20 @@ const ActionOptions = ({
|
||||
onOptionsChange({ ...options, cellsToExtract } as GoogleSheetsOptions)
|
||||
|
||||
const UpdatingCellItem = useMemo(
|
||||
() => (props: TableListItemProps<Cell>) =>
|
||||
<CellWithValueStack {...props} columns={sheet?.columns ?? []} />,
|
||||
() =>
|
||||
function Component(props: TableListItemProps<Cell>) {
|
||||
return <CellWithValueStack {...props} columns={sheet?.columns ?? []} />
|
||||
},
|
||||
[sheet?.columns]
|
||||
)
|
||||
|
||||
const ExtractingCellItem = useMemo(
|
||||
() => (props: TableListItemProps<ExtractingCell>) =>
|
||||
<CellWithVariableIdStack {...props} columns={sheet?.columns ?? []} />,
|
||||
() =>
|
||||
function Component(props: TableListItemProps<ExtractingCell>) {
|
||||
return (
|
||||
<CellWithVariableIdStack {...props} columns={sheet?.columns ?? []} />
|
||||
)
|
||||
},
|
||||
[sheet?.columns]
|
||||
)
|
||||
|
||||
|
@ -143,8 +143,10 @@ export const WebhookSettings = ({
|
||||
}
|
||||
|
||||
const ResponseMappingInputs = useMemo(
|
||||
() => (props: TableListItemProps<ResponseVariableMapping>) =>
|
||||
<DataVariableInputs {...props} dataItems={responseKeys} />,
|
||||
() =>
|
||||
function Component(props: TableListItemProps<ResponseVariableMapping>) {
|
||||
return <DataVariableInputs {...props} dataItems={responseKeys} />
|
||||
},
|
||||
[responseKeys]
|
||||
)
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const getDeepKeys = (obj: any): string[] => {
|
||||
let keys: string[] = []
|
||||
for (const key in obj) {
|
||||
|
@ -36,7 +36,7 @@ export const AnnoucementModal = ({ isOpen, onClose }: Props) => {
|
||||
<Modal isOpen={isOpen} onClose={handleCloseClick} size="2xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>What's new in Typebot 2.0?</ModalHeader>
|
||||
<ModalHeader>What's new in Typebot 2.0?</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody as={Stack} spacing="6" pb="10">
|
||||
<Text>Typebo 2.0 has been launched February the 15th 🎉.</Text>
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import { TypebotViewer } from 'bot-engine'
|
||||
import { useUser } from '@/features/account'
|
||||
import { Answer, Typebot } from 'models'
|
||||
import { AnswerInput, Typebot } from 'models'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { getViewerUrl, sendRequest } from 'utils'
|
||||
import confetti from 'canvas-confetti'
|
||||
@ -79,7 +79,7 @@ export const OnboardingModal = ({ totalTypebots }: Props) => {
|
||||
setTypebot(data as Typebot)
|
||||
}
|
||||
|
||||
const handleNewAnswer = async (answer: Answer) => {
|
||||
const handleNewAnswer = async (answer: AnswerInput) => {
|
||||
const isName = answer.variableId === 'cl126f4hf000i2e6d8zvzc3t1'
|
||||
const isCompany = answer.variableId === 'cl126jqww000w2e6dq9yv4ifq'
|
||||
const isCategories = answer.variableId === 'cl126mo3t001b2e6dvyi16bkd'
|
||||
|
@ -11,7 +11,7 @@ enum ActionType {
|
||||
Flush = 'FLUSH',
|
||||
}
|
||||
|
||||
export interface Actions<T> {
|
||||
export interface Actions<T extends { updatedAt: string } | undefined> {
|
||||
set: (
|
||||
newPresent: T | ((current: T) => T),
|
||||
options?: { updateDate: boolean }
|
||||
@ -24,13 +24,13 @@ export interface Actions<T> {
|
||||
presentRef: React.MutableRefObject<T>
|
||||
}
|
||||
|
||||
interface Action<T> {
|
||||
interface Action<T extends { updatedAt: string } | undefined> {
|
||||
type: ActionType
|
||||
newPresent?: T
|
||||
updateDate?: boolean
|
||||
}
|
||||
|
||||
export interface State<T> {
|
||||
export interface State<T extends { updatedAt: string } | undefined> {
|
||||
past: T[]
|
||||
present: T
|
||||
future: T[]
|
||||
@ -42,7 +42,10 @@ const initialState = {
|
||||
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
|
||||
|
||||
switch (action.type) {
|
||||
@ -98,8 +101,6 @@ const reducer = <T>(state: State<T>, action: Action<T>) => {
|
||||
past: [...past, present].filter(isDefined),
|
||||
present: {
|
||||
...newPresent,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
updatedAt: updateDate ? new Date() : newPresent.updatedAt,
|
||||
},
|
||||
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, {
|
||||
...initialState,
|
||||
present: initialPresent,
|
||||
}) as [State<T>, React.Dispatch<Action<T>>]
|
||||
})
|
||||
const presentRef = useRef<T>(initialPresent)
|
||||
|
||||
const canUndo = state.past.length !== 0
|
||||
@ -136,7 +139,7 @@ const useUndo = <T>(initialPresent: T): [State<T>, Actions<T>] => {
|
||||
const set = useCallback(
|
||||
(newPresent: T | ((current: T) => T), options = { updateDate: true }) => {
|
||||
const updatedTypebot =
|
||||
'id' in newPresent
|
||||
newPresent && 'id' in newPresent
|
||||
? newPresent
|
||||
: (newPresent as (current: T) => T)(presentRef.current)
|
||||
presentRef.current = updatedTypebot
|
||||
@ -153,7 +156,10 @@ const useUndo = <T>(initialPresent: T): [State<T>, Actions<T>] => {
|
||||
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
|
||||
|
@ -16,7 +16,6 @@ const editorContext = createContext<{
|
||||
setRightPanel: Dispatch<SetStateAction<RightPanel | undefined>>
|
||||
startPreviewAtGroup: string | undefined
|
||||
setStartPreviewAtGroup: Dispatch<SetStateAction<string | undefined>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
|
@ -88,7 +88,6 @@ const typebotContext = createContext<
|
||||
ItemsActions &
|
||||
VariablesActions &
|
||||
EdgesActions
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
>({})
|
||||
|
||||
|
@ -14,7 +14,6 @@ const typebotDndContext = createContext<{
|
||||
setDraggedTypebot: Dispatch<SetStateAction<TypebotInDashboard | undefined>>
|
||||
mouseOverFolderId?: string | null
|
||||
setMouseOverFolderId: Dispatch<SetStateAction<string | undefined | null>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
}>({})
|
||||
|
||||
|
@ -47,12 +47,11 @@ export const GroupNode = ({ group, groupIndex }: Props) => {
|
||||
)
|
||||
}
|
||||
|
||||
const DraggableGroupNode = memo(
|
||||
({
|
||||
const NonMemoizedDraggableGroupNode = ({
|
||||
group,
|
||||
groupIndex,
|
||||
onGroupDrag,
|
||||
}: Props & { onGroupDrag: (newCoord: Coordinates) => void }) => {
|
||||
}: Props & { onGroupDrag: (newCoord: Coordinates) => void }) => {
|
||||
const {
|
||||
connectingIds,
|
||||
setConnectingIds,
|
||||
@ -131,8 +130,7 @@ const DraggableGroupNode = memo(
|
||||
const handleMouseLeave = () => {
|
||||
if (isReadOnly) return
|
||||
setMouseOverGroup(undefined)
|
||||
if (connectingIds)
|
||||
setConnectingIds({ ...connectingIds, target: undefined })
|
||||
if (connectingIds) setConnectingIds({ ...connectingIds, target: undefined })
|
||||
}
|
||||
|
||||
const startPreviewAtThisGroup = () => {
|
||||
@ -239,5 +237,6 @@ const DraggableGroupNode = memo(
|
||||
)}
|
||||
</ContextMenu>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const DraggableGroupNode = memo(NonMemoizedDraggableGroupNode)
|
||||
|
@ -27,7 +27,6 @@ const graphDndContext = createContext<{
|
||||
setMouseOverGroup: Dispatch<SetStateAction<NodeInfo | undefined>>
|
||||
mouseOverBlock?: NodeInfo
|
||||
setMouseOverBlock: Dispatch<SetStateAction<NodeInfo | undefined>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
|
@ -74,7 +74,6 @@ const graphContext = createContext<{
|
||||
isReadOnly: boolean
|
||||
focusedGroupId?: string
|
||||
setFocusedGroupId: Dispatch<SetStateAction<string | undefined>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({
|
||||
graphPosition: graphPositionDefaultValue,
|
||||
|
@ -12,7 +12,6 @@ import { GroupsCoordinates, Coordinates } from './GraphProvider'
|
||||
const groupsCoordinatesContext = createContext<{
|
||||
groupsCoordinates: GroupsCoordinates
|
||||
updateGroupCoordinates: (groupId: string, newCoord: Coordinates) => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
|
@ -17,7 +17,6 @@ const resultsContext = createContext<{
|
||||
onDeleteResults: (totalResultsDeleted: number) => void
|
||||
fetchNextPage: () => void
|
||||
refetchResults: () => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import { ToolIcon, EyeIcon, EyeOffIcon, GripIcon } from '@/components/icons'
|
||||
import { ResultHeaderCell } from 'models'
|
||||
import React, { forwardRef, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { isNotDefined } from 'utils'
|
||||
import {
|
||||
DndContext,
|
||||
@ -128,7 +128,7 @@ export const ColumnSettingsButton = ({
|
||||
</SortableContext>
|
||||
<Portal>
|
||||
<DragOverlay dropAnimation={{ duration: 0 }}>
|
||||
{draggingColumnId ? <SortableColumnOverlay /> : null}
|
||||
{draggingColumnId ? <Flex /> : null}
|
||||
</DragOverlay>
|
||||
</Portal>
|
||||
</DndContext>
|
||||
@ -210,9 +210,3 @@ const SortableColumns = ({
|
||||
</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,
|
||||
Button,
|
||||
chakra,
|
||||
Checkbox,
|
||||
Flex,
|
||||
HStack,
|
||||
Stack,
|
||||
@ -26,6 +25,7 @@ import { Row } from './Row'
|
||||
import { HeaderRow } from './HeaderRow'
|
||||
import { CellValueType, TableData } from '../../types'
|
||||
import { HeaderIcon } from '../../utils'
|
||||
import { IndeterminateCheckbox } from './IndeterminateCheckbox'
|
||||
|
||||
type ResultsTableProps = {
|
||||
resultHeader: ResultHeaderCell[]
|
||||
@ -238,23 +238,3 @@ export const ResultsTable = ({
|
||||
</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>
|
||||
deleteCurrentWorkspace: () => Promise<void>
|
||||
refreshWorkspace: (expectedUpdates: Partial<Workspace>) => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
|
@ -88,7 +88,7 @@ const DeleteWorkspaceButton = ({
|
||||
message={
|
||||
<Text>
|
||||
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>
|
||||
}
|
||||
confirmButtonLabel="Delete"
|
||||
|
@ -7,8 +7,7 @@ export const useAutoSave = <T>(
|
||||
item,
|
||||
debounceTimeout,
|
||||
}: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
handler: () => Promise<any>
|
||||
handler: () => Promise<void>
|
||||
item?: T
|
||||
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 })
|
||||
|
@ -1,4 +1,4 @@
|
||||
import NextErrorComponent from 'next/error'
|
||||
import NextErrorComponent, { ErrorProps } from 'next/error'
|
||||
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import { NextPageContext } from 'next'
|
||||
@ -24,14 +24,14 @@ const MyError = ({
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when
|
||||
// getInitialProps has run
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
errorInitialProps.hasGetInitialPropsRun = true
|
||||
|
||||
// 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")
|
||||
const response = await drive({
|
||||
version: 'v3',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
auth: auth.client,
|
||||
}).files.list({
|
||||
q: "mimeType='application/vnd.google-apps.spreadsheet'",
|
||||
|
@ -128,5 +128,4 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export default withSentry(cors(webhookHandler as any))
|
||||
|
@ -76,9 +76,9 @@ export const readFile = (file: File): Promise<string> => {
|
||||
}
|
||||
|
||||
export const timeSince = (date: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
const seconds = Math.floor((new Date() - new Date(date)) / 1000)
|
||||
const seconds = Math.floor(
|
||||
(new Date().getTime() - new Date(date).getTime()) / 1000
|
||||
)
|
||||
|
||||
let interval = seconds / 31536000
|
||||
|
||||
|
@ -6,6 +6,5 @@
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
|
@ -1,29 +1,4 @@
|
||||
module.exports = {
|
||||
ignorePatterns: ['node_modules'],
|
||||
env: {
|
||||
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],
|
||||
},
|
||||
root: true,
|
||||
extends: ['custom'],
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ export const EasyEmbed = () => {
|
||||
>
|
||||
Embedding your typebot in your applications is a walk in the park.
|
||||
Typebot gives you several step-by-step platform-specific
|
||||
instructions. Your typebot will always feel "native".
|
||||
instructions. Your typebot will always feel "native".
|
||||
</Text>
|
||||
<Flex data-aos="fade">
|
||||
<Button
|
||||
|
@ -67,8 +67,8 @@ export const RealTimeResults = () => {
|
||||
data-aos="fade"
|
||||
>
|
||||
One of the main advantage of a chat application is that you collect
|
||||
the user's responses on each question.{' '}
|
||||
<strong>You won't loose any valuable data.</strong>
|
||||
the user's responses on each question.{' '}
|
||||
<strong>You won't loose any valuable data.</strong>
|
||||
</Text>
|
||||
<Flex>
|
||||
<Button
|
||||
|
@ -10,7 +10,7 @@ export const Testimonials = () => {
|
||||
<Flex as="section" justify="center">
|
||||
<VStack spacing={12} pt={'52'} px="4">
|
||||
<Heading textAlign={'center'} data-aos="fade">
|
||||
They've tried, they never looked back. 💙
|
||||
They've tried, they never looked back. 💙
|
||||
</Heading>
|
||||
<Stack
|
||||
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({
|
||||
fonts,
|
||||
components,
|
||||
|
@ -5,6 +5,7 @@
|
||||
"dev": "ENVSH_ENV=.env.local bash ../../env.sh next dev -p 3002",
|
||||
"start": "next start",
|
||||
"build": "next build",
|
||||
"lint": "next lint",
|
||||
"analyze": "cross-env ANALYZE=true next build"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -30,16 +31,14 @@
|
||||
"@types/aos": "3.0.4",
|
||||
"@types/node": "18.11.9",
|
||||
"@types/react": "18.0.25",
|
||||
"@typescript-eslint/eslint-plugin": "5.43.0",
|
||||
"@typescript-eslint/parser": "5.43.0",
|
||||
"autoprefixer": "10.4.13",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.27.0",
|
||||
"eslint-config-next": "13.0.3",
|
||||
"eslint-plugin-react": "7.31.10",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"next-transpile-modules": "10.0.0",
|
||||
"postcss": "8.4.19",
|
||||
"prettier": "2.7.1",
|
||||
"typescript": "4.8.4"
|
||||
"typescript": "4.8.4",
|
||||
"tsconfig": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
@ -21,12 +21,12 @@ const AboutPage = () => {
|
||||
textAlign="justify"
|
||||
>
|
||||
<Flex w="full">
|
||||
<Heading as="h1">Typebot's story</Heading>
|
||||
<Heading as="h1">Typebot's story</Heading>
|
||||
</Flex>
|
||||
|
||||
<Text>
|
||||
Typebot's team is composed of only me, Baptiste Arnaud, a Software
|
||||
Engineer based in France.
|
||||
Typebot's team is composed of only me, Baptiste Arnaud, a
|
||||
Software Engineer based in France.
|
||||
</Text>
|
||||
<Flex w="full" justify="center">
|
||||
<Box as="figure" maxW="200px">
|
||||
@ -35,13 +35,13 @@ const AboutPage = () => {
|
||||
</Flex>
|
||||
|
||||
<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.
|
||||
</Text>
|
||||
|
||||
<Text>
|
||||
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.
|
||||
</Text>
|
||||
<Text>
|
||||
@ -55,15 +55,16 @@ const AboutPage = () => {
|
||||
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
|
||||
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>
|
||||
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 />
|
||||
<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
|
||||
about the product. If you want to remove it and also have access to
|
||||
other advanced features, you have to subscribe for $39 per month.
|
||||
"Made with Typebot" small badge that potentially gets people
|
||||
to know about the product. If you want to remove it and also have
|
||||
access to other advanced features, you have to subscribe for $39 per
|
||||
month.
|
||||
</Text>
|
||||
<Text>
|
||||
If you have any questions, feel free to reach out to me at{' '}
|
||||
|
@ -56,7 +56,7 @@ const Pricing = () => {
|
||||
<VStack>
|
||||
<Heading fontSize="6xl">Plans fit for you</Heading>
|
||||
<Text maxW="900px" fontSize="xl" textAlign="center">
|
||||
Whether you're a{' '}
|
||||
Whether you're a{' '}
|
||||
<Text as="span" color="orange.200" fontWeight="bold">
|
||||
solo business owner
|
||||
</Text>{' '}
|
||||
@ -100,7 +100,7 @@ const Pricing = () => {
|
||||
<Text fontSize="lg">
|
||||
Need custom limits? Specific features?{' '}
|
||||
<TextLink href={'https://typebot.io/enterprise-lead-form'}>
|
||||
Let's chat!
|
||||
Let's chat!
|
||||
</TextLink>
|
||||
</Text>
|
||||
</Stack>
|
||||
@ -155,9 +155,9 @@ const Faq = () => {
|
||||
<AccordionPanel pb={4}>
|
||||
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
|
||||
chat with your bot but their uploads won't be stored anymore. You will
|
||||
need to upgrade the limit or free up some space to continue collecting
|
||||
your users' files.
|
||||
chat with your bot but their uploads won't be stored anymore. You
|
||||
will need to upgrade the limit or free up some space to continue
|
||||
collecting your users' files.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
@ -189,7 +189,7 @@ const Faq = () => {
|
||||
<TextLink href="mailto:baptiste@typebot.io">
|
||||
shoot me an email
|
||||
</TextLink>{' '}
|
||||
and we'll figure things out 😀
|
||||
and we'll figure things out 😀
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
@ -1,22 +1,10 @@
|
||||
{
|
||||
"extends": "tsconfig/nextjs.json",
|
||||
"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": ".",
|
||||
"composite": true
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
|
@ -1,43 +1,4 @@
|
||||
module.exports = {
|
||||
ignorePatterns: ['node_modules'],
|
||||
env: {
|
||||
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',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
root: true,
|
||||
extends: ['custom'],
|
||||
}
|
||||
|
@ -39,21 +39,17 @@
|
||||
"@types/qs": "6.9.7",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/sanitize-html": "2.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.43.0",
|
||||
"@typescript-eslint/parser": "5.43.0",
|
||||
"dotenv": "16.0.3",
|
||||
"emails": "workspace:*",
|
||||
"eslint": "8.27.0",
|
||||
"eslint-config-next": "13.0.3",
|
||||
"eslint-plugin-react": "7.31.10",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"google-auth-library": "8.7.0",
|
||||
"models": "workspace:*",
|
||||
"next-transpile-modules": "10.0.0",
|
||||
"node-fetch": "^3.3.0",
|
||||
"papaparse": "5.3.2",
|
||||
"tsconfig": "workspace:*",
|
||||
"typescript": "4.8.4",
|
||||
"utils": "workspace:*",
|
||||
"configs": "workspace:*"
|
||||
"utils": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test'
|
||||
import path from 'path'
|
||||
import { playwrightBaseConfig } from 'configs/playwright'
|
||||
import { playwrightBaseConfig } from 'utils/playwright/baseConfig'
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
...playwrightBaseConfig,
|
||||
|
@ -1,5 +1,11 @@
|
||||
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 React, { useEffect, useState } from 'react'
|
||||
import { isDefined, isNotDefined } from 'utils'
|
||||
@ -106,7 +112,7 @@ export const TypebotPage = ({
|
||||
}
|
||||
|
||||
const handleNewAnswer = async (
|
||||
answer: Answer & { uploadedFiles: boolean }
|
||||
answer: AnswerInput & { uploadedFiles: boolean }
|
||||
) => {
|
||||
if (!resultId) return setError(new Error('Error: result was not created'))
|
||||
if (publishedTypebot.settings.general.isResultSavingEnabled !== false) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Answer } from 'models'
|
||||
import { Answer, AnswerInput } from 'models'
|
||||
import { sendRequest } from 'utils'
|
||||
|
||||
export const upsertAnswerQuery = async (
|
||||
answer: Answer & { resultId: string } & { uploadedFiles?: boolean }
|
||||
answer: AnswerInput & { resultId: string } & { uploadedFiles?: boolean }
|
||||
) =>
|
||||
sendRequest<Answer>({
|
||||
url: `/api/typebots/t/results/r/answers`,
|
||||
|
@ -151,22 +151,30 @@ test('Should correctly parse metadata', async ({ page }) => {
|
||||
).toBe(customMetadata.title)
|
||||
expect(
|
||||
await page.evaluate(
|
||||
() => (document.querySelector('meta[name="description"]') as any).content
|
||||
() =>
|
||||
(document.querySelector('meta[name="description"]') as HTMLMetaElement)
|
||||
.content
|
||||
)
|
||||
).toBe(customMetadata.description)
|
||||
expect(
|
||||
await page.evaluate(
|
||||
() => (document.querySelector('meta[property="og:image"]') as any).content
|
||||
() =>
|
||||
(document.querySelector('meta[property="og:image"]') as HTMLMetaElement)
|
||||
.content
|
||||
)
|
||||
).toBe(customMetadata.imageUrl)
|
||||
expect(
|
||||
await page.evaluate(() =>
|
||||
(document.querySelector('link[rel="icon"]') as any).getAttribute('href')
|
||||
(
|
||||
document.querySelector('link[rel="icon"]') as HTMLLinkElement
|
||||
).getAttribute('href')
|
||||
)
|
||||
).toBe(customMetadata.favIconUrl)
|
||||
expect(
|
||||
await page.evaluate(
|
||||
() => (document.querySelector('meta[name="author"]') as any).content
|
||||
() =>
|
||||
(document.querySelector('meta[name="author"]') as HTMLMetaElement)
|
||||
.content
|
||||
)
|
||||
).toBe('John Doe')
|
||||
await expect(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import NextErrorComponent from 'next/error'
|
||||
import NextErrorComponent, { ErrorProps } from 'next/error'
|
||||
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import { NextPageContext } from 'next'
|
||||
@ -24,14 +24,14 @@ const MyError = ({
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when
|
||||
// getInitialProps has run
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
errorInitialProps.hasGetInitialPropsRun = true
|
||||
|
||||
// Returning early because we don't want to log 404 errors to Sentry.
|
||||
|
@ -84,20 +84,21 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
}`,
|
||||
})
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const error = err as any
|
||||
return 'raw' in error
|
||||
? res.status(error.raw.statusCode).send({
|
||||
if (typeof err === 'object' && err && 'raw' in err) {
|
||||
const error = (err as { raw: Stripe.StripeRawError }).raw
|
||||
res.status(error.statusCode ?? 500).send({
|
||||
error: {
|
||||
name: `${error.raw.type} ${error.raw.param}`,
|
||||
message: error.raw.message,
|
||||
name: `${error.type} ${error.param}`,
|
||||
message: error.message,
|
||||
},
|
||||
})
|
||||
: res.status(500).send({
|
||||
error,
|
||||
} else {
|
||||
res.status(500).send({
|
||||
err,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ import { saveErrorLog, saveSuccessLog } from '@/features/logs/api'
|
||||
import { parseSampleResult } from '@/features/webhook/api'
|
||||
|
||||
const cors = initMiddleware(Cors())
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
await cors(req, res)
|
||||
if (req.method === 'POST') {
|
||||
@ -34,11 +35,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { resultValues, variables } = (
|
||||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||
) as {
|
||||
resultValues:
|
||||
| (Omit<ResultValues, 'createdAt'> & {
|
||||
createdAt: string
|
||||
})
|
||||
| undefined
|
||||
resultValues: ResultValues | undefined
|
||||
variables: Variable[]
|
||||
}
|
||||
const typebot = (await prisma.typebot.findUnique({
|
||||
@ -87,9 +84,7 @@ export const executeWebhook =
|
||||
webhook: Webhook,
|
||||
variables: Variable[],
|
||||
groupId: string,
|
||||
resultValues?: Omit<ResultValues, 'createdAt'> & {
|
||||
createdAt: string
|
||||
},
|
||||
resultValues?: ResultValues,
|
||||
resultId?: string
|
||||
): Promise<WebhookResponse> => {
|
||||
if (!webhook.url || !webhook.method)
|
||||
@ -199,9 +194,7 @@ const getBodyContent =
|
||||
groupId,
|
||||
}: {
|
||||
body?: string | null
|
||||
resultValues?: Omit<ResultValues, 'createdAt'> & {
|
||||
createdAt: string
|
||||
}
|
||||
resultValues?: ResultValues
|
||||
groupId: string
|
||||
}): Promise<string | undefined> => {
|
||||
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 } => {
|
||||
try {
|
||||
return { data: JSON.parse(json), isJson: true }
|
||||
|
@ -16,6 +16,7 @@ import Cors from 'cors'
|
||||
import { executeWebhook } from '../../executeWebhook'
|
||||
|
||||
const cors = initMiddleware(Cors())
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
await cors(req, res)
|
||||
if (req.method === 'POST') {
|
||||
@ -26,11 +27,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { resultValues, variables } = (
|
||||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||
) as {
|
||||
resultValues:
|
||||
| (Omit<ResultValues, 'createdAt'> & {
|
||||
createdAt: string
|
||||
})
|
||||
| undefined
|
||||
resultValues: ResultValues
|
||||
variables: Variable[]
|
||||
}
|
||||
const typebot = (await prisma.typebot.findUnique({
|
||||
|
@ -1,16 +1,11 @@
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import { WorkspaceRole } from 'db'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { InputBlockType, PublicTypebot } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { badRequest, generatePresignedUrl, methodNotAllowed } from 'utils/api'
|
||||
import { byId, getStorageLimit, isDefined, env } from 'utils'
|
||||
import {
|
||||
sendAlmostReachedStorageLimitEmail,
|
||||
sendReachedStorageLimitEmail,
|
||||
} from 'emails'
|
||||
import { byId } from 'utils'
|
||||
|
||||
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
|
||||
// const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
|
||||
|
||||
const handler = async (
|
||||
req: NextApiRequest,
|
||||
@ -57,112 +52,111 @@ const handler = async (
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const checkStorageLimit = async (typebotId: string) => {
|
||||
const typebot = await prisma.typebot.findFirst({
|
||||
where: { id: typebotId },
|
||||
include: {
|
||||
workspace: {
|
||||
select: {
|
||||
id: true,
|
||||
additionalStorageIndex: true,
|
||||
plan: true,
|
||||
storageLimitFirstEmailSentAt: true,
|
||||
storageLimitSecondEmailSentAt: true,
|
||||
customStorageLimit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!typebot?.workspace) throw new Error('Workspace not found')
|
||||
const { workspace } = typebot
|
||||
const {
|
||||
_sum: { storageUsed: totalStorageUsed },
|
||||
} = await prisma.answer.aggregate({
|
||||
where: {
|
||||
storageUsed: { gt: 0 },
|
||||
result: {
|
||||
typebot: {
|
||||
workspace: {
|
||||
id: typebot?.workspaceId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
_sum: { storageUsed: true },
|
||||
})
|
||||
if (!totalStorageUsed) return false
|
||||
const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null
|
||||
const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null
|
||||
const storageLimit = getStorageLimit(typebot.workspace)
|
||||
const storageLimitBytes = storageLimit * 1024 * 1024 * 1024
|
||||
if (
|
||||
totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
||||
!hasSentFirstEmail &&
|
||||
env('E2E_TEST') !== 'true'
|
||||
)
|
||||
await sendAlmostReachStorageLimitNotification({
|
||||
workspaceId: workspace.id,
|
||||
storageLimit,
|
||||
})
|
||||
if (
|
||||
totalStorageUsed >= storageLimitBytes &&
|
||||
!hasSentSecondEmail &&
|
||||
env('E2E_TEST') !== 'true'
|
||||
)
|
||||
await sendReachStorageLimitNotification({
|
||||
workspaceId: workspace.id,
|
||||
storageLimit,
|
||||
})
|
||||
return totalStorageUsed >= storageLimitBytes
|
||||
}
|
||||
// const checkStorageLimit = async (typebotId: string) => {
|
||||
// const typebot = await prisma.typebot.findFirst({
|
||||
// where: { id: typebotId },
|
||||
// include: {
|
||||
// workspace: {
|
||||
// select: {
|
||||
// id: true,
|
||||
// additionalStorageIndex: true,
|
||||
// plan: true,
|
||||
// storageLimitFirstEmailSentAt: true,
|
||||
// storageLimitSecondEmailSentAt: true,
|
||||
// customStorageLimit: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// if (!typebot?.workspace) throw new Error('Workspace not found')
|
||||
// const { workspace } = typebot
|
||||
// const {
|
||||
// _sum: { storageUsed: totalStorageUsed },
|
||||
// } = await prisma.answer.aggregate({
|
||||
// where: {
|
||||
// storageUsed: { gt: 0 },
|
||||
// result: {
|
||||
// typebot: {
|
||||
// workspace: {
|
||||
// id: typebot?.workspaceId,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// _sum: { storageUsed: true },
|
||||
// })
|
||||
// if (!totalStorageUsed) return false
|
||||
// const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null
|
||||
// const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null
|
||||
// const storageLimit = getStorageLimit(typebot.workspace)
|
||||
// const storageLimitBytes = storageLimit * 1024 * 1024 * 1024
|
||||
// if (
|
||||
// totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
||||
// !hasSentFirstEmail &&
|
||||
// env('E2E_TEST') !== 'true'
|
||||
// )
|
||||
// await sendAlmostReachStorageLimitNotification({
|
||||
// workspaceId: workspace.id,
|
||||
// storageLimit,
|
||||
// })
|
||||
// if (
|
||||
// totalStorageUsed >= storageLimitBytes &&
|
||||
// !hasSentSecondEmail &&
|
||||
// env('E2E_TEST') !== 'true'
|
||||
// )
|
||||
// await sendReachStorageLimitNotification({
|
||||
// workspaceId: workspace.id,
|
||||
// storageLimit,
|
||||
// })
|
||||
// return totalStorageUsed >= storageLimitBytes
|
||||
// }
|
||||
|
||||
const sendAlmostReachStorageLimitNotification = async ({
|
||||
workspaceId,
|
||||
storageLimit,
|
||||
}: {
|
||||
workspaceId: string
|
||||
storageLimit: number
|
||||
}) => {
|
||||
const members = await prisma.memberInWorkspace.findMany({
|
||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
include: { user: { select: { email: true } } },
|
||||
})
|
||||
// const sendAlmostReachStorageLimitNotification = async ({
|
||||
// workspaceId,
|
||||
// storageLimit,
|
||||
// }: {
|
||||
// workspaceId: string
|
||||
// storageLimit: number
|
||||
// }) => {
|
||||
// const members = await prisma.memberInWorkspace.findMany({
|
||||
// where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
// include: { user: { select: { email: true } } },
|
||||
// })
|
||||
|
||||
await sendAlmostReachedStorageLimitEmail({
|
||||
to: members.map((member) => member.user.email).filter(isDefined),
|
||||
storageLimit,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
})
|
||||
// await sendAlmostReachedStorageLimitEmail({
|
||||
// to: members.map((member) => member.user.email).filter(isDefined),
|
||||
// storageLimit,
|
||||
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
// })
|
||||
|
||||
await prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { storageLimitFirstEmailSentAt: new Date() },
|
||||
})
|
||||
}
|
||||
// await prisma.workspace.update({
|
||||
// where: { id: workspaceId },
|
||||
// data: { storageLimitFirstEmailSentAt: new Date() },
|
||||
// })
|
||||
// }
|
||||
|
||||
const sendReachStorageLimitNotification = async ({
|
||||
workspaceId,
|
||||
storageLimit,
|
||||
}: {
|
||||
workspaceId: string
|
||||
storageLimit: number
|
||||
}) => {
|
||||
const members = await prisma.memberInWorkspace.findMany({
|
||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
include: { user: { select: { email: true } } },
|
||||
})
|
||||
// const sendReachStorageLimitNotification = async ({
|
||||
// workspaceId,
|
||||
// storageLimit,
|
||||
// }: {
|
||||
// workspaceId: string
|
||||
// storageLimit: number
|
||||
// }) => {
|
||||
// const members = await prisma.memberInWorkspace.findMany({
|
||||
// where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
// include: { user: { select: { email: true } } },
|
||||
// })
|
||||
|
||||
await sendReachedStorageLimitEmail({
|
||||
to: members.map((member) => member.user.email).filter(isDefined),
|
||||
storageLimit,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
})
|
||||
// await sendReachedStorageLimitEmail({
|
||||
// to: members.map((member) => member.user.email).filter(isDefined),
|
||||
// storageLimit,
|
||||
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
// })
|
||||
|
||||
await prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { storageLimitSecondEmailSentAt: new Date() },
|
||||
})
|
||||
}
|
||||
// await prisma.workspace.update({
|
||||
// where: { id: workspaceId },
|
||||
// data: { storageLimitSecondEmailSentAt: new Date() },
|
||||
// })
|
||||
// }
|
||||
|
||||
export default withSentry(handler)
|
||||
|
@ -31,7 +31,7 @@ const defaultTransportOptions = {
|
||||
|
||||
const defaultFrom = {
|
||||
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) => {
|
||||
@ -54,9 +54,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
} = (
|
||||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||
) as SendEmailOptions & {
|
||||
resultValues: Omit<ResultValues, 'createdAt'> & {
|
||||
createdAt: string
|
||||
}
|
||||
resultValues: ResultValues
|
||||
fileUrls?: string
|
||||
}
|
||||
const { name: replyToName } = parseEmailRecipient(replyTo)
|
||||
@ -166,9 +164,7 @@ const getEmailBody = async ({
|
||||
resultValues,
|
||||
}: {
|
||||
typebotId: string
|
||||
resultValues: Omit<ResultValues, 'createdAt'> & {
|
||||
createdAt: string
|
||||
}
|
||||
resultValues: ResultValues
|
||||
} & Pick<SendEmailOptions, 'isCustomBody' | 'isBodyCode' | 'body'>): Promise<
|
||||
{ html?: string; text?: string } | undefined
|
||||
> => {
|
||||
|
@ -1,16 +1,10 @@
|
||||
import { authenticateUser } from '@/features/auth/api'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { Workspace, WorkspaceRole } from 'db'
|
||||
import {
|
||||
sendAlmostReachedChatsLimitEmail,
|
||||
sendReachedChatsLimitEmail,
|
||||
} from 'emails'
|
||||
import { ResultWithAnswers } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { env, getChatsLimit, isDefined } from 'utils'
|
||||
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) => {
|
||||
if (req.method === 'GET') {
|
||||
@ -63,106 +57,105 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
methodNotAllowed(res)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const checkChatsUsage = async (
|
||||
workspace: Pick<
|
||||
Workspace,
|
||||
| 'id'
|
||||
| 'plan'
|
||||
| 'additionalChatsIndex'
|
||||
| 'chatsLimitFirstEmailSentAt'
|
||||
| 'chatsLimitSecondEmailSentAt'
|
||||
| 'customChatsLimit'
|
||||
>
|
||||
) => {
|
||||
const chatsLimit = getChatsLimit(workspace)
|
||||
if (chatsLimit === -1) return
|
||||
const now = new Date()
|
||||
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||
const firstDayOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
|
||||
const chatsCount = await prisma.result.count({
|
||||
where: {
|
||||
typebot: { workspaceId: workspace.id },
|
||||
hasStarted: true,
|
||||
createdAt: { gte: firstDayOfMonth, lte: lastDayOfMonth },
|
||||
},
|
||||
})
|
||||
const hasSentFirstEmail =
|
||||
workspace.chatsLimitFirstEmailSentAt !== null &&
|
||||
workspace.chatsLimitFirstEmailSentAt < firstDayOfNextMonth &&
|
||||
workspace.chatsLimitFirstEmailSentAt > firstDayOfMonth
|
||||
const hasSentSecondEmail =
|
||||
workspace.chatsLimitSecondEmailSentAt !== null &&
|
||||
workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth &&
|
||||
workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth
|
||||
if (
|
||||
chatsCount >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
||||
!hasSentFirstEmail &&
|
||||
env('E2E_TEST') !== 'true'
|
||||
)
|
||||
await sendAlmostReachChatsLimitNotification({
|
||||
workspaceId: workspace.id,
|
||||
chatsLimit,
|
||||
})
|
||||
if (
|
||||
chatsCount >= chatsLimit &&
|
||||
!hasSentSecondEmail &&
|
||||
env('E2E_TEST') !== 'true'
|
||||
)
|
||||
await sendReachedAlertNotification({
|
||||
workspaceId: workspace.id,
|
||||
chatsLimit,
|
||||
})
|
||||
return chatsCount >= chatsLimit
|
||||
}
|
||||
// const checkChatsUsage = async (
|
||||
// workspace: Pick<
|
||||
// Workspace,
|
||||
// | 'id'
|
||||
// | 'plan'
|
||||
// | 'additionalChatsIndex'
|
||||
// | 'chatsLimitFirstEmailSentAt'
|
||||
// | 'chatsLimitSecondEmailSentAt'
|
||||
// | 'customChatsLimit'
|
||||
// >
|
||||
// ) => {
|
||||
// const chatsLimit = getChatsLimit(workspace)
|
||||
// if (chatsLimit === -1) return
|
||||
// const now = new Date()
|
||||
// const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
// const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||
// const firstDayOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
|
||||
// const chatsCount = await prisma.result.count({
|
||||
// where: {
|
||||
// typebot: { workspaceId: workspace.id },
|
||||
// hasStarted: true,
|
||||
// createdAt: { gte: firstDayOfMonth, lte: lastDayOfMonth },
|
||||
// },
|
||||
// })
|
||||
// const hasSentFirstEmail =
|
||||
// workspace.chatsLimitFirstEmailSentAt !== null &&
|
||||
// workspace.chatsLimitFirstEmailSentAt < firstDayOfNextMonth &&
|
||||
// workspace.chatsLimitFirstEmailSentAt > firstDayOfMonth
|
||||
// const hasSentSecondEmail =
|
||||
// workspace.chatsLimitSecondEmailSentAt !== null &&
|
||||
// workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth &&
|
||||
// workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth
|
||||
// if (
|
||||
// chatsCount >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
||||
// !hasSentFirstEmail &&
|
||||
// env('E2E_TEST') !== 'true'
|
||||
// )
|
||||
// await sendAlmostReachChatsLimitNotification({
|
||||
// workspaceId: workspace.id,
|
||||
// chatsLimit,
|
||||
// })
|
||||
// if (
|
||||
// chatsCount >= chatsLimit &&
|
||||
// !hasSentSecondEmail &&
|
||||
// env('E2E_TEST') !== 'true'
|
||||
// )
|
||||
// await sendReachedAlertNotification({
|
||||
// workspaceId: workspace.id,
|
||||
// chatsLimit,
|
||||
// })
|
||||
// return chatsCount >= chatsLimit
|
||||
// }
|
||||
|
||||
const sendAlmostReachChatsLimitNotification = async ({
|
||||
workspaceId,
|
||||
chatsLimit,
|
||||
}: {
|
||||
workspaceId: string
|
||||
chatsLimit: number
|
||||
}) => {
|
||||
const members = await prisma.memberInWorkspace.findMany({
|
||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
include: { user: { select: { email: true } } },
|
||||
})
|
||||
// const sendAlmostReachChatsLimitNotification = async ({
|
||||
// workspaceId,
|
||||
// chatsLimit,
|
||||
// }: {
|
||||
// workspaceId: string
|
||||
// chatsLimit: number
|
||||
// }) => {
|
||||
// const members = await prisma.memberInWorkspace.findMany({
|
||||
// where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
// include: { user: { select: { email: true } } },
|
||||
// })
|
||||
|
||||
await sendAlmostReachedChatsLimitEmail({
|
||||
to: members.map((member) => member.user.email).filter(isDefined),
|
||||
chatsLimit,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
})
|
||||
// await sendAlmostReachedChatsLimitEmail({
|
||||
// to: members.map((member) => member.user.email).filter(isDefined),
|
||||
// chatsLimit,
|
||||
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
// })
|
||||
|
||||
await prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { chatsLimitFirstEmailSentAt: new Date() },
|
||||
})
|
||||
}
|
||||
// await prisma.workspace.update({
|
||||
// where: { id: workspaceId },
|
||||
// data: { chatsLimitFirstEmailSentAt: new Date() },
|
||||
// })
|
||||
// }
|
||||
|
||||
const sendReachedAlertNotification = async ({
|
||||
workspaceId,
|
||||
chatsLimit,
|
||||
}: {
|
||||
workspaceId: string
|
||||
chatsLimit: number
|
||||
}) => {
|
||||
const members = await prisma.memberInWorkspace.findMany({
|
||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
include: { user: { select: { email: true } } },
|
||||
})
|
||||
// const sendReachedAlertNotification = async ({
|
||||
// workspaceId,
|
||||
// chatsLimit,
|
||||
// }: {
|
||||
// workspaceId: string
|
||||
// chatsLimit: number
|
||||
// }) => {
|
||||
// const members = await prisma.memberInWorkspace.findMany({
|
||||
// where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
// include: { user: { select: { email: true } } },
|
||||
// })
|
||||
|
||||
await sendReachedChatsLimitEmail({
|
||||
to: members.map((member) => member.user.email).filter(isDefined),
|
||||
chatsLimit,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
})
|
||||
// await sendReachedChatsLimitEmail({
|
||||
// to: members.map((member) => member.user.email).filter(isDefined),
|
||||
// chatsLimit,
|
||||
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
// })
|
||||
|
||||
await prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { chatsLimitSecondEmailSentAt: new Date() },
|
||||
})
|
||||
}
|
||||
// await prisma.workspace.update({
|
||||
// where: { id: workspaceId },
|
||||
// data: { chatsLimitSecondEmailSentAt: new Date() },
|
||||
// })
|
||||
// }
|
||||
|
||||
export default handler
|
||||
|
@ -1,26 +1,10 @@
|
||||
{
|
||||
"extends": "tsconfig/nextjs.json",
|
||||
"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": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"composite": true,
|
||||
"downlevelIteration": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
|
@ -20,10 +20,13 @@
|
||||
"test:viewer": "cd apps/viewer && pnpm test",
|
||||
"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",
|
||||
"generate-change-log": "pnpx gitmoji-changelog"
|
||||
"generate-change-log": "pnpx gitmoji-changelog",
|
||||
"lint": "turbo run lint",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cz-emoji": "1.3.2-canary.2",
|
||||
"husky": "^8.0.2",
|
||||
"turbo": "1.6.3"
|
||||
},
|
||||
"config": {
|
||||
@ -31,5 +34,5 @@
|
||||
"path": "node_modules/cz-emoji"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@7.16.1"
|
||||
"packageManager": "pnpm@7.17.0"
|
||||
}
|
||||
|
@ -1,48 +1,8 @@
|
||||
module.exports = {
|
||||
ignorePatterns: ['node_modules'],
|
||||
env: {
|
||||
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',
|
||||
root: true,
|
||||
extends: ['custom'],
|
||||
rules: {
|
||||
'react/no-unescaped-entities': [0],
|
||||
'prettier/prettier': 'error',
|
||||
'react/display-name': [0],
|
||||
'@next/next/no-img-element': [0],
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
'*/src/*',
|
||||
'src/*',
|
||||
'*/src',
|
||||
'@/features/*/*',
|
||||
'@/index',
|
||||
'!@/features/blocks/*',
|
||||
'!@/features/*/api',
|
||||
],
|
||||
},
|
||||
],
|
||||
'@next/next/no-img-element': 'off',
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
},
|
||||
}
|
||||
|
@ -6,9 +6,9 @@
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"build": "pnpm tsc --noEmit && tsup",
|
||||
"dev": "tsup --watch",
|
||||
"lint": "eslint --fix -c ./.eslintrc.js \"./src/**/*.ts*\""
|
||||
"lint": "eslint \"src/**/*.ts*\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@stripe/react-stripe-js": "1.15.0",
|
||||
@ -28,16 +28,11 @@
|
||||
"@types/react-phone-number-input": "3.0.14",
|
||||
"@types/react-scroll": "1.8.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",
|
||||
"baptistearno-tsup": "^0.1.0",
|
||||
"tsup": "6.5.0",
|
||||
"db": "workspace:*",
|
||||
"eslint": "8.27.0",
|
||||
"eslint-config-next": "13.0.3",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"eslint-plugin-react": "7.31.10",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"models": "workspace:*",
|
||||
"postcss": "8.4.19",
|
||||
"prettier": "2.7.1",
|
||||
@ -46,7 +41,8 @@
|
||||
"tailwindcss": "3.2.4",
|
||||
"typescript": "4.8.4",
|
||||
"utils": "workspace:*",
|
||||
"typebot-js": "workspace:*"
|
||||
"typebot-js": "workspace:*",
|
||||
"tsconfig": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"db": "workspace:*",
|
||||
|
@ -13,8 +13,10 @@ import { ResizeObserver } from 'resize-observer'
|
||||
|
||||
type Props = { hostAvatarSrc?: string; keepShowing: boolean }
|
||||
|
||||
export const AvatarSideContainer = forwardRef(
|
||||
({ hostAvatarSrc, keepShowing }: Props, ref: ForwardedRef<unknown>) => {
|
||||
export const AvatarSideContainer = forwardRef(function AvatarSideContainer(
|
||||
{ hostAvatarSrc, keepShowing }: Props,
|
||||
ref: ForwardedRef<unknown>
|
||||
) {
|
||||
const { document } = useFrame()
|
||||
const [show, setShow] = useState(false)
|
||||
const [avatarTopOffset, setAvatarTopOffset] = useState(0)
|
||||
@ -67,5 +69,4 @@ export const AvatarSideContainer = forwardRef(
|
||||
</CSSTransition>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -53,7 +53,7 @@ export const InputChatBlock = ({
|
||||
blockId: block.id,
|
||||
groupId: block.groupId,
|
||||
content: value,
|
||||
variableId: variableId ?? null,
|
||||
variableId,
|
||||
uploadedFiles: block.type === InputBlockType.FILE,
|
||||
})
|
||||
if (!isEditting) onTransitionEnd({ label, value, itemId }, isRetry)
|
||||
|
@ -246,7 +246,6 @@ const ChatChunks = ({
|
||||
}: Props) => {
|
||||
const [isSkipped, setIsSkipped] = useState(false)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const avatarSideContainerRef = useRef<any>()
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -8,6 +8,7 @@ import { ConversationContainer } from './ConversationContainer'
|
||||
import { AnswersProvider } from '../providers/AnswersProvider'
|
||||
import {
|
||||
Answer,
|
||||
AnswerInput,
|
||||
BackgroundType,
|
||||
Edge,
|
||||
PublicTypebot,
|
||||
@ -27,7 +28,9 @@ export type TypebotViewerProps = {
|
||||
startGroupId?: string
|
||||
isLoading?: boolean
|
||||
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
|
||||
onCompleted?: () => void
|
||||
onVariablesUpdated?: (variables: VariableWithValue[]) => void
|
||||
@ -58,7 +61,7 @@ export const TypebotViewer = ({
|
||||
const handleNewGroupVisible = (edge: Edge) =>
|
||||
onNewGroupVisible && onNewGroupVisible(edge)
|
||||
|
||||
const handleNewAnswer = (answer: Answer & { uploadedFiles: boolean }) =>
|
||||
const handleNewAnswer = (answer: AnswerInput & { uploadedFiles: boolean }) =>
|
||||
onNewAnswer && onNewAnswer(answer)
|
||||
|
||||
const handleNewLog = (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) =>
|
||||
|
@ -5,11 +5,10 @@ type ShortTextInputProps = {
|
||||
onChange: (value: string) => void
|
||||
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'>
|
||||
|
||||
export const ShortTextInput = React.forwardRef(
|
||||
(
|
||||
export const ShortTextInput = React.forwardRef(function ShortTextInput(
|
||||
{ onChange, ...props }: ShortTextInputProps,
|
||||
ref: React.ForwardedRef<HTMLInputElement>
|
||||
) => {
|
||||
) {
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
@ -21,5 +20,4 @@ export const ShortTextInput = React.forwardRef(
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -5,11 +5,11 @@ type TextareaProps = {
|
||||
onChange: (value: string) => void
|
||||
} & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange'>
|
||||
|
||||
export const Textarea = React.forwardRef(
|
||||
(
|
||||
export const Textarea = React.forwardRef(function Textarea(
|
||||
{ onChange, ...props }: TextareaProps,
|
||||
ref: React.ForwardedRef<HTMLTextAreaElement>
|
||||
) => (
|
||||
) {
|
||||
return (
|
||||
<textarea
|
||||
ref={ref}
|
||||
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}
|
||||
/>
|
||||
)
|
||||
)
|
||||
})
|
||||
|
@ -9,6 +9,7 @@ import { parseVariables } from '@/features/variables'
|
||||
import { useChat } from '@/providers/ChatProvider'
|
||||
import { useTypebot } from '@/providers/TypebotProvider'
|
||||
import { createPaymentIntentQuery } from '../../queries/createPaymentIntentQuery'
|
||||
import { Stripe } from '@stripe/stripe-js'
|
||||
|
||||
type Props = {
|
||||
options: PaymentInputOptions
|
||||
@ -23,8 +24,7 @@ export const StripePaymentForm = ({ options, onSuccess }: Props) => {
|
||||
onNewLog,
|
||||
} = useTypebot()
|
||||
const { window: frameWindow, document: frameDocument } = useFrame()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [stripe, setStripe] = useState<any>(null)
|
||||
const [stripe, setStripe] = useState<Stripe | null>(null)
|
||||
const [clientSecret, setClientSecret] = useState('')
|
||||
const [amountLabel, setAmountLabel] = useState('')
|
||||
|
||||
|
@ -19,7 +19,6 @@ export const PhoneInput = ({
|
||||
hasGuestAvatar,
|
||||
}: PhoneInputProps) => {
|
||||
const [inputValue, setInputValue] = useState(defaultValue ?? '')
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const inputRef = useRef<any>(null)
|
||||
|
||||
const handleChange = (inputValue: Value | undefined) =>
|
||||
|
@ -1,7 +1,14 @@
|
||||
import { GoogleAnalyticsOptions } from 'models'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
declare const gtag: any
|
||||
declare const gtag: (
|
||||
type: string,
|
||||
action: string | undefined,
|
||||
options: {
|
||||
event_category: string | undefined
|
||||
event_label: string | undefined
|
||||
value: number | undefined
|
||||
}
|
||||
) => void
|
||||
|
||||
const initGoogleAnalytics = (id: string): Promise<void> =>
|
||||
new Promise((resolve) => {
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { safeStringify } from '@/features/variables'
|
||||
import {
|
||||
Answer,
|
||||
AnswerInput,
|
||||
ResultValues,
|
||||
VariableWithUnknowValue,
|
||||
VariableWithValue,
|
||||
} from 'models'
|
||||
import React, { createContext, ReactNode, useContext, useState } from 'react'
|
||||
import { createContext, ReactNode, useContext, useState } from 'react'
|
||||
|
||||
const answersContext = createContext<{
|
||||
resultId?: string
|
||||
resultValues: ResultValues
|
||||
addAnswer: (
|
||||
answer: Answer & { uploadedFiles: boolean }
|
||||
answer: AnswerInput & { uploadedFiles: boolean }
|
||||
) => Promise<void> | undefined
|
||||
updateVariables: (variables: VariableWithUnknowValue[]) => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
@ -26,7 +26,7 @@ export const AnswersProvider = ({
|
||||
}: {
|
||||
resultId?: string
|
||||
onNewAnswer: (
|
||||
answer: Answer & { uploadedFiles: boolean }
|
||||
answer: AnswerInput & { uploadedFiles: boolean }
|
||||
) => Promise<void> | undefined
|
||||
onVariablesUpdated?: (variables: VariableWithValue[]) => void
|
||||
children: ReactNode
|
||||
@ -34,10 +34,10 @@ export const AnswersProvider = ({
|
||||
const [resultValues, setResultValues] = useState<ResultValues>({
|
||||
answers: [],
|
||||
variables: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
})
|
||||
|
||||
const addAnswer = (answer: Answer & { uploadedFiles: boolean }) => {
|
||||
const addAnswer = (answer: AnswerInput & { uploadedFiles: boolean }) => {
|
||||
setResultValues((resultValues) => ({
|
||||
...resultValues,
|
||||
answers: [...resultValues.answers, answer],
|
||||
|
@ -2,7 +2,6 @@ import React, { createContext, ReactNode, useContext } from 'react'
|
||||
|
||||
const chatContext = createContext<{
|
||||
scroll: () => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
|
@ -39,7 +39,6 @@ const typebotContext = createContext<{
|
||||
edgeId: string
|
||||
}) => void
|
||||
onNewLog: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
|
@ -1,24 +1,10 @@
|
||||
{
|
||||
"extends": "tsconfig/react-library.json",
|
||||
"include": ["src/**/*"],
|
||||
"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": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"downlevelIteration": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defineConfig } from 'baptistearno-tsup'
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig((options) => ({
|
||||
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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prisma": "4.6.1",
|
||||
"typescript": "4.8.4",
|
||||
"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",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"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",
|
||||
"serve": "http-server dist -a localhost -p 3223 -o"
|
||||
"serve": "http-server dist -a localhost -p 3223 -o",
|
||||
"lint": "eslint \"src/**/*.ts*\""
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Baptiste Arnaud",
|
||||
@ -22,7 +23,10 @@
|
||||
"nodemailer": "6.8.0",
|
||||
"react": "18.2.0",
|
||||
"tsx": "3.12.1",
|
||||
"utils": "workspace:*"
|
||||
"utils": "workspace:*",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"tsconfig": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@faire/mjml-react": "2.1.4",
|
||||
|
@ -42,15 +42,15 @@ export const AlmostReachedChatsLimitEmail = ({
|
||||
</MjmlSection>
|
||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||
<MjmlColumn>
|
||||
<Text>Your bots are chatting a lot. That's amazing. 💙</Text>
|
||||
<Text>Your bots are chatting a lot. That's amazing. 💙</Text>
|
||||
<Text>
|
||||
This means you've almost reached your monthly chats limit. You
|
||||
currently reached 80% of {readableChatsLimit} chats.
|
||||
This means you've almost reached your monthly chats limit.
|
||||
You currently reached 80% of {readableChatsLimit} chats.
|
||||
</Text>
|
||||
<Text>This limit will be reset on {readableResetDate}.</Text>
|
||||
<Text fontWeight={800}>
|
||||
Your bots won't start the chat if you reach the limit before this
|
||||
date❗
|
||||
Your bots won't start the chat if you reach the limit before
|
||||
this date❗
|
||||
</Text>
|
||||
<Text>
|
||||
If you need more monthly responses, you will need to upgrade your
|
@ -33,17 +33,18 @@ export const AlmostReachedStorageLimitEmail = ({
|
||||
</MjmlSection>
|
||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||
<MjmlColumn>
|
||||
<Text>Your bots are working a lot. That's amazing. 🤖</Text>
|
||||
<Text>Your bots are working a lot. That's amazing. 🤖</Text>
|
||||
<Text>
|
||||
This means you've almost reached your storage limit. You currently
|
||||
reached 80% of your {readableStorageLimit} storage limit.
|
||||
This means you've almost reached your storage limit. You
|
||||
currently reached 80% of your {readableStorageLimit} storage
|
||||
limit.
|
||||
</Text>
|
||||
<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>
|
||||
To make sure it won't happen, you need to upgrade your plan or
|
||||
delete existing results to free up space.
|
||||
To make sure it won't happen, you need to upgrade your plan
|
||||
or delete existing results to free up space.
|
||||
</Text>
|
||||
<MjmlSpacer height="24px" />
|
||||
<Button link={url}>Upgrade workspace</Button>
|
@ -42,7 +42,7 @@ export const GuestInvitationEmail = ({
|
||||
</Text>
|
||||
<Text>
|
||||
From now on you will see this typebot in your dashboard under his
|
||||
workspace "{workspaceName}" 👍
|
||||
workspace "{workspaceName}" 👍
|
||||
</Text>
|
||||
<Text>
|
||||
Make sure to log in as <i>{guestEmail}</i>.
|
@ -43,15 +43,15 @@ export const ReachedChatsLimitEmail = ({
|
||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||
<MjmlColumn>
|
||||
<Text>
|
||||
It just happened, you've reached your monthly {readableChatsLimit}{' '}
|
||||
chats limit 😮
|
||||
It just happened, you've reached your monthly{' '}
|
||||
{readableChatsLimit} chats limit 😮
|
||||
</Text>
|
||||
<Text fontWeight={800}>
|
||||
It means your bots are closed until {readableResetDate}❗
|
||||
</Text>
|
||||
<Text>
|
||||
If you'd like to continue chatting with your users this month,
|
||||
then you need to upgrade your plan. 🚀
|
||||
If you'd like to continue chatting with your users this
|
||||
month, then you need to upgrade your plan. 🚀
|
||||
</Text>
|
||||
|
||||
<MjmlSpacer height="24px" />
|
@ -34,14 +34,14 @@ export const ReachedStorageLimitEmail = ({
|
||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||
<MjmlColumn>
|
||||
<Text>
|
||||
It just happened, you've reached your {readableStorageLimit}{' '}
|
||||
It just happened, you've reached your {readableStorageLimit}{' '}
|
||||
storage limit 😮
|
||||
</Text>
|
||||
<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>
|
||||
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. 🚀
|
||||
</Text>
|
||||
|
@ -1,6 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"esModuleInterop": true
|
||||
}
|
||||
"extends": "tsconfig/react-library.json",
|
||||
"include": ["src/**/*"],
|
||||
"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