@rebasepro/auth 0.1.2 → 0.2.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/dist/index.umd.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react"), require("react/jsx-runtime"), require("@rebasepro/ui"), require("lucide-react"), require("@rebasepro/core")) : typeof define === "function" && define.amd ? define(["exports", "react", "react/jsx-runtime", "@rebasepro/ui", "lucide-react", "@rebasepro/core"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase Auth"] = {}, global.react, global.jsxRuntime, global.ui, global.lucideReact, global.core));
3
- })(this, (function(exports2, react, jsxRuntime, ui, lucideReact, core) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react")) : typeof define === "function" && define.amd ? define(["exports", "react"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase Auth"] = {}, global.react));
3
+ })(this, (function(exports2, react) {
4
4
  "use strict";
5
5
  let baseApiUrl = "";
6
6
  function setApiUrl(url) {
@@ -71,12 +71,11 @@
71
71
  });
72
72
  return handleResponse(response);
73
73
  }
74
- async function googleLogin(tokenOrPayload, tokenType = "idToken") {
75
- const body = typeof tokenOrPayload === "string" ? { [tokenType]: tokenOrPayload } : tokenOrPayload;
74
+ async function googleLogin(payload) {
76
75
  const response = await fetchWithHandling(`${baseApiUrl}/api/auth/google`, {
77
76
  method: "POST",
78
77
  headers: { "Content-Type": "application/json" },
79
- body: JSON.stringify(body)
78
+ body: JSON.stringify(payload)
80
79
  });
81
80
  return handleResponse(response);
82
81
  }
@@ -89,13 +88,11 @@
89
88
  return handleResponse(response);
90
89
  }
91
90
  async function refreshAccessToken(refreshToken) {
92
- console.log("[AUTH-API] Calling refresh endpoint...");
93
91
  const response = await fetchWithHandling(`${baseApiUrl}/api/auth/refresh`, {
94
92
  method: "POST",
95
93
  headers: { "Content-Type": "application/json" },
96
94
  body: JSON.stringify({ refreshToken })
97
95
  });
98
- console.log("[AUTH-API] Refresh response status:", response.status);
99
96
  return handleResponse(response);
100
97
  }
101
98
  async function logout(refreshToken) {
@@ -197,7 +194,11 @@
197
194
  return handleResponse(response);
198
195
  }
199
196
  let authConfigInflight = null;
197
+ let authConfigCached = null;
200
198
  async function fetchAuthConfig() {
199
+ if (authConfigCached) {
200
+ return authConfigCached;
201
+ }
201
202
  if (authConfigInflight) {
202
203
  return authConfigInflight;
203
204
  }
@@ -209,11 +210,17 @@
209
210
  return handleResponse(response);
210
211
  })();
211
212
  try {
212
- return await authConfigInflight;
213
+ const result = await authConfigInflight;
214
+ authConfigCached = result;
215
+ return result;
213
216
  } finally {
214
217
  authConfigInflight = null;
215
218
  }
216
219
  }
220
+ function clearAuthConfigCache() {
221
+ authConfigCached = null;
222
+ authConfigInflight = null;
223
+ }
217
224
  const STORAGE_KEY = "rebase_auth";
218
225
  const TOKEN_REFRESH_BUFFER_MS = 2 * 60 * 1e3;
219
226
  function convertToUser(userInfo) {
@@ -224,18 +231,14 @@
224
231
  photoURL: userInfo.photoURL || null,
225
232
  providerId: "custom",
226
233
  isAnonymous: false,
227
- roles: userInfo.roles || []
234
+ roles: userInfo.roles || [],
235
+ metadata: userInfo.metadata
228
236
  };
229
237
  }
230
238
  function saveAuthToStorage(tokens, user) {
231
239
  try {
232
- const data = {
233
- tokens,
234
- user
235
- };
240
+ const data = { tokens, user };
236
241
  localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
237
- const expiryDate = new Date(tokens.accessTokenExpiresAt);
238
- const expiryStr = Number.isFinite(tokens.accessTokenExpiresAt) ? expiryDate.toISOString() : "invalid";
239
242
  } catch (e) {
240
243
  }
241
244
  }
@@ -282,6 +285,9 @@
282
285
  setApiUrl(apiUrl);
283
286
  }
284
287
  }, [client, apiUrl]);
288
+ const clearError = react.useCallback(() => {
289
+ setAuthProviderError(null);
290
+ }, []);
285
291
  const clearSessionAndSignOut = react.useCallback(() => {
286
292
  tokensRef.current = null;
287
293
  clearAuthFromStorage();
@@ -318,7 +324,6 @@
318
324
  if (latestStoredData) {
319
325
  saveAuthToStorage(newTokens, latestStoredData.user);
320
326
  }
321
- const newExpiryStr = Number.isFinite(newTokens.accessTokenExpiresAt) ? new Date(newTokens.accessTokenExpiresAt).toISOString() : "invalid";
322
327
  return newTokens;
323
328
  } catch (error) {
324
329
  if (error instanceof Error && error.code === "NETWORK_ERROR") {
@@ -423,26 +428,20 @@
423
428
  }
424
429
  }, [client, getAuthToken, refreshAccessToken$1, clearSessionAndSignOut]);
425
430
  const handleAuthSuccess = react.useCallback(async (userInfo, tokens) => {
426
- console.log("[Auth] handleAuthSuccess called, user:", userInfo.email, "uid:", userInfo.uid);
427
431
  tokensRef.current = tokens;
428
432
  let convertedUser = convertToUser(userInfo);
429
433
  if (defineRolesFor) {
430
434
  const customRoles = await defineRolesFor(convertedUser);
431
435
  if (customRoles) {
432
- convertedUser = {
433
- ...convertedUser,
434
- roles: customRoles.map((r) => r.id)
435
- };
436
+ convertedUser = { ...convertedUser, roles: customRoles.map((r) => r.id) };
436
437
  }
437
438
  }
438
439
  saveAuthToStorage(tokens, userInfo);
439
- console.log("[Auth] Calling setUser, roles:", convertedUser.roles);
440
440
  setUser(convertedUser);
441
441
  setAuthError(null);
442
442
  setAuthProviderError(null);
443
443
  setLoginSkipped(false);
444
444
  scheduleTokenRefresh(tokens);
445
- console.log("[Auth] handleAuthSuccess completed");
446
445
  }, [scheduleTokenRefresh, defineRolesFor]);
447
446
  const emailPasswordLogin = react.useCallback(async (email, password) => {
448
447
  setAuthLoading(true);
@@ -470,11 +469,11 @@
470
469
  setAuthLoading(false);
471
470
  }
472
471
  }, [handleAuthSuccess]);
473
- const googleLogin$1 = react.useCallback(async (tokenOrPayload, tokenType) => {
472
+ const googleLogin$1 = react.useCallback(async (payload) => {
474
473
  setAuthLoading(true);
475
474
  setAuthProviderError(null);
476
475
  try {
477
- const response = typeof tokenOrPayload === "string" ? await googleLogin(tokenOrPayload, tokenType ?? "idToken") : await googleLogin(tokenOrPayload);
476
+ const response = await googleLogin(payload);
478
477
  await handleAuthSuccess(response.user, response.tokens);
479
478
  } catch (error) {
480
479
  setAuthProviderError(error);
@@ -768,6 +767,8 @@
768
767
  fetchSessions: fetchSessions$1,
769
768
  revokeSession: revokeSession$1,
770
769
  revokeAllSessions: revokeAllSessions$1,
770
+ clearError,
771
+ setAuthProviderError,
771
772
  extra,
772
773
  setExtra,
773
774
  capabilities: {
@@ -907,6 +908,12 @@
907
908
  setLoading(false);
908
909
  return;
909
910
  }
911
+ const userRoles = currentUser.roles ?? [];
912
+ const isUserAdmin = userRoles.some((r) => r === "admin" || r === "schema-admin");
913
+ if (!isUserAdmin) {
914
+ setLoading(false);
915
+ return;
916
+ }
910
917
  if (lastLoadedUidRef.current === currentUser.uid) {
911
918
  setLoading(false);
912
919
  return;
@@ -1077,7 +1084,7 @@
1077
1084
  saveRole,
1078
1085
  deleteRole,
1079
1086
  isAdmin,
1080
- allowDefaultRolesCreation: true,
1087
+ allowDefaultRolesCreation: isAdmin,
1081
1088
  includeCollectionConfigPermissions: true,
1082
1089
  defineRolesFor,
1083
1090
  getUser,
@@ -1087,1196 +1094,8 @@
1087
1094
  bootstrapAdmin
1088
1095
  };
1089
1096
  }
1090
- function RebaseLoginView({
1091
- logo,
1092
- authController,
1093
- noUserComponent,
1094
- disableSignupScreen = false,
1095
- disabled = false,
1096
- notAllowedError,
1097
- googleEnabled = false,
1098
- googleClientId
1099
- }) {
1100
- const modeState = core.useModeController();
1101
- const { mode: colorMode, setMode: setColorMode } = modeState;
1102
- const { t } = core.useTranslation();
1103
- const [mode, setMode] = react.useState("buttons");
1104
- const [fadeIn, setFadeIn] = react.useState(false);
1105
- const [viewVisible, setViewVisible] = react.useState(true);
1106
- const switchMode = (newMode) => {
1107
- setViewVisible(false);
1108
- setTimeout(() => {
1109
- setMode(newMode);
1110
- setViewVisible(true);
1111
- }, 150);
1112
- };
1113
- const isBootstrapMode = authController.needsSetup;
1114
- react.useEffect(() => {
1115
- const timer = setTimeout(() => setFadeIn(true), 50);
1116
- return () => clearTimeout(timer);
1117
- }, []);
1118
- function buildErrorView() {
1119
- if (!authController.authProviderError) return null;
1120
- if (authController.user != null) return null;
1121
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", children: /* @__PURE__ */ jsxRuntime.jsx(core.ErrorView, { error: authController.authProviderError.message ?? authController.authProviderError }) });
1122
- }
1123
- let logoComponent;
1124
- if (logo) {
1125
- logoComponent = /* @__PURE__ */ jsxRuntime.jsx(
1126
- "img",
1127
- {
1128
- src: logo,
1129
- style: {
1130
- height: "100%",
1131
- width: "100%",
1132
- objectFit: "cover"
1133
- },
1134
- alt: "Logo"
1135
- }
1136
- );
1137
- } else {
1138
- logoComponent = /* @__PURE__ */ jsxRuntime.jsx(core.RebaseLogo, {});
1139
- }
1140
- let notAllowedMessage;
1141
- if (notAllowedError) {
1142
- if (typeof notAllowedError === "string") {
1143
- notAllowedMessage = notAllowedError;
1144
- } else if (notAllowedError instanceof Error) {
1145
- notAllowedMessage = notAllowedError.message;
1146
- } else {
1147
- notAllowedMessage = "It looks like you don't have access to the CMS, based on the specified Authenticator configuration";
1148
- }
1149
- }
1150
- const showRegistration = !disableSignupScreen && authController.registrationEnabled;
1151
- return /* @__PURE__ */ jsxRuntime.jsxs(
1152
- "div",
1153
- {
1154
- className: ui.cls(
1155
- "relative flex items-center justify-center h-screen w-screen p-4 transition-opacity duration-500 bg-white dark:bg-surface-900",
1156
- fadeIn ? "opacity-100" : "opacity-0"
1157
- ),
1158
- children: [
1159
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-4 right-4 flex items-center gap-1 z-10", children: [
1160
- /* @__PURE__ */ jsxRuntime.jsx(core.LanguageToggle, {}),
1161
- /* @__PURE__ */ jsxRuntime.jsxs(
1162
- ui.Menu,
1163
- {
1164
- trigger: /* @__PURE__ */ jsxRuntime.jsx(
1165
- ui.IconButton,
1166
- {
1167
- color: "inherit",
1168
- "aria-label": "Toggle theme",
1169
- children: colorMode === "dark" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoonIcon, { size: ui.iconSize.small }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SunIcon, { size: ui.iconSize.small })
1170
- }
1171
- ),
1172
- children: [
1173
- /* @__PURE__ */ jsxRuntime.jsxs(ui.MenuItem, { onClick: () => setColorMode("dark"), children: [
1174
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoonIcon, { size: ui.iconSize.smallest }),
1175
- " ",
1176
- t("dark_mode")
1177
- ] }),
1178
- /* @__PURE__ */ jsxRuntime.jsxs(ui.MenuItem, { onClick: () => setColorMode("light"), children: [
1179
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SunIcon, { size: ui.iconSize.smallest }),
1180
- " ",
1181
- t("light_mode")
1182
- ] }),
1183
- /* @__PURE__ */ jsxRuntime.jsxs(ui.MenuItem, { onClick: () => setColorMode("system"), children: [
1184
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SunMoonIcon, { size: ui.iconSize.smallest }),
1185
- " ",
1186
- t("system_mode")
1187
- ] })
1188
- ]
1189
- }
1190
- )
1191
- ] }),
1192
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center w-[480px] max-w-full p-8 sm:p-10", children: [
1193
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-32 h-32 m-2 mb-6", children: logoComponent }),
1194
- notAllowedMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 w-full", children: /* @__PURE__ */ jsxRuntime.jsx(core.ErrorView, { error: notAllowedMessage }) }),
1195
- mode !== "forgot" && buildErrorView(),
1196
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cls(
1197
- "w-full transition-opacity duration-150",
1198
- viewVisible ? "opacity-100" : "opacity-0"
1199
- ), children: [
1200
- isBootstrapMode && !authController.user && /* @__PURE__ */ jsxRuntime.jsx(
1201
- LoginForm,
1202
- {
1203
- authController,
1204
- registrationMode: true,
1205
- onClose: () => {
1206
- },
1207
- onForgotPassword: () => {
1208
- },
1209
- noUserComponent,
1210
- disableSignupScreen: false,
1211
- bootstrapMode: true
1212
- }
1213
- ),
1214
- !isBootstrapMode && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1215
- mode === "buttons" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full flex flex-col gap-3 mt-2", children: [
1216
- /* @__PURE__ */ jsxRuntime.jsx(
1217
- LoginButton,
1218
- {
1219
- disabled,
1220
- text: "Sign in with email",
1221
- icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MailIcon, {}),
1222
- onClick: () => switchMode("login")
1223
- }
1224
- ),
1225
- googleEnabled && googleClientId && /* @__PURE__ */ jsxRuntime.jsx(
1226
- GoogleLoginButton,
1227
- {
1228
- disabled,
1229
- googleClientId,
1230
- authController
1231
- }
1232
- ),
1233
- showRegistration && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 text-center", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Typography, { variant: "body2", color: "secondary", children: [
1234
- "Don't have an account?",
1235
- " ",
1236
- /* @__PURE__ */ jsxRuntime.jsx(
1237
- "button",
1238
- {
1239
- type: "button",
1240
- className: ui.cls(
1241
- "font-semibold hover:underline cursor-pointer",
1242
- "text-primary-600 dark:text-primary-400"
1243
- ),
1244
- onClick: () => switchMode("register"),
1245
- children: "Create one"
1246
- }
1247
- )
1248
- ] }) })
1249
- ] }),
1250
- mode === "login" && /* @__PURE__ */ jsxRuntime.jsx(
1251
- LoginForm,
1252
- {
1253
- authController,
1254
- registrationMode: false,
1255
- onClose: () => switchMode("buttons"),
1256
- onForgotPassword: () => switchMode("forgot"),
1257
- noUserComponent,
1258
- disableSignupScreen,
1259
- switchToRegister: showRegistration ? () => switchMode("register") : void 0
1260
- }
1261
- ),
1262
- mode === "register" && /* @__PURE__ */ jsxRuntime.jsx(
1263
- LoginForm,
1264
- {
1265
- authController,
1266
- registrationMode: true,
1267
- onClose: () => switchMode("buttons"),
1268
- onForgotPassword: () => switchMode("forgot"),
1269
- noUserComponent,
1270
- disableSignupScreen,
1271
- switchToLogin: () => switchMode("login")
1272
- }
1273
- ),
1274
- mode === "forgot" && /* @__PURE__ */ jsxRuntime.jsx(
1275
- ForgotPasswordForm,
1276
- {
1277
- authController,
1278
- onClose: () => switchMode("login")
1279
- }
1280
- )
1281
- ] })
1282
- ] })
1283
- ] })
1284
- ]
1285
- }
1286
- );
1287
- }
1288
- function LoginButton({
1289
- icon,
1290
- onClick,
1291
- text,
1292
- disabled
1293
- }) {
1294
- return /* @__PURE__ */ jsxRuntime.jsx(
1295
- ui.Button,
1296
- {
1297
- disabled,
1298
- className: "w-full",
1299
- variant: "outlined",
1300
- size: "large",
1301
- onClick,
1302
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center w-full gap-3 py-1", children: [
1303
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex items-center justify-center w-5 h-5", children: icon }),
1304
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "button", children: text })
1305
- ] })
1306
- }
1307
- );
1308
- }
1309
- const GoogleIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", width: "20", height: "20", children: [
1310
- /* @__PURE__ */ jsxRuntime.jsx(
1311
- "path",
1312
- {
1313
- fill: "#4285F4",
1314
- d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
1315
- }
1316
- ),
1317
- /* @__PURE__ */ jsxRuntime.jsx(
1318
- "path",
1319
- {
1320
- fill: "#34A853",
1321
- d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
1322
- }
1323
- ),
1324
- /* @__PURE__ */ jsxRuntime.jsx(
1325
- "path",
1326
- {
1327
- fill: "#FBBC05",
1328
- d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
1329
- }
1330
- ),
1331
- /* @__PURE__ */ jsxRuntime.jsx(
1332
- "path",
1333
- {
1334
- fill: "#EA4335",
1335
- d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
1336
- }
1337
- )
1338
- ] });
1339
- function GoogleLoginButton({
1340
- disabled,
1341
- googleClientId,
1342
- authController
1343
- }) {
1344
- const codeClientRef = react.useRef(null);
1345
- react.useEffect(() => {
1346
- const google = window.google;
1347
- if (!google || codeClientRef.current) return;
1348
- codeClientRef.current = google.accounts.oauth2.initCodeClient({
1349
- client_id: googleClientId,
1350
- scope: "openid email profile",
1351
- ux_mode: "popup",
1352
- callback: async (response) => {
1353
- if (response.error || !response.code) {
1354
- console.error("Google login error:", response.error);
1355
- return;
1356
- }
1357
- try {
1358
- await authController.googleLogin({
1359
- code: response.code,
1360
- redirectUri: "postmessage"
1361
- });
1362
- } catch (err) {
1363
- console.error("Google login error:", err);
1364
- }
1365
- }
1366
- });
1367
- }, [googleClientId, authController]);
1368
- const handleClick = () => {
1369
- if (!codeClientRef.current) {
1370
- console.error("Google Sign-In not loaded");
1371
- return;
1372
- }
1373
- codeClientRef.current.requestCode();
1374
- };
1375
- return /* @__PURE__ */ jsxRuntime.jsx(
1376
- LoginButton,
1377
- {
1378
- disabled,
1379
- text: "Sign in with Google",
1380
- icon: /* @__PURE__ */ jsxRuntime.jsx(GoogleIcon, {}),
1381
- onClick: handleClick
1382
- }
1383
- );
1384
- }
1385
- function LoginForm({
1386
- onClose,
1387
- onForgotPassword,
1388
- authController,
1389
- registrationMode,
1390
- noUserComponent,
1391
- disableSignupScreen,
1392
- bootstrapMode = false,
1393
- switchToRegister,
1394
- switchToLogin
1395
- }) {
1396
- const passwordRef = react.useRef(null);
1397
- const [email, setEmail] = react.useState();
1398
- const [password, setPassword] = react.useState();
1399
- const [displayName, setDisplayName] = react.useState();
1400
- react.useEffect(() => {
1401
- if (!document) return;
1402
- const escFunction = (event) => {
1403
- if (event.keyCode === 27) {
1404
- onClose();
1405
- }
1406
- };
1407
- document.addEventListener("keydown", escFunction, false);
1408
- return () => {
1409
- document.removeEventListener("keydown", escFunction, false);
1410
- };
1411
- }, [onClose]);
1412
- function handleEnterPassword() {
1413
- if (email && password) {
1414
- authController.emailPasswordLogin(email, password);
1415
- }
1416
- }
1417
- function handleRegistration() {
1418
- if (email && password) {
1419
- authController.register(email, password, displayName);
1420
- }
1421
- }
1422
- const handleSubmit = (event) => {
1423
- event.preventDefault();
1424
- if (registrationMode)
1425
- handleRegistration();
1426
- else
1427
- handleEnterPassword();
1428
- };
1429
- const title = bootstrapMode ? "Welcome!" : registrationMode ? "Create account" : "Sign in";
1430
- const subtitle = bootstrapMode ? "Create your admin account to get started. This account will have admin privileges." : registrationMode ? "Fill in your details to create a new account" : "Enter your credentials to continue";
1431
- const buttonLabel = registrationMode ? "Create account" : "Sign in";
1432
- return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col w-full gap-1 mt-2", children: [
1433
- !bootstrapMode && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full mb-2 -ml-2.5", children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeftIcon, {}) }) }),
1434
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "h6", className: "mb-0.5", children: title }),
1435
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "body2", color: "secondary", className: "mb-5", children: subtitle }),
1436
- registrationMode && noUserComponent && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full mb-2", children: noUserComponent }),
1437
- registrationMode && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full mb-3", children: [
1438
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", color: "secondary", className: "mb-1", children: "Display Name" }),
1439
- /* @__PURE__ */ jsxRuntime.jsx(
1440
- ui.TextField,
1441
- {
1442
- placeholder: "Jane Doe (optional)",
1443
- className: "w-full",
1444
- value: displayName ?? "",
1445
- disabled: authController.initialLoading,
1446
- type: "text",
1447
- size: "medium",
1448
- onChange: (event) => setDisplayName(event.target.value)
1449
- }
1450
- )
1451
- ] }),
1452
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full mb-3", children: [
1453
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", color: "secondary", className: "mb-1", children: "Email" }),
1454
- /* @__PURE__ */ jsxRuntime.jsx(
1455
- ui.TextField,
1456
- {
1457
- placeholder: "you@example.com",
1458
- className: "w-full",
1459
- autoFocus: true,
1460
- value: email ?? "",
1461
- disabled: authController.initialLoading,
1462
- type: "email",
1463
- size: "medium",
1464
- onChange: (event) => setEmail(event.target.value)
1465
- }
1466
- )
1467
- ] }),
1468
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full mb-1", children: [
1469
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", color: "secondary", className: "mb-1", children: "Password" }),
1470
- /* @__PURE__ */ jsxRuntime.jsx(
1471
- ui.TextField,
1472
- {
1473
- placeholder: "••••••••",
1474
- className: "w-full",
1475
- value: password ?? "",
1476
- disabled: authController.initialLoading,
1477
- inputRef: passwordRef,
1478
- type: "password",
1479
- size: "medium",
1480
- onChange: (event) => setPassword(event.target.value)
1481
- }
1482
- )
1483
- ] }),
1484
- registrationMode && /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "caption", color: "secondary", className: "mb-3", children: "Password must be 8+ characters with uppercase, lowercase, and a number" }),
1485
- !registrationMode && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full text-right mb-3", children: /* @__PURE__ */ jsxRuntime.jsx(
1486
- "button",
1487
- {
1488
- type: "button",
1489
- className: ui.cls(
1490
- "text-xs font-medium hover:underline cursor-pointer",
1491
- "text-primary-600 dark:text-primary-400"
1492
- ),
1493
- onClick: onForgotPassword,
1494
- children: "Forgot password?"
1495
- }
1496
- ) }),
1497
- /* @__PURE__ */ jsxRuntime.jsx(
1498
- ui.LoadingButton,
1499
- {
1500
- type: "submit",
1501
- variant: "filled",
1502
- color: "primary",
1503
- className: "w-full mt-1",
1504
- size: "large",
1505
- loading: authController.authLoading,
1506
- disabled: authController.authLoading || !email || !password,
1507
- children: buttonLabel
1508
- }
1509
- ),
1510
- switchToRegister && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 text-center", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Typography, { variant: "body2", color: "secondary", children: [
1511
- "Don't have an account?",
1512
- " ",
1513
- /* @__PURE__ */ jsxRuntime.jsx(
1514
- "button",
1515
- {
1516
- type: "button",
1517
- className: ui.cls(
1518
- "font-semibold hover:underline cursor-pointer",
1519
- "text-primary-600 dark:text-primary-400"
1520
- ),
1521
- onClick: switchToRegister,
1522
- children: "Create one"
1523
- }
1524
- )
1525
- ] }) }),
1526
- switchToLogin && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 text-center", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Typography, { variant: "body2", color: "secondary", children: [
1527
- "Already have an account?",
1528
- " ",
1529
- /* @__PURE__ */ jsxRuntime.jsx(
1530
- "button",
1531
- {
1532
- type: "button",
1533
- className: ui.cls(
1534
- "font-semibold hover:underline cursor-pointer",
1535
- "text-primary-600 dark:text-primary-400"
1536
- ),
1537
- onClick: switchToLogin,
1538
- children: "Sign in"
1539
- }
1540
- )
1541
- ] }) })
1542
- ] });
1543
- }
1544
- function ForgotPasswordForm({
1545
- onClose,
1546
- authController
1547
- }) {
1548
- const [email, setEmail] = react.useState("");
1549
- const [submitted, setSubmitted] = react.useState(false);
1550
- const [error, setError] = react.useState(null);
1551
- react.useEffect(() => {
1552
- if (!document) return;
1553
- const escFunction = (event) => {
1554
- if (event.keyCode === 27) {
1555
- onClose();
1556
- }
1557
- };
1558
- document.addEventListener("keydown", escFunction, false);
1559
- return () => {
1560
- document.removeEventListener("keydown", escFunction, false);
1561
- };
1562
- }, [onClose]);
1563
- const handleSubmit = async (event) => {
1564
- event.preventDefault();
1565
- setError(null);
1566
- if (!email) {
1567
- setError("Please enter your email address");
1568
- return;
1569
- }
1570
- try {
1571
- await authController.forgotPassword(email);
1572
- setSubmitted(true);
1573
- } catch (err) {
1574
- if (err instanceof Error && err.code === "EMAIL_NOT_CONFIGURED") {
1575
- setError("Password reset is not available. Please contact your administrator.");
1576
- } else {
1577
- setSubmitted(true);
1578
- }
1579
- }
1580
- };
1581
- if (submitted) {
1582
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col w-full gap-4 mt-2", children: [
1583
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full -ml-2.5", children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeftIcon, {}) }) }),
1584
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cls(
1585
- "text-center rounded-xl p-6",
1586
- "bg-surface-50 dark:bg-surface-950"
1587
- ), children: [
1588
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-3xl mb-3", children: "📧" }),
1589
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "subtitle1", className: "mb-2", children: "Check your email" }),
1590
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Typography, { variant: "body2", color: "secondary", children: [
1591
- "If an account exists for ",
1592
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: email }),
1593
- ", you'll receive a password reset link shortly."
1594
- ] })
1595
- ] }),
1596
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: onClose, variant: "text", className: "mt-2", children: "Back to sign in" })
1597
- ] });
1598
- }
1599
- return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col w-full gap-1 mt-2", children: [
1600
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full mb-2 -ml-2.5", children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeftIcon, {}) }) }),
1601
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "h6", className: "mb-0.5", children: "Reset password" }),
1602
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "body2", color: "secondary", className: "mb-5", children: "Enter your email and we'll send you a reset link." }),
1603
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full mb-3", children: /* @__PURE__ */ jsxRuntime.jsx(core.ErrorView, { error }) }),
1604
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full mb-3", children: [
1605
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", color: "secondary", className: "mb-1", children: "Email" }),
1606
- /* @__PURE__ */ jsxRuntime.jsx(
1607
- ui.TextField,
1608
- {
1609
- placeholder: "you@example.com",
1610
- className: "w-full",
1611
- autoFocus: true,
1612
- value: email,
1613
- type: "email",
1614
- size: "medium",
1615
- onChange: (event) => setEmail(event.target.value)
1616
- }
1617
- )
1618
- ] }),
1619
- /* @__PURE__ */ jsxRuntime.jsx(
1620
- ui.LoadingButton,
1621
- {
1622
- type: "submit",
1623
- variant: "filled",
1624
- color: "primary",
1625
- className: "w-full",
1626
- size: "large",
1627
- loading: authController.authLoading,
1628
- disabled: authController.authLoading || !email,
1629
- children: "Send reset link"
1630
- }
1631
- )
1632
- ] });
1633
- }
1634
- function RebaseAuth({ loginView }) {
1635
- const dispatch = core.useRebaseRegistryDispatch();
1636
- const registeredRef = react.useRef(false);
1637
- react.useLayoutEffect(() => {
1638
- dispatch.registerAuth({ loginView });
1639
- registeredRef.current = true;
1640
- return () => {
1641
- registeredRef.current = false;
1642
- dispatch.unregisterAuth();
1643
- };
1644
- }, [dispatch, loginView]);
1645
- return null;
1646
- }
1647
- function createUserManagementAdminViews({ userManagement, apiUrl, getAuthToken, collections = [] }) {
1648
- return [
1649
- {
1650
- slug: "dev/users",
1651
- name: "CMS Users",
1652
- icon: "face",
1653
- view: /* @__PURE__ */ jsxRuntime.jsx(UsersView, { userManagement, apiUrl, getAuthToken })
1654
- },
1655
- {
1656
- slug: "dev/roles",
1657
- name: "Roles",
1658
- icon: "gpp_good",
1659
- view: /* @__PURE__ */ jsxRuntime.jsx(RolesView, { userManagement, collections })
1660
- }
1661
- ];
1662
- }
1663
- function RoleChip({ role }) {
1664
- let colorScheme;
1665
- if (role.isAdmin) {
1666
- colorScheme = "blue";
1667
- } else if (role.id === "editor") {
1668
- colorScheme = "yellow";
1669
- } else if (role.id === "viewer") {
1670
- colorScheme = "gray";
1671
- } else {
1672
- colorScheme = ui.getColorSchemeForSeed(role.id);
1673
- }
1674
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Chip, { colorScheme, children: role.name }, role.id);
1675
- }
1676
- function UsersView({ userManagement, apiUrl, getAuthToken }) {
1677
- const { users, roles, saveUser, deleteUser, loading } = userManagement;
1678
- const usersError = "usersError" in userManagement ? userManagement.usersError : void 0;
1679
- const snackbarController = core.useSnackbarController();
1680
- const { user: loggedInUser } = core.useAuthController();
1681
- const [dialogOpen, setDialogOpen] = react.useState(false);
1682
- const [selectedUser, setSelectedUser] = react.useState();
1683
- const [deleteConfirmOpen, setDeleteConfirmOpen] = react.useState(false);
1684
- const [userToDelete, setUserToDelete] = react.useState();
1685
- const [deleteInProgress, setDeleteInProgress] = react.useState(false);
1686
- const [formKey, setFormKey] = react.useState(0);
1687
- const [bootstrapping, setBootstrapping] = react.useState(false);
1688
- const hasAdmin = users.some((u) => u.roles?.includes("admin"));
1689
- const handleBootstrap = async () => {
1690
- setBootstrapping(true);
1691
- try {
1692
- const token = await getAuthToken();
1693
- const response = await fetch(`${apiUrl}/api/admin/bootstrap`, {
1694
- method: "POST",
1695
- headers: {
1696
- "Content-Type": "application/json",
1697
- "Authorization": `Bearer ${token}`
1698
- }
1699
- });
1700
- const data = await response.json();
1701
- if (!response.ok) {
1702
- throw new Error(data.error?.message || "Bootstrap failed");
1703
- }
1704
- snackbarController.open({
1705
- type: "success",
1706
- message: "You are now an admin! Refreshing..."
1707
- });
1708
- window.location.reload();
1709
- } catch (error) {
1710
- snackbarController.open({
1711
- type: "error",
1712
- message: error instanceof Error ? error.message : "Failed to bootstrap admin"
1713
- });
1714
- } finally {
1715
- setBootstrapping(false);
1716
- }
1717
- };
1718
- const handleAddUser = () => {
1719
- setSelectedUser(void 0);
1720
- setFormKey((k) => k + 1);
1721
- setDialogOpen(true);
1722
- };
1723
- const handleEditUser = (user) => {
1724
- setSelectedUser(user);
1725
- setDialogOpen(true);
1726
- };
1727
- const handleClose = () => {
1728
- setDialogOpen(false);
1729
- setSelectedUser(void 0);
1730
- };
1731
- const handleDelete = async () => {
1732
- if (!userToDelete) return;
1733
- setDeleteInProgress(true);
1734
- try {
1735
- await deleteUser(userToDelete);
1736
- snackbarController.open({
1737
- type: "success",
1738
- message: "User deleted successfully"
1739
- });
1740
- setDeleteConfirmOpen(false);
1741
- setUserToDelete(void 0);
1742
- } catch (error) {
1743
- snackbarController.open({
1744
- type: "error",
1745
- message: error instanceof Error ? error.message : "Error deleting user"
1746
- });
1747
- } finally {
1748
- setDeleteInProgress(false);
1749
- }
1750
- };
1751
- if (loading) {
1752
- return /* @__PURE__ */ jsxRuntime.jsx(ui.CenteredView, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.CircularProgress, {}) });
1753
- }
1754
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "w-full flex flex-col py-4 gap-4", maxWidth: "6xl", children: [
1755
- !hasAdmin && loggedInUser && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-yellow-100 dark:bg-yellow-900 border border-yellow-400 dark:border-yellow-700 rounded p-4 flex items-center justify-between", children: [
1756
- /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", className: "text-yellow-800 dark:text-yellow-200", children: "No admin users exist. You can make yourself an admin." }) }),
1757
- /* @__PURE__ */ jsxRuntime.jsx(
1758
- ui.Button,
1759
- {
1760
- onClick: handleBootstrap,
1761
- disabled: bootstrapping,
1762
- children: bootstrapping ? /* @__PURE__ */ jsxRuntime.jsx(ui.CircularProgress, { size: "small" }) : "Make me admin"
1763
- }
1764
- )
1765
- ] }),
1766
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center mt-12", children: [
1767
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { gutterBottom: true, variant: "h4", className: "grow", component: "h4", children: "Users" }),
1768
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlusIcon, {}), onClick: handleAddUser, children: "Add user" })
1769
- ] }),
1770
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { className: "w-full", children: [
1771
- /* @__PURE__ */ jsxRuntime.jsxs(ui.TableHeader, { children: [
1772
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { header: true, className: "truncate w-16" }),
1773
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { header: true, children: "Email" }),
1774
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { header: true, children: "Name" }),
1775
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { header: true, children: "Roles" })
1776
- ] }),
1777
- /* @__PURE__ */ jsxRuntime.jsxs(ui.TableBody, { children: [
1778
- users.map((user) => /* @__PURE__ */ jsxRuntime.jsxs(ui.TableRow, { onClick: () => handleEditUser(user), children: [
1779
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { style: { width: "64px" }, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { asChild: true, title: "Delete this user", children: /* @__PURE__ */ jsxRuntime.jsx(
1780
- ui.IconButton,
1781
- {
1782
- size: "small",
1783
- onClick: (e) => {
1784
- e.stopPropagation();
1785
- setUserToDelete(user);
1786
- setDeleteConfirmOpen(true);
1787
- },
1788
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2Icon, {})
1789
- }
1790
- ) }) }),
1791
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { children: user.email }),
1792
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { className: "font-medium", children: user.displayName }),
1793
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: user.roles?.map((roleId) => {
1794
- const role = roles.find((r) => r.id === roleId);
1795
- return role ? /* @__PURE__ */ jsxRuntime.jsx(RoleChip, { role }, roleId) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: roleId }, roleId);
1796
- }) }) })
1797
- ] }, user.uid)),
1798
- users.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.TableRow, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { colspan: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.CenteredView, { className: "flex flex-col gap-4 my-8 items-center", children: [
1799
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", children: usersError ? "You don't have permission to view users" : "There are no users yet" }),
1800
- usersError && /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "caption", className: "text-surface-500", children: "Contact an administrator if you need access to this section." })
1801
- ] }) }) })
1802
- ] })
1803
- ] }) }),
1804
- /* @__PURE__ */ jsxRuntime.jsx(
1805
- UserDetailsForm,
1806
- {
1807
- open: dialogOpen,
1808
- user: selectedUser,
1809
- roles,
1810
- saveUser,
1811
- handleClose
1812
- },
1813
- selectedUser?.uid ?? `new-${formKey}`
1814
- ),
1815
- /* @__PURE__ */ jsxRuntime.jsx(
1816
- core.ConfirmationDialog,
1817
- {
1818
- open: deleteConfirmOpen,
1819
- loading: deleteInProgress,
1820
- onAccept: handleDelete,
1821
- onCancel: () => {
1822
- setDeleteConfirmOpen(false);
1823
- setUserToDelete(void 0);
1824
- },
1825
- title: /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: "Delete?" }),
1826
- body: /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: "Are you sure you want to delete this user?" })
1827
- }
1828
- )
1829
- ] });
1830
- }
1831
- function UserDetailsForm({
1832
- open,
1833
- user: userProp,
1834
- roles,
1835
- saveUser,
1836
- handleClose
1837
- }) {
1838
- const snackbarController = core.useSnackbarController();
1839
- const isNewUser = !userProp;
1840
- const [displayName, setDisplayName] = react.useState(userProp?.displayName || "");
1841
- const [email, setEmail] = react.useState(userProp?.email || "");
1842
- const [selectedRoleIds, setSelectedRoleIds] = react.useState(
1843
- userProp?.roles || ["editor"]
1844
- );
1845
- const [isSubmitting, setIsSubmitting] = react.useState(false);
1846
- const [errors, setErrors] = react.useState({});
1847
- const [submitCount, setSubmitCount] = react.useState(0);
1848
- const validate = () => {
1849
- const newErrors = {};
1850
- if (!displayName) newErrors.displayName = "Required";
1851
- if (!email) newErrors.email = "Required";
1852
- else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = "Invalid email";
1853
- if (selectedRoleIds.length === 0) newErrors.roles = "At least one role is required";
1854
- setErrors(newErrors);
1855
- return Object.keys(newErrors).length === 0;
1856
- };
1857
- const handleSubmit = async (e) => {
1858
- e.preventDefault();
1859
- setSubmitCount((c) => c + 1);
1860
- if (!validate()) return;
1861
- setIsSubmitting(true);
1862
- try {
1863
- const userToSave = {
1864
- uid: userProp?.uid || crypto.randomUUID(),
1865
- email,
1866
- displayName: displayName || null,
1867
- photoURL: userProp?.photoURL || null,
1868
- providerId: "custom",
1869
- isAnonymous: false,
1870
- roles: selectedRoleIds
1871
- };
1872
- await saveUser(userToSave);
1873
- handleClose();
1874
- } catch (error) {
1875
- snackbarController.open({
1876
- type: "error",
1877
- message: error instanceof Error ? error.message : "Failed to save user"
1878
- });
1879
- } finally {
1880
- setIsSubmitting(false);
1881
- }
1882
- };
1883
- const dirty = isNewUser || displayName !== (userProp?.displayName || "") || email !== (userProp?.email || "") || JSON.stringify(selectedRoleIds.sort()) !== JSON.stringify((userProp?.roles || []).sort());
1884
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, { open, onOpenChange: (open2) => !open2 ? handleClose() : void 0, maxWidth: "4xl", children: /* @__PURE__ */ jsxRuntime.jsxs(
1885
- "form",
1886
- {
1887
- onSubmit: handleSubmit,
1888
- autoComplete: "off",
1889
- noValidate: true,
1890
- style: {
1891
- display: "flex",
1892
- flexDirection: "column",
1893
- position: "relative",
1894
- height: "100%"
1895
- },
1896
- children: [
1897
- /* @__PURE__ */ jsxRuntime.jsx(ui.DialogTitle, { variant: "h4", gutterBottom: false, children: "User" }),
1898
- /* @__PURE__ */ jsxRuntime.jsx(ui.DialogContent, { className: "h-full grow", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-12 gap-4", children: [
1899
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-12", children: [
1900
- /* @__PURE__ */ jsxRuntime.jsx(
1901
- ui.TextField,
1902
- {
1903
- name: "displayName",
1904
- required: true,
1905
- error: submitCount > 0 && Boolean(errors.displayName),
1906
- value: displayName,
1907
- onChange: (e) => setDisplayName(e.target.value),
1908
- label: "Name"
1909
- }
1910
- ),
1911
- /* @__PURE__ */ jsxRuntime.jsx(FieldCaption, { children: submitCount > 0 && errors.displayName ? errors.displayName : "Name of this user" })
1912
- ] }),
1913
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-12", children: [
1914
- /* @__PURE__ */ jsxRuntime.jsx(
1915
- ui.TextField,
1916
- {
1917
- required: true,
1918
- error: submitCount > 0 && Boolean(errors.email),
1919
- name: "email",
1920
- value: email,
1921
- onChange: (e) => setEmail(e.target.value),
1922
- label: "Email",
1923
- disabled: !isNewUser
1924
- }
1925
- ),
1926
- /* @__PURE__ */ jsxRuntime.jsx(FieldCaption, { children: submitCount > 0 && errors.email ? errors.email : "Email of this user" })
1927
- ] }),
1928
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-12", children: /* @__PURE__ */ jsxRuntime.jsx(
1929
- ui.MultiSelect,
1930
- {
1931
- className: "w-full",
1932
- label: "Roles",
1933
- value: selectedRoleIds,
1934
- onValueChange: (value) => setSelectedRoleIds(value),
1935
- children: roles.map((role) => /* @__PURE__ */ jsxRuntime.jsx(ui.MultiSelectItem, { value: role.id, children: /* @__PURE__ */ jsxRuntime.jsx(RoleChip, { role }) }, role.id))
1936
- }
1937
- ) })
1938
- ] }) }),
1939
- /* @__PURE__ */ jsxRuntime.jsxs(ui.DialogActions, { children: [
1940
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "text", onClick: handleClose, children: "Cancel" }),
1941
- /* @__PURE__ */ jsxRuntime.jsx(
1942
- ui.LoadingButton,
1943
- {
1944
- variant: "filled",
1945
- type: "submit",
1946
- disabled: !dirty,
1947
- loading: isSubmitting,
1948
- children: isNewUser ? "Create user" : "Update"
1949
- }
1950
- )
1951
- ] })
1952
- ]
1953
- }
1954
- ) });
1955
- }
1956
- function RolesView({ userManagement, collections = [] }) {
1957
- const { roles, saveRole, deleteRole, loading, allowDefaultRolesCreation } = userManagement;
1958
- const rolesError = "rolesError" in userManagement ? userManagement.rolesError : void 0;
1959
- const snackbarController = core.useSnackbarController();
1960
- const [dialogOpen, setDialogOpen] = react.useState(false);
1961
- const [selectedRole, setSelectedRole] = react.useState();
1962
- const [deleteConfirmOpen, setDeleteConfirmOpen] = react.useState(false);
1963
- const [roleToDelete, setRoleToDelete] = react.useState();
1964
- const [deleteInProgress, setDeleteInProgress] = react.useState(false);
1965
- const handleAddRole = () => {
1966
- setSelectedRole(void 0);
1967
- setDialogOpen(true);
1968
- };
1969
- const handleEditRole = (role) => {
1970
- setSelectedRole(role);
1971
- setDialogOpen(true);
1972
- };
1973
- const handleClose = () => {
1974
- setDialogOpen(false);
1975
- setSelectedRole(void 0);
1976
- };
1977
- const handleDelete = async () => {
1978
- if (!roleToDelete) return;
1979
- setDeleteInProgress(true);
1980
- try {
1981
- await deleteRole(roleToDelete);
1982
- snackbarController.open({
1983
- type: "success",
1984
- message: "Role deleted successfully"
1985
- });
1986
- setDeleteConfirmOpen(false);
1987
- setRoleToDelete(void 0);
1988
- } catch (error) {
1989
- snackbarController.open({
1990
- type: "error",
1991
- message: error instanceof Error ? error.message : "Error deleting role"
1992
- });
1993
- } finally {
1994
- setDeleteInProgress(false);
1995
- }
1996
- };
1997
- const createDefaultRoles = () => {
1998
- const defaultRoles = [
1999
- {
2000
- id: "admin",
2001
- name: "Admin",
2002
- isAdmin: true
2003
- },
2004
- {
2005
- id: "editor",
2006
- name: "Editor",
2007
- isAdmin: false
2008
- },
2009
- {
2010
- id: "viewer",
2011
- name: "Viewer",
2012
- isAdmin: false
2013
- }
2014
- ];
2015
- defaultRoles.forEach((role) => saveRole(role));
2016
- };
2017
- if (loading) {
2018
- return /* @__PURE__ */ jsxRuntime.jsx(ui.CenteredView, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.CircularProgress, {}) });
2019
- }
2020
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "w-full flex flex-col py-4 gap-4", maxWidth: "6xl", children: [
2021
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center mt-12", children: [
2022
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { gutterBottom: true, variant: "h4", className: "grow", component: "h4", children: "Roles" }),
2023
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlusIcon, {}), onClick: handleAddRole, children: "Add role" })
2024
- ] }),
2025
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { className: "w-full", children: [
2026
- /* @__PURE__ */ jsxRuntime.jsxs(ui.TableHeader, { children: [
2027
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { header: true, className: "w-16" }),
2028
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { header: true, children: "Role" }),
2029
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { header: true, className: "items-center", children: "Is Admin" })
2030
- ] }),
2031
- /* @__PURE__ */ jsxRuntime.jsxs(ui.TableBody, { children: [
2032
- roles.map((role) => {
2033
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.TableRow, { onClick: () => handleEditRole(role), children: [
2034
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { style: { width: "64px" }, children: !role.isAdmin && /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { asChild: true, title: "Delete this role", children: /* @__PURE__ */ jsxRuntime.jsx(
2035
- ui.IconButton,
2036
- {
2037
- size: "small",
2038
- onClick: (e) => {
2039
- e.stopPropagation();
2040
- setRoleToDelete(role);
2041
- setDeleteConfirmOpen(true);
2042
- },
2043
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2Icon, {})
2044
- }
2045
- ) }) }),
2046
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { children: /* @__PURE__ */ jsxRuntime.jsx(RoleChip, { role }) }),
2047
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { className: "items-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, { checked: role.isAdmin ?? false }) })
2048
- ] }, role.id);
2049
- }),
2050
- roles.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.TableRow, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { colspan: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.CenteredView, { className: "flex flex-col gap-4 my-8 items-center", children: [
2051
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", children: rolesError ? "You don't have permission to view roles" : "You don't have any roles yet." }),
2052
- rolesError && /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "caption", className: "text-surface-500", children: "Contact an administrator if you need access to this section." }),
2053
- !rolesError && allowDefaultRolesCreation && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: createDefaultRoles, children: "Create default roles" })
2054
- ] }) }) })
2055
- ] })
2056
- ] }) }),
2057
- /* @__PURE__ */ jsxRuntime.jsx(
2058
- RoleDetailsForm,
2059
- {
2060
- open: dialogOpen,
2061
- role: selectedRole,
2062
- saveRole,
2063
- handleClose,
2064
- collections
2065
- },
2066
- selectedRole?.id ?? "new"
2067
- ),
2068
- /* @__PURE__ */ jsxRuntime.jsx(
2069
- core.ConfirmationDialog,
2070
- {
2071
- open: deleteConfirmOpen,
2072
- loading: deleteInProgress,
2073
- onAccept: handleDelete,
2074
- onCancel: () => {
2075
- setDeleteConfirmOpen(false);
2076
- setRoleToDelete(void 0);
2077
- },
2078
- title: /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: "Delete?" }),
2079
- body: /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: "Are you sure you want to delete this role?" })
2080
- }
2081
- )
2082
- ] });
2083
- }
2084
- function RoleDetailsForm({
2085
- open,
2086
- role: roleProp,
2087
- saveRole,
2088
- handleClose,
2089
- collections = []
2090
- }) {
2091
- const snackbarController = core.useSnackbarController();
2092
- const isNewRole = !roleProp;
2093
- const [roleId, setRoleId] = react.useState(roleProp?.id || "");
2094
- const [roleName, setRoleName] = react.useState(roleProp?.name || "");
2095
- const [isAdmin, setIsAdmin] = react.useState(roleProp?.isAdmin ?? false);
2096
- const [isSubmitting, setIsSubmitting] = react.useState(false);
2097
- const [errors, setErrors] = react.useState({});
2098
- const [submitCount, setSubmitCount] = react.useState(0);
2099
- const validate = () => {
2100
- const newErrors = {};
2101
- if (!roleId) newErrors.id = "Required";
2102
- if (!roleName) newErrors.name = "Required";
2103
- setErrors(newErrors);
2104
- return Object.keys(newErrors).length === 0;
2105
- };
2106
- const handleSubmit = async (e) => {
2107
- e.preventDefault();
2108
- setSubmitCount((c) => c + 1);
2109
- if (!validate()) return;
2110
- setIsSubmitting(true);
2111
- try {
2112
- await saveRole({
2113
- id: roleId,
2114
- name: roleName,
2115
- isAdmin
2116
- });
2117
- handleClose();
2118
- } catch (error) {
2119
- snackbarController.open({
2120
- type: "error",
2121
- message: error instanceof Error ? error.message : "Failed to save role"
2122
- });
2123
- } finally {
2124
- setIsSubmitting(false);
2125
- }
2126
- };
2127
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, { open, onOpenChange: (open2) => !open2 ? handleClose() : void 0, maxWidth: "6xl", children: /* @__PURE__ */ jsxRuntime.jsxs(
2128
- "form",
2129
- {
2130
- onSubmit: handleSubmit,
2131
- autoComplete: "off",
2132
- noValidate: true,
2133
- style: {
2134
- display: "flex",
2135
- flexDirection: "column",
2136
- position: "relative",
2137
- height: "100%"
2138
- },
2139
- children: [
2140
- /* @__PURE__ */ jsxRuntime.jsx(ui.DialogTitle, { variant: "h4", gutterBottom: false, children: "Role" }),
2141
- /* @__PURE__ */ jsxRuntime.jsx(ui.DialogContent, { className: "h-full grow overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-12 gap-4", children: [
2142
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-12 sm:col-span-4", children: [
2143
- /* @__PURE__ */ jsxRuntime.jsx(
2144
- ui.TextField,
2145
- {
2146
- name: "id",
2147
- required: true,
2148
- error: submitCount > 0 && Boolean(errors.id),
2149
- value: roleId,
2150
- onChange: (e) => setRoleId(e.target.value),
2151
- label: "Role ID",
2152
- disabled: !isNewRole
2153
- }
2154
- ),
2155
- /* @__PURE__ */ jsxRuntime.jsx(FieldCaption, { children: submitCount > 0 && errors.id ? errors.id : "Unique identifier for this role" })
2156
- ] }),
2157
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-12 sm:col-span-4", children: [
2158
- /* @__PURE__ */ jsxRuntime.jsx(
2159
- ui.TextField,
2160
- {
2161
- name: "name",
2162
- required: true,
2163
- error: submitCount > 0 && Boolean(errors.name),
2164
- value: roleName,
2165
- onChange: (e) => setRoleName(e.target.value),
2166
- label: "Role Name"
2167
- }
2168
- ),
2169
- /* @__PURE__ */ jsxRuntime.jsx(FieldCaption, { children: submitCount > 0 && errors.name ? errors.name : "Display name for this role" })
2170
- ] }),
2171
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-12 sm:col-span-4 flex items-start pt-2", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer mt-3", children: [
2172
- /* @__PURE__ */ jsxRuntime.jsx(
2173
- ui.Checkbox,
2174
- {
2175
- checked: isAdmin,
2176
- onCheckedChange: (checked) => setIsAdmin(Boolean(checked))
2177
- }
2178
- ),
2179
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Is Admin" })
2180
- ] }) }),
2181
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-12", children: /* @__PURE__ */ jsxRuntime.jsx(CollectionPermissionsMatrix, { roleId, isAdmin, collections }) })
2182
- ] }) }),
2183
- /* @__PURE__ */ jsxRuntime.jsxs(ui.DialogActions, { children: [
2184
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "text", onClick: handleClose, children: "Cancel" }),
2185
- /* @__PURE__ */ jsxRuntime.jsx(
2186
- ui.LoadingButton,
2187
- {
2188
- variant: "filled",
2189
- type: "submit",
2190
- loading: isSubmitting,
2191
- children: isNewRole ? "Create role" : "Update"
2192
- }
2193
- )
2194
- ] })
2195
- ]
2196
- }
2197
- ) });
2198
- }
2199
- const CRUD_OPS = [
2200
- {
2201
- op: "select",
2202
- label: "Read"
2203
- },
2204
- {
2205
- op: "insert",
2206
- label: "Create"
2207
- },
2208
- {
2209
- op: "update",
2210
- label: "Edit"
2211
- },
2212
- {
2213
- op: "delete",
2214
- label: "Delete"
2215
- }
2216
- ];
2217
- function hasRoleAccess(rules, roleId, op) {
2218
- if (!rules || rules.length === 0) return true;
2219
- const applicable = rules.filter(
2220
- (r) => r.operation === op || r.operation === "all" || r.operations?.includes(op) || r.operations?.includes("all")
2221
- );
2222
- if (applicable.length === 0) return false;
2223
- const forRole = applicable.filter(
2224
- (r) => !r.roles || r.roles.length === 0 || r.roles.includes(roleId) || r.roles.includes("public")
2225
- );
2226
- if (forRole.length === 0) return false;
2227
- for (const r of forRole) {
2228
- if ((r.mode ?? "permissive") === "restrictive") return false;
2229
- }
2230
- return forRole.some((r) => (r.mode ?? "permissive") === "permissive");
2231
- }
2232
- function PermCell({ granted }) {
2233
- return /* @__PURE__ */ jsxRuntime.jsx(
2234
- "span",
2235
- {
2236
- className: granted ? "text-green-500 dark:text-green-400 text-base select-none" : "text-surface-300 dark:text-surface-600 text-base select-none",
2237
- children: granted ? "✓" : "✗"
2238
- }
2239
- );
2240
- }
2241
- function CollectionPermissionsMatrix({ roleId, isAdmin, collections }) {
2242
- if (!collections || collections.length === 0) {
2243
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", className: "text-surface-400", children: "No collections configured" }) });
2244
- }
2245
- const topLevel = collections;
2246
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6", children: [
2247
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", className: "mb-2 block text-surface-600 dark:text-surface-400 uppercase tracking-wide text-xs", children: "Collection permissions" }),
2248
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-surface-200 dark:border-surface-700 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
2249
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.TableRow, { children: [
2250
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { header: true, children: "Collection" }),
2251
- CRUD_OPS.map(({ op, label }) => /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { header: true, className: "text-center w-24", children: label }, op))
2252
- ] }) }),
2253
- /* @__PURE__ */ jsxRuntime.jsx(ui.TableBody, { children: topLevel.map((collection) => {
2254
- const extCol = collection;
2255
- const noRules = !extCol.securityRules || extCol.securityRules.length === 0;
2256
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.TableRow, { children: [
2257
- /* @__PURE__ */ jsxRuntime.jsxs(ui.TableCell, { children: [
2258
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2259
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: collection.name }),
2260
- noRules && !isAdmin && /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { title: "No security rules — unrestricted", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Chip, { className: "text-xs", colorScheme: "yellow", children: "No rules" }) })
2261
- ] }),
2262
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-surface-400 font-mono", children: collection.slug })
2263
- ] }),
2264
- CRUD_OPS.map(({ op }) => /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { className: "text-center", children: /* @__PURE__ */ jsxRuntime.jsx(PermCell, { granted: isAdmin || hasRoleAccess(extCol.securityRules, roleId, op) }) }, op))
2265
- ] }, collection.slug);
2266
- }) })
2267
- ] }) }),
2268
- !roleId && /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "caption", className: "mt-2 text-surface-400 italic", children: "Enter a role ID above to preview permissions" })
2269
- ] });
2270
- }
2271
- function FieldCaption({ children }) {
2272
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "caption", className: "text-surface-500 mt-1.5 ml-1 block", children });
2273
- }
2274
1097
  exports2.AuthApiError = AuthApiError;
2275
- exports2.RebaseAuth = RebaseAuth;
2276
- exports2.RebaseLoginView = RebaseLoginView;
2277
- exports2.RolesView = RolesView;
2278
- exports2.UsersView = UsersView;
2279
- exports2.createUserManagementAdminViews = createUserManagementAdminViews;
1098
+ exports2.clearAuthConfigCache = clearAuthConfigCache;
2280
1099
  exports2.fetchAuthConfig = fetchAuthConfig;
2281
1100
  exports2.getApiUrl = getApiUrl;
2282
1101
  exports2.setApiUrl = setApiUrl;