@firecms/core 3.0.0-canary.279 → 3.0.0-canary.280

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.
Files changed (46) hide show
  1. package/dist/components/UserDisplay.d.ts +7 -0
  2. package/dist/components/VirtualTable/fields/VirtualTableUserSelect.d.ts +12 -0
  3. package/dist/contexts/InternalUserManagementContext.d.ts +3 -0
  4. package/dist/core/FireCMS.d.ts +0 -1
  5. package/dist/core/field_configs.d.ts +1 -1
  6. package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +12 -0
  7. package/dist/hooks/index.d.ts +1 -0
  8. package/dist/hooks/useInternalUserManagementController.d.ts +12 -0
  9. package/dist/index.es.js +474 -84
  10. package/dist/index.es.js.map +1 -1
  11. package/dist/index.umd.js +473 -83
  12. package/dist/index.umd.js.map +1 -1
  13. package/dist/preview/components/UserPreview.d.ts +8 -0
  14. package/dist/preview/index.d.ts +1 -0
  15. package/dist/types/firecms.d.ts +15 -0
  16. package/dist/types/firecms_context.d.ts +16 -0
  17. package/dist/types/index.d.ts +1 -0
  18. package/dist/types/internal_user_management.d.ts +20 -0
  19. package/dist/types/plugins.d.ts +2 -0
  20. package/dist/types/properties.d.ts +9 -0
  21. package/dist/types/property_config.d.ts +1 -1
  22. package/dist/types/user.d.ts +1 -1
  23. package/package.json +5 -5
  24. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +12 -0
  25. package/src/components/UserDisplay.tsx +54 -0
  26. package/src/components/VirtualTable/fields/VirtualTableUserSelect.tsx +99 -0
  27. package/src/contexts/InternalUserManagementContext.tsx +4 -0
  28. package/src/core/FireCMS.tsx +22 -13
  29. package/src/core/field_configs.tsx +15 -1
  30. package/src/form/field_bindings/UserSelectFieldBinding.tsx +94 -0
  31. package/src/hooks/index.tsx +2 -0
  32. package/src/hooks/useFireCMSContext.tsx +6 -2
  33. package/src/hooks/useInternalUserManagementController.tsx +16 -0
  34. package/src/preview/PropertyPreview.tsx +8 -0
  35. package/src/preview/components/UserPreview.tsx +22 -0
  36. package/src/preview/index.ts +1 -0
  37. package/src/types/firecms.tsx +16 -0
  38. package/src/types/firecms_context.tsx +17 -0
  39. package/src/types/index.ts +1 -0
  40. package/src/types/internal_user_management.ts +24 -0
  41. package/src/types/plugins.tsx +3 -0
  42. package/src/types/properties.ts +10 -0
  43. package/src/types/property_config.tsx +1 -0
  44. package/src/types/user.ts +1 -1
  45. package/src/util/entities.ts +1 -1
  46. package/src/util/entity_cache.ts +2 -2
@@ -0,0 +1,8 @@
1
+ import { PropertyPreviewProps } from "../PropertyPreviewProps";
2
+ /**
3
+ * Preview component for displaying user information.
4
+ * This is a simple wrapper around UserDisplay.
5
+ *
6
+ * @group Preview components
7
+ */
8
+ export declare function UserPreview({ value }: PropertyPreviewProps<string>): import("react/jsx-runtime").JSX.Element | null;
@@ -21,3 +21,4 @@ export * from "./components/EnumValuesChip";
21
21
  export * from "./components/EmptyValue";
22
22
  export * from "./components/ImagePreview";
23
23
  export * from "./components/ReferencePreview";
24
+ export * from "./components/UserPreview";
@@ -13,6 +13,7 @@ import { UserConfigurationPersistence } from "./local_config_persistence";
13
13
  import { FireCMSPlugin } from "./plugins";
14
14
  import { CMSAnalyticsEvent } from "./analytics";
15
15
  import { EntityAction } from "./entity_actions";
16
+ import { InternalUserManagement } from "./internal_user_management";
16
17
  /**
17
18
  * Use this callback to build entity collections dynamically.
18
19
  * You can use the user to decide which collections to show.
@@ -123,6 +124,7 @@ export type FireCMSProps<USER extends User> = {
123
124
  * Use plugins to modify the behaviour of the CMS.
124
125
  * DEPRECATED: use the `plugins` prop in the `useBuildNavigationController` instead.
125
126
  * This prop will work as a fallback for the `plugins` prop in the `useBuildNavigationController`.
127
+ * @deprecated
126
128
  */
127
129
  plugins?: FireCMSPlugin<any, any, any>[];
128
130
  /**
@@ -134,6 +136,19 @@ export type FireCMSProps<USER extends User> = {
134
136
  * The function must return a URL that gets opened when the button is clicked
135
137
  */
136
138
  entityLinkBuilder?: EntityLinkBuilder;
139
+ /**
140
+ * You can use this props to provide your own user management implementation.
141
+ * Note that this will not affect the UI, but it will be used to show user information
142
+ * in various places of the CMS, for example, to show who created or modified an entity,
143
+ * or to assign ownership of an entity.
144
+ *
145
+ * You can also use this data to be retrieved in your custom properties,
146
+ * for example, to show a list of users in a dropdown.
147
+ *
148
+ * If you are using the FireCMS user management plugin, this
149
+ * prop will be implemented automatically.
150
+ */
151
+ userManagement?: InternalUserManagement;
137
152
  components?: {
138
153
  /**
139
154
  * Component to render when a reference is missing
@@ -10,6 +10,7 @@ import { SideDialogsController } from "./side_dialogs_controller";
10
10
  import { DialogsController } from "./dialogs_controller";
11
11
  import { CustomizationController } from "./customization_controller";
12
12
  import { AnalyticsController } from "./analytics_controller";
13
+ import { InternalUserManagement } from "./internal_user_management";
13
14
  /**
14
15
  * Context that includes the internal controllers and contexts used by the app.
15
16
  * Some controllers and context included in this context can be accessed
@@ -67,4 +68,19 @@ export type FireCMSContext<USER extends User = User, AuthControllerType extends
67
68
  * Callback to send analytics events
68
69
  */
69
70
  analyticsController?: AnalyticsController;
71
+ /**
72
+ * This section is used to manage users in the CMS.
73
+ * It is used to show user information in various places of the CMS,
74
+ * for example, to show who created or modified an entity,
75
+ * or to assign ownership of an entity.
76
+ *
77
+ * In the base CMS, this information is not used for access control.
78
+ * You can pass your own implementation of this section, to populate
79
+ * the dropdown of users when assigning ownership of an entity,
80
+ * or to show more information about the user.
81
+ *
82
+ * If you are using the FireCMS user management plugin, this
83
+ * section will be implemented automatically.
84
+ */
85
+ userManagement: InternalUserManagement<USER>;
70
86
  };
@@ -13,6 +13,7 @@ export * from "./fields";
13
13
  export * from "./property_config";
14
14
  export * from "./datasource";
15
15
  export * from "./entity_link_builder";
16
+ export * from "./internal_user_management";
16
17
  export * from "./side_entity_controller";
17
18
  export * from "./side_dialogs_controller";
18
19
  export * from "./firecms_context";
@@ -0,0 +1,20 @@
1
+ import { User } from "./user";
2
+ export type InternalUserManagement<USER extends User = User> = {
3
+ /**
4
+ * List of users to be managed by the CMS.
5
+ */
6
+ users: USER[];
7
+ /**
8
+ * Function to get a user by its uid. This is used to show
9
+ * user information when assigning ownership of an entity.
10
+ *
11
+ * You can pass your own implementation if you want to show
12
+ * more information about the user.
13
+ *
14
+ * If you are using the FireCMS user management plugin, this
15
+ * function will be implemented automatically.
16
+ *
17
+ * @param uid
18
+ */
19
+ getUser: (uid: string) => USER | null;
20
+ };
@@ -7,6 +7,7 @@ import { CMSType, Property } from "./properties";
7
7
  import { EntityStatus } from "./entities";
8
8
  import { ResolvedProperty } from "./resolved_entities";
9
9
  import { NavigationGroupMapping } from "./navigation";
10
+ import { InternalUserManagement } from "./internal_user_management";
10
11
  /**
11
12
  * Interface used to define plugins for FireCMS.
12
13
  * NOTE: This is a work in progress and the API is not stable yet.
@@ -37,6 +38,7 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
37
38
  }>>;
38
39
  props?: PROPS;
39
40
  };
41
+ userManagement?: InternalUserManagement;
40
42
  homePage?: {
41
43
  /**
42
44
  * Additional actions to be rendered in the home page, close to the search bar.
@@ -291,6 +291,15 @@ export interface StringProperty extends BaseProperty<string> {
291
291
  * indicate that this string refers to a path in your storage provider.
292
292
  */
293
293
  storage?: StorageConfig;
294
+ /**
295
+ * This property is used to indicate that the string is a user ID, and
296
+ * it will be rendered as a user picker.
297
+ * Note that the user ID needs to be the one used in your authentication
298
+ * provider, e.g. Firebase Auth.
299
+ * You can also use a property builder to specify the user path dynamically
300
+ * based on other values of the entity.
301
+ */
302
+ userSelect?: boolean;
294
303
  /**
295
304
  * If the value of this property is a URL, you can set this flag to true
296
305
  * to add a link, or one of the supported media types to render a preview
@@ -37,4 +37,4 @@ export type PropertyConfig<T extends CMSType = any> = {
37
37
  */
38
38
  description?: string;
39
39
  };
40
- export type PropertyConfigId = "text_field" | "multiline" | "markdown" | "url" | "email" | "select" | "multi_select" | "number_input" | "number_select" | "multi_number_select" | "file_upload" | "multi_file_upload" | "group" | "key_value" | "reference" | "reference_as_string" | "multi_references" | "switch" | "date_time" | "repeat" | "custom_array" | "block";
40
+ export type PropertyConfigId = "text_field" | "multiline" | "markdown" | "url" | "email" | "user_select" | "select" | "multi_select" | "number_input" | "number_select" | "multi_number_select" | "file_upload" | "multi_file_upload" | "group" | "key_value" | "reference" | "reference_as_string" | "multi_references" | "switch" | "date_time" | "repeat" | "custom_array" | "block";
@@ -35,7 +35,7 @@ export type User = {
35
35
  */
36
36
  readonly isAnonymous: boolean;
37
37
  /**
38
- *
38
+ * Custom roles assigned to the user.
39
39
  */
40
40
  roles?: Role[];
41
41
  getIdToken?: (forceRefresh?: boolean) => Promise<string>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
3
  "type": "module",
4
- "version": "3.0.0-canary.279",
4
+ "version": "3.0.0-canary.280",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/firecmsco"
@@ -53,9 +53,9 @@
53
53
  "@dnd-kit/core": "^6.3.1",
54
54
  "@dnd-kit/modifiers": "^9.0.0",
55
55
  "@dnd-kit/sortable": "^10.0.0",
56
- "@firecms/editor": "^3.0.0-canary.279",
57
- "@firecms/formex": "^3.0.0-canary.279",
58
- "@firecms/ui": "^3.0.0-canary.279",
56
+ "@firecms/editor": "^3.0.0-canary.280",
57
+ "@firecms/formex": "^3.0.0-canary.280",
58
+ "@firecms/ui": "^3.0.0-canary.280",
59
59
  "@radix-ui/react-portal": "^1.1.9",
60
60
  "clsx": "^2.1.1",
61
61
  "date-fns": "^3.6.0",
@@ -108,7 +108,7 @@
108
108
  "dist",
109
109
  "src"
110
110
  ],
111
- "gitHead": "43a93dc4d64d13ff6b146cb8e42d483e36253df3",
111
+ "gitHead": "4c511808713ad163340dd67cb07f4bbba3d15c56",
112
112
  "publishConfig": {
113
113
  "access": "public"
114
114
  },
@@ -17,6 +17,7 @@ import { VirtualTableSelect } from "../VirtualTable/fields/VirtualTableSelect";
17
17
  import { VirtualTableNumberInput } from "../VirtualTable/fields/VirtualTableNumberInput";
18
18
  import { VirtualTableSwitch } from "../VirtualTable/fields/VirtualTableSwitch";
19
19
  import { VirtualTableDateField } from "../VirtualTable/fields/VirtualTableDateField";
20
+ import { VirtualTableUserSelect } from "../VirtualTable/fields/VirtualTableUserSelect";
20
21
 
21
22
  import { TableStorageUpload } from "./fields/TableStorageUpload";
22
23
  import { TableReferenceField } from "./fields/TableReferenceField";
@@ -332,6 +333,17 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any>>(
332
333
  updateValue={updateValue}
333
334
  />;
334
335
  fullHeight = true;
336
+ } else if (stringProperty.userSelect) {
337
+ innerComponent = <VirtualTableUserSelect name={propertyKey as string}
338
+ multiple={false}
339
+ focused={selected}
340
+ disabled={disabled}
341
+ small={getPreviewSizeFrom(size) !== "medium"}
342
+ error={validationError ?? error}
343
+ internalValue={internalValue as string}
344
+ updateValue={updateValue}
345
+ />;
346
+ fullHeight = true;
335
347
  } else if (stringProperty.markdown || !stringProperty.storage || !stringProperty.reference) {
336
348
  const multiline = Boolean(stringProperty.multiline) || Boolean(stringProperty.markdown);
337
349
  innerComponent = <VirtualTableInput error={validationError ?? error}
@@ -0,0 +1,54 @@
1
+ import { User } from "../types";
2
+ import { AccountCircleIcon, cls, defaultBorderMixin } from "@firecms/ui";
3
+
4
+ /**
5
+ * Component to render a single user with name and email
6
+ */
7
+ export function UserDisplay({
8
+ user,
9
+ }: { user: User | null }) {
10
+ if (!user) {
11
+ return <span className="text-text-secondary dark:text-text-secondary-dark">Select a user</span>;
12
+ }
13
+
14
+ const avatarSizeClass = "w-6 h-6";
15
+
16
+ return (
17
+ <div className={cls(
18
+ "inline-flex items-center gap-4 px-2 py-1 rounded-xl",
19
+ "bg-surface-accent-100 dark:bg-surface-accent-800",
20
+ "border",
21
+ defaultBorderMixin
22
+ )}>
23
+ {user.photoURL ? (
24
+ <img
25
+ src={user.photoURL}
26
+ alt={user.displayName || user.email || "User"}
27
+ className={cls(
28
+ "rounded-full object-cover",
29
+ avatarSizeClass
30
+ )}
31
+ />
32
+ ) : (
33
+ <AccountCircleIcon
34
+ className={cls(
35
+ "text-text-secondary dark:text-text-secondary-dark",
36
+ avatarSizeClass
37
+ )}
38
+ />
39
+ )}
40
+ <div className="flex flex-col min-w-0">
41
+ <span className={cls("font-regular truncate", "text-sm")}>
42
+ {user.displayName || user.email || "-"}
43
+ </span>
44
+ {user.displayName && user.email && (
45
+ <span className={cls("text-text-secondary dark:text-text-secondary-dark truncate",
46
+ "text-xs"
47
+ )}>
48
+ {user.email}
49
+ </span>
50
+ )}
51
+ </div>
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,99 @@
1
+ import React, { useCallback, useEffect } from "react";
2
+ import { MultiSelect, MultiSelectItem, Select, SelectItem } from "@firecms/ui";
3
+ import { useInternalUserManagementController } from "../../../hooks";
4
+ import { UserDisplay } from "../../UserDisplay";
5
+
6
+ export function VirtualTableUserSelect(props: {
7
+ name: string;
8
+ error: Error | undefined;
9
+ multiple: boolean;
10
+ disabled: boolean;
11
+ small: boolean;
12
+ internalValue: string | string[] | undefined;
13
+ updateValue: (newValue: (string | string[] | null)) => void;
14
+ focused: boolean;
15
+ onBlur?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
16
+ }) {
17
+
18
+ const {
19
+ internalValue,
20
+ disabled,
21
+ small,
22
+ focused,
23
+ updateValue,
24
+ multiple
25
+ } = props;
26
+
27
+ const { users, getUser } = useInternalUserManagementController();
28
+
29
+ const validValue = (Array.isArray(internalValue) && multiple) ||
30
+ (!Array.isArray(internalValue) && !multiple);
31
+
32
+ const ref = React.useRef<HTMLButtonElement>(null);
33
+ useEffect(() => {
34
+ if (ref.current && focused) {
35
+ ref.current?.focus({ preventScroll: true });
36
+ }
37
+ }, [focused, ref]);
38
+
39
+ const onChange = useCallback((updatedValue: string | string[]) => {
40
+ if (!updatedValue) {
41
+ updateValue(null);
42
+ } else {
43
+ updateValue(updatedValue);
44
+ }
45
+ }, [updateValue]);
46
+
47
+ const renderValue = (userId: string) => {
48
+ const user = getUser(userId);
49
+ return <UserDisplay user={user} />;
50
+ };
51
+
52
+ return (
53
+ multiple
54
+ ? <MultiSelect
55
+ inputRef={ref}
56
+ className="w-full h-full p-0 bg-transparent"
57
+ position={"item-aligned"}
58
+ disabled={disabled}
59
+ includeClear={false}
60
+ useChips={false}
61
+ value={validValue
62
+ ? (internalValue as string[])
63
+ : ([])}
64
+ onValueChange={onChange}>
65
+ {users?.map((user) => (
66
+ <MultiSelectItem
67
+ key={user.uid}
68
+ value={user.uid}>
69
+ <UserDisplay
70
+ user={user} />
71
+ </MultiSelectItem>
72
+ ))}
73
+ </MultiSelect>
74
+ : <Select
75
+ inputRef={ref}
76
+ size={"large"}
77
+ fullWidth={true}
78
+ className="w-full h-full p-0 bg-transparent"
79
+ position={"item-aligned"}
80
+ disabled={disabled}
81
+ padding={false}
82
+ value={validValue
83
+ ? internalValue as string
84
+ : ""}
85
+ onValueChange={onChange}
86
+ renderValue={renderValue}>
87
+ {users?.map((user) => (
88
+ <SelectItem
89
+ key={user.uid}
90
+ value={user.uid}>
91
+ <UserDisplay
92
+ user={user}/>
93
+ </SelectItem>
94
+ ))}
95
+ </Select>
96
+
97
+ );
98
+ }
99
+
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import { InternalUserManagement, NavigationController } from "../types";
3
+
4
+ export const InternalUserManagementContext = React.createContext<InternalUserManagement<any>>({} as InternalUserManagement);
@@ -20,6 +20,7 @@ import { CustomizationControllerContext } from "../contexts/CustomizationControl
20
20
  import { AnalyticsContext } from "../contexts/AnalyticsContext";
21
21
  import { useProjectLog } from "../hooks/useProjectLog";
22
22
  import { BreadcrumbsProvider } from "../contexts/BreacrumbsContext";
23
+ import { InternalUserManagementContext } from "../contexts/InternalUserManagementContext";
23
24
 
24
25
  /**
25
26
  * If you are using independent components of the CMS
@@ -30,7 +31,6 @@ import { BreadcrumbsProvider } from "../contexts/BreacrumbsContext";
30
31
  *
31
32
  * You only need to use this component if you are building a custom app.
32
33
  *
33
-
34
34
  * @group Core
35
35
  */
36
36
  export function FireCMS<USER extends User>(props: FireCMSProps<USER>) {
@@ -44,21 +44,28 @@ export function FireCMS<USER extends User>(props: FireCMSProps<USER>) {
44
44
  authController,
45
45
  storageSource,
46
46
  dataSourceDelegate,
47
- plugins: pluginsProp,
47
+ plugins: _pluginsProp,
48
48
  onAnalyticsEvent,
49
49
  propertyConfigs,
50
50
  entityViews,
51
51
  entityActions,
52
52
  components,
53
53
  navigationController,
54
- apiKey
54
+ apiKey,
55
+ userManagement: _userManagement
55
56
  } = props;
56
57
 
57
- if (pluginsProp) {
58
+ if (_pluginsProp) {
58
59
  console.warn("The `plugins` prop is deprecated in the FireCMS component. You should pass your plugins to `useBuildNavigationController` instead.");
59
60
  }
60
61
 
61
- const plugins = navigationController.plugins ?? pluginsProp;
62
+ const plugins = navigationController.plugins ?? _pluginsProp;
63
+ const userManagement = plugins?.find(p => p.userManagement)?.userManagement
64
+ ?? _userManagement
65
+ ?? {
66
+ users: [],
67
+ getUser: (uid: string) => null
68
+ };
62
69
 
63
70
  const sideDialogsController = useBuildSideDialogsController();
64
71
  const sideEntityController = useBuildSideEntityController(navigationController, sideDialogsController, authController);
@@ -156,14 +163,16 @@ export function FireCMS<USER extends User>(props: FireCMSProps<USER>) {
156
163
  value={sideEntityController}>
157
164
  <NavigationContext.Provider
158
165
  value={navigationController}>
159
- <DialogsProvider>
160
- <BreadcrumbsProvider>
161
- <FireCMSInternal
162
- loading={loading}>
163
- {children}
164
- </FireCMSInternal>
165
- </BreadcrumbsProvider>
166
- </DialogsProvider>
166
+ <InternalUserManagementContext.Provider value={userManagement}>
167
+ <DialogsProvider>
168
+ <BreadcrumbsProvider>
169
+ <FireCMSInternal
170
+ loading={loading}>
171
+ {children}
172
+ </FireCMSInternal>
173
+ </BreadcrumbsProvider>
174
+ </DialogsProvider>
175
+ </InternalUserManagementContext.Provider>
167
176
  </NavigationContext.Provider>
168
177
  </SideEntityControllerContext.Provider>
169
178
  </SideDialogsControllerContext.Provider>
@@ -32,14 +32,16 @@ import {
32
32
  ListAltIcon,
33
33
  ListIcon,
34
34
  MailIcon,
35
- NumbersIcon,
35
+ NumbersIcon, PersonIcon,
36
36
  RepeatIcon,
37
37
  ScheduleIcon,
38
38
  ShortTextIcon,
39
39
  SubjectIcon,
40
40
  UploadFileIcon,
41
+ VerifiedUserIcon,
41
42
  ViewStreamIcon
42
43
  } from "@firecms/ui";
44
+ import { UserSelectFieldBinding } from "../form/field_bindings/UserSelectFieldBinding";
43
45
 
44
46
  export function isDefaultFieldConfigId(id: string) {
45
47
  return Object.keys(DEFAULT_FIELD_CONFIGS).includes(id);
@@ -143,6 +145,16 @@ export const DEFAULT_FIELD_CONFIGS: Record<string, PropertyConfig<any>> = {
143
145
  Field: MultiSelectFieldBinding
144
146
  }
145
147
  },
148
+ user_select: {
149
+ key: "user_select",
150
+ name: "User select",
151
+ description: "Select a user from the user management system. Store the user ID.",
152
+ Icon: PersonIcon,
153
+ property: {
154
+ dataType: "string",
155
+ Field: UserSelectFieldBinding
156
+ }
157
+ },
146
158
  number_input: {
147
159
  key: "number_input",
148
160
  name: "Number input",
@@ -360,6 +372,8 @@ export function getDefaultFieldId(property: Property | ResolvedProperty) {
360
372
  return "email";
361
373
  } else if (property.enumValues) {
362
374
  return "select";
375
+ } else if (property.userSelect) {
376
+ return "user_select";
363
377
  } else if (property.reference) {
364
378
  return "reference_as_string";
365
379
  } else {
@@ -0,0 +1,94 @@
1
+ import React, { useCallback } from "react";
2
+
3
+ import { FieldProps, User } from "../../types";
4
+ import { FieldHelperText, LabelWithIcon } from "../components";
5
+ import { getIconForProperty } from "../../util";
6
+ import { CloseIcon, cls, IconButton, Select, SelectItem } from "@firecms/ui";
7
+ import { PropertyIdCopyTooltip } from "../../components";
8
+ import { useInternalUserManagementController } from "../../hooks";
9
+ import { UserDisplay } from "../../components/UserDisplay";
10
+
11
+ type UserSelectProps = FieldProps<string>;
12
+
13
+ /**
14
+ * Field binding for selecting a user from the internal user management system.
15
+ * Renders a select dropdown with user information including name and email.
16
+ *
17
+ * This is one of the internal components that get mapped natively inside forms
18
+ * and tables to the specified properties.
19
+ * @group Form fields
20
+ */
21
+ export function UserSelectFieldBinding({
22
+ propertyKey,
23
+ value,
24
+ setValue,
25
+ error,
26
+ showError,
27
+ disabled,
28
+ autoFocus,
29
+ touched,
30
+ property,
31
+ includeDescription,
32
+ size = "large"
33
+ }: UserSelectProps) {
34
+
35
+ const { users, getUser } = useInternalUserManagementController();
36
+
37
+ const handleClearClick = useCallback((e: React.MouseEvent) => {
38
+ e.stopPropagation();
39
+ e.preventDefault();
40
+ setValue(null);
41
+ }, [setValue]);
42
+
43
+ return (
44
+ <>
45
+ <Select
46
+ value={value !== undefined && value != null ? value.toString() : ""}
47
+ disabled={disabled}
48
+ size={size}
49
+ fullWidth={true}
50
+ position="item-aligned"
51
+ inputClassName={cls("w-full")}
52
+ label={
53
+ <PropertyIdCopyTooltip propertyKey={propertyKey}>
54
+ <LabelWithIcon
55
+ icon={getIconForProperty(property, "small")}
56
+ required={property.validation?.required}
57
+ title={property.name}
58
+ className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5 my-0"}
59
+ />
60
+ </PropertyIdCopyTooltip>}
61
+ endAdornment={
62
+ property.clearable && !disabled && value && <IconButton
63
+ size="small"
64
+ onClick={handleClearClick}>
65
+ <CloseIcon size={"small"}/>
66
+ </IconButton>
67
+ }
68
+ onValueChange={(updatedValue: string) => {
69
+ const newValue = updatedValue || null;
70
+ return setValue(newValue);
71
+ }}
72
+ renderValue={(userId: string) => {
73
+ const user = getUser(userId);
74
+ return <UserDisplay user={user} />;
75
+ }}
76
+ >
77
+ {users && users.map((user) => {
78
+ return <SelectItem
79
+ key={user.uid}
80
+ value={user.uid}>
81
+ <UserDisplay user={user} />
82
+ </SelectItem>
83
+ })}
84
+ </Select>
85
+
86
+ <FieldHelperText includeDescription={includeDescription}
87
+ showError={showError}
88
+ error={error}
89
+ disabled={disabled}
90
+ property={property}/>
91
+
92
+ </>
93
+ );
94
+ }
@@ -20,6 +20,8 @@ export * from "./useClipboard";
20
20
  export * from "./useLargeLayout";
21
21
  export * from "./useCollapsedGroups";
22
22
 
23
+ export * from "./useInternalUserManagementController";
24
+
23
25
  export * from "./useReferenceDialog";
24
26
  export * from "./useBrowserTitleAndIcon";
25
27
  export * from "./useCustomizationController";
@@ -11,6 +11,7 @@ import { useDialogsController } from "./useDialogsController";
11
11
  import { useCustomizationController } from "./useCustomizationController";
12
12
  import { useAnalyticsController } from "./useAnalyticsController";
13
13
  import React, { useEffect } from "react";
14
+ import { useInternalUserManagementController } from "./useInternalUserManagementController";
14
15
 
15
16
  /**
16
17
  * Hook to retrieve the {@link FireCMSContext}.
@@ -34,6 +35,7 @@ export const useFireCMSContext = <USER extends User = User, AuthControllerType e
34
35
  const dialogsController = useDialogsController();
35
36
  const customizationController = useCustomizationController();
36
37
  const analyticsController = useAnalyticsController();
38
+ const userManagement = useInternalUserManagementController<USER>();
37
39
 
38
40
  const fireCMSContextRef = React.useRef<FireCMSContext<USER, AuthControllerType>>({
39
41
  authController,
@@ -46,7 +48,8 @@ export const useFireCMSContext = <USER extends User = User, AuthControllerType e
46
48
  userConfigPersistence,
47
49
  dialogsController,
48
50
  customizationController,
49
- analyticsController
51
+ analyticsController,
52
+ userManagement
50
53
  });
51
54
 
52
55
  useEffect(() => {
@@ -61,7 +64,8 @@ export const useFireCMSContext = <USER extends User = User, AuthControllerType e
61
64
  userConfigPersistence,
62
65
  dialogsController,
63
66
  customizationController,
64
- analyticsController
67
+ analyticsController,
68
+ userManagement
65
69
  };
66
70
  }, [authController, dialogsController, navigation, sideDialogsController]);
67
71
 
@@ -0,0 +1,16 @@
1
+ import { useContext } from "react";
2
+ import { InternalUserManagement, NavigationController, User } from "../types";
3
+ import { NavigationContext } from "../contexts/NavigationContext";
4
+ import { InternalUserManagementContext } from "../contexts/InternalUserManagementContext";
5
+
6
+ /**
7
+ * Use this hook to get the internal user management of the app.
8
+ * Note that this is different from the user management plugin controller.
9
+ * This controller will be eventually replaced by the one provided
10
+ * by the user management plugin.
11
+ *
12
+ * Use at your own risk!
13
+ *
14
+ * @group Hooks and utilities
15
+ */
16
+ export const useInternalUserManagementController = <USER extends User = User>(): InternalUserManagement<USER> => useContext(InternalUserManagementContext);