diff --git a/apps/builder/src/components/MotionStack.tsx b/apps/builder/src/components/MotionStack.tsx new file mode 100644 index 00000000000..cfc73e7984b --- /dev/null +++ b/apps/builder/src/components/MotionStack.tsx @@ -0,0 +1,12 @@ +import { forwardRef, Stack, StackProps } from '@chakra-ui/react' +import { motion, MotionProps, isValidMotionProp } from 'framer-motion' + +export const MotionStack = motion( + forwardRef((props, ref) => { + const chakraProps = Object.fromEntries( + Object.entries(props).filter(([key]) => !isValidMotionProp(key)) + ) + + return + }) +) diff --git a/apps/builder/src/components/NewVersionPopup.tsx b/apps/builder/src/components/NewVersionPopup.tsx new file mode 100644 index 00000000000..11e994e9f8f --- /dev/null +++ b/apps/builder/src/components/NewVersionPopup.tsx @@ -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() + 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 ( + + + + + Typebot is ready to update! + + Click to restart + + + + + ) +} diff --git a/apps/builder/src/components/icons.tsx b/apps/builder/src/components/icons.tsx index 9aabe54ce0e..9e8d80d6047 100644 --- a/apps/builder/src/components/icons.tsx +++ b/apps/builder/src/components/icons.tsx @@ -530,3 +530,12 @@ export const ListIcon = (props: IconProps) => ( ) + +export const PackageIcon = (props: IconProps) => ( + + + + + + +) diff --git a/apps/builder/src/pages/_app.tsx b/apps/builder/src/pages/_app.tsx index a6f6e66bc5c..ce57eba4d6f 100644 --- a/apps/builder/src/pages/_app.tsx +++ b/apps/builder/src/pages/_app.tsx @@ -18,6 +18,7 @@ import { toTitleCase } from 'utils' import { Session } from 'next-auth' import { Plan } from 'db' import { trpc } from '@/lib/trpc' +import { NewVersionPopup } from '@/components/NewVersionPopup' const { ToastContainer, toast } = createStandaloneToast(customTheme) @@ -59,12 +60,14 @@ const App = ({ + ) : ( + )} diff --git a/apps/builder/src/pages/api/version.ts b/apps/builder/src/pages/api/version.ts new file mode 100644 index 00000000000..20bc11e9fca --- /dev/null +++ b/apps/builder/src/pages/api/version.ts @@ -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