feat: 🔥 Remove save button
This commit is contained in:
@ -37,7 +37,7 @@ export const GoogleSheetsSettingsBody = ({
|
|||||||
onOptionsChange,
|
onOptionsChange,
|
||||||
stepId,
|
stepId,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { save, hasUnsavedChanges } = useTypebot()
|
const { save } = useTypebot()
|
||||||
const { sheets, isLoading } = useSheets({
|
const { sheets, isLoading } = useSheets({
|
||||||
credentialsId: options?.credentialsId,
|
credentialsId: options?.credentialsId,
|
||||||
spreadsheetId: options?.spreadsheetId,
|
spreadsheetId: options?.spreadsheetId,
|
||||||
@ -83,10 +83,7 @@ export const GoogleSheetsSettingsBody = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleCreateNewClick = async () => {
|
const handleCreateNewClick = async () => {
|
||||||
if (hasUnsavedChanges) {
|
await save()
|
||||||
const errorToastId = await save()
|
|
||||||
if (errorToastId) return
|
|
||||||
}
|
|
||||||
const linkElement = document.createElement('a')
|
const linkElement = document.createElement('a')
|
||||||
linkElement.href = getGoogleSheetsConsentScreenUrl(
|
linkElement.href = getGoogleSheetsConsentScreenUrl(
|
||||||
window.location.href,
|
window.location.href,
|
||||||
|
@ -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 && (
|
|
||||||
<Text fontSize="sm" color="gray.500">
|
|
||||||
Unsaved changes
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<Tooltip label="Save changes">
|
|
||||||
<IconButton
|
|
||||||
isDisabled={!hasUnsavedChanges}
|
|
||||||
onClick={handleSaveClick}
|
|
||||||
isLoading={isSavingLoading}
|
|
||||||
icon={
|
|
||||||
hasUnsavedChanges ? <SaveIcon /> : <CheckIcon color="green.400" />
|
|
||||||
}
|
|
||||||
aria-label={hasUnsavedChanges ? 'Save' : 'Saved'}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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 { ChevronLeftIcon, RedoIcon, UndoIcon } from 'assets/icons'
|
||||||
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
|
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
|
||||||
import { RightPanel, useEditor } from 'contexts/EditorContext'
|
import { RightPanel, useEditor } from 'contexts/EditorContext'
|
||||||
@ -7,7 +15,6 @@ import { useRouter } from 'next/router'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { PublishButton } from '../buttons/PublishButton'
|
import { PublishButton } from '../buttons/PublishButton'
|
||||||
import { EditableTypebotName } from './EditableTypebotName'
|
import { EditableTypebotName } from './EditableTypebotName'
|
||||||
import { SaveButton } from './SaveButton'
|
|
||||||
|
|
||||||
export const headerHeight = 56
|
export const headerHeight = 56
|
||||||
|
|
||||||
@ -21,7 +28,7 @@ export const TypebotHeader = () => {
|
|||||||
redo,
|
redo,
|
||||||
canUndo,
|
canUndo,
|
||||||
canRedo,
|
canRedo,
|
||||||
publishedTypebot,
|
isSavingLoading,
|
||||||
} = useTypebot()
|
} = useTypebot()
|
||||||
const { setRightPanel } = useEditor()
|
const { setRightPanel } = useEditor()
|
||||||
|
|
||||||
@ -96,7 +103,13 @@ export const TypebotHeader = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
<Flex pos="absolute" left="1rem" justify="center" align="center">
|
<HStack
|
||||||
|
pos="absolute"
|
||||||
|
left="1rem"
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
spacing="6"
|
||||||
|
>
|
||||||
<HStack alignItems="center">
|
<HStack alignItems="center">
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Back"
|
aria-label="Back"
|
||||||
@ -129,10 +142,17 @@ export const TypebotHeader = () => {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
{isSavingLoading && (
|
||||||
|
<HStack>
|
||||||
|
<Spinner speed="0.7s" size="sm" color="gray.400" />
|
||||||
|
<Text fontSize="sm" color="gray.400">
|
||||||
|
Saving...
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
|
||||||
<HStack right="40px" pos="absolute">
|
<HStack right="40px" pos="absolute">
|
||||||
<SaveButton />
|
|
||||||
<Button onClick={handlePreviewClick}>Preview</Button>
|
<Button onClick={handlePreviewClick}>Preview</Button>
|
||||||
<PublishButton />
|
<PublishButton />
|
||||||
</HStack>
|
</HStack>
|
||||||
|
@ -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 { PublicTypebot, Settings, Theme, Typebot } from 'models'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import {
|
import {
|
||||||
@ -32,7 +32,8 @@ import useUndo from 'services/utils/useUndo'
|
|||||||
import { useDebounce } from 'use-debounce'
|
import { useDebounce } from 'use-debounce'
|
||||||
import { itemsAction, ItemsActions } from './actions/items'
|
import { itemsAction, ItemsActions } from './actions/items'
|
||||||
import { generate } from 'short-uuid'
|
import { generate } from 'short-uuid'
|
||||||
const autoSaveTimeout = 40000
|
import { deepEqual } from 'fast-equals'
|
||||||
|
const autoSaveTimeout = 10000
|
||||||
|
|
||||||
type UpdateTypebotPayload = Partial<{
|
type UpdateTypebotPayload = Partial<{
|
||||||
theme: Theme
|
theme: Theme
|
||||||
@ -49,9 +50,8 @@ const typebotContext = createContext<
|
|||||||
publishedTypebot?: PublicTypebot
|
publishedTypebot?: PublicTypebot
|
||||||
isPublished: boolean
|
isPublished: boolean
|
||||||
isPublishing: boolean
|
isPublishing: boolean
|
||||||
hasUnsavedChanges: boolean
|
|
||||||
isSavingLoading: boolean
|
isSavingLoading: boolean
|
||||||
save: () => Promise<ToastId | undefined>
|
save: () => Promise<void>
|
||||||
undo: () => void
|
undo: () => void
|
||||||
redo: () => void
|
redo: () => void
|
||||||
canRedo: boolean
|
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 [
|
const [
|
||||||
{ present: localTypebot },
|
{ present: localTypebot },
|
||||||
{
|
{
|
||||||
@ -106,12 +113,16 @@ export const TypebotContext = ({
|
|||||||
] = useUndo<Typebot | undefined>(undefined)
|
] = useUndo<Typebot | undefined>(undefined)
|
||||||
|
|
||||||
const saveTypebot = async () => {
|
const saveTypebot = async () => {
|
||||||
|
if (deepEqual(typebot, localTypebot)) return
|
||||||
const typebotToSave = currentTypebotRef.current
|
const typebotToSave = currentTypebotRef.current
|
||||||
if (!typebotToSave) return
|
if (!typebotToSave) return
|
||||||
setIsSavingLoading(true)
|
setIsSavingLoading(true)
|
||||||
const { error } = await updateTypebot(typebotToSave.id, typebotToSave)
|
const { error } = await updateTypebot(typebotToSave.id, typebotToSave)
|
||||||
setIsSavingLoading(false)
|
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 })
|
mutate({ typebot: typebotToSave })
|
||||||
window.removeEventListener('beforeunload', preventUserFromRefreshing)
|
window.removeEventListener('beforeunload', preventUserFromRefreshing)
|
||||||
}
|
}
|
||||||
@ -130,18 +141,9 @@ export const TypebotContext = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasUnsavedChanges = useMemo(
|
|
||||||
() =>
|
|
||||||
isDefined(typebot) &&
|
|
||||||
isDefined(localTypebot) &&
|
|
||||||
!checkIfTypebotsAreEqual(localTypebot, typebot),
|
|
||||||
[typebot, localTypebot]
|
|
||||||
)
|
|
||||||
|
|
||||||
useAutoSave({
|
useAutoSave({
|
||||||
handler: saveTypebot,
|
handler: saveTypebot,
|
||||||
item: localTypebot,
|
item: localTypebot,
|
||||||
canSave: hasUnsavedChanges,
|
|
||||||
debounceTimeout: autoSaveTimeout,
|
debounceTimeout: autoSaveTimeout,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -257,7 +259,6 @@ export const TypebotContext = ({
|
|||||||
value={{
|
value={{
|
||||||
typebot: localTypebot,
|
typebot: localTypebot,
|
||||||
publishedTypebot,
|
publishedTypebot,
|
||||||
hasUnsavedChanges,
|
|
||||||
isSavingLoading,
|
isSavingLoading,
|
||||||
save: saveTypebot,
|
save: saveTypebot,
|
||||||
undo,
|
undo,
|
||||||
@ -306,18 +307,24 @@ export const useFetchedTypebot = ({
|
|||||||
const useAutoSave = <T,>({
|
const useAutoSave = <T,>({
|
||||||
handler,
|
handler,
|
||||||
item,
|
item,
|
||||||
canSave,
|
|
||||||
debounceTimeout,
|
debounceTimeout,
|
||||||
}: {
|
}: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
handler: (item?: T) => Promise<any>
|
handler: (item?: T) => Promise<any>
|
||||||
item?: T
|
item?: T
|
||||||
canSave: boolean
|
|
||||||
debounceTimeout: number
|
debounceTimeout: number
|
||||||
}) => {
|
}) => {
|
||||||
const [debouncedItem] = useDebounce(item, debounceTimeout)
|
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(() => {
|
return useEffect(() => {
|
||||||
if (canSave) handler(item)
|
handler(item)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [debouncedItem])
|
}, [debouncedItem])
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user