@djangocfg/layouts 1.4.30 → 2.0.2

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
@@ -0,0 +1,160 @@
1
+ /**
2
+ * User Menu Component for Layouts
3
+ *
4
+ * Simplified user menu component that works without AppContext
5
+ * Uses only useAuth() hook
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import React from 'react';
11
+ import Link from 'next/link';
12
+ import { User, LogOut, Settings } from 'lucide-react';
13
+ import {
14
+ DropdownMenu,
15
+ DropdownMenuContent,
16
+ DropdownMenuGroup,
17
+ DropdownMenuItem,
18
+ DropdownMenuLabel,
19
+ DropdownMenuSeparator,
20
+ DropdownMenuTrigger,
21
+ Avatar,
22
+ AvatarFallback,
23
+ AvatarImage,
24
+ Button,
25
+ } from '@djangocfg/ui/components';
26
+ import { useAuth } from '../../auth';
27
+
28
+ export interface UserMenuProps {
29
+ variant?: 'desktop' | 'mobile';
30
+ profilePath?: string;
31
+ dashboardPath?: string;
32
+ authPath?: string;
33
+ }
34
+
35
+ export function UserMenu({
36
+ variant = 'desktop',
37
+ profilePath = '/profile',
38
+ dashboardPath = '/dashboard',
39
+ authPath = '/auth',
40
+ }: UserMenuProps) {
41
+ const { user, isAuthenticated, logout } = useAuth();
42
+ const [mounted, setMounted] = React.useState(false);
43
+
44
+ React.useEffect(() => {
45
+ setMounted(true);
46
+ }, []);
47
+
48
+ if (!mounted) {
49
+ return null;
50
+ }
51
+
52
+ if (!isAuthenticated || !user) {
53
+ // Guest user - show sign in button
54
+ if (variant === 'mobile') {
55
+ return (
56
+ <Link href={authPath}>
57
+ <Button variant="default" className="w-full">
58
+ Sign In
59
+ </Button>
60
+ </Link>
61
+ );
62
+ }
63
+ return (
64
+ <Link href={authPath}>
65
+ <Button variant="default" size="sm">
66
+ Sign In
67
+ </Button>
68
+ </Link>
69
+ );
70
+ }
71
+
72
+ // Authenticated user
73
+ const displayName = user.display_username || user.email || 'User';
74
+ const userInitial = displayName.charAt(0).toUpperCase();
75
+ const userAvatar = user.avatar || '';
76
+
77
+ if (variant === 'mobile') {
78
+ return (
79
+ <div className="space-y-3">
80
+ <div className="flex items-center gap-3 px-4 py-3">
81
+ <Avatar className="h-10 w-10">
82
+ <AvatarImage src={userAvatar} alt={displayName} />
83
+ <AvatarFallback>{userInitial}</AvatarFallback>
84
+ </Avatar>
85
+ <div className="flex-1 min-w-0">
86
+ <p className="text-sm font-medium text-foreground truncate">
87
+ {displayName}
88
+ </p>
89
+ <p className="text-xs text-muted-foreground truncate">
90
+ {user.email}
91
+ </p>
92
+ </div>
93
+ </div>
94
+ <div className="space-y-1">
95
+ {profilePath && (
96
+ <Link
97
+ href={profilePath}
98
+ className="flex items-center gap-3 px-4 py-3 text-sm font-medium text-foreground hover:bg-accent hover:text-accent-foreground rounded-sm transition-colors"
99
+ >
100
+ <Settings className="h-4 w-4" />
101
+ Profile
102
+ </Link>
103
+ )}
104
+ <button
105
+ onClick={() => logout()}
106
+ className="flex items-center gap-3 px-4 py-3 text-sm font-medium text-destructive hover:bg-destructive/10 rounded-sm transition-colors w-full text-left"
107
+ >
108
+ <LogOut className="h-4 w-4" />
109
+ Sign Out
110
+ </button>
111
+ </div>
112
+ </div>
113
+ );
114
+ }
115
+
116
+ // Desktop variant
117
+ return (
118
+ <DropdownMenu>
119
+ <DropdownMenuTrigger asChild>
120
+ <Button variant="ghost" size="icon" className="rounded-full">
121
+ <Avatar className="h-8 w-8">
122
+ <AvatarImage src={userAvatar} alt={displayName} />
123
+ <AvatarFallback>{userInitial}</AvatarFallback>
124
+ </Avatar>
125
+ <span className="sr-only">User menu</span>
126
+ </Button>
127
+ </DropdownMenuTrigger>
128
+ <DropdownMenuContent align="end" className="w-56">
129
+ <DropdownMenuLabel>
130
+ <div className="flex flex-col space-y-1">
131
+ <p className="text-sm font-medium leading-none">{displayName}</p>
132
+ <p className="text-xs leading-none text-muted-foreground">
133
+ {user.email}
134
+ </p>
135
+ </div>
136
+ </DropdownMenuLabel>
137
+ <DropdownMenuSeparator />
138
+ <DropdownMenuGroup>
139
+ {profilePath && (
140
+ <DropdownMenuItem asChild>
141
+ <Link href={profilePath} className="flex items-center">
142
+ <Settings className="mr-2 h-4 w-4" />
143
+ <span>Profile</span>
144
+ </Link>
145
+ </DropdownMenuItem>
146
+ )}
147
+ </DropdownMenuGroup>
148
+ <DropdownMenuSeparator />
149
+ <DropdownMenuItem
150
+ onClick={() => logout()}
151
+ className="text-destructive focus:text-destructive"
152
+ >
153
+ <LogOut className="mr-2 h-4 w-4" />
154
+ <span>Sign Out</span>
155
+ </DropdownMenuItem>
156
+ </DropdownMenuContent>
157
+ </DropdownMenu>
158
+ );
159
+ }
160
+
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Shared Layout Components
3
+ */
4
+
5
+ export { UserMenu } from './UserMenu';
6
+ export type { UserMenuProps } from './UserMenu';
7
+
@@ -1,14 +1,21 @@
1
- // ============================================================================
2
- // Layouts - Page Layout Components
3
- // ============================================================================
1
+ /**
2
+ * Layouts exports
3
+ *
4
+ * Simple, straightforward layout components
5
+ * Import and use directly with props - no complex configs needed!
6
+ */
4
7
 
8
+ // Smart layout router
9
+ export * from './AppLayout';
10
+
11
+ // Basic layouts
12
+ export * from './PublicLayout';
13
+ export * from './PrivateLayout';
14
+ export * from './AuthLayout';
15
+ export * from './AdminLayout';
5
16
 
17
+ // Additional layouts
6
18
  export * from './ProfileLayout';
7
19
  export * from './SupportLayout';
8
20
  export * from './PaymentsLayout';
9
- export * from './AppLayout';
10
- export * from './ErrorLayout';
11
- export * from './SimpleLayout';
12
21
 
13
- // Note: CfgLayout is now part of AppLayout exports
14
- // Import it via: import { CfgLayout, useCfgApp } from '@djangocfg/layouts';
@@ -2,7 +2,7 @@
2
2
  * AnalyticsProvider Component
3
3
  *
4
4
  * Initializes Google Analytics and auto-tracks pageviews.
5
- * Must be placed inside AppContextProvider and AuthProvider.
5
+ * Can work standalone with trackingId prop, or use AppContext if available.
6
6
  */
7
7
 
8
8
  'use client';
@@ -12,17 +12,21 @@ import { useAnalytics } from './useAnalytics';
12
12
 
13
13
  interface AnalyticsProviderProps {
14
14
  children: ReactNode;
15
+ /** Google Analytics tracking ID (optional if using AppContext) */
16
+ trackingId?: string;
15
17
  }
16
18
 
17
19
  /**
18
20
  * Analytics Provider that initializes tracking
19
21
  * Automatically:
20
- * - Initializes GA4 with tracking ID from config
22
+ * - Initializes GA4 with tracking ID from prop or config
21
23
  * - Sets user ID when authenticated
22
24
  * - Tracks page views on route changes
23
25
  */
24
- export function AnalyticsProvider({ children }: AnalyticsProviderProps) {
25
- useAnalytics(); // Initialize and auto-track
26
+ export function AnalyticsProvider({ children, trackingId }: AnalyticsProviderProps) {
27
+ // If trackingId is provided as prop, use it directly
28
+ // Otherwise, useAnalytics will try to get it from AppContext
29
+ useAnalytics(trackingId);
26
30
  return <>{children}</>;
27
31
  }
28
32
 
@@ -9,9 +9,8 @@
9
9
  'use client';
10
10
 
11
11
  import { useEffect } from 'react';
12
- import { useRouter } from 'next/router';
12
+ import { usePathname } from 'next/navigation';
13
13
  import ReactGA from 'react-ga4';
14
- import { useAppContext } from '../../layouts/AppLayout/context';
15
14
  import { useAuth } from '../../auth';
16
15
 
17
16
  // Check if we're in production
@@ -99,12 +98,12 @@ export const Analytics = {
99
98
  * event('button_click', { category: 'engagement', label: 'signup' });
100
99
  * ```
101
100
  */
102
- export function useAnalytics() {
103
- const router = useRouter();
104
- const { config } = useAppContext();
101
+ export function useAnalytics(trackingIdProp?: string) {
102
+ const pathname = usePathname();
105
103
  const { user, isAuthenticated } = useAuth();
106
104
 
107
- const trackingId = config.analytics?.googleTrackingId;
105
+ // Use trackingId from prop (passed from AppLayout)
106
+ const trackingId = trackingIdProp;
108
107
  const isEnabled = isProduction && Boolean(trackingId);
109
108
 
110
109
  // Initialize GA4
@@ -119,23 +118,14 @@ export function useAnalytics() {
119
118
  Analytics.setUser(String(user.id));
120
119
  }, [isEnabled, isAuthenticated, user?.id]);
121
120
 
122
- // Auto-track page views on route change
121
+ // Auto-track page views on route change (App Router)
123
122
  useEffect(() => {
124
- if (!isEnabled) return;
123
+ if (!isEnabled || typeof window === 'undefined') return;
125
124
 
126
- // Track initial page view
127
- Analytics.pageview(window.location.pathname + window.location.search);
128
-
129
- // Track on route change
130
- const handleRouteChange = (url: string) => {
131
- Analytics.pageview(url);
132
- };
133
-
134
- router.events.on('routeChangeComplete', handleRouteChange);
135
- return () => {
136
- router.events.off('routeChangeComplete', handleRouteChange);
137
- };
138
- }, [router.events, isEnabled]);
125
+ // Track page view when pathname changes
126
+ const url = pathname + (window.location.search || '');
127
+ Analytics.pageview(url);
128
+ }, [pathname, isEnabled]);
139
129
 
140
130
  return {
141
131
  isEnabled,
@@ -7,7 +7,7 @@
7
7
 
8
8
  import React, { useCallback, useEffect, useState } from 'react';
9
9
  import { createPortal } from 'react-dom';
10
- import {useRouter} from 'next/router';
10
+ import { usePathname } from 'next/navigation';
11
11
  import { Card, CardContent, CardHeader, Button, useIsMobile, useLocalStorage } from '@djangocfg/ui';
12
12
  import {
13
13
  MessageSquare,
@@ -49,7 +49,7 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({
49
49
  toggleTimestamps,
50
50
  } = useChatUI();
51
51
 
52
- const router = useRouter();
52
+ const pathname = usePathname();
53
53
  const isMobile = useIsMobile();
54
54
 
55
55
  const [isLoading, setIsLoading] = useState(false);
@@ -66,8 +66,8 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({
66
66
  []
67
67
  );
68
68
 
69
- const isSupport = router.pathname.startsWith('/private/support');
70
- const isContact = router.pathname.startsWith('/private/contact');
69
+ const isSupport = pathname.startsWith('/private/support');
70
+ const isContact = pathname.startsWith('/private/contact');
71
71
 
72
72
  // Mount portal target
73
73
  useEffect(() => {
@@ -50,31 +50,44 @@ export function ContactFormProvider({ children, apiUrl }: ContactFormProviderPro
50
50
  const [error, setError] = useState<Error | null>(null);
51
51
  const [lastResponse, setLastResponse] = useState<LeadSubmissionResult | null>(null);
52
52
 
53
- // Configure API on mount
54
- useEffect(() => {
55
- configureAPI({ baseUrl: apiUrl });
56
- }, [apiUrl]);
53
+ // Always use local API route to avoid CORS issues
54
+ // Pass apiUrl in request body so route.ts knows where to proxy
57
55
 
58
- // Submit function using generated fetcher
56
+ // Submit function - always uses local API route
59
57
  const submit = useCallback(async (data: LeadSubmissionData): Promise<LeadSubmissionResult> => {
60
58
  setIsSubmitting(true);
61
59
  setError(null);
62
60
 
63
61
  try {
64
- // Use the generated fetcher which handles Zod validation
65
- const response = await createLeadsSubmitCreate({
66
- name: data.name,
67
- email: data.email,
68
- message: data.message,
69
- company: data.company ?? undefined,
70
- company_site: data.company_site ?? undefined,
71
- contact_type: data.contact_type ? CONTACT_TYPE_MAP[data.contact_type] : undefined,
72
- contact_value: data.contact_value ?? undefined,
73
- subject: data.subject ?? undefined,
74
- extra: data.extra ?? undefined,
75
- site_url: data.site_url,
62
+ // Always use local Next.js API route to avoid CORS issues
63
+ // Pass apiUrl from ContactPage in the request body so route.ts knows where to proxy
64
+ const res = await fetch('/api/contact', {
65
+ method: 'POST',
66
+ headers: {
67
+ 'Content-Type': 'application/json',
68
+ },
69
+ body: JSON.stringify({
70
+ _apiUrl: apiUrl || '', // Pass apiUrl to route.ts for proxying (can be empty for fallback)
71
+ name: data.name,
72
+ email: data.email,
73
+ message: data.message,
74
+ company: data.company,
75
+ company_site: data.company_site,
76
+ contact_type: data.contact_type,
77
+ contact_value: data.contact_value,
78
+ subject: data.subject,
79
+ extra: data.extra,
80
+ site_url: data.site_url || (typeof window !== 'undefined' ? window.location.origin : undefined),
81
+ }),
76
82
  });
77
83
 
84
+ if (!res.ok) {
85
+ const errorData = await res.json().catch(() => ({ message: 'Failed to submit contact form' }));
86
+ throw new Error(errorData.message || `HTTP ${res.status}`);
87
+ }
88
+
89
+ const response: LeadSubmissionResult = await res.json();
90
+
78
91
  const result: LeadSubmissionResult = {
79
92
  success: response.success,
80
93
  message: response.message,
@@ -90,7 +103,7 @@ export function ContactFormProvider({ children, apiUrl }: ContactFormProviderPro
90
103
  } finally {
91
104
  setIsSubmitting(false);
92
105
  }
93
- }, []);
106
+ }, [apiUrl]);
94
107
 
95
108
  // Reset error state
96
109
  const resetError = useCallback(() => {
@@ -98,7 +111,7 @@ export function ContactFormProvider({ children, apiUrl }: ContactFormProviderPro
98
111
  }, []);
99
112
 
100
113
  const value: ContactFormContextValue = {
101
- apiUrl,
114
+ apiUrl: '/api/contact', // Always use local endpoint
102
115
  submit,
103
116
  isSubmitting,
104
117
  error,
@@ -10,10 +10,8 @@ import type { ContactDetail, LeadSubmissionResult } from './types';
10
10
  // Config
11
11
  // ============================================================================
12
12
 
13
- const isDev = process.env.NODE_ENV === 'development';
14
-
15
13
  const DEFAULT_CONFIG = {
16
- apiUrl: isDev ? 'http://localhost:8000' : 'https://api.reforms.ai',
14
+ apiUrl: 'https://api.reforms.ai',
17
15
  email: 'markolofsen@gmail.com',
18
16
  whatsapp: '+62 813 39646301',
19
17
  calendly: 'https://calendly.com/markolofsen/meeting',
@@ -24,7 +22,7 @@ const DEFAULT_CONFIG = {
24
22
  // ============================================================================
25
23
 
26
24
  export interface ContactPageProps {
27
- /** Override API URL */
25
+ /** Override API URL (defaults to DEFAULT_CONFIG.apiUrl) */
28
26
  apiUrl?: string;
29
27
  /** Override email */
30
28
  email?: string;
@@ -19,9 +19,10 @@ export interface ContactFormProviderProps {
19
19
  children: ReactNode;
20
20
  /**
21
21
  * API base URL for lead submission
22
- * @example "https://api.example.com"
22
+ * If not provided or empty, will use local Next.js API route (/api/contact)
23
+ * @example "https://api.example.com" or undefined to use /api/contact
23
24
  */
24
- apiUrl: string;
25
+ apiUrl?: string;
25
26
  }
26
27
 
27
28
  // ============================================================================
@@ -7,6 +7,5 @@ export type { ChatWidgetProps, ChatUIState } from './Chat';
7
7
 
8
8
  export * from './Breadcrumbs';
9
9
  export * from './AuthDialog';
10
- export * from './VideoPlayer';
11
10
  export * from './ContactForm';
12
11
  export * from './Analytics';
@@ -1,204 +0,0 @@
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
- ✅ **Переиспользование** - хуки для всего