@rebasepro/admin 0.1.2 → 0.2.3

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 (243) hide show
  1. package/LICENSE +21 -6
  2. package/dist/{CollectionEditorDialog-ywdxhs1L.js → CollectionEditorDialog-CmGXXSY9.js} +42 -209
  3. package/dist/CollectionEditorDialog-CmGXXSY9.js.map +1 -0
  4. package/dist/{CollectionsStudioView-BDzMFzqH.js → CollectionsStudioView-DcLHT5bU.js} +6 -8
  5. package/dist/CollectionsStudioView-DcLHT5bU.js.map +1 -0
  6. package/dist/{ContentHomePage-0tHuEIm_.js → ContentHomePage-C7vFqKSe.js} +5 -7
  7. package/dist/ContentHomePage-C7vFqKSe.js.map +1 -0
  8. package/dist/{ExportCollectionAction-BIrq92To.js → ExportCollectionAction-BfN34eWX.js} +36 -38
  9. package/dist/ExportCollectionAction-BfN34eWX.js.map +1 -0
  10. package/dist/{ImportCollectionAction-h8yg_To8.js → ImportCollectionAction-SZrInjhx.js} +5 -7
  11. package/dist/ImportCollectionAction-SZrInjhx.js.map +1 -0
  12. package/dist/{PropertyEditView-BuZrNnBN.js → PropertyEditView-Cvryrb3B.js} +563 -489
  13. package/dist/PropertyEditView-Cvryrb3B.js.map +1 -0
  14. package/dist/{RolesView-CMPsaIXo.js → RolesView-BCb7qwWs.js} +22 -11
  15. package/dist/RolesView-BCb7qwWs.js.map +1 -0
  16. package/dist/{UsersView-BkeblMVT.js → UsersView-Cex24r8H.js} +7 -71
  17. package/dist/UsersView-Cex24r8H.js.map +1 -0
  18. package/dist/collection_editor/ui/collection_editor/LayoutModeSwitch.d.ts +2 -2
  19. package/dist/collection_editor/ui/collection_editor/properties/RelationPropertyField.d.ts +1 -7
  20. package/dist/collection_editor/ui/collection_editor/properties/VectorPropertyField.d.ts +3 -0
  21. package/dist/collection_editor_ui.js +5 -5
  22. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +1 -1
  23. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +1 -1
  24. package/dist/components/EntityCollectionView/EntityCollectionListView.d.ts +18 -2
  25. package/dist/components/EntityCollectionView/FilterPresetsButton.d.ts +21 -0
  26. package/dist/components/EntityDetailView.d.ts +31 -0
  27. package/dist/components/EntityEditView.d.ts +3 -2
  28. package/dist/components/ReferenceTable/EntitySelectionTable.d.ts +1 -1
  29. package/dist/components/admin/CreationResultDialog.d.ts +5 -0
  30. package/dist/components/admin/RolesFilterSelect.d.ts +2 -0
  31. package/dist/components/admin/UserRolesSelectField.d.ts +2 -0
  32. package/dist/components/common/default_entity_actions.d.ts +7 -1
  33. package/dist/components/field_configs.d.ts +1 -1
  34. package/dist/components/index.d.ts +1 -0
  35. package/dist/data_import/utils/data.d.ts +1 -1
  36. package/dist/data_import/utils/file_headers.d.ts +6 -1
  37. package/dist/data_import/utils/file_to_json.d.ts +1 -11
  38. package/dist/data_import/utils/transforms.d.ts +11 -0
  39. package/dist/editor.js +2 -4
  40. package/dist/editor.js.map +1 -1
  41. package/dist/form/EntityForm.d.ts +1 -1
  42. package/dist/form/field_bindings/RelationFieldBinding.d.ts +1 -1
  43. package/dist/form/field_bindings/VectorFieldBinding.d.ts +11 -0
  44. package/dist/form/index.d.ts +1 -0
  45. package/dist/hooks/navigation/useResolvedViews.d.ts +2 -1
  46. package/dist/{index-eRJbMvHi.js → index-DjduZG1T.js} +3 -3
  47. package/dist/index-DjduZG1T.js.map +1 -0
  48. package/dist/{index-BuZaHcyc.js → index-MKPc70-v.js} +3 -3
  49. package/dist/index-MKPc70-v.js.map +1 -0
  50. package/dist/{index-CS6uJ7oW.js → index-PLIQXpTt.js} +4 -6
  51. package/dist/index-PLIQXpTt.js.map +1 -0
  52. package/dist/index.d.ts +4 -1
  53. package/dist/index.js +352 -148
  54. package/dist/index.js.map +1 -1
  55. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  56. package/dist/types/components/EntityFormProps.d.ts +2 -1
  57. package/dist/types/fields.d.ts +3 -3
  58. package/dist/util/navigation_utils.d.ts +1 -1
  59. package/dist/{util-zfU1zOCX.js → util-DbWax_sV.js} +5453 -2641
  60. package/dist/util-DbWax_sV.js.map +1 -0
  61. package/package.json +46 -39
  62. package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
  63. package/src/collection_editor/ui/AddKanbanColumnAction.tsx +12 -2
  64. package/src/collection_editor/ui/CollectionViewHeaderAction.tsx +1 -2
  65. package/src/collection_editor/ui/EditorCollectionAction.tsx +1 -2
  66. package/src/collection_editor/ui/EditorCollectionActionStart.tsx +1 -2
  67. package/src/collection_editor/ui/EditorEntityAction.tsx +1 -2
  68. package/src/collection_editor/ui/HomePageEditorCollectionAction.tsx +1 -2
  69. package/src/collection_editor/ui/NewCollectionButton.tsx +1 -2
  70. package/src/collection_editor/ui/NewCollectionCard.tsx +4 -6
  71. package/src/collection_editor/ui/PropertyAddColumnComponent.tsx +1 -2
  72. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +10 -2
  73. package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +18 -2
  74. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +23 -17
  75. package/src/collection_editor/ui/collection_editor/CollectionEditorWelcomeView.tsx +16 -2
  76. package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +19 -9
  77. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +13 -2
  78. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +24 -2
  79. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +22 -3
  80. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +1 -2
  81. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +11 -2
  82. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +12 -2
  83. package/src/collection_editor/ui/collection_editor/EntityActionsEditTab.tsx +16 -3
  84. package/src/collection_editor/ui/collection_editor/EnumForm.tsx +17 -2
  85. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +18 -2
  86. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +1 -2
  87. package/src/collection_editor/ui/collection_editor/KanbanConfigSection.tsx +1 -2
  88. package/src/collection_editor/ui/collection_editor/LayoutModeSwitch.tsx +17 -5
  89. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +32 -6
  90. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +7 -7
  91. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +14 -2
  92. package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +16 -2
  93. package/src/collection_editor/ui/collection_editor/ViewModeSwitch.tsx +9 -2
  94. package/src/collection_editor/ui/collection_editor/properties/BlockPropertyField.tsx +1 -2
  95. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -2
  96. package/src/collection_editor/ui/collection_editor/properties/MarkdownPropertyField.tsx +9 -2
  97. package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +37 -57
  98. package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +11 -2
  99. package/src/collection_editor/ui/collection_editor/properties/VectorPropertyField.tsx +34 -0
  100. package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +15 -7
  101. package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsPanel.tsx +1 -2
  102. package/src/collection_editor/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +15 -3
  103. package/src/collection_editor/ui/collection_editor/properties/conditions/property_paths.ts +1 -1
  104. package/src/collection_editor/ui/collection_editor/properties/validation/ValidationPanel.tsx +1 -2
  105. package/src/collection_editor/useLocalCollectionsConfigController.tsx +0 -2
  106. package/src/collection_editor/validateCollectionJson.ts +97 -10
  107. package/src/components/AdminModeSyncer.tsx +1 -1
  108. package/src/components/ArrayContainer.tsx +19 -15
  109. package/src/components/ClearFilterSortButton.tsx +1 -2
  110. package/src/components/CollectionEditorDialogs.tsx +1 -1
  111. package/src/components/DefaultAppBar.tsx +15 -3
  112. package/src/components/DefaultDrawer.tsx +3 -3
  113. package/src/components/DrawerNavigationGroup.tsx +1 -2
  114. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +14 -6
  115. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +1 -1
  116. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
  117. package/src/components/EntityCollectionTable/fields/TableMultipleRelationField.tsx +1 -2
  118. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -2
  119. package/src/components/EntityCollectionTable/fields/TableRelationField.tsx +1 -2
  120. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +1 -2
  121. package/src/components/EntityCollectionTable/fields/VirtualTableSelect.tsx +0 -1
  122. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +15 -27
  123. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +1 -2
  124. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -2
  125. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -5
  126. package/src/components/EntityCollectionTable/table_bindings.tsx +51 -45
  127. package/src/components/EntityCollectionView/Board.tsx +1 -2
  128. package/src/components/EntityCollectionView/BoardColumn.tsx +9 -2
  129. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +5 -4
  130. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +18 -16
  131. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +16 -17
  132. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +87 -18
  133. package/src/components/EntityCollectionView/EntityCollectionView.tsx +20 -11
  134. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +6 -7
  135. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +14 -5
  136. package/src/components/EntityCollectionView/FilterPresetsButton.tsx +292 -0
  137. package/src/components/EntityCollectionView/FiltersDialog.tsx +1 -2
  138. package/src/components/EntityCollectionView/SplitListView.tsx +76 -25
  139. package/src/components/EntityCollectionView/ViewModeToggle.tsx +20 -7
  140. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +1 -1
  141. package/src/components/EntityCollectionView/useBoardDataController.tsx +74 -6
  142. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +1 -1
  143. package/src/components/EntityDetailView.tsx +619 -0
  144. package/src/components/EntityEditView.tsx +29 -10
  145. package/src/components/EntityEditViewFormActions.tsx +20 -7
  146. package/src/components/EntityPreview.tsx +14 -5
  147. package/src/components/EntitySidePanel.tsx +116 -62
  148. package/src/components/EntityView.tsx +1 -2
  149. package/src/components/HomePage/ContentHomePage.tsx +1 -1
  150. package/src/components/HomePage/FavouritesView.tsx +1 -2
  151. package/src/components/HomePage/NavigationCard.tsx +1 -2
  152. package/src/components/HomePage/NavigationCardBinding.tsx +1 -2
  153. package/src/components/HomePage/NavigationGroup.tsx +1 -2
  154. package/src/components/HomePage/SmallNavigationCard.tsx +1 -2
  155. package/src/components/PropertyIdCopyTooltip.tsx +1 -2
  156. package/src/components/RebaseAuthGate.tsx +2 -2
  157. package/src/components/RebaseNavigation.tsx +9 -7
  158. package/src/components/ReferenceTable/EntitySelectionTable.tsx +12 -8
  159. package/src/components/RelationSelector.tsx +34 -6
  160. package/src/components/SearchIconsView.tsx +10 -2
  161. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  162. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -2
  163. package/src/components/SideDialogs.tsx +63 -38
  164. package/src/components/UserSelector.tsx +30 -6
  165. package/src/components/admin/CreationResultDialog.tsx +135 -0
  166. package/src/components/admin/RolesFilterSelect.tsx +45 -0
  167. package/src/components/admin/RolesView.tsx +53 -14
  168. package/src/components/admin/UserRolesSelectField.tsx +50 -0
  169. package/src/components/admin/UsersView.tsx +41 -124
  170. package/src/components/app/Scaffold.tsx +1 -2
  171. package/src/components/common/default_entity_actions.tsx +119 -12
  172. package/src/components/field_configs.tsx +39 -3
  173. package/src/components/history/EntityHistoryEntry.tsx +1 -2
  174. package/src/components/history/EntityHistoryView.tsx +1 -2
  175. package/src/components/index.ts +2 -0
  176. package/src/data_export/export/BasicExportAction.tsx +35 -38
  177. package/src/data_export/export/ExportCollectionAction.tsx +39 -40
  178. package/src/data_import/components/DataNewPropertiesMapping.tsx +15 -2
  179. package/src/data_import/components/ImportFileUpload.tsx +1 -2
  180. package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +1 -2
  181. package/src/data_import/import/ImportCollectionAction.tsx +21 -8
  182. package/src/data_import/utils/data.ts +23 -5
  183. package/src/data_import/utils/file_headers.ts +13 -89
  184. package/src/data_import/utils/file_to_json.ts +43 -68
  185. package/src/data_import/utils/transforms.ts +47 -0
  186. package/src/editor/components/SlashCommandMenu.tsx +17 -2
  187. package/src/editor/components/editor-bubble-item.tsx +1 -1
  188. package/src/editor/extensions/Image/index.ts +1 -1
  189. package/src/editor/extensions/Image.ts +1 -1
  190. package/src/editor/selectors/color-selector.tsx +1 -2
  191. package/src/editor/selectors/link-selector.tsx +1 -2
  192. package/src/editor/selectors/node-selector.tsx +16 -2
  193. package/src/editor/selectors/text-buttons.tsx +1 -2
  194. package/src/editor/utils/prosemirror-utils.ts +1 -1
  195. package/src/form/EntityForm.tsx +16 -6
  196. package/src/form/EntityFormActions.tsx +11 -3
  197. package/src/form/PropertyFieldBinding.tsx +5 -12
  198. package/src/form/components/FieldHelperText.tsx +1 -2
  199. package/src/form/components/LocalChangesMenu.tsx +17 -2
  200. package/src/form/components/StorageItemPreview.tsx +1 -2
  201. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -2
  202. package/src/form/field_bindings/KeyValueFieldBinding.tsx +17 -2
  203. package/src/form/field_bindings/MapFieldBinding.tsx +1 -1
  204. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +10 -3
  205. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +1 -2
  206. package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +1 -2
  207. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +7 -7
  208. package/src/form/field_bindings/RelationFieldBinding.tsx +150 -147
  209. package/src/form/field_bindings/RepeatFieldBinding.tsx +1 -1
  210. package/src/form/field_bindings/SelectFieldBinding.tsx +1 -2
  211. package/src/form/field_bindings/TextFieldBinding.tsx +10 -2
  212. package/src/form/field_bindings/VectorFieldBinding.tsx +202 -0
  213. package/src/form/index.tsx +1 -0
  214. package/src/form/validation.ts +54 -2
  215. package/src/hooks/navigation/useBuildNavigationStateController.tsx +2 -1
  216. package/src/hooks/navigation/useResolvedViews.tsx +30 -15
  217. package/src/hooks/navigation/useTopLevelNavigation.ts +1 -1
  218. package/src/index.ts +6 -0
  219. package/src/preview/PropertyPreview.tsx +1 -1
  220. package/src/preview/components/ImagePreview.tsx +1 -1
  221. package/src/preview/components/UrlComponentPreview.tsx +1 -2
  222. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +2 -2
  223. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +23 -24
  224. package/src/routes/RebaseRoute.tsx +64 -35
  225. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  226. package/src/types/components/EntityFormProps.tsx +3 -1
  227. package/src/types/fields.tsx +4 -3
  228. package/src/util/navigation_utils.ts +4 -3
  229. package/src/util/previews.ts +1 -1
  230. package/src/util/property_utils.tsx +22 -6
  231. package/src/util/resolutions.ts +2 -2
  232. package/dist/CollectionEditorDialog-ywdxhs1L.js.map +0 -1
  233. package/dist/CollectionsStudioView-BDzMFzqH.js.map +0 -1
  234. package/dist/ContentHomePage-0tHuEIm_.js.map +0 -1
  235. package/dist/ExportCollectionAction-BIrq92To.js.map +0 -1
  236. package/dist/ImportCollectionAction-h8yg_To8.js.map +0 -1
  237. package/dist/PropertyEditView-BuZrNnBN.js.map +0 -1
  238. package/dist/RolesView-CMPsaIXo.js.map +0 -1
  239. package/dist/UsersView-BkeblMVT.js.map +0 -1
  240. package/dist/index-BuZaHcyc.js.map +0 -1
  241. package/dist/index-CS6uJ7oW.js.map +0 -1
  242. package/dist/index-eRJbMvHi.js.map +0 -1
  243. package/dist/util-zfU1zOCX.js.map +0 -1
@@ -1,8 +1,21 @@
1
1
 
2
2
  import React from "react";
3
3
  import { useFormex } from "@rebasepro/formex";
4
- import { IconButton, Select, SelectItem, DebouncedTextField, Typography, Chip, TextField, cls, defaultBorderMixin, Button, BooleanSwitchWithLabel , iconSize } from "@rebasepro/ui";
5
- import { Trash2Icon } from "lucide-react";
4
+ import {
5
+ BooleanSwitchWithLabel,
6
+ Button,
7
+ Chip,
8
+ cls,
9
+ DebouncedTextField,
10
+ defaultBorderMixin,
11
+ IconButton,
12
+ iconSize,
13
+ Select,
14
+ SelectItem,
15
+ TextField,
16
+ Trash2Icon,
17
+ Typography
18
+ } from "@rebasepro/ui";
6
19
  import { getFieldConfig, DEFAULT_FIELD_CONFIGS } from "../../../../_cms_internals";
7
20
  import { Properties, Property, EnumValueConfig } from "@rebasepro/types";
8
21
  import { isPropertyBuilder } from "@rebasepro/common";
@@ -806,17 +819,12 @@ export function ConditionsEditor({ disabled, collectionProperties }: ConditionsE
806
819
  // Get current conditions from form values
807
820
  const conditions: Record<string, unknown> = (values as PropertyWithId & { conditions?: Record<string, unknown> }).conditions ?? {};
808
821
 
809
- // DEBUG: Log conditions to see what's being loaded
810
- console.log("[ConditionsEditor] Loaded conditions:", conditions);
811
-
812
822
  const activeConditions: { type: ConditionType; group: ConditionGroup }[] = [];
813
823
 
814
824
  for (const type of CONDITION_TYPES) {
815
825
  const jsonLogic = conditions[type.id as keyof typeof conditions];
816
826
  if (jsonLogic) {
817
- console.log(`[ConditionsEditor] Parsing ${type.id}:`, jsonLogic);
818
827
  const group = jsonLogicToGroup(jsonLogic as Record<string, any>);
819
- console.log("[ConditionsEditor] Parsed group:", group);
820
828
  if (group) {
821
829
  activeConditions.push({ type: type.id,
822
830
  group });
@@ -1,7 +1,6 @@
1
1
  import { PropsWithChildren } from "react";
2
2
 
3
- import { ExpandablePanel, Typography } from "@rebasepro/ui";
4
- import { SettingsIcon } from "lucide-react";
3
+ import { ExpandablePanel, SettingsIcon, Typography } from "@rebasepro/ui";
5
4
 
6
5
  export function ConditionsPanel({
7
6
  children
@@ -1,8 +1,20 @@
1
1
 
2
2
  import React from "react";
3
3
  import { useFormex } from "@rebasepro/formex";
4
- import { IconButton, Select, SelectItem, Typography, MultiSelect, MultiSelectItem, cls, defaultBorderMixin, DebouncedTextField, BooleanSwitchWithLabel , iconSize } from "@rebasepro/ui";
5
- import { Trash2Icon } from "lucide-react";
4
+ import {
5
+ BooleanSwitchWithLabel,
6
+ cls,
7
+ DebouncedTextField,
8
+ defaultBorderMixin,
9
+ IconButton,
10
+ iconSize,
11
+ MultiSelect,
12
+ MultiSelectItem,
13
+ Select,
14
+ SelectItem,
15
+ Trash2Icon,
16
+ Typography
17
+ } from "@rebasepro/ui";
6
18
  import { getFieldConfig, DEFAULT_FIELD_CONFIGS } from "../../../../_cms_internals";
7
19
  import { Properties, Property, EnumValueConfig } from "@rebasepro/types";
8
20
  import { isPropertyBuilder } from "@rebasepro/common";
@@ -269,7 +281,7 @@ function parseEnumFilterRule(rule: Record<string, unknown>): EnumConditionConfig
269
281
  const selectedEnumIds = objectToArray(selectedEnumIdsRaw);
270
282
  if (selectedEnumIds.length === 0 && selectedEnumIdsRaw) {
271
283
  // If it was truthy but we got no values, parsing failed
272
- console.log("[EnumConditionsEditor] Warning: Could not parse selectedEnumIds:", selectedEnumIdsRaw);
284
+ console.warn("[EnumConditionsEditor] Could not parse selectedEnumIds:", selectedEnumIdsRaw);
273
285
  }
274
286
 
275
287
  // Parse the condition
@@ -39,7 +39,7 @@ export function getPropertyPaths(
39
39
 
40
40
  // For arrays with object items, add the nested paths too
41
41
  if (property.type === "array" && property.of) {
42
- const ofPropertyOrBuilder = property.of as unknown as Property;
42
+ const ofPropertyOrBuilder = property.of as Property;
43
43
  // Skip if the array's 'of' is a PropertyBuilder
44
44
  if (!isPropertyBuilder(ofPropertyOrBuilder)) {
45
45
  const ofProperty = ofPropertyOrBuilder as Property;
@@ -1,7 +1,6 @@
1
1
  import { PropsWithChildren } from "react";
2
2
 
3
- import { ExpandablePanel, Typography } from "@rebasepro/ui";
4
- import { CheckSquareIcon } from "lucide-react";
3
+ import { CheckSquareIcon, ExpandablePanel, Typography } from "@rebasepro/ui";
5
4
 
6
5
  export function ValidationPanel({
7
6
  children
@@ -16,7 +16,6 @@ export function useLocalCollectionsConfigController(
16
16
  const parsedCollections = baseCollections;
17
17
 
18
18
  const request = async (endpoint: string, payload: Record<string, unknown>) => {
19
- console.log("dispatching dev server request", endpoint, payload);
20
19
  try {
21
20
  let token = options?.getAuthToken ? await options.getAuthToken() : null;
22
21
  let baseUrl = typeof clientOrUrl === "string" ? clientOrUrl : "";
@@ -38,7 +37,6 @@ export function useLocalCollectionsConfigController(
38
37
  headers,
39
38
  body: JSON.stringify(payload)
40
39
  });
41
- console.log("dev server response", endpoint, response.status);
42
40
  if (!response.ok) {
43
41
  const text = await response.text();
44
42
  let err: Record<string, unknown> = {};
@@ -10,8 +10,11 @@ const VALID_DATA_TYPES = [
10
10
  "date",
11
11
  "geopoint",
12
12
  "reference",
13
+ "relation",
13
14
  "array",
14
- "map"
15
+ "map",
16
+ "vector",
17
+ "binary"
15
18
  ] as const;
16
19
 
17
20
  type DataType = typeof VALID_DATA_TYPES[number];
@@ -78,7 +81,7 @@ function validateProperty(
78
81
  validateProperty(ofProp, `${path}.of[${index}]`, errors);
79
82
  });
80
83
  } else {
81
- validateProperty(property.of as unknown as Record<string, unknown>, `${path}.of`, errors);
84
+ validateProperty(property.of as Record<string, unknown>, `${path}.of`, errors);
82
85
  }
83
86
  }
84
87
  // oneOf validation
@@ -88,15 +91,15 @@ function validateProperty(
88
91
  path: `${path}.oneOf`,
89
92
  message: "Must be an object"
90
93
  });
91
- } else if ((property.oneOf as unknown as Record<string, unknown>).properties) {
92
- validateProperties((property.oneOf as unknown as Record<string, unknown>).properties as unknown as Record<string, unknown>, `${path}.oneOf.properties`, errors);
94
+ } else if ((property.oneOf as Record<string, unknown>).properties) {
95
+ validateProperties((property.oneOf as Record<string, unknown>).properties as Record<string, unknown>, `${path}.oneOf.properties`, errors);
93
96
  }
94
97
  }
95
98
  }
96
99
 
97
100
  // Validate map properties
98
101
  if (property.dataType === "map" && property.properties) {
99
- validateProperties(property.properties as unknown as Record<string, unknown>, `${path}.properties`, errors);
102
+ validateProperties(property.properties as Record<string, unknown>, `${path}.properties`, errors);
100
103
  }
101
104
 
102
105
  // Validate reference path
@@ -109,6 +112,90 @@ function validateProperty(
109
112
  }
110
113
  }
111
114
 
115
+ // Validate relation property
116
+ if (property.dataType === "relation") {
117
+ if (property.target !== undefined && typeof property.target !== "string" && typeof property.target !== "function") {
118
+ errors.push({
119
+ path: `${path}.target`,
120
+ message: "Must be a string (collection slug) or a function"
121
+ });
122
+ }
123
+ if (property.cardinality !== undefined && property.cardinality !== "one" && property.cardinality !== "many") {
124
+ errors.push({
125
+ path: `${path}.cardinality`,
126
+ message: "Must be either 'one' or 'many'"
127
+ });
128
+ }
129
+ if (property.direction !== undefined && property.direction !== "owning" && property.direction !== "inverse") {
130
+ errors.push({
131
+ path: `${path}.direction`,
132
+ message: "Must be either 'owning' or 'inverse'"
133
+ });
134
+ }
135
+ if (property.localKey !== undefined && typeof property.localKey !== "string") {
136
+ errors.push({
137
+ path: `${path}.localKey`,
138
+ message: "Must be a string"
139
+ });
140
+ }
141
+ if (property.foreignKeyOnTarget !== undefined && typeof property.foreignKeyOnTarget !== "string") {
142
+ errors.push({
143
+ path: `${path}.foreignKeyOnTarget`,
144
+ message: "Must be a string"
145
+ });
146
+ }
147
+ if (property.through !== undefined) {
148
+ if (typeof property.through !== "object" || property.through === null) {
149
+ errors.push({
150
+ path: `${path}.through`,
151
+ message: "Must be an object"
152
+ });
153
+ } else {
154
+ const throughObj = property.through as Record<string, unknown>;
155
+ if (throughObj.table !== undefined && typeof throughObj.table !== "string") {
156
+ errors.push({
157
+ path: `${path}.through.table`,
158
+ message: "Must be a string"
159
+ });
160
+ }
161
+ if (throughObj.sourceColumn !== undefined && typeof throughObj.sourceColumn !== "string") {
162
+ errors.push({
163
+ path: `${path}.through.sourceColumn`,
164
+ message: "Must be a string"
165
+ });
166
+ }
167
+ if (throughObj.targetColumn !== undefined && typeof throughObj.targetColumn !== "string") {
168
+ errors.push({
169
+ path: `${path}.through.targetColumn`,
170
+ message: "Must be a string"
171
+ });
172
+ }
173
+ }
174
+ }
175
+ if (property.onUpdate !== undefined && typeof property.onUpdate !== "string") {
176
+ errors.push({
177
+ path: `${path}.onUpdate`,
178
+ message: "Must be a string"
179
+ });
180
+ }
181
+ if (property.onDelete !== undefined && typeof property.onDelete !== "string") {
182
+ errors.push({
183
+ path: `${path}.onDelete`,
184
+ message: "Must be a string"
185
+ });
186
+ }
187
+ }
188
+
189
+ // Validate vector property
190
+ if (property.dataType === "vector") {
191
+ if (property.dimensions !== undefined && (typeof property.dimensions !== "number" || isNaN(property.dimensions) || property.dimensions <= 0)) {
192
+ errors.push({
193
+ path: `${path}.dimensions`,
194
+ message: "Must be a positive number"
195
+ });
196
+ }
197
+ }
198
+
112
199
  // Validate storage config for string
113
200
  if (property.dataType === "string" && property.storage) {
114
201
  if (typeof property.storage !== "object") {
@@ -147,7 +234,7 @@ function validateProperties(
147
234
  }
148
235
 
149
236
  for (const [key, property] of Object.entries(properties)) {
150
- validateProperty(property as unknown as Record<string, unknown>, `${path}.${key}`, errors);
237
+ validateProperty(property as Record<string, unknown>, `${path}.${key}`, errors);
151
238
  }
152
239
  }
153
240
 
@@ -260,8 +347,8 @@ function validateOptionalFields(
260
347
  path: "kanban",
261
348
  message: "Must be an object"
262
349
  });
263
- } else if ((collection.kanban as unknown as Record<string, unknown>).columnProperty !== undefined &&
264
- typeof (collection.kanban as unknown as Record<string, unknown>).columnProperty !== "string") {
350
+ } else if ((collection.kanban as Record<string, unknown>).columnProperty !== undefined &&
351
+ typeof (collection.kanban as Record<string, unknown>).columnProperty !== "string") {
265
352
  errors.push({
266
353
  path: "kanban.columnProperty",
267
354
  message: "Must be a string"
@@ -316,7 +403,7 @@ function validateCollectionObject(
316
403
 
317
404
  // Properties validation
318
405
  if (collection.properties !== undefined) {
319
- validateProperties(collection.properties as unknown as Record<string, unknown>, "properties", errors);
406
+ validateProperties(collection.properties as Record<string, unknown>, "properties", errors);
320
407
  }
321
408
 
322
409
  // Optional fields
@@ -369,7 +456,7 @@ export function validateCollectionJson(jsonString: string): CollectionValidation
369
456
  };
370
457
  }
371
458
 
372
- validateCollectionObject(parsed as unknown as Record<string, unknown>, errors);
459
+ validateCollectionObject(parsed as Record<string, unknown>, errors);
373
460
 
374
461
  return {
375
462
  valid: errors.length === 0,
@@ -2,7 +2,7 @@ import type { AppView } from "@rebasepro/types";
2
2
  import React, { useEffect } from "react";
3
3
  import { useLocation } from "react-router-dom";
4
4
  ;
5
- import { useUrlController } from "../hooks";
5
+ import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
6
6
  import { useAdminModeController } from "@rebasepro/core";
7
7
  export interface AdminModeSyncerProps {
8
8
  /**
@@ -13,8 +13,22 @@ import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
13
13
 
14
14
  import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
15
15
  import { CSS } from "@dnd-kit/utilities";
16
- import { Button, cls, HandleIcon, IconButton, Menu, MenuItem, Tooltip, useOutsideAlerter , iconSize } from "@rebasepro/ui";
17
- import { PlusIcon, CopyIcon, ChevronDownIcon, ChevronUpIcon, MinusIcon } from "lucide-react";
16
+ import {
17
+ Button,
18
+ ChevronDownIcon,
19
+ ChevronUpIcon,
20
+ cls,
21
+ CopyIcon,
22
+ HandleIcon,
23
+ IconButton,
24
+ iconSize,
25
+ Menu,
26
+ MenuItem,
27
+ MinusIcon,
28
+ PlusIcon,
29
+ Tooltip,
30
+ useOutsideAlerter
31
+ } from "@rebasepro/ui";
18
32
  import { useTranslation } from "@rebasepro/core";
19
33
  import { getHashValue } from "@rebasepro/utils";
20
34
 
@@ -95,24 +109,14 @@ function SortableItem({
95
109
  transform,
96
110
  transition,
97
111
  isDragging
98
- } = sortable
99
- ? useSortable({ id })
100
- : {
101
- attributes: {},
102
- listeners: {},
103
- setNodeRef: (node: HTMLElement | null) => {
104
- },
105
- transform: null,
106
- transition: undefined,
107
- isDragging: false
108
- };
112
+ } = useSortable({ id });
109
113
 
110
114
  const style = transform ? {
111
115
  transform: CSS.Transform.toString(transform),
112
116
  transition
113
117
  } : {};
114
- const dragHandleProps = sortable ? { ...listeners,
115
- ...attributes } : {};
118
+ const dragHandleProps = { ...listeners,
119
+ ...attributes };
116
120
 
117
121
  return (
118
122
  <ArrayContainerItem
@@ -1,5 +1,4 @@
1
- import { Button, Tooltip , iconSize } from "@rebasepro/ui";
2
- import { FilterXIcon } from "lucide-react";
1
+ import { Button, FilterXIcon, iconSize, Tooltip } from "@rebasepro/ui";
3
2
  import { EntityTableController } from "@rebasepro/types";
4
3
  import { useTranslation } from "@rebasepro/core";
5
4
 
@@ -47,7 +47,7 @@ export function CollectionEditorDialogs() {
47
47
  <CollectionEditorDialog
48
48
  open={false}
49
49
  isNewCollection={false}
50
- configController={{} as any}
50
+ configController={undefined!}
51
51
  handleClose={() => {}}
52
52
  {...collectionDialogProps}
53
53
  />
@@ -4,10 +4,22 @@ import React from "react";
4
4
  import { Link, useNavigate } from "react-router-dom";
5
5
  import { RebaseLogo, LanguageToggle } from "@rebasepro/core";
6
6
  import { ErrorBoundary , iconSize } from "@rebasepro/ui";
7
- import { MoonIcon, SunIcon, SettingsIcon, SunMoonIcon, LogOutIcon } from "lucide-react";
8
- import { Avatar, cls, IconButton, Menu, MenuItem, Skeleton, Typography } from "@rebasepro/ui";
7
+ import {
8
+ Avatar,
9
+ cls,
10
+ IconButton,
11
+ LogOutIcon,
12
+ Menu,
13
+ MenuItem,
14
+ MoonIcon,
15
+ SettingsIcon,
16
+ Skeleton,
17
+ SunIcon,
18
+ SunMoonIcon,
19
+ Typography
20
+ } from "@rebasepro/ui";
9
21
  import { useAuthController, useLargeLayout, useModeController, useAdminModeController, useTranslation } from "@rebasepro/core";
10
- import { useUrlController } from "../hooks";
22
+ import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
11
23
  import { User } from "@rebasepro/types";
12
24
  import { useApp } from "./app/useApp";
13
25
  import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
@@ -2,13 +2,13 @@ import type { NavigationEntry, NavigationResult } from "@rebasepro/types";
2
2
  import React, { useMemo } from "react";
3
3
 
4
4
  import { useCollapsedGroups, buildCollapsedDefaults, useLargeLayout, useAdminModeController, useTranslation, useSlot, useRebaseContext, useAnalyticsController, useRebaseRegistry } from "@rebasepro/core";
5
- import { useNavigationStateController, useUrlController } from "../hooks";
5
+ import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
6
+ import { useNavigationStateController } from "../hooks/navigation/contexts/NavigationStateContext";
6
7
 
7
8
 
8
9
  import { Link, useNavigate } from "react-router-dom";
9
10
  import { AnalyticsEvent } from "@rebasepro/types";
10
- import { cls, Tooltip, Typography , iconSize } from "@rebasepro/ui";
11
- import { ChevronsLeftIcon, ChevronsRightIcon } from "lucide-react";
11
+ import { ChevronsLeftIcon, ChevronsRightIcon, cls, iconSize, Tooltip, Typography } from "@rebasepro/ui";
12
12
  import { DrawerNavigationGroup } from "./DrawerNavigationGroup";
13
13
  import { RebaseLogo } from "@rebasepro/core";
14
14
  import { useApp } from "./app/useApp";
@@ -1,7 +1,6 @@
1
1
  import type { NavigationEntry } from "@rebasepro/types";
2
2
  import React from "react";
3
- import { cls, iconSize, Typography } from "@rebasepro/ui";
4
- import { ChevronDownIcon } from "lucide-react";
3
+ import { ChevronDownIcon, cls, iconSize, Typography } from "@rebasepro/ui";
5
4
  ;
6
5
  import { IconForView } from "@rebasepro/core";
7
6
  import { DrawerNavigationItem } from "./DrawerNavigationItem";
@@ -3,12 +3,21 @@ import type { EntityAction } from "@rebasepro/types";
3
3
  import React, { MouseEvent, useCallback } from "react";
4
4
 
5
5
  import { CollectionSize, Entity, SelectionController } from "@rebasepro/types";
6
- import { Badge, Checkbox, cls, IconButton, Menu, MenuItem, Skeleton, Tooltip } from "@rebasepro/ui";
7
- import { MoreVerticalIcon } from "lucide-react";
8
- import { useRebaseContext, useLargeLayout, useTranslation } from "@rebasepro/core";
6
+ import {
7
+ Badge,
8
+ Checkbox,
9
+ cls,
10
+ IconButton,
11
+ Menu,
12
+ MenuItem,
13
+ MoreVerticalIcon,
14
+ Skeleton,
15
+ Tooltip
16
+ } from "@rebasepro/ui";
17
+ import { useTranslation } from "@rebasepro/core";
9
18
  import { getEntityFromCache } from "@rebasepro/core";
10
19
  import { getLocalChangesBackup } from "@rebasepro/common";
11
- import { useSideEntityController, useCMSContext } from "../../index";
20
+ import { useCMSContext } from "../../index";
12
21
 
13
22
  /**
14
23
  *
@@ -60,7 +69,7 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
60
69
  selectionController?: SelectionController;
61
70
  highlightEntity?: (entity: Entity<any>) => void;
62
71
  unhighlightEntity?: (entity: Entity<any>) => void;
63
- openEntityMode: "side_panel" | "full_screen" | "split";
72
+ openEntityMode: "side_panel" | "full_screen" | "split" | "dialog";
64
73
  // Sortable props for dnd-kit integration
65
74
  sortableNodeRef?: (node: HTMLElement | null) => void;
66
75
  sortableStyle?: React.CSSProperties;
@@ -69,7 +78,6 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
69
78
  isDraggable?: boolean;
70
79
  }) {
71
80
 
72
- const largeLayout = useLargeLayout();
73
81
 
74
82
  const context = useCMSContext();
75
83
  const sideEntityCtrl = context.sideEntityController;
@@ -213,7 +213,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
213
213
  : <>
214
214
  {additionalField.value?.({
215
215
  entity,
216
- context: context as unknown as RebaseContext
216
+ context: context as RebaseContext
217
217
  })?.toString()}
218
218
  </>;
219
219
 
@@ -146,7 +146,7 @@ export type EntityCollectionTableProps<M extends Record<string, unknown>,
146
146
 
147
147
  enablePopupIcon: boolean;
148
148
 
149
- openEntityMode?: "side_panel" | "full_screen" | "split";
149
+ openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
150
150
 
151
151
  /**
152
152
  * Callback when columns are reordered via drag-and-drop
@@ -3,8 +3,7 @@ import type { EntityCollection } from "@rebasepro/types";
3
3
  import React, { useCallback } from "react";
4
4
  import { deepEqual as equal } from "fast-equals";
5
5
 
6
- import { cls } from "@rebasepro/ui";
7
- import { PencilIcon } from "lucide-react";
6
+ import { cls, PencilIcon } from "@rebasepro/ui";
8
7
  import { getRelationFrom, normalizeToEntityRelation } from "@rebasepro/common";
9
8
 
10
9
  import { RelationPreview } from "../../../preview";
@@ -9,8 +9,7 @@ import { CollectionSize, Entity, EntityReference, FilterValues } from "@rebasepr
9
9
  import { getPreviewSizeFrom } from "../../../preview/util";
10
10
  import { useCustomizationController } from "@rebasepro/core";
11
11
  import { ErrorView } from "@rebasepro/core";
12
- import { cls } from "@rebasepro/ui";
13
- import { PencilIcon } from "lucide-react";
12
+ import { cls, PencilIcon } from "@rebasepro/ui";
14
13
  import { EntityPreviewContainer } from "../../EntityPreview";
15
14
  import { getReferenceFrom } from "@rebasepro/common";
16
15
  import { useCollectionRegistryController } from "../../../index";
@@ -9,8 +9,7 @@ import { CollectionSize, Entity, EntityRelation, FilterValues, Relation } from "
9
9
  import { getPreviewSizeFrom } from "../../../preview/util";
10
10
  import { } from "@rebasepro/core";
11
11
  import { ErrorView } from "@rebasepro/core";
12
- import { cls } from "@rebasepro/ui";
13
- import { PencilIcon } from "lucide-react";
12
+ import { cls, PencilIcon } from "@rebasepro/ui";
14
13
  import { EntityPreviewContainer } from "../../EntityPreview";
15
14
  import { getRelationFrom, normalizeToEntityRelation } from "@rebasepro/common";
16
15
  import { TableMultipleRelationField } from "./TableMultipleRelationField";
@@ -7,11 +7,10 @@ import { PreviewSize } from "../../../types";
7
7
  import { useDropzone } from "react-dropzone";
8
8
  import { PropertyPreview } from "../../../preview";
9
9
  import { ErrorBoundary } from "@rebasepro/ui";
10
- import { PencilIcon } from "lucide-react";
10
+ import { cls, IconButton, PencilIcon, Typography } from "@rebasepro/ui";
11
11
  import { useSnackbarController, useStorageSource, useTranslation, StorageFieldItem, useStorageUploadController } from "@rebasepro/core";
12
12
  import { getThumbnailMeasure } from "../../../preview/util";
13
13
  import { StorageUploadProgress } from "../../../form/components/StorageUploadProgress";
14
- import { cls, IconButton, Typography } from "@rebasepro/ui";
15
14
  import { EntityTableCellActions } from "../internal/EntityTableCellActions";
16
15
 
17
16
  const dropZoneClasses = "max-w-full box-border relative pt-[2px] items-center border border-transparent outline-hidden rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary-solid";
@@ -40,7 +40,6 @@ export function VirtualTableSelect(props: {
40
40
  }, [focused, ref]);
41
41
 
42
42
  const onChange = useCallback((updatedValue: string | string[]) => {
43
- console.trace("onChange");
44
43
  if (valueType === "number") {
45
44
  if (multiple) {
46
45
  const newValue = (updatedValue as string[]).map((v) => parseFloat(v));
@@ -49,15 +49,10 @@ export function CollectionTableToolbar({
49
49
  <div
50
50
  className={cls(defaultBorderMixin, "no-scrollbar min-h-[52px] overflow-x-auto px-2 md:px-4 bg-surface-50 dark:bg-surface-900 border-b flex flex-row justify-between items-center w-full")}>
51
51
 
52
- <div className="flex items-center gap-1 md:mr-4 mr-2">
52
+ <div className="flex items-center gap-1 md:mr-4 mr-2 min-w-0">
53
53
 
54
54
  {/* View toggle — hidden in compact */}
55
- <div className={cls(
56
- "transition-all duration-300 ease-out overflow-hidden",
57
- compact ? "max-w-0 opacity-0" : "max-w-[200px] opacity-100"
58
- )}>
59
- {viewModeToggle}
60
- </div>
55
+ {!compact && viewModeToggle}
61
56
 
62
57
  {title && <div className={"flex items-center"}>
63
58
  {title}
@@ -70,28 +65,21 @@ export function CollectionTableToolbar({
70
65
  <div className="flex items-center gap-1">
71
66
 
72
67
  {/* Loading spinner — hidden in compact */}
73
- {largeLayout && <div className={cls(
74
- "mr-4 transition-all duration-300 ease-out overflow-hidden",
75
- compact ? "w-0 opacity-0" : "w-[22px] opacity-100"
76
- )}>
77
- {loading &&
78
- <CircularProgress size={"smallest"}/>}
79
- </div>}
68
+ {largeLayout && !compact && loading && (
69
+ <div className="mr-4">
70
+ <CircularProgress size={"smallest"}/>
71
+ </div>
72
+ )}
80
73
 
81
74
  {/* Search bar — hidden in compact */}
82
- <div className={cls(
83
- "transition-all duration-300 ease-out",
84
- compact ? "max-w-0 opacity-0 overflow-hidden" : "max-w-[300px] opacity-100"
85
- )}>
86
- {onTextSearch &&
87
- <SearchBar
88
- key={"search-bar"}
89
- size={"small"}
90
- placeholder={t("search")}
91
- onTextSearch={onTextSearch}
92
- expandable={true}
93
- initialValue={initialSearchText}/>}
94
- </div>
75
+ {!compact && onTextSearch &&
76
+ <SearchBar
77
+ key={"search-bar"}
78
+ size={"small"}
79
+ placeholder={t("search")}
80
+ onTextSearch={onTextSearch}
81
+ expandable={true}
82
+ initialValue={initialSearchText}/>}
95
83
 
96
84
  {/* Secondary actions — always inline */}
97
85
  <div className="flex items-center gap-1">
@@ -3,8 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
3
3
  import useMeasure from "react-use-measure";
4
4
 
5
5
  import { cls, Tooltip , iconSize } from "@rebasepro/ui";
6
- import { MinusCircleIcon } from "lucide-react";
7
- import { ErrorBoundary } from "@rebasepro/ui";
6
+ import { ErrorBoundary, MinusCircleIcon } from "@rebasepro/ui";
8
7
  import { getRowHeight, TableSize } from "@rebasepro/core";
9
8
  import { ErrorTooltip } from "@rebasepro/core";
10
9
 
@@ -1,5 +1,4 @@
1
- import { IconButton } from "@rebasepro/ui";
2
- import { AlertCircleIcon } from "lucide-react";
1
+ import { AlertCircleIcon, IconButton } from "@rebasepro/ui";
3
2
  import { ErrorTooltip } from "@rebasepro/core";
4
3
  import { useCallback, useEffect, useRef } from "react";
5
4