ding-react-admin 1.0.4 → 2.0.4
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 -21
- package/README.md +254 -201
- package/dist/index.css +2 -0
- package/dist/index.js +2073 -1202
- package/dist/src/components/NavMenuLabel.d.ts +8 -0
- package/dist/src/components/NavMenuLabel.d.ts.map +1 -0
- package/dist/src/components/NavMenuSearch.d.ts +9 -0
- package/dist/src/components/NavMenuSearch.d.ts.map +1 -0
- package/dist/src/components/ScrollableArea.d.ts +10 -0
- package/dist/src/components/ScrollableArea.d.ts.map +1 -0
- package/dist/src/components/index.d.ts +3 -0
- package/dist/src/components/index.d.ts.map +1 -1
- package/dist/src/context/AuthProvider.d.ts +2 -0
- package/dist/src/context/AuthProvider.d.ts.map +1 -1
- package/dist/src/crud/FormGlobalErrorsAlert.d.ts +5 -0
- package/dist/src/crud/FormGlobalErrorsAlert.d.ts.map +1 -0
- package/dist/src/crud/InlineFormSet.d.ts +2 -17
- package/dist/src/crud/InlineFormSet.d.ts.map +1 -1
- package/dist/src/crud/ResourceForm.d.ts +2 -10
- package/dist/src/crud/ResourceForm.d.ts.map +1 -1
- package/dist/src/crud/ResourceFormModal.d.ts +7 -1
- package/dist/src/crud/ResourceFormModal.d.ts.map +1 -1
- package/dist/src/crud/ResourceList.d.ts.map +1 -1
- package/dist/src/crud/ResourceRecordForm.d.ts +27 -0
- package/dist/src/crud/ResourceRecordForm.d.ts.map +1 -0
- package/dist/src/crud/columns/ImageColumn.d.ts +14 -0
- package/dist/src/crud/columns/ImageColumn.d.ts.map +1 -0
- package/dist/src/crud/context/InlineFieldsRegistry.d.ts +15 -0
- package/dist/src/crud/context/InlineFieldsRegistry.d.ts.map +1 -0
- package/dist/src/crud/context/PayloadFieldsContext.d.ts +18 -0
- package/dist/src/crud/context/PayloadFieldsContext.d.ts.map +1 -0
- package/dist/src/crud/fields/FileField.d.ts +12 -0
- package/dist/src/crud/fields/FileField.d.ts.map +1 -0
- package/dist/src/crud/fields/ImageField.d.ts +14 -0
- package/dist/src/crud/fields/ImageField.d.ts.map +1 -0
- package/dist/src/crud/fields/ReferenceField.d.ts +12 -2
- package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
- package/dist/src/crud/fields/ReferenceInputActions.d.ts +16 -0
- package/dist/src/crud/fields/ReferenceInputActions.d.ts.map +1 -0
- package/dist/src/crud/fields/ReferenceManyField.d.ts +10 -1
- package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
- package/dist/src/crud/fields/uploadFieldUtils.d.ts +5 -0
- package/dist/src/crud/fields/uploadFieldUtils.d.ts.map +1 -0
- package/dist/src/crud/fields/useUploadPreviewUrl.d.ts +4 -0
- package/dist/src/crud/fields/useUploadPreviewUrl.d.ts.map +1 -0
- package/dist/src/crud/filters/ReferenceFilter.d.ts +1 -1
- package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -1
- package/dist/src/crud/index.d.ts +27 -9
- package/dist/src/crud/index.d.ts.map +1 -1
- package/dist/src/crud/types.d.ts +21 -7
- package/dist/src/crud/types.d.ts.map +1 -1
- package/dist/src/crud/utils/buildFormPayload.d.ts +6 -0
- package/dist/src/crud/utils/buildFormPayload.d.ts.map +1 -0
- package/dist/src/crud/utils/buildInlineRowsPayload.d.ts +6 -0
- package/dist/src/crud/utils/buildInlineRowsPayload.d.ts.map +1 -0
- package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts +5 -0
- package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts.map +1 -0
- package/dist/src/crud/utils/choiceSelectionUtils.d.ts +10 -0
- package/dist/src/crud/utils/choiceSelectionUtils.d.ts.map +1 -0
- package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts +2 -0
- package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts.map +1 -0
- package/dist/src/crud/utils/formErrors.d.ts +18 -7
- package/dist/src/crud/utils/formErrors.d.ts.map +1 -1
- package/dist/src/crud/utils/formErrors.test.d.ts +2 -0
- package/dist/src/crud/utils/formErrors.test.d.ts.map +1 -0
- package/dist/src/crud/utils/getFormValue.d.ts +3 -0
- package/dist/src/crud/utils/getFormValue.d.ts.map +1 -0
- package/dist/src/crud/utils/hasUploadValues.d.ts +3 -0
- package/dist/src/crud/utils/hasUploadValues.d.ts.map +1 -0
- package/dist/src/crud/utils/nestedFieldPath.d.ts +3 -0
- package/dist/src/crud/utils/nestedFieldPath.d.ts.map +1 -0
- package/dist/src/crud/utils/prepareFormSubmitBody.d.ts +8 -0
- package/dist/src/crud/utils/prepareFormSubmitBody.d.ts.map +1 -0
- package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts +4 -0
- package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts.map +1 -0
- package/dist/src/crud/utils/setFormValue.d.ts +3 -0
- package/dist/src/crud/utils/setFormValue.d.ts.map +1 -0
- package/dist/src/crud/utils/toFormData.d.ts +13 -0
- package/dist/src/crud/utils/toFormData.d.ts.map +1 -0
- package/dist/src/crud/utils/uploadReferenceUtils.d.ts +5 -0
- package/dist/src/crud/utils/uploadReferenceUtils.d.ts.map +1 -0
- package/dist/src/crud/utils/useChoices.d.ts +27 -1
- package/dist/src/crud/utils/useChoices.d.ts.map +1 -1
- package/dist/src/crud/utils/useFormRecord.d.ts +44 -0
- package/dist/src/crud/utils/useFormRecord.d.ts.map +1 -0
- package/dist/src/data/createRestResourceHandlers.d.ts +5 -5
- package/dist/src/data/createRestResourceHandlers.d.ts.map +1 -1
- package/dist/src/data/dataProviderTypes.d.ts +6 -5
- package/dist/src/data/dataProviderTypes.d.ts.map +1 -1
- package/dist/src/data/parseFormErrorHelpers.d.ts +23 -6
- package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -1
- package/dist/src/data/resourceHandlers.d.ts +1 -1
- package/dist/src/data/resourceHandlers.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/layouts/AdminLayout.d.ts +1 -1
- package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
- package/dist/src/layouts/AuthPageLayout.d.ts.map +1 -1
- package/dist/src/layouts/navFilter.d.ts +5 -0
- package/dist/src/layouts/navFilter.d.ts.map +1 -0
- package/dist/src/layouts/navMenuItems.d.ts +6 -0
- package/dist/src/layouts/navMenuItems.d.ts.map +1 -0
- package/dist/src/types.d.ts +12 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +79 -59
- package/src/app/AdminApp.tsx +34 -34
- package/src/components/AuthAlternateLink.tsx +23 -23
- package/src/components/DensitySwitch.tsx +31 -31
- package/src/components/NavMenuLabel.tsx +26 -0
- package/src/components/NavMenuSearch.tsx +76 -0
- package/src/components/ScrollableArea.tsx +46 -0
- package/src/components/ThemeSwitch.tsx +28 -28
- package/src/components/ThemeToolbar.tsx +34 -34
- package/src/components/index.ts +7 -4
- package/src/components/navMenu.css +21 -0
- package/src/components/scrollable.css +28 -0
- package/src/context/AppThemeProvider.tsx +128 -128
- package/src/context/AuthProvider.tsx +89 -77
- package/src/context/DataProvider.tsx +30 -30
- package/src/context/PermissionsProvider.tsx +57 -57
- package/src/crud/FilterBar.tsx +36 -36
- package/src/crud/FormGlobalErrorsAlert.tsx +36 -0
- package/src/crud/FormSteps.tsx +135 -135
- package/src/crud/FormTabs.tsx +126 -126
- package/src/crud/InlineFormSet.tsx +162 -219
- package/src/crud/ListActionsBar.tsx +106 -106
- package/src/crud/ResourceForm.tsx +83 -166
- package/src/crud/ResourceFormModal.tsx +75 -133
- package/src/crud/ResourceList.tsx +1 -0
- package/src/crud/ResourceRecordForm.tsx +166 -0
- package/src/crud/columns/BooleanColumn.tsx +32 -32
- package/src/crud/columns/CustomColumn.tsx +33 -33
- package/src/crud/columns/DateColumn.tsx +28 -28
- package/src/crud/columns/ImageColumn.tsx +58 -0
- package/src/crud/columns/NumberColumn.tsx +31 -31
- package/src/crud/columns/ReferenceColumn.tsx +85 -85
- package/src/crud/columns/ReferenceManyColumn.tsx +67 -67
- package/src/crud/columns/TextColumn.tsx +38 -38
- package/src/crud/context/FilterContext.tsx +71 -71
- package/src/crud/context/FormContext.tsx +39 -39
- package/src/crud/context/FormSectionContext.tsx +22 -22
- package/src/crud/context/InlineFieldsRegistry.tsx +61 -0
- package/src/crud/context/ListContext.tsx +93 -93
- package/src/crud/context/PayloadFieldsContext.tsx +62 -0
- package/src/crud/fields/BooleanField.tsx +38 -38
- package/src/crud/fields/DateField.tsx +53 -53
- package/src/crud/fields/FieldWrapper.tsx +87 -87
- package/src/crud/fields/FileField.tsx +123 -0
- package/src/crud/fields/ImageField.tsx +120 -0
- package/src/crud/fields/NumberField.tsx +52 -52
- package/src/crud/fields/PasswordField.tsx +99 -99
- package/src/crud/fields/ReferenceField.tsx +237 -96
- package/src/crud/fields/ReferenceInputActions.tsx +106 -0
- package/src/crud/fields/ReferenceManyField.tsx +200 -76
- package/src/crud/fields/SelectField.tsx +48 -48
- package/src/crud/fields/TextField.tsx +46 -46
- package/src/crud/fields/uploadFieldUtils.ts +21 -0
- package/src/crud/fields/useUploadPreviewUrl.ts +20 -0
- package/src/crud/filters/BooleanFilter.tsx +38 -38
- package/src/crud/filters/DateFilter.tsx +35 -35
- package/src/crud/filters/NumberFilter.tsx +33 -33
- package/src/crud/filters/ReferenceFilter.tsx +192 -173
- package/src/crud/filters/SelectFilter.tsx +44 -44
- package/src/crud/filters/TextFilter.tsx +36 -36
- package/src/crud/index.ts +99 -90
- package/src/crud/types.ts +146 -132
- package/src/crud/utils/buildFormPayload.ts +24 -0
- package/src/crud/utils/buildInlineRowsPayload.ts +37 -0
- package/src/crud/utils/buildResourceFormSubmitBody.ts +29 -0
- package/src/crud/utils/choiceSelectionUtils.test.ts +45 -0
- package/src/crud/utils/choiceSelectionUtils.ts +116 -0
- package/src/crud/utils/formErrors.test.ts +168 -0
- package/src/crud/utils/formErrors.ts +138 -38
- package/src/crud/utils/formSectionErrors.ts +85 -85
- package/src/crud/utils/{getByPath.ts → getFormValue.ts} +13 -13
- package/src/crud/utils/hasUploadValues.ts +9 -0
- package/src/crud/utils/nestedFieldPath.ts +8 -0
- package/src/crud/utils/prepareFormSubmitBody.ts +22 -0
- package/src/crud/utils/referenceSelectNotFoundContent.tsx +7 -0
- package/src/crud/utils/{setByPath.ts → setFormValue.ts} +18 -18
- package/src/crud/utils/sortQueryParam.ts +37 -37
- package/src/crud/utils/toFormData.ts +79 -0
- package/src/crud/utils/uploadReferenceUtils.ts +44 -0
- package/src/crud/utils/useAbortableEffect.ts +17 -17
- package/src/crud/utils/useChoices.ts +330 -182
- package/src/crud/utils/useFormRecord.ts +179 -0
- package/src/crud/utils/useListQueryState.ts +191 -191
- package/src/data/abortError.test.ts +19 -19
- package/src/data/abortError.ts +10 -10
- package/src/data/createMemoryResourceHandlers.ts +78 -78
- package/src/data/createRestResourceHandlers.ts +63 -57
- package/src/data/dataProviderTypes.ts +7 -5
- package/src/data/dataUtils.test.ts +228 -108
- package/src/data/inMemoryList.ts +97 -97
- package/src/data/parseFormErrorHelpers.test.ts +201 -84
- package/src/data/parseFormErrorHelpers.ts +376 -224
- package/src/data/resourceHandlers.ts +1 -1
- package/src/data/sortHelpers.ts +48 -48
- package/src/index.ts +211 -195
- package/src/layouts/AdminLayout.tsx +530 -381
- package/src/layouts/AuthPageLayout.tsx +87 -81
- package/src/layouts/navFilter.ts +49 -0
- package/src/layouts/navMenuItems.tsx +44 -0
- package/src/pages/LoginPage.tsx +80 -80
- package/src/pages/PlaceholderPage.tsx +9 -9
- package/src/permissions/resourcePermissions.ts +47 -47
- package/src/router/createAdminRouter.tsx +64 -64
- package/src/router/guards.tsx +63 -63
- package/src/router/routeAccess.ts +89 -89
- package/src/types.ts +10 -0
- package/src/vite-env.d.ts +1 -1
- package/dist/src/crud/context/SubmitFieldsContext.d.ts +0 -18
- package/dist/src/crud/context/SubmitFieldsContext.d.ts.map +0 -1
- package/dist/src/crud/utils/getByPath.d.ts +0 -3
- package/dist/src/crud/utils/getByPath.d.ts.map +0 -1
- package/dist/src/crud/utils/inlineArrayName.d.ts +0 -3
- package/dist/src/crud/utils/inlineArrayName.d.ts.map +0 -1
- package/dist/src/crud/utils/inlineFieldName.d.ts +0 -3
- package/dist/src/crud/utils/inlineFieldName.d.ts.map +0 -1
- package/dist/src/crud/utils/pickBySources.d.ts +0 -6
- package/dist/src/crud/utils/pickBySources.d.ts.map +0 -1
- package/dist/src/crud/utils/setByPath.d.ts +0 -3
- package/dist/src/crud/utils/setByPath.d.ts.map +0 -1
- package/dist/src/crud/utils/useResourceFormData.d.ts +0 -47
- package/dist/src/crud/utils/useResourceFormData.d.ts.map +0 -1
- package/src/crud/context/SubmitFieldsContext.tsx +0 -62
- package/src/crud/utils/inlineArrayName.ts +0 -4
- package/src/crud/utils/inlineFieldName.ts +0 -8
- package/src/crud/utils/pickBySources.ts +0 -24
- package/src/crud/utils/useResourceFormData.ts +0 -231
package/src/router/guards.tsx
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
2
|
-
import { Navigate } from "react-router-dom";
|
|
3
|
-
import { useAuth } from "../context/AuthProvider";
|
|
4
|
-
import { usePermissions } from "../context/PermissionsProvider";
|
|
5
|
-
|
|
6
|
-
export function Guard({
|
|
7
|
-
when,
|
|
8
|
-
redirect,
|
|
9
|
-
children,
|
|
10
|
-
}: {
|
|
11
|
-
when: boolean;
|
|
12
|
-
redirect: string;
|
|
13
|
-
children: ReactElement;
|
|
14
|
-
}) {
|
|
15
|
-
return when ? children : <Navigate to={redirect} replace />;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function Protected({
|
|
19
|
-
children,
|
|
20
|
-
redirectTo = "/login",
|
|
21
|
-
}: {
|
|
22
|
-
children: ReactElement;
|
|
23
|
-
redirectTo?: string;
|
|
24
|
-
}) {
|
|
25
|
-
const { isAuthenticated } = useAuth();
|
|
26
|
-
return (
|
|
27
|
-
<Guard when={isAuthenticated} redirect={redirectTo}>
|
|
28
|
-
{children}
|
|
29
|
-
</Guard>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function GuestOnly({
|
|
34
|
-
children,
|
|
35
|
-
redirectTo = "/",
|
|
36
|
-
}: {
|
|
37
|
-
children: ReactElement;
|
|
38
|
-
redirectTo?: string;
|
|
39
|
-
}) {
|
|
40
|
-
const { isAuthenticated } = useAuth();
|
|
41
|
-
return (
|
|
42
|
-
<Guard when={!isAuthenticated} redirect={redirectTo}>
|
|
43
|
-
{children}
|
|
44
|
-
</Guard>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function RequirePermission({
|
|
49
|
-
permission,
|
|
50
|
-
redirect,
|
|
51
|
-
children,
|
|
52
|
-
}: {
|
|
53
|
-
permission: string;
|
|
54
|
-
redirect: string;
|
|
55
|
-
children: ReactElement;
|
|
56
|
-
}) {
|
|
57
|
-
const can = usePermissions();
|
|
58
|
-
return (
|
|
59
|
-
<Guard when={can(permission)} redirect={redirect}>
|
|
60
|
-
{children}
|
|
61
|
-
</Guard>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Navigate } from "react-router-dom";
|
|
3
|
+
import { useAuth } from "../context/AuthProvider";
|
|
4
|
+
import { usePermissions } from "../context/PermissionsProvider";
|
|
5
|
+
|
|
6
|
+
export function Guard({
|
|
7
|
+
when,
|
|
8
|
+
redirect,
|
|
9
|
+
children,
|
|
10
|
+
}: {
|
|
11
|
+
when: boolean;
|
|
12
|
+
redirect: string;
|
|
13
|
+
children: ReactElement;
|
|
14
|
+
}) {
|
|
15
|
+
return when ? children : <Navigate to={redirect} replace />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Protected({
|
|
19
|
+
children,
|
|
20
|
+
redirectTo = "/login",
|
|
21
|
+
}: {
|
|
22
|
+
children: ReactElement;
|
|
23
|
+
redirectTo?: string;
|
|
24
|
+
}) {
|
|
25
|
+
const { isAuthenticated } = useAuth();
|
|
26
|
+
return (
|
|
27
|
+
<Guard when={isAuthenticated} redirect={redirectTo}>
|
|
28
|
+
{children}
|
|
29
|
+
</Guard>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function GuestOnly({
|
|
34
|
+
children,
|
|
35
|
+
redirectTo = "/",
|
|
36
|
+
}: {
|
|
37
|
+
children: ReactElement;
|
|
38
|
+
redirectTo?: string;
|
|
39
|
+
}) {
|
|
40
|
+
const { isAuthenticated } = useAuth();
|
|
41
|
+
return (
|
|
42
|
+
<Guard when={!isAuthenticated} redirect={redirectTo}>
|
|
43
|
+
{children}
|
|
44
|
+
</Guard>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function RequirePermission({
|
|
49
|
+
permission,
|
|
50
|
+
redirect,
|
|
51
|
+
children,
|
|
52
|
+
}: {
|
|
53
|
+
permission: string;
|
|
54
|
+
redirect: string;
|
|
55
|
+
children: ReactElement;
|
|
56
|
+
}) {
|
|
57
|
+
const can = usePermissions();
|
|
58
|
+
return (
|
|
59
|
+
<Guard when={can(permission)} redirect={redirect}>
|
|
60
|
+
{children}
|
|
61
|
+
</Guard>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
import type { RouteObject } from "react-router-dom";
|
|
2
|
-
import type { AdminRouteChild, AuthRedirects, RouteAccess } from "../types";
|
|
3
|
-
|
|
4
|
-
export function getRouteAccess(route: AdminRouteChild): RouteAccess {
|
|
5
|
-
return route.access ?? "protected";
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function partitionAdminRoutes(routes: AdminRouteChild[]) {
|
|
9
|
-
const guest: AdminRouteChild[] = [];
|
|
10
|
-
const publicRoutes: AdminRouteChild[] = [];
|
|
11
|
-
const protectedRoutes: AdminRouteChild[] = [];
|
|
12
|
-
|
|
13
|
-
for (const route of routes) {
|
|
14
|
-
const access = getRouteAccess(route);
|
|
15
|
-
if (access === "guest") guest.push(route);
|
|
16
|
-
else if (access === "public") publicRoutes.push(route);
|
|
17
|
-
else protectedRoutes.push(route);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return { guest, public: publicRoutes, protected: protectedRoutes };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** React Router path segment for a top-level route (`/login` → `login`). */
|
|
24
|
-
export function toRouterSegment(path: string): string {
|
|
25
|
-
return path.replace(/^\/+/, "");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function toAbsolutePath(path: string): string {
|
|
29
|
-
return `/${toRouterSegment(path)}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Resolve redirect targets from declared routes and optional overrides.
|
|
34
|
-
* Throws when protected or guest routes exist but paths cannot be determined.
|
|
35
|
-
*/
|
|
36
|
-
export function deriveAuthPaths(
|
|
37
|
-
routes: AdminRouteChild[],
|
|
38
|
-
redirects?: AuthRedirects,
|
|
39
|
-
): { loginPath: string; homePath: string } {
|
|
40
|
-
const { guest, protected: protectedRoutes } = partitionAdminRoutes(routes);
|
|
41
|
-
const firstGuest = guest.find((r) => "path" in r && r.path);
|
|
42
|
-
const indexProtected = protectedRoutes.find(
|
|
43
|
-
(r) => "index" in r && r.index,
|
|
44
|
-
);
|
|
45
|
-
const firstProtectedPath = protectedRoutes.find(
|
|
46
|
-
(r) => "path" in r && r.path,
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
let loginPath: string | undefined = redirects?.unauthenticated;
|
|
50
|
-
if (!loginPath && firstGuest && "path" in firstGuest && firstGuest.path) {
|
|
51
|
-
loginPath = toAbsolutePath(firstGuest.path);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let homePath: string | undefined = redirects?.afterLogin;
|
|
55
|
-
if (!homePath) {
|
|
56
|
-
if (indexProtected) homePath = "/";
|
|
57
|
-
else if (
|
|
58
|
-
firstProtectedPath &&
|
|
59
|
-
"path" in firstProtectedPath &&
|
|
60
|
-
firstProtectedPath.path
|
|
61
|
-
) {
|
|
62
|
-
homePath = toAbsolutePath(firstProtectedPath.path);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (protectedRoutes.length > 0 && !loginPath) {
|
|
67
|
-
throw new Error(
|
|
68
|
-
'createAdminRouter: protected routes require redirects.unauthenticated or a guest route (access: "guest").',
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (guest.length > 0 && !homePath) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
'createAdminRouter: guest routes require redirects.afterLogin or a protected route (index or path).',
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
loginPath: loginPath ?? "/",
|
|
80
|
-
homePath: homePath ?? "/",
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function toProtectedRouteObject(route: AdminRouteChild): RouteObject {
|
|
85
|
-
if ("index" in route && route.index) {
|
|
86
|
-
return { index: true, element: route.element };
|
|
87
|
-
}
|
|
88
|
-
return { path: route.path!, element: route.element };
|
|
89
|
-
}
|
|
1
|
+
import type { RouteObject } from "react-router-dom";
|
|
2
|
+
import type { AdminRouteChild, AuthRedirects, RouteAccess } from "../types";
|
|
3
|
+
|
|
4
|
+
export function getRouteAccess(route: AdminRouteChild): RouteAccess {
|
|
5
|
+
return route.access ?? "protected";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function partitionAdminRoutes(routes: AdminRouteChild[]) {
|
|
9
|
+
const guest: AdminRouteChild[] = [];
|
|
10
|
+
const publicRoutes: AdminRouteChild[] = [];
|
|
11
|
+
const protectedRoutes: AdminRouteChild[] = [];
|
|
12
|
+
|
|
13
|
+
for (const route of routes) {
|
|
14
|
+
const access = getRouteAccess(route);
|
|
15
|
+
if (access === "guest") guest.push(route);
|
|
16
|
+
else if (access === "public") publicRoutes.push(route);
|
|
17
|
+
else protectedRoutes.push(route);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { guest, public: publicRoutes, protected: protectedRoutes };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** React Router path segment for a top-level route (`/login` → `login`). */
|
|
24
|
+
export function toRouterSegment(path: string): string {
|
|
25
|
+
return path.replace(/^\/+/, "");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function toAbsolutePath(path: string): string {
|
|
29
|
+
return `/${toRouterSegment(path)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve redirect targets from declared routes and optional overrides.
|
|
34
|
+
* Throws when protected or guest routes exist but paths cannot be determined.
|
|
35
|
+
*/
|
|
36
|
+
export function deriveAuthPaths(
|
|
37
|
+
routes: AdminRouteChild[],
|
|
38
|
+
redirects?: AuthRedirects,
|
|
39
|
+
): { loginPath: string; homePath: string } {
|
|
40
|
+
const { guest, protected: protectedRoutes } = partitionAdminRoutes(routes);
|
|
41
|
+
const firstGuest = guest.find((r) => "path" in r && r.path);
|
|
42
|
+
const indexProtected = protectedRoutes.find(
|
|
43
|
+
(r) => "index" in r && r.index,
|
|
44
|
+
);
|
|
45
|
+
const firstProtectedPath = protectedRoutes.find(
|
|
46
|
+
(r) => "path" in r && r.path,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
let loginPath: string | undefined = redirects?.unauthenticated;
|
|
50
|
+
if (!loginPath && firstGuest && "path" in firstGuest && firstGuest.path) {
|
|
51
|
+
loginPath = toAbsolutePath(firstGuest.path);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let homePath: string | undefined = redirects?.afterLogin;
|
|
55
|
+
if (!homePath) {
|
|
56
|
+
if (indexProtected) homePath = "/";
|
|
57
|
+
else if (
|
|
58
|
+
firstProtectedPath &&
|
|
59
|
+
"path" in firstProtectedPath &&
|
|
60
|
+
firstProtectedPath.path
|
|
61
|
+
) {
|
|
62
|
+
homePath = toAbsolutePath(firstProtectedPath.path);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (protectedRoutes.length > 0 && !loginPath) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'createAdminRouter: protected routes require redirects.unauthenticated or a guest route (access: "guest").',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (guest.length > 0 && !homePath) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
'createAdminRouter: guest routes require redirects.afterLogin or a protected route (index or path).',
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
loginPath: loginPath ?? "/",
|
|
80
|
+
homePath: homePath ?? "/",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function toProtectedRouteObject(route: AdminRouteChild): RouteObject {
|
|
85
|
+
if ("index" in route && route.index) {
|
|
86
|
+
return { index: true, element: route.element };
|
|
87
|
+
}
|
|
88
|
+
return { path: route.path!, element: route.element };
|
|
89
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -35,6 +35,11 @@ export type AuthAdapter = {
|
|
|
35
35
|
logout: () => void;
|
|
36
36
|
/** Synchronous read of session (e.g. token). Used after mount and after login/logout. */
|
|
37
37
|
getToken: () => string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Label for the account menu in `AdminLayout` (top-right button).
|
|
40
|
+
* Return `null` when no user is loaded. Defaults to `"User"` when omitted.
|
|
41
|
+
*/
|
|
42
|
+
getUserLabel?: () => string | null;
|
|
38
43
|
};
|
|
39
44
|
|
|
40
45
|
/** How a route participates in auth when using `AdminApp` / `createAdminRouter`. */
|
|
@@ -77,6 +82,11 @@ export type AdminLayoutProps = {
|
|
|
77
82
|
loginPath?: string;
|
|
78
83
|
/** localStorage key for sider collapsed state. Default `ding-react-admin-sider-collapsed`. */
|
|
79
84
|
siderCollapsedStorageKey?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Sidebar menu search. Enabled by default; pass `false` to hide.
|
|
87
|
+
* Use an object to customize the placeholder.
|
|
88
|
+
*/
|
|
89
|
+
navSearch?: boolean | { placeholder?: string };
|
|
80
90
|
};
|
|
81
91
|
|
|
82
92
|
export type { AuthPageLayoutProps } from "./layouts/AuthPageLayout";
|
package/src/vite-env.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { MutableRefObject, ReactNode } from 'react';
|
|
2
|
-
/** Tracks which top-level field sources are included in the save payload. */
|
|
3
|
-
export declare function SubmitFieldsProvider({ children, fieldsRef, }: {
|
|
4
|
-
children: ReactNode;
|
|
5
|
-
fieldsRef: MutableRefObject<Set<string>>;
|
|
6
|
-
}): import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
export declare function useSubmitFieldsOptional(): MutableRefObject<Set<string>> | null;
|
|
8
|
-
/**
|
|
9
|
-
* Remember a field's `source` for submit (`pickBySources`).
|
|
10
|
-
* Top-level fields only — skip for inline row cells (nested `name` paths).
|
|
11
|
-
*/
|
|
12
|
-
export declare function useSubmitField(source: string, enabled?: boolean): void;
|
|
13
|
-
/**
|
|
14
|
-
* Remember a field's `source` for tab/step error highlighting.
|
|
15
|
-
* Top-level fields only — no-op outside FormTabs / FormSteps.
|
|
16
|
-
*/
|
|
17
|
-
export declare function useSectionField(source: string, enabled?: boolean): void;
|
|
18
|
-
//# sourceMappingURL=SubmitFieldsContext.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SubmitFieldsContext.d.ts","sourceRoot":"","sources":["../../../../src/crud/context/SubmitFieldsContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAMf,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;CAC1C,2CAMA;AAED,wBAAgB,uBAAuB,yCAEtC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,UAAO,QAU5D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,UAAO,QAU7D"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getByPath.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/getByPath.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,GACX,OAAO,CAQT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"inlineArrayName.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/inlineArrayName.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,UAE9D"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"inlineFieldName.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/inlineFieldName.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,UAGf"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Build a submit payload from form values using declared field `source` paths.
|
|
3
|
-
* Flat `username` → `{ username }`; nested `invoiceLine.product` → `{ invoiceLine: { product } }`.
|
|
4
|
-
*/
|
|
5
|
-
export declare function pickBySources(values: Record<string, unknown>, sources: string[]): Record<string, unknown>;
|
|
6
|
-
//# sourceMappingURL=pickBySources.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pickBySources.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/pickBySources.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,MAAM,EAAE,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"setByPath.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/setByPath.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GACb,IAAI,CAYN"}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { MessageInstance } from 'antd/es/message/interface';
|
|
2
|
-
import { MutableRefObject } from 'react';
|
|
3
|
-
import { NavigateFunction } from 'react-router-dom';
|
|
4
|
-
import { DataProvider } from '../../data/dataProviderTypes';
|
|
5
|
-
import { FieldValues, UseFormReturn } from 'react-hook-form';
|
|
6
|
-
import { InlineFormSetProps } from '../InlineFormSet';
|
|
7
|
-
export type ResourceFormInlineConfig = Pick<InlineFormSetProps, "resource" | "foreignKey" | "name">;
|
|
8
|
-
export declare function resolveInlineArrayName(config: ResourceFormInlineConfig): string;
|
|
9
|
-
type LoadOptions<T extends FieldValues> = {
|
|
10
|
-
dp: DataProvider;
|
|
11
|
-
resource: string;
|
|
12
|
-
id: string | undefined;
|
|
13
|
-
isNew: boolean;
|
|
14
|
-
form: UseFormReturn<T>;
|
|
15
|
-
message: MessageInstance;
|
|
16
|
-
defaultValues?: Partial<T>;
|
|
17
|
-
inlines?: ResourceFormInlineConfig[];
|
|
18
|
-
};
|
|
19
|
-
/** Edit mode: fetch parent (+ inline rows) → form.reset → bump formVersion for field arrays. */
|
|
20
|
-
export declare function useResourceFormLoad<T extends FieldValues>({ dp, resource, id, isNew, form, message, defaultValues, inlines, }: LoadOptions<T>): {
|
|
21
|
-
loading: boolean;
|
|
22
|
-
formVersion: number;
|
|
23
|
-
existingInlineIds: Record<string, (string | number)[]>;
|
|
24
|
-
};
|
|
25
|
-
type SubmitOptions<T extends FieldValues & {
|
|
26
|
-
id?: unknown;
|
|
27
|
-
}> = {
|
|
28
|
-
dp: DataProvider;
|
|
29
|
-
resource: string;
|
|
30
|
-
id: string | undefined;
|
|
31
|
-
isNew: boolean;
|
|
32
|
-
form: UseFormReturn<T>;
|
|
33
|
-
message: MessageInstance;
|
|
34
|
-
navigate: NavigateFunction;
|
|
35
|
-
listPath: string;
|
|
36
|
-
submitFieldsRef: MutableRefObject<Set<string>>;
|
|
37
|
-
inlines?: ResourceFormInlineConfig[];
|
|
38
|
-
existingInlineIds: Record<string, (string | number)[]>;
|
|
39
|
-
onSaved?: (record: T) => void;
|
|
40
|
-
stayOnPage?: boolean;
|
|
41
|
-
};
|
|
42
|
-
/** Save: pickBySources → create/update parent → saveInlineRows for each inline config. */
|
|
43
|
-
export declare function useResourceFormSubmit<T extends FieldValues & {
|
|
44
|
-
id?: unknown;
|
|
45
|
-
}>({ dp, resource, id, isNew, form, message, navigate, listPath, submitFieldsRef, inlines, existingInlineIds, onSaved, stayOnPage, }: SubmitOptions<T>): (values: T) => Promise<void>;
|
|
46
|
-
export {};
|
|
47
|
-
//# sourceMappingURL=useResourceFormData.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useResourceFormData.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/useResourceFormData.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,kBAAkB,CAAC;AAM1B,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,kBAAkB,EAClB,UAAU,GAAG,YAAY,GAAG,MAAM,CACnC,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,UAEtE;AAED,KAAK,WAAW,CAAC,CAAC,SAAS,WAAW,IAAI;IACxC,EAAE,EAAE,YAAY,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IACvB,OAAO,EAAE,eAAe,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACtC,CAAC;AAEF,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,WAAW,EAAE,EACzD,EAAE,EACF,QAAQ,EACR,EAAE,EACF,KAAK,EACL,IAAI,EACJ,OAAO,EACP,aAAa,EACb,OAAO,GACR,EAAE,WAAW,CAAC,CAAC,CAAC;;;;EAyDhB;AAED,KAAK,aAAa,CAAC,CAAC,SAAS,WAAW,GAAG;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAE,IAAI;IAC7D,EAAE,EAAE,YAAY,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IACvB,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACrC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,0FAA0F;AAC1F,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,WAAW,GAAG;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,EAC9E,EAAE,EACF,QAAQ,EACR,EAAE,EACF,KAAK,EACL,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,OAAO,EACP,iBAAiB,EACjB,OAAO,EACP,UAAU,GACX,EAAE,aAAa,CAAC,CAAC,CAAC,YAEA,CAAC,mBAoFnB"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
useContext,
|
|
4
|
-
useEffect,
|
|
5
|
-
type MutableRefObject,
|
|
6
|
-
type ReactNode,
|
|
7
|
-
} from "react";
|
|
8
|
-
import { useFormSectionSourcesOptional } from "./FormSectionContext";
|
|
9
|
-
|
|
10
|
-
const SubmitFieldsContext =
|
|
11
|
-
createContext<MutableRefObject<Set<string>> | null>(null);
|
|
12
|
-
|
|
13
|
-
/** Tracks which top-level field sources are included in the save payload. */
|
|
14
|
-
export function SubmitFieldsProvider({
|
|
15
|
-
children,
|
|
16
|
-
fieldsRef,
|
|
17
|
-
}: {
|
|
18
|
-
children: ReactNode;
|
|
19
|
-
fieldsRef: MutableRefObject<Set<string>>;
|
|
20
|
-
}) {
|
|
21
|
-
return (
|
|
22
|
-
<SubmitFieldsContext.Provider value={fieldsRef}>
|
|
23
|
-
{children}
|
|
24
|
-
</SubmitFieldsContext.Provider>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function useSubmitFieldsOptional() {
|
|
29
|
-
return useContext(SubmitFieldsContext);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Remember a field's `source` for submit (`pickBySources`).
|
|
34
|
-
* Top-level fields only — skip for inline row cells (nested `name` paths).
|
|
35
|
-
*/
|
|
36
|
-
export function useSubmitField(source: string, enabled = true) {
|
|
37
|
-
const fieldsRef = useSubmitFieldsOptional();
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
if (!enabled || !fieldsRef) return;
|
|
41
|
-
fieldsRef.current.add(source);
|
|
42
|
-
return () => {
|
|
43
|
-
fieldsRef.current.delete(source);
|
|
44
|
-
};
|
|
45
|
-
}, [fieldsRef, source, enabled]);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Remember a field's `source` for tab/step error highlighting.
|
|
50
|
-
* Top-level fields only — no-op outside FormTabs / FormSteps.
|
|
51
|
-
*/
|
|
52
|
-
export function useSectionField(source: string, enabled = true) {
|
|
53
|
-
const sectionRef = useFormSectionSourcesOptional();
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
if (!enabled || !sectionRef) return;
|
|
57
|
-
sectionRef.current.add(source);
|
|
58
|
-
return () => {
|
|
59
|
-
sectionRef.current.delete(source);
|
|
60
|
-
};
|
|
61
|
-
}, [sectionRef, source, enabled]);
|
|
62
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { getByPath } from "./getByPath";
|
|
2
|
-
import { setByPath } from "./setByPath";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Build a submit payload from form values using declared field `source` paths.
|
|
6
|
-
* Flat `username` → `{ username }`; nested `invoiceLine.product` → `{ invoiceLine: { product } }`.
|
|
7
|
-
*/
|
|
8
|
-
export function pickBySources(
|
|
9
|
-
values: Record<string, unknown>,
|
|
10
|
-
sources: string[],
|
|
11
|
-
): Record<string, unknown> {
|
|
12
|
-
if (sources.length === 0) {
|
|
13
|
-
return { ...values };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const result: Record<string, unknown> = {};
|
|
17
|
-
for (const source of sources) {
|
|
18
|
-
const value = getByPath(values, source);
|
|
19
|
-
if (value !== undefined) {
|
|
20
|
-
setByPath(result, source, value);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return result;
|
|
24
|
-
}
|