feat(editor): ✨ Add auto save
This commit is contained in:
@ -26,7 +26,7 @@ export const TypebotHeader = () => {
|
|||||||
const handleNameSubmit = (name: string) => updateTypebot({ name })
|
const handleNameSubmit = (name: string) => updateTypebot({ name })
|
||||||
|
|
||||||
const handlePreviewClick = async () => {
|
const handlePreviewClick = async () => {
|
||||||
await save()
|
save().then()
|
||||||
setRightPanel(RightPanel.PREVIEW)
|
setRightPanel(RightPanel.PREVIEW)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,9 @@ import { choiceItemsAction, ChoiceItemsActions } from './actions/choiceItems'
|
|||||||
import { variablesAction, VariablesActions } from './actions/variables'
|
import { variablesAction, VariablesActions } from './actions/variables'
|
||||||
import { edgesAction, EdgesActions } from './actions/edges'
|
import { edgesAction, EdgesActions } from './actions/edges'
|
||||||
import { webhooksAction, WebhooksAction } from './actions/webhooks'
|
import { webhooksAction, WebhooksAction } from './actions/webhooks'
|
||||||
|
import { useDebounce } from 'use-debounce'
|
||||||
|
|
||||||
|
const autoSaveTimeout = 10000
|
||||||
|
|
||||||
type UpdateTypebotPayload = Partial<{
|
type UpdateTypebotPayload = Partial<{
|
||||||
theme: Theme
|
theme: Theme
|
||||||
@ -85,6 +88,12 @@ export const TypebotContext = ({
|
|||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [debouncedLocalTypebot] = useDebounce(localTypebot, autoSaveTimeout)
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasUnsavedChanges) saveTypebot()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [debouncedLocalTypebot])
|
||||||
|
|
||||||
const [localPublishedTypebot, setLocalPublishedTypebot] =
|
const [localPublishedTypebot, setLocalPublishedTypebot] =
|
||||||
useState<PublicTypebot>()
|
useState<PublicTypebot>()
|
||||||
const [isSavingLoading, setIsSavingLoading] = useState(false)
|
const [isSavingLoading, setIsSavingLoading] = useState(false)
|
||||||
|
@ -9,14 +9,22 @@ import 'assets/styles/plate.css'
|
|||||||
import 'focus-visible/dist/focus-visible'
|
import 'focus-visible/dist/focus-visible'
|
||||||
import 'assets/styles/submissionsTable.css'
|
import 'assets/styles/submissionsTable.css'
|
||||||
import 'assets/styles/codeMirror.css'
|
import 'assets/styles/codeMirror.css'
|
||||||
|
import { UserContext } from 'contexts/UserContext'
|
||||||
|
import { TypebotContext } from 'contexts/TypebotContext'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
const App = ({ Component, pageProps }: AppProps) => {
|
const App = ({ Component, pageProps }: AppProps) => {
|
||||||
useRouterProgressBar()
|
useRouterProgressBar()
|
||||||
|
const { query } = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChakraProvider theme={customTheme}>
|
<ChakraProvider theme={customTheme}>
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
|
<UserContext>
|
||||||
|
<TypebotContext typebotId={query.typebotId?.toString()}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
|
</TypebotContext>
|
||||||
|
</UserContext>
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
</ChakraProvider>
|
</ChakraProvider>
|
||||||
)
|
)
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
import { Stack } from '@chakra-ui/react'
|
import { Stack } from '@chakra-ui/react'
|
||||||
import { AccountHeader } from 'components/account/AccountHeader'
|
import { AccountHeader } from 'components/account/AccountHeader'
|
||||||
import { Seo } from 'components/Seo'
|
import { Seo } from 'components/Seo'
|
||||||
import { UserContext } from 'contexts/UserContext'
|
|
||||||
import { AccountContent } from 'layouts/account/AccountContent'
|
import { AccountContent } from 'layouts/account/AccountContent'
|
||||||
|
|
||||||
const AccountSubscriptionPage = () => {
|
const AccountSubscriptionPage = () => (
|
||||||
return (
|
|
||||||
<UserContext>
|
|
||||||
<Seo title="My account" />{' '}
|
|
||||||
<Stack>
|
<Stack>
|
||||||
|
<Seo title="My account" />
|
||||||
<AccountHeader />
|
<AccountHeader />
|
||||||
<AccountContent />
|
<AccountContent />
|
||||||
</Stack>
|
</Stack>
|
||||||
</UserContext>
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
export default AccountSubscriptionPage
|
export default AccountSubscriptionPage
|
||||||
|
@ -3,12 +3,14 @@ import { SignInForm } from 'components/auth/SignInForm'
|
|||||||
import { Heading, VStack } from '@chakra-ui/react'
|
import { Heading, VStack } from '@chakra-ui/react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
|
||||||
const RegisterPage = () => {
|
const RegisterPage = () => {
|
||||||
const { query } = useRouter()
|
const { query } = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack spacing={4} h="100vh" justifyContent="center">
|
<VStack spacing={4} h="100vh" justifyContent="center">
|
||||||
|
<Seo title="Register" />
|
||||||
<Heading>Create an account</Heading>
|
<Heading>Create an account</Heading>
|
||||||
<AuthSwitcher type="register" />
|
<AuthSwitcher type="register" />
|
||||||
<SignInForm defaultEmail={query.g?.toString()} />
|
<SignInForm defaultEmail={query.g?.toString()} />
|
||||||
|
@ -2,10 +2,12 @@ import { AuthSwitcher } from 'components/auth/AuthSwitcher'
|
|||||||
import { SignInForm } from 'components/auth/SignInForm'
|
import { SignInForm } from 'components/auth/SignInForm'
|
||||||
import { Heading, VStack } from '@chakra-ui/react'
|
import { Heading, VStack } from '@chakra-ui/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
|
||||||
const SignInPage = () => {
|
const SignInPage = () => {
|
||||||
return (
|
return (
|
||||||
<VStack spacing={4} h="100vh" justifyContent="center">
|
<VStack spacing={4} h="100vh" justifyContent="center">
|
||||||
|
<Seo title="Sign in" />
|
||||||
<Heading>Sign in</Heading>
|
<Heading>Sign in</Heading>
|
||||||
<AuthSwitcher type="signin" />
|
<AuthSwitcher type="signin" />
|
||||||
<SignInForm />
|
<SignInForm />
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
|
||||||
import { Board } from 'components/board/Board'
|
|
||||||
import { Seo } from 'components/Seo'
|
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
|
||||||
import { EditorContext } from 'contexts/EditorContext'
|
|
||||||
import { TypebotContext } from 'contexts/TypebotContext/TypebotContext'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import { KBarProvider } from 'kbar'
|
|
||||||
import React from 'react'
|
|
||||||
import { actions } from 'libs/kbar'
|
|
||||||
import { KBar } from 'components/shared/KBar'
|
|
||||||
import { UserContext } from 'contexts/UserContext'
|
|
||||||
|
|
||||||
const TypebotEditPage = () => {
|
|
||||||
const { query } = useRouter()
|
|
||||||
return (
|
|
||||||
<UserContext>
|
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
|
||||||
<Seo title="Editor" />
|
|
||||||
<EditorContext>
|
|
||||||
<KBarProvider actions={actions}>
|
|
||||||
<KBar />
|
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
|
||||||
<TypebotHeader />
|
|
||||||
<Board />
|
|
||||||
</Flex>
|
|
||||||
</KBarProvider>
|
|
||||||
</EditorContext>
|
|
||||||
</TypebotContext>
|
|
||||||
</UserContext>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TypebotEditPage
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
|
||||||
import { ResultsContent } from 'layouts/results/ResultsContent'
|
|
||||||
import { Seo } from 'components/Seo'
|
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
|
||||||
import { TypebotContext } from 'contexts/TypebotContext/TypebotContext'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import React from 'react'
|
|
||||||
import { UserContext } from 'contexts/UserContext'
|
|
||||||
|
|
||||||
const ResultsPage = () => {
|
|
||||||
const { query } = useRouter()
|
|
||||||
return (
|
|
||||||
<UserContext>
|
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
|
||||||
<Seo title="Share" />
|
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
|
||||||
<TypebotHeader />
|
|
||||||
<ResultsContent />
|
|
||||||
</Flex>
|
|
||||||
</TypebotContext>
|
|
||||||
</UserContext>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ResultsPage
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
|
||||||
import { ResultsContent } from 'layouts/results/ResultsContent'
|
|
||||||
import { Seo } from 'components/Seo'
|
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
|
||||||
import { TypebotContext } from 'contexts/TypebotContext/TypebotContext'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import React from 'react'
|
|
||||||
import { UserContext } from 'contexts/UserContext'
|
|
||||||
|
|
||||||
const AnalyticsPage = () => {
|
|
||||||
const { query } = useRouter()
|
|
||||||
return (
|
|
||||||
<UserContext>
|
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
|
||||||
<Seo title="Analytics" />
|
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
|
||||||
<TypebotHeader />
|
|
||||||
<ResultsContent />
|
|
||||||
</Flex>
|
|
||||||
</TypebotContext>
|
|
||||||
</UserContext>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AnalyticsPage
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
|
||||||
import { Seo } from 'components/Seo'
|
|
||||||
import { SettingsContent } from 'components/settings/SettingsContent'
|
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
|
||||||
import { TypebotContext } from 'contexts/TypebotContext/TypebotContext'
|
|
||||||
import { UserContext } from 'contexts/UserContext'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
const SettingsPage = () => {
|
|
||||||
const { query } = useRouter()
|
|
||||||
return (
|
|
||||||
<UserContext>
|
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
|
||||||
<Seo title="Settings" />
|
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
|
||||||
<TypebotHeader />
|
|
||||||
<SettingsContent />
|
|
||||||
</Flex>
|
|
||||||
</TypebotContext>
|
|
||||||
</UserContext>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SettingsPage
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
|
||||||
import { Seo } from 'components/Seo'
|
|
||||||
import { ShareContent } from 'components/share/ShareContent'
|
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
|
||||||
import { TypebotContext } from 'contexts/TypebotContext/TypebotContext'
|
|
||||||
import { UserContext } from 'contexts/UserContext'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
const SharePage = () => {
|
|
||||||
const { query } = useRouter()
|
|
||||||
return (
|
|
||||||
<UserContext>
|
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
|
||||||
<Seo title="Share" />
|
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
|
||||||
<TypebotHeader />
|
|
||||||
<ShareContent />
|
|
||||||
</Flex>
|
|
||||||
</TypebotContext>
|
|
||||||
</UserContext>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SharePage
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
|
||||||
import { Seo } from 'components/Seo'
|
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
|
||||||
import { ThemeContent } from 'components/theme/ThemeContent'
|
|
||||||
import { TypebotContext } from 'contexts/TypebotContext/TypebotContext'
|
|
||||||
import { UserContext } from 'contexts/UserContext'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
const ThemePage = () => {
|
|
||||||
const { query } = useRouter()
|
|
||||||
return (
|
|
||||||
<UserContext>
|
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
|
||||||
<Seo title="Theme" />
|
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
|
||||||
<TypebotHeader />
|
|
||||||
<ThemeContent />
|
|
||||||
</Flex>
|
|
||||||
</TypebotContext>
|
|
||||||
</UserContext>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ThemePage
|
|
23
apps/builder/pages/typebots/[typebotId]/edit.tsx
Normal file
23
apps/builder/pages/typebots/[typebotId]/edit.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Flex } from '@chakra-ui/layout'
|
||||||
|
import { Board } from 'components/board/Board'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
|
import { EditorContext } from 'contexts/EditorContext'
|
||||||
|
import { KBarProvider } from 'kbar'
|
||||||
|
import React from 'react'
|
||||||
|
import { actions } from 'libs/kbar'
|
||||||
|
import { KBar } from 'components/shared/KBar'
|
||||||
|
|
||||||
|
const TypebotEditPage = () => (
|
||||||
|
<EditorContext>
|
||||||
|
<Seo title="Editor" />
|
||||||
|
<KBarProvider actions={actions}>
|
||||||
|
<KBar />
|
||||||
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
|
<TypebotHeader />
|
||||||
|
<Board />
|
||||||
|
</Flex>
|
||||||
|
</KBarProvider>
|
||||||
|
</EditorContext>
|
||||||
|
)
|
||||||
|
export default TypebotEditPage
|
15
apps/builder/pages/typebots/[typebotId]/results.tsx
Normal file
15
apps/builder/pages/typebots/[typebotId]/results.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Flex } from '@chakra-ui/layout'
|
||||||
|
import { ResultsContent } from 'layouts/results/ResultsContent'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const ResultsPage = () => (
|
||||||
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
|
<Seo title="Share" />
|
||||||
|
<TypebotHeader />
|
||||||
|
<ResultsContent />
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ResultsPage
|
@ -0,0 +1,15 @@
|
|||||||
|
import { Flex } from '@chakra-ui/layout'
|
||||||
|
import { ResultsContent } from 'layouts/results/ResultsContent'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const AnalyticsPage = () => (
|
||||||
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
|
<Seo title="Analytics" />
|
||||||
|
<TypebotHeader />
|
||||||
|
<ResultsContent />
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default AnalyticsPage
|
15
apps/builder/pages/typebots/[typebotId]/settings.tsx
Normal file
15
apps/builder/pages/typebots/[typebotId]/settings.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Flex } from '@chakra-ui/layout'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
import { SettingsContent } from 'components/settings/SettingsContent'
|
||||||
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const SettingsPage = () => (
|
||||||
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
|
<Seo title="Settings" />
|
||||||
|
<TypebotHeader />
|
||||||
|
<SettingsContent />
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default SettingsPage
|
15
apps/builder/pages/typebots/[typebotId]/share.tsx
Normal file
15
apps/builder/pages/typebots/[typebotId]/share.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Flex } from '@chakra-ui/layout'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
import { ShareContent } from 'components/share/ShareContent'
|
||||||
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const SharePage = () => (
|
||||||
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
|
<Seo title="Share" />
|
||||||
|
<TypebotHeader />
|
||||||
|
<ShareContent />
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default SharePage
|
15
apps/builder/pages/typebots/[typebotId]/theme.tsx
Normal file
15
apps/builder/pages/typebots/[typebotId]/theme.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Flex } from '@chakra-ui/layout'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
|
import { ThemeContent } from 'components/theme/ThemeContent'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const ThemePage = () => (
|
||||||
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
|
<Seo title="Theme" />
|
||||||
|
<TypebotHeader />
|
||||||
|
<ThemeContent />
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ThemePage
|
@ -2,19 +2,14 @@ import React from 'react'
|
|||||||
import { Stack } from '@chakra-ui/react'
|
import { Stack } from '@chakra-ui/react'
|
||||||
import { Seo } from 'components/Seo'
|
import { Seo } from 'components/Seo'
|
||||||
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||||
import { UserContext } from 'contexts/UserContext'
|
|
||||||
import { TemplatesContent } from 'layouts/dashboard/TemplatesContent'
|
import { TemplatesContent } from 'layouts/dashboard/TemplatesContent'
|
||||||
|
|
||||||
const TemplatesPage = () => {
|
const TemplatesPage = () => (
|
||||||
return (
|
|
||||||
<UserContext>
|
|
||||||
<Seo title="Templates" />
|
|
||||||
<Stack>
|
<Stack>
|
||||||
|
<Seo title="Templates" />
|
||||||
<DashboardHeader />
|
<DashboardHeader />
|
||||||
<TemplatesContent />
|
<TemplatesContent />
|
||||||
</Stack>
|
</Stack>
|
||||||
</UserContext>
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TemplatesPage
|
export default TemplatesPage
|
||||||
|
@ -6,7 +6,6 @@ import { FolderContent } from 'components/dashboard/FolderContent'
|
|||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useFolderContent } from 'services/folders'
|
import { useFolderContent } from 'services/folders'
|
||||||
import { Spinner, useToast } from '@chakra-ui/react'
|
import { Spinner, useToast } from '@chakra-ui/react'
|
||||||
import { UserContext } from 'contexts/UserContext'
|
|
||||||
|
|
||||||
const FolderPage = () => {
|
const FolderPage = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -27,9 +26,8 @@ const FolderPage = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserContext>
|
|
||||||
<Seo title="My typebots" />
|
|
||||||
<Stack>
|
<Stack>
|
||||||
|
<Seo title="My typebots" />
|
||||||
<DashboardHeader />
|
<DashboardHeader />
|
||||||
{!folder ? (
|
{!folder ? (
|
||||||
<Flex flex="1">
|
<Flex flex="1">
|
||||||
@ -39,7 +37,6 @@ const FolderPage = () => {
|
|||||||
<FolderContent folder={folder} />
|
<FolderContent folder={folder} />
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</UserContext>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user