@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,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Content
|
|
3
|
+
*
|
|
4
|
+
* Main content wrapper for dashboard pages
|
|
5
|
+
* Refactored from _old/DashboardLayout - uses context only!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { cn } from '@djangocfg/ui/lib';
|
|
12
|
+
import { useAppContext } from '../../../context';
|
|
13
|
+
|
|
14
|
+
interface DashboardContentProps {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const paddingVariants = {
|
|
20
|
+
none: '',
|
|
21
|
+
default: 'p-6',
|
|
22
|
+
sm: 'p-4',
|
|
23
|
+
md: 'p-6',
|
|
24
|
+
lg: 'p-8',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Dashboard Content Component
|
|
29
|
+
*
|
|
30
|
+
* Features:
|
|
31
|
+
* - Configurable padding from context
|
|
32
|
+
* - Full-width container
|
|
33
|
+
* - Custom className support
|
|
34
|
+
*
|
|
35
|
+
* Padding controlled by config.privateLayout.contentPadding
|
|
36
|
+
*/
|
|
37
|
+
export function DashboardContent({
|
|
38
|
+
children,
|
|
39
|
+
className,
|
|
40
|
+
}: DashboardContentProps) {
|
|
41
|
+
const { config } = useAppContext();
|
|
42
|
+
const { privateLayout } = config;
|
|
43
|
+
|
|
44
|
+
const padding =
|
|
45
|
+
paddingVariants[
|
|
46
|
+
privateLayout.contentPadding as keyof typeof paddingVariants
|
|
47
|
+
] || paddingVariants.default;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<main
|
|
51
|
+
className={cn(
|
|
52
|
+
'w-full bg-background relative min-h-full',
|
|
53
|
+
padding,
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
{children}
|
|
58
|
+
</main>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Header
|
|
3
|
+
*
|
|
4
|
+
* Header for private/dashboard layout
|
|
5
|
+
* Refactored from _old/DashboardLayout - uses context only!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import { Bell, LogIn, LogOut, User } from 'lucide-react';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Avatar,
|
|
15
|
+
AvatarFallback,
|
|
16
|
+
AvatarImage,
|
|
17
|
+
Badge,
|
|
18
|
+
Button,
|
|
19
|
+
DropdownMenu,
|
|
20
|
+
DropdownMenuContent,
|
|
21
|
+
DropdownMenuItem,
|
|
22
|
+
DropdownMenuLabel,
|
|
23
|
+
DropdownMenuSeparator,
|
|
24
|
+
DropdownMenuTrigger,
|
|
25
|
+
Separator,
|
|
26
|
+
SidebarTrigger,
|
|
27
|
+
} from '@djangocfg/ui';
|
|
28
|
+
import { ThemeToggle } from '@djangocfg/ui/theme';
|
|
29
|
+
import { useAuthDialog } from '../../../../../snippets';
|
|
30
|
+
import { useAppContext } from '../../../context';
|
|
31
|
+
import { useAuth } from '../../../../../auth';
|
|
32
|
+
import { useNavigation } from '../../../hooks';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Dashboard Header Component
|
|
36
|
+
*
|
|
37
|
+
* Features:
|
|
38
|
+
* - Sidebar trigger (mobile)
|
|
39
|
+
* - Page title
|
|
40
|
+
* - Custom header actions
|
|
41
|
+
* - Notifications button with badge
|
|
42
|
+
* - Theme toggle
|
|
43
|
+
* - User dropdown with avatar, profile, logout
|
|
44
|
+
* - Login button for guests
|
|
45
|
+
*
|
|
46
|
+
* All data from context!
|
|
47
|
+
*/
|
|
48
|
+
export function DashboardHeader() {
|
|
49
|
+
const { config } = useAppContext();
|
|
50
|
+
const { user } = useAuth();
|
|
51
|
+
const { getPageTitle } = useNavigation();
|
|
52
|
+
const { openAuthDialog } = useAuthDialog();
|
|
53
|
+
const { logout } = useAuth();
|
|
54
|
+
|
|
55
|
+
const { privateLayout } = config;
|
|
56
|
+
const pageTitle = getPageTitle();
|
|
57
|
+
|
|
58
|
+
// Notification handler - TODO: implement notification system
|
|
59
|
+
const handleNotificationClick = () => {
|
|
60
|
+
console.log('Notifications clicked');
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const handleLogin = () => {
|
|
64
|
+
openAuthDialog();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<header className="sticky top-0 py-2 z-50 h-16 flex items-center justify-between px-4 shrink-0 bg-background border-b border-border">
|
|
69
|
+
{/* Left side */}
|
|
70
|
+
<div className="flex items-center gap-4">
|
|
71
|
+
<SidebarTrigger className="-ml-1" />
|
|
72
|
+
<Separator orientation="vertical" className="mr-2 h-4" />
|
|
73
|
+
|
|
74
|
+
{pageTitle && (
|
|
75
|
+
<h1 className="text-lg font-semibold text-foreground">{pageTitle}</h1>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Right side */}
|
|
80
|
+
<div className="flex items-center gap-3">
|
|
81
|
+
{/* Custom header actions */}
|
|
82
|
+
{privateLayout.headerActions}
|
|
83
|
+
|
|
84
|
+
{/* Notifications */}
|
|
85
|
+
<Button
|
|
86
|
+
variant="ghost"
|
|
87
|
+
size="icon"
|
|
88
|
+
className="relative"
|
|
89
|
+
onClick={handleNotificationClick}
|
|
90
|
+
>
|
|
91
|
+
<Bell className="h-5 w-5" />
|
|
92
|
+
{/* TODO: implement notification count from context */}
|
|
93
|
+
</Button>
|
|
94
|
+
|
|
95
|
+
{/* Theme Toggle */}
|
|
96
|
+
<ThemeToggle />
|
|
97
|
+
|
|
98
|
+
{/* User menu or Login button */}
|
|
99
|
+
{user ? (
|
|
100
|
+
<DropdownMenu>
|
|
101
|
+
<DropdownMenuTrigger asChild>
|
|
102
|
+
<Button variant="ghost" className="flex items-center gap-2 p-2">
|
|
103
|
+
<Avatar className="h-8 w-8">
|
|
104
|
+
<AvatarImage
|
|
105
|
+
src={user.avatar || ''}
|
|
106
|
+
alt={user.display_username || user.email || ''}
|
|
107
|
+
/>
|
|
108
|
+
<AvatarFallback className="bg-primary/10 text-primary">
|
|
109
|
+
{user.display_username?.charAt(0)?.toUpperCase() || 'U'}
|
|
110
|
+
</AvatarFallback>
|
|
111
|
+
</Avatar>
|
|
112
|
+
<span className="hidden md:block text-sm font-medium">
|
|
113
|
+
{user.display_username}
|
|
114
|
+
</span>
|
|
115
|
+
</Button>
|
|
116
|
+
</DropdownMenuTrigger>
|
|
117
|
+
|
|
118
|
+
<DropdownMenuContent align="end" className="w-48">
|
|
119
|
+
<DropdownMenuLabel className="p-0 font-normal">
|
|
120
|
+
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
121
|
+
<Avatar className="h-8 w-8">
|
|
122
|
+
<AvatarImage
|
|
123
|
+
src={user.avatar || ''}
|
|
124
|
+
alt={user.display_username || user.email || ''}
|
|
125
|
+
/>
|
|
126
|
+
<AvatarFallback className="bg-primary/10 text-primary">
|
|
127
|
+
{user.display_username?.charAt(0)?.toUpperCase() || 'U'}
|
|
128
|
+
</AvatarFallback>
|
|
129
|
+
</Avatar>
|
|
130
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
131
|
+
<span className="truncate font-semibold">
|
|
132
|
+
{user.display_username || user.full_name || user.email}
|
|
133
|
+
</span>
|
|
134
|
+
<span className="truncate text-xs text-muted-foreground">
|
|
135
|
+
{user.email}
|
|
136
|
+
</span>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</DropdownMenuLabel>
|
|
140
|
+
|
|
141
|
+
<DropdownMenuSeparator />
|
|
142
|
+
|
|
143
|
+
<DropdownMenuItem asChild>
|
|
144
|
+
<a
|
|
145
|
+
href={privateLayout.profileHref}
|
|
146
|
+
className="flex items-center gap-2"
|
|
147
|
+
>
|
|
148
|
+
<User className="h-4 w-4" />
|
|
149
|
+
Profile
|
|
150
|
+
</a>
|
|
151
|
+
</DropdownMenuItem>
|
|
152
|
+
|
|
153
|
+
<DropdownMenuSeparator />
|
|
154
|
+
|
|
155
|
+
<DropdownMenuItem onClick={logout}>
|
|
156
|
+
<LogOut className="mr-2 h-4 w-4" />
|
|
157
|
+
Logout
|
|
158
|
+
</DropdownMenuItem>
|
|
159
|
+
</DropdownMenuContent>
|
|
160
|
+
</DropdownMenu>
|
|
161
|
+
) : (
|
|
162
|
+
<Button onClick={handleLogin} size="sm" className="gap-2">
|
|
163
|
+
<LogIn className="h-4 w-4" />
|
|
164
|
+
<span className="hidden sm:inline">Sign In</span>
|
|
165
|
+
</Button>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
</header>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Sidebar
|
|
3
|
+
*
|
|
4
|
+
* Sidebar navigation for private/dashboard layout
|
|
5
|
+
* Refactored from _old/DashboardLayout - uses context only!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import Link from 'next/link';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Sidebar,
|
|
15
|
+
SidebarContent,
|
|
16
|
+
SidebarFooter,
|
|
17
|
+
SidebarGroup,
|
|
18
|
+
SidebarGroupContent,
|
|
19
|
+
SidebarGroupLabel,
|
|
20
|
+
SidebarHeader,
|
|
21
|
+
SidebarMenu,
|
|
22
|
+
SidebarMenuBadge,
|
|
23
|
+
SidebarMenuButton,
|
|
24
|
+
SidebarMenuItem,
|
|
25
|
+
SidebarMenuSub,
|
|
26
|
+
SidebarMenuSubButton,
|
|
27
|
+
SidebarMenuSubItem,
|
|
28
|
+
useSidebar,
|
|
29
|
+
} from '@djangocfg/ui/components';
|
|
30
|
+
import { useAppContext } from '../../../context';
|
|
31
|
+
import { useNavigation } from '../../../hooks';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Dashboard Sidebar Component
|
|
35
|
+
*
|
|
36
|
+
* Features:
|
|
37
|
+
* - Project logo and name (clickable to home)
|
|
38
|
+
* - Menu groups with labels
|
|
39
|
+
* - Menu items with icons, labels, badges
|
|
40
|
+
* - Sub-menu items (nested navigation)
|
|
41
|
+
* - Active state detection
|
|
42
|
+
* - Optional footer content
|
|
43
|
+
*
|
|
44
|
+
* All data from context!
|
|
45
|
+
*/
|
|
46
|
+
export function DashboardSidebar() {
|
|
47
|
+
const { config } = useAppContext();
|
|
48
|
+
const { currentPath } = useNavigation();
|
|
49
|
+
const { state, isMobile } = useSidebar();
|
|
50
|
+
|
|
51
|
+
const { app, privateLayout } = config;
|
|
52
|
+
|
|
53
|
+
const isActiveRoute = (path: string) => {
|
|
54
|
+
// Only exact match - no prefix matching
|
|
55
|
+
// This ensures /private/jobs ONLY matches /private/jobs
|
|
56
|
+
// and NOT /private, /private/jobs/123, etc.
|
|
57
|
+
return currentPath === path;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Sidebar collapsible="icon">
|
|
62
|
+
<SidebarHeader>
|
|
63
|
+
<div
|
|
64
|
+
className="flex items-center gap-3"
|
|
65
|
+
style={state === "collapsed" ? {
|
|
66
|
+
paddingLeft: '7px',
|
|
67
|
+
paddingTop: '0.5rem',
|
|
68
|
+
paddingBottom: '0.5rem',
|
|
69
|
+
transition: 'padding 200ms ease-in-out'
|
|
70
|
+
} : {
|
|
71
|
+
padding: '0.5rem',
|
|
72
|
+
transition: 'padding 200ms ease-in-out'
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<Link href={privateLayout.homeHref}>
|
|
76
|
+
<div className="flex items-center gap-3">
|
|
77
|
+
{app.logoPath ? (
|
|
78
|
+
<img
|
|
79
|
+
src={app.logoPath}
|
|
80
|
+
alt={app.name}
|
|
81
|
+
className={isMobile ? "h-10 w-10 flex-shrink-0" : "h-8 w-8 flex-shrink-0"}
|
|
82
|
+
/>
|
|
83
|
+
) : (
|
|
84
|
+
<div className={isMobile ? "h-10 w-10 bg-primary rounded-lg flex items-center justify-center flex-shrink-0" : "h-8 w-8 bg-primary rounded-lg flex items-center justify-center flex-shrink-0"}>
|
|
85
|
+
<span className="text-primary-foreground font-bold text-sm">
|
|
86
|
+
{app.name.charAt(0).toUpperCase()}
|
|
87
|
+
</span>
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
{state !== "collapsed" && (
|
|
91
|
+
<span className={isMobile ? "font-semibold text-foreground truncate text-base" : "font-semibold text-foreground truncate"} style={{ whiteSpace: 'nowrap' }}>
|
|
92
|
+
{app.name}
|
|
93
|
+
</span>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
</Link>
|
|
97
|
+
</div>
|
|
98
|
+
</SidebarHeader>
|
|
99
|
+
|
|
100
|
+
<SidebarContent>
|
|
101
|
+
{privateLayout.menuGroups.map((group) => (
|
|
102
|
+
<SidebarGroup key={group.label}>
|
|
103
|
+
<SidebarGroupLabel>{group.label}</SidebarGroupLabel>
|
|
104
|
+
<SidebarGroupContent>
|
|
105
|
+
<SidebarMenu>
|
|
106
|
+
{group.items.map((item) => {
|
|
107
|
+
const isActive = isActiveRoute(item.path);
|
|
108
|
+
const Icon = item.icon;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<SidebarMenuItem key={item.path}>
|
|
112
|
+
<SidebarMenuButton
|
|
113
|
+
asChild
|
|
114
|
+
isActive={isActive}
|
|
115
|
+
tooltip={item.label}
|
|
116
|
+
size={isMobile ? "lg" : "default"}
|
|
117
|
+
>
|
|
118
|
+
<Link href={item.path}>
|
|
119
|
+
<Icon className={isMobile ? "h-5 w-5" : "h-4 w-4"} />
|
|
120
|
+
<span className={isMobile ? "text-base" : ""}>{item.label}</span>
|
|
121
|
+
{item.badge && (
|
|
122
|
+
<SidebarMenuBadge>{item.badge}</SidebarMenuBadge>
|
|
123
|
+
)}
|
|
124
|
+
</Link>
|
|
125
|
+
</SidebarMenuButton>
|
|
126
|
+
|
|
127
|
+
{/* Submenu */}
|
|
128
|
+
{item.subItems && item.subItems.length > 0 && (
|
|
129
|
+
<SidebarMenuSub>
|
|
130
|
+
{item.subItems.map((subItem) => {
|
|
131
|
+
const isSubActive = isActiveRoute(subItem.path);
|
|
132
|
+
const SubIcon = subItem.icon;
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<SidebarMenuSubItem key={subItem.path}>
|
|
136
|
+
<SidebarMenuSubButton
|
|
137
|
+
asChild
|
|
138
|
+
isActive={isSubActive}
|
|
139
|
+
size={isMobile ? "md" : "md"}
|
|
140
|
+
>
|
|
141
|
+
<Link href={subItem.path}>
|
|
142
|
+
<SubIcon className={isMobile ? "h-5 w-5" : "h-4 w-4"} />
|
|
143
|
+
<span className={isMobile ? "text-base" : ""}>{subItem.label}</span>
|
|
144
|
+
</Link>
|
|
145
|
+
</SidebarMenuSubButton>
|
|
146
|
+
</SidebarMenuSubItem>
|
|
147
|
+
);
|
|
148
|
+
})}
|
|
149
|
+
</SidebarMenuSub>
|
|
150
|
+
)}
|
|
151
|
+
</SidebarMenuItem>
|
|
152
|
+
);
|
|
153
|
+
})}
|
|
154
|
+
</SidebarMenu>
|
|
155
|
+
</SidebarGroupContent>
|
|
156
|
+
</SidebarGroup>
|
|
157
|
+
))}
|
|
158
|
+
</SidebarContent>
|
|
159
|
+
|
|
160
|
+
{/* TODO: implement footer content if needed */}
|
|
161
|
+
{/* <SidebarFooter>Footer content here</SidebarFooter> */}
|
|
162
|
+
</Sidebar>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Layout
|
|
3
|
+
*
|
|
4
|
+
* Layout for public pages (landing, docs, etc.)
|
|
5
|
+
* All data accessed through context - no prop drilling
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import React, { ReactNode } from 'react';
|
|
11
|
+
import { Navigation } from './components/Navigation';
|
|
12
|
+
import { Footer } from './components/Footer';
|
|
13
|
+
|
|
14
|
+
export interface PublicLayoutProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Public Layout Component
|
|
20
|
+
*
|
|
21
|
+
* Features:
|
|
22
|
+
* - Top navigation bar
|
|
23
|
+
* - User menu integration
|
|
24
|
+
* - Footer with links
|
|
25
|
+
* - Mobile responsive
|
|
26
|
+
*
|
|
27
|
+
* All data from useAppContext() and useAuth() - no props needed!
|
|
28
|
+
*/
|
|
29
|
+
export function PublicLayout({ children }: PublicLayoutProps) {
|
|
30
|
+
return (
|
|
31
|
+
<div className="min-h-screen flex flex-col">
|
|
32
|
+
{/* Navigation - gets data from context */}
|
|
33
|
+
<Navigation />
|
|
34
|
+
|
|
35
|
+
{/* Main Content */}
|
|
36
|
+
<main className="flex-1">
|
|
37
|
+
{children}
|
|
38
|
+
</main>
|
|
39
|
+
|
|
40
|
+
{/* Footer - gets data from context */}
|
|
41
|
+
<Footer />
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desktop User Menu
|
|
3
|
+
*
|
|
4
|
+
* User dropdown menu for desktop navigation
|
|
5
|
+
* Refactored from _old/MainLayout - uses context only!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { useRouter } from 'next/router';
|
|
12
|
+
import { ChevronDown, LayoutDashboard, LogOut, User } from 'lucide-react';
|
|
13
|
+
import { ButtonLink } from '@djangocfg/ui/components';
|
|
14
|
+
import { useAppContext } from '../../../context';
|
|
15
|
+
import { useAuth } from '../../../../../auth';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Desktop User Menu Component
|
|
19
|
+
*
|
|
20
|
+
* Features:
|
|
21
|
+
* - Sign in button for guests
|
|
22
|
+
* - Dashboard link for authenticated users (if not on dashboard)
|
|
23
|
+
* - User dropdown with email and profile link
|
|
24
|
+
* - Logout button
|
|
25
|
+
*
|
|
26
|
+
* All data from context!
|
|
27
|
+
*/
|
|
28
|
+
export function DesktopUserMenu() {
|
|
29
|
+
const router = useRouter();
|
|
30
|
+
const { config, userMenuOpen, toggleUserMenu, closeUserMenu } = useAppContext();
|
|
31
|
+
const { user, isAuthenticated, logout } = useAuth();
|
|
32
|
+
|
|
33
|
+
const { routes, publicLayout } = config;
|
|
34
|
+
|
|
35
|
+
const handleLogout = () => {
|
|
36
|
+
logout();
|
|
37
|
+
closeUserMenu();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const isDashboard = publicLayout.userMenu.dashboardPath
|
|
41
|
+
? router.pathname.includes(publicLayout.userMenu.dashboardPath)
|
|
42
|
+
: false;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="flex items-center gap-3">
|
|
46
|
+
{/* Authenticated user */}
|
|
47
|
+
{isAuthenticated ? (
|
|
48
|
+
<div className="flex items-center gap-3">
|
|
49
|
+
{/* Dashboard button (only if not on dashboard) */}
|
|
50
|
+
{publicLayout.userMenu.dashboardPath && !isDashboard && (
|
|
51
|
+
<ButtonLink
|
|
52
|
+
href={publicLayout.userMenu.dashboardPath}
|
|
53
|
+
variant="default"
|
|
54
|
+
size="sm"
|
|
55
|
+
>
|
|
56
|
+
<LayoutDashboard className="size-4 mr-2" />
|
|
57
|
+
Dashboard
|
|
58
|
+
</ButtonLink>
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{/* User Dropdown */}
|
|
62
|
+
<div className="relative">
|
|
63
|
+
<button
|
|
64
|
+
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors text-foreground hover:text-primary hover:bg-accent/50"
|
|
65
|
+
onClick={toggleUserMenu}
|
|
66
|
+
aria-haspopup="true"
|
|
67
|
+
aria-expanded={userMenuOpen}
|
|
68
|
+
>
|
|
69
|
+
<User className="size-4" />
|
|
70
|
+
<span className="max-w-[120px] truncate">{user?.email}</span>
|
|
71
|
+
<ChevronDown
|
|
72
|
+
className={`size-4 transition-transform ${
|
|
73
|
+
userMenuOpen ? 'rotate-180' : ''
|
|
74
|
+
}`}
|
|
75
|
+
/>
|
|
76
|
+
</button>
|
|
77
|
+
|
|
78
|
+
{userMenuOpen && (
|
|
79
|
+
<>
|
|
80
|
+
{/* Backdrop */}
|
|
81
|
+
<div
|
|
82
|
+
className="fixed inset-0 z-[9995]"
|
|
83
|
+
onClick={closeUserMenu}
|
|
84
|
+
aria-hidden="true"
|
|
85
|
+
/>
|
|
86
|
+
{/* Dropdown */}
|
|
87
|
+
<div
|
|
88
|
+
className="absolute top-full right-0 mt-2 w-48 rounded-lg shadow-lg backdrop-blur-xl z-[9996] bg-card/95 border border-border/50"
|
|
89
|
+
role="menu"
|
|
90
|
+
aria-label="User menu"
|
|
91
|
+
>
|
|
92
|
+
<div className="p-2">
|
|
93
|
+
{/* User info */}
|
|
94
|
+
<div className="px-3 py-2 text-sm mb-2 border-b text-muted-foreground border-border/30">
|
|
95
|
+
Signed in as:
|
|
96
|
+
<div className="font-medium truncate text-foreground">
|
|
97
|
+
{user?.email}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{/* Profile link */}
|
|
102
|
+
<ButtonLink
|
|
103
|
+
href={publicLayout.userMenu.profilePath}
|
|
104
|
+
variant="ghost"
|
|
105
|
+
size="sm"
|
|
106
|
+
className="w-full justify-start"
|
|
107
|
+
onClick={closeUserMenu}
|
|
108
|
+
>
|
|
109
|
+
<User className="size-4 mr-2" />
|
|
110
|
+
Profile
|
|
111
|
+
</ButtonLink>
|
|
112
|
+
|
|
113
|
+
{/* Logout button */}
|
|
114
|
+
<button
|
|
115
|
+
onClick={handleLogout}
|
|
116
|
+
className="w-full flex items-center gap-2 px-3 py-2 text-sm rounded-lg transition-colors text-destructive hover:bg-destructive/[0.1]"
|
|
117
|
+
>
|
|
118
|
+
<LogOut className="size-4" />
|
|
119
|
+
<span>Sign out</span>
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
) : (
|
|
128
|
+
/* Guest - Sign in button */
|
|
129
|
+
<ButtonLink href={routes.auth} variant="default" size="sm" className="h-9 gap-1.5">
|
|
130
|
+
<User className="w-4 h-4" />
|
|
131
|
+
Sign In
|
|
132
|
+
</ButtonLink>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|