@payez/next-mvp 4.1.7 → 4.1.9

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 (154) hide show
  1. package/README.md +792 -792
  2. package/dist/auth/better-auth.js +15 -3
  3. package/dist/components/account/MobileNavDrawer.js +18 -18
  4. package/dist/components/account/UserAvatarMenu.js +1 -1
  5. package/dist/components/admin/AlertSettingsTab.js +40 -40
  6. package/dist/components/admin/LoggingSettingsTab.js +28 -28
  7. package/dist/pages/login/page.js +28 -28
  8. package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
  9. package/package.json +1 -1
  10. package/scripts/dev-broker.ps1 +35 -35
  11. package/scripts/dev-local.ps1 +45 -45
  12. package/src/api/auth-handler.ts +548 -548
  13. package/src/api-handlers/account/change-password.ts +142 -142
  14. package/src/api-handlers/account/update-phone.ts +79 -79
  15. package/src/api-handlers/account/verify-email.ts +125 -125
  16. package/src/api-handlers/account/verify-sms.ts +125 -125
  17. package/src/api-handlers/admin/analytics.ts +443 -443
  18. package/src/api-handlers/admin/audit.ts +223 -223
  19. package/src/api-handlers/admin/index.ts +63 -63
  20. package/src/api-handlers/admin/redis-sessions.ts +251 -251
  21. package/src/api-handlers/admin/sessions.ts +318 -318
  22. package/src/api-handlers/admin/site-logs.ts +365 -365
  23. package/src/api-handlers/admin/users.ts +242 -242
  24. package/src/api-handlers/admin/vibe-data.ts +324 -324
  25. package/src/api-handlers/auth/refresh.ts +682 -682
  26. package/src/api-handlers/auth/signout.ts +199 -199
  27. package/src/api-handlers/auth/status.ts +20 -20
  28. package/src/api-handlers/auth/update-session.ts +110 -110
  29. package/src/api-handlers/auth/verify-code.ts +112 -112
  30. package/src/api-handlers/session/refresh-viability.ts +36 -36
  31. package/src/api-handlers/test/force-expire.ts +60 -60
  32. package/src/auth/auth-decision.ts +229 -229
  33. package/src/auth/better-auth.ts +15 -3
  34. package/src/auth/route-config.ts +219 -219
  35. package/src/auth/types/auth-types.ts +555 -555
  36. package/src/auth/types/index.ts +7 -7
  37. package/src/auth/utils/idp-client.ts +445 -445
  38. package/src/auth/utils/index.ts +6 -6
  39. package/src/auth/utils/token-utils.ts +243 -243
  40. package/src/client/AuthContext.tsx +143 -143
  41. package/src/client/fetch-with-auth.ts +47 -47
  42. package/src/components/SessionSync.tsx +138 -138
  43. package/src/components/SignalRHealthCheck.tsx +131 -131
  44. package/src/components/account/MobileNavDrawer.tsx +305 -305
  45. package/src/components/account/UserAvatarMenu.tsx +266 -266
  46. package/src/components/account/index.ts +13 -13
  47. package/src/components/admin/AlertSettingsTab.tsx +728 -728
  48. package/src/components/admin/AnalyticsTab.tsx +703 -703
  49. package/src/components/admin/DataBrowserTab.tsx +505 -505
  50. package/src/components/admin/LoggingSettingsTab.tsx +665 -665
  51. package/src/components/admin/SessionsTab.tsx +414 -414
  52. package/src/components/admin/StatsTab.tsx +379 -379
  53. package/src/components/admin/VibeAdminLayout.tsx +187 -187
  54. package/src/components/admin/index.ts +59 -59
  55. package/src/components/auth/FederatedAuthSection.tsx +95 -95
  56. package/src/components/auth/ModeAwareLoginPage.tsx +135 -135
  57. package/src/components/auth/ModeAwareSignupPage.tsx +267 -267
  58. package/src/components/auth/TraditionalAuthSection.tsx +99 -99
  59. package/src/components/recovery/CompleteStep.tsx +36 -36
  60. package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
  61. package/src/components/recovery/SelectMethodStep.tsx +73 -73
  62. package/src/components/recovery/SetPasswordStep.tsx +97 -97
  63. package/src/components/recovery/VerifyCodeStep.tsx +90 -90
  64. package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -160
  65. package/src/components/reserved/ReservedStatusBox.tsx +118 -118
  66. package/src/components/ui/BetaBadge.tsx +58 -58
  67. package/src/components/ui/Footer.tsx +93 -93
  68. package/src/config/env.ts +57 -57
  69. package/src/config/logger.ts +62 -62
  70. package/src/config/vibe-log-transport.ts +264 -264
  71. package/src/hooks/useAuth.ts +117 -117
  72. package/src/hooks/useAuthSettings.ts +97 -97
  73. package/src/hooks/useAvailableProviders.ts +120 -120
  74. package/src/hooks/usePasswordValidation.ts +127 -127
  75. package/src/hooks/usePublicAuthSettings.ts +149 -149
  76. package/src/hooks/useSessionExpiration.ts +101 -101
  77. package/src/hooks/useViabilitySession.ts +336 -336
  78. package/src/index.ts +60 -60
  79. package/src/lib/api-handler.ts +643 -643
  80. package/src/lib/app-slug.ts +178 -178
  81. package/src/lib/geolocation.ts +265 -265
  82. package/src/lib/idp-fetch.ts +110 -110
  83. package/src/lib/jwt-decode.ts +82 -82
  84. package/src/lib/roles.ts +177 -177
  85. package/src/lib/site-logger.ts +245 -245
  86. package/src/lib/standardized-client-api.ts +901 -901
  87. package/src/lib/test-aware-get-token.ts +41 -41
  88. package/src/lib/user-agent-parser.ts +252 -252
  89. package/src/logging/api/admin-analytics.ts +51 -51
  90. package/src/logging/api/audit-log.ts +53 -53
  91. package/src/logging/components/AdminAnalyticsLayout.tsx +49 -49
  92. package/src/logging/components/AuditLogViewer.tsx +125 -125
  93. package/src/logging/components/ErrorMetricsCard.tsx +98 -98
  94. package/src/logging/components/HealthMetricsCard.tsx +70 -70
  95. package/src/logging/hooks/useAdminAnalytics.ts +22 -22
  96. package/src/logging/hooks/useAuditLog.ts +24 -24
  97. package/src/logging/hooks/useErrorMetrics.ts +40 -40
  98. package/src/logging/hooks/useHealthMetrics.ts +44 -44
  99. package/src/logging/index.ts +18 -18
  100. package/src/logging/types/analytics.ts +81 -81
  101. package/src/logging/types/audit.ts +31 -31
  102. package/src/logging/types/index.ts +3 -3
  103. package/src/middleware/create-middleware.ts +663 -663
  104. package/src/middleware/rbac-check.ts +279 -279
  105. package/src/middleware/twofa-presets.ts +224 -224
  106. package/src/pages/admin-login/page.tsx +221 -221
  107. package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -527
  108. package/src/pages/admin-page-permissions/index.ts +7 -7
  109. package/src/pages/admin-roles/RolesAdminPage.tsx +533 -533
  110. package/src/pages/admin-roles/modals.tsx +469 -469
  111. package/src/pages/client-admin/ClientSiteAdminPage.tsx +382 -382
  112. package/src/pages/client-admin/index.ts +33 -33
  113. package/src/pages/coming-soon/page.tsx +83 -83
  114. package/src/pages/login/page.tsx +456 -456
  115. package/src/pages/profile/EnhancedProfilePage.tsx +485 -485
  116. package/src/pages/profile/index.ts +9 -9
  117. package/src/pages/profile/page.tsx +166 -166
  118. package/src/pages/recovery/page.tsx +234 -234
  119. package/src/pages/roles/MyRolesPage.tsx +211 -211
  120. package/src/pages/roles/components.tsx +294 -294
  121. package/src/pages/roles/index.ts +17 -17
  122. package/src/pages/security/EnhancedSecurityPage.tsx +565 -565
  123. package/src/pages/security/index.ts +9 -9
  124. package/src/pages/security/page.tsx +507 -507
  125. package/src/pages/settings/EnhancedSettingsPage.tsx +642 -642
  126. package/src/pages/settings/index.ts +9 -9
  127. package/src/pages/settings/page.tsx +47 -47
  128. package/src/pages/showcase/ShowcasePage.tsx +532 -532
  129. package/src/pages/showcase/index.ts +13 -13
  130. package/src/pages/test-env/EmergencyLogoutPage.tsx +180 -180
  131. package/src/pages/test-env/JwtInspectPage.tsx +456 -456
  132. package/src/pages/test-env/RefreshTokenPage.tsx +157 -157
  133. package/src/pages/test-env/TestEnvPage.tsx +118 -118
  134. package/src/pages/test-env/index.ts +25 -25
  135. package/src/pages/verify-code/page.tsx +652 -652
  136. package/src/routes/account/update-phone.ts +13 -13
  137. package/src/routes/auth/logout.ts +108 -108
  138. package/src/routes/auth/nextauth.ts +45 -45
  139. package/src/routes/auth/settings.ts +75 -75
  140. package/src/routes/auth/viability.ts +224 -224
  141. package/src/server/auth-guard.ts +170 -170
  142. package/src/server/slim-middleware.ts +117 -117
  143. package/src/server/with-auth.ts +93 -93
  144. package/src/stores/authStore.ts +1899 -1899
  145. package/src/templates/instrumentation.ts +41 -41
  146. package/src/utils/logout.ts +30 -30
  147. package/src/vibe/client.ts +590 -590
  148. package/src/vibe/enterprise-auth.ts +208 -208
  149. package/src/vibe/errors.ts +185 -185
  150. package/src/vibe/generic.ts +431 -431
  151. package/src/vibe/hooks/index.ts +367 -367
  152. package/src/vibe/index.ts +132 -132
  153. package/src/vibe/sessions.ts +551 -551
  154. package/src/vibe/types.ts +577 -577
@@ -326,8 +326,20 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
326
326
  return false;
327
327
  }
328
328
  // Build IDP token data
329
- // CRITICAL: If the IDP withholds refresh_token without setting requiresTwoFactor,
330
- // treat missing refresh token as "2FA required" so middleware redirects to verify-code.
329
+ // ORIGINAL INTENT: IDP withholds refresh_token until 2FA is complete, so
330
+ // !hasRefreshToken was a reliable "2FA required" signal.
331
+ //
332
+ // BUG [IDP-2FA-BUG]: IDP is currently returning refresh_token on the initial
333
+ // oauth-callback even for require_2fa tenants that haven't done 2FA yet.
334
+ // This made !hasRefreshToken=false → requiresTwoFactor=false → mfaVerified=true,
335
+ // bypassing the 2FA gate entirely.
336
+ //
337
+ // FIX: Always set mfaVerified=false on the initial OAuth exchange.
338
+ // The tenant's require_2fa setting is enforced by the viability endpoint
339
+ // (tenantRequiresTwoFactor=true), which redirects to verify-code whenever
340
+ // mfaVerified=false. After real 2FA, transitionTo2FASession() sets it true.
341
+ // Revert this comment + restore `mfaVerified: !requiresTwoFactor` once the
342
+ // IDP correctly withholds refresh_token until post-2FA.
331
343
  const hasRefreshToken = !!result.refresh_token;
332
344
  const requiresTwoFactor = result.user?.requiresTwoFactor ?? result.requiresTwoFactor ?? !hasRefreshToken;
333
345
  const idpTokenData = {
@@ -341,7 +353,7 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
341
353
  name: result.user?.full_name || result.user?.name || result.name || name,
342
354
  image: image,
343
355
  roles: result.user?.roles || result.roles || [],
344
- mfaVerified: !requiresTwoFactor,
356
+ mfaVerified: false, // Always false post-OAuth — see IDP-2FA-BUG note above
345
357
  idpClientId: result.client_id ? String(result.client_id) : undefined,
346
358
  merchantId: result.merchant_id ? String(result.merchant_id) : undefined,
347
359
  };
@@ -58,24 +58,24 @@ function MobileNavDrawer({ isOpen, onClose, navItems, customSections, basePath =
58
58
  const userEmail = session?.user?.email;
59
59
  const displaySource = userName || userEmail;
60
60
  const userInitial = displaySource?.charAt(0).toUpperCase() || '?';
61
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `
62
- fixed inset-0 bg-black/50 backdrop-blur-sm z-40 lg:hidden
63
- transition-opacity duration-300
64
- ${isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'}
65
- `, onClick: onClose, "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { role: "dialog", "aria-modal": "true", "aria-label": "Navigation menu", "aria-expanded": isOpen, className: `
66
- fixed top-0 right-0 bottom-0 w-80 max-w-[85vw]
67
- bg-white dark:bg-slate-900
68
- shadow-[-8px_0_32px_rgba(0,0,0,0.15)]
69
- dark:shadow-[-8px_0_32px_rgba(0,0,0,0.4)]
70
- z-50 lg:hidden
71
- overflow-y-auto
72
- transition-transform duration-300 ease-out
73
- ${isOpen ? 'translate-x-0' : 'translate-x-full'}
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
- flex items-center gap-3 px-4 py-3.5 rounded-xl
76
- transition-colors duration-200
61
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `
62
+ fixed inset-0 bg-black/50 backdrop-blur-sm z-40 lg:hidden
63
+ transition-opacity duration-300
64
+ ${isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'}
65
+ `, onClick: onClose, "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { role: "dialog", "aria-modal": "true", "aria-label": "Navigation menu", "aria-expanded": isOpen, className: `
66
+ fixed top-0 right-0 bottom-0 w-80 max-w-[85vw]
67
+ bg-white dark:bg-slate-900
68
+ shadow-[-8px_0_32px_rgba(0,0,0,0.15)]
69
+ dark:shadow-[-8px_0_32px_rgba(0,0,0,0.4)]
70
+ z-50 lg:hidden
71
+ overflow-y-auto
72
+ transition-transform duration-300 ease-out
73
+ ${isOpen ? 'translate-x-0' : 'translate-x-full'}
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: "\n p-2 rounded-xl\n text-gray-400 hover:text-gray-900\n dark:hover:text-white\n hover:bg-gray-100 dark:hover:bg-white/10\n transition-colors\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
+ flex items-center gap-3 px-4 py-3.5 rounded-xl
76
+ transition-colors duration-200
77
77
  ${isActiveRoute(item.href)
78
78
  ? 'bg-blue-500/10 text-blue-500'
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" }))) })] })] }));
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: "\n flex items-center gap-3 px-4 py-3 rounded-xl\n text-gray-900 dark:text-white\n hover:bg-gray-100 dark:hover:bg-white/10\n transition-colors\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: "\n flex items-center gap-3 px-4 py-3 rounded-xl w-full text-left\n text-gray-900 dark:text-white\n hover:bg-gray-100 dark:hover:bg-white/10\n transition-colors\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: "\n w-full px-4 py-3 rounded-xl\n text-blue-500 font-semibold\n border border-blue-500/30\n hover:bg-blue-500/10\n transition-colors\n ", children: "Login" }) }))) : (authFooter ?? ((0, jsx_runtime_1.jsx)(link_1.default, { href: basePath, onClick: onClose, className: "\n flex items-center justify-center gap-2\n w-full px-4 py-3 rounded-xl\n text-gray-500 dark:text-slate-400 font-medium\n hover:bg-gray-100 dark:hover:bg-white/10\n transition-colors\n ", children: "Account Settings" }))) })] })] }));
81
81
  }
@@ -80,7 +80,7 @@ function UserAvatarMenu({ basePath = '', showProfile = true, showSettings = true
80
80
  router.push(item.href);
81
81
  }
82
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" }) })] }))] }));
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\n bg-white dark:bg-slate-900\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
84
  }
85
85
  function MenuItem({ icon, label, onClick, variant = 'default' }) {
86
86
  const baseClasses = "flex items-center w-full px-4 py-2 text-sm cursor-pointer transition-colors";
@@ -277,22 +277,22 @@ function AlertSettingsTab({ isDark = true, apiBasePath = '/api/admin/alerts', on
277
277
  e.preventDefault();
278
278
  addRecipient();
279
279
  }
280
- }, placeholder: "Enter email address", className: `
281
- w-full px-3 py-2 rounded-lg text-sm
282
- ${themeClasses.inputBg} ${themeClasses.textPrimary} border
283
- focus:outline-none focus:ring-2 focus:ring-indigo-500
284
- ${recipientError ? (isDark ? 'border-red-500' : 'border-red-400') : ''}
285
- ` }), recipientError && ((0, jsx_runtime_1.jsx)("p", { className: `text-xs mt-1 ${isDark ? 'text-red-400' : 'text-red-500'}`, children: recipientError }))] }), (0, jsx_runtime_1.jsxs)("button", { onClick: addRecipient, className: `
286
- px-4 py-2 rounded-lg font-medium text-sm transition-colors
287
- ${isDark ? 'bg-indigo-600 hover:bg-indigo-700' : 'bg-indigo-500 hover:bg-indigo-600'}
288
- text-white flex items-center gap-2
280
+ }, placeholder: "Enter email address", className: `
281
+ w-full px-3 py-2 rounded-lg text-sm
282
+ ${themeClasses.inputBg} ${themeClasses.textPrimary} border
283
+ focus:outline-none focus:ring-2 focus:ring-indigo-500
284
+ ${recipientError ? (isDark ? 'border-red-500' : 'border-red-400') : ''}
285
+ ` }), recipientError && ((0, jsx_runtime_1.jsx)("p", { className: `text-xs mt-1 ${isDark ? 'text-red-400' : 'text-red-500'}`, children: recipientError }))] }), (0, jsx_runtime_1.jsxs)("button", { onClick: addRecipient, className: `
286
+ px-4 py-2 rounded-lg font-medium text-sm transition-colors
287
+ ${isDark ? 'bg-indigo-600 hover:bg-indigo-700' : 'bg-indigo-500 hover:bg-indigo-600'}
288
+ text-white flex items-center gap-2
289
289
  `, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 16 }), "Add"] })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${themeClasses.cardBg} border rounded-xl overflow-hidden`, children: [(0, jsx_runtime_1.jsxs)("div", { className: `px-5 py-4 border-b ${isDark ? 'border-slate-700' : 'border-gray-200'}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Clock, { className: isDark ? 'text-cyan-400' : 'text-cyan-500', size: 20 }), (0, jsx_runtime_1.jsx)("h3", { className: `font-semibold ${themeClasses.textPrimary}`, children: "Delivery Mode" })] }), (0, jsx_runtime_1.jsx)("p", { className: `text-sm ${themeClasses.textMuted} mt-1`, children: "How alerts are delivered to recipients" })] }), (0, jsx_runtime_1.jsx)("div", { className: "p-5", children: (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3", children: DELIVERY_MODES.map((mode) => {
290
290
  const isSelected = editedMode === mode.value;
291
- return ((0, jsx_runtime_1.jsxs)("button", { onClick: () => setEditedMode(mode.value), className: `
292
- p-4 rounded-lg border-2 text-left transition-all
291
+ return ((0, jsx_runtime_1.jsxs)("button", { onClick: () => setEditedMode(mode.value), className: `
292
+ p-4 rounded-lg border-2 text-left transition-all
293
293
  ${isSelected
294
294
  ? 'border-indigo-500 ' + (isDark ? 'bg-indigo-500/10' : 'bg-indigo-50')
295
- : (isDark ? 'border-slate-700 hover:border-slate-600' : 'border-gray-200 hover:border-gray-300')}
295
+ : (isDark ? 'border-slate-700 hover:border-slate-600' : 'border-gray-200 hover:border-gray-300')}
296
296
  `, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 mb-1", children: [(0, jsx_runtime_1.jsx)("div", { className: `w-3 h-3 rounded-full border-2 flex items-center justify-center ${isSelected ? 'border-indigo-500' : (isDark ? 'border-slate-500' : 'border-gray-400')}`, children: isSelected && (0, jsx_runtime_1.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-indigo-500" }) }), (0, jsx_runtime_1.jsx)("span", { className: `font-medium ${themeClasses.textPrimary}`, children: mode.label })] }), (0, jsx_runtime_1.jsx)("p", { className: `text-xs ${themeClasses.textMuted} ml-5`, children: mode.description })] }, mode.value));
297
297
  }) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${themeClasses.cardBg} border rounded-xl overflow-hidden`, children: [(0, jsx_runtime_1.jsxs)("div", { className: `px-5 py-4 border-b ${isDark ? 'border-slate-700' : 'border-gray-200'}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Bell, { className: isDark ? 'text-amber-400' : 'text-amber-500', size: 20 }), (0, jsx_runtime_1.jsx)("h3", { className: `font-semibold ${themeClasses.textPrimary}`, children: "Alert Types" })] }), (0, jsx_runtime_1.jsx)("p", { className: `text-sm ${themeClasses.textMuted} mt-1`, children: "Configure which alerts to receive and their thresholds" })] }), (0, jsx_runtime_1.jsx)("div", { className: "divide-y divide-slate-700/50", children: ALERT_TYPES.map((alertType) => {
298
298
  const config = editedAlerts[alertType.id];
@@ -305,47 +305,47 @@ function AlertSettingsTab({ isDark = true, apiBasePath = '/api/admin/alerts', on
305
305
  else {
306
306
  updateAlertConfig(alertType.id, { threshold: val });
307
307
  }
308
- }, disabled: !config.enabled && !alertType.alwaysSent, className: `
309
- w-16 px-2 py-1.5 rounded text-sm text-center
310
- ${themeClasses.inputBg} ${themeClasses.textPrimary} border
311
- focus:outline-none focus:ring-2 focus:ring-indigo-500
312
- disabled:opacity-50
313
- ` }), (0, jsx_runtime_1.jsx)("span", { className: `text-xs ${themeClasses.textMuted} whitespace-nowrap`, children: alertType.thresholdUnit })] })), !alertType.alwaysSent && ((0, jsx_runtime_1.jsx)("button", { onClick: () => updateAlertConfig(alertType.id, { enabled: !config.enabled }), className: `
314
- relative w-11 h-6 rounded-full transition-colors
308
+ }, disabled: !config.enabled && !alertType.alwaysSent, className: `
309
+ w-16 px-2 py-1.5 rounded text-sm text-center
310
+ ${themeClasses.inputBg} ${themeClasses.textPrimary} border
311
+ focus:outline-none focus:ring-2 focus:ring-indigo-500
312
+ disabled:opacity-50
313
+ ` }), (0, jsx_runtime_1.jsx)("span", { className: `text-xs ${themeClasses.textMuted} whitespace-nowrap`, children: alertType.thresholdUnit })] })), !alertType.alwaysSent && ((0, jsx_runtime_1.jsx)("button", { onClick: () => updateAlertConfig(alertType.id, { enabled: !config.enabled }), className: `
314
+ relative w-11 h-6 rounded-full transition-colors
315
315
  ${config.enabled
316
316
  ? (isDark ? 'bg-indigo-600' : 'bg-indigo-500')
317
- : (isDark ? 'bg-slate-600' : 'bg-gray-300')}
318
- `, children: (0, jsx_runtime_1.jsx)("span", { className: `
319
- absolute top-1 left-1 w-4 h-4 rounded-full bg-white transition-transform
320
- ${config.enabled ? 'translate-x-5' : ''}
317
+ : (isDark ? 'bg-slate-600' : 'bg-gray-300')}
318
+ `, children: (0, jsx_runtime_1.jsx)("span", { className: `
319
+ absolute top-1 left-1 w-4 h-4 rounded-full bg-white transition-transform
320
+ ${config.enabled ? 'translate-x-5' : ''}
321
321
  ` }) }))] })] }) }, alertType.id));
322
- }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${themeClasses.cardBg} border rounded-xl overflow-hidden`, children: [(0, jsx_runtime_1.jsx)("div", { className: `px-5 py-4 border-b ${isDark ? 'border-slate-700' : 'border-gray-200'}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Zap, { className: isDark ? 'text-emerald-400' : 'text-emerald-500', size: 20 }), (0, jsx_runtime_1.jsx)("h3", { className: `font-semibold ${themeClasses.textPrimary}`, children: "Rate Limiting" })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "p-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-4", children: [(0, jsx_runtime_1.jsx)("label", { className: `text-sm ${themeClasses.textSecondary}`, children: "Maximum alerts:" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: "1", max: "100", value: editedRateLimit, onChange: (e) => setEditedRateLimit(parseInt(e.target.value) || 10), className: `
323
- w-20 px-3 py-2 rounded-lg text-sm
324
- ${themeClasses.inputBg} ${themeClasses.textPrimary} border
325
- focus:outline-none focus:ring-2 focus:ring-indigo-500
322
+ }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${themeClasses.cardBg} border rounded-xl overflow-hidden`, children: [(0, jsx_runtime_1.jsx)("div", { className: `px-5 py-4 border-b ${isDark ? 'border-slate-700' : 'border-gray-200'}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Zap, { className: isDark ? 'text-emerald-400' : 'text-emerald-500', size: 20 }), (0, jsx_runtime_1.jsx)("h3", { className: `font-semibold ${themeClasses.textPrimary}`, children: "Rate Limiting" })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "p-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-4", children: [(0, jsx_runtime_1.jsx)("label", { className: `text-sm ${themeClasses.textSecondary}`, children: "Maximum alerts:" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: "1", max: "100", value: editedRateLimit, onChange: (e) => setEditedRateLimit(parseInt(e.target.value) || 10), className: `
323
+ w-20 px-3 py-2 rounded-lg text-sm
324
+ ${themeClasses.inputBg} ${themeClasses.textPrimary} border
325
+ focus:outline-none focus:ring-2 focus:ring-indigo-500
326
326
  ` }), (0, jsx_runtime_1.jsx)("span", { className: `text-sm ${themeClasses.textMuted}`, children: "per hour" })] }), (0, jsx_runtime_1.jsxs)("div", { className: `flex items-start gap-2 mt-3 text-xs ${themeClasses.textMuted}`, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Info, { size: 14, className: "flex-shrink-0 mt-0.5" }), (0, jsx_runtime_1.jsx)("span", { children: "Critical alerts (Storage Critical, Agent Expired) are always sent regardless of this limit." })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${themeClasses.cardBg} border rounded-xl overflow-hidden`, children: [(0, jsx_runtime_1.jsx)("div", { className: `px-5 py-4 border-b ${isDark ? 'border-slate-700' : 'border-gray-200'}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Send, { className: isDark ? 'text-blue-400' : 'text-blue-500', size: 20 }), (0, jsx_runtime_1.jsx)("h3", { className: `font-semibold ${themeClasses.textPrimary}`, children: "Test & History" })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "p-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsxs)("select", { disabled: sendingTest || editedRecipients.length === 0, onChange: (e) => {
327
327
  if (e.target.value) {
328
328
  sendTestAlert(e.target.value);
329
329
  e.target.value = '';
330
330
  }
331
- }, className: `
332
- px-4 py-2.5 pr-10 rounded-lg text-sm font-medium cursor-pointer appearance-none
333
- ${isDark ? 'bg-blue-900/30 text-blue-300 border-blue-700 hover:bg-blue-900/50' : 'bg-blue-50 text-blue-700 border-blue-300 hover:bg-blue-100'}
334
- border focus:outline-none focus:ring-2 focus:ring-blue-500
335
- disabled:opacity-50 disabled:cursor-not-allowed
331
+ }, className: `
332
+ px-4 py-2.5 pr-10 rounded-lg text-sm font-medium cursor-pointer appearance-none
333
+ ${isDark ? 'bg-blue-900/30 text-blue-300 border-blue-700 hover:bg-blue-900/50' : 'bg-blue-50 text-blue-700 border-blue-300 hover:bg-blue-100'}
334
+ border focus:outline-none focus:ring-2 focus:ring-blue-500
335
+ disabled:opacity-50 disabled:cursor-not-allowed
336
336
  `, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: "Send Test Alert..." }), TEST_ALERT_OPTIONS.map((opt) => ((0, jsx_runtime_1.jsx)("option", { value: opt.value, children: opt.label }, opt.value)))] }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: `absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 pointer-events-none ${isDark ? 'text-blue-400' : 'text-blue-500'}` })] }), (0, jsx_runtime_1.jsxs)("button", { onClick: () => {
337
337
  // TODO: Open history modal or navigate to history page
338
338
  window.open(`${apiBasePath}/history`, '_blank');
339
- }, className: `
340
- px-4 py-2.5 rounded-lg text-sm font-medium flex items-center gap-2
341
- ${isDark ? 'bg-slate-700 text-white hover:bg-slate-600' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}
342
- border ${isDark ? 'border-slate-600' : 'border-gray-300'}
343
- `, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.History, { size: 16 }), "View Alert History"] })] }), editedRecipients.length === 0 && ((0, jsx_runtime_1.jsx)("p", { className: `text-xs mt-3 ${themeClasses.textMuted}`, children: "Add at least one recipient to send test alerts." }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-end gap-4", children: [hasChanges && ((0, jsx_runtime_1.jsx)("span", { className: `text-sm ${themeClasses.textMuted}`, children: "You have unsaved changes" })), (0, jsx_runtime_1.jsx)("button", { onClick: saveSettings, disabled: !hasChanges || saving, className: `
344
- px-6 py-2.5 rounded-lg font-medium text-sm transition-all
339
+ }, className: `
340
+ px-4 py-2.5 rounded-lg text-sm font-medium flex items-center gap-2
341
+ ${isDark ? 'bg-slate-700 text-white hover:bg-slate-600' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}
342
+ border ${isDark ? 'border-slate-600' : 'border-gray-300'}
343
+ `, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.History, { size: 16 }), "View Alert History"] })] }), editedRecipients.length === 0 && ((0, jsx_runtime_1.jsx)("p", { className: `text-xs mt-3 ${themeClasses.textMuted}`, children: "Add at least one recipient to send test alerts." }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-end gap-4", children: [hasChanges && ((0, jsx_runtime_1.jsx)("span", { className: `text-sm ${themeClasses.textMuted}`, children: "You have unsaved changes" })), (0, jsx_runtime_1.jsx)("button", { onClick: saveSettings, disabled: !hasChanges || saving, className: `
344
+ px-6 py-2.5 rounded-lg font-medium text-sm transition-all
345
345
  ${hasChanges
346
346
  ? 'bg-indigo-600 text-white hover:bg-indigo-700'
347
- : (isDark ? 'bg-slate-700 text-slate-400' : 'bg-gray-200 text-gray-400')}
348
- disabled:cursor-not-allowed
347
+ : (isDark ? 'bg-slate-700 text-slate-400' : 'bg-gray-200 text-gray-400')}
348
+ disabled:cursor-not-allowed
349
349
  `, children: saving ? ((0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.RefreshCw, { className: "w-4 h-4 animate-spin" }), "Saving..."] })) : ('Save Settings') })] })] }));
350
350
  }
351
351
  exports.default = AlertSettingsTab;
@@ -280,18 +280,18 @@ function LoggingSettingsTab({ isDark = true, apiBasePath = '/api/admin/logging',
280
280
  }
281
281
  return ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [error && ((0, jsx_runtime_1.jsxs)("div", { className: `flex items-center gap-3 p-4 rounded-lg ${isDark ? 'bg-red-900/30 border-red-700' : 'bg-red-50 border-red-300'} border`, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.AlertCircle, { className: isDark ? 'text-red-400' : 'text-red-500', size: 20 }), (0, jsx_runtime_1.jsx)("span", { className: isDark ? 'text-red-300' : 'text-red-700', children: error })] })), successMessage && ((0, jsx_runtime_1.jsxs)("div", { className: `flex items-center gap-3 p-4 rounded-lg ${isDark ? 'bg-green-900/30 border-green-700' : 'bg-green-50 border-green-300'} border`, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: isDark ? 'text-green-400' : 'text-green-500', size: 20 }), (0, jsx_runtime_1.jsx)("span", { className: isDark ? 'text-green-300' : 'text-green-700', children: successMessage })] })), (0, jsx_runtime_1.jsxs)("div", { className: `${themeClasses.cardBg} border rounded-xl overflow-hidden`, children: [(0, jsx_runtime_1.jsxs)("div", { className: `px-5 py-4 border-b ${isDark ? 'border-slate-700' : 'border-gray-200'}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Zap, { className: isDark ? 'text-amber-400' : 'text-amber-500', size: 20 }), (0, jsx_runtime_1.jsx)("h3", { className: `font-semibold ${themeClasses.textPrimary}`, children: "Quick Setup" })] }), (0, jsx_runtime_1.jsx)("p", { className: `text-sm ${themeClasses.textMuted} mt-1`, children: "Choose a preset to quickly configure log levels" })] }), (0, jsx_runtime_1.jsx)("div", { className: "p-5", children: (0, jsx_runtime_1.jsx)("div", { className: "flex flex-wrap gap-3", children: PRESETS.map((preset) => {
282
282
  const isActive = getActivePreset() === preset.id;
283
- return ((0, jsx_runtime_1.jsx)("button", { onClick: () => applyPreset(preset.id), className: `
284
- px-4 py-2.5 rounded-lg font-medium text-sm transition-all
283
+ return ((0, jsx_runtime_1.jsx)("button", { onClick: () => applyPreset(preset.id), className: `
284
+ px-4 py-2.5 rounded-lg font-medium text-sm transition-all
285
285
  ${isActive
286
286
  ? 'bg-indigo-600 text-white ring-2 ring-indigo-500 ring-offset-2 ' + (isDark ? 'ring-offset-slate-800' : 'ring-offset-white')
287
- : (isDark ? 'bg-slate-700 text-white hover:bg-slate-600' : 'bg-gray-100 text-gray-700 hover:bg-gray-200')}
287
+ : (isDark ? 'bg-slate-700 text-white hover:bg-slate-600' : 'bg-gray-100 text-gray-700 hover:bg-gray-200')}
288
288
  `, children: preset.label }, preset.id));
289
289
  }) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${themeClasses.cardBg} border rounded-xl overflow-hidden`, children: [(0, jsx_runtime_1.jsxs)("div", { className: `px-5 py-4 border-b ${isDark ? 'border-slate-700' : 'border-gray-200'}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Bug, { className: isDark ? 'text-violet-400' : 'text-violet-500', size: 20 }), (0, jsx_runtime_1.jsx)("h3", { className: `font-semibold ${themeClasses.textPrimary}`, children: "Log Levels" })] }), (0, jsx_runtime_1.jsx)("p", { className: `text-sm ${themeClasses.textMuted} mt-1`, children: "Set verbosity per category" })] }), (0, jsx_runtime_1.jsx)("div", { className: "p-5", children: (0, jsx_runtime_1.jsx)("div", { className: "space-y-4", children: CATEGORIES.map((category) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: `font-medium ${themeClasses.textPrimary}`, children: category.label }), (0, jsx_runtime_1.jsx)("p", { className: `text-xs ${themeClasses.textMuted}`, children: category.description })] }), (0, jsx_runtime_1.jsx)("select", { value: editedLevels?.[category.id] || 'info', onChange: (e) => {
290
290
  setEditedLevels(prev => prev ? { ...prev, [category.id]: e.target.value } : null);
291
- }, className: `
292
- px-3 py-2 rounded-lg text-sm font-medium cursor-pointer
293
- ${themeClasses.inputBg} ${themeClasses.textPrimary} border
294
- focus:outline-none focus:ring-2 focus:ring-indigo-500
291
+ }, className: `
292
+ px-3 py-2 rounded-lg text-sm font-medium cursor-pointer
293
+ ${themeClasses.inputBg} ${themeClasses.textPrimary} border
294
+ focus:outline-none focus:ring-2 focus:ring-indigo-500
295
295
  `, children: LOG_LEVELS.map((level) => ((0, jsx_runtime_1.jsx)("option", { value: level.value, children: level.label }, level.value))) })] }, category.id))) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${themeClasses.cardBg} border rounded-xl overflow-hidden`, children: [(0, jsx_runtime_1.jsxs)("div", { className: `px-5 py-4 border-b ${isDark ? 'border-slate-700' : 'border-gray-200'}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Clock, { className: isDark ? 'text-cyan-400' : 'text-cyan-500', size: 20 }), (0, jsx_runtime_1.jsx)("h3", { className: `font-semibold ${themeClasses.textPrimary}`, children: "Retention Settings" })] }), (0, jsx_runtime_1.jsx)("p", { className: `text-sm ${themeClasses.textMuted} mt-1`, children: "How long to keep logs by severity level" })] }), (0, jsx_runtime_1.jsx)("div", { className: "p-5", children: (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4", children: [
296
296
  { key: 'debug_days', label: 'Debug' },
297
297
  { key: 'info_days', label: 'Info' },
@@ -300,22 +300,22 @@ function LoggingSettingsTab({ isDark = true, apiBasePath = '/api/admin/logging',
300
300
  { key: 'critical_days', label: 'Critical' },
301
301
  ].map((item) => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: `block text-sm ${themeClasses.textSecondary} mb-1`, children: item.label }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("input", { type: "number", min: "1", max: "365", value: editedRetention?.[item.key] || 7, onChange: (e) => {
302
302
  setEditedRetention(prev => prev ? { ...prev, [item.key]: parseInt(e.target.value) || 7 } : null);
303
- }, className: `
304
- w-20 px-3 py-2 rounded-lg text-sm
305
- ${themeClasses.inputBg} ${themeClasses.textPrimary} border
306
- focus:outline-none focus:ring-2 focus:ring-indigo-500
303
+ }, className: `
304
+ w-20 px-3 py-2 rounded-lg text-sm
305
+ ${themeClasses.inputBg} ${themeClasses.textPrimary} border
306
+ focus:outline-none focus:ring-2 focus:ring-indigo-500
307
307
  ` }), (0, jsx_runtime_1.jsx)("span", { className: `text-sm ${themeClasses.textMuted}`, children: "days" })] })] }, item.key))) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${themeClasses.cardBg} border rounded-xl overflow-hidden`, children: [(0, jsx_runtime_1.jsx)("div", { className: `px-5 py-4 border-b ${isDark ? 'border-slate-700' : 'border-gray-200'}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.HardDrive, { className: isDark ? 'text-emerald-400' : 'text-emerald-500', size: 20 }), (0, jsx_runtime_1.jsx)("h3", { className: `font-semibold ${themeClasses.textPrimary}`, children: "Storage Limits" })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "p-5 space-y-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center gap-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("label", { className: `text-sm ${themeClasses.textSecondary}`, children: "Max size:" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: "1", max: "1000", value: editedLimits?.max_size_mb || 10, onChange: (e) => {
308
308
  setEditedLimits(prev => prev ? { ...prev, max_size_mb: parseInt(e.target.value) || 10 } : null);
309
- }, className: `
310
- w-20 px-3 py-2 rounded-lg text-sm
311
- ${themeClasses.inputBg} ${themeClasses.textPrimary} border
312
- focus:outline-none focus:ring-2 focus:ring-indigo-500
309
+ }, className: `
310
+ w-20 px-3 py-2 rounded-lg text-sm
311
+ ${themeClasses.inputBg} ${themeClasses.textPrimary} border
312
+ focus:outline-none focus:ring-2 focus:ring-indigo-500
313
313
  ` }), (0, jsx_runtime_1.jsx)("span", { className: `text-sm ${themeClasses.textMuted}`, children: "MB" })] }), (0, jsx_runtime_1.jsx)("span", { className: themeClasses.textMuted, children: "or" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("input", { type: "number", min: "100", max: "1000000", step: "1000", value: editedLimits?.max_rows || 20000, onChange: (e) => {
314
314
  setEditedLimits(prev => prev ? { ...prev, max_rows: parseInt(e.target.value) || 20000 } : null);
315
- }, className: `
316
- w-28 px-3 py-2 rounded-lg text-sm
317
- ${themeClasses.inputBg} ${themeClasses.textPrimary} border
318
- focus:outline-none focus:ring-2 focus:ring-indigo-500
315
+ }, className: `
316
+ w-28 px-3 py-2 rounded-lg text-sm
317
+ ${themeClasses.inputBg} ${themeClasses.textPrimary} border
318
+ focus:outline-none focus:ring-2 focus:ring-indigo-500
319
319
  ` }), (0, jsx_runtime_1.jsx)("span", { className: `text-sm ${themeClasses.textMuted}`, children: "rows" })] })] }), usage && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [(0, jsx_runtime_1.jsx)("span", { className: `text-sm ${themeClasses.textSecondary}`, children: "Current usage" }), (0, jsx_runtime_1.jsxs)("span", { className: `text-sm font-medium ${themeClasses.textPrimary}`, children: [usage.total_size_mb?.toFixed(1) || 0, " MB of ", usage.limits.max_size_mb, " MB (", usage.percent_used || 0, "%)"] })] }), (0, jsx_runtime_1.jsx)("div", { className: `h-3 rounded-full overflow-hidden ${isDark ? 'bg-slate-700' : 'bg-gray-200'}`, children: (0, jsx_runtime_1.jsx)("div", { className: `h-full rounded-full transition-all ${(usage.percent_used || 0) > 90 ? 'bg-red-500' :
320
320
  (usage.percent_used || 0) > 70 ? 'bg-amber-500' :
321
321
  'bg-emerald-500'}`, style: { width: `${Math.min(usage.percent_used || 0, 100)}%` } }) }), (0, jsx_runtime_1.jsxs)("div", { className: `flex items-center justify-between mt-2 text-xs ${themeClasses.textMuted}`, children: [(0, jsx_runtime_1.jsxs)("span", { children: [(usage.total_rows || 0).toLocaleString(), " rows"] }), usage.oldest_entry && ((0, jsx_runtime_1.jsxs)("span", { children: ["Oldest: ", new Date(usage.oldest_entry).toLocaleDateString()] }))] })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsxs)("select", { disabled: pruning, onChange: (e) => {
@@ -323,17 +323,17 @@ function LoggingSettingsTab({ isDark = true, apiBasePath = '/api/admin/logging',
323
323
  handlePrune(e.target.value);
324
324
  e.target.value = '';
325
325
  }
326
- }, className: `
327
- px-4 py-2.5 pr-10 rounded-lg text-sm font-medium cursor-pointer appearance-none
328
- ${isDark ? 'bg-red-900/30 text-red-300 border-red-700 hover:bg-red-900/50' : 'bg-red-50 text-red-700 border-red-300 hover:bg-red-100'}
329
- border focus:outline-none focus:ring-2 focus:ring-red-500
330
- disabled:opacity-50 disabled:cursor-not-allowed
331
- `, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: "Prune Now..." }), PRUNE_OPTIONS.map((opt) => ((0, jsx_runtime_1.jsx)("option", { value: opt.value, children: opt.label }, opt.value)))] }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: `absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 pointer-events-none ${isDark ? 'text-red-400' : 'text-red-500'}` })] }), pruning && ((0, jsx_runtime_1.jsxs)("span", { className: `flex items-center gap-2 text-sm ${themeClasses.textMuted}`, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.RefreshCw, { className: "w-4 h-4 animate-spin" }), "Pruning..."] }))] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-end gap-4", children: [hasChanges && ((0, jsx_runtime_1.jsx)("span", { className: `text-sm ${themeClasses.textMuted}`, children: "You have unsaved changes" })), (0, jsx_runtime_1.jsx)("button", { onClick: saveSettings, disabled: !hasChanges || saving, className: `
332
- px-6 py-2.5 rounded-lg font-medium text-sm transition-all
326
+ }, className: `
327
+ px-4 py-2.5 pr-10 rounded-lg text-sm font-medium cursor-pointer appearance-none
328
+ ${isDark ? 'bg-red-900/30 text-red-300 border-red-700 hover:bg-red-900/50' : 'bg-red-50 text-red-700 border-red-300 hover:bg-red-100'}
329
+ border focus:outline-none focus:ring-2 focus:ring-red-500
330
+ disabled:opacity-50 disabled:cursor-not-allowed
331
+ `, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: "Prune Now..." }), PRUNE_OPTIONS.map((opt) => ((0, jsx_runtime_1.jsx)("option", { value: opt.value, children: opt.label }, opt.value)))] }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: `absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 pointer-events-none ${isDark ? 'text-red-400' : 'text-red-500'}` })] }), pruning && ((0, jsx_runtime_1.jsxs)("span", { className: `flex items-center gap-2 text-sm ${themeClasses.textMuted}`, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.RefreshCw, { className: "w-4 h-4 animate-spin" }), "Pruning..."] }))] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-end gap-4", children: [hasChanges && ((0, jsx_runtime_1.jsx)("span", { className: `text-sm ${themeClasses.textMuted}`, children: "You have unsaved changes" })), (0, jsx_runtime_1.jsx)("button", { onClick: saveSettings, disabled: !hasChanges || saving, className: `
332
+ px-6 py-2.5 rounded-lg font-medium text-sm transition-all
333
333
  ${hasChanges
334
334
  ? 'bg-indigo-600 text-white hover:bg-indigo-700'
335
- : (isDark ? 'bg-slate-700 text-slate-400' : 'bg-gray-200 text-gray-400')}
336
- disabled:cursor-not-allowed
335
+ : (isDark ? 'bg-slate-700 text-slate-400' : 'bg-gray-200 text-gray-400')}
336
+ disabled:cursor-not-allowed
337
337
  `, children: saving ? ((0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.RefreshCw, { className: "w-4 h-4 animate-spin" }), "Saving..."] })) : ('Save Settings') })] })] }));
338
338
  }
339
339
  exports.default = LoggingSettingsTab;
@@ -176,34 +176,34 @@ function LoginForm() {
176
176
  if (!forceShowForm && !loginError && (loading || (status === 'loading' && !session))) {
177
177
  return ((0, jsx_runtime_1.jsxs)("div", { className: `flex flex-col items-center justify-center min-h-screen ${colors.background}`, children: [(0, jsx_runtime_1.jsxs)("svg", { className: "animate-spin h-6 w-6", style: { color: buttonColor }, viewBox: "0 0 24 24", fill: "none", children: [(0, jsx_runtime_1.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), (0, jsx_runtime_1.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })] }), (0, jsx_runtime_1.jsx)("p", { className: `mt-4 text-sm ${colors.muted}`, children: status === 'loading' ? 'Loading...' : 'Authenticating...' })] }));
178
178
  }
179
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: `
180
- .themed-input:focus {
181
- outline: none;
182
- border-color: ${buttonColor} !important;
183
- box-shadow: 0 0 0 2px ${buttonColor}40 !important;
184
- }
185
- .themed-button {
186
- background-color: ${buttonColor} !important;
187
- }
188
- .themed-button:hover:not(:disabled) {
189
- background-color: ${buttonHoverColor} !important;
190
- }
191
- .themed-link {
192
- color: ${buttonColor} !important;
193
- }
194
- .themed-link:hover {
195
- color: ${buttonHoverColor} !important;
196
- }
197
- .themed-status-submitting {
198
- background-color: ${buttonColor}1A !important;
199
- border-color: ${buttonColor}33 !important;
200
- }
201
- .themed-status-submitting-text {
202
- color: ${buttonColor} !important;
203
- }
204
- .themed-status-submitting svg {
205
- color: ${buttonColor} !important;
206
- }
179
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: `
180
+ .themed-input:focus {
181
+ outline: none;
182
+ border-color: ${buttonColor} !important;
183
+ box-shadow: 0 0 0 2px ${buttonColor}40 !important;
184
+ }
185
+ .themed-button {
186
+ background-color: ${buttonColor} !important;
187
+ }
188
+ .themed-button:hover:not(:disabled) {
189
+ background-color: ${buttonHoverColor} !important;
190
+ }
191
+ .themed-link {
192
+ color: ${buttonColor} !important;
193
+ }
194
+ .themed-link:hover {
195
+ color: ${buttonHoverColor} !important;
196
+ }
197
+ .themed-status-submitting {
198
+ background-color: ${buttonColor}1A !important;
199
+ border-color: ${buttonColor}33 !important;
200
+ }
201
+ .themed-status-submitting-text {
202
+ color: ${buttonColor} !important;
203
+ }
204
+ .themed-status-submitting svg {
205
+ color: ${buttonColor} !important;
206
+ }
207
207
  ` } }), (0, jsx_runtime_1.jsx)("div", { className: `min-h-screen flex items-center justify-center px-4 ${colors.background}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "max-w-md w-full", children: [(0, jsx_runtime_1.jsxs)("div", { className: `rounded-2xl shadow-xl p-8 ${colors.card} ${colors.border} text-gray-800`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center mb-8", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-3xl font-bold mb-2", style: { color: 'inherit' }, children: branding.tagline || 'Welcome Back' }), (0, jsx_runtime_1.jsx)("p", { className: colors.muted, children: "Sign in to your account" })] }), (0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-6", "data-testid": "login-form", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "email", className: "block text-sm font-medium mb-2", style: { color: 'inherit' }, children: "Email Address" }), (0, jsx_runtime_1.jsx)("input", { id: "email", type: "email", required: true, autoFocus: true, autoComplete: "email", value: email, onChange: (e) => setEmail(e.target.value), className: `themed-input w-full px-4 py-3 border border-gray-300 rounded-lg transition-colors`, placeholder: "you@example.com", disabled: isSubmitting, style: { backgroundColor: 'white', color: '#1f2937' } })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "password", className: "block text-sm font-medium mb-2", style: { color: 'inherit' }, children: "Password" }), (0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)("input", { id: "password", type: showPassword ? "text" : "password", required: true, autoComplete: "current-password", value: password, onChange: (e) => setPassword(e.target.value), className: `themed-input w-full px-4 py-3 pr-12 border border-gray-300 rounded-lg transition-colors`, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", disabled: isSubmitting, style: { backgroundColor: 'white', color: '#1f2937' } }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setShowPassword(!showPassword), className: "absolute right-3 top-1/2 transform -translate-y-1/2 hover:opacity-70", style: { color: 'inherit', opacity: 0.7 }, "aria-label": showPassword ? "Hide password" : "Show password", children: showPassword ? ((0, jsx_runtime_1.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", className: "w-5 h-5", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L6.464 6.464m7.535 7.535l3.415 3.414M3 3l3.464 3.464M21 21l-3.415-3.414" }) })) : ((0, jsx_runtime_1.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", className: "w-5 h-5", children: [(0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" }), (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" })] })) })] })] }), (0, jsx_runtime_1.jsx)(ReservedStatusBox_1.default, { candidates: [
208
208
  "Ready",
209
209
  "Authenticating...",
@@ -47,13 +47,13 @@ const DATE_FORMATS = [
47
47
  ];
48
48
  // Toggle component
49
49
  function Toggle({ enabled, onChange, disabled = false, isDark = true, }) {
50
- return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => !disabled && onChange(!enabled), disabled: disabled, className: `
51
- relative inline-flex h-6 w-11 items-center rounded-full transition-colors
52
- ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
53
- ${enabled ? 'bg-blue-600' : isDark ? 'bg-slate-600' : 'bg-gray-300'}
54
- `, children: (0, jsx_runtime_1.jsx)("span", { className: `
55
- inline-block h-4 w-4 transform rounded-full bg-white transition-transform
56
- ${enabled ? 'translate-x-6' : 'translate-x-1'}
50
+ return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => !disabled && onChange(!enabled), disabled: disabled, className: `
51
+ relative inline-flex h-6 w-11 items-center rounded-full transition-colors
52
+ ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
53
+ ${enabled ? 'bg-blue-600' : isDark ? 'bg-slate-600' : 'bg-gray-300'}
54
+ `, children: (0, jsx_runtime_1.jsx)("span", { className: `
55
+ inline-block h-4 w-4 transform rounded-full bg-white transition-transform
56
+ ${enabled ? 'translate-x-6' : 'translate-x-1'}
57
57
  ` }) }));
58
58
  }
59
59
  // Section wrapper component
@@ -73,9 +73,9 @@ function Select({ value, onChange, options, isDark, }) {
73
73
  const bgColor = isDark ? 'bg-slate-700' : 'bg-white';
74
74
  const borderColor = isDark ? 'border-slate-600' : 'border-gray-300';
75
75
  const textColor = isDark ? 'text-white' : 'text-gray-900';
76
- return ((0, jsx_runtime_1.jsx)("select", { value: value, onChange: (e) => onChange(e.target.value), className: `
77
- px-3 py-2 rounded-md border ${borderColor} ${bgColor} ${textColor}
78
- focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-[200px]
76
+ return ((0, jsx_runtime_1.jsx)("select", { value: value, onChange: (e) => onChange(e.target.value), className: `
77
+ px-3 py-2 rounded-md border ${borderColor} ${bgColor} ${textColor}
78
+ focus:outline-none focus:ring-2 focus:ring-blue-500 min-w-[200px]
79
79
  `, children: options.map((opt) => ((0, jsx_runtime_1.jsx)("option", { value: opt.value, children: opt.label }, opt.value))) }));
80
80
  }
81
81
  function EnhancedSettingsPage({ initialPreferences, preferencesEndpoint = '/api/v1/user/preferences', languagesEndpoint = '/api/v1/lookup/languages', timezonesEndpoint = '/api/v1/lookup/timezones', onSave, onThemeChange, }) {
@@ -206,17 +206,17 @@ function EnhancedSettingsPage({ initialPreferences, preferencesEndpoint = '/api/
206
206
  const smsEnabled = preferences.notifications?.sms_enabled ?? false;
207
207
  const profileVisibility = preferences.privacy?.profile_visibility || 'team';
208
208
  const showActivityStatus = preferences.privacy?.show_activity_status ?? true;
209
- return ((0, jsx_runtime_1.jsx)("div", { className: `min-h-screen ${bgColor}`, children: (0, jsx_runtime_1.jsxs)("div", { className: `max-w-2xl mx-auto ${layout?.padding || 'p-6'}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-6", children: [(0, jsx_runtime_1.jsx)("h1", { className: `text-3xl font-bold ${textPrimary}`, children: "Settings" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleSave, disabled: saving, className: `
210
- px-4 py-2 rounded-md font-medium transition-colors
211
- ${saving ? 'opacity-50 cursor-not-allowed' : ''}
212
- bg-blue-600 text-white hover:bg-blue-700
213
- `, children: saving ? 'Saving...' : 'Save Changes' })] }), error && ((0, jsx_runtime_1.jsx)("div", { className: "mb-4 p-3 rounded-md bg-red-500/10 border border-red-500/50 text-red-500", children: error })), successMessage && ((0, jsx_runtime_1.jsx)("div", { className: "mb-4 p-3 rounded-md bg-green-500/10 border border-green-500/50 text-green-500", children: successMessage })), (0, jsx_runtime_1.jsxs)("div", { className: `${cardBg} rounded-lg shadow-lg border ${borderColor} p-6`, children: [(0, jsx_runtime_1.jsxs)(SettingsSection, { title: "Appearance", isDark: isDark, children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("label", { className: `block text-sm font-medium ${textMuted} mb-2`, children: "Theme" }), (0, jsx_runtime_1.jsx)("div", { className: "flex gap-2", children: ['light', 'dark', 'system'].map((theme) => ((0, jsx_runtime_1.jsx)("button", { onClick: () => handleThemeChange(theme), className: `
214
- px-4 py-2 rounded-md capitalize transition-colors
209
+ return ((0, jsx_runtime_1.jsx)("div", { className: `min-h-screen ${bgColor}`, children: (0, jsx_runtime_1.jsxs)("div", { className: `max-w-2xl mx-auto ${layout?.padding || 'p-6'}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-6", children: [(0, jsx_runtime_1.jsx)("h1", { className: `text-3xl font-bold ${textPrimary}`, children: "Settings" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleSave, disabled: saving, className: `
210
+ px-4 py-2 rounded-md font-medium transition-colors
211
+ ${saving ? 'opacity-50 cursor-not-allowed' : ''}
212
+ bg-blue-600 text-white hover:bg-blue-700
213
+ `, children: saving ? 'Saving...' : 'Save Changes' })] }), error && ((0, jsx_runtime_1.jsx)("div", { className: "mb-4 p-3 rounded-md bg-red-500/10 border border-red-500/50 text-red-500", children: error })), successMessage && ((0, jsx_runtime_1.jsx)("div", { className: "mb-4 p-3 rounded-md bg-green-500/10 border border-green-500/50 text-green-500", children: successMessage })), (0, jsx_runtime_1.jsxs)("div", { className: `${cardBg} rounded-lg shadow-lg border ${borderColor} p-6`, children: [(0, jsx_runtime_1.jsxs)(SettingsSection, { title: "Appearance", isDark: isDark, children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("label", { className: `block text-sm font-medium ${textMuted} mb-2`, children: "Theme" }), (0, jsx_runtime_1.jsx)("div", { className: "flex gap-2", children: ['light', 'dark', 'system'].map((theme) => ((0, jsx_runtime_1.jsx)("button", { onClick: () => handleThemeChange(theme), className: `
214
+ px-4 py-2 rounded-md capitalize transition-colors
215
215
  ${currentTheme === theme
216
216
  ? 'bg-blue-600 text-white'
217
217
  : isDark
218
218
  ? 'bg-slate-700 text-gray-300 hover:bg-slate-600'
219
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}
219
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}
220
220
  `, children: theme }, theme))) })] }), (0, jsx_runtime_1.jsx)(SettingRow, { label: "Compact Mode", description: "Reduce spacing and padding for denser layouts", isDark: isDark, children: (0, jsx_runtime_1.jsx)(Toggle, { enabled: compactMode, onChange: (value) => updatePreference('appearance', 'compact_mode', value), isDark: isDark }) })] }), (0, jsx_runtime_1.jsx)(SettingsSection, { title: "Localization", isDark: isDark, children: (0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: `block text-sm font-medium ${textMuted} mb-2`, children: "Language" }), (0, jsx_runtime_1.jsx)(Select, { value: currentLanguage, onChange: (value) => updatePreference('localization', 'language', value), options: languages.map((l) => ({ value: l.code, label: l.name })), isDark: isDark })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: `block text-sm font-medium ${textMuted} mb-2`, children: "Timezone" }), (0, jsx_runtime_1.jsx)(Select, { value: currentTimezone, onChange: (value) => updatePreference('localization', 'timezone', value), options: timezones.map((tz) => ({ value: tz.id, label: `${tz.display} (${tz.offset})` })), isDark: isDark }), preferences.localization?.timezone_display && ((0, jsx_runtime_1.jsxs)("p", { className: `mt-1 text-sm ${textMuted}`, children: ["Currently: ", preferences.localization.timezone_display] }))] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: `block text-sm font-medium ${textMuted} mb-2`, children: "Date Format" }), (0, jsx_runtime_1.jsx)(Select, { value: currentDateFormat, onChange: (value) => updatePreference('localization', 'date_format', value), options: DATE_FORMATS.map((df) => ({ value: df.value, label: df.label })), isDark: isDark }), (0, jsx_runtime_1.jsxs)("p", { className: `mt-1 text-sm ${textMuted}`, children: ["Example: ", DATE_FORMATS.find((df) => df.value === currentDateFormat)?.example] })] })] }) }), (0, jsx_runtime_1.jsxs)(SettingsSection, { title: "Notifications", isDark: isDark, children: [(0, jsx_runtime_1.jsx)(SettingRow, { label: "Email Notifications", description: "Receive updates and alerts via email", isDark: isDark, children: (0, jsx_runtime_1.jsx)(Toggle, { enabled: emailEnabled, onChange: (value) => updatePreference('notifications', 'email_enabled', value), isDark: isDark }) }), emailEnabled && ((0, jsx_runtime_1.jsxs)("div", { className: `ml-6 border-l-2 ${borderColor} pl-4 space-y-2`, children: [(0, jsx_runtime_1.jsx)(SettingRow, { label: "Security alerts", description: "Login from new device, etc.", isDark: isDark, children: (0, jsx_runtime_1.jsx)(Toggle, { enabled: preferences.notifications?.email_security_alerts ?? true, onChange: (value) => updatePreference('notifications', 'email_security_alerts', value), isDark: isDark }) }), (0, jsx_runtime_1.jsx)(SettingRow, { label: "Account updates", description: "Password expiry, etc.", isDark: isDark, children: (0, jsx_runtime_1.jsx)(Toggle, { enabled: preferences.notifications?.email_account_updates ?? true, onChange: (value) => updatePreference('notifications', 'email_account_updates', value), isDark: isDark }) }), (0, jsx_runtime_1.jsx)(SettingRow, { label: "Product news and tips", isDark: isDark, children: (0, jsx_runtime_1.jsx)(Toggle, { enabled: preferences.notifications?.email_product_news ?? false, onChange: (value) => updatePreference('notifications', 'email_product_news', value), isDark: isDark }) }), (0, jsx_runtime_1.jsx)(SettingRow, { label: "Marketing and promotions", isDark: isDark, children: (0, jsx_runtime_1.jsx)(Toggle, { enabled: preferences.notifications?.email_marketing ?? false, onChange: (value) => updatePreference('notifications', 'email_marketing', value), isDark: isDark }) })] })), (0, jsx_runtime_1.jsx)(SettingRow, { label: "SMS Notifications", description: "Receive urgent alerts via text message", isDark: isDark, children: (0, jsx_runtime_1.jsx)(Toggle, { enabled: smsEnabled, onChange: (value) => updatePreference('notifications', 'sms_enabled', value), isDark: isDark }) }), smsEnabled && ((0, jsx_runtime_1.jsx)("div", { className: `ml-6 border-l-2 ${borderColor} pl-4`, children: (0, jsx_runtime_1.jsx)(SettingRow, { label: "Security alerts only", isDark: isDark, children: (0, jsx_runtime_1.jsx)(Toggle, { enabled: preferences.notifications?.sms_security_alerts ?? true, onChange: (value) => updatePreference('notifications', 'sms_security_alerts', value), isDark: isDark }) }) }))] }), (0, jsx_runtime_1.jsxs)(SettingsSection, { title: "Privacy", isDark: isDark, children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("label", { className: `block text-sm font-medium ${textMuted} mb-2`, children: "Profile Visibility" }), (0, jsx_runtime_1.jsx)("div", { className: "space-y-2", children: [
221
221
  { value: 'private', label: 'Private', description: 'Only you can see your profile' },
222
222
  { value: 'team', label: 'Team', description: 'Team members can see your profile' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payez/next-mvp",
3
- "version": "4.1.7",
3
+ "version": "4.1.9",
4
4
  "sideEffects": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,35 +1,35 @@
1
- # Dev profile: prod-like broker bootstrap via KV-signed client_assertion
2
- # Usage: powershell -File dev-broker.ps1 -ClientId "my_app" -Port 3000 -AppName "MyApp"
3
- # This script enables broker mode for PayEz MVP authentication
4
- #
5
- # IMPORTANT: Broker mode provides production-like OAuth/OIDC authentication
6
-
7
- param(
8
- [string]$ClientId = "2",
9
- [int]$Port = 3000,
10
- [string]$AppName = "PayEz MVP"
11
- )
12
-
13
- Write-Host "Starting $AppName in BROKER mode..." -ForegroundColor Cyan
14
-
15
- # Kill any existing process on the specified port
16
- $connections = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue
17
- if ($connections) {
18
- Write-Host "Port $Port is in use. Killing existing process..." -ForegroundColor Yellow
19
- $connections | ForEach-Object {
20
- Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue
21
- }
22
- Start-Sleep -Milliseconds 500
23
- Write-Host "Port $Port is now free" -ForegroundColor Green
24
- }
25
-
26
- # Set required environment variables for broker mode
27
- $env:NEXT_CLIENT_ID = "$ClientId"
28
- $env:USE_BROKER_MODE = "true"
29
-
30
- Write-Host "Client ID: $ClientId" -ForegroundColor Green
31
- Write-Host "Broker mode: ENABLED" -ForegroundColor Green
32
- Write-Host "Starting Next.js dev server on port $Port..." -ForegroundColor Cyan
33
-
34
- # Use npx directly to avoid npm batch job nesting issues
35
- npx next dev -p $Port
1
+ # Dev profile: prod-like broker bootstrap via KV-signed client_assertion
2
+ # Usage: powershell -File dev-broker.ps1 -ClientId "my_app" -Port 3000 -AppName "MyApp"
3
+ # This script enables broker mode for PayEz MVP authentication
4
+ #
5
+ # IMPORTANT: Broker mode provides production-like OAuth/OIDC authentication
6
+
7
+ param(
8
+ [string]$ClientId = "2",
9
+ [int]$Port = 3000,
10
+ [string]$AppName = "PayEz MVP"
11
+ )
12
+
13
+ Write-Host "Starting $AppName in BROKER mode..." -ForegroundColor Cyan
14
+
15
+ # Kill any existing process on the specified port
16
+ $connections = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue
17
+ if ($connections) {
18
+ Write-Host "Port $Port is in use. Killing existing process..." -ForegroundColor Yellow
19
+ $connections | ForEach-Object {
20
+ Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue
21
+ }
22
+ Start-Sleep -Milliseconds 500
23
+ Write-Host "Port $Port is now free" -ForegroundColor Green
24
+ }
25
+
26
+ # Set required environment variables for broker mode
27
+ $env:NEXT_CLIENT_ID = "$ClientId"
28
+ $env:USE_BROKER_MODE = "true"
29
+
30
+ Write-Host "Client ID: $ClientId" -ForegroundColor Green
31
+ Write-Host "Broker mode: ENABLED" -ForegroundColor Green
32
+ Write-Host "Starting Next.js dev server on port $Port..." -ForegroundColor Cyan
33
+
34
+ # Use npx directly to avoid npm batch job nesting issues
35
+ npx next dev -p $Port