feat: implement complete web push notification system

- Add self-hosted push notifications using VAPID (no external services)
- Create PushSubscription model in database
- Implement server-side push logic with web-push library
- Add notification helpers for list events (items, invitations, collaborators)
- Create NotificationSettings UI component for profile page
- Add usePushNotifications React hook for subscription management
- Create API routes for subscription management and VAPID key
- Update service worker with push event handlers
- Integrate notifications into item actions (add, remove, complete)
- Integrate notifications into sharing actions (invite, accept, remove)
- Add VAPID key generation script
- Add comprehensive documentation and setup guides

Notifications are sent when:
- Items are added/removed/completed in shared lists
- Users are invited to lists
- Invitations are accepted
- Collaborators are removed from lists

Works on iOS 16.4+ (when installed to home screen), Android, and desktop browsers.
This commit is contained in:
2025-12-07 01:22:16 +00:00
parent 625ca40e48
commit 7cda58440f
21 changed files with 2144 additions and 8 deletions

View File

@@ -0,0 +1,76 @@
'use client';
import { usePushNotifications } from '@/hooks/usePushNotifications';
import { Bell, BellOff } from 'lucide-react';
export default function NotificationSettings() {
const { permission, isSubscribed, isLoading, error, subscribe, unsubscribe } =
usePushNotifications();
const handleToggle = async () => {
if (isSubscribed) {
await unsubscribe();
} else {
await subscribe();
}
};
// Don't show if notifications aren't supported
if (!('Notification' in window) || !('serviceWorker' in navigator)) {
return null;
}
return (
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-start justify-between">
<div className="flex-1">
<h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
{isSubscribed ? <Bell className="w-5 h-5" /> : <BellOff className="w-5 h-5" />}
Push Notifications
</h3>
<p className="text-sm text-gray-600 mt-1">
Get notified when someone adds items, shares lists, or invites you to collaborate
</p>
{permission === 'denied' && (
<div className="mt-3 p-3 bg-red-50 border border-red-200 rounded-md">
<p className="text-sm text-red-800">
Notifications are blocked. Please enable them in your browser settings.
</p>
</div>
)}
{error && (
<div className="mt-3 p-3 bg-red-50 border border-red-200 rounded-md">
<p className="text-sm text-red-800">{error}</p>
</div>
)}
{isSubscribed && (
<div className="mt-3 p-3 bg-green-50 border border-green-200 rounded-md">
<p className="text-sm text-green-800">
✓ You'll receive notifications on this device
</p>
</div>
)}
</div>
<button
onClick={handleToggle}
disabled={isLoading || permission === 'denied'}
className={`ml-4 px-4 py-2 rounded-md font-medium transition-colors ${
isSubscribed
? 'bg-gray-200 text-gray-700 hover:bg-gray-300'
: 'bg-blue-600 text-white hover:bg-blue-700'
} disabled:opacity-50 disabled:cursor-not-allowed`}
>
{isLoading ? 'Loading...' : isSubscribed ? 'Disable' : 'Enable'}
</button>
</div>
<div className="mt-4 text-xs text-gray-500">
<p>💡 Tip: Install this app to your home screen for the best notification experience</p>
</div>
</div>
);
}