diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx
index a4a31a228..1df22e7d1 100644
--- a/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx
+++ b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx
@@ -37,7 +37,7 @@ export const GoogleSheetsSettingsBody = ({
onOptionsChange,
stepId,
}: Props) => {
- const { save, hasUnsavedChanges } = useTypebot()
+ const { save } = useTypebot()
const { sheets, isLoading } = useSheets({
credentialsId: options?.credentialsId,
spreadsheetId: options?.spreadsheetId,
@@ -83,10 +83,7 @@ export const GoogleSheetsSettingsBody = ({
}
const handleCreateNewClick = async () => {
- if (hasUnsavedChanges) {
- const errorToastId = await save()
- if (errorToastId) return
- }
+ await save()
const linkElement = document.createElement('a')
linkElement.href = getGoogleSheetsConsentScreenUrl(
window.location.href,
diff --git a/apps/builder/components/shared/TypebotHeader/SaveButton.tsx b/apps/builder/components/shared/TypebotHeader/SaveButton.tsx
deleted file mode 100644
index 3c5005bef..000000000
--- a/apps/builder/components/shared/TypebotHeader/SaveButton.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { IconButton, Text, Tooltip } from '@chakra-ui/react'
-import { CheckIcon, SaveIcon } from 'assets/icons'
-import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
-import React from 'react'
-
-export const SaveButton = () => {
- const { save, isSavingLoading, hasUnsavedChanges } = useTypebot()
-
- const handleSaveClick = async () => {
- await save()
- }
-
- return (
- <>
- {hasUnsavedChanges && (
-
- Unsaved changes
-
- )}
-
- :
- }
- aria-label={hasUnsavedChanges ? 'Save' : 'Saved'}
- />
-
- >
- )
-}
diff --git a/apps/builder/components/shared/TypebotHeader/TypebotHeader.tsx b/apps/builder/components/shared/TypebotHeader/TypebotHeader.tsx
index 5370c6077..d7469e46c 100644
--- a/apps/builder/components/shared/TypebotHeader/TypebotHeader.tsx
+++ b/apps/builder/components/shared/TypebotHeader/TypebotHeader.tsx
@@ -1,4 +1,12 @@
-import { Flex, HStack, Button, IconButton, Tooltip } from '@chakra-ui/react'
+import {
+ Flex,
+ HStack,
+ Button,
+ IconButton,
+ Tooltip,
+ Spinner,
+ Text,
+} from '@chakra-ui/react'
import { ChevronLeftIcon, RedoIcon, UndoIcon } from 'assets/icons'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { RightPanel, useEditor } from 'contexts/EditorContext'
@@ -7,7 +15,6 @@ import { useRouter } from 'next/router'
import React from 'react'
import { PublishButton } from '../buttons/PublishButton'
import { EditableTypebotName } from './EditableTypebotName'
-import { SaveButton } from './SaveButton'
export const headerHeight = 56
@@ -21,7 +28,7 @@ export const TypebotHeader = () => {
redo,
canUndo,
canRedo,
- publishedTypebot,
+ isSavingLoading,
} = useTypebot()
const { setRightPanel } = useEditor()
@@ -96,7 +103,13 @@ export const TypebotHeader = () => {
)}
-
+
{
/>
-
+ {isSavingLoading && (
+
+
+
+ Saving...
+
+
+ )}
+
-
diff --git a/apps/builder/contexts/TypebotContext/TypebotContext.tsx b/apps/builder/contexts/TypebotContext/TypebotContext.tsx
index 42de77542..0da73f3aa 100644
--- a/apps/builder/contexts/TypebotContext/TypebotContext.tsx
+++ b/apps/builder/contexts/TypebotContext/TypebotContext.tsx
@@ -1,4 +1,4 @@
-import { ToastId, useToast } from '@chakra-ui/react'
+import { useToast } from '@chakra-ui/react'
import { PublicTypebot, Settings, Theme, Typebot } from 'models'
import { useRouter } from 'next/router'
import {
@@ -32,7 +32,8 @@ import useUndo from 'services/utils/useUndo'
import { useDebounce } from 'use-debounce'
import { itemsAction, ItemsActions } from './actions/items'
import { generate } from 'short-uuid'
-const autoSaveTimeout = 40000
+import { deepEqual } from 'fast-equals'
+const autoSaveTimeout = 10000
type UpdateTypebotPayload = Partial<{
theme: Theme
@@ -49,9 +50,8 @@ const typebotContext = createContext<
publishedTypebot?: PublicTypebot
isPublished: boolean
isPublishing: boolean
- hasUnsavedChanges: boolean
isSavingLoading: boolean
- save: () => Promise
+ save: () => Promise
undo: () => void
redo: () => void
canRedo: boolean
@@ -93,6 +93,13 @@ export const TypebotContext = ({
}),
})
+ useEffect(() => {
+ if (!typebot || !localTypebot || deepEqual(typebot, localTypebot)) return
+ if (typebot?.blocks.length === localTypebot?.blocks.length)
+ setLocalTypebot({ ...typebot })
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [typebot])
+
const [
{ present: localTypebot },
{
@@ -106,12 +113,16 @@ export const TypebotContext = ({
] = useUndo(undefined)
const saveTypebot = async () => {
+ if (deepEqual(typebot, localTypebot)) return
const typebotToSave = currentTypebotRef.current
if (!typebotToSave) return
setIsSavingLoading(true)
const { error } = await updateTypebot(typebotToSave.id, typebotToSave)
setIsSavingLoading(false)
- if (error) return toast({ title: error.name, description: error.message })
+ if (error) {
+ toast({ title: error.name, description: error.message })
+ return
+ }
mutate({ typebot: typebotToSave })
window.removeEventListener('beforeunload', preventUserFromRefreshing)
}
@@ -130,18 +141,9 @@ export const TypebotContext = ({
})
}
- const hasUnsavedChanges = useMemo(
- () =>
- isDefined(typebot) &&
- isDefined(localTypebot) &&
- !checkIfTypebotsAreEqual(localTypebot, typebot),
- [typebot, localTypebot]
- )
-
useAutoSave({
handler: saveTypebot,
item: localTypebot,
- canSave: hasUnsavedChanges,
debounceTimeout: autoSaveTimeout,
})
@@ -257,7 +259,6 @@ export const TypebotContext = ({
value={{
typebot: localTypebot,
publishedTypebot,
- hasUnsavedChanges,
isSavingLoading,
save: saveTypebot,
undo,
@@ -306,18 +307,24 @@ export const useFetchedTypebot = ({
const useAutoSave = ({
handler,
item,
- canSave,
debounceTimeout,
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler: (item?: T) => Promise
item?: T
- canSave: boolean
debounceTimeout: number
}) => {
const [debouncedItem] = useDebounce(item, debounceTimeout)
+ useEffect(() => {
+ const save = () => handler(item)
+ document.addEventListener('visibilitychange', save)
+ return () => {
+ document.removeEventListener('visibilitychange', save)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
return useEffect(() => {
- if (canSave) handler(item)
+ handler(item)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedItem])
}