@rebasepro/app 0.0.1-canary.4829d6e
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/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/auth/api.d.ts +66 -0
- package/dist/auth/index.d.ts +13 -0
- package/dist/auth/types.d.ts +127 -0
- package/dist/auth/useRebaseAuthController.d.ts +13 -0
- package/dist/components/AIIcon.d.ts +17 -0
- package/dist/components/AuthSimulationMenu.d.ts +0 -0
- package/dist/components/ConfirmationDialog.d.ts +9 -0
- package/dist/components/Debug/UIReferenceView.d.ts +13 -0
- package/dist/components/Debug/UIStyleGuide.d.ts +2 -0
- package/dist/components/Debug/collection-views/CardViewDemo.d.ts +2 -0
- package/dist/components/Debug/collection-views/CollectionTableDemo.d.ts +2 -0
- package/dist/components/Debug/collection-views/KanbanBoardDemo.d.ts +2 -0
- package/dist/components/Debug/collection-views/index.d.ts +3 -0
- package/dist/components/Debug/collection-views/sample_data.d.ts +23 -0
- package/dist/components/Debug/crm-dashboard/CalendarWidget.d.ts +22 -0
- package/dist/components/Debug/crm-dashboard/CrmDashboardDemo.d.ts +2 -0
- package/dist/components/Debug/crm-dashboard/DashboardMetrics.d.ts +15 -0
- package/dist/components/Debug/crm-dashboard/PipelineOverview.d.ts +34 -0
- package/dist/components/Debug/crm-dashboard/RecentActivity.d.ts +19 -0
- package/dist/components/Debug/crm-dashboard/TaskFilters.d.ts +30 -0
- package/dist/components/Debug/crm-dashboard/TaskTable.d.ts +50 -0
- package/dist/components/Debug/crm-dashboard/TasksView.d.ts +41 -0
- package/dist/components/ErrorTooltip.d.ts +3 -0
- package/dist/components/ErrorView.d.ts +21 -0
- package/dist/components/LanguageToggle.d.ts +2 -0
- package/dist/components/LoginView/LoginView.d.ts +113 -0
- package/dist/components/LoginView/index.d.ts +2 -0
- package/dist/components/NotFoundPage.d.ts +2 -0
- package/dist/components/RebaseAuth.d.ts +10 -0
- package/dist/components/RebaseLogo.d.ts +7 -0
- package/dist/components/SchemaDriftBanner.d.ts +27 -0
- package/dist/components/UnsavedChangesDialog.d.ts +9 -0
- package/dist/components/UserDisplay.d.ts +7 -0
- package/dist/components/UserSelectPopover.d.ts +63 -0
- package/dist/components/UserSettingsView.d.ts +2 -0
- package/dist/components/common/index.d.ts +6 -0
- package/dist/components/common/table_height.d.ts +5 -0
- package/dist/components/common/types.d.ts +66 -0
- package/dist/components/common/useColumnsIds.d.ts +9 -0
- package/dist/components/common/useDataTableController.d.ts +45 -0
- package/dist/components/common/useDebouncedData.d.ts +9 -0
- package/dist/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/components/index.d.ts +18 -0
- package/dist/contexts/AdminModeController.d.ts +4 -0
- package/dist/contexts/AnalyticsContext.d.ts +3 -0
- package/dist/contexts/AuthControllerContext.d.ts +3 -0
- package/dist/contexts/CollectionScopeContext.d.ts +50 -0
- package/dist/contexts/ComponentOverrideContext.d.ts +42 -0
- package/dist/contexts/CustomizationControllerContext.d.ts +3 -0
- package/dist/contexts/DataDriverContext.d.ts +3 -0
- package/dist/contexts/DataSourcesContext.d.ts +31 -0
- package/dist/contexts/DatabaseAdminContext.d.ts +3 -0
- package/dist/contexts/DialogsProvider.d.ts +4 -0
- package/dist/contexts/EffectiveRoleController.d.ts +4 -0
- package/dist/contexts/ModeController.d.ts +4 -0
- package/dist/contexts/RebaseClientInstanceContext.d.ts +6 -0
- package/dist/contexts/RebaseDataContext.d.ts +3 -0
- package/dist/contexts/SnackbarProvider.d.ts +2 -0
- package/dist/contexts/StorageSourceContext.d.ts +3 -0
- package/dist/contexts/StorageSourcesContext.d.ts +25 -0
- package/dist/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
- package/dist/contexts/index.d.ts +17 -0
- package/dist/core/PluginLifecycleManager.d.ts +17 -0
- package/dist/core/PluginProviderStack.d.ts +21 -0
- package/dist/core/Rebase.d.ts +15 -0
- package/dist/core/RebaseProps.d.ts +291 -0
- package/dist/core/RebaseRouter.d.ts +4 -0
- package/dist/core/RebaseRoutes.d.ts +17 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/hooks/ApiConfigContext.d.ts +24 -0
- package/dist/hooks/data/delete.d.ts +33 -0
- package/dist/hooks/data/save.d.ts +40 -0
- package/dist/hooks/data/useCollection.d.ts +62 -0
- package/dist/hooks/data/useData.d.ts +13 -0
- package/dist/hooks/data/useDataOrder.d.ts +12 -0
- package/dist/hooks/data/useFetch.d.ts +43 -0
- package/dist/hooks/data/useRelationSelector.d.ts +52 -0
- package/dist/hooks/index.d.ts +36 -0
- package/dist/hooks/useAdminModeController.d.ts +19 -0
- package/dist/hooks/useAnalyticsController.d.ts +5 -0
- package/dist/hooks/useAuthController.d.ts +11 -0
- package/dist/hooks/useAuthSubscription.d.ts +2 -0
- package/dist/hooks/useBackendStorageSource.d.ts +30 -0
- package/dist/hooks/useBridgeRegistration.d.ts +18 -0
- package/dist/hooks/useBrowserTitleAndIcon.d.ts +6 -0
- package/dist/hooks/useBuildAdminModeController.d.ts +6 -0
- package/dist/hooks/useBuildEffectiveRoleController.d.ts +8 -0
- package/dist/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
- package/dist/hooks/useBuildModeController.d.ts +6 -0
- package/dist/hooks/useClipboard.d.ts +57 -0
- package/dist/hooks/useCollapsedGroups.d.ts +27 -0
- package/dist/hooks/useComponentOverride.d.ts +32 -0
- package/dist/hooks/useCustomizationController.d.ts +11 -0
- package/dist/hooks/useDialogsController.d.ts +11 -0
- package/dist/hooks/useEffectiveRoleController.d.ts +7 -0
- package/dist/hooks/useLargeLayout.d.ts +1 -0
- package/dist/hooks/useModeController.d.ts +19 -0
- package/dist/hooks/usePermissions.d.ts +12 -0
- package/dist/hooks/useRebaseClient.d.ts +5 -0
- package/dist/hooks/useRebaseContext.d.ts +11 -0
- package/dist/hooks/useRebaseRegistry.d.ts +34 -0
- package/dist/hooks/useResolvedComponent.d.ts +47 -0
- package/dist/hooks/useSlot.d.ts +18 -0
- package/dist/hooks/useSnackbarController.d.ts +20 -0
- package/dist/hooks/useStorageSource.d.ts +6 -0
- package/dist/hooks/useStudioBridge.d.ts +79 -0
- package/dist/hooks/useTranslation.d.ts +17 -0
- package/dist/hooks/useUnsavedChangesDialog.d.ts +12 -0
- package/dist/hooks/useUserConfigurationPersistence.d.ts +8 -0
- package/dist/i18n/RebaseI18nProvider.d.ts +33 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.es.js +16843 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +17021 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/internal/common.d.ts +14 -0
- package/dist/internal/useRestoreScroll.d.ts +15 -0
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/en.d.ts +10 -0
- package/dist/locales/es.d.ts +10 -0
- package/dist/locales/fr.d.ts +2 -0
- package/dist/locales/hi.d.ts +2 -0
- package/dist/locales/it.d.ts +2 -0
- package/dist/locales/pt.d.ts +7 -0
- package/dist/util/constants.d.ts +1 -0
- package/dist/util/createFormexStub.d.ts +2 -0
- package/dist/util/entity_cache.d.ts +22 -0
- package/dist/util/enums.d.ts +5 -0
- package/dist/util/icon_list.d.ts +5 -0
- package/dist/util/icons.d.ts +20 -0
- package/dist/util/index.d.ts +8 -0
- package/dist/util/previews.d.ts +4 -0
- package/dist/util/useStorageUploadController.d.ts +44 -0
- package/dist/vitePlugin.d.ts +60 -0
- package/dist/vitePlugin.js +113 -0
- package/package.json +145 -0
- package/src/auth/api.ts +148 -0
- package/src/auth/index.ts +25 -0
- package/src/auth/types.ts +115 -0
- package/src/auth/useRebaseAuthController.ts +328 -0
- package/src/components/AIIcon.tsx +47 -0
- package/src/components/AuthSimulationMenu.tsx +0 -0
- package/src/components/ConfirmationDialog.tsx +48 -0
- package/src/components/Debug/UIReferenceView.tsx +958 -0
- package/src/components/Debug/UIStyleGuide.tsx +156 -0
- package/src/components/Debug/collection-views/CardViewDemo.tsx +85 -0
- package/src/components/Debug/collection-views/CollectionTableDemo.tsx +107 -0
- package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +90 -0
- package/src/components/Debug/collection-views/index.ts +3 -0
- package/src/components/Debug/collection-views/sample_data.ts +42 -0
- package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +389 -0
- package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +1102 -0
- package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +133 -0
- package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +290 -0
- package/src/components/Debug/crm-dashboard/RecentActivity.tsx +215 -0
- package/src/components/Debug/crm-dashboard/TaskFilters.tsx +184 -0
- package/src/components/Debug/crm-dashboard/TaskTable.tsx +427 -0
- package/src/components/Debug/crm-dashboard/TasksView.tsx +234 -0
- package/src/components/ErrorTooltip.tsx +12 -0
- package/src/components/ErrorView.tsx +94 -0
- package/src/components/LanguageToggle.tsx +59 -0
- package/src/components/LoginView/LoginView.tsx +932 -0
- package/src/components/LoginView/index.ts +2 -0
- package/src/components/NotFoundPage.tsx +27 -0
- package/src/components/RebaseAuth.tsx +27 -0
- package/src/components/RebaseLogo.tsx +29 -0
- package/src/components/SchemaDriftBanner.tsx +102 -0
- package/src/components/UnsavedChangesDialog.tsx +46 -0
- package/src/components/UserDisplay.tsx +54 -0
- package/src/components/UserSelectPopover.tsx +399 -0
- package/src/components/UserSettingsView.tsx +318 -0
- package/src/components/common/index.ts +6 -0
- package/src/components/common/table_height.tsx +21 -0
- package/src/components/common/types.tsx +66 -0
- package/src/components/common/useColumnsIds.tsx +222 -0
- package/src/components/common/useDataTableController.tsx +514 -0
- package/src/components/common/useDebouncedData.ts +49 -0
- package/src/components/common/useScrollRestoration.tsx +68 -0
- package/src/components/index.tsx +26 -0
- package/src/contexts/AdminModeController.tsx +11 -0
- package/src/contexts/AnalyticsContext.tsx +4 -0
- package/src/contexts/AuthControllerContext.tsx +4 -0
- package/src/contexts/CollectionScopeContext.tsx +67 -0
- package/src/contexts/ComponentOverrideContext.tsx +81 -0
- package/src/contexts/CustomizationControllerContext.tsx +4 -0
- package/src/contexts/DataDriverContext.tsx +4 -0
- package/src/contexts/DataSourcesContext.tsx +36 -0
- package/src/contexts/DatabaseAdminContext.tsx +4 -0
- package/src/contexts/DialogsProvider.tsx +56 -0
- package/src/contexts/EffectiveRoleController.tsx +12 -0
- package/src/contexts/ModeController.tsx +11 -0
- package/src/contexts/RebaseClientInstanceContext.tsx +7 -0
- package/src/contexts/RebaseDataContext.tsx +4 -0
- package/src/contexts/SnackbarProvider.tsx +15 -0
- package/src/contexts/StorageSourceContext.tsx +4 -0
- package/src/contexts/StorageSourcesContext.tsx +30 -0
- package/src/contexts/UserConfigurationPersistenceContext.tsx +4 -0
- package/src/contexts/index.ts +17 -0
- package/src/core/PluginLifecycleManager.tsx +95 -0
- package/src/core/PluginProviderStack.tsx +40 -0
- package/src/core/Rebase.tsx +439 -0
- package/src/core/RebaseProps.tsx +320 -0
- package/src/core/RebaseRouter.tsx +17 -0
- package/src/core/RebaseRoutes.tsx +24 -0
- package/src/core/index.tsx +5 -0
- package/src/hooks/ApiConfigContext.tsx +41 -0
- package/src/hooks/data/delete.ts +55 -0
- package/src/hooks/data/save.ts +77 -0
- package/src/hooks/data/useCollection.tsx +176 -0
- package/src/hooks/data/useData.tsx +18 -0
- package/src/hooks/data/useDataOrder.ts +26 -0
- package/src/hooks/data/useFetch.tsx +140 -0
- package/src/hooks/data/useRelationSelector.tsx +234 -0
- package/src/hooks/index.tsx +45 -0
- package/src/hooks/useAdminModeController.tsx +23 -0
- package/src/hooks/useAnalyticsController.tsx +8 -0
- package/src/hooks/useAuthController.tsx +14 -0
- package/src/hooks/useAuthSubscription.ts +87 -0
- package/src/hooks/useBackendStorageSource.ts +288 -0
- package/src/hooks/useBridgeRegistration.tsx +32 -0
- package/src/hooks/useBrowserTitleAndIcon.tsx +23 -0
- package/src/hooks/useBuildAdminModeController.tsx +24 -0
- package/src/hooks/useBuildEffectiveRoleController.tsx +31 -0
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +75 -0
- package/src/hooks/useBuildModeController.tsx +71 -0
- package/src/hooks/useClipboard.tsx +158 -0
- package/src/hooks/useCollapsedGroups.ts +116 -0
- package/src/hooks/useComponentOverride.tsx +59 -0
- package/src/hooks/useCustomizationController.tsx +14 -0
- package/src/hooks/useDialogsController.tsx +15 -0
- package/src/hooks/useEffectiveRoleController.tsx +10 -0
- package/src/hooks/useLargeLayout.tsx +65 -0
- package/src/hooks/useModeController.tsx +23 -0
- package/src/hooks/usePermissions.ts +44 -0
- package/src/hooks/useRebaseClient.tsx +10 -0
- package/src/hooks/useRebaseContext.tsx +83 -0
- package/src/hooks/useRebaseRegistry.tsx +91 -0
- package/src/hooks/useResolvedComponent.tsx +159 -0
- package/src/hooks/useSlot.tsx +64 -0
- package/src/hooks/useSnackbarController.tsx +52 -0
- package/src/hooks/useStorageSource.tsx +11 -0
- package/src/hooks/useStudioBridge.tsx +209 -0
- package/src/hooks/useTranslation.ts +33 -0
- package/src/hooks/useUnsavedChangesDialog.tsx +64 -0
- package/src/hooks/useUserConfigurationPersistence.tsx +11 -0
- package/src/i18n/RebaseI18nProvider.tsx +160 -0
- package/src/index.ts +25 -0
- package/src/internal/common.tsx +16 -0
- package/src/internal/useRestoreScroll.tsx +69 -0
- package/src/locales/de.ts +902 -0
- package/src/locales/en.ts +980 -0
- package/src/locales/es.ts +922 -0
- package/src/locales/fr.ts +901 -0
- package/src/locales/hi.ts +901 -0
- package/src/locales/it.ts +901 -0
- package/src/locales/pt.ts +909 -0
- package/src/util/constants.ts +7 -0
- package/src/util/createFormexStub.tsx +66 -0
- package/src/util/entity_cache.ts +239 -0
- package/src/util/enums.ts +32 -0
- package/src/util/icon_list.ts +22 -0
- package/src/util/icons.tsx +149 -0
- package/src/util/index.ts +12 -0
- package/src/util/previews.ts +105 -0
- package/src/util/useStorageUploadController.tsx +350 -0
- package/src/vitePlugin.ts +196 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
|
|
2
|
+
import React, { useEffect, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
Avatar,
|
|
5
|
+
Button,
|
|
6
|
+
Card,
|
|
7
|
+
Chip,
|
|
8
|
+
CircularProgress,
|
|
9
|
+
cls,
|
|
10
|
+
IconButton,
|
|
11
|
+
Tab,
|
|
12
|
+
Tabs,
|
|
13
|
+
TextField,
|
|
14
|
+
Trash2Icon,
|
|
15
|
+
Typography
|
|
16
|
+
} from "@rebasepro/ui";
|
|
17
|
+
import { useAuthController, useTranslation } from "../hooks";
|
|
18
|
+
|
|
19
|
+
interface SessionInfo {
|
|
20
|
+
id: string;
|
|
21
|
+
userAgent?: string;
|
|
22
|
+
ipAddress?: string;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
isCurrentSession?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ExtendedAuthController {
|
|
28
|
+
user: { displayName?: string | null; photoURL?: string | null; email?: string | null } | null;
|
|
29
|
+
updateProfile?: (displayName: string, photoURL: string) => Promise<void>;
|
|
30
|
+
changePassword?: (oldPassword: string, newPassword: string) => Promise<void>;
|
|
31
|
+
fetchSessions?: () => Promise<SessionInfo[]>;
|
|
32
|
+
revokeSession?: (id: string) => Promise<void>;
|
|
33
|
+
revokeAllSessions?: () => Promise<void>;
|
|
34
|
+
signOut: () => Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type ActiveTab = "profile" | "security" | "sessions";
|
|
38
|
+
|
|
39
|
+
export function UserSettingsView() {
|
|
40
|
+
const authController = useAuthController() as ExtendedAuthController;
|
|
41
|
+
const user = authController.user;
|
|
42
|
+
const { t } = useTranslation();
|
|
43
|
+
|
|
44
|
+
const hasPasswordChange = !!authController.changePassword;
|
|
45
|
+
const [activeTab, setActiveTab] = useState<ActiveTab>("profile");
|
|
46
|
+
|
|
47
|
+
// Profile state
|
|
48
|
+
const [displayName, setDisplayName] = useState(user?.displayName || "");
|
|
49
|
+
const [photoURL, setPhotoURL] = useState(user?.photoURL || "");
|
|
50
|
+
const [savingProfile, setSavingProfile] = useState(false);
|
|
51
|
+
const [profileError, setProfileError] = useState<string | null>(null);
|
|
52
|
+
|
|
53
|
+
// Password change state
|
|
54
|
+
const [currentPassword, setCurrentPassword] = useState("");
|
|
55
|
+
const [newPassword, setNewPassword] = useState("");
|
|
56
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
57
|
+
const [changingPassword, setChangingPassword] = useState(false);
|
|
58
|
+
const [passwordError, setPasswordError] = useState<string | null>(null);
|
|
59
|
+
const [passwordSuccess, setPasswordSuccess] = useState<string | null>(null);
|
|
60
|
+
|
|
61
|
+
// Sessions state
|
|
62
|
+
const [sessions, setSessions] = useState<SessionInfo[]>([]);
|
|
63
|
+
const [loadingSessions, setLoadingSessions] = useState(false);
|
|
64
|
+
const [sessionsError, setSessionsError] = useState<string | null>(null);
|
|
65
|
+
const [revokingSessionId, setRevokingSessionId] = useState<string | null>(null);
|
|
66
|
+
const [revokingAll, setRevokingAll] = useState(false);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
setDisplayName(user?.displayName || "");
|
|
70
|
+
setPhotoURL(user?.photoURL || "");
|
|
71
|
+
if (activeTab === "sessions") {
|
|
72
|
+
loadSessions();
|
|
73
|
+
}
|
|
74
|
+
}, [activeTab, user]);
|
|
75
|
+
|
|
76
|
+
const handleSaveProfile = async () => {
|
|
77
|
+
setSavingProfile(true);
|
|
78
|
+
setProfileError(null);
|
|
79
|
+
try {
|
|
80
|
+
if (authController.updateProfile) {
|
|
81
|
+
await authController.updateProfile(displayName, photoURL);
|
|
82
|
+
} else {
|
|
83
|
+
throw new Error("updateProfile not implemented in this auth controller.");
|
|
84
|
+
}
|
|
85
|
+
} catch (e: unknown) {
|
|
86
|
+
setProfileError(e instanceof Error ? e.message : String(e));
|
|
87
|
+
} finally {
|
|
88
|
+
setSavingProfile(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleChangePassword = async () => {
|
|
93
|
+
setPasswordError(null);
|
|
94
|
+
setPasswordSuccess(null);
|
|
95
|
+
|
|
96
|
+
// Validate
|
|
97
|
+
if (newPassword.length < 8) {
|
|
98
|
+
setPasswordError(t("password_too_short"));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (newPassword !== confirmPassword) {
|
|
102
|
+
setPasswordError(t("passwords_dont_match"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setChangingPassword(true);
|
|
107
|
+
try {
|
|
108
|
+
if (authController.changePassword) {
|
|
109
|
+
await authController.changePassword(currentPassword, newPassword);
|
|
110
|
+
setPasswordSuccess(t("password_changed"));
|
|
111
|
+
setCurrentPassword("");
|
|
112
|
+
setNewPassword("");
|
|
113
|
+
setConfirmPassword("");
|
|
114
|
+
// Backend invalidates all sessions on password change,
|
|
115
|
+
// so the user will be logged out shortly
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
authController.signOut();
|
|
118
|
+
}, 2000);
|
|
119
|
+
}
|
|
120
|
+
} catch (e: unknown) {
|
|
121
|
+
setPasswordError(e instanceof Error ? e.message : String(e));
|
|
122
|
+
} finally {
|
|
123
|
+
setChangingPassword(false);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const loadSessions = async () => {
|
|
128
|
+
setLoadingSessions(true);
|
|
129
|
+
setSessionsError(null);
|
|
130
|
+
try {
|
|
131
|
+
if (authController.fetchSessions) {
|
|
132
|
+
const fetchedSessions = await authController.fetchSessions();
|
|
133
|
+
const sortedSessions = (fetchedSessions || []).sort((a: SessionInfo, b: SessionInfo) =>
|
|
134
|
+
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
135
|
+
);
|
|
136
|
+
setSessions(sortedSessions);
|
|
137
|
+
} else {
|
|
138
|
+
throw new Error("fetchSessions not implemented in this auth controller.");
|
|
139
|
+
}
|
|
140
|
+
} catch (e: unknown) {
|
|
141
|
+
setSessionsError(e instanceof Error ? e.message : String(e));
|
|
142
|
+
} finally {
|
|
143
|
+
setLoadingSessions(false);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const handleRevokeSession = async (id: string, isCurrentSession?: boolean) => {
|
|
148
|
+
setRevokingSessionId(id);
|
|
149
|
+
try {
|
|
150
|
+
if (authController.revokeSession) {
|
|
151
|
+
await authController.revokeSession(id);
|
|
152
|
+
setSessions(sessions.filter(s => s.id !== id));
|
|
153
|
+
if (isCurrentSession) {
|
|
154
|
+
await authController.signOut();
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
throw new Error("revokeSession not implemented in this auth controller.");
|
|
158
|
+
}
|
|
159
|
+
} catch (e: unknown) {
|
|
160
|
+
setSessionsError(e instanceof Error ? e.message : String(e));
|
|
161
|
+
} finally {
|
|
162
|
+
setRevokingSessionId(null);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const handleRevokeAll = async () => {
|
|
167
|
+
setRevokingAll(true);
|
|
168
|
+
try {
|
|
169
|
+
if (authController.revokeAllSessions) {
|
|
170
|
+
await authController.revokeAllSessions();
|
|
171
|
+
} else {
|
|
172
|
+
throw new Error("revokeAllSessions not implemented in this auth controller.");
|
|
173
|
+
}
|
|
174
|
+
} catch (e: unknown) {
|
|
175
|
+
setSessionsError(e instanceof Error ? e.message : String(e));
|
|
176
|
+
} finally {
|
|
177
|
+
setRevokingAll(false);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (!user) return null;
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<div className="flex-grow max-w-4xl w-full mx-auto p-4 sm:p-6 md:p-12">
|
|
185
|
+
<Typography variant="h4" className="mb-8">{t("account_settings")}</Typography>
|
|
186
|
+
|
|
187
|
+
<Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as ActiveTab)} className="mb-8">
|
|
188
|
+
<Tab value="profile">{t("profile")}</Tab>
|
|
189
|
+
{hasPasswordChange && <Tab value="security">{t("security")}</Tab>}
|
|
190
|
+
<Tab value="sessions">{t("sessions")}</Tab>
|
|
191
|
+
</Tabs>
|
|
192
|
+
|
|
193
|
+
{activeTab === "profile" && (
|
|
194
|
+
<div className="flex flex-col gap-6 max-w-xl">
|
|
195
|
+
<div className="flex items-center gap-6 mb-2">
|
|
196
|
+
<Avatar src={photoURL || undefined} className="w-24 h-24 text-3xl">
|
|
197
|
+
{displayName ? displayName[0].toUpperCase() : (user.email ? user.email[0].toUpperCase() : "A")}
|
|
198
|
+
</Avatar>
|
|
199
|
+
</div>
|
|
200
|
+
<TextField
|
|
201
|
+
label={t("display_name")}
|
|
202
|
+
value={displayName}
|
|
203
|
+
onChange={(e) => setDisplayName(e.target.value)}
|
|
204
|
+
/>
|
|
205
|
+
<TextField
|
|
206
|
+
label={t("photo_url")}
|
|
207
|
+
value={photoURL}
|
|
208
|
+
onChange={(e) => setPhotoURL(e.target.value)}
|
|
209
|
+
/>
|
|
210
|
+
{profileError && <Typography color="error">{profileError}</Typography>}
|
|
211
|
+
<div className="mt-4">
|
|
212
|
+
<Button variant="filled" onClick={handleSaveProfile} disabled={savingProfile}>
|
|
213
|
+
{savingProfile ? t("saving") : t("save_profile")}
|
|
214
|
+
</Button>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
|
|
219
|
+
{activeTab === "security" && hasPasswordChange && (
|
|
220
|
+
<div className="flex flex-col gap-6 max-w-xl">
|
|
221
|
+
<Typography variant="h6" className="mb-2">{t("change_password")}</Typography>
|
|
222
|
+
|
|
223
|
+
<TextField
|
|
224
|
+
label={t("current_password")}
|
|
225
|
+
type="password"
|
|
226
|
+
value={currentPassword}
|
|
227
|
+
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
228
|
+
autoComplete="current-password"
|
|
229
|
+
/>
|
|
230
|
+
<TextField
|
|
231
|
+
label={t("new_password")}
|
|
232
|
+
type="password"
|
|
233
|
+
value={newPassword}
|
|
234
|
+
onChange={(e) => setNewPassword(e.target.value)}
|
|
235
|
+
autoComplete="new-password"
|
|
236
|
+
/>
|
|
237
|
+
<TextField
|
|
238
|
+
label={t("confirm_password")}
|
|
239
|
+
type="password"
|
|
240
|
+
value={confirmPassword}
|
|
241
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
242
|
+
autoComplete="new-password"
|
|
243
|
+
/>
|
|
244
|
+
|
|
245
|
+
{passwordError && (
|
|
246
|
+
<Typography color="error">{passwordError}</Typography>
|
|
247
|
+
)}
|
|
248
|
+
{passwordSuccess && (
|
|
249
|
+
<Typography className="text-emerald-600 dark:text-emerald-400">{passwordSuccess}</Typography>
|
|
250
|
+
)}
|
|
251
|
+
|
|
252
|
+
<div className="mt-4">
|
|
253
|
+
<Button
|
|
254
|
+
variant="filled"
|
|
255
|
+
onClick={handleChangePassword}
|
|
256
|
+
disabled={changingPassword || !currentPassword || !newPassword || !confirmPassword}
|
|
257
|
+
>
|
|
258
|
+
{changingPassword ? t("changing_password") : t("change_password")}
|
|
259
|
+
</Button>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
)}
|
|
263
|
+
|
|
264
|
+
{activeTab === "sessions" && (
|
|
265
|
+
<div className="flex flex-col gap-4 max-w-3xl">
|
|
266
|
+
{loadingSessions ? (
|
|
267
|
+
<div className="flex justify-center p-8"><CircularProgress/></div>
|
|
268
|
+
) : sessionsError ? (
|
|
269
|
+
<Typography color="error">{sessionsError}</Typography>
|
|
270
|
+
) : sessions.length === 0 ? (
|
|
271
|
+
<Typography>{t("no_active_sessions")}</Typography>
|
|
272
|
+
) : (
|
|
273
|
+
<div className="flex flex-col gap-4">
|
|
274
|
+
<div className="flex justify-end mb-2">
|
|
275
|
+
<Button
|
|
276
|
+
variant="text"
|
|
277
|
+
color="error"
|
|
278
|
+
onClick={handleRevokeAll}
|
|
279
|
+
disabled={revokingAll}
|
|
280
|
+
>
|
|
281
|
+
{revokingAll ? t("revoking") : t("revoke_all_sessions")}
|
|
282
|
+
</Button>
|
|
283
|
+
</div>
|
|
284
|
+
{sessions.map(session => (
|
|
285
|
+
<Card key={session.id} className="flex justify-between items-center p-4">
|
|
286
|
+
<div className="flex flex-col">
|
|
287
|
+
<div className="flex items-center gap-2 mb-1">
|
|
288
|
+
<Typography variant="body1">
|
|
289
|
+
{session.userAgent || t("unknown_device")}
|
|
290
|
+
</Typography>
|
|
291
|
+
{session.isCurrentSession && (
|
|
292
|
+
<Chip colorScheme="blue" size="small">
|
|
293
|
+
{t("current")}
|
|
294
|
+
</Chip>
|
|
295
|
+
)}
|
|
296
|
+
</div>
|
|
297
|
+
<Typography variant="caption" color="secondary">
|
|
298
|
+
IP: {session.ipAddress || "Unknown"} • Created: {new Date(session.createdAt).toLocaleString()}
|
|
299
|
+
</Typography>
|
|
300
|
+
</div>
|
|
301
|
+
<div className="ml-4">
|
|
302
|
+
{revokingSessionId === session.id ? (
|
|
303
|
+
<CircularProgress size="small"/>
|
|
304
|
+
) : (
|
|
305
|
+
<IconButton onClick={() => handleRevokeSession(session.id, session.isCurrentSession)} aria-label="Revoke Session">
|
|
306
|
+
<Trash2Icon/>
|
|
307
|
+
</IconButton>
|
|
308
|
+
)}
|
|
309
|
+
</div>
|
|
310
|
+
</Card>
|
|
311
|
+
))}
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
)}
|
|
316
|
+
</div>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @group Components
|
|
3
|
+
*/
|
|
4
|
+
export type TableSize = "xs" | "s" | "m" | "l" | "xl";
|
|
5
|
+
|
|
6
|
+
export function getRowHeight(size: TableSize): number {
|
|
7
|
+
switch (size) {
|
|
8
|
+
case "xl":
|
|
9
|
+
return 400;
|
|
10
|
+
case "l":
|
|
11
|
+
return 280;
|
|
12
|
+
case "m":
|
|
13
|
+
return 140;
|
|
14
|
+
case "s":
|
|
15
|
+
return 80;
|
|
16
|
+
case "xs":
|
|
17
|
+
return 54;
|
|
18
|
+
default:
|
|
19
|
+
throw Error("Missing mapping for collection size -> height");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Property, Entity } from "@rebasepro/types";
|
|
2
|
+
import { CollectionSize, SelectedCellProps } from "@rebasepro/types";
|
|
3
|
+
|
|
4
|
+
export type DataCollectionTableController<M extends Record<string, unknown>> = {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This cell is displayed as selected
|
|
8
|
+
*/
|
|
9
|
+
selectedCell?: SelectedCellProps;
|
|
10
|
+
/**
|
|
11
|
+
* Store used to sync selection state across cells efficiently.
|
|
12
|
+
*/
|
|
13
|
+
selectionStore?: { getEntity: () => SelectedCellProps | undefined; subscribe: (cb: () => void) => () => void };
|
|
14
|
+
/**
|
|
15
|
+
* Select a table cell
|
|
16
|
+
* @param cell
|
|
17
|
+
*/
|
|
18
|
+
select: (cell?: SelectedCellProps<M>) => void;
|
|
19
|
+
/**
|
|
20
|
+
* The cell that is displayed as a popup view.
|
|
21
|
+
* @param cell
|
|
22
|
+
*/
|
|
23
|
+
setPopupCell?: (cell?: SelectedCellProps<M>) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Callback used when the value of a cell has changed.
|
|
26
|
+
* @param params
|
|
27
|
+
*/
|
|
28
|
+
onValueChange?: (params: OnCellValueChangeParams<unknown, Entity<M>>) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Size of the elements in the collection
|
|
31
|
+
*/
|
|
32
|
+
size: CollectionSize;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Props passed in a callback when the content of a cell in a table has been edited
|
|
37
|
+
* @group Collection components
|
|
38
|
+
*/
|
|
39
|
+
export interface OnCellValueChangeParams<T = unknown, D = unknown> {
|
|
40
|
+
value: T,
|
|
41
|
+
propertyKey: string,
|
|
42
|
+
data?: D,
|
|
43
|
+
onValueUpdated: () => void
|
|
44
|
+
setError: (e: Error | undefined) => void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @group Collection components
|
|
49
|
+
*/
|
|
50
|
+
export type UniqueFieldValidator = (props: {
|
|
51
|
+
name: string,
|
|
52
|
+
value: unknown,
|
|
53
|
+
property: Property,
|
|
54
|
+
entityId?: string | number
|
|
55
|
+
}) => Promise<boolean>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Callback when a cell has changed in a table
|
|
59
|
+
* @group Collection components
|
|
60
|
+
*/
|
|
61
|
+
export type OnCellValueChange<T, M extends Record<string, unknown>> = (params: OnCellValueChangeParams<T, Entity<M>>) => Promise<void> | void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @group Collection components
|
|
65
|
+
*/
|
|
66
|
+
export type OnColumnResizeParams = { width: number, key: string };
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import type { CollectionConfig, Property, MapProperty, Properties } from "@rebasepro/types";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
;
|
|
4
|
+
import { getSubcollections } from "@rebasepro/common";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get a property in a property tree from a dot-path like `address.street`.
|
|
8
|
+
* Inlined here to avoid importing property-aware utilities from the CMS layer.
|
|
9
|
+
*/
|
|
10
|
+
function getPropertyInPath(properties: Properties, path: string): Property | undefined {
|
|
11
|
+
if (typeof properties === "object") {
|
|
12
|
+
if (path in properties) {
|
|
13
|
+
return (properties as Record<string, Property>)[path];
|
|
14
|
+
}
|
|
15
|
+
if (path.includes(".")) {
|
|
16
|
+
const pathSegments = path.split(".");
|
|
17
|
+
const childProperty = (properties as Record<string, Property>)[pathSegments[0]];
|
|
18
|
+
if (typeof childProperty === "object" && childProperty.type === "map" && childProperty.properties) {
|
|
19
|
+
return getPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type PropertyColumnConfig = {
|
|
27
|
+
key: string;
|
|
28
|
+
disabled: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function getSubcollectionColumnId(collection: CollectionConfig<any>) {
|
|
32
|
+
return `subcollection:${collection.slug}`;
|
|
33
|
+
}
|
|
34
|
+
export function useColumnIds<M extends Record<string, any>>(collection: CollectionConfig<M>, includeSubcollections: boolean): PropertyColumnConfig[] {
|
|
35
|
+
return useMemo(() => {
|
|
36
|
+
if (collection.propertiesOrder) {
|
|
37
|
+
return hideAndExpandKeys(collection, collection.propertiesOrder);
|
|
38
|
+
}
|
|
39
|
+
return getDefaultColumnKeys(collection, includeSubcollections);
|
|
40
|
+
}, [collection, includeSubcollections]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function hideAndExpandKeys<M extends Record<string, any>>(collection: CollectionConfig<M>, keys: string[]): PropertyColumnConfig[] {
|
|
44
|
+
|
|
45
|
+
// First, figure out which spread map roots have individual child keys in the order
|
|
46
|
+
// If so, we should NOT auto-expand them - just use the explicit child keys
|
|
47
|
+
const rootsWithExplicitChildren = new Set<string>();
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
if (key.includes(".")) {
|
|
50
|
+
const rootKey = key.split(".")[0];
|
|
51
|
+
const rootProperty = collection.properties[rootKey];
|
|
52
|
+
if (rootProperty && rootProperty.type === "map" && rootProperty.ui?.spreadChildren && rootProperty.properties) {
|
|
53
|
+
rootsWithExplicitChildren.add(rootKey);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Track processed keys to avoid duplicates
|
|
59
|
+
const processedPropertyKeys = new Set<string>();
|
|
60
|
+
|
|
61
|
+
const result = keys.flatMap((key) => {
|
|
62
|
+
// Skip if already processed (handles duplicates in propertiesOrder)
|
|
63
|
+
if (processedPropertyKeys.has(key)) return [null];
|
|
64
|
+
|
|
65
|
+
// Check if it's a top-level property
|
|
66
|
+
const property = collection.properties[key];
|
|
67
|
+
if (property) {
|
|
68
|
+
processedPropertyKeys.add(key);
|
|
69
|
+
if (property.ui?.hideFromCollection)
|
|
70
|
+
return [null];
|
|
71
|
+
if (property.ui?.disabled && typeof property.ui?.disabled === "object" && property.ui?.disabled.hidden)
|
|
72
|
+
return [null];
|
|
73
|
+
|
|
74
|
+
if (property.type === "map" && property.ui?.spreadChildren && property.properties) {
|
|
75
|
+
// Check if this spread map has explicit child keys in propertiesOrder
|
|
76
|
+
if (rootsWithExplicitChildren.has(key)) {
|
|
77
|
+
// DON'T auto-expand - the children are explicitly listed elsewhere
|
|
78
|
+
return [null];
|
|
79
|
+
}
|
|
80
|
+
// Auto-expand all children
|
|
81
|
+
const childConfigs = getColumnKeysForProperty(property, key);
|
|
82
|
+
childConfigs.forEach(c => processedPropertyKeys.add(c.key));
|
|
83
|
+
return childConfigs;
|
|
84
|
+
}
|
|
85
|
+
return [{
|
|
86
|
+
key,
|
|
87
|
+
disabled: Boolean(property.ui?.disabled) || Boolean(property.ui?.readOnly)
|
|
88
|
+
}];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check if it's a nested key like "data.mode" (for spread map properties)
|
|
92
|
+
if (key.includes(".")) {
|
|
93
|
+
const rootKey = key.split(".")[0];
|
|
94
|
+
const rootProperty = collection.properties[rootKey];
|
|
95
|
+
|
|
96
|
+
if (rootProperty && rootProperty.type === "map" && rootProperty.properties) {
|
|
97
|
+
const nestedProperty = getPropertyInPath(collection.properties, key) as Property | undefined;
|
|
98
|
+
if (nestedProperty) {
|
|
99
|
+
processedPropertyKeys.add(key);
|
|
100
|
+
// Mark root as seen
|
|
101
|
+
processedPropertyKeys.add(rootKey);
|
|
102
|
+
|
|
103
|
+
if (nestedProperty.ui?.hideFromCollection)
|
|
104
|
+
return [null];
|
|
105
|
+
if (nestedProperty.ui?.disabled && typeof nestedProperty.ui?.disabled === "object" && nestedProperty.ui?.disabled.hidden)
|
|
106
|
+
return [null];
|
|
107
|
+
|
|
108
|
+
return [{
|
|
109
|
+
key,
|
|
110
|
+
disabled: Boolean(rootProperty.ui?.disabled) || Boolean(rootProperty.ui?.readOnly) ||
|
|
111
|
+
Boolean(nestedProperty.ui?.disabled) || Boolean(nestedProperty.ui?.readOnly)
|
|
112
|
+
}];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check additional fields
|
|
118
|
+
const additionalField = collection.additionalFields?.find(field => field.key === key);
|
|
119
|
+
if (additionalField) {
|
|
120
|
+
return [{
|
|
121
|
+
key,
|
|
122
|
+
disabled: true
|
|
123
|
+
}];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check subcollections
|
|
127
|
+
const subcollections = getSubcollections(collection);
|
|
128
|
+
if (subcollections) {
|
|
129
|
+
const subCollection = subcollections
|
|
130
|
+
.find(subCol => getSubcollectionColumnId(subCol) === key);
|
|
131
|
+
if (subCollection) {
|
|
132
|
+
return [{
|
|
133
|
+
key,
|
|
134
|
+
disabled: true
|
|
135
|
+
}];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return [null];
|
|
140
|
+
}).filter(Boolean) as PropertyColumnConfig[];
|
|
141
|
+
|
|
142
|
+
// Add any missing properties that weren't in propertiesOrder
|
|
143
|
+
// This ensures properties NEVER disappear
|
|
144
|
+
for (const propKey of Object.keys(collection.properties)) {
|
|
145
|
+
// Skip if already processed
|
|
146
|
+
if (processedPropertyKeys.has(propKey)) continue;
|
|
147
|
+
|
|
148
|
+
const property = collection.properties[propKey];
|
|
149
|
+
if (!property) continue;
|
|
150
|
+
if (property.ui?.hideFromCollection) continue;
|
|
151
|
+
if (property.ui?.disabled && typeof property.ui?.disabled === "object" && property.ui?.disabled.hidden) continue;
|
|
152
|
+
|
|
153
|
+
if (property.type === "map" && property.ui?.spreadChildren && property.properties) {
|
|
154
|
+
// For spread maps, add all children that weren't already added
|
|
155
|
+
const allChildConfigs = getColumnKeysForProperty(property as MapProperty, propKey);
|
|
156
|
+
for (const childConfig of allChildConfigs) {
|
|
157
|
+
if (!processedPropertyKeys.has(childConfig.key)) {
|
|
158
|
+
result.push(childConfig);
|
|
159
|
+
processedPropertyKeys.add(childConfig.key);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
result.push({
|
|
164
|
+
key: propKey,
|
|
165
|
+
disabled: Boolean(property.ui?.disabled) || Boolean(property.ui?.readOnly)
|
|
166
|
+
});
|
|
167
|
+
processedPropertyKeys.add(propKey);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getDefaultColumnKeys<M extends Record<string, any> = any>(collection: CollectionConfig<M>, includeSubCollections: boolean) {
|
|
175
|
+
const propertyKeys = Object.keys(collection.properties);
|
|
176
|
+
|
|
177
|
+
const additionalFields = collection.additionalFields ?? [];
|
|
178
|
+
const subCollections: CollectionConfig[] = getSubcollections(collection) ?? [];
|
|
179
|
+
|
|
180
|
+
const columnIds: string[] = [
|
|
181
|
+
...propertyKeys,
|
|
182
|
+
// Filter out additional fields whose key already exists in propertyKeys to avoid duplicate column keys
|
|
183
|
+
...additionalFields.filter((field) => !propertyKeys.includes(field.key)).map((field) => field.key)
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
if (includeSubCollections) {
|
|
187
|
+
const subCollectionIds = subCollections
|
|
188
|
+
.map((collection) => getSubcollectionColumnId(collection));
|
|
189
|
+
columnIds.push(...subCollectionIds.filter((subColId) => !columnIds.includes(subColId)));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return hideAndExpandKeys(collection, columnIds);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function getColumnKeysForProperty(property: Property, key: string, disabled?: boolean): PropertyColumnConfig[] {
|
|
196
|
+
if (property.type === "map" && property.ui?.spreadChildren && property.properties) {
|
|
197
|
+
return Object.entries(property.properties)
|
|
198
|
+
.flatMap(([childKey, childProperty]) => getColumnKeysForProperty(
|
|
199
|
+
childProperty as Readonly<Property>,
|
|
200
|
+
`${key}.${childKey}`,
|
|
201
|
+
disabled || Boolean(property.ui?.disabled) || Boolean(property.ui?.readOnly))
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return [{
|
|
205
|
+
key,
|
|
206
|
+
disabled: disabled || Boolean(property.ui?.disabled) || Boolean(property.ui?.readOnly)
|
|
207
|
+
}];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function getFormFieldKeys(collection: CollectionConfig): string[] {
|
|
211
|
+
const propertyKeys = collection.properties ? Object.keys(collection.properties) : [];
|
|
212
|
+
const additionalFields = collection.additionalFields ?? [];
|
|
213
|
+
const allKeys = [
|
|
214
|
+
...propertyKeys,
|
|
215
|
+
...additionalFields.map((field) => field.key)
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
if (collection.propertiesOrder) {
|
|
219
|
+
return collection.propertiesOrder.filter(key => allKeys.includes(key));
|
|
220
|
+
}
|
|
221
|
+
return allKeys;
|
|
222
|
+
}
|