@djangocfg/layouts 2.1.138 → 2.1.140
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.
- package/README.md +2 -96
- package/package.json +14 -14
- package/src/components/errors/ErrorsTracker/components/ErrorToast.tsx +3 -21
- package/src/components/errors/ErrorsTracker/providers/ErrorTrackingProvider.tsx +83 -2
- package/src/layouts/AppLayout/AppLayout.tsx +0 -6
- package/src/layouts/AppLayout/BaseApp.tsx +47 -75
- package/src/layouts/types/index.ts +0 -1
- package/src/layouts/types/layout.types.ts +0 -4
- package/src/snippets/index.ts +0 -23
- package/src/snippets/PushNotifications/@docs/README.md +0 -191
- package/src/snippets/PushNotifications/@docs/guides/django-integration.md +0 -648
- package/src/snippets/PushNotifications/@docs/guides/service-worker.md +0 -467
- package/src/snippets/PushNotifications/@docs/guides/vapid-setup.md +0 -352
- package/src/snippets/PushNotifications/README.md +0 -328
- package/src/snippets/PushNotifications/components/PushPrompt.tsx +0 -184
- package/src/snippets/PushNotifications/config.ts +0 -20
- package/src/snippets/PushNotifications/context/DjangoPushContext.tsx +0 -192
- package/src/snippets/PushNotifications/hooks/useDjangoPush.ts +0 -260
- package/src/snippets/PushNotifications/hooks/usePushNotifications.ts +0 -225
- package/src/snippets/PushNotifications/index.ts +0 -97
- package/src/snippets/PushNotifications/types/config.ts +0 -28
- package/src/snippets/PushNotifications/types/index.ts +0 -9
- package/src/snippets/PushNotifications/types/push.ts +0 -21
- package/src/snippets/PushNotifications/utils/localStorage.ts +0 -60
- package/src/snippets/PushNotifications/utils/logger.ts +0 -149
- package/src/snippets/PushNotifications/utils/platform.ts +0 -326
- package/src/snippets/PushNotifications/utils/vapid.ts +0 -226
package/README.md
CHANGED
|
@@ -29,10 +29,6 @@ export default function RootLayout({ children }) {
|
|
|
29
29
|
analytics={{ googleTrackingId: 'G-XXXXXXXXXX' }}
|
|
30
30
|
centrifugo={{ enabled: true, url: process.env.NEXT_PUBLIC_CENTRIFUGO_URL }}
|
|
31
31
|
pwaInstall={{ enabled: true, showInstallHint: true }}
|
|
32
|
-
pushNotifications={{
|
|
33
|
-
enabled: true,
|
|
34
|
-
vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_KEY || ''
|
|
35
|
-
}}
|
|
36
32
|
mcpChat={{ enabled: true, autoDetectEnvironment: true }}
|
|
37
33
|
>
|
|
38
34
|
{children}
|
|
@@ -51,7 +47,6 @@ export default function RootLayout({ children }) {
|
|
|
51
47
|
- **AnalyticsProvider** - Google Analytics (optional)
|
|
52
48
|
- **CentrifugoProvider** - WebSocket real-time (optional)
|
|
53
49
|
- **PwaProvider** - PWA installation (optional)
|
|
54
|
-
- **DjangoPushProvider** - Django web push integration with history (optional)
|
|
55
50
|
- **ErrorTrackingProvider** - Error handling and tracking
|
|
56
51
|
- **ErrorBoundary** - React error boundary
|
|
57
52
|
- **MCP Chat Widget** - AI chat assistant (optional)
|
|
@@ -60,7 +55,6 @@ export default function RootLayout({ children }) {
|
|
|
60
55
|
- **PageProgress** - NProgress bar for route changes
|
|
61
56
|
- **Toaster** - Toast notifications container
|
|
62
57
|
- **A2HSHint** - PWA install hint (if enabled)
|
|
63
|
-
- **PushPrompt** - Push notification prompt (if enabled)
|
|
64
58
|
|
|
65
59
|
> **Note:** Auth functionality is provided by `@djangocfg/api` package. See [@djangocfg/api documentation](../api/README.md) for auth usage.
|
|
66
60
|
|
|
@@ -84,10 +78,6 @@ export default function RootLayout({ children }) {
|
|
|
84
78
|
theme={{ defaultTheme: 'system' }}
|
|
85
79
|
analytics={{ googleTrackingId: 'G-XXXXXXXXXX' }}
|
|
86
80
|
pwaInstall={{ enabled: true }}
|
|
87
|
-
pushNotifications={{
|
|
88
|
-
enabled: true,
|
|
89
|
-
vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_KEY || ''
|
|
90
|
-
}}
|
|
91
81
|
|
|
92
82
|
// Layout components
|
|
93
83
|
publicLayout={{
|
|
@@ -298,11 +288,7 @@ Built-in tracking for:
|
|
|
298
288
|
- **OAuth events** - GitHub OAuth start, success, failure
|
|
299
289
|
- **Errors** - React ErrorBoundary errors
|
|
300
290
|
|
|
301
|
-
## PWA
|
|
302
|
-
|
|
303
|
-
Progressive Web App features and Web Push notifications support.
|
|
304
|
-
|
|
305
|
-
### PWA Installation
|
|
291
|
+
## PWA Installation
|
|
306
292
|
|
|
307
293
|
Enable PWA installation prompts with `pwaInstall` config:
|
|
308
294
|
|
|
@@ -333,89 +319,13 @@ import { BaseApp } from '@djangocfg/layouts';
|
|
|
333
319
|
**Page Resume:**
|
|
334
320
|
When `resumeLastPage: true`, the app saves the current pathname on every navigation and restores it when the PWA is launched. Pages like `/auth`, `/login`, `/error` are automatically excluded. Data expires after 24 hours.
|
|
335
321
|
|
|
336
|
-
### Push Notifications
|
|
337
|
-
|
|
338
|
-
Enable Web Push notifications with `pushNotifications` config:
|
|
339
|
-
|
|
340
|
-
```tsx
|
|
341
|
-
import { BaseApp } from '@djangocfg/layouts';
|
|
342
|
-
|
|
343
|
-
<BaseApp
|
|
344
|
-
pushNotifications={{
|
|
345
|
-
enabled: true,
|
|
346
|
-
vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_KEY || '',
|
|
347
|
-
subscribeEndpoint: '/api/push/subscribe', // Backend subscription endpoint
|
|
348
|
-
requirePWA: true, // Only show if installed as PWA
|
|
349
|
-
autoSubscribe: false, // Auto-subscribe on install
|
|
350
|
-
delayMs: 5000, // Delay before showing prompt
|
|
351
|
-
resetAfterDays: 7, // Re-show after dismissal
|
|
352
|
-
}}
|
|
353
|
-
>
|
|
354
|
-
{children}
|
|
355
|
-
</BaseApp>
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
**Features:**
|
|
359
|
-
- **PushPrompt** - Permission request prompt
|
|
360
|
-
- **VAPID authentication** - Secure push with VAPID keys
|
|
361
|
-
- **Subscription management** - Auto-subscribe and unsubscribe
|
|
362
|
-
- **PWA integration** - Can require PWA installation first
|
|
363
|
-
- **Custom timing** - Configurable delay and reset
|
|
364
|
-
|
|
365
322
|
### Usage
|
|
366
323
|
|
|
367
324
|
```tsx
|
|
368
|
-
import { usePwa
|
|
325
|
+
import { usePwa } from '@djangocfg/layouts/snippets';
|
|
369
326
|
|
|
370
327
|
// PWA status
|
|
371
328
|
const { isPWA, isInstallable } = usePwa();
|
|
372
|
-
|
|
373
|
-
// Push notifications (Django integration)
|
|
374
|
-
const {
|
|
375
|
-
isSupported,
|
|
376
|
-
isSubscribed,
|
|
377
|
-
permission,
|
|
378
|
-
pushes,
|
|
379
|
-
subscribe,
|
|
380
|
-
unsubscribe,
|
|
381
|
-
sendPush,
|
|
382
|
-
clearPushes,
|
|
383
|
-
removePush,
|
|
384
|
-
} = useDjangoPushContext();
|
|
385
|
-
|
|
386
|
-
// Subscribe to push
|
|
387
|
-
await subscribe();
|
|
388
|
-
|
|
389
|
-
// Send test notification (via Django API)
|
|
390
|
-
await sendPush({
|
|
391
|
-
title: 'Hello!',
|
|
392
|
-
body: 'Test notification',
|
|
393
|
-
icon: '/icon.png',
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
// Manage push history
|
|
397
|
-
clearPushes(); // Clear all
|
|
398
|
-
removePush(pushId); // Remove specific push
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
### VAPID Keys Generation
|
|
402
|
-
|
|
403
|
-
Generate VAPID keys for push notifications:
|
|
404
|
-
|
|
405
|
-
```bash
|
|
406
|
-
# Using web-push CLI
|
|
407
|
-
npx web-push generate-vapid-keys
|
|
408
|
-
|
|
409
|
-
# Or use openssl
|
|
410
|
-
openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem
|
|
411
|
-
openssl ec -in vapid_private.pem -pubout -out vapid_public.pem
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
Add keys to `.env.local`:
|
|
415
|
-
|
|
416
|
-
```env
|
|
417
|
-
NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key_here
|
|
418
|
-
VAPID_PRIVATE_KEY=your_private_key_here
|
|
419
329
|
```
|
|
420
330
|
|
|
421
331
|
## Snippets
|
|
@@ -427,7 +337,6 @@ import {
|
|
|
427
337
|
Breadcrumbs,
|
|
428
338
|
AuthDialog,
|
|
429
339
|
usePwa,
|
|
430
|
-
useDjangoPushContext,
|
|
431
340
|
} from '@djangocfg/layouts/snippets';
|
|
432
341
|
```
|
|
433
342
|
|
|
@@ -438,11 +347,8 @@ import {
|
|
|
438
347
|
| `AnalyticsProvider` | Analytics wrapper component |
|
|
439
348
|
| `usePwa` | PWA status hook (isPWA, isInstallable, etc.) |
|
|
440
349
|
| `usePWAPageResume` | Resume last page on PWA launch |
|
|
441
|
-
| `useDjangoPushContext` | Django push notifications hook (subscribe, send, history) |
|
|
442
|
-
| `usePush` | Generic push hook (for non-Django apps) |
|
|
443
350
|
| `A2HSHint` | Add to Home Screen hint component |
|
|
444
351
|
| `PWAPageResumeManager` | Component for PWA page resume (use via BaseApp config) |
|
|
445
|
-
| `PushPrompt` | Push notification permission prompt |
|
|
446
352
|
|
|
447
353
|
> **Extension Snippets:** Additional components are available in extension packages:
|
|
448
354
|
> - `@djangocfg/ext-leads` - ContactForm, ContactPage, ContactInfo
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.140",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -74,12 +74,12 @@
|
|
|
74
74
|
"check": "tsc --noEmit"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"@djangocfg/api": "^2.1.
|
|
78
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
79
|
-
"@djangocfg/i18n": "^2.1.
|
|
80
|
-
"@djangocfg/ui-core": "^2.1.
|
|
81
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
82
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
77
|
+
"@djangocfg/api": "^2.1.140",
|
|
78
|
+
"@djangocfg/centrifugo": "^2.1.140",
|
|
79
|
+
"@djangocfg/i18n": "^2.1.140",
|
|
80
|
+
"@djangocfg/ui-core": "^2.1.140",
|
|
81
|
+
"@djangocfg/ui-nextjs": "^2.1.140",
|
|
82
|
+
"@djangocfg/ui-tools": "^2.1.140",
|
|
83
83
|
"@hookform/resolvers": "^5.2.2",
|
|
84
84
|
"consola": "^3.4.2",
|
|
85
85
|
"lucide-react": "^0.545.0",
|
|
@@ -102,13 +102,13 @@
|
|
|
102
102
|
"uuid": "^11.1.0"
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
|
-
"@djangocfg/api": "^2.1.
|
|
106
|
-
"@djangocfg/i18n": "^2.1.
|
|
107
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
108
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
109
|
-
"@djangocfg/ui-core": "^2.1.
|
|
110
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
111
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
105
|
+
"@djangocfg/api": "^2.1.140",
|
|
106
|
+
"@djangocfg/i18n": "^2.1.140",
|
|
107
|
+
"@djangocfg/centrifugo": "^2.1.140",
|
|
108
|
+
"@djangocfg/typescript-config": "^2.1.140",
|
|
109
|
+
"@djangocfg/ui-core": "^2.1.140",
|
|
110
|
+
"@djangocfg/ui-nextjs": "^2.1.140",
|
|
111
|
+
"@djangocfg/ui-tools": "^2.1.140",
|
|
112
112
|
"@types/node": "^24.7.2",
|
|
113
113
|
"@types/react": "^19.1.0",
|
|
114
114
|
"@types/react-dom": "^19.1.0",
|
|
@@ -60,36 +60,18 @@ function buildValidationDescription(
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Build CORS error description
|
|
63
|
+
* Simplified: just show that server is unavailable, without CORS details
|
|
63
64
|
*/
|
|
64
65
|
function buildCORSDescription(
|
|
65
66
|
detail: CORSErrorDetail,
|
|
66
67
|
config: Required<CORSErrorConfig>
|
|
67
68
|
): React.ReactNode {
|
|
68
69
|
const domain = extractDomain(detail.url);
|
|
69
|
-
const parts: string[] = [];
|
|
70
|
-
|
|
71
|
-
// Add method and URL info
|
|
72
|
-
if (config.showMethod && config.showUrl) {
|
|
73
|
-
parts.push(`${detail.method} ${detail.url}`);
|
|
74
|
-
} else if (config.showUrl) {
|
|
75
|
-
parts.push(detail.url);
|
|
76
|
-
} else if (config.showMethod) {
|
|
77
|
-
parts.push(`${detail.method} request blocked`);
|
|
78
|
-
}
|
|
79
70
|
|
|
80
71
|
return (
|
|
81
72
|
<div className="flex flex-col gap-2 text-sm">
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{parts.join(' • ')}
|
|
85
|
-
</div>
|
|
86
|
-
)}
|
|
87
|
-
|
|
88
|
-
<div className="flex flex-col gap-2">
|
|
89
|
-
<div className="font-medium">Request blocked by CORS policy</div>
|
|
90
|
-
<div className="text-xs opacity-75">
|
|
91
|
-
Check CORS configuration on {domain}
|
|
92
|
-
</div>
|
|
73
|
+
<div className="opacity-90">
|
|
74
|
+
Server {domain} is unavailable
|
|
93
75
|
</div>
|
|
94
76
|
|
|
95
77
|
<ErrorButtons detail={detail} />
|
|
@@ -65,6 +65,84 @@ function generateErrorId(type: string): string {
|
|
|
65
65
|
return `${type}-error-${Date.now()}-${++errorIdCounter}`;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Generate deduplication key for error
|
|
70
|
+
* Used to prevent duplicate toasts for the same error
|
|
71
|
+
*/
|
|
72
|
+
function getErrorDedupeKey(detail: ErrorDetail): string {
|
|
73
|
+
switch (detail.type) {
|
|
74
|
+
case 'validation':
|
|
75
|
+
return `validation:${detail.path}:${detail.method}`;
|
|
76
|
+
case 'cors':
|
|
77
|
+
return `cors:${detail.url}`;
|
|
78
|
+
case 'network':
|
|
79
|
+
return `network:${detail.url}:${detail.statusCode || 'unknown'}`;
|
|
80
|
+
case 'centrifugo':
|
|
81
|
+
return `centrifugo:${detail.method}:${detail.code || 'unknown'}`;
|
|
82
|
+
default:
|
|
83
|
+
return `unknown:${Date.now()}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Deduplication window in ms - same errors within this window are ignored */
|
|
88
|
+
const DEDUPE_WINDOW_MS = 5000;
|
|
89
|
+
|
|
90
|
+
/** Max entries in deduplication map before forced cleanup */
|
|
91
|
+
const DEDUPE_MAX_ENTRIES = 50;
|
|
92
|
+
|
|
93
|
+
/** Map to track recent errors for deduplication (browser-only) */
|
|
94
|
+
let recentErrors: Map<string, number> | null = null;
|
|
95
|
+
|
|
96
|
+
function getRecentErrorsMap(): Map<string, number> {
|
|
97
|
+
// Lazy init - only create in browser
|
|
98
|
+
if (typeof window === 'undefined') {
|
|
99
|
+
// Return dummy map for SSR (won't persist)
|
|
100
|
+
return new Map();
|
|
101
|
+
}
|
|
102
|
+
if (!recentErrors) {
|
|
103
|
+
recentErrors = new Map();
|
|
104
|
+
}
|
|
105
|
+
return recentErrors;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if error should be shown (not a duplicate)
|
|
110
|
+
* Returns true if error should be shown, false if it's a duplicate
|
|
111
|
+
*/
|
|
112
|
+
function shouldShowError(detail: ErrorDetail): boolean {
|
|
113
|
+
const map = getRecentErrorsMap();
|
|
114
|
+
const key = getErrorDedupeKey(detail);
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
const lastSeen = map.get(key);
|
|
117
|
+
|
|
118
|
+
// Clean up old entries when map gets too large
|
|
119
|
+
if (map.size > DEDUPE_MAX_ENTRIES) {
|
|
120
|
+
for (const [k, timestamp] of map.entries()) {
|
|
121
|
+
if (now - timestamp > DEDUPE_WINDOW_MS) {
|
|
122
|
+
map.delete(k);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// If still too large after cleanup, clear oldest half
|
|
126
|
+
if (map.size > DEDUPE_MAX_ENTRIES) {
|
|
127
|
+
const entries = Array.from(map.entries())
|
|
128
|
+
.sort((a, b) => a[1] - b[1])
|
|
129
|
+
.slice(0, Math.floor(map.size / 2));
|
|
130
|
+
for (const [k] of entries) {
|
|
131
|
+
map.delete(k);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check if we've seen this error recently
|
|
137
|
+
if (lastSeen && now - lastSeen < DEDUPE_WINDOW_MS) {
|
|
138
|
+
return false; // Duplicate, don't show
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Mark as seen
|
|
142
|
+
map.set(key, now);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
68
146
|
export interface ErrorTrackingProviderProps {
|
|
69
147
|
children: ReactNode;
|
|
70
148
|
validation?: Partial<ValidationErrorConfig>;
|
|
@@ -136,6 +214,9 @@ export function ErrorTrackingProvider({
|
|
|
136
214
|
*/
|
|
137
215
|
const handleError = useCallback(
|
|
138
216
|
(detail: ErrorDetail, config: Required<ValidationErrorConfig | CORSErrorConfig | NetworkErrorConfig | CentrifugoErrorConfig>) => {
|
|
217
|
+
// Check for duplicate errors (within deduplication window)
|
|
218
|
+
const isUnique = shouldShowError(detail);
|
|
219
|
+
|
|
139
220
|
// Create stored error with ID
|
|
140
221
|
const storedError: StoredError = {
|
|
141
222
|
...detail,
|
|
@@ -151,8 +232,8 @@ export function ErrorTrackingProvider({
|
|
|
151
232
|
// Call custom error handler
|
|
152
233
|
const shouldShowToast = onError?.(detail) !== false;
|
|
153
234
|
|
|
154
|
-
// Show toast notification using Sonner
|
|
155
|
-
if (config.showToast && shouldShowToast) {
|
|
235
|
+
// Show toast notification using Sonner (only for unique errors)
|
|
236
|
+
if (config.showToast && shouldShowToast && isUnique) {
|
|
156
237
|
const toastOptions = createErrorToast(detail, config);
|
|
157
238
|
toast.error(toastOptions.title, {
|
|
158
239
|
description: toastOptions.description,
|
|
@@ -47,7 +47,6 @@ import type {
|
|
|
47
47
|
SWRConfigOptions,
|
|
48
48
|
McpChatConfig,
|
|
49
49
|
PwaInstallConfig,
|
|
50
|
-
PushNotificationsConfig,
|
|
51
50
|
} from '../types';
|
|
52
51
|
import type { AuthConfig } from '@djangocfg/api/auth';
|
|
53
52
|
|
|
@@ -147,9 +146,6 @@ export interface AppLayoutProps {
|
|
|
147
146
|
/** PWA Install configuration */
|
|
148
147
|
pwaInstall?: PwaInstallConfig;
|
|
149
148
|
|
|
150
|
-
/** Push Notifications configuration */
|
|
151
|
-
pushNotifications?: PushNotificationsConfig;
|
|
152
|
-
|
|
153
149
|
/** i18n configuration for locale switching (applies to all layouts) */
|
|
154
150
|
i18n?: I18nLayoutConfig;
|
|
155
151
|
}
|
|
@@ -272,7 +268,6 @@ export function AppLayout(props: AppLayoutProps) {
|
|
|
272
268
|
swr,
|
|
273
269
|
mcpChat,
|
|
274
270
|
pwaInstall,
|
|
275
|
-
pushNotifications,
|
|
276
271
|
} = props;
|
|
277
272
|
|
|
278
273
|
return (
|
|
@@ -286,7 +281,6 @@ export function AppLayout(props: AppLayoutProps) {
|
|
|
286
281
|
swr={swr}
|
|
287
282
|
mcpChat={mcpChat}
|
|
288
283
|
pwaInstall={pwaInstall}
|
|
289
|
-
pushNotifications={pushNotifications}
|
|
290
284
|
>
|
|
291
285
|
<AppLayoutContent {...props} />
|
|
292
286
|
</BaseApp>
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* - AnalyticsProvider (Google Analytics, optional)
|
|
11
11
|
* - CentrifugoProvider (WebSocket real-time, optional)
|
|
12
12
|
* - PwaProvider (PWA installation, optional)
|
|
13
|
-
* - DjangoPushProvider (Django web push integration, optional)
|
|
14
13
|
* - ErrorTrackingProvider (error handling)
|
|
15
14
|
* - ErrorBoundary (React error boundary, enabled by default)
|
|
16
15
|
* - MCP Chat Widget (optional, lazy loaded)
|
|
@@ -19,17 +18,12 @@
|
|
|
19
18
|
* ```tsx
|
|
20
19
|
* import { BaseApp } from '@djangocfg/layouts';
|
|
21
20
|
*
|
|
22
|
-
* // With PWA Install
|
|
21
|
+
* // With PWA Install
|
|
23
22
|
* <BaseApp
|
|
24
23
|
* theme={{ defaultTheme: 'system' }}
|
|
25
24
|
* auth={{ loginPath: '/auth/login' }}
|
|
26
25
|
* analytics={{ googleTrackingId: 'G-XXXXXXXXXX' }}
|
|
27
26
|
* pwaInstall={{ enabled: true, logo: '/logo192.png' }}
|
|
28
|
-
* pushNotifications={{
|
|
29
|
-
* enabled: true,
|
|
30
|
-
* vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_KEY,
|
|
31
|
-
* requirePWA: true
|
|
32
|
-
* }}
|
|
33
27
|
* mcpChat={{ enabled: true, autoDetectEnvironment: true }}
|
|
34
28
|
* >
|
|
35
29
|
* {children}
|
|
@@ -46,7 +40,6 @@
|
|
|
46
40
|
|
|
47
41
|
import dynamic from 'next/dynamic';
|
|
48
42
|
import NextTopLoader from 'nextjs-toploader';
|
|
49
|
-
import { ReactNode } from 'react';
|
|
50
43
|
import { SWRConfig } from 'swr';
|
|
51
44
|
|
|
52
45
|
import { getCentrifugoAuthTokenRetrieve } from '@djangocfg/api';
|
|
@@ -58,7 +51,6 @@ import { ErrorBoundary } from '../../components/errors/ErrorBoundary';
|
|
|
58
51
|
import { ErrorTrackingProvider } from '../../components/errors/ErrorsTracker';
|
|
59
52
|
import { AnalyticsProvider } from '../../snippets/Analytics';
|
|
60
53
|
import { AuthDialog } from '../../snippets/AuthDialog';
|
|
61
|
-
import { DjangoPushProvider, PushPrompt } from '../../snippets/PushNotifications';
|
|
62
54
|
import { A2HSHint, PWAPageResumeManager, PwaProvider } from '../../snippets/PWAInstall';
|
|
63
55
|
|
|
64
56
|
import type { BaseLayoutProps } from '../types/layout.types';
|
|
@@ -76,7 +68,7 @@ export type BaseAppProps = BaseLayoutProps;
|
|
|
76
68
|
* BaseApp - Core providers wrapper for any React/Next.js app
|
|
77
69
|
*
|
|
78
70
|
* Includes: ThemeProvider, TooltipProvider, SWRConfig, AuthProvider, AnalyticsProvider,
|
|
79
|
-
* CentrifugoProvider, PwaProvider,
|
|
71
|
+
* CentrifugoProvider, PwaProvider, ErrorTrackingProvider,
|
|
80
72
|
* ErrorBoundary (optional), MCP Chat (optional)
|
|
81
73
|
* Also renders global Toaster and PageProgress components
|
|
82
74
|
*/
|
|
@@ -91,7 +83,6 @@ export function BaseApp({
|
|
|
91
83
|
swr,
|
|
92
84
|
mcpChat,
|
|
93
85
|
pwaInstall,
|
|
94
|
-
pushNotifications,
|
|
95
86
|
}: BaseAppProps) {
|
|
96
87
|
// ErrorBoundary is enabled by default
|
|
97
88
|
const enableErrorBoundary = errorBoundary?.enabled !== false;
|
|
@@ -100,9 +91,6 @@ export function BaseApp({
|
|
|
100
91
|
const pwaInstallEnabled = pwaInstall?.enabled !== false;
|
|
101
92
|
const showInstallHint = pwaInstallEnabled && pwaInstall?.showInstallHint !== false;
|
|
102
93
|
|
|
103
|
-
// Check if Push Notifications is enabled
|
|
104
|
-
const pushEnabled = pushNotifications?.enabled !== false && !!pushNotifications?.vapidPublicKey;
|
|
105
|
-
|
|
106
94
|
// Centrifugo configuration
|
|
107
95
|
const centrifugoUrl = centrifugo?.url || process.env.NEXT_PUBLIC_CENTRIFUGO_URL;
|
|
108
96
|
const centrifugoEnabled = centrifugo?.enabled !== false;
|
|
@@ -132,69 +120,53 @@ export function BaseApp({
|
|
|
132
120
|
}}
|
|
133
121
|
>
|
|
134
122
|
<PwaProvider enabled={pwaInstallEnabled}>
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
|
|
123
|
+
<ErrorTrackingProvider
|
|
124
|
+
validation={errorTracking?.validation}
|
|
125
|
+
cors={errorTracking?.cors}
|
|
126
|
+
network={errorTracking?.network}
|
|
127
|
+
onError={errorTracking?.onError}
|
|
138
128
|
>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
129
|
+
{children}
|
|
130
|
+
<NextTopLoader
|
|
131
|
+
color="hsl(var(--primary))"
|
|
132
|
+
height={3}
|
|
133
|
+
showSpinner={false}
|
|
134
|
+
shadow="0 0 10px hsl(var(--primary)), 0 0 5px hsl(var(--primary))"
|
|
135
|
+
/>
|
|
136
|
+
<Toaster />
|
|
137
|
+
|
|
138
|
+
{/* PWA Install Hint */}
|
|
139
|
+
{showInstallHint && (
|
|
140
|
+
<A2HSHint
|
|
141
|
+
resetAfterDays={pwaInstall?.resetAfterDays}
|
|
142
|
+
delayMs={pwaInstall?.delayMs}
|
|
143
|
+
logo={pwaInstall?.logo}
|
|
144
|
+
/>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{/* PWA Page Resume Manager */}
|
|
148
|
+
{pwaInstallEnabled && pwaInstall?.resumeLastPage && (
|
|
149
|
+
<PWAPageResumeManager enabled={true} />
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{/* MCP Chat Widget - lazy loaded */}
|
|
153
|
+
{mcpChat?.enabled && (
|
|
154
|
+
<AIChatWidget
|
|
155
|
+
apiEndpoint={mcpChat.apiEndpoint}
|
|
156
|
+
title={mcpChat.title}
|
|
157
|
+
placeholder={mcpChat.placeholder}
|
|
158
|
+
greeting={mcpChat.greeting}
|
|
159
|
+
position={mcpChat.position}
|
|
160
|
+
variant={mcpChat.variant}
|
|
161
|
+
enableStreaming={mcpChat.enableStreaming}
|
|
162
|
+
autoDetectEnvironment={mcpChat.autoDetectEnvironment}
|
|
163
|
+
className={mcpChat.className}
|
|
151
164
|
/>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
resetAfterDays={pwaInstall?.resetAfterDays}
|
|
158
|
-
delayMs={pwaInstall?.delayMs}
|
|
159
|
-
logo={pwaInstall?.logo}
|
|
160
|
-
/>
|
|
161
|
-
)}
|
|
162
|
-
|
|
163
|
-
{/* PWA Page Resume Manager */}
|
|
164
|
-
{pwaInstallEnabled && pwaInstall?.resumeLastPage && (
|
|
165
|
-
<PWAPageResumeManager enabled={true} />
|
|
166
|
-
)}
|
|
167
|
-
|
|
168
|
-
{/* Push Notifications Prompt */}
|
|
169
|
-
{pushEnabled && (
|
|
170
|
-
<PushPrompt
|
|
171
|
-
vapidPublicKey={pushNotifications!.vapidPublicKey}
|
|
172
|
-
subscribeEndpoint={pushNotifications?.subscribeEndpoint}
|
|
173
|
-
requirePWA={pushNotifications?.requirePWA ?? true}
|
|
174
|
-
delayMs={pushNotifications?.delayMs}
|
|
175
|
-
resetAfterDays={pushNotifications?.resetAfterDays}
|
|
176
|
-
/>
|
|
177
|
-
)}
|
|
178
|
-
|
|
179
|
-
{/* MCP Chat Widget - lazy loaded */}
|
|
180
|
-
{mcpChat?.enabled && (
|
|
181
|
-
<AIChatWidget
|
|
182
|
-
apiEndpoint={mcpChat.apiEndpoint}
|
|
183
|
-
title={mcpChat.title}
|
|
184
|
-
placeholder={mcpChat.placeholder}
|
|
185
|
-
greeting={mcpChat.greeting}
|
|
186
|
-
position={mcpChat.position}
|
|
187
|
-
variant={mcpChat.variant}
|
|
188
|
-
enableStreaming={mcpChat.enableStreaming}
|
|
189
|
-
autoDetectEnvironment={mcpChat.autoDetectEnvironment}
|
|
190
|
-
className={mcpChat.className}
|
|
191
|
-
/>
|
|
192
|
-
)}
|
|
193
|
-
|
|
194
|
-
{/* Auth Dialog - global auth prompt */}
|
|
195
|
-
<AuthDialog authPath={auth?.routes?.auth} />
|
|
196
|
-
</ErrorTrackingProvider>
|
|
197
|
-
</DjangoPushProvider>
|
|
165
|
+
)}
|
|
166
|
+
|
|
167
|
+
{/* Auth Dialog - global auth prompt */}
|
|
168
|
+
<AuthDialog authPath={auth?.routes?.auth} />
|
|
169
|
+
</ErrorTrackingProvider>
|
|
198
170
|
</PwaProvider>
|
|
199
171
|
</CentrifugoProvider>
|
|
200
172
|
</AnalyticsProvider>
|
|
@@ -15,7 +15,6 @@ export type { ThemeConfig, SWRConfigOptions, McpChatConfig, CentrifugoConfig } f
|
|
|
15
15
|
// External provider configs (re-export for convenience)
|
|
16
16
|
export type { AnalyticsConfig } from '../../snippets/Analytics/types';
|
|
17
17
|
export type { PwaInstallConfig } from '../../snippets/PWAInstall/types';
|
|
18
|
-
export type { PushNotificationsConfig } from '../../snippets/PushNotifications/types';
|
|
19
18
|
export type {
|
|
20
19
|
ErrorBoundaryConfig,
|
|
21
20
|
ErrorTrackingConfig,
|
|
@@ -10,7 +10,6 @@ import type { AuthConfig } from '@djangocfg/api/auth';
|
|
|
10
10
|
// Import provider configs from their modules
|
|
11
11
|
import type { AnalyticsConfig } from '../../snippets/Analytics/types';
|
|
12
12
|
import type { PwaInstallConfig } from '../../snippets/PWAInstall/types';
|
|
13
|
-
import type { PushNotificationsConfig } from '../../snippets/PushNotifications/types';
|
|
14
13
|
import type { ErrorBoundaryConfig, ErrorTrackingConfig } from '../../components/errors/types';
|
|
15
14
|
|
|
16
15
|
// Import local provider configs
|
|
@@ -55,7 +54,4 @@ export interface BaseLayoutProps {
|
|
|
55
54
|
|
|
56
55
|
/** PWA Install configuration (from snippets/PWAInstall) */
|
|
57
56
|
pwaInstall?: PwaInstallConfig;
|
|
58
|
-
|
|
59
|
-
/** Push Notifications configuration (from snippets/PushNotifications) */
|
|
60
|
-
pushNotifications?: PushNotificationsConfig;
|
|
61
57
|
}
|
package/src/snippets/index.ts
CHANGED
|
@@ -76,26 +76,3 @@ export type {
|
|
|
76
76
|
UseIsPWAOptions,
|
|
77
77
|
} from './PWAInstall';
|
|
78
78
|
|
|
79
|
-
// Push Notifications
|
|
80
|
-
export {
|
|
81
|
-
PushProvider,
|
|
82
|
-
usePush,
|
|
83
|
-
DjangoPushProvider,
|
|
84
|
-
useDjangoPushContext,
|
|
85
|
-
PushPrompt,
|
|
86
|
-
usePushNotifications,
|
|
87
|
-
useDjangoPush,
|
|
88
|
-
DEFAULT_VAPID_PUBLIC_KEY,
|
|
89
|
-
urlBase64ToUint8Array,
|
|
90
|
-
isValidVapidKey,
|
|
91
|
-
getVapidKeyInfo,
|
|
92
|
-
safeUrlBase64ToUint8Array,
|
|
93
|
-
VapidKeyError,
|
|
94
|
-
clearAllPushData,
|
|
95
|
-
} from './PushNotifications';
|
|
96
|
-
export type {
|
|
97
|
-
PushNotificationState,
|
|
98
|
-
PushNotificationOptions,
|
|
99
|
-
PushMessage,
|
|
100
|
-
VapidKeyErrorCode,
|
|
101
|
-
} from './PushNotifications';
|