@djangocfg/layouts 1.0.2 → 1.0.4
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/package.json +5 -5
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthHelp.tsx +7 -5
- package/src/layouts/AppLayout/layouts/AuthLayout/IdentifierForm.tsx +3 -3
- package/src/layouts/AppLayout/layouts/AuthLayout/OTPForm.tsx +26 -10
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +1 -1
- package/src/layouts/AppLayout/layouts/PublicLayout/components/DesktopUserMenu.tsx +6 -6
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +1 -1
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenu.tsx +43 -133
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenuUserCard.tsx +150 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +2 -2
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +41 -57
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +188 -57
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +323 -0
- package/src/layouts/PaymentsLayout/components/index.ts +1 -0
- package/src/layouts/PaymentsLayout/context/RootPaymentsContext.tsx +129 -0
- package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeysList.tsx +2 -2
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +6 -6
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +2 -2
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +9 -4
- package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +2 -3
- package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +2 -3
- package/src/snippets/Chat/components/SessionList.tsx +1 -1
- package/src/snippets/Chat/hooks/useInfiniteSessions.ts +2 -2
- package/src/snippets/VideoPlayer/VideoPlayer.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Layout system and components for Unrealon applications",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DjangoCFG",
|
|
@@ -53,10 +53,10 @@
|
|
|
53
53
|
"check": "tsc --noEmit"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
+
"@djangocfg/api": "^1.0.4",
|
|
57
|
+
"@djangocfg/og-image": "^1.0.4",
|
|
58
|
+
"@djangocfg/ui": "^1.0.4",
|
|
56
59
|
"@hookform/resolvers": "^5.2.0",
|
|
57
|
-
"@djangocfg/api": "^1.0.2",
|
|
58
|
-
"@djangocfg/og-image": "^1.0.2",
|
|
59
|
-
"@djangocfg/ui": "^1.0.2",
|
|
60
60
|
"consola": "^3.4.2",
|
|
61
61
|
"lucide-react": "^0.468.0",
|
|
62
62
|
"next": "^15.4.4",
|
|
@@ -76,10 +76,10 @@
|
|
|
76
76
|
"vidstack": "0.6.15"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
+
"@djangocfg/typescript-config": "^1.0.4",
|
|
79
80
|
"@types/node": "^24.7.2",
|
|
80
81
|
"@types/react": "19.2.2",
|
|
81
82
|
"@types/react-dom": "19.2.1",
|
|
82
|
-
"@djangocfg/typescript-config": "^1.0.2",
|
|
83
83
|
"eslint": "^9.37.0",
|
|
84
84
|
"typescript": "^5.9.3"
|
|
85
85
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Mail, MessageCircle } from 'lucide-react';
|
|
1
|
+
import { Mail, MessageCircle, HelpCircle } from 'lucide-react';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
import { Button } from '@djangocfg/ui/components';
|
|
@@ -51,7 +51,7 @@ export const AuthHelp: React.FC<AuthHelpProps> = ({
|
|
|
51
51
|
if (variant === 'compact') {
|
|
52
52
|
return (
|
|
53
53
|
<div
|
|
54
|
-
className={`flex items-center justify-between p-3 bg-muted/30 rounded-
|
|
54
|
+
className={`flex items-center justify-between p-3 bg-muted/30 rounded-sm border border-border ${className}`}
|
|
55
55
|
>
|
|
56
56
|
<div className="flex items-center space-x-2">
|
|
57
57
|
{getChannelIcon()}
|
|
@@ -64,7 +64,8 @@ export const AuthHelp: React.FC<AuthHelpProps> = ({
|
|
|
64
64
|
size="sm"
|
|
65
65
|
className="text-xs"
|
|
66
66
|
>
|
|
67
|
-
<a href={supportUrl} target="_blank" rel="noopener noreferrer">
|
|
67
|
+
<a href={supportUrl} target="_blank" rel="noopener noreferrer" className="flex items-center gap-1">
|
|
68
|
+
<HelpCircle className="w-3 h-3" />
|
|
68
69
|
Need help?
|
|
69
70
|
</a>
|
|
70
71
|
</Button>
|
|
@@ -77,7 +78,7 @@ export const AuthHelp: React.FC<AuthHelpProps> = ({
|
|
|
77
78
|
|
|
78
79
|
return (
|
|
79
80
|
<div
|
|
80
|
-
className={`space-y-3 p-3 bg-muted/30 rounded-
|
|
81
|
+
className={`space-y-3 p-3 bg-muted/30 rounded-sm border border-border ${className}`}
|
|
81
82
|
>
|
|
82
83
|
<div className="flex items-start space-x-2">
|
|
83
84
|
{getChannelIcon()}
|
|
@@ -100,7 +101,8 @@ export const AuthHelp: React.FC<AuthHelpProps> = ({
|
|
|
100
101
|
size="sm"
|
|
101
102
|
className="text-xs h-7 px-2"
|
|
102
103
|
>
|
|
103
|
-
<a href={supportUrl} target="_blank" rel="noopener noreferrer">
|
|
104
|
+
<a href={supportUrl} target="_blank" rel="noopener noreferrer" className="flex items-center gap-1">
|
|
105
|
+
<HelpCircle className="w-3 h-3" />
|
|
104
106
|
Get Help
|
|
105
107
|
</a>
|
|
106
108
|
</Button>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Mail, Phone, User } from 'lucide-react';
|
|
2
|
+
import { Mail, Phone, User, Send } from 'lucide-react';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
@@ -231,7 +231,7 @@ export const IdentifierForm: React.FC = () => {
|
|
|
231
231
|
</div>
|
|
232
232
|
) : (
|
|
233
233
|
<div className="flex items-center gap-2">
|
|
234
|
-
|
|
234
|
+
<Send className="w-4 h-4" />
|
|
235
235
|
Send verification code
|
|
236
236
|
</div>
|
|
237
237
|
)}
|
|
@@ -314,7 +314,7 @@ export const IdentifierForm: React.FC = () => {
|
|
|
314
314
|
</div>
|
|
315
315
|
) : (
|
|
316
316
|
<div className="flex items-center gap-2">
|
|
317
|
-
|
|
317
|
+
<Send className="w-4 h-4" />
|
|
318
318
|
Send verification code
|
|
319
319
|
</div>
|
|
320
320
|
)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Mail, MessageCircle } from 'lucide-react';
|
|
2
|
+
import { Mail, MessageCircle, ArrowLeft, RotateCw, ShieldCheck } from 'lucide-react';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
@@ -80,27 +80,27 @@ export const OTPForm: React.FC = () => {
|
|
|
80
80
|
<InputOTPGroup className="gap-2">
|
|
81
81
|
<InputOTPSlot
|
|
82
82
|
index={0}
|
|
83
|
-
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-
|
|
83
|
+
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-sm shadow-sm"
|
|
84
84
|
/>
|
|
85
85
|
<InputOTPSlot
|
|
86
86
|
index={1}
|
|
87
|
-
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-
|
|
87
|
+
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-sm shadow-sm"
|
|
88
88
|
/>
|
|
89
89
|
<InputOTPSlot
|
|
90
90
|
index={2}
|
|
91
|
-
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-
|
|
91
|
+
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-sm shadow-sm"
|
|
92
92
|
/>
|
|
93
93
|
<InputOTPSlot
|
|
94
94
|
index={3}
|
|
95
|
-
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-
|
|
95
|
+
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-sm shadow-sm"
|
|
96
96
|
/>
|
|
97
97
|
<InputOTPSlot
|
|
98
98
|
index={4}
|
|
99
|
-
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-
|
|
99
|
+
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-sm shadow-sm"
|
|
100
100
|
/>
|
|
101
101
|
<InputOTPSlot
|
|
102
102
|
index={5}
|
|
103
|
-
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-
|
|
103
|
+
className="h-12 w-12 text-lg font-semibold border-2 border-border bg-background rounded-sm shadow-sm"
|
|
104
104
|
/>
|
|
105
105
|
</InputOTPGroup>
|
|
106
106
|
</InputOTP>
|
|
@@ -113,7 +113,17 @@ export const OTPForm: React.FC = () => {
|
|
|
113
113
|
className="w-full h-11 text-base font-medium"
|
|
114
114
|
disabled={isLoading || otp.length < 6}
|
|
115
115
|
>
|
|
116
|
-
{isLoading ?
|
|
116
|
+
{isLoading ? (
|
|
117
|
+
<div className="flex items-center gap-2">
|
|
118
|
+
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
|
|
119
|
+
Verifying...
|
|
120
|
+
</div>
|
|
121
|
+
) : (
|
|
122
|
+
<div className="flex items-center gap-2">
|
|
123
|
+
<ShieldCheck className="w-5 h-5" />
|
|
124
|
+
Verify Code
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
117
127
|
</Button>
|
|
118
128
|
|
|
119
129
|
<div className="flex gap-3">
|
|
@@ -124,7 +134,10 @@ export const OTPForm: React.FC = () => {
|
|
|
124
134
|
disabled={isLoading}
|
|
125
135
|
className="flex-1 h-10"
|
|
126
136
|
>
|
|
127
|
-
|
|
137
|
+
<div className="flex items-center gap-2">
|
|
138
|
+
<ArrowLeft className="w-4 h-4" />
|
|
139
|
+
Back
|
|
140
|
+
</div>
|
|
128
141
|
</Button>
|
|
129
142
|
|
|
130
143
|
<Button
|
|
@@ -134,7 +147,10 @@ export const OTPForm: React.FC = () => {
|
|
|
134
147
|
disabled={isLoading}
|
|
135
148
|
className="flex-1 h-10"
|
|
136
149
|
>
|
|
137
|
-
|
|
150
|
+
<div className="flex items-center gap-2">
|
|
151
|
+
<RotateCw className="w-4 h-4" />
|
|
152
|
+
Resend
|
|
153
|
+
</div>
|
|
138
154
|
</Button>
|
|
139
155
|
</div>
|
|
140
156
|
</div>
|
|
@@ -81,7 +81,7 @@ export function DashboardSidebar() {
|
|
|
81
81
|
className={isMobile ? "h-10 w-10 flex-shrink-0" : "h-8 w-8 flex-shrink-0"}
|
|
82
82
|
/>
|
|
83
83
|
) : (
|
|
84
|
-
<div className={isMobile ? "h-10 w-10 bg-primary rounded-
|
|
84
|
+
<div className={isMobile ? "h-10 w-10 bg-primary rounded-sm flex items-center justify-center flex-shrink-0" : "h-8 w-8 bg-primary rounded-sm flex items-center justify-center flex-shrink-0"}>
|
|
85
85
|
<span className="text-primary-foreground font-bold text-sm">
|
|
86
86
|
{app.name.charAt(0).toUpperCase()}
|
|
87
87
|
</span>
|
|
@@ -61,7 +61,7 @@ export function DesktopUserMenu() {
|
|
|
61
61
|
{/* User Dropdown */}
|
|
62
62
|
<div className="relative">
|
|
63
63
|
<button
|
|
64
|
-
className="flex items-center gap-2 px-3 py-2 rounded-
|
|
64
|
+
className="flex items-center gap-2 px-3 py-2 rounded-sm text-sm font-medium transition-colors text-foreground hover:text-primary hover:bg-accent/50"
|
|
65
65
|
onClick={toggleUserMenu}
|
|
66
66
|
aria-haspopup="true"
|
|
67
67
|
aria-expanded={userMenuOpen}
|
|
@@ -85,15 +85,15 @@ export function DesktopUserMenu() {
|
|
|
85
85
|
/>
|
|
86
86
|
{/* Dropdown */}
|
|
87
87
|
<div
|
|
88
|
-
className="absolute top-full right-0 mt-2 w-48 rounded-
|
|
88
|
+
className="absolute top-full right-0 mt-2 w-48 rounded-sm shadow-sm backdrop-blur-xl z-[9996] bg-popover border border-border"
|
|
89
89
|
role="menu"
|
|
90
90
|
aria-label="User menu"
|
|
91
91
|
>
|
|
92
92
|
<div className="p-2">
|
|
93
93
|
{/* User info */}
|
|
94
|
-
<div className="px-3 py-2 text-sm mb-2 border-b
|
|
95
|
-
Signed in as
|
|
96
|
-
<div className="font-medium truncate text-foreground">
|
|
94
|
+
<div className="px-3 py-2 text-sm mb-2 border-b border-border">
|
|
95
|
+
<div className="text-muted-foreground">Signed in as:</div>
|
|
96
|
+
<div className="font-medium truncate text-popover-foreground mt-1">
|
|
97
97
|
{user?.email}
|
|
98
98
|
</div>
|
|
99
99
|
</div>
|
|
@@ -113,7 +113,7 @@ export function DesktopUserMenu() {
|
|
|
113
113
|
{/* Logout button */}
|
|
114
114
|
<button
|
|
115
115
|
onClick={handleLogout}
|
|
116
|
-
className="w-full flex items-center gap-2 px-3 py-2 text-sm rounded-
|
|
116
|
+
className="w-full flex items-center gap-2 px-3 py-2 text-sm rounded-sm transition-colors text-destructive hover:bg-destructive/[0.1]"
|
|
117
117
|
>
|
|
118
118
|
<LogOut className="size-4" />
|
|
119
119
|
<span>Sign out</span>
|
|
@@ -139,7 +139,7 @@ export function Footer() {
|
|
|
139
139
|
{/* Badge */}
|
|
140
140
|
{footer.badge && (
|
|
141
141
|
<div className="pt-2">
|
|
142
|
-
<span className="inline-flex items-center gap-2 px-3 py-1.5 rounded-
|
|
142
|
+
<span className="inline-flex items-center gap-2 px-3 py-1.5 rounded-sm bg-primary/10 hover:bg-primary/15 border border-primary/20 text-xs font-medium text-primary transition-colors">
|
|
143
143
|
<footer.badge.icon className="w-3.5 h-3.5" />
|
|
144
144
|
{footer.badge.text}
|
|
145
145
|
</span>
|
|
@@ -10,12 +10,11 @@
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { createPortal } from 'react-dom';
|
|
12
12
|
import Link from 'next/link';
|
|
13
|
-
import {
|
|
14
|
-
import { ButtonLink, Card, CardContent } from '@djangocfg/ui/components';
|
|
15
|
-
import { ThemeToggle } from '@djangocfg/ui/theme';
|
|
13
|
+
import { X } from 'lucide-react';
|
|
16
14
|
import { useAppContext } from '../../../context';
|
|
17
15
|
import { useAuth } from '../../../../../auth';
|
|
18
16
|
import { useNavigation } from '../../../hooks';
|
|
17
|
+
import { MobileMenuUserCard } from './MobileMenuUserCard';
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* Mobile Menu Component
|
|
@@ -37,34 +36,47 @@ export function MobileMenu() {
|
|
|
37
36
|
|
|
38
37
|
const { app, publicLayout, routes } = config;
|
|
39
38
|
|
|
40
|
-
//
|
|
41
|
-
const [
|
|
39
|
+
// Track if we should render (stays true during close animation)
|
|
40
|
+
const [shouldRender, setShouldRender] = React.useState(false);
|
|
42
41
|
|
|
43
|
-
//
|
|
42
|
+
// Track animation state separately
|
|
43
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
44
|
+
|
|
45
|
+
// Handle opening
|
|
44
46
|
React.useEffect(() => {
|
|
45
47
|
if (mobileMenuOpen) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
setShouldRender(true);
|
|
49
|
+
// Trigger animation after render
|
|
50
|
+
requestAnimationFrame(() => {
|
|
51
|
+
requestAnimationFrame(() => {
|
|
52
|
+
setIsOpen(true);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
48
55
|
} else {
|
|
49
|
-
|
|
56
|
+
// Start close animation
|
|
57
|
+
setIsOpen(false);
|
|
58
|
+
// Wait for animation to finish before unmounting
|
|
59
|
+
const timer = setTimeout(() => {
|
|
60
|
+
setShouldRender(false);
|
|
61
|
+
}, 300);
|
|
62
|
+
return () => clearTimeout(timer);
|
|
50
63
|
}
|
|
51
64
|
}, [mobileMenuOpen]);
|
|
52
65
|
|
|
53
66
|
const handleLogout = () => {
|
|
54
67
|
logout();
|
|
55
|
-
|
|
68
|
+
closeMobileMenu();
|
|
56
69
|
};
|
|
57
70
|
|
|
58
71
|
const handleClose = () => {
|
|
59
|
-
|
|
60
|
-
setTimeout(closeMobileMenu, 300); // Wait for animation
|
|
72
|
+
closeMobileMenu();
|
|
61
73
|
};
|
|
62
74
|
|
|
63
75
|
const handleNavigate = () => {
|
|
64
|
-
|
|
76
|
+
closeMobileMenu();
|
|
65
77
|
};
|
|
66
78
|
|
|
67
|
-
if (!
|
|
79
|
+
if (!shouldRender) return null;
|
|
68
80
|
|
|
69
81
|
// Portal to body to avoid z-index and positioning issues
|
|
70
82
|
if (typeof window === 'undefined') return null;
|
|
@@ -73,8 +85,8 @@ export function MobileMenu() {
|
|
|
73
85
|
<>
|
|
74
86
|
{/* Backdrop with fade animation */}
|
|
75
87
|
<div
|
|
76
|
-
className={`fixed inset-0 z-[
|
|
77
|
-
|
|
88
|
+
className={`fixed inset-0 z-[150] bg-black/50 backdrop-blur-sm transition-opacity duration-300 ease-in-out lg:hidden ${
|
|
89
|
+
isOpen ? 'opacity-100' : 'opacity-0'
|
|
78
90
|
}`}
|
|
79
91
|
onClick={handleClose}
|
|
80
92
|
aria-hidden="true"
|
|
@@ -82,8 +94,8 @@ export function MobileMenu() {
|
|
|
82
94
|
|
|
83
95
|
{/* Menu Content with slide animation */}
|
|
84
96
|
<div
|
|
85
|
-
className={`fixed top-0 right-0 bottom-0 w-80 z-[
|
|
86
|
-
|
|
97
|
+
className={`fixed top-0 right-0 bottom-0 w-80 z-[200] bg-popover border-l border-border shadow-2xl transition-transform duration-300 ease-in-out lg:hidden ${
|
|
98
|
+
isOpen ? 'translate-x-0' : 'translate-x-full'
|
|
87
99
|
}`}
|
|
88
100
|
role="dialog"
|
|
89
101
|
aria-modal="true"
|
|
@@ -104,7 +116,7 @@ export function MobileMenu() {
|
|
|
104
116
|
</div>
|
|
105
117
|
<button
|
|
106
118
|
onClick={handleClose}
|
|
107
|
-
className="p-2 rounded-
|
|
119
|
+
className="p-2 rounded-sm transition-colors hover:bg-accent/50"
|
|
108
120
|
aria-label="Close menu"
|
|
109
121
|
>
|
|
110
122
|
<X className="size-5" />
|
|
@@ -113,118 +125,16 @@ export function MobileMenu() {
|
|
|
113
125
|
|
|
114
126
|
{/* Scrollable Content */}
|
|
115
127
|
<div className="flex-1 overflow-y-auto p-4 space-y-6">
|
|
116
|
-
{/* User Menu Card
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
alt={user?.email || 'User'}
|
|
127
|
-
className="w-10 h-10 rounded-full object-cover"
|
|
128
|
-
/>
|
|
129
|
-
) : (
|
|
130
|
-
<User className="w-5 h-5 text-primary-foreground" />
|
|
131
|
-
)}
|
|
132
|
-
</div>
|
|
133
|
-
<div className="flex-1 min-w-0">
|
|
134
|
-
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
135
|
-
Signed in as
|
|
136
|
-
</p>
|
|
137
|
-
<p className="text-sm font-semibold truncate text-foreground">
|
|
138
|
-
{user?.email}
|
|
139
|
-
</p>
|
|
140
|
-
</div>
|
|
141
|
-
<div className="flex items-center gap-1">
|
|
142
|
-
<div className="size-2 rounded-full animate-pulse bg-green-500"></div>
|
|
143
|
-
<span className="text-xs font-medium text-green-600 dark:text-green-400">
|
|
144
|
-
Active
|
|
145
|
-
</span>
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
|
|
149
|
-
{/* Action Buttons */}
|
|
150
|
-
<div className="space-y-3">
|
|
151
|
-
{/* Dashboard link */}
|
|
152
|
-
{publicLayout.userMenu.dashboardPath && (
|
|
153
|
-
<ButtonLink
|
|
154
|
-
href={publicLayout.userMenu.dashboardPath}
|
|
155
|
-
variant="default"
|
|
156
|
-
size="sm"
|
|
157
|
-
className="w-full justify-center"
|
|
158
|
-
onClick={handleNavigate}
|
|
159
|
-
>
|
|
160
|
-
<Crown className="w-4 h-4 mr-2" />
|
|
161
|
-
Dashboard
|
|
162
|
-
</ButtonLink>
|
|
163
|
-
)}
|
|
164
|
-
|
|
165
|
-
{/* Profile link */}
|
|
166
|
-
<ButtonLink
|
|
167
|
-
href={publicLayout.userMenu.profilePath}
|
|
168
|
-
variant="outline"
|
|
169
|
-
size="sm"
|
|
170
|
-
className="w-full justify-center"
|
|
171
|
-
onClick={handleNavigate}
|
|
172
|
-
>
|
|
173
|
-
<Settings className="w-4 h-4 mr-2" />
|
|
174
|
-
Profile Settings
|
|
175
|
-
</ButtonLink>
|
|
176
|
-
|
|
177
|
-
{/* Logout */}
|
|
178
|
-
<button
|
|
179
|
-
onClick={handleLogout}
|
|
180
|
-
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm rounded-lg transition-colors border text-destructive border-destructive/30 hover:bg-destructive/10"
|
|
181
|
-
>
|
|
182
|
-
<LogOut className="w-4 h-4" />
|
|
183
|
-
Sign Out
|
|
184
|
-
</button>
|
|
185
|
-
|
|
186
|
-
{/* Theme toggle */}
|
|
187
|
-
<div className="flex justify-center pt-2 border-t border-border/30">
|
|
188
|
-
<ThemeToggle />
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
</CardContent>
|
|
192
|
-
</Card>
|
|
193
|
-
) : (
|
|
194
|
-
/* Guest Card */
|
|
195
|
-
<Card className="border-border bg-accent/30">
|
|
196
|
-
<CardContent className="p-4">
|
|
197
|
-
<div className="text-center space-y-4">
|
|
198
|
-
<div className="w-12 h-12 rounded-full flex items-center justify-center mx-auto bg-muted flex-shrink-0">
|
|
199
|
-
<User className="w-6 h-6 text-muted-foreground" />
|
|
200
|
-
</div>
|
|
201
|
-
<div>
|
|
202
|
-
<p className="text-sm font-medium mb-1 text-foreground">
|
|
203
|
-
Welcome!
|
|
204
|
-
</p>
|
|
205
|
-
<p className="text-xs text-muted-foreground">
|
|
206
|
-
Sign in to access your dashboard
|
|
207
|
-
</p>
|
|
208
|
-
</div>
|
|
209
|
-
<ButtonLink
|
|
210
|
-
href={routes.auth}
|
|
211
|
-
variant="default"
|
|
212
|
-
size="lg"
|
|
213
|
-
className="w-full justify-center"
|
|
214
|
-
onClick={handleNavigate}
|
|
215
|
-
>
|
|
216
|
-
<User className="w-5 h-5 mr-2" />
|
|
217
|
-
Sign In
|
|
218
|
-
</ButtonLink>
|
|
219
|
-
|
|
220
|
-
{/* Theme toggle */}
|
|
221
|
-
<div className="flex justify-center pt-2 border-t border-border/30">
|
|
222
|
-
<ThemeToggle />
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
</CardContent>
|
|
226
|
-
</Card>
|
|
227
|
-
)}
|
|
128
|
+
{/* User Menu Card */}
|
|
129
|
+
<MobileMenuUserCard
|
|
130
|
+
isAuthenticated={isAuthenticated}
|
|
131
|
+
user={user}
|
|
132
|
+
dashboardPath={publicLayout.userMenu.dashboardPath}
|
|
133
|
+
profilePath={publicLayout.userMenu.profilePath}
|
|
134
|
+
authPath={routes.auth}
|
|
135
|
+
onLogout={handleLogout}
|
|
136
|
+
onNavigate={handleNavigate}
|
|
137
|
+
/>
|
|
228
138
|
|
|
229
139
|
{/* Navigation Sections */}
|
|
230
140
|
<div className="space-y-6">
|
|
@@ -238,7 +148,7 @@ export function MobileMenu() {
|
|
|
238
148
|
<div key={section.title} className="space-y-2">
|
|
239
149
|
<Link
|
|
240
150
|
href={item.path}
|
|
241
|
-
className={`block px-4 py-3 rounded-
|
|
151
|
+
className={`block px-4 py-3 rounded-sm text-base font-medium transition-colors ${
|
|
242
152
|
isActive(item.path)
|
|
243
153
|
? 'text-primary border border-primary/20 bg-primary/[0.1]'
|
|
244
154
|
: 'text-muted-foreground hover:text-primary hover:bg-accent/50'
|
|
@@ -262,7 +172,7 @@ export function MobileMenu() {
|
|
|
262
172
|
<Link
|
|
263
173
|
key={item.path}
|
|
264
174
|
href={item.path}
|
|
265
|
-
className={`block px-4 py-3 rounded-
|
|
175
|
+
className={`block px-4 py-3 rounded-sm text-base font-medium transition-colors ${
|
|
266
176
|
isActive(item.path)
|
|
267
177
|
? 'bg-accent text-accent-foreground'
|
|
268
178
|
: 'text-foreground hover:bg-accent hover:text-accent-foreground'
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mobile Menu User Card Component
|
|
3
|
+
*
|
|
4
|
+
* Displays user information and action buttons in mobile menu
|
|
5
|
+
* - Authenticated: shows user info with dashboard, profile, and logout
|
|
6
|
+
* - Guest: shows welcome message with sign in button
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use client';
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { Crown, LogOut, Settings, User } from 'lucide-react';
|
|
13
|
+
import { Button, ButtonLink, Card, CardContent } from '@djangocfg/ui/components';
|
|
14
|
+
import { ThemeToggle } from '@djangocfg/ui/theme';
|
|
15
|
+
|
|
16
|
+
interface MobileMenuUserCardProps {
|
|
17
|
+
isAuthenticated: boolean;
|
|
18
|
+
user?: {
|
|
19
|
+
email?: string;
|
|
20
|
+
avatar?: string;
|
|
21
|
+
} | null;
|
|
22
|
+
dashboardPath?: string;
|
|
23
|
+
profilePath: string;
|
|
24
|
+
authPath: string;
|
|
25
|
+
onLogout: () => void;
|
|
26
|
+
onNavigate: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function MobileMenuUserCard({
|
|
30
|
+
isAuthenticated,
|
|
31
|
+
user,
|
|
32
|
+
dashboardPath,
|
|
33
|
+
profilePath,
|
|
34
|
+
authPath,
|
|
35
|
+
onLogout,
|
|
36
|
+
onNavigate,
|
|
37
|
+
}: MobileMenuUserCardProps) {
|
|
38
|
+
if (isAuthenticated) {
|
|
39
|
+
return (
|
|
40
|
+
<Card className="border-primary/20 shadow-lg !bg-accent/50">
|
|
41
|
+
<CardContent className="p-4">
|
|
42
|
+
{/* User Info Header */}
|
|
43
|
+
<div className="flex items-center gap-3 mb-4 p-3 rounded-sm border border-border bg-accent/70">
|
|
44
|
+
<div className="w-10 h-10 rounded-full flex items-center justify-center bg-primary flex-shrink-0 overflow-hidden relative">
|
|
45
|
+
{user?.avatar ? (
|
|
46
|
+
<img
|
|
47
|
+
src={user.avatar}
|
|
48
|
+
alt={user?.email || 'User'}
|
|
49
|
+
className="w-10 h-10 rounded-full object-cover"
|
|
50
|
+
/>
|
|
51
|
+
) : (
|
|
52
|
+
<User className="w-5 h-5 text-primary-foreground" />
|
|
53
|
+
)}
|
|
54
|
+
{/* Active indicator */}
|
|
55
|
+
<div className="absolute -bottom-0.5 -right-0.5 size-3 rounded-full bg-green-500 border-2 border-background" />
|
|
56
|
+
</div>
|
|
57
|
+
<div className="flex-1 min-w-0">
|
|
58
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
59
|
+
Signed in as
|
|
60
|
+
</p>
|
|
61
|
+
<p className="text-sm font-semibold truncate text-foreground">
|
|
62
|
+
{user?.email}
|
|
63
|
+
</p>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{/* Action Buttons */}
|
|
68
|
+
<div className="space-y-3">
|
|
69
|
+
{/* Dashboard link */}
|
|
70
|
+
{dashboardPath && (
|
|
71
|
+
<ButtonLink
|
|
72
|
+
href={dashboardPath}
|
|
73
|
+
variant="default"
|
|
74
|
+
size="sm"
|
|
75
|
+
className="w-full h-9"
|
|
76
|
+
onClick={onNavigate}
|
|
77
|
+
>
|
|
78
|
+
<Crown className="w-4 h-4 mr-2" />
|
|
79
|
+
Dashboard
|
|
80
|
+
</ButtonLink>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
{/* Quick Actions - Icons only */}
|
|
84
|
+
<div className="flex items-center justify-center gap-2 pt-3 mt-1 border-t border-border/30">
|
|
85
|
+
{/* Profile Settings */}
|
|
86
|
+
<ButtonLink
|
|
87
|
+
href={profilePath}
|
|
88
|
+
variant="ghost"
|
|
89
|
+
size="icon"
|
|
90
|
+
className="h-9 w-9"
|
|
91
|
+
onClick={onNavigate}
|
|
92
|
+
aria-label="Profile Settings"
|
|
93
|
+
>
|
|
94
|
+
<Settings className="h-5 w-5" />
|
|
95
|
+
</ButtonLink>
|
|
96
|
+
|
|
97
|
+
{/* Theme Toggle */}
|
|
98
|
+
<ThemeToggle />
|
|
99
|
+
|
|
100
|
+
{/* Sign Out */}
|
|
101
|
+
<Button
|
|
102
|
+
onClick={onLogout}
|
|
103
|
+
variant="ghost"
|
|
104
|
+
size="icon"
|
|
105
|
+
className="h-9 w-9 text-destructive hover:bg-destructive/10 hover:text-destructive"
|
|
106
|
+
aria-label="Sign Out"
|
|
107
|
+
>
|
|
108
|
+
<LogOut className="h-5 w-5" />
|
|
109
|
+
</Button>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</CardContent>
|
|
113
|
+
</Card>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Guest Card
|
|
118
|
+
return (
|
|
119
|
+
<Card className="border-border !bg-accent/50">
|
|
120
|
+
<CardContent className="p-4">
|
|
121
|
+
<div className="text-center space-y-4">
|
|
122
|
+
<div className="w-12 h-12 rounded-full flex items-center justify-center mx-auto bg-muted">
|
|
123
|
+
<User className="w-6 h-6 text-muted-foreground" />
|
|
124
|
+
</div>
|
|
125
|
+
<div>
|
|
126
|
+
<p className="text-sm font-medium mb-1 text-foreground">Welcome!</p>
|
|
127
|
+
<p className="text-xs text-muted-foreground">
|
|
128
|
+
Sign in to access your dashboard
|
|
129
|
+
</p>
|
|
130
|
+
</div>
|
|
131
|
+
<ButtonLink
|
|
132
|
+
href={authPath}
|
|
133
|
+
variant="default"
|
|
134
|
+
size="default"
|
|
135
|
+
className="w-full"
|
|
136
|
+
onClick={onNavigate}
|
|
137
|
+
>
|
|
138
|
+
<User className="w-5 h-5 mr-2" />
|
|
139
|
+
Sign In
|
|
140
|
+
</ButtonLink>
|
|
141
|
+
|
|
142
|
+
{/* Theme toggle */}
|
|
143
|
+
<div className="flex justify-center pt-2 border-t border-border/30">
|
|
144
|
+
<ThemeToggle />
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</CardContent>
|
|
148
|
+
</Card>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
@@ -49,7 +49,7 @@ export function Navigation() {
|
|
|
49
49
|
const { app, publicLayout } = config;
|
|
50
50
|
|
|
51
51
|
return (
|
|
52
|
-
<nav className="sticky top-0 w-full border-b backdrop-blur-xl z-[
|
|
52
|
+
<nav className="sticky top-0 w-full border-b backdrop-blur-xl z-[100] bg-background/80 border-border/30">
|
|
53
53
|
<div className="w-full px-4 sm:px-6 lg:px-8">
|
|
54
54
|
<div className="flex items-center justify-between h-16">
|
|
55
55
|
{/* Left side - Logo and Navigation Menu */}
|
|
@@ -143,7 +143,7 @@ export function Navigation() {
|
|
|
143
143
|
{isMobile && (
|
|
144
144
|
<button
|
|
145
145
|
onClick={toggleMobileMenu}
|
|
146
|
-
className="p-3 rounded-
|
|
146
|
+
className="p-3 rounded-sm 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
147
|
aria-label="Toggle mobile menu"
|
|
148
148
|
>
|
|
149
149
|
<Menu className="size-5" />
|