diff --git a/SETUP_PUSH_NOTIFICATIONS.md b/SETUP_PUSH_NOTIFICATIONS.md new file mode 100644 index 0000000..4a01a33 --- /dev/null +++ b/SETUP_PUSH_NOTIFICATIONS.md @@ -0,0 +1,83 @@ +# Setup Push Notifications - Required Steps + +## You MUST do these 3 steps for notifications to work: + +### Step 1: Install the web-push package +```bash +npm install +``` + +This installs the `web-push` package that was added to package.json. + +### Step 2: Generate VAPID keys +```bash +npm run vapid:generate +``` + +This will output something like: +``` +VAPID_PUBLIC_KEY=BNxxx... +VAPID_PRIVATE_KEY=xxx... +VAPID_SUBJECT=mailto:your-email@example.com +``` + +**Copy these 3 lines and add them to your `.env` file.** + +⚠️ **Important**: Keep the VAPID_PRIVATE_KEY secret! Don't commit it to git. + +### Step 3: Update your database +```bash +npx prisma db push +``` + +This adds the `PushSubscription` table to your database. + +### Step 4: Restart your server +```bash +npm run dev +``` + +## That's it! + +Now when users open your app: +- After 3 seconds, they'll see a notification prompt at the bottom-right +- They can click "Enable Notifications" to subscribe +- Or they can dismiss it and enable later from their profile page + +## What happens automatically: + +✅ Notification prompt appears 3 seconds after app loads +✅ Only shows if user hasn't been asked before +✅ Users can dismiss temporarily ("Later") or permanently ("Don't ask again") +✅ Notifications are sent when: + - Items are added/removed/completed in shared lists + - Users are invited to lists + - Invitations are accepted + - Collaborators are removed + +## Testing + +1. Open your app in Chrome +2. Wait 3 seconds +3. Click "Enable Notifications" when the prompt appears +4. Open another browser/incognito window +5. Log in as a different user +6. Add an item to a shared list +7. See the notification in the first browser! + +## Troubleshooting + +**"Push notifications not configured" error** +- Make sure you ran `npm run vapid:generate` +- Make sure you added the keys to `.env` +- Restart your dev server + +**Prompt doesn't appear** +- Check browser console for errors +- Make sure you're on localhost or HTTPS +- Clear localStorage and refresh if you dismissed it permanently + +**Notifications don't send** +- Check server console for errors +- Verify VAPID keys are correct in `.env` +- Make sure database was updated with `npx prisma db push` diff --git a/app/globals.css b/app/globals.css index 43fec02..ac5d5e2 100644 --- a/app/globals.css +++ b/app/globals.css @@ -63,6 +63,17 @@ mark { } } + @keyframes slide-up { + from { + transform: translateY(100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } + } + .animate-slide-in-right { animation: slide-in-right 0.3s ease-out; } @@ -75,6 +86,10 @@ mark { animation: scale-in 0.2s ease-out; } + .animate-slide-up { + animation: slide-up 0.4s ease-out; + } + /* Smooth transitions for interactive elements */ .transition-smooth { transition: all 0.2s ease-in-out; diff --git a/app/layout.tsx b/app/layout.tsx index 0aa4fe6..664082a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata, Viewport } from 'next' import './globals.css' import ServiceWorkerRegistration from '@/components/ServiceWorkerRegistration' import OfflineIndicator from '@/components/OfflineIndicator' +import NotificationPrompt from '@/components/NotificationPrompt' import { ToastProvider } from '@/components/ToastContainer' import ErrorBoundary from '@/components/ErrorBoundary' @@ -75,6 +76,7 @@ export default function RootLayout({ + {children} diff --git a/components/NotificationPrompt.tsx b/components/NotificationPrompt.tsx new file mode 100644 index 0000000..fd25b61 --- /dev/null +++ b/components/NotificationPrompt.tsx @@ -0,0 +1,104 @@ +'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 ( +
+
+
+
+ +
+ +
+

+ Stay Updated +

+

+ Get notified when items are added, completed, or when you're invited to lists +

+ +
+ + +
+ + +
+ + +
+
+
+ ); +}