diff --git a/src/components/VersionUpdateBanner/index.tsx b/src/components/VersionUpdateBanner/index.tsx new file mode 100644 index 0000000..cd752ba --- /dev/null +++ b/src/components/VersionUpdateBanner/index.tsx @@ -0,0 +1,144 @@ +import { Button } from '@/components/ui/button' +import { RefreshCw, X } from 'lucide-react' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' + +export default function VersionUpdateBanner() { + const { t } = useTranslation() + const [updateAvailable, setUpdateAvailable] = useState(false) + const [isDismissed, setIsDismissed] = useState(false) + const [isUpdating, setIsUpdating] = useState(false) + + useEffect(() => { + if (typeof window === 'undefined' || !('serviceWorker' in navigator)) { + return + } + + let registration: ServiceWorkerRegistration | null = null + + const checkForUpdates = async () => { + try { + registration = await navigator.serviceWorker.ready + if (!registration) return + + // Check if there's a waiting service worker (new version ready) + if (registration.waiting) { + // There's already a new version waiting + setUpdateAvailable(true) + } + + // Listen for updates + const handleUpdateFound = () => { + const newWorker = registration?.installing + if (!newWorker) return + + const handleStateChange = () => { + if (newWorker.state === 'installed') { + // New version installed + if (navigator.serviceWorker.controller) { + // There's a new version ready (not the first install) + setUpdateAvailable(true) + } + } + } + + newWorker.addEventListener('statechange', handleStateChange) + } + + registration.addEventListener('updatefound', handleUpdateFound) + + // Check for updates periodically + const checkInterval = setInterval(() => { + if (registration) { + registration.update() + } + }, 60000) // Check every minute + + // Initial update check + registration.update() + + return () => { + clearInterval(checkInterval) + if (registration) { + registration.removeEventListener('updatefound', handleUpdateFound as EventListener) + } + } + } catch (error) { + console.error('Error checking for updates:', error) + } + } + + checkForUpdates() + }, []) + + const handleUpdate = () => { + setIsUpdating(true) + // Reload the page to activate the new service worker + window.location.reload() + } + + const handleDismiss = () => { + setIsDismissed(true) + // Store dismissal in localStorage to avoid showing it again this session + sessionStorage.setItem('versionUpdateDismissed', 'true') + } + + // Check if user already dismissed this session + useEffect(() => { + const dismissed = sessionStorage.getItem('versionUpdateDismissed') + if (dismissed === 'true') { + setIsDismissed(true) + } + }, []) + + if (!updateAvailable || isDismissed) { + return null + } + + return ( +
+
+
+ +
+

+ {t('A new version is available')} +

+

+ {t('Click update to get the latest features and improvements')} +

+
+
+
+ + +
+
+
+ ) +} + diff --git a/src/pages/primary/NoteListPage/index.tsx b/src/pages/primary/NoteListPage/index.tsx index 54c38cf..b403a48 100644 --- a/src/pages/primary/NoteListPage/index.tsx +++ b/src/pages/primary/NoteListPage/index.tsx @@ -1,6 +1,7 @@ import { usePrimaryNoteView } from '@/PageManager' import BookmarkList from '@/components/BookmarkList' import RelayInfo from '@/components/RelayInfo' +import VersionUpdateBanner from '@/components/VersionUpdateBanner' import { Button } from '@/components/ui/button' import PrimaryPageLayout from '@/layouts/PrimaryPageLayout' import { useCurrentRelays } from '@/providers/CurrentRelaysProvider' @@ -110,6 +111,7 @@ const NoteListPage = forwardRef((_, ref) => { } displayScrollToTopButton > + {content} )