@burdenoff/microfe-vibecontrols 2026.519.6 → 2026.520.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/agents/AgentForm.js +152 -148
- package/dist/components/agents/AgentForm.js.map +1 -1
- package/dist/components/catalog/CatalogForm.js +46 -42
- package/dist/components/catalog/CatalogForm.js.map +1 -1
- package/dist/components/idp-components/ComponentForm.js +69 -65
- package/dist/components/idp-components/ComponentForm.js.map +1 -1
- package/dist/components/shared/EntityTagPicker.js +5 -1
- package/dist/components/shared/EntityTagPicker.js.map +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +45 -54
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/hooks/useDraftEntityTags.js +29 -0
- package/dist/hooks/useDraftEntityTags.js.map +1 -0
- package/dist/pages/ActionsPage.js +109 -96
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +345 -337
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +107 -97
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +126 -116
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +598 -586
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +499 -485
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +310 -300
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibesPage.js +309 -296
- package/dist/pages/VibesPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,11 +3,11 @@ import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProv
|
|
|
3
3
|
import { X as r } from "lucide-react";
|
|
4
4
|
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
5
|
//#region src/components/idp-components/ComponentForm.tsx
|
|
6
|
-
var o = ({ isOpen: o, onClose: s, onSubmit: c, initialData: l, title: u }) => {
|
|
7
|
-
let { t:
|
|
8
|
-
let n =
|
|
6
|
+
var o = ({ isOpen: o, onClose: s, onSubmit: c, initialData: l, title: u, extraSection: d }) => {
|
|
7
|
+
let { t: f } = n(), p = (e, t) => {
|
|
8
|
+
let n = f(e);
|
|
9
9
|
return n === e ? t : n;
|
|
10
|
-
},
|
|
10
|
+
}, m = e(null), h = e(null), g = e(null), _ = e(null), v = e(null), y = e(null), b = e(null), x = e(null), S = e(null), [C, w] = t(!1), [T, E] = t(null), [D, O] = t({});
|
|
11
11
|
return o ? /* @__PURE__ */ i("div", {
|
|
12
12
|
className: "fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4",
|
|
13
13
|
children: /* @__PURE__ */ a("div", {
|
|
@@ -17,7 +17,7 @@ var o = ({ isOpen: o, onClose: s, onSubmit: c, initialData: l, title: u }) => {
|
|
|
17
17
|
className: "flex items-center justify-between mb-4",
|
|
18
18
|
children: [/* @__PURE__ */ i("h2", {
|
|
19
19
|
className: "text-lg font-semibold text-text-primary",
|
|
20
|
-
children: u ||
|
|
20
|
+
children: u || p("component.createComponent", "Create Component")
|
|
21
21
|
}), /* @__PURE__ */ i("button", {
|
|
22
22
|
onClick: s,
|
|
23
23
|
className: "p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary",
|
|
@@ -27,38 +27,38 @@ var o = ({ isOpen: o, onClose: s, onSubmit: c, initialData: l, title: u }) => {
|
|
|
27
27
|
/* @__PURE__ */ a("div", {
|
|
28
28
|
className: "space-y-4",
|
|
29
29
|
children: [
|
|
30
|
-
|
|
30
|
+
T && /* @__PURE__ */ i("div", {
|
|
31
31
|
className: "p-3 bg-status-error-bg text-status-error-text text-sm rounded-md",
|
|
32
|
-
children:
|
|
32
|
+
children: T
|
|
33
33
|
}),
|
|
34
34
|
/* @__PURE__ */ a("div", { children: [
|
|
35
35
|
/* @__PURE__ */ i("label", {
|
|
36
36
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
37
|
-
children:
|
|
37
|
+
children: p("component.nameLabel", "Name *")
|
|
38
38
|
}),
|
|
39
39
|
/* @__PURE__ */ i("input", {
|
|
40
|
-
ref:
|
|
40
|
+
ref: m,
|
|
41
41
|
type: "text",
|
|
42
42
|
defaultValue: l?.name || "",
|
|
43
|
-
placeholder:
|
|
44
|
-
onChange: () =>
|
|
43
|
+
placeholder: p("component.namePlaceholder", "my-service"),
|
|
44
|
+
onChange: () => D.name && O((e) => ({
|
|
45
45
|
...e,
|
|
46
46
|
name: void 0
|
|
47
47
|
})),
|
|
48
|
-
className: `w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder ${
|
|
48
|
+
className: `w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder ${D.name ? "border-status-error-border" : "border-border-default"}`
|
|
49
49
|
}),
|
|
50
|
-
|
|
50
|
+
D.name && /* @__PURE__ */ i("p", {
|
|
51
51
|
className: "mt-1 text-xs text-status-error-text",
|
|
52
|
-
children:
|
|
52
|
+
children: D.name
|
|
53
53
|
})
|
|
54
54
|
] }),
|
|
55
55
|
/* @__PURE__ */ a("div", { children: [/* @__PURE__ */ i("label", {
|
|
56
56
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
57
|
-
children:
|
|
57
|
+
children: p("component.descriptionLabel", "Description")
|
|
58
58
|
}), /* @__PURE__ */ i("textarea", {
|
|
59
|
-
ref:
|
|
59
|
+
ref: h,
|
|
60
60
|
defaultValue: l?.description || "",
|
|
61
|
-
placeholder:
|
|
61
|
+
placeholder: p("component.descriptionPlaceholder", "Describe this component..."),
|
|
62
62
|
rows: 3,
|
|
63
63
|
className: "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder resize-none"
|
|
64
64
|
})] }),
|
|
@@ -66,64 +66,64 @@ var o = ({ isOpen: o, onClose: s, onSubmit: c, initialData: l, title: u }) => {
|
|
|
66
66
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
|
|
67
67
|
children: [/* @__PURE__ */ a("div", { children: [/* @__PURE__ */ i("label", {
|
|
68
68
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
69
|
-
children:
|
|
69
|
+
children: p("component.typeLabel", "Type")
|
|
70
70
|
}), /* @__PURE__ */ a("select", {
|
|
71
|
-
ref:
|
|
71
|
+
ref: g,
|
|
72
72
|
defaultValue: l?.type || "SERVICE",
|
|
73
73
|
className: "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary",
|
|
74
74
|
children: [
|
|
75
75
|
/* @__PURE__ */ i("option", {
|
|
76
76
|
value: "SERVICE",
|
|
77
|
-
children:
|
|
77
|
+
children: p("component.typeService", "Service")
|
|
78
78
|
}),
|
|
79
79
|
/* @__PURE__ */ i("option", {
|
|
80
80
|
value: "FRONTEND",
|
|
81
|
-
children:
|
|
81
|
+
children: p("component.typeFrontend", "Frontend")
|
|
82
82
|
}),
|
|
83
83
|
/* @__PURE__ */ i("option", {
|
|
84
84
|
value: "LIBRARY",
|
|
85
|
-
children:
|
|
85
|
+
children: p("component.typeLibrary", "Library")
|
|
86
86
|
}),
|
|
87
87
|
/* @__PURE__ */ i("option", {
|
|
88
88
|
value: "API",
|
|
89
|
-
children:
|
|
89
|
+
children: p("component.typeApi", "API")
|
|
90
90
|
}),
|
|
91
91
|
/* @__PURE__ */ i("option", {
|
|
92
92
|
value: "MOBILE",
|
|
93
|
-
children:
|
|
93
|
+
children: p("component.typeMobile", "Mobile")
|
|
94
94
|
}),
|
|
95
95
|
/* @__PURE__ */ i("option", {
|
|
96
96
|
value: "DATABASE",
|
|
97
|
-
children:
|
|
97
|
+
children: p("component.typeDatabase", "Database")
|
|
98
98
|
}),
|
|
99
99
|
/* @__PURE__ */ i("option", {
|
|
100
100
|
value: "INFRASTRUCTURE",
|
|
101
|
-
children:
|
|
101
|
+
children: p("component.typeInfrastructure", "Infrastructure")
|
|
102
102
|
}),
|
|
103
103
|
/* @__PURE__ */ i("option", {
|
|
104
104
|
value: "CUSTOM",
|
|
105
|
-
children:
|
|
105
|
+
children: p("component.typeCustom", "Custom")
|
|
106
106
|
})
|
|
107
107
|
]
|
|
108
108
|
})] }), /* @__PURE__ */ a("div", { children: [/* @__PURE__ */ i("label", {
|
|
109
109
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
110
|
-
children:
|
|
110
|
+
children: p("component.lifecycleLabel", "Lifecycle")
|
|
111
111
|
}), /* @__PURE__ */ a("select", {
|
|
112
|
-
ref:
|
|
112
|
+
ref: _,
|
|
113
113
|
defaultValue: l?.lifecycle || "EXPERIMENTAL",
|
|
114
114
|
className: "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary",
|
|
115
115
|
children: [
|
|
116
116
|
/* @__PURE__ */ i("option", {
|
|
117
117
|
value: "EXPERIMENTAL",
|
|
118
|
-
children:
|
|
118
|
+
children: p("component.lifecycleExperimental", "Experimental")
|
|
119
119
|
}),
|
|
120
120
|
/* @__PURE__ */ i("option", {
|
|
121
121
|
value: "PRODUCTION",
|
|
122
|
-
children:
|
|
122
|
+
children: p("component.lifecycleProduction", "Production")
|
|
123
123
|
}),
|
|
124
124
|
/* @__PURE__ */ i("option", {
|
|
125
125
|
value: "DEPRECATED",
|
|
126
|
-
children:
|
|
126
|
+
children: p("component.lifecycleDeprecated", "Deprecated")
|
|
127
127
|
})
|
|
128
128
|
]
|
|
129
129
|
})] })]
|
|
@@ -132,21 +132,21 @@ var o = ({ isOpen: o, onClose: s, onSubmit: c, initialData: l, title: u }) => {
|
|
|
132
132
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
|
|
133
133
|
children: [/* @__PURE__ */ a("div", { children: [/* @__PURE__ */ i("label", {
|
|
134
134
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
135
|
-
children:
|
|
135
|
+
children: p("component.languageLabel", "Language")
|
|
136
136
|
}), /* @__PURE__ */ i("input", {
|
|
137
|
-
ref:
|
|
137
|
+
ref: v,
|
|
138
138
|
type: "text",
|
|
139
139
|
defaultValue: l?.language || "",
|
|
140
|
-
placeholder:
|
|
140
|
+
placeholder: p("component.languagePlaceholder", "TypeScript"),
|
|
141
141
|
className: "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder"
|
|
142
142
|
})] }), /* @__PURE__ */ a("div", { children: [/* @__PURE__ */ i("label", {
|
|
143
143
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
144
|
-
children:
|
|
144
|
+
children: p("component.frameworkLabel", "Framework")
|
|
145
145
|
}), /* @__PURE__ */ i("input", {
|
|
146
|
-
ref:
|
|
146
|
+
ref: y,
|
|
147
147
|
type: "text",
|
|
148
148
|
defaultValue: l?.framework || "",
|
|
149
|
-
placeholder:
|
|
149
|
+
placeholder: p("component.frameworkPlaceholder", "React"),
|
|
150
150
|
className: "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder"
|
|
151
151
|
})] })]
|
|
152
152
|
}),
|
|
@@ -154,74 +154,78 @@ var o = ({ isOpen: o, onClose: s, onSubmit: c, initialData: l, title: u }) => {
|
|
|
154
154
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
|
|
155
155
|
children: [/* @__PURE__ */ a("div", { children: [/* @__PURE__ */ i("label", {
|
|
156
156
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
157
|
-
children:
|
|
157
|
+
children: p("component.versionLabel", "Version")
|
|
158
158
|
}), /* @__PURE__ */ i("input", {
|
|
159
|
-
ref:
|
|
159
|
+
ref: b,
|
|
160
160
|
type: "text",
|
|
161
161
|
defaultValue: l?.version || "",
|
|
162
|
-
placeholder:
|
|
162
|
+
placeholder: p("component.versionPlaceholder", "1.0.0"),
|
|
163
163
|
className: "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder"
|
|
164
164
|
})] }), /* @__PURE__ */ a("div", { children: [/* @__PURE__ */ i("label", {
|
|
165
165
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
166
|
-
children:
|
|
166
|
+
children: p("component.ownerLabel", "Owner")
|
|
167
167
|
}), /* @__PURE__ */ i("input", {
|
|
168
|
-
ref:
|
|
168
|
+
ref: x,
|
|
169
169
|
type: "text",
|
|
170
170
|
defaultValue: l?.owner || "",
|
|
171
|
-
placeholder:
|
|
171
|
+
placeholder: p("component.ownerPlaceholder", "team-name"),
|
|
172
172
|
className: "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder"
|
|
173
173
|
})] })]
|
|
174
174
|
}),
|
|
175
175
|
/* @__PURE__ */ a("div", { children: [/* @__PURE__ */ i("label", {
|
|
176
176
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
177
|
-
children:
|
|
177
|
+
children: p("component.tagsLabel", "Tags (comma-separated)")
|
|
178
178
|
}), /* @__PURE__ */ i("input", {
|
|
179
|
-
ref:
|
|
179
|
+
ref: S,
|
|
180
180
|
type: "text",
|
|
181
181
|
defaultValue: l?.tags?.join(", ") || "",
|
|
182
|
-
placeholder:
|
|
182
|
+
placeholder: p("component.tagsPlaceholder", "backend, api, graphql"),
|
|
183
183
|
className: "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder"
|
|
184
184
|
})] })
|
|
185
185
|
]
|
|
186
186
|
}),
|
|
187
|
+
d && /* @__PURE__ */ i("div", {
|
|
188
|
+
className: "mt-4 border-t border-border-default pt-4",
|
|
189
|
+
children: d
|
|
190
|
+
}),
|
|
187
191
|
/* @__PURE__ */ a("div", {
|
|
188
192
|
className: "flex flex-col sm:flex-row justify-end gap-3 mt-6",
|
|
189
193
|
children: [/* @__PURE__ */ i("button", {
|
|
190
194
|
onClick: s,
|
|
191
|
-
disabled:
|
|
195
|
+
disabled: C,
|
|
192
196
|
className: "px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50",
|
|
193
|
-
children:
|
|
197
|
+
children: p("common.cancel", "Cancel")
|
|
194
198
|
}), /* @__PURE__ */ i("button", {
|
|
195
199
|
onClick: async () => {
|
|
196
|
-
let e =
|
|
200
|
+
let e = m.current?.value?.trim();
|
|
197
201
|
if (!e) {
|
|
198
|
-
|
|
202
|
+
O({ name: p("component.nameRequired", "Name is required") }), E(p("component.fillRequired", "Please fill in all required fields"));
|
|
199
203
|
return;
|
|
200
204
|
}
|
|
201
|
-
|
|
205
|
+
O({});
|
|
202
206
|
let t = {
|
|
203
207
|
name: e,
|
|
204
|
-
description:
|
|
205
|
-
type:
|
|
206
|
-
lifecycle:
|
|
207
|
-
language:
|
|
208
|
-
framework:
|
|
209
|
-
version:
|
|
210
|
-
owner:
|
|
211
|
-
tags:
|
|
208
|
+
description: h.current?.value?.trim() || "",
|
|
209
|
+
type: g.current?.value || "SERVICE",
|
|
210
|
+
lifecycle: _.current?.value || "EXPERIMENTAL",
|
|
211
|
+
language: v.current?.value?.trim() || "",
|
|
212
|
+
framework: y.current?.value?.trim() || "",
|
|
213
|
+
version: b.current?.value?.trim() || "",
|
|
214
|
+
owner: x.current?.value?.trim() || "",
|
|
215
|
+
tags: S.current?.value ? S.current.value.split(",").map((e) => e.trim()).filter(Boolean) : []
|
|
212
216
|
};
|
|
213
|
-
|
|
217
|
+
w(!0), E(null);
|
|
214
218
|
try {
|
|
215
219
|
await c(t), s();
|
|
216
220
|
} catch (e) {
|
|
217
|
-
|
|
221
|
+
E(e instanceof Error ? e.message : p("component.failedSave", "Failed to save component"));
|
|
218
222
|
} finally {
|
|
219
|
-
|
|
223
|
+
w(!1);
|
|
220
224
|
}
|
|
221
225
|
},
|
|
222
|
-
disabled:
|
|
226
|
+
disabled: C,
|
|
223
227
|
className: "px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50",
|
|
224
|
-
children:
|
|
228
|
+
children: C ? p("common.saving", "Saving...") : l?.name ? p("common.save", "Save") : p("component.createComponent", "Create Component")
|
|
225
229
|
})]
|
|
226
230
|
})
|
|
227
231
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentForm.js","names":[],"sources":["../../../src/components/idp-components/ComponentForm.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport type { FC } from 'react';\nimport { X } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport interface ComponentFormData {\n name: string;\n description: string;\n type:\n | 'SERVICE'\n | 'FRONTEND'\n | 'LIBRARY'\n | 'API'\n | 'MOBILE'\n | 'DATABASE'\n | 'INFRASTRUCTURE'\n | 'CUSTOM';\n lifecycle: 'EXPERIMENTAL' | 'PRODUCTION' | 'DEPRECATED';\n language: string;\n framework: string;\n version: string;\n owner: string;\n tags: string[];\n}\n\nexport interface ComponentFormProps {\n isOpen: boolean;\n onClose: () => void;\n onSubmit: (data: ComponentFormData) => Promise<void>;\n initialData?: Partial<ComponentFormData>;\n title?: string;\n}\n\nexport const ComponentForm: FC<ComponentFormProps> = ({\n isOpen,\n onClose,\n onSubmit,\n initialData,\n title,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const nameRef = useRef<HTMLInputElement>(null);\n const descriptionRef = useRef<HTMLTextAreaElement>(null);\n const typeRef = useRef<HTMLSelectElement>(null);\n const lifecycleRef = useRef<HTMLSelectElement>(null);\n const languageRef = useRef<HTMLInputElement>(null);\n const frameworkRef = useRef<HTMLInputElement>(null);\n const versionRef = useRef<HTMLInputElement>(null);\n const ownerRef = useRef<HTMLInputElement>(null);\n const tagsRef = useRef<HTMLInputElement>(null);\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [fieldErrors, setFieldErrors] = useState<{ name?: string }>({});\n\n if (!isOpen) return null;\n\n const handleSubmit = async () => {\n const name = nameRef.current?.value?.trim();\n if (!name) {\n setFieldErrors({ name: tr('component.nameRequired', 'Name is required') });\n setError(tr('component.fillRequired', 'Please fill in all required fields'));\n return;\n }\n setFieldErrors({});\n\n const data: ComponentFormData = {\n name,\n description: descriptionRef.current?.value?.trim() || '',\n type: (typeRef.current?.value as ComponentFormData['type']) || 'SERVICE',\n lifecycle: (lifecycleRef.current?.value as ComponentFormData['lifecycle']) || 'EXPERIMENTAL',\n language: languageRef.current?.value?.trim() || '',\n framework: frameworkRef.current?.value?.trim() || '',\n version: versionRef.current?.value?.trim() || '',\n owner: ownerRef.current?.value?.trim() || '',\n tags: tagsRef.current?.value\n ? tagsRef.current.value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : [],\n };\n\n setLoading(true);\n setError(null);\n try {\n await onSubmit(data);\n onClose();\n } catch (err) {\n setError(\n err instanceof Error ? err.message : tr('component.failedSave', 'Failed to save component')\n );\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <div className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\">\n <div className=\"bg-bg-surface rounded-lg p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {title || tr('component.createComponent', 'Create Component')}\n </h2>\n <button\n onClick={onClose}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary\"\n >\n <X className=\"w-5 h-5\" />\n </button>\n </div>\n <div className=\"space-y-4\">\n {error && (\n <div className=\"p-3 bg-status-error-bg text-status-error-text text-sm rounded-md\">\n {error}\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.nameLabel', 'Name *')}\n </label>\n <input\n ref={nameRef}\n type=\"text\"\n defaultValue={initialData?.name || ''}\n placeholder={tr('component.namePlaceholder', 'my-service')}\n onChange={() =>\n fieldErrors.name && setFieldErrors((e) => ({ ...e, name: undefined }))\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder ${fieldErrors.name ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.name && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.name}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.descriptionLabel', 'Description')}\n </label>\n <textarea\n ref={descriptionRef}\n defaultValue={initialData?.description || ''}\n placeholder={tr('component.descriptionPlaceholder', 'Describe this component...')}\n rows={3}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder resize-none\"\n />\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.typeLabel', 'Type')}\n </label>\n <select\n ref={typeRef}\n defaultValue={initialData?.type || 'SERVICE'}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary\"\n >\n <option value=\"SERVICE\">{tr('component.typeService', 'Service')}</option>\n <option value=\"FRONTEND\">{tr('component.typeFrontend', 'Frontend')}</option>\n <option value=\"LIBRARY\">{tr('component.typeLibrary', 'Library')}</option>\n <option value=\"API\">{tr('component.typeApi', 'API')}</option>\n <option value=\"MOBILE\">{tr('component.typeMobile', 'Mobile')}</option>\n <option value=\"DATABASE\">{tr('component.typeDatabase', 'Database')}</option>\n <option value=\"INFRASTRUCTURE\">\n {tr('component.typeInfrastructure', 'Infrastructure')}\n </option>\n <option value=\"CUSTOM\">{tr('component.typeCustom', 'Custom')}</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.lifecycleLabel', 'Lifecycle')}\n </label>\n <select\n ref={lifecycleRef}\n defaultValue={initialData?.lifecycle || 'EXPERIMENTAL'}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary\"\n >\n <option value=\"EXPERIMENTAL\">\n {tr('component.lifecycleExperimental', 'Experimental')}\n </option>\n <option value=\"PRODUCTION\">\n {tr('component.lifecycleProduction', 'Production')}\n </option>\n <option value=\"DEPRECATED\">\n {tr('component.lifecycleDeprecated', 'Deprecated')}\n </option>\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.languageLabel', 'Language')}\n </label>\n <input\n ref={languageRef}\n type=\"text\"\n defaultValue={initialData?.language || ''}\n placeholder={tr('component.languagePlaceholder', 'TypeScript')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.frameworkLabel', 'Framework')}\n </label>\n <input\n ref={frameworkRef}\n type=\"text\"\n defaultValue={initialData?.framework || ''}\n placeholder={tr('component.frameworkPlaceholder', 'React')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.versionLabel', 'Version')}\n </label>\n <input\n ref={versionRef}\n type=\"text\"\n defaultValue={initialData?.version || ''}\n placeholder={tr('component.versionPlaceholder', '1.0.0')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.ownerLabel', 'Owner')}\n </label>\n <input\n ref={ownerRef}\n type=\"text\"\n defaultValue={initialData?.owner || ''}\n placeholder={tr('component.ownerPlaceholder', 'team-name')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.tagsLabel', 'Tags (comma-separated)')}\n </label>\n <input\n ref={tagsRef}\n type=\"text\"\n defaultValue={initialData?.tags?.join(', ') || ''}\n placeholder={tr('component.tagsPlaceholder', 'backend, api, graphql')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n </div>\n <div className=\"flex flex-col sm:flex-row justify-end gap-3 mt-6\">\n <button\n onClick={onClose}\n disabled={loading}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n onClick={handleSubmit}\n disabled={loading}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {loading\n ? tr('common.saving', 'Saving...')\n : initialData?.name\n ? tr('common.save', 'Save')\n : tr('component.createComponent', 'Create Component')}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ComponentForm.js","names":[],"sources":["../../../src/components/idp-components/ComponentForm.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport type { FC, ReactNode } from 'react';\nimport { X } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport interface ComponentFormData {\n name: string;\n description: string;\n type:\n | 'SERVICE'\n | 'FRONTEND'\n | 'LIBRARY'\n | 'API'\n | 'MOBILE'\n | 'DATABASE'\n | 'INFRASTRUCTURE'\n | 'CUSTOM';\n lifecycle: 'EXPERIMENTAL' | 'PRODUCTION' | 'DEPRECATED';\n language: string;\n framework: string;\n version: string;\n owner: string;\n tags: string[];\n}\n\nexport interface ComponentFormProps {\n isOpen: boolean;\n onClose: () => void;\n onSubmit: (data: ComponentFormData) => Promise<void>;\n initialData?: Partial<ComponentFormData>;\n title?: string;\n /** Optional extra content (e.g. a draft EntityTagPicker) rendered above the footer. */\n extraSection?: ReactNode;\n}\n\nexport const ComponentForm: FC<ComponentFormProps> = ({\n isOpen,\n onClose,\n onSubmit,\n initialData,\n title,\n extraSection,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const nameRef = useRef<HTMLInputElement>(null);\n const descriptionRef = useRef<HTMLTextAreaElement>(null);\n const typeRef = useRef<HTMLSelectElement>(null);\n const lifecycleRef = useRef<HTMLSelectElement>(null);\n const languageRef = useRef<HTMLInputElement>(null);\n const frameworkRef = useRef<HTMLInputElement>(null);\n const versionRef = useRef<HTMLInputElement>(null);\n const ownerRef = useRef<HTMLInputElement>(null);\n const tagsRef = useRef<HTMLInputElement>(null);\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [fieldErrors, setFieldErrors] = useState<{ name?: string }>({});\n\n if (!isOpen) return null;\n\n const handleSubmit = async () => {\n const name = nameRef.current?.value?.trim();\n if (!name) {\n setFieldErrors({ name: tr('component.nameRequired', 'Name is required') });\n setError(tr('component.fillRequired', 'Please fill in all required fields'));\n return;\n }\n setFieldErrors({});\n\n const data: ComponentFormData = {\n name,\n description: descriptionRef.current?.value?.trim() || '',\n type: (typeRef.current?.value as ComponentFormData['type']) || 'SERVICE',\n lifecycle: (lifecycleRef.current?.value as ComponentFormData['lifecycle']) || 'EXPERIMENTAL',\n language: languageRef.current?.value?.trim() || '',\n framework: frameworkRef.current?.value?.trim() || '',\n version: versionRef.current?.value?.trim() || '',\n owner: ownerRef.current?.value?.trim() || '',\n tags: tagsRef.current?.value\n ? tagsRef.current.value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : [],\n };\n\n setLoading(true);\n setError(null);\n try {\n await onSubmit(data);\n onClose();\n } catch (err) {\n setError(\n err instanceof Error ? err.message : tr('component.failedSave', 'Failed to save component')\n );\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <div className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\">\n <div className=\"bg-bg-surface rounded-lg p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {title || tr('component.createComponent', 'Create Component')}\n </h2>\n <button\n onClick={onClose}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary\"\n >\n <X className=\"w-5 h-5\" />\n </button>\n </div>\n <div className=\"space-y-4\">\n {error && (\n <div className=\"p-3 bg-status-error-bg text-status-error-text text-sm rounded-md\">\n {error}\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.nameLabel', 'Name *')}\n </label>\n <input\n ref={nameRef}\n type=\"text\"\n defaultValue={initialData?.name || ''}\n placeholder={tr('component.namePlaceholder', 'my-service')}\n onChange={() =>\n fieldErrors.name && setFieldErrors((e) => ({ ...e, name: undefined }))\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder ${fieldErrors.name ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.name && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.name}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.descriptionLabel', 'Description')}\n </label>\n <textarea\n ref={descriptionRef}\n defaultValue={initialData?.description || ''}\n placeholder={tr('component.descriptionPlaceholder', 'Describe this component...')}\n rows={3}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder resize-none\"\n />\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.typeLabel', 'Type')}\n </label>\n <select\n ref={typeRef}\n defaultValue={initialData?.type || 'SERVICE'}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary\"\n >\n <option value=\"SERVICE\">{tr('component.typeService', 'Service')}</option>\n <option value=\"FRONTEND\">{tr('component.typeFrontend', 'Frontend')}</option>\n <option value=\"LIBRARY\">{tr('component.typeLibrary', 'Library')}</option>\n <option value=\"API\">{tr('component.typeApi', 'API')}</option>\n <option value=\"MOBILE\">{tr('component.typeMobile', 'Mobile')}</option>\n <option value=\"DATABASE\">{tr('component.typeDatabase', 'Database')}</option>\n <option value=\"INFRASTRUCTURE\">\n {tr('component.typeInfrastructure', 'Infrastructure')}\n </option>\n <option value=\"CUSTOM\">{tr('component.typeCustom', 'Custom')}</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.lifecycleLabel', 'Lifecycle')}\n </label>\n <select\n ref={lifecycleRef}\n defaultValue={initialData?.lifecycle || 'EXPERIMENTAL'}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary\"\n >\n <option value=\"EXPERIMENTAL\">\n {tr('component.lifecycleExperimental', 'Experimental')}\n </option>\n <option value=\"PRODUCTION\">\n {tr('component.lifecycleProduction', 'Production')}\n </option>\n <option value=\"DEPRECATED\">\n {tr('component.lifecycleDeprecated', 'Deprecated')}\n </option>\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.languageLabel', 'Language')}\n </label>\n <input\n ref={languageRef}\n type=\"text\"\n defaultValue={initialData?.language || ''}\n placeholder={tr('component.languagePlaceholder', 'TypeScript')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.frameworkLabel', 'Framework')}\n </label>\n <input\n ref={frameworkRef}\n type=\"text\"\n defaultValue={initialData?.framework || ''}\n placeholder={tr('component.frameworkPlaceholder', 'React')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.versionLabel', 'Version')}\n </label>\n <input\n ref={versionRef}\n type=\"text\"\n defaultValue={initialData?.version || ''}\n placeholder={tr('component.versionPlaceholder', '1.0.0')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.ownerLabel', 'Owner')}\n </label>\n <input\n ref={ownerRef}\n type=\"text\"\n defaultValue={initialData?.owner || ''}\n placeholder={tr('component.ownerPlaceholder', 'team-name')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.tagsLabel', 'Tags (comma-separated)')}\n </label>\n <input\n ref={tagsRef}\n type=\"text\"\n defaultValue={initialData?.tags?.join(', ') || ''}\n placeholder={tr('component.tagsPlaceholder', 'backend, api, graphql')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n </div>\n {extraSection && (\n <div className=\"mt-4 border-t border-border-default pt-4\">{extraSection}</div>\n )}\n <div className=\"flex flex-col sm:flex-row justify-end gap-3 mt-6\">\n <button\n onClick={onClose}\n disabled={loading}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n onClick={handleSubmit}\n disabled={loading}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {loading\n ? tr('common.saving', 'Saving...')\n : initialData?.name\n ? tr('common.save', 'Save')\n : tr('component.createComponent', 'Create Component')}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -6,7 +6,11 @@ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
|
6
6
|
//#region src/components/shared/EntityTagPicker.tsx
|
|
7
7
|
var v = 4e3, y = 250;
|
|
8
8
|
function b({ entityType: b, entityId: x, onDraftChange: S, readOnly: C = !1 }) {
|
|
9
|
-
let w = e(), T = !x, [E, D] = u(!1), [O, k] = u(""), [A, j] = u(null), [M, N] = u([])
|
|
9
|
+
let w = e(), T = !x, [E, D] = u(!1), [O, k] = u(""), [A, j] = u(null), [M, N] = u([]);
|
|
10
|
+
s(() => {
|
|
11
|
+
T && S?.([]);
|
|
12
|
+
}, []);
|
|
13
|
+
let { data: P, loading: F, refetch: I } = n(b, x ?? ""), { data: L, loading: R } = t(50, { skip: !E }), [z, { data: B, loading: V }] = r(), [H, { loading: U }] = i(), [W, { loading: G }] = a(), K = c(() => P?.vibecontrolsEntityTags ?? [], [P]), q = c(() => T ? M.map((e) => ({
|
|
10
14
|
key: e.id,
|
|
11
15
|
tagId: e.id,
|
|
12
16
|
label: e.label
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityTagPicker.js","names":[],"sources":["../../../src/components/shared/EntityTagPicker.tsx"],"sourcesContent":["/**\n * EntityTagPicker — Reusable tag picker for any VibeControls entity\n *\n * Two modes:\n * - Persisted (entityId provided): self-fetches assignments and writes\n * immediately via tag/untag mutations. Used on detail/edit surfaces.\n * - Draft (entityId omitted/empty): no queries or mutations — selections\n * are held locally and surfaced via `onDraftChange(tagIds)`. The parent\n * create form persists them after the entity is created (e.g. via\n * replaceVibecontrolsEntityTags with the new id). Used in create\n * forms/wizards where no entity id exists yet.\n *\n * Usage:\n * <EntityTagPicker entityType=\"VIBECONTROLS_VIBE\" entityId={vibe.id} />\n * <EntityTagPicker entityType=\"VIBECONTROLS_VIBE\" onDraftChange={setTagIds} />\n */\nimport { useState, useCallback, useMemo, useEffect, useRef } from 'react';\nimport { Tag, Plus, X, Search, Loader2 } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useGetEntityTags,\n useAvailableTags,\n useSearchTags,\n useTagEntity,\n useUntagEntity,\n} from '../../hooks/useEntityTags';\nimport type { AvailableTag, EntityTagAssignment } from '../../hooks/useEntityTags';\n\ninterface EntityTagPickerProps {\n entityType: string;\n /**\n * Existing entity id. Omit (or pass empty) to run in draft mode for\n * create forms — selections are emitted via `onDraftChange` instead of\n * being persisted immediately.\n */\n entityId?: string;\n /**\n * Draft-mode callback. Receives the current selected tag ids whenever\n * the draft selection changes. Only invoked when `entityId` is absent.\n */\n onDraftChange?: (tagIds: string[]) => void;\n readOnly?: boolean;\n}\n\nconst ERROR_DISMISS_MS = 4000;\nconst SEARCH_DEBOUNCE_MS = 250;\n\nexport function EntityTagPicker({\n entityType,\n entityId,\n onDraftChange,\n readOnly = false,\n}: EntityTagPickerProps) {\n const tr = useTr();\n const draftMode = !entityId;\n const [isAdding, setIsAdding] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n // Draft-mode local selection (no persistence until the parent flushes it).\n const [draftTags, setDraftTags] = useState<AvailableTag[]>([]);\n\n const {\n data: tagsData,\n loading: tagsLoading,\n refetch: refetchTags,\n } = useGetEntityTags(entityType, entityId ?? '');\n const { data: availableData, loading: availableLoading } = useAvailableTags(50, {\n skip: !isAdding,\n });\n const [searchTags, { data: searchData, loading: searchLoading }] = useSearchTags();\n const [tagEntity, { loading: tagging }] = useTagEntity();\n const [untagEntity, { loading: untagging }] = useUntagEntity();\n\n // Apollo Client v4 returns DeepPartialObject from useQuery data, but our\n // hook types declare full shapes — we cast back to the declared types since\n // the resolver is guaranteed to return non-null fields per the schema.\n const assignedTags = useMemo(\n () => (tagsData?.vibecontrolsEntityTags ?? []) as EntityTagAssignment[],\n [tagsData]\n );\n\n // Unified chip list — draft selections in draft mode, persisted\n // assignments otherwise. `tagId` drives removal; `key` is render-stable.\n const chipItems = useMemo(\n () =>\n draftMode\n ? draftTags.map((t) => ({ key: t.id, tagId: t.id, label: t.label }))\n : assignedTags.map((a) => ({\n key: a.id,\n tagId: a.tagId,\n label:\n a.tag?.label ?? tr('vibecontrols.shared.entityTagPicker.untitledTag', 'Untitled tag'),\n })),\n [draftMode, draftTags, assignedTags, tr]\n );\n\n const assignedTagIds = useMemo(() => new Set(chipItems.map((c) => c.tagId)), [chipItems]);\n\n const suggestions: AvailableTag[] = useMemo(() => {\n const source = searchQuery\n ? (searchData?.searchVibecontrolsTags as AvailableTag[] | undefined)\n : (availableData?.availableVibecontrolsTags as AvailableTag[] | undefined);\n return (source ?? []).filter((t) => !assignedTagIds.has(t.id));\n }, [searchQuery, searchData, availableData, assignedTagIds]);\n\n // Auto-dismiss error toast after a few seconds\n useEffect(() => {\n if (!errorMessage) return;\n const timer = setTimeout(() => setErrorMessage(null), ERROR_DISMISS_MS);\n return () => clearTimeout(timer);\n }, [errorMessage]);\n\n // Debounce the search query so we don't fire a request on every keystroke.\n const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n return () => {\n if (searchTimerRef.current) clearTimeout(searchTimerRef.current);\n };\n }, []);\n\n const handleSearch = useCallback(\n (query: string) => {\n setSearchQuery(query);\n if (searchTimerRef.current) clearTimeout(searchTimerRef.current);\n if (query.length < 2) return;\n searchTimerRef.current = setTimeout(() => {\n searchTags({ variables: { query, limit: 10 } });\n }, SEARCH_DEBOUNCE_MS);\n },\n [searchTags]\n );\n\n const handleAddTag = useCallback(\n async (tag: AvailableTag) => {\n if (draftMode) {\n setDraftTags((prev) => {\n if (prev.some((t) => t.id === tag.id)) return prev;\n const next = [...prev, tag];\n onDraftChange?.(next.map((t) => t.id));\n return next;\n });\n setSearchQuery('');\n setErrorMessage(null);\n return;\n }\n try {\n await tagEntity({\n variables: { input: { entityType, entityId: entityId ?? '', tagId: tag.id } },\n });\n await refetchTags();\n setSearchQuery('');\n setErrorMessage(null);\n } catch (err) {\n setErrorMessage(\n err instanceof Error\n ? err.message\n : tr('vibecontrols.shared.entityTagPicker.failedToAddTag', 'Failed to add tag')\n );\n }\n },\n [draftMode, onDraftChange, tagEntity, entityType, entityId, refetchTags, tr]\n );\n\n const handleRemoveTag = useCallback(\n async (tagId: string) => {\n if (draftMode) {\n setDraftTags((prev) => {\n const next = prev.filter((t) => t.id !== tagId);\n onDraftChange?.(next.map((t) => t.id));\n return next;\n });\n setErrorMessage(null);\n return;\n }\n try {\n await untagEntity({\n variables: { input: { entityType, entityId: entityId ?? '', tagId } },\n });\n await refetchTags();\n setErrorMessage(null);\n } catch (err) {\n setErrorMessage(\n err instanceof Error\n ? err.message\n : tr('vibecontrols.shared.entityTagPicker.failedToRemoveTag', 'Failed to remove tag')\n );\n }\n },\n [draftMode, onDraftChange, untagEntity, entityType, entityId, refetchTags, tr]\n );\n\n if (tagsLoading && !draftMode) {\n return (\n <div\n className=\"flex items-center gap-2 text-text-secondary text-sm\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n {tr('vibecontrols.shared.entityTagPicker.loadingTags', 'Loading tags...')}\n </div>\n );\n }\n\n return (\n <div className=\"space-y-3\">\n {errorMessage && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700\"\n >\n <span className=\"flex-1\">{errorMessage}</span>\n <button\n type=\"button\"\n onClick={() => setErrorMessage(null)}\n aria-label={tr('vibecontrols.shared.entityTagPicker.dismissError', 'Dismiss error')}\n className=\"text-red-700 hover:text-red-900\"\n >\n <X className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n </button>\n </div>\n )}\n\n <div\n className=\"flex flex-wrap gap-2\"\n aria-label={tr('vibecontrols.shared.entityTagPicker.assignedTags', 'Assigned tags')}\n >\n {chipItems.length === 0 && !isAdding && (\n <p className=\"text-text-secondary text-sm\">\n {tr('vibecontrols.shared.entityTagPicker.noTagsAssigned', 'No tags assigned')}\n </p>\n )}\n {chipItems.map((chip) => (\n <span\n key={chip.key}\n className=\"inline-flex items-center gap-1 px-3 py-1 bg-action-primary-bg/10 text-action-primary-bg text-sm rounded-full\"\n >\n <Tag className=\"h-3 w-3\" aria-hidden=\"true\" />\n {chip.label}\n {!readOnly && (\n <button\n type=\"button\"\n onClick={() => handleRemoveTag(chip.tagId)}\n disabled={untagging}\n aria-label={`Remove tag ${chip.label}`}\n className=\"ml-1 hover:text-red-500 transition-colors disabled:opacity-50\"\n >\n <X className=\"h-3 w-3\" aria-hidden=\"true\" />\n </button>\n )}\n </span>\n ))}\n {!readOnly && !isAdding && (\n <button\n type=\"button\"\n onClick={() => setIsAdding(true)}\n aria-label={tr('vibecontrols.shared.entityTagPicker.addTag', 'Add tag')}\n className=\"inline-flex items-center gap-1 px-3 py-1 border border-dashed border-border-default text-text-secondary text-sm rounded-full hover:border-action-primary-bg hover:text-action-primary-bg transition-colors\"\n >\n <Plus className=\"h-3 w-3\" aria-hidden=\"true\" />\n {tr('vibecontrols.shared.entityTagPicker.addTag', 'Add Tag')}\n </button>\n )}\n </div>\n\n {isAdding && (\n <div className=\"border border-border-default rounded-lg p-3 space-y-2 bg-bg-surface\">\n <div className=\"relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => handleSearch(e.target.value)}\n placeholder={tr(\n 'vibecontrols.shared.entityTagPicker.searchPlaceholder',\n 'Search tags...'\n )}\n autoFocus\n aria-label={tr(\n 'vibecontrols.shared.entityTagPicker.searchAvailableTags',\n 'Search available tags'\n )}\n className=\"w-full pl-9 pr-3 py-2 text-sm bg-bg-default border border-border-default rounded-md focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div\n className=\"flex flex-wrap gap-2 max-h-32 overflow-y-auto\"\n aria-label={tr('vibecontrols.shared.entityTagPicker.tagSuggestions', 'Tag suggestions')}\n >\n {(searchLoading || (availableLoading && !searchQuery)) && (\n <span className=\"text-text-secondary text-xs\">\n {tr('vibecontrols.shared.entityTagPicker.loading', 'Loading...')}\n </span>\n )}\n {!searchLoading && !availableLoading && suggestions.length === 0 && (\n <span className=\"text-text-secondary text-xs\">\n {searchQuery\n ? tr('vibecontrols.shared.entityTagPicker.noMatchingTags', 'No matching tags')\n : tr('vibecontrols.shared.entityTagPicker.noAvailableTags', 'No available tags')}\n </span>\n )}\n {suggestions.map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n onClick={() => handleAddTag(tag)}\n disabled={tagging}\n aria-label={`Add tag ${tag.label}`}\n className=\"inline-flex items-center gap-1 px-3 py-1 bg-bg-sunken text-text-primary text-sm rounded-full hover:bg-action-primary-bg/10 hover:text-action-primary-bg transition-colors cursor-pointer disabled:opacity-50\"\n >\n <Plus className=\"h-3 w-3\" aria-hidden=\"true\" />\n {tag.label}\n </button>\n ))}\n </div>\n <button\n type=\"button\"\n onClick={() => {\n setIsAdding(false);\n setSearchQuery('');\n }}\n className=\"text-xs text-text-secondary hover:text-text-primary\"\n >\n {tr('vibecontrols.shared.entityTagPicker.done', 'Done')}\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EntityTagPicker.js","names":[],"sources":["../../../src/components/shared/EntityTagPicker.tsx"],"sourcesContent":["/**\n * EntityTagPicker — Reusable tag picker for any VibeControls entity\n *\n * Two modes:\n * - Persisted (entityId provided): self-fetches assignments and writes\n * immediately via tag/untag mutations. Used on detail/edit surfaces.\n * - Draft (entityId omitted/empty): no queries or mutations — selections\n * are held locally and surfaced via `onDraftChange(tagIds)`. The parent\n * create form persists them after the entity is created (e.g. via\n * replaceVibecontrolsEntityTags with the new id). Used in create\n * forms/wizards where no entity id exists yet.\n *\n * Usage:\n * <EntityTagPicker entityType=\"VIBECONTROLS_VIBE\" entityId={vibe.id} />\n * <EntityTagPicker entityType=\"VIBECONTROLS_VIBE\" onDraftChange={setTagIds} />\n */\nimport { useState, useCallback, useMemo, useEffect, useRef } from 'react';\nimport { Tag, Plus, X, Search, Loader2 } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useGetEntityTags,\n useAvailableTags,\n useSearchTags,\n useTagEntity,\n useUntagEntity,\n} from '../../hooks/useEntityTags';\nimport type { AvailableTag, EntityTagAssignment } from '../../hooks/useEntityTags';\n\ninterface EntityTagPickerProps {\n entityType: string;\n /**\n * Existing entity id. Omit (or pass empty) to run in draft mode for\n * create forms — selections are emitted via `onDraftChange` instead of\n * being persisted immediately.\n */\n entityId?: string;\n /**\n * Draft-mode callback. Receives the current selected tag ids whenever\n * the draft selection changes. Only invoked when `entityId` is absent.\n */\n onDraftChange?: (tagIds: string[]) => void;\n readOnly?: boolean;\n}\n\nconst ERROR_DISMISS_MS = 4000;\nconst SEARCH_DEBOUNCE_MS = 250;\n\nexport function EntityTagPicker({\n entityType,\n entityId,\n onDraftChange,\n readOnly = false,\n}: EntityTagPickerProps) {\n const tr = useTr();\n const draftMode = !entityId;\n const [isAdding, setIsAdding] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n // Draft-mode local selection (no persistence until the parent flushes it).\n const [draftTags, setDraftTags] = useState<AvailableTag[]>([]);\n\n // Reset parent draft-id state on mount so a fresh create form never inherits\n // tag ids left over from a previous create on the same page-level hook.\n useEffect(() => {\n if (draftMode) onDraftChange?.([]);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const {\n data: tagsData,\n loading: tagsLoading,\n refetch: refetchTags,\n } = useGetEntityTags(entityType, entityId ?? '');\n const { data: availableData, loading: availableLoading } = useAvailableTags(50, {\n skip: !isAdding,\n });\n const [searchTags, { data: searchData, loading: searchLoading }] = useSearchTags();\n const [tagEntity, { loading: tagging }] = useTagEntity();\n const [untagEntity, { loading: untagging }] = useUntagEntity();\n\n // Apollo Client v4 returns DeepPartialObject from useQuery data, but our\n // hook types declare full shapes — we cast back to the declared types since\n // the resolver is guaranteed to return non-null fields per the schema.\n const assignedTags = useMemo(\n () => (tagsData?.vibecontrolsEntityTags ?? []) as EntityTagAssignment[],\n [tagsData]\n );\n\n // Unified chip list — draft selections in draft mode, persisted\n // assignments otherwise. `tagId` drives removal; `key` is render-stable.\n const chipItems = useMemo(\n () =>\n draftMode\n ? draftTags.map((t) => ({ key: t.id, tagId: t.id, label: t.label }))\n : assignedTags.map((a) => ({\n key: a.id,\n tagId: a.tagId,\n label:\n a.tag?.label ?? tr('vibecontrols.shared.entityTagPicker.untitledTag', 'Untitled tag'),\n })),\n [draftMode, draftTags, assignedTags, tr]\n );\n\n const assignedTagIds = useMemo(() => new Set(chipItems.map((c) => c.tagId)), [chipItems]);\n\n const suggestions: AvailableTag[] = useMemo(() => {\n const source = searchQuery\n ? (searchData?.searchVibecontrolsTags as AvailableTag[] | undefined)\n : (availableData?.availableVibecontrolsTags as AvailableTag[] | undefined);\n return (source ?? []).filter((t) => !assignedTagIds.has(t.id));\n }, [searchQuery, searchData, availableData, assignedTagIds]);\n\n // Auto-dismiss error toast after a few seconds\n useEffect(() => {\n if (!errorMessage) return;\n const timer = setTimeout(() => setErrorMessage(null), ERROR_DISMISS_MS);\n return () => clearTimeout(timer);\n }, [errorMessage]);\n\n // Debounce the search query so we don't fire a request on every keystroke.\n const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n return () => {\n if (searchTimerRef.current) clearTimeout(searchTimerRef.current);\n };\n }, []);\n\n const handleSearch = useCallback(\n (query: string) => {\n setSearchQuery(query);\n if (searchTimerRef.current) clearTimeout(searchTimerRef.current);\n if (query.length < 2) return;\n searchTimerRef.current = setTimeout(() => {\n searchTags({ variables: { query, limit: 10 } });\n }, SEARCH_DEBOUNCE_MS);\n },\n [searchTags]\n );\n\n const handleAddTag = useCallback(\n async (tag: AvailableTag) => {\n if (draftMode) {\n setDraftTags((prev) => {\n if (prev.some((t) => t.id === tag.id)) return prev;\n const next = [...prev, tag];\n onDraftChange?.(next.map((t) => t.id));\n return next;\n });\n setSearchQuery('');\n setErrorMessage(null);\n return;\n }\n try {\n await tagEntity({\n variables: { input: { entityType, entityId: entityId ?? '', tagId: tag.id } },\n });\n await refetchTags();\n setSearchQuery('');\n setErrorMessage(null);\n } catch (err) {\n setErrorMessage(\n err instanceof Error\n ? err.message\n : tr('vibecontrols.shared.entityTagPicker.failedToAddTag', 'Failed to add tag')\n );\n }\n },\n [draftMode, onDraftChange, tagEntity, entityType, entityId, refetchTags, tr]\n );\n\n const handleRemoveTag = useCallback(\n async (tagId: string) => {\n if (draftMode) {\n setDraftTags((prev) => {\n const next = prev.filter((t) => t.id !== tagId);\n onDraftChange?.(next.map((t) => t.id));\n return next;\n });\n setErrorMessage(null);\n return;\n }\n try {\n await untagEntity({\n variables: { input: { entityType, entityId: entityId ?? '', tagId } },\n });\n await refetchTags();\n setErrorMessage(null);\n } catch (err) {\n setErrorMessage(\n err instanceof Error\n ? err.message\n : tr('vibecontrols.shared.entityTagPicker.failedToRemoveTag', 'Failed to remove tag')\n );\n }\n },\n [draftMode, onDraftChange, untagEntity, entityType, entityId, refetchTags, tr]\n );\n\n if (tagsLoading && !draftMode) {\n return (\n <div\n className=\"flex items-center gap-2 text-text-secondary text-sm\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n {tr('vibecontrols.shared.entityTagPicker.loadingTags', 'Loading tags...')}\n </div>\n );\n }\n\n return (\n <div className=\"space-y-3\">\n {errorMessage && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700\"\n >\n <span className=\"flex-1\">{errorMessage}</span>\n <button\n type=\"button\"\n onClick={() => setErrorMessage(null)}\n aria-label={tr('vibecontrols.shared.entityTagPicker.dismissError', 'Dismiss error')}\n className=\"text-red-700 hover:text-red-900\"\n >\n <X className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n </button>\n </div>\n )}\n\n <div\n className=\"flex flex-wrap gap-2\"\n aria-label={tr('vibecontrols.shared.entityTagPicker.assignedTags', 'Assigned tags')}\n >\n {chipItems.length === 0 && !isAdding && (\n <p className=\"text-text-secondary text-sm\">\n {tr('vibecontrols.shared.entityTagPicker.noTagsAssigned', 'No tags assigned')}\n </p>\n )}\n {chipItems.map((chip) => (\n <span\n key={chip.key}\n className=\"inline-flex items-center gap-1 px-3 py-1 bg-action-primary-bg/10 text-action-primary-bg text-sm rounded-full\"\n >\n <Tag className=\"h-3 w-3\" aria-hidden=\"true\" />\n {chip.label}\n {!readOnly && (\n <button\n type=\"button\"\n onClick={() => handleRemoveTag(chip.tagId)}\n disabled={untagging}\n aria-label={`Remove tag ${chip.label}`}\n className=\"ml-1 hover:text-red-500 transition-colors disabled:opacity-50\"\n >\n <X className=\"h-3 w-3\" aria-hidden=\"true\" />\n </button>\n )}\n </span>\n ))}\n {!readOnly && !isAdding && (\n <button\n type=\"button\"\n onClick={() => setIsAdding(true)}\n aria-label={tr('vibecontrols.shared.entityTagPicker.addTag', 'Add tag')}\n className=\"inline-flex items-center gap-1 px-3 py-1 border border-dashed border-border-default text-text-secondary text-sm rounded-full hover:border-action-primary-bg hover:text-action-primary-bg transition-colors\"\n >\n <Plus className=\"h-3 w-3\" aria-hidden=\"true\" />\n {tr('vibecontrols.shared.entityTagPicker.addTag', 'Add Tag')}\n </button>\n )}\n </div>\n\n {isAdding && (\n <div className=\"border border-border-default rounded-lg p-3 space-y-2 bg-bg-surface\">\n <div className=\"relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => handleSearch(e.target.value)}\n placeholder={tr(\n 'vibecontrols.shared.entityTagPicker.searchPlaceholder',\n 'Search tags...'\n )}\n autoFocus\n aria-label={tr(\n 'vibecontrols.shared.entityTagPicker.searchAvailableTags',\n 'Search available tags'\n )}\n className=\"w-full pl-9 pr-3 py-2 text-sm bg-bg-default border border-border-default rounded-md focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div\n className=\"flex flex-wrap gap-2 max-h-32 overflow-y-auto\"\n aria-label={tr('vibecontrols.shared.entityTagPicker.tagSuggestions', 'Tag suggestions')}\n >\n {(searchLoading || (availableLoading && !searchQuery)) && (\n <span className=\"text-text-secondary text-xs\">\n {tr('vibecontrols.shared.entityTagPicker.loading', 'Loading...')}\n </span>\n )}\n {!searchLoading && !availableLoading && suggestions.length === 0 && (\n <span className=\"text-text-secondary text-xs\">\n {searchQuery\n ? tr('vibecontrols.shared.entityTagPicker.noMatchingTags', 'No matching tags')\n : tr('vibecontrols.shared.entityTagPicker.noAvailableTags', 'No available tags')}\n </span>\n )}\n {suggestions.map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n onClick={() => handleAddTag(tag)}\n disabled={tagging}\n aria-label={`Add tag ${tag.label}`}\n className=\"inline-flex items-center gap-1 px-3 py-1 bg-bg-sunken text-text-primary text-sm rounded-full hover:bg-action-primary-bg/10 hover:text-action-primary-bg transition-colors cursor-pointer disabled:opacity-50\"\n >\n <Plus className=\"h-3 w-3\" aria-hidden=\"true\" />\n {tag.label}\n </button>\n ))}\n </div>\n <button\n type=\"button\"\n onClick={() => {\n setIsAdding(false);\n setSearchQuery('');\n }}\n className=\"text-xs text-text-secondary hover:text-text-primary\"\n >\n {tr('vibecontrols.shared.entityTagPicker.done', 'Done')}\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|