@burdenoff/microfe-workspaces 2026.626.3 → 2026.627.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/ProjectCard.js +174 -155
- package/dist/components/ProjectCard.js.map +1 -1
- package/dist/components/RenameProjectDialog.js +82 -0
- package/dist/components/RenameProjectDialog.js.map +1 -0
- package/dist/components/WorkspaceCard.js +36 -34
- package/dist/components/WorkspaceCard.js.map +1 -1
- package/dist/generated/global-operations.js +2 -0
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/pages/ProjectDetailPage.js +244 -226
- package/dist/pages/ProjectDetailPage.js.map +1 -1
- package/dist/pages/ProjectsListPage.js +16 -15
- package/dist/pages/ProjectsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,172 +5,191 @@ import { nativeConfirm as r, nativeImpact as i } from "../utils/nativeBridge.js"
|
|
|
5
5
|
import { useArchiveProject as a, useDeleteProject as o, useUnarchiveProject as s } from "../hooks/useProjectMutations.js";
|
|
6
6
|
import { formatDate as c } from "../utils/formatters.js";
|
|
7
7
|
import { getProjectDisplayTags as l } from "../utils/projectTags.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
8
|
+
import { RenameProjectDialog as u } from "./RenameProjectDialog.js";
|
|
9
|
+
import { useState as d } from "react";
|
|
10
|
+
import { Archive as f, Fingerprint as p, FolderKanban as m, MoreVertical as h, Pencil as g } from "lucide-react";
|
|
11
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
12
|
+
import { Badge as b, Button as x, CardContent as S, CardHeader as C, CardTitle as w, DropdownMenu as T, DropdownMenuContent as E, DropdownMenuItem as D, DropdownMenuTrigger as O, EntityIdModal as k, GlassCard as A, useEntityIdModal as j } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
import { useNavigate as M } from "react-router-dom";
|
|
14
|
+
import { TagBadge as N } from "@burdenoff/fe-libs/tag";
|
|
13
15
|
//#region src/components/ProjectCard.tsx
|
|
14
|
-
function
|
|
15
|
-
let
|
|
16
|
-
|
|
16
|
+
function P({ project: P, selected: F = !1, onToggleSelect: I, onTagClick: L, canArchive: R = !1, canDelete: z = !1, canRename: B = !1 }) {
|
|
17
|
+
let V = M(), { basePath: H = "/workspaces" } = e(), { withCurrentSearch: U } = n(), { mutate: W } = a(P.workspaceId), { mutate: G } = s(P.workspaceId), { mutate: K } = o(P.workspaceId), q = j(), [J, Y] = d(!1), X = l(P), Z = () => {
|
|
18
|
+
V(U(`${H}/${P.workspaceId}/projects/${P.id}`));
|
|
17
19
|
};
|
|
18
|
-
return /* @__PURE__ */
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
(e
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
20
|
+
return /* @__PURE__ */ y(_, { children: [
|
|
21
|
+
/* @__PURE__ */ y(A, {
|
|
22
|
+
treatment: "insight",
|
|
23
|
+
className: `cursor-pointer relative focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50 ${F ? "ring-2 ring-primary/25 border-primary/50" : ""}`,
|
|
24
|
+
onClick: Z,
|
|
25
|
+
onKeyDown: (e) => {
|
|
26
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), Z());
|
|
27
|
+
},
|
|
28
|
+
role: "button",
|
|
29
|
+
tabIndex: 0,
|
|
30
|
+
"aria-label": `Open project ${P.name}`,
|
|
31
|
+
children: [/* @__PURE__ */ v(C, { children: /* @__PURE__ */ y("div", {
|
|
32
|
+
className: "flex items-start justify-between",
|
|
33
|
+
children: [/* @__PURE__ */ y("div", {
|
|
34
|
+
className: "flex items-center gap-3 flex-1",
|
|
35
|
+
children: [
|
|
36
|
+
I && /* @__PURE__ */ v("input", {
|
|
37
|
+
type: "checkbox",
|
|
38
|
+
checked: F,
|
|
39
|
+
onChange: (e) => I(e.target.checked),
|
|
40
|
+
onClick: (e) => e.stopPropagation(),
|
|
41
|
+
"aria-label": `Select project ${P.name}`,
|
|
42
|
+
className: "mt-1 size-4 rounded border-border-subtle text-primary"
|
|
43
|
+
}),
|
|
44
|
+
/* @__PURE__ */ v("div", {
|
|
45
|
+
className: "p-3 rounded-lg bg-action-primary-bg/10 transition-colors",
|
|
46
|
+
children: /* @__PURE__ */ v(m, {
|
|
47
|
+
className: "text-primary",
|
|
48
|
+
size: 24
|
|
49
|
+
})
|
|
50
|
+
}),
|
|
51
|
+
/* @__PURE__ */ y("div", {
|
|
52
|
+
className: "flex-1 min-w-0",
|
|
53
|
+
children: [/* @__PURE__ */ v(w, {
|
|
54
|
+
className: "truncate",
|
|
55
|
+
children: P.name
|
|
56
|
+
}), /* @__PURE__ */ v("p", {
|
|
57
|
+
className: "truncate text-sm text-text-secondary",
|
|
58
|
+
children: P.key
|
|
59
|
+
})]
|
|
46
60
|
})
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
]
|
|
62
|
+
}), /* @__PURE__ */ y(T, { children: [/* @__PURE__ */ v(O, {
|
|
63
|
+
asChild: !0,
|
|
64
|
+
onClick: (e) => e.stopPropagation(),
|
|
65
|
+
children: /* @__PURE__ */ y(x, {
|
|
66
|
+
variant: "ghost",
|
|
67
|
+
size: "sm",
|
|
68
|
+
className: "size-8 p-0",
|
|
69
|
+
children: [/* @__PURE__ */ v(h, { size: 16 }), /* @__PURE__ */ v("span", {
|
|
70
|
+
className: "sr-only",
|
|
71
|
+
children: "Open project actions"
|
|
56
72
|
})]
|
|
57
73
|
})
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
}), /* @__PURE__ */ y(E, {
|
|
75
|
+
align: "end",
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ y(D, {
|
|
78
|
+
onClick: (e) => {
|
|
79
|
+
e.stopPropagation(), q.show({
|
|
80
|
+
id: P.id,
|
|
81
|
+
name: P.name,
|
|
82
|
+
type: "Project",
|
|
83
|
+
details: [{
|
|
84
|
+
label: "Key",
|
|
85
|
+
value: P.key
|
|
86
|
+
}, {
|
|
87
|
+
label: "Workspace ID",
|
|
88
|
+
value: P.workspaceId
|
|
89
|
+
}]
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
children: [/* @__PURE__ */ v(p, {
|
|
93
|
+
size: 16,
|
|
94
|
+
className: "mr-2"
|
|
95
|
+
}), "Get ID"]
|
|
96
|
+
}),
|
|
97
|
+
B && /* @__PURE__ */ y(D, {
|
|
98
|
+
onClick: (e) => {
|
|
99
|
+
e.stopPropagation(), Y(!0);
|
|
100
|
+
},
|
|
101
|
+
children: [/* @__PURE__ */ v(g, {
|
|
102
|
+
size: 16,
|
|
103
|
+
className: "mr-2"
|
|
104
|
+
}), "Rename"]
|
|
105
|
+
}),
|
|
106
|
+
R && /* @__PURE__ */ y(D, {
|
|
107
|
+
onClick: (e) => {
|
|
108
|
+
e.stopPropagation(), P.archived ? G(P.id) : W(P.id);
|
|
109
|
+
},
|
|
110
|
+
children: [/* @__PURE__ */ v(f, {
|
|
111
|
+
size: 16,
|
|
112
|
+
className: "mr-2"
|
|
113
|
+
}), P.archived ? "Unarchive" : "Archive"]
|
|
114
|
+
}),
|
|
115
|
+
z && /* @__PURE__ */ v(D, {
|
|
116
|
+
onClick: async (e) => {
|
|
117
|
+
e.stopPropagation(), i("medium");
|
|
118
|
+
let t = await r({
|
|
119
|
+
title: "Delete project",
|
|
120
|
+
message: `Are you sure you want to delete "${P.name}"?`,
|
|
121
|
+
okButtonTitle: "Delete",
|
|
122
|
+
cancelButtonTitle: "Cancel"
|
|
123
|
+
});
|
|
124
|
+
(t === null ? confirm(`Are you sure you want to delete "${P.name}"?`) : t) && K(P.id);
|
|
125
|
+
},
|
|
126
|
+
className: "text-status-error-text",
|
|
127
|
+
children: "Delete"
|
|
128
|
+
})
|
|
129
|
+
]
|
|
130
|
+
})] })]
|
|
131
|
+
}) }), /* @__PURE__ */ y(S, {
|
|
132
|
+
className: "space-y-3",
|
|
73
133
|
children: [
|
|
74
|
-
/* @__PURE__ */
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
},
|
|
89
|
-
children: [/* @__PURE__ */ h(d, {
|
|
90
|
-
size: 16,
|
|
91
|
-
className: "mr-2"
|
|
92
|
-
}), "Get ID"]
|
|
134
|
+
/* @__PURE__ */ y("div", {
|
|
135
|
+
className: "flex items-center gap-2",
|
|
136
|
+
children: [/* @__PURE__ */ v(b, {
|
|
137
|
+
className: `border ${t(P.status)}`,
|
|
138
|
+
children: P.status
|
|
139
|
+
}), P.archived && /* @__PURE__ */ y(b, {
|
|
140
|
+
variant: "outline",
|
|
141
|
+
className: "border-workspace-archived-border text-workspace-archived-text",
|
|
142
|
+
children: [/* @__PURE__ */ v(f, {
|
|
143
|
+
size: 12,
|
|
144
|
+
className: "mr-1"
|
|
145
|
+
}), "Archived"]
|
|
146
|
+
})]
|
|
93
147
|
}),
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
},
|
|
98
|
-
children: [/* @__PURE__ */ h(u, {
|
|
99
|
-
size: 16,
|
|
100
|
-
className: "mr-2"
|
|
101
|
-
}), j.archived ? "Unarchive" : "Archive"]
|
|
148
|
+
/* @__PURE__ */ y("div", {
|
|
149
|
+
className: "text-xs text-text-secondary",
|
|
150
|
+
children: ["Created ", c(P.createdAt)]
|
|
102
151
|
}),
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
152
|
+
X.length > 0 && /* @__PURE__ */ y("div", {
|
|
153
|
+
className: "flex flex-wrap gap-1.5",
|
|
154
|
+
"aria-label": "Project tags",
|
|
155
|
+
children: [X.slice(0, 4).map((e) => /* @__PURE__ */ v("span", {
|
|
156
|
+
...L ? {
|
|
157
|
+
role: "button",
|
|
158
|
+
tabIndex: 0,
|
|
159
|
+
onClick: (t) => {
|
|
160
|
+
t.stopPropagation(), L(e);
|
|
161
|
+
},
|
|
162
|
+
onKeyDown: (t) => {
|
|
163
|
+
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), t.stopPropagation(), L(e));
|
|
164
|
+
}
|
|
165
|
+
} : {},
|
|
166
|
+
children: /* @__PURE__ */ v(N, {
|
|
167
|
+
id: e.tagId,
|
|
168
|
+
label: e.label,
|
|
169
|
+
color: e.color ?? void 0,
|
|
170
|
+
icon: e.icon ?? void 0,
|
|
171
|
+
description: e.description ?? void 0,
|
|
172
|
+
size: "sm",
|
|
173
|
+
className: L ? "cursor-pointer" : void 0
|
|
174
|
+
})
|
|
175
|
+
}, e.tagId)), X.length > 4 && /* @__PURE__ */ y(b, {
|
|
176
|
+
variant: "outline",
|
|
177
|
+
className: "text-xs",
|
|
178
|
+
children: ["+", X.length - 4]
|
|
179
|
+
})]
|
|
116
180
|
})
|
|
117
181
|
]
|
|
118
|
-
})]
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}), j.archived && /* @__PURE__ */ g(_, {
|
|
128
|
-
variant: "outline",
|
|
129
|
-
className: "border-workspace-archived-border text-workspace-archived-text",
|
|
130
|
-
children: [/* @__PURE__ */ h(u, {
|
|
131
|
-
size: 12,
|
|
132
|
-
className: "mr-1"
|
|
133
|
-
}), "Archived"]
|
|
134
|
-
})]
|
|
135
|
-
}),
|
|
136
|
-
/* @__PURE__ */ g("div", {
|
|
137
|
-
className: "text-xs text-text-secondary",
|
|
138
|
-
children: ["Created ", c(j.createdAt)]
|
|
139
|
-
}),
|
|
140
|
-
W.length > 0 && /* @__PURE__ */ g("div", {
|
|
141
|
-
className: "flex flex-wrap gap-1.5",
|
|
142
|
-
"aria-label": "Project tags",
|
|
143
|
-
children: [W.slice(0, 4).map((e) => /* @__PURE__ */ h("span", {
|
|
144
|
-
...P ? {
|
|
145
|
-
role: "button",
|
|
146
|
-
tabIndex: 0,
|
|
147
|
-
onClick: (t) => {
|
|
148
|
-
t.stopPropagation(), P(e);
|
|
149
|
-
},
|
|
150
|
-
onKeyDown: (t) => {
|
|
151
|
-
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), t.stopPropagation(), P(e));
|
|
152
|
-
}
|
|
153
|
-
} : {},
|
|
154
|
-
children: /* @__PURE__ */ h(A, {
|
|
155
|
-
id: e.tagId,
|
|
156
|
-
label: e.label,
|
|
157
|
-
color: e.color ?? void 0,
|
|
158
|
-
icon: e.icon ?? void 0,
|
|
159
|
-
description: e.description ?? void 0,
|
|
160
|
-
size: "sm",
|
|
161
|
-
className: P ? "cursor-pointer" : void 0
|
|
162
|
-
})
|
|
163
|
-
}, e.tagId)), W.length > 4 && /* @__PURE__ */ g(_, {
|
|
164
|
-
variant: "outline",
|
|
165
|
-
className: "text-xs",
|
|
166
|
-
children: ["+", W.length - 4]
|
|
167
|
-
})]
|
|
168
|
-
})
|
|
169
|
-
]
|
|
170
|
-
})]
|
|
171
|
-
}), /* @__PURE__ */ h(E, { control: U })] });
|
|
182
|
+
})]
|
|
183
|
+
}),
|
|
184
|
+
/* @__PURE__ */ v(k, { control: q }),
|
|
185
|
+
J && /* @__PURE__ */ v(u, {
|
|
186
|
+
project: P,
|
|
187
|
+
open: J,
|
|
188
|
+
onOpenChange: Y
|
|
189
|
+
})
|
|
190
|
+
] });
|
|
172
191
|
}
|
|
173
192
|
//#endregion
|
|
174
|
-
export {
|
|
193
|
+
export { P as ProjectCard };
|
|
175
194
|
|
|
176
195
|
//# sourceMappingURL=ProjectCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectCard.js","names":[],"sources":["../../src/components/ProjectCard.tsx"],"sourcesContent":["import type React from 'react';\nimport { FolderKanban, Archive, MoreVertical, Fingerprint } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n GlassCard,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n EntityIdModal,\n useEntityIdModal,\n} from '@burdenoff/fe-libs/ui';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\n\nimport type { Project, ProjectTag } from '../types';\nimport { formatDate } from '../utils/formatters';\nimport { getProjectDisplayTags } from '../utils/projectTags';\nimport { getProjectStatusClasses } from '../utils/tokenHelpers';\nimport {\n useArchiveProject,\n useUnarchiveProject,\n useDeleteProject,\n} from '../hooks/useProjectMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { nativeConfirm, nativeImpact } from '../utils/nativeBridge';\n\ninterface ProjectCardProps {\n project: Project;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n onTagClick?: (tag: ProjectTag) => void;\n canArchive?: boolean;\n canDelete?: boolean;\n}\n\nexport function ProjectCard({\n project,\n selected = false,\n onToggleSelect,\n onTagClick,\n canArchive = false,\n canDelete = false,\n}: ProjectCardProps) {\n const navigate = useNavigate();\n const { basePath = '/workspaces' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { mutate: archiveProject } = useArchiveProject(project.workspaceId);\n const { mutate: unarchiveProject } = useUnarchiveProject(project.workspaceId);\n const { mutate: deleteProject } = useDeleteProject(project.workspaceId);\n const idModal = useEntityIdModal();\n const projectTags = getProjectDisplayTags(project);\n\n const handleCardClick = () => {\n navigate(withCurrentSearch(`${basePath}/${project.workspaceId}/projects/${project.id}`));\n };\n\n const handleArchive = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (project.archived) {\n unarchiveProject(project.id);\n } else {\n archiveProject(project.id);\n }\n };\n\n const handleDelete = async (e: React.MouseEvent) => {\n e.stopPropagation();\n void nativeImpact('medium');\n const native = await nativeConfirm({\n title: 'Delete project',\n message: `Are you sure you want to delete \"${project.name}\"?`,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok =\n native === null ? confirm(`Are you sure you want to delete \"${project.name}\"?`) : native;\n if (ok) {\n deleteProject(project.id);\n }\n };\n\n const handleCardKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleCardClick();\n }\n };\n\n return (\n <>\n <GlassCard\n treatment=\"insight\"\n className={`cursor-pointer relative focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50 ${\n selected ? 'ring-2 ring-primary/25 border-primary/50' : ''\n }`}\n onClick={handleCardClick}\n onKeyDown={handleCardKeyDown}\n role=\"button\"\n tabIndex={0}\n aria-label={`Open project ${project.name}`}\n >\n <CardHeader>\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3 flex-1\">\n {onToggleSelect && (\n <input\n type=\"checkbox\"\n checked={selected}\n onChange={(event) => onToggleSelect(event.target.checked)}\n onClick={(event) => event.stopPropagation()}\n aria-label={`Select project ${project.name}`}\n className=\"mt-1 size-4 rounded border-border-subtle text-primary\"\n />\n )}\n <div className=\"p-3 rounded-lg bg-action-primary-bg/10 transition-colors\">\n <FolderKanban className=\"text-primary\" size={24} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <CardTitle className=\"truncate\">{project.name}</CardTitle>\n <p className=\"truncate text-sm text-text-secondary\">{project.key}</p>\n </div>\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>\n <Button variant=\"ghost\" size=\"sm\" className=\"size-8 p-0\">\n <MoreVertical size={16} />\n <span className=\"sr-only\">Open project actions</span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n onClick={(e) => {\n e.stopPropagation();\n idModal.show({\n id: project.id,\n name: project.name,\n type: 'Project',\n details: [\n { label: 'Key', value: project.key },\n { label: 'Workspace ID', value: project.workspaceId },\n ],\n });\n }}\n >\n <Fingerprint size={16} className=\"mr-2\" />\n Get ID\n </DropdownMenuItem>\n {canArchive && (\n <DropdownMenuItem onClick={handleArchive}>\n <Archive size={16} className=\"mr-2\" />\n {project.archived ? 'Unarchive' : 'Archive'}\n </DropdownMenuItem>\n )}\n {canDelete && (\n <DropdownMenuItem onClick={handleDelete} className=\"text-status-error-text\">\n Delete\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <Badge className={`border ${getProjectStatusClasses(project.status)}`}>\n {project.status}\n </Badge>\n {project.archived && (\n <Badge\n variant=\"outline\"\n className=\"border-workspace-archived-border text-workspace-archived-text\"\n >\n <Archive size={12} className=\"mr-1\" />\n Archived\n </Badge>\n )}\n </div>\n <div className=\"text-xs text-text-secondary\">Created {formatDate(project.createdAt)}</div>\n {projectTags.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Project tags\">\n {projectTags.slice(0, 4).map((tag) => (\n <span\n key={tag.tagId}\n {...(onTagClick\n ? {\n role: 'button',\n tabIndex: 0,\n onClick: (event: React.MouseEvent<HTMLSpanElement>) => {\n event.stopPropagation();\n onTagClick(tag);\n },\n onKeyDown: (event: React.KeyboardEvent<HTMLSpanElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n event.stopPropagation();\n onTagClick(tag);\n }\n },\n }\n : {})}\n >\n <TagBadge\n id={tag.tagId}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n size=\"sm\"\n className={onTagClick ? 'cursor-pointer' : undefined}\n />\n </span>\n ))}\n {projectTags.length > 4 && (\n <Badge variant=\"outline\" className=\"text-xs\">\n +{projectTags.length - 4}\n </Badge>\n )}\n </div>\n )}\n </CardContent>\n </GlassCard>\n <EntityIdModal control={idModal} />\n </>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAyCA,SAAgB,EAAY,EAC1B,YACA,cAAW,IACX,mBACA,eACA,gBAAa,IACb,eAAY,MACO;CACnB,IAAM,IAAW,GAAa,EACxB,EAAE,cAAW,kBAAkB,GAAsB,EACrD,EAAE,yBAAsB,GAAqB,EAC7C,EAAE,QAAQ,MAAmB,EAAkB,EAAQ,YAAY,EACnE,EAAE,QAAQ,MAAqB,EAAoB,EAAQ,YAAY,EACvE,EAAE,QAAQ,MAAkB,EAAiB,EAAQ,YAAY,EACjE,IAAU,GAAkB,EAC5B,IAAc,EAAsB,EAAQ,EAE5C,UAAwB;AAC5B,IAAS,EAAkB,GAAG,EAAS,GAAG,EAAQ,YAAY,YAAY,EAAQ,KAAK,CAAC;;AAmC1F,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,mHACT,IAAW,6CAA6C;EAE1D,SAAS;EACT,YAfqB,MAA+C;AACxE,IAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,GAAiB;;EAaf,MAAK;EACL,UAAU;EACV,cAAY,gBAAgB,EAAQ;YATtC,CAWE,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,KACC,kBAAC,SAAD;MACE,MAAK;MACL,SAAS;MACT,WAAW,MAAU,EAAe,EAAM,OAAO,QAAQ;MACzD,UAAU,MAAU,EAAM,iBAAiB;MAC3C,cAAY,kBAAkB,EAAQ;MACtC,WAAU;MACV,CAAA;KAEJ,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAc,WAAU;OAAe,MAAM;OAAM,CAAA;MAC/C,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAW,WAAU;iBAAY,EAAQ;OAAiB,CAAA,EAC1D,kBAAC,KAAD;OAAG,WAAU;iBAAwC,EAAQ;OAAQ,CAAA,CACjE;;KACF;OACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;IAAqB,SAAA;IAAQ,UAAU,MAAM,EAAE,iBAAiB;cAC9D,kBAAC,GAAD;KAAQ,SAAQ;KAAQ,MAAK;KAAK,WAAU;eAA5C,CACE,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA,EAC1B,kBAAC,QAAD;MAAM,WAAU;gBAAU;MAA2B,CAAA,CAC9C;;IACW,CAAA,EACtB,kBAAC,GAAD;IAAqB,OAAM;cAA3B;KACE,kBAAC,GAAD;MACE,UAAU,MAAM;AAEd,OADA,EAAE,iBAAiB,EACnB,EAAQ,KAAK;QACX,IAAI,EAAQ;QACZ,MAAM,EAAQ;QACd,MAAM;QACN,SAAS,CACP;SAAE,OAAO;SAAO,OAAO,EAAQ;SAAK,EACpC;SAAE,OAAO;SAAgB,OAAO,EAAQ;SAAa,CACtD;QACF,CAAC;;gBAXN,CAcE,kBAAC,GAAD;OAAa,MAAM;OAAI,WAAU;OAAS,CAAA,EAAA,SAEzB;;KAClB,KACC,kBAAC,GAAD;MAAkB,UA5FX,MAAwB;AAE7C,OADA,EAAE,iBAAiB,EACf,EAAQ,WACV,EAAiB,EAAQ,GAAG,GAE5B,EAAe,EAAQ,GAAG;;gBAuFd,CACE,kBAAC,GAAD;OAAS,MAAM;OAAI,WAAU;OAAS,CAAA,EACrC,EAAQ,WAAW,cAAc,UACjB;;KAEpB,KACC,kBAAC,GAAD;MAAkB,SAzFb,OAAO,MAAwB;AAE7C,OADL,EAAE,iBAAiB,EACd,EAAa,SAAS;OAC3B,IAAM,IAAS,MAAM,EAAc;QACjC,OAAO;QACP,SAAS,oCAAoC,EAAQ,KAAK;QAC1D,eAAe;QACf,mBAAmB;QACpB,CAAC;AAGF,QADE,MAAW,OAAO,QAAQ,oCAAoC,EAAQ,KAAK,IAAI,GAAG,MAElF,EAAc,EAAQ,GAAG;;MA6E4B,WAAU;gBAAyB;MAEzD,CAAA;KAED;MACT,EAAA,CAAA,CACX;MACK,CAAA,EACb,kBAAC,GAAD;GAAa,WAAU;aAAvB;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAO,WAAW,UAAU,EAAwB,EAAQ,OAAO;gBAChE,EAAQ;MACH,CAAA,EACP,EAAQ,YACP,kBAAC,GAAD;MACE,SAAQ;MACR,WAAU;gBAFZ,CAIE,kBAAC,GAAD;OAAS,MAAM;OAAI,WAAU;OAAS,CAAA,EAAA,WAEhC;QAEN;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CAA6C,YAAS,EAAW,EAAQ,UAAU,CAAO;;IACzF,EAAY,SAAS,KACpB,kBAAC,OAAD;KAAK,WAAU;KAAyB,cAAW;eAAnD,CACG,EAAY,MAAM,GAAG,EAAE,CAAC,KAAK,MAC5B,kBAAC,QAAD;MAEE,GAAK,IACD;OACE,MAAM;OACN,UAAU;OACV,UAAU,MAA6C;AAErD,QADA,EAAM,iBAAiB,EACvB,EAAW,EAAI;;OAEjB,YAAY,MAAgD;AAC1D,SAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,EAAM,iBAAiB,EACvB,EAAW,EAAI;;OAGpB,GACD,EAAE;gBAEN,kBAAC,GAAD;OACE,IAAI,EAAI;OACR,OAAO,EAAI;OACX,OAAO,EAAI,SAAS,KAAA;OACpB,MAAM,EAAI,QAAQ,KAAA;OAClB,aAAa,EAAI,eAAe,KAAA;OAChC,MAAK;OACL,WAAW,IAAa,mBAAmB,KAAA;OAC3C,CAAA;MACG,EA5BA,EAAI,MA4BJ,CACP,EACD,EAAY,SAAS,KACpB,kBAAC,GAAD;MAAO,SAAQ;MAAU,WAAU;gBAAnC,CAA6C,KACzC,EAAY,SAAS,EACjB;QAEN;;IAEI;KACJ;KACZ,kBAAC,GAAD,EAAe,SAAS,GAAW,CAAA,CAClC,EAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"ProjectCard.js","names":[],"sources":["../../src/components/ProjectCard.tsx"],"sourcesContent":["import type React from 'react';\nimport { useState } from 'react';\nimport { FolderKanban, Archive, MoreVertical, Fingerprint, Pencil } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n GlassCard,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n EntityIdModal,\n useEntityIdModal,\n} from '@burdenoff/fe-libs/ui';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\n\nimport type { Project, ProjectTag } from '../types';\nimport { formatDate } from '../utils/formatters';\nimport { getProjectDisplayTags } from '../utils/projectTags';\nimport { getProjectStatusClasses } from '../utils/tokenHelpers';\nimport {\n useArchiveProject,\n useUnarchiveProject,\n useDeleteProject,\n} from '../hooks/useProjectMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { nativeConfirm, nativeImpact } from '../utils/nativeBridge';\nimport { RenameProjectDialog } from './RenameProjectDialog';\n\ninterface ProjectCardProps {\n project: Project;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n onTagClick?: (tag: ProjectTag) => void;\n canArchive?: boolean;\n canDelete?: boolean;\n canRename?: boolean;\n}\n\nexport function ProjectCard({\n project,\n selected = false,\n onToggleSelect,\n onTagClick,\n canArchive = false,\n canDelete = false,\n canRename = false,\n}: ProjectCardProps) {\n const navigate = useNavigate();\n const { basePath = '/workspaces' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { mutate: archiveProject } = useArchiveProject(project.workspaceId);\n const { mutate: unarchiveProject } = useUnarchiveProject(project.workspaceId);\n const { mutate: deleteProject } = useDeleteProject(project.workspaceId);\n const idModal = useEntityIdModal();\n const [showRenameDialog, setShowRenameDialog] = useState(false);\n const projectTags = getProjectDisplayTags(project);\n\n const handleCardClick = () => {\n navigate(withCurrentSearch(`${basePath}/${project.workspaceId}/projects/${project.id}`));\n };\n\n const handleArchive = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (project.archived) {\n unarchiveProject(project.id);\n } else {\n archiveProject(project.id);\n }\n };\n\n const handleDelete = async (e: React.MouseEvent) => {\n e.stopPropagation();\n void nativeImpact('medium');\n const native = await nativeConfirm({\n title: 'Delete project',\n message: `Are you sure you want to delete \"${project.name}\"?`,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok =\n native === null ? confirm(`Are you sure you want to delete \"${project.name}\"?`) : native;\n if (ok) {\n deleteProject(project.id);\n }\n };\n\n const handleCardKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleCardClick();\n }\n };\n\n return (\n <>\n <GlassCard\n treatment=\"insight\"\n className={`cursor-pointer relative focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50 ${\n selected ? 'ring-2 ring-primary/25 border-primary/50' : ''\n }`}\n onClick={handleCardClick}\n onKeyDown={handleCardKeyDown}\n role=\"button\"\n tabIndex={0}\n aria-label={`Open project ${project.name}`}\n >\n <CardHeader>\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3 flex-1\">\n {onToggleSelect && (\n <input\n type=\"checkbox\"\n checked={selected}\n onChange={(event) => onToggleSelect(event.target.checked)}\n onClick={(event) => event.stopPropagation()}\n aria-label={`Select project ${project.name}`}\n className=\"mt-1 size-4 rounded border-border-subtle text-primary\"\n />\n )}\n <div className=\"p-3 rounded-lg bg-action-primary-bg/10 transition-colors\">\n <FolderKanban className=\"text-primary\" size={24} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <CardTitle className=\"truncate\">{project.name}</CardTitle>\n <p className=\"truncate text-sm text-text-secondary\">{project.key}</p>\n </div>\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>\n <Button variant=\"ghost\" size=\"sm\" className=\"size-8 p-0\">\n <MoreVertical size={16} />\n <span className=\"sr-only\">Open project actions</span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n onClick={(e) => {\n e.stopPropagation();\n idModal.show({\n id: project.id,\n name: project.name,\n type: 'Project',\n details: [\n { label: 'Key', value: project.key },\n { label: 'Workspace ID', value: project.workspaceId },\n ],\n });\n }}\n >\n <Fingerprint size={16} className=\"mr-2\" />\n Get ID\n </DropdownMenuItem>\n {canRename && (\n <DropdownMenuItem\n onClick={(e) => {\n e.stopPropagation();\n setShowRenameDialog(true);\n }}\n >\n <Pencil size={16} className=\"mr-2\" />\n Rename\n </DropdownMenuItem>\n )}\n {canArchive && (\n <DropdownMenuItem onClick={handleArchive}>\n <Archive size={16} className=\"mr-2\" />\n {project.archived ? 'Unarchive' : 'Archive'}\n </DropdownMenuItem>\n )}\n {canDelete && (\n <DropdownMenuItem onClick={handleDelete} className=\"text-status-error-text\">\n Delete\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <Badge className={`border ${getProjectStatusClasses(project.status)}`}>\n {project.status}\n </Badge>\n {project.archived && (\n <Badge\n variant=\"outline\"\n className=\"border-workspace-archived-border text-workspace-archived-text\"\n >\n <Archive size={12} className=\"mr-1\" />\n Archived\n </Badge>\n )}\n </div>\n <div className=\"text-xs text-text-secondary\">Created {formatDate(project.createdAt)}</div>\n {projectTags.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Project tags\">\n {projectTags.slice(0, 4).map((tag) => (\n <span\n key={tag.tagId}\n {...(onTagClick\n ? {\n role: 'button',\n tabIndex: 0,\n onClick: (event: React.MouseEvent<HTMLSpanElement>) => {\n event.stopPropagation();\n onTagClick(tag);\n },\n onKeyDown: (event: React.KeyboardEvent<HTMLSpanElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n event.stopPropagation();\n onTagClick(tag);\n }\n },\n }\n : {})}\n >\n <TagBadge\n id={tag.tagId}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n size=\"sm\"\n className={onTagClick ? 'cursor-pointer' : undefined}\n />\n </span>\n ))}\n {projectTags.length > 4 && (\n <Badge variant=\"outline\" className=\"text-xs\">\n +{projectTags.length - 4}\n </Badge>\n )}\n </div>\n )}\n </CardContent>\n </GlassCard>\n <EntityIdModal control={idModal} />\n {showRenameDialog && (\n <RenameProjectDialog\n project={project}\n open={showRenameDialog}\n onOpenChange={setShowRenameDialog}\n />\n )}\n </>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useUpdateProject as e } from "../hooks/useProjectMutations.js";
|
|
2
|
+
import { useState as t } from "react";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
import { Button as i, ResponsiveDialog as a, ResponsiveDialogContent as o, ResponsiveDialogDescription as s, ResponsiveDialogHeader as c, ResponsiveDialogTitle as l } from "@burdenoff/fe-libs/ui";
|
|
5
|
+
import { FormControl as u, FormDescription as d, FormField as f, FormItem as p, FormLabel as m, FormMessage as h, FormProvider as g, Input as _, useForm as v, z as y } from "@burdenoff/fe-libs/form";
|
|
6
|
+
//#region src/components/RenameProjectDialog.tsx
|
|
7
|
+
var b = y.object({ name: y.string().trim().min(2, "Name must be at least 2 characters").max(100, "Name must be less than 100 characters") });
|
|
8
|
+
function x({ project: y, open: x, onOpenChange: S }) {
|
|
9
|
+
let { mutate: C, isPending: w } = e(y.workspaceId), [T, E] = t(null), D = v({
|
|
10
|
+
schema: b,
|
|
11
|
+
defaultValues: { name: y.name }
|
|
12
|
+
}), O = D.watch("name").trim() === y.name.trim(), k = (e) => {
|
|
13
|
+
let t = e.name.trim();
|
|
14
|
+
if (t === y.name.trim()) {
|
|
15
|
+
S(!1);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
E(null), C({
|
|
19
|
+
id: y.id,
|
|
20
|
+
input: { name: t }
|
|
21
|
+
}, {
|
|
22
|
+
onSuccess: () => {
|
|
23
|
+
S(!1);
|
|
24
|
+
},
|
|
25
|
+
onError: (e) => {
|
|
26
|
+
E(e instanceof Error ? e.message : "Failed to rename project");
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
return /* @__PURE__ */ n(a, {
|
|
31
|
+
open: x,
|
|
32
|
+
onOpenChange: S,
|
|
33
|
+
children: /* @__PURE__ */ r(o, {
|
|
34
|
+
className: "sm:max-w-[500px]",
|
|
35
|
+
children: [/* @__PURE__ */ r(c, { children: [/* @__PURE__ */ n(l, { children: "Rename Project" }), /* @__PURE__ */ n(s, { children: "Give this project a new name. The project key stays the same." })] }), /* @__PURE__ */ n(g, {
|
|
36
|
+
...D,
|
|
37
|
+
children: /* @__PURE__ */ r("form", {
|
|
38
|
+
onSubmit: D.handleSubmit(k),
|
|
39
|
+
className: "space-y-4",
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ n(f, {
|
|
42
|
+
control: D.control,
|
|
43
|
+
name: "name",
|
|
44
|
+
render: ({ field: e }) => /* @__PURE__ */ r(p, { children: [
|
|
45
|
+
/* @__PURE__ */ n(m, { children: "Project Name" }),
|
|
46
|
+
/* @__PURE__ */ n(u, { children: /* @__PURE__ */ n(_, {
|
|
47
|
+
...e,
|
|
48
|
+
placeholder: "My Project"
|
|
49
|
+
}) }),
|
|
50
|
+
/* @__PURE__ */ n(d, { children: "A descriptive name for your project" }),
|
|
51
|
+
/* @__PURE__ */ n(h, {})
|
|
52
|
+
] })
|
|
53
|
+
}),
|
|
54
|
+
T && /* @__PURE__ */ n("p", {
|
|
55
|
+
className: "text-sm text-status-error-text",
|
|
56
|
+
role: "alert",
|
|
57
|
+
children: T
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ r("div", {
|
|
60
|
+
className: "flex justify-end gap-3 pt-4",
|
|
61
|
+
children: [/* @__PURE__ */ n(i, {
|
|
62
|
+
type: "button",
|
|
63
|
+
variant: "outline",
|
|
64
|
+
onClick: () => S(!1),
|
|
65
|
+
disabled: w,
|
|
66
|
+
children: "Cancel"
|
|
67
|
+
}), /* @__PURE__ */ n(i, {
|
|
68
|
+
type: "submit",
|
|
69
|
+
disabled: w || O,
|
|
70
|
+
children: w ? "Saving..." : "Save"
|
|
71
|
+
})]
|
|
72
|
+
})
|
|
73
|
+
]
|
|
74
|
+
})
|
|
75
|
+
})]
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { x as RenameProjectDialog };
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=RenameProjectDialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RenameProjectDialog.js","names":[],"sources":["../../src/components/RenameProjectDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Button,\n} from '@burdenoff/fe-libs/ui';\nimport {\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n FormDescription,\n Input,\n useForm,\n FormProvider,\n z,\n} from '@burdenoff/fe-libs/form';\n\nimport { useUpdateProject } from '../hooks/useProjectMutations';\n\nconst renameProjectSchema = z.object({\n // `.trim()` first so length validation runs on the trimmed value — otherwise a\n // padded name like \" a \" passes min(2) but submits as the 1-char \"a\".\n name: z\n .string()\n .trim()\n .min(2, 'Name must be at least 2 characters')\n .max(100, 'Name must be less than 100 characters'),\n});\n\ntype RenameProjectFormData = z.infer<typeof renameProjectSchema>;\n\ninterface RenameProjectDialogProps {\n project: { id: string; name: string; workspaceId: string };\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nexport function RenameProjectDialog({ project, open, onOpenChange }: RenameProjectDialogProps) {\n const { mutate: updateProject, isPending } = useUpdateProject(project.workspaceId);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n // This component is mounted only while open (the parent gates it with\n // `{open && <RenameProjectDialog .../>}`), so `defaultValues` always reflects\n // the current name on each open — no prop-syncing effect needed.\n const form = useForm({\n schema: renameProjectSchema,\n defaultValues: { name: project.name },\n });\n\n const currentName = form.watch('name');\n const isUnchanged = currentName.trim() === project.name.trim();\n\n const onSubmit = (data: RenameProjectFormData) => {\n const trimmed = data.name.trim();\n if (trimmed === project.name.trim()) {\n onOpenChange(false);\n return;\n }\n\n setSubmitError(null);\n updateProject(\n { id: project.id, input: { name: trimmed } },\n {\n onSuccess: () => {\n onOpenChange(false);\n },\n onError: (error) => {\n setSubmitError(error instanceof Error ? error.message : 'Failed to rename project');\n },\n }\n );\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-[500px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Rename Project</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Give this project a new name. The project key stays the same.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"name\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Project Name</FormLabel>\n <FormControl>\n <Input {...field} placeholder=\"My Project\" />\n </FormControl>\n <FormDescription>A descriptive name for your project</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n {submitError && (\n <p className=\"text-sm text-status-error-text\" role=\"alert\">\n {submitError}\n </p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={isPending}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={isPending || isUnchanged}>\n {isPending ? 'Saving...' : 'Save'}\n </Button>\n </div>\n </form>\n </FormProvider>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;AAwBA,IAAM,IAAsB,EAAE,OAAO,EAGnC,MAAM,EACH,QAAQ,CACR,MAAM,CACN,IAAI,GAAG,qCAAqC,CAC5C,IAAI,KAAK,wCAAwC,EACrD,CAAC;AAUF,SAAgB,EAAoB,EAAE,YAAS,SAAM,mBAA0C;CAC7F,IAAM,EAAE,QAAQ,GAAe,iBAAc,EAAiB,EAAQ,YAAY,EAC5E,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAK7D,IAAO,EAAQ;EACnB,QAAQ;EACR,eAAe,EAAE,MAAM,EAAQ,MAAM;EACtC,CAAC,EAGI,IADc,EAAK,MAAM,OAAO,CACN,MAAM,KAAK,EAAQ,KAAK,MAAM,EAExD,KAAY,MAAgC;EAChD,IAAM,IAAU,EAAK,KAAK,MAAM;AAChC,MAAI,MAAY,EAAQ,KAAK,MAAM,EAAE;AACnC,KAAa,GAAM;AACnB;;AAIF,EADA,EAAe,KAAK,EACpB,EACE;GAAE,IAAI,EAAQ;GAAI,OAAO,EAAE,MAAM,GAAS;GAAE,EAC5C;GACE,iBAAiB;AACf,MAAa,GAAM;;GAErB,UAAU,MAAU;AAClB,MAAe,aAAiB,QAAQ,EAAM,UAAU,2BAA2B;;GAEtF,CACF;;AAGH,QACE,kBAAC,GAAD;EAAwB;EAAoB;YAC1C,kBAAC,GAAD;GAAyB,WAAU;aAAnC,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,kBAAsC,CAAA,EAC7D,kBAAC,GAAD,EAAA,UAA6B,iEAEC,CAAA,CACP,EAAA,CAAA,EAEzB,kBAAC,GAAD;IAAc,GAAI;cAChB,kBAAC,QAAD;KAAM,UAAU,EAAK,aAAa,EAAS;KAAE,WAAU;eAAvD;MACE,kBAAC,GAAD;OACE,SAAS,EAAK;OACd,MAAK;OACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;QACE,kBAAC,GAAD,EAAA,UAAW,gBAAwB,CAAA;QACnC,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;SAAO,GAAI;SAAO,aAAY;SAAe,CAAA,EACjC,CAAA;QACd,kBAAC,GAAD,EAAA,UAAiB,uCAAqD,CAAA;QACtE,kBAAC,GAAD,EAAe,CAAA;QACN,EAAA,CAAA;OAEb,CAAA;MAED,KACC,kBAAC,KAAD;OAAG,WAAU;OAAiC,MAAK;iBAChD;OACC,CAAA;MAGN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,MAAK;QACL,SAAQ;QACR,eAAe,EAAa,GAAM;QAClC,UAAU;kBACX;QAEQ,CAAA,EACT,kBAAC,GAAD;QAAQ,MAAK;QAAS,UAAU,KAAa;kBAC1C,IAAY,cAAc;QACpB,CAAA,CACL;;MACD;;IACM,CAAA,CACS;;EACT,CAAA"}
|
|
@@ -2,76 +2,78 @@ import { useWorkspacesContext as e } from "../providers/WorkspacesProvider.js";
|
|
|
2
2
|
import { getWorkspaceStatusClasses as t } from "../utils/tokenHelpers.js";
|
|
3
3
|
import { useScopedNavigation as n } from "../hooks/useScopedNavigation.js";
|
|
4
4
|
import { Building2 as r, FolderKanban as i, Users as a } from "lucide-react";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { useActiveContext as o } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
6
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
7
|
+
import { Badge as l, CardContent as u, CardHeader as d, CardTitle as f, GlassCard as p } from "@burdenoff/fe-libs/ui";
|
|
8
|
+
import { useNavigate as m } from "react-router-dom";
|
|
8
9
|
//#region src/components/WorkspaceCard.tsx
|
|
9
|
-
function
|
|
10
|
-
let
|
|
11
|
-
h
|
|
10
|
+
function h({ workspace: h }) {
|
|
11
|
+
let g = m(), { basePath: _ = "/" } = e(), { withCurrentSearch: v } = n(), { setOrganization: y, setWorkspace: b, setProject: x, setTenant: S } = o(), C = () => {
|
|
12
|
+
let e = h.organizationId ?? h.orgId;
|
|
13
|
+
e && y(e), b(h.id), x(null), typeof h.tenantId == "string" && S(h.tenantId), g(v(`${_}/${h.id}`));
|
|
12
14
|
};
|
|
13
|
-
return /* @__PURE__ */
|
|
15
|
+
return /* @__PURE__ */ c(p, {
|
|
14
16
|
treatment: "insight",
|
|
15
17
|
className: "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50",
|
|
16
|
-
onClick:
|
|
18
|
+
onClick: C,
|
|
17
19
|
onKeyDown: (e) => {
|
|
18
|
-
(e.key === "Enter" || e.key === " ") && (e.preventDefault(),
|
|
20
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), C());
|
|
19
21
|
},
|
|
20
22
|
role: "button",
|
|
21
23
|
tabIndex: 0,
|
|
22
|
-
"aria-label": `Open workspace ${
|
|
24
|
+
"aria-label": `Open workspace ${h.name}`,
|
|
23
25
|
"data-tour": "workspaces-card",
|
|
24
|
-
children: [/* @__PURE__ */
|
|
26
|
+
children: [/* @__PURE__ */ s(d, { children: /* @__PURE__ */ c("div", {
|
|
25
27
|
className: "flex items-center gap-3",
|
|
26
|
-
children: [/* @__PURE__ */
|
|
28
|
+
children: [/* @__PURE__ */ s("div", {
|
|
27
29
|
className: "p-3 rounded-lg bg-action-primary-bg/10 transition-colors",
|
|
28
|
-
children: /* @__PURE__ */
|
|
30
|
+
children: /* @__PURE__ */ s(r, {
|
|
29
31
|
className: "text-primary",
|
|
30
32
|
size: 24
|
|
31
33
|
})
|
|
32
|
-
}), /* @__PURE__ */
|
|
34
|
+
}), /* @__PURE__ */ c("div", {
|
|
33
35
|
className: "flex-1 min-w-0",
|
|
34
|
-
children: [/* @__PURE__ */
|
|
36
|
+
children: [/* @__PURE__ */ s(f, {
|
|
35
37
|
className: "text-lg truncate",
|
|
36
|
-
children:
|
|
37
|
-
}), /* @__PURE__ */
|
|
38
|
+
children: h.name
|
|
39
|
+
}), /* @__PURE__ */ s("div", {
|
|
38
40
|
className: "flex items-center gap-2 mt-1",
|
|
39
|
-
children: /* @__PURE__ */
|
|
40
|
-
className: `text-xs border ${t(
|
|
41
|
-
children:
|
|
41
|
+
children: /* @__PURE__ */ s(l, {
|
|
42
|
+
className: `text-xs border ${t(h.status)}`,
|
|
43
|
+
children: h.status
|
|
42
44
|
})
|
|
43
45
|
})]
|
|
44
46
|
})]
|
|
45
|
-
}) }), /* @__PURE__ */
|
|
47
|
+
}) }), /* @__PURE__ */ c(u, { children: [/* @__PURE__ */ c("div", {
|
|
46
48
|
className: "flex flex-wrap gap-4 text-sm text-text-secondary",
|
|
47
|
-
children: [typeof
|
|
49
|
+
children: [typeof h.memberCount == "number" && h.memberCount > 0 && /* @__PURE__ */ c("div", {
|
|
48
50
|
className: "flex items-center gap-1",
|
|
49
|
-
children: [/* @__PURE__ */
|
|
51
|
+
children: [/* @__PURE__ */ s(a, {
|
|
50
52
|
size: 16,
|
|
51
53
|
"aria-hidden": "true"
|
|
52
|
-
}), /* @__PURE__ */
|
|
53
|
-
|
|
54
|
+
}), /* @__PURE__ */ c("span", { children: [
|
|
55
|
+
h.memberCount,
|
|
54
56
|
" member",
|
|
55
|
-
|
|
57
|
+
h.memberCount === 1 ? "" : "s"
|
|
56
58
|
] })]
|
|
57
|
-
}), typeof
|
|
59
|
+
}), typeof h.projectCount == "number" && h.projectCount > 0 && /* @__PURE__ */ c("div", {
|
|
58
60
|
className: "flex items-center gap-1",
|
|
59
|
-
children: [/* @__PURE__ */
|
|
61
|
+
children: [/* @__PURE__ */ s(i, {
|
|
60
62
|
size: 16,
|
|
61
63
|
"aria-hidden": "true"
|
|
62
|
-
}), /* @__PURE__ */
|
|
63
|
-
|
|
64
|
+
}), /* @__PURE__ */ c("span", { children: [
|
|
65
|
+
h.projectCount,
|
|
64
66
|
" project",
|
|
65
|
-
|
|
67
|
+
h.projectCount === 1 ? "" : "s"
|
|
66
68
|
] })]
|
|
67
69
|
})]
|
|
68
|
-
}),
|
|
70
|
+
}), h.domain && /* @__PURE__ */ s("p", {
|
|
69
71
|
className: "mt-2 truncate text-xs text-text-muted",
|
|
70
|
-
children:
|
|
72
|
+
children: h.domain
|
|
71
73
|
})] })]
|
|
72
74
|
});
|
|
73
75
|
}
|
|
74
76
|
//#endregion
|
|
75
|
-
export {
|
|
77
|
+
export { h as WorkspaceCard };
|
|
76
78
|
|
|
77
79
|
//# sourceMappingURL=WorkspaceCard.js.map
|