@djangocfg/layouts 1.0.3 → 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 +2 -2
- package/src/layouts/AppLayout/layouts/AuthLayout/OTPForm.tsx +6 -6
- 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/components/CreatePaymentDialog.tsx +46 -24
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +94 -25
- package/src/layouts/PaymentsLayout/context/RootPaymentsContext.tsx +6 -11
- package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeysList.tsx +2 -2
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +2 -2
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +3 -3
- 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,9 +53,9 @@
|
|
|
53
53
|
"check": "tsc --noEmit"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@djangocfg/api": "^1.0.
|
|
57
|
-
"@djangocfg/og-image": "^1.0.
|
|
58
|
-
"@djangocfg/ui": "^1.0.
|
|
56
|
+
"@djangocfg/api": "^1.0.4",
|
|
57
|
+
"@djangocfg/og-image": "^1.0.4",
|
|
58
|
+
"@djangocfg/ui": "^1.0.4",
|
|
59
59
|
"@hookform/resolvers": "^5.2.0",
|
|
60
60
|
"consola": "^3.4.2",
|
|
61
61
|
"lucide-react": "^0.468.0",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"vidstack": "0.6.15"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@djangocfg/typescript-config": "^1.0.
|
|
79
|
+
"@djangocfg/typescript-config": "^1.0.4",
|
|
80
80
|
"@types/node": "^24.7.2",
|
|
81
81
|
"@types/react": "19.2.2",
|
|
82
82
|
"@types/react-dom": "19.2.1",
|
|
@@ -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()}
|
|
@@ -78,7 +78,7 @@ export const AuthHelp: React.FC<AuthHelpProps> = ({
|
|
|
78
78
|
|
|
79
79
|
return (
|
|
80
80
|
<div
|
|
81
|
-
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}`}
|
|
82
82
|
>
|
|
83
83
|
<div className="flex items-start space-x-2">
|
|
84
84
|
{getChannelIcon()}
|
|
@@ -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>
|
|
@@ -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" />
|
|
@@ -30,7 +30,7 @@ import { Plus, RefreshCw } from 'lucide-react';
|
|
|
30
30
|
import { useForm } from 'react-hook-form';
|
|
31
31
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
32
32
|
import { usePaymentsContext, useRootPaymentsContext } from '@djangocfg/api/cfg/contexts';
|
|
33
|
-
import { Schemas
|
|
33
|
+
import { Schemas } from '@djangocfg/api/cfg/generated';
|
|
34
34
|
import { paymentsLogger } from '../../../utils/logger';
|
|
35
35
|
import { PAYMENTS_DIALOG_EVENTS, closePaymentsDialog } from '../events';
|
|
36
36
|
import { openPaymentDetails } from './PaymentDetailsDialog';
|
|
@@ -39,7 +39,6 @@ import type { ComboboxOption } from '@djangocfg/ui';
|
|
|
39
39
|
|
|
40
40
|
const { PaymentCreateRequestSchema } = Schemas;
|
|
41
41
|
type PaymentCreateRequest = Schemas.PaymentCreateRequest;
|
|
42
|
-
const { PaymentCreateRequestCurrencyCode, PaymentCreateRequestProvider } = Enums;
|
|
43
42
|
|
|
44
43
|
export const CreatePaymentDialog: React.FC = () => {
|
|
45
44
|
const [open, setOpen] = useState(false);
|
|
@@ -55,33 +54,54 @@ export const CreatePaymentDialog: React.FC = () => {
|
|
|
55
54
|
resolver: zodResolver(PaymentCreateRequestSchema),
|
|
56
55
|
defaultValues: {
|
|
57
56
|
amount_usd: 10,
|
|
58
|
-
currency_code:
|
|
57
|
+
currency_code: 'USDT', // Default to USDT
|
|
59
58
|
},
|
|
60
59
|
});
|
|
61
60
|
|
|
62
61
|
// Group currencies by token and create combobox options
|
|
62
|
+
// Backend automatically selects network, so we group by currency code
|
|
63
63
|
const currencyOptions = useMemo((): ComboboxOption[] => {
|
|
64
64
|
if (!providerCurrencies?.results) return [];
|
|
65
65
|
|
|
66
66
|
const enabledCurrencies = providerCurrencies.results.filter(pc => pc.is_enabled);
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
// Group by currency code and collect all networks
|
|
69
|
+
const currencyMap = new Map<string, { pc: ProviderCurrency, networks: string[] }>();
|
|
70
|
+
|
|
71
|
+
for (const pc of enabledCurrencies) {
|
|
72
|
+
const code = pc.currency.code.toUpperCase();
|
|
73
|
+
if (!currencyMap.has(code)) {
|
|
74
|
+
currencyMap.set(code, { pc, networks: [] });
|
|
75
|
+
}
|
|
76
|
+
if (pc.network?.name) {
|
|
77
|
+
currencyMap.get(code)!.networks.push(pc.network.name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Array.from(currencyMap.values()).map(({ pc, networks }) => {
|
|
82
|
+
// Show network info only if there's exactly one network, otherwise backend will choose
|
|
83
|
+
let label = pc.currency.code;
|
|
84
|
+
let description = pc.currency.name;
|
|
85
|
+
|
|
86
|
+
if (networks.length === 1) {
|
|
87
|
+
label = `${pc.currency.code} (${networks[0]})`;
|
|
88
|
+
} else if (networks.length > 1) {
|
|
89
|
+
description = `${pc.currency.name} • Multiple networks available`;
|
|
90
|
+
}
|
|
72
91
|
|
|
73
92
|
return {
|
|
74
|
-
value
|
|
93
|
+
// Use currency code (e.g. "USDT") as value for API
|
|
94
|
+
value: pc.currency.code.toUpperCase(),
|
|
75
95
|
label,
|
|
76
96
|
description,
|
|
77
97
|
};
|
|
78
98
|
});
|
|
79
99
|
}, [providerCurrencies]);
|
|
80
100
|
|
|
81
|
-
// Get ProviderCurrency by
|
|
101
|
+
// Get first ProviderCurrency by currency code (e.g. "USDT")
|
|
82
102
|
const getProviderCurrency = (currencyCode: string) => {
|
|
83
103
|
return providerCurrencies?.results?.find(
|
|
84
|
-
pc => pc.
|
|
104
|
+
pc => pc.is_enabled && pc.currency.code.toUpperCase() === currencyCode.toUpperCase()
|
|
85
105
|
);
|
|
86
106
|
};
|
|
87
107
|
|
|
@@ -127,13 +147,17 @@ export const CreatePaymentDialog: React.FC = () => {
|
|
|
127
147
|
try {
|
|
128
148
|
setIsSubmitting(true);
|
|
129
149
|
|
|
130
|
-
const
|
|
150
|
+
const result = await createPayment(data);
|
|
131
151
|
handleClose();
|
|
132
152
|
closePaymentsDialog();
|
|
133
153
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
154
|
+
// The API returns a wrapped response with { success, message, payment }
|
|
155
|
+
// Extract the payment ID from the result
|
|
156
|
+
const paymentData = result as any;
|
|
157
|
+
const paymentId = paymentData?.payment?.id || paymentData?.id;
|
|
158
|
+
|
|
159
|
+
if (paymentId) {
|
|
160
|
+
openPaymentDetails(String(paymentId));
|
|
137
161
|
}
|
|
138
162
|
} catch (error) {
|
|
139
163
|
paymentsLogger.error('Failed to create payment:', error);
|
|
@@ -214,15 +238,13 @@ export const CreatePaymentDialog: React.FC = () => {
|
|
|
214
238
|
const pc = getProviderCurrency(option.value);
|
|
215
239
|
if (!pc) return option.label;
|
|
216
240
|
return (
|
|
217
|
-
<div className="flex items-center gap-2 flex-1">
|
|
218
|
-
<TokenIcon symbol={pc.currency.code} size={
|
|
219
|
-
<div className="flex flex-col flex-1">
|
|
220
|
-
<span className="font-medium">
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
</span>
|
|
225
|
-
)}
|
|
241
|
+
<div className="flex items-center gap-2 flex-1 min-w-0">
|
|
242
|
+
<TokenIcon symbol={pc.currency.code} size={20} className="shrink-0" />
|
|
243
|
+
<div className="flex flex-col flex-1 min-w-0">
|
|
244
|
+
<span className="font-medium truncate">
|
|
245
|
+
{pc.currency.code}
|
|
246
|
+
{pc.network && ` (${pc.network.name})`}
|
|
247
|
+
</span>
|
|
226
248
|
</div>
|
|
227
249
|
</div>
|
|
228
250
|
);
|
|
@@ -244,7 +266,7 @@ export const CreatePaymentDialog: React.FC = () => {
|
|
|
244
266
|
if (!pc) return null;
|
|
245
267
|
|
|
246
268
|
return (
|
|
247
|
-
<div className="rounded-
|
|
269
|
+
<div className="rounded-sm bg-muted p-4 space-y-3">
|
|
248
270
|
{/* Amount to Send in Crypto */}
|
|
249
271
|
<div className="flex items-center justify-between">
|
|
250
272
|
<span className="text-sm text-muted-foreground">You will send</span>
|
|
@@ -15,34 +15,58 @@ import {
|
|
|
15
15
|
DialogTitle,
|
|
16
16
|
Button,
|
|
17
17
|
TokenIcon,
|
|
18
|
-
useEventListener,
|
|
19
18
|
} from '@djangocfg/ui';
|
|
20
|
-
import { Copy, ExternalLink, CheckCircle2, Clock, XCircle, AlertCircle } from 'lucide-react';
|
|
21
|
-
import
|
|
19
|
+
import { Copy, ExternalLink, CheckCircle2, Clock, XCircle, AlertCircle, RefreshCw } from 'lucide-react';
|
|
20
|
+
import { Hooks, api } from '@djangocfg/api';
|
|
21
|
+
import type { API } from '@djangocfg/api';
|
|
22
22
|
|
|
23
23
|
export const PAYMENT_DETAILS_EVENTS = {
|
|
24
24
|
OPEN_PAYMENT_DETAILS: 'open-payment-details',
|
|
25
25
|
CLOSE_PAYMENT_DETAILS: 'close-payment-details',
|
|
26
26
|
} as const;
|
|
27
27
|
|
|
28
|
-
export
|
|
29
|
-
payment?: Payment | null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const PaymentDetailsDialog: React.FC<PaymentDetailsDialogProps> = ({ payment: initialPayment }) => {
|
|
28
|
+
export const PaymentDetailsDialog: React.FC = () => {
|
|
33
29
|
const [open, setOpen] = useState(false);
|
|
34
|
-
const [
|
|
30
|
+
const [paymentId, setPaymentId] = useState<string | null>(null);
|
|
35
31
|
const [copied, setCopied] = useState(false);
|
|
36
32
|
const [timeLeft, setTimeLeft] = useState<string>('');
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
34
|
+
// Load payment data by ID using hook
|
|
35
|
+
// Only fetch when dialog is open and paymentId is set
|
|
36
|
+
const shouldFetch = open && !!paymentId;
|
|
37
|
+
const { data: payment, isLoading, error, mutate } = Hooks.usePaymentsPaymentRetrieve(
|
|
38
|
+
shouldFetch ? paymentId : '',
|
|
39
|
+
api as unknown as API
|
|
40
|
+
);
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
// Debug logging
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (error) {
|
|
45
|
+
console.error('Payment loading error:', error);
|
|
46
|
+
}
|
|
47
|
+
}, [error]);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const handleOpen = (event: Event) => {
|
|
51
|
+
const customEvent = event as CustomEvent<{ paymentId: string }>;
|
|
52
|
+
console.log('Opening payment details for ID:', customEvent.detail.paymentId);
|
|
53
|
+
setPaymentId(customEvent.detail.paymentId);
|
|
54
|
+
setOpen(true);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleClose = () => {
|
|
58
|
+
setOpen(false);
|
|
59
|
+
setPaymentId(null);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
window.addEventListener(PAYMENT_DETAILS_EVENTS.OPEN_PAYMENT_DETAILS, handleOpen);
|
|
63
|
+
window.addEventListener(PAYMENT_DETAILS_EVENTS.CLOSE_PAYMENT_DETAILS, handleClose);
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
window.removeEventListener(PAYMENT_DETAILS_EVENTS.OPEN_PAYMENT_DETAILS, handleOpen);
|
|
67
|
+
window.removeEventListener(PAYMENT_DETAILS_EVENTS.CLOSE_PAYMENT_DETAILS, handleClose);
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
46
70
|
|
|
47
71
|
const handleClose = () => {
|
|
48
72
|
setOpen(false);
|
|
@@ -99,7 +123,45 @@ export const PaymentDetailsDialog: React.FC<PaymentDetailsDialogProps> = ({ paym
|
|
|
99
123
|
}
|
|
100
124
|
};
|
|
101
125
|
|
|
102
|
-
if (!
|
|
126
|
+
if (!open) return null;
|
|
127
|
+
|
|
128
|
+
// Loading state
|
|
129
|
+
if (isLoading) {
|
|
130
|
+
return (
|
|
131
|
+
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
132
|
+
<DialogContent className="sm:max-w-lg">
|
|
133
|
+
<DialogHeader>
|
|
134
|
+
<DialogTitle>Payment Details</DialogTitle>
|
|
135
|
+
<DialogDescription>Loading payment information...</DialogDescription>
|
|
136
|
+
</DialogHeader>
|
|
137
|
+
<div className="flex items-center justify-center py-12">
|
|
138
|
+
<RefreshCw className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
139
|
+
</div>
|
|
140
|
+
</DialogContent>
|
|
141
|
+
</Dialog>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Error state - only show error if we actually tried to fetch and failed
|
|
146
|
+
if (shouldFetch && !isLoading && (error || !payment)) {
|
|
147
|
+
return (
|
|
148
|
+
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
149
|
+
<DialogContent className="sm:max-w-lg">
|
|
150
|
+
<DialogHeader>
|
|
151
|
+
<DialogTitle>Payment Details</DialogTitle>
|
|
152
|
+
<DialogDescription>Failed to load payment information</DialogDescription>
|
|
153
|
+
</DialogHeader>
|
|
154
|
+
<div className="flex flex-col items-center justify-center py-12 space-y-4">
|
|
155
|
+
<XCircle className="h-12 w-12 text-destructive" />
|
|
156
|
+
<p className="text-sm text-muted-foreground">
|
|
157
|
+
{error ? `Error: ${error}` : 'Payment not found'}
|
|
158
|
+
</p>
|
|
159
|
+
<Button onClick={() => mutate()}>Try Again</Button>
|
|
160
|
+
</div>
|
|
161
|
+
</DialogContent>
|
|
162
|
+
</Dialog>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
103
165
|
|
|
104
166
|
const statusInfo = getStatusInfo();
|
|
105
167
|
const StatusIcon = statusInfo.icon;
|
|
@@ -121,7 +183,7 @@ export const PaymentDetailsDialog: React.FC<PaymentDetailsDialogProps> = ({ paym
|
|
|
121
183
|
|
|
122
184
|
<div className="space-y-6">
|
|
123
185
|
{/* Status Badge */}
|
|
124
|
-
<div className={`flex items-center gap-3 p-4 rounded-
|
|
186
|
+
<div className={`flex items-center gap-3 p-4 rounded-sm ${statusInfo.bg}`}>
|
|
125
187
|
<StatusIcon className={`h-5 w-5 ${statusInfo.color}`} />
|
|
126
188
|
<div className="flex-1">
|
|
127
189
|
<div className="font-semibold capitalize">{payment.status}</div>
|
|
@@ -135,10 +197,10 @@ export const PaymentDetailsDialog: React.FC<PaymentDetailsDialogProps> = ({ paym
|
|
|
135
197
|
|
|
136
198
|
{/* Amount Information */}
|
|
137
199
|
<div className="space-y-3">
|
|
138
|
-
<div className="flex items-center justify-between p-4 bg-muted rounded-
|
|
200
|
+
<div className="flex items-center justify-between p-4 bg-muted rounded-sm">
|
|
139
201
|
<span className="text-sm text-muted-foreground">Amount to send</span>
|
|
140
202
|
<div className="flex items-center gap-2">
|
|
141
|
-
<TokenIcon symbol={payment.currency || 'BTC'} size={20} />
|
|
203
|
+
<TokenIcon symbol={String(payment.currency || 'BTC')} size={20} />
|
|
142
204
|
<span className="font-mono font-bold text-lg">
|
|
143
205
|
{payment.amount_crypto?.toFixed(8)} {payment.currency}
|
|
144
206
|
</span>
|
|
@@ -150,6 +212,13 @@ export const PaymentDetailsDialog: React.FC<PaymentDetailsDialogProps> = ({ paym
|
|
|
150
212
|
<span className="font-semibold text-lg">${payment.amount_usd?.toFixed(2)} USD</span>
|
|
151
213
|
</div>
|
|
152
214
|
|
|
215
|
+
{payment.provider_payment_id && (
|
|
216
|
+
<div className="flex items-center justify-between px-4">
|
|
217
|
+
<span className="text-sm text-muted-foreground">Payment Order #</span>
|
|
218
|
+
<span className="font-mono font-medium">{payment.provider_payment_id}</span>
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
|
|
153
222
|
{payment.network && (
|
|
154
223
|
<div className="flex items-center justify-between px-4">
|
|
155
224
|
<span className="text-sm text-muted-foreground">Network</span>
|
|
@@ -160,7 +229,7 @@ export const PaymentDetailsDialog: React.FC<PaymentDetailsDialogProps> = ({ paym
|
|
|
160
229
|
|
|
161
230
|
{/* QR Code */}
|
|
162
231
|
{qrCodeUrl && payment.status === 'pending' && (
|
|
163
|
-
<div className="flex justify-center p-6 bg-white rounded-
|
|
232
|
+
<div className="flex justify-center p-6 bg-white rounded-sm">
|
|
164
233
|
<img src={qrCodeUrl} alt="Payment QR Code" className="w-48 h-48" />
|
|
165
234
|
</div>
|
|
166
235
|
)}
|
|
@@ -170,7 +239,7 @@ export const PaymentDetailsDialog: React.FC<PaymentDetailsDialogProps> = ({ paym
|
|
|
170
239
|
<div className="space-y-2">
|
|
171
240
|
<label className="text-sm font-medium">Payment Address</label>
|
|
172
241
|
<div className="flex items-center gap-2">
|
|
173
|
-
<div className="flex-1 p-3 bg-muted rounded-
|
|
242
|
+
<div className="flex-1 p-3 bg-muted rounded-sm font-mono text-sm break-all">
|
|
174
243
|
{payment.pay_address}
|
|
175
244
|
</div>
|
|
176
245
|
<Button
|
|
@@ -193,7 +262,7 @@ export const PaymentDetailsDialog: React.FC<PaymentDetailsDialogProps> = ({ paym
|
|
|
193
262
|
{payment.transaction_hash && (
|
|
194
263
|
<div className="space-y-2">
|
|
195
264
|
<label className="text-sm font-medium">Transaction Hash</label>
|
|
196
|
-
<div className="p-3 bg-muted rounded-
|
|
265
|
+
<div className="p-3 bg-muted rounded-sm font-mono text-sm break-all">
|
|
197
266
|
{payment.transaction_hash}
|
|
198
267
|
</div>
|
|
199
268
|
</div>
|
|
@@ -241,10 +310,10 @@ export const PaymentDetailsDialog: React.FC<PaymentDetailsDialogProps> = ({ paym
|
|
|
241
310
|
};
|
|
242
311
|
|
|
243
312
|
// Helper function to open payment details dialog
|
|
244
|
-
export const openPaymentDetails = (
|
|
313
|
+
export const openPaymentDetails = (paymentId: string) => {
|
|
245
314
|
window.dispatchEvent(
|
|
246
315
|
new CustomEvent(PAYMENT_DETAILS_EVENTS.OPEN_PAYMENT_DETAILS, {
|
|
247
|
-
detail: {
|
|
316
|
+
detail: { paymentId },
|
|
248
317
|
})
|
|
249
318
|
);
|
|
250
319
|
};
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React, { createContext, useContext, useEffect, type ReactNode } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
usePaymentsCurrenciesList,
|
|
7
|
-
usePaymentsProviderCurrenciesList,
|
|
8
|
-
usePaymentsNetworksList,
|
|
9
|
-
} from '@djangocfg/api/cfg';
|
|
10
|
-
import type { API } from '@djangocfg/api/cfg';
|
|
4
|
+
import { api, Hooks } from '@djangocfg/api';
|
|
5
|
+
import type { API } from '@djangocfg/api';
|
|
11
6
|
import type {
|
|
12
7
|
Currency,
|
|
13
8
|
PaginatedCurrencyListList,
|
|
@@ -15,7 +10,7 @@ import type {
|
|
|
15
10
|
PaginatedProviderCurrencyList,
|
|
16
11
|
Network,
|
|
17
12
|
PaginatedNetworkList,
|
|
18
|
-
} from '@djangocfg/api
|
|
13
|
+
} from '@djangocfg/api';
|
|
19
14
|
|
|
20
15
|
// ─────────────────────────────────────────────────────────────────────────
|
|
21
16
|
// Context Type
|
|
@@ -58,7 +53,7 @@ export function RootPaymentsProvider({ children }: { children: ReactNode }) {
|
|
|
58
53
|
error: currenciesError,
|
|
59
54
|
isLoading: isLoadingCurrencies,
|
|
60
55
|
mutate: mutateCurrencies,
|
|
61
|
-
} = usePaymentsCurrenciesList({}, api as unknown as API);
|
|
56
|
+
} = Hooks.usePaymentsCurrenciesList({}, api as unknown as API);
|
|
62
57
|
|
|
63
58
|
// List all provider currencies
|
|
64
59
|
const {
|
|
@@ -66,7 +61,7 @@ export function RootPaymentsProvider({ children }: { children: ReactNode }) {
|
|
|
66
61
|
error: providerCurrenciesError,
|
|
67
62
|
isLoading: isLoadingProviderCurrencies,
|
|
68
63
|
mutate: mutateProviderCurrencies,
|
|
69
|
-
} = usePaymentsProviderCurrenciesList({}, api as unknown as API);
|
|
64
|
+
} = Hooks.usePaymentsProviderCurrenciesList({}, api as unknown as API);
|
|
70
65
|
|
|
71
66
|
// List all networks
|
|
72
67
|
const {
|
|
@@ -74,7 +69,7 @@ export function RootPaymentsProvider({ children }: { children: ReactNode }) {
|
|
|
74
69
|
error: networksError,
|
|
75
70
|
isLoading: isLoadingNetworks,
|
|
76
71
|
mutate: mutateNetworks,
|
|
77
|
-
} = usePaymentsNetworksList({}, api as unknown as API);
|
|
72
|
+
} = Hooks.usePaymentsNetworksList({}, api as unknown as API);
|
|
78
73
|
|
|
79
74
|
const refreshCurrencies = async () => {
|
|
80
75
|
await mutateCurrencies();
|
|
@@ -88,7 +88,7 @@ export const ApiKeysList: React.FC = () => {
|
|
|
88
88
|
<CardContent>
|
|
89
89
|
<div className="space-y-4">
|
|
90
90
|
{Array.from({ length: 3 }).map((_, i) => (
|
|
91
|
-
<div key={i} className="flex items-center justify-between p-4 border rounded-
|
|
91
|
+
<div key={i} className="flex items-center justify-between p-4 border rounded-sm">
|
|
92
92
|
<div className="space-y-2 flex-1">
|
|
93
93
|
<Skeleton className="h-4 w-32" />
|
|
94
94
|
<Skeleton className="h-3 w-48" />
|
|
@@ -131,7 +131,7 @@ export const ApiKeysList: React.FC = () => {
|
|
|
131
131
|
{keysList.map((key) => (
|
|
132
132
|
<div
|
|
133
133
|
key={key.id}
|
|
134
|
-
className="flex items-center justify-between p-4 border rounded-
|
|
134
|
+
className="flex items-center justify-between p-4 border rounded-sm hover:bg-accent/50 transition-colors"
|
|
135
135
|
>
|
|
136
136
|
<div className="flex-1 min-w-0">
|
|
137
137
|
<div className="flex items-center gap-3 mb-2">
|
|
@@ -72,7 +72,7 @@ export const RecentPayments: React.FC = () => {
|
|
|
72
72
|
</CardHeader>
|
|
73
73
|
<CardContent className="space-y-3">
|
|
74
74
|
{Array.from({ length: 5 }).map((_, i) => (
|
|
75
|
-
<div key={i} className="flex items-center justify-between p-3 border rounded-
|
|
75
|
+
<div key={i} className="flex items-center justify-between p-3 border rounded-sm">
|
|
76
76
|
<div className="space-y-2">
|
|
77
77
|
<Skeleton className="h-4 w-32" />
|
|
78
78
|
<Skeleton className="h-3 w-24" />
|
|
@@ -112,7 +112,7 @@ export const RecentPayments: React.FC = () => {
|
|
|
112
112
|
{payments.map((payment) => (
|
|
113
113
|
<div
|
|
114
114
|
key={payment.id}
|
|
115
|
-
className="flex items-center justify-between p-3 border rounded-
|
|
115
|
+
className="flex items-center justify-between p-3 border rounded-sm hover:bg-accent cursor-pointer transition-colors"
|
|
116
116
|
onClick={() => openPaymentDetailsDialog(payment.id)}
|
|
117
117
|
>
|
|
118
118
|
<div className="flex-1">
|
|
@@ -156,7 +156,7 @@ export const PaymentsList: React.FC = () => {
|
|
|
156
156
|
{isLoadingPayments ? (
|
|
157
157
|
<div className="space-y-3">
|
|
158
158
|
{Array.from({ length: 5 }).map((_, i) => (
|
|
159
|
-
<div key={i} className="flex items-center justify-between p-4 border rounded-
|
|
159
|
+
<div key={i} className="flex items-center justify-between p-4 border rounded-sm">
|
|
160
160
|
<div className="space-y-2">
|
|
161
161
|
<Skeleton className="h-4 w-32" />
|
|
162
162
|
<Skeleton className="h-3 w-24" />
|
|
@@ -201,7 +201,7 @@ export const PaymentsList: React.FC = () => {
|
|
|
201
201
|
<TableRow
|
|
202
202
|
key={payment.id}
|
|
203
203
|
className="cursor-pointer hover:bg-accent"
|
|
204
|
-
onClick={() => openPaymentDetails(payment)}
|
|
204
|
+
onClick={() => openPaymentDetails(payment.id)}
|
|
205
205
|
>
|
|
206
206
|
<TableCell>
|
|
207
207
|
<div>
|
|
@@ -234,7 +234,7 @@ export const PaymentsList: React.FC = () => {
|
|
|
234
234
|
size="sm"
|
|
235
235
|
onClick={(e) => {
|
|
236
236
|
e.stopPropagation();
|
|
237
|
-
openPaymentDetails(payment);
|
|
237
|
+
openPaymentDetails(payment.id);
|
|
238
238
|
}}
|
|
239
239
|
>
|
|
240
240
|
<ExternalLink className="h-4 w-4" />
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import useSWRInfinite from 'swr/infinite';
|
|
7
|
-
import { api,
|
|
7
|
+
import { api, Fetchers, CfgSupportTypes } from '@djangocfg/api';
|
|
8
8
|
import type { API } from '@djangocfg/api';
|
|
9
|
-
import { CfgSupportTypes } from '@djangocfg/api';
|
|
10
9
|
|
|
11
10
|
type PaginatedMessageList = CfgSupportTypes.PaginatedMessageList;
|
|
12
11
|
type Message = CfgSupportTypes.Message;
|
|
@@ -41,7 +40,7 @@ export function useInfiniteMessages(ticketUuid: string | null): UseInfiniteMessa
|
|
|
41
40
|
};
|
|
42
41
|
|
|
43
42
|
const fetcher = async ([, ticket_uuid, page, pageSize]: [string, string, number, number]) => {
|
|
44
|
-
return getSupportTicketsMessagesList(
|
|
43
|
+
return Fetchers.getSupportTicketsMessagesList(
|
|
45
44
|
ticket_uuid,
|
|
46
45
|
{ page, page_size: pageSize },
|
|
47
46
|
api as unknown as API
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import useSWRInfinite from 'swr/infinite';
|
|
7
|
-
import { api,
|
|
7
|
+
import { api, Fetchers, CfgSupportTypes } from '@djangocfg/api';
|
|
8
8
|
import type { API } from '@djangocfg/api';
|
|
9
|
-
import { CfgSupportTypes } from '@djangocfg/api';
|
|
10
9
|
|
|
11
10
|
type PaginatedTicketList = CfgSupportTypes.PaginatedTicketList;
|
|
12
11
|
type Ticket = CfgSupportTypes.Ticket;
|
|
@@ -37,7 +36,7 @@ export function useInfiniteTickets(): UseInfiniteTicketsReturn {
|
|
|
37
36
|
};
|
|
38
37
|
|
|
39
38
|
const fetcher = async ([, page, pageSize]: [string, number, number]) => {
|
|
40
|
-
return getSupportTicketsList(
|
|
39
|
+
return Fetchers.getSupportTicketsList(
|
|
41
40
|
{ page, page_size: pageSize },
|
|
42
41
|
api as unknown as API
|
|
43
42
|
);
|
|
@@ -102,7 +102,7 @@ export const SessionList: React.FC<SessionListProps> = ({
|
|
|
102
102
|
{sessions.map((session, index) => (
|
|
103
103
|
<div
|
|
104
104
|
key={session.id}
|
|
105
|
-
className="group relative flex items-start gap-3 p-4 border rounded-
|
|
105
|
+
className="group relative flex items-start gap-3 p-4 border rounded-sm
|
|
106
106
|
hover:bg-muted/50 hover:border-primary/50 hover:shadow-md
|
|
107
107
|
transition-all duration-200 cursor-pointer
|
|
108
108
|
animate-in fade-in slide-in-from-left-2"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import useSWRInfinite from 'swr/infinite';
|
|
7
|
-
import { api,
|
|
7
|
+
import { api, Fetchers, CfgKnowbaseTypes } from '@djangocfg/api';
|
|
8
8
|
import type { API } from '@djangocfg/api';
|
|
9
9
|
|
|
10
10
|
type PaginatedChatSessionList = CfgKnowbaseTypes.PaginatedChatSessionList;
|
|
@@ -25,7 +25,7 @@ export function useInfiniteSessions() {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
const fetcher = async ([, page, pageSize]: [string, number, number]) => {
|
|
28
|
-
return getKnowbaseAdminSessionsList(
|
|
28
|
+
return Fetchers.getKnowbaseAdminSessionsList(
|
|
29
29
|
{ page, page_size: pageSize },
|
|
30
30
|
api as unknown as API
|
|
31
31
|
);
|
|
@@ -71,7 +71,7 @@ export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps>(({
|
|
|
71
71
|
{/* Video Player */}
|
|
72
72
|
<div
|
|
73
73
|
className={cn(
|
|
74
|
-
"relative w-full overflow-hidden rounded-
|
|
74
|
+
"relative w-full overflow-hidden rounded-sm bg-black",
|
|
75
75
|
theme === 'minimal' && "rounded-none",
|
|
76
76
|
theme === 'modern' && "rounded-xl shadow-2xl"
|
|
77
77
|
)}
|