@payez/next-mvp 4.1.9 → 4.1.10

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 (161) hide show
  1. package/README.md +792 -792
  2. package/dist/api-handlers/auth/refresh.js +38 -15
  3. package/dist/auth/better-auth.js +7 -18
  4. package/dist/components/account/MobileNavDrawer.js +18 -18
  5. package/dist/components/account/UserAvatarMenu.js +1 -1
  6. package/dist/components/admin/AlertSettingsTab.js +40 -40
  7. package/dist/components/admin/LoggingSettingsTab.js +28 -28
  8. package/dist/lib/ensure-fresh-access-token.js +30 -7
  9. package/dist/lib/idp-fetch.js +25 -9
  10. package/dist/lib/session-store.d.ts +1 -0
  11. package/dist/lib/session-store.js +12 -8
  12. package/dist/pages/login/page.js +28 -28
  13. package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
  14. package/package.json +893 -893
  15. package/scripts/dev-broker.ps1 +35 -35
  16. package/scripts/dev-local.ps1 +45 -45
  17. package/src/api/auth-handler.ts +548 -548
  18. package/src/api-handlers/account/change-password.ts +142 -142
  19. package/src/api-handlers/account/update-phone.ts +79 -79
  20. package/src/api-handlers/account/verify-email.ts +125 -125
  21. package/src/api-handlers/account/verify-sms.ts +125 -125
  22. package/src/api-handlers/admin/analytics.ts +443 -443
  23. package/src/api-handlers/admin/audit.ts +223 -223
  24. package/src/api-handlers/admin/index.ts +63 -63
  25. package/src/api-handlers/admin/redis-sessions.ts +251 -251
  26. package/src/api-handlers/admin/sessions.ts +318 -318
  27. package/src/api-handlers/admin/site-logs.ts +365 -365
  28. package/src/api-handlers/admin/users.ts +242 -242
  29. package/src/api-handlers/admin/vibe-data.ts +324 -324
  30. package/src/api-handlers/auth/refresh.ts +705 -682
  31. package/src/api-handlers/auth/signout.ts +199 -199
  32. package/src/api-handlers/auth/status.ts +20 -20
  33. package/src/api-handlers/auth/update-session.ts +110 -110
  34. package/src/api-handlers/auth/verify-code.ts +112 -112
  35. package/src/api-handlers/session/refresh-viability.ts +36 -36
  36. package/src/api-handlers/test/force-expire.ts +60 -60
  37. package/src/auth/auth-decision.ts +229 -229
  38. package/src/auth/better-auth.ts +7 -18
  39. package/src/auth/route-config.ts +219 -219
  40. package/src/auth/types/auth-types.ts +555 -555
  41. package/src/auth/types/index.ts +7 -7
  42. package/src/auth/utils/idp-client.ts +445 -445
  43. package/src/auth/utils/index.ts +6 -6
  44. package/src/auth/utils/token-utils.ts +243 -243
  45. package/src/client/AuthContext.tsx +143 -143
  46. package/src/client/fetch-with-auth.ts +47 -47
  47. package/src/components/SessionSync.tsx +138 -138
  48. package/src/components/SignalRHealthCheck.tsx +131 -131
  49. package/src/components/account/MobileNavDrawer.tsx +305 -305
  50. package/src/components/account/UserAvatarMenu.tsx +266 -266
  51. package/src/components/account/index.ts +13 -13
  52. package/src/components/admin/AlertSettingsTab.tsx +728 -728
  53. package/src/components/admin/AnalyticsTab.tsx +703 -703
  54. package/src/components/admin/DataBrowserTab.tsx +505 -505
  55. package/src/components/admin/LoggingSettingsTab.tsx +665 -665
  56. package/src/components/admin/SessionsTab.tsx +414 -414
  57. package/src/components/admin/StatsTab.tsx +379 -379
  58. package/src/components/admin/VibeAdminLayout.tsx +187 -187
  59. package/src/components/admin/index.ts +59 -59
  60. package/src/components/auth/FederatedAuthSection.tsx +95 -95
  61. package/src/components/auth/ModeAwareLoginPage.tsx +135 -135
  62. package/src/components/auth/ModeAwareSignupPage.tsx +267 -267
  63. package/src/components/auth/TraditionalAuthSection.tsx +99 -99
  64. package/src/components/recovery/CompleteStep.tsx +36 -36
  65. package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
  66. package/src/components/recovery/SelectMethodStep.tsx +73 -73
  67. package/src/components/recovery/SetPasswordStep.tsx +97 -97
  68. package/src/components/recovery/VerifyCodeStep.tsx +90 -90
  69. package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -160
  70. package/src/components/reserved/ReservedStatusBox.tsx +118 -118
  71. package/src/components/ui/BetaBadge.tsx +58 -58
  72. package/src/components/ui/Footer.tsx +93 -93
  73. package/src/config/env.ts +57 -57
  74. package/src/config/logger.ts +62 -62
  75. package/src/config/vibe-log-transport.ts +264 -264
  76. package/src/hooks/useAuth.ts +117 -117
  77. package/src/hooks/useAuthSettings.ts +97 -97
  78. package/src/hooks/useAvailableProviders.ts +120 -120
  79. package/src/hooks/usePasswordValidation.ts +127 -127
  80. package/src/hooks/usePublicAuthSettings.ts +149 -149
  81. package/src/hooks/useSessionExpiration.ts +101 -101
  82. package/src/hooks/useViabilitySession.ts +336 -336
  83. package/src/index.ts +60 -60
  84. package/src/lib/api-handler.ts +643 -643
  85. package/src/lib/app-slug.ts +178 -178
  86. package/src/lib/ensure-fresh-access-token.ts +358 -335
  87. package/src/lib/geolocation.ts +265 -265
  88. package/src/lib/idp-fetch.ts +26 -10
  89. package/src/lib/jwt-decode.ts +82 -82
  90. package/src/lib/roles.ts +177 -177
  91. package/src/lib/session-store.ts +4 -1
  92. package/src/lib/site-logger.ts +245 -245
  93. package/src/lib/standardized-client-api.ts +901 -901
  94. package/src/lib/test-aware-get-token.ts +41 -41
  95. package/src/lib/user-agent-parser.ts +252 -252
  96. package/src/logging/api/admin-analytics.ts +51 -51
  97. package/src/logging/api/audit-log.ts +53 -53
  98. package/src/logging/components/AdminAnalyticsLayout.tsx +49 -49
  99. package/src/logging/components/AuditLogViewer.tsx +125 -125
  100. package/src/logging/components/ErrorMetricsCard.tsx +98 -98
  101. package/src/logging/components/HealthMetricsCard.tsx +70 -70
  102. package/src/logging/hooks/useAdminAnalytics.ts +22 -22
  103. package/src/logging/hooks/useAuditLog.ts +24 -24
  104. package/src/logging/hooks/useErrorMetrics.ts +40 -40
  105. package/src/logging/hooks/useHealthMetrics.ts +44 -44
  106. package/src/logging/index.ts +18 -18
  107. package/src/logging/types/analytics.ts +81 -81
  108. package/src/logging/types/audit.ts +31 -31
  109. package/src/logging/types/index.ts +3 -3
  110. package/src/middleware/create-middleware.ts +663 -663
  111. package/src/middleware/rbac-check.ts +279 -279
  112. package/src/middleware/twofa-presets.ts +224 -224
  113. package/src/pages/admin-login/page.tsx +221 -221
  114. package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -527
  115. package/src/pages/admin-page-permissions/index.ts +7 -7
  116. package/src/pages/admin-roles/RolesAdminPage.tsx +533 -533
  117. package/src/pages/admin-roles/modals.tsx +469 -469
  118. package/src/pages/client-admin/ClientSiteAdminPage.tsx +382 -382
  119. package/src/pages/client-admin/index.ts +33 -33
  120. package/src/pages/coming-soon/page.tsx +83 -83
  121. package/src/pages/login/page.tsx +456 -456
  122. package/src/pages/profile/EnhancedProfilePage.tsx +485 -485
  123. package/src/pages/profile/index.ts +9 -9
  124. package/src/pages/profile/page.tsx +166 -166
  125. package/src/pages/recovery/page.tsx +234 -234
  126. package/src/pages/roles/MyRolesPage.tsx +211 -211
  127. package/src/pages/roles/components.tsx +294 -294
  128. package/src/pages/roles/index.ts +17 -17
  129. package/src/pages/security/EnhancedSecurityPage.tsx +565 -565
  130. package/src/pages/security/index.ts +9 -9
  131. package/src/pages/security/page.tsx +507 -507
  132. package/src/pages/settings/EnhancedSettingsPage.tsx +642 -642
  133. package/src/pages/settings/index.ts +9 -9
  134. package/src/pages/settings/page.tsx +47 -47
  135. package/src/pages/showcase/ShowcasePage.tsx +532 -532
  136. package/src/pages/showcase/index.ts +13 -13
  137. package/src/pages/test-env/EmergencyLogoutPage.tsx +180 -180
  138. package/src/pages/test-env/JwtInspectPage.tsx +456 -456
  139. package/src/pages/test-env/RefreshTokenPage.tsx +157 -157
  140. package/src/pages/test-env/TestEnvPage.tsx +118 -118
  141. package/src/pages/test-env/index.ts +25 -25
  142. package/src/pages/verify-code/page.tsx +652 -652
  143. package/src/routes/account/update-phone.ts +13 -13
  144. package/src/routes/auth/logout.ts +108 -108
  145. package/src/routes/auth/nextauth.ts +45 -45
  146. package/src/routes/auth/settings.ts +75 -75
  147. package/src/routes/auth/viability.ts +224 -224
  148. package/src/server/auth-guard.ts +170 -170
  149. package/src/server/slim-middleware.ts +117 -117
  150. package/src/server/with-auth.ts +93 -93
  151. package/src/stores/authStore.ts +1899 -1899
  152. package/src/templates/instrumentation.ts +41 -41
  153. package/src/utils/logout.ts +30 -30
  154. package/src/vibe/client.ts +590 -590
  155. package/src/vibe/enterprise-auth.ts +208 -208
  156. package/src/vibe/errors.ts +185 -185
  157. package/src/vibe/generic.ts +431 -431
  158. package/src/vibe/hooks/index.ts +367 -367
  159. package/src/vibe/index.ts +132 -132
  160. package/src/vibe/sessions.ts +551 -551
  161. package/src/vibe/types.ts +577 -577
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SESSION_TTL = void 0;
15
16
  exports.generateSessionToken = generateSessionToken;
16
17
  exports.createSession = createSession;
17
18
  exports.getSession = getSession;
@@ -46,7 +47,10 @@ const getSessionVersionKey = (token) => `${(0, app_slug_1.getSessionPrefix)()}ve
46
47
  // Better Auth uses a different key format: ba:{appSlug}:{token}
47
48
  const getBetterAuthSessionKey = (token, appSlug) => `ba:${appSlug || 'app'}:${token}`;
48
49
  const REFRESH_LOCK_TTL = 60; // 60 seconds
49
- const SESSION_TTL = 3 * 24 * 60 * 60; // 3 days in seconds (matches refresh token lifetime)
50
+ // 3 days in seconds (matches refresh token lifetime). EXPORTED as the single source
51
+ // of session TTL — better-auth.ts derives its BA-session Redis TTL from this so the
52
+ // two can never drift apart again (the 7d-vs-3d drift = the zombie-session window, QA 6952).
53
+ exports.SESSION_TTL = 3 * 24 * 60 * 60;
50
54
  /**
51
55
  * Generates a new session token.
52
56
  * @returns A new session token string.
@@ -66,8 +70,8 @@ async function createSession(data) {
66
70
  const versionKey = getSessionVersionKey(sessionToken);
67
71
  try {
68
72
  await redis_1.default.multi()
69
- .setex(key, SESSION_TTL, JSON.stringify(data))
70
- .setex(versionKey, SESSION_TTL, '1')
73
+ .setex(key, exports.SESSION_TTL, JSON.stringify(data))
74
+ .setex(versionKey, exports.SESSION_TTL, '1')
71
75
  .exec();
72
76
  }
73
77
  catch (error) {
@@ -148,7 +152,7 @@ async function getBetterAuthSession(sessionToken, appSlug) {
148
152
  */
149
153
  async function touchSession(token) {
150
154
  const key = getSessionKey(token);
151
- await redis_1.default.expire(key, SESSION_TTL);
155
+ await redis_1.default.expire(key, exports.SESSION_TTL);
152
156
  }
153
157
  /**
154
158
  * Retrieves a session along with a version identifier for optimistic locking.
@@ -214,9 +218,9 @@ async function setSession(sessionToken, data) {
214
218
  const key = getSessionKey(sessionToken);
215
219
  const versionKey = getSessionVersionKey(sessionToken);
216
220
  await redis_1.default.multi()
217
- .setex(key, SESSION_TTL, JSON.stringify(data))
221
+ .setex(key, exports.SESSION_TTL, JSON.stringify(data))
218
222
  .incr(versionKey)
219
- .expire(versionKey, SESSION_TTL)
223
+ .expire(versionKey, exports.SESSION_TTL)
220
224
  .exec();
221
225
  }
222
226
  /**
@@ -254,9 +258,9 @@ async function updateSession(sessionToken, updates) {
254
258
  const updatedSession = { ...currentSession, ...updates };
255
259
  // Write the entire updated session back to Redis with version increment
256
260
  await redis_1.default.multi()
257
- .setex(key, SESSION_TTL, JSON.stringify(updatedSession))
261
+ .setex(key, exports.SESSION_TTL, JSON.stringify(updatedSession))
258
262
  .incr(versionKey)
259
- .expire(versionKey, SESSION_TTL)
263
+ .expire(versionKey, exports.SESSION_TTL)
260
264
  .exec();
261
265
  return updatedSession;
262
266
  }
@@ -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' },