@djangocfg/layouts 2.1.37 → 2.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +204 -18
  2. package/package.json +5 -5
  3. package/src/components/errors/index.ts +9 -0
  4. package/src/components/errors/types.ts +38 -0
  5. package/src/layouts/AppLayout/AppLayout.tsx +33 -45
  6. package/src/layouts/AppLayout/BaseApp.tsx +104 -33
  7. package/src/layouts/AuthLayout/AuthContext.tsx +7 -1
  8. package/src/layouts/AuthLayout/OAuthProviders.tsx +1 -10
  9. package/src/layouts/AuthLayout/OTPForm.tsx +1 -0
  10. package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -1
  11. package/src/layouts/PublicLayout/PublicLayout.tsx +1 -1
  12. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
  13. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
  14. package/src/layouts/_components/UserMenu.tsx +1 -1
  15. package/src/layouts/index.ts +1 -1
  16. package/src/layouts/types/index.ts +47 -0
  17. package/src/layouts/types/layout.types.ts +61 -0
  18. package/src/layouts/types/providers.types.ts +65 -0
  19. package/src/layouts/types/ui.types.ts +103 -0
  20. package/src/snippets/Analytics/index.ts +1 -0
  21. package/src/snippets/Analytics/types.ts +10 -0
  22. package/src/snippets/PWAInstall/@docs/README.md +92 -0
  23. package/src/snippets/PWAInstall/README.md +185 -0
  24. package/src/snippets/{PWA → PWAInstall}/components/A2HSHint.tsx +85 -84
  25. package/src/snippets/PWAInstall/components/DesktopGuide.tsx +229 -0
  26. package/src/snippets/PWAInstall/context/InstallContext.tsx +102 -0
  27. package/src/snippets/{PWA → PWAInstall}/hooks/useInstallPrompt.ts +3 -0
  28. package/src/snippets/{PWA → PWAInstall}/index.ts +12 -31
  29. package/src/snippets/{PWA → PWAInstall}/types/components.ts +0 -6
  30. package/src/snippets/PWAInstall/types/config.ts +22 -0
  31. package/src/snippets/{PWA → PWAInstall}/types/index.ts +4 -4
  32. package/src/snippets/{PWA → PWAInstall}/utils/localStorage.ts +1 -23
  33. package/src/snippets/PushNotifications/@docs/README.md +191 -0
  34. package/src/snippets/PushNotifications/@docs/guides/django-integration.md +648 -0
  35. package/src/snippets/PushNotifications/@docs/guides/service-worker.md +467 -0
  36. package/src/snippets/PushNotifications/@docs/guides/vapid-setup.md +352 -0
  37. package/src/snippets/PushNotifications/README.md +328 -0
  38. package/src/snippets/{PWA → PushNotifications}/config.ts +2 -2
  39. package/src/snippets/PushNotifications/context/DjangoPushContext.tsx +190 -0
  40. package/src/snippets/{PWA → PushNotifications}/hooks/useDjangoPush.ts +63 -81
  41. package/src/snippets/{PWA → PushNotifications}/hooks/usePushNotifications.ts +12 -8
  42. package/src/snippets/PushNotifications/index.ts +87 -0
  43. package/src/snippets/PushNotifications/types/config.ts +28 -0
  44. package/src/snippets/PushNotifications/types/index.ts +9 -0
  45. package/src/snippets/PushNotifications/utils/localStorage.ts +60 -0
  46. package/src/snippets/PushNotifications/utils/logger.ts +149 -0
  47. package/src/snippets/PushNotifications/utils/platform.ts +151 -0
  48. package/src/snippets/index.ts +37 -12
  49. package/src/layouts/shared/index.ts +0 -21
  50. package/src/layouts/shared/types.ts +0 -247
  51. package/src/snippets/PWA/@refactoring/ARCHITECTURE_ANALYSIS.md +0 -1179
  52. package/src/snippets/PWA/@refactoring/EXECUTIVE_SUMMARY.md +0 -271
  53. package/src/snippets/PWA/@refactoring/README.md +0 -204
  54. package/src/snippets/PWA/@refactoring/REFACTORING_PROPOSALS.md +0 -1109
  55. package/src/snippets/PWA/@refactoring2/COMPARISON-WITH-NEXTJS.md +0 -718
  56. package/src/snippets/PWA/@refactoring2/P1-FIXES-COMPLETED.md +0 -188
  57. package/src/snippets/PWA/@refactoring2/POST-P0-ANALYSIS.md +0 -362
  58. package/src/snippets/PWA/@refactoring2/README.md +0 -85
  59. package/src/snippets/PWA/@refactoring2/RECOMMENDATIONS.md +0 -1321
  60. package/src/snippets/PWA/@refactoring2/REMAINING-ISSUES.md +0 -557
  61. package/src/snippets/PWA/README.md +0 -387
  62. package/src/snippets/PWA/context/DjangoPushContext.tsx +0 -105
  63. package/src/snippets/PWA/context/InstallContext.tsx +0 -118
  64. package/src/snippets/PWA/context/PushContext.tsx +0 -156
  65. /package/src/layouts/{shared → types}/README.md +0 -0
  66. /package/src/snippets/{PWA/@docs/research.md → PWAInstall/@docs/research/ios-android-install-flows.md} +0 -0
  67. /package/src/snippets/{PWA → PWAInstall}/components/IOSGuide.tsx +0 -0
  68. /package/src/snippets/{PWA → PWAInstall}/components/IOSGuideDrawer.tsx +0 -0
  69. /package/src/snippets/{PWA → PWAInstall}/components/IOSGuideModal.tsx +0 -0
  70. /package/src/snippets/{PWA → PWAInstall}/hooks/useIsPWA.ts +0 -0
  71. /package/src/snippets/{PWA → PWAInstall}/types/install.ts +0 -0
  72. /package/src/snippets/{PWA → PWAInstall}/types/platform.ts +0 -0
  73. /package/src/snippets/{PWA → PWAInstall}/utils/logger.ts +0 -0
  74. /package/src/snippets/{PWA → PWAInstall}/utils/platform.ts +0 -0
  75. /package/src/snippets/{PWA → PushNotifications}/components/PushPrompt.tsx +0 -0
  76. /package/src/snippets/{PWA → PushNotifications}/types/push.ts +0 -0
  77. /package/src/snippets/{PWA → PushNotifications}/utils/vapid.ts +0 -0
@@ -0,0 +1,352 @@
1
+ # VAPID Setup Guide
2
+
3
+ Complete guide to generating and configuring VAPID keys for Web Push Notifications.
4
+
5
+ ## What is VAPID?
6
+
7
+ **VAPID** (Voluntary Application Server Identification) is a standard that authenticates your web push notifications.
8
+
9
+ ### Why VAPID?
10
+
11
+ Without VAPID, push services can't verify that push notifications actually come from your server. VAPID provides:
12
+
13
+ - **Authentication** - Proves notifications come from you
14
+ - **Security** - Prevents notification spoofing
15
+ - **Privacy** - Required by modern push services
16
+
17
+ ## Key Pairs
18
+
19
+ VAPID uses a **public/private key pair**:
20
+
21
+ | Key | Location | Purpose |
22
+ |-----|----------|---------|
23
+ | **Public** | Frontend + Backend | Identify your app to push service |
24
+ | **Private** | Backend ONLY | Sign push notifications |
25
+
26
+ ⚠️ **CRITICAL**: Never expose private key in frontend code!
27
+
28
+ ## Step 1: Generate VAPID Keys
29
+
30
+ ### Option A: Using web-push (Recommended)
31
+
32
+ ```bash
33
+ # Install web-push globally
34
+ npm install -g web-push
35
+
36
+ # Generate keys
37
+ web-push generate-vapid-keys
38
+ ```
39
+
40
+ **Output:**
41
+ ```
42
+ =======================================
43
+
44
+ Public Key:
45
+ BM8xTpJKZ1234567890abcdefghijklmnopqrstuvwxyz...
46
+
47
+ Private Key:
48
+ abc123def456ghi789jkl012mno345pqr678stu901vwx...
49
+
50
+ =======================================
51
+ ```
52
+
53
+ ### Option B: Using npx (No install)
54
+
55
+ ```bash
56
+ npx web-push generate-vapid-keys
57
+ ```
58
+
59
+ ### Option C: Programmatically
60
+
61
+ ```javascript
62
+ // generate-vapid.js
63
+ const webpush = require('web-push');
64
+
65
+ const vapidKeys = webpush.generateVAPIDKeys();
66
+
67
+ console.log('Public Key:', vapidKeys.publicKey);
68
+ console.log('Private Key:', vapidKeys.privateKey);
69
+ ```
70
+
71
+ ```bash
72
+ node generate-vapid.js
73
+ ```
74
+
75
+ ## Step 2: Store Keys Securely
76
+
77
+ ### Frontend Environment Variables
78
+
79
+ Create/update `.env.local` (frontend):
80
+
81
+ ```bash
82
+ # .env.local (frontend - Next.js app)
83
+ NEXT_PUBLIC_VAPID_PUBLIC_KEY=BM8xTpJKZ1234567890abcdefghijklmnopqrstuvwxyz...
84
+ ```
85
+
86
+ **Notes:**
87
+ - ✅ `NEXT_PUBLIC_` prefix makes it available in browser
88
+ - ✅ Public key is safe to expose
89
+ - ✅ Commit `.env.example` with placeholder
90
+ - ❌ Never commit `.env.local` with real keys
91
+
92
+ ### Backend Environment Variables
93
+
94
+ Create/update `.env` (backend - Django):
95
+
96
+ ```bash
97
+ # .env (backend - Django)
98
+ VAPID_PUBLIC_KEY=BM8xTpJKZ1234567890abcdefghijklmnopqrstuvwxyz...
99
+ VAPID_PRIVATE_KEY=abc123def456ghi789jkl012mno345pqr678stu901vwx...
100
+ VAPID_MAILTO=mailto:your-email@example.com
101
+ ```
102
+
103
+ **Notes:**
104
+ - ⚠️ **CRITICAL**: Private key NEVER in frontend
105
+ - ⚠️ Never commit `.env` to git
106
+ - ✅ Use secure environment (production secrets manager)
107
+ - ✅ `VAPID_MAILTO` should be valid email with `mailto:` prefix
108
+
109
+ ### Example .env.example
110
+
111
+ Create `.env.example` (safe to commit):
112
+
113
+ ```bash
114
+ # Frontend (.env.example)
115
+ NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key_here
116
+
117
+ # Backend (.env.example)
118
+ VAPID_PUBLIC_KEY=your_public_key_here
119
+ VAPID_PRIVATE_KEY=your_private_key_here
120
+ VAPID_MAILTO=mailto:your-email@example.com
121
+ ```
122
+
123
+ ## Step 3: Configure Frontend
124
+
125
+ ### Using PushNotifications Snippet
126
+
127
+ ```tsx
128
+ // app/layout.tsx
129
+ import { DjangoPushProvider } from '@/snippets/PushNotifications';
130
+
131
+ const VAPID_KEY = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!;
132
+
133
+ export default function Layout({ children }) {
134
+ return (
135
+ <DjangoPushProvider vapidPublicKey={VAPID_KEY}>
136
+ {children}
137
+ </DjangoPushProvider>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ### Manual Configuration
143
+
144
+ ```tsx
145
+ import { usePushNotifications } from '@/snippets/PushNotifications';
146
+
147
+ function Component() {
148
+ const { subscribe } = usePushNotifications({
149
+ vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,
150
+ subscribeEndpoint: '/api/push/subscribe',
151
+ });
152
+
153
+ return <button onClick={subscribe}>Subscribe</button>;
154
+ }
155
+ ```
156
+
157
+ ## Step 4: Configure Backend (Django)
158
+
159
+ ### Install Dependencies
160
+
161
+ ```bash
162
+ pip install pywebpush
163
+ ```
164
+
165
+ ### Django Settings
166
+
167
+ ```python
168
+ # settings.py
169
+ import os
170
+
171
+ # VAPID Configuration
172
+ VAPID_PUBLIC_KEY = os.getenv('VAPID_PUBLIC_KEY')
173
+ VAPID_PRIVATE_KEY = os.getenv('VAPID_PRIVATE_KEY')
174
+ VAPID_MAILTO = os.getenv('VAPID_MAILTO', 'mailto:admin@example.com')
175
+
176
+ # Validate VAPID keys
177
+ if not VAPID_PUBLIC_KEY or not VAPID_PRIVATE_KEY:
178
+ raise ValueError("VAPID keys not configured. Run: web-push generate-vapid-keys")
179
+ ```
180
+
181
+ ### Django View (Subscribe Endpoint)
182
+
183
+ ```python
184
+ # views.py
185
+ from django.http import JsonResponse
186
+ from django.views.decorators.csrf import csrf_exempt
187
+ from django.conf import settings
188
+ import json
189
+
190
+ @csrf_exempt
191
+ def push_subscribe(request):
192
+ if request.method == 'POST':
193
+ subscription_info = json.loads(request.body)
194
+
195
+ # Store subscription in database
196
+ # (Your model here)
197
+
198
+ return JsonResponse({
199
+ 'success': True,
200
+ 'publicKey': settings.VAPID_PUBLIC_KEY
201
+ })
202
+
203
+ return JsonResponse({'error': 'Method not allowed'}, status=405)
204
+ ```
205
+
206
+ ### Send Push Notification
207
+
208
+ ```python
209
+ # utils/push.py
210
+ from pywebpush import webpush, WebPushException
211
+ from django.conf import settings
212
+ import json
213
+
214
+ def send_push_notification(subscription_info, notification_data):
215
+ """
216
+ Send push notification to a subscriber
217
+
218
+ Args:
219
+ subscription_info: PushSubscription object from frontend
220
+ notification_data: dict with 'title', 'body', 'icon', etc.
221
+ """
222
+ try:
223
+ webpush(
224
+ subscription_info=subscription_info,
225
+ data=json.dumps(notification_data),
226
+ vapid_private_key=settings.VAPID_PRIVATE_KEY,
227
+ vapid_claims={
228
+ "sub": settings.VAPID_MAILTO
229
+ }
230
+ )
231
+ return True
232
+ except WebPushException as e:
233
+ print(f"Push failed: {e}")
234
+ return False
235
+ ```
236
+
237
+ ## Step 5: Verify Configuration
238
+
239
+ ### Frontend Verification
240
+
241
+ ```tsx
242
+ // Check if VAPID key is loaded
243
+ console.log('VAPID Key:', process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY?.slice(0, 10) + '...');
244
+ ```
245
+
246
+ Expected output:
247
+ ```
248
+ VAPID Key: BM8xTpJKZ1...
249
+ ```
250
+
251
+ ### Backend Verification
252
+
253
+ ```python
254
+ # Django shell
255
+ python manage.py shell
256
+
257
+ >>> from django.conf import settings
258
+ >>> print(f"Public: {settings.VAPID_PUBLIC_KEY[:10]}...")
259
+ >>> print(f"Private: {settings.VAPID_PRIVATE_KEY[:10]}...")
260
+ >>> print(f"Mailto: {settings.VAPID_MAILTO}")
261
+ ```
262
+
263
+ Expected output:
264
+ ```
265
+ Public: BM8xTpJKZ1...
266
+ Private: abc123def4...
267
+ Mailto: mailto:you@example.com
268
+ ```
269
+
270
+ ## Security Checklist
271
+
272
+ - [ ] ✅ Public key in `NEXT_PUBLIC_` env var (frontend)
273
+ - [ ] ✅ Private key in backend-only env var (no `NEXT_PUBLIC_`)
274
+ - [ ] ✅ `.env.local` and `.env` in `.gitignore`
275
+ - [ ] ✅ `.env.example` committed with placeholders
276
+ - [ ] ⚠️ Private key NEVER in frontend code
277
+ - [ ] ⚠️ Private key NEVER in git
278
+ - [ ] ⚠️ Production keys in secure secrets manager
279
+ - [ ] ✅ VAPID_MAILTO is valid email with `mailto:` prefix
280
+
281
+ ## Troubleshooting
282
+
283
+ ### Error: "VAPID key must be base64 URL-safe"
284
+
285
+ **Problem**: Invalid key format
286
+
287
+ **Solution**: Regenerate keys with `web-push generate-vapid-keys`
288
+
289
+ ### Error: "Public key not found"
290
+
291
+ **Problem**: Environment variable not loaded
292
+
293
+ **Solution**:
294
+ 1. Check `.env.local` exists in frontend root
295
+ 2. Restart dev server (`npm run dev`)
296
+ 3. Verify: `console.log(process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY)`
297
+
298
+ ### Error: "Push notification failed"
299
+
300
+ **Problem**: Private key or mailto incorrect
301
+
302
+ **Solution**:
303
+ 1. Check backend `.env` has `VAPID_PRIVATE_KEY` and `VAPID_MAILTO`
304
+ 2. Verify mailto format: `mailto:email@example.com`
305
+ 3. Restart Django server
306
+
307
+ ## Production Deployment
308
+
309
+ ### Vercel (Frontend)
310
+
311
+ ```bash
312
+ # Add environment variable in Vercel dashboard
313
+ vercel env add NEXT_PUBLIC_VAPID_PUBLIC_KEY production
314
+ ```
315
+
316
+ Or via CLI:
317
+ ```bash
318
+ vercel env add NEXT_PUBLIC_VAPID_PUBLIC_KEY
319
+ # Paste key when prompted
320
+ ```
321
+
322
+ ### Heroku (Backend)
323
+
324
+ ```bash
325
+ heroku config:set VAPID_PUBLIC_KEY="BM8xT..."
326
+ heroku config:set VAPID_PRIVATE_KEY="abc123..."
327
+ heroku config:set VAPID_MAILTO="mailto:you@example.com"
328
+ ```
329
+
330
+ ### Docker
331
+
332
+ ```yaml
333
+ # docker-compose.yml
334
+ services:
335
+ backend:
336
+ environment:
337
+ - VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY}
338
+ - VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY}
339
+ - VAPID_MAILTO=${VAPID_MAILTO}
340
+ ```
341
+
342
+ ## Next Steps
343
+
344
+ - [Django Integration Guide](./django-integration.md) - Connect backend
345
+ - [Service Worker Setup](./service-worker.md) - Configure SW
346
+ - [Main README](../../README.md) - API reference
347
+
348
+ ## References
349
+
350
+ - [Web Push Protocol](https://datatracker.ietf.org/doc/html/rfc8030)
351
+ - [VAPID Specification](https://datatracker.ietf.org/doc/html/rfc8292)
352
+ - [web-push Library](https://github.com/web-push-libs/web-push)
@@ -0,0 +1,328 @@
1
+ # Push Notifications
2
+
3
+ Web Push Notifications snippet for web applications.
4
+
5
+ ## Responsibility
6
+
7
+ **Web Push Notifications management**
8
+
9
+ - Push subscription/unsubscription
10
+ - Permission management
11
+ - VAPID key handling
12
+ - Django-CFG backend integration
13
+ - Push message accumulation and display
14
+
15
+ ## Quick Start
16
+
17
+ ### Basic Usage
18
+
19
+ ```tsx
20
+ // app/layout.tsx
21
+ import { PushProvider, usePush, PushPrompt } from '@/snippets/PushNotifications';
22
+
23
+ const VAPID_KEY = process.env.NEXT_PUBLIC_VAPID_KEY;
24
+
25
+ export default function RootLayout({ children }) {
26
+ return (
27
+ <html>
28
+ <body>
29
+ <PushProvider vapidPublicKey={VAPID_KEY}>
30
+ {children}
31
+ <PushPrompt requirePWA={true} />
32
+ </PushProvider>
33
+ </body>
34
+ </html>
35
+ );
36
+ }
37
+ ```
38
+
39
+ ### Django Integration
40
+
41
+ ```tsx
42
+ // app/layout.tsx
43
+ import { DjangoPushProvider } from '@/snippets/PushNotifications';
44
+
45
+ export default function RootLayout({ children }) {
46
+ return (
47
+ <html>
48
+ <body>
49
+ <DjangoPushProvider
50
+ vapidPublicKey={process.env.NEXT_PUBLIC_VAPID_KEY}
51
+ autoSubscribe={false}
52
+ >
53
+ {children}
54
+ </DjangoPushProvider>
55
+ </body>
56
+ </html>
57
+ );
58
+ }
59
+ ```
60
+
61
+ ## API
62
+
63
+ ### `<PushProvider>`
64
+
65
+ Generic push notifications provider:
66
+
67
+ ```tsx
68
+ <PushProvider
69
+ vapidPublicKey={VAPID_KEY}
70
+ subscribeEndpoint="/api/push/subscribe" // Optional
71
+ sendEndpoint="/api/push/send" // Optional
72
+ onPushReceived={(push) => console.log(push)}
73
+ >
74
+ {children}
75
+ </PushProvider>
76
+ ```
77
+
78
+ ### `<DjangoPushProvider>`
79
+
80
+ Django-CFG specific provider:
81
+
82
+ ```tsx
83
+ <DjangoPushProvider
84
+ vapidPublicKey={VAPID_KEY}
85
+ autoSubscribe={false}
86
+ onSubscribed={(subscription) => console.log('Subscribed!', subscription)}
87
+ onSubscribeError={(error) => console.error('Error:', error)}
88
+ onUnsubscribed={() => console.log('Unsubscribed')}
89
+ >
90
+ {children}
91
+ </DjangoPushProvider>
92
+ ```
93
+
94
+ ### `<PushPrompt />`
95
+
96
+ Push notification permission prompt:
97
+
98
+ ```tsx
99
+ <PushPrompt
100
+ vapidPublicKey={VAPID_KEY}
101
+ requirePWA={true} // Only show if PWA installed (default: true)
102
+ delayMs={5000} // Delay before showing (default: 5000)
103
+ resetAfterDays={7} // Auto-reset after days (default: 7)
104
+ onEnabled={() => console.log('Enabled!')}
105
+ onDismissed={() => console.log('Dismissed')}
106
+ />
107
+ ```
108
+
109
+ **Behavior:**
110
+ - Shows after PWA is installed (if `requirePWA={true}`)
111
+ - Waits for delay before showing
112
+ - Dismissible (saved to localStorage)
113
+ - Auto-resets after configured days
114
+ - Respects user's permission choice
115
+
116
+ ### Hooks
117
+
118
+ #### `usePush()`
119
+
120
+ Access push state and actions:
121
+
122
+ ```tsx
123
+ import { usePush } from '@/snippets/PushNotifications';
124
+
125
+ function NotifyButton() {
126
+ const {
127
+ isSupported,
128
+ permission,
129
+ isSubscribed,
130
+ subscription,
131
+ pushes,
132
+ subscribe,
133
+ unsubscribe,
134
+ sendPush,
135
+ clearPushes,
136
+ removePush,
137
+ } = usePush();
138
+
139
+ return (
140
+ <div>
141
+ {!isSubscribed && (
142
+ <button onClick={subscribe}>Enable Notifications</button>
143
+ )}
144
+ {pushes.map(push => (
145
+ <div key={push.id}>{push.title}</div>
146
+ ))}
147
+ </div>
148
+ );
149
+ }
150
+ ```
151
+
152
+ #### `useDjangoPushContext()`
153
+
154
+ Django-specific push context:
155
+
156
+ ```tsx
157
+ import { useDjangoPushContext } from '@/snippets/PushNotifications';
158
+
159
+ function NotifyButton() {
160
+ const {
161
+ isSupported,
162
+ permission,
163
+ isSubscribed,
164
+ subscription,
165
+ isLoading,
166
+ error,
167
+ subscribe,
168
+ unsubscribe,
169
+ sendTestPush,
170
+ } = useDjangoPushContext();
171
+
172
+ const handleTest = async () => {
173
+ await sendTestPush({
174
+ title: 'Test',
175
+ body: 'This is a test notification',
176
+ url: '/dashboard',
177
+ });
178
+ };
179
+
180
+ return (
181
+ <div>
182
+ {!isSubscribed && (
183
+ <button onClick={subscribe} disabled={isLoading}>
184
+ Enable Notifications
185
+ </button>
186
+ )}
187
+ {isSubscribed && (
188
+ <button onClick={handleTest}>Send Test</button>
189
+ )}
190
+ </div>
191
+ );
192
+ }
193
+ ```
194
+
195
+ #### `usePushNotifications()`
196
+
197
+ Low-level push hook (used internally):
198
+
199
+ ```tsx
200
+ import { usePushNotifications } from '@/snippets/PushNotifications';
201
+
202
+ function Component() {
203
+ const {
204
+ isSupported,
205
+ permission,
206
+ isSubscribed,
207
+ subscription,
208
+ subscribe,
209
+ unsubscribe,
210
+ } = usePushNotifications({
211
+ vapidPublicKey: VAPID_KEY,
212
+ subscribeEndpoint: '/api/push/subscribe',
213
+ });
214
+
215
+ return <div>{/* ... */}</div>;
216
+ }
217
+ ```
218
+
219
+ ## Usage with PWA Install
220
+
221
+ Use together with the PWAInstall snippet:
222
+
223
+ ```tsx
224
+ import { PwaProvider, A2HSHint } from '@/snippets/PWAInstall';
225
+ import { DjangoPushProvider, PushPrompt } from '@/snippets/PushNotifications';
226
+
227
+ export default function Layout({ children }) {
228
+ return (
229
+ <PwaProvider>
230
+ <DjangoPushProvider vapidPublicKey={VAPID_KEY}>
231
+ {children}
232
+
233
+ {/* PWA Install hint */}
234
+ <A2HSHint resetAfterDays={3} />
235
+
236
+ {/* Push notification prompt (shown after PWA install) */}
237
+ <PushPrompt
238
+ requirePWA={true}
239
+ delayMs={5000}
240
+ resetAfterDays={7}
241
+ />
242
+ </DjangoPushProvider>
243
+ </PwaProvider>
244
+ );
245
+ }
246
+ ```
247
+
248
+ ## Browser Support
249
+
250
+ | Platform | Browser | Support |
251
+ |----------|---------|---------|
252
+ | iOS | Safari 16.4+ | ✅ Push supported |
253
+ | iOS | Chrome/Firefox | ❌ No push support |
254
+ | Android | Chrome | ✅ Full support |
255
+ | Android | Firefox | ✅ Full support |
256
+ | Desktop | Chrome/Edge/Firefox | ✅ Full support |
257
+
258
+ ## Architecture
259
+
260
+ ```
261
+ PushNotifications/
262
+ ├── context/
263
+ │ ├── PushContext.tsx # Generic push state
264
+ │ └── DjangoPushContext.tsx # Django integration
265
+ ├── components/
266
+ │ └── PushPrompt.tsx # Permission prompt
267
+ ├── hooks/
268
+ │ ├── usePushNotifications.ts # Core push logic
269
+ │ └── useDjangoPush.ts # Django push logic
270
+ ├── utils/
271
+ │ ├── vapid.ts # VAPID key utils
272
+ │ ├── localStorage.ts # Persistence
273
+ │ └── logger.ts # Logging
274
+ └── types/
275
+ └── push.ts
276
+ ```
277
+
278
+ ## Separation from PWA Install
279
+
280
+ This snippet is **completely independent** from PWA installation:
281
+
282
+ - **PWAInstall** → Handles device installation
283
+ - **PushNotifications** → Handles web push subscriptions
284
+
285
+ Both can be used together or separately.
286
+
287
+ ## VAPID Keys
288
+
289
+ Generate VAPID keys using:
290
+
291
+ ```bash
292
+ npx web-push generate-vapid-keys
293
+ ```
294
+
295
+ Store public key in environment:
296
+ ```env
297
+ NEXT_PUBLIC_VAPID_KEY=BM8xT...
298
+ ```
299
+
300
+ ## Service Worker
301
+
302
+ Ensure your service worker handles push events:
303
+
304
+ ```js
305
+ // public/sw.js
306
+ self.addEventListener('push', (event) => {
307
+ const data = event.data.json();
308
+
309
+ event.waitUntil(
310
+ self.registration.showNotification(data.title, {
311
+ body: data.body,
312
+ icon: data.icon || '/icon-192.png',
313
+ badge: data.badge || '/badge-72.png',
314
+ data: data.data,
315
+ })
316
+ );
317
+ });
318
+
319
+ self.addEventListener('notificationclick', (event) => {
320
+ event.notification.close();
321
+
322
+ if (event.notification.data?.url) {
323
+ event.waitUntil(
324
+ clients.openWindow(event.notification.data.url)
325
+ );
326
+ }
327
+ });
328
+ ```
@@ -1,7 +1,7 @@
1
1
  /**
2
- * PWA Configuration
2
+ * Push Notifications Configuration
3
3
  *
4
- * Centralized constants for PWA functionality.
4
+ * Centralized constants for push notifications functionality.
5
5
  *
6
6
  * SECURITY NOTE:
7
7
  * - VAPID_PRIVATE_KEY should NEVER be in frontend code