@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.
Files changed (147) hide show
  1. package/dist/api/auth-handler.d.ts +1 -2
  2. package/dist/api/auth-handler.js +9 -9
  3. package/dist/api-handlers/account/change-password.js +110 -112
  4. package/dist/api-handlers/admin/analytics.d.ts +19 -20
  5. package/dist/api-handlers/admin/analytics.js +378 -379
  6. package/dist/api-handlers/admin/audit.d.ts +19 -20
  7. package/dist/api-handlers/admin/audit.js +213 -214
  8. package/dist/api-handlers/admin/index.d.ts +21 -22
  9. package/dist/api-handlers/admin/index.js +42 -43
  10. package/dist/api-handlers/admin/redis-sessions.d.ts +35 -36
  11. package/dist/api-handlers/admin/redis-sessions.js +203 -204
  12. package/dist/api-handlers/admin/sessions.d.ts +20 -21
  13. package/dist/api-handlers/admin/sessions.js +283 -284
  14. package/dist/api-handlers/admin/site-logs.d.ts +45 -46
  15. package/dist/api-handlers/admin/site-logs.js +317 -318
  16. package/dist/api-handlers/admin/stats.d.ts +20 -21
  17. package/dist/api-handlers/admin/stats.js +239 -240
  18. package/dist/api-handlers/admin/users.d.ts +19 -20
  19. package/dist/api-handlers/admin/users.js +221 -222
  20. package/dist/api-handlers/admin/vibe-data.d.ts +79 -80
  21. package/dist/api-handlers/admin/vibe-data.js +267 -268
  22. package/dist/api-handlers/auth/refresh.js +633 -635
  23. package/dist/api-handlers/auth/signout.js +186 -187
  24. package/dist/api-handlers/auth/status.js +4 -7
  25. package/dist/api-handlers/auth/update-session.d.ts +1 -1
  26. package/dist/api-handlers/auth/update-session.js +12 -14
  27. package/dist/api-handlers/auth/verify-code.d.ts +43 -43
  28. package/dist/api-handlers/auth/verify-code.js +90 -94
  29. package/dist/api-handlers/session/viability.js +114 -146
  30. package/dist/api-handlers/test/force-expire.js +59 -65
  31. package/dist/auth/auth-decision.js +182 -182
  32. package/dist/auth/better-auth.d.ts +3 -6
  33. package/dist/auth/better-auth.js +3 -6
  34. package/dist/auth/route-config.js +2 -2
  35. package/dist/auth/utils/token-utils.d.ts +83 -84
  36. package/dist/auth/utils/token-utils.js +218 -219
  37. package/dist/client/AuthContext.js +115 -112
  38. package/dist/client/better-auth-client.d.ts +1020 -1020
  39. package/dist/client/fetch-with-auth.js +2 -2
  40. package/dist/components/SessionSync.js +121 -119
  41. package/dist/components/account/MobileNavDrawer.js +64 -64
  42. package/dist/components/account/UserAvatarMenu.js +91 -88
  43. package/dist/components/admin/VibeAdminLayout.js +71 -69
  44. package/dist/hooks/useAuth.js +9 -7
  45. package/dist/hooks/useAuthSettings.js +93 -93
  46. package/dist/hooks/useAvailableProviders.d.ts +43 -45
  47. package/dist/hooks/useAvailableProviders.js +112 -108
  48. package/dist/hooks/useSessionExpiration.d.ts +2 -3
  49. package/dist/hooks/useSessionExpiration.js +2 -2
  50. package/dist/hooks/useViabilitySession.js +3 -2
  51. package/dist/index.js +4 -6
  52. package/dist/lib/app-slug.d.ts +95 -95
  53. package/dist/lib/app-slug.js +172 -172
  54. package/dist/lib/standardized-client-api.js +10 -5
  55. package/dist/lib/startup-init.js +21 -25
  56. package/dist/lib/test-aware-get-token.js +86 -81
  57. package/dist/lib/token-lifecycle.d.ts +78 -52
  58. package/dist/lib/token-lifecycle.js +360 -398
  59. package/dist/pages/admin-login/page.js +73 -83
  60. package/dist/pages/client-admin/ClientSiteAdminPage.js +179 -177
  61. package/dist/pages/login/page.js +202 -211
  62. package/dist/pages/showcase/ShowcasePage.js +142 -140
  63. package/dist/pages/test-env/EmergencyLogoutPage.js +99 -98
  64. package/dist/pages/test-env/JwtInspectPage.js +116 -114
  65. package/dist/pages/test-env/RefreshTokenPage.js +4 -2
  66. package/dist/pages/test-env/TestEnvPage.js +51 -49
  67. package/dist/pages/verify-code/page.js +412 -408
  68. package/dist/routes/auth/logout.d.ts +31 -31
  69. package/dist/routes/auth/logout.js +98 -113
  70. package/dist/routes/auth/nextauth.d.ts +14 -11
  71. package/dist/routes/auth/nextauth.js +25 -57
  72. package/dist/routes/auth/session.js +157 -179
  73. package/dist/routes/auth/viability.js +190 -201
  74. package/dist/server/auth.d.ts +50 -0
  75. package/dist/server/auth.js +62 -0
  76. package/dist/stores/authStore.js +19 -23
  77. package/dist/utils/logout.js +5 -5
  78. package/package.json +1 -3
  79. package/src/api/auth-handler.ts +550 -549
  80. package/src/api-handlers/account/change-password.ts +5 -8
  81. package/src/api-handlers/admin/analytics.ts +4 -6
  82. package/src/api-handlers/admin/audit.ts +5 -7
  83. package/src/api-handlers/admin/index.ts +1 -2
  84. package/src/api-handlers/admin/redis-sessions.ts +6 -8
  85. package/src/api-handlers/admin/sessions.ts +5 -7
  86. package/src/api-handlers/admin/site-logs.ts +8 -10
  87. package/src/api-handlers/admin/stats.ts +4 -6
  88. package/src/api-handlers/admin/users.ts +5 -7
  89. package/src/api-handlers/admin/vibe-data.ts +10 -12
  90. package/src/api-handlers/auth/refresh.ts +5 -7
  91. package/src/api-handlers/auth/signout.ts +5 -6
  92. package/src/api-handlers/auth/status.ts +4 -7
  93. package/src/api-handlers/auth/update-session.ts +123 -125
  94. package/src/api-handlers/auth/verify-code.ts +9 -13
  95. package/src/api-handlers/session/viability.ts +10 -47
  96. package/src/api-handlers/test/force-expire.ts +4 -11
  97. package/src/auth/auth-decision.ts +1 -1
  98. package/src/auth/better-auth.ts +138 -141
  99. package/src/auth/route-config.ts +219 -219
  100. package/src/auth/utils/token-utils.ts +0 -1
  101. package/src/client/AuthContext.tsx +6 -2
  102. package/src/client/fetch-with-auth.ts +47 -47
  103. package/src/components/SessionSync.tsx +6 -5
  104. package/src/components/account/MobileNavDrawer.tsx +3 -3
  105. package/src/components/account/UserAvatarMenu.tsx +6 -3
  106. package/src/components/admin/VibeAdminLayout.tsx +4 -2
  107. package/src/config/logger.ts +1 -1
  108. package/src/hooks/useAuth.ts +117 -115
  109. package/src/hooks/useAuthSettings.ts +2 -2
  110. package/src/hooks/useAvailableProviders.ts +9 -5
  111. package/src/hooks/useSessionExpiration.ts +101 -102
  112. package/src/hooks/useViabilitySession.ts +336 -335
  113. package/src/index.ts +60 -63
  114. package/src/lib/api-handler.ts +0 -1
  115. package/src/lib/app-slug.ts +6 -6
  116. package/src/lib/standardized-client-api.ts +901 -895
  117. package/src/lib/startup-init.ts +243 -247
  118. package/src/lib/test-aware-get-token.ts +22 -12
  119. package/src/lib/token-lifecycle.ts +12 -53
  120. package/src/pages/admin-login/page.tsx +9 -17
  121. package/src/pages/client-admin/ClientSiteAdminPage.tsx +4 -2
  122. package/src/pages/login/page.tsx +21 -28
  123. package/src/pages/showcase/ShowcasePage.tsx +4 -2
  124. package/src/pages/test-env/EmergencyLogoutPage.tsx +7 -6
  125. package/src/pages/test-env/JwtInspectPage.tsx +5 -3
  126. package/src/pages/test-env/RefreshTokenPage.tsx +157 -155
  127. package/src/pages/test-env/TestEnvPage.tsx +4 -2
  128. package/src/pages/verify-code/page.tsx +10 -6
  129. package/src/routes/auth/logout.ts +7 -25
  130. package/src/routes/auth/nextauth.ts +45 -71
  131. package/src/routes/auth/session.ts +25 -50
  132. package/src/routes/auth/viability.ts +7 -19
  133. package/src/server/auth.ts +60 -0
  134. package/src/stores/authStore.ts +1899 -1904
  135. package/src/utils/logout.ts +30 -30
  136. package/src/auth/auth-options.ts +0 -237
  137. package/src/auth/callbacks/index.ts +0 -7
  138. package/src/auth/callbacks/jwt.ts +0 -382
  139. package/src/auth/callbacks/session.ts +0 -243
  140. package/src/auth/callbacks/signin.ts +0 -56
  141. package/src/auth/events/index.ts +0 -5
  142. package/src/auth/events/signout.ts +0 -33
  143. package/src/auth/providers/credentials.ts +0 -256
  144. package/src/auth/providers/index.ts +0 -6
  145. package/src/auth/providers/oauth.ts +0 -114
  146. package/src/lib/nextauth-secret.ts +0 -121
  147. 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 react_1 = require("next-auth/react");
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 (0, react_1.getSession)();
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 react_2 = require("next-auth/react");
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: session, status } = (0, react_2.useSession)();
42
- const { setSession, clearSession } = (0, authStore_1.useAuthStore)();
43
- // Guard against duplicate sign-out calls
44
- const isSigningOutRef = (0, react_1.useRef)(false);
45
- (0, react_1.useEffect)(() => {
46
- let isMounted = true;
47
- // Only process when NextAuth has finished loading
48
- if (status === 'loading') {
49
- return;
50
- }
51
- // Strict validation: Check if session is actually valid
52
- const isValid = (0, session_1.isValidSession)(session);
53
- // CRITICAL FIX: If NextAuth says "authenticated" but session is invalid,
54
- // this is a broken state that must be fixed immediately
55
- if (status === 'authenticated' && !isValid) {
56
- // GUARD: Prevent duplicate sign-out
57
- if (isSigningOutRef.current) {
58
- console.warn('[SessionSync] Sign-out already in progress, skipping');
59
- return;
60
- }
61
- isSigningOutRef.current = true;
62
- console.error('[SessionSync] CRITICAL: Invalid session detected despite authenticated status!');
63
- console.error('[SessionSync] This indicates a session with empty user data - forcing sign-out');
64
- // Log diagnostic info with PII redaction
65
- // Note: session is typed as Session | null from NextAuth, but may have invalid/partial data
66
- const sessionData = session; // Explicit cast needed for error logging
67
- console.error('[SessionSync] Session data:', {
68
- hasSessionObject: !!sessionData,
69
- hasUser: !!sessionData?.user,
70
- userId: sanitizeForLog(sessionData?.user?.id, 'userId'),
71
- userEmail: sanitizeForLog(sessionData?.user?.email, 'email'),
72
- hasAccessToken: !!sessionData?.accessToken,
73
- });
74
- // Clear the auth store immediately
75
- clearSession();
76
- // FIX: Force clear all auth cookies including stale provisional tokens (app-slug prefixed)
77
- // This prevents infinite loop when provisional token exists but session is invalid
78
- // Root cause: OAuth creates provisional token before Redis session exists
79
- try {
80
- const secureSession = (0, app_slug_1.getSecureSessionCookieName)();
81
- const secureCsrf = (0, app_slug_1.getSecureCsrfCookieName)();
82
- const callbackUrl = (0, app_slug_1.getCallbackUrlCookieName)();
83
- document.cookie = `${secureSession}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; Secure; SameSite=Lax`;
84
- document.cookie = `${secureCsrf}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax`;
85
- document.cookie = `__Secure-${callbackUrl}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; Secure; SameSite=Lax`;
86
- }
87
- catch (e) {
88
- // Cookie clearing failed - non-critical, continue with signout
89
- }
90
- // Force NextAuth to sign out (this will clear cookies and trigger redirect)
91
- (0, react_2.signOut)({ redirect: false })
92
- .then(() => {
93
- if (isMounted) {
94
- // Use generic error code instead of implementation details
95
- window.location.href = '/account-auth/login?error=SessionExpired&code=1001';
96
- }
97
- })
98
- .catch((err) => {
99
- console.error('[SessionSync] Error during forced signout:', err);
100
- if (isMounted) {
101
- window.location.href = '/account-auth/login?error=SessionExpired&code=1001';
102
- }
103
- });
104
- return;
105
- }
106
- // Normal flow: Update store based on valid session status
107
- if (status === 'authenticated' && isValid) {
108
- setSession(session);
109
- }
110
- else if (status === 'unauthenticated') {
111
- clearSession();
112
- }
113
- // Cleanup function to prevent post-unmount updates
114
- return () => {
115
- isMounted = false;
116
- };
117
- }, [session, status, setSession, clearSession]);
118
- return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
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 react_2 = require("next-auth/react");
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 } = (0, react_2.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
- (0, react_2.signIn)(undefined, { 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() || '?';
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 react_2 = require("next-auth/react");
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: session, status } = (0, react_2.useSession)();
16
- const router = (0, navigation_1.useRouter)();
17
- const [isOpen, setIsOpen] = (0, react_1.useState)(false);
18
- const menuRef = (0, react_1.useRef)(null);
19
- // Close menu when clicking outside
20
- (0, react_1.useEffect)(() => {
21
- function handleClickOutside(event) {
22
- if (menuRef.current && !menuRef.current.contains(event.target)) {
23
- setIsOpen(false);
24
- }
25
- }
26
- if (isOpen) {
27
- document.addEventListener('mousedown', handleClickOutside);
28
- return () => document.removeEventListener('mousedown', handleClickOutside);
29
- }
30
- }, [isOpen]);
31
- // Close menu on Escape
32
- (0, react_1.useEffect)(() => {
33
- function handleEscape(event) {
34
- if (event.key === 'Escape') {
35
- setIsOpen(false);
36
- }
37
- }
38
- if (isOpen) {
39
- document.addEventListener('keydown', handleEscape);
40
- return () => document.removeEventListener('keydown', handleEscape);
41
- }
42
- }, [isOpen]);
43
- // Loading state
44
- if (status === 'loading') {
45
- return ((0, jsx_runtime_1.jsx)("div", { className: "h-10 w-10 rounded-full bg-gray-200 dark:bg-white/10 animate-pulse" }));
46
- }
47
- // Not authenticated
48
- if (!session?.user) {
49
- return null;
50
- }
51
- // Derive display initial from name or email — ignore anon/internal IDs
52
- const userName = session.user?.name;
53
- const userEmail = session.user.email;
54
- const displaySource = userName || userEmail;
55
- const userInitial = displaySource?.charAt(0).toUpperCase() || '?';
56
- const handleNavigation = (path) => {
57
- setIsOpen(false);
58
- router.push(path);
59
- };
60
- const handleSignOut = async () => {
61
- setIsOpen(false);
62
- if (onSignOut) {
63
- onSignOut();
64
- }
65
- else {
66
- // Use NEXT_PUBLIC env var or default to root
67
- const logoutUrl = process.env.NEXT_PUBLIC_LOGOUT_REDIRECT_URL || '/';
68
- await (0, react_2.signOut)({ callbackUrl: logoutUrl });
69
- }
70
- };
71
- const handleItemClick = (item) => {
72
- setIsOpen(false);
73
- if (item.onClick) {
74
- item.onClick();
75
- }
76
- else if (item.href) {
77
- router.push(item.href);
78
- }
79
- };
80
- 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" }) })] }))] }));
81
- }
82
- function MenuItem({ icon, label, onClick, variant = 'default' }) {
83
- const baseClasses = "flex items-center w-full px-4 py-2 text-sm cursor-pointer transition-colors";
84
- const variantClasses = variant === 'danger'
85
- ? "text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-slate-800"
86
- : "text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-slate-800";
87
- 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 })] }));
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
+ }