@payez/next-mvp 4.1.9 → 4.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +792 -792
- package/dist/api-handlers/auth/refresh.js +38 -15
- package/dist/auth/better-auth.js +4 -3
- package/dist/components/account/MobileNavDrawer.js +18 -18
- package/dist/components/account/UserAvatarMenu.js +1 -1
- package/dist/components/admin/AlertSettingsTab.js +40 -40
- package/dist/components/admin/LoggingSettingsTab.js +28 -28
- package/dist/lib/ensure-fresh-access-token.js +42 -8
- package/dist/lib/idp-fetch.js +25 -9
- package/dist/lib/session-store.d.ts +1 -0
- package/dist/lib/session-store.js +12 -8
- package/dist/lib/token-lifecycle.d.ts +2 -0
- package/dist/lib/token-lifecycle.js +21 -3
- package/dist/pages/login/page.js +28 -28
- package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
- package/package.json +893 -893
- package/scripts/dev-broker.ps1 +35 -35
- package/scripts/dev-local.ps1 +45 -45
- package/src/api/auth-handler.ts +548 -548
- package/src/api-handlers/account/change-password.ts +142 -142
- package/src/api-handlers/account/update-phone.ts +79 -79
- package/src/api-handlers/account/verify-email.ts +125 -125
- package/src/api-handlers/account/verify-sms.ts +125 -125
- package/src/api-handlers/admin/analytics.ts +443 -443
- package/src/api-handlers/admin/audit.ts +223 -223
- package/src/api-handlers/admin/index.ts +63 -63
- package/src/api-handlers/admin/redis-sessions.ts +251 -251
- package/src/api-handlers/admin/sessions.ts +318 -318
- package/src/api-handlers/admin/site-logs.ts +365 -365
- package/src/api-handlers/admin/users.ts +242 -242
- package/src/api-handlers/admin/vibe-data.ts +324 -324
- package/src/api-handlers/auth/refresh.ts +705 -682
- package/src/api-handlers/auth/signout.ts +199 -199
- package/src/api-handlers/auth/status.ts +20 -20
- package/src/api-handlers/auth/update-session.ts +110 -110
- package/src/api-handlers/auth/verify-code.ts +112 -112
- package/src/api-handlers/session/refresh-viability.ts +36 -36
- package/src/api-handlers/test/force-expire.ts +60 -60
- package/src/auth/auth-decision.ts +229 -229
- package/src/auth/better-auth.ts +467 -466
- package/src/auth/route-config.ts +219 -219
- package/src/auth/types/auth-types.ts +555 -555
- package/src/auth/types/index.ts +7 -7
- package/src/auth/utils/idp-client.ts +445 -445
- package/src/auth/utils/index.ts +6 -6
- package/src/auth/utils/token-utils.ts +243 -243
- package/src/client/AuthContext.tsx +143 -143
- package/src/client/fetch-with-auth.ts +47 -47
- package/src/components/SessionSync.tsx +138 -138
- package/src/components/SignalRHealthCheck.tsx +131 -131
- package/src/components/account/MobileNavDrawer.tsx +305 -305
- package/src/components/account/UserAvatarMenu.tsx +266 -266
- package/src/components/account/index.ts +13 -13
- package/src/components/admin/AlertSettingsTab.tsx +728 -728
- package/src/components/admin/AnalyticsTab.tsx +703 -703
- package/src/components/admin/DataBrowserTab.tsx +505 -505
- package/src/components/admin/LoggingSettingsTab.tsx +665 -665
- package/src/components/admin/SessionsTab.tsx +414 -414
- package/src/components/admin/StatsTab.tsx +379 -379
- package/src/components/admin/VibeAdminLayout.tsx +187 -187
- package/src/components/admin/index.ts +59 -59
- package/src/components/auth/FederatedAuthSection.tsx +95 -95
- package/src/components/auth/ModeAwareLoginPage.tsx +135 -135
- package/src/components/auth/ModeAwareSignupPage.tsx +267 -267
- package/src/components/auth/TraditionalAuthSection.tsx +99 -99
- package/src/components/recovery/CompleteStep.tsx +36 -36
- package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
- package/src/components/recovery/SelectMethodStep.tsx +73 -73
- package/src/components/recovery/SetPasswordStep.tsx +97 -97
- package/src/components/recovery/VerifyCodeStep.tsx +90 -90
- package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -160
- package/src/components/reserved/ReservedStatusBox.tsx +118 -118
- package/src/components/ui/BetaBadge.tsx +58 -58
- package/src/components/ui/Footer.tsx +93 -93
- package/src/config/env.ts +57 -57
- package/src/config/logger.ts +62 -62
- package/src/config/vibe-log-transport.ts +264 -264
- package/src/hooks/useAuth.ts +117 -117
- package/src/hooks/useAuthSettings.ts +97 -97
- package/src/hooks/useAvailableProviders.ts +120 -120
- package/src/hooks/usePasswordValidation.ts +127 -127
- package/src/hooks/usePublicAuthSettings.ts +149 -149
- package/src/hooks/useSessionExpiration.ts +101 -101
- package/src/hooks/useViabilitySession.ts +336 -336
- package/src/index.ts +60 -60
- package/src/lib/api-handler.ts +643 -643
- package/src/lib/app-slug.ts +178 -178
- package/src/lib/ensure-fresh-access-token.ts +369 -335
- package/src/lib/geolocation.ts +265 -265
- package/src/lib/idp-fetch.ts +26 -10
- package/src/lib/jwt-decode.ts +82 -82
- package/src/lib/roles.ts +177 -177
- package/src/lib/session-store.ts +4 -1
- package/src/lib/site-logger.ts +245 -245
- package/src/lib/standardized-client-api.ts +901 -901
- package/src/lib/test-aware-get-token.ts +41 -41
- package/src/lib/token-lifecycle.ts +30 -3
- package/src/lib/user-agent-parser.ts +252 -252
- package/src/logging/api/admin-analytics.ts +51 -51
- package/src/logging/api/audit-log.ts +53 -53
- package/src/logging/components/AdminAnalyticsLayout.tsx +49 -49
- package/src/logging/components/AuditLogViewer.tsx +125 -125
- package/src/logging/components/ErrorMetricsCard.tsx +98 -98
- package/src/logging/components/HealthMetricsCard.tsx +70 -70
- package/src/logging/hooks/useAdminAnalytics.ts +22 -22
- package/src/logging/hooks/useAuditLog.ts +24 -24
- package/src/logging/hooks/useErrorMetrics.ts +40 -40
- package/src/logging/hooks/useHealthMetrics.ts +44 -44
- package/src/logging/index.ts +18 -18
- package/src/logging/types/analytics.ts +81 -81
- package/src/logging/types/audit.ts +31 -31
- package/src/logging/types/index.ts +3 -3
- package/src/middleware/create-middleware.ts +663 -663
- package/src/middleware/rbac-check.ts +279 -279
- package/src/middleware/twofa-presets.ts +224 -224
- package/src/pages/admin-login/page.tsx +221 -221
- package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -527
- package/src/pages/admin-page-permissions/index.ts +7 -7
- package/src/pages/admin-roles/RolesAdminPage.tsx +533 -533
- package/src/pages/admin-roles/modals.tsx +469 -469
- package/src/pages/client-admin/ClientSiteAdminPage.tsx +382 -382
- package/src/pages/client-admin/index.ts +33 -33
- package/src/pages/coming-soon/page.tsx +83 -83
- package/src/pages/login/page.tsx +456 -456
- package/src/pages/profile/EnhancedProfilePage.tsx +485 -485
- package/src/pages/profile/index.ts +9 -9
- package/src/pages/profile/page.tsx +166 -166
- package/src/pages/recovery/page.tsx +234 -234
- package/src/pages/roles/MyRolesPage.tsx +211 -211
- package/src/pages/roles/components.tsx +294 -294
- package/src/pages/roles/index.ts +17 -17
- package/src/pages/security/EnhancedSecurityPage.tsx +565 -565
- package/src/pages/security/index.ts +9 -9
- package/src/pages/security/page.tsx +507 -507
- package/src/pages/settings/EnhancedSettingsPage.tsx +642 -642
- package/src/pages/settings/index.ts +9 -9
- package/src/pages/settings/page.tsx +47 -47
- package/src/pages/showcase/ShowcasePage.tsx +532 -532
- package/src/pages/showcase/index.ts +13 -13
- package/src/pages/test-env/EmergencyLogoutPage.tsx +180 -180
- package/src/pages/test-env/JwtInspectPage.tsx +456 -456
- package/src/pages/test-env/RefreshTokenPage.tsx +157 -157
- package/src/pages/test-env/TestEnvPage.tsx +118 -118
- package/src/pages/test-env/index.ts +25 -25
- package/src/pages/verify-code/page.tsx +652 -652
- package/src/routes/account/update-phone.ts +13 -13
- package/src/routes/auth/logout.ts +108 -108
- package/src/routes/auth/nextauth.ts +45 -45
- package/src/routes/auth/settings.ts +75 -75
- package/src/routes/auth/viability.ts +224 -224
- package/src/server/auth-guard.ts +170 -170
- package/src/server/slim-middleware.ts +117 -117
- package/src/server/with-auth.ts +93 -93
- package/src/stores/authStore.ts +1899 -1899
- package/src/templates/instrumentation.ts +41 -41
- package/src/utils/logout.ts +30 -30
- package/src/vibe/client.ts +590 -590
- package/src/vibe/enterprise-auth.ts +208 -208
- package/src/vibe/errors.ts +185 -185
- package/src/vibe/generic.ts +431 -431
- package/src/vibe/hooks/index.ts +367 -367
- package/src/vibe/index.ts +132 -132
- package/src/vibe/sessions.ts +551 -551
- 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
|
-
|
|
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
|
}
|
|
@@ -31,6 +31,8 @@ export interface TokenError {
|
|
|
31
31
|
error: 'NO_SESSION' | 'NO_TOKEN' | 'EXPIRED' | 'REFRESH_FAILED' | 'SESSION_EXPIRED_NO_REFRESH';
|
|
32
32
|
message: string;
|
|
33
33
|
terminal?: boolean;
|
|
34
|
+
code?: string;
|
|
35
|
+
resolution?: string;
|
|
34
36
|
}
|
|
35
37
|
export type EnsureFreshTokenResult = TokenResult | TokenError;
|
|
36
38
|
/**
|
|
@@ -150,7 +150,16 @@ async function triggerRefresh(request, sessionToken, retryCount = 0) {
|
|
|
150
150
|
// Don't retry - user must re-authenticate
|
|
151
151
|
if (responseData.code === 'NO_REFRESH_TOKEN' || responseData.terminal === true) {
|
|
152
152
|
console.error('[TOKEN_LIFECYCLE] TERMINAL: Session has no refresh token - user must re-login');
|
|
153
|
-
|
|
153
|
+
// WO-1: preserve the verbatim upstream reason. refresh.ts puts the human
|
|
154
|
+
// message in `error` and the code in `code`; carry both up so the consumer
|
|
155
|
+
// can tell the user WHY (not just "session expired") and stop a futile loop.
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
terminal: true,
|
|
159
|
+
code: responseData.code,
|
|
160
|
+
message: responseData.error,
|
|
161
|
+
resolution: responseData.resolution,
|
|
162
|
+
};
|
|
154
163
|
}
|
|
155
164
|
// For other 401s, check if maybe session was refreshed by another request
|
|
156
165
|
if (response.status === 401 && retryCount < maxRetries) {
|
|
@@ -318,12 +327,21 @@ async function ensureFreshToken(request) {
|
|
|
318
327
|
if (!refreshResult.success) {
|
|
319
328
|
// Check for terminal state - session cannot be recovered
|
|
320
329
|
if (refreshResult.terminal) {
|
|
321
|
-
console.error('[TOKEN_LIFECYCLE] TERMINAL: Session expired
|
|
330
|
+
console.error('[TOKEN_LIFECYCLE] TERMINAL: Session expired - redirect to login', {
|
|
331
|
+
code: refreshResult.code,
|
|
332
|
+
message: refreshResult.message,
|
|
333
|
+
});
|
|
334
|
+
// WO-1: keep `error` as the stable terminal category (existing consumers
|
|
335
|
+
// branch on SESSION_EXPIRED_NO_REFRESH) but surface the verbatim upstream
|
|
336
|
+
// reason via code/message/resolution so the user learns WHY and a genuinely
|
|
337
|
+
// dead session (e.g. unapproved account) doesn't loop login→bounce→login.
|
|
322
338
|
return {
|
|
323
339
|
success: false,
|
|
324
340
|
error: 'SESSION_EXPIRED_NO_REFRESH',
|
|
325
|
-
message: 'Session expired. Please sign in again.',
|
|
341
|
+
message: refreshResult.message || 'Session expired. Please sign in again.',
|
|
326
342
|
terminal: true,
|
|
343
|
+
code: refreshResult.code,
|
|
344
|
+
resolution: refreshResult.resolution,
|
|
327
345
|
};
|
|
328
346
|
}
|
|
329
347
|
console.warn('[TOKEN_LIFECYCLE] Refresh failed');
|
package/dist/pages/login/page.js
CHANGED
|
@@ -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' },
|