@payez/next-mvp 4.8.1 → 4.8.3
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/POST-INSTALL.md +408 -408
- package/README.md +859 -859
- package/dist/api-handlers/admin/audit.js +13 -21
- package/dist/api-handlers/admin/sessions.js +59 -45
- package/dist/api-handlers/admin/stats.js +1 -1
- package/dist/api-handlers/admin/users.js +15 -23
- package/dist/api-handlers/admin/vibe-data.d.ts +5 -0
- package/dist/api-handlers/admin/vibe-data.js +5 -0
- 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/session-store.js +21 -21
- package/dist/lib/startup-init.js +19 -19
- package/dist/pages/login/page.js +28 -28
- package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
- package/dist/vibe/client.d.ts +1 -1
- package/dist/vibe/client.js +2 -1
- package/dist/vibe/filters.d.ts +36 -0
- package/dist/vibe/filters.js +47 -0
- package/dist/vibe/generic.js +2 -1
- package/dist/vibe/sessions.js +28 -3
- package/package.json +2 -2
- package/scripts/check-publish-eol.mjs +33 -15
- package/src/api/auth-handler.ts +550 -550
- package/src/api/index.ts +17 -17
- package/src/api-handlers/account/change-password.ts +170 -170
- package/src/api-handlers/account/masked-info.ts +46 -46
- package/src/api-handlers/account/profile.ts +99 -99
- package/src/api-handlers/account/recovery/forward.ts +117 -117
- package/src/api-handlers/account/recovery/initiate.ts +59 -59
- package/src/api-handlers/account/recovery/send-code.ts +21 -21
- package/src/api-handlers/account/recovery/verify-code.ts +20 -20
- package/src/api-handlers/account/reset-password.ts +87 -87
- package/src/api-handlers/account/send-code.ts +70 -70
- package/src/api-handlers/account/update-phone.ts +84 -84
- package/src/api-handlers/account/validate-password.ts +139 -139
- package/src/api-handlers/account/verify-email.ts +134 -134
- package/src/api-handlers/account/verify-sms.ts +134 -134
- package/src/api-handlers/admin/analytics.ts +452 -452
- package/src/api-handlers/admin/audit.ts +223 -232
- package/src/api-handlers/admin/index.ts +63 -63
- package/src/api-handlers/admin/redis-sessions.ts +259 -259
- package/src/api-handlers/admin/sessions.ts +341 -327
- package/src/api-handlers/admin/site-logs.ts +388 -388
- package/src/api-handlers/admin/stats.ts +260 -260
- package/src/api-handlers/admin/users.ts +240 -251
- package/src/api-handlers/admin/vibe-data.ts +338 -333
- package/src/api-handlers/anon/preferences.ts +123 -123
- package/src/api-handlers/auth/credential-login-email-code.ts +92 -92
- package/src/api-handlers/auth/credential-login.ts +948 -948
- package/src/api-handlers/auth/jwks.ts +31 -31
- package/src/api-handlers/auth/login.ts +252 -252
- package/src/api-handlers/auth/passkey-signin.ts +116 -116
- package/src/api-handlers/auth/refresh.ts +829 -829
- package/src/api-handlers/auth/signout.ts +201 -201
- package/src/api-handlers/auth/status.ts +23 -23
- package/src/api-handlers/auth/update-session.ts +144 -144
- package/src/api-handlers/auth/validate.ts +45 -45
- package/src/api-handlers/auth/verify-code.ts +116 -116
- package/src/api-handlers/mfa/backup-code-verify.ts +170 -170
- package/src/api-handlers/mfa/backup-codes.ts +42 -42
- package/src/api-handlers/mfa/methods.ts +47 -47
- package/src/api-handlers/mfa/totp-confirm.ts +43 -43
- package/src/api-handlers/mfa/totp-enroll.ts +43 -43
- package/src/api-handlers/mfa/totp-verify.ts +172 -172
- package/src/api-handlers/oauth/resume.ts +182 -182
- package/src/api-handlers/onboarding/send-email-code.ts +18 -18
- package/src/api-handlers/onboarding/set-password.ts +21 -21
- package/src/api-handlers/onboarding/verify-code.ts +20 -20
- package/src/api-handlers/onboarding/verify-welcome-email.ts +17 -17
- package/src/api-handlers/passkeys/list.ts +32 -32
- package/src/api-handlers/passkeys/manage.ts +55 -55
- package/src/api-handlers/passkeys/register-options.ts +43 -43
- package/src/api-handlers/passkeys/register-verify.ts +42 -42
- package/src/api-handlers/session/refresh-viability.ts +56 -56
- package/src/api-handlers/session/viability.ts +238 -238
- package/src/api-handlers/test/force-expire.ts +167 -167
- package/src/auth/auth-decision.ts +292 -292
- package/src/auth/better-auth.ts +1688 -1688
- package/src/auth/idp-oauth.ts +310 -310
- package/src/auth/password-reset-bridge.ts +98 -98
- package/src/auth/revocation-fields.ts +16 -16
- package/src/auth/route-config.ts +236 -236
- package/src/auth/safe-callback.ts +199 -199
- package/src/auth/session-cookie-clear.ts +30 -30
- package/src/auth/session-reset-link.ts +159 -159
- package/src/auth/session-response-sanitizer.ts +151 -151
- package/src/auth/types/auth-types.ts +555 -555
- package/src/auth/types/index.ts +7 -7
- package/src/auth/unauthenticated-routes.ts +2 -2
- package/src/auth/utils/idp-client.ts +364 -364
- package/src/auth/utils/index.ts +6 -6
- package/src/auth/utils/token-utils.ts +243 -243
- package/src/client/AuthContext.tsx +192 -192
- package/src/client/better-auth-client.ts +70 -70
- package/src/client/fetch-with-auth.ts +47 -47
- package/src/client/fetchWithSession.ts +21 -21
- package/src/client/index.ts +13 -13
- package/src/client/useAnonSession.ts +131 -131
- 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 +277 -277
- 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 +508 -508
- package/src/components/admin/LoggingSettingsTab.tsx +665 -665
- package/src/components/admin/SessionsTab.tsx +421 -421
- package/src/components/admin/StatsTab.tsx +379 -379
- package/src/components/admin/VibeAdminContext.tsx +87 -87
- package/src/components/admin/VibeAdminLayout.tsx +187 -187
- package/src/components/admin/index.ts +59 -59
- package/src/components/auth/CredentialSignInForm.tsx +240 -240
- package/src/components/auth/FederatedAuthSection.tsx +129 -129
- package/src/components/auth/ModeAwareLoginPage.tsx +146 -146
- package/src/components/auth/ModeAwareSignupPage.tsx +200 -200
- package/src/components/auth/PasswordResetBridge.tsx +30 -30
- package/src/components/auth/TraditionalAuthSection.tsx +103 -103
- package/src/components/recovery/CompleteStep.tsx +36 -36
- package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
- package/src/components/recovery/RequestedStep.tsx +30 -30
- package/src/components/recovery/SelectMethodStep.tsx +73 -73
- package/src/components/recovery/SetPasswordStep.tsx +118 -118
- 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 +55 -55
- package/src/config/logger.ts +62 -62
- package/src/config/logging-config.ts +82 -82
- package/src/config/unauthenticated-routes.ts +19 -19
- package/src/config/vibe-log-transport.ts +264 -264
- package/src/edge/internal-api-url.ts +65 -65
- package/src/edge/middleware.ts +42 -42
- package/src/hooks/useAuth.ts +117 -117
- package/src/hooks/useAuthSettings.ts +109 -109
- package/src/hooks/useAvailableProviders.ts +143 -143
- package/src/hooks/usePasswordValidation.ts +127 -127
- package/src/hooks/useProfile.ts +82 -82
- package/src/hooks/usePublicAuthSettings.ts +171 -171
- package/src/hooks/useSessionExpiration.ts +101 -101
- package/src/hooks/useViabilitySession.ts +337 -337
- package/src/index.ts +64 -64
- package/src/lib/anon-session.ts +213 -213
- package/src/lib/api-handler.ts +643 -643
- package/src/lib/app-slug.ts +228 -228
- package/src/lib/authorize-resume.ts +172 -172
- package/src/lib/bearer-fetch.ts +31 -31
- package/src/lib/client-key.ts +88 -88
- package/src/lib/code-step-copy.ts +83 -83
- package/src/lib/credential-sign-in-client.ts +101 -101
- package/src/lib/demo-mode.ts +18 -18
- package/src/lib/device-id.ts +67 -67
- package/src/lib/ensure-fresh-access-token.ts +509 -509
- package/src/lib/geolocation.ts +265 -265
- package/src/lib/idp-client-config.ts +1005 -1005
- package/src/lib/idp-fetch.ts +169 -169
- package/src/lib/idp-url-guard.ts +37 -37
- package/src/lib/internal-api.ts +171 -171
- package/src/lib/jwt-decode-client.ts +45 -45
- package/src/lib/jwt-decode.ts +82 -82
- package/src/lib/lock-window.ts +49 -49
- package/src/lib/login-error-message.ts +41 -41
- package/src/lib/never-in-response.ts +64 -64
- package/src/lib/nextauth-secret.ts +168 -168
- package/src/lib/onboarding-proxy.ts +57 -57
- package/src/lib/password-rules.ts +50 -50
- package/src/lib/rate-limit-service.ts +9 -9
- package/src/lib/redis.ts +105 -105
- package/src/lib/refresh-token-validator.ts +64 -64
- package/src/lib/roles.ts +177 -177
- package/src/lib/safe-error-response.ts +149 -149
- package/src/lib/secret-validation.ts +8 -8
- package/src/lib/session-cookie-shape.ts +185 -185
- package/src/lib/session-store.ts +1092 -1092
- package/src/lib/session.ts +34 -34
- package/src/lib/single-flight.ts +64 -64
- package/src/lib/site-logger.ts +245 -245
- package/src/lib/standardized-client-api.ts +901 -901
- package/src/lib/startup-init.ts +276 -276
- package/src/lib/test-aware-get-token.ts +41 -41
- package/src/lib/token-expiry.ts +40 -40
- package/src/lib/token-lifecycle.ts +546 -546
- package/src/lib/token-log-id.ts +69 -69
- package/src/lib/twofa-rejection.ts +91 -91
- package/src/lib/types/api-responses.ts +336 -336
- 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 +1141 -1141
- package/src/middleware/rbac-check.ts +279 -279
- package/src/middleware/twofa-presets.ts +224 -224
- package/src/models/DecodedAccessToken.ts +16 -16
- package/src/models/SessionModel.ts +331 -331
- package/src/pages/admin-login/page.tsx +111 -111
- 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/index.ts +9 -9
- 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 +338 -338
- package/src/pages/onboarding/index.ts +11 -11
- package/src/pages/onboarding/page.tsx +212 -212
- package/src/pages/profile/EnhancedProfilePage.tsx +514 -514
- package/src/pages/profile/index.ts +9 -9
- package/src/pages/profile/page.tsx +166 -166
- package/src/pages/recovery/page.tsx +267 -267
- 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 +660 -660
- package/src/pages/security/index.ts +12 -12
- package/src/pages/security/page.tsx +507 -507
- package/src/pages/service-unavailable/page.tsx +63 -63
- 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 +192 -192
- package/src/pages/test-env/JwtInspectPage.tsx +449 -449
- package/src/pages/test-env/RefreshTokenPage.tsx +178 -178
- package/src/pages/test-env/TestEnvPage.tsx +130 -130
- package/src/pages/test-env/guarded.tsx +81 -81
- package/src/pages/test-env/index.ts +77 -77
- package/src/pages/test-env/redact-credentials.ts +96 -96
- package/src/pages/verify-code/page.tsx +746 -746
- package/src/routes/account/index.ts +31 -31
- package/src/routes/account/masked-info.ts +36 -36
- package/src/routes/account/send-code.ts +39 -39
- package/src/routes/account/update-phone.ts +13 -13
- package/src/routes/account/verify-email.ts +40 -40
- package/src/routes/account/verify-sms.ts +40 -40
- package/src/routes/auth/credential-login-email-code.ts +6 -6
- package/src/routes/auth/credential-login.ts +6 -6
- package/src/routes/auth/index.ts +22 -22
- package/src/routes/auth/logout.ts +111 -111
- package/src/routes/auth/nextauth.ts +45 -45
- package/src/routes/auth/passkey-signin.ts +13 -13
- package/src/routes/auth/refresh.ts +77 -77
- package/src/routes/auth/session-reset.ts +37 -37
- package/src/routes/auth/session.ts +261 -261
- package/src/routes/auth/settings.ts +181 -181
- package/src/routes/auth/viability.ts +519 -519
- package/src/routes/index.ts +17 -17
- package/src/routes/mfa/backup-code-verify.ts +1 -1
- package/src/routes/mfa/backup-codes.ts +10 -10
- package/src/routes/mfa/methods.ts +22 -22
- package/src/routes/mfa/totp-confirm.ts +10 -10
- package/src/routes/mfa/totp-enroll.ts +10 -10
- package/src/routes/mfa/totp-verify.ts +14 -14
- package/src/routes/onboarding/index.ts +15 -15
- package/src/routes/onboarding/send-email-code.ts +2 -2
- package/src/routes/onboarding/set-password.ts +2 -2
- package/src/routes/onboarding/verify-code.ts +2 -2
- package/src/routes/onboarding/verify-welcome-email.ts +2 -2
- package/src/routes/passkeys/list.ts +10 -10
- package/src/routes/passkeys/manage.ts +23 -23
- package/src/routes/passkeys/register-options.ts +10 -10
- package/src/routes/passkeys/register-verify.ts +10 -10
- package/src/routes/session/index.ts +7 -7
- package/src/routes/session/refresh-viability.ts +17 -17
- package/src/server/auth-guard.ts +203 -203
- package/src/server/auth.ts +486 -486
- package/src/server/decode-session.ts +309 -309
- package/src/server/slim-middleware.ts +117 -117
- package/src/server/verified-session-token.ts +103 -103
- package/src/server/with-auth.ts +93 -93
- package/src/services/signalrActivityService.ts +258 -258
- package/src/stores/authStore.ts +1900 -1900
- package/src/templates/instrumentation.ts +60 -60
- package/src/theme/ThemeProvider.tsx +39 -39
- package/src/theme/default.ts +33 -33
- package/src/theme/index.ts +31 -31
- package/src/theme/types.ts +69 -69
- package/src/theme/useTheme.ts +57 -57
- package/src/theme/utils.ts +40 -40
- package/src/types/api.ts +12 -12
- package/src/types/auth.d.ts +23 -23
- package/src/types/auth.ts +48 -48
- package/src/types/logging.ts +11 -11
- package/src/types/recovery.ts +54 -54
- package/src/utils/api.ts +401 -401
- package/src/utils/circuitBreaker.ts +40 -40
- package/src/utils/error-message.ts +108 -108
- package/src/utils/layout/reservedSpace.ts +124 -124
- package/src/utils/logout.ts +30 -30
- package/src/vibe/client.ts +878 -877
- package/src/vibe/enterprise-auth.ts +208 -208
- package/src/vibe/errors.ts +209 -209
- package/src/vibe/filters.ts +52 -0
- package/src/vibe/generic.ts +340 -339
- package/src/vibe/hooks/index.ts +367 -367
- package/src/vibe/index.ts +133 -133
- package/src/vibe/sessions.ts +925 -899
- package/src/vibe/types.ts +577 -577
|
@@ -48,6 +48,7 @@ const startup_init_1 = require("../../lib/startup-init");
|
|
|
48
48
|
const client_key_1 = require("../../lib/client-key");
|
|
49
49
|
const roles_1 = require("../../lib/roles");
|
|
50
50
|
const crypto_1 = require("crypto");
|
|
51
|
+
const filters_1 = require("../../vibe/filters");
|
|
51
52
|
async function checkAdminRole(request) {
|
|
52
53
|
const session = await (0, auth_1.getSession)(request);
|
|
53
54
|
if (!session?.user) {
|
|
@@ -141,28 +142,19 @@ function createAuditHandler(config) {
|
|
|
141
142
|
const queryBody = {
|
|
142
143
|
page,
|
|
143
144
|
pageSize,
|
|
144
|
-
orderBy:
|
|
145
|
-
|
|
145
|
+
// PAY-1810: orderBy is a { field: dir } map; a bare string is rejected or ignored by Core.
|
|
146
|
+
orderBy: (0, filters_1.orderByMap)('created_at', 'desc'),
|
|
146
147
|
};
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (endDate) {
|
|
158
|
-
conditions.push({ field: 'created_at', operator: 'lte', value: endDate });
|
|
159
|
-
}
|
|
160
|
-
if (conditions.length === 1) {
|
|
161
|
-
queryBody.filter = conditions[0];
|
|
162
|
-
}
|
|
163
|
-
else if (conditions.length > 1) {
|
|
164
|
-
queryBody.filter = { operator: 'and', conditions };
|
|
165
|
-
}
|
|
148
|
+
// PAY-1810: Core filter forms only (vibe/filters.ts). The old `{ operator, conditions }` had no `filters` key, so two or more filters
|
|
149
|
+
// returned zero rows.
|
|
150
|
+
const filter = (0, filters_1.allOf)([
|
|
151
|
+
action ? (0, filters_1.eq)('action', action) : null,
|
|
152
|
+
userId ? (0, filters_1.eq)('user_id', userId) : null,
|
|
153
|
+
startDate ? (0, filters_1.gte)('created_at', startDate) : null,
|
|
154
|
+
endDate ? (0, filters_1.lte)('created_at', endDate) : null,
|
|
155
|
+
]);
|
|
156
|
+
if (filter)
|
|
157
|
+
queryBody.filter = filter;
|
|
166
158
|
const result = await vibeServiceRequest('/v1/collections/vibe_app/tables/audit_logs/query', { method: 'POST', body: queryBody });
|
|
167
159
|
if (!result.ok) {
|
|
168
160
|
return server_1.NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
@@ -49,6 +49,7 @@ const startup_init_1 = require("../../lib/startup-init");
|
|
|
49
49
|
const client_key_1 = require("../../lib/client-key");
|
|
50
50
|
const roles_1 = require("../../lib/roles");
|
|
51
51
|
const crypto_1 = require("crypto");
|
|
52
|
+
const filters_1 = require("../../vibe/filters");
|
|
52
53
|
/**
|
|
53
54
|
* Check if the current user has admin role
|
|
54
55
|
*/
|
|
@@ -156,23 +157,17 @@ function createSessionsHandler(config) {
|
|
|
156
157
|
const queryBody = {
|
|
157
158
|
page: 1,
|
|
158
159
|
pageSize: 100,
|
|
159
|
-
orderBy:
|
|
160
|
-
|
|
160
|
+
// PAY-1810: orderBy is a { field: dir } map; a bare string is rejected or ignored by Core.
|
|
161
|
+
orderBy: (0, filters_1.orderByMap)('created_at', 'desc'),
|
|
161
162
|
};
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (conditions.length === 1) {
|
|
171
|
-
queryBody.filter = conditions[0];
|
|
172
|
-
}
|
|
173
|
-
else if (conditions.length > 1) {
|
|
174
|
-
queryBody.filter = { operator: 'and', conditions };
|
|
175
|
-
}
|
|
163
|
+
// PAY-1810: Core filter forms only (vibe/filters.ts): compound uses `filters`, and substring is `contains` (`like` is not a Core
|
|
164
|
+
// operator and a flat unknown operator is DROPPED, which returned every session).
|
|
165
|
+
const filter = (0, filters_1.allOf)([
|
|
166
|
+
status && status !== 'all' ? (0, filters_1.eq)('status', status) : null,
|
|
167
|
+
email ? (0, filters_1.contains)('email', email) : null,
|
|
168
|
+
]);
|
|
169
|
+
if (filter)
|
|
170
|
+
queryBody.filter = filter;
|
|
176
171
|
const result = await vibeServiceRequest('/v1/collections/vibe_app/tables/login_sessions/query', { method: 'POST', body: queryBody });
|
|
177
172
|
if (!result.ok) {
|
|
178
173
|
return server_1.NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
@@ -239,7 +234,9 @@ function createSessionsHandler(config) {
|
|
|
239
234
|
if (action === 'revoke' && sessionId) {
|
|
240
235
|
// Revoke single session
|
|
241
236
|
const result = await vibeServiceRequest(`/v1/collections/vibe_app/tables/login_sessions/${sessionId}`, {
|
|
242
|
-
|
|
237
|
+
// PAY-1810 (NightHawk 75394): PATCH, not PUT. A PUT to a Vibe document REPLACES it (Core UpdateAsync SET data = body), so this 3-field body
|
|
238
|
+
// erased idp_user_id / session_token_hash / the rest of the row: the revoked row then vanished from the revocation check and the user list.
|
|
239
|
+
method: 'PATCH',
|
|
243
240
|
body: {
|
|
244
241
|
status: 'revoked',
|
|
245
242
|
revoked_at: new Date().toISOString(),
|
|
@@ -252,39 +249,56 @@ function createSessionsHandler(config) {
|
|
|
252
249
|
return server_1.NextResponse.json({ success: true });
|
|
253
250
|
}
|
|
254
251
|
if (action === 'revoke_all' && userId) {
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
operator: 'and',
|
|
261
|
-
conditions: [
|
|
262
|
-
{ field: 'idp_user_id', operator: 'eq', value: userId },
|
|
263
|
-
{ field: 'status', operator: 'eq', value: 'active' },
|
|
264
|
-
],
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
if (!queryResult.ok) {
|
|
269
|
-
return server_1.NextResponse.json({ error: queryResult.error, code: 'UPSTREAM_ERROR', correlationId: queryResult.correlationId }, { status: queryResult.status || 500 });
|
|
270
|
-
}
|
|
271
|
-
const sessions = queryResult.data?.data || queryResult.data?.documents || [];
|
|
252
|
+
// PAY-1810: one /query call returns ONE page (Core's default page size is 20), so a user with more active sessions than a page would keep
|
|
253
|
+
// the rest signed in. Revoked sessions leave the `status = active` filter, so re-reading page 1 walks the whole set. It stops when a read
|
|
254
|
+
// is empty (complete), when a round revokes nothing (no progress: never loops on a failing write), or after REVOKE_ALL_MAX_ROUNDS.
|
|
255
|
+
const REVOKE_ALL_PAGE_SIZE = 50;
|
|
256
|
+
const REVOKE_ALL_MAX_ROUNDS = 20;
|
|
272
257
|
let revokedCount = 0;
|
|
273
|
-
|
|
274
|
-
for (
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
method: 'PUT',
|
|
258
|
+
let complete = false;
|
|
259
|
+
for (let round = 0; round < REVOKE_ALL_MAX_ROUNDS; round++) {
|
|
260
|
+
const queryResult = await vibeServiceRequest('/v1/collections/vibe_app/tables/login_sessions/query', {
|
|
261
|
+
method: 'POST',
|
|
278
262
|
body: {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
263
|
+
// PAY-1810: `conditions` is not a Core key (it read zero rows, so revoke_all revoked 0 and answered success); `filters` is.
|
|
264
|
+
filter: (0, filters_1.allOf)([(0, filters_1.eq)('idp_user_id', userId), (0, filters_1.eq)('status', 'active')]),
|
|
265
|
+
page: 1,
|
|
266
|
+
pageSize: REVOKE_ALL_PAGE_SIZE,
|
|
282
267
|
},
|
|
283
268
|
});
|
|
284
|
-
if (
|
|
285
|
-
|
|
269
|
+
if (!queryResult.ok) {
|
|
270
|
+
// The first read failing is a plain upstream error (as before). A later one leaves a partial result, reported as incomplete.
|
|
271
|
+
if (round === 0) {
|
|
272
|
+
return server_1.NextResponse.json({ error: queryResult.error, code: 'UPSTREAM_ERROR', correlationId: queryResult.correlationId }, { status: queryResult.status || 500 });
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
const sessions = queryResult.data?.data || queryResult.data?.documents || [];
|
|
277
|
+
if (sessions.length === 0) {
|
|
278
|
+
complete = true;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
let revokedThisRound = 0;
|
|
282
|
+
for (const session of sessions) {
|
|
283
|
+
const id = session.id || session.document_id;
|
|
284
|
+
const result = await vibeServiceRequest(`/v1/collections/vibe_app/tables/login_sessions/${id}`, {
|
|
285
|
+
method: 'PATCH', // PAY-1810: PATCH merges {status, revoked_at, revoked_by}; PUT would replace the whole row (see the single revoke above)
|
|
286
|
+
body: {
|
|
287
|
+
status: 'revoked',
|
|
288
|
+
revoked_at: new Date().toISOString(),
|
|
289
|
+
revoked_by: `admin:${adminCheck.userId}`,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
if (result.ok) {
|
|
293
|
+
revokedCount++;
|
|
294
|
+
revokedThisRound++;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (revokedThisRound === 0)
|
|
298
|
+
break;
|
|
286
299
|
}
|
|
287
|
-
|
|
300
|
+
// success only when the whole set was revoked: a sign-out control must not say success when every write failed (Scout 75399).
|
|
301
|
+
return server_1.NextResponse.json({ success: complete, revokedCount, complete });
|
|
288
302
|
}
|
|
289
303
|
return server_1.NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
290
304
|
},
|
|
@@ -142,7 +142,7 @@ function createStatsHandler(config) {
|
|
|
142
142
|
// 1. Users count via HMAC proxy (Vibe collection query)
|
|
143
143
|
vibeServiceRequest('/v1/collections/vibe_app/tables/users/query', {
|
|
144
144
|
method: 'POST',
|
|
145
|
-
body: { page: 1, pageSize: 500, orderBy:
|
|
145
|
+
body: { page: 1, pageSize: 500, orderBy: { created_at: 'desc' } }, // PAY-1810: orderBy is a { field: dir } map
|
|
146
146
|
}),
|
|
147
147
|
// 2. Tier distribution from analytics endpoint (uses purchases table)
|
|
148
148
|
vibeServiceRequest('/v1/analytics/tier-distribution?includeTrend=false', { method: 'GET' }),
|
|
@@ -48,6 +48,7 @@ const startup_init_1 = require("../../lib/startup-init");
|
|
|
48
48
|
const client_key_1 = require("../../lib/client-key");
|
|
49
49
|
const roles_1 = require("../../lib/roles");
|
|
50
50
|
const crypto_1 = require("crypto");
|
|
51
|
+
const filters_1 = require("../../vibe/filters");
|
|
51
52
|
async function checkAdminRole(request) {
|
|
52
53
|
const session = await (0, auth_1.getSession)(request);
|
|
53
54
|
if (!session?.user) {
|
|
@@ -139,28 +140,17 @@ function createUsersHandler(config) {
|
|
|
139
140
|
const queryBody = {
|
|
140
141
|
page,
|
|
141
142
|
pageSize,
|
|
142
|
-
orderBy:
|
|
143
|
-
|
|
143
|
+
// PAY-1810: orderBy is a { field: dir } map; a bare string is rejected or ignored by Core.
|
|
144
|
+
orderBy: (0, filters_1.orderByMap)('created_at', 'desc'),
|
|
144
145
|
};
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
if (tier) {
|
|
156
|
-
conditions.push({ field: 'tier', operator: 'eq', value: tier });
|
|
157
|
-
}
|
|
158
|
-
if (conditions.length === 1) {
|
|
159
|
-
queryBody.filter = conditions[0];
|
|
160
|
-
}
|
|
161
|
-
else if (conditions.length > 1) {
|
|
162
|
-
queryBody.filter = { operator: 'and', conditions };
|
|
163
|
-
}
|
|
146
|
+
// PAY-1810: Core filter forms only (vibe/filters.ts). The old `{ operator, conditions }` had no `filters` key, so a search returned zero rows,
|
|
147
|
+
// and `like` is not a Core operator; substring is `contains` (case-insensitive).
|
|
148
|
+
const filter = (0, filters_1.allOf)([
|
|
149
|
+
search ? (0, filters_1.anyOf)([(0, filters_1.contains)('email', search), (0, filters_1.contains)('display_name', search)]) : null,
|
|
150
|
+
tier ? (0, filters_1.eq)('tier', tier) : null,
|
|
151
|
+
]);
|
|
152
|
+
if (filter)
|
|
153
|
+
queryBody.filter = filter;
|
|
164
154
|
const result = await vibeServiceRequest('/v1/collections/vibe_app/tables/users/query', { method: 'POST', body: queryBody });
|
|
165
155
|
if (!result.ok) {
|
|
166
156
|
return server_1.NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
@@ -211,14 +201,16 @@ function createUsersHandler(config) {
|
|
|
211
201
|
return server_1.NextResponse.json({ stats });
|
|
212
202
|
}
|
|
213
203
|
if (action === 'update_tier' && userId && tier) {
|
|
214
|
-
const result = await vibeServiceRequest(`/v1/collections/vibe_app/tables/users/${userId}`, { method: '
|
|
204
|
+
const result = await vibeServiceRequest(`/v1/collections/vibe_app/tables/users/${userId}`, { method: 'PATCH', body: { tier } } // PAY-2041: merge one field; PUT replaced the whole user row
|
|
205
|
+
);
|
|
215
206
|
if (!result.ok) {
|
|
216
207
|
return server_1.NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
217
208
|
}
|
|
218
209
|
return server_1.NextResponse.json({ success: true });
|
|
219
210
|
}
|
|
220
211
|
if (action === 'update_credits' && userId && credits !== undefined) {
|
|
221
|
-
const result = await vibeServiceRequest(`/v1/collections/vibe_app/tables/users/${userId}`, { method: '
|
|
212
|
+
const result = await vibeServiceRequest(`/v1/collections/vibe_app/tables/users/${userId}`, { method: 'PATCH', body: { credits } } // PAY-2041: merge one field; PUT replaced the whole user row
|
|
213
|
+
);
|
|
222
214
|
if (!result.ok) {
|
|
223
215
|
return server_1.NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
224
216
|
}
|
|
@@ -48,6 +48,11 @@ export declare function createGetRecordHandler(config: AdminVibeHandlerConfig):
|
|
|
48
48
|
/**
|
|
49
49
|
* PUT /api/admin/vibe/data/[collection]/[table]/[id]
|
|
50
50
|
* Update record (admin access)
|
|
51
|
+
*
|
|
52
|
+
* PAY-2041: this is deliberately a REPLACE. An editor save sends the whole record, and Core PUT sets the
|
|
53
|
+
* document data to the body, so a field the admin removed stays removed. Do not switch it to PATCH: a merge
|
|
54
|
+
* could never delete a key. Callers that hold only some fields (VibeTable.update, generic update, the
|
|
55
|
+
* users tier/credits writes) use PATCH instead. Ruled by BAPert 75440 (NightHawk 75437, Scout 75439 agree).
|
|
51
56
|
*/
|
|
52
57
|
export declare function createUpdateRecordHandler(config: AdminVibeHandlerConfig): (request: NextRequest, { params }: {
|
|
53
58
|
params: Promise<{
|
|
@@ -218,6 +218,11 @@ function createGetRecordHandler(config) {
|
|
|
218
218
|
/**
|
|
219
219
|
* PUT /api/admin/vibe/data/[collection]/[table]/[id]
|
|
220
220
|
* Update record (admin access)
|
|
221
|
+
*
|
|
222
|
+
* PAY-2041: this is deliberately a REPLACE. An editor save sends the whole record, and Core PUT sets the
|
|
223
|
+
* document data to the body, so a field the admin removed stays removed. Do not switch it to PATCH: a merge
|
|
224
|
+
* could never delete a key. Callers that hold only some fields (VibeTable.update, generic update, the
|
|
225
|
+
* users tier/credits writes) use PATCH instead. Ruled by BAPert 75440 (NightHawk 75437, Scout 75439 agree).
|
|
221
226
|
*/
|
|
222
227
|
function createUpdateRecordHandler(config) {
|
|
223
228
|
return async function PUT(request, { params }) {
|
|
@@ -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: "\
|
|
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: "\
|
|
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
|
}
|
|
@@ -92,7 +92,7 @@ function UserAvatarMenu({ basePath = '', showProfile = true, showSettings = true
|
|
|
92
92
|
router.push(item.href);
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
|
-
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\
|
|
95
|
+
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" }) })] }))] }));
|
|
96
96
|
}
|
|
97
97
|
function MenuItem({ icon, label, onClick, variant = 'default' }) {
|
|
98
98
|
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;
|