ding-react-admin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +3067 -0
  5. package/dist/src/app/AdminApp.d.ts +7 -0
  6. package/dist/src/app/AdminApp.d.ts.map +1 -0
  7. package/dist/src/components/AuthAlternateLink.d.ts +7 -0
  8. package/dist/src/components/AuthAlternateLink.d.ts.map +1 -0
  9. package/dist/src/components/DensitySwitch.d.ts +2 -0
  10. package/dist/src/components/DensitySwitch.d.ts.map +1 -0
  11. package/dist/src/components/ThemeSwitch.d.ts +2 -0
  12. package/dist/src/components/ThemeSwitch.d.ts.map +1 -0
  13. package/dist/src/components/ThemeToolbar.d.ts +2 -0
  14. package/dist/src/components/ThemeToolbar.d.ts.map +1 -0
  15. package/dist/src/components/index.d.ts +5 -0
  16. package/dist/src/components/index.d.ts.map +1 -0
  17. package/dist/src/context/AppThemeProvider.d.ts +13 -0
  18. package/dist/src/context/AppThemeProvider.d.ts.map +1 -0
  19. package/dist/src/context/AuthProvider.d.ts +18 -0
  20. package/dist/src/context/AuthProvider.d.ts.map +1 -0
  21. package/dist/src/context/DataProvider.d.ts +10 -0
  22. package/dist/src/context/DataProvider.d.ts.map +1 -0
  23. package/dist/src/context/PermissionsProvider.d.ts +18 -0
  24. package/dist/src/context/PermissionsProvider.d.ts.map +1 -0
  25. package/dist/src/crud/FilterBar.d.ts +6 -0
  26. package/dist/src/crud/FilterBar.d.ts.map +1 -0
  27. package/dist/src/crud/FormSteps.d.ts +20 -0
  28. package/dist/src/crud/FormSteps.d.ts.map +1 -0
  29. package/dist/src/crud/FormTabs.d.ts +13 -0
  30. package/dist/src/crud/FormTabs.d.ts.map +1 -0
  31. package/dist/src/crud/InlineFormSet.d.ts +29 -0
  32. package/dist/src/crud/InlineFormSet.d.ts.map +1 -0
  33. package/dist/src/crud/ListActionsBar.d.ts +15 -0
  34. package/dist/src/crud/ListActionsBar.d.ts.map +1 -0
  35. package/dist/src/crud/ResourceForm.d.ts +21 -0
  36. package/dist/src/crud/ResourceForm.d.ts.map +1 -0
  37. package/dist/src/crud/ResourceFormModal.d.ts +10 -0
  38. package/dist/src/crud/ResourceFormModal.d.ts.map +1 -0
  39. package/dist/src/crud/ResourceList.d.ts +38 -0
  40. package/dist/src/crud/ResourceList.d.ts.map +1 -0
  41. package/dist/src/crud/columns/BooleanColumn.d.ts +6 -0
  42. package/dist/src/crud/columns/BooleanColumn.d.ts.map +1 -0
  43. package/dist/src/crud/columns/CustomColumn.d.ts +9 -0
  44. package/dist/src/crud/columns/CustomColumn.d.ts.map +1 -0
  45. package/dist/src/crud/columns/DateColumn.d.ts +6 -0
  46. package/dist/src/crud/columns/DateColumn.d.ts.map +1 -0
  47. package/dist/src/crud/columns/NumberColumn.d.ts +6 -0
  48. package/dist/src/crud/columns/NumberColumn.d.ts.map +1 -0
  49. package/dist/src/crud/columns/ReferenceColumn.d.ts +6 -0
  50. package/dist/src/crud/columns/ReferenceColumn.d.ts.map +1 -0
  51. package/dist/src/crud/columns/ReferenceManyColumn.d.ts +6 -0
  52. package/dist/src/crud/columns/ReferenceManyColumn.d.ts.map +1 -0
  53. package/dist/src/crud/columns/TextColumn.d.ts +7 -0
  54. package/dist/src/crud/columns/TextColumn.d.ts.map +1 -0
  55. package/dist/src/crud/context/FilterContext.d.ts +17 -0
  56. package/dist/src/crud/context/FilterContext.d.ts.map +1 -0
  57. package/dist/src/crud/context/FormContext.d.ts +16 -0
  58. package/dist/src/crud/context/FormContext.d.ts.map +1 -0
  59. package/dist/src/crud/context/FormFieldsContext.d.ts +14 -0
  60. package/dist/src/crud/context/FormFieldsContext.d.ts.map +1 -0
  61. package/dist/src/crud/context/FormSectionContext.d.ts +7 -0
  62. package/dist/src/crud/context/FormSectionContext.d.ts.map +1 -0
  63. package/dist/src/crud/context/InlineFormContext.d.ts +16 -0
  64. package/dist/src/crud/context/InlineFormContext.d.ts.map +1 -0
  65. package/dist/src/crud/context/ListContext.d.ts +22 -0
  66. package/dist/src/crud/context/ListContext.d.ts.map +1 -0
  67. package/dist/src/crud/fields/BooleanField.d.ts +7 -0
  68. package/dist/src/crud/fields/BooleanField.d.ts.map +1 -0
  69. package/dist/src/crud/fields/DateField.d.ts +8 -0
  70. package/dist/src/crud/fields/DateField.d.ts.map +1 -0
  71. package/dist/src/crud/fields/FieldWrapper.d.ts +29 -0
  72. package/dist/src/crud/fields/FieldWrapper.d.ts.map +1 -0
  73. package/dist/src/crud/fields/NumberField.d.ts +12 -0
  74. package/dist/src/crud/fields/NumberField.d.ts.map +1 -0
  75. package/dist/src/crud/fields/PasswordField.d.ts +11 -0
  76. package/dist/src/crud/fields/PasswordField.d.ts.map +1 -0
  77. package/dist/src/crud/fields/ReferenceField.d.ts +18 -0
  78. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -0
  79. package/dist/src/crud/fields/ReferenceManyField.d.ts +9 -0
  80. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -0
  81. package/dist/src/crud/fields/SelectField.d.ts +10 -0
  82. package/dist/src/crud/fields/SelectField.d.ts.map +1 -0
  83. package/dist/src/crud/fields/TextField.d.ts +10 -0
  84. package/dist/src/crud/fields/TextField.d.ts.map +1 -0
  85. package/dist/src/crud/fields/useInlineOrFormField.d.ts +10 -0
  86. package/dist/src/crud/fields/useInlineOrFormField.d.ts.map +1 -0
  87. package/dist/src/crud/filters/BooleanFilter.d.ts +4 -0
  88. package/dist/src/crud/filters/BooleanFilter.d.ts.map +1 -0
  89. package/dist/src/crud/filters/DateFilter.d.ts +4 -0
  90. package/dist/src/crud/filters/DateFilter.d.ts.map +1 -0
  91. package/dist/src/crud/filters/NumberFilter.d.ts +4 -0
  92. package/dist/src/crud/filters/NumberFilter.d.ts.map +1 -0
  93. package/dist/src/crud/filters/ReferenceFilter.d.ts +13 -0
  94. package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -0
  95. package/dist/src/crud/filters/SelectFilter.d.ts +7 -0
  96. package/dist/src/crud/filters/SelectFilter.d.ts.map +1 -0
  97. package/dist/src/crud/filters/TextFilter.d.ts +6 -0
  98. package/dist/src/crud/filters/TextFilter.d.ts.map +1 -0
  99. package/dist/src/crud/index.d.ts +44 -0
  100. package/dist/src/crud/index.d.ts.map +1 -0
  101. package/dist/src/crud/types.d.ts +99 -0
  102. package/dist/src/crud/types.d.ts.map +1 -0
  103. package/dist/src/crud/utils/formErrors.d.ts +9 -0
  104. package/dist/src/crud/utils/formErrors.d.ts.map +1 -0
  105. package/dist/src/crud/utils/formSectionErrors.d.ts +6 -0
  106. package/dist/src/crud/utils/formSectionErrors.d.ts.map +1 -0
  107. package/dist/src/crud/utils/getByPath.d.ts +3 -0
  108. package/dist/src/crud/utils/getByPath.d.ts.map +1 -0
  109. package/dist/src/crud/utils/inlineArrayName.d.ts +3 -0
  110. package/dist/src/crud/utils/inlineArrayName.d.ts.map +1 -0
  111. package/dist/src/crud/utils/pickBySources.d.ts +6 -0
  112. package/dist/src/crud/utils/pickBySources.d.ts.map +1 -0
  113. package/dist/src/crud/utils/setByPath.d.ts +3 -0
  114. package/dist/src/crud/utils/setByPath.d.ts.map +1 -0
  115. package/dist/src/crud/utils/sortQueryParam.d.ts +7 -0
  116. package/dist/src/crud/utils/sortQueryParam.d.ts.map +1 -0
  117. package/dist/src/crud/utils/useChoices.d.ts +10 -0
  118. package/dist/src/crud/utils/useChoices.d.ts.map +1 -0
  119. package/dist/src/crud/utils/useListQueryState.d.ts +22 -0
  120. package/dist/src/crud/utils/useListQueryState.d.ts.map +1 -0
  121. package/dist/src/data/createMemoryResourceHandlers.d.ts +23 -0
  122. package/dist/src/data/createMemoryResourceHandlers.d.ts.map +1 -0
  123. package/dist/src/data/createRestResourceHandlers.d.ts +22 -0
  124. package/dist/src/data/createRestResourceHandlers.d.ts.map +1 -0
  125. package/dist/src/data/dataProviderTypes.d.ts +67 -0
  126. package/dist/src/data/dataProviderTypes.d.ts.map +1 -0
  127. package/dist/src/data/dataUtils.test.d.ts +2 -0
  128. package/dist/src/data/dataUtils.test.d.ts.map +1 -0
  129. package/dist/src/data/inMemoryList.d.ts +12 -0
  130. package/dist/src/data/inMemoryList.d.ts.map +1 -0
  131. package/dist/src/data/parseFormErrorHelpers.d.ts +35 -0
  132. package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -0
  133. package/dist/src/data/parseFormErrorHelpers.test.d.ts +2 -0
  134. package/dist/src/data/parseFormErrorHelpers.test.d.ts.map +1 -0
  135. package/dist/src/data/resourceHandlers.d.ts +33 -0
  136. package/dist/src/data/resourceHandlers.d.ts.map +1 -0
  137. package/dist/src/data/sortHelpers.d.ts +17 -0
  138. package/dist/src/data/sortHelpers.d.ts.map +1 -0
  139. package/dist/src/index.d.ts +31 -0
  140. package/dist/src/index.d.ts.map +1 -0
  141. package/dist/src/layouts/AdminLayout.d.ts +3 -0
  142. package/dist/src/layouts/AdminLayout.d.ts.map +1 -0
  143. package/dist/src/layouts/AuthPageLayout.d.ts +11 -0
  144. package/dist/src/layouts/AuthPageLayout.d.ts.map +1 -0
  145. package/dist/src/pages/LoginPage.d.ts +3 -0
  146. package/dist/src/pages/LoginPage.d.ts.map +1 -0
  147. package/dist/src/pages/PlaceholderPage.d.ts +4 -0
  148. package/dist/src/pages/PlaceholderPage.d.ts.map +1 -0
  149. package/dist/src/permissions/resourcePermissions.d.ts +22 -0
  150. package/dist/src/permissions/resourcePermissions.d.ts.map +1 -0
  151. package/dist/src/router/createAdminRouter.d.ts +3 -0
  152. package/dist/src/router/createAdminRouter.d.ts.map +1 -0
  153. package/dist/src/router/guards.d.ts +20 -0
  154. package/dist/src/router/guards.d.ts.map +1 -0
  155. package/dist/src/router/routeAccess.d.ts +21 -0
  156. package/dist/src/router/routeAccess.d.ts.map +1 -0
  157. package/dist/src/types.d.ts +123 -0
  158. package/dist/src/types.d.ts.map +1 -0
  159. package/package.json +58 -0
  160. package/src/app/AdminApp.tsx +34 -0
  161. package/src/components/AuthAlternateLink.tsx +23 -0
  162. package/src/components/DensitySwitch.tsx +31 -0
  163. package/src/components/ThemeSwitch.tsx +28 -0
  164. package/src/components/ThemeToolbar.tsx +34 -0
  165. package/src/components/index.ts +4 -0
  166. package/src/context/AppThemeProvider.tsx +128 -0
  167. package/src/context/AuthProvider.tsx +77 -0
  168. package/src/context/DataProvider.tsx +30 -0
  169. package/src/context/PermissionsProvider.tsx +57 -0
  170. package/src/crud/FilterBar.tsx +36 -0
  171. package/src/crud/FormSteps.tsx +135 -0
  172. package/src/crud/FormTabs.tsx +126 -0
  173. package/src/crud/InlineFormSet.tsx +231 -0
  174. package/src/crud/ListActionsBar.tsx +106 -0
  175. package/src/crud/ResourceForm.tsx +281 -0
  176. package/src/crud/ResourceFormModal.tsx +121 -0
  177. package/src/crud/ResourceList.tsx +633 -0
  178. package/src/crud/columns/BooleanColumn.tsx +32 -0
  179. package/src/crud/columns/CustomColumn.tsx +33 -0
  180. package/src/crud/columns/DateColumn.tsx +28 -0
  181. package/src/crud/columns/NumberColumn.tsx +31 -0
  182. package/src/crud/columns/ReferenceColumn.tsx +85 -0
  183. package/src/crud/columns/ReferenceManyColumn.tsx +67 -0
  184. package/src/crud/columns/TextColumn.tsx +38 -0
  185. package/src/crud/context/FilterContext.tsx +71 -0
  186. package/src/crud/context/FormContext.tsx +39 -0
  187. package/src/crud/context/FormFieldsContext.tsx +76 -0
  188. package/src/crud/context/FormSectionContext.tsx +22 -0
  189. package/src/crud/context/InlineFormContext.tsx +71 -0
  190. package/src/crud/context/ListContext.tsx +93 -0
  191. package/src/crud/fields/BooleanField.tsx +32 -0
  192. package/src/crud/fields/DateField.tsx +47 -0
  193. package/src/crud/fields/FieldWrapper.tsx +109 -0
  194. package/src/crud/fields/NumberField.tsx +50 -0
  195. package/src/crud/fields/PasswordField.tsx +98 -0
  196. package/src/crud/fields/ReferenceField.tsx +191 -0
  197. package/src/crud/fields/ReferenceManyField.tsx +70 -0
  198. package/src/crud/fields/SelectField.tsx +42 -0
  199. package/src/crud/fields/TextField.tsx +44 -0
  200. package/src/crud/fields/useInlineOrFormField.tsx +91 -0
  201. package/src/crud/filters/BooleanFilter.tsx +38 -0
  202. package/src/crud/filters/DateFilter.tsx +35 -0
  203. package/src/crud/filters/NumberFilter.tsx +33 -0
  204. package/src/crud/filters/ReferenceFilter.tsx +173 -0
  205. package/src/crud/filters/SelectFilter.tsx +44 -0
  206. package/src/crud/filters/TextFilter.tsx +36 -0
  207. package/src/crud/index.ts +73 -0
  208. package/src/crud/types.ts +130 -0
  209. package/src/crud/utils/formErrors.ts +38 -0
  210. package/src/crud/utils/formSectionErrors.ts +85 -0
  211. package/src/crud/utils/getByPath.ts +13 -0
  212. package/src/crud/utils/inlineArrayName.ts +4 -0
  213. package/src/crud/utils/pickBySources.ts +24 -0
  214. package/src/crud/utils/setByPath.ts +18 -0
  215. package/src/crud/utils/sortQueryParam.ts +37 -0
  216. package/src/crud/utils/useChoices.ts +182 -0
  217. package/src/crud/utils/useListQueryState.ts +191 -0
  218. package/src/data/createMemoryResourceHandlers.ts +78 -0
  219. package/src/data/createRestResourceHandlers.ts +57 -0
  220. package/src/data/dataProviderTypes.ts +101 -0
  221. package/src/data/dataUtils.test.ts +108 -0
  222. package/src/data/inMemoryList.ts +97 -0
  223. package/src/data/parseFormErrorHelpers.test.ts +84 -0
  224. package/src/data/parseFormErrorHelpers.ts +224 -0
  225. package/src/data/resourceHandlers.ts +124 -0
  226. package/src/data/sortHelpers.ts +48 -0
  227. package/src/index.ts +182 -0
  228. package/src/layouts/AdminLayout.tsx +381 -0
  229. package/src/layouts/AuthPageLayout.tsx +81 -0
  230. package/src/pages/LoginPage.tsx +80 -0
  231. package/src/pages/PlaceholderPage.tsx +9 -0
  232. package/src/permissions/resourcePermissions.ts +47 -0
  233. package/src/router/createAdminRouter.tsx +64 -0
  234. package/src/router/guards.tsx +63 -0
  235. package/src/router/routeAccess.ts +89 -0
  236. package/src/types.ts +135 -0
  237. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,9 @@
1
+ import { Typography } from "antd";
2
+
3
+ export function PlaceholderPage({ title }: { title: string }) {
4
+ return (
5
+ <Typography.Title level={3} style={{ marginTop: 0 }}>
6
+ {title}
7
+ </Typography.Title>
8
+ );
9
+ }
@@ -0,0 +1,47 @@
1
+ import type { PermissionsChecker } from "../context/PermissionsProvider";
2
+
3
+ /** Opaque permission strings per CRUD action (values come from your backend). */
4
+ export type ResourcePermissions = {
5
+ /** List page, nav, route guard */
6
+ list?: string;
7
+ /** getOne — falls back to `list` when omitted */
8
+ read?: string;
9
+ /** New button, create modal, POST */
10
+ add?: string;
11
+ /** Edit / Quick edit, PATCH/PUT */
12
+ change?: string;
13
+ /** Row delete, bulk delete */
14
+ delete?: string;
15
+ };
16
+
17
+ export type ResourcePermissionSlot = keyof ResourcePermissions;
18
+
19
+ /** When `permissions` is omitted, allow. When provided, missing slot → deny. */
20
+ export function checkResourcePermission(
21
+ can: PermissionsChecker,
22
+ permissions: ResourcePermissions | undefined,
23
+ slot: ResourcePermissionSlot,
24
+ ): boolean {
25
+ if (!permissions) return true;
26
+ let perm = permissions[slot];
27
+ if (slot === "read" && !perm) perm = permissions.list;
28
+ if (!perm) return false;
29
+ return can(perm);
30
+ }
31
+
32
+ export function filterNavByPermission<T extends { permission?: string; children?: T[] }>(
33
+ items: T[],
34
+ can: PermissionsChecker,
35
+ ): T[] {
36
+ return items
37
+ .map((item) => {
38
+ if (item.children?.length) {
39
+ const children = filterNavByPermission(item.children, can);
40
+ if (children.length === 0) return null;
41
+ return { ...item, children };
42
+ }
43
+ if (item.permission && !can(item.permission)) return null;
44
+ return item;
45
+ })
46
+ .filter((item): item is T => item !== null);
47
+ }
@@ -0,0 +1,64 @@
1
+ import { Navigate, createBrowserRouter } from "react-router-dom";
2
+ import { AdminLayout } from "../layouts/AdminLayout";
3
+ import type { CreateAdminRouterOptions } from "../types";
4
+ import { GuestOnly, Protected } from "./guards";
5
+ import {
6
+ deriveAuthPaths,
7
+ partitionAdminRoutes,
8
+ toProtectedRouteObject,
9
+ toRouterSegment,
10
+ } from "./routeAccess";
11
+
12
+ export function createAdminRouter({
13
+ navItems,
14
+ children,
15
+ layoutProps,
16
+ redirects,
17
+ }: CreateAdminRouterOptions) {
18
+ const { loginPath, homePath } = deriveAuthPaths(children, redirects);
19
+ const { guest, public: publicRoutes, protected: protectedRoutes } =
20
+ partitionAdminRoutes(children);
21
+
22
+ const routerRoutes = [];
23
+
24
+ for (const route of guest) {
25
+ if (!("path" in route) || !route.path) continue;
26
+ routerRoutes.push({
27
+ path: toRouterSegment(route.path),
28
+ element: (
29
+ <GuestOnly redirectTo={homePath}>{route.element}</GuestOnly>
30
+ ),
31
+ });
32
+ }
33
+
34
+ for (const route of publicRoutes) {
35
+ if (!("path" in route) || !route.path) continue;
36
+ routerRoutes.push({
37
+ path: toRouterSegment(route.path),
38
+ element: route.element,
39
+ });
40
+ }
41
+
42
+ if (protectedRoutes.length > 0) {
43
+ routerRoutes.push({
44
+ path: "/",
45
+ element: (
46
+ <Protected redirectTo={loginPath}>
47
+ <AdminLayout
48
+ navItems={navItems}
49
+ loginPath={loginPath}
50
+ {...layoutProps}
51
+ />
52
+ </Protected>
53
+ ),
54
+ children: protectedRoutes.map(toProtectedRouteObject),
55
+ });
56
+ }
57
+
58
+ routerRoutes.push({
59
+ path: "*",
60
+ element: <Navigate to={homePath} replace />,
61
+ });
62
+
63
+ return createBrowserRouter(routerRoutes);
64
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
package/src/types.ts ADDED
@@ -0,0 +1,135 @@
1
+ import type { MenuProps } from "antd";
2
+ import type { ReactElement, ReactNode } from "react";
3
+
4
+ export type ThemeMode = "light" | "dark" | "system";
5
+
6
+ export type ThemeDensity = "comfortable" | "compact";
7
+
8
+ /**
9
+ * Sidebar / app-hub navigation row. Maps to Ant Design `Menu` items.
10
+ *
11
+ * - **`children`** — nested submenu; parent rows should use a `path` that you **do not** navigate to as a leaf (for example `/catalog`), unless you define that route.
12
+ * - **`label`** — string or any React node (badges, extra markup).
13
+ * - **`Icon`** — optional; omit on group-only rows if you prefer text-only labels.
14
+ */
15
+ export type NavItem = {
16
+ /** Menu item key and `navigate()` target for leaf items (pathname). Parents use this as submenu key. */
17
+ path: string;
18
+ label: ReactNode;
19
+ Icon?: React.ComponentType;
20
+ /** Hide this item when the user lacks this permission string. */
21
+ permission?: string;
22
+ children?: NavItem[];
23
+ };
24
+
25
+ /** Credentials passed from login forms to `AuthAdapter.login` and `useAuth().login`. */
26
+ export type LoginCredentials = {
27
+ username: string;
28
+ password: string;
29
+ [key: string]: unknown;
30
+ };
31
+
32
+ /** Pluggable login/logout/token backend (e.g. sessionStorage, your REST API). */
33
+ export type AuthAdapter = {
34
+ login: (credentials: LoginCredentials) => Promise<void>;
35
+ logout: () => void;
36
+ /** Synchronous read of session (e.g. token). Used after mount and after login/logout. */
37
+ getToken: () => string | null;
38
+ };
39
+
40
+ /** How a route participates in auth when using `AdminApp` / `createAdminRouter`. */
41
+ export type RouteAccess =
42
+ /** Inside `AdminLayout`; requires a session. Default. */
43
+ | "protected"
44
+ /** Top-level; no session required (signup, forgot-password, etc.). */
45
+ | "public"
46
+ /** Top-level; only for guests (`GuestOnly`). Use for login. */
47
+ | "guest";
48
+
49
+ export type AuthRedirects = {
50
+ /** Where unauthenticated users are sent. Required with protected routes unless a guest route is declared. */
51
+ unauthenticated?: string;
52
+ /** After login and when visiting guest routes while authenticated. Required with guest routes unless a protected route is declared. */
53
+ afterLogin?: string;
54
+ };
55
+
56
+ export type AppThemeProviderProps = {
57
+ children: ReactNode;
58
+ /** localStorage key for theme mode. Default `ding-react-admin-theme-mode`. */
59
+ modeStorageKey?: string;
60
+ /** localStorage key for density. Default `ding-react-admin-theme-density`. */
61
+ densityStorageKey?: string;
62
+ };
63
+
64
+ export type AdminLayoutProps = {
65
+ navItems: NavItem[];
66
+ /** Shown in sider header when expanded; drawer title on mobile. */
67
+ brand?: ReactNode;
68
+ /** Shown in sider header when collapsed. */
69
+ collapsedBrand?: ReactNode;
70
+ /** Optional drawer title override (defaults to `brand`). */
71
+ mobileDrawerTitle?: ReactNode;
72
+ headerExtras?: ReactNode;
73
+ /** Full menu items for the account dropdown. If omitted, a default “Log out” item is used. */
74
+ userMenuItems?: MenuProps["items"];
75
+ onUserMenuClick?: MenuProps["onClick"];
76
+ /** Where to send the user after logout. Default `/login`. */
77
+ loginPath?: string;
78
+ /** localStorage key for sider collapsed state. Default `ding-react-admin-sider-collapsed`. */
79
+ siderCollapsedStorageKey?: string;
80
+ };
81
+
82
+ export type { AuthPageLayoutProps } from "./layouts/AuthPageLayout";
83
+ export type { AuthAlternateLinkProps } from "./components/AuthAlternateLink";
84
+
85
+ export type LoginPageProps = {
86
+ title?: ReactNode;
87
+ description?: ReactNode;
88
+ /** @deprecated Use `brand` — shown above the card, not in the card header. */
89
+ logo?: ReactNode;
90
+ /** Brand mark centered above the card. */
91
+ brand?: ReactNode;
92
+ /** Extra form items rendered before the submit button. */
93
+ extraFields?: ReactNode;
94
+ showThemeToolbar?: boolean;
95
+ /** Navigate here after successful login. Default `/`. */
96
+ afterLoginPath?: string;
97
+ /** Row below the card linking to registration (or another auth flow). */
98
+ alternateAuth?: {
99
+ prompt?: string;
100
+ linkText: string;
101
+ to: string;
102
+ };
103
+ /** Custom footer below the card; overrides `alternateAuth` when set. */
104
+ footer?: ReactNode;
105
+ };
106
+
107
+ type AdminRouteChildBase = {
108
+ element: ReactElement;
109
+ /** Default `protected`. See [docs/routing.md](../docs/routing.md). */
110
+ access?: RouteAccess;
111
+ };
112
+
113
+ /** Route list for `AdminApp` / `createAdminRouter`. */
114
+ export type AdminRouteChild =
115
+ | (AdminRouteChildBase & { index: true; path?: undefined })
116
+ | (AdminRouteChildBase & { path: string; index?: undefined });
117
+
118
+ export type CreateAdminRouterOptions = {
119
+ navItems: NavItem[];
120
+ /** App routes; split by `access` into guest, public, and protected segments. */
121
+ children: AdminRouteChild[];
122
+ layoutProps?: Partial<AdminLayoutProps>;
123
+ redirects?: AuthRedirects;
124
+ };
125
+
126
+ export type AdminAppProps = {
127
+ navItems: NavItem[];
128
+ routes: AdminRouteChild[];
129
+ /** Override login / post-login redirect paths when they cannot be inferred from routes. */
130
+ authRedirects?: AuthRedirects;
131
+ layoutProps?: Partial<AdminLayoutProps>;
132
+ theme?: Partial<
133
+ Pick<AppThemeProviderProps, "modeStorageKey" | "densityStorageKey">
134
+ >;
135
+ };
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />