@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.
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/package.json +86 -0
- package/src/auth/README.md +962 -0
- package/src/auth/context/AuthContext.tsx +458 -0
- package/src/auth/context/index.ts +2 -0
- package/src/auth/context/types.ts +63 -0
- package/src/auth/hooks/index.ts +6 -0
- package/src/auth/hooks/useAuthForm.ts +329 -0
- package/src/auth/hooks/useAuthGuard.ts +23 -0
- package/src/auth/hooks/useAuthRedirect.ts +51 -0
- package/src/auth/hooks/useAutoAuth.ts +42 -0
- package/src/auth/hooks/useLocalStorage.ts +211 -0
- package/src/auth/hooks/useSessionStorage.ts +186 -0
- package/src/auth/index.ts +10 -0
- package/src/auth/middlewares/index.ts +1 -0
- package/src/auth/middlewares/proxy.ts +24 -0
- package/src/auth/server.ts +6 -0
- package/src/auth/utils/errors.ts +34 -0
- package/src/auth/utils/index.ts +2 -0
- package/src/auth/utils/validation.ts +14 -0
- package/src/index.ts +15 -0
- package/src/layouts/AppLayout/AppLayout.tsx +123 -0
- package/src/layouts/AppLayout/README.md +204 -0
- package/src/layouts/AppLayout/SUMMARY.md +240 -0
- package/src/layouts/AppLayout/USAGE.md +312 -0
- package/src/layouts/AppLayout/components/PageProgress.tsx +104 -0
- package/src/layouts/AppLayout/components/Seo.tsx +87 -0
- package/src/layouts/AppLayout/components/index.ts +6 -0
- package/src/layouts/AppLayout/context/AppContext.tsx +146 -0
- package/src/layouts/AppLayout/context/index.ts +5 -0
- package/src/layouts/AppLayout/hooks/index.ts +6 -0
- package/src/layouts/AppLayout/hooks/useLayoutMode.ts +26 -0
- package/src/layouts/AppLayout/hooks/useNavigation.ts +49 -0
- package/src/layouts/AppLayout/index.ts +31 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthContext.tsx +51 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthHelp.tsx +111 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +40 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/IdentifierForm.tsx +330 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/OTPForm.tsx +158 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +13 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/types.ts +61 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +92 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +60 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +170 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +164 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +7 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/index.ts +5 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/PublicLayout.tsx +44 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/DesktopUserMenu.tsx +136 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +262 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenu.tsx +289 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +159 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/index.ts +5 -0
- package/src/layouts/AppLayout/layouts/index.ts +7 -0
- package/src/layouts/AppLayout/providers/CoreProviders.tsx +47 -0
- package/src/layouts/AppLayout/providers/index.ts +5 -0
- package/src/layouts/AppLayout/types/config.ts +40 -0
- package/src/layouts/AppLayout/types/index.ts +10 -0
- package/src/layouts/AppLayout/types/layout.ts +47 -0
- package/src/layouts/AppLayout/types/navigation.ts +41 -0
- package/src/layouts/AppLayout/types/routes.ts +45 -0
- package/src/layouts/AppLayout/utils/index.ts +5 -0
- package/src/layouts/AppLayout/utils/routeDetection.ts +31 -0
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +125 -0
- package/src/layouts/PaymentsLayout/README.md +133 -0
- package/src/layouts/PaymentsLayout/components/CreateApiKeyDialog.tsx +172 -0
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +203 -0
- package/src/layouts/PaymentsLayout/components/DeleteApiKeyDialog.tsx +100 -0
- package/src/layouts/PaymentsLayout/components/index.ts +4 -0
- package/src/layouts/PaymentsLayout/events.ts +106 -0
- package/src/layouts/PaymentsLayout/index.ts +20 -0
- package/src/layouts/PaymentsLayout/types.ts +19 -0
- package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeyMetrics.tsx +109 -0
- package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeysList.tsx +194 -0
- package/src/layouts/PaymentsLayout/views/apikeys/components/index.ts +3 -0
- package/src/layouts/PaymentsLayout/views/apikeys/index.tsx +19 -0
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +99 -0
- package/src/layouts/PaymentsLayout/views/overview/components/MetricsCards.tsx +103 -0
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +138 -0
- package/src/layouts/PaymentsLayout/views/overview/components/index.ts +4 -0
- package/src/layouts/PaymentsLayout/views/overview/index.tsx +23 -0
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +282 -0
- package/src/layouts/PaymentsLayout/views/payments/components/index.ts +2 -0
- package/src/layouts/PaymentsLayout/views/payments/index.tsx +18 -0
- package/src/layouts/PaymentsLayout/views/tariffs/index.tsx +29 -0
- package/src/layouts/PaymentsLayout/views/transactions/index.tsx +29 -0
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +110 -0
- package/src/layouts/ProfileLayout/components/AvatarSection.tsx +146 -0
- package/src/layouts/ProfileLayout/components/ProfileForm.tsx +208 -0
- package/src/layouts/ProfileLayout/components/index.ts +3 -0
- package/src/layouts/ProfileLayout/index.ts +3 -0
- package/src/layouts/SupportLayout/README.md +91 -0
- package/src/layouts/SupportLayout/SupportLayout.tsx +178 -0
- package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +154 -0
- package/src/layouts/SupportLayout/components/MessageInput.tsx +92 -0
- package/src/layouts/SupportLayout/components/MessageList.tsx +312 -0
- package/src/layouts/SupportLayout/components/TicketCard.tsx +96 -0
- package/src/layouts/SupportLayout/components/TicketList.tsx +152 -0
- package/src/layouts/SupportLayout/components/index.ts +6 -0
- package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +260 -0
- package/src/layouts/SupportLayout/context/index.ts +2 -0
- package/src/layouts/SupportLayout/events.ts +31 -0
- package/src/layouts/SupportLayout/hooks/index.ts +2 -0
- package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +118 -0
- package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +91 -0
- package/src/layouts/SupportLayout/index.ts +6 -0
- package/src/layouts/SupportLayout/types.ts +23 -0
- package/src/layouts/index.ts +9 -0
- package/src/snippets/AuthDialog/AuthDialog.tsx +88 -0
- package/src/snippets/AuthDialog/events.ts +21 -0
- package/src/snippets/AuthDialog/index.ts +3 -0
- package/src/snippets/AuthDialog/useAuthDialog.ts +27 -0
- package/src/snippets/Breadcrumbs.tsx +80 -0
- package/src/snippets/Chat/ChatUIContext.tsx +110 -0
- package/src/snippets/Chat/ChatWidget.tsx +476 -0
- package/src/snippets/Chat/README.md +122 -0
- package/src/snippets/Chat/components/MessageInput.tsx +124 -0
- package/src/snippets/Chat/components/MessageList.tsx +168 -0
- package/src/snippets/Chat/components/SessionList.tsx +192 -0
- package/src/snippets/Chat/components/index.ts +9 -0
- package/src/snippets/Chat/hooks/index.ts +6 -0
- package/src/snippets/Chat/hooks/useInfiniteSessions.ts +83 -0
- package/src/snippets/Chat/index.tsx +44 -0
- package/src/snippets/Chat/types.ts +79 -0
- package/src/snippets/VideoPlayer/README.md +203 -0
- package/src/snippets/VideoPlayer/VideoControls.tsx +133 -0
- package/src/snippets/VideoPlayer/VideoPlayer.tsx +114 -0
- package/src/snippets/VideoPlayer/index.ts +8 -0
- package/src/snippets/VideoPlayer/types.ts +61 -0
- package/src/snippets/index.ts +10 -0
- package/src/styles/dashboard.css +41 -0
- package/src/styles/index.css +20 -0
- package/src/styles/sources.css +6 -0
- package/src/types/index.ts +1 -0
- package/src/types/pageConfig.ts +103 -0
- package/src/utils/index.ts +6 -0
- 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,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,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
|
+
};
|