@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,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Layout Navigation
|
|
3
|
+
*
|
|
4
|
+
* Full-featured navigation using @djangocfg/ui components
|
|
5
|
+
* Refactored from _old/MainLayout - uses context only!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import Link from 'next/link';
|
|
12
|
+
import { Menu } from 'lucide-react';
|
|
13
|
+
import {
|
|
14
|
+
NavigationMenu,
|
|
15
|
+
NavigationMenuContent,
|
|
16
|
+
NavigationMenuItem,
|
|
17
|
+
NavigationMenuLink,
|
|
18
|
+
NavigationMenuList,
|
|
19
|
+
NavigationMenuTrigger,
|
|
20
|
+
navigationMenuTriggerStyle,
|
|
21
|
+
} from '@djangocfg/ui/components';
|
|
22
|
+
import { ThemeToggle } from '@djangocfg/ui/theme';
|
|
23
|
+
import { cn } from '@djangocfg/ui/lib';
|
|
24
|
+
import { useIsMobile } from '@djangocfg/ui/hooks';
|
|
25
|
+
import { useAppContext } from '../../../context';
|
|
26
|
+
import { useAuth } from '../../../../../auth';
|
|
27
|
+
import { useNavigation } from '../../../hooks';
|
|
28
|
+
import { DesktopUserMenu } from './DesktopUserMenu';
|
|
29
|
+
import { MobileMenu } from './MobileMenu';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Navigation Component
|
|
33
|
+
*
|
|
34
|
+
* Features:
|
|
35
|
+
* - Logo and branding
|
|
36
|
+
* - NavigationMenu from @djangocfg/ui (Radix UI based)
|
|
37
|
+
* - Theme toggle
|
|
38
|
+
* - User menu (desktop)
|
|
39
|
+
* - Mobile menu button
|
|
40
|
+
*
|
|
41
|
+
* All data from context - zero prop drilling!
|
|
42
|
+
*/
|
|
43
|
+
export function Navigation() {
|
|
44
|
+
const { config, toggleMobileMenu } = useAppContext();
|
|
45
|
+
const { user, isAuthenticated, logout } = useAuth();
|
|
46
|
+
const { isActive } = useNavigation();
|
|
47
|
+
const isMobile = useIsMobile();
|
|
48
|
+
|
|
49
|
+
const { app, publicLayout } = config;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<nav className="sticky top-0 w-full border-b backdrop-blur-xl z-[9990] bg-surface-gradient border-border/30">
|
|
53
|
+
<div className="w-full px-4 sm:px-6 lg:px-8">
|
|
54
|
+
<div className="flex items-center justify-between h-16">
|
|
55
|
+
{/* Left side - Logo and Navigation Menu */}
|
|
56
|
+
<div className="flex items-center gap-6 flex-1">
|
|
57
|
+
{/* Logo */}
|
|
58
|
+
<Link
|
|
59
|
+
href={publicLayout.navigation.homePath}
|
|
60
|
+
className="flex items-center gap-3 group"
|
|
61
|
+
>
|
|
62
|
+
<img
|
|
63
|
+
src={app.logoPath}
|
|
64
|
+
alt={`${app.name} Logo`}
|
|
65
|
+
className="h-8 w-auto object-contain transition-transform duration-300 group-hover:scale-105"
|
|
66
|
+
/>
|
|
67
|
+
<span className="text-xl font-bold transition-colors duration-300 text-foreground group-hover:text-primary">
|
|
68
|
+
{app.name}
|
|
69
|
+
</span>
|
|
70
|
+
</Link>
|
|
71
|
+
|
|
72
|
+
{/* Desktop Navigation Menu using @djangocfg/ui NavigationMenu */}
|
|
73
|
+
{!isMobile && (
|
|
74
|
+
<div>
|
|
75
|
+
<NavigationMenu>
|
|
76
|
+
<NavigationMenuList>
|
|
77
|
+
{publicLayout.navigation.menuSections.map((section) => {
|
|
78
|
+
// Single item section - render as direct link
|
|
79
|
+
if (section.items.length === 1) {
|
|
80
|
+
const item = section.items[0];
|
|
81
|
+
if (!item) return null;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<NavigationMenuItem key={section.title}>
|
|
85
|
+
<NavigationMenuLink asChild>
|
|
86
|
+
<Link
|
|
87
|
+
href={item.path}
|
|
88
|
+
className={cn(
|
|
89
|
+
navigationMenuTriggerStyle(),
|
|
90
|
+
isActive(item.path) && 'bg-accent text-accent-foreground'
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
{item.label}
|
|
94
|
+
</Link>
|
|
95
|
+
</NavigationMenuLink>
|
|
96
|
+
</NavigationMenuItem>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Multiple items - render as dropdown menu
|
|
101
|
+
return (
|
|
102
|
+
<NavigationMenuItem key={section.title}>
|
|
103
|
+
<NavigationMenuTrigger>{section.title}</NavigationMenuTrigger>
|
|
104
|
+
<NavigationMenuContent>
|
|
105
|
+
<ul className="grid gap-3 p-4" style={{ width: '400px' }}>
|
|
106
|
+
{section.items.map((item) => (
|
|
107
|
+
<li key={item.path}>
|
|
108
|
+
<NavigationMenuLink asChild>
|
|
109
|
+
<Link
|
|
110
|
+
href={item.path}
|
|
111
|
+
className={cn(
|
|
112
|
+
'block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors whitespace-nowrap hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
|
|
113
|
+
isActive(item.path) && 'bg-accent/50'
|
|
114
|
+
)}
|
|
115
|
+
>
|
|
116
|
+
<div className="text-sm font-medium leading-none">
|
|
117
|
+
{item.label}
|
|
118
|
+
</div>
|
|
119
|
+
</Link>
|
|
120
|
+
</NavigationMenuLink>
|
|
121
|
+
</li>
|
|
122
|
+
))}
|
|
123
|
+
</ul>
|
|
124
|
+
</NavigationMenuContent>
|
|
125
|
+
</NavigationMenuItem>
|
|
126
|
+
);
|
|
127
|
+
})}
|
|
128
|
+
</NavigationMenuList>
|
|
129
|
+
</NavigationMenu>
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
{/* Right side - Theme Toggle & User Menu */}
|
|
135
|
+
{!isMobile && (
|
|
136
|
+
<div className="flex items-center gap-2">
|
|
137
|
+
<ThemeToggle />
|
|
138
|
+
<DesktopUserMenu />
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{/* Mobile Menu Button - Only visible on mobile */}
|
|
143
|
+
{isMobile && (
|
|
144
|
+
<button
|
|
145
|
+
onClick={toggleMobileMenu}
|
|
146
|
+
className="p-3 rounded-lg border shadow-sm transition-all duration-200 bg-card/50 hover:bg-card border-border/50 hover:border-primary/50 text-foreground hover:text-primary"
|
|
147
|
+
aria-label="Toggle mobile menu"
|
|
148
|
+
>
|
|
149
|
+
<Menu className="size-5" />
|
|
150
|
+
</button>
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Mobile Menu Drawer */}
|
|
156
|
+
<MobileMenu />
|
|
157
|
+
</nav>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Providers
|
|
3
|
+
*
|
|
4
|
+
* Wraps application with all necessary providers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import React, { ReactNode } from 'react';
|
|
10
|
+
import { ThemeProvider, Toaster } from '@djangocfg/ui';
|
|
11
|
+
import { AuthProvider } from '../../../auth';
|
|
12
|
+
import type { AppLayoutConfig } from '../types';
|
|
13
|
+
|
|
14
|
+
export interface CoreProvidersProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
config: AppLayoutConfig;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Core Providers Wrapper
|
|
21
|
+
*
|
|
22
|
+
* Provides:
|
|
23
|
+
* - ThemeProvider (dark/light mode)
|
|
24
|
+
* - AuthProvider (authentication)
|
|
25
|
+
* - Toaster (notifications)
|
|
26
|
+
*/
|
|
27
|
+
export function CoreProviders({ children, config }: CoreProvidersProps) {
|
|
28
|
+
return (
|
|
29
|
+
<ThemeProvider>
|
|
30
|
+
<AuthProvider
|
|
31
|
+
config={{
|
|
32
|
+
apiUrl: config.api.baseUrl,
|
|
33
|
+
routes: {
|
|
34
|
+
auth: config.routes.auth,
|
|
35
|
+
defaultCallback: config.routes.defaultCallback,
|
|
36
|
+
defaultAuthCallback: config.routes.defaultAuthCallback || config.routes.defaultCallback,
|
|
37
|
+
},
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
</AuthProvider>
|
|
42
|
+
|
|
43
|
+
{/* Global toast notifications */}
|
|
44
|
+
<Toaster />
|
|
45
|
+
</ThemeProvider>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
6
|
+
import type { PublicLayoutConfig, PrivateLayoutConfig } from './layout';
|
|
7
|
+
import type { RouteConfig } from './routes';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Main AppLayout configuration
|
|
11
|
+
*/
|
|
12
|
+
export interface AppLayoutConfig {
|
|
13
|
+
/** Application metadata */
|
|
14
|
+
app: {
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
logoPath: string;
|
|
18
|
+
siteUrl?: string;
|
|
19
|
+
icons?: {
|
|
20
|
+
logo192?: string;
|
|
21
|
+
logo384?: string;
|
|
22
|
+
logo512?: string;
|
|
23
|
+
logoVector?: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** API configuration */
|
|
28
|
+
api: {
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** Route configuration */
|
|
33
|
+
routes: RouteConfig;
|
|
34
|
+
|
|
35
|
+
/** Public layout configuration */
|
|
36
|
+
publicLayout: PublicLayoutConfig;
|
|
37
|
+
|
|
38
|
+
/** Private layout configuration */
|
|
39
|
+
privateLayout: PrivateLayoutConfig;
|
|
40
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout Configuration Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
6
|
+
import type { LucideIcon } from 'lucide-react';
|
|
7
|
+
import type { NavigationSection, DashboardMenuGroup } from './navigation';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Public layout configuration
|
|
11
|
+
*/
|
|
12
|
+
export interface PublicLayoutConfig {
|
|
13
|
+
navigation: {
|
|
14
|
+
homePath: string;
|
|
15
|
+
menuSections: NavigationSection[];
|
|
16
|
+
};
|
|
17
|
+
userMenu: {
|
|
18
|
+
dashboardPath?: string;
|
|
19
|
+
profilePath: string;
|
|
20
|
+
};
|
|
21
|
+
footer: {
|
|
22
|
+
badge: {
|
|
23
|
+
icon: LucideIcon;
|
|
24
|
+
text: string;
|
|
25
|
+
};
|
|
26
|
+
links: {
|
|
27
|
+
docs?: string;
|
|
28
|
+
privacy?: string;
|
|
29
|
+
terms?: string;
|
|
30
|
+
security?: string;
|
|
31
|
+
cookies?: string;
|
|
32
|
+
};
|
|
33
|
+
menuSections: NavigationSection[];
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Private/Dashboard layout configuration
|
|
39
|
+
*/
|
|
40
|
+
export interface PrivateLayoutConfig {
|
|
41
|
+
homeHref: string;
|
|
42
|
+
profileHref: string;
|
|
43
|
+
showChat?: boolean;
|
|
44
|
+
menuGroups: DashboardMenuGroup[];
|
|
45
|
+
contentPadding?: 'default' | 'none';
|
|
46
|
+
headerActions?: ReactNode;
|
|
47
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { LucideIcon } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Navigation menu item
|
|
9
|
+
*/
|
|
10
|
+
export interface NavigationItem {
|
|
11
|
+
label: string;
|
|
12
|
+
path: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Navigation menu section
|
|
17
|
+
*/
|
|
18
|
+
export interface NavigationSection {
|
|
19
|
+
title: string;
|
|
20
|
+
items: NavigationItem[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Dashboard menu item with icon
|
|
25
|
+
*/
|
|
26
|
+
export interface DashboardMenuItem {
|
|
27
|
+
path: string;
|
|
28
|
+
label: string;
|
|
29
|
+
icon: LucideIcon;
|
|
30
|
+
badge?: string | number;
|
|
31
|
+
subItems?: DashboardMenuItem[]; // Support nested menu items
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Dashboard menu group
|
|
36
|
+
*/
|
|
37
|
+
export interface DashboardMenuGroup {
|
|
38
|
+
label: string;
|
|
39
|
+
order: number;
|
|
40
|
+
items: DashboardMenuItem[];
|
|
41
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Route configuration
|
|
7
|
+
*/
|
|
8
|
+
export interface RouteConfig {
|
|
9
|
+
/** Authentication route */
|
|
10
|
+
auth: string;
|
|
11
|
+
|
|
12
|
+
/** Default redirect after login */
|
|
13
|
+
defaultCallback: string;
|
|
14
|
+
|
|
15
|
+
/** Redirect for authenticated users on auth page */
|
|
16
|
+
defaultAuthCallback?: string;
|
|
17
|
+
|
|
18
|
+
/** Route detector functions */
|
|
19
|
+
detectors: RouteDetectors;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Route detection functions
|
|
24
|
+
*/
|
|
25
|
+
export interface RouteDetectors {
|
|
26
|
+
/** Check if route is public */
|
|
27
|
+
isPublicRoute: (path: string) => boolean;
|
|
28
|
+
|
|
29
|
+
/** Check if route is private/protected */
|
|
30
|
+
isPrivateRoute: (path: string) => boolean;
|
|
31
|
+
|
|
32
|
+
/** Check if route is auth page */
|
|
33
|
+
isAuthRoute: (path: string) => boolean;
|
|
34
|
+
|
|
35
|
+
/** Get redirect URL for unauthenticated users */
|
|
36
|
+
getUnauthenticatedRedirect: (path: string) => string | null;
|
|
37
|
+
|
|
38
|
+
/** Get page title for route */
|
|
39
|
+
getPageTitle: (path: string) => string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Layout mode based on route
|
|
44
|
+
*/
|
|
45
|
+
export type LayoutMode = 'public' | 'private' | 'auth';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route Detection Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { LayoutMode, RouteDetectors } from '../types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Determine layout mode from pathname
|
|
9
|
+
*/
|
|
10
|
+
export function determineLayoutMode(
|
|
11
|
+
pathname: string,
|
|
12
|
+
detectors: RouteDetectors
|
|
13
|
+
): LayoutMode {
|
|
14
|
+
if (detectors.isAuthRoute(pathname)) return 'auth';
|
|
15
|
+
if (detectors.isPrivateRoute(pathname)) return 'private';
|
|
16
|
+
return 'public';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if redirect is needed for unauthenticated user
|
|
21
|
+
*/
|
|
22
|
+
export function getRedirectUrl(
|
|
23
|
+
pathname: string,
|
|
24
|
+
isAuthenticated: boolean,
|
|
25
|
+
detectors: RouteDetectors
|
|
26
|
+
): string | null {
|
|
27
|
+
if (!isAuthenticated) {
|
|
28
|
+
return detectors.getUnauthenticatedRedirect(pathname);
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payments Layout
|
|
3
|
+
* Main layout with tabs for different payment-related views
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use client';
|
|
7
|
+
|
|
8
|
+
import React, { useState } from 'react';
|
|
9
|
+
import {
|
|
10
|
+
PaymentsProvider,
|
|
11
|
+
BalancesProvider,
|
|
12
|
+
ApiKeysProvider,
|
|
13
|
+
OverviewProvider,
|
|
14
|
+
} from '@djangocfg/api/cfg/contexts';
|
|
15
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@djangocfg/ui';
|
|
16
|
+
import { Wallet, CreditCard, History, Key, Crown } from 'lucide-react';
|
|
17
|
+
import type { PaymentTab } from './types';
|
|
18
|
+
import { OverviewView } from './views/overview';
|
|
19
|
+
import { PaymentsView } from './views/payments';
|
|
20
|
+
import { TransactionsView } from './views/transactions';
|
|
21
|
+
import { ApiKeysView } from './views/apikeys';
|
|
22
|
+
import { TariffsView } from './views/tariffs';
|
|
23
|
+
import { CreateApiKeyDialog, DeleteApiKeyDialog } from './components';
|
|
24
|
+
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
26
|
+
// Payments Layout Content
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const PaymentsLayoutContent: React.FC = () => {
|
|
30
|
+
const [activeTab, setActiveTab] = useState<PaymentTab>('overview');
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="h-full p-6 space-y-6">
|
|
34
|
+
{/* Page Header */}
|
|
35
|
+
<div>
|
|
36
|
+
<h1 className="text-3xl font-bold tracking-tight">Payments & Billing</h1>
|
|
37
|
+
<p className="text-muted-foreground">
|
|
38
|
+
Manage your payments, subscriptions, API keys, and account balance
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
{/* Main Content with Tabs */}
|
|
43
|
+
<Tabs
|
|
44
|
+
value={activeTab}
|
|
45
|
+
onValueChange={(value) => setActiveTab(value as PaymentTab)}
|
|
46
|
+
className="space-y-6"
|
|
47
|
+
>
|
|
48
|
+
<TabsList className="inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground">
|
|
49
|
+
<TabsTrigger value="overview" className="inline-flex items-center gap-2 px-3 py-1.5">
|
|
50
|
+
<Wallet className="h-4 w-4" />
|
|
51
|
+
<span className="hidden sm:inline">Overview</span>
|
|
52
|
+
</TabsTrigger>
|
|
53
|
+
<TabsTrigger value="payments" className="inline-flex items-center gap-2 px-3 py-1.5">
|
|
54
|
+
<CreditCard className="h-4 w-4" />
|
|
55
|
+
<span className="hidden sm:inline">Payments</span>
|
|
56
|
+
</TabsTrigger>
|
|
57
|
+
<TabsTrigger value="transactions" className="inline-flex items-center gap-2 px-3 py-1.5">
|
|
58
|
+
<History className="h-4 w-4" />
|
|
59
|
+
<span className="hidden sm:inline">Transactions</span>
|
|
60
|
+
</TabsTrigger>
|
|
61
|
+
<TabsTrigger value="apikeys" className="inline-flex items-center gap-2 px-3 py-1.5">
|
|
62
|
+
<Key className="h-4 w-4" />
|
|
63
|
+
<span className="hidden sm:inline">API Keys</span>
|
|
64
|
+
</TabsTrigger>
|
|
65
|
+
<TabsTrigger value="tariffs" className="inline-flex items-center gap-2 px-3 py-1.5">
|
|
66
|
+
<Crown className="h-4 w-4" />
|
|
67
|
+
<span className="hidden sm:inline">Tariffs</span>
|
|
68
|
+
</TabsTrigger>
|
|
69
|
+
</TabsList>
|
|
70
|
+
|
|
71
|
+
{/* Overview Tab */}
|
|
72
|
+
<TabsContent value="overview" className="space-y-6">
|
|
73
|
+
<OverviewView />
|
|
74
|
+
</TabsContent>
|
|
75
|
+
|
|
76
|
+
{/* Payments Tab */}
|
|
77
|
+
<TabsContent value="payments" className="space-y-6">
|
|
78
|
+
<PaymentsView />
|
|
79
|
+
</TabsContent>
|
|
80
|
+
|
|
81
|
+
{/* Transactions Tab */}
|
|
82
|
+
<TabsContent value="transactions" className="space-y-6">
|
|
83
|
+
<TransactionsView />
|
|
84
|
+
</TabsContent>
|
|
85
|
+
|
|
86
|
+
{/* API Keys Tab */}
|
|
87
|
+
<TabsContent value="apikeys" className="space-y-6">
|
|
88
|
+
<ApiKeysView />
|
|
89
|
+
</TabsContent>
|
|
90
|
+
|
|
91
|
+
{/* Tariffs Tab */}
|
|
92
|
+
<TabsContent value="tariffs" className="space-y-6">
|
|
93
|
+
<TariffsView />
|
|
94
|
+
</TabsContent>
|
|
95
|
+
</Tabs>
|
|
96
|
+
|
|
97
|
+
{/* Global Dialogs */}
|
|
98
|
+
<CreateApiKeyDialog />
|
|
99
|
+
<DeleteApiKeyDialog />
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
105
|
+
// Payments Layout (with providers)
|
|
106
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
export interface PaymentsLayoutProps {
|
|
109
|
+
children?: React.ReactNode;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const PaymentsLayout: React.FC<PaymentsLayoutProps> = () => {
|
|
113
|
+
return (
|
|
114
|
+
<OverviewProvider>
|
|
115
|
+
<PaymentsProvider>
|
|
116
|
+
<BalancesProvider>
|
|
117
|
+
<ApiKeysProvider>
|
|
118
|
+
<PaymentsLayoutContent />
|
|
119
|
+
</ApiKeysProvider>
|
|
120
|
+
</BalancesProvider>
|
|
121
|
+
</PaymentsProvider>
|
|
122
|
+
</OverviewProvider>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|