@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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/package.json +86 -0
  4. package/src/auth/README.md +962 -0
  5. package/src/auth/context/AuthContext.tsx +458 -0
  6. package/src/auth/context/index.ts +2 -0
  7. package/src/auth/context/types.ts +63 -0
  8. package/src/auth/hooks/index.ts +6 -0
  9. package/src/auth/hooks/useAuthForm.ts +329 -0
  10. package/src/auth/hooks/useAuthGuard.ts +23 -0
  11. package/src/auth/hooks/useAuthRedirect.ts +51 -0
  12. package/src/auth/hooks/useAutoAuth.ts +42 -0
  13. package/src/auth/hooks/useLocalStorage.ts +211 -0
  14. package/src/auth/hooks/useSessionStorage.ts +186 -0
  15. package/src/auth/index.ts +10 -0
  16. package/src/auth/middlewares/index.ts +1 -0
  17. package/src/auth/middlewares/proxy.ts +24 -0
  18. package/src/auth/server.ts +6 -0
  19. package/src/auth/utils/errors.ts +34 -0
  20. package/src/auth/utils/index.ts +2 -0
  21. package/src/auth/utils/validation.ts +14 -0
  22. package/src/index.ts +15 -0
  23. package/src/layouts/AppLayout/AppLayout.tsx +123 -0
  24. package/src/layouts/AppLayout/README.md +204 -0
  25. package/src/layouts/AppLayout/SUMMARY.md +240 -0
  26. package/src/layouts/AppLayout/USAGE.md +312 -0
  27. package/src/layouts/AppLayout/components/PageProgress.tsx +104 -0
  28. package/src/layouts/AppLayout/components/Seo.tsx +87 -0
  29. package/src/layouts/AppLayout/components/index.ts +6 -0
  30. package/src/layouts/AppLayout/context/AppContext.tsx +146 -0
  31. package/src/layouts/AppLayout/context/index.ts +5 -0
  32. package/src/layouts/AppLayout/hooks/index.ts +6 -0
  33. package/src/layouts/AppLayout/hooks/useLayoutMode.ts +26 -0
  34. package/src/layouts/AppLayout/hooks/useNavigation.ts +49 -0
  35. package/src/layouts/AppLayout/index.ts +31 -0
  36. package/src/layouts/AppLayout/layouts/AuthLayout/AuthContext.tsx +51 -0
  37. package/src/layouts/AppLayout/layouts/AuthLayout/AuthHelp.tsx +111 -0
  38. package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +40 -0
  39. package/src/layouts/AppLayout/layouts/AuthLayout/IdentifierForm.tsx +330 -0
  40. package/src/layouts/AppLayout/layouts/AuthLayout/OTPForm.tsx +158 -0
  41. package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +13 -0
  42. package/src/layouts/AppLayout/layouts/AuthLayout/types.ts +61 -0
  43. package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +92 -0
  44. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +60 -0
  45. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +170 -0
  46. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +164 -0
  47. package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +7 -0
  48. package/src/layouts/AppLayout/layouts/PrivateLayout/index.ts +5 -0
  49. package/src/layouts/AppLayout/layouts/PublicLayout/PublicLayout.tsx +44 -0
  50. package/src/layouts/AppLayout/layouts/PublicLayout/components/DesktopUserMenu.tsx +136 -0
  51. package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +262 -0
  52. package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenu.tsx +289 -0
  53. package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +159 -0
  54. package/src/layouts/AppLayout/layouts/PublicLayout/index.ts +5 -0
  55. package/src/layouts/AppLayout/layouts/index.ts +7 -0
  56. package/src/layouts/AppLayout/providers/CoreProviders.tsx +47 -0
  57. package/src/layouts/AppLayout/providers/index.ts +5 -0
  58. package/src/layouts/AppLayout/types/config.ts +40 -0
  59. package/src/layouts/AppLayout/types/index.ts +10 -0
  60. package/src/layouts/AppLayout/types/layout.ts +47 -0
  61. package/src/layouts/AppLayout/types/navigation.ts +41 -0
  62. package/src/layouts/AppLayout/types/routes.ts +45 -0
  63. package/src/layouts/AppLayout/utils/index.ts +5 -0
  64. package/src/layouts/AppLayout/utils/routeDetection.ts +31 -0
  65. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +125 -0
  66. package/src/layouts/PaymentsLayout/README.md +133 -0
  67. package/src/layouts/PaymentsLayout/components/CreateApiKeyDialog.tsx +172 -0
  68. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +203 -0
  69. package/src/layouts/PaymentsLayout/components/DeleteApiKeyDialog.tsx +100 -0
  70. package/src/layouts/PaymentsLayout/components/index.ts +4 -0
  71. package/src/layouts/PaymentsLayout/events.ts +106 -0
  72. package/src/layouts/PaymentsLayout/index.ts +20 -0
  73. package/src/layouts/PaymentsLayout/types.ts +19 -0
  74. package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeyMetrics.tsx +109 -0
  75. package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeysList.tsx +194 -0
  76. package/src/layouts/PaymentsLayout/views/apikeys/components/index.ts +3 -0
  77. package/src/layouts/PaymentsLayout/views/apikeys/index.tsx +19 -0
  78. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +99 -0
  79. package/src/layouts/PaymentsLayout/views/overview/components/MetricsCards.tsx +103 -0
  80. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +138 -0
  81. package/src/layouts/PaymentsLayout/views/overview/components/index.ts +4 -0
  82. package/src/layouts/PaymentsLayout/views/overview/index.tsx +23 -0
  83. package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +282 -0
  84. package/src/layouts/PaymentsLayout/views/payments/components/index.ts +2 -0
  85. package/src/layouts/PaymentsLayout/views/payments/index.tsx +18 -0
  86. package/src/layouts/PaymentsLayout/views/tariffs/index.tsx +29 -0
  87. package/src/layouts/PaymentsLayout/views/transactions/index.tsx +29 -0
  88. package/src/layouts/ProfileLayout/ProfileLayout.tsx +110 -0
  89. package/src/layouts/ProfileLayout/components/AvatarSection.tsx +146 -0
  90. package/src/layouts/ProfileLayout/components/ProfileForm.tsx +208 -0
  91. package/src/layouts/ProfileLayout/components/index.ts +3 -0
  92. package/src/layouts/ProfileLayout/index.ts +3 -0
  93. package/src/layouts/SupportLayout/README.md +91 -0
  94. package/src/layouts/SupportLayout/SupportLayout.tsx +178 -0
  95. package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +154 -0
  96. package/src/layouts/SupportLayout/components/MessageInput.tsx +92 -0
  97. package/src/layouts/SupportLayout/components/MessageList.tsx +312 -0
  98. package/src/layouts/SupportLayout/components/TicketCard.tsx +96 -0
  99. package/src/layouts/SupportLayout/components/TicketList.tsx +152 -0
  100. package/src/layouts/SupportLayout/components/index.ts +6 -0
  101. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +260 -0
  102. package/src/layouts/SupportLayout/context/index.ts +2 -0
  103. package/src/layouts/SupportLayout/events.ts +31 -0
  104. package/src/layouts/SupportLayout/hooks/index.ts +2 -0
  105. package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +118 -0
  106. package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +91 -0
  107. package/src/layouts/SupportLayout/index.ts +6 -0
  108. package/src/layouts/SupportLayout/types.ts +23 -0
  109. package/src/layouts/index.ts +9 -0
  110. package/src/snippets/AuthDialog/AuthDialog.tsx +88 -0
  111. package/src/snippets/AuthDialog/events.ts +21 -0
  112. package/src/snippets/AuthDialog/index.ts +3 -0
  113. package/src/snippets/AuthDialog/useAuthDialog.ts +27 -0
  114. package/src/snippets/Breadcrumbs.tsx +80 -0
  115. package/src/snippets/Chat/ChatUIContext.tsx +110 -0
  116. package/src/snippets/Chat/ChatWidget.tsx +476 -0
  117. package/src/snippets/Chat/README.md +122 -0
  118. package/src/snippets/Chat/components/MessageInput.tsx +124 -0
  119. package/src/snippets/Chat/components/MessageList.tsx +168 -0
  120. package/src/snippets/Chat/components/SessionList.tsx +192 -0
  121. package/src/snippets/Chat/components/index.ts +9 -0
  122. package/src/snippets/Chat/hooks/index.ts +6 -0
  123. package/src/snippets/Chat/hooks/useInfiniteSessions.ts +83 -0
  124. package/src/snippets/Chat/index.tsx +44 -0
  125. package/src/snippets/Chat/types.ts +79 -0
  126. package/src/snippets/VideoPlayer/README.md +203 -0
  127. package/src/snippets/VideoPlayer/VideoControls.tsx +133 -0
  128. package/src/snippets/VideoPlayer/VideoPlayer.tsx +114 -0
  129. package/src/snippets/VideoPlayer/index.ts +8 -0
  130. package/src/snippets/VideoPlayer/types.ts +61 -0
  131. package/src/snippets/index.ts +10 -0
  132. package/src/styles/dashboard.css +41 -0
  133. package/src/styles/index.css +20 -0
  134. package/src/styles/sources.css +6 -0
  135. package/src/types/index.ts +1 -0
  136. package/src/types/pageConfig.ts +103 -0
  137. package/src/utils/index.ts +6 -0
  138. package/src/utils/logger.ts +57 -0
@@ -0,0 +1,186 @@
1
+ import { useState } from 'react';
2
+
3
+ /**
4
+ * Simple sessionStorage hook with better error handling
5
+ * @param key - Storage key
6
+ * @param initialValue - Default value if key doesn't exist
7
+ * @returns [value, setValue, removeValue] - Current value, setter function, and remove function
8
+ */
9
+ export function useSessionStorage<T>(key: string, initialValue: T) {
10
+ // Get initial value from sessionStorage or use provided initialValue
11
+ const [storedValue, setStoredValue] = useState<T>(() => {
12
+ if (typeof window === 'undefined') {
13
+ return initialValue;
14
+ }
15
+
16
+ try {
17
+ const item = window.sessionStorage.getItem(key);
18
+ return item ? JSON.parse(item) : initialValue;
19
+ } catch (error) {
20
+ console.error(`Error reading sessionStorage key "${key}":`, error);
21
+ return initialValue;
22
+ }
23
+ });
24
+
25
+ // Check data size and limit
26
+ const checkDataSize = (data: any): boolean => {
27
+ try {
28
+ const jsonString = JSON.stringify(data);
29
+ const sizeInBytes = new Blob([jsonString]).size;
30
+ const sizeInKB = sizeInBytes / 1024;
31
+
32
+ // Limit to 1MB per item
33
+ if (sizeInKB > 1024) {
34
+ console.warn(`Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`);
35
+ return false;
36
+ }
37
+
38
+ return true;
39
+ } catch (error) {
40
+ console.error(`Error checking data size for key "${key}":`, error);
41
+ return false;
42
+ }
43
+ };
44
+
45
+ // Clear old data when sessionStorage is full
46
+ const clearOldData = () => {
47
+ try {
48
+ const keys = Object.keys(sessionStorage).filter(key => key && typeof key === 'string');
49
+ // Remove oldest items if we have more than 50 items
50
+ if (keys.length > 50) {
51
+ const itemsToRemove = Math.ceil(keys.length * 0.2);
52
+ for (let i = 0; i < itemsToRemove; i++) {
53
+ try {
54
+ const key = keys[i];
55
+ if (key) {
56
+ sessionStorage.removeItem(key);
57
+ sessionStorage.removeItem(`${key}_timestamp`);
58
+ }
59
+ } catch {
60
+ // Ignore errors when removing items
61
+ }
62
+ }
63
+ }
64
+ } catch (error) {
65
+ console.error('Error clearing old sessionStorage data:', error);
66
+ }
67
+ };
68
+
69
+ // Force clear all data if quota is exceeded
70
+ const forceClearAll = () => {
71
+ try {
72
+ const keys = Object.keys(sessionStorage);
73
+ for (const key of keys) {
74
+ try {
75
+ sessionStorage.removeItem(key);
76
+ } catch {
77
+ // Ignore errors when removing items
78
+ }
79
+ }
80
+ } catch (error) {
81
+ console.error('Error force clearing sessionStorage:', error);
82
+ }
83
+ };
84
+
85
+ // Update sessionStorage when value changes
86
+ const setValue = (value: T | ((val: T) => T)) => {
87
+ try {
88
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
89
+
90
+ // Check data size before attempting to save
91
+ if (!checkDataSize(valueToStore)) {
92
+ console.warn(`Data size too large for key "${key}", removing key`);
93
+ // Remove the key if data is too large
94
+ try {
95
+ window.sessionStorage.removeItem(key);
96
+ window.sessionStorage.removeItem(`${key}_timestamp`);
97
+ } catch {
98
+ // Ignore errors when removing
99
+ }
100
+ // Still update the state
101
+ setStoredValue(valueToStore);
102
+ return;
103
+ }
104
+
105
+ setStoredValue(valueToStore);
106
+
107
+ if (typeof window !== 'undefined') {
108
+ // Try to set the value
109
+ try {
110
+ window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
111
+ // Add timestamp for cleanup
112
+ window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
113
+ } catch (storageError: any) {
114
+ // If quota exceeded, clear old data and try again
115
+ if (storageError.name === 'QuotaExceededError' ||
116
+ storageError.code === 22 ||
117
+ storageError.message?.includes('quota')) {
118
+ console.warn('sessionStorage quota exceeded, clearing old data...');
119
+ clearOldData();
120
+
121
+ // Try again after clearing
122
+ try {
123
+ window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
124
+ window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
125
+ } catch (retryError) {
126
+ console.error(`Failed to set sessionStorage key "${key}" after clearing old data:`, retryError);
127
+ // If still fails, force clear all and try one more time
128
+ try {
129
+ forceClearAll();
130
+ window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
131
+ window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
132
+ } catch (finalError) {
133
+ console.error(`Failed to set sessionStorage key "${key}" after force clearing:`, finalError);
134
+ // If still fails, just update the state without sessionStorage
135
+ setStoredValue(valueToStore);
136
+ }
137
+ }
138
+ } else {
139
+ throw storageError;
140
+ }
141
+ }
142
+ }
143
+ } catch (error) {
144
+ console.error(`Error setting sessionStorage key "${key}":`, error);
145
+ // Still update the state even if sessionStorage fails
146
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
147
+ setStoredValue(valueToStore);
148
+ }
149
+ };
150
+
151
+ // Remove value from sessionStorage
152
+ const removeValue = () => {
153
+ try {
154
+ setStoredValue(initialValue);
155
+ if (typeof window !== 'undefined') {
156
+ try {
157
+ window.sessionStorage.removeItem(key);
158
+ window.sessionStorage.removeItem(`${key}_timestamp`);
159
+ } catch (removeError: any) {
160
+ // If removal fails due to quota, try to clear some data first
161
+ if (removeError.name === 'QuotaExceededError' ||
162
+ removeError.code === 22 ||
163
+ removeError.message?.includes('quota')) {
164
+ console.warn('sessionStorage quota exceeded during removal, clearing old data...');
165
+ clearOldData();
166
+
167
+ try {
168
+ window.sessionStorage.removeItem(key);
169
+ window.sessionStorage.removeItem(`${key}_timestamp`);
170
+ } catch (retryError) {
171
+ console.error(`Failed to remove sessionStorage key "${key}" after clearing:`, retryError);
172
+ // If still fails, force clear all
173
+ forceClearAll();
174
+ }
175
+ } else {
176
+ throw removeError;
177
+ }
178
+ }
179
+ }
180
+ } catch (error) {
181
+ console.error(`Error removing sessionStorage key "${key}":`, error);
182
+ }
183
+ };
184
+
185
+ return [storedValue, setValue, removeValue] as const;
186
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Auth Module
3
+ *
4
+ * Authentication system with context, hooks and UI components
5
+ */
6
+
7
+ export * from './context';
8
+ export * from './hooks';
9
+ export * from './utils';
10
+ export * from './middlewares';
@@ -0,0 +1 @@
1
+ export { middleware, config } from './proxy';
@@ -0,0 +1,24 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ export function middleware(request: NextRequest) {
4
+ const { pathname, search } = request.nextUrl;
5
+ const apiUrl = process.env.NEXT_PUBLIC_API_URL;
6
+
7
+ // Proxy /media/* - Images and static files
8
+ if (pathname.startsWith('/media/')) {
9
+ const targetUrl = `${apiUrl}${pathname}${search}`;
10
+ return NextResponse.rewrite(targetUrl, { request: { headers: request.headers } });
11
+ }
12
+
13
+ // Proxy /api/* - API endpoints
14
+ if (pathname.startsWith('/api/')) {
15
+ const targetUrl = `${apiUrl}${pathname}${search}`;
16
+ return NextResponse.rewrite(targetUrl, { request: { headers: request.headers } });
17
+ }
18
+
19
+ return NextResponse.next();
20
+ }
21
+
22
+ export const config = {
23
+ matcher: ['/media/:path*', '/api/:path*'],
24
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @djangocfg/auth/server - Server-side exports
3
+ * Contains middleware and other server-only functionality
4
+ */
5
+
6
+ export * from './middlewares';
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Format authentication error messages
3
+ */
4
+ export const formatAuthError = (error: any): string => {
5
+ if (typeof error === 'string') {
6
+ return error;
7
+ }
8
+
9
+ if (error?.message) {
10
+ return error.message;
11
+ }
12
+
13
+ if (error?.response?.data?.message) {
14
+ return error.response.data.message;
15
+ }
16
+
17
+ if (error?.response?.data?.detail) {
18
+ return error.response.data.detail;
19
+ }
20
+
21
+ return 'An unexpected error occurred';
22
+ };
23
+
24
+ /**
25
+ * Common error messages
26
+ */
27
+ export const AUTH_ERRORS = {
28
+ INVALID_EMAIL: 'Please enter a valid email address',
29
+ INVALID_OTP: 'Please enter a valid 6-digit verification code',
30
+ NETWORK_ERROR: 'Network error. Please check your connection',
31
+ SERVER_ERROR: 'Server error. Please try again later',
32
+ UNAUTHORIZED: 'Unauthorized. Please sign in again',
33
+ TOKEN_EXPIRED: 'Session expired. Please sign in again',
34
+ } as const;
@@ -0,0 +1,2 @@
1
+ export { validateEmail } from './validation';
2
+ export { formatAuthError } from './errors';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Email validation utility
3
+ */
4
+ export const validateEmail = (email: string): boolean => {
5
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
6
+ return emailRegex.test(email);
7
+ };
8
+
9
+ /**
10
+ * OTP validation utility
11
+ */
12
+ export const validateOTP = (otp: string): boolean => {
13
+ return /^\d{6}$/.test(otp);
14
+ };
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @djangocfg/layouts
3
+ *
4
+ * Reusable layout components and authentication system
5
+ */
6
+
7
+ // Auth system
8
+ export * from './auth';
9
+
10
+ // Layout components
11
+ export * from './layouts';
12
+
13
+ // Snippets - Reusable UI components
14
+ export * from './snippets';
15
+
@@ -0,0 +1,123 @@
1
+ /**
2
+ * AppLayout - Unified Application Layout System
3
+ *
4
+ * Single component that handles all layout logic:
5
+ * - Auto-detects route type (public/private/auth)
6
+ * - Applies correct layout automatically
7
+ * - Manages all state through context
8
+ * - Zero prop drilling
9
+ *
10
+ * Usage in _app.tsx:
11
+ * ```tsx
12
+ * <AppLayout config={appLayoutConfig}>
13
+ * <Component {...pageProps} />
14
+ * </AppLayout>
15
+ * ```
16
+ */
17
+
18
+ 'use client';
19
+
20
+ import React, { ReactNode, useState, useEffect } from 'react';
21
+ import { useRouter } from 'next/router';
22
+ import { AppContextProvider } from './context';
23
+ import { CoreProviders } from './providers';
24
+ import { Seo, PageProgress } from './components';
25
+ import { PublicLayout } from './layouts/PublicLayout';
26
+ import { PrivateLayout } from './layouts/PrivateLayout';
27
+ import { AuthLayout } from './layouts/AuthLayout';
28
+ import { determineLayoutMode, getRedirectUrl } from './utils';
29
+ import { useAuth } from '../../auth';
30
+ import type { AppLayoutConfig } from './types';
31
+
32
+ export interface AppLayoutProps {
33
+ children: ReactNode;
34
+ config: AppLayoutConfig;
35
+ }
36
+
37
+ /**
38
+ * Layout Router Component
39
+ *
40
+ * Determines which layout to use based on route
41
+ * Uses AppContext - no props passed down!
42
+ */
43
+ function LayoutRouter({ children }: { children: ReactNode }) {
44
+ const router = useRouter();
45
+ const { isAuthenticated, isLoading } = useAuth();
46
+ const [isMounted, setIsMounted] = useState(false);
47
+
48
+ // SSR/Hydration protection
49
+ useEffect(() => {
50
+ setIsMounted(true);
51
+ }, []);
52
+
53
+ // Get layout mode from context
54
+ const [layoutMode, setLayoutMode] = useState<'public' | 'private' | 'auth'>('public');
55
+
56
+ useEffect(() => {
57
+ // This will be properly determined by AppContext
58
+ // For now, simple detection
59
+ if (router.pathname.startsWith('/auth')) {
60
+ setLayoutMode('auth');
61
+ } else if (router.pathname.startsWith('/private')) {
62
+ setLayoutMode('private');
63
+ } else {
64
+ setLayoutMode('public');
65
+ }
66
+ }, [router.pathname]);
67
+
68
+ // Show loading during SSR or auth check
69
+ if (!isMounted || isLoading) {
70
+ return (
71
+ <div className="min-h-screen flex items-center justify-center">
72
+ <div className="text-muted-foreground">Loading...</div>
73
+ </div>
74
+ );
75
+ }
76
+
77
+ // Render appropriate layout
78
+ switch (layoutMode) {
79
+ case 'auth':
80
+ return <AuthLayout>{children}</AuthLayout>;
81
+
82
+ case 'private':
83
+ return <PrivateLayout>{children}</PrivateLayout>;
84
+
85
+ case 'public':
86
+ default:
87
+ return <PublicLayout>{children}</PublicLayout>;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * AppLayout - Main Component
93
+ *
94
+ * Single entry point for all layout logic
95
+ * Wrap your app once in _app.tsx
96
+ */
97
+ export function AppLayout({ children, config }: AppLayoutProps) {
98
+ return (
99
+ <CoreProviders config={config}>
100
+ <AppContextProvider config={config}>
101
+ {/* SEO Meta Tags */}
102
+ <Seo
103
+ pageConfig={{
104
+ title: config.app.name,
105
+ description: config.app.description,
106
+ ogImage: {
107
+ title: config.app.name,
108
+ subtitle: config.app.description,
109
+ },
110
+ }}
111
+ icons={config.app.icons}
112
+ siteUrl={config.app.siteUrl}
113
+ />
114
+
115
+ {/* Loading Progress Bar */}
116
+ <PageProgress />
117
+
118
+ {/* Smart Layout Router */}
119
+ <LayoutRouter>{children}</LayoutRouter>
120
+ </AppContextProvider>
121
+ </CoreProviders>
122
+ );
123
+ }
@@ -0,0 +1,204 @@
1
+ # AppLayout - Unified Application Layout System
2
+
3
+ Умный самодостаточный компонент для управления всеми layout'ами приложения.
4
+
5
+ ## 📁 Структура
6
+
7
+ ```
8
+ AppLayout/
9
+ ├── types/ # Все типы
10
+ │ ├── config.ts # AppLayoutConfig
11
+ │ ├── layout.ts # PublicLayoutConfig, PrivateLayoutConfig
12
+ │ ├── navigation.ts # NavigationItem, DashboardMenuItem
13
+ │ ├── routes.ts # RouteConfig, RouteDetectors
14
+ │ └── index.ts
15
+
16
+ ├── context/ # Unified App Context
17
+ │ ├── AppContext.tsx # Главный контекст приложения
18
+ │ └── index.ts
19
+
20
+ ├── hooks/ # Custom hooks
21
+ │ ├── useLayoutMode.ts # Определение текущего режима
22
+ │ ├── useNavigation.ts # Навигационные хуки
23
+ │ └── index.ts
24
+
25
+ ├── providers/ # Provider components
26
+ │ ├── ThemeProvider.tsx # Тема
27
+ │ ├── AuthProvider.tsx # Аутентификация
28
+ │ └── index.ts
29
+
30
+ ├── layouts/ # Layout renderers
31
+ │ ├── PublicLayout/ # Публичный layout
32
+ │ ├── PrivateLayout/ # Приватный layout (Dashboard)
33
+ │ ├── AuthLayout/ # Auth layout (минимальный)
34
+ │ └── index.ts
35
+
36
+ ├── components/ # UI components
37
+ │ ├── Seo.tsx # SEO meta tags
38
+ │ ├── PageProgress.tsx # Loading bar
39
+ │ └── index.ts
40
+
41
+ ├── utils/ # Utilities
42
+ │ ├── routeDetection.ts # Определение типа маршрута
43
+ │ └── index.ts
44
+
45
+ ├── AppLayout.tsx # Главный компонент
46
+ ├── index.ts # Public exports
47
+ └── README.md # Документация
48
+ ```
49
+
50
+ ## 🎯 Основная идея
51
+
52
+ **Единая точка входа** - один компонент `<AppLayout>` управляет всем:
53
+ - Автоматически определяет тип страницы (public/private/auth)
54
+ - Применяет нужный layout
55
+ - Управляет состоянием через единый контекст
56
+ - Предоставляет хуки для доступа к функционалу
57
+
58
+ ## 🚀 Использование
59
+
60
+ ### В _app.tsx (единственное место подключения)
61
+
62
+ ```tsx
63
+ import { AppLayout } from '@djangocfg/layouts';
64
+ import { appLayoutConfig } from '@/core/appLayoutConfig';
65
+
66
+ export default function App({ Component, pageProps }: AppProps) {
67
+ return (
68
+ <AppLayout config={appLayoutConfig}>
69
+ <Component {...pageProps} />
70
+ </AppLayout>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ### Конфигурация
76
+
77
+ ```tsx
78
+ // core/appLayoutConfig.ts
79
+ import type { AppLayoutConfig } from '@djangocfg/layouts';
80
+
81
+ export const appLayoutConfig: AppLayoutConfig = {
82
+ app: {
83
+ name: 'My App',
84
+ logoPath: '/logo.svg',
85
+ },
86
+ api: {
87
+ baseUrl: process.env.NEXT_PUBLIC_API_URL,
88
+ },
89
+ routes: {
90
+ auth: '/auth',
91
+ defaultCallback: '/dashboard',
92
+ detectors: {
93
+ isPublicRoute: (path) => !path.startsWith('/private'),
94
+ isPrivateRoute: (path) => path.startsWith('/private'),
95
+ isAuthRoute: (path) => path.startsWith('/auth'),
96
+ getUnauthenticatedRedirect: (path) =>
97
+ path.startsWith('/private') ? '/auth' : null,
98
+ getPageTitle: (path) => 'My App',
99
+ },
100
+ },
101
+ publicLayout: {
102
+ navigation: { /* ... */ },
103
+ userMenu: { /* ... */ },
104
+ footer: { /* ... */ },
105
+ },
106
+ privateLayout: {
107
+ menuGroups: [ /* ... */ ],
108
+ showChat: true,
109
+ },
110
+ };
111
+ ```
112
+
113
+ ## 🎨 Использование в компонентах
114
+
115
+ ### Доступ к контексту
116
+
117
+ ```tsx
118
+ import { useAppContext } from '@djangocfg/layouts';
119
+
120
+ function MyComponent() {
121
+ const {
122
+ config,
123
+ layoutMode,
124
+ mobileMenuOpen,
125
+ toggleMobileMenu
126
+ } = useAppContext();
127
+
128
+ return (
129
+ <button onClick={toggleMobileMenu}>
130
+ {layoutMode === 'private' ? 'Dashboard' : 'Home'}
131
+ </button>
132
+ );
133
+ }
134
+ ```
135
+
136
+ ### Использование хуков
137
+
138
+ ```tsx
139
+ import { useLayoutMode, useNavigation } from '@djangocfg/layouts';
140
+
141
+ function MyComponent() {
142
+ const mode = useLayoutMode(); // 'public' | 'private' | 'auth'
143
+ const { isActive } = useNavigation();
144
+
145
+ if (mode === 'private') {
146
+ return <DashboardView />;
147
+ }
148
+
149
+ return <PublicView />;
150
+ }
151
+ ```
152
+
153
+ ## 🏗️ Архитектурные принципы
154
+
155
+ ### 1. Single Source of Truth
156
+ Вся конфигурация в одном месте - `AppLayoutConfig`
157
+
158
+ ### 2. Context над Props
159
+ Никакого prop drilling - всё через `useAppContext()`
160
+
161
+ ### 3. Декомпозиция
162
+ Каждая папка отвечает за свою область:
163
+ - `types/` - только типы
164
+ - `context/` - состояние и контекст
165
+ - `hooks/` - переиспользуемая логика
166
+ - `layouts/` - рендеринг layout'ов
167
+ - `components/` - UI компоненты
168
+
169
+ ### 4. Автоматизация
170
+ Layout определяется автоматически на основе маршрута
171
+
172
+ ### 5. Расширяемость
173
+ Легко добавить новый layout или функционал
174
+
175
+ ## 📦 Экспорты
176
+
177
+ ```tsx
178
+ // Главный компонент
179
+ export { AppLayout } from './AppLayout';
180
+
181
+ // Контекст и хуки
182
+ export { useAppContext } from './context';
183
+ export { useLayoutMode, useNavigation } from './hooks';
184
+
185
+ // Типы
186
+ export type {
187
+ AppLayoutConfig,
188
+ PublicLayoutConfig,
189
+ PrivateLayoutConfig,
190
+ RouteConfig,
191
+ NavigationItem,
192
+ DashboardMenuItem,
193
+ } from './types';
194
+ ```
195
+
196
+ ## 🎯 Преимущества
197
+
198
+ ✅ **Одно место подключения** - только в `_app.tsx`
199
+ ✅ **Нет prop drilling** - всё через контекст
200
+ ✅ **Автоматический роутинг** - layout определяется сам
201
+ ✅ **Типобезопасность** - TypeScript везде
202
+ ✅ **Легкая настройка** - один конфиг объект
203
+ ✅ **Декомпозиция** - чистая структура папок
204
+ ✅ **Переиспользование** - хуки для всего