@firecms/core 3.0.0-rc.1 → 3.0.0-rc.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/components/UserDisplay.d.ts +7 -0
- package/dist/components/VirtualTable/fields/VirtualTableUserSelect.d.ts +12 -0
- package/dist/contexts/InternalUserManagementContext.d.ts +3 -0
- package/dist/core/FireCMS.d.ts +0 -1
- package/dist/core/field_configs.d.ts +1 -1
- package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +12 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useCollapsedGroups.d.ts +9 -0
- package/dist/hooks/useInternalUserManagementController.d.ts +12 -0
- package/dist/index.es.js +617 -208
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +615 -206
- package/dist/index.umd.js.map +1 -1
- package/dist/preview/components/UserPreview.d.ts +8 -0
- package/dist/preview/index.d.ts +1 -0
- package/dist/types/collections.d.ts +2 -0
- package/dist/types/entities.d.ts +5 -1
- package/dist/types/firecms.d.ts +15 -0
- package/dist/types/firecms_context.d.ts +16 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/internal_user_management.d.ts +20 -0
- package/dist/types/plugins.d.ts +2 -0
- package/dist/types/properties.d.ts +9 -0
- package/dist/types/property_config.d.ts +1 -1
- package/dist/types/user.d.ts +1 -1
- package/package.json +5 -5
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +12 -0
- package/src/components/ErrorView.tsx +1 -1
- package/src/components/HomePage/DefaultHomePage.tsx +9 -26
- package/src/components/HomePage/HomePageDnD.tsx +3 -45
- package/src/components/HomePage/RenameGroupDialog.tsx +9 -3
- package/src/components/PropertyConfigBadge.tsx +2 -2
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -1
- package/src/components/UserDisplay.tsx +55 -0
- package/src/components/VirtualTable/fields/VirtualTableUserSelect.tsx +99 -0
- package/src/components/common/useColumnsIds.tsx +1 -8
- package/src/contexts/InternalUserManagementContext.tsx +4 -0
- package/src/core/FireCMS.tsx +22 -13
- package/src/core/field_configs.tsx +15 -1
- package/src/form/PropertyFieldBinding.tsx +4 -0
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +94 -0
- package/src/hooks/index.tsx +3 -0
- package/src/hooks/useBrowserTitleAndIcon.tsx +1 -1
- package/src/hooks/useCollapsedGroups.ts +64 -0
- package/src/hooks/useFireCMSContext.tsx +6 -2
- package/src/hooks/useInternalUserManagementController.tsx +16 -0
- package/src/preview/PropertyPreview.tsx +8 -0
- package/src/preview/components/ReferencePreview.tsx +4 -2
- package/src/preview/components/UserPreview.tsx +27 -0
- package/src/preview/index.ts +1 -0
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +1 -1
- package/src/types/collections.ts +2 -0
- package/src/types/entities.ts +7 -1
- package/src/types/firecms.tsx +16 -0
- package/src/types/firecms_context.tsx +17 -0
- package/src/types/index.ts +1 -0
- package/src/types/internal_user_management.ts +24 -0
- package/src/types/plugins.tsx +3 -0
- package/src/types/properties.ts +10 -0
- package/src/types/property_config.tsx +1 -0
- package/src/types/user.ts +1 -1
- package/src/util/entities.ts +1 -1
- package/src/util/entity_cache.ts +2 -2
package/src/core/FireCMS.tsx
CHANGED
|
@@ -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:
|
|
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 (
|
|
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 ??
|
|
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
|
-
<
|
|
160
|
-
<
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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 {
|
|
@@ -93,6 +93,10 @@ function PropertyFieldBindingInternal<T extends CMSType = CMSType, M extends Rec
|
|
|
93
93
|
const authController = useAuthController();
|
|
94
94
|
const customizationController = useCustomizationController();
|
|
95
95
|
|
|
96
|
+
if(propertyKey === "created_by"){
|
|
97
|
+
console.log("Rendering field for created_by", {propertyKey, property, context});
|
|
98
|
+
}
|
|
99
|
+
|
|
96
100
|
return (
|
|
97
101
|
<Field
|
|
98
102
|
key={propertyKey}
|
|
@@ -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
|
+
}
|
package/src/hooks/index.tsx
CHANGED
|
@@ -18,6 +18,9 @@ export * from "./useSnackbarController";
|
|
|
18
18
|
export * from "./useModeController";
|
|
19
19
|
export * from "./useClipboard";
|
|
20
20
|
export * from "./useLargeLayout";
|
|
21
|
+
export * from "./useCollapsedGroups";
|
|
22
|
+
|
|
23
|
+
export * from "./useInternalUserManagementController";
|
|
21
24
|
|
|
22
25
|
export * from "./useReferenceDialog";
|
|
23
26
|
export * from "./useBrowserTitleAndIcon";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
2
|
|
|
3
|
-
const fireCMSLogo = "data:image/png;base64,
|
|
3
|
+
const fireCMSLogo = "data:image/png;base64,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";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Internal hook to handle the browser title and icon
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom hook for managing collapsed/expanded state of navigation groups
|
|
5
|
+
* with localStorage persistence. Automatically cleans up stale group entries
|
|
6
|
+
* when groups are removed from the navigation.
|
|
7
|
+
*/
|
|
8
|
+
export function useCollapsedGroups(groupNames: string[]) {
|
|
9
|
+
// Load collapsed groups from localStorage on mount
|
|
10
|
+
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>(() => {
|
|
11
|
+
try {
|
|
12
|
+
const stored = localStorage.getItem('firecms-collapsed-groups');
|
|
13
|
+
return stored ? JSON.parse(stored) : {};
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Save to localStorage whenever collapsedGroups changes
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
try {
|
|
22
|
+
localStorage.setItem('firecms-collapsed-groups', JSON.stringify(collapsedGroups));
|
|
23
|
+
} catch {
|
|
24
|
+
// Silently fail if localStorage is not available
|
|
25
|
+
}
|
|
26
|
+
}, [collapsedGroups]);
|
|
27
|
+
|
|
28
|
+
// Clean up collapsed groups state when groups change - remove entries for groups that no longer exist
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
// Only clean up if we have actual groups loaded (avoid cleaning up during initial load)
|
|
31
|
+
if (groupNames.length === 0) return;
|
|
32
|
+
|
|
33
|
+
const currentGroupNames = new Set(groupNames);
|
|
34
|
+
|
|
35
|
+
setCollapsedGroups(prev => {
|
|
36
|
+
const cleaned = Object.fromEntries(
|
|
37
|
+
Object.entries(prev).filter(([groupName]) => currentGroupNames.has(groupName))
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Only update if something actually changed
|
|
41
|
+
const prevKeys = Object.keys(prev);
|
|
42
|
+
const cleanedKeys = Object.keys(cleaned);
|
|
43
|
+
|
|
44
|
+
if (prevKeys.length === cleanedKeys.length && prevKeys.every(key => cleanedKeys.includes(key))) {
|
|
45
|
+
return prev;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return cleaned;
|
|
49
|
+
});
|
|
50
|
+
}, [groupNames]);
|
|
51
|
+
|
|
52
|
+
const isGroupCollapsed = useCallback((name: string) => {
|
|
53
|
+
return !!collapsedGroups[name];
|
|
54
|
+
}, [collapsedGroups]);
|
|
55
|
+
|
|
56
|
+
const toggleGroupCollapsed = useCallback((name: string) => {
|
|
57
|
+
setCollapsedGroups(prev => ({ ...prev, [name]: !prev[name] }));
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
isGroupCollapsed,
|
|
62
|
+
toggleGroupCollapsed
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -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);
|
|
@@ -31,6 +31,7 @@ import { DatePreview } from "./components/DatePreview";
|
|
|
31
31
|
import { BooleanPreview } from "./components/BooleanPreview";
|
|
32
32
|
import { NumberPropertyPreview } from "./property_previews/NumberPropertyPreview";
|
|
33
33
|
import { ErrorView } from "../components";
|
|
34
|
+
import { UserPreview } from "./components/UserPreview";
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* @group Preview components
|
|
@@ -97,6 +98,13 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
97
98
|
previewType={stringProperty.url}/>;
|
|
98
99
|
} else if (stringProperty.markdown) {
|
|
99
100
|
content = <Markdown source={value} size={"small"}/>;
|
|
101
|
+
} else if (stringProperty.userSelect) {
|
|
102
|
+
content = <UserPreview
|
|
103
|
+
value={value}
|
|
104
|
+
property={stringProperty}
|
|
105
|
+
propertyKey={propertyKey}
|
|
106
|
+
size={props.size}
|
|
107
|
+
/>;
|
|
100
108
|
} else if (stringProperty.reference) {
|
|
101
109
|
if (typeof stringProperty.reference.path === "string") {
|
|
102
110
|
content = <ReferencePreview
|
|
@@ -4,7 +4,7 @@ import { Entity, EntityCollection, EntityReference } from "../../types";
|
|
|
4
4
|
import { useCustomizationController, useEntityFetch, useNavigationController } from "../../hooks";
|
|
5
5
|
import { PreviewSize } from "../PropertyPreviewProps";
|
|
6
6
|
import { Skeleton } from "@firecms/ui";
|
|
7
|
-
import { ErrorView } from "../../components";
|
|
7
|
+
import { ErrorBoundary, ErrorView } from "../../components";
|
|
8
8
|
import { EntityPreview, EntityPreviewContainer } from "../../components/EntityPreview";
|
|
9
9
|
|
|
10
10
|
export type ReferencePreviewProps = {
|
|
@@ -32,7 +32,9 @@ export const ReferencePreview = function ReferencePreview(props: ReferencePrevie
|
|
|
32
32
|
tooltip={JSON.stringify(reference)}/>
|
|
33
33
|
</EntityPreviewContainer>;
|
|
34
34
|
}
|
|
35
|
-
return <
|
|
35
|
+
return <ErrorBoundary>
|
|
36
|
+
<ReferencePreviewInternal {...props} />
|
|
37
|
+
</ErrorBoundary>;
|
|
36
38
|
};
|
|
37
39
|
|
|
38
40
|
function ReferencePreviewInternal({
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { PropertyPreviewProps } from "../PropertyPreviewProps";
|
|
3
|
+
import { useInternalUserManagementController } from "../../hooks";
|
|
4
|
+
import { UserDisplay } from "../../components/UserDisplay";
|
|
5
|
+
import { EmptyValue } from "./EmptyValue";
|
|
6
|
+
import { Typography } from "@firecms/ui";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Preview component for displaying user information.
|
|
10
|
+
* This is a simple wrapper around UserDisplay.
|
|
11
|
+
*
|
|
12
|
+
* @group Preview components
|
|
13
|
+
*/
|
|
14
|
+
export function UserPreview({ value }: PropertyPreviewProps<string>) {
|
|
15
|
+
const { getUser } = useInternalUserManagementController();
|
|
16
|
+
|
|
17
|
+
if (!value) {
|
|
18
|
+
return <EmptyValue/>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const user = getUser(value);
|
|
22
|
+
if (!user) {
|
|
23
|
+
return <Typography variant={"caption"} color={"secondary"}>User not found: {value}</Typography>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return <UserDisplay user={user}/>;
|
|
27
|
+
}
|
package/src/preview/index.ts
CHANGED
|
@@ -42,7 +42,7 @@ export function ArrayPropertyPreview({
|
|
|
42
42
|
const childSize: PreviewSize = size === "medium" ? "medium" : "small";
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
|
-
<div className="flex flex-col gap-2">
|
|
45
|
+
<div className="w-full flex flex-col gap-2">
|
|
46
46
|
{values &&
|
|
47
47
|
values.map((value, index) => {
|
|
48
48
|
const of: ResolvedProperty = property.resolvedProperties[index] ??
|
package/src/types/collections.ts
CHANGED
|
@@ -69,6 +69,8 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
|
|
|
69
69
|
* https://fonts.google.com/icons
|
|
70
70
|
* e.g. 'account_tree' or 'person'.
|
|
71
71
|
* Find all the icons in https://firecms.co/docs/icons
|
|
72
|
+
* You can also pass a React node if you want to render a custom icon.
|
|
73
|
+
* If not specified, a default icon will be used.
|
|
72
74
|
*/
|
|
73
75
|
icon?: string | React.ReactNode;
|
|
74
76
|
|
package/src/types/entities.ts
CHANGED
|
@@ -50,9 +50,15 @@ export class EntityReference {
|
|
|
50
50
|
*/
|
|
51
51
|
readonly path: string;
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Optional database ID where the entity is stored (if multiple databases are used)
|
|
55
|
+
*/
|
|
56
|
+
readonly databaseId?: string;
|
|
57
|
+
|
|
58
|
+
constructor(id: string, path: string, databaseId?: string) {
|
|
54
59
|
this.id = id;
|
|
55
60
|
this.path = path;
|
|
61
|
+
this.databaseId = databaseId;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
get pathWithId() {
|
package/src/types/firecms.tsx
CHANGED
|
@@ -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
|
/**
|
|
18
19
|
* Use this callback to build entity collections dynamically.
|
|
@@ -139,6 +140,7 @@ export type FireCMSProps<USER extends User> = {
|
|
|
139
140
|
* Use plugins to modify the behaviour of the CMS.
|
|
140
141
|
* DEPRECATED: use the `plugins` prop in the `useBuildNavigationController` instead.
|
|
141
142
|
* This prop will work as a fallback for the `plugins` prop in the `useBuildNavigationController`.
|
|
143
|
+
* @deprecated
|
|
142
144
|
*/
|
|
143
145
|
plugins?: FireCMSPlugin<any, any, any>[];
|
|
144
146
|
|
|
@@ -153,6 +155,20 @@ export type FireCMSProps<USER extends User> = {
|
|
|
153
155
|
*/
|
|
154
156
|
entityLinkBuilder?: EntityLinkBuilder;
|
|
155
157
|
|
|
158
|
+
/**
|
|
159
|
+
* You can use this props to provide your own user management implementation.
|
|
160
|
+
* Note that this will not affect the UI, but it will be used to show user information
|
|
161
|
+
* in various places of the CMS, for example, to show who created or modified an entity,
|
|
162
|
+
* or to assign ownership of an entity.
|
|
163
|
+
*
|
|
164
|
+
* You can also use this data to be retrieved in your custom properties,
|
|
165
|
+
* for example, to show a list of users in a dropdown.
|
|
166
|
+
*
|
|
167
|
+
* If you are using the FireCMS user management plugin, this
|
|
168
|
+
* prop will be implemented automatically.
|
|
169
|
+
*/
|
|
170
|
+
userManagement?: InternalUserManagement
|
|
171
|
+
|
|
156
172
|
components?: {
|
|
157
173
|
|
|
158
174
|
/**
|
|
@@ -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
|
/**
|
|
15
16
|
* Context that includes the internal controllers and contexts used by the app.
|
|
@@ -80,4 +81,20 @@ export type FireCMSContext<USER extends User = User, AuthControllerType extends
|
|
|
80
81
|
*/
|
|
81
82
|
analyticsController?: AnalyticsController;
|
|
82
83
|
|
|
84
|
+
/**
|
|
85
|
+
* This section is used to manage users in the CMS.
|
|
86
|
+
* It is used to show user information in various places of the CMS,
|
|
87
|
+
* for example, to show who created or modified an entity,
|
|
88
|
+
* or to assign ownership of an entity.
|
|
89
|
+
*
|
|
90
|
+
* In the base CMS, this information is not used for access control.
|
|
91
|
+
* You can pass your own implementation of this section, to populate
|
|
92
|
+
* the dropdown of users when assigning ownership of an entity,
|
|
93
|
+
* or to show more information about the user.
|
|
94
|
+
*
|
|
95
|
+
* If you are using the FireCMS user management plugin, this
|
|
96
|
+
* section will be implemented automatically.
|
|
97
|
+
*/
|
|
98
|
+
userManagement: InternalUserManagement<USER>
|
|
99
|
+
|
|
83
100
|
};
|
package/src/types/index.ts
CHANGED
|
@@ -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,24 @@
|
|
|
1
|
+
import { User } from "./user";
|
|
2
|
+
|
|
3
|
+
export type InternalUserManagement<USER extends User = User> = {
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* List of users to be managed by the CMS.
|
|
7
|
+
*/
|
|
8
|
+
users: USER[];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Function to get a user by its uid. This is used to show
|
|
12
|
+
* user information when assigning ownership of an entity.
|
|
13
|
+
*
|
|
14
|
+
* You can pass your own implementation if you want to show
|
|
15
|
+
* more information about the user.
|
|
16
|
+
*
|
|
17
|
+
* If you are using the FireCMS user management plugin, this
|
|
18
|
+
* function will be implemented automatically.
|
|
19
|
+
*
|
|
20
|
+
* @param uid
|
|
21
|
+
*/
|
|
22
|
+
getUser: (uid: string) => USER | null;
|
|
23
|
+
|
|
24
|
+
}
|
package/src/types/plugins.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { CMSType, Property } from "./properties";
|
|
|
8
8
|
import { EntityStatus } from "./entities";
|
|
9
9
|
import { ResolvedProperty } from "./resolved_entities";
|
|
10
10
|
import { NavigationGroupMapping } from "./navigation";
|
|
11
|
+
import { InternalUserManagement } from "./internal_user_management";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Interface used to define plugins for FireCMS.
|
|
@@ -43,6 +44,8 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
|
|
|
43
44
|
props?: PROPS;
|
|
44
45
|
};
|
|
45
46
|
|
|
47
|
+
userManagement?: InternalUserManagement
|
|
48
|
+
|
|
46
49
|
homePage?: {
|
|
47
50
|
|
|
48
51
|
/**
|
package/src/types/properties.ts
CHANGED
|
@@ -388,6 +388,16 @@ export interface StringProperty extends BaseProperty<string> {
|
|
|
388
388
|
*/
|
|
389
389
|
storage?: StorageConfig;
|
|
390
390
|
|
|
391
|
+
/**
|
|
392
|
+
* This property is used to indicate that the string is a user ID, and
|
|
393
|
+
* it will be rendered as a user picker.
|
|
394
|
+
* Note that the user ID needs to be the one used in your authentication
|
|
395
|
+
* provider, e.g. Firebase Auth.
|
|
396
|
+
* You can also use a property builder to specify the user path dynamically
|
|
397
|
+
* based on other values of the entity.
|
|
398
|
+
*/
|
|
399
|
+
userSelect?: boolean;
|
|
400
|
+
|
|
391
401
|
/**
|
|
392
402
|
* If the value of this property is a URL, you can set this flag to true
|
|
393
403
|
* to add a link, or one of the supported media types to render a preview
|
package/src/types/user.ts
CHANGED
package/src/util/entities.ts
CHANGED
|
@@ -142,7 +142,7 @@ export function sanitizeData<M extends Record<string, any>>
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
export function getReferenceFrom<M extends Record<string, any>>(entity: Entity<M>): EntityReference {
|
|
145
|
-
return new EntityReference(entity.id, entity.path);
|
|
145
|
+
return new EntityReference(entity.id, entity.path, entity.databaseId);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
export function traverseValuesProperties<M extends Record<string, any>>(
|