@djangocfg/layouts 2.1.36 → 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 (64) 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 +105 -28
  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/McpChat/context/ChatContext.tsx +9 -0
  23. package/src/snippets/PWAInstall/@docs/README.md +92 -0
  24. package/src/snippets/PWAInstall/@docs/research/ios-android-install-flows.md +576 -0
  25. package/src/snippets/PWAInstall/README.md +185 -0
  26. package/src/snippets/PWAInstall/components/A2HSHint.tsx +227 -0
  27. package/src/snippets/PWAInstall/components/DesktopGuide.tsx +229 -0
  28. package/src/snippets/PWAInstall/components/IOSGuide.tsx +29 -0
  29. package/src/snippets/PWAInstall/components/IOSGuideDrawer.tsx +101 -0
  30. package/src/snippets/PWAInstall/components/IOSGuideModal.tsx +101 -0
  31. package/src/snippets/PWAInstall/context/InstallContext.tsx +102 -0
  32. package/src/snippets/PWAInstall/hooks/useInstallPrompt.ts +167 -0
  33. package/src/snippets/PWAInstall/hooks/useIsPWA.ts +115 -0
  34. package/src/snippets/PWAInstall/index.ts +76 -0
  35. package/src/snippets/PWAInstall/types/components.ts +95 -0
  36. package/src/snippets/PWAInstall/types/config.ts +22 -0
  37. package/src/snippets/PWAInstall/types/index.ts +26 -0
  38. package/src/snippets/PWAInstall/types/install.ts +38 -0
  39. package/src/snippets/PWAInstall/types/platform.ts +29 -0
  40. package/src/snippets/PWAInstall/utils/localStorage.ts +181 -0
  41. package/src/snippets/PWAInstall/utils/logger.ts +149 -0
  42. package/src/snippets/PWAInstall/utils/platform.ts +151 -0
  43. package/src/snippets/PushNotifications/@docs/README.md +191 -0
  44. package/src/snippets/PushNotifications/@docs/guides/django-integration.md +648 -0
  45. package/src/snippets/PushNotifications/@docs/guides/service-worker.md +467 -0
  46. package/src/snippets/PushNotifications/@docs/guides/vapid-setup.md +352 -0
  47. package/src/snippets/PushNotifications/README.md +328 -0
  48. package/src/snippets/PushNotifications/components/PushPrompt.tsx +165 -0
  49. package/src/snippets/PushNotifications/config.ts +20 -0
  50. package/src/snippets/PushNotifications/context/DjangoPushContext.tsx +190 -0
  51. package/src/snippets/PushNotifications/hooks/useDjangoPush.ts +259 -0
  52. package/src/snippets/PushNotifications/hooks/usePushNotifications.ts +209 -0
  53. package/src/snippets/PushNotifications/index.ts +87 -0
  54. package/src/snippets/PushNotifications/types/config.ts +28 -0
  55. package/src/snippets/PushNotifications/types/index.ts +9 -0
  56. package/src/snippets/PushNotifications/types/push.ts +21 -0
  57. package/src/snippets/PushNotifications/utils/localStorage.ts +60 -0
  58. package/src/snippets/PushNotifications/utils/logger.ts +149 -0
  59. package/src/snippets/PushNotifications/utils/platform.ts +151 -0
  60. package/src/snippets/PushNotifications/utils/vapid.ts +226 -0
  61. package/src/snippets/index.ts +55 -0
  62. package/src/layouts/shared/index.ts +0 -21
  63. package/src/layouts/shared/types.ts +0 -211
  64. /package/src/layouts/{shared → types}/README.md +0 -0
@@ -0,0 +1,209 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Push Notifications Hook
5
+ *
6
+ * Manages push notification subscription state
7
+ * Integrates with @djangocfg/nextjs/pwa
8
+ */
9
+
10
+ import { useState, useEffect } from 'react';
11
+ import type { PushNotificationState, PushNotificationOptions } from '../types';
12
+ import { pwaLogger } from '../utils/logger';
13
+ import { urlBase64ToUint8Array, VapidKeyError } from '../utils/vapid';
14
+
15
+ export function usePushNotifications(options?: PushNotificationOptions) {
16
+ const [state, setState] = useState<PushNotificationState>({
17
+ isSupported: false,
18
+ permission: 'default',
19
+ isSubscribed: false,
20
+ subscription: null,
21
+ });
22
+
23
+ // Check if push notifications are supported
24
+ useEffect(() => {
25
+ if (typeof window === 'undefined') return;
26
+
27
+ const isSupported = 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window;
28
+
29
+ setState((prev) => ({
30
+ ...prev,
31
+ isSupported,
32
+ permission: isSupported ? Notification.permission : 'denied',
33
+ }));
34
+
35
+ // Check existing subscription
36
+ if (isSupported) {
37
+ navigator.serviceWorker.ready
38
+ .then((registration) => registration.pushManager.getSubscription())
39
+ .then((subscription) => {
40
+ setState((prev) => ({
41
+ ...prev,
42
+ isSubscribed: !!subscription,
43
+ subscription,
44
+ }));
45
+ })
46
+ .catch((error) => {
47
+ pwaLogger.error('[usePushNotifications] Failed to get subscription:', error);
48
+ });
49
+ }
50
+ }, []);
51
+
52
+ /**
53
+ * Subscribe to push notifications
54
+ * @returns subscription on success, null on failure
55
+ */
56
+ const subscribe = async (): Promise<PushSubscription | null> => {
57
+ if (!state.isSupported) {
58
+ pwaLogger.warn('[usePushNotifications] Push notifications not supported');
59
+ return null;
60
+ }
61
+
62
+ if (!options?.vapidPublicKey) {
63
+ pwaLogger.error('[usePushNotifications] VAPID public key required');
64
+ return null;
65
+ }
66
+
67
+ try {
68
+ // Pre-flight check: Test network connectivity
69
+ pwaLogger.debug('[usePushNotifications] Running pre-flight checks...');
70
+
71
+ // Check if we're online
72
+ if (!navigator.onLine) {
73
+ pwaLogger.error('[usePushNotifications] No internet connection');
74
+ throw new Error('No internet connection. Please check your network and try again.');
75
+ }
76
+
77
+ // Request permission
78
+ const permission = await Notification.requestPermission();
79
+ setState((prev) => ({ ...prev, permission }));
80
+
81
+ if (permission !== 'granted') {
82
+ pwaLogger.warn('[usePushNotifications] Permission not granted:', permission);
83
+ return null;
84
+ }
85
+
86
+ // Subscribe to push
87
+ const registration = await navigator.serviceWorker.ready;
88
+
89
+ // Convert and validate VAPID key
90
+ let applicationServerKey: Uint8Array;
91
+ try {
92
+ pwaLogger.debug('[usePushNotifications] Converting VAPID key...');
93
+ applicationServerKey = urlBase64ToUint8Array(options.vapidPublicKey);
94
+ pwaLogger.info('[usePushNotifications] VAPID key validated successfully');
95
+ } catch (e) {
96
+ if (e instanceof VapidKeyError) {
97
+ pwaLogger.error(`[usePushNotifications] Invalid VAPID key: ${e.message} (code: ${e.code})`);
98
+ } else {
99
+ pwaLogger.error('[usePushNotifications] Failed to convert VAPID key:', e);
100
+ }
101
+ return null;
102
+ }
103
+
104
+ // Diagnostic logging (only in debug mode)
105
+ pwaLogger.debug('[usePushNotifications] Service Worker state:', {
106
+ controller: navigator.serviceWorker.controller ? 'active' : 'none',
107
+ registrationActive: registration.active ? 'yes' : 'no',
108
+ permission: Notification.permission,
109
+ });
110
+
111
+ // Check for existing subscription and unsubscribe
112
+ const existingSub = await registration.pushManager.getSubscription();
113
+ if (existingSub) {
114
+ pwaLogger.debug('[usePushNotifications] Unsubscribing from existing subscription...');
115
+ await existingSub.unsubscribe();
116
+ }
117
+
118
+ // Prepare subscription options
119
+ const subscribeOptions = {
120
+ userVisibleOnly: true,
121
+ applicationServerKey: applicationServerKey as unknown as BufferSource,
122
+ };
123
+
124
+ pwaLogger.debug('[usePushNotifications] Subscribing with VAPID key...');
125
+
126
+ // Attempt subscribe
127
+ const subscription = await registration.pushManager.subscribe(subscribeOptions);
128
+
129
+ // Send subscription to server (legacy endpoint support)
130
+ if (options.subscribeEndpoint) {
131
+ await fetch(options.subscribeEndpoint, {
132
+ method: 'POST',
133
+ headers: { 'Content-Type': 'application/json' },
134
+ body: JSON.stringify(subscription),
135
+ });
136
+ }
137
+
138
+ setState((prev) => ({
139
+ ...prev,
140
+ isSubscribed: true,
141
+ subscription,
142
+ }));
143
+
144
+ pwaLogger.success('[usePushNotifications] Successfully subscribed to push notifications');
145
+ return subscription;
146
+ } catch (error: any) {
147
+ pwaLogger.error('[usePushNotifications] Subscribe failed:', error);
148
+
149
+ // Specific diagnostic for push service errors
150
+ if (error.name === 'AbortError' || error.message?.includes('push service error')) {
151
+ pwaLogger.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
152
+ pwaLogger.error('❌ PUSH SERVICE ERROR - Cannot connect to FCM');
153
+ pwaLogger.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
154
+ pwaLogger.error('');
155
+ pwaLogger.error('🔍 This is NOT a code bug - it\'s a network/security block.');
156
+ pwaLogger.error('');
157
+ pwaLogger.error('✅ Quick Fixes (try in order):');
158
+ pwaLogger.error(' 1. Disable VPN/Proxy and refresh page');
159
+ pwaLogger.error(' 2. Open Incognito window (Cmd+Shift+N)');
160
+ pwaLogger.error(' 3. Try different browser (Safari, Firefox)');
161
+ pwaLogger.error(' 4. Use mobile hotspot instead of WiFi');
162
+ pwaLogger.error('');
163
+ pwaLogger.error('🔧 Technical Details:');
164
+ pwaLogger.error(' • Browser tries to connect to FCM (ports 5228-5230)');
165
+ pwaLogger.error(' • VPN/Firewall/Privacy settings may block these ports');
166
+ pwaLogger.error(' • Check browser console for "AbortError" details');
167
+ pwaLogger.error('');
168
+ pwaLogger.error('📚 Learn more: https://web.dev/push-notifications-overview/');
169
+ pwaLogger.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
170
+ } else if (error.message?.includes('No internet connection')) {
171
+ pwaLogger.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
172
+ pwaLogger.error('❌ NO INTERNET CONNECTION');
173
+ pwaLogger.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
174
+ pwaLogger.error('Please check your network connection and try again.');
175
+ } else {
176
+ pwaLogger.error('Unknown error:', error.name, error.message);
177
+ }
178
+
179
+ return null;
180
+ }
181
+ };
182
+
183
+ const unsubscribe = async (): Promise<boolean> => {
184
+ if (!state.subscription) {
185
+ pwaLogger.warn('[usePushNotifications] No active subscription to unsubscribe');
186
+ return false;
187
+ }
188
+
189
+ try {
190
+ await state.subscription.unsubscribe();
191
+ setState((prev) => ({
192
+ ...prev,
193
+ isSubscribed: false,
194
+ subscription: null,
195
+ }));
196
+ pwaLogger.info('[usePushNotifications] Successfully unsubscribed from push notifications');
197
+ return true;
198
+ } catch (error) {
199
+ pwaLogger.error('[usePushNotifications] Unsubscribe failed:', error);
200
+ return false;
201
+ }
202
+ };
203
+
204
+ return {
205
+ ...state,
206
+ subscribe,
207
+ unsubscribe,
208
+ };
209
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Push Notifications Snippet
3
+ *
4
+ * Web Push Notifications management with Django-CFG integration
5
+ * Includes push history, subscription management, and Django API integration
6
+ *
7
+ * @example Usage
8
+ * ```tsx
9
+ * import { DjangoPushProvider, useDjangoPushContext } from '@/snippets/PushNotifications';
10
+ *
11
+ * // In layout
12
+ * export default function Layout({ children }) {
13
+ * return (
14
+ * <DjangoPushProvider
15
+ * vapidPublicKey={process.env.NEXT_PUBLIC_VAPID_KEY}
16
+ * autoSubscribe={false}
17
+ * >
18
+ * {children}
19
+ * <PushPrompt requirePWA={true} />
20
+ * </DjangoPushProvider>
21
+ * );
22
+ * }
23
+ *
24
+ * // In component
25
+ * function PushDemo() {
26
+ * const { isSubscribed, pushes, subscribe, sendPush } = useDjangoPushContext();
27
+ *
28
+ * return (
29
+ * <div>
30
+ * {!isSubscribed && <button onClick={subscribe}>Subscribe</button>}
31
+ * <div>Received {pushes.length} notifications</div>
32
+ * </div>
33
+ * );
34
+ * }
35
+ * ```
36
+ *
37
+ * @note For backwards compatibility, you can also use `PushProvider` and `usePush`
38
+ * aliases which point to the same Django implementation.
39
+ */
40
+
41
+ // Main API - Django Push Integration
42
+ export { DjangoPushProvider, useDjangoPushContext } from './context/DjangoPushContext';
43
+ export type { PushMessage } from './context/DjangoPushContext';
44
+
45
+ // Backwards compatibility aliases
46
+ export { DjangoPushProvider as PushProvider } from './context/DjangoPushContext';
47
+ export { useDjangoPushContext as usePush } from './context/DjangoPushContext';
48
+
49
+ // Components
50
+ export { PushPrompt } from './components/PushPrompt';
51
+
52
+ // Hooks
53
+ export { usePushNotifications } from './hooks/usePushNotifications';
54
+ export { useDjangoPush } from './hooks/useDjangoPush';
55
+
56
+ // Config
57
+ export { DEFAULT_VAPID_PUBLIC_KEY } from './config';
58
+
59
+ // Utilities
60
+ export {
61
+ urlBase64ToUint8Array,
62
+ isValidVapidKey,
63
+ getVapidKeyInfo,
64
+ safeUrlBase64ToUint8Array,
65
+ VapidKeyError,
66
+ type VapidKeyErrorCode,
67
+ } from './utils/vapid';
68
+
69
+ export {
70
+ pwaLogger,
71
+ enablePWADebug,
72
+ disablePWADebug,
73
+ isPWADebugEnabled,
74
+ } from './utils/logger';
75
+
76
+ export {
77
+ STORAGE_KEYS,
78
+ markPushDismissed,
79
+ isPushDismissedRecently,
80
+ clearAllPushData,
81
+ } from './utils/localStorage';
82
+
83
+ // Types - Configuration
84
+ export type { PushNotificationsConfig } from './types';
85
+
86
+ // Types
87
+ export type { PushNotificationState, PushNotificationOptions } from './types';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Push Notifications Configuration Types
3
+ *
4
+ * Configuration for Web Push Notifications with VAPID authentication
5
+ */
6
+
7
+ export interface PushNotificationsConfig {
8
+ /** Enable push notifications */
9
+ enabled?: boolean;
10
+
11
+ /** VAPID public key for push notifications (required when enabled) */
12
+ vapidPublicKey: string;
13
+
14
+ /** API endpoint for subscription (default: '/api/push/subscribe') */
15
+ subscribeEndpoint?: string;
16
+
17
+ /** Only show push prompt if PWA is installed (default: true) */
18
+ requirePWA?: boolean;
19
+
20
+ /** Delay before showing push prompt after PWA install (ms) */
21
+ delayMs?: number;
22
+
23
+ /** Number of days before re-showing dismissed push prompt */
24
+ resetAfterDays?: number;
25
+
26
+ /** Auto-subscribe on mount if permission already granted */
27
+ autoSubscribe?: boolean;
28
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Push Notifications Types - Central Export
3
+ */
4
+
5
+ // Configuration
6
+ export type { PushNotificationsConfig } from './config';
7
+
8
+ // Push Notifications
9
+ export type { PushNotificationState, PushNotificationOptions } from './push';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Push Notification Types
3
+ */
4
+
5
+ /**
6
+ * Push notification state
7
+ */
8
+ export interface PushNotificationState {
9
+ isSupported: boolean;
10
+ permission: NotificationPermission;
11
+ isSubscribed: boolean;
12
+ subscription: PushSubscription | null;
13
+ }
14
+
15
+ /**
16
+ * Push notification options
17
+ */
18
+ export interface PushNotificationOptions {
19
+ vapidPublicKey: string;
20
+ subscribeEndpoint?: string;
21
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * LocalStorage utilities for Push Notifications state persistence
3
+ */
4
+
5
+ /**
6
+ * Storage keys
7
+ */
8
+ export const STORAGE_KEYS = {
9
+ PUSH_DISMISSED: 'pwa_push_dismissed_at',
10
+ } as const;
11
+
12
+ /**
13
+ * Check if push prompt was dismissed recently
14
+ * @param resetDays Number of days before re-showing (default: 7)
15
+ */
16
+ export function isPushDismissedRecently(resetDays: number = 7): boolean {
17
+ return isDismissedRecentlyHelper(resetDays, STORAGE_KEYS.PUSH_DISMISSED);
18
+ }
19
+
20
+ /**
21
+ * Mark push prompt as dismissed
22
+ */
23
+ export function markPushDismissed(): void {
24
+ if (typeof window === 'undefined') return;
25
+ try {
26
+ localStorage.setItem(STORAGE_KEYS.PUSH_DISMISSED, Date.now().toString());
27
+ } catch {
28
+ // Fail silently
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Helper: Check if a key was dismissed recently
34
+ * @internal
35
+ */
36
+ function isDismissedRecentlyHelper(resetDays: number, key: string): boolean {
37
+ if (typeof window === 'undefined') return false;
38
+ try {
39
+ const dismissed = localStorage.getItem(key);
40
+ if (!dismissed) return false;
41
+ const dismissedAt = parseInt(dismissed, 10);
42
+ const daysSince = (Date.now() - dismissedAt) / (1000 * 60 * 60 * 24);
43
+ return daysSince < resetDays;
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Clear all push notification data
51
+ */
52
+ export function clearAllPushData(): void {
53
+ if (typeof window === 'undefined') return;
54
+
55
+ try {
56
+ localStorage.removeItem(STORAGE_KEYS.PUSH_DISMISSED);
57
+ } catch {
58
+ // Fail silently
59
+ }
60
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * PWA Logger with Conditional Logging
3
+ *
4
+ * Provides logging utilities that respect environment and debug settings:
5
+ * - In production: Only errors are logged
6
+ * - In development: All levels are logged
7
+ * - Debug mode: Can be enabled in production via localStorage
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { pwaLogger } from '../utils/logger';
12
+ *
13
+ * pwaLogger.info('Info message'); // Only in dev
14
+ * pwaLogger.warn('Warning message'); // Only in dev
15
+ * pwaLogger.error('Error message'); // Always logged
16
+ * pwaLogger.debug('Debug message'); // Only when debug enabled
17
+ * ```
18
+ *
19
+ * Enable debug mode in production:
20
+ * ```typescript
21
+ * import { enablePWADebug } from '../utils/logger';
22
+ * enablePWADebug();
23
+ * // or in console:
24
+ * localStorage.setItem('pwa_debug', 'true');
25
+ * ```
26
+ */
27
+
28
+ import { consola } from 'consola';
29
+
30
+ const isDevelopment = process.env.NODE_ENV === 'development';
31
+
32
+ /**
33
+ * Check if debug mode is enabled via localStorage
34
+ */
35
+ function isDebugEnabled(): boolean {
36
+ if (typeof window === 'undefined') return false;
37
+ try {
38
+ return localStorage.getItem('pwa_debug') === 'true';
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * PWA Logger instance with conditional logging
46
+ */
47
+ export const pwaLogger = {
48
+ /**
49
+ * Info level logging
50
+ * Only logs in development or when debug is enabled
51
+ */
52
+ info: (...args: Parameters<typeof consola.info>): void => {
53
+ if (isDevelopment || isDebugEnabled()) {
54
+ consola.info(...args);
55
+ }
56
+ },
57
+
58
+ /**
59
+ * Warning level logging
60
+ * Only logs in development or when debug is enabled
61
+ */
62
+ warn: (...args: Parameters<typeof consola.warn>): void => {
63
+ if (isDevelopment || isDebugEnabled()) {
64
+ consola.warn(...args);
65
+ }
66
+ },
67
+
68
+ /**
69
+ * Error level logging
70
+ * Always logs (production + development)
71
+ */
72
+ error: (...args: Parameters<typeof consola.error>): void => {
73
+ consola.error(...args);
74
+ },
75
+
76
+ /**
77
+ * Debug level logging
78
+ * Only logs when debug is explicitly enabled
79
+ */
80
+ debug: (...args: Parameters<typeof consola.debug>): void => {
81
+ if (isDebugEnabled()) {
82
+ consola.debug(...args);
83
+ }
84
+ },
85
+
86
+ /**
87
+ * Success level logging
88
+ * Only logs in development or when debug is enabled
89
+ */
90
+ success: (...args: Parameters<typeof consola.success>): void => {
91
+ if (isDevelopment || isDebugEnabled()) {
92
+ consola.success(...args);
93
+ }
94
+ },
95
+ };
96
+
97
+ /**
98
+ * Enable debug mode
99
+ *
100
+ * This allows seeing debug logs in production.
101
+ * Call this function or set localStorage manually:
102
+ * `localStorage.setItem('pwa_debug', 'true')`
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * import { enablePWADebug } from '@djangocfg/layouts/snippets';
107
+ * enablePWADebug();
108
+ * // Reload page to see debug logs
109
+ * ```
110
+ */
111
+ export function enablePWADebug(): void {
112
+ if (typeof window !== 'undefined') {
113
+ try {
114
+ localStorage.setItem('pwa_debug', 'true');
115
+ consola.info('[PWA] Debug mode enabled. Reload page to see debug logs.');
116
+ } catch (e) {
117
+ consola.error('[PWA] Failed to enable debug mode:', e);
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Disable debug mode
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * import { disablePWADebug } from '@djangocfg/layouts/snippets';
128
+ * disablePWADebug();
129
+ * ```
130
+ */
131
+ export function disablePWADebug(): void {
132
+ if (typeof window !== 'undefined') {
133
+ try {
134
+ localStorage.removeItem('pwa_debug');
135
+ consola.info('[PWA] Debug mode disabled.');
136
+ } catch (e) {
137
+ consola.error('[PWA] Failed to disable debug mode:', e);
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Check if debug mode is currently active
144
+ *
145
+ * @returns true if debug mode is enabled
146
+ */
147
+ export function isPWADebugEnabled(): boolean {
148
+ return isDebugEnabled();
149
+ }