diff --git a/app/page.tsx b/app/page.tsx index 8906f1d..ea7476c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -3,6 +3,7 @@ import ListCard from '@/components/ListCard' import NewListButton from '@/components/NewListButton' import GlobalSearch from '@/components/GlobalSearch' import EmptyState from '@/components/EmptyState' +import RealtimeUpdater from '@/components/RealtimeUpdater' import Link from 'next/link' import { getServerSession } from 'next-auth' import { authOptions } from '@/pages/api/auth/[...nextauth]' @@ -21,6 +22,7 @@ export default async function Home() { return (
+

Ultimate Lists

diff --git a/components/DeleteItemButton.tsx b/components/DeleteItemButton.tsx index d435e2c..cb92706 100644 --- a/components/DeleteItemButton.tsx +++ b/components/DeleteItemButton.tsx @@ -25,6 +25,7 @@ export default function DeleteItemButton({ itemId, listId, variant = 'full' }: D try { await deleteItem(itemId) showToast('Task deleted successfully', 'success') + setShowConfirm(false) if (listId) { router.push(`/list/${listId}`) } else { @@ -33,6 +34,7 @@ export default function DeleteItemButton({ itemId, listId, variant = 'full' }: D } catch (error) { console.error('Error deleting item:', error) showToast('Failed to delete task', 'error') + } finally { setIsDeleting(false) setShowConfirm(false) } diff --git a/components/RealtimeUpdater.tsx b/components/RealtimeUpdater.tsx new file mode 100644 index 0000000..e7c92f4 --- /dev/null +++ b/components/RealtimeUpdater.tsx @@ -0,0 +1,22 @@ +'use client' + +import { useRealtimeSync } from '@/hooks/useRealtimeSync' + +interface RealtimeUpdaterProps { + listId?: string + pollInterval?: number +} + +/** + * Component that enables real-time updates for a page + * Just include this component and it will poll for updates + */ +export default function RealtimeUpdater({ listId, pollInterval = 3000 }: RealtimeUpdaterProps) { + useRealtimeSync({ + listId, + enabled: true, + pollInterval, + }) + + return null +} diff --git a/hooks/useRealtimeSync.ts b/hooks/useRealtimeSync.ts index b4f28eb..6f3803a 100644 --- a/hooks/useRealtimeSync.ts +++ b/hooks/useRealtimeSync.ts @@ -1,10 +1,10 @@ 'use client' -import { useEffect, useRef, useCallback, useState } from 'react' +import { useEffect, useRef, useCallback } from 'react' import { useRouter } from 'next/navigation' interface UseRealtimeSyncOptions { - listId: string + listId?: string enabled?: boolean pollInterval?: number onUpdate?: () => void @@ -12,7 +12,7 @@ interface UseRealtimeSyncOptions { /** * Hook to enable real-time synchronization of list data - * Polls the server for updates and refreshes the page when changes are detected + * Polls the server for updates by refreshing the route */ export function useRealtimeSync({ listId, @@ -21,48 +21,14 @@ export function useRealtimeSync({ onUpdate }: UseRealtimeSyncOptions) { const router = useRouter() - const [lastUpdated, setLastUpdated] = useState(Date.now()) const intervalRef = useRef(null) - const isPollingRef = useRef(false) - const checkForUpdates = useCallback(async () => { - // Prevent concurrent polling - if (isPollingRef.current) return - - try { - isPollingRef.current = true - - // Fetch the latest update timestamp for the list - const response = await fetch(`/api/lists/${listId}/last-updated`, { - method: 'GET', - headers: { - 'Cache-Control': 'no-cache', - }, - }) - - if (response.ok) { - const data = await response.json() - const serverTimestamp = new Date(data.lastUpdated).getTime() - - // If server timestamp is newer than our last known update, refresh - if (serverTimestamp > lastUpdated) { - setLastUpdated(serverTimestamp) - - // Refresh the current route to get latest data - router.refresh() - - // Call optional callback - if (onUpdate) { - onUpdate() - } - } - } - } catch (error) { - console.error('Error checking for updates:', error) - } finally { - isPollingRef.current = false + const refresh = useCallback(() => { + router.refresh() + if (onUpdate) { + onUpdate() } - }, [listId, lastUpdated, router, onUpdate]) + }, [router, onUpdate]) useEffect(() => { if (!enabled) { @@ -74,7 +40,7 @@ export function useRealtimeSync({ } // Start polling - intervalRef.current = setInterval(checkForUpdates, pollInterval) + intervalRef.current = setInterval(refresh, pollInterval) // Cleanup on unmount return () => { @@ -83,13 +49,13 @@ export function useRealtimeSync({ intervalRef.current = null } } - }, [enabled, pollInterval, checkForUpdates]) + }, [enabled, pollInterval, refresh]) - // Also check for updates when the page becomes visible again + // Also refresh when the page becomes visible again useEffect(() => { const handleVisibilityChange = () => { if (document.visibilityState === 'visible' && enabled) { - checkForUpdates() + refresh() } } @@ -97,10 +63,9 @@ export function useRealtimeSync({ return () => { document.removeEventListener('visibilitychange', handleVisibilityChange) } - }, [enabled, checkForUpdates]) + }, [enabled, refresh]) return { - lastUpdated, - checkForUpdates, + refresh, } } diff --git a/hooks/useRealtimeUpdates.ts b/hooks/useRealtimeUpdates.ts new file mode 100644 index 0000000..62809cb --- /dev/null +++ b/hooks/useRealtimeUpdates.ts @@ -0,0 +1,37 @@ +'use client' + +import { useEffect, useCallback } from 'react' +import { useRouter } from 'next/navigation' + +/** + * Hook for real-time updates via polling + * Refreshes data at regular intervals to show changes from other users/browsers + */ +export function useRealtimeUpdates(intervalMs: number = 3000) { + const router = useRouter() + + const refresh = useCallback(() => { + router.refresh() + }, [router]) + + useEffect(() => { + // Poll for updates + const interval = setInterval(() => { + refresh() + }, intervalMs) + + // Also refresh when window regains focus + const handleFocus = () => { + refresh() + } + + window.addEventListener('focus', handleFocus) + + return () => { + clearInterval(interval) + window.removeEventListener('focus', handleFocus) + } + }, [intervalMs, refresh]) + + return { refresh } +}