@payez/next-mvp 3.9.1 → 4.0.0
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/dist/api/auth-handler.d.ts +1 -2
- package/dist/api/auth-handler.js +9 -9
- package/dist/api-handlers/account/change-password.js +110 -112
- package/dist/api-handlers/admin/analytics.d.ts +19 -20
- package/dist/api-handlers/admin/analytics.js +378 -379
- package/dist/api-handlers/admin/audit.d.ts +19 -20
- package/dist/api-handlers/admin/audit.js +213 -214
- package/dist/api-handlers/admin/index.d.ts +21 -22
- package/dist/api-handlers/admin/index.js +42 -43
- package/dist/api-handlers/admin/redis-sessions.d.ts +35 -36
- package/dist/api-handlers/admin/redis-sessions.js +203 -204
- package/dist/api-handlers/admin/sessions.d.ts +20 -21
- package/dist/api-handlers/admin/sessions.js +283 -284
- package/dist/api-handlers/admin/site-logs.d.ts +45 -46
- package/dist/api-handlers/admin/site-logs.js +317 -318
- package/dist/api-handlers/admin/stats.d.ts +20 -21
- package/dist/api-handlers/admin/stats.js +239 -240
- package/dist/api-handlers/admin/users.d.ts +19 -20
- package/dist/api-handlers/admin/users.js +221 -222
- package/dist/api-handlers/admin/vibe-data.d.ts +79 -80
- package/dist/api-handlers/admin/vibe-data.js +267 -268
- package/dist/api-handlers/auth/refresh.js +633 -635
- package/dist/api-handlers/auth/signout.js +186 -187
- package/dist/api-handlers/auth/status.js +4 -7
- package/dist/api-handlers/auth/update-session.d.ts +1 -1
- package/dist/api-handlers/auth/update-session.js +12 -14
- package/dist/api-handlers/auth/verify-code.d.ts +43 -43
- package/dist/api-handlers/auth/verify-code.js +90 -94
- package/dist/api-handlers/session/viability.js +114 -146
- package/dist/api-handlers/test/force-expire.js +59 -65
- package/dist/auth/auth-decision.js +182 -182
- package/dist/auth/better-auth.d.ts +3 -6
- package/dist/auth/better-auth.js +3 -6
- package/dist/auth/route-config.js +2 -2
- package/dist/auth/utils/token-utils.d.ts +83 -84
- package/dist/auth/utils/token-utils.js +218 -219
- package/dist/client/AuthContext.js +115 -112
- package/dist/client/better-auth-client.d.ts +1020 -1020
- package/dist/client/fetch-with-auth.js +2 -2
- package/dist/components/SessionSync.js +121 -119
- package/dist/components/account/MobileNavDrawer.js +64 -64
- package/dist/components/account/UserAvatarMenu.js +91 -88
- package/dist/components/admin/VibeAdminLayout.js +71 -69
- package/dist/hooks/useAuth.js +9 -7
- package/dist/hooks/useAuthSettings.js +93 -93
- package/dist/hooks/useAvailableProviders.d.ts +43 -45
- package/dist/hooks/useAvailableProviders.js +112 -108
- package/dist/hooks/useSessionExpiration.d.ts +2 -3
- package/dist/hooks/useSessionExpiration.js +2 -2
- package/dist/hooks/useViabilitySession.js +3 -2
- package/dist/index.js +4 -6
- package/dist/lib/app-slug.d.ts +95 -95
- package/dist/lib/app-slug.js +172 -172
- package/dist/lib/standardized-client-api.js +10 -5
- package/dist/lib/startup-init.js +21 -25
- package/dist/lib/test-aware-get-token.js +86 -81
- package/dist/lib/token-lifecycle.d.ts +78 -52
- package/dist/lib/token-lifecycle.js +360 -398
- package/dist/pages/admin-login/page.js +73 -83
- package/dist/pages/client-admin/ClientSiteAdminPage.js +179 -177
- package/dist/pages/login/page.js +202 -211
- package/dist/pages/showcase/ShowcasePage.js +142 -140
- package/dist/pages/test-env/EmergencyLogoutPage.js +99 -98
- package/dist/pages/test-env/JwtInspectPage.js +116 -114
- package/dist/pages/test-env/RefreshTokenPage.js +4 -2
- package/dist/pages/test-env/TestEnvPage.js +51 -49
- package/dist/pages/verify-code/page.js +412 -408
- package/dist/routes/auth/logout.d.ts +31 -31
- package/dist/routes/auth/logout.js +98 -113
- package/dist/routes/auth/nextauth.d.ts +14 -11
- package/dist/routes/auth/nextauth.js +25 -57
- package/dist/routes/auth/session.js +157 -179
- package/dist/routes/auth/viability.js +190 -201
- package/dist/server/auth.d.ts +50 -0
- package/dist/server/auth.js +62 -0
- package/dist/stores/authStore.js +19 -23
- package/dist/utils/logout.js +5 -5
- package/package.json +1 -3
- package/src/api/auth-handler.ts +550 -549
- package/src/api-handlers/account/change-password.ts +5 -8
- package/src/api-handlers/admin/analytics.ts +4 -6
- package/src/api-handlers/admin/audit.ts +5 -7
- package/src/api-handlers/admin/index.ts +1 -2
- package/src/api-handlers/admin/redis-sessions.ts +6 -8
- package/src/api-handlers/admin/sessions.ts +5 -7
- package/src/api-handlers/admin/site-logs.ts +8 -10
- package/src/api-handlers/admin/stats.ts +4 -6
- package/src/api-handlers/admin/users.ts +5 -7
- package/src/api-handlers/admin/vibe-data.ts +10 -12
- package/src/api-handlers/auth/refresh.ts +5 -7
- package/src/api-handlers/auth/signout.ts +5 -6
- package/src/api-handlers/auth/status.ts +4 -7
- package/src/api-handlers/auth/update-session.ts +123 -125
- package/src/api-handlers/auth/verify-code.ts +9 -13
- package/src/api-handlers/session/viability.ts +10 -47
- package/src/api-handlers/test/force-expire.ts +4 -11
- package/src/auth/auth-decision.ts +1 -1
- package/src/auth/better-auth.ts +138 -141
- package/src/auth/route-config.ts +219 -219
- package/src/auth/utils/token-utils.ts +0 -1
- package/src/client/AuthContext.tsx +6 -2
- package/src/client/fetch-with-auth.ts +47 -47
- package/src/components/SessionSync.tsx +6 -5
- package/src/components/account/MobileNavDrawer.tsx +3 -3
- package/src/components/account/UserAvatarMenu.tsx +6 -3
- package/src/components/admin/VibeAdminLayout.tsx +4 -2
- package/src/config/logger.ts +1 -1
- package/src/hooks/useAuth.ts +117 -115
- package/src/hooks/useAuthSettings.ts +2 -2
- package/src/hooks/useAvailableProviders.ts +9 -5
- package/src/hooks/useSessionExpiration.ts +101 -102
- package/src/hooks/useViabilitySession.ts +336 -335
- package/src/index.ts +60 -63
- package/src/lib/api-handler.ts +0 -1
- package/src/lib/app-slug.ts +6 -6
- package/src/lib/standardized-client-api.ts +901 -895
- package/src/lib/startup-init.ts +243 -247
- package/src/lib/test-aware-get-token.ts +22 -12
- package/src/lib/token-lifecycle.ts +12 -53
- package/src/pages/admin-login/page.tsx +9 -17
- package/src/pages/client-admin/ClientSiteAdminPage.tsx +4 -2
- package/src/pages/login/page.tsx +21 -28
- package/src/pages/showcase/ShowcasePage.tsx +4 -2
- package/src/pages/test-env/EmergencyLogoutPage.tsx +7 -6
- package/src/pages/test-env/JwtInspectPage.tsx +5 -3
- package/src/pages/test-env/RefreshTokenPage.tsx +157 -155
- package/src/pages/test-env/TestEnvPage.tsx +4 -2
- package/src/pages/verify-code/page.tsx +10 -6
- package/src/routes/auth/logout.ts +7 -25
- package/src/routes/auth/nextauth.ts +45 -71
- package/src/routes/auth/session.ts +25 -50
- package/src/routes/auth/viability.ts +7 -19
- package/src/server/auth.ts +60 -0
- package/src/stores/authStore.ts +1899 -1904
- package/src/utils/logout.ts +30 -30
- package/src/auth/auth-options.ts +0 -237
- package/src/auth/callbacks/index.ts +0 -7
- package/src/auth/callbacks/jwt.ts +0 -382
- package/src/auth/callbacks/session.ts +0 -243
- package/src/auth/callbacks/signin.ts +0 -56
- package/src/auth/events/index.ts +0 -5
- package/src/auth/events/signout.ts +0 -33
- package/src/auth/providers/credentials.ts +0 -256
- package/src/auth/providers/index.ts +0 -6
- package/src/auth/providers/oauth.ts +0 -114
- package/src/lib/nextauth-secret.ts +0 -121
- package/src/types/next-auth.d.ts +0 -15
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.fetchWithAuth = fetchWithAuth;
|
|
4
4
|
// src/client/fetch-with-auth.ts
|
|
5
|
-
const
|
|
5
|
+
const better_auth_client_1 = require("./better-auth-client");
|
|
6
6
|
/**
|
|
7
7
|
* A wrapper for the `fetch` API that automatically injects the session's
|
|
8
8
|
* accessToken into the Authorization header and handles 401 Unauthorized
|
|
@@ -15,7 +15,7 @@ const react_1 = require("next-auth/react");
|
|
|
15
15
|
*/
|
|
16
16
|
async function fetchWithAuth(url, options = {}) {
|
|
17
17
|
// 1. Retrieve the client-side session to get the accessToken.
|
|
18
|
-
const session = await
|
|
18
|
+
const { data: session } = await better_auth_client_1.authClient.getSession();
|
|
19
19
|
// 2. Inject the accessToken into the Authorization header.
|
|
20
20
|
const headers = new Headers(options.headers);
|
|
21
21
|
if (session?.accessToken) {
|
|
@@ -1,119 +1,121 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
'use client';
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.SessionSync = SessionSync;
|
|
5
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
-
const react_1 = require("react");
|
|
7
|
-
const
|
|
8
|
-
const authStore_1 = require("../stores/authStore");
|
|
9
|
-
const session_1 = require("../lib/session");
|
|
10
|
-
const app_slug_1 = require("../lib/app-slug");
|
|
11
|
-
/**
|
|
12
|
-
* Sanitize sensitive data for logging
|
|
13
|
-
* Never log full user IDs or emails in any environment
|
|
14
|
-
*/
|
|
15
|
-
function sanitizeForLog(value, type) {
|
|
16
|
-
if (!value)
|
|
17
|
-
return '(empty)';
|
|
18
|
-
if (type === 'email') {
|
|
19
|
-
return '***@***'; // Never log emails
|
|
20
|
-
}
|
|
21
|
-
if (type === 'userId') {
|
|
22
|
-
// Only show first 8 chars in development
|
|
23
|
-
if (process.env.NODE_ENV === 'development') {
|
|
24
|
-
return value.substring(0, 8) + '...';
|
|
25
|
-
}
|
|
26
|
-
return '***'; // Fully redact in production
|
|
27
|
-
}
|
|
28
|
-
return '***';
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* SessionSync - Bridges NextAuth session with Zustand auth store
|
|
32
|
-
*
|
|
33
|
-
* CRITICAL: This component enforces strict session validation. If NextAuth
|
|
34
|
-
* reports an authenticated status but the session data is invalid (empty user ID,
|
|
35
|
-
* empty email, or missing access token), it forces a sign-out to prevent
|
|
36
|
-
* contradictory state like "hasSession: true, userId: ''"
|
|
37
|
-
*
|
|
38
|
-
* This ensures the app NEVER shows authenticated UI with empty/invalid session data.
|
|
39
|
-
*/
|
|
40
|
-
function SessionSync({ children }) {
|
|
41
|
-
const { data:
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
document.cookie =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.SessionSync = SessionSync;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const better_auth_client_1 = require("../client/better-auth-client");
|
|
8
|
+
const authStore_1 = require("../stores/authStore");
|
|
9
|
+
const session_1 = require("../lib/session");
|
|
10
|
+
const app_slug_1 = require("../lib/app-slug");
|
|
11
|
+
/**
|
|
12
|
+
* Sanitize sensitive data for logging
|
|
13
|
+
* Never log full user IDs or emails in any environment
|
|
14
|
+
*/
|
|
15
|
+
function sanitizeForLog(value, type) {
|
|
16
|
+
if (!value)
|
|
17
|
+
return '(empty)';
|
|
18
|
+
if (type === 'email') {
|
|
19
|
+
return '***@***'; // Never log emails
|
|
20
|
+
}
|
|
21
|
+
if (type === 'userId') {
|
|
22
|
+
// Only show first 8 chars in development
|
|
23
|
+
if (process.env.NODE_ENV === 'development') {
|
|
24
|
+
return value.substring(0, 8) + '...';
|
|
25
|
+
}
|
|
26
|
+
return '***'; // Fully redact in production
|
|
27
|
+
}
|
|
28
|
+
return '***';
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* SessionSync - Bridges NextAuth session with Zustand auth store
|
|
32
|
+
*
|
|
33
|
+
* CRITICAL: This component enforces strict session validation. If NextAuth
|
|
34
|
+
* reports an authenticated status but the session data is invalid (empty user ID,
|
|
35
|
+
* empty email, or missing access token), it forces a sign-out to prevent
|
|
36
|
+
* contradictory state like "hasSession: true, userId: ''"
|
|
37
|
+
*
|
|
38
|
+
* This ensures the app NEVER shows authenticated UI with empty/invalid session data.
|
|
39
|
+
*/
|
|
40
|
+
function SessionSync({ children }) {
|
|
41
|
+
const { data: sessionData, isPending } = better_auth_client_1.authClient.useSession();
|
|
42
|
+
const session = sessionData;
|
|
43
|
+
const status = isPending ? 'loading' : session ? 'authenticated' : 'unauthenticated';
|
|
44
|
+
const { setSession, clearSession } = (0, authStore_1.useAuthStore)();
|
|
45
|
+
// Guard against duplicate sign-out calls
|
|
46
|
+
const isSigningOutRef = (0, react_1.useRef)(false);
|
|
47
|
+
(0, react_1.useEffect)(() => {
|
|
48
|
+
let isMounted = true;
|
|
49
|
+
// Only process when NextAuth has finished loading
|
|
50
|
+
if (status === 'loading') {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Strict validation: Check if session is actually valid
|
|
54
|
+
const isValid = (0, session_1.isValidSession)(session);
|
|
55
|
+
// CRITICAL FIX: If NextAuth says "authenticated" but session is invalid,
|
|
56
|
+
// this is a broken state that must be fixed immediately
|
|
57
|
+
if (status === 'authenticated' && !isValid) {
|
|
58
|
+
// GUARD: Prevent duplicate sign-out
|
|
59
|
+
if (isSigningOutRef.current) {
|
|
60
|
+
console.warn('[SessionSync] Sign-out already in progress, skipping');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
isSigningOutRef.current = true;
|
|
64
|
+
console.error('[SessionSync] CRITICAL: Invalid session detected despite authenticated status!');
|
|
65
|
+
console.error('[SessionSync] This indicates a session with empty user data - forcing sign-out');
|
|
66
|
+
// Log diagnostic info with PII redaction
|
|
67
|
+
// Note: session is typed as Session | null from NextAuth, but may have invalid/partial data
|
|
68
|
+
const sessionData = session; // Explicit cast needed for error logging
|
|
69
|
+
console.error('[SessionSync] Session data:', {
|
|
70
|
+
hasSessionObject: !!sessionData,
|
|
71
|
+
hasUser: !!sessionData?.user,
|
|
72
|
+
userId: sanitizeForLog(sessionData?.user?.id, 'userId'),
|
|
73
|
+
userEmail: sanitizeForLog(sessionData?.user?.email, 'email'),
|
|
74
|
+
hasAccessToken: !!sessionData?.accessToken,
|
|
75
|
+
});
|
|
76
|
+
// Clear the auth store immediately
|
|
77
|
+
clearSession();
|
|
78
|
+
// FIX: Force clear all auth cookies including stale provisional tokens (app-slug prefixed)
|
|
79
|
+
// This prevents infinite loop when provisional token exists but session is invalid
|
|
80
|
+
// Root cause: OAuth creates provisional token before Redis session exists
|
|
81
|
+
try {
|
|
82
|
+
const secureSession = (0, app_slug_1.getSecureSessionCookieName)();
|
|
83
|
+
const secureCsrf = (0, app_slug_1.getSecureCsrfCookieName)();
|
|
84
|
+
const callbackUrl = (0, app_slug_1.getCallbackUrlCookieName)();
|
|
85
|
+
document.cookie = `${secureSession}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; Secure; SameSite=Lax`;
|
|
86
|
+
document.cookie = `${secureCsrf}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax`;
|
|
87
|
+
document.cookie = `__Secure-${callbackUrl}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; Secure; SameSite=Lax`;
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
// Cookie clearing failed - non-critical, continue with signout
|
|
91
|
+
}
|
|
92
|
+
// Force Better Auth to sign out (this will clear cookies and trigger redirect)
|
|
93
|
+
better_auth_client_1.authClient.signOut()
|
|
94
|
+
.then(() => {
|
|
95
|
+
if (isMounted) {
|
|
96
|
+
// Use generic error code instead of implementation details
|
|
97
|
+
window.location.href = '/account-auth/login?error=SessionExpired&code=1001';
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
.catch((err) => {
|
|
101
|
+
console.error('[SessionSync] Error during forced signout:', err);
|
|
102
|
+
if (isMounted) {
|
|
103
|
+
window.location.href = '/account-auth/login?error=SessionExpired&code=1001';
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Normal flow: Update store based on valid session status
|
|
109
|
+
if (status === 'authenticated' && isValid) {
|
|
110
|
+
setSession(session);
|
|
111
|
+
}
|
|
112
|
+
else if (status === 'unauthenticated') {
|
|
113
|
+
clearSession();
|
|
114
|
+
}
|
|
115
|
+
// Cleanup function to prevent post-unmount updates
|
|
116
|
+
return () => {
|
|
117
|
+
isMounted = false;
|
|
118
|
+
};
|
|
119
|
+
}, [session, status, setSession, clearSession]);
|
|
120
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
|
|
121
|
+
}
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
'use client';
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.MobileNavDrawer = MobileNavDrawer;
|
|
8
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
-
const react_1 = require("react");
|
|
10
|
-
const
|
|
11
|
-
const navigation_1 = require("next/navigation");
|
|
12
|
-
const image_1 = __importDefault(require("next/image"));
|
|
13
|
-
const link_1 = __importDefault(require("next/link"));
|
|
14
|
-
const lucide_react_1 = require("lucide-react");
|
|
15
|
-
function MobileNavDrawer({ isOpen, onClose, navItems, customSections, basePath = '/account', onSignIn, signInCallbackUrl = '/dashboard', unauthActions, authFooter, }) {
|
|
16
|
-
const { data: session } =
|
|
17
|
-
const pathname = (0, navigation_1.usePathname)();
|
|
18
|
-
const isAuthenticated = !!session?.user;
|
|
19
|
-
const isActiveRoute = (0, react_1.useCallback)((href) => pathname?.startsWith(href) ?? false, [pathname]);
|
|
20
|
-
// Close on Escape key
|
|
21
|
-
(0, react_1.useEffect)(() => {
|
|
22
|
-
function handleEscape(event) {
|
|
23
|
-
if (event.key === 'Escape') {
|
|
24
|
-
onClose();
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
if (isOpen) {
|
|
28
|
-
document.addEventListener('keydown', handleEscape);
|
|
29
|
-
return () => document.removeEventListener('keydown', handleEscape);
|
|
30
|
-
}
|
|
31
|
-
}, [isOpen, onClose]);
|
|
32
|
-
// Lock body scroll when open
|
|
33
|
-
(0, react_1.useEffect)(() => {
|
|
34
|
-
if (isOpen) {
|
|
35
|
-
document.body.style.overflow = 'hidden';
|
|
36
|
-
return () => {
|
|
37
|
-
document.body.style.overflow = '';
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
}, [isOpen]);
|
|
41
|
-
const handleSignIn = () => {
|
|
42
|
-
onClose();
|
|
43
|
-
if (onSignIn) {
|
|
44
|
-
onSignIn();
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const handleSectionItemClick = (item) => {
|
|
51
|
-
onClose();
|
|
52
|
-
if (item.onClick) {
|
|
53
|
-
item.onClick();
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
// Derive display initial from name or email
|
|
57
|
-
const userName = session?.user?.name;
|
|
58
|
-
const userEmail = session?.user?.email;
|
|
59
|
-
const displaySource = userName || userEmail;
|
|
60
|
-
const userInitial = displaySource?.charAt(0).toUpperCase() || '?';
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.MobileNavDrawer = MobileNavDrawer;
|
|
8
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const better_auth_client_1 = require("../../client/better-auth-client");
|
|
11
|
+
const navigation_1 = require("next/navigation");
|
|
12
|
+
const image_1 = __importDefault(require("next/image"));
|
|
13
|
+
const link_1 = __importDefault(require("next/link"));
|
|
14
|
+
const lucide_react_1 = require("lucide-react");
|
|
15
|
+
function MobileNavDrawer({ isOpen, onClose, navItems, customSections, basePath = '/account', onSignIn, signInCallbackUrl = '/dashboard', unauthActions, authFooter, }) {
|
|
16
|
+
const { data: session } = better_auth_client_1.authClient.useSession();
|
|
17
|
+
const pathname = (0, navigation_1.usePathname)();
|
|
18
|
+
const isAuthenticated = !!session?.user;
|
|
19
|
+
const isActiveRoute = (0, react_1.useCallback)((href) => pathname?.startsWith(href) ?? false, [pathname]);
|
|
20
|
+
// Close on Escape key
|
|
21
|
+
(0, react_1.useEffect)(() => {
|
|
22
|
+
function handleEscape(event) {
|
|
23
|
+
if (event.key === 'Escape') {
|
|
24
|
+
onClose();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (isOpen) {
|
|
28
|
+
document.addEventListener('keydown', handleEscape);
|
|
29
|
+
return () => document.removeEventListener('keydown', handleEscape);
|
|
30
|
+
}
|
|
31
|
+
}, [isOpen, onClose]);
|
|
32
|
+
// Lock body scroll when open
|
|
33
|
+
(0, react_1.useEffect)(() => {
|
|
34
|
+
if (isOpen) {
|
|
35
|
+
document.body.style.overflow = 'hidden';
|
|
36
|
+
return () => {
|
|
37
|
+
document.body.style.overflow = '';
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}, [isOpen]);
|
|
41
|
+
const handleSignIn = () => {
|
|
42
|
+
onClose();
|
|
43
|
+
if (onSignIn) {
|
|
44
|
+
onSignIn();
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
better_auth_client_1.authClient.signIn.social({ provider: 'google', callbackURL: signInCallbackUrl });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const handleSectionItemClick = (item) => {
|
|
51
|
+
onClose();
|
|
52
|
+
if (item.onClick) {
|
|
53
|
+
item.onClick();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
// Derive display initial from name or email
|
|
57
|
+
const userName = session?.user?.name;
|
|
58
|
+
const userEmail = session?.user?.email;
|
|
59
|
+
const displaySource = userName || userEmail;
|
|
60
|
+
const userInitial = displaySource?.charAt(0).toUpperCase() || '?';
|
|
61
61
|
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `
|
|
62
62
|
fixed inset-0 bg-black/50 backdrop-blur-sm z-40 lg:hidden
|
|
63
63
|
transition-opacity duration-300
|
|
@@ -74,8 +74,8 @@ function MobileNavDrawer({ isOpen, onClose, navItems, customSections, basePath =
|
|
|
74
74
|
`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-white/10", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-lg font-semibold text-gray-900 dark:text-white", children: "Menu" }), (0, jsx_runtime_1.jsx)("button", { onClick: onClose, className: "\r\n p-2 rounded-xl\r\n text-gray-400 hover:text-gray-900\r\n dark:hover:text-white\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", "aria-label": "Close menu", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "h-5 w-5" }) })] }), isAuthenticated && session?.user && ((0, jsx_runtime_1.jsx)("div", { className: "p-4 border-b border-gray-200 dark:border-white/10", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [session.user.image ? ((0, jsx_runtime_1.jsx)(image_1.default, { src: session.user.image, alt: "", width: 48, height: 48, className: "w-12 h-12 rounded-full", unoptimized: true })) : ((0, jsx_runtime_1.jsx)("div", { className: "w-12 h-12 rounded-full bg-blue-500 flex items-center justify-center text-white font-semibold text-lg", children: userInitial })), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 min-w-0", children: [userName && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-gray-900 dark:text-white truncate", children: userName })), userEmail && ((0, jsx_runtime_1.jsx)("p", { className: "text-xs text-gray-500 dark:text-slate-400 truncate", children: userEmail }))] })] }) })), (0, jsx_runtime_1.jsx)("div", { className: "p-2", children: navItems.map((item) => ((0, jsx_runtime_1.jsxs)(link_1.default, { href: item.href, onClick: onClose, className: `
|
|
75
75
|
flex items-center gap-3 px-4 py-3.5 rounded-xl
|
|
76
76
|
transition-colors duration-200
|
|
77
|
-
${isActiveRoute(item.href)
|
|
78
|
-
? 'bg-blue-500/10 text-blue-500'
|
|
77
|
+
${isActiveRoute(item.href)
|
|
78
|
+
? 'bg-blue-500/10 text-blue-500'
|
|
79
79
|
: 'text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-white/10'}
|
|
80
|
-
`, children: [item.icon && (0, jsx_runtime_1.jsx)("span", { className: "text-xl", children: item.icon }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: item.label }), isActiveRoute(item.href) && ((0, jsx_runtime_1.jsx)("span", { className: "ml-auto w-2 h-2 rounded-full bg-blue-500" }))] }, item.href))) }), customSections?.map((section, sectionIndex) => ((0, jsx_runtime_1.jsxs)("div", { className: "p-2 border-t border-gray-200 dark:border-white/10", children: [section.title && ((0, jsx_runtime_1.jsx)("p", { className: "px-4 py-2 text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wider", children: section.title })), section.items.map((item, itemIndex) => item.href ? ((0, jsx_runtime_1.jsxs)(link_1.default, { href: item.href, onClick: onClose, className: "\r\n flex items-center gap-3 px-4 py-3 rounded-xl\r\n text-gray-900 dark:text-white\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", children: [item.icon && (0, jsx_runtime_1.jsx)("span", { className: "text-xl", children: item.icon }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: item.label })] }, itemIndex)) : ((0, jsx_runtime_1.jsxs)("button", { onClick: () => handleSectionItemClick(item), className: "\r\n flex items-center gap-3 px-4 py-3 rounded-xl w-full text-left\r\n text-gray-900 dark:text-white\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", children: [item.icon && (0, jsx_runtime_1.jsx)("span", { className: "text-xl", children: item.icon }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: item.label })] }, itemIndex)))] }, sectionIndex))), (0, jsx_runtime_1.jsx)("div", { className: "p-4 mt-auto border-t border-gray-200 dark:border-white/10", children: !isAuthenticated ? (unauthActions ?? ((0, jsx_runtime_1.jsx)("div", { className: "space-y-3", children: (0, jsx_runtime_1.jsx)("button", { onClick: handleSignIn, className: "\r\n w-full px-4 py-3 rounded-xl\r\n text-blue-500 font-semibold\r\n border border-blue-500/30\r\n hover:bg-blue-500/10\r\n transition-colors\r\n ", children: "Login" }) }))) : (authFooter ?? ((0, jsx_runtime_1.jsx)(link_1.default, { href: basePath, onClick: onClose, className: "\r\n flex items-center justify-center gap-2\r\n w-full px-4 py-3 rounded-xl\r\n text-gray-500 dark:text-slate-400 font-medium\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", children: "Account Settings" }))) })] })] }));
|
|
81
|
-
}
|
|
80
|
+
`, children: [item.icon && (0, jsx_runtime_1.jsx)("span", { className: "text-xl", children: item.icon }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: item.label }), isActiveRoute(item.href) && ((0, jsx_runtime_1.jsx)("span", { className: "ml-auto w-2 h-2 rounded-full bg-blue-500" }))] }, item.href))) }), customSections?.map((section, sectionIndex) => ((0, jsx_runtime_1.jsxs)("div", { className: "p-2 border-t border-gray-200 dark:border-white/10", children: [section.title && ((0, jsx_runtime_1.jsx)("p", { className: "px-4 py-2 text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wider", children: section.title })), section.items.map((item, itemIndex) => item.href ? ((0, jsx_runtime_1.jsxs)(link_1.default, { href: item.href, onClick: onClose, className: "\r\n flex items-center gap-3 px-4 py-3 rounded-xl\r\n text-gray-900 dark:text-white\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", children: [item.icon && (0, jsx_runtime_1.jsx)("span", { className: "text-xl", children: item.icon }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: item.label })] }, itemIndex)) : ((0, jsx_runtime_1.jsxs)("button", { onClick: () => handleSectionItemClick(item), className: "\r\n flex items-center gap-3 px-4 py-3 rounded-xl w-full text-left\r\n text-gray-900 dark:text-white\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", children: [item.icon && (0, jsx_runtime_1.jsx)("span", { className: "text-xl", children: item.icon }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: item.label })] }, itemIndex)))] }, sectionIndex))), (0, jsx_runtime_1.jsx)("div", { className: "p-4 mt-auto border-t border-gray-200 dark:border-white/10", children: !isAuthenticated ? (unauthActions ?? ((0, jsx_runtime_1.jsx)("div", { className: "space-y-3", children: (0, jsx_runtime_1.jsx)("button", { onClick: handleSignIn, className: "\r\n w-full px-4 py-3 rounded-xl\r\n text-blue-500 font-semibold\r\n border border-blue-500/30\r\n hover:bg-blue-500/10\r\n transition-colors\r\n ", children: "Login" }) }))) : (authFooter ?? ((0, jsx_runtime_1.jsx)(link_1.default, { href: basePath, onClick: onClose, className: "\r\n flex items-center justify-center gap-2\r\n w-full px-4 py-3 rounded-xl\r\n text-gray-500 dark:text-slate-400 font-medium\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", children: "Account Settings" }))) })] })] }));
|
|
81
|
+
}
|
|
@@ -1,88 +1,91 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
'use client';
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.UserAvatarMenu = UserAvatarMenu;
|
|
8
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
-
const react_1 = require("react");
|
|
10
|
-
const
|
|
11
|
-
const navigation_1 = require("next/navigation");
|
|
12
|
-
const image_1 = __importDefault(require("next/image"));
|
|
13
|
-
const lucide_react_1 = require("lucide-react");
|
|
14
|
-
function UserAvatarMenu({ basePath = '', showProfile = true, showSettings = true, showSecurity = true, customItems, onSignOut, }) {
|
|
15
|
-
const { data:
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
(0, react_1.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.UserAvatarMenu = UserAvatarMenu;
|
|
8
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const better_auth_client_1 = require("../../client/better-auth-client");
|
|
11
|
+
const navigation_1 = require("next/navigation");
|
|
12
|
+
const image_1 = __importDefault(require("next/image"));
|
|
13
|
+
const lucide_react_1 = require("lucide-react");
|
|
14
|
+
function UserAvatarMenu({ basePath = '', showProfile = true, showSettings = true, showSecurity = true, customItems, onSignOut, }) {
|
|
15
|
+
const { data: sessionData, isPending } = better_auth_client_1.authClient.useSession();
|
|
16
|
+
const session = sessionData;
|
|
17
|
+
const status = isPending ? 'loading' : session ? 'authenticated' : 'unauthenticated';
|
|
18
|
+
const router = (0, navigation_1.useRouter)();
|
|
19
|
+
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
|
20
|
+
const menuRef = (0, react_1.useRef)(null);
|
|
21
|
+
// Close menu when clicking outside
|
|
22
|
+
(0, react_1.useEffect)(() => {
|
|
23
|
+
function handleClickOutside(event) {
|
|
24
|
+
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
|
25
|
+
setIsOpen(false);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (isOpen) {
|
|
29
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
30
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
31
|
+
}
|
|
32
|
+
}, [isOpen]);
|
|
33
|
+
// Close menu on Escape
|
|
34
|
+
(0, react_1.useEffect)(() => {
|
|
35
|
+
function handleEscape(event) {
|
|
36
|
+
if (event.key === 'Escape') {
|
|
37
|
+
setIsOpen(false);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (isOpen) {
|
|
41
|
+
document.addEventListener('keydown', handleEscape);
|
|
42
|
+
return () => document.removeEventListener('keydown', handleEscape);
|
|
43
|
+
}
|
|
44
|
+
}, [isOpen]);
|
|
45
|
+
// Loading state
|
|
46
|
+
if (status === 'loading') {
|
|
47
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "h-10 w-10 rounded-full bg-gray-200 dark:bg-white/10 animate-pulse" }));
|
|
48
|
+
}
|
|
49
|
+
// Not authenticated
|
|
50
|
+
if (!session?.user) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
// Derive display initial from name or email — ignore anon/internal IDs
|
|
54
|
+
const userName = session.user?.name;
|
|
55
|
+
const userEmail = session.user.email;
|
|
56
|
+
const displaySource = userName || userEmail;
|
|
57
|
+
const userInitial = displaySource?.charAt(0).toUpperCase() || '?';
|
|
58
|
+
const handleNavigation = (path) => {
|
|
59
|
+
setIsOpen(false);
|
|
60
|
+
router.push(path);
|
|
61
|
+
};
|
|
62
|
+
const handleSignOut = async () => {
|
|
63
|
+
setIsOpen(false);
|
|
64
|
+
if (onSignOut) {
|
|
65
|
+
onSignOut();
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Use NEXT_PUBLIC env var or default to root
|
|
69
|
+
const logoutUrl = process.env.NEXT_PUBLIC_LOGOUT_REDIRECT_URL || '/';
|
|
70
|
+
await better_auth_client_1.authClient.signOut();
|
|
71
|
+
window.location.href = logoutUrl;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const handleItemClick = (item) => {
|
|
75
|
+
setIsOpen(false);
|
|
76
|
+
if (item.onClick) {
|
|
77
|
+
item.onClick();
|
|
78
|
+
}
|
|
79
|
+
else if (item.href) {
|
|
80
|
+
router.push(item.href);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: menuRef, className: "relative", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => setIsOpen(!isOpen), className: "flex items-center justify-center h-10 w-10 rounded-full overflow-hidden bg-blue-500 text-white font-semibold text-lg hover:opacity-90 transition-opacity focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-slate-900", "aria-label": "User menu", "aria-expanded": isOpen, "aria-haspopup": "true", children: session.user.image ? ((0, jsx_runtime_1.jsx)(image_1.default, { src: session.user.image, alt: "", width: 40, height: 40, className: "w-10 h-10 rounded-full object-cover", unoptimized: true })) : (userInitial) }), isOpen && ((0, jsx_runtime_1.jsxs)("div", { className: "absolute right-0 mt-2 w-56 rounded-md shadow-lg z-50\r\n bg-white dark:bg-slate-900\r\n border border-gray-200 dark:border-slate-700", role: "menu", "aria-orientation": "vertical", children: [(0, jsx_runtime_1.jsx)("div", { className: "px-4 py-3 border-b border-gray-200 dark:border-slate-700", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [session.user.image ? ((0, jsx_runtime_1.jsx)(image_1.default, { src: session.user.image, alt: "", width: 32, height: 32, className: "w-8 h-8 rounded-full flex-shrink-0", unoptimized: true })) : ((0, jsx_runtime_1.jsx)("div", { className: "w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white font-semibold text-sm flex-shrink-0", children: userInitial })), (0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [userName && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-gray-700 dark:text-slate-200 truncate", children: userName })), userEmail && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-500 dark:text-slate-400 truncate", children: userEmail })), !userName && !userEmail && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-500 dark:text-slate-400", children: "Signed in" }))] })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "py-1", children: [showProfile && ((0, jsx_runtime_1.jsx)(MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.User, { className: "h-4 w-4" }), label: "Profile", onClick: () => handleNavigation(`${basePath}/profile`) })), showSettings && ((0, jsx_runtime_1.jsx)(MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Settings, { className: "h-4 w-4" }), label: "Settings", onClick: () => handleNavigation(`${basePath}/settings`) })), showSecurity && ((0, jsx_runtime_1.jsx)(MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Shield, { className: "h-4 w-4" }), label: "Security", onClick: () => handleNavigation(`${basePath}/security`) })), customItems?.map((item, index) => ((0, jsx_runtime_1.jsx)(MenuItem, { icon: item.icon, label: item.label, onClick: () => handleItemClick(item) }, index)))] }), (0, jsx_runtime_1.jsx)("div", { className: "border-t border-gray-200 dark:border-slate-700 py-1", children: (0, jsx_runtime_1.jsx)(MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.LogOut, { className: "h-4 w-4" }), label: "Sign Out", onClick: handleSignOut, variant: "danger" }) })] }))] }));
|
|
84
|
+
}
|
|
85
|
+
function MenuItem({ icon, label, onClick, variant = 'default' }) {
|
|
86
|
+
const baseClasses = "flex items-center w-full px-4 py-2 text-sm cursor-pointer transition-colors";
|
|
87
|
+
const variantClasses = variant === 'danger'
|
|
88
|
+
? "text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-slate-800"
|
|
89
|
+
: "text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-slate-800";
|
|
90
|
+
return ((0, jsx_runtime_1.jsxs)("button", { onClick: onClick, className: `${baseClasses} ${variantClasses}`, role: "menuitem", children: [icon && (0, jsx_runtime_1.jsx)("span", { className: "mr-3", children: icon }), (0, jsx_runtime_1.jsx)("span", { children: label })] }));
|
|
91
|
+
}
|