@djangocfg/layouts 1.4.30 → 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.
Files changed (119) hide show
  1. package/README.md +277 -18
  2. package/package.json +15 -24
  3. package/src/auth/context/AuthContext.tsx +5 -5
  4. package/src/auth/hooks/useAuthGuard.ts +1 -1
  5. package/src/auth/hooks/useAutoAuth.ts +8 -7
  6. package/src/components/ErrorBoundary.tsx +78 -0
  7. package/src/components/JsonLd.tsx +31 -0
  8. package/src/components/LucideIcon.tsx +91 -0
  9. package/src/components/PageProgress.tsx +127 -0
  10. package/src/components/Suspense.tsx +29 -0
  11. package/src/{layouts/AppLayout/components → components}/UpdateNotifier/UpdateNotifier.tsx +56 -49
  12. package/src/components/index.ts +10 -0
  13. package/src/index.ts +25 -7
  14. package/src/layouts/AdminLayout/AdminLayout.tsx +46 -0
  15. package/src/layouts/AdminLayout/index.ts +7 -0
  16. package/src/layouts/AppLayout/AppLayout.tsx +278 -326
  17. package/src/layouts/AppLayout/index.ts +2 -39
  18. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthContext.tsx +3 -2
  19. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthHelp.tsx +1 -0
  20. package/src/layouts/AuthLayout/AuthLayout.tsx +61 -0
  21. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/IdentifierForm.tsx +47 -34
  22. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/OTPForm.tsx +2 -3
  23. package/src/layouts/AuthLayout/index.ts +24 -0
  24. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/types.ts +1 -0
  25. package/src/layouts/PrivateLayout/PrivateLayout.tsx +144 -0
  26. package/src/layouts/PrivateLayout/components/PrivateContent.tsx +32 -0
  27. package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +57 -0
  28. package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +141 -0
  29. package/src/layouts/PrivateLayout/components/index.ts +8 -0
  30. package/src/layouts/PrivateLayout/index.ts +7 -0
  31. package/src/layouts/ProfileLayout/ProfileLayout.tsx +15 -7
  32. package/src/layouts/PublicLayout/PublicLayout.tsx +121 -0
  33. package/src/layouts/PublicLayout/components/PublicFooter.tsx +190 -0
  34. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +117 -0
  35. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +101 -0
  36. package/src/layouts/PublicLayout/components/index.ts +8 -0
  37. package/src/layouts/PublicLayout/index.ts +7 -0
  38. package/src/layouts/_components/UserMenu.tsx +160 -0
  39. package/src/layouts/_components/index.ts +7 -0
  40. package/src/layouts/index.ts +15 -8
  41. package/src/snippets/Analytics/AnalyticsProvider.tsx +8 -4
  42. package/src/snippets/Analytics/useAnalytics.ts +11 -21
  43. package/src/snippets/Chat/ChatWidget.tsx +4 -4
  44. package/src/snippets/ContactForm/ContactFormProvider.tsx +32 -19
  45. package/src/snippets/ContactForm/ContactPage.tsx +2 -4
  46. package/src/snippets/ContactForm/types.ts +3 -2
  47. package/src/snippets/index.ts +0 -1
  48. package/src/layouts/AppLayout/README.md +0 -204
  49. package/src/layouts/AppLayout/SUMMARY.md +0 -240
  50. package/src/layouts/AppLayout/USAGE.md +0 -312
  51. package/src/layouts/AppLayout/components/ErrorBoundary.tsx +0 -112
  52. package/src/layouts/AppLayout/components/PageProgress.tsx +0 -123
  53. package/src/layouts/AppLayout/components/Seo.tsx +0 -171
  54. package/src/layouts/AppLayout/components/UserMenu.tsx +0 -385
  55. package/src/layouts/AppLayout/components/index.ts +0 -11
  56. package/src/layouts/AppLayout/context/AppContext.tsx +0 -151
  57. package/src/layouts/AppLayout/context/index.ts +0 -5
  58. package/src/layouts/AppLayout/hooks/index.ts +0 -8
  59. package/src/layouts/AppLayout/hooks/useLayoutMode.ts +0 -26
  60. package/src/layouts/AppLayout/hooks/useNavigation.ts +0 -51
  61. package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +0 -224
  62. package/src/layouts/AppLayout/layouts/AdminLayout/README.md +0 -409
  63. package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.example.tsx +0 -98
  64. package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.tsx +0 -149
  65. package/src/layouts/AppLayout/layouts/AdminLayout/components/ParentSync.tsx +0 -146
  66. package/src/layouts/AppLayout/layouts/AdminLayout/components/index.ts +0 -3
  67. package/src/layouts/AppLayout/layouts/AdminLayout/context/CfgAppContext.tsx +0 -48
  68. package/src/layouts/AppLayout/layouts/AdminLayout/context/index.ts +0 -2
  69. package/src/layouts/AppLayout/layouts/AdminLayout/hooks/index.ts +0 -6
  70. package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +0 -279
  71. package/src/layouts/AppLayout/layouts/AdminLayout/index.ts +0 -24
  72. package/src/layouts/AppLayout/layouts/AdminLayout/lottie/energizing.json +0 -1
  73. package/src/layouts/AppLayout/layouts/AdminLayout/types/index.ts +0 -45
  74. package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +0 -41
  75. package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +0 -15
  76. package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +0 -82
  77. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +0 -62
  78. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +0 -89
  79. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +0 -181
  80. package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +0 -9
  81. package/src/layouts/AppLayout/layouts/PrivateLayout/index.ts +0 -5
  82. package/src/layouts/AppLayout/layouts/PublicLayout/PublicLayout.tsx +0 -44
  83. package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +0 -242
  84. package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileDrawer.tsx +0 -150
  85. package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +0 -169
  86. package/src/layouts/AppLayout/layouts/PublicLayout/index.ts +0 -5
  87. package/src/layouts/AppLayout/layouts/index.ts +0 -7
  88. package/src/layouts/AppLayout/providers/CoreProviders.tsx +0 -80
  89. package/src/layouts/AppLayout/providers/index.ts +0 -5
  90. package/src/layouts/AppLayout/types/config.ts +0 -79
  91. package/src/layouts/AppLayout/types/index.ts +0 -11
  92. package/src/layouts/AppLayout/types/layout.ts +0 -54
  93. package/src/layouts/AppLayout/types/navigation.ts +0 -43
  94. package/src/layouts/AppLayout/types/page.ts +0 -80
  95. package/src/layouts/AppLayout/types/routes.ts +0 -43
  96. package/src/layouts/AppLayout/utils/index.ts +0 -5
  97. package/src/layouts/AppLayout/utils/routeDetection.ts +0 -31
  98. package/src/layouts/ErrorLayout/ErrorLayout.tsx +0 -173
  99. package/src/layouts/ErrorLayout/errorConfig.tsx +0 -152
  100. package/src/layouts/ErrorLayout/index.ts +0 -8
  101. package/src/layouts/SimpleLayout/SimpleLayout.tsx +0 -72
  102. package/src/layouts/SimpleLayout/index.ts +0 -3
  103. package/src/snippets/VideoPlayer/README.md +0 -238
  104. package/src/snippets/VideoPlayer/VideoControls.tsx +0 -137
  105. package/src/snippets/VideoPlayer/VideoPlayer.tsx +0 -248
  106. package/src/snippets/VideoPlayer/index.ts +0 -8
  107. package/src/snippets/VideoPlayer/types.ts +0 -61
  108. package/src/types/index.ts +0 -2
  109. package/src/types/pageConfig.ts +0 -100
  110. /package/src/{validation → components/ErrorsTracker}/README.md +0 -0
  111. /package/src/{validation → components/ErrorsTracker}/components/ErrorButtons.tsx +0 -0
  112. /package/src/{validation → components/ErrorsTracker}/components/ErrorToast.tsx +0 -0
  113. /package/src/{validation → components/ErrorsTracker}/hooks.ts +0 -0
  114. /package/src/{validation → components/ErrorsTracker}/index.ts +0 -0
  115. /package/src/{validation → components/ErrorsTracker}/providers/ErrorTrackingProvider.tsx +0 -0
  116. /package/src/{validation → components/ErrorsTracker}/types.ts +0 -0
  117. /package/src/{validation → components/ErrorsTracker}/utils/curl-generator.ts +0 -0
  118. /package/src/{validation → components/ErrorsTracker}/utils/formatters.ts +0 -0
  119. /package/src/{layouts/AppLayout/components → components}/UpdateNotifier/index.ts +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { createContext, useContext } from 'react';
4
4
 
5
- import { useAuthForm } from '../../../../auth/hooks';
5
+ import { useAuthForm } from '../../auth/hooks';
6
6
 
7
7
  import type { AuthContextType, AuthProps } from './types';
8
8
 
@@ -14,7 +14,7 @@ export const AuthProvider: React.FC<AuthProps> = ({
14
14
  supportUrl,
15
15
  termsUrl,
16
16
  privacyUrl,
17
- enablePhoneAuth = false, // Default to true for backward compatibility
17
+ enablePhoneAuth = false, // Default to false for backward compatibility
18
18
  onIdentifierSuccess,
19
19
  onOTPSuccess,
20
20
  onError,
@@ -51,3 +51,4 @@ export const useAuthContext = () => {
51
51
  }
52
52
  return context;
53
53
  };
54
+
@@ -111,3 +111,4 @@ export const AuthHelp: React.FC<AuthHelpProps> = ({
111
111
  </div>
112
112
  );
113
113
  };
114
+
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Auth Layout
3
+ *
4
+ * Layout for authentication pages with OTP authentication (email/phone)
5
+ * Supports two-step authentication flow: identifier input → OTP verification
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { AuthLayout } from '@djangocfg/layouts';
10
+ *
11
+ * <AuthLayout
12
+ * sourceUrl="https://example.com"
13
+ * supportUrl="https://example.com/support"
14
+ * termsUrl="https://example.com/terms"
15
+ * privacyUrl="https://example.com/privacy"
16
+ * enablePhoneAuth={true}
17
+ * >
18
+ * {/* Optional custom content above forms *\/}
19
+ * </AuthLayout>
20
+ * ```
21
+ */
22
+
23
+ 'use client';
24
+
25
+ import React from 'react';
26
+
27
+ import { AuthProvider, useAuthContext } from './AuthContext';
28
+ import { IdentifierForm } from './IdentifierForm';
29
+ import { OTPForm } from './OTPForm';
30
+ import { Suspense } from '../../components';
31
+
32
+ import type { AuthProps } from './types';
33
+
34
+ export type AuthLayoutProps = AuthProps;
35
+
36
+ export const AuthLayout: React.FC<AuthProps> = (props) => {
37
+ return (
38
+ <Suspense>
39
+ <AuthProvider {...props}>
40
+ <div
41
+ className={`min-h-screen flex flex-col items-center justify-center bg-background py-6 px-4 sm:py-12 sm:px-6 lg:px-8 ${props.className || ''}`}
42
+ >
43
+ <div className="w-full sm:max-w-md space-y-8">
44
+ {props.children}
45
+
46
+ <AuthContent />
47
+ </div>
48
+ </div>
49
+ </AuthProvider>
50
+ </Suspense>
51
+ );
52
+ };
53
+
54
+ // Separate component to use the context
55
+ const AuthContent: React.FC = () => {
56
+ const { step } = useAuthContext();
57
+
58
+ return (
59
+ <div>{step === 'identifier' ? <IdentifierForm /> : <OTPForm />}</div>
60
+ );
61
+ };
@@ -184,23 +184,29 @@ export const IdentifierForm: React.FC = () => {
184
184
  <div className="text-sm text-muted-foreground leading-5">
185
185
  <Label htmlFor="terms" className="cursor-pointer">
186
186
  I agree to the{' '}
187
- <a
188
- href={termsUrl}
189
- target="_blank"
190
- rel="noopener noreferrer"
191
- className="text-primary hover:underline font-medium"
192
- >
193
- Terms of Service
194
- </a>{' '}
195
- and{' '}
196
- <a
197
- href={privacyUrl}
198
- target="_blank"
199
- rel="noopener noreferrer"
200
- className="text-primary hover:underline font-medium"
201
- >
202
- Privacy Policy
203
- </a>
187
+ {termsUrl && (
188
+ <>
189
+ <a
190
+ href={termsUrl}
191
+ target="_blank"
192
+ rel="noopener noreferrer"
193
+ className="text-primary hover:underline font-medium"
194
+ >
195
+ Terms of Service
196
+ </a>{' '}
197
+ and{' '}
198
+ </>
199
+ )}
200
+ {privacyUrl && (
201
+ <a
202
+ href={privacyUrl}
203
+ target="_blank"
204
+ rel="noopener noreferrer"
205
+ className="text-primary hover:underline font-medium"
206
+ >
207
+ Privacy Policy
208
+ </a>
209
+ )}
204
210
  </Label>
205
211
  </div>
206
212
  </div>
@@ -267,23 +273,29 @@ export const IdentifierForm: React.FC = () => {
267
273
  <div className="text-sm text-muted-foreground leading-5">
268
274
  <Label htmlFor="terms-email" className="cursor-pointer">
269
275
  I agree to the{' '}
270
- <a
271
- href={termsUrl}
272
- target="_blank"
273
- rel="noopener noreferrer"
274
- className="text-primary hover:underline font-medium"
275
- >
276
- Terms of Service
277
- </a>{' '}
278
- and{' '}
279
- <a
280
- href={privacyUrl}
281
- target="_blank"
282
- rel="noopener noreferrer"
283
- className="text-primary hover:underline font-medium"
284
- >
285
- Privacy Policy
286
- </a>
276
+ {termsUrl && (
277
+ <>
278
+ <a
279
+ href={termsUrl}
280
+ target="_blank"
281
+ rel="noopener noreferrer"
282
+ className="text-primary hover:underline font-medium"
283
+ >
284
+ Terms of Service
285
+ </a>{' '}
286
+ and{' '}
287
+ </>
288
+ )}
289
+ {privacyUrl && (
290
+ <a
291
+ href={privacyUrl}
292
+ target="_blank"
293
+ rel="noopener noreferrer"
294
+ className="text-primary hover:underline font-medium"
295
+ >
296
+ Privacy Policy
297
+ </a>
298
+ )}
287
299
  </Label>
288
300
  </div>
289
301
  </div>
@@ -322,3 +334,4 @@ export const IdentifierForm: React.FC = () => {
322
334
  </Card>
323
335
  );
324
336
  };
337
+
@@ -1,6 +1,5 @@
1
- "use client"
1
+ 'use client';
2
2
 
3
- // @ts-nocheck
4
3
  import React from 'react';
5
4
  import { Mail, MessageCircle, ArrowLeft, RotateCw, ShieldCheck } from 'lucide-react';
6
5
 
@@ -73,7 +72,6 @@ export const OTPForm: React.FC = () => {
73
72
  Enter verification code
74
73
  </label>
75
74
  <div className="flex justify-center">
76
- {/* @ts-expect-error - TypeScript doesn't recognize children in JSX props for discriminated union */}
77
75
  <InputOTP
78
76
  value={otp}
79
77
  onChange={setOtp}
@@ -176,3 +174,4 @@ export const OTPForm: React.FC = () => {
176
174
  </Card>
177
175
  );
178
176
  };
177
+
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Auth Layout exports
3
+ */
4
+
5
+ export { AuthLayout } from './AuthLayout';
6
+ export type { AuthLayoutProps } from './AuthLayout';
7
+
8
+ // Context and hooks (renamed to avoid conflict with main AuthProvider)
9
+ export { AuthProvider as AuthLayoutProvider, useAuthContext } from './AuthContext';
10
+
11
+ // Forms
12
+ export { IdentifierForm } from './IdentifierForm';
13
+ export { OTPForm } from './OTPForm';
14
+
15
+ // Help component
16
+ export { AuthHelp } from './AuthHelp';
17
+
18
+ // Types (renamed to avoid conflict with main AuthContextType)
19
+ export type {
20
+ AuthContextType as AuthLayoutContextType,
21
+ AuthProps as AuthLayoutFormProps,
22
+ AuthHelpProps
23
+ } from './types';
24
+
@@ -59,3 +59,4 @@ export interface AuthHelpProps {
59
59
  className?: string;
60
60
  variant?: 'default' | 'compact';
61
61
  }
62
+
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Private Layout
3
+ *
4
+ * Layout for authenticated user pages (dashboard, profile, etc.)
5
+ * Import and use directly with props - no complex configs needed!
6
+ *
7
+ * Features:
8
+ * - Responsive sidebar with mobile burger menu
9
+ * - Keyboard shortcut (Ctrl/Cmd + B) to toggle sidebar
10
+ * - Header with sidebar trigger and user menu
11
+ * - Configurable content padding
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * import { PrivateLayout } from '@djangocfg/layouts';
16
+ *
17
+ * <PrivateLayout
18
+ * sidebar={{
19
+ * items: [
20
+ * { label: 'Dashboard', href: '/dashboard', icon: 'LayoutDashboard' },
21
+ * { label: 'Profile', href: '/profile', icon: 'User' }
22
+ * ]
23
+ * }}
24
+ * header={{
25
+ * title: 'Dashboard',
26
+ * profilePath: '/profile' // Optional, defaults to '/profile'
27
+ * }}
28
+ * >
29
+ * {children}
30
+ * </PrivateLayout>
31
+ *
32
+ * Note: User data (name, email, avatar) is automatically loaded from useAuth() context
33
+ * Keyboard shortcut: Ctrl/Cmd + B to toggle sidebar
34
+ * ```
35
+ */
36
+
37
+ 'use client';
38
+
39
+ import React, { ReactNode } from 'react';
40
+ import { SidebarProvider, SidebarInset, Preloader, Button, ButtonLink } from '@djangocfg/ui/components';
41
+ import { useAuth } from '../../auth';
42
+ import { PrivateSidebar, PrivateHeader, PrivateContent } from './components';
43
+ import type { LucideIcon as LucideIconType } from 'lucide-react';
44
+
45
+ export interface SidebarItem {
46
+ label: string;
47
+ href: string;
48
+ icon?: string | LucideIconType;
49
+ badge?: string | number;
50
+ }
51
+
52
+ export interface SidebarConfig {
53
+ items: SidebarItem[];
54
+ homeHref?: string;
55
+ }
56
+
57
+ export interface HeaderConfig {
58
+ title?: string;
59
+ /** Profile path (optional, defaults to '/profile') */
60
+ profilePath?: string;
61
+ }
62
+
63
+ export interface PrivateLayoutProps {
64
+ children: ReactNode;
65
+ /** Sidebar configuration */
66
+ sidebar?: SidebarConfig;
67
+ /** Header configuration */
68
+ header?: HeaderConfig;
69
+ /** Content padding */
70
+ contentPadding?: 'none' | 'default';
71
+ }
72
+
73
+ export function PrivateLayout({
74
+ children,
75
+ sidebar,
76
+ header,
77
+ contentPadding = 'default',
78
+ }: PrivateLayoutProps) {
79
+ const { isAuthenticated, isLoading, user } = useAuth();
80
+
81
+ // Debug logging in development
82
+ if (process.env.NODE_ENV === 'development') {
83
+ console.log('[PrivateLayout] Render state:', {
84
+ isLoading,
85
+ isAuthenticated,
86
+ hasUser: !!user,
87
+ hasSidebar: !!sidebar,
88
+ hasHeader: !!header,
89
+ });
90
+ }
91
+
92
+ // Show loading state while auth is being checked
93
+ if (isLoading) {
94
+ return (
95
+ <Preloader
96
+ variant="fullscreen"
97
+ text="Authenticating..."
98
+ size="lg"
99
+ backdrop={true}
100
+ backdropOpacity={80}
101
+ />
102
+ );
103
+ }
104
+
105
+ // Don't render if user is not authenticated
106
+ if (!isAuthenticated) {
107
+ if (process.env.NODE_ENV === 'development') {
108
+ console.warn('[PrivateLayout] User not authenticated, returning null');
109
+ }
110
+ return (
111
+ <div className="flex flex-col items-center justify-center min-h-screen gap-4">
112
+ <h3 className="text-2xl font-bold">
113
+ Not Authenticated
114
+ </h3>
115
+ <p className="text-muted-foreground">
116
+ Please log in to continue.
117
+ </p>
118
+ <ButtonLink
119
+ variant="outline"
120
+ href="/auth"
121
+ >
122
+ Login
123
+ </ButtonLink>
124
+ </div>
125
+ );
126
+ }
127
+
128
+ return (
129
+ <SidebarProvider defaultOpen={true}>
130
+ {/* Sidebar */}
131
+ {sidebar && <PrivateSidebar sidebar={sidebar} />}
132
+
133
+ {/* Main content area */}
134
+ <SidebarInset className="flex flex-col">
135
+ {/* Header with sidebar trigger */}
136
+ {(header || isAuthenticated) && <PrivateHeader header={header} />}
137
+
138
+ {/* Page content */}
139
+ <PrivateContent padding={contentPadding}>{children}</PrivateContent>
140
+ </SidebarInset>
141
+ </SidebarProvider>
142
+ );
143
+ }
144
+
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Private Layout Content
3
+ *
4
+ * Main content wrapper for PrivateLayout
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React, { ReactNode } from 'react';
10
+ import { cn } from '@djangocfg/ui/lib';
11
+
12
+ interface PrivateContentProps {
13
+ children: ReactNode;
14
+ padding?: 'none' | 'default';
15
+ }
16
+
17
+ export function PrivateContent({
18
+ children,
19
+ padding = 'default',
20
+ }: PrivateContentProps) {
21
+ return (
22
+ <main
23
+ className={cn(
24
+ 'flex-1 overflow-y-auto',
25
+ padding === 'default' && 'p-4 sm:p-6 lg:p-8'
26
+ )}
27
+ >
28
+ {children}
29
+ </main>
30
+ );
31
+ }
32
+
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Private Layout Header
3
+ *
4
+ * Header component for PrivateLayout with sidebar trigger
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+ import Link from 'next/link';
11
+ import {
12
+ Button,
13
+ Separator,
14
+ SidebarTrigger,
15
+ } from '@djangocfg/ui/components';
16
+ import { ThemeToggle } from '@djangocfg/ui/theme';
17
+ import { useAuth } from '../../../auth';
18
+ import { UserMenu } from '../../_components/UserMenu';
19
+ import type { HeaderConfig } from '../PrivateLayout';
20
+
21
+ interface PrivateHeaderProps {
22
+ header?: HeaderConfig;
23
+ }
24
+
25
+ export function PrivateHeader({ header }: PrivateHeaderProps) {
26
+ const { user, logout } = useAuth();
27
+ const profilePath = header?.profilePath || '/profile';
28
+
29
+ return (
30
+ <header className="sticky top-0 z-10 h-16 flex items-center justify-between px-4 shrink-0 bg-background border-b border-border">
31
+ {/* Left side */}
32
+ <div className="flex items-center gap-4">
33
+ <SidebarTrigger className="-ml-1" />
34
+ <Separator orientation="vertical" className="mr-2 h-4" />
35
+
36
+ {header?.title && (
37
+ <h1 className="text-lg font-semibold text-foreground">
38
+ {header.title}
39
+ </h1>
40
+ )}
41
+ </div>
42
+
43
+ {/* Right side */}
44
+ <div className="flex items-center gap-3">
45
+ {/* Theme Toggle */}
46
+ <ThemeToggle />
47
+
48
+ {/* User Menu */}
49
+ <UserMenu
50
+ variant="desktop"
51
+ profilePath={profilePath}
52
+ />
53
+ </div>
54
+ </header>
55
+ );
56
+ }
57
+
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Private Layout Sidebar
3
+ *
4
+ * Sidebar navigation component for PrivateLayout
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+ import Link from 'next/link';
11
+ import { usePathname } from 'next/navigation';
12
+ import {
13
+ Sidebar,
14
+ SidebarContent,
15
+ SidebarGroup,
16
+ SidebarGroupContent,
17
+ SidebarGroupLabel,
18
+ SidebarHeader,
19
+ SidebarMenu,
20
+ SidebarMenuBadge,
21
+ SidebarMenuButton,
22
+ SidebarMenuItem,
23
+ useSidebar,
24
+ } from '@djangocfg/ui/components';
25
+ import { cn } from '@djangocfg/ui/lib';
26
+ import { LucideIcon } from '../../../components';
27
+ import type { SidebarItem, SidebarConfig } from '../PrivateLayout';
28
+
29
+ interface PrivateSidebarProps {
30
+ sidebar: SidebarConfig;
31
+ }
32
+
33
+ export function PrivateSidebar({ sidebar }: PrivateSidebarProps) {
34
+ const pathname = usePathname();
35
+ const { state, isMobile } = useSidebar();
36
+ const homeHref = sidebar.homeHref || '/';
37
+
38
+ const isActive = (href: string) => {
39
+ const matches = pathname === href || pathname.startsWith(href + '/');
40
+ if (!matches) return false;
41
+
42
+ // Check if there's a more specific (longer) path that also matches
43
+ return !sidebar.items.some(otherItem =>
44
+ otherItem.href !== href &&
45
+ otherItem.href.startsWith(href + '/') &&
46
+ (pathname === otherItem.href || pathname.startsWith(otherItem.href + '/'))
47
+ );
48
+ };
49
+
50
+ return (
51
+ <Sidebar collapsible="icon">
52
+ <SidebarHeader>
53
+ <div
54
+ className="flex items-center gap-3"
55
+ style={
56
+ state === 'collapsed'
57
+ ? {
58
+ paddingLeft: '7px',
59
+ paddingTop: '0.5rem',
60
+ paddingBottom: '0.5rem',
61
+ transition: 'padding 200ms ease-in-out',
62
+ }
63
+ : {
64
+ padding: '0.5rem',
65
+ transition: 'padding 200ms ease-in-out',
66
+ }
67
+ }
68
+ >
69
+ <Link href={homeHref}>
70
+ <div className="flex items-center gap-3">
71
+ <div
72
+ className={cn(
73
+ 'bg-primary rounded-sm flex items-center justify-center flex-shrink-0',
74
+ isMobile ? 'h-10 w-10' : 'h-8 w-8'
75
+ )}
76
+ >
77
+ <span className="text-primary-foreground font-bold text-sm">
78
+ D
79
+ </span>
80
+ </div>
81
+ {state !== 'collapsed' && (
82
+ <span
83
+ className={cn(
84
+ 'font-semibold text-foreground truncate',
85
+ isMobile && 'text-base'
86
+ )}
87
+ style={{ whiteSpace: 'nowrap' }}
88
+ >
89
+ Dashboard
90
+ </span>
91
+ )}
92
+ </div>
93
+ </Link>
94
+ </div>
95
+ </SidebarHeader>
96
+
97
+ <SidebarContent>
98
+ <SidebarGroup>
99
+ <SidebarGroupContent>
100
+ <SidebarMenu>
101
+ {sidebar.items.map((item) => {
102
+ const active = isActive(item.href);
103
+
104
+ return (
105
+ <SidebarMenuItem key={item.href}>
106
+ <SidebarMenuButton
107
+ asChild
108
+ isActive={active}
109
+ tooltip={item.label}
110
+ size={isMobile ? 'lg' : 'default'}
111
+ >
112
+ <Link href={item.href}>
113
+ {item.icon && (
114
+ <LucideIcon
115
+ icon={
116
+ typeof item.icon === 'string'
117
+ ? item.icon
118
+ : item.icon
119
+ }
120
+ className={isMobile ? 'h-5 w-5' : 'h-4 w-4'}
121
+ />
122
+ )}
123
+ <span className={isMobile ? 'text-base' : ''}>
124
+ {item.label}
125
+ </span>
126
+ {item.badge && (
127
+ <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>
128
+ )}
129
+ </Link>
130
+ </SidebarMenuButton>
131
+ </SidebarMenuItem>
132
+ );
133
+ })}
134
+ </SidebarMenu>
135
+ </SidebarGroupContent>
136
+ </SidebarGroup>
137
+ </SidebarContent>
138
+ </Sidebar>
139
+ );
140
+ }
141
+
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Private Layout Components
3
+ */
4
+
5
+ export { PrivateSidebar } from './PrivateSidebar';
6
+ export { PrivateHeader } from './PrivateHeader';
7
+ export { PrivateContent } from './PrivateContent';
8
+
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Private Layout exports
3
+ */
4
+
5
+ export { PrivateLayout } from './PrivateLayout';
6
+ export type { PrivateLayoutProps, SidebarItem, SidebarConfig, HeaderConfig } from './PrivateLayout';
7
+
@@ -43,13 +43,15 @@ const ProfileContent = ({
43
43
  });
44
44
  };
45
45
 
46
+ React.useEffect(() => {
47
+ if (onUnauthenticated) {
48
+ onUnauthenticated();
49
+ }
50
+ }, [onUnauthenticated]);
51
+
52
+
46
53
  // Show auth check if no user
47
54
  if (!user && !isLoading) {
48
- React.useEffect(() => {
49
- if (onUnauthenticated) {
50
- onUnauthenticated();
51
- }
52
- }, [onUnauthenticated]);
53
55
 
54
56
  return (
55
57
  <div className="flex items-center justify-center min-h-screen">
@@ -63,8 +65,14 @@ const ProfileContent = ({
63
65
 
64
66
  if (isLoading) {
65
67
  return (
66
- <div className="flex items-center justify-center min-h-screen">
67
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
68
+ <div>
69
+ <Preloader
70
+ variant="fullscreen"
71
+ text="Authenticating..."
72
+ size="lg"
73
+ backdrop={true}
74
+ backdropOpacity={80}
75
+ />
68
76
  </div>
69
77
  );
70
78
  }