@djangocfg/layouts 2.1.37 → 2.1.38

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,467 @@
1
+ # Service Worker Setup for Push Notifications
2
+
3
+ Complete guide to configuring Service Worker for receiving and displaying push notifications.
4
+
5
+ ## Overview
6
+
7
+ Service Workers handle push notifications **even when your app is closed**. They:
8
+
9
+ 1. Listen for push events from the browser
10
+ 2. Display notifications to the user
11
+ 3. Handle notification clicks
12
+
13
+ ## Prerequisites
14
+
15
+ - ✅ VAPID keys configured ([VAPID Setup Guide](./vapid-setup.md))
16
+ - ✅ Service worker registered in your app
17
+ - ✅ HTTPS (required for service workers)
18
+
19
+ ## Step 1: Create Service Worker File
20
+
21
+ ### Location
22
+
23
+ ```
24
+ public/
25
+ └── sw.js # Service worker file
26
+ ```
27
+
28
+ ### Basic Service Worker
29
+
30
+ ```javascript
31
+ // public/sw.js
32
+
33
+ // Service Worker version (increment to force update)
34
+ const SW_VERSION = 'v1.0.0';
35
+
36
+ console.log(`[SW] ${SW_VERSION} loaded`);
37
+
38
+ // Install event
39
+ self.addEventListener('install', (event) => {
40
+ console.log('[SW] Installing...');
41
+ self.skipWaiting(); // Activate immediately
42
+ });
43
+
44
+ // Activate event
45
+ self.addEventListener('activate', (event) => {
46
+ console.log('[SW] Activating...');
47
+ event.waitUntil(clients.claim()); // Take control immediately
48
+ });
49
+
50
+ // Push event - receive notification
51
+ self.addEventListener('push', (event) => {
52
+ console.log('[SW] Push received');
53
+
54
+ // Parse notification data
55
+ let data = {
56
+ title: 'Notification',
57
+ body: 'You have a new notification',
58
+ icon: '/icon-192.png',
59
+ badge: '/badge-72.png',
60
+ };
61
+
62
+ if (event.data) {
63
+ try {
64
+ data = event.data.json();
65
+ } catch (e) {
66
+ console.error('[SW] Failed to parse push data:', e);
67
+ }
68
+ }
69
+
70
+ // Show notification
71
+ event.waitUntil(
72
+ self.registration.showNotification(data.title, {
73
+ body: data.body,
74
+ icon: data.icon || '/icon-192.png',
75
+ badge: data.badge || '/badge-72.png',
76
+ data: data.data || {}, // Custom data for click handler
77
+ tag: data.tag || 'default', // Notification grouping
78
+ requireInteraction: data.requireInteraction || false,
79
+ })
80
+ );
81
+ });
82
+
83
+ // Notification click event
84
+ self.addEventListener('notificationclick', (event) => {
85
+ console.log('[SW] Notification clicked');
86
+
87
+ event.notification.close();
88
+
89
+ // Handle click action
90
+ const urlToOpen = event.notification.data?.url || '/';
91
+
92
+ event.waitUntil(
93
+ clients.matchAll({ type: 'window', includeUncontrolled: true })
94
+ .then((clientList) => {
95
+ // Check if app is already open
96
+ for (const client of clientList) {
97
+ if (client.url === urlToOpen && 'focus' in client) {
98
+ return client.focus();
99
+ }
100
+ }
101
+
102
+ // Open new window
103
+ if (clients.openWindow) {
104
+ return clients.openWindow(urlToOpen);
105
+ }
106
+ })
107
+ );
108
+ });
109
+ ```
110
+
111
+ ## Step 2: Register Service Worker
112
+
113
+ ### Next.js App
114
+
115
+ ```tsx
116
+ // app/layout.tsx or components/ServiceWorkerRegistration.tsx
117
+ 'use client';
118
+
119
+ import { useEffect } from 'react';
120
+
121
+ export function ServiceWorkerRegistration() {
122
+ useEffect(() => {
123
+ if ('serviceWorker' in navigator) {
124
+ navigator.serviceWorker
125
+ .register('/sw.js')
126
+ .then((registration) => {
127
+ console.log('[App] SW registered:', registration);
128
+ })
129
+ .catch((error) => {
130
+ console.error('[App] SW registration failed:', error);
131
+ });
132
+ }
133
+ }, []);
134
+
135
+ return null;
136
+ }
137
+ ```
138
+
139
+ ```tsx
140
+ // app/layout.tsx
141
+ import { ServiceWorkerRegistration } from '@/components/ServiceWorkerRegistration';
142
+
143
+ export default function RootLayout({ children }) {
144
+ return (
145
+ <html>
146
+ <body>
147
+ <ServiceWorkerRegistration />
148
+ {children}
149
+ </body>
150
+ </html>
151
+ );
152
+ }
153
+ ```
154
+
155
+ ## Step 3: Advanced Features
156
+
157
+ ### Custom Notification Options
158
+
159
+ ```javascript
160
+ // public/sw.js - Enhanced notification options
161
+
162
+ self.addEventListener('push', (event) => {
163
+ const data = event.data ? event.data.json() : {};
164
+
165
+ const options = {
166
+ // Basic
167
+ body: data.body || 'New notification',
168
+ icon: data.icon || '/icon-192.png',
169
+ badge: data.badge || '/badge-72.png',
170
+
171
+ // Visual
172
+ image: data.image, // Large image
173
+ vibrate: [200, 100, 200], // Vibration pattern
174
+ silent: data.silent || false,
175
+
176
+ // Interaction
177
+ requireInteraction: data.important || false,
178
+ tag: data.tag || 'default', // Group notifications
179
+
180
+ // Actions (buttons)
181
+ actions: data.actions || [
182
+ { action: 'view', title: 'View' },
183
+ { action: 'dismiss', title: 'Dismiss' },
184
+ ],
185
+
186
+ // Custom data
187
+ data: {
188
+ url: data.url || '/',
189
+ timestamp: Date.now(),
190
+ ...data.data,
191
+ },
192
+ };
193
+
194
+ event.waitUntil(
195
+ self.registration.showNotification(data.title || 'Notification', options)
196
+ );
197
+ });
198
+ ```
199
+
200
+ ### Handle Notification Actions
201
+
202
+ ```javascript
203
+ // public/sw.js - Handle action buttons
204
+
205
+ self.addEventListener('notificationclick', (event) => {
206
+ event.notification.close();
207
+
208
+ if (event.action === 'view') {
209
+ // User clicked "View" button
210
+ const url = event.notification.data.url || '/';
211
+ event.waitUntil(clients.openWindow(url));
212
+ } else if (event.action === 'dismiss') {
213
+ // User clicked "Dismiss" button
214
+ // Notification already closed
215
+ } else {
216
+ // User clicked notification body
217
+ const url = event.notification.data.url || '/';
218
+ event.waitUntil(clients.openWindow(url));
219
+ }
220
+ });
221
+ ```
222
+
223
+ ### Background Sync
224
+
225
+ ```javascript
226
+ // public/sw.js - Sync notifications with server
227
+
228
+ self.addEventListener('sync', (event) => {
229
+ if (event.tag === 'sync-notifications') {
230
+ event.waitUntil(
231
+ fetch('/api/notifications/sync')
232
+ .then(response => response.json())
233
+ .then(data => {
234
+ // Handle synced notifications
235
+ console.log('[SW] Synced notifications:', data);
236
+ })
237
+ );
238
+ }
239
+ });
240
+ ```
241
+
242
+ ## Step 4: Testing
243
+
244
+ ### Local Testing
245
+
246
+ ```javascript
247
+ // Browser console - Send test notification
248
+
249
+ // 1. Get service worker registration
250
+ navigator.serviceWorker.ready.then((registration) => {
251
+ // 2. Show test notification
252
+ registration.showNotification('Test', {
253
+ body: 'This is a test notification',
254
+ icon: '/icon-192.png',
255
+ data: { url: '/dashboard' },
256
+ });
257
+ });
258
+ ```
259
+
260
+ ### Backend Testing
261
+
262
+ ```python
263
+ # Django - Send test push
264
+ from utils.push import send_push_notification
265
+
266
+ subscription = {
267
+ "endpoint": "https://fcm.googleapis.com/fcm/send/...",
268
+ "keys": {
269
+ "auth": "...",
270
+ "p256dh": "..."
271
+ }
272
+ }
273
+
274
+ notification = {
275
+ "title": "Test Notification",
276
+ "body": "Testing push from backend",
277
+ "icon": "/icon-192.png",
278
+ "data": {"url": "/dashboard"}
279
+ }
280
+
281
+ send_push_notification(subscription, notification)
282
+ ```
283
+
284
+ ## Step 5: Production Optimization
285
+
286
+ ### Service Worker Caching
287
+
288
+ ```javascript
289
+ // public/sw.js - Add caching for offline support
290
+
291
+ const CACHE_NAME = 'pwa-cache-v1';
292
+
293
+ const urlsToCache = [
294
+ '/',
295
+ '/icon-192.png',
296
+ '/badge-72.png',
297
+ ];
298
+
299
+ self.addEventListener('install', (event) => {
300
+ event.waitUntil(
301
+ caches.open(CACHE_NAME)
302
+ .then((cache) => cache.addAll(urlsToCache))
303
+ );
304
+ });
305
+
306
+ self.addEventListener('fetch', (event) => {
307
+ event.respondWith(
308
+ caches.match(event.request)
309
+ .then((response) => response || fetch(event.request))
310
+ );
311
+ });
312
+ ```
313
+
314
+ ### Update Strategy
315
+
316
+ ```javascript
317
+ // public/sw.js - Smart update strategy
318
+
319
+ const SW_VERSION = 'v1.0.1'; // Increment on changes
320
+
321
+ self.addEventListener('activate', (event) => {
322
+ event.waitUntil(
323
+ // Delete old caches
324
+ caches.keys().then((cacheNames) => {
325
+ return Promise.all(
326
+ cacheNames.map((cacheName) => {
327
+ if (cacheName !== CACHE_NAME) {
328
+ console.log('[SW] Deleting old cache:', cacheName);
329
+ return caches.delete(cacheName);
330
+ }
331
+ })
332
+ );
333
+ })
334
+ );
335
+ });
336
+ ```
337
+
338
+ ## Notification Permissions
339
+
340
+ ### Check Permission State
341
+
342
+ ```tsx
343
+ // Check if user granted permission
344
+ const permission = Notification.permission;
345
+ // 'default' | 'granted' | 'denied'
346
+ ```
347
+
348
+ ### Request Permission
349
+
350
+ ```tsx
351
+ import { useDjangoPushContext } from '@/snippets/PushNotifications';
352
+
353
+ function NotifyButton() {
354
+ const { subscribe, permission } = useDjangoPushContext();
355
+
356
+ if (permission === 'denied') {
357
+ return <p>Notifications blocked</p>;
358
+ }
359
+
360
+ return (
361
+ <button onClick={subscribe}>
362
+ {permission === 'granted' ? 'Subscribed' : 'Enable Notifications'}
363
+ </button>
364
+ );
365
+ }
366
+ ```
367
+
368
+ ## Browser Compatibility
369
+
370
+ | Feature | Chrome | Firefox | Safari | Edge |
371
+ |---------|--------|---------|--------|------|
372
+ | Service Workers | ✅ 40+ | ✅ 44+ | ✅ 11.1+ | ✅ 17+ |
373
+ | Push API | ✅ 42+ | ✅ 44+ | ✅ 16.4+ | ✅ 17+ |
374
+ | Notification Actions | ✅ 48+ | ✅ 56+ | ❌ | ✅ 18+ |
375
+ | Badge | ✅ 53+ | ✅ 60+ | ⚠️ Limited | ✅ 18+ |
376
+
377
+ **iOS Note**: Push requires Safari 16.4+ and PWA must be installed.
378
+
379
+ ## Troubleshooting
380
+
381
+ ### Issue: Service Worker Not Registering
382
+
383
+ **Solution**:
384
+ ```javascript
385
+ // Check if HTTPS
386
+ if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
387
+ console.error('Service workers require HTTPS');
388
+ }
389
+
390
+ // Check browser support
391
+ if (!('serviceWorker' in navigator)) {
392
+ console.error('Service workers not supported');
393
+ }
394
+ ```
395
+
396
+ ### Issue: Push Not Received
397
+
398
+ **Check**:
399
+ 1. Service worker registered: `navigator.serviceWorker.ready`
400
+ 2. Push subscription active: `registration.pushManager.getSubscription()`
401
+ 3. Notification permission: `Notification.permission === 'granted'`
402
+ 4. VAPID keys match between frontend and backend
403
+
404
+ ### Issue: Notification Not Showing
405
+
406
+ **Solution**:
407
+ ```javascript
408
+ // Ensure notification is shown in push event
409
+ self.addEventListener('push', (event) => {
410
+ // ❌ Wrong - async not awaited
411
+ self.registration.showNotification('Test');
412
+
413
+ // ✅ Correct - use event.waitUntil
414
+ event.waitUntil(
415
+ self.registration.showNotification('Test')
416
+ );
417
+ });
418
+ ```
419
+
420
+ ## Security
421
+
422
+ ### Content Security Policy
423
+
424
+ ```html
425
+ <!-- Add to HTML head -->
426
+ <meta http-equiv="Content-Security-Policy"
427
+ content="default-src 'self';
428
+ connect-src 'self' https://*.googleapis.com;
429
+ img-src 'self' data: https:;">
430
+ ```
431
+
432
+ ### Validate Push Data
433
+
434
+ ```javascript
435
+ // public/sw.js - Validate notification data
436
+
437
+ function isValidNotification(data) {
438
+ return (
439
+ data &&
440
+ typeof data.title === 'string' &&
441
+ typeof data.body === 'string'
442
+ );
443
+ }
444
+
445
+ self.addEventListener('push', (event) => {
446
+ const data = event.data ? event.data.json() : null;
447
+
448
+ if (!isValidNotification(data)) {
449
+ console.error('[SW] Invalid notification data');
450
+ return;
451
+ }
452
+
453
+ // Process valid notification...
454
+ });
455
+ ```
456
+
457
+ ## Next Steps
458
+
459
+ - [VAPID Setup](./vapid-setup.md) - Configure VAPID keys
460
+ - [Django Integration](./django-integration.md) - Backend setup
461
+ - [Main README](../../README.md) - API reference
462
+
463
+ ## References
464
+
465
+ - [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
466
+ - [Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)
467
+ - [Notifications API](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API)