♿ Add an update notification popup
Appears when a new version of Typebot is available Closes #312
This commit is contained in:
12
apps/builder/src/components/MotionStack.tsx
Normal file
12
apps/builder/src/components/MotionStack.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { forwardRef, Stack, StackProps } from '@chakra-ui/react'
|
||||||
|
import { motion, MotionProps, isValidMotionProp } from 'framer-motion'
|
||||||
|
|
||||||
|
export const MotionStack = motion(
|
||||||
|
forwardRef<MotionProps & StackProps, 'div'>((props, ref) => {
|
||||||
|
const chakraProps = Object.fromEntries(
|
||||||
|
Object.entries(props).filter(([key]) => !isValidMotionProp(key))
|
||||||
|
)
|
||||||
|
|
||||||
|
return <Stack ref={ref} {...chakraProps} />
|
||||||
|
})
|
||||||
|
)
|
||||||
80
apps/builder/src/components/NewVersionPopup.tsx
Normal file
80
apps/builder/src/components/NewVersionPopup.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { useTypebot } from '@/features/editor'
|
||||||
|
import { HStack, Stack, Text } from '@chakra-ui/react'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { sendRequest } from 'utils'
|
||||||
|
import { PackageIcon } from './icons'
|
||||||
|
import { MotionStack } from './MotionStack'
|
||||||
|
|
||||||
|
const intervalDuration = 1000 * 30 // 30 seconds
|
||||||
|
|
||||||
|
export const NewVersionPopup = () => {
|
||||||
|
const { save } = useTypebot()
|
||||||
|
const [currentVersion, setCurrentVersion] = useState<string>()
|
||||||
|
const [isNewVersionAvailable, setIsNewVersionAvailable] = useState(false)
|
||||||
|
const [isReloading, setIsReloading] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isNewVersionAvailable) return
|
||||||
|
let cancelRequest = false
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
const { data } = await sendRequest<{
|
||||||
|
commitSha: string | undefined
|
||||||
|
}>('/api/version')
|
||||||
|
if (!data || cancelRequest) return
|
||||||
|
if (!currentVersion) {
|
||||||
|
setCurrentVersion(data.commitSha)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (currentVersion !== data.commitSha) {
|
||||||
|
setIsNewVersionAvailable(true)
|
||||||
|
}
|
||||||
|
}, intervalDuration)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelRequest = true
|
||||||
|
clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, [currentVersion, isNewVersionAvailable])
|
||||||
|
|
||||||
|
const saveAndReload = async () => {
|
||||||
|
if (isReloading) return
|
||||||
|
setIsReloading(true)
|
||||||
|
if (save) await save()
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNewVersionAvailable) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MotionStack
|
||||||
|
pos="fixed"
|
||||||
|
bottom="20px"
|
||||||
|
left="20px"
|
||||||
|
bgColor="blue.400"
|
||||||
|
color="white"
|
||||||
|
cursor="pointer"
|
||||||
|
p="4"
|
||||||
|
px="4"
|
||||||
|
rounded="xl"
|
||||||
|
shadow="lg"
|
||||||
|
onClick={saveAndReload}
|
||||||
|
zIndex={10}
|
||||||
|
initial={{ opacity: 0, scale: 0.5 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
whileTap={{ scale: 0.98 }}
|
||||||
|
borderWidth="2px"
|
||||||
|
borderColor="blue.300"
|
||||||
|
>
|
||||||
|
<HStack spacing={3}>
|
||||||
|
<PackageIcon boxSize="32px" />
|
||||||
|
<Stack spacing={0}>
|
||||||
|
<Text fontWeight="bold">Typebot is ready to update!</Text>
|
||||||
|
<Text fontSize="sm" color="gray.200">
|
||||||
|
Click to restart
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</HStack>
|
||||||
|
</MotionStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -530,3 +530,12 @@ export const ListIcon = (props: IconProps) => (
|
|||||||
<line x1="3" y1="18" x2="3.01" y2="18"></line>
|
<line x1="3" y1="18" x2="3.01" y2="18"></line>
|
||||||
</Icon>
|
</Icon>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const PackageIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<line x1="16.5" y1="9.4" x2="7.5" y2="4.21"></line>
|
||||||
|
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||||
|
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
||||||
|
<line x1="12" y1="22.08" x2="12" y2="12"></line>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { toTitleCase } from 'utils'
|
|||||||
import { Session } from 'next-auth'
|
import { Session } from 'next-auth'
|
||||||
import { Plan } from 'db'
|
import { Plan } from 'db'
|
||||||
import { trpc } from '@/lib/trpc'
|
import { trpc } from '@/lib/trpc'
|
||||||
|
import { NewVersionPopup } from '@/components/NewVersionPopup'
|
||||||
|
|
||||||
const { ToastContainer, toast } = createStandaloneToast(customTheme)
|
const { ToastContainer, toast } = createStandaloneToast(customTheme)
|
||||||
|
|
||||||
@@ -59,12 +60,14 @@ const App = ({
|
|||||||
<WorkspaceProvider typebotId={typebotId}>
|
<WorkspaceProvider typebotId={typebotId}>
|
||||||
<Component />
|
<Component />
|
||||||
<SupportBubble />
|
<SupportBubble />
|
||||||
|
<NewVersionPopup />
|
||||||
</WorkspaceProvider>
|
</WorkspaceProvider>
|
||||||
</TypebotProvider>
|
</TypebotProvider>
|
||||||
) : (
|
) : (
|
||||||
<WorkspaceProvider>
|
<WorkspaceProvider>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
<SupportBubble />
|
<SupportBubble />
|
||||||
|
<NewVersionPopup />
|
||||||
</WorkspaceProvider>
|
</WorkspaceProvider>
|
||||||
)}
|
)}
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
|
|||||||
7
apps/builder/src/pages/api/version.ts
Normal file
7
apps/builder/src/pages/api/version.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
const handler = async (_req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
return res.send({ commitSha: process.env.VERCEL_GIT_COMMIT_SHA })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handler
|
||||||
Reference in New Issue
Block a user