@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,87 @@
1
+ import Head from 'next/head';
2
+ import { generateOgImageUrl } from '@djangocfg/og-image/utils';
3
+
4
+ import { PageConfig } from '../../../types/pageConfig';
5
+
6
+ interface SeoProps {
7
+ pageConfig: PageConfig;
8
+ icons?: {
9
+ logo192?: string;
10
+ logo384?: string;
11
+ logo512?: string;
12
+ logoVector?: string;
13
+ };
14
+ siteUrl?: string;
15
+ }
16
+
17
+ export default function Seo({ pageConfig, icons, siteUrl }: SeoProps) {
18
+ const {
19
+ title,
20
+ description,
21
+ keywords,
22
+ jsonLd,
23
+ ogImage,
24
+ } = pageConfig;
25
+
26
+ const ogTitle = ogImage?.title || title;
27
+ const ogSubtitle = ogImage?.subtitle || description;
28
+
29
+ // Generate OG image URL using @djangocfg/og-image utilities
30
+ const ogImageUrl = ogImage
31
+ ? generateOgImageUrl('/api/og', {
32
+ title: ogTitle || 'Untitled',
33
+ subtitle: ogSubtitle || '',
34
+ description: ogSubtitle || '',
35
+ })
36
+ : null;
37
+
38
+ // Make absolute URL if siteUrl provided
39
+ const absoluteOgImageUrl = ogImageUrl && siteUrl ? `${siteUrl}${ogImageUrl}` : ogImageUrl;
40
+
41
+ return (
42
+ <Head>
43
+ <title>{title}</title>
44
+ <meta name="description" content={description} />
45
+ {keywords && <meta name="keywords" content={keywords} />}
46
+
47
+ {/* Favicon */}
48
+ <link rel="icon" type="image/png" href={icons?.logo192 || '/favicon.png'} />
49
+
50
+ {/* Open Graph */}
51
+ <meta property="og:title" content={ogTitle} />
52
+ <meta property="og:description" content={ogSubtitle} />
53
+ <meta property="og:type" content="website" />
54
+
55
+ {/* Site Name */}
56
+ {pageConfig.projectName && (
57
+ <meta property="og:site_name" content={pageConfig.projectName} />
58
+ )}
59
+
60
+ {/* Twitter */}
61
+ <meta name="twitter:card" content="summary_large_image" />
62
+ <meta name="twitter:title" content={ogTitle} />
63
+ <meta name="twitter:description" content={ogSubtitle} />
64
+
65
+ {/* OG Image */}
66
+ {absoluteOgImageUrl && (
67
+ <>
68
+ <meta property="og:image" content={absoluteOgImageUrl} />
69
+ <meta property="og:image:width" content="1200" />
70
+ <meta property="og:image:height" content="630" />
71
+ <meta property="og:image:type" content="image/png" />
72
+ <meta name="twitter:image" content={absoluteOgImageUrl} />
73
+ </>
74
+ )}
75
+
76
+ {/* JSON-LD */}
77
+ {jsonLd && (
78
+ <script
79
+ type="application/ld+json"
80
+ dangerouslySetInnerHTML={{
81
+ __html: JSON.stringify(jsonLd),
82
+ }}
83
+ />
84
+ )}
85
+ </Head>
86
+ );
87
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Components Module
3
+ */
4
+
5
+ export { default as Seo } from './Seo';
6
+ export { default as PageProgress } from './PageProgress';
@@ -0,0 +1,146 @@
1
+ /**
2
+ * App Context
3
+ *
4
+ * Unified context for entire application layout system
5
+ * Provides centralized access to configuration, state, and utilities
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import React, { createContext, useContext, useState, ReactNode, useMemo } from 'react';
11
+ import { useRouter } from 'next/router';
12
+ import type { AppLayoutConfig, LayoutMode, RouteDetectors } from '../types';
13
+
14
+ // ═══════════════════════════════════════════════════════════════════════════
15
+ // Context Types
16
+ // ═══════════════════════════════════════════════════════════════════════════
17
+
18
+ interface AppContextValue {
19
+ // Configuration
20
+ config: AppLayoutConfig;
21
+
22
+ // Route detection
23
+ routes: RouteDetectors;
24
+ currentPath: string;
25
+ layoutMode: LayoutMode;
26
+
27
+ // Mobile menu state
28
+ mobileMenuOpen: boolean;
29
+ openMobileMenu: () => void;
30
+ closeMobileMenu: () => void;
31
+ toggleMobileMenu: () => void;
32
+
33
+ // User menu state (desktop dropdown)
34
+ userMenuOpen: boolean;
35
+ openUserMenu: () => void;
36
+ closeUserMenu: () => void;
37
+ toggleUserMenu: () => void;
38
+
39
+ // Sidebar state (dashboard)
40
+ sidebarCollapsed: boolean;
41
+ collapseSidebar: () => void;
42
+ expandSidebar: () => void;
43
+ toggleSidebar: () => void;
44
+ }
45
+
46
+ // ═══════════════════════════════════════════════════════════════════════════
47
+ // Context Creation
48
+ // ═══════════════════════════════════════════════════════════════════════════
49
+
50
+ const AppContext = createContext<AppContextValue | null>(null);
51
+
52
+ // ═══════════════════════════════════════════════════════════════════════════
53
+ // Provider Component
54
+ // ═══════════════════════════════════════════════════════════════════════════
55
+
56
+ export interface AppContextProviderProps {
57
+ children: ReactNode;
58
+ config: AppLayoutConfig;
59
+ }
60
+
61
+ /**
62
+ * AppContext Provider
63
+ *
64
+ * Provides unified application context to all child components
65
+ * Manages layout state and exposes configuration
66
+ */
67
+ export function AppContextProvider({ children, config }: AppContextProviderProps) {
68
+ const router = useRouter();
69
+
70
+ // UI state
71
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
72
+ const [userMenuOpen, setUserMenuOpen] = useState(false);
73
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
74
+
75
+ // Determine current layout mode
76
+ const layoutMode = useMemo((): LayoutMode => {
77
+ const { isAuthRoute, isPrivateRoute } = config.routes.detectors;
78
+
79
+ if (isAuthRoute(router.pathname)) return 'auth';
80
+ if (isPrivateRoute(router.pathname)) return 'private';
81
+ return 'public';
82
+ }, [router.pathname, config.routes.detectors]);
83
+
84
+ // Mobile menu handlers
85
+ const openMobileMenu = () => setMobileMenuOpen(true);
86
+ const closeMobileMenu = () => setMobileMenuOpen(false);
87
+ const toggleMobileMenu = () => setMobileMenuOpen(prev => !prev);
88
+
89
+ // User menu handlers
90
+ const openUserMenu = () => setUserMenuOpen(true);
91
+ const closeUserMenu = () => setUserMenuOpen(false);
92
+ const toggleUserMenu = () => setUserMenuOpen(prev => !prev);
93
+
94
+ // Sidebar handlers
95
+ const collapseSidebar = () => setSidebarCollapsed(true);
96
+ const expandSidebar = () => setSidebarCollapsed(false);
97
+ const toggleSidebar = () => setSidebarCollapsed(prev => !prev);
98
+
99
+ const value: AppContextValue = {
100
+ config,
101
+ routes: config.routes.detectors,
102
+ currentPath: router.pathname,
103
+ layoutMode,
104
+ mobileMenuOpen,
105
+ openMobileMenu,
106
+ closeMobileMenu,
107
+ toggleMobileMenu,
108
+ userMenuOpen,
109
+ openUserMenu,
110
+ closeUserMenu,
111
+ toggleUserMenu,
112
+ sidebarCollapsed,
113
+ collapseSidebar,
114
+ expandSidebar,
115
+ toggleSidebar,
116
+ };
117
+
118
+ return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
119
+ }
120
+
121
+ // ═══════════════════════════════════════════════════════════════════════════
122
+ // Hook
123
+ // ═══════════════════════════════════════════════════════════════════════════
124
+
125
+ /**
126
+ * Hook to access AppContext
127
+ *
128
+ * @throws {Error} If used outside AppContextProvider
129
+ *
130
+ * @example
131
+ * ```tsx
132
+ * const { config, layoutMode, toggleMobileMenu } = useAppContext();
133
+ * ```
134
+ */
135
+ export function useAppContext(): AppContextValue {
136
+ const context = useContext(AppContext);
137
+
138
+ if (!context) {
139
+ throw new Error(
140
+ 'useAppContext must be used within AppContextProvider. ' +
141
+ 'Make sure your component is wrapped with <AppLayout>.'
142
+ );
143
+ }
144
+
145
+ return context;
146
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Context Module
3
+ */
4
+
5
+ export * from './AppContext';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Hooks Module
3
+ */
4
+
5
+ export * from './useLayoutMode';
6
+ export * from './useNavigation';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * useLayoutMode Hook
3
+ *
4
+ * Returns current layout mode
5
+ */
6
+
7
+ import { useAppContext } from '../context';
8
+ import type { LayoutMode } from '../types';
9
+
10
+ /**
11
+ * Get current layout mode
12
+ *
13
+ * @returns Current layout mode ('public' | 'private' | 'auth')
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * const mode = useLayoutMode();
18
+ * if (mode === 'private') {
19
+ * return <DashboardContent />;
20
+ * }
21
+ * ```
22
+ */
23
+ export function useLayoutMode(): LayoutMode {
24
+ const { layoutMode } = useAppContext();
25
+ return layoutMode;
26
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * useNavigation Hook
3
+ *
4
+ * Navigation utilities
5
+ */
6
+
7
+ import { useRouter } from 'next/router';
8
+ import { useAppContext } from '../context';
9
+
10
+ export interface UseNavigationReturn {
11
+ /** Current pathname */
12
+ currentPath: string;
13
+
14
+ /** Check if path is active */
15
+ isActive: (path: string) => boolean;
16
+
17
+ /** Get page title for current route */
18
+ getPageTitle: () => string;
19
+ }
20
+
21
+ /**
22
+ * Navigation utilities hook
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * const { isActive, getPageTitle } = useNavigation();
27
+ * const title = getPageTitle();
28
+ * ```
29
+ */
30
+ export function useNavigation(): UseNavigationReturn {
31
+ const router = useRouter();
32
+ const { routes, currentPath } = useAppContext();
33
+
34
+ const isActive = (path: string): boolean => {
35
+ if (currentPath === path) return true;
36
+ if (path !== '/' && currentPath.startsWith(path)) return true;
37
+ return false;
38
+ };
39
+
40
+ const getPageTitle = (): string => {
41
+ return routes.getPageTitle(currentPath);
42
+ };
43
+
44
+ return {
45
+ currentPath,
46
+ isActive,
47
+ getPageTitle,
48
+ };
49
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * AppLayout - Unified Application Layout System
3
+ *
4
+ * Single self-sufficient component for all layout needs
5
+ */
6
+
7
+ // Main component
8
+ export { AppLayout } from './AppLayout';
9
+
10
+ // Types
11
+ export type {
12
+ AppLayoutConfig,
13
+ PublicLayoutConfig,
14
+ PrivateLayoutConfig,
15
+ RouteConfig,
16
+ RouteDetectors,
17
+ LayoutMode,
18
+ NavigationItem,
19
+ NavigationSection,
20
+ DashboardMenuItem,
21
+ DashboardMenuGroup,
22
+ } from './types';
23
+
24
+ // Context and hooks
25
+ export { useAppContext, AppContextProvider } from './context';
26
+ export { useLayoutMode, useNavigation } from './hooks';
27
+
28
+ // Layouts (for custom usage if needed)
29
+ export { PublicLayout } from './layouts/PublicLayout';
30
+ export { PrivateLayout } from './layouts/PrivateLayout';
31
+ export { AuthLayout } from './layouts/AuthLayout';
@@ -0,0 +1,51 @@
1
+ import React, { createContext, useContext } from 'react';
2
+
3
+ import { useAuthForm } from '../../../../auth/hooks';
4
+
5
+ import type { AuthContextType, AuthProps } from './types';
6
+
7
+ const AuthContext = createContext<AuthContextType | undefined>(undefined);
8
+
9
+ export const AuthProvider: React.FC<AuthProps> = ({
10
+ children,
11
+ sourceUrl: sourceUrlProp,
12
+ supportUrl,
13
+ termsUrl,
14
+ privacyUrl,
15
+ enablePhoneAuth = true, // Default to true for backward compatibility
16
+ onIdentifierSuccess,
17
+ onOTPSuccess,
18
+ onError,
19
+ }) => {
20
+ const sourceUrl = sourceUrlProp || (typeof window !== 'undefined' ? window.location.origin : '');
21
+
22
+ // Use the auth form hook with required sourceUrl
23
+ const authForm = useAuthForm({
24
+ onIdentifierSuccess,
25
+ onOTPSuccess,
26
+ onError,
27
+ sourceUrl,
28
+ });
29
+
30
+ const value: AuthContextType = {
31
+ // Form state from auth form hook
32
+ ...authForm,
33
+
34
+ // UI-specific configuration
35
+ sourceUrl,
36
+ supportUrl,
37
+ termsUrl,
38
+ privacyUrl,
39
+ enablePhoneAuth,
40
+ };
41
+
42
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
43
+ };
44
+
45
+ export const useAuthContext = () => {
46
+ const context = useContext(AuthContext);
47
+ if (context === undefined) {
48
+ throw new Error('useAuthContext must be used within an AuthProvider');
49
+ }
50
+ return context;
51
+ };
@@ -0,0 +1,111 @@
1
+ import { Mail, MessageCircle } from 'lucide-react';
2
+ import React from 'react';
3
+
4
+ import { Button } from '@djangocfg/ui/components';
5
+
6
+ import { useAuthContext } from './AuthContext';
7
+
8
+ import type { AuthHelpProps } from './types';
9
+
10
+ export const AuthHelp: React.FC<AuthHelpProps> = ({
11
+ className = '',
12
+ variant = 'default',
13
+ }) => {
14
+ const { supportUrl, channel } = useAuthContext();
15
+
16
+ const getChannelIcon = () => {
17
+ return channel === 'phone' ? (
18
+ <MessageCircle className="w-4 h-4 text-muted-foreground" />
19
+ ) : (
20
+ <Mail className="w-4 h-4 text-muted-foreground" />
21
+ );
22
+ };
23
+
24
+ const getHelpText = () => {
25
+ return channel === 'phone' ? 'Check WhatsApp/SMS' : 'Check spam folder';
26
+ };
27
+
28
+ const getDetailedHelp = () => {
29
+ if (channel === 'phone') {
30
+ return {
31
+ title: "Didn't receive the code?",
32
+ tips: [
33
+ '• Check your WhatsApp messages',
34
+ '• Look for SMS messages',
35
+ '• Ensure you have signal/internet',
36
+ '• Wait a few minutes for delivery',
37
+ ],
38
+ };
39
+ } else {
40
+ return {
41
+ title: "Didn't receive the email?",
42
+ tips: [
43
+ '• Check your spam or junk folder',
44
+ '• Make sure you entered the correct email address',
45
+ '• Wait a few minutes for the email to arrive',
46
+ ],
47
+ };
48
+ }
49
+ };
50
+
51
+ if (variant === 'compact') {
52
+ return (
53
+ <div
54
+ className={`flex items-center justify-between p-3 bg-muted/30 rounded-lg border border-border ${className}`}
55
+ >
56
+ <div className="flex items-center space-x-2">
57
+ {getChannelIcon()}
58
+ <span className="text-sm text-muted-foreground">{getHelpText()}</span>
59
+ </div>
60
+ {supportUrl && (
61
+ <Button
62
+ asChild
63
+ variant="ghost"
64
+ size="sm"
65
+ className="text-xs"
66
+ >
67
+ <a href={supportUrl} target="_blank" rel="noopener noreferrer">
68
+ Need help?
69
+ </a>
70
+ </Button>
71
+ )}
72
+ </div>
73
+ );
74
+ }
75
+
76
+ const helpData = getDetailedHelp();
77
+
78
+ return (
79
+ <div
80
+ className={`space-y-3 p-3 bg-muted/30 rounded-lg border border-border ${className}`}
81
+ >
82
+ <div className="flex items-start space-x-2">
83
+ {getChannelIcon()}
84
+ <div className="space-y-1">
85
+ <h4 className="text-sm font-medium text-foreground">{helpData.title}</h4>
86
+ <div className="text-xs text-muted-foreground space-y-0.5">
87
+ {helpData.tips.map((tip, index) => (
88
+ <p key={index}>{tip}</p>
89
+ ))}
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ {supportUrl && (
95
+ <div className="flex items-center justify-between pt-2 border-t border-border">
96
+ <span className="text-xs text-muted-foreground">Still having trouble?</span>
97
+ <Button
98
+ asChild
99
+ variant="ghost"
100
+ size="sm"
101
+ className="text-xs h-7 px-2"
102
+ >
103
+ <a href={supportUrl} target="_blank" rel="noopener noreferrer">
104
+ Get Help
105
+ </a>
106
+ </Button>
107
+ </div>
108
+ )}
109
+ </div>
110
+ );
111
+ };
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+
3
+ import { AuthProvider, useAuthContext } from './AuthContext';
4
+ import { IdentifierForm } from './IdentifierForm';
5
+ import { OTPForm } from './OTPForm';
6
+
7
+ import type { AuthProps } from './types';
8
+
9
+ export const AuthLayout: React.FC<AuthProps> = (props) => {
10
+ return (
11
+ <AuthProvider {...props}>
12
+ <div
13
+ className={`min-h-screen flex flex-col items-center justify-center bg-background py-12 px-4 sm:px-6 lg:px-8 ${props.className || ''}`}
14
+ >
15
+ <div className="max-w-md w-full space-y-8 flex-1 flex flex-col justify-center">
16
+ {props.children}
17
+
18
+ <AuthContent />
19
+ </div>
20
+ </div>
21
+ </AuthProvider>
22
+ );
23
+ };
24
+
25
+ // Separate component to use the context
26
+ const AuthContent: React.FC = () => {
27
+ const { step, error } = useAuthContext();
28
+
29
+ return (
30
+ <>
31
+ {error && (
32
+ <div className="bg-destructive/10 border border-destructive/20 text-destructive px-4 py-3 rounded-md">
33
+ {error}
34
+ </div>
35
+ )}
36
+
37
+ <div>{step === 'identifier' ? <IdentifierForm /> : <OTPForm />}</div>
38
+ </>
39
+ );
40
+ };