@djangocfg/layouts 1.0.1
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/LICENSE +21 -0
- package/README.md +77 -0
- package/package.json +86 -0
- package/src/auth/README.md +962 -0
- package/src/auth/context/AuthContext.tsx +458 -0
- package/src/auth/context/index.ts +2 -0
- package/src/auth/context/types.ts +63 -0
- package/src/auth/hooks/index.ts +6 -0
- package/src/auth/hooks/useAuthForm.ts +329 -0
- package/src/auth/hooks/useAuthGuard.ts +23 -0
- package/src/auth/hooks/useAuthRedirect.ts +51 -0
- package/src/auth/hooks/useAutoAuth.ts +42 -0
- package/src/auth/hooks/useLocalStorage.ts +211 -0
- package/src/auth/hooks/useSessionStorage.ts +186 -0
- package/src/auth/index.ts +10 -0
- package/src/auth/middlewares/index.ts +1 -0
- package/src/auth/middlewares/proxy.ts +24 -0
- package/src/auth/server.ts +6 -0
- package/src/auth/utils/errors.ts +34 -0
- package/src/auth/utils/index.ts +2 -0
- package/src/auth/utils/validation.ts +14 -0
- package/src/index.ts +15 -0
- package/src/layouts/AppLayout/AppLayout.tsx +123 -0
- package/src/layouts/AppLayout/README.md +204 -0
- package/src/layouts/AppLayout/SUMMARY.md +240 -0
- package/src/layouts/AppLayout/USAGE.md +312 -0
- package/src/layouts/AppLayout/components/PageProgress.tsx +104 -0
- package/src/layouts/AppLayout/components/Seo.tsx +87 -0
- package/src/layouts/AppLayout/components/index.ts +6 -0
- package/src/layouts/AppLayout/context/AppContext.tsx +146 -0
- package/src/layouts/AppLayout/context/index.ts +5 -0
- package/src/layouts/AppLayout/hooks/index.ts +6 -0
- package/src/layouts/AppLayout/hooks/useLayoutMode.ts +26 -0
- package/src/layouts/AppLayout/hooks/useNavigation.ts +49 -0
- package/src/layouts/AppLayout/index.ts +31 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthContext.tsx +51 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthHelp.tsx +111 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +40 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/IdentifierForm.tsx +330 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/OTPForm.tsx +158 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +13 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/types.ts +61 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +92 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +60 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +170 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +164 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +7 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/index.ts +5 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/PublicLayout.tsx +44 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/DesktopUserMenu.tsx +136 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +262 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenu.tsx +289 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +159 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/index.ts +5 -0
- package/src/layouts/AppLayout/layouts/index.ts +7 -0
- package/src/layouts/AppLayout/providers/CoreProviders.tsx +47 -0
- package/src/layouts/AppLayout/providers/index.ts +5 -0
- package/src/layouts/AppLayout/types/config.ts +40 -0
- package/src/layouts/AppLayout/types/index.ts +10 -0
- package/src/layouts/AppLayout/types/layout.ts +47 -0
- package/src/layouts/AppLayout/types/navigation.ts +41 -0
- package/src/layouts/AppLayout/types/routes.ts +45 -0
- package/src/layouts/AppLayout/utils/index.ts +5 -0
- package/src/layouts/AppLayout/utils/routeDetection.ts +31 -0
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +125 -0
- package/src/layouts/PaymentsLayout/README.md +133 -0
- package/src/layouts/PaymentsLayout/components/CreateApiKeyDialog.tsx +172 -0
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +203 -0
- package/src/layouts/PaymentsLayout/components/DeleteApiKeyDialog.tsx +100 -0
- package/src/layouts/PaymentsLayout/components/index.ts +4 -0
- package/src/layouts/PaymentsLayout/events.ts +106 -0
- package/src/layouts/PaymentsLayout/index.ts +20 -0
- package/src/layouts/PaymentsLayout/types.ts +19 -0
- package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeyMetrics.tsx +109 -0
- package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeysList.tsx +194 -0
- package/src/layouts/PaymentsLayout/views/apikeys/components/index.ts +3 -0
- package/src/layouts/PaymentsLayout/views/apikeys/index.tsx +19 -0
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +99 -0
- package/src/layouts/PaymentsLayout/views/overview/components/MetricsCards.tsx +103 -0
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +138 -0
- package/src/layouts/PaymentsLayout/views/overview/components/index.ts +4 -0
- package/src/layouts/PaymentsLayout/views/overview/index.tsx +23 -0
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +282 -0
- package/src/layouts/PaymentsLayout/views/payments/components/index.ts +2 -0
- package/src/layouts/PaymentsLayout/views/payments/index.tsx +18 -0
- package/src/layouts/PaymentsLayout/views/tariffs/index.tsx +29 -0
- package/src/layouts/PaymentsLayout/views/transactions/index.tsx +29 -0
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +110 -0
- package/src/layouts/ProfileLayout/components/AvatarSection.tsx +146 -0
- package/src/layouts/ProfileLayout/components/ProfileForm.tsx +208 -0
- package/src/layouts/ProfileLayout/components/index.ts +3 -0
- package/src/layouts/ProfileLayout/index.ts +3 -0
- package/src/layouts/SupportLayout/README.md +91 -0
- package/src/layouts/SupportLayout/SupportLayout.tsx +178 -0
- package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +154 -0
- package/src/layouts/SupportLayout/components/MessageInput.tsx +92 -0
- package/src/layouts/SupportLayout/components/MessageList.tsx +312 -0
- package/src/layouts/SupportLayout/components/TicketCard.tsx +96 -0
- package/src/layouts/SupportLayout/components/TicketList.tsx +152 -0
- package/src/layouts/SupportLayout/components/index.ts +6 -0
- package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +260 -0
- package/src/layouts/SupportLayout/context/index.ts +2 -0
- package/src/layouts/SupportLayout/events.ts +31 -0
- package/src/layouts/SupportLayout/hooks/index.ts +2 -0
- package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +118 -0
- package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +91 -0
- package/src/layouts/SupportLayout/index.ts +6 -0
- package/src/layouts/SupportLayout/types.ts +23 -0
- package/src/layouts/index.ts +9 -0
- package/src/snippets/AuthDialog/AuthDialog.tsx +88 -0
- package/src/snippets/AuthDialog/events.ts +21 -0
- package/src/snippets/AuthDialog/index.ts +3 -0
- package/src/snippets/AuthDialog/useAuthDialog.ts +27 -0
- package/src/snippets/Breadcrumbs.tsx +80 -0
- package/src/snippets/Chat/ChatUIContext.tsx +110 -0
- package/src/snippets/Chat/ChatWidget.tsx +476 -0
- package/src/snippets/Chat/README.md +122 -0
- package/src/snippets/Chat/components/MessageInput.tsx +124 -0
- package/src/snippets/Chat/components/MessageList.tsx +168 -0
- package/src/snippets/Chat/components/SessionList.tsx +192 -0
- package/src/snippets/Chat/components/index.ts +9 -0
- package/src/snippets/Chat/hooks/index.ts +6 -0
- package/src/snippets/Chat/hooks/useInfiniteSessions.ts +83 -0
- package/src/snippets/Chat/index.tsx +44 -0
- package/src/snippets/Chat/types.ts +79 -0
- package/src/snippets/VideoPlayer/README.md +203 -0
- package/src/snippets/VideoPlayer/VideoControls.tsx +133 -0
- package/src/snippets/VideoPlayer/VideoPlayer.tsx +114 -0
- package/src/snippets/VideoPlayer/index.ts +8 -0
- package/src/snippets/VideoPlayer/types.ts +61 -0
- package/src/snippets/index.ts +10 -0
- package/src/styles/dashboard.css +41 -0
- package/src/styles/index.css +20 -0
- package/src/styles/sources.css +6 -0
- package/src/types/index.ts +1 -0
- package/src/types/pageConfig.ts +103 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/logger.ts +57 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Footer Component
|
|
3
|
+
*
|
|
4
|
+
* Responsive footer with Desktop and Mobile variants
|
|
5
|
+
* Refactored from _old/MainLayout - uses context only!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import Link from 'next/link';
|
|
12
|
+
import { useIsMobile } from '@djangocfg/ui/hooks';
|
|
13
|
+
import { useAppContext } from '../../../context';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Footer Component
|
|
17
|
+
*
|
|
18
|
+
* Features:
|
|
19
|
+
* - Responsive (Desktop/Mobile variants)
|
|
20
|
+
* - Project info with logo and description
|
|
21
|
+
* - Badge (optional)
|
|
22
|
+
* - Social links (GitHub, LinkedIn, Twitter, Telegram)
|
|
23
|
+
* - Menu sections (single items or grouped)
|
|
24
|
+
* - Legal links (Privacy, Terms, Security, Cookies, etc.)
|
|
25
|
+
* - Copyright and branding
|
|
26
|
+
*
|
|
27
|
+
* All data from context!
|
|
28
|
+
*/
|
|
29
|
+
export function Footer() {
|
|
30
|
+
const { config } = useAppContext();
|
|
31
|
+
const isMobile = useIsMobile();
|
|
32
|
+
|
|
33
|
+
const { app, publicLayout } = config;
|
|
34
|
+
const footer = publicLayout.footer;
|
|
35
|
+
const currentYear = new Date().getFullYear();
|
|
36
|
+
|
|
37
|
+
if (isMobile) {
|
|
38
|
+
return (
|
|
39
|
+
<footer className="lg:hidden bg-background border-t border-border mt-auto">
|
|
40
|
+
<div className="w-full px-4 py-8">
|
|
41
|
+
{/* Project Info */}
|
|
42
|
+
<div className="text-center space-y-4 mb-6">
|
|
43
|
+
<div className="flex items-center justify-center space-x-2">
|
|
44
|
+
<div className="w-6 h-6 flex items-center justify-center">
|
|
45
|
+
<img
|
|
46
|
+
src={app.logoPath}
|
|
47
|
+
alt={`${app.name} Logo`}
|
|
48
|
+
className="w-full h-full object-contain"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
<span className="text-lg font-bold text-foreground">{app.name}</span>
|
|
52
|
+
</div>
|
|
53
|
+
{app.description && (
|
|
54
|
+
<p className="text-muted-foreground text-sm leading-relaxed max-w-md mx-auto">
|
|
55
|
+
{app.description}
|
|
56
|
+
</p>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
{/* Quick Links */}
|
|
61
|
+
<div className="flex flex-wrap justify-center gap-4 mb-6">
|
|
62
|
+
{footer.links.docs && (
|
|
63
|
+
<a
|
|
64
|
+
href={footer.links.docs}
|
|
65
|
+
target="_blank"
|
|
66
|
+
rel="noopener noreferrer"
|
|
67
|
+
className="text-sm text-muted-foreground hover:text-primary transition-colors"
|
|
68
|
+
title="Documentation"
|
|
69
|
+
>
|
|
70
|
+
Docs
|
|
71
|
+
</a>
|
|
72
|
+
)}
|
|
73
|
+
{footer.links.privacy && (
|
|
74
|
+
<Link
|
|
75
|
+
href={footer.links.privacy}
|
|
76
|
+
className="text-sm text-muted-foreground hover:text-primary transition-colors"
|
|
77
|
+
>
|
|
78
|
+
Privacy
|
|
79
|
+
</Link>
|
|
80
|
+
)}
|
|
81
|
+
{footer.links.terms && (
|
|
82
|
+
<Link
|
|
83
|
+
href={footer.links.terms}
|
|
84
|
+
className="text-sm text-muted-foreground hover:text-primary transition-colors"
|
|
85
|
+
>
|
|
86
|
+
Terms
|
|
87
|
+
</Link>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{/* Bottom Section */}
|
|
92
|
+
<div className="border-t border-border pt-4">
|
|
93
|
+
<div className="text-center space-y-2">
|
|
94
|
+
<div className="text-sm text-muted-foreground">
|
|
95
|
+
© {currentYear} {app.name}. All rights reserved.
|
|
96
|
+
</div>
|
|
97
|
+
<div className="text-sm text-muted-foreground">
|
|
98
|
+
Made with ❤️ by{' '}
|
|
99
|
+
<a
|
|
100
|
+
href="https://reforms.ai"
|
|
101
|
+
target="_blank"
|
|
102
|
+
rel="noopener noreferrer"
|
|
103
|
+
className="hover:text-primary transition-colors"
|
|
104
|
+
>
|
|
105
|
+
ReformsAI
|
|
106
|
+
</a>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</footer>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Desktop Footer
|
|
116
|
+
return (
|
|
117
|
+
<footer className="max-lg:hidden bg-background border-t border-border mt-auto">
|
|
118
|
+
<div className="w-full px-8 lg:px-16 xl:px-24 py-12">
|
|
119
|
+
<div className="flex flex-col gap-8">
|
|
120
|
+
{/* Top Section - Two Column Layout */}
|
|
121
|
+
<div className="flex gap-8">
|
|
122
|
+
{/* Left Column - Project Info */}
|
|
123
|
+
<div className="space-y-4" style={{ width: '30%', minWidth: '300px' }}>
|
|
124
|
+
<div className="flex items-center gap-2">
|
|
125
|
+
<div className="w-8 h-8 flex items-center justify-center">
|
|
126
|
+
<img
|
|
127
|
+
src={app.logoPath}
|
|
128
|
+
alt={`${app.name} Logo`}
|
|
129
|
+
className="w-full h-full object-contain"
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
<span className="text-xl font-bold text-foreground">{app.name}</span>
|
|
133
|
+
</div>
|
|
134
|
+
{app.description && (
|
|
135
|
+
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
136
|
+
{app.description}
|
|
137
|
+
</p>
|
|
138
|
+
)}
|
|
139
|
+
{/* Badge */}
|
|
140
|
+
{footer.badge && (
|
|
141
|
+
<div className="pt-2">
|
|
142
|
+
<span className="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg bg-primary/10 hover:bg-primary/15 border border-primary/20 text-xs font-medium text-primary transition-colors">
|
|
143
|
+
<footer.badge.icon className="w-3.5 h-3.5" />
|
|
144
|
+
{footer.badge.text}
|
|
145
|
+
</span>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{/* Right Column - Footer Menu Sections */}
|
|
151
|
+
<div className="grid grid-cols-2 gap-8">
|
|
152
|
+
{footer.menuSections.map((section) => {
|
|
153
|
+
// Single item section - render as direct link
|
|
154
|
+
if (section.items.length === 1) {
|
|
155
|
+
const item = section.items[0];
|
|
156
|
+
if (!item) return null;
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div key={section.title}>
|
|
160
|
+
<Link
|
|
161
|
+
href={item.path}
|
|
162
|
+
className="text-muted-foreground hover:text-primary text-sm transition-colors"
|
|
163
|
+
>
|
|
164
|
+
{item.label}
|
|
165
|
+
</Link>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Multiple items - render as section
|
|
171
|
+
return (
|
|
172
|
+
<div key={section.title}>
|
|
173
|
+
<h3 className="text-lg font-semibold text-foreground mb-4">
|
|
174
|
+
{section.title}
|
|
175
|
+
</h3>
|
|
176
|
+
<ul className="space-y-2">
|
|
177
|
+
{section.items.map((item) => (
|
|
178
|
+
<li key={item.path}>
|
|
179
|
+
<Link
|
|
180
|
+
href={item.path}
|
|
181
|
+
className="text-muted-foreground hover:text-primary text-sm transition-colors"
|
|
182
|
+
>
|
|
183
|
+
{item.label}
|
|
184
|
+
</Link>
|
|
185
|
+
</li>
|
|
186
|
+
))}
|
|
187
|
+
</ul>
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
})}
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{/* Bottom Section */}
|
|
195
|
+
<div className="border-t border-border" style={{ marginTop: '2rem', paddingTop: '2rem' }}>
|
|
196
|
+
<div className="flex justify-between items-center gap-4">
|
|
197
|
+
<div className="text-xs text-muted-foreground">
|
|
198
|
+
© {currentYear} {app.name}. All rights reserved.
|
|
199
|
+
</div>
|
|
200
|
+
<div className="text-xs text-muted-foreground flex items-center gap-1">
|
|
201
|
+
Made with ❤️ by{' '}
|
|
202
|
+
<a
|
|
203
|
+
href="https://reforms.ai"
|
|
204
|
+
target="_blank"
|
|
205
|
+
rel="noopener noreferrer"
|
|
206
|
+
className="hover:text-primary transition-colors"
|
|
207
|
+
>
|
|
208
|
+
ReformsAI
|
|
209
|
+
</a>
|
|
210
|
+
</div>
|
|
211
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
212
|
+
{footer.links.docs && (
|
|
213
|
+
<a
|
|
214
|
+
href={footer.links.docs}
|
|
215
|
+
target="_blank"
|
|
216
|
+
rel="noopener noreferrer"
|
|
217
|
+
className="text-xs text-muted-foreground hover:text-primary transition-colors"
|
|
218
|
+
title="Documentation"
|
|
219
|
+
>
|
|
220
|
+
Docs
|
|
221
|
+
</a>
|
|
222
|
+
)}
|
|
223
|
+
{footer.links.privacy && (
|
|
224
|
+
<Link
|
|
225
|
+
href={footer.links.privacy}
|
|
226
|
+
className="text-xs text-muted-foreground hover:text-primary transition-colors"
|
|
227
|
+
>
|
|
228
|
+
Privacy Policy
|
|
229
|
+
</Link>
|
|
230
|
+
)}
|
|
231
|
+
{footer.links.terms && (
|
|
232
|
+
<Link
|
|
233
|
+
href={footer.links.terms}
|
|
234
|
+
className="text-xs text-muted-foreground hover:text-primary transition-colors"
|
|
235
|
+
>
|
|
236
|
+
Terms of Service
|
|
237
|
+
</Link>
|
|
238
|
+
)}
|
|
239
|
+
{footer.links.security && (
|
|
240
|
+
<Link
|
|
241
|
+
href={footer.links.security}
|
|
242
|
+
className="text-xs text-muted-foreground hover:text-primary transition-colors"
|
|
243
|
+
>
|
|
244
|
+
Security
|
|
245
|
+
</Link>
|
|
246
|
+
)}
|
|
247
|
+
{footer.links.cookies && (
|
|
248
|
+
<Link
|
|
249
|
+
href={footer.links.cookies}
|
|
250
|
+
className="text-xs text-muted-foreground hover:text-primary transition-colors"
|
|
251
|
+
>
|
|
252
|
+
Cookies
|
|
253
|
+
</Link>
|
|
254
|
+
)}
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
</footer>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mobile Menu Drawer
|
|
3
|
+
*
|
|
4
|
+
* Full-screen slide-in menu for mobile devices
|
|
5
|
+
* Refactored from _old/MainLayout - uses context only!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { createPortal } from 'react-dom';
|
|
12
|
+
import Link from 'next/link';
|
|
13
|
+
import { Crown, LogOut, Settings, User, X } from 'lucide-react';
|
|
14
|
+
import { ButtonLink, Card, CardContent } from '@djangocfg/ui/components';
|
|
15
|
+
import { ThemeToggle } from '@djangocfg/ui/theme';
|
|
16
|
+
import { useAppContext } from '../../../context';
|
|
17
|
+
import { useAuth } from '../../../../../auth';
|
|
18
|
+
import { useNavigation } from '../../../hooks';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Mobile Menu Component
|
|
22
|
+
*
|
|
23
|
+
* Features:
|
|
24
|
+
* - Slide-in drawer from right
|
|
25
|
+
* - User card with info (authenticated)
|
|
26
|
+
* - Welcome card with sign in (guest)
|
|
27
|
+
* - Navigation sections
|
|
28
|
+
* - Theme toggle
|
|
29
|
+
* - Backdrop overlay
|
|
30
|
+
*
|
|
31
|
+
* All data from context!
|
|
32
|
+
*/
|
|
33
|
+
export function MobileMenu() {
|
|
34
|
+
const { config, mobileMenuOpen, closeMobileMenu } = useAppContext();
|
|
35
|
+
const { user, isAuthenticated, logout } = useAuth();
|
|
36
|
+
const { isActive } = useNavigation();
|
|
37
|
+
|
|
38
|
+
const { app, publicLayout, routes } = config;
|
|
39
|
+
|
|
40
|
+
// Animation state
|
|
41
|
+
const [isAnimating, setIsAnimating] = React.useState(false);
|
|
42
|
+
|
|
43
|
+
// Trigger animation when menu opens
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (mobileMenuOpen) {
|
|
46
|
+
// Small delay to trigger animation
|
|
47
|
+
setTimeout(() => setIsAnimating(true), 10);
|
|
48
|
+
} else {
|
|
49
|
+
setIsAnimating(false);
|
|
50
|
+
}
|
|
51
|
+
}, [mobileMenuOpen]);
|
|
52
|
+
|
|
53
|
+
const handleLogout = () => {
|
|
54
|
+
logout();
|
|
55
|
+
handleClose();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleClose = () => {
|
|
59
|
+
setIsAnimating(false);
|
|
60
|
+
setTimeout(closeMobileMenu, 300); // Wait for animation
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const handleNavigate = () => {
|
|
64
|
+
handleClose();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (!mobileMenuOpen) return null;
|
|
68
|
+
|
|
69
|
+
// Portal to body to avoid z-index and positioning issues
|
|
70
|
+
if (typeof window === 'undefined') return null;
|
|
71
|
+
|
|
72
|
+
return createPortal(
|
|
73
|
+
<>
|
|
74
|
+
{/* Backdrop with fade animation */}
|
|
75
|
+
<div
|
|
76
|
+
className={`fixed inset-0 z-[9998] bg-black/50 backdrop-blur-sm transition-opacity duration-300 lg:hidden ${
|
|
77
|
+
isAnimating ? 'opacity-100' : 'opacity-0'
|
|
78
|
+
}`}
|
|
79
|
+
onClick={handleClose}
|
|
80
|
+
aria-hidden="true"
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
{/* Menu Content with slide animation */}
|
|
84
|
+
<div
|
|
85
|
+
className={`fixed top-0 right-0 bottom-0 w-80 z-[9999] bg-card backdrop-blur-xl border-l border-border shadow-2xl transition-transform duration-300 ease-out lg:hidden ${
|
|
86
|
+
isAnimating ? 'translate-x-0' : 'translate-x-full'
|
|
87
|
+
}`}
|
|
88
|
+
role="dialog"
|
|
89
|
+
aria-modal="true"
|
|
90
|
+
aria-label="Mobile navigation menu"
|
|
91
|
+
>
|
|
92
|
+
<div className="flex flex-col h-full">
|
|
93
|
+
{/* Header */}
|
|
94
|
+
<div className="flex items-center justify-between p-4 border-b border-border/30">
|
|
95
|
+
<div className="flex items-center gap-3">
|
|
96
|
+
<img
|
|
97
|
+
src={app.logoPath}
|
|
98
|
+
alt={`${app.name} Logo`}
|
|
99
|
+
className="h-8 w-auto object-contain"
|
|
100
|
+
/>
|
|
101
|
+
<span className="text-lg font-bold text-foreground">
|
|
102
|
+
{app.name}
|
|
103
|
+
</span>
|
|
104
|
+
</div>
|
|
105
|
+
<button
|
|
106
|
+
onClick={handleClose}
|
|
107
|
+
className="p-2 rounded-lg transition-colors hover:bg-accent/50"
|
|
108
|
+
aria-label="Close menu"
|
|
109
|
+
>
|
|
110
|
+
<X className="size-5" />
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
{/* Scrollable Content */}
|
|
115
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-6">
|
|
116
|
+
{/* User Menu Card - Authenticated */}
|
|
117
|
+
{isAuthenticated ? (
|
|
118
|
+
<Card className="border-primary/20 shadow-lg bg-accent/30">
|
|
119
|
+
<CardContent className="p-4">
|
|
120
|
+
{/* User Info Header */}
|
|
121
|
+
<div className="flex items-center gap-3 mb-4 p-3 rounded-lg border border-border bg-accent/50">
|
|
122
|
+
<div className="w-10 h-10 rounded-full flex items-center justify-center bg-primary flex-shrink-0 overflow-hidden">
|
|
123
|
+
{user?.avatar ? (
|
|
124
|
+
<img
|
|
125
|
+
src={user.avatar}
|
|
126
|
+
alt={user?.email || 'User'}
|
|
127
|
+
className="w-10 h-10 rounded-full object-cover"
|
|
128
|
+
/>
|
|
129
|
+
) : (
|
|
130
|
+
<User className="w-5 h-5 text-primary-foreground" />
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
<div className="flex-1 min-w-0">
|
|
134
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
135
|
+
Signed in as
|
|
136
|
+
</p>
|
|
137
|
+
<p className="text-sm font-semibold truncate text-foreground">
|
|
138
|
+
{user?.email}
|
|
139
|
+
</p>
|
|
140
|
+
</div>
|
|
141
|
+
<div className="flex items-center gap-1">
|
|
142
|
+
<div className="size-2 rounded-full animate-pulse bg-green-500"></div>
|
|
143
|
+
<span className="text-xs font-medium text-green-600 dark:text-green-400">
|
|
144
|
+
Active
|
|
145
|
+
</span>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{/* Action Buttons */}
|
|
150
|
+
<div className="space-y-3">
|
|
151
|
+
{/* Dashboard link */}
|
|
152
|
+
{publicLayout.userMenu.dashboardPath && (
|
|
153
|
+
<ButtonLink
|
|
154
|
+
href={publicLayout.userMenu.dashboardPath}
|
|
155
|
+
variant="default"
|
|
156
|
+
size="sm"
|
|
157
|
+
className="w-full justify-center"
|
|
158
|
+
onClick={handleNavigate}
|
|
159
|
+
>
|
|
160
|
+
<Crown className="w-4 h-4 mr-2" />
|
|
161
|
+
Dashboard
|
|
162
|
+
</ButtonLink>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{/* Profile link */}
|
|
166
|
+
<ButtonLink
|
|
167
|
+
href={publicLayout.userMenu.profilePath}
|
|
168
|
+
variant="outline"
|
|
169
|
+
size="sm"
|
|
170
|
+
className="w-full justify-center"
|
|
171
|
+
onClick={handleNavigate}
|
|
172
|
+
>
|
|
173
|
+
<Settings className="w-4 h-4 mr-2" />
|
|
174
|
+
Profile Settings
|
|
175
|
+
</ButtonLink>
|
|
176
|
+
|
|
177
|
+
{/* Logout */}
|
|
178
|
+
<button
|
|
179
|
+
onClick={handleLogout}
|
|
180
|
+
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm rounded-lg transition-colors border text-destructive border-destructive/30 hover:bg-destructive/10"
|
|
181
|
+
>
|
|
182
|
+
<LogOut className="w-4 h-4" />
|
|
183
|
+
Sign Out
|
|
184
|
+
</button>
|
|
185
|
+
|
|
186
|
+
{/* Theme toggle */}
|
|
187
|
+
<div className="flex justify-center pt-2 border-t border-border/30">
|
|
188
|
+
<ThemeToggle />
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</CardContent>
|
|
192
|
+
</Card>
|
|
193
|
+
) : (
|
|
194
|
+
/* Guest Card */
|
|
195
|
+
<Card className="border-border bg-accent/30">
|
|
196
|
+
<CardContent className="p-4">
|
|
197
|
+
<div className="text-center space-y-4">
|
|
198
|
+
<div className="w-12 h-12 rounded-full flex items-center justify-center mx-auto bg-muted flex-shrink-0">
|
|
199
|
+
<User className="w-6 h-6 text-muted-foreground" />
|
|
200
|
+
</div>
|
|
201
|
+
<div>
|
|
202
|
+
<p className="text-sm font-medium mb-1 text-foreground">
|
|
203
|
+
Welcome!
|
|
204
|
+
</p>
|
|
205
|
+
<p className="text-xs text-muted-foreground">
|
|
206
|
+
Sign in to access your dashboard
|
|
207
|
+
</p>
|
|
208
|
+
</div>
|
|
209
|
+
<ButtonLink
|
|
210
|
+
href={routes.auth}
|
|
211
|
+
variant="default"
|
|
212
|
+
size="lg"
|
|
213
|
+
className="w-full justify-center"
|
|
214
|
+
onClick={handleNavigate}
|
|
215
|
+
>
|
|
216
|
+
<User className="w-5 h-5 mr-2" />
|
|
217
|
+
Sign In
|
|
218
|
+
</ButtonLink>
|
|
219
|
+
|
|
220
|
+
{/* Theme toggle */}
|
|
221
|
+
<div className="flex justify-center pt-2 border-t border-border/30">
|
|
222
|
+
<ThemeToggle />
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
</CardContent>
|
|
226
|
+
</Card>
|
|
227
|
+
)}
|
|
228
|
+
|
|
229
|
+
{/* Navigation Sections */}
|
|
230
|
+
<div className="space-y-6">
|
|
231
|
+
{publicLayout.navigation.menuSections.map((section) => {
|
|
232
|
+
// Single item section
|
|
233
|
+
if (section.items.length === 1) {
|
|
234
|
+
const item = section.items[0];
|
|
235
|
+
if (!item) return null;
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<div key={section.title} className="space-y-2">
|
|
239
|
+
<Link
|
|
240
|
+
href={item.path}
|
|
241
|
+
className={`block px-4 py-3 rounded-lg text-base font-medium transition-colors ${
|
|
242
|
+
isActive(item.path)
|
|
243
|
+
? 'text-primary border border-primary/20 bg-primary/[0.1]'
|
|
244
|
+
: 'text-muted-foreground hover:text-primary hover:bg-accent/50'
|
|
245
|
+
}`}
|
|
246
|
+
onClick={handleNavigate}
|
|
247
|
+
>
|
|
248
|
+
{item.label}
|
|
249
|
+
</Link>
|
|
250
|
+
</div>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Multiple items section
|
|
255
|
+
return (
|
|
256
|
+
<div key={section.title} className="space-y-3">
|
|
257
|
+
<h3 className="px-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
258
|
+
{section.title}
|
|
259
|
+
</h3>
|
|
260
|
+
<div className="space-y-1">
|
|
261
|
+
{section.items.map((item) => (
|
|
262
|
+
<Link
|
|
263
|
+
key={item.path}
|
|
264
|
+
href={item.path}
|
|
265
|
+
className={`block px-4 py-3 rounded-lg text-base font-medium transition-colors ${
|
|
266
|
+
isActive(item.path)
|
|
267
|
+
? 'bg-accent text-accent-foreground'
|
|
268
|
+
: 'text-foreground hover:bg-accent hover:text-accent-foreground'
|
|
269
|
+
}`}
|
|
270
|
+
onClick={handleNavigate}
|
|
271
|
+
>
|
|
272
|
+
{item.label}
|
|
273
|
+
</Link>
|
|
274
|
+
))}
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
})}
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
{/* Bottom spacer */}
|
|
282
|
+
<div className="h-20"></div>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
</>,
|
|
287
|
+
document.body
|
|
288
|
+
);
|
|
289
|
+
}
|