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.
Files changed (230) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +254 -201
  3. package/dist/index.css +2 -0
  4. package/dist/index.js +2073 -1202
  5. package/dist/src/components/NavMenuLabel.d.ts +8 -0
  6. package/dist/src/components/NavMenuLabel.d.ts.map +1 -0
  7. package/dist/src/components/NavMenuSearch.d.ts +9 -0
  8. package/dist/src/components/NavMenuSearch.d.ts.map +1 -0
  9. package/dist/src/components/ScrollableArea.d.ts +10 -0
  10. package/dist/src/components/ScrollableArea.d.ts.map +1 -0
  11. package/dist/src/components/index.d.ts +3 -0
  12. package/dist/src/components/index.d.ts.map +1 -1
  13. package/dist/src/context/AuthProvider.d.ts +2 -0
  14. package/dist/src/context/AuthProvider.d.ts.map +1 -1
  15. package/dist/src/crud/FormGlobalErrorsAlert.d.ts +5 -0
  16. package/dist/src/crud/FormGlobalErrorsAlert.d.ts.map +1 -0
  17. package/dist/src/crud/InlineFormSet.d.ts +2 -17
  18. package/dist/src/crud/InlineFormSet.d.ts.map +1 -1
  19. package/dist/src/crud/ResourceForm.d.ts +2 -10
  20. package/dist/src/crud/ResourceForm.d.ts.map +1 -1
  21. package/dist/src/crud/ResourceFormModal.d.ts +7 -1
  22. package/dist/src/crud/ResourceFormModal.d.ts.map +1 -1
  23. package/dist/src/crud/ResourceList.d.ts.map +1 -1
  24. package/dist/src/crud/ResourceRecordForm.d.ts +27 -0
  25. package/dist/src/crud/ResourceRecordForm.d.ts.map +1 -0
  26. package/dist/src/crud/columns/ImageColumn.d.ts +14 -0
  27. package/dist/src/crud/columns/ImageColumn.d.ts.map +1 -0
  28. package/dist/src/crud/context/InlineFieldsRegistry.d.ts +15 -0
  29. package/dist/src/crud/context/InlineFieldsRegistry.d.ts.map +1 -0
  30. package/dist/src/crud/context/PayloadFieldsContext.d.ts +18 -0
  31. package/dist/src/crud/context/PayloadFieldsContext.d.ts.map +1 -0
  32. package/dist/src/crud/fields/FileField.d.ts +12 -0
  33. package/dist/src/crud/fields/FileField.d.ts.map +1 -0
  34. package/dist/src/crud/fields/ImageField.d.ts +14 -0
  35. package/dist/src/crud/fields/ImageField.d.ts.map +1 -0
  36. package/dist/src/crud/fields/ReferenceField.d.ts +12 -2
  37. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
  38. package/dist/src/crud/fields/ReferenceInputActions.d.ts +16 -0
  39. package/dist/src/crud/fields/ReferenceInputActions.d.ts.map +1 -0
  40. package/dist/src/crud/fields/ReferenceManyField.d.ts +10 -1
  41. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
  42. package/dist/src/crud/fields/uploadFieldUtils.d.ts +5 -0
  43. package/dist/src/crud/fields/uploadFieldUtils.d.ts.map +1 -0
  44. package/dist/src/crud/fields/useUploadPreviewUrl.d.ts +4 -0
  45. package/dist/src/crud/fields/useUploadPreviewUrl.d.ts.map +1 -0
  46. package/dist/src/crud/filters/ReferenceFilter.d.ts +1 -1
  47. package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -1
  48. package/dist/src/crud/index.d.ts +27 -9
  49. package/dist/src/crud/index.d.ts.map +1 -1
  50. package/dist/src/crud/types.d.ts +21 -7
  51. package/dist/src/crud/types.d.ts.map +1 -1
  52. package/dist/src/crud/utils/buildFormPayload.d.ts +6 -0
  53. package/dist/src/crud/utils/buildFormPayload.d.ts.map +1 -0
  54. package/dist/src/crud/utils/buildInlineRowsPayload.d.ts +6 -0
  55. package/dist/src/crud/utils/buildInlineRowsPayload.d.ts.map +1 -0
  56. package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts +5 -0
  57. package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts.map +1 -0
  58. package/dist/src/crud/utils/choiceSelectionUtils.d.ts +10 -0
  59. package/dist/src/crud/utils/choiceSelectionUtils.d.ts.map +1 -0
  60. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts +2 -0
  61. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts.map +1 -0
  62. package/dist/src/crud/utils/formErrors.d.ts +18 -7
  63. package/dist/src/crud/utils/formErrors.d.ts.map +1 -1
  64. package/dist/src/crud/utils/formErrors.test.d.ts +2 -0
  65. package/dist/src/crud/utils/formErrors.test.d.ts.map +1 -0
  66. package/dist/src/crud/utils/getFormValue.d.ts +3 -0
  67. package/dist/src/crud/utils/getFormValue.d.ts.map +1 -0
  68. package/dist/src/crud/utils/hasUploadValues.d.ts +3 -0
  69. package/dist/src/crud/utils/hasUploadValues.d.ts.map +1 -0
  70. package/dist/src/crud/utils/nestedFieldPath.d.ts +3 -0
  71. package/dist/src/crud/utils/nestedFieldPath.d.ts.map +1 -0
  72. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts +8 -0
  73. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts.map +1 -0
  74. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts +4 -0
  75. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts.map +1 -0
  76. package/dist/src/crud/utils/setFormValue.d.ts +3 -0
  77. package/dist/src/crud/utils/setFormValue.d.ts.map +1 -0
  78. package/dist/src/crud/utils/toFormData.d.ts +13 -0
  79. package/dist/src/crud/utils/toFormData.d.ts.map +1 -0
  80. package/dist/src/crud/utils/uploadReferenceUtils.d.ts +5 -0
  81. package/dist/src/crud/utils/uploadReferenceUtils.d.ts.map +1 -0
  82. package/dist/src/crud/utils/useChoices.d.ts +27 -1
  83. package/dist/src/crud/utils/useChoices.d.ts.map +1 -1
  84. package/dist/src/crud/utils/useFormRecord.d.ts +44 -0
  85. package/dist/src/crud/utils/useFormRecord.d.ts.map +1 -0
  86. package/dist/src/data/createRestResourceHandlers.d.ts +5 -5
  87. package/dist/src/data/createRestResourceHandlers.d.ts.map +1 -1
  88. package/dist/src/data/dataProviderTypes.d.ts +6 -5
  89. package/dist/src/data/dataProviderTypes.d.ts.map +1 -1
  90. package/dist/src/data/parseFormErrorHelpers.d.ts +23 -6
  91. package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -1
  92. package/dist/src/data/resourceHandlers.d.ts +1 -1
  93. package/dist/src/data/resourceHandlers.d.ts.map +1 -1
  94. package/dist/src/index.d.ts +4 -4
  95. package/dist/src/index.d.ts.map +1 -1
  96. package/dist/src/layouts/AdminLayout.d.ts +1 -1
  97. package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
  98. package/dist/src/layouts/AuthPageLayout.d.ts.map +1 -1
  99. package/dist/src/layouts/navFilter.d.ts +5 -0
  100. package/dist/src/layouts/navFilter.d.ts.map +1 -0
  101. package/dist/src/layouts/navMenuItems.d.ts +6 -0
  102. package/dist/src/layouts/navMenuItems.d.ts.map +1 -0
  103. package/dist/src/types.d.ts +12 -0
  104. package/dist/src/types.d.ts.map +1 -1
  105. package/package.json +79 -59
  106. package/src/app/AdminApp.tsx +34 -34
  107. package/src/components/AuthAlternateLink.tsx +23 -23
  108. package/src/components/DensitySwitch.tsx +31 -31
  109. package/src/components/NavMenuLabel.tsx +26 -0
  110. package/src/components/NavMenuSearch.tsx +76 -0
  111. package/src/components/ScrollableArea.tsx +46 -0
  112. package/src/components/ThemeSwitch.tsx +28 -28
  113. package/src/components/ThemeToolbar.tsx +34 -34
  114. package/src/components/index.ts +7 -4
  115. package/src/components/navMenu.css +21 -0
  116. package/src/components/scrollable.css +28 -0
  117. package/src/context/AppThemeProvider.tsx +128 -128
  118. package/src/context/AuthProvider.tsx +89 -77
  119. package/src/context/DataProvider.tsx +30 -30
  120. package/src/context/PermissionsProvider.tsx +57 -57
  121. package/src/crud/FilterBar.tsx +36 -36
  122. package/src/crud/FormGlobalErrorsAlert.tsx +36 -0
  123. package/src/crud/FormSteps.tsx +135 -135
  124. package/src/crud/FormTabs.tsx +126 -126
  125. package/src/crud/InlineFormSet.tsx +162 -219
  126. package/src/crud/ListActionsBar.tsx +106 -106
  127. package/src/crud/ResourceForm.tsx +83 -166
  128. package/src/crud/ResourceFormModal.tsx +75 -133
  129. package/src/crud/ResourceList.tsx +1 -0
  130. package/src/crud/ResourceRecordForm.tsx +166 -0
  131. package/src/crud/columns/BooleanColumn.tsx +32 -32
  132. package/src/crud/columns/CustomColumn.tsx +33 -33
  133. package/src/crud/columns/DateColumn.tsx +28 -28
  134. package/src/crud/columns/ImageColumn.tsx +58 -0
  135. package/src/crud/columns/NumberColumn.tsx +31 -31
  136. package/src/crud/columns/ReferenceColumn.tsx +85 -85
  137. package/src/crud/columns/ReferenceManyColumn.tsx +67 -67
  138. package/src/crud/columns/TextColumn.tsx +38 -38
  139. package/src/crud/context/FilterContext.tsx +71 -71
  140. package/src/crud/context/FormContext.tsx +39 -39
  141. package/src/crud/context/FormSectionContext.tsx +22 -22
  142. package/src/crud/context/InlineFieldsRegistry.tsx +61 -0
  143. package/src/crud/context/ListContext.tsx +93 -93
  144. package/src/crud/context/PayloadFieldsContext.tsx +62 -0
  145. package/src/crud/fields/BooleanField.tsx +38 -38
  146. package/src/crud/fields/DateField.tsx +53 -53
  147. package/src/crud/fields/FieldWrapper.tsx +87 -87
  148. package/src/crud/fields/FileField.tsx +123 -0
  149. package/src/crud/fields/ImageField.tsx +120 -0
  150. package/src/crud/fields/NumberField.tsx +52 -52
  151. package/src/crud/fields/PasswordField.tsx +99 -99
  152. package/src/crud/fields/ReferenceField.tsx +237 -96
  153. package/src/crud/fields/ReferenceInputActions.tsx +106 -0
  154. package/src/crud/fields/ReferenceManyField.tsx +200 -76
  155. package/src/crud/fields/SelectField.tsx +48 -48
  156. package/src/crud/fields/TextField.tsx +46 -46
  157. package/src/crud/fields/uploadFieldUtils.ts +21 -0
  158. package/src/crud/fields/useUploadPreviewUrl.ts +20 -0
  159. package/src/crud/filters/BooleanFilter.tsx +38 -38
  160. package/src/crud/filters/DateFilter.tsx +35 -35
  161. package/src/crud/filters/NumberFilter.tsx +33 -33
  162. package/src/crud/filters/ReferenceFilter.tsx +192 -173
  163. package/src/crud/filters/SelectFilter.tsx +44 -44
  164. package/src/crud/filters/TextFilter.tsx +36 -36
  165. package/src/crud/index.ts +99 -90
  166. package/src/crud/types.ts +146 -132
  167. package/src/crud/utils/buildFormPayload.ts +24 -0
  168. package/src/crud/utils/buildInlineRowsPayload.ts +37 -0
  169. package/src/crud/utils/buildResourceFormSubmitBody.ts +29 -0
  170. package/src/crud/utils/choiceSelectionUtils.test.ts +45 -0
  171. package/src/crud/utils/choiceSelectionUtils.ts +116 -0
  172. package/src/crud/utils/formErrors.test.ts +168 -0
  173. package/src/crud/utils/formErrors.ts +138 -38
  174. package/src/crud/utils/formSectionErrors.ts +85 -85
  175. package/src/crud/utils/{getByPath.ts → getFormValue.ts} +13 -13
  176. package/src/crud/utils/hasUploadValues.ts +9 -0
  177. package/src/crud/utils/nestedFieldPath.ts +8 -0
  178. package/src/crud/utils/prepareFormSubmitBody.ts +22 -0
  179. package/src/crud/utils/referenceSelectNotFoundContent.tsx +7 -0
  180. package/src/crud/utils/{setByPath.ts → setFormValue.ts} +18 -18
  181. package/src/crud/utils/sortQueryParam.ts +37 -37
  182. package/src/crud/utils/toFormData.ts +79 -0
  183. package/src/crud/utils/uploadReferenceUtils.ts +44 -0
  184. package/src/crud/utils/useAbortableEffect.ts +17 -17
  185. package/src/crud/utils/useChoices.ts +330 -182
  186. package/src/crud/utils/useFormRecord.ts +179 -0
  187. package/src/crud/utils/useListQueryState.ts +191 -191
  188. package/src/data/abortError.test.ts +19 -19
  189. package/src/data/abortError.ts +10 -10
  190. package/src/data/createMemoryResourceHandlers.ts +78 -78
  191. package/src/data/createRestResourceHandlers.ts +63 -57
  192. package/src/data/dataProviderTypes.ts +7 -5
  193. package/src/data/dataUtils.test.ts +228 -108
  194. package/src/data/inMemoryList.ts +97 -97
  195. package/src/data/parseFormErrorHelpers.test.ts +201 -84
  196. package/src/data/parseFormErrorHelpers.ts +376 -224
  197. package/src/data/resourceHandlers.ts +1 -1
  198. package/src/data/sortHelpers.ts +48 -48
  199. package/src/index.ts +211 -195
  200. package/src/layouts/AdminLayout.tsx +530 -381
  201. package/src/layouts/AuthPageLayout.tsx +87 -81
  202. package/src/layouts/navFilter.ts +49 -0
  203. package/src/layouts/navMenuItems.tsx +44 -0
  204. package/src/pages/LoginPage.tsx +80 -80
  205. package/src/pages/PlaceholderPage.tsx +9 -9
  206. package/src/permissions/resourcePermissions.ts +47 -47
  207. package/src/router/createAdminRouter.tsx +64 -64
  208. package/src/router/guards.tsx +63 -63
  209. package/src/router/routeAccess.ts +89 -89
  210. package/src/types.ts +10 -0
  211. package/src/vite-env.d.ts +1 -1
  212. package/dist/src/crud/context/SubmitFieldsContext.d.ts +0 -18
  213. package/dist/src/crud/context/SubmitFieldsContext.d.ts.map +0 -1
  214. package/dist/src/crud/utils/getByPath.d.ts +0 -3
  215. package/dist/src/crud/utils/getByPath.d.ts.map +0 -1
  216. package/dist/src/crud/utils/inlineArrayName.d.ts +0 -3
  217. package/dist/src/crud/utils/inlineArrayName.d.ts.map +0 -1
  218. package/dist/src/crud/utils/inlineFieldName.d.ts +0 -3
  219. package/dist/src/crud/utils/inlineFieldName.d.ts.map +0 -1
  220. package/dist/src/crud/utils/pickBySources.d.ts +0 -6
  221. package/dist/src/crud/utils/pickBySources.d.ts.map +0 -1
  222. package/dist/src/crud/utils/setByPath.d.ts +0 -3
  223. package/dist/src/crud/utils/setByPath.d.ts.map +0 -1
  224. package/dist/src/crud/utils/useResourceFormData.d.ts +0 -47
  225. package/dist/src/crud/utils/useResourceFormData.d.ts.map +0 -1
  226. package/src/crud/context/SubmitFieldsContext.tsx +0 -62
  227. package/src/crud/utils/inlineArrayName.ts +0 -4
  228. package/src/crud/utils/inlineFieldName.ts +0 -8
  229. package/src/crud/utils/pickBySources.ts +0 -24
  230. package/src/crud/utils/useResourceFormData.ts +0 -231
@@ -1,81 +1,87 @@
1
- import { Flex, theme } from "antd";
2
- import type { ReactNode } from "react";
3
- import { ThemeToolbar } from "../components/ThemeToolbar";
4
-
5
- export type AuthPageLayoutProps = {
6
- children: ReactNode;
7
- /** Brand mark centered above the card. */
8
- brand?: ReactNode;
9
- /** Row below the card (e.g. link to register or login). */
10
- footer?: ReactNode;
11
- showThemeToolbar?: boolean;
12
- };
13
-
14
- export function AuthPageLayout({
15
- children,
16
- brand,
17
- footer,
18
- showThemeToolbar = true,
19
- }: AuthPageLayoutProps) {
20
- const { token } = theme.useToken();
21
-
22
- return (
23
- <Flex
24
- vertical
25
- align="stretch"
26
- style={{
27
- height: "100dvh",
28
- maxHeight: "100dvh",
29
- width: "100%",
30
- overflow: "hidden",
31
- background: token.colorBgLayout,
32
- }}
33
- >
34
- {showThemeToolbar ? (
35
- <Flex
36
- justify="flex-end"
37
- style={{
38
- flexShrink: 0,
39
- width: "100%",
40
- padding: 16,
41
- background: token.colorBgLayout,
42
- }}
43
- >
44
- <ThemeToolbar />
45
- </Flex>
46
- ) : null}
47
- {brand ? (
48
- <div
49
- style={{
50
- flexShrink: 0,
51
- textAlign: "center",
52
- padding: "0 24px 16px",
53
- }}
54
- >
55
- {brand}
56
- </div>
57
- ) : null}
58
- <Flex
59
- flex={1}
60
- vertical
61
- align="center"
62
- justify="flex-start"
63
- style={{
64
- width: "100%",
65
- minHeight: 0,
66
- padding: "0 24px 24px",
67
- overflow: "auto",
68
- overflowX: "hidden",
69
- background: token.colorBgLayout,
70
- }}
71
- >
72
- {children}
73
- {footer ? (
74
- <div style={{ marginTop: 16, width: "100%", maxWidth: 520 }}>
75
- {footer}
76
- </div>
77
- ) : null}
78
- </Flex>
79
- </Flex>
80
- );
81
- }
1
+ import { Flex, theme } from "antd";
2
+ import type { ReactNode } from "react";
3
+ import { ScrollableArea } from "../components/ScrollableArea";
4
+ import { ThemeToolbar } from "../components/ThemeToolbar";
5
+
6
+ export type AuthPageLayoutProps = {
7
+ children: ReactNode;
8
+ /** Brand mark centered above the card. */
9
+ brand?: ReactNode;
10
+ /** Row below the card (e.g. link to register or login). */
11
+ footer?: ReactNode;
12
+ showThemeToolbar?: boolean;
13
+ };
14
+
15
+ export function AuthPageLayout({
16
+ children,
17
+ brand,
18
+ footer,
19
+ showThemeToolbar = true,
20
+ }: AuthPageLayoutProps) {
21
+ const { token } = theme.useToken();
22
+
23
+ return (
24
+ <Flex
25
+ vertical
26
+ align="stretch"
27
+ style={{
28
+ height: "100dvh",
29
+ maxHeight: "100dvh",
30
+ width: "100%",
31
+ overflow: "hidden",
32
+ background: token.colorBgLayout,
33
+ }}
34
+ >
35
+ {showThemeToolbar ? (
36
+ <Flex
37
+ justify="flex-end"
38
+ style={{
39
+ flexShrink: 0,
40
+ width: "100%",
41
+ padding: 16,
42
+ background: token.colorBgLayout,
43
+ }}
44
+ >
45
+ <ThemeToolbar />
46
+ </Flex>
47
+ ) : null}
48
+ {brand ? (
49
+ <div
50
+ style={{
51
+ flexShrink: 0,
52
+ textAlign: "center",
53
+ padding: "0 24px 16px",
54
+ }}
55
+ >
56
+ {brand}
57
+ </div>
58
+ ) : null}
59
+ <ScrollableArea
60
+ style={{
61
+ flex: 1,
62
+ minHeight: 0,
63
+ width: "100%",
64
+ background: token.colorBgLayout,
65
+ }}
66
+ >
67
+ <Flex
68
+ vertical
69
+ align="center"
70
+ justify="flex-start"
71
+ style={{
72
+ width: "100%",
73
+ minHeight: "100%",
74
+ padding: "0 24px 24px",
75
+ }}
76
+ >
77
+ {children}
78
+ {footer ? (
79
+ <div style={{ marginTop: 16, width: "100%", maxWidth: 520 }}>
80
+ {footer}
81
+ </div>
82
+ ) : null}
83
+ </Flex>
84
+ </ScrollableArea>
85
+ </Flex>
86
+ );
87
+ }
@@ -0,0 +1,49 @@
1
+ import type { NavItem } from "../types";
2
+
3
+ export function getNavItemLabel(item: NavItem): string {
4
+ const { label } = item;
5
+ if (typeof label === "string") return label;
6
+ if (typeof label === "number") return String(label);
7
+ return "";
8
+ }
9
+
10
+ export function filterNavItems(items: NavItem[], query: string): NavItem[] {
11
+ const q = query.trim().toLowerCase();
12
+ if (!q) return items;
13
+
14
+ function walk(nodes: NavItem[]): NavItem[] {
15
+ const result: NavItem[] = [];
16
+ for (const node of nodes) {
17
+ const label = getNavItemLabel(node).toLowerCase();
18
+ const selfMatch = label.includes(q);
19
+
20
+ if (node.children?.length) {
21
+ const filteredChildren = walk(node.children);
22
+ if (selfMatch) {
23
+ result.push(node);
24
+ } else if (filteredChildren.length > 0) {
25
+ result.push({ ...node, children: filteredChildren });
26
+ }
27
+ } else if (selfMatch) {
28
+ result.push(node);
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+
34
+ return walk(items);
35
+ }
36
+
37
+ export function collectSubmenuKeys(items: NavItem[]): string[] {
38
+ const keys: string[] = [];
39
+ function walk(nodes: NavItem[]) {
40
+ for (const node of nodes) {
41
+ if (node.children?.length) {
42
+ keys.push(node.path);
43
+ walk(node.children);
44
+ }
45
+ }
46
+ }
47
+ walk(items);
48
+ return keys;
49
+ }
@@ -0,0 +1,44 @@
1
+ import type { MenuProps } from "antd";
2
+ import { NavMenuLabel } from "../components/NavMenuLabel";
3
+ import type { NavItem } from "../types";
4
+ import { getNavItemLabel } from "./navFilter";
5
+
6
+ export function navItemsToAntdItems(
7
+ items: NavItem[],
8
+ options?: { showLabelTooltip?: boolean },
9
+ ): NonNullable<MenuProps["items"]> {
10
+ const showLabelTooltip = options?.showLabelTooltip !== false;
11
+
12
+ return items.map((item) => {
13
+ const IconComp = item.Icon;
14
+ const icon = IconComp ? <IconComp /> : undefined;
15
+ const title = getNavItemLabel(item);
16
+ const label =
17
+ title && showLabelTooltip ? (
18
+ <NavMenuLabel label={item.label} title={title} />
19
+ ) : (
20
+ item.label
21
+ );
22
+
23
+ // Expanded: NavMenuLabel tooltip only (omit `title` to avoid native DOM tooltip).
24
+ // Collapsed: string title for the sider icon tooltip.
25
+ const itemTitle: { title?: string } =
26
+ !showLabelTooltip && title ? { title } : {};
27
+
28
+ if (item.children?.length) {
29
+ return {
30
+ key: item.path,
31
+ icon,
32
+ label,
33
+ ...itemTitle,
34
+ children: navItemsToAntdItems(item.children, options),
35
+ };
36
+ }
37
+ return {
38
+ key: item.path,
39
+ icon,
40
+ label,
41
+ ...itemTitle,
42
+ };
43
+ });
44
+ }
@@ -1,80 +1,80 @@
1
- import { Button, Card, Form, Input, Typography } from "antd";
2
- import { useNavigate } from "react-router-dom";
3
- import { AuthAlternateLink } from "../components/AuthAlternateLink";
4
- import { useAuth } from "../context/AuthProvider";
5
- import { AuthPageLayout } from "../layouts/AuthPageLayout";
6
- import type { LoginPageProps } from "../types";
7
-
8
- export function LoginPage({
9
- title = "Sign in",
10
- description = "Use any username and password to continue.",
11
- logo,
12
- brand,
13
- extraFields,
14
- showThemeToolbar = true,
15
- afterLoginPath = "/",
16
- alternateAuth,
17
- footer,
18
- }: LoginPageProps) {
19
- const { login } = useAuth();
20
- const navigate = useNavigate();
21
- const brandNode = brand ?? logo;
22
-
23
- const resolvedFooter =
24
- footer ??
25
- (alternateAuth ? (
26
- <AuthAlternateLink
27
- prompt={alternateAuth.prompt ?? "Don't have an account?"}
28
- linkText={alternateAuth.linkText}
29
- to={alternateAuth.to}
30
- />
31
- ) : null);
32
-
33
- return (
34
- <AuthPageLayout
35
- brand={brandNode}
36
- footer={resolvedFooter}
37
- showThemeToolbar={showThemeToolbar}
38
- >
39
- <Card style={{ width: "100%", maxWidth: 360 }} title={title}>
40
- {description ? (
41
- <Typography.Paragraph type="secondary" style={{ marginTop: 0 }}>
42
- {description}
43
- </Typography.Paragraph>
44
- ) : null}
45
- <Form
46
- layout="vertical"
47
- onFinish={async (values: Record<string, unknown>) => {
48
- await login({
49
- username: String(values.username ?? ""),
50
- password: String(values.password ?? ""),
51
- ...values,
52
- });
53
- navigate(afterLoginPath, { replace: true });
54
- }}
55
- >
56
- <Form.Item
57
- name="username"
58
- label="Username"
59
- rules={[{ required: true, message: "Required" }]}
60
- >
61
- <Input autoComplete="username" />
62
- </Form.Item>
63
- <Form.Item
64
- name="password"
65
- label="Password"
66
- rules={[{ required: true, message: "Required" }]}
67
- >
68
- <Input.Password autoComplete="current-password" />
69
- </Form.Item>
70
- {extraFields}
71
- <Form.Item style={{ marginBottom: 0 }}>
72
- <Button type="primary" htmlType="submit" block>
73
- Log in
74
- </Button>
75
- </Form.Item>
76
- </Form>
77
- </Card>
78
- </AuthPageLayout>
79
- );
80
- }
1
+ import { Button, Card, Form, Input, Typography } from "antd";
2
+ import { useNavigate } from "react-router-dom";
3
+ import { AuthAlternateLink } from "../components/AuthAlternateLink";
4
+ import { useAuth } from "../context/AuthProvider";
5
+ import { AuthPageLayout } from "../layouts/AuthPageLayout";
6
+ import type { LoginPageProps } from "../types";
7
+
8
+ export function LoginPage({
9
+ title = "Sign in",
10
+ description = "Use any username and password to continue.",
11
+ logo,
12
+ brand,
13
+ extraFields,
14
+ showThemeToolbar = true,
15
+ afterLoginPath = "/",
16
+ alternateAuth,
17
+ footer,
18
+ }: LoginPageProps) {
19
+ const { login } = useAuth();
20
+ const navigate = useNavigate();
21
+ const brandNode = brand ?? logo;
22
+
23
+ const resolvedFooter =
24
+ footer ??
25
+ (alternateAuth ? (
26
+ <AuthAlternateLink
27
+ prompt={alternateAuth.prompt ?? "Don't have an account?"}
28
+ linkText={alternateAuth.linkText}
29
+ to={alternateAuth.to}
30
+ />
31
+ ) : null);
32
+
33
+ return (
34
+ <AuthPageLayout
35
+ brand={brandNode}
36
+ footer={resolvedFooter}
37
+ showThemeToolbar={showThemeToolbar}
38
+ >
39
+ <Card style={{ width: "100%", maxWidth: 360 }} title={title}>
40
+ {description ? (
41
+ <Typography.Paragraph type="secondary" style={{ marginTop: 0 }}>
42
+ {description}
43
+ </Typography.Paragraph>
44
+ ) : null}
45
+ <Form
46
+ layout="vertical"
47
+ onFinish={async (values: Record<string, unknown>) => {
48
+ await login({
49
+ username: String(values.username ?? ""),
50
+ password: String(values.password ?? ""),
51
+ ...values,
52
+ });
53
+ navigate(afterLoginPath, { replace: true });
54
+ }}
55
+ >
56
+ <Form.Item
57
+ name="username"
58
+ label="Username"
59
+ rules={[{ required: true, message: "Required" }]}
60
+ >
61
+ <Input autoComplete="username" />
62
+ </Form.Item>
63
+ <Form.Item
64
+ name="password"
65
+ label="Password"
66
+ rules={[{ required: true, message: "Required" }]}
67
+ >
68
+ <Input.Password autoComplete="current-password" />
69
+ </Form.Item>
70
+ {extraFields}
71
+ <Form.Item style={{ marginBottom: 0 }}>
72
+ <Button type="primary" htmlType="submit" block>
73
+ Log in
74
+ </Button>
75
+ </Form.Item>
76
+ </Form>
77
+ </Card>
78
+ </AuthPageLayout>
79
+ );
80
+ }
@@ -1,9 +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
- }
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
+ }
@@ -1,47 +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
- }
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
+ }
@@ -1,64 +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
- }
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
+ }