@dloizides/ui-access-admin 1.2.2 → 1.4.0

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.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
2
- import { StyleSheet, View, Text, Linking } from 'react-native';
2
+ import { StyleSheet, View, Text, useWindowDimensions, Linking, ScrollView } from 'react-native';
3
3
  import { Button } from '@dloizides/ui-buttons';
4
4
  import { useUi, ToastHost, createToastBus } from '@dloizides/ui-feedback';
5
- import { copyText, ModalShell, ModalDropdown, DropdownVariant } from '@dloizides/ui-layout';
5
+ import { copyText, LAYOUT_COLLAPSE_BREAKPOINT, ModalShell, ModalDropdown, DropdownVariant } from '@dloizides/ui-layout';
6
6
  import { jsxs, jsx } from 'react/jsx-runtime';
7
7
  import { SelectControl, FormField, FormSwitch, Field } from '@dloizides/ui-forms';
8
8
  import { FilterBar, DataTable } from '@dloizides/ui-tables';
@@ -167,12 +167,25 @@ var ACCESS_ADMIN_I18N = {
167
167
  usersUpdateSuccess: "accessAdmin.users.updateSuccess",
168
168
  usersUpdateFailed: "accessAdmin.users.updateFailed",
169
169
  usersResetSuccess: "accessAdmin.users.resetSuccess",
170
- usersResetFailed: "accessAdmin.users.resetFailed"
170
+ usersResetFailed: "accessAdmin.users.resetFailed",
171
+ usersFieldBlockedReason: "accessAdmin.users.fieldBlockedReason",
172
+ usersBlockedReasonPlaceholder: "accessAdmin.users.blockedReasonPlaceholder",
173
+ usersBlockedReasonHint: "accessAdmin.users.blockedReasonHint",
174
+ permissionsHeading: "accessAdmin.permissions.heading",
175
+ permissionsNote: "accessAdmin.permissions.note",
176
+ permissionsColPermission: "accessAdmin.permissions.colPermission",
177
+ permissionsGranted: "accessAdmin.permissions.granted",
178
+ permissionsNotGranted: "accessAdmin.permissions.notGranted",
179
+ permissionsCellLabel: "accessAdmin.permissions.cellLabel",
180
+ permissionsReadOnlyHint: "accessAdmin.permissions.readOnlyHint",
181
+ permissionsRoleCount: "accessAdmin.permissions.roleCount",
182
+ permissionsEmpty: "accessAdmin.permissions.empty"
171
183
  };
172
184
  var ACCESS_ADMIN_TEST_IDS = {
173
185
  linkPanel: "access-link-panel",
174
186
  accountPanel: "account-link-panel",
175
- usersPanel: "user-management-panel"
187
+ usersPanel: "user-management-panel",
188
+ permissionMatrix: "permission-matrix"
176
189
  };
177
190
 
178
191
  // src/expiry/expiryPolicy.ts
@@ -317,7 +330,7 @@ var AccessLinkPanel = ({
317
330
  }) => {
318
331
  const { t } = useUi();
319
332
  const palette = useAccessAdminPalette();
320
- const styles = useMemo(() => buildStyles(palette), [palette]);
333
+ const styles2 = useMemo(() => buildStyles(palette), [palette]);
321
334
  const state = useCapabilityLinkMint({
322
335
  subject,
323
336
  policy,
@@ -345,16 +358,16 @@ var AccessLinkPanel = ({
345
358
  onResult(t(ACCESS_ADMIN_I18N.copyFailed, subject.displayName), "error" /* Error */);
346
359
  });
347
360
  };
348
- return /* @__PURE__ */ jsxs(View, { style: styles.panel, testID: testIDPrefix, children: [
349
- /* @__PURE__ */ jsx(Text, { style: styles.heading, children: t(ACCESS_ADMIN_I18N.panelHeading, subject.displayName) }),
350
- /* @__PURE__ */ jsx(Text, { style: styles.note, children: t(ACCESS_ADMIN_I18N.panelNote) }),
351
- /* @__PURE__ */ jsx(Text, { style: styles.warning, testID: `${testIDPrefix}-capability-warning`, children: t(ACCESS_ADMIN_I18N.capabilityWarning) }),
352
- /* @__PURE__ */ jsx(Text, { style: styles.note, testID: `${testIDPrefix}-expiry-preview`, children: t(ACCESS_ADMIN_I18N.expiryPreview, expiryDate) }),
353
- state.minted !== null ? /* @__PURE__ */ jsxs(View, { style: styles.revealBox, testID: `${testIDPrefix}-minted`, children: [
354
- /* @__PURE__ */ jsx(Text, { style: styles.revealTitle, children: t(ACCESS_ADMIN_I18N.mintedTitle, subject.displayName) }),
355
- /* @__PURE__ */ jsx(Text, { style: styles.note, children: t(ACCESS_ADMIN_I18N.mintedHint) }),
356
- /* @__PURE__ */ jsx(Text, { selectable: true, style: styles.url, testID: `${testIDPrefix}-minted-url`, children: mintedUrl }),
357
- /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
361
+ return /* @__PURE__ */ jsxs(View, { style: styles2.panel, testID: testIDPrefix, children: [
362
+ /* @__PURE__ */ jsx(Text, { style: styles2.heading, children: t(ACCESS_ADMIN_I18N.panelHeading, subject.displayName) }),
363
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, children: t(ACCESS_ADMIN_I18N.panelNote) }),
364
+ /* @__PURE__ */ jsx(Text, { style: styles2.warning, testID: `${testIDPrefix}-capability-warning`, children: t(ACCESS_ADMIN_I18N.capabilityWarning) }),
365
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, testID: `${testIDPrefix}-expiry-preview`, children: t(ACCESS_ADMIN_I18N.expiryPreview, expiryDate) }),
366
+ state.minted !== null ? /* @__PURE__ */ jsxs(View, { style: styles2.revealBox, testID: `${testIDPrefix}-minted`, children: [
367
+ /* @__PURE__ */ jsx(Text, { style: styles2.revealTitle, children: t(ACCESS_ADMIN_I18N.mintedTitle, subject.displayName) }),
368
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, children: t(ACCESS_ADMIN_I18N.mintedHint) }),
369
+ /* @__PURE__ */ jsx(Text, { selectable: true, style: styles2.url, testID: `${testIDPrefix}-minted-url`, children: mintedUrl }),
370
+ /* @__PURE__ */ jsxs(View, { style: styles2.actions, children: [
358
371
  /* @__PURE__ */ jsx(
359
372
  Button,
360
373
  {
@@ -388,13 +401,13 @@ var AccessLinkPanel = ({
388
401
  }
389
402
  )
390
403
  ] }),
391
- /* @__PURE__ */ jsx(Text, { style: styles.note, children: t(ACCESS_ADMIN_I18N.emailNote) })
404
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, children: t(ACCESS_ADMIN_I18N.emailNote) })
392
405
  ] }) : null,
393
- linksFailed ? /* @__PURE__ */ jsx(Text, { style: styles.warning, testID: `${testIDPrefix}-load-error`, children: t(ACCESS_ADMIN_I18N.liveLoadError) }) : null,
394
- !linksFailed && liveLinks.length > 0 ? /* @__PURE__ */ jsxs(View, { style: styles.liveBox, children: [
395
- /* @__PURE__ */ jsx(Text, { style: styles.subheading, children: t(ACCESS_ADMIN_I18N.liveHeading, String(liveLinks.length)) }),
396
- liveLinks.map((link) => /* @__PURE__ */ jsxs(View, { style: styles.liveRow, children: [
397
- /* @__PURE__ */ jsx(Text, { style: styles.liveMeta, children: t(
406
+ linksFailed ? /* @__PURE__ */ jsx(Text, { style: styles2.warning, testID: `${testIDPrefix}-load-error`, children: t(ACCESS_ADMIN_I18N.liveLoadError) }) : null,
407
+ !linksFailed && liveLinks.length > 0 ? /* @__PURE__ */ jsxs(View, { style: styles2.liveBox, children: [
408
+ /* @__PURE__ */ jsx(Text, { style: styles2.subheading, children: t(ACCESS_ADMIN_I18N.liveHeading, String(liveLinks.length)) }),
409
+ liveLinks.map((link) => /* @__PURE__ */ jsxs(View, { style: styles2.liveRow, children: [
410
+ /* @__PURE__ */ jsx(Text, { style: styles2.liveMeta, children: t(
398
411
  ACCESS_ADMIN_I18N.liveMeta,
399
412
  formatLinkDate(link.createdAt, ""),
400
413
  formatLinkDate(link.expiresAt, t(ACCESS_ADMIN_I18N.expiresNever))
@@ -402,8 +415,8 @@ var AccessLinkPanel = ({
402
415
  renderLinkActions === void 0 ? null : renderLinkActions(link)
403
416
  ] }, link.id))
404
417
  ] }) : null,
405
- !linksFailed && liveLinks.length === 0 ? /* @__PURE__ */ jsx(Text, { style: styles.note, testID: `${testIDPrefix}-no-live`, children: t(ACCESS_ADMIN_I18N.noLive) }) : null,
406
- /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
418
+ !linksFailed && liveLinks.length === 0 ? /* @__PURE__ */ jsx(Text, { style: styles2.note, testID: `${testIDPrefix}-no-live`, children: t(ACCESS_ADMIN_I18N.noLive) }) : null,
419
+ /* @__PURE__ */ jsxs(View, { style: styles2.actions, children: [
407
420
  /* @__PURE__ */ jsx(
408
421
  Button,
409
422
  {
@@ -549,7 +562,7 @@ var AccountLinkPanel = ({
549
562
  }) => {
550
563
  const { t } = useUi();
551
564
  const palette = useAccessAdminPalette();
552
- const styles = useMemo(() => buildStyles2(palette), [palette]);
565
+ const styles2 = useMemo(() => buildStyles2(palette), [palette]);
553
566
  const state = useSubjectAccountLink({
554
567
  subject,
555
568
  boundAccountId,
@@ -564,9 +577,9 @@ var AccountLinkPanel = ({
564
577
  [accounts, bindings, subject, unnamedLabel]
565
578
  );
566
579
  const selectedLabel = options.find((option) => option.value === state.selectedAccountId)?.label ?? "";
567
- return /* @__PURE__ */ jsxs(View, { style: styles.panel, testID: testIDPrefix, children: [
568
- /* @__PURE__ */ jsx(Text, { style: styles.heading, children: t(ACCESS_ADMIN_I18N.accountPanelHeading, subject.displayName) }),
569
- /* @__PURE__ */ jsx(Text, { style: styles.note, children: t(ACCESS_ADMIN_I18N.accountPanelNote) }),
580
+ return /* @__PURE__ */ jsxs(View, { style: styles2.panel, testID: testIDPrefix, children: [
581
+ /* @__PURE__ */ jsx(Text, { style: styles2.heading, children: t(ACCESS_ADMIN_I18N.accountPanelHeading, subject.displayName) }),
582
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, children: t(ACCESS_ADMIN_I18N.accountPanelNote) }),
570
583
  options.length > 0 ? /* @__PURE__ */ jsx(
571
584
  SelectControl,
572
585
  {
@@ -579,8 +592,8 @@ var AccountLinkPanel = ({
579
592
  testID: `${testIDPrefix}-picker`,
580
593
  value: state.selectedAccountId ?? ""
581
594
  }
582
- ) : /* @__PURE__ */ jsx(Text, { style: styles.note, testID: `${testIDPrefix}-no-accounts`, children: t(ACCESS_ADMIN_I18N.accountNone) }),
583
- /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
595
+ ) : /* @__PURE__ */ jsx(Text, { style: styles2.note, testID: `${testIDPrefix}-no-accounts`, children: t(ACCESS_ADMIN_I18N.accountNone) }),
596
+ /* @__PURE__ */ jsxs(View, { style: styles2.actions, children: [
584
597
  /* @__PURE__ */ jsx(
585
598
  Button,
586
599
  {
@@ -673,6 +686,18 @@ function roleOptions(assignableRoles) {
673
686
  function tenantValueToId(value) {
674
687
  return value === NO_TENANT_VALUE ? null : value;
675
688
  }
689
+ function isBlocking(wasActive, active) {
690
+ return wasActive && !active;
691
+ }
692
+ function isBlockReasonMissing(wasActive, active, blockedReason) {
693
+ return isBlocking(wasActive, active) && blockedReason.trim() === "";
694
+ }
695
+ function buildEditPatch(input) {
696
+ const patch = { displayName: input.displayName.trim(), role: input.role, active: input.active };
697
+ if (isBlocking(input.wasActive, input.active)) patch.blockedReason = input.blockedReason.trim();
698
+ if (input.tenantId !== void 0) patch.tenantId = input.tenantId;
699
+ return patch;
700
+ }
676
701
  var ACTIONS_GAP = 8;
677
702
  function buildUserColumns({
678
703
  t,
@@ -728,7 +753,7 @@ function buildUserColumns({
728
753
  variant: "secondary"
729
754
  }
730
755
  ),
731
- /* @__PURE__ */ jsx(
756
+ onReset !== void 0 ? /* @__PURE__ */ jsx(
732
757
  Button,
733
758
  {
734
759
  accessibilityHint: t(ACCESS_ADMIN_I18N.usersResetHint),
@@ -739,7 +764,7 @@ function buildUserColumns({
739
764
  testID: `${testIDPrefix}-reset-${user.id}`,
740
765
  variant: "secondary"
741
766
  }
742
- )
767
+ ) : null
743
768
  ] })
744
769
  }
745
770
  ];
@@ -911,6 +936,7 @@ var EditUserModal = ({
911
936
  const [role, setRole] = useState("");
912
937
  const [tenantValue, setTenantValue] = useState(NO_TENANT_VALUE);
913
938
  const [active, setActive] = useState(true);
939
+ const [blockedReason, setBlockedReason] = useState("");
914
940
  const [submitting, setSubmitting] = useState(false);
915
941
  const userId = user?.id;
916
942
  useEffect(() => {
@@ -919,28 +945,36 @@ var EditUserModal = ({
919
945
  setRole(user.roles[0] ?? "");
920
946
  setTenantValue(user.tenantId ?? NO_TENANT_VALUE);
921
947
  setActive(user.active);
948
+ setBlockedReason(user.blockedReason ?? "");
922
949
  setSubmitting(false);
923
950
  }, [visible, userId, user]);
924
- const canSubmit = !submitting && displayName.trim() !== "" && role !== "";
951
+ const wasActive = user?.active ?? true;
952
+ const canSubmit = !submitting && displayName.trim() !== "" && role !== "" && !isBlockReasonMissing(wasActive, active, blockedReason);
925
953
  const handleSubmit = async () => {
926
954
  if (user === null) return;
927
955
  setSubmitting(true);
928
- const patch = {
929
- displayName: displayName.trim(),
956
+ const patch = buildEditPatch({
957
+ displayName,
930
958
  role,
931
- active
932
- };
933
- if (isPlatformAdmin) patch.tenantId = tenantValueToId(tenantValue);
959
+ wasActive,
960
+ active,
961
+ blockedReason,
962
+ tenantId: isPlatformAdmin ? tenantValueToId(tenantValue) : void 0
963
+ });
934
964
  const ok = await onSubmit(user.id, patch);
935
965
  setSubmitting(false);
936
966
  if (ok) onCancel();
937
967
  };
968
+ const submitIfReady = () => {
969
+ if (canSubmit) void handleSubmit();
970
+ };
938
971
  return /* @__PURE__ */ jsx(ModalShell, { onCancel, title: t(ACCESS_ADMIN_I18N.usersEditTitle), visible, children: /* @__PURE__ */ jsxs(View, { style: modalFormStyles.body, testID: testIDPrefix, children: [
939
972
  /* @__PURE__ */ jsx(
940
973
  FormField,
941
974
  {
942
975
  label: t(ACCESS_ADMIN_I18N.usersFieldDisplayName),
943
976
  onChangeText: setDisplayName,
977
+ onSubmitEditing: submitIfReady,
944
978
  placeholder: t(ACCESS_ADMIN_I18N.usersDisplayNamePlaceholder),
945
979
  required: true,
946
980
  testID: `${testIDPrefix}-display-name`,
@@ -981,6 +1015,19 @@ var EditUserModal = ({
981
1015
  value: active
982
1016
  }
983
1017
  ),
1018
+ active ? null : /* @__PURE__ */ jsx(
1019
+ FormField,
1020
+ {
1021
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersBlockedReasonHint),
1022
+ label: t(ACCESS_ADMIN_I18N.usersFieldBlockedReason),
1023
+ onChangeText: setBlockedReason,
1024
+ onSubmitEditing: submitIfReady,
1025
+ placeholder: t(ACCESS_ADMIN_I18N.usersBlockedReasonPlaceholder),
1026
+ required: wasActive,
1027
+ testID: `${testIDPrefix}-blocked-reason`,
1028
+ value: blockedReason
1029
+ }
1030
+ ),
984
1031
  /* @__PURE__ */ jsxs(View, { style: modalFormStyles.actions, children: [
985
1032
  /* @__PURE__ */ jsx(
986
1033
  Button,
@@ -1073,6 +1120,8 @@ var ResetPasswordModal = ({
1073
1120
  ] })
1074
1121
  ] }) });
1075
1122
  };
1123
+ var MISSING_CREATE = "createUser is not provided";
1124
+ var MISSING_RESET = "resetPassword is not provided";
1076
1125
  function useUserManagement(api, t) {
1077
1126
  const [users, setUsers] = useState([]);
1078
1127
  const [loading, setLoading] = useState(true);
@@ -1114,7 +1163,7 @@ function useUserManagement(api, t) {
1114
1163
  );
1115
1164
  const create = useCallback(
1116
1165
  (input) => runWrite(
1117
- () => api.createUser(input),
1166
+ () => api.createUser ? api.createUser(input) : Promise.reject(new Error(MISSING_CREATE)),
1118
1167
  ACCESS_ADMIN_I18N.usersCreateSuccess,
1119
1168
  ACCESS_ADMIN_I18N.usersCreateFailed
1120
1169
  ),
@@ -1130,7 +1179,7 @@ function useUserManagement(api, t) {
1130
1179
  );
1131
1180
  const resetPassword = useCallback(
1132
1181
  (id, password) => runWrite(
1133
- () => api.resetPassword(id, password),
1182
+ () => api.resetPassword ? api.resetPassword(id, password) : Promise.reject(new Error(MISSING_RESET)),
1134
1183
  ACCESS_ADMIN_I18N.usersResetSuccess,
1135
1184
  ACCESS_ADMIN_I18N.usersResetFailed
1136
1185
  ),
@@ -1162,7 +1211,7 @@ var UserManagementPanel = ({
1162
1211
  testID = ACCESS_ADMIN_TEST_IDS.usersPanel
1163
1212
  }) => {
1164
1213
  const palette = useAccessAdminPalette();
1165
- const styles = useMemo(() => buildStyles3(palette), [palette]);
1214
+ const styles2 = useMemo(() => buildStyles3(palette), [palette]);
1166
1215
  const controller = useUserManagement(api, t);
1167
1216
  const [query, setQuery] = useState("");
1168
1217
  const [openModal, setOpenModal] = useState("none" /* None */);
@@ -1180,18 +1229,26 @@ var UserManagementPanel = ({
1180
1229
  setOpenModal("reset" /* Reset */);
1181
1230
  };
1182
1231
  const closeModal = () => setOpenModal("none" /* None */);
1232
+ const canCreate = api.createUser !== void 0;
1233
+ const canReset = api.resetPassword !== void 0;
1183
1234
  const columns = useMemo(
1184
- () => buildUserColumns({ t, tenants, testIDPrefix: testID, onEdit: openEdit, onReset: openReset }),
1185
- [t, tenants, testID]
1235
+ () => buildUserColumns({
1236
+ t,
1237
+ tenants,
1238
+ testIDPrefix: testID,
1239
+ onEdit: openEdit,
1240
+ onReset: canReset ? openReset : void 0
1241
+ }),
1242
+ [t, tenants, testID, canReset]
1186
1243
  );
1187
- return /* @__PURE__ */ jsxs(View, { style: styles.panel, testID, children: [
1244
+ return /* @__PURE__ */ jsxs(View, { style: styles2.panel, testID, children: [
1188
1245
  /* @__PURE__ */ jsx(ToastHost, { subscribe: controller.toastSubscribe, toastTestID: `${testID}-toast` }),
1189
- /* @__PURE__ */ jsx(Text, { style: styles.heading, children: t(ACCESS_ADMIN_I18N.usersHeading) }),
1190
- /* @__PURE__ */ jsx(Text, { style: styles.note, children: t(ACCESS_ADMIN_I18N.usersNote) }),
1246
+ /* @__PURE__ */ jsx(Text, { style: styles2.heading, children: t(ACCESS_ADMIN_I18N.usersHeading) }),
1247
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, children: t(ACCESS_ADMIN_I18N.usersNote) }),
1191
1248
  /* @__PURE__ */ jsx(
1192
1249
  FilterBar,
1193
1250
  {
1194
- actions: /* @__PURE__ */ jsx(
1251
+ actions: canCreate ? /* @__PURE__ */ jsx(
1195
1252
  Button,
1196
1253
  {
1197
1254
  accessibilityHint: t(ACCESS_ADMIN_I18N.usersCreateHint),
@@ -1200,11 +1257,11 @@ var UserManagementPanel = ({
1200
1257
  onPress: () => setOpenModal("create" /* Create */),
1201
1258
  testID: `${testID}-create`
1202
1259
  }
1203
- ),
1260
+ ) : void 0,
1204
1261
  resultsCount: visibleUsers.length,
1205
1262
  resultsLabel: t(ACCESS_ADMIN_I18N.usersResultsLabel),
1206
1263
  testID: `${testID}-filter-bar`,
1207
- children: /* @__PURE__ */ jsx(View, { style: styles.search, children: /* @__PURE__ */ jsx(
1264
+ children: /* @__PURE__ */ jsx(View, { style: styles2.search, children: /* @__PURE__ */ jsx(
1208
1265
  FormField,
1209
1266
  {
1210
1267
  accessibilityLabel: t(ACCESS_ADMIN_I18N.usersSearchLabel),
@@ -1219,8 +1276,8 @@ var UserManagementPanel = ({
1219
1276
  ) })
1220
1277
  }
1221
1278
  ),
1222
- controller.loadFailed ? /* @__PURE__ */ jsxs(View, { style: styles.errorBox, testID: `${testID}-load-error`, children: [
1223
- /* @__PURE__ */ jsx(Text, { style: styles.warning, children: t(ACCESS_ADMIN_I18N.usersLoadError) }),
1279
+ controller.loadFailed ? /* @__PURE__ */ jsxs(View, { style: styles2.errorBox, testID: `${testID}-load-error`, children: [
1280
+ /* @__PURE__ */ jsx(Text, { style: styles2.warning, children: t(ACCESS_ADMIN_I18N.usersLoadError) }),
1224
1281
  /* @__PURE__ */ jsx(
1225
1282
  Button,
1226
1283
  {
@@ -1245,7 +1302,7 @@ var UserManagementPanel = ({
1245
1302
  zebra: true
1246
1303
  }
1247
1304
  ),
1248
- /* @__PURE__ */ jsx(
1305
+ canCreate ? /* @__PURE__ */ jsx(
1249
1306
  CreateUserModal,
1250
1307
  {
1251
1308
  assignableRoles,
@@ -1257,7 +1314,7 @@ var UserManagementPanel = ({
1257
1314
  testIDPrefix: `${testID}-create-modal`,
1258
1315
  visible: openModal === "create" /* Create */
1259
1316
  }
1260
- ),
1317
+ ) : null,
1261
1318
  /* @__PURE__ */ jsx(
1262
1319
  EditUserModal,
1263
1320
  {
@@ -1272,7 +1329,7 @@ var UserManagementPanel = ({
1272
1329
  visible: openModal === "edit" /* Edit */
1273
1330
  }
1274
1331
  ),
1275
- /* @__PURE__ */ jsx(
1332
+ canReset ? /* @__PURE__ */ jsx(
1276
1333
  ResetPasswordModal,
1277
1334
  {
1278
1335
  onCancel: closeModal,
@@ -1282,7 +1339,7 @@ var UserManagementPanel = ({
1282
1339
  user: activeUser,
1283
1340
  visible: openModal === "reset" /* Reset */
1284
1341
  }
1285
- )
1342
+ ) : null
1286
1343
  ] });
1287
1344
  };
1288
1345
  var buildStyles3 = (c) => StyleSheet.create({
@@ -1302,6 +1359,116 @@ var buildStyles3 = (c) => StyleSheet.create({
1302
1359
  warning: { color: c.danger, fontSize: FONT_SM3, fontWeight: "600" }
1303
1360
  });
1304
1361
 
1305
- export { ACCESS_ADMIN_I18N, ACCESS_ADMIN_TEST_IDS, AccessLinkPanel, AccessResultTone, AccountLinkPanel, UserManagementPanel, accountDisplayLabel, activeLinksForSubject, buildAccountOptions, buildCapabilityLinkMailto, capabilityExpiryDate, capabilityExpiryDays, daysUntilAnchor, formatLinkDate, linksForSubject, useAccessAdminPalette, useCapabilityLinkMint, useSubjectAccountLink };
1362
+ // src/PermissionMatrix/permissionMatrixModel.ts
1363
+ function buildPermissionMatrix(roles, permissions, grants) {
1364
+ return roles.map((role) => {
1365
+ const held = new Set(grants[role.id] ?? []);
1366
+ const cells = permissions.map((permission) => ({ permission, granted: held.has(permission.id) }));
1367
+ return { role, cells, grantedCount: cells.filter((cell) => cell.granted).length };
1368
+ });
1369
+ }
1370
+ function grantStatusLabel(granted, t) {
1371
+ return granted ? t(ACCESS_ADMIN_I18N.permissionsGranted) : t(ACCESS_ADMIN_I18N.permissionsNotGranted);
1372
+ }
1373
+ function cellAccessibilityLabel(role, permission, granted, t) {
1374
+ return t(ACCESS_ADMIN_I18N.permissionsCellLabel, role.label, permission.label, grantStatusLabel(granted, t));
1375
+ }
1376
+ var SPACE_XS4 = 4;
1377
+ var SPACE_SM5 = 8;
1378
+ var SPACE_MD5 = 12;
1379
+ var RADIUS = 8;
1380
+ var MAX_WIDTH = 1200;
1381
+ var LABEL_COLUMN_WIDTH = 280;
1382
+ var ROLE_COLUMN_WIDTH = 140;
1383
+ var HEADING_SIZE = 18;
1384
+ var BODY_SIZE = 14;
1385
+ var LINE_HEIGHT = 20;
1386
+ var styles = StyleSheet.create({
1387
+ root: { alignSelf: "center", gap: SPACE_MD5, maxWidth: MAX_WIDTH, width: "100%" },
1388
+ heading: { fontSize: HEADING_SIZE, fontWeight: "600", lineHeight: LINE_HEIGHT + SPACE_XS4 },
1389
+ body: { fontSize: BODY_SIZE, lineHeight: LINE_HEIGHT },
1390
+ strong: { fontSize: BODY_SIZE, fontWeight: "600", lineHeight: LINE_HEIGHT },
1391
+ list: { gap: SPACE_MD5 },
1392
+ card: { borderRadius: RADIUS, borderWidth: StyleSheet.hairlineWidth, gap: SPACE_SM5, padding: SPACE_MD5 },
1393
+ stackRow: { flexDirection: "row", flexWrap: "wrap", gap: SPACE_SM5, justifyContent: "space-between" },
1394
+ stackLabel: { flexBasis: 0, flexGrow: 1, minWidth: 0 },
1395
+ tableRow: { borderBottomWidth: StyleSheet.hairlineWidth, flexDirection: "row" },
1396
+ labelCell: { padding: SPACE_SM5, width: LABEL_COLUMN_WIDTH },
1397
+ roleCell: { padding: SPACE_SM5, width: ROLE_COLUMN_WIDTH }
1398
+ });
1399
+ var statusColor = (granted, palette) => granted ? palette.textPrimary : palette.textSecondary;
1400
+ var StackedMatrix = ({ columns, palette, t, testID }) => /* @__PURE__ */ jsx(View, { style: styles.list, children: columns.map(({ role, cells, grantedCount }) => /* @__PURE__ */ jsxs(
1401
+ View,
1402
+ {
1403
+ style: [styles.card, { backgroundColor: palette.panelSurface, borderColor: palette.border }],
1404
+ testID: `${testID}-role-${role.id}`,
1405
+ children: [
1406
+ /* @__PURE__ */ jsx(Text, { style: [styles.strong, { color: palette.textPrimary }], children: role.label }),
1407
+ /* @__PURE__ */ jsx(Text, { style: [styles.body, { color: palette.textSecondary }], children: t(ACCESS_ADMIN_I18N.permissionsRoleCount, String(grantedCount), String(cells.length)) }),
1408
+ cells.map(({ permission, granted }) => /* @__PURE__ */ jsxs(
1409
+ View,
1410
+ {
1411
+ accessibilityHint: t(ACCESS_ADMIN_I18N.permissionsReadOnlyHint),
1412
+ accessibilityLabel: cellAccessibilityLabel(role, permission, granted, t),
1413
+ accessible: true,
1414
+ style: styles.stackRow,
1415
+ testID: `${testID}-cell-${role.id}-${permission.id}`,
1416
+ children: [
1417
+ /* @__PURE__ */ jsx(Text, { style: [styles.body, styles.stackLabel, { color: palette.textPrimary }], children: permission.label }),
1418
+ /* @__PURE__ */ jsx(Text, { style: [granted ? styles.strong : styles.body, { color: statusColor(granted, palette) }], children: grantStatusLabel(granted, t) })
1419
+ ]
1420
+ },
1421
+ permission.id
1422
+ ))
1423
+ ]
1424
+ },
1425
+ role.id
1426
+ )) });
1427
+ var TableMatrix = ({ columns, permissions, palette, t, testID }) => /* @__PURE__ */ jsx(ScrollView, { horizontal: true, testID: `${testID}-table`, children: /* @__PURE__ */ jsxs(View, { style: [styles.card, { backgroundColor: palette.panelSurface, borderColor: palette.border }], children: [
1428
+ /* @__PURE__ */ jsxs(View, { style: [styles.tableRow, { borderColor: palette.border }], children: [
1429
+ /* @__PURE__ */ jsx(Text, { style: [styles.strong, styles.labelCell, { color: palette.textPrimary }], children: t(ACCESS_ADMIN_I18N.permissionsColPermission) }),
1430
+ columns.map(({ role }) => /* @__PURE__ */ jsx(Text, { style: [styles.strong, styles.roleCell, { color: palette.textPrimary }], children: role.label }, role.id))
1431
+ ] }),
1432
+ permissions.map((permission, index) => /* @__PURE__ */ jsxs(View, { style: [styles.tableRow, { borderColor: palette.border }], children: [
1433
+ /* @__PURE__ */ jsx(Text, { style: [styles.body, styles.labelCell, { color: palette.textPrimary }], children: permission.label }),
1434
+ columns.map(({ role, cells }) => {
1435
+ const granted = cells[index]?.granted === true;
1436
+ return /* @__PURE__ */ jsx(
1437
+ Text,
1438
+ {
1439
+ accessibilityHint: t(ACCESS_ADMIN_I18N.permissionsReadOnlyHint),
1440
+ accessibilityLabel: cellAccessibilityLabel(role, permission, granted, t),
1441
+ style: [granted ? styles.strong : styles.body, styles.roleCell, { color: statusColor(granted, palette) }],
1442
+ testID: `${testID}-cell-${role.id}-${permission.id}`,
1443
+ children: grantStatusLabel(granted, t)
1444
+ },
1445
+ role.id
1446
+ );
1447
+ })
1448
+ ] }, permission.id))
1449
+ ] }) });
1450
+ var PermissionMatrix = ({
1451
+ roles,
1452
+ permissions,
1453
+ grants,
1454
+ t,
1455
+ testID = ACCESS_ADMIN_TEST_IDS.permissionMatrix
1456
+ }) => {
1457
+ const palette = useAccessAdminPalette();
1458
+ const { width } = useWindowDimensions();
1459
+ const columns = useMemo(() => buildPermissionMatrix(roles, permissions, grants), [roles, permissions, grants]);
1460
+ const isWide = width >= LAYOUT_COLLAPSE_BREAKPOINT;
1461
+ const isEmpty = roles.length === 0 || permissions.length === 0;
1462
+ const partProps = { columns, permissions, palette, t, testID };
1463
+ return /* @__PURE__ */ jsxs(View, { style: styles.root, testID, children: [
1464
+ /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: [styles.heading, { color: palette.textPrimary }], children: t(ACCESS_ADMIN_I18N.permissionsHeading) }),
1465
+ /* @__PURE__ */ jsx(Text, { style: [styles.body, { color: palette.textSecondary }], children: t(ACCESS_ADMIN_I18N.permissionsNote) }),
1466
+ isEmpty ? /* @__PURE__ */ jsx(Text, { style: [styles.body, { color: palette.textSecondary }], testID: `${testID}-empty`, children: t(ACCESS_ADMIN_I18N.permissionsEmpty) }) : null,
1467
+ !isEmpty && isWide ? /* @__PURE__ */ jsx(TableMatrix, { ...partProps }) : null,
1468
+ !isEmpty && !isWide ? /* @__PURE__ */ jsx(StackedMatrix, { ...partProps }) : null
1469
+ ] });
1470
+ };
1471
+
1472
+ export { ACCESS_ADMIN_I18N, ACCESS_ADMIN_TEST_IDS, AccessLinkPanel, AccessResultTone, AccountLinkPanel, PermissionMatrix, UserManagementPanel, accountDisplayLabel, activeLinksForSubject, buildAccountOptions, buildCapabilityLinkMailto, buildPermissionMatrix, capabilityExpiryDate, capabilityExpiryDays, daysUntilAnchor, formatLinkDate, linksForSubject, useAccessAdminPalette, useCapabilityLinkMint, useSubjectAccountLink };
1306
1473
  //# sourceMappingURL=index.mjs.map
1307
1474
  //# sourceMappingURL=index.mjs.map