- Create NotificationPrompt component that appears 3 seconds after app loads - Add to main layout so it shows on all pages (for logged-in users) - Prompt only shows if permission not yet requested - Users can dismiss temporarily or permanently - Add slide-up animation for smooth appearance - Create simplified SETUP_PUSH_NOTIFICATIONS.md guide Users will now be automatically prompted to enable notifications instead of having to go to the profile page.
105 lines
3.3 KiB
TypeScript
105 lines
3.3 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);
|
|
|
|
useEffect(() => {
|
|
// 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);
|
|
}
|
|
}, [permission, isSubscribed, dismissed]);
|
|
|
|
const handleEnable = async () => {
|
|
const success = await subscribe();
|
|
if (success) {
|
|
setShowPrompt(false);
|
|
}
|
|
};
|
|
|
|
const handleDismiss = () => {
|
|
setShowPrompt(false);
|
|
setDismissed(true);
|
|
};
|
|
|
|
const handleDismissForever = () => {
|
|
localStorage.setItem('notification-prompt-dismissed', 'true');
|
|
setShowPrompt(false);
|
|
setDismissed(true);
|
|
};
|
|
|
|
if (!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>
|
|
);
|
|
}
|