@dloizides/ui-access-admin 1.0.1 → 1.2.2

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,10 +1,11 @@
1
1
  import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
2
- import { View, Text, StyleSheet, Linking } from 'react-native';
2
+ import { StyleSheet, View, Text, Linking } from 'react-native';
3
3
  import { Button } from '@dloizides/ui-buttons';
4
- import { useUi } from '@dloizides/ui-feedback';
5
- import { copyText } from '@dloizides/ui-layout';
4
+ import { useUi, ToastHost, createToastBus } from '@dloizides/ui-feedback';
5
+ import { copyText, ModalShell, ModalDropdown, DropdownVariant } from '@dloizides/ui-layout';
6
6
  import { jsxs, jsx } from 'react/jsx-runtime';
7
- import { SelectControl } from '@dloizides/ui-forms';
7
+ import { SelectControl, FormField, FormSwitch, Field } from '@dloizides/ui-forms';
8
+ import { FilterBar, DataTable } from '@dloizides/ui-tables';
8
9
 
9
10
  // src/AccessLinkPanel/AccessLinkPanel.tsx
10
11
 
@@ -86,11 +87,92 @@ var ACCESS_ADMIN_I18N = {
86
87
  /** `{{p1}}` = subject display name. */
87
88
  accountUnlinkSuccess: "accessAdmin.account.unlinkSuccess",
88
89
  accountClose: "accessAdmin.account.close",
89
- accountCloseHint: "accessAdmin.account.closeHint"
90
+ accountCloseHint: "accessAdmin.account.closeHint",
91
+ // ── UserManagementPanel: framing + toolbar ──────────────────────────────────
92
+ /** Panel heading. */
93
+ usersHeading: "accessAdmin.users.heading",
94
+ /** Supporting line under the heading. */
95
+ usersNote: "accessAdmin.users.note",
96
+ /** Placeholder in the search box. */
97
+ usersSearchPlaceholder: "accessAdmin.users.searchPlaceholder",
98
+ /** Accessible name of the search box. */
99
+ usersSearchLabel: "accessAdmin.users.searchLabel",
100
+ usersSearchHint: "accessAdmin.users.searchHint",
101
+ /** Word shown next to the live result count in the FilterBar. */
102
+ usersResultsLabel: "accessAdmin.users.resultsLabel",
103
+ usersCreate: "accessAdmin.users.create",
104
+ usersCreateHint: "accessAdmin.users.createHint",
105
+ // ── UserManagementPanel: table columns ──────────────────────────────────────
106
+ usersColEmail: "accessAdmin.users.colEmail",
107
+ usersColDisplayName: "accessAdmin.users.colDisplayName",
108
+ usersColRoles: "accessAdmin.users.colRoles",
109
+ usersColTenant: "accessAdmin.users.colTenant",
110
+ usersColActive: "accessAdmin.users.colActive",
111
+ usersColCreated: "accessAdmin.users.colCreated",
112
+ usersColActions: "accessAdmin.users.colActions",
113
+ // ── UserManagementPanel: cell values ────────────────────────────────────────
114
+ usersActiveYes: "accessAdmin.users.activeYes",
115
+ usersActiveNo: "accessAdmin.users.activeNo",
116
+ /** Tenant cell for a user bound to no tenant (a platform-level account). */
117
+ usersTenantNone: "accessAdmin.users.tenantNone",
118
+ /** Roles cell for a user with no roles yet. */
119
+ usersNoRoles: "accessAdmin.users.noRoles",
120
+ /** Created cell when the API did not return a timestamp. */
121
+ usersCreatedUnknown: "accessAdmin.users.createdUnknown",
122
+ // ── UserManagementPanel: per-row actions ────────────────────────────────────
123
+ usersEdit: "accessAdmin.users.edit",
124
+ usersEditHint: "accessAdmin.users.editHint",
125
+ usersReset: "accessAdmin.users.reset",
126
+ usersResetHint: "accessAdmin.users.resetHint",
127
+ // ── UserManagementPanel: load states ────────────────────────────────────────
128
+ usersLoading: "accessAdmin.users.loading",
129
+ usersEmpty: "accessAdmin.users.empty",
130
+ /** Shown when the user list FAILED to load — never conflated with "there are none". */
131
+ usersLoadError: "accessAdmin.users.loadError",
132
+ usersRetry: "accessAdmin.users.retry",
133
+ usersRetryHint: "accessAdmin.users.retryHint",
134
+ // ── UserManagementPanel: modals — titles ────────────────────────────────────
135
+ usersCreateTitle: "accessAdmin.users.createTitle",
136
+ usersEditTitle: "accessAdmin.users.editTitle",
137
+ usersResetTitle: "accessAdmin.users.resetTitle",
138
+ // ── UserManagementPanel: modals — fields (shared across modals) ──────────────
139
+ usersFieldEmail: "accessAdmin.users.fieldEmail",
140
+ usersEmailPlaceholder: "accessAdmin.users.emailPlaceholder",
141
+ usersFieldPassword: "accessAdmin.users.fieldPassword",
142
+ usersPasswordPlaceholder: "accessAdmin.users.passwordPlaceholder",
143
+ usersFieldDisplayName: "accessAdmin.users.fieldDisplayName",
144
+ usersDisplayNamePlaceholder: "accessAdmin.users.displayNamePlaceholder",
145
+ usersFieldRole: "accessAdmin.users.fieldRole",
146
+ usersRolePlaceholder: "accessAdmin.users.rolePlaceholder",
147
+ usersRolePickerHint: "accessAdmin.users.rolePickerHint",
148
+ usersFieldTenant: "accessAdmin.users.fieldTenant",
149
+ usersTenantPlaceholder: "accessAdmin.users.tenantPlaceholder",
150
+ usersTenantPickerHint: "accessAdmin.users.tenantPickerHint",
151
+ usersFieldActive: "accessAdmin.users.fieldActive",
152
+ usersActiveToggleHint: "accessAdmin.users.activeToggleHint",
153
+ usersFieldNewPassword: "accessAdmin.users.fieldNewPassword",
154
+ usersNewPasswordPlaceholder: "accessAdmin.users.newPasswordPlaceholder",
155
+ // ── UserManagementPanel: modals — actions ───────────────────────────────────
156
+ usersSubmitCreate: "accessAdmin.users.submitCreate",
157
+ usersSubmitCreateHint: "accessAdmin.users.submitCreateHint",
158
+ usersSubmitSave: "accessAdmin.users.submitSave",
159
+ usersSubmitSaveHint: "accessAdmin.users.submitSaveHint",
160
+ usersSubmitReset: "accessAdmin.users.submitReset",
161
+ usersSubmitResetHint: "accessAdmin.users.submitResetHint",
162
+ usersCancel: "accessAdmin.users.cancel",
163
+ usersCancelHint: "accessAdmin.users.cancelHint",
164
+ // ── UserManagementPanel: outcome toasts ─────────────────────────────────────
165
+ usersCreateSuccess: "accessAdmin.users.createSuccess",
166
+ usersCreateFailed: "accessAdmin.users.createFailed",
167
+ usersUpdateSuccess: "accessAdmin.users.updateSuccess",
168
+ usersUpdateFailed: "accessAdmin.users.updateFailed",
169
+ usersResetSuccess: "accessAdmin.users.resetSuccess",
170
+ usersResetFailed: "accessAdmin.users.resetFailed"
90
171
  };
91
172
  var ACCESS_ADMIN_TEST_IDS = {
92
173
  linkPanel: "access-link-panel",
93
- accountPanel: "account-link-panel"
174
+ accountPanel: "account-link-panel",
175
+ usersPanel: "user-management-panel"
94
176
  };
95
177
 
96
178
  // src/expiry/expiryPolicy.ts
@@ -553,6 +635,673 @@ var buildStyles2 = (c) => StyleSheet.create({
553
635
  }
554
636
  });
555
637
 
556
- export { ACCESS_ADMIN_I18N, ACCESS_ADMIN_TEST_IDS, AccessLinkPanel, AccessResultTone, AccountLinkPanel, accountDisplayLabel, activeLinksForSubject, buildAccountOptions, buildCapabilityLinkMailto, capabilityExpiryDate, capabilityExpiryDays, daysUntilAnchor, formatLinkDate, linksForSubject, useAccessAdminPalette, useCapabilityLinkMint, useSubjectAccountLink };
638
+ // src/UserManagementPanel/userManagementFormat.ts
639
+ var ROLE_SEPARATOR = ", ";
640
+ var NO_TENANT_VALUE = "";
641
+ function rolesLabel(roles, t) {
642
+ return roles.length > 0 ? roles.join(ROLE_SEPARATOR) : t(ACCESS_ADMIN_I18N.usersNoRoles);
643
+ }
644
+ function tenantLabel(tenantId, tenants, t) {
645
+ if (tenantId === null) return t(ACCESS_ADMIN_I18N.usersTenantNone);
646
+ const match = tenants.find((tenant) => tenant.id === tenantId);
647
+ return match !== void 0 ? match.label : tenantId;
648
+ }
649
+ function activeLabel(active, t) {
650
+ return active ? t(ACCESS_ADMIN_I18N.usersActiveYes) : t(ACCESS_ADMIN_I18N.usersActiveNo);
651
+ }
652
+ function createdLabel(createdAt, t) {
653
+ if (createdAt === void 0 || createdAt === "") return t(ACCESS_ADMIN_I18N.usersCreatedUnknown);
654
+ const parsed = new Date(createdAt);
655
+ if (Number.isNaN(parsed.getTime())) return t(ACCESS_ADMIN_I18N.usersCreatedUnknown);
656
+ return parsed.toISOString().slice(0, "YYYY-MM-DD".length);
657
+ }
658
+ function filterUsers(users, query) {
659
+ const needle = query.trim().toLowerCase();
660
+ if (needle === "") return users;
661
+ return users.filter((user) => {
662
+ const haystack = `${user.email} ${user.displayName} ${user.roles.join(" ")}`.toLowerCase();
663
+ return haystack.includes(needle);
664
+ });
665
+ }
666
+ function tenantOptions(tenants, t) {
667
+ const platform = { label: t(ACCESS_ADMIN_I18N.usersTenantNone), value: NO_TENANT_VALUE };
668
+ return [platform, ...tenants.map((tenant) => ({ label: tenant.label, value: tenant.id }))];
669
+ }
670
+ function roleOptions(assignableRoles) {
671
+ return assignableRoles.map((role) => ({ label: role, value: role }));
672
+ }
673
+ function tenantValueToId(value) {
674
+ return value === NO_TENANT_VALUE ? null : value;
675
+ }
676
+ var ACTIONS_GAP = 8;
677
+ function buildUserColumns({
678
+ t,
679
+ tenants,
680
+ testIDPrefix,
681
+ onEdit,
682
+ onReset
683
+ }) {
684
+ return [
685
+ {
686
+ key: "email",
687
+ header: t(ACCESS_ADMIN_I18N.usersColEmail),
688
+ render: (user) => user.email
689
+ },
690
+ {
691
+ key: "displayName",
692
+ header: t(ACCESS_ADMIN_I18N.usersColDisplayName),
693
+ render: (user) => user.displayName
694
+ },
695
+ {
696
+ key: "roles",
697
+ header: t(ACCESS_ADMIN_I18N.usersColRoles),
698
+ render: (user) => rolesLabel(user.roles, t)
699
+ },
700
+ {
701
+ key: "tenant",
702
+ header: t(ACCESS_ADMIN_I18N.usersColTenant),
703
+ render: (user) => tenantLabel(user.tenantId, tenants, t)
704
+ },
705
+ {
706
+ key: "active",
707
+ header: t(ACCESS_ADMIN_I18N.usersColActive),
708
+ render: (user) => activeLabel(user.active, t)
709
+ },
710
+ {
711
+ key: "created",
712
+ header: t(ACCESS_ADMIN_I18N.usersColCreated),
713
+ render: (user) => createdLabel(user.createdAt, t)
714
+ },
715
+ {
716
+ key: "actions",
717
+ header: t(ACCESS_ADMIN_I18N.usersColActions),
718
+ render: (user) => /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", flexWrap: "wrap", gap: ACTIONS_GAP }, children: [
719
+ /* @__PURE__ */ jsx(
720
+ Button,
721
+ {
722
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersEditHint),
723
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersEdit),
724
+ label: t(ACCESS_ADMIN_I18N.usersEdit),
725
+ onPress: () => onEdit(user),
726
+ size: "sm",
727
+ testID: `${testIDPrefix}-edit-${user.id}`,
728
+ variant: "secondary"
729
+ }
730
+ ),
731
+ /* @__PURE__ */ jsx(
732
+ Button,
733
+ {
734
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersResetHint),
735
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersReset),
736
+ label: t(ACCESS_ADMIN_I18N.usersReset),
737
+ onPress: () => onReset(user),
738
+ size: "sm",
739
+ testID: `${testIDPrefix}-reset-${user.id}`,
740
+ variant: "secondary"
741
+ }
742
+ )
743
+ ] })
744
+ }
745
+ ];
746
+ }
747
+ var PickerField = ({
748
+ testID,
749
+ label,
750
+ hint,
751
+ placeholder,
752
+ value,
753
+ options,
754
+ onChange
755
+ }) => {
756
+ const selected = options.find((option) => option.value === value);
757
+ const selectionLabel = selected !== void 0 ? selected.label : placeholder;
758
+ return /* @__PURE__ */ jsx(Field, { label, children: /* @__PURE__ */ jsx(
759
+ ModalDropdown,
760
+ {
761
+ accessibilityHint: hint,
762
+ accessibilityLabel: `${label}: ${selectionLabel}`,
763
+ onChange,
764
+ options,
765
+ testID,
766
+ value,
767
+ variant: DropdownVariant.Modal
768
+ }
769
+ ) });
770
+ };
771
+ var SPACE_SM3 = 8;
772
+ var SPACE_MD3 = 12;
773
+ var modalFormStyles = StyleSheet.create({
774
+ actions: { flexDirection: "row", flexWrap: "wrap", gap: SPACE_SM3, marginTop: SPACE_SM3 },
775
+ body: { gap: SPACE_MD3, padding: SPACE_MD3 }
776
+ });
777
+ var CreateUserModal = ({
778
+ visible,
779
+ isPlatformAdmin,
780
+ assignableRoles,
781
+ tenants,
782
+ t,
783
+ testIDPrefix,
784
+ onSubmit,
785
+ onCancel
786
+ }) => {
787
+ const [email, setEmail] = useState("");
788
+ const [password, setPassword] = useState("");
789
+ const [displayName, setDisplayName] = useState("");
790
+ const [role, setRole] = useState("");
791
+ const [tenantValue, setTenantValue] = useState(NO_TENANT_VALUE);
792
+ const [submitting, setSubmitting] = useState(false);
793
+ useEffect(() => {
794
+ if (!visible) return;
795
+ setEmail("");
796
+ setPassword("");
797
+ setDisplayName("");
798
+ setRole("");
799
+ setTenantValue(NO_TENANT_VALUE);
800
+ setSubmitting(false);
801
+ }, [visible]);
802
+ const canSubmit = !submitting && email.trim() !== "" && password !== "" && displayName.trim() !== "" && role !== "";
803
+ const handleSubmit = async () => {
804
+ setSubmitting(true);
805
+ const tenantId = isPlatformAdmin ? tenantValueToId(tenantValue) : null;
806
+ const ok = await onSubmit({ email: email.trim(), password, displayName: displayName.trim(), role, tenantId });
807
+ setSubmitting(false);
808
+ if (ok) onCancel();
809
+ };
810
+ return /* @__PURE__ */ jsx(ModalShell, { onCancel, title: t(ACCESS_ADMIN_I18N.usersCreateTitle), visible, children: /* @__PURE__ */ jsxs(View, { style: modalFormStyles.body, testID: testIDPrefix, children: [
811
+ /* @__PURE__ */ jsx(
812
+ FormField,
813
+ {
814
+ autoCapitalize: "none",
815
+ keyboardType: "email-address",
816
+ label: t(ACCESS_ADMIN_I18N.usersFieldEmail),
817
+ onChangeText: setEmail,
818
+ placeholder: t(ACCESS_ADMIN_I18N.usersEmailPlaceholder),
819
+ required: true,
820
+ testID: `${testIDPrefix}-email`,
821
+ value: email
822
+ }
823
+ ),
824
+ /* @__PURE__ */ jsx(
825
+ FormField,
826
+ {
827
+ label: t(ACCESS_ADMIN_I18N.usersFieldPassword),
828
+ onChangeText: setPassword,
829
+ placeholder: t(ACCESS_ADMIN_I18N.usersPasswordPlaceholder),
830
+ required: true,
831
+ secureTextEntry: true,
832
+ testID: `${testIDPrefix}-password`,
833
+ value: password
834
+ }
835
+ ),
836
+ /* @__PURE__ */ jsx(
837
+ FormField,
838
+ {
839
+ label: t(ACCESS_ADMIN_I18N.usersFieldDisplayName),
840
+ onChangeText: setDisplayName,
841
+ placeholder: t(ACCESS_ADMIN_I18N.usersDisplayNamePlaceholder),
842
+ required: true,
843
+ testID: `${testIDPrefix}-display-name`,
844
+ value: displayName
845
+ }
846
+ ),
847
+ /* @__PURE__ */ jsx(
848
+ PickerField,
849
+ {
850
+ hint: t(ACCESS_ADMIN_I18N.usersRolePickerHint),
851
+ label: t(ACCESS_ADMIN_I18N.usersFieldRole),
852
+ onChange: setRole,
853
+ options: roleOptions(assignableRoles),
854
+ placeholder: t(ACCESS_ADMIN_I18N.usersRolePlaceholder),
855
+ testID: `${testIDPrefix}-role`,
856
+ value: role
857
+ }
858
+ ),
859
+ isPlatformAdmin ? /* @__PURE__ */ jsx(
860
+ PickerField,
861
+ {
862
+ hint: t(ACCESS_ADMIN_I18N.usersTenantPickerHint),
863
+ label: t(ACCESS_ADMIN_I18N.usersFieldTenant),
864
+ onChange: setTenantValue,
865
+ options: tenantOptions(tenants, t),
866
+ placeholder: t(ACCESS_ADMIN_I18N.usersTenantPlaceholder),
867
+ testID: `${testIDPrefix}-tenant`,
868
+ value: tenantValue
869
+ }
870
+ ) : null,
871
+ /* @__PURE__ */ jsxs(View, { style: modalFormStyles.actions, children: [
872
+ /* @__PURE__ */ jsx(
873
+ Button,
874
+ {
875
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersSubmitCreateHint),
876
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersSubmitCreate),
877
+ disabled: !canSubmit,
878
+ label: t(ACCESS_ADMIN_I18N.usersSubmitCreate),
879
+ loading: submitting,
880
+ onPress: handleSubmit,
881
+ testID: `${testIDPrefix}-submit`
882
+ }
883
+ ),
884
+ /* @__PURE__ */ jsx(
885
+ Button,
886
+ {
887
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersCancelHint),
888
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersCancel),
889
+ disabled: submitting,
890
+ label: t(ACCESS_ADMIN_I18N.usersCancel),
891
+ onPress: onCancel,
892
+ testID: `${testIDPrefix}-cancel`,
893
+ variant: "secondary"
894
+ }
895
+ )
896
+ ] })
897
+ ] }) });
898
+ };
899
+ var EditUserModal = ({
900
+ visible,
901
+ user,
902
+ isPlatformAdmin,
903
+ assignableRoles,
904
+ tenants,
905
+ t,
906
+ testIDPrefix,
907
+ onSubmit,
908
+ onCancel
909
+ }) => {
910
+ const [displayName, setDisplayName] = useState("");
911
+ const [role, setRole] = useState("");
912
+ const [tenantValue, setTenantValue] = useState(NO_TENANT_VALUE);
913
+ const [active, setActive] = useState(true);
914
+ const [submitting, setSubmitting] = useState(false);
915
+ const userId = user?.id;
916
+ useEffect(() => {
917
+ if (!visible || user === null) return;
918
+ setDisplayName(user.displayName);
919
+ setRole(user.roles[0] ?? "");
920
+ setTenantValue(user.tenantId ?? NO_TENANT_VALUE);
921
+ setActive(user.active);
922
+ setSubmitting(false);
923
+ }, [visible, userId, user]);
924
+ const canSubmit = !submitting && displayName.trim() !== "" && role !== "";
925
+ const handleSubmit = async () => {
926
+ if (user === null) return;
927
+ setSubmitting(true);
928
+ const patch = {
929
+ displayName: displayName.trim(),
930
+ role,
931
+ active
932
+ };
933
+ if (isPlatformAdmin) patch.tenantId = tenantValueToId(tenantValue);
934
+ const ok = await onSubmit(user.id, patch);
935
+ setSubmitting(false);
936
+ if (ok) onCancel();
937
+ };
938
+ return /* @__PURE__ */ jsx(ModalShell, { onCancel, title: t(ACCESS_ADMIN_I18N.usersEditTitle), visible, children: /* @__PURE__ */ jsxs(View, { style: modalFormStyles.body, testID: testIDPrefix, children: [
939
+ /* @__PURE__ */ jsx(
940
+ FormField,
941
+ {
942
+ label: t(ACCESS_ADMIN_I18N.usersFieldDisplayName),
943
+ onChangeText: setDisplayName,
944
+ placeholder: t(ACCESS_ADMIN_I18N.usersDisplayNamePlaceholder),
945
+ required: true,
946
+ testID: `${testIDPrefix}-display-name`,
947
+ value: displayName
948
+ }
949
+ ),
950
+ /* @__PURE__ */ jsx(
951
+ PickerField,
952
+ {
953
+ hint: t(ACCESS_ADMIN_I18N.usersRolePickerHint),
954
+ label: t(ACCESS_ADMIN_I18N.usersFieldRole),
955
+ onChange: setRole,
956
+ options: roleOptions(assignableRoles),
957
+ placeholder: t(ACCESS_ADMIN_I18N.usersRolePlaceholder),
958
+ testID: `${testIDPrefix}-role`,
959
+ value: role
960
+ }
961
+ ),
962
+ isPlatformAdmin ? /* @__PURE__ */ jsx(
963
+ PickerField,
964
+ {
965
+ hint: t(ACCESS_ADMIN_I18N.usersTenantPickerHint),
966
+ label: t(ACCESS_ADMIN_I18N.usersFieldTenant),
967
+ onChange: setTenantValue,
968
+ options: tenantOptions(tenants, t),
969
+ placeholder: t(ACCESS_ADMIN_I18N.usersTenantPlaceholder),
970
+ testID: `${testIDPrefix}-tenant`,
971
+ value: tenantValue
972
+ }
973
+ ) : null,
974
+ /* @__PURE__ */ jsx(
975
+ FormSwitch,
976
+ {
977
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersActiveToggleHint),
978
+ label: t(ACCESS_ADMIN_I18N.usersFieldActive),
979
+ onValueChange: setActive,
980
+ testID: `${testIDPrefix}-active`,
981
+ value: active
982
+ }
983
+ ),
984
+ /* @__PURE__ */ jsxs(View, { style: modalFormStyles.actions, children: [
985
+ /* @__PURE__ */ jsx(
986
+ Button,
987
+ {
988
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersSubmitSaveHint),
989
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersSubmitSave),
990
+ disabled: !canSubmit,
991
+ label: t(ACCESS_ADMIN_I18N.usersSubmitSave),
992
+ loading: submitting,
993
+ onPress: handleSubmit,
994
+ testID: `${testIDPrefix}-submit`
995
+ }
996
+ ),
997
+ /* @__PURE__ */ jsx(
998
+ Button,
999
+ {
1000
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersCancelHint),
1001
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersCancel),
1002
+ disabled: submitting,
1003
+ label: t(ACCESS_ADMIN_I18N.usersCancel),
1004
+ onPress: onCancel,
1005
+ testID: `${testIDPrefix}-cancel`,
1006
+ variant: "secondary"
1007
+ }
1008
+ )
1009
+ ] })
1010
+ ] }) });
1011
+ };
1012
+ var ResetPasswordModal = ({
1013
+ visible,
1014
+ user,
1015
+ t,
1016
+ testIDPrefix,
1017
+ onSubmit,
1018
+ onCancel
1019
+ }) => {
1020
+ const [password, setPassword] = useState("");
1021
+ const [submitting, setSubmitting] = useState(false);
1022
+ useEffect(() => {
1023
+ if (!visible) return;
1024
+ setPassword("");
1025
+ setSubmitting(false);
1026
+ }, [visible]);
1027
+ const canSubmit = !submitting && password !== "";
1028
+ const handleSubmit = async () => {
1029
+ if (user === null) return;
1030
+ setSubmitting(true);
1031
+ const ok = await onSubmit(user.id, password);
1032
+ setSubmitting(false);
1033
+ if (ok) onCancel();
1034
+ };
1035
+ return /* @__PURE__ */ jsx(ModalShell, { onCancel, title: t(ACCESS_ADMIN_I18N.usersResetTitle), visible, children: /* @__PURE__ */ jsxs(View, { style: modalFormStyles.body, testID: testIDPrefix, children: [
1036
+ /* @__PURE__ */ jsx(
1037
+ FormField,
1038
+ {
1039
+ label: t(ACCESS_ADMIN_I18N.usersFieldNewPassword),
1040
+ onChangeText: setPassword,
1041
+ placeholder: t(ACCESS_ADMIN_I18N.usersNewPasswordPlaceholder),
1042
+ required: true,
1043
+ secureTextEntry: true,
1044
+ testID: `${testIDPrefix}-password`,
1045
+ value: password
1046
+ }
1047
+ ),
1048
+ /* @__PURE__ */ jsxs(View, { style: modalFormStyles.actions, children: [
1049
+ /* @__PURE__ */ jsx(
1050
+ Button,
1051
+ {
1052
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersSubmitResetHint),
1053
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersSubmitReset),
1054
+ disabled: !canSubmit,
1055
+ label: t(ACCESS_ADMIN_I18N.usersSubmitReset),
1056
+ loading: submitting,
1057
+ onPress: handleSubmit,
1058
+ testID: `${testIDPrefix}-submit`
1059
+ }
1060
+ ),
1061
+ /* @__PURE__ */ jsx(
1062
+ Button,
1063
+ {
1064
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersCancelHint),
1065
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersCancel),
1066
+ disabled: submitting,
1067
+ label: t(ACCESS_ADMIN_I18N.usersCancel),
1068
+ onPress: onCancel,
1069
+ testID: `${testIDPrefix}-cancel`,
1070
+ variant: "secondary"
1071
+ }
1072
+ )
1073
+ ] })
1074
+ ] }) });
1075
+ };
1076
+ function useUserManagement(api, t) {
1077
+ const [users, setUsers] = useState([]);
1078
+ const [loading, setLoading] = useState(true);
1079
+ const [loadFailed, setLoadFailed] = useState(false);
1080
+ const bus = useMemo(() => createToastBus(), []);
1081
+ const mountedRef = useRef(true);
1082
+ useEffect(() => {
1083
+ return () => {
1084
+ mountedRef.current = false;
1085
+ };
1086
+ }, []);
1087
+ const reload = useCallback(() => {
1088
+ setLoading(true);
1089
+ setLoadFailed(false);
1090
+ void api.listUsers().then((next) => {
1091
+ if (!mountedRef.current) return;
1092
+ setUsers(next);
1093
+ setLoading(false);
1094
+ }).catch(() => {
1095
+ if (!mountedRef.current) return;
1096
+ setLoadFailed(true);
1097
+ setLoading(false);
1098
+ });
1099
+ }, [api]);
1100
+ useEffect(() => reload(), [reload]);
1101
+ const runWrite = useCallback(
1102
+ async (write, successKey, failureKey) => {
1103
+ try {
1104
+ await write();
1105
+ bus.success(t(successKey));
1106
+ reload();
1107
+ return true;
1108
+ } catch {
1109
+ bus.error(t(failureKey));
1110
+ return false;
1111
+ }
1112
+ },
1113
+ [bus, reload, t]
1114
+ );
1115
+ const create = useCallback(
1116
+ (input) => runWrite(
1117
+ () => api.createUser(input),
1118
+ ACCESS_ADMIN_I18N.usersCreateSuccess,
1119
+ ACCESS_ADMIN_I18N.usersCreateFailed
1120
+ ),
1121
+ [api, runWrite]
1122
+ );
1123
+ const update = useCallback(
1124
+ (id, patch) => runWrite(
1125
+ () => api.updateUser(id, patch),
1126
+ ACCESS_ADMIN_I18N.usersUpdateSuccess,
1127
+ ACCESS_ADMIN_I18N.usersUpdateFailed
1128
+ ),
1129
+ [api, runWrite]
1130
+ );
1131
+ const resetPassword = useCallback(
1132
+ (id, password) => runWrite(
1133
+ () => api.resetPassword(id, password),
1134
+ ACCESS_ADMIN_I18N.usersResetSuccess,
1135
+ ACCESS_ADMIN_I18N.usersResetFailed
1136
+ ),
1137
+ [api, runWrite]
1138
+ );
1139
+ return {
1140
+ users,
1141
+ loading,
1142
+ loadFailed,
1143
+ reload,
1144
+ create,
1145
+ update,
1146
+ resetPassword,
1147
+ toastSubscribe: bus.subscribe
1148
+ };
1149
+ }
1150
+ var SPACE_XS3 = 4;
1151
+ var SPACE_SM4 = 8;
1152
+ var SPACE_MD4 = 12;
1153
+ var FONT_SM3 = 12;
1154
+ var FONT_MD3 = 13;
1155
+ var SEARCH_MIN_WIDTH = 220;
1156
+ var UserManagementPanel = ({
1157
+ api,
1158
+ isPlatformAdmin,
1159
+ assignableRoles,
1160
+ tenants = [],
1161
+ t,
1162
+ testID = ACCESS_ADMIN_TEST_IDS.usersPanel
1163
+ }) => {
1164
+ const palette = useAccessAdminPalette();
1165
+ const styles = useMemo(() => buildStyles3(palette), [palette]);
1166
+ const controller = useUserManagement(api, t);
1167
+ const [query, setQuery] = useState("");
1168
+ const [openModal, setOpenModal] = useState("none" /* None */);
1169
+ const [activeUser, setActiveUser] = useState(null);
1170
+ const visibleUsers = useMemo(
1171
+ () => filterUsers(controller.users, query),
1172
+ [controller.users, query]
1173
+ );
1174
+ const openEdit = (user) => {
1175
+ setActiveUser(user);
1176
+ setOpenModal("edit" /* Edit */);
1177
+ };
1178
+ const openReset = (user) => {
1179
+ setActiveUser(user);
1180
+ setOpenModal("reset" /* Reset */);
1181
+ };
1182
+ const closeModal = () => setOpenModal("none" /* None */);
1183
+ const columns = useMemo(
1184
+ () => buildUserColumns({ t, tenants, testIDPrefix: testID, onEdit: openEdit, onReset: openReset }),
1185
+ [t, tenants, testID]
1186
+ );
1187
+ return /* @__PURE__ */ jsxs(View, { style: styles.panel, testID, children: [
1188
+ /* @__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) }),
1191
+ /* @__PURE__ */ jsx(
1192
+ FilterBar,
1193
+ {
1194
+ actions: /* @__PURE__ */ jsx(
1195
+ Button,
1196
+ {
1197
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersCreateHint),
1198
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersCreate),
1199
+ label: t(ACCESS_ADMIN_I18N.usersCreate),
1200
+ onPress: () => setOpenModal("create" /* Create */),
1201
+ testID: `${testID}-create`
1202
+ }
1203
+ ),
1204
+ resultsCount: visibleUsers.length,
1205
+ resultsLabel: t(ACCESS_ADMIN_I18N.usersResultsLabel),
1206
+ testID: `${testID}-filter-bar`,
1207
+ children: /* @__PURE__ */ jsx(View, { style: styles.search, children: /* @__PURE__ */ jsx(
1208
+ FormField,
1209
+ {
1210
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersSearchLabel),
1211
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersSearchHint),
1212
+ autoCapitalize: "none",
1213
+ label: t(ACCESS_ADMIN_I18N.usersSearchLabel),
1214
+ onChangeText: setQuery,
1215
+ placeholder: t(ACCESS_ADMIN_I18N.usersSearchPlaceholder),
1216
+ testID: `${testID}-search`,
1217
+ value: query
1218
+ }
1219
+ ) })
1220
+ }
1221
+ ),
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) }),
1224
+ /* @__PURE__ */ jsx(
1225
+ Button,
1226
+ {
1227
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersRetryHint),
1228
+ accessibilityLabel: t(ACCESS_ADMIN_I18N.usersRetry),
1229
+ label: t(ACCESS_ADMIN_I18N.usersRetry),
1230
+ onPress: controller.reload,
1231
+ testID: `${testID}-retry`,
1232
+ variant: "secondary"
1233
+ }
1234
+ )
1235
+ ] }) : /* @__PURE__ */ jsx(
1236
+ DataTable,
1237
+ {
1238
+ columns,
1239
+ emptyLabel: t(ACCESS_ADMIN_I18N.usersEmpty),
1240
+ keyExtractor: (user) => user.id,
1241
+ loading: controller.loading,
1242
+ loadingLabel: t(ACCESS_ADMIN_I18N.usersLoading),
1243
+ rows: visibleUsers,
1244
+ testID: `${testID}-table`,
1245
+ zebra: true
1246
+ }
1247
+ ),
1248
+ /* @__PURE__ */ jsx(
1249
+ CreateUserModal,
1250
+ {
1251
+ assignableRoles,
1252
+ isPlatformAdmin,
1253
+ onCancel: closeModal,
1254
+ onSubmit: controller.create,
1255
+ t,
1256
+ tenants,
1257
+ testIDPrefix: `${testID}-create-modal`,
1258
+ visible: openModal === "create" /* Create */
1259
+ }
1260
+ ),
1261
+ /* @__PURE__ */ jsx(
1262
+ EditUserModal,
1263
+ {
1264
+ assignableRoles,
1265
+ isPlatformAdmin,
1266
+ onCancel: closeModal,
1267
+ onSubmit: controller.update,
1268
+ t,
1269
+ tenants,
1270
+ testIDPrefix: `${testID}-edit-modal`,
1271
+ user: activeUser,
1272
+ visible: openModal === "edit" /* Edit */
1273
+ }
1274
+ ),
1275
+ /* @__PURE__ */ jsx(
1276
+ ResetPasswordModal,
1277
+ {
1278
+ onCancel: closeModal,
1279
+ onSubmit: controller.resetPassword,
1280
+ t,
1281
+ testIDPrefix: `${testID}-reset-modal`,
1282
+ user: activeUser,
1283
+ visible: openModal === "reset" /* Reset */
1284
+ }
1285
+ )
1286
+ ] });
1287
+ };
1288
+ var buildStyles3 = (c) => StyleSheet.create({
1289
+ errorBox: {
1290
+ backgroundColor: c.panelSurface,
1291
+ borderColor: c.border,
1292
+ borderRadius: SPACE_SM4,
1293
+ borderWidth: 1,
1294
+ gap: SPACE_SM4,
1295
+ marginTop: SPACE_SM4,
1296
+ padding: SPACE_MD4
1297
+ },
1298
+ heading: { color: c.textPrimary, fontSize: FONT_MD3, fontWeight: "700" },
1299
+ note: { color: c.textSecondary, fontSize: FONT_SM3 },
1300
+ panel: { gap: SPACE_XS3 },
1301
+ search: { minWidth: SEARCH_MIN_WIDTH },
1302
+ warning: { color: c.danger, fontSize: FONT_SM3, fontWeight: "600" }
1303
+ });
1304
+
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 };
557
1306
  //# sourceMappingURL=index.mjs.map
558
1307
  //# sourceMappingURL=index.mjs.map