@djangocfg/layouts 1.4.29 → 2.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/README.md +277 -18
- package/package.json +15 -24
- package/src/auth/context/AuthContext.tsx +8 -5
- package/src/auth/hooks/useAuthGuard.ts +1 -1
- package/src/auth/hooks/useAutoAuth.ts +8 -7
- package/src/components/ErrorBoundary.tsx +78 -0
- package/src/components/JsonLd.tsx +31 -0
- package/src/components/LucideIcon.tsx +91 -0
- package/src/components/PageProgress.tsx +127 -0
- package/src/components/Suspense.tsx +29 -0
- package/src/{layouts/AppLayout/components → components}/UpdateNotifier/UpdateNotifier.tsx +56 -49
- package/src/components/index.ts +10 -0
- package/src/index.ts +25 -7
- package/src/layouts/AdminLayout/AdminLayout.tsx +46 -0
- package/src/layouts/AdminLayout/index.ts +7 -0
- package/src/layouts/AppLayout/AppLayout.tsx +278 -326
- package/src/layouts/AppLayout/index.ts +2 -39
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthContext.tsx +3 -2
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthHelp.tsx +1 -0
- package/src/layouts/AuthLayout/AuthLayout.tsx +61 -0
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/IdentifierForm.tsx +47 -34
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/OTPForm.tsx +2 -3
- package/src/layouts/AuthLayout/index.ts +24 -0
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/types.ts +1 -0
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +144 -0
- package/src/layouts/PrivateLayout/components/PrivateContent.tsx +32 -0
- package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +57 -0
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +141 -0
- package/src/layouts/PrivateLayout/components/index.ts +8 -0
- package/src/layouts/PrivateLayout/index.ts +7 -0
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +15 -7
- package/src/layouts/PublicLayout/PublicLayout.tsx +121 -0
- package/src/layouts/PublicLayout/components/PublicFooter.tsx +190 -0
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +117 -0
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +101 -0
- package/src/layouts/PublicLayout/components/index.ts +8 -0
- package/src/layouts/PublicLayout/index.ts +7 -0
- package/src/layouts/_components/UserMenu.tsx +160 -0
- package/src/layouts/_components/index.ts +7 -0
- package/src/layouts/index.ts +15 -8
- package/src/snippets/Analytics/AnalyticsProvider.tsx +8 -4
- package/src/snippets/Analytics/useAnalytics.ts +11 -21
- package/src/snippets/Chat/ChatWidget.tsx +4 -4
- package/src/snippets/ContactForm/ContactFormProvider.tsx +32 -19
- package/src/snippets/ContactForm/ContactPage.tsx +2 -4
- package/src/snippets/ContactForm/types.ts +3 -2
- package/src/snippets/index.ts +0 -1
- package/src/layouts/AppLayout/README.md +0 -204
- package/src/layouts/AppLayout/SUMMARY.md +0 -240
- package/src/layouts/AppLayout/USAGE.md +0 -312
- package/src/layouts/AppLayout/components/ErrorBoundary.tsx +0 -112
- package/src/layouts/AppLayout/components/PageProgress.tsx +0 -123
- package/src/layouts/AppLayout/components/Seo.tsx +0 -171
- package/src/layouts/AppLayout/components/UserMenu.tsx +0 -385
- package/src/layouts/AppLayout/components/index.ts +0 -11
- package/src/layouts/AppLayout/context/AppContext.tsx +0 -151
- package/src/layouts/AppLayout/context/index.ts +0 -5
- package/src/layouts/AppLayout/hooks/index.ts +0 -8
- package/src/layouts/AppLayout/hooks/useLayoutMode.ts +0 -26
- package/src/layouts/AppLayout/hooks/useNavigation.ts +0 -51
- package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +0 -224
- package/src/layouts/AppLayout/layouts/AdminLayout/README.md +0 -409
- package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.example.tsx +0 -98
- package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.tsx +0 -149
- package/src/layouts/AppLayout/layouts/AdminLayout/components/ParentSync.tsx +0 -146
- package/src/layouts/AppLayout/layouts/AdminLayout/components/index.ts +0 -3
- package/src/layouts/AppLayout/layouts/AdminLayout/context/CfgAppContext.tsx +0 -48
- package/src/layouts/AppLayout/layouts/AdminLayout/context/index.ts +0 -2
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/index.ts +0 -6
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +0 -279
- package/src/layouts/AppLayout/layouts/AdminLayout/index.ts +0 -24
- package/src/layouts/AppLayout/layouts/AdminLayout/lottie/energizing.json +0 -1
- package/src/layouts/AppLayout/layouts/AdminLayout/types/index.ts +0 -45
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +0 -41
- package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +0 -15
- package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +0 -82
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +0 -62
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +0 -89
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +0 -181
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +0 -9
- package/src/layouts/AppLayout/layouts/PrivateLayout/index.ts +0 -5
- package/src/layouts/AppLayout/layouts/PublicLayout/PublicLayout.tsx +0 -44
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +0 -242
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileDrawer.tsx +0 -150
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +0 -169
- package/src/layouts/AppLayout/layouts/PublicLayout/index.ts +0 -5
- package/src/layouts/AppLayout/layouts/index.ts +0 -7
- package/src/layouts/AppLayout/providers/CoreProviders.tsx +0 -80
- package/src/layouts/AppLayout/providers/index.ts +0 -5
- package/src/layouts/AppLayout/types/config.ts +0 -79
- package/src/layouts/AppLayout/types/index.ts +0 -11
- package/src/layouts/AppLayout/types/layout.ts +0 -54
- package/src/layouts/AppLayout/types/navigation.ts +0 -43
- package/src/layouts/AppLayout/types/page.ts +0 -80
- package/src/layouts/AppLayout/types/routes.ts +0 -43
- package/src/layouts/AppLayout/utils/index.ts +0 -5
- package/src/layouts/AppLayout/utils/routeDetection.ts +0 -31
- package/src/layouts/ErrorLayout/ErrorLayout.tsx +0 -173
- package/src/layouts/ErrorLayout/errorConfig.tsx +0 -152
- package/src/layouts/ErrorLayout/index.ts +0 -8
- package/src/layouts/SimpleLayout/SimpleLayout.tsx +0 -72
- package/src/layouts/SimpleLayout/index.ts +0 -3
- package/src/snippets/VideoPlayer/README.md +0 -238
- package/src/snippets/VideoPlayer/VideoControls.tsx +0 -137
- package/src/snippets/VideoPlayer/VideoPlayer.tsx +0 -248
- package/src/snippets/VideoPlayer/index.ts +0 -8
- package/src/snippets/VideoPlayer/types.ts +0 -61
- package/src/types/index.ts +0 -2
- package/src/types/pageConfig.ts +0 -100
- /package/src/{validation → components/ErrorsTracker}/README.md +0 -0
- /package/src/{validation → components/ErrorsTracker}/components/ErrorButtons.tsx +0 -0
- /package/src/{validation → components/ErrorsTracker}/components/ErrorToast.tsx +0 -0
- /package/src/{validation → components/ErrorsTracker}/hooks.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/index.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/providers/ErrorTrackingProvider.tsx +0 -0
- /package/src/{validation → components/ErrorsTracker}/types.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/utils/curl-generator.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/utils/formatters.ts +0 -0
- /package/src/{layouts/AppLayout/components → components}/UpdateNotifier/index.ts +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Layout
|
|
3
|
+
*
|
|
4
|
+
* Simple layout for public pages (home, docs, contact, legal pages)
|
|
5
|
+
* Import and use directly with props - no complex configs needed!
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Responsive navigation with mobile drawer
|
|
9
|
+
* - Footer with links and copyright
|
|
10
|
+
* - User menu integration
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { PublicLayout } from '@djangocfg/layouts';
|
|
15
|
+
*
|
|
16
|
+
* <PublicLayout
|
|
17
|
+
* logo="/logo.svg"
|
|
18
|
+
* siteName="My App"
|
|
19
|
+
* navigation={[
|
|
20
|
+
* { label: 'Home', href: '/' },
|
|
21
|
+
* { label: 'Docs', href: '/docs' }
|
|
22
|
+
* ]}
|
|
23
|
+
* footer={{
|
|
24
|
+
* links: { privacy: '/privacy', terms: '/terms' }
|
|
25
|
+
* }}
|
|
26
|
+
* >
|
|
27
|
+
* {children}
|
|
28
|
+
* </PublicLayout>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
'use client';
|
|
33
|
+
|
|
34
|
+
import { ReactNode, useState } from 'react';
|
|
35
|
+
import {
|
|
36
|
+
PublicNavigation,
|
|
37
|
+
PublicMobileDrawer,
|
|
38
|
+
PublicFooter,
|
|
39
|
+
} from './components';
|
|
40
|
+
|
|
41
|
+
export interface NavigationItem {
|
|
42
|
+
label: string;
|
|
43
|
+
href: string;
|
|
44
|
+
icon?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface FooterConfig {
|
|
48
|
+
links?: {
|
|
49
|
+
privacy?: string;
|
|
50
|
+
terms?: string;
|
|
51
|
+
security?: string;
|
|
52
|
+
cookies?: string;
|
|
53
|
+
docs?: string;
|
|
54
|
+
};
|
|
55
|
+
/** Copyright text (optional, auto-generated from siteName if not provided) */
|
|
56
|
+
copyright?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface PublicLayoutProps {
|
|
60
|
+
children: ReactNode;
|
|
61
|
+
/** Logo path or URL */
|
|
62
|
+
logo?: string;
|
|
63
|
+
/** Site name */
|
|
64
|
+
siteName?: string;
|
|
65
|
+
/** Navigation items */
|
|
66
|
+
navigation?: NavigationItem[];
|
|
67
|
+
/** Footer configuration */
|
|
68
|
+
footer?: FooterConfig;
|
|
69
|
+
/** User menu paths (optional, uses useAuth() for authentication state) */
|
|
70
|
+
userMenu?: {
|
|
71
|
+
/** Profile page path */
|
|
72
|
+
profilePath?: string;
|
|
73
|
+
/** Dashboard page path */
|
|
74
|
+
dashboardPath?: string;
|
|
75
|
+
/** Auth page path (for sign in button) */
|
|
76
|
+
authPath?: string;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function PublicLayout({
|
|
81
|
+
children,
|
|
82
|
+
logo,
|
|
83
|
+
siteName = 'App',
|
|
84
|
+
navigation = [],
|
|
85
|
+
footer,
|
|
86
|
+
userMenu,
|
|
87
|
+
}: PublicLayoutProps) {
|
|
88
|
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="min-h-screen flex flex-col">
|
|
92
|
+
{/* Navigation */}
|
|
93
|
+
<PublicNavigation
|
|
94
|
+
logo={logo}
|
|
95
|
+
siteName={siteName}
|
|
96
|
+
navigation={navigation}
|
|
97
|
+
userMenu={userMenu}
|
|
98
|
+
onMobileMenuClick={() => setMobileMenuOpen(true)}
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
{/* Mobile Drawer */}
|
|
102
|
+
<PublicMobileDrawer
|
|
103
|
+
isOpen={mobileMenuOpen}
|
|
104
|
+
onClose={() => setMobileMenuOpen(false)}
|
|
105
|
+
logo={logo}
|
|
106
|
+
siteName={siteName}
|
|
107
|
+
navigation={navigation}
|
|
108
|
+
userMenu={userMenu}
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
{/* Main Content */}
|
|
112
|
+
<main className="flex-1">{children}</main>
|
|
113
|
+
|
|
114
|
+
{/* Footer */}
|
|
115
|
+
{footer && (
|
|
116
|
+
<PublicFooter logo={logo} siteName={siteName} footer={footer} />
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Layout Footer
|
|
3
|
+
*
|
|
4
|
+
* Footer component for PublicLayout
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import Link from 'next/link';
|
|
11
|
+
import { useIsMobile } from '@djangocfg/ui/hooks';
|
|
12
|
+
|
|
13
|
+
interface FooterConfig {
|
|
14
|
+
links?: {
|
|
15
|
+
privacy?: string;
|
|
16
|
+
terms?: string;
|
|
17
|
+
security?: string;
|
|
18
|
+
cookies?: string;
|
|
19
|
+
docs?: string;
|
|
20
|
+
};
|
|
21
|
+
copyright?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface PublicFooterProps {
|
|
25
|
+
logo?: string;
|
|
26
|
+
siteName: string;
|
|
27
|
+
footer?: FooterConfig;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function PublicFooter({
|
|
31
|
+
logo,
|
|
32
|
+
siteName,
|
|
33
|
+
footer,
|
|
34
|
+
}: PublicFooterProps) {
|
|
35
|
+
const isMobile = useIsMobile();
|
|
36
|
+
const currentYear = new Date().getFullYear();
|
|
37
|
+
const copyright =
|
|
38
|
+
footer?.copyright ||
|
|
39
|
+
`© ${currentYear} ${siteName}. All rights reserved.`;
|
|
40
|
+
|
|
41
|
+
if (isMobile) {
|
|
42
|
+
return (
|
|
43
|
+
<footer className="lg:hidden bg-background border-t border-border mt-auto">
|
|
44
|
+
<div className="w-full px-4 py-8">
|
|
45
|
+
{/* Project Info */}
|
|
46
|
+
<div className="text-center space-y-4 mb-6">
|
|
47
|
+
<div className="flex items-center justify-center gap-2">
|
|
48
|
+
{logo && (
|
|
49
|
+
<div className="w-6 h-6 flex items-center justify-center">
|
|
50
|
+
<img
|
|
51
|
+
src={logo}
|
|
52
|
+
alt={`${siteName} Logo`}
|
|
53
|
+
className="w-full h-full object-contain"
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
<span className="text-lg font-bold text-foreground">
|
|
58
|
+
{siteName}
|
|
59
|
+
</span>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{/* Quick Links */}
|
|
64
|
+
<div className="flex flex-wrap justify-center gap-4 mb-6 items-center">
|
|
65
|
+
{footer?.links?.docs && (
|
|
66
|
+
<a
|
|
67
|
+
href={footer.links.docs}
|
|
68
|
+
target="_blank"
|
|
69
|
+
rel="noopener noreferrer"
|
|
70
|
+
className="text-sm text-muted-foreground hover:text-primary transition-colors"
|
|
71
|
+
title="Documentation"
|
|
72
|
+
>
|
|
73
|
+
Docs
|
|
74
|
+
</a>
|
|
75
|
+
)}
|
|
76
|
+
{footer?.links?.privacy && (
|
|
77
|
+
<Link
|
|
78
|
+
href={footer.links.privacy}
|
|
79
|
+
className="text-sm text-muted-foreground hover:text-primary transition-colors"
|
|
80
|
+
>
|
|
81
|
+
Privacy
|
|
82
|
+
</Link>
|
|
83
|
+
)}
|
|
84
|
+
{footer?.links?.terms && (
|
|
85
|
+
<Link
|
|
86
|
+
href={footer.links.terms}
|
|
87
|
+
className="text-sm text-muted-foreground hover:text-primary transition-colors"
|
|
88
|
+
>
|
|
89
|
+
Terms
|
|
90
|
+
</Link>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{/* Bottom Section */}
|
|
95
|
+
<div className="border-t border-border pt-4">
|
|
96
|
+
<div className="text-center space-y-2">
|
|
97
|
+
<div className="text-xs text-muted-foreground">{copyright}</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</footer>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Desktop Footer
|
|
106
|
+
return (
|
|
107
|
+
<footer className="max-lg:hidden bg-background border-t border-border mt-auto">
|
|
108
|
+
<div className="w-full px-8 lg:px-16 xl:px-24 py-12">
|
|
109
|
+
<div className="flex flex-col gap-8">
|
|
110
|
+
{/* Top Section */}
|
|
111
|
+
<div className="flex gap-8">
|
|
112
|
+
{/* Left Column - Project Info */}
|
|
113
|
+
<div className="space-y-4" style={{ width: '30%', minWidth: '300px' }}>
|
|
114
|
+
<div className="flex items-center gap-2">
|
|
115
|
+
{logo && (
|
|
116
|
+
<div className="w-8 h-8 flex items-center justify-center">
|
|
117
|
+
<img
|
|
118
|
+
src={logo}
|
|
119
|
+
alt={`${siteName} Logo`}
|
|
120
|
+
className="w-full h-full object-contain"
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
<span className="text-xl font-bold text-foreground">
|
|
125
|
+
{siteName}
|
|
126
|
+
</span>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{/* Bottom Section */}
|
|
132
|
+
<div
|
|
133
|
+
className="border-t border-border"
|
|
134
|
+
style={{ marginTop: '2rem', paddingTop: '2rem' }}
|
|
135
|
+
>
|
|
136
|
+
<div className="flex justify-between items-center gap-4">
|
|
137
|
+
<div className="text-xs text-muted-foreground">{copyright}</div>
|
|
138
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
139
|
+
{footer?.links?.docs && (
|
|
140
|
+
<a
|
|
141
|
+
href={footer.links.docs}
|
|
142
|
+
target="_blank"
|
|
143
|
+
rel="noopener noreferrer"
|
|
144
|
+
className="text-xs text-muted-foreground hover:text-primary transition-colors"
|
|
145
|
+
title="Documentation"
|
|
146
|
+
>
|
|
147
|
+
Docs
|
|
148
|
+
</a>
|
|
149
|
+
)}
|
|
150
|
+
{footer?.links?.privacy && (
|
|
151
|
+
<Link
|
|
152
|
+
href={footer.links.privacy}
|
|
153
|
+
className="text-xs text-muted-foreground hover:text-primary transition-colors"
|
|
154
|
+
>
|
|
155
|
+
Privacy Policy
|
|
156
|
+
</Link>
|
|
157
|
+
)}
|
|
158
|
+
{footer?.links?.terms && (
|
|
159
|
+
<Link
|
|
160
|
+
href={footer.links.terms}
|
|
161
|
+
className="text-xs text-muted-foreground hover:text-primary transition-colors"
|
|
162
|
+
>
|
|
163
|
+
Terms of Service
|
|
164
|
+
</Link>
|
|
165
|
+
)}
|
|
166
|
+
{footer?.links?.security && (
|
|
167
|
+
<Link
|
|
168
|
+
href={footer.links.security}
|
|
169
|
+
className="text-xs text-muted-foreground hover:text-primary transition-colors"
|
|
170
|
+
>
|
|
171
|
+
Security
|
|
172
|
+
</Link>
|
|
173
|
+
)}
|
|
174
|
+
{footer?.links?.cookies && (
|
|
175
|
+
<Link
|
|
176
|
+
href={footer.links.cookies}
|
|
177
|
+
className="text-xs text-muted-foreground hover:text-primary transition-colors"
|
|
178
|
+
>
|
|
179
|
+
Cookies
|
|
180
|
+
</Link>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</footer>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Layout Mobile Drawer
|
|
3
|
+
*
|
|
4
|
+
* Mobile drawer component for PublicLayout navigation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import Link from 'next/link';
|
|
11
|
+
import { X } from 'lucide-react';
|
|
12
|
+
import {
|
|
13
|
+
Drawer,
|
|
14
|
+
DrawerContent,
|
|
15
|
+
DrawerHeader,
|
|
16
|
+
DrawerTitle,
|
|
17
|
+
DrawerClose,
|
|
18
|
+
Button,
|
|
19
|
+
} from '@djangocfg/ui/components';
|
|
20
|
+
import { ThemeToggle } from '@djangocfg/ui/theme';
|
|
21
|
+
import { useAuth } from '../../../auth';
|
|
22
|
+
import { UserMenu } from '../../_components/UserMenu';
|
|
23
|
+
import type { NavigationItem } from '../PublicLayout';
|
|
24
|
+
|
|
25
|
+
interface PublicMobileDrawerProps {
|
|
26
|
+
isOpen: boolean;
|
|
27
|
+
onClose: () => void;
|
|
28
|
+
logo?: string;
|
|
29
|
+
siteName: string;
|
|
30
|
+
navigation: NavigationItem[];
|
|
31
|
+
userMenu?: {
|
|
32
|
+
profilePath?: string;
|
|
33
|
+
dashboardPath?: string;
|
|
34
|
+
authPath?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function PublicMobileDrawer({
|
|
39
|
+
isOpen,
|
|
40
|
+
onClose,
|
|
41
|
+
logo,
|
|
42
|
+
siteName,
|
|
43
|
+
navigation,
|
|
44
|
+
userMenu,
|
|
45
|
+
}: PublicMobileDrawerProps) {
|
|
46
|
+
const { isAuthenticated } = useAuth();
|
|
47
|
+
|
|
48
|
+
const handleNavigate = () => {
|
|
49
|
+
onClose();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Drawer open={isOpen} onOpenChange={(open) => !open && onClose()} direction="right">
|
|
54
|
+
<DrawerContent direction="right" className="w-80 lg:hidden">
|
|
55
|
+
{/* Header */}
|
|
56
|
+
<DrawerHeader className="flex flex-row items-center justify-between p-4 border-b border-border/30">
|
|
57
|
+
<div className="flex items-center gap-3">
|
|
58
|
+
{logo && (
|
|
59
|
+
<img
|
|
60
|
+
src={logo}
|
|
61
|
+
alt={`${siteName} Logo`}
|
|
62
|
+
className="h-8 w-auto object-contain"
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
<DrawerTitle className="text-lg font-bold text-foreground">
|
|
66
|
+
{siteName}
|
|
67
|
+
</DrawerTitle>
|
|
68
|
+
</div>
|
|
69
|
+
<DrawerClose className="p-2 rounded-sm transition-colors hover:bg-accent/50">
|
|
70
|
+
<X className="size-5" />
|
|
71
|
+
<span className="sr-only">Close menu</span>
|
|
72
|
+
</DrawerClose>
|
|
73
|
+
</DrawerHeader>
|
|
74
|
+
|
|
75
|
+
{/* Scrollable Content */}
|
|
76
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-6">
|
|
77
|
+
{/* Theme Toggle */}
|
|
78
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-border/30">
|
|
79
|
+
<span className="text-sm font-medium text-foreground">Theme</span>
|
|
80
|
+
<ThemeToggle />
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
{/* User Menu */}
|
|
84
|
+
<UserMenu
|
|
85
|
+
variant="mobile"
|
|
86
|
+
profilePath={userMenu?.profilePath}
|
|
87
|
+
dashboardPath={userMenu?.dashboardPath}
|
|
88
|
+
authPath={userMenu?.authPath}
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
{/* Navigation Items */}
|
|
92
|
+
<div className="space-y-3">
|
|
93
|
+
<h3 className="px-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
94
|
+
Menu
|
|
95
|
+
</h3>
|
|
96
|
+
<div className="space-y-1">
|
|
97
|
+
{navigation.map((item) => (
|
|
98
|
+
<Link
|
|
99
|
+
key={item.href}
|
|
100
|
+
href={item.href}
|
|
101
|
+
className="block px-4 py-3 rounded-sm text-base font-medium transition-colors text-foreground hover:bg-accent hover:text-accent-foreground"
|
|
102
|
+
onClick={handleNavigate}
|
|
103
|
+
>
|
|
104
|
+
{item.label}
|
|
105
|
+
</Link>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
{/* Bottom spacer */}
|
|
111
|
+
<div style={{ height: '15vh' }}></div>
|
|
112
|
+
</div>
|
|
113
|
+
</DrawerContent>
|
|
114
|
+
</Drawer>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Layout Navigation
|
|
3
|
+
*
|
|
4
|
+
* Navigation component for PublicLayout with mobile drawer support
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import Link from 'next/link';
|
|
11
|
+
import { Menu } from 'lucide-react';
|
|
12
|
+
import { Button } from '@djangocfg/ui/components';
|
|
13
|
+
import { ThemeToggle } from '@djangocfg/ui/theme';
|
|
14
|
+
import { cn } from '@djangocfg/ui/lib';
|
|
15
|
+
import { useIsMobile } from '@djangocfg/ui/hooks';
|
|
16
|
+
import { useAuth } from '../../../auth';
|
|
17
|
+
import { UserMenu } from '../../_components/UserMenu';
|
|
18
|
+
import type { NavigationItem } from '../PublicLayout';
|
|
19
|
+
|
|
20
|
+
interface PublicNavigationProps {
|
|
21
|
+
logo?: string;
|
|
22
|
+
siteName: string;
|
|
23
|
+
navigation: NavigationItem[];
|
|
24
|
+
userMenu?: {
|
|
25
|
+
profilePath?: string;
|
|
26
|
+
dashboardPath?: string;
|
|
27
|
+
authPath?: string;
|
|
28
|
+
};
|
|
29
|
+
onMobileMenuClick: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function PublicNavigation({
|
|
33
|
+
logo,
|
|
34
|
+
siteName,
|
|
35
|
+
navigation,
|
|
36
|
+
userMenu,
|
|
37
|
+
onMobileMenuClick,
|
|
38
|
+
}: PublicNavigationProps) {
|
|
39
|
+
const { isAuthenticated } = useAuth();
|
|
40
|
+
const isMobile = useIsMobile();
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<nav className="sticky top-0 w-full backdrop-blur-xl z-50 border-b bg-background/80">
|
|
44
|
+
<div className="w-full px-4 sm:px-6 lg:px-8">
|
|
45
|
+
<div className="flex items-center justify-between py-4">
|
|
46
|
+
{/* Logo */}
|
|
47
|
+
<Link href="/" className="flex items-center gap-2">
|
|
48
|
+
{logo && (
|
|
49
|
+
<img src={logo} alt={siteName} className="h-8 w-8" />
|
|
50
|
+
)}
|
|
51
|
+
<span className="font-bold text-lg">{siteName}</span>
|
|
52
|
+
</Link>
|
|
53
|
+
|
|
54
|
+
{/* Desktop Navigation */}
|
|
55
|
+
<div className="hidden md:flex items-center gap-6">
|
|
56
|
+
{navigation.map((item) => (
|
|
57
|
+
<Link
|
|
58
|
+
key={item.href}
|
|
59
|
+
href={item.href}
|
|
60
|
+
className="text-sm font-medium hover:text-primary transition-colors"
|
|
61
|
+
>
|
|
62
|
+
{item.label}
|
|
63
|
+
</Link>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{/* User Menu / Actions */}
|
|
68
|
+
<div className="flex items-center gap-4">
|
|
69
|
+
{!isMobile && (
|
|
70
|
+
<>
|
|
71
|
+
{/* Theme Toggle */}
|
|
72
|
+
<ThemeToggle />
|
|
73
|
+
|
|
74
|
+
{/* User Menu */}
|
|
75
|
+
<UserMenu
|
|
76
|
+
variant="desktop"
|
|
77
|
+
profilePath={userMenu?.profilePath}
|
|
78
|
+
dashboardPath={userMenu?.dashboardPath}
|
|
79
|
+
authPath={userMenu?.authPath}
|
|
80
|
+
/>
|
|
81
|
+
</>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{/* Mobile Menu Button */}
|
|
85
|
+
{isMobile && (
|
|
86
|
+
<Button
|
|
87
|
+
variant="ghost"
|
|
88
|
+
size="icon"
|
|
89
|
+
onClick={onMobileMenuClick}
|
|
90
|
+
aria-label="Toggle mobile menu"
|
|
91
|
+
>
|
|
92
|
+
<Menu className="h-5 w-5" />
|
|
93
|
+
</Button>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</nav>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|