@rebasepro/app 0.17.3 → 0.18.1
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/README.md +4 -1
- package/dist/components/index.d.ts +0 -3
- package/dist/contexts/AuthControllerContext.d.ts +2 -1
- package/dist/contexts/CustomizationControllerContext.d.ts +2 -1
- package/dist/contexts/DialogsProvider.d.ts +2 -1
- package/dist/contexts/RebaseDataContext.d.ts +10 -1
- package/dist/contexts/StorageSourceContext.d.ts +11 -1
- package/dist/core/RebaseProps.d.ts +2 -12
- package/dist/debug/index.d.ts +19 -0
- package/dist/debug.js +3631 -0
- package/dist/hooks/data/delete.d.ts +16 -7
- package/dist/hooks/data/save.d.ts +18 -4
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useAdminModeController.d.ts +5 -4
- package/dist/hooks/useNavigationGroupLabel.d.ts +17 -0
- package/dist/hooks/useStudioBridge.d.ts +33 -0
- package/dist/index.es.js +2599 -6980
- package/dist/index.es.js.map +1 -1
- package/dist/vitePlugin.d.ts +22 -0
- package/dist/vitePlugin.js +40 -1
- package/package.json +42 -31
- package/src/auth/api.ts +0 -134
- package/src/auth/index.ts +0 -26
- package/src/auth/types.ts +0 -115
- package/src/auth/useRebaseAuthController.ts +0 -345
- package/src/collections/collection_view_config.ts +0 -76
- package/src/collections/entity-display-cache.ts +0 -195
- package/src/collections/entity-display.ts +0 -77
- package/src/collections/entity_image_preview.ts +0 -61
- package/src/collections/filter-operator-resolution.ts +0 -119
- package/src/collections/form-layout.ts +0 -394
- package/src/collections/index.ts +0 -13
- package/src/collections/navigation_from_path.ts +0 -123
- package/src/collections/navigation_utils.ts +0 -171
- package/src/collections/parent_references_from_path.ts +0 -59
- package/src/collections/property-path.ts +0 -30
- package/src/collections/property_presentation.ts +0 -264
- package/src/collections/summary-property.ts +0 -114
- package/src/collections/title-property.ts +0 -312
- package/src/components/AIIcon.tsx +0 -47
- package/src/components/AuthSimulationMenu.tsx +0 -0
- package/src/components/ConfirmationDialog.tsx +0 -48
- package/src/components/Debug/UIReferenceView.tsx +0 -1053
- package/src/components/Debug/UIStyleGuide.tsx +0 -159
- package/src/components/Debug/collection-views/CardViewDemo.tsx +0 -87
- package/src/components/Debug/collection-views/CollectionTableDemo.tsx +0 -107
- package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +0 -89
- package/src/components/Debug/collection-views/index.ts +0 -3
- package/src/components/Debug/collection-views/sample_data.ts +0 -44
- package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +0 -387
- package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +0 -1100
- package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +0 -131
- package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +0 -288
- package/src/components/Debug/crm-dashboard/RecentActivity.tsx +0 -213
- package/src/components/Debug/crm-dashboard/TaskFilters.tsx +0 -182
- package/src/components/Debug/crm-dashboard/TaskTable.tsx +0 -425
- package/src/components/Debug/crm-dashboard/TasksView.tsx +0 -234
- package/src/components/ErrorTooltip.tsx +0 -12
- package/src/components/ErrorView.tsx +0 -94
- package/src/components/LanguageToggle.tsx +0 -59
- package/src/components/LoginView/LoginView.tsx +0 -1235
- package/src/components/LoginView/index.ts +0 -2
- package/src/components/LoginView/oauth-redirect-flow.ts +0 -184
- package/src/components/NotFoundPage.tsx +0 -27
- package/src/components/RebaseAuth.tsx +0 -27
- package/src/components/RebaseLogo.tsx +0 -88
- package/src/components/SchemaDriftBanner.tsx +0 -102
- package/src/components/UnsavedChangesDialog.tsx +0 -46
- package/src/components/UserDisplay.tsx +0 -54
- package/src/components/UserSelectPopover.tsx +0 -399
- package/src/components/UserSettingsView.tsx +0 -318
- package/src/components/common/index.ts +0 -6
- package/src/components/common/table_height.tsx +0 -21
- package/src/components/common/types.tsx +0 -66
- package/src/components/common/useColumnsIds.tsx +0 -261
- package/src/components/common/useDataTableController.tsx +0 -571
- package/src/components/common/useDebouncedData.ts +0 -49
- package/src/components/common/useScrollRestoration.tsx +0 -68
- package/src/components/index.tsx +0 -26
- package/src/contexts/AdminModeController.tsx +0 -11
- package/src/contexts/AnalyticsContext.tsx +0 -4
- package/src/contexts/AuthControllerContext.tsx +0 -4
- package/src/contexts/CollectionResolverContext.tsx +0 -24
- package/src/contexts/CollectionScopeContext.tsx +0 -68
- package/src/contexts/ComponentOverrideContext.tsx +0 -81
- package/src/contexts/CustomizationControllerContext.tsx +0 -4
- package/src/contexts/DataDriverContext.tsx +0 -4
- package/src/contexts/DataSourcesContext.tsx +0 -36
- package/src/contexts/DatabaseAdminContext.tsx +0 -4
- package/src/contexts/DialogsProvider.tsx +0 -56
- package/src/contexts/EffectiveRoleController.tsx +0 -12
- package/src/contexts/ModeController.tsx +0 -11
- package/src/contexts/RebaseClientInstanceContext.tsx +0 -7
- package/src/contexts/RebaseDataContext.tsx +0 -4
- package/src/contexts/SnackbarProvider.tsx +0 -15
- package/src/contexts/StorageSourceContext.tsx +0 -4
- package/src/contexts/StorageSourcesContext.tsx +0 -30
- package/src/contexts/UserConfigurationPersistenceContext.tsx +0 -4
- package/src/contexts/index.ts +0 -18
- package/src/core/PluginLifecycleManager.tsx +0 -96
- package/src/core/PluginProviderStack.tsx +0 -40
- package/src/core/Rebase.tsx +0 -488
- package/src/core/RebaseProps.tsx +0 -333
- package/src/core/RebaseRouter.tsx +0 -18
- package/src/core/RebaseRoutes.tsx +0 -24
- package/src/core/index.tsx +0 -5
- package/src/hooks/ApiConfigContext.tsx +0 -83
- package/src/hooks/data/collectionQuery.ts +0 -55
- package/src/hooks/data/delete.ts +0 -56
- package/src/hooks/data/save.ts +0 -78
- package/src/hooks/data/useCollection.tsx +0 -183
- package/src/hooks/data/useData.tsx +0 -18
- package/src/hooks/data/useDataOrder.ts +0 -26
- package/src/hooks/data/useFetch.tsx +0 -156
- package/src/hooks/data/useRelationSelector.tsx +0 -280
- package/src/hooks/index.tsx +0 -45
- package/src/hooks/useAdminModeController.tsx +0 -23
- package/src/hooks/useAnalyticsController.tsx +0 -8
- package/src/hooks/useAuthController.tsx +0 -15
- package/src/hooks/useAuthSubscription.ts +0 -130
- package/src/hooks/useBackendStorageSource.ts +0 -295
- package/src/hooks/useBridgeRegistration.tsx +0 -32
- package/src/hooks/useBrowserTitleAndIcon.tsx +0 -23
- package/src/hooks/useBuildAdminModeController.tsx +0 -29
- package/src/hooks/useBuildEffectiveRoleController.tsx +0 -30
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +0 -83
- package/src/hooks/useBuildModeController.tsx +0 -73
- package/src/hooks/useClipboard.tsx +0 -158
- package/src/hooks/useCollapsedGroups.ts +0 -116
- package/src/hooks/useComponentOverride.tsx +0 -59
- package/src/hooks/useCustomizationController.tsx +0 -14
- package/src/hooks/useDialogsController.tsx +0 -15
- package/src/hooks/useEffectiveRoleController.tsx +0 -10
- package/src/hooks/useLargeLayout.tsx +0 -65
- package/src/hooks/useModeController.tsx +0 -23
- package/src/hooks/useNavigationBlocker.tsx +0 -108
- package/src/hooks/usePermissions.ts +0 -45
- package/src/hooks/useRebaseClient.tsx +0 -10
- package/src/hooks/useRebaseContext.tsx +0 -84
- package/src/hooks/useRebaseRegistry.tsx +0 -91
- package/src/hooks/useResolvedComponent.tsx +0 -195
- package/src/hooks/useSlot.tsx +0 -64
- package/src/hooks/useSnackbarController.tsx +0 -80
- package/src/hooks/useStorageSource.tsx +0 -11
- package/src/hooks/useStudioBridge.tsx +0 -258
- package/src/hooks/useTranslation.ts +0 -38
- package/src/hooks/useUnsavedChangesDialog.tsx +0 -67
- package/src/hooks/useUserConfigurationPersistence.tsx +0 -11
- package/src/i18n/RebaseI18nProvider.tsx +0 -159
- package/src/index.ts +0 -32
- package/src/internal/common.tsx +0 -28
- package/src/internal/useRestoreScroll.tsx +0 -69
- package/src/locales/de.ts +0 -941
- package/src/locales/en.ts +0 -1021
- package/src/locales/es.ts +0 -962
- package/src/locales/fr.ts +0 -941
- package/src/locales/hi.ts +0 -941
- package/src/locales/it.ts +0 -941
- package/src/locales/pt.ts +0 -949
- package/src/util/constants.ts +0 -7
- package/src/util/createFormexStub.tsx +0 -66
- package/src/util/entity_cache.ts +0 -242
- package/src/util/enums.ts +0 -34
- package/src/util/icon_list.ts +0 -22
- package/src/util/icons.tsx +0 -129
- package/src/util/index.ts +0 -13
- package/src/util/local_storage.ts +0 -66
- package/src/util/previews.ts +0 -127
- package/src/util/useStorageUploadController.tsx +0 -354
- package/src/vitePlugin.ts +0 -215
- /package/dist/{components/Debug → debug}/UIReferenceView.d.ts +0 -0
- /package/dist/{components/Debug → debug}/UIStyleGuide.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/CardViewDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/CollectionTableDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/KanbanBoardDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/index.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/sample_data.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/CalendarWidget.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/CrmDashboardDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/DashboardMetrics.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/PipelineOverview.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/RecentActivity.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TaskFilters.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TaskTable.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TasksView.d.ts +0 -0
|
@@ -1,318 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { Property, Entity } from "@rebasepro/types";
|
|
2
|
-
import { CollectionSize, SelectedCellProps } from "@rebasepro/cms-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 };
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import type { Property, MapProperty, Properties, RelationProperty } from "@rebasepro/types";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
-
;
|
|
4
|
-
import { getChildViewDeclaringProperties, getChildViewRelationPropertyKeys, getSubcollections } from "@rebasepro/common";
|
|
5
|
-
import type { AdminCollection } from "@rebasepro/cms-types";
|
|
6
|
-
import { getPropertyInPath } from "../../collections/property-path";
|
|
7
|
-
import { isDisabled } from "../../collections/property_presentation";
|
|
8
|
-
|
|
9
|
-
export type PropertyColumnConfig = {
|
|
10
|
-
key: string;
|
|
11
|
-
disabled: boolean;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export function getSubcollectionColumnId(collection: AdminCollection<any>) {
|
|
15
|
-
return `subcollection:${collection.slug}`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* The jump-to-tab columns a collection table does not need, because the relation
|
|
20
|
-
* behind them already has a column of its own.
|
|
21
|
-
*
|
|
22
|
-
* Every child view gets a 200px button column that opens its tab. For a relation
|
|
23
|
-
* declared in `relations` that button is the relation's only presence in the
|
|
24
|
-
* table. For one declared as a property it is the second: the property's own
|
|
25
|
-
* column is already there, hydrated by the list fetch's `include: ["*"]`, showing
|
|
26
|
-
* the child rows themselves — and carrying the *same heading*, because the tab
|
|
27
|
-
* takes its name from the declaring property. Two columns called "Applications",
|
|
28
|
-
* one of them a button, and nothing in the header to tell them apart.
|
|
29
|
-
*
|
|
30
|
-
* The property column wins: it shows what the children are, and each chip in it
|
|
31
|
-
* opens one. The tab stays reachable by opening the record, which is what the
|
|
32
|
-
* rest of the table's rows do anyway.
|
|
33
|
-
*
|
|
34
|
-
* Unless the author hid that column — `hideFromCollection` on the property, or a
|
|
35
|
-
* `propertiesOrder` that omits it, is a statement about the column and not about
|
|
36
|
-
* the relation, so the button comes back rather than the relation dropping out
|
|
37
|
-
* of the table altogether.
|
|
38
|
-
*/
|
|
39
|
-
export function getRedundantChildViewColumnIds<M extends Record<string, any>>(collection: AdminCollection<M>): Set<string> {
|
|
40
|
-
const redundant = new Set<string>();
|
|
41
|
-
const properties = collection.properties ?? {};
|
|
42
|
-
const order = collection.propertiesOrder as string[] | undefined;
|
|
43
|
-
|
|
44
|
-
for (const [viewKey, propertyKey] of getChildViewDeclaringProperties(collection)) {
|
|
45
|
-
const property = properties[propertyKey] as Property | undefined;
|
|
46
|
-
if (!property || property.admin?.hideFromCollection) continue;
|
|
47
|
-
if (order && !order.includes(propertyKey)) continue;
|
|
48
|
-
redundant.add(`subcollection:${viewKey}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return redundant;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function useColumnIds<M extends Record<string, any>>(collection: AdminCollection<M>, includeSubcollections: boolean): PropertyColumnConfig[] {
|
|
55
|
-
return useMemo(() => {
|
|
56
|
-
if (collection.propertiesOrder) {
|
|
57
|
-
return hideAndExpandKeys(collection, collection.propertiesOrder);
|
|
58
|
-
}
|
|
59
|
-
return getDefaultColumnKeys(collection, includeSubcollections);
|
|
60
|
-
}, [collection, includeSubcollections]);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function hideAndExpandKeys<M extends Record<string, any>>(collection: AdminCollection<M>, keys: string[]): PropertyColumnConfig[] {
|
|
64
|
-
|
|
65
|
-
// First, figure out which spread map roots have individual child keys in the order
|
|
66
|
-
// If so, we should NOT auto-expand them - just use the explicit child keys
|
|
67
|
-
const rootsWithExplicitChildren = new Set<string>();
|
|
68
|
-
for (const key of keys) {
|
|
69
|
-
if (key.includes(".")) {
|
|
70
|
-
const rootKey = key.split(".")[0];
|
|
71
|
-
const rootProperty = collection.properties[rootKey];
|
|
72
|
-
if (rootProperty && rootProperty.type === "map" && rootProperty.admin?.spreadChildren && rootProperty.properties) {
|
|
73
|
-
rootsWithExplicitChildren.add(rootKey);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Track processed keys to avoid duplicates
|
|
79
|
-
const processedPropertyKeys = new Set<string>();
|
|
80
|
-
|
|
81
|
-
// Both column paths funnel through here, including a `propertiesOrder` that
|
|
82
|
-
// was saved back with the button column in it.
|
|
83
|
-
const redundantChildViewColumns = getRedundantChildViewColumnIds(collection);
|
|
84
|
-
|
|
85
|
-
const result = keys.flatMap((key) => {
|
|
86
|
-
// Skip if already processed (handles duplicates in propertiesOrder)
|
|
87
|
-
if (processedPropertyKeys.has(key)) return [null];
|
|
88
|
-
if (redundantChildViewColumns.has(key)) return [null];
|
|
89
|
-
|
|
90
|
-
// Check if it's a top-level property
|
|
91
|
-
const property = collection.properties[key];
|
|
92
|
-
if (property) {
|
|
93
|
-
processedPropertyKeys.add(key);
|
|
94
|
-
if (property.admin?.hideFromCollection)
|
|
95
|
-
return [null];
|
|
96
|
-
if (property.admin?.disabled && typeof property.admin?.disabled === "object" && property.admin?.disabled.hidden)
|
|
97
|
-
return [null];
|
|
98
|
-
|
|
99
|
-
if (property.type === "map" && property.admin?.spreadChildren && property.properties) {
|
|
100
|
-
// Check if this spread map has explicit child keys in propertiesOrder
|
|
101
|
-
if (rootsWithExplicitChildren.has(key)) {
|
|
102
|
-
// DON'T auto-expand - the children are explicitly listed elsewhere
|
|
103
|
-
return [null];
|
|
104
|
-
}
|
|
105
|
-
// Auto-expand all children
|
|
106
|
-
const childConfigs = getColumnKeysForProperty(property, key);
|
|
107
|
-
childConfigs.forEach(c => processedPropertyKeys.add(c.key));
|
|
108
|
-
return childConfigs;
|
|
109
|
-
}
|
|
110
|
-
return [{
|
|
111
|
-
key,
|
|
112
|
-
disabled: isDisabled(property) || Boolean(property.admin?.readOnly)
|
|
113
|
-
}];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Check if it's a nested key like "data.mode" (for spread map properties)
|
|
117
|
-
if (key.includes(".")) {
|
|
118
|
-
const rootKey = key.split(".")[0];
|
|
119
|
-
const rootProperty = collection.properties[rootKey];
|
|
120
|
-
|
|
121
|
-
if (rootProperty && rootProperty.type === "map" && rootProperty.properties) {
|
|
122
|
-
const nestedProperty = getPropertyInPath(collection.properties, key) as Property | undefined;
|
|
123
|
-
if (nestedProperty) {
|
|
124
|
-
processedPropertyKeys.add(key);
|
|
125
|
-
// Mark root as seen
|
|
126
|
-
processedPropertyKeys.add(rootKey);
|
|
127
|
-
|
|
128
|
-
if (nestedProperty.admin?.hideFromCollection)
|
|
129
|
-
return [null];
|
|
130
|
-
if (nestedProperty.admin?.disabled && typeof nestedProperty.admin?.disabled === "object" && nestedProperty.admin?.disabled.hidden)
|
|
131
|
-
return [null];
|
|
132
|
-
|
|
133
|
-
return [{
|
|
134
|
-
key,
|
|
135
|
-
disabled: isDisabled(rootProperty) || Boolean(rootProperty.admin?.readOnly) ||
|
|
136
|
-
isDisabled(nestedProperty) || Boolean(nestedProperty.admin?.readOnly)
|
|
137
|
-
}];
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Check additional fields
|
|
143
|
-
const additionalField = collection.additionalFields?.find(field => field.key === key);
|
|
144
|
-
if (additionalField) {
|
|
145
|
-
return [{
|
|
146
|
-
key,
|
|
147
|
-
disabled: true
|
|
148
|
-
}];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Check subcollections
|
|
152
|
-
const subcollections = getSubcollections(collection);
|
|
153
|
-
if (subcollections) {
|
|
154
|
-
const subCollection = subcollections
|
|
155
|
-
.find(subCol => getSubcollectionColumnId(subCol) === key);
|
|
156
|
-
if (subCollection) {
|
|
157
|
-
return [{
|
|
158
|
-
key,
|
|
159
|
-
disabled: true
|
|
160
|
-
}];
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return [null];
|
|
165
|
-
}).filter(Boolean) as PropertyColumnConfig[];
|
|
166
|
-
|
|
167
|
-
// Add any missing properties that weren't in propertiesOrder
|
|
168
|
-
// This ensures properties NEVER disappear
|
|
169
|
-
for (const propKey of Object.keys(collection.properties)) {
|
|
170
|
-
// Skip if already processed
|
|
171
|
-
if (processedPropertyKeys.has(propKey)) continue;
|
|
172
|
-
|
|
173
|
-
const property = collection.properties[propKey];
|
|
174
|
-
if (!property) continue;
|
|
175
|
-
if (property.admin?.hideFromCollection) continue;
|
|
176
|
-
if (property.admin?.disabled && typeof property.admin?.disabled === "object" && property.admin?.disabled.hidden) continue;
|
|
177
|
-
|
|
178
|
-
if (property.type === "map" && property.admin?.spreadChildren && property.properties) {
|
|
179
|
-
// For spread maps, add all children that weren't already added
|
|
180
|
-
const allChildConfigs = getColumnKeysForProperty(property as MapProperty, propKey);
|
|
181
|
-
for (const childConfig of allChildConfigs) {
|
|
182
|
-
if (!processedPropertyKeys.has(childConfig.key)) {
|
|
183
|
-
result.push(childConfig);
|
|
184
|
-
processedPropertyKeys.add(childConfig.key);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
} else {
|
|
188
|
-
result.push({
|
|
189
|
-
key: propKey,
|
|
190
|
-
disabled: isDisabled(property) || Boolean(property.admin?.readOnly)
|
|
191
|
-
});
|
|
192
|
-
processedPropertyKeys.add(propKey);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return result;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function getDefaultColumnKeys<M extends Record<string, any> = any>(collection: AdminCollection<M>, includeSubCollections: boolean) {
|
|
200
|
-
const propertyKeys = Object.keys(collection.properties);
|
|
201
|
-
|
|
202
|
-
const additionalFields = collection.additionalFields ?? [];
|
|
203
|
-
const subCollections: AdminCollection[] = getSubcollections(collection) ?? [];
|
|
204
|
-
|
|
205
|
-
const columnIds: string[] = [
|
|
206
|
-
...propertyKeys,
|
|
207
|
-
// Filter out additional fields whose key already exists in propertyKeys to avoid duplicate column keys
|
|
208
|
-
...additionalFields.filter((field) => !propertyKeys.includes(field.key)).map((field) => field.key)
|
|
209
|
-
];
|
|
210
|
-
|
|
211
|
-
if (includeSubCollections) {
|
|
212
|
-
const subCollectionIds = subCollections
|
|
213
|
-
.map((collection) => getSubcollectionColumnId(collection));
|
|
214
|
-
columnIds.push(...subCollectionIds.filter((subColId) => !columnIds.includes(subColId)));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return hideAndExpandKeys(collection, columnIds);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export function getColumnKeysForProperty(property: Property, key: string, disabled?: boolean): PropertyColumnConfig[] {
|
|
221
|
-
// A key with no property behind it describes no column. Callers walk whole
|
|
222
|
-
// property maps here to build a table's columns, so reading `.type` off a
|
|
223
|
-
// hole threw away every other column with it.
|
|
224
|
-
if (!property) return [];
|
|
225
|
-
if (property.type === "map" && property.admin?.spreadChildren && property.properties) {
|
|
226
|
-
return Object.entries(property.properties)
|
|
227
|
-
.flatMap(([childKey, childProperty]) => getColumnKeysForProperty(
|
|
228
|
-
childProperty as Readonly<Property>,
|
|
229
|
-
`${key}.${childKey}`,
|
|
230
|
-
disabled || isDisabled(property) || Boolean(property.admin?.readOnly))
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
return [{
|
|
234
|
-
key,
|
|
235
|
-
disabled: disabled || isDisabled(property) || Boolean(property.admin?.readOnly)
|
|
236
|
-
}];
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export function getFormFieldKeys(collection: AdminCollection): string[] {
|
|
240
|
-
const properties = collection.properties ?? {};
|
|
241
|
-
// A many-relation the entity view already lists as a tab is not an input:
|
|
242
|
-
// the tab is the treatment, and rendering the same declaration again as a
|
|
243
|
-
// picker asked the author to select a collection's own children from a
|
|
244
|
-
// dropdown. `renderInForm` is how a project that wants the picker asks for
|
|
245
|
-
// it back — opt in, because the tab is the default.
|
|
246
|
-
const consumedByChildView = getChildViewRelationPropertyKeys(collection);
|
|
247
|
-
const propertyKeys = Object.keys(properties).filter(key => {
|
|
248
|
-
if (!consumedByChildView.has(key)) return true;
|
|
249
|
-
return (properties[key] as RelationProperty).admin?.renderInForm === true;
|
|
250
|
-
});
|
|
251
|
-
const additionalFields = collection.additionalFields ?? [];
|
|
252
|
-
const allKeys = [
|
|
253
|
-
...propertyKeys,
|
|
254
|
-
...additionalFields.map((field) => field.key)
|
|
255
|
-
];
|
|
256
|
-
|
|
257
|
-
if (collection.propertiesOrder) {
|
|
258
|
-
return collection.propertiesOrder.filter(key => allKeys.includes(key));
|
|
259
|
-
}
|
|
260
|
-
return allKeys;
|
|
261
|
-
}
|