@djangocfg/layouts 1.2.20 → 1.2.21
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/package.json +5 -5
- package/src/auth/context/AuthContext.tsx +12 -1
- package/src/layouts/AppLayout/AppLayout.tsx +13 -0
- package/src/layouts/AppLayout/components/PackageVersions/packageVersions.config.ts +8 -8
- package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +43 -2
- package/src/layouts/AppLayout/layouts/AdminLayout/components/ParentSync.tsx +20 -71
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +21 -4
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.21",
|
|
4
4
|
"description": "Layout system and components for Unrealon applications",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DjangoCFG",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"check": "tsc --noEmit"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@djangocfg/api": "^1.2.
|
|
57
|
-
"@djangocfg/og-image": "^1.2.
|
|
58
|
-
"@djangocfg/ui": "^1.2.
|
|
56
|
+
"@djangocfg/api": "^1.2.21",
|
|
57
|
+
"@djangocfg/og-image": "^1.2.21",
|
|
58
|
+
"@djangocfg/ui": "^1.2.21",
|
|
59
59
|
"@hookform/resolvers": "^5.2.0",
|
|
60
60
|
"consola": "^3.4.2",
|
|
61
61
|
"lucide-react": "^0.468.0",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"vidstack": "0.6.15"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@djangocfg/typescript-config": "^1.2.
|
|
79
|
+
"@djangocfg/typescript-config": "^1.2.21",
|
|
80
80
|
"@types/node": "^24.7.2",
|
|
81
81
|
"@types/react": "19.2.2",
|
|
82
82
|
"@types/react-dom": "19.2.1",
|
|
@@ -93,25 +93,36 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
93
93
|
|
|
94
94
|
// Simple profile loading without retry - now uses AccountsContext
|
|
95
95
|
const loadCurrentProfile = useCallback(async (): Promise<void> => {
|
|
96
|
+
// console.log('[AuthContext] loadCurrentProfile called');
|
|
96
97
|
try {
|
|
97
98
|
// Ensure API clients are properly initialized with current token
|
|
98
|
-
|
|
99
|
+
const isAuth = api.isAuthenticated();
|
|
100
|
+
const token = api.getToken();
|
|
101
|
+
// console.log('[AuthContext] isAuthenticated:', isAuth, 'token:', token ? token.substring(0, 20) + '...' : 'null');
|
|
102
|
+
|
|
103
|
+
if (!isAuth) {
|
|
104
|
+
console.warn('[AuthContext] No valid authentication token, throwing error');
|
|
99
105
|
throw new Error('No valid authentication token');
|
|
100
106
|
}
|
|
101
107
|
|
|
108
|
+
// console.log('[AuthContext] Refreshing profile from AccountsContext...');
|
|
102
109
|
// Refresh profile from AccountsContext
|
|
103
110
|
const refreshedProfile = await accounts.refreshProfile();
|
|
104
111
|
|
|
105
112
|
if (refreshedProfile) {
|
|
113
|
+
// console.log('[AuthContext] Profile loaded successfully:', refreshedProfile.id, 'is_staff:', refreshedProfile.is_staff, 'is_superuser:', refreshedProfile.is_superuser);
|
|
106
114
|
authLogger.info('Profile loaded successfully:', refreshedProfile.id);
|
|
107
115
|
} else {
|
|
116
|
+
console.warn('[AuthContext] Profile refresh returned undefined - but keeping tokens');
|
|
108
117
|
authLogger.warn('Profile refresh returned undefined - but keeping tokens');
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
// Always mark as initialized if we have valid tokens
|
|
112
121
|
// Don't clear tokens just because profile fetch failed
|
|
113
122
|
setInitialized(true);
|
|
123
|
+
// console.log('[AuthContext] loadCurrentProfile completed, initialized=true');
|
|
114
124
|
} catch (error) {
|
|
125
|
+
console.error('[AuthContext] Failed to load profile:', error);
|
|
115
126
|
authLogger.error('Failed to load profile:', error);
|
|
116
127
|
// Use global error handler first, fallback to clearing state
|
|
117
128
|
if (!handleGlobalAuthError(error, 'loadCurrentProfile')) {
|
|
@@ -110,6 +110,19 @@ function LayoutRouter({
|
|
|
110
110
|
// Admin routes: Always show loading during SSR and initial client render
|
|
111
111
|
// This prevents hydration mismatch when isAuthenticated differs between server/client
|
|
112
112
|
if (isAdminRoute && !forceLayout) {
|
|
113
|
+
// In embedded mode (iframe), render AdminLayout immediately to receive postMessage
|
|
114
|
+
const isEmbedded = typeof window !== 'undefined' && window !== window.parent;
|
|
115
|
+
|
|
116
|
+
if (isEmbedded) {
|
|
117
|
+
// Skip loading checks - AdminLayout will handle auth via postMessage
|
|
118
|
+
return (
|
|
119
|
+
<AdminLayout enableParentSync={true}>
|
|
120
|
+
{children}
|
|
121
|
+
</AdminLayout>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Standalone mode: show loading during initialization
|
|
113
126
|
if (!isMounted || isLoading) {
|
|
114
127
|
return (
|
|
115
128
|
<div className="min-h-screen flex items-center justify-center">
|
|
@@ -16,36 +16,36 @@ export interface PackageInfo {
|
|
|
16
16
|
/**
|
|
17
17
|
* Package versions registry
|
|
18
18
|
* Auto-synced from package.json files
|
|
19
|
-
* Last updated: 2025-11-
|
|
19
|
+
* Last updated: 2025-11-02T11:45:27.117Z
|
|
20
20
|
*/
|
|
21
21
|
const PACKAGE_VERSIONS: PackageInfo[] = [
|
|
22
22
|
{
|
|
23
23
|
"name": "@djangocfg/ui",
|
|
24
|
-
"version": "1.2.
|
|
24
|
+
"version": "1.2.21"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"name": "@djangocfg/api",
|
|
28
|
-
"version": "1.2.
|
|
28
|
+
"version": "1.2.21"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"name": "@djangocfg/layouts",
|
|
32
|
-
"version": "1.2.
|
|
32
|
+
"version": "1.2.21"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"name": "@djangocfg/markdown",
|
|
36
|
-
"version": "1.2.
|
|
36
|
+
"version": "1.2.21"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"name": "@djangocfg/og-image",
|
|
40
|
-
"version": "1.2.
|
|
40
|
+
"version": "1.2.21"
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"name": "@djangocfg/eslint-config",
|
|
44
|
-
"version": "1.2.
|
|
44
|
+
"version": "1.2.21"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"name": "@djangocfg/typescript-config",
|
|
48
|
-
"version": "1.2.
|
|
48
|
+
"version": "1.2.21"
|
|
49
49
|
}
|
|
50
50
|
];
|
|
51
51
|
|
|
@@ -82,27 +82,68 @@ export function AdminLayout({
|
|
|
82
82
|
config,
|
|
83
83
|
enableParentSync = true
|
|
84
84
|
}: AdminLayoutProps) {
|
|
85
|
-
const
|
|
85
|
+
const [isMounted, setIsMounted] = React.useState(false);
|
|
86
|
+
const { user, isLoading, loadCurrentProfile } = useAuth();
|
|
87
|
+
// console.log('[AdminLayout] Rendering with user:', user, 'isLoading:', isLoading);
|
|
88
|
+
|
|
89
|
+
// Track mount state to prevent hydration mismatch
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
setIsMounted(true);
|
|
92
|
+
}, []);
|
|
86
93
|
|
|
87
94
|
// useCfgApp hook is called here to initialize iframe communication
|
|
88
95
|
// Automatically sets tokens in API client when received from parent
|
|
89
96
|
const { isEmbedded } = useCfgApp({
|
|
90
|
-
onAuthTokenReceived: (authToken, refreshToken) => {
|
|
97
|
+
onAuthTokenReceived: async (authToken, refreshToken) => {
|
|
98
|
+
// console.log('[AdminLayout] onAuthTokenReceived called');
|
|
99
|
+
// console.log('[AdminLayout] authToken:', authToken.substring(0, 20) + '...', 'refreshToken:', refreshToken ? refreshToken.substring(0, 20) + '...' : 'null');
|
|
100
|
+
|
|
91
101
|
// Always set tokens in API client
|
|
92
102
|
api.setToken(authToken, refreshToken);
|
|
103
|
+
// console.log('[AdminLayout] Tokens set in API client');
|
|
104
|
+
|
|
105
|
+
// Load user profile after setting tokens
|
|
106
|
+
// console.log('[AdminLayout] Loading user profile...');
|
|
107
|
+
await loadCurrentProfile();
|
|
108
|
+
// console.log('[AdminLayout] User profile loaded');
|
|
93
109
|
|
|
94
110
|
// Call custom handler if provided
|
|
95
111
|
if (config?.onAuthTokenReceived) {
|
|
112
|
+
// console.log('[AdminLayout] Calling custom onAuthTokenReceived handler');
|
|
96
113
|
config.onAuthTokenReceived(authToken, refreshToken);
|
|
97
114
|
}
|
|
98
115
|
}
|
|
99
116
|
});
|
|
100
117
|
|
|
118
|
+
// console.log('[AdminLayout] isEmbedded:', isEmbedded);
|
|
119
|
+
|
|
120
|
+
// During SSR and initial render, show loading to prevent hydration mismatch
|
|
121
|
+
if (!isMounted) {
|
|
122
|
+
return (
|
|
123
|
+
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
124
|
+
<div className="text-muted-foreground">Loading admin panel...</div>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Show loading while auth is initializing (waiting for tokens from parent or profile loading)
|
|
130
|
+
if (isLoading) {
|
|
131
|
+
// console.log('[AdminLayout] Showing loading state - isLoading:', isLoading);
|
|
132
|
+
return (
|
|
133
|
+
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
134
|
+
<div className="text-muted-foreground">Loading admin panel...</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
101
139
|
// Check if user has staff or superuser privileges
|
|
102
140
|
const hasAdminAccess = user?.is_staff || user?.is_superuser;
|
|
141
|
+
// console.log('[AdminLayout] user:', user, 'hasAdminAccess:', hasAdminAccess, 'is_staff:', user?.is_staff, 'is_superuser:', user?.is_superuser);
|
|
103
142
|
|
|
104
143
|
// Only render AdminLayout features for staff/superuser
|
|
105
144
|
if (!hasAdminAccess) {
|
|
145
|
+
console.warn('[AdminLayout] Access denied - user does not have admin access');
|
|
146
|
+
// console.log('[AdminLayout] User details:', JSON.stringify(user, null, 2));
|
|
106
147
|
return (
|
|
107
148
|
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
108
149
|
<div className="text-center space-y-4 p-8">
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
'use client';
|
|
10
10
|
|
|
11
11
|
import { useEffect, useState } from 'react';
|
|
12
|
+
import { useRouter } from 'next/router';
|
|
12
13
|
import { useAuth } from '../../../../../auth';
|
|
13
14
|
import { useThemeContext } from '@djangocfg/ui';
|
|
14
15
|
import { useCfgApp } from '../hooks/useApp';
|
|
@@ -47,15 +48,20 @@ export function ParentSync() {
|
|
|
47
48
|
* Separated to avoid SSR issues during static export
|
|
48
49
|
*/
|
|
49
50
|
function ParentSyncClient() {
|
|
51
|
+
const router = useRouter();
|
|
50
52
|
const auth = useAuth();
|
|
51
53
|
const { setTheme } = useThemeContext();
|
|
52
54
|
const { isEmbedded, isMounted, parentTheme } = useCfgApp();
|
|
53
55
|
|
|
54
56
|
// 1. Sync theme from parent → iframe
|
|
55
57
|
useEffect(() => {
|
|
58
|
+
// console.log('[ParentSync] Theme sync effect triggered - isEmbedded:', isEmbedded, 'parentTheme:', parentTheme);
|
|
56
59
|
if (isEmbedded && parentTheme) {
|
|
57
|
-
// console.log('[ParentSync]
|
|
60
|
+
// console.log('[ParentSync] Syncing theme from parent:', parentTheme);
|
|
58
61
|
setTheme(parentTheme);
|
|
62
|
+
// console.log('[ParentSync] setTheme called with:', parentTheme);
|
|
63
|
+
} else {
|
|
64
|
+
// console.log('[ParentSync] Skipping theme sync - isEmbedded:', isEmbedded, 'parentTheme:', parentTheme);
|
|
59
65
|
}
|
|
60
66
|
}, [isEmbedded, parentTheme, setTheme]);
|
|
61
67
|
|
|
@@ -89,94 +95,37 @@ function ParentSyncClient() {
|
|
|
89
95
|
}
|
|
90
96
|
}, [auth.isAuthenticated, auth.isLoading, auth.user, isEmbedded, isMounted]);
|
|
91
97
|
|
|
92
|
-
// 3.
|
|
98
|
+
// 3. iframe-resize removed - was causing log spam
|
|
99
|
+
|
|
100
|
+
// 4. Send navigation events to parent (for "Open in New Window" button)
|
|
93
101
|
useEffect(() => {
|
|
94
102
|
// Only send if embedded and mounted
|
|
95
103
|
if (!isEmbedded || !isMounted) {
|
|
96
104
|
return;
|
|
97
105
|
}
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
const sendHeight = () => {
|
|
101
|
-
const height = document.documentElement.scrollHeight;
|
|
102
|
-
// console.log('[ParentSync] 📏 Sending height to parent:', height);
|
|
103
|
-
|
|
107
|
+
const handleRouteChange = (url: string) => {
|
|
104
108
|
try {
|
|
105
109
|
window.parent.postMessage({
|
|
106
|
-
type: 'iframe-
|
|
107
|
-
data: {
|
|
110
|
+
type: 'iframe-navigation',
|
|
111
|
+
data: { path: url }
|
|
108
112
|
}, '*');
|
|
109
113
|
} catch (e) {
|
|
110
|
-
console.error('[ParentSync]
|
|
114
|
+
console.error('[ParentSync] Failed to send navigation event:', e);
|
|
111
115
|
}
|
|
112
116
|
};
|
|
113
117
|
|
|
114
|
-
//
|
|
115
|
-
|
|
118
|
+
// Listen to Next.js router events
|
|
119
|
+
router.events.on('routeChangeComplete', handleRouteChange);
|
|
116
120
|
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
sendHeight();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Observe body element for size changes
|
|
123
|
-
resizeObserver.observe(document.body);
|
|
121
|
+
// Send initial route
|
|
122
|
+
handleRouteChange(router.asPath);
|
|
124
123
|
|
|
125
124
|
// Cleanup
|
|
126
125
|
return () => {
|
|
127
|
-
|
|
126
|
+
router.events.off('routeChangeComplete', handleRouteChange);
|
|
128
127
|
};
|
|
129
|
-
}, [isEmbedded, isMounted]);
|
|
130
|
-
|
|
131
|
-
// 4. Send navigation events to parent (for "Open in New Window" button)
|
|
132
|
-
useEffect(() => {
|
|
133
|
-
// Only send if embedded and mounted
|
|
134
|
-
if (!isEmbedded || !isMounted) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Import Next.js router dynamically
|
|
139
|
-
import('next/router').then(({ default: Router }) => {
|
|
140
|
-
const handleRouteChange = (url: string) => {
|
|
141
|
-
// console.log('[ParentSync] 🔗 Route changed:', url);
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
window.parent.postMessage({
|
|
145
|
-
type: 'iframe-navigation',
|
|
146
|
-
data: { path: url }
|
|
147
|
-
}, '*');
|
|
148
|
-
} catch (e) {
|
|
149
|
-
console.error('[ParentSync] ❌ Failed to send navigation event:', e);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Also send height update after route change
|
|
153
|
-
setTimeout(() => {
|
|
154
|
-
const height = document.documentElement.scrollHeight;
|
|
155
|
-
try {
|
|
156
|
-
window.parent.postMessage({
|
|
157
|
-
type: 'iframe-resize',
|
|
158
|
-
data: { height }
|
|
159
|
-
}, '*');
|
|
160
|
-
} catch (e) {
|
|
161
|
-
console.error('[ParentSync] ❌ Failed to send height:', e);
|
|
162
|
-
}
|
|
163
|
-
}, 100);
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// Listen to Next.js router events
|
|
167
|
-
Router.events.on('routeChangeComplete', handleRouteChange);
|
|
168
|
-
|
|
169
|
-
// Send initial route
|
|
170
|
-
handleRouteChange(Router.asPath);
|
|
171
|
-
|
|
172
|
-
// Cleanup
|
|
173
|
-
return () => {
|
|
174
|
-
Router.events.off('routeChangeComplete', handleRouteChange);
|
|
175
|
-
};
|
|
176
|
-
}).catch(err => {
|
|
177
|
-
console.warn('[ParentSync] Failed to load next/router:', err);
|
|
178
|
-
});
|
|
179
|
-
}, [isEmbedded, isMounted]);
|
|
128
|
+
}, [isEmbedded, isMounted, router]);
|
|
180
129
|
|
|
181
130
|
// This component doesn't render anything
|
|
182
131
|
return null;
|
|
@@ -111,21 +111,36 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
111
111
|
|
|
112
112
|
// Listen for messages from parent window
|
|
113
113
|
const handleMessage = (event: MessageEvent) => {
|
|
114
|
+
// console.log('[useCfgApp] RAW message event:', {
|
|
115
|
+
// origin: event.origin,
|
|
116
|
+
// source: event.source === window.parent ? 'parent' : 'other',
|
|
117
|
+
// dataType: typeof event.data,
|
|
118
|
+
// data: event.data
|
|
119
|
+
// });
|
|
120
|
+
|
|
114
121
|
const { type, data } = event.data || {};
|
|
122
|
+
// console.log('[useCfgApp] Received message:', type, data);
|
|
115
123
|
|
|
116
124
|
switch (type) {
|
|
117
125
|
case 'parent-auth':
|
|
126
|
+
// console.log('[useCfgApp] parent-auth message received');
|
|
118
127
|
// Receive authentication tokens from parent
|
|
119
128
|
if (data?.authToken && options?.onAuthTokenReceived) {
|
|
129
|
+
// console.log('[useCfgApp] Auth tokens found, calling onAuthTokenReceived callback');
|
|
130
|
+
// console.log('[useCfgApp] authToken:', data.authToken.substring(0, 20) + '...', 'refreshToken:', data.refreshToken ? data.refreshToken.substring(0, 20) + '...' : 'null');
|
|
120
131
|
try {
|
|
121
132
|
options.onAuthTokenReceived(data.authToken, data.refreshToken);
|
|
133
|
+
// console.log('[useCfgApp] onAuthTokenReceived callback completed successfully');
|
|
122
134
|
} catch (e) {
|
|
123
135
|
console.error('[useCfgApp] Failed to process auth tokens:', e);
|
|
124
136
|
}
|
|
137
|
+
} else {
|
|
138
|
+
console.warn('[useCfgApp] parent-auth message received but authToken or callback missing:', { hasToken: !!data?.authToken, hasCallback: !!options?.onAuthTokenReceived });
|
|
125
139
|
}
|
|
126
140
|
break;
|
|
127
141
|
|
|
128
142
|
case 'parent-theme':
|
|
143
|
+
// console.log('[useCfgApp] parent-theme message received:', data?.theme);
|
|
129
144
|
// Receive theme from parent
|
|
130
145
|
if (data?.theme) {
|
|
131
146
|
try {
|
|
@@ -133,26 +148,25 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
133
148
|
if (data.themeMode) {
|
|
134
149
|
setParentThemeMode(data.themeMode);
|
|
135
150
|
}
|
|
151
|
+
// console.log('[useCfgApp] Theme set successfully:', data.theme);
|
|
136
152
|
} catch (e) {
|
|
137
153
|
console.error('[useCfgApp] Failed to process theme:', e);
|
|
138
154
|
}
|
|
139
155
|
}
|
|
140
156
|
break;
|
|
141
157
|
|
|
142
|
-
case 'parent-resize':
|
|
143
|
-
// Handle parent window resize (optional)
|
|
144
|
-
break;
|
|
145
|
-
|
|
146
158
|
default:
|
|
147
159
|
break;
|
|
148
160
|
}
|
|
149
161
|
};
|
|
150
162
|
|
|
151
163
|
window.addEventListener('message', handleMessage);
|
|
164
|
+
// console.log('[useCfgApp] Message listener registered, isEmbedded:', inIframe);
|
|
152
165
|
|
|
153
166
|
// Send iframe-ready since listener is registered
|
|
154
167
|
if (inIframe) {
|
|
155
168
|
try {
|
|
169
|
+
// console.log('[useCfgApp] Sending iframe-ready message to parent');
|
|
156
170
|
window.parent.postMessage({
|
|
157
171
|
type: 'iframe-ready',
|
|
158
172
|
data: {
|
|
@@ -160,9 +174,12 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
160
174
|
referrer: document.referrer
|
|
161
175
|
}
|
|
162
176
|
}, '*');
|
|
177
|
+
// console.log('[useCfgApp] iframe-ready message sent');
|
|
163
178
|
} catch (e) {
|
|
164
179
|
console.error('[useCfgApp] Failed to notify parent about ready state:', e);
|
|
165
180
|
}
|
|
181
|
+
} else {
|
|
182
|
+
// console.log('[useCfgApp] Not in iframe, skipping iframe-ready message');
|
|
166
183
|
}
|
|
167
184
|
|
|
168
185
|
return () => {
|
|
@@ -47,7 +47,7 @@ export function DashboardHeader({ isAdmin = false }: DashboardHeaderProps) {
|
|
|
47
47
|
|
|
48
48
|
// Notification handler - TODO: implement notification system
|
|
49
49
|
const handleNotificationClick = () => {
|
|
50
|
-
console.log('Notifications clicked');
|
|
50
|
+
// console.log('Notifications clicked');
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
return (
|