🚸 (account) Improve account form and fix cyclic dependencies
This commit is contained in:
@@ -14,13 +14,13 @@ import {
|
|||||||
Tag,
|
Tag,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { EditIcon, PlusIcon, TrashIcon } from '@/components/icons'
|
import { EditIcon, PlusIcon, TrashIcon } from '@/components/icons'
|
||||||
import { useTypebot } from '@/features/editor'
|
import { useTypebot } from '@/features/editor/providers/TypebotProvider/TypebotProvider'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { Variable } from 'models'
|
import { Variable } from 'models'
|
||||||
import React, { useState, useRef, ChangeEvent, useEffect } from 'react'
|
import React, { useState, useRef, ChangeEvent, useEffect } from 'react'
|
||||||
import { byId, isDefined, isNotDefined } from 'utils'
|
import { byId, isDefined, isNotDefined } from 'utils'
|
||||||
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
||||||
import { useParentModal } from '@/features/graph'
|
import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialVariableId?: string
|
initialVariableId?: string
|
||||||
|
|||||||
@@ -355,8 +355,8 @@ export const SendEmailIcon = (props: IconProps) => (
|
|||||||
export const GithubIcon = (props: IconProps) => (
|
export const GithubIcon = (props: IconProps) => (
|
||||||
<Icon viewBox="0 0 98 96" xmlns="http://www.w3.org/2000/svg" {...props}>
|
<Icon viewBox="0 0 98 96" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||||
fill={useColorModeValue('#24292f', 'white')}
|
fill={useColorModeValue('#24292f', 'white')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Variable } from 'models'
|
|||||||
import React, { ChangeEvent, useEffect, useRef, useState } from 'react'
|
import React, { ChangeEvent, useEffect, useRef, useState } from 'react'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
import { env } from 'utils'
|
import { env } from 'utils'
|
||||||
import { VariablesButton } from '../../features/variables/components/VariablesButton'
|
import { VariablesButton } from '@/features/variables'
|
||||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||||
|
|
||||||
export type TextBoxProps = {
|
export type TextBoxProps = {
|
||||||
|
|||||||
@@ -5,27 +5,28 @@ import {
|
|||||||
ReactNode,
|
ReactNode,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { isDefined, isNotDefined } from 'utils'
|
import { env, isDefined, isNotDefined } from 'utils'
|
||||||
import { dequal } from 'dequal'
|
|
||||||
import { User } from 'db'
|
import { User } from 'db'
|
||||||
import { setUser as setSentryUser } from '@sentry/nextjs'
|
import { setUser as setSentryUser } from '@sentry/nextjs'
|
||||||
import { useToast } from '@/hooks/useToast'
|
import { useToast } from '@/hooks/useToast'
|
||||||
import { updateUserQuery } from './queries/updateUserQuery'
|
import { updateUserQuery } from './queries/updateUserQuery'
|
||||||
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
|
|
||||||
const userContext = createContext<{
|
const userContext = createContext<{
|
||||||
user?: User
|
user?: User
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
isSaving: boolean
|
|
||||||
hasUnsavedChanges: boolean
|
|
||||||
currentWorkspaceId?: string
|
currentWorkspaceId?: string
|
||||||
updateUser: (newUser: Partial<User>) => void
|
updateUser: (newUser: Partial<User>) => void
|
||||||
saveUser: (newUser?: Partial<User>) => Promise<void>
|
}>({
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
isLoading: false,
|
||||||
//@ts-ignore
|
updateUser: () => {
|
||||||
}>({})
|
console.log('updateUser not implemented')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const debounceTimeout = 1000
|
||||||
|
|
||||||
export const UserProvider = ({ children }: { children: ReactNode }) => {
|
export const UserProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -34,13 +35,6 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
const [currentWorkspaceId, setCurrentWorkspaceId] = useState<string>()
|
const [currentWorkspaceId, setCurrentWorkspaceId] = useState<string>()
|
||||||
|
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
|
||||||
|
|
||||||
const hasUnsavedChanges = useMemo(
|
|
||||||
() => !dequal(session?.user, user),
|
|
||||||
[session?.user, user]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDefined(user) || isNotDefined(session)) return
|
if (isDefined(user) || isNotDefined(session)) return
|
||||||
setCurrentWorkspaceId(
|
setCurrentWorkspaceId(
|
||||||
@@ -70,31 +64,36 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
|
|
||||||
const isSigningIn = () => ['/signin', '/register'].includes(router.pathname)
|
const isSigningIn = () => ['/signin', '/register'].includes(router.pathname)
|
||||||
|
|
||||||
const updateUser = (newUser: Partial<User>) => {
|
const updateUser = (updates: Partial<User>) => {
|
||||||
if (isNotDefined(user)) return
|
if (isNotDefined(user)) return
|
||||||
setUser({ ...user, ...newUser })
|
const newUser = { ...user, ...updates }
|
||||||
|
setUser(newUser)
|
||||||
|
saveUser(newUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveUser = async (newUser?: Partial<User>) => {
|
const saveUser = useDebouncedCallback(
|
||||||
if (isNotDefined(user)) return
|
async (newUser?: Partial<User>) => {
|
||||||
setIsSaving(true)
|
if (isNotDefined(user)) return
|
||||||
if (newUser) updateUser(newUser)
|
const { error } = await updateUserQuery(user.id, { ...user, ...newUser })
|
||||||
const { error } = await updateUserQuery(user.id, { ...user, ...newUser })
|
if (error) showToast({ title: error.name, description: error.message })
|
||||||
if (error) showToast({ title: error.name, description: error.message })
|
await refreshUser()
|
||||||
await refreshUser()
|
},
|
||||||
setIsSaving(false)
|
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
||||||
}
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
saveUser.flush()
|
||||||
|
}
|
||||||
|
}, [saveUser])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<userContext.Provider
|
<userContext.Provider
|
||||||
value={{
|
value={{
|
||||||
user,
|
user,
|
||||||
isSaving,
|
|
||||||
isLoading: status === 'loading',
|
isLoading: status === 'loading',
|
||||||
hasUnsavedChanges,
|
|
||||||
currentWorkspaceId,
|
currentWorkspaceId,
|
||||||
updateUser,
|
updateUser,
|
||||||
saveUser,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ test.describe.configure({ mode: 'parallel' })
|
|||||||
test('should display user info properly', async ({ page }) => {
|
test('should display user info properly', async ({ page }) => {
|
||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
await page.click('text=Settings & Members')
|
await page.click('text=Settings & Members')
|
||||||
const saveButton = page.locator('button:has-text("Save")')
|
|
||||||
await expect(saveButton).toBeHidden()
|
|
||||||
expect(
|
expect(
|
||||||
page.locator('input[type="email"]').getAttribute('disabled')
|
page.locator('input[type="email"]').getAttribute('disabled')
|
||||||
).toBeDefined()
|
).toBeDefined()
|
||||||
await page.fill('#name', 'John Doe')
|
await page.getByRole('textbox', { name: 'Name:' }).fill('John Doe')
|
||||||
expect(saveButton).toBeVisible()
|
|
||||||
await page.setInputFiles('input[type="file"]', getTestAsset('avatar.jpg'))
|
await page.setInputFiles('input[type="file"]', getTestAsset('avatar.jpg'))
|
||||||
await expect(page.locator('img >> nth=1')).toHaveAttribute(
|
await expect(page.locator('img >> nth=1')).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
|
|||||||
@@ -1,35 +1,28 @@
|
|||||||
import {
|
import { Stack, HStack, Avatar, Text, Tooltip } from '@chakra-ui/react'
|
||||||
Stack,
|
|
||||||
HStack,
|
|
||||||
Avatar,
|
|
||||||
Button,
|
|
||||||
FormControl,
|
|
||||||
FormLabel,
|
|
||||||
Input,
|
|
||||||
Tooltip,
|
|
||||||
Flex,
|
|
||||||
Text,
|
|
||||||
} from '@chakra-ui/react'
|
|
||||||
import { UploadIcon } from '@/components/icons'
|
import { UploadIcon } from '@/components/icons'
|
||||||
import React, { ChangeEvent } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { isDefined } from 'utils'
|
|
||||||
import { ApiTokensList } from './ApiTokensList'
|
import { ApiTokensList } from './ApiTokensList'
|
||||||
import { UploadButton } from '@/components/ImageUploadContent/UploadButton'
|
import { UploadButton } from '@/components/ImageUploadContent/UploadButton'
|
||||||
import { useUser } from '@/features/account'
|
import { useUser } from '@/features/account'
|
||||||
|
import { Input } from '@/components/inputs/Input'
|
||||||
|
|
||||||
export const MyAccountForm = () => {
|
export const MyAccountForm = () => {
|
||||||
const { user, updateUser, saveUser, hasUnsavedChanges, isSaving } = useUser()
|
const { user, updateUser } = useUser()
|
||||||
|
const [name, setName] = useState(user?.name ?? '')
|
||||||
|
const [email, setEmail] = useState(user?.email ?? '')
|
||||||
|
|
||||||
const handleFileUploaded = async (url: string) => {
|
const handleFileUploaded = async (url: string) => {
|
||||||
updateUser({ image: url })
|
updateUser({ image: url })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleNameChange = (newName: string) => {
|
||||||
updateUser({ name: e.target.value })
|
setName(newName)
|
||||||
|
updateUser({ name: newName })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleEmailChange = (newEmail: string) => {
|
||||||
updateUser({ email: e.target.value })
|
setEmail(newEmail)
|
||||||
|
updateUser({ email: newEmail })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -56,40 +49,26 @@ export const MyAccountForm = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
<FormControl>
|
<Input
|
||||||
<FormLabel htmlFor="name">Name</FormLabel>
|
value={name}
|
||||||
<Input id="name" value={user?.name ?? ''} onChange={handleNameChange} />
|
onChange={handleNameChange}
|
||||||
</FormControl>
|
label="Name:"
|
||||||
{isDefined(user?.email) && (
|
withVariableButton={false}
|
||||||
<Tooltip
|
debounceTimeout={0}
|
||||||
label="Updating email is not available."
|
/>
|
||||||
placement="left"
|
<Tooltip label="Updating email is not available. Contact the support if you want to change it.">
|
||||||
hasArrow
|
<span>
|
||||||
>
|
<Input
|
||||||
<FormControl>
|
type="email"
|
||||||
<FormLabel htmlFor="email">Email address</FormLabel>
|
value={email}
|
||||||
<Input
|
onChange={handleEmailChange}
|
||||||
id="email"
|
label="Email address:"
|
||||||
type="email"
|
withVariableButton={false}
|
||||||
isDisabled
|
debounceTimeout={0}
|
||||||
value={user?.email ?? ''}
|
isDisabled
|
||||||
onChange={handleEmailChange}
|
/>
|
||||||
/>
|
</span>
|
||||||
</FormControl>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasUnsavedChanges && (
|
|
||||||
<Flex justifyContent="flex-end">
|
|
||||||
<Button
|
|
||||||
colorScheme="blue"
|
|
||||||
onClick={() => saveUser()}
|
|
||||||
isLoading={isSaving}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{user && <ApiTokensList user={user} />}
|
{user && <ApiTokensList user={user} />}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -7,20 +7,20 @@ import { AppearanceRadioGroup } from './AppearanceRadioGroup'
|
|||||||
|
|
||||||
export const UserPreferencesForm = () => {
|
export const UserPreferencesForm = () => {
|
||||||
const { setColorMode } = useColorMode()
|
const { setColorMode } = useColorMode()
|
||||||
const { saveUser, user } = useUser()
|
const { user, updateUser } = useUser()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user?.graphNavigation)
|
if (!user?.graphNavigation)
|
||||||
saveUser({ graphNavigation: GraphNavigation.TRACKPAD })
|
updateUser({ graphNavigation: GraphNavigation.TRACKPAD })
|
||||||
}, [saveUser, user?.graphNavigation])
|
}, [updateUser, user?.graphNavigation])
|
||||||
|
|
||||||
const changeGraphNavigation = async (value: string) => {
|
const changeGraphNavigation = async (value: string) => {
|
||||||
await saveUser({ graphNavigation: value as GraphNavigation })
|
updateUser({ graphNavigation: value as GraphNavigation })
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeAppearance = async (value: string) => {
|
const changeAppearance = async (value: string) => {
|
||||||
setColorMode(value)
|
setColorMode(value)
|
||||||
await saveUser({ preferredAppAppearance: value })
|
updateUser({ preferredAppAppearance: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const OnboardingModal = ({ totalTypebots }: Props) => {
|
|||||||
'/bots/onboarding.json',
|
'/bots/onboarding.json',
|
||||||
'/bots/onboarding-dark.json'
|
'/bots/onboarding-dark.json'
|
||||||
)
|
)
|
||||||
const { user, saveUser } = useUser()
|
const { user, updateUser } = useUser()
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||||
const [typebot, setTypebot] = useState<Typebot>()
|
const [typebot, setTypebot] = useState<Typebot>()
|
||||||
const confettiCanvaContainer = useRef<HTMLCanvasElement | null>(null)
|
const confettiCanvaContainer = useRef<HTMLCanvasElement | null>(null)
|
||||||
@@ -88,15 +88,15 @@ export const OnboardingModal = ({ totalTypebots }: Props) => {
|
|||||||
const isCompany = answer.variableId === 'cl126jqww000w2e6dq9yv4ifq'
|
const isCompany = answer.variableId === 'cl126jqww000w2e6dq9yv4ifq'
|
||||||
const isCategories = answer.variableId === 'cl126mo3t001b2e6dvyi16bkd'
|
const isCategories = answer.variableId === 'cl126mo3t001b2e6dvyi16bkd'
|
||||||
const isOtherCategories = answer.variableId === 'cl126q38p001q2e6d0hj23f6b'
|
const isOtherCategories = answer.variableId === 'cl126q38p001q2e6d0hj23f6b'
|
||||||
if (isName) saveUser({ name: answer.content })
|
if (isName) updateUser({ name: answer.content })
|
||||||
if (isCompany) saveUser({ company: answer.content })
|
if (isCompany) updateUser({ company: answer.content })
|
||||||
if (isCategories) {
|
if (isCategories) {
|
||||||
const onboardingCategories = answer.content.split(', ')
|
const onboardingCategories = answer.content.split(', ')
|
||||||
saveUser({ onboardingCategories })
|
updateUser({ onboardingCategories })
|
||||||
setChosenCategories(onboardingCategories)
|
setChosenCategories(onboardingCategories)
|
||||||
}
|
}
|
||||||
if (isOtherCategories)
|
if (isOtherCategories)
|
||||||
saveUser({
|
updateUser({
|
||||||
onboardingCategories: [...chosenCategories, answer.content],
|
onboardingCategories: [...chosenCategories, answer.content],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { isDefined, isNotDefined, omit } from 'utils'
|
import { isDefined, isNotDefined, omit } from 'utils'
|
||||||
|
import { edgesAction, EdgesActions } from './actions/edges'
|
||||||
|
import { itemsAction, ItemsActions } from './actions/items'
|
||||||
import { GroupsActions, groupsActions } from './actions/groups'
|
import { GroupsActions, groupsActions } from './actions/groups'
|
||||||
import { blocksAction, BlocksActions } from './actions/blocks'
|
import { blocksAction, BlocksActions } from './actions/blocks'
|
||||||
import { variablesAction, VariablesActions } from './actions/variables'
|
import { variablesAction, VariablesActions } from './actions/variables'
|
||||||
import { edgesAction, EdgesActions } from './actions/edges'
|
|
||||||
import { itemsAction, ItemsActions } from './actions/items'
|
|
||||||
import { dequal } from 'dequal'
|
import { dequal } from 'dequal'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { useToast } from '@/hooks/useToast'
|
import { useToast } from '@/hooks/useToast'
|
||||||
@@ -30,17 +30,19 @@ import useUndo from '../../hooks/useUndo'
|
|||||||
import { useLinkedTypebots } from '@/hooks/useLinkedTypebots'
|
import { useLinkedTypebots } from '@/hooks/useLinkedTypebots'
|
||||||
import { updateTypebotQuery } from '../../queries/updateTypebotQuery'
|
import { updateTypebotQuery } from '../../queries/updateTypebotQuery'
|
||||||
import { preventUserFromRefreshing } from '@/utils/helpers'
|
import { preventUserFromRefreshing } from '@/utils/helpers'
|
||||||
import { updatePublishedTypebotQuery } from '@/features/publish'
|
|
||||||
import { saveWebhookQuery } from '@/features/blocks/integrations/webhook/queries/saveWebhookQuery'
|
|
||||||
import {
|
import {
|
||||||
createPublishedTypebotQuery,
|
createPublishedTypebotQuery,
|
||||||
|
updatePublishedTypebotQuery,
|
||||||
deletePublishedTypebotQuery,
|
deletePublishedTypebotQuery,
|
||||||
|
} from '@/features/publish/queries'
|
||||||
|
import { saveWebhookQuery } from '@/features/blocks/integrations/webhook/queries/saveWebhookQuery'
|
||||||
|
import {
|
||||||
checkIfTypebotsAreEqual,
|
checkIfTypebotsAreEqual,
|
||||||
checkIfPublished,
|
checkIfPublished,
|
||||||
parseTypebotToPublicTypebot,
|
parseTypebotToPublicTypebot,
|
||||||
parseDefaultPublicId,
|
parseDefaultPublicId,
|
||||||
parsePublicTypebotToTypebot,
|
parsePublicTypebotToTypebot,
|
||||||
} from '@/features/publish'
|
} from '@/features/publish/utils'
|
||||||
import { useAutoSave } from '@/hooks/useAutoSave'
|
import { useAutoSave } from '@/hooks/useAutoSave'
|
||||||
|
|
||||||
const autoSaveTimeout = 10000
|
const autoSaveTimeout = 10000
|
||||||
@@ -60,6 +62,7 @@ type UpdateTypebotPayload = Partial<{
|
|||||||
export type SetTypebot = (
|
export type SetTypebot = (
|
||||||
newPresent: Typebot | ((current: Typebot) => Typebot)
|
newPresent: Typebot | ((current: Typebot) => Typebot)
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
const typebotContext = createContext<
|
const typebotContext = createContext<
|
||||||
{
|
{
|
||||||
typebot?: Typebot
|
typebot?: Typebot
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
DraggableBlockType,
|
DraggableBlockType,
|
||||||
BlockIndices,
|
BlockIndices,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { removeEmptyGroups } from './groups'
|
|
||||||
import { WritableDraft } from 'immer/dist/types/types-external'
|
import { WritableDraft } from 'immer/dist/types/types-external'
|
||||||
import { SetTypebot } from '../TypebotProvider'
|
import { SetTypebot } from '../TypebotProvider'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
@@ -13,7 +12,7 @@ import { cleanUpEdgeDraft, deleteEdgeDraft } from './edges'
|
|||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { byId, isWebhookBlock, blockHasItems } from 'utils'
|
import { byId, isWebhookBlock, blockHasItems } from 'utils'
|
||||||
import { duplicateItemDraft } from './items'
|
import { duplicateItemDraft } from './items'
|
||||||
import { parseNewBlock } from '@/features/graph'
|
import { parseNewBlock } from '@/features/graph/utils'
|
||||||
|
|
||||||
export type BlocksActions = {
|
export type BlocksActions = {
|
||||||
createBlock: (
|
createBlock: (
|
||||||
@@ -30,7 +29,7 @@ export type BlocksActions = {
|
|||||||
deleteBlock: (indices: BlockIndices) => void
|
deleteBlock: (indices: BlockIndices) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const blocksAction = (setTypebot: SetTypebot): BlocksActions => ({
|
export const blocksAction = (setTypebot: SetTypebot): BlocksActions => ({
|
||||||
createBlock: (
|
createBlock: (
|
||||||
groupId: string,
|
groupId: string,
|
||||||
block: DraggableBlock | DraggableBlockType,
|
block: DraggableBlock | DraggableBlockType,
|
||||||
@@ -78,7 +77,7 @@ const removeBlockFromGroup =
|
|||||||
typebot.groups[groupIndex].blocks.splice(blockIndex, 1)
|
typebot.groups[groupIndex].blocks.splice(blockIndex, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const createBlockDraft = (
|
export const createBlockDraft = (
|
||||||
typebot: WritableDraft<Typebot>,
|
typebot: WritableDraft<Typebot>,
|
||||||
block: DraggableBlock | DraggableBlockType,
|
block: DraggableBlock | DraggableBlockType,
|
||||||
groupId: string,
|
groupId: string,
|
||||||
@@ -134,7 +133,7 @@ const moveBlockToGroup = (
|
|||||||
typebot.groups[groupIndex].blocks.splice(blockIndex ?? 0, 0, newBlock)
|
typebot.groups[groupIndex].blocks.splice(blockIndex ?? 0, 0, newBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateBlockDraft =
|
export const duplicateBlockDraft =
|
||||||
(groupId: string) =>
|
(groupId: string) =>
|
||||||
(block: Block): Block => {
|
(block: Block): Block => {
|
||||||
const blockId = cuid()
|
const blockId = cuid()
|
||||||
@@ -162,4 +161,19 @@ const duplicateBlockDraft =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { blocksAction, createBlockDraft, duplicateBlockDraft }
|
export const deleteGroupDraft =
|
||||||
|
(typebot: WritableDraft<Typebot>) => (groupIndex: number) => {
|
||||||
|
cleanUpEdgeDraft(typebot, typebot.groups[groupIndex].id)
|
||||||
|
typebot.groups.splice(groupIndex, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const removeEmptyGroups = (typebot: WritableDraft<Typebot>) => {
|
||||||
|
const emptyGroupsIndices = typebot.groups.reduce<number[]>(
|
||||||
|
(arr, group, idx) => {
|
||||||
|
group.blocks.length === 0 && arr.push(idx)
|
||||||
|
return arr
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
emptyGroupsIndices.forEach(deleteGroupDraft(typebot))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
import { WritableDraft } from 'immer/dist/internal'
|
import { Group, DraggableBlock, DraggableBlockType, BlockIndices } from 'models'
|
||||||
import {
|
|
||||||
Group,
|
|
||||||
DraggableBlock,
|
|
||||||
DraggableBlockType,
|
|
||||||
BlockIndices,
|
|
||||||
Typebot,
|
|
||||||
} from 'models'
|
|
||||||
import { SetTypebot } from '../TypebotProvider'
|
import { SetTypebot } from '../TypebotProvider'
|
||||||
import { cleanUpEdgeDraft } from './edges'
|
import {
|
||||||
import { createBlockDraft, duplicateBlockDraft } from './blocks'
|
deleteGroupDraft,
|
||||||
|
createBlockDraft,
|
||||||
|
duplicateBlockDraft,
|
||||||
|
} from './blocks'
|
||||||
import { Coordinates } from '@/features/graph'
|
import { Coordinates } from '@/features/graph'
|
||||||
|
|
||||||
export type GroupsActions = {
|
export type GroupsActions = {
|
||||||
@@ -82,21 +78,4 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
const deleteGroupDraft =
|
export { groupsActions }
|
||||||
(typebot: WritableDraft<Typebot>) => (groupIndex: number) => {
|
|
||||||
cleanUpEdgeDraft(typebot, typebot.groups[groupIndex].id)
|
|
||||||
typebot.groups.splice(groupIndex, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeEmptyGroups = (typebot: WritableDraft<Typebot>) => {
|
|
||||||
const emptyGroupsIndices = typebot.groups.reduce<number[]>(
|
|
||||||
(arr, group, idx) => {
|
|
||||||
group.blocks.length === 0 && arr.push(idx)
|
|
||||||
return arr
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
emptyGroupsIndices.forEach(deleteGroupDraft(typebot))
|
|
||||||
}
|
|
||||||
|
|
||||||
export { groupsActions, removeEmptyGroups }
|
|
||||||
|
|||||||
@@ -13,19 +13,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'no-restricted-imports': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
patterns: [
|
|
||||||
'*/src/*',
|
|
||||||
'src/*',
|
|
||||||
'*/src',
|
|
||||||
'@/features/*/*',
|
|
||||||
'!@/features/blocks/*',
|
|
||||||
'!@/features/*/api',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@typescript-eslint/no-namespace': 'off',
|
'@typescript-eslint/no-namespace': 'off',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user