@firecms/collection_editor 3.0.0 → 3.1.0-canary.02232f4

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 (119) hide show
  1. package/dist/ConfigControllerProvider.d.ts +6 -0
  2. package/dist/api/generateCollectionApi.d.ts +71 -0
  3. package/dist/api/index.d.ts +1 -0
  4. package/dist/index.d.ts +5 -1
  5. package/dist/index.es.js +15234 -8138
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +15199 -8103
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/locales/de.d.ts +120 -0
  10. package/dist/locales/en.d.ts +120 -0
  11. package/dist/locales/es.d.ts +120 -0
  12. package/dist/locales/fr.d.ts +120 -0
  13. package/dist/locales/hi.d.ts +120 -0
  14. package/dist/locales/it.d.ts +120 -0
  15. package/dist/locales/pt.d.ts +120 -0
  16. package/dist/types/collection_editor_controller.d.ts +14 -0
  17. package/dist/types/collection_inference.d.ts +8 -2
  18. package/dist/types/config_controller.d.ts +31 -1
  19. package/dist/ui/AddKanbanColumnAction.d.ts +11 -0
  20. package/dist/ui/KanbanSetupAction.d.ts +10 -0
  21. package/dist/ui/collection_editor/AICollectionGeneratorPopover.d.ts +37 -0
  22. package/dist/ui/collection_editor/AIModifiedPathsContext.d.ts +20 -0
  23. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +2 -3
  24. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +24 -0
  25. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +4 -1
  26. package/dist/ui/collection_editor/CollectionJsonImportDialog.d.ts +7 -0
  27. package/dist/ui/collection_editor/CollectionYupValidation.d.ts +9 -13
  28. package/dist/ui/collection_editor/DisplaySettingsForm.d.ts +3 -0
  29. package/dist/ui/collection_editor/EntityActionsEditTab.d.ts +2 -1
  30. package/dist/ui/collection_editor/ExtendSettingsForm.d.ts +14 -0
  31. package/dist/ui/collection_editor/GeneralSettingsForm.d.ts +7 -0
  32. package/dist/ui/collection_editor/KanbanConfigSection.d.ts +4 -0
  33. package/dist/ui/collection_editor/PropertyEditView.d.ts +6 -1
  34. package/dist/ui/collection_editor/PropertyTree.d.ts +2 -1
  35. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +2 -1
  36. package/dist/ui/collection_editor/ViewModeSwitch.d.ts +6 -0
  37. package/dist/ui/collection_editor/properties/EnumPropertyField.d.ts +2 -1
  38. package/dist/ui/collection_editor/properties/conditions/ConditionsEditor.d.ts +10 -0
  39. package/dist/ui/collection_editor/properties/conditions/ConditionsPanel.d.ts +2 -0
  40. package/dist/ui/collection_editor/properties/conditions/EnumConditionsEditor.d.ts +6 -0
  41. package/dist/ui/collection_editor/properties/conditions/index.d.ts +6 -0
  42. package/dist/ui/collection_editor/properties/conditions/property_paths.d.ts +19 -0
  43. package/dist/useCollectionEditorPlugin.d.ts +7 -1
  44. package/dist/utils/validateCollectionJson.d.ts +22 -0
  45. package/package.json +15 -15
  46. package/src/ConfigControllerProvider.tsx +82 -47
  47. package/src/api/generateCollectionApi.ts +119 -0
  48. package/src/api/index.ts +1 -0
  49. package/src/index.ts +28 -1
  50. package/src/locales/de.ts +125 -0
  51. package/src/locales/en.ts +145 -0
  52. package/src/locales/es.ts +125 -0
  53. package/src/locales/fr.ts +125 -0
  54. package/src/locales/hi.ts +125 -0
  55. package/src/locales/it.ts +125 -0
  56. package/src/locales/pt.ts +125 -0
  57. package/src/types/collection_editor_controller.tsx +16 -3
  58. package/src/types/collection_inference.ts +15 -2
  59. package/src/types/config_controller.tsx +37 -1
  60. package/src/ui/AddKanbanColumnAction.tsx +203 -0
  61. package/src/ui/EditorCollectionAction.tsx +3 -3
  62. package/src/ui/EditorCollectionActionStart.tsx +1 -2
  63. package/src/ui/EditorEntityAction.tsx +3 -2
  64. package/src/ui/HomePageEditorCollectionAction.tsx +41 -13
  65. package/src/ui/KanbanSetupAction.tsx +38 -0
  66. package/src/ui/MissingReferenceWidget.tsx +1 -1
  67. package/src/ui/NewCollectionButton.tsx +4 -2
  68. package/src/ui/NewCollectionCard.tsx +7 -4
  69. package/src/ui/PropertyAddColumnComponent.tsx +4 -3
  70. package/src/ui/collection_editor/AICollectionGeneratorPopover.tsx +243 -0
  71. package/src/ui/collection_editor/AIModifiedPathsContext.tsx +88 -0
  72. package/src/ui/collection_editor/CollectionDetailsForm.tsx +222 -268
  73. package/src/ui/collection_editor/CollectionEditorDialog.tsx +270 -204
  74. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +138 -71
  75. package/src/ui/collection_editor/CollectionJsonImportDialog.tsx +171 -0
  76. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +202 -101
  77. package/src/ui/collection_editor/DisplaySettingsForm.tsx +335 -0
  78. package/src/ui/collection_editor/EntityActionsEditTab.tsx +106 -97
  79. package/src/ui/collection_editor/EntityActionsSelectDialog.tsx +8 -10
  80. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +5 -7
  81. package/src/ui/collection_editor/EnumForm.tsx +153 -102
  82. package/src/ui/collection_editor/ExtendSettingsForm.tsx +94 -0
  83. package/src/ui/collection_editor/GeneralSettingsForm.tsx +335 -0
  84. package/src/ui/collection_editor/GetCodeDialog.tsx +63 -41
  85. package/src/ui/collection_editor/KanbanConfigSection.tsx +209 -0
  86. package/src/ui/collection_editor/LayoutModeSwitch.tsx +27 -43
  87. package/src/ui/collection_editor/PropertyEditView.tsx +272 -199
  88. package/src/ui/collection_editor/PropertyFieldPreview.tsx +1 -1
  89. package/src/ui/collection_editor/PropertyTree.tsx +130 -58
  90. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +169 -163
  91. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +0 -2
  92. package/src/ui/collection_editor/ViewModeSwitch.tsx +43 -0
  93. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +6 -3
  94. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +5 -2
  95. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +0 -2
  96. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +4 -1
  97. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +6 -4
  98. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +126 -42
  99. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +32 -24
  100. package/src/ui/collection_editor/properties/MapPropertyField.tsx +8 -9
  101. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +128 -53
  102. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +3 -1
  103. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +6 -9
  104. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +65 -49
  105. package/src/ui/collection_editor/properties/StringPropertyField.tsx +3 -1
  106. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +12 -10
  107. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +23 -4
  108. package/src/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +866 -0
  109. package/src/ui/collection_editor/properties/conditions/ConditionsPanel.tsx +28 -0
  110. package/src/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +599 -0
  111. package/src/ui/collection_editor/properties/conditions/index.ts +6 -0
  112. package/src/ui/collection_editor/properties/conditions/property_paths.ts +92 -0
  113. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +5 -2
  114. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +7 -5
  115. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +10 -7
  116. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +11 -9
  117. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +5 -2
  118. package/src/useCollectionEditorPlugin.tsx +53 -22
  119. package/src/utils/validateCollectionJson.ts +380 -0
@@ -1,6 +1,7 @@
1
- import React from "react";
2
- import { EntityCollection, prettifyIdentifier, } from "@firecms/core";
3
- import { Card, Chip, cls, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
1
+ import React, { useState } from "react";
2
+ import { AIIcon, EntityCollection, prettifyIdentifier, useTranslation } from "@firecms/core";
3
+ import { Button, Card, Chip, cls, CodeIcon, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
4
+ import { CollectionJsonImportDialog } from "./CollectionJsonImportDialog";
4
5
 
5
6
  import { productsCollectionTemplate } from "./templates/products_template";
6
7
  import { blogCollectionTemplate } from "./templates/blog_template";
@@ -9,20 +10,27 @@ import { ImportFileUpload } from "@firecms/data_import";
9
10
  import { pagesCollectionTemplate } from "./templates/pages_template";
10
11
  import { useFormex } from "@firecms/formex";
11
12
  import { useCollectionEditorController } from "../../useCollectionEditorController";
13
+ import { AICollectionGeneratorPopover } from "./AICollectionGeneratorPopover";
14
+ import { CollectionGenerationCallback } from "../../api/generateCollectionApi";
12
15
 
13
16
  export function CollectionEditorWelcomeView({
14
- path,
15
- parentCollection,
16
- onContinue,
17
- existingCollectionPaths
18
- }: {
17
+ path,
18
+ parentCollection,
19
+ onContinue,
20
+ existingCollectionPaths,
21
+ generateCollection,
22
+ onAnalyticsEvent
23
+ }: {
19
24
  path: string;
20
25
  parentCollection?: EntityCollection;
21
26
  onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
22
27
  existingCollectionPaths?: string[];
28
+ generateCollection?: CollectionGenerationCallback;
29
+ onAnalyticsEvent?: (event: string, params?: object) => void;
23
30
  }) {
24
31
 
25
32
  const { pathSuggestions } = useCollectionEditorController();
33
+ const { t } = useTranslation();
26
34
 
27
35
  const filteredSuggestions = (pathSuggestions ?? []).filter(s => !(existingCollectionPaths ?? []).find(c => c.trim().toLowerCase() === s.trim().toLowerCase()));
28
36
 
@@ -33,6 +41,8 @@ export function CollectionEditorWelcomeView({
33
41
  submitCount
34
42
  } = useFormex<EntityCollection>();
35
43
 
44
+ const [jsonImportOpen, setJsonImportOpen] = useState(false);
45
+
36
46
  return (
37
47
  <div className={"overflow-auto my-auto"}>
38
48
  <Container maxWidth={"4xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
@@ -40,35 +50,35 @@ export function CollectionEditorWelcomeView({
40
50
  <div
41
51
  className="flex flex-row py-2 pt-3 items-center">
42
52
  <Typography variant={"h4"} className={"flex-grow"}>
43
- New collection
53
+ {t("new_collection")}
44
54
  </Typography>
45
55
  </div>
46
56
 
47
57
  {parentCollection && <Chip colorScheme={"tealDarker"}>
48
58
  <Typography variant={"caption"}>
49
- This is a subcollection of <b>{parentCollection.name}</b>
59
+ {t("this_is_subcollection_of")} <b>{parentCollection.name}</b>
50
60
  </Typography>
51
61
  </Chip>}
52
62
 
53
63
  {(filteredSuggestions ?? []).length > 0 && <div className={"my-2"}>
54
64
 
55
65
  <Typography variant={"caption"}
56
- color={"secondary"}>
57
- Use one of the existing paths in your database:
66
+ color={"secondary"}>
67
+ {t("use_existing_paths_database")}
58
68
  </Typography>
59
69
  <div className={"flex flex-wrap gap-x-2 gap-y-1 items-center my-2 min-h-7"}>
60
70
 
61
71
  {filteredSuggestions?.map((suggestion, index) => (
62
72
  <Chip key={suggestion}
63
- colorScheme={"cyanLighter"}
64
- onClick={() => {
65
- setFieldValue("name", prettifyIdentifier(suggestion));
66
- setFieldValue("id", suggestion);
67
- setFieldValue("path", suggestion);
68
- setFieldValue("properties", undefined);
69
- onContinue();
70
- }}
71
- size="small">
73
+ colorScheme={"cyanLighter"}
74
+ onClick={() => {
75
+ setFieldValue("name", prettifyIdentifier(suggestion));
76
+ setFieldValue("id", suggestion);
77
+ setFieldValue("path", suggestion);
78
+ setFieldValue("properties", undefined);
79
+ onContinue();
80
+ }}
81
+ size="small">
72
82
  {suggestion}
73
83
  </Chip>
74
84
  ))}
@@ -76,61 +86,118 @@ export function CollectionEditorWelcomeView({
76
86
  </div>
77
87
 
78
88
  </div>}
89
+ <div className="flex flex-row gap-8">
79
90
 
80
- <div className={"my-2"}>
81
- <Typography variant={"caption"}
82
- color={"secondary"}>
83
- ● Select a template:
84
- </Typography>
85
-
86
- <div className={"flex gap-4"}>
87
- <TemplateButton title={"Products"}
88
- subtitle={"A collection of products with images, prices and stock"}
89
- icon={<Icon size={"small"}
90
- iconKey={productsCollectionTemplate.icon! as string}/>}
91
- onClick={() => {
92
- setValues(productsCollectionTemplate);
93
- onContinue();
94
- }}/>
95
- <TemplateButton title={"Users"}
96
- subtitle={"A collection of users with emails, names and roles"}
97
- icon={<Icon size={"small"} iconKey={usersCollectionTemplate.icon! as string}/>}
98
- onClick={() => {
99
- setValues(usersCollectionTemplate);
100
- onContinue();
101
- }}/>
102
- <TemplateButton title={"Blog posts"}
103
- subtitle={"A collection of blog posts with images, authors and complex content"}
104
- icon={<Icon size={"small"} iconKey={blogCollectionTemplate.icon! as string}/>}
105
- onClick={() => {
106
- setValues(blogCollectionTemplate);
107
- onContinue();
108
- }}/>
109
- <TemplateButton title={"Pages"}
110
- subtitle={"A collection of pages with images, authors and complex content"}
111
- icon={<Icon size={"small"} iconKey={pagesCollectionTemplate.icon! as string}/>}
112
- onClick={() => {
113
- setValues(pagesCollectionTemplate);
114
- onContinue();
115
- }}/>
91
+ {generateCollection && (
92
+ <div className={"my-2"}>
93
+ <Typography variant={"caption"}
94
+ color={"secondary"}
95
+ className={"mb-2"}>
96
+ ● {t("describe_collection_ai")}
97
+ </Typography>
98
+
99
+ <AICollectionGeneratorPopover
100
+ onGenerated={(generatedCollection) => {
101
+ setValues(generatedCollection);
102
+ onContinue();
103
+ }}
104
+ generateCollection={generateCollection}
105
+ onAnalyticsEvent={onAnalyticsEvent}
106
+ trigger={
107
+ <Button
108
+ variant="outlined"
109
+ startIcon={<AIIcon size="small" />}
110
+ >
111
+ {t("generate_with_ai")}
112
+ </Button>
113
+ }
114
+ />
115
+ </div>
116
+ )}
117
+
118
+ <div className={"my-2"}>
119
+ <Typography variant={"caption"}
120
+ color={"secondary"}
121
+ className={"mb-2"}>
122
+ {t("create_from_json_config")}
123
+ </Typography>
124
+
125
+ <Button
126
+ variant={"outlined"}
127
+ onClick={() => setJsonImportOpen(true)}
128
+ startIcon={<CodeIcon size="small" />}
129
+ >
130
+ {t("paste_json_config")}
131
+ </Button>
132
+
133
+ <CollectionJsonImportDialog
134
+ open={jsonImportOpen}
135
+ onClose={() => setJsonImportOpen(false)}
136
+ onImport={(collection) => {
137
+ setValues(collection);
138
+ onContinue();
139
+ }}
140
+ />
116
141
  </div>
117
142
 
143
+
144
+
118
145
  </div>
119
146
 
147
+
120
148
  {!parentCollection && <div>
121
149
 
122
150
  <Typography variant={"caption"}
123
- color={"secondary"}
124
- className={"mb-2"}>
125
- Create a collection from a file (csv, json, xls, xslx...)
151
+ color={"secondary"}
152
+ className={"mb-2"}>
153
+ {t("create_collection_from_file_formats")}
126
154
  </Typography>
127
155
 
128
- <ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)}/>
156
+ <ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)} />
129
157
 
130
158
  </div>}
131
159
 
160
+ <div className={"my-2"}>
161
+ <Typography variant={"caption"}
162
+ color={"secondary"}>
163
+ ● {t("select_template")}
164
+ </Typography>
165
+
166
+ <div className={"flex gap-2"}>
167
+ <TemplateButton title={t("products")}
168
+ subtitle={t("collection_products_subtitle")}
169
+ icon={<Icon size={"small"}
170
+ iconKey={productsCollectionTemplate.icon! as string} />}
171
+ onClick={() => {
172
+ setValues(productsCollectionTemplate);
173
+ onContinue();
174
+ }} />
175
+ <TemplateButton title={t("users")}
176
+ subtitle={t("collection_users_subtitle")}
177
+ icon={<Icon size={"small"} iconKey={usersCollectionTemplate.icon! as string} />}
178
+ onClick={() => {
179
+ setValues(usersCollectionTemplate);
180
+ onContinue();
181
+ }} />
182
+ <TemplateButton title={t("blog_posts")}
183
+ subtitle={t("collection_blog_posts_subtitle")}
184
+ icon={<Icon size={"small"} iconKey={blogCollectionTemplate.icon! as string} />}
185
+ onClick={() => {
186
+ setValues(blogCollectionTemplate);
187
+ onContinue();
188
+ }} />
189
+ <TemplateButton title={t("pages")}
190
+ subtitle={t("collection_pages_subtitle")}
191
+ icon={<Icon size={"small"} iconKey={pagesCollectionTemplate.icon! as string} />}
192
+ onClick={() => {
193
+ setValues(pagesCollectionTemplate);
194
+ onContinue();
195
+ }} />
196
+ </div>
197
+
198
+ </div>
199
+
132
200
 
133
- {/*<div style={{ height: "52px" }}/>*/}
134
201
 
135
202
  </Container>
136
203
  </div>
@@ -138,11 +205,11 @@ export function CollectionEditorWelcomeView({
138
205
  }
139
206
 
140
207
  export function TemplateButton({
141
- title,
142
- subtitle,
143
- icon,
144
- onClick
145
- }: {
208
+ title,
209
+ subtitle,
210
+ icon,
211
+ onClick
212
+ }: {
146
213
  title: string,
147
214
  icon: React.ReactNode,
148
215
  subtitle: string,
@@ -151,12 +218,12 @@ export function TemplateButton({
151
218
 
152
219
  return (
153
220
  <Tooltip title={subtitle}
154
- asChild={true}>
221
+ asChild={true}>
155
222
  <Card
156
223
  onClick={onClick}
157
224
  className={cls(
158
- "my-2 rounded-md border mx-0 p-6 px-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
159
- "text-surface-700 dark:text-surface-accent-300",
225
+ "my-2 rounded-md border px-4 py-3 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
226
+ "text-text-secondary dark:text-text-secondary-dark",
160
227
  "hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
161
228
  "border-surface-400 dark:border-surface-600 "
162
229
  )}
@@ -164,7 +231,7 @@ export function TemplateButton({
164
231
  {icon}
165
232
  <div className={"flex flex-col items-start"}>
166
233
 
167
- <Typography variant={"subtitle1"}>
234
+ <Typography variant={"subtitle2"}>
168
235
  {title}
169
236
  </Typography>
170
237
 
@@ -0,0 +1,171 @@
1
+ import React, { useCallback, useState } from "react";
2
+ import {
3
+ Button,
4
+ cls,
5
+ CodeIcon,
6
+ Dialog,
7
+ DialogActions,
8
+ DialogContent,
9
+ DialogTitle,
10
+ Typography
11
+ } from "@firecms/ui";
12
+ import { EntityCollection } from "@firecms/core";
13
+ import { validateCollectionJson, CollectionValidationError } from "../../utils/validateCollectionJson";
14
+
15
+ const EXAMPLE_JSON = `{
16
+ "id": "products",
17
+ "name": "Products",
18
+ "path": "products",
19
+ "icon": "shopping_cart",
20
+ "properties": {
21
+ "name": {
22
+ "dataType": "string",
23
+ "name": "Name",
24
+ "validation": { "required": true }
25
+ },
26
+ "price": {
27
+ "dataType": "number",
28
+ "name": "Price"
29
+ },
30
+ "available": {
31
+ "dataType": "boolean",
32
+ "name": "Available"
33
+ }
34
+ }
35
+ }`;
36
+
37
+ export interface CollectionJsonImportDialogProps {
38
+ open: boolean;
39
+ onClose: () => void;
40
+ onImport: (collection: EntityCollection) => void;
41
+ }
42
+
43
+ export function CollectionJsonImportDialog({
44
+ open,
45
+ onClose,
46
+ onImport
47
+ }: CollectionJsonImportDialogProps) {
48
+ const [jsonValue, setJsonValue] = useState<string>("");
49
+ const [errors, setErrors] = useState<CollectionValidationError[]>([]);
50
+ const [touched, setTouched] = useState(false);
51
+
52
+ const handleJsonChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
53
+ const value = e.target.value;
54
+ setJsonValue(value);
55
+ setTouched(true);
56
+
57
+ if (!value.trim()) {
58
+ setErrors([]);
59
+ return;
60
+ }
61
+
62
+ const result = validateCollectionJson(value);
63
+ setErrors(result.errors);
64
+ }, []);
65
+
66
+ const handleImport = useCallback(() => {
67
+ const result = validateCollectionJson(jsonValue);
68
+ if (result.valid && result.collection) {
69
+ onImport(result.collection);
70
+ setJsonValue("");
71
+ setErrors([]);
72
+ setTouched(false);
73
+ onClose();
74
+ }
75
+ }, [jsonValue, onImport, onClose]);
76
+
77
+ const handleClose = useCallback(() => {
78
+ setJsonValue("");
79
+ setErrors([]);
80
+ setTouched(false);
81
+ onClose();
82
+ }, [onClose]);
83
+
84
+ const isValid = touched && jsonValue.trim() && errors.length === 0;
85
+
86
+ return (
87
+ <Dialog
88
+ open={open}
89
+ onOpenChange={(open) => !open && handleClose()}
90
+ maxWidth="2xl"
91
+ >
92
+ <DialogTitle className="flex items-center gap-2">
93
+ <CodeIcon size="small" />
94
+ Import Collection from JSON
95
+ </DialogTitle>
96
+ <DialogContent className="flex flex-col gap-4">
97
+ <Typography variant="body2" color="secondary">
98
+ Paste a JSON object representing your collection configuration.
99
+ The JSON must include <code className="bg-surface-200 dark:bg-surface-700 px-1 rounded">id</code>,
100
+ <code className="bg-surface-200 dark:bg-surface-700 px-1 rounded">name</code>,
101
+ <code className="bg-surface-200 dark:bg-surface-700 px-1 rounded">path</code>, and
102
+ <code className="bg-surface-200 dark:bg-surface-700 px-1 rounded">properties</code>.
103
+ </Typography>
104
+
105
+ <textarea
106
+ value={jsonValue}
107
+ onChange={handleJsonChange}
108
+ placeholder={EXAMPLE_JSON}
109
+ rows={12}
110
+ className={cls(
111
+ "w-full p-3 font-mono text-sm rounded-md border resize-none overflow-y-auto",
112
+ "bg-surface-50 dark:bg-surface-900",
113
+ "focus:outline-none focus:ring-2 focus:ring-primary",
114
+ "h-[300px]",
115
+ errors.length > 0 && touched
116
+ ? "border-red-500 dark:border-red-400"
117
+ : "border-surface-300 dark:border-surface-600"
118
+ )}
119
+ />
120
+
121
+ {errors.length > 0 && touched && (
122
+ <div className="p-3 bg-red-50 dark:bg-red-900/20 rounded-md border border-red-200 dark:border-red-800">
123
+ <Typography variant="body2" className="font-medium text-red-700 dark:text-red-400 mb-2">
124
+ Validation errors:
125
+ </Typography>
126
+ <ul className="list-disc list-inside space-y-1">
127
+ {errors.map((error, index) => (
128
+ <li key={index} className="text-sm text-red-600 dark:text-red-400">
129
+ {error.path ? (
130
+ <>
131
+ <code className="bg-red-100 dark:bg-red-900/40 px-1 rounded">
132
+ {error.path}
133
+ </code>
134
+ : {error.message}
135
+ </>
136
+ ) : (
137
+ error.message
138
+ )}
139
+ </li>
140
+ ))}
141
+ </ul>
142
+ </div>
143
+ )}
144
+
145
+ {isValid && (
146
+ <div className="p-3 bg-green-50 dark:bg-green-900/20 rounded-md border border-green-200 dark:border-green-800">
147
+ <Typography variant="body2" className="text-green-700 dark:text-green-400">
148
+ ✓ JSON is valid and ready to import
149
+ </Typography>
150
+ </div>
151
+ )}
152
+ </DialogContent>
153
+ <DialogActions>
154
+ <Button
155
+ variant="text"
156
+ onClick={handleClose}
157
+ >
158
+ Cancel
159
+ </Button>
160
+ <Button
161
+ variant="filled"
162
+ color="primary"
163
+ onClick={handleImport}
164
+ disabled={!isValid}
165
+ >
166
+ Import Collection
167
+ </Button>
168
+ </DialogActions>
169
+ </Dialog>
170
+ );
171
+ }