Files
Todo/components/NotificationPrompt.tsx
Paul Bailey 1838502b8b fix: prevent hydration error in NotificationPrompt
Add mounted state to prevent SSR rendering.
Component now only renders after client-side mount.
Fixes login blocking hydration error.
2025-12-07 01:59:55 +00:00

114 lines
3.5 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { usePushNotifications } from '@/hooks/usePushNotifications';
import { Bell, X } from 'lucide-react';
export default function NotificationPrompt() {
const { permission, isSubscribed, subscribe } = usePushNotifications();
const [showPrompt, setShowPrompt] = useState(false);
const [dismissed, setDismissed] = useState(false);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
if (!mounted) return;
// Check if we should show the prompt
const shouldShow =
'Notification' in window &&
'serviceWorker' in navigator &&
permission === 'default' && // Not yet asked
!isSubscribed && // Not subscribed
!dismissed && // Not dismissed this session
!localStorage.getItem('notification-prompt-dismissed'); // Not permanently dismissed
if (shouldShow) {
// Show prompt after a short delay (better UX)
const timer = setTimeout(() => {
setShowPrompt(true);
}, 3000); // 3 seconds after app loads
return () => clearTimeout(timer);
}
}, [mounted, permission, isSubscribed, dismissed]);
const handleEnable = async () => {
const success = await subscribe();
if (success) {
setShowPrompt(false);
}
};
const handleDismiss = () => {
setShowPrompt(false);
setDismissed(true);
};
const handleDismissForever = () => {
if (typeof localStorage !== 'undefined') {
localStorage.setItem('notification-prompt-dismissed', 'true');
}
setShowPrompt(false);
setDismissed(true);
};
if (!mounted || !showPrompt) {
return null;
}
return (
<div className="fixed bottom-4 right-4 left-4 sm:left-auto sm:w-96 z-50 animate-slide-up">
<div className="bg-white rounded-lg shadow-2xl border border-gray-200 p-4">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
<Bell className="w-5 h-5 text-blue-600" />
</div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold text-gray-900 mb-1">
Stay Updated
</h3>
<p className="text-sm text-gray-600 mb-3">
Get notified when items are added, completed, or when you're invited to lists
</p>
<div className="flex gap-2">
<button
onClick={handleEnable}
className="flex-1 px-3 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 transition-colors"
>
Enable Notifications
</button>
<button
onClick={handleDismiss}
className="px-3 py-2 text-gray-600 text-sm font-medium hover:text-gray-900 transition-colors"
>
Later
</button>
</div>
<button
onClick={handleDismissForever}
className="text-xs text-gray-500 hover:text-gray-700 mt-2 underline"
>
Don't ask again
</button>
</div>
<button
onClick={handleDismiss}
className="flex-shrink-0 text-gray-400 hover:text-gray-600 transition-colors"
aria-label="Close"
>
<X className="w-5 h-5" />
</button>
</div>
</div>
</div>
);
}