@burdenoff/microfe-workspaces 2026.830.1 → 2026.915.1
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/CreateProjectDialog.js +65 -61
- package/dist/components/CreateProjectDialog.js.map +1 -1
- package/dist/components/CreateWorkspaceDialog.js +93 -90
- package/dist/components/CreateWorkspaceDialog.js.map +1 -1
- package/dist/components/InviteMemberDialog.js +122 -111
- package/dist/components/InviteMemberDialog.js.map +1 -1
- package/dist/components/MemberListItem.js +55 -58
- package/dist/components/MemberListItem.js.map +1 -1
- package/dist/components/ProjectCard.js +93 -92
- package/dist/components/ProjectCard.js.map +1 -1
- package/dist/components/ProjectMemberPickerDialog.js +78 -93
- package/dist/components/ProjectMemberPickerDialog.js.map +1 -1
- package/dist/components/RenameProjectDialog.js +47 -44
- package/dist/components/RenameProjectDialog.js.map +1 -1
- package/dist/components/WorkspaceCard.js +42 -43
- package/dist/components/WorkspaceCard.js.map +1 -1
- package/dist/components/WorkspacesErrorBoundary.js +43 -34
- package/dist/components/WorkspacesErrorBoundary.js.map +1 -1
- package/dist/components/tags/ProjectTagsWidget.js +75 -77
- package/dist/components/tags/ProjectTagsWidget.js.map +1 -1
- package/dist/hooks/useTr.js +14 -0
- package/dist/hooks/useTr.js.map +1 -0
- package/dist/index.js +2 -1
- package/dist/pages/ActivityListPage.js +3 -3
- package/dist/pages/CreateWorkspacePage.js +10 -10
- package/dist/pages/InviteAcceptancePage.js +49 -49
- package/dist/pages/InviteAcceptancePage.js.map +1 -1
- package/dist/pages/MembersListPage.js +6 -6
- package/dist/pages/PendingInvitesPage.js +3 -3
- package/dist/pages/ProjectDetailPage.js +4 -4
- package/dist/pages/ProjectDetailPage.js.map +1 -1
- package/dist/pages/ProjectMembersPage.js +3 -3
- package/dist/pages/ProjectsListPage.js +5 -5
- package/dist/pages/WorkspaceDetailPage.js +4 -4
- package/dist/pages/WorkspaceInvitePage.js +4 -4
- package/dist/pages/WorkspaceSettingsPage.js +7 -7
- package/dist/pages/WorkspacesListPage.js +9 -9
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js +4 -4
- package/dist/pages/dashboard/widgets/StorageWidget.js +3 -3
- package/dist/pages/dashboard/widgets/WelcomeWidget.js +26 -16
- package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +1 -1
- package/dist/translations/en.js +908 -0
- package/dist/translations/en.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { useWorkspacesContext as e } from "../providers/WorkspacesProvider.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
2
|
+
import { useTr as t } from "../hooks/useTr.js";
|
|
3
|
+
import { nativeImpact as n, nativeNotify as r } from "../utils/nativeBridge.js";
|
|
4
|
+
import { useInfiniteWorkspaceMembers as i } from "../hooks/useWorkspaceMembers.js";
|
|
5
|
+
import { useAddProjectMember as a } from "../hooks/useProjectMemberMutations.js";
|
|
6
|
+
import { useInfiniteProjectMembers as ee } from "../hooks/useProjectMembers.js";
|
|
7
|
+
import { useEffect as o, useMemo as s, useState as c } from "react";
|
|
8
|
+
import { AlertCircle as l, Search as u, UserPlus as d } from "lucide-react";
|
|
9
|
+
import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
10
|
+
import { Badge as te, Button as h, Input as ne, ResponsiveDialog as re, ResponsiveDialogContent as ie, ResponsiveDialogDescription as ae, ResponsiveDialogHeader as oe, ResponsiveDialogTitle as g, Skeleton as _ } from "@burdenoff/fe-libs/ui";
|
|
10
11
|
import { ActorIdentity as v, useActorProfiles as y } from "@burdenoff/fe-libs/shared";
|
|
11
12
|
//#region src/components/ProjectMemberPickerDialog.tsx
|
|
12
13
|
var b = 100, x = 10;
|
|
@@ -17,9 +18,9 @@ function S(e) {
|
|
|
17
18
|
email: typeof t?.email == "string" ? t.email : void 0
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
|
-
function C({ open: C, onOpenChange: w, workspaceId: T, projectId: E, projectName:
|
|
21
|
-
let { apiGatewayUrl:
|
|
22
|
-
|
|
21
|
+
function C({ open: C, onOpenChange: w, workspaceId: T, projectId: E, projectName: se }) {
|
|
22
|
+
let D = t(), { apiGatewayUrl: ce, authToken: le, organizationId: ue } = e(), { mutate: O, isPending: k } = a(T), [A, j] = c(""), [M, N] = c(null), [P, F] = c(null), { data: I, isLoading: L, isError: R, error: z, hasNextPage: B, fetchNextPage: V, isFetchingNextPage: H, refetch: de } = i(T, b, C), { data: U, isLoading: fe, isError: W, error: G, hasNextPage: K, fetchNextPage: q, isFetchingNextPage: J } = ee(E, T, b, C);
|
|
23
|
+
o(() => {
|
|
23
24
|
!C || !K || J || W || (U?.pages.length ?? 0) >= x || q();
|
|
24
25
|
}, [
|
|
25
26
|
q,
|
|
@@ -29,15 +30,15 @@ function C({ open: C, onOpenChange: w, workspaceId: T, projectId: E, projectName
|
|
|
29
30
|
C,
|
|
30
31
|
U?.pages.length
|
|
31
32
|
]);
|
|
32
|
-
let Y =
|
|
33
|
+
let Y = s(() => I?.pages.flatMap((e) => e.items) ?? [], [I?.pages]), X = s(() => new Set(U?.pages.flatMap((e) => e.items.map((e) => e.userId)) ?? []), [U?.pages]), pe = I?.pages[0]?.total ?? Y.length, Z = (U?.pages.length ?? 0) >= x, me = !W && K && !Z, Q = y(s(() => Y.map((e) => ({
|
|
33
34
|
actorId: e.userId,
|
|
34
35
|
actorType: "user"
|
|
35
36
|
})), [Y]), {
|
|
36
|
-
apiGatewayUrl:
|
|
37
|
-
authToken:
|
|
38
|
-
orgId:
|
|
37
|
+
apiGatewayUrl: ce,
|
|
38
|
+
authToken: le,
|
|
39
|
+
orgId: ue,
|
|
39
40
|
workspaceId: T
|
|
40
|
-
}), $ =
|
|
41
|
+
}), $ = s(() => {
|
|
41
42
|
let e = A.trim().toLowerCase();
|
|
42
43
|
return Y.filter((t) => {
|
|
43
44
|
if (X.has(t.userId)) return !1;
|
|
@@ -52,151 +53,135 @@ function C({ open: C, onOpenChange: w, workspaceId: T, projectId: E, projectName
|
|
|
52
53
|
Q,
|
|
53
54
|
A,
|
|
54
55
|
Y
|
|
55
|
-
]),
|
|
56
|
-
|
|
56
|
+
]), he = (e) => {
|
|
57
|
+
n("medium"), N(e), F(null), O({
|
|
57
58
|
projectId: E,
|
|
58
59
|
userId: e
|
|
59
60
|
}, {
|
|
60
61
|
onSuccess: () => {
|
|
61
|
-
|
|
62
|
+
r("success"), N(null), w(!1);
|
|
62
63
|
},
|
|
63
64
|
onError: (e) => {
|
|
64
|
-
|
|
65
|
+
r("error"), N(null), F(e instanceof Error ? e.message : D("projectMemberPickerDialog.addError", "Failed to add member to project"));
|
|
65
66
|
}
|
|
66
67
|
});
|
|
67
68
|
};
|
|
68
|
-
return /* @__PURE__ */
|
|
69
|
+
return /* @__PURE__ */ p(re, {
|
|
69
70
|
open: C,
|
|
70
71
|
onOpenChange: w,
|
|
71
|
-
children: /* @__PURE__ */
|
|
72
|
+
children: /* @__PURE__ */ m(ie, {
|
|
72
73
|
className: "z-[120] flex h-[min(85vh,760px)] flex-col overflow-hidden sm:max-w-[720px]",
|
|
73
|
-
children: [/* @__PURE__ */
|
|
74
|
-
"Add an existing workspace member to ",
|
|
75
|
-
D || "this project",
|
|
76
|
-
". Only current workspace members are eligible."
|
|
77
|
-
] })] }), /* @__PURE__ */ u("div", {
|
|
74
|
+
children: [/* @__PURE__ */ m(oe, { children: [/* @__PURE__ */ p(g, { children: D("projectMemberPickerDialog.title", "Add member to project") }), /* @__PURE__ */ p(ae, { children: D("projectMemberPickerDialog.description", "Add an existing workspace member to {{projectName}}. Only current workspace members are eligible.", { projectName: se ?? D("projectMemberPickerDialog.thisProject", "this project") }) })] }), /* @__PURE__ */ m("div", {
|
|
78
75
|
className: "space-y-4 overflow-hidden",
|
|
79
76
|
children: [
|
|
80
|
-
|
|
77
|
+
P ? /* @__PURE__ */ m("div", {
|
|
81
78
|
className: "flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text",
|
|
82
|
-
children: [/* @__PURE__ */ l
|
|
79
|
+
children: [/* @__PURE__ */ p(l, { className: "size-4 shrink-0" }), /* @__PURE__ */ p("span", { children: P })]
|
|
83
80
|
}) : null,
|
|
84
|
-
G instanceof Error ? /* @__PURE__ */
|
|
81
|
+
G instanceof Error ? /* @__PURE__ */ m("div", {
|
|
85
82
|
className: "flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text",
|
|
86
|
-
children: [/* @__PURE__ */ l
|
|
83
|
+
children: [/* @__PURE__ */ p(l, { className: "size-4 shrink-0" }), /* @__PURE__ */ p("span", { children: G.message })]
|
|
87
84
|
}) : null,
|
|
88
|
-
|
|
85
|
+
z instanceof Error ? /* @__PURE__ */ m("div", {
|
|
89
86
|
className: "space-y-3 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text",
|
|
90
|
-
children: [/* @__PURE__ */
|
|
87
|
+
children: [/* @__PURE__ */ m("div", {
|
|
91
88
|
className: "flex items-center gap-2",
|
|
92
|
-
children: [/* @__PURE__ */ l
|
|
93
|
-
}), /* @__PURE__ */
|
|
89
|
+
children: [/* @__PURE__ */ p(l, { className: "size-4 shrink-0" }), /* @__PURE__ */ p("span", { children: z.message })]
|
|
90
|
+
}), /* @__PURE__ */ p(h, {
|
|
94
91
|
variant: "outline",
|
|
95
|
-
onClick: () => void
|
|
96
|
-
children: "Retry Loading Members"
|
|
92
|
+
onClick: () => void de(),
|
|
93
|
+
children: D("projectMemberPickerDialog.retryLoadingMembers", "Retry Loading Members")
|
|
97
94
|
})]
|
|
98
95
|
}) : null,
|
|
99
|
-
/* @__PURE__ */
|
|
96
|
+
/* @__PURE__ */ m("div", {
|
|
100
97
|
className: "flex items-center gap-2",
|
|
101
|
-
children: [/* @__PURE__ */
|
|
98
|
+
children: [/* @__PURE__ */ m("div", {
|
|
102
99
|
className: "relative flex-1",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
100
|
+
children: [/* @__PURE__ */ p(u, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ p(ne, {
|
|
104
101
|
value: A,
|
|
105
102
|
onChange: (e) => j(e.target.value),
|
|
106
|
-
placeholder: "Search workspace members",
|
|
103
|
+
placeholder: D("projectMemberPickerDialog.searchPlaceholder", "Search workspace members"),
|
|
107
104
|
className: "pl-9",
|
|
108
105
|
disabled: k
|
|
109
106
|
})]
|
|
110
|
-
}), /* @__PURE__ */
|
|
107
|
+
}), /* @__PURE__ */ p(te, {
|
|
111
108
|
variant: "secondary",
|
|
112
|
-
children:
|
|
113
|
-
z ? `${$.length}+` : $.length,
|
|
114
|
-
" ",
|
|
115
|
-
"eligible"
|
|
116
|
-
]
|
|
109
|
+
children: D("projectMemberPickerDialog.eligibleCount", "{{count}} eligible", { count: B ? `${$.length}+` : $.length })
|
|
117
110
|
})]
|
|
118
111
|
}),
|
|
119
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ p("div", {
|
|
120
113
|
className: "max-h-[480px] space-y-3 overflow-y-auto pr-1",
|
|
121
|
-
children:
|
|
114
|
+
children: R ? null : L || fe || me || J ? Array.from({ length: 5 }).map((e, t) => /* @__PURE__ */ m("div", {
|
|
122
115
|
className: "rounded-lg border bg-bg-surface p-4",
|
|
123
|
-
children: [/* @__PURE__ */
|
|
124
|
-
}, t)) : G instanceof Error ? null : $.length === 0 ? /* @__PURE__ */
|
|
116
|
+
children: [/* @__PURE__ */ p(_, { className: "h-5 w-40" }), /* @__PURE__ */ p(_, { className: "mt-2 h-4 w-56" })]
|
|
117
|
+
}, t)) : G instanceof Error ? null : $.length === 0 ? /* @__PURE__ */ m("div", {
|
|
125
118
|
className: "rounded-lg border bg-bg-surface p-8 text-center",
|
|
126
119
|
children: [
|
|
127
|
-
/* @__PURE__ */
|
|
128
|
-
/* @__PURE__ */
|
|
120
|
+
/* @__PURE__ */ p(d, { className: "mx-auto mb-3 size-10 text-text-secondary" }),
|
|
121
|
+
/* @__PURE__ */ p("h3", {
|
|
129
122
|
className: "text-lg font-semibold",
|
|
130
|
-
children: "No eligible members found"
|
|
123
|
+
children: D("projectMemberPickerDialog.emptyTitle", "No eligible members found")
|
|
131
124
|
}),
|
|
132
|
-
/* @__PURE__ */
|
|
125
|
+
/* @__PURE__ */ p("p", {
|
|
133
126
|
className: "mt-2 text-sm text-text-secondary",
|
|
134
|
-
children: "All workspace members are already part of this project, or nothing matched your search."
|
|
127
|
+
children: D("projectMemberPickerDialog.emptyDescription", "All workspace members are already part of this project, or nothing matched your search.")
|
|
135
128
|
}),
|
|
136
|
-
Z ? /* @__PURE__ */
|
|
129
|
+
Z ? /* @__PURE__ */ p("p", {
|
|
137
130
|
className: "mt-3 text-xs text-text-secondary",
|
|
138
|
-
children:
|
|
139
|
-
"Loaded the first ",
|
|
140
|
-
x * b,
|
|
141
|
-
" ",
|
|
142
|
-
"project members to avoid an excessively long blocking load."
|
|
143
|
-
]
|
|
131
|
+
children: D("projectMemberPickerDialog.autoloadCapNote", "Loaded the first {{count}} project members to avoid an excessively long blocking load.", { count: x * b })
|
|
144
132
|
}) : null,
|
|
145
|
-
|
|
133
|
+
B ? /* @__PURE__ */ p("div", {
|
|
146
134
|
className: "mt-4",
|
|
147
|
-
children: /* @__PURE__ */
|
|
135
|
+
children: /* @__PURE__ */ p(h, {
|
|
148
136
|
variant: "outline",
|
|
149
|
-
onClick: () => void
|
|
150
|
-
disabled:
|
|
151
|
-
children:
|
|
137
|
+
onClick: () => void V(),
|
|
138
|
+
disabled: H || k,
|
|
139
|
+
children: H ? D("projectMemberPickerDialog.loadingMore", "Loading more…") : D("projectMemberPickerDialog.loadMore", "Load More Members")
|
|
152
140
|
})
|
|
153
141
|
}) : null
|
|
154
142
|
]
|
|
155
|
-
}) : /* @__PURE__ */
|
|
156
|
-
let t = Q[e.userId], n = S(e), r = k &&
|
|
157
|
-
return /* @__PURE__ */
|
|
143
|
+
}) : /* @__PURE__ */ m(f, { children: [$.map((e) => {
|
|
144
|
+
let t = Q[e.userId], n = S(e), r = k && M === e.userId;
|
|
145
|
+
return /* @__PURE__ */ m("div", {
|
|
158
146
|
className: "flex items-center justify-between gap-4 rounded-lg border bg-bg-surface p-4",
|
|
159
|
-
children: [/* @__PURE__ */
|
|
147
|
+
children: [/* @__PURE__ */ p("div", {
|
|
160
148
|
className: "min-w-0 flex-1",
|
|
161
|
-
children: t ? /* @__PURE__ */
|
|
149
|
+
children: t ? /* @__PURE__ */ p(v, {
|
|
162
150
|
profile: t,
|
|
163
151
|
variant: "cell"
|
|
164
|
-
}) : /* @__PURE__ */
|
|
152
|
+
}) : /* @__PURE__ */ m("div", {
|
|
165
153
|
className: "min-w-0",
|
|
166
|
-
children: [/* @__PURE__ */
|
|
154
|
+
children: [/* @__PURE__ */ p("p", {
|
|
167
155
|
className: "truncate font-medium",
|
|
168
156
|
children: n.name
|
|
169
|
-
}), n.email ? /* @__PURE__ */
|
|
157
|
+
}), n.email ? /* @__PURE__ */ p("p", {
|
|
170
158
|
className: "truncate text-sm text-text-secondary",
|
|
171
159
|
children: n.email
|
|
172
|
-
}) : /* @__PURE__ */
|
|
160
|
+
}) : /* @__PURE__ */ p("p", {
|
|
173
161
|
className: "truncate text-xs text-text-secondary",
|
|
174
162
|
children: e.userId
|
|
175
163
|
})]
|
|
176
164
|
})
|
|
177
|
-
}), /* @__PURE__ */
|
|
165
|
+
}), /* @__PURE__ */ m(h, {
|
|
178
166
|
size: "sm",
|
|
179
|
-
onClick: () =>
|
|
167
|
+
onClick: () => he(e.userId),
|
|
180
168
|
disabled: k,
|
|
181
|
-
children: [/* @__PURE__ */
|
|
169
|
+
children: [/* @__PURE__ */ p(d, { className: "mr-2 size-4" }), r ? D("projectMemberPickerDialog.adding", "Adding...") : D("projectMemberPickerDialog.add", "Add")]
|
|
182
170
|
})]
|
|
183
171
|
}, e.id);
|
|
184
|
-
}),
|
|
172
|
+
}), B ? /* @__PURE__ */ m("div", {
|
|
185
173
|
className: "flex flex-col items-center gap-2 rounded-lg border border-dashed bg-bg-sunken/20 p-4 text-center",
|
|
186
|
-
children: [/* @__PURE__ */
|
|
174
|
+
children: [/* @__PURE__ */ p("p", {
|
|
187
175
|
className: "text-sm text-text-secondary",
|
|
188
|
-
children:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
" workspace members."
|
|
194
|
-
]
|
|
195
|
-
}), /* @__PURE__ */ l(d, {
|
|
176
|
+
children: D("projectMemberPickerDialog.showingCount", "Showing {{shown}} of {{total}} workspace members.", {
|
|
177
|
+
shown: Y.length,
|
|
178
|
+
total: pe
|
|
179
|
+
})
|
|
180
|
+
}), /* @__PURE__ */ p(h, {
|
|
196
181
|
variant: "outline",
|
|
197
|
-
onClick: () => void
|
|
198
|
-
disabled:
|
|
199
|
-
children:
|
|
182
|
+
onClick: () => void V(),
|
|
183
|
+
disabled: H || k,
|
|
184
|
+
children: H ? D("projectMemberPickerDialog.loadingMore", "Loading more…") : D("projectMemberPickerDialog.loadMore", "Load More Members")
|
|
200
185
|
})]
|
|
201
186
|
}) : null] })
|
|
202
187
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectMemberPickerDialog.js","names":[],"sources":["../../src/components/ProjectMemberPickerDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { AlertCircle, Search, UserPlus } from 'lucide-react';\nimport {\n ActorIdentity,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport {\n Badge,\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useAddProjectMember } from '../hooks/useProjectMemberMutations';\nimport { useInfiniteProjectMembers } from '../hooks/useProjectMembers';\nimport { useInfiniteWorkspaceMembers } from '../hooks/useWorkspaceMembers';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport type { WorkspaceMember } from '../types';\n\nconst MEMBER_PAGE_SIZE = 100;\nconst MAX_AUTOLOADED_PROJECT_MEMBER_PAGES = 10;\n\nfunction getFallbackDisplay(member: WorkspaceMember): { name: string; email?: string } {\n const meta = member.context?.metadata;\n const name =\n typeof meta?.displayName === 'string'\n ? meta.displayName\n : typeof meta?.name === 'string'\n ? meta.name\n : member.userId;\n const email = typeof meta?.email === 'string' ? meta.email : undefined;\n return { name, email };\n}\n\nexport interface ProjectMemberPickerDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n workspaceId: string;\n projectId: string;\n projectName?: string;\n}\n\nexport function ProjectMemberPickerDialog({\n open,\n onOpenChange,\n workspaceId,\n projectId,\n projectName,\n}: ProjectMemberPickerDialogProps) {\n const { apiGatewayUrl, authToken, organizationId } = useWorkspacesContext();\n const { mutate: addProjectMember, isPending: isAdding } = useAddProjectMember(workspaceId);\n const [searchValue, setSearchValue] = useState('');\n const [addingUserId, setAddingUserId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const {\n data: workspaceMembersPages,\n isLoading: isLoadingMembers,\n isError: isErrorWorkspaceMembers,\n error: workspaceMembersError,\n hasNextPage: hasMoreWorkspaceMembers,\n fetchNextPage: fetchNextWorkspaceMembersPage,\n isFetchingNextPage: isFetchingNextWorkspaceMembersPage,\n refetch: refetchWorkspaceMembers,\n } = useInfiniteWorkspaceMembers(workspaceId, MEMBER_PAGE_SIZE, open);\n\n const {\n data: projectMembersPages,\n isLoading: isLoadingProjectMembers,\n isError: isErrorProjectMembers,\n error: projectMembersError,\n hasNextPage: hasMoreProjectMembers,\n fetchNextPage: fetchNextProjectMembersPage,\n isFetchingNextPage: isFetchingNextProjectMembersPage,\n } = useInfiniteProjectMembers(projectId, workspaceId, MEMBER_PAGE_SIZE, open);\n\n useEffect(() => {\n if (\n !open ||\n !hasMoreProjectMembers ||\n isFetchingNextProjectMembersPage ||\n isErrorProjectMembers ||\n (projectMembersPages?.pages.length ?? 0) >= MAX_AUTOLOADED_PROJECT_MEMBER_PAGES\n ) {\n return;\n }\n\n void fetchNextProjectMembersPage();\n }, [\n fetchNextProjectMembersPage,\n hasMoreProjectMembers,\n isErrorProjectMembers,\n isFetchingNextProjectMembersPage,\n open,\n projectMembersPages?.pages.length,\n ]);\n\n const workspaceMembers = useMemo(\n () => workspaceMembersPages?.pages.flatMap((page) => page.items) ?? [],\n [workspaceMembersPages?.pages]\n );\n const existingUserIdSet = useMemo(\n () =>\n new Set(\n projectMembersPages?.pages.flatMap((page) => page.items.map((member) => member.userId)) ??\n []\n ),\n [projectMembersPages?.pages]\n );\n const totalWorkspaceMembers = workspaceMembersPages?.pages[0]?.total ?? workspaceMembers.length;\n const reachedProjectMemberAutoloadCap =\n (projectMembersPages?.pages.length ?? 0) >= MAX_AUTOLOADED_PROJECT_MEMBER_PAGES;\n const isAutoloadingProjectMembers =\n !isErrorProjectMembers && hasMoreProjectMembers && !reachedProjectMemberAutoloadCap;\n\n const actors = useMemo<ActorInput[]>(\n () => workspaceMembers.map((member) => ({ actorId: member.userId, actorType: 'user' })),\n [workspaceMembers]\n );\n const profileMap: ActorProfileMap = useActorProfiles(actors, {\n apiGatewayUrl,\n authToken,\n orgId: organizationId,\n workspaceId,\n });\n\n const eligibleMembers = useMemo(() => {\n const needle = searchValue.trim().toLowerCase();\n\n return workspaceMembers.filter((member) => {\n if (existingUserIdSet.has(member.userId)) {\n return false;\n }\n\n if (!needle) {\n return true;\n }\n\n const profile = profileMap[member.userId];\n if (profile) {\n if (profile.displayName.toLowerCase().includes(needle)) return true;\n if (profile.email?.toLowerCase().includes(needle)) return true;\n }\n\n const fallback = getFallbackDisplay(member);\n return (\n fallback.name.toLowerCase().includes(needle) ||\n fallback.email?.toLowerCase().includes(needle) ||\n member.userId.toLowerCase().includes(needle)\n );\n });\n }, [existingUserIdSet, profileMap, searchValue, workspaceMembers]);\n\n const handleAddMember = (userId: string) => {\n void nativeImpact('medium');\n setAddingUserId(userId);\n setError(null);\n\n addProjectMember(\n { projectId, userId },\n {\n onSuccess: () => {\n void nativeNotify('success');\n setAddingUserId(null);\n onOpenChange(false);\n },\n onError: (mutationError) => {\n void nativeNotify('error');\n setAddingUserId(null);\n setError(\n mutationError instanceof Error\n ? mutationError.message\n : 'Failed to add member to project'\n );\n },\n }\n );\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"z-[120] flex h-[min(85vh,760px)] flex-col overflow-hidden sm:max-w-[720px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add member to project</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Add an existing workspace member to {projectName || 'this project'}. Only current\n workspace members are eligible.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 overflow-hidden\">\n {error ? (\n <div className=\"flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{error}</span>\n </div>\n ) : null}\n\n {projectMembersError instanceof Error ? (\n <div className=\"flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{projectMembersError.message}</span>\n </div>\n ) : null}\n\n {workspaceMembersError instanceof Error ? (\n <div className=\"space-y-3 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <div className=\"flex items-center gap-2\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{workspaceMembersError.message}</span>\n </div>\n <Button variant=\"outline\" onClick={() => void refetchWorkspaceMembers()}>\n Retry Loading Members\n </Button>\n </div>\n ) : null}\n\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n value={searchValue}\n onChange={(event) => setSearchValue(event.target.value)}\n placeholder=\"Search workspace members\"\n className=\"pl-9\"\n disabled={isAdding}\n />\n </div>\n <Badge variant=\"secondary\">\n {hasMoreWorkspaceMembers ? `${eligibleMembers.length}+` : eligibleMembers.length}{' '}\n eligible\n </Badge>\n </div>\n\n <div className=\"max-h-[480px] space-y-3 overflow-y-auto pr-1\">\n {isErrorWorkspaceMembers ? null : isLoadingMembers ||\n isLoadingProjectMembers ||\n isAutoloadingProjectMembers ||\n isFetchingNextProjectMembersPage ? (\n Array.from({ length: 5 }).map((_, index) => (\n <div key={index} className=\"rounded-lg border bg-bg-surface p-4\">\n <Skeleton className=\"h-5 w-40\" />\n <Skeleton className=\"mt-2 h-4 w-56\" />\n </div>\n ))\n ) : projectMembersError instanceof Error ? null : eligibleMembers.length === 0 ? (\n <div className=\"rounded-lg border bg-bg-surface p-8 text-center\">\n <UserPlus className=\"mx-auto mb-3 size-10 text-text-secondary\" />\n <h3 className=\"text-lg font-semibold\">No eligible members found</h3>\n <p className=\"mt-2 text-sm text-text-secondary\">\n All workspace members are already part of this project, or nothing matched your\n search.\n </p>\n {reachedProjectMemberAutoloadCap ? (\n <p className=\"mt-3 text-xs text-text-secondary\">\n Loaded the first {MAX_AUTOLOADED_PROJECT_MEMBER_PAGES * MEMBER_PAGE_SIZE}{' '}\n project members to avoid an excessively long blocking load.\n </p>\n ) : null}\n {hasMoreWorkspaceMembers ? (\n <div className=\"mt-4\">\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextWorkspaceMembersPage()}\n disabled={isFetchingNextWorkspaceMembersPage || isAdding}\n >\n {isFetchingNextWorkspaceMembersPage ? 'Loading more…' : 'Load More Members'}\n </Button>\n </div>\n ) : null}\n </div>\n ) : (\n <>\n {eligibleMembers.map((member) => {\n const profile = profileMap[member.userId];\n const fallback = getFallbackDisplay(member);\n const isSubmitting = isAdding && addingUserId === member.userId;\n\n return (\n <div\n key={member.id}\n className=\"flex items-center justify-between gap-4 rounded-lg border bg-bg-surface p-4\"\n >\n <div className=\"min-w-0 flex-1\">\n {profile ? (\n <ActorIdentity profile={profile} variant=\"cell\" />\n ) : (\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium\">{fallback.name}</p>\n {fallback.email ? (\n <p className=\"truncate text-sm text-text-secondary\">\n {fallback.email}\n </p>\n ) : (\n <p className=\"truncate text-xs text-text-secondary\">\n {member.userId}\n </p>\n )}\n </div>\n )}\n </div>\n\n <Button\n size=\"sm\"\n onClick={() => handleAddMember(member.userId)}\n disabled={isAdding}\n >\n <UserPlus className=\"mr-2 size-4\" />\n {isSubmitting ? 'Adding...' : 'Add'}\n </Button>\n </div>\n );\n })}\n\n {hasMoreWorkspaceMembers ? (\n <div className=\"flex flex-col items-center gap-2 rounded-lg border border-dashed bg-bg-sunken/20 p-4 text-center\">\n <p className=\"text-sm text-text-secondary\">\n Showing {workspaceMembers.length} of {totalWorkspaceMembers} workspace\n members.\n </p>\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextWorkspaceMembersPage()}\n disabled={isFetchingNextWorkspaceMembersPage || isAdding}\n >\n {isFetchingNextWorkspaceMembersPage ? 'Loading more…' : 'Load More Members'}\n </Button>\n </div>\n ) : null}\n </>\n )}\n </div>\n </div>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectMemberPickerDialog.js","names":[],"sources":["../../src/components/ProjectMemberPickerDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { AlertCircle, Search, UserPlus } from 'lucide-react';\nimport {\n ActorIdentity,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport {\n Badge,\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useAddProjectMember } from '../hooks/useProjectMemberMutations';\nimport { useInfiniteProjectMembers } from '../hooks/useProjectMembers';\nimport { useInfiniteWorkspaceMembers } from '../hooks/useWorkspaceMembers';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useTr } from '../hooks/useTr';\nimport { nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport type { WorkspaceMember } from '../types';\n\nconst MEMBER_PAGE_SIZE = 100;\nconst MAX_AUTOLOADED_PROJECT_MEMBER_PAGES = 10;\n\nfunction getFallbackDisplay(member: WorkspaceMember): { name: string; email?: string } {\n const meta = member.context?.metadata;\n const name =\n typeof meta?.displayName === 'string'\n ? meta.displayName\n : typeof meta?.name === 'string'\n ? meta.name\n : member.userId;\n const email = typeof meta?.email === 'string' ? meta.email : undefined;\n return { name, email };\n}\n\nexport interface ProjectMemberPickerDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n workspaceId: string;\n projectId: string;\n projectName?: string;\n}\n\nexport function ProjectMemberPickerDialog({\n open,\n onOpenChange,\n workspaceId,\n projectId,\n projectName,\n}: ProjectMemberPickerDialogProps) {\n const tr = useTr();\n const { apiGatewayUrl, authToken, organizationId } = useWorkspacesContext();\n const { mutate: addProjectMember, isPending: isAdding } = useAddProjectMember(workspaceId);\n const [searchValue, setSearchValue] = useState('');\n const [addingUserId, setAddingUserId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const {\n data: workspaceMembersPages,\n isLoading: isLoadingMembers,\n isError: isErrorWorkspaceMembers,\n error: workspaceMembersError,\n hasNextPage: hasMoreWorkspaceMembers,\n fetchNextPage: fetchNextWorkspaceMembersPage,\n isFetchingNextPage: isFetchingNextWorkspaceMembersPage,\n refetch: refetchWorkspaceMembers,\n } = useInfiniteWorkspaceMembers(workspaceId, MEMBER_PAGE_SIZE, open);\n\n const {\n data: projectMembersPages,\n isLoading: isLoadingProjectMembers,\n isError: isErrorProjectMembers,\n error: projectMembersError,\n hasNextPage: hasMoreProjectMembers,\n fetchNextPage: fetchNextProjectMembersPage,\n isFetchingNextPage: isFetchingNextProjectMembersPage,\n } = useInfiniteProjectMembers(projectId, workspaceId, MEMBER_PAGE_SIZE, open);\n\n useEffect(() => {\n if (\n !open ||\n !hasMoreProjectMembers ||\n isFetchingNextProjectMembersPage ||\n isErrorProjectMembers ||\n (projectMembersPages?.pages.length ?? 0) >= MAX_AUTOLOADED_PROJECT_MEMBER_PAGES\n ) {\n return;\n }\n\n void fetchNextProjectMembersPage();\n }, [\n fetchNextProjectMembersPage,\n hasMoreProjectMembers,\n isErrorProjectMembers,\n isFetchingNextProjectMembersPage,\n open,\n projectMembersPages?.pages.length,\n ]);\n\n const workspaceMembers = useMemo(\n () => workspaceMembersPages?.pages.flatMap((page) => page.items) ?? [],\n [workspaceMembersPages?.pages]\n );\n const existingUserIdSet = useMemo(\n () =>\n new Set(\n projectMembersPages?.pages.flatMap((page) => page.items.map((member) => member.userId)) ??\n []\n ),\n [projectMembersPages?.pages]\n );\n const totalWorkspaceMembers = workspaceMembersPages?.pages[0]?.total ?? workspaceMembers.length;\n const reachedProjectMemberAutoloadCap =\n (projectMembersPages?.pages.length ?? 0) >= MAX_AUTOLOADED_PROJECT_MEMBER_PAGES;\n const isAutoloadingProjectMembers =\n !isErrorProjectMembers && hasMoreProjectMembers && !reachedProjectMemberAutoloadCap;\n\n const actors = useMemo<ActorInput[]>(\n () => workspaceMembers.map((member) => ({ actorId: member.userId, actorType: 'user' })),\n [workspaceMembers]\n );\n const profileMap: ActorProfileMap = useActorProfiles(actors, {\n apiGatewayUrl,\n authToken,\n orgId: organizationId,\n workspaceId,\n });\n\n const eligibleMembers = useMemo(() => {\n const needle = searchValue.trim().toLowerCase();\n\n return workspaceMembers.filter((member) => {\n if (existingUserIdSet.has(member.userId)) {\n return false;\n }\n\n if (!needle) {\n return true;\n }\n\n const profile = profileMap[member.userId];\n if (profile) {\n if (profile.displayName.toLowerCase().includes(needle)) return true;\n if (profile.email?.toLowerCase().includes(needle)) return true;\n }\n\n const fallback = getFallbackDisplay(member);\n return (\n fallback.name.toLowerCase().includes(needle) ||\n fallback.email?.toLowerCase().includes(needle) ||\n member.userId.toLowerCase().includes(needle)\n );\n });\n }, [existingUserIdSet, profileMap, searchValue, workspaceMembers]);\n\n const handleAddMember = (userId: string) => {\n void nativeImpact('medium');\n setAddingUserId(userId);\n setError(null);\n\n addProjectMember(\n { projectId, userId },\n {\n onSuccess: () => {\n void nativeNotify('success');\n setAddingUserId(null);\n onOpenChange(false);\n },\n onError: (mutationError) => {\n void nativeNotify('error');\n setAddingUserId(null);\n setError(\n mutationError instanceof Error\n ? mutationError.message\n : tr('projectMemberPickerDialog.addError', 'Failed to add member to project')\n );\n },\n }\n );\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"z-[120] flex h-[min(85vh,760px)] flex-col overflow-hidden sm:max-w-[720px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('projectMemberPickerDialog.title', 'Add member to project')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'projectMemberPickerDialog.description',\n 'Add an existing workspace member to {{projectName}}. Only current workspace members are eligible.',\n {\n projectName:\n projectName ?? tr('projectMemberPickerDialog.thisProject', 'this project'),\n }\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 overflow-hidden\">\n {error ? (\n <div className=\"flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{error}</span>\n </div>\n ) : null}\n\n {projectMembersError instanceof Error ? (\n <div className=\"flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{projectMembersError.message}</span>\n </div>\n ) : null}\n\n {workspaceMembersError instanceof Error ? (\n <div className=\"space-y-3 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <div className=\"flex items-center gap-2\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{workspaceMembersError.message}</span>\n </div>\n <Button variant=\"outline\" onClick={() => void refetchWorkspaceMembers()}>\n {tr('projectMemberPickerDialog.retryLoadingMembers', 'Retry Loading Members')}\n </Button>\n </div>\n ) : null}\n\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n value={searchValue}\n onChange={(event) => setSearchValue(event.target.value)}\n placeholder={tr(\n 'projectMemberPickerDialog.searchPlaceholder',\n 'Search workspace members'\n )}\n className=\"pl-9\"\n disabled={isAdding}\n />\n </div>\n <Badge variant=\"secondary\">\n {tr('projectMemberPickerDialog.eligibleCount', '{{count}} eligible', {\n count: hasMoreWorkspaceMembers\n ? `${eligibleMembers.length}+`\n : eligibleMembers.length,\n })}\n </Badge>\n </div>\n\n <div className=\"max-h-[480px] space-y-3 overflow-y-auto pr-1\">\n {isErrorWorkspaceMembers ? null : isLoadingMembers ||\n isLoadingProjectMembers ||\n isAutoloadingProjectMembers ||\n isFetchingNextProjectMembersPage ? (\n Array.from({ length: 5 }).map((_, index) => (\n <div key={index} className=\"rounded-lg border bg-bg-surface p-4\">\n <Skeleton className=\"h-5 w-40\" />\n <Skeleton className=\"mt-2 h-4 w-56\" />\n </div>\n ))\n ) : projectMembersError instanceof Error ? null : eligibleMembers.length === 0 ? (\n <div className=\"rounded-lg border bg-bg-surface p-8 text-center\">\n <UserPlus className=\"mx-auto mb-3 size-10 text-text-secondary\" />\n <h3 className=\"text-lg font-semibold\">\n {tr('projectMemberPickerDialog.emptyTitle', 'No eligible members found')}\n </h3>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {tr(\n 'projectMemberPickerDialog.emptyDescription',\n 'All workspace members are already part of this project, or nothing matched your search.'\n )}\n </p>\n {reachedProjectMemberAutoloadCap ? (\n <p className=\"mt-3 text-xs text-text-secondary\">\n {tr(\n 'projectMemberPickerDialog.autoloadCapNote',\n 'Loaded the first {{count}} project members to avoid an excessively long blocking load.',\n { count: MAX_AUTOLOADED_PROJECT_MEMBER_PAGES * MEMBER_PAGE_SIZE }\n )}\n </p>\n ) : null}\n {hasMoreWorkspaceMembers ? (\n <div className=\"mt-4\">\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextWorkspaceMembersPage()}\n disabled={isFetchingNextWorkspaceMembersPage || isAdding}\n >\n {isFetchingNextWorkspaceMembersPage\n ? tr('projectMemberPickerDialog.loadingMore', 'Loading more…')\n : tr('projectMemberPickerDialog.loadMore', 'Load More Members')}\n </Button>\n </div>\n ) : null}\n </div>\n ) : (\n <>\n {eligibleMembers.map((member) => {\n const profile = profileMap[member.userId];\n const fallback = getFallbackDisplay(member);\n const isSubmitting = isAdding && addingUserId === member.userId;\n\n return (\n <div\n key={member.id}\n className=\"flex items-center justify-between gap-4 rounded-lg border bg-bg-surface p-4\"\n >\n <div className=\"min-w-0 flex-1\">\n {profile ? (\n <ActorIdentity profile={profile} variant=\"cell\" />\n ) : (\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium\">{fallback.name}</p>\n {fallback.email ? (\n <p className=\"truncate text-sm text-text-secondary\">\n {fallback.email}\n </p>\n ) : (\n <p className=\"truncate text-xs text-text-secondary\">\n {member.userId}\n </p>\n )}\n </div>\n )}\n </div>\n\n <Button\n size=\"sm\"\n onClick={() => handleAddMember(member.userId)}\n disabled={isAdding}\n >\n <UserPlus className=\"mr-2 size-4\" />\n {isSubmitting\n ? tr('projectMemberPickerDialog.adding', 'Adding...')\n : tr('projectMemberPickerDialog.add', 'Add')}\n </Button>\n </div>\n );\n })}\n\n {hasMoreWorkspaceMembers ? (\n <div className=\"flex flex-col items-center gap-2 rounded-lg border border-dashed bg-bg-sunken/20 p-4 text-center\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'projectMemberPickerDialog.showingCount',\n 'Showing {{shown}} of {{total}} workspace members.',\n {\n shown: workspaceMembers.length,\n total: totalWorkspaceMembers,\n }\n )}\n </p>\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextWorkspaceMembersPage()}\n disabled={isFetchingNextWorkspaceMembersPage || isAdding}\n >\n {isFetchingNextWorkspaceMembersPage\n ? tr('projectMemberPickerDialog.loadingMore', 'Loading more…')\n : tr('projectMemberPickerDialog.loadMore', 'Load More Members')}\n </Button>\n </div>\n ) : null}\n </>\n )}\n </div>\n </div>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,73 +1,76 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { useTr as e } from "../hooks/useTr.js";
|
|
2
|
+
import { useUpdateProject as t } from "../hooks/useProjectMutations.js";
|
|
3
|
+
import { useMemo as n, useState as r } from "react";
|
|
4
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
|
+
import { Button as o, ResponsiveDialog as s, ResponsiveDialogContent as c, ResponsiveDialogDescription as l, ResponsiveDialogHeader as u, ResponsiveDialogTitle as d } from "@burdenoff/fe-libs/ui";
|
|
6
|
+
import { FormControl as f, FormDescription as p, FormField as m, FormItem as h, FormLabel as g, FormMessage as _, FormProvider as v, Input as y, useForm as b, z as x } from "@burdenoff/fe-libs/form";
|
|
6
7
|
//#region src/components/RenameProjectDialog.tsx
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
function S(e) {
|
|
9
|
+
return x.object({ name: x.string().trim().min(2, e("renameProjectDialog.validation.nameMin", "Name must be at least 2 characters")).max(100, e("renameProjectDialog.validation.nameMax", "Name must be less than 100 characters")) });
|
|
10
|
+
}
|
|
11
|
+
function C({ project: x, open: C, onOpenChange: w }) {
|
|
12
|
+
let T = e(), { mutate: E, isPending: D } = t(x.workspaceId), [O, k] = r(null), A = b({
|
|
13
|
+
schema: n(() => S(T), [T]),
|
|
14
|
+
defaultValues: { name: x.name }
|
|
15
|
+
}), j = A.watch("name").trim() === x.name.trim(), M = (e) => {
|
|
13
16
|
let t = e.name.trim();
|
|
14
|
-
if (t ===
|
|
15
|
-
|
|
17
|
+
if (t === x.name.trim()) {
|
|
18
|
+
w(!1);
|
|
16
19
|
return;
|
|
17
20
|
}
|
|
18
|
-
|
|
19
|
-
id:
|
|
21
|
+
k(null), E({
|
|
22
|
+
id: x.id,
|
|
20
23
|
input: { name: t }
|
|
21
24
|
}, {
|
|
22
25
|
onSuccess: () => {
|
|
23
|
-
|
|
26
|
+
w(!1);
|
|
24
27
|
},
|
|
25
28
|
onError: (e) => {
|
|
26
|
-
|
|
29
|
+
k(e instanceof Error ? e.message : T("renameProjectDialog.submitError", "Failed to rename project"));
|
|
27
30
|
}
|
|
28
31
|
});
|
|
29
32
|
};
|
|
30
|
-
return /* @__PURE__ */
|
|
31
|
-
open:
|
|
32
|
-
onOpenChange:
|
|
33
|
-
children: /* @__PURE__ */
|
|
33
|
+
return /* @__PURE__ */ i(s, {
|
|
34
|
+
open: C,
|
|
35
|
+
onOpenChange: w,
|
|
36
|
+
children: /* @__PURE__ */ a(c, {
|
|
34
37
|
className: "sm:max-w-[500px]",
|
|
35
|
-
children: [/* @__PURE__ */
|
|
36
|
-
...
|
|
37
|
-
children: /* @__PURE__ */
|
|
38
|
-
onSubmit:
|
|
38
|
+
children: [/* @__PURE__ */ a(u, { children: [/* @__PURE__ */ i(d, { children: T("renameProjectDialog.title", "Rename Project") }), /* @__PURE__ */ i(l, { children: T("renameProjectDialog.description", "Give this project a new name. The project key stays the same.") })] }), /* @__PURE__ */ i(v, {
|
|
39
|
+
...A,
|
|
40
|
+
children: /* @__PURE__ */ a("form", {
|
|
41
|
+
onSubmit: A.handleSubmit(M),
|
|
39
42
|
className: "space-y-4",
|
|
40
43
|
children: [
|
|
41
|
-
/* @__PURE__ */
|
|
42
|
-
control:
|
|
44
|
+
/* @__PURE__ */ i(m, {
|
|
45
|
+
control: A.control,
|
|
43
46
|
name: "name",
|
|
44
|
-
render: ({ field: e }) => /* @__PURE__ */
|
|
45
|
-
/* @__PURE__ */
|
|
46
|
-
/* @__PURE__ */
|
|
47
|
+
render: ({ field: e }) => /* @__PURE__ */ a(h, { children: [
|
|
48
|
+
/* @__PURE__ */ i(g, { children: T("renameProjectDialog.nameLabel", "Project Name") }),
|
|
49
|
+
/* @__PURE__ */ i(f, { children: /* @__PURE__ */ i(y, {
|
|
47
50
|
...e,
|
|
48
|
-
placeholder: "My Project"
|
|
51
|
+
placeholder: T("renameProjectDialog.namePlaceholder", "My Project")
|
|
49
52
|
}) }),
|
|
50
|
-
/* @__PURE__ */
|
|
51
|
-
/* @__PURE__ */
|
|
53
|
+
/* @__PURE__ */ i(p, { children: T("renameProjectDialog.nameHint", "A descriptive name for your project") }),
|
|
54
|
+
/* @__PURE__ */ i(_, {})
|
|
52
55
|
] })
|
|
53
56
|
}),
|
|
54
|
-
|
|
57
|
+
O && /* @__PURE__ */ i("p", {
|
|
55
58
|
className: "text-sm text-status-error-text",
|
|
56
59
|
role: "alert",
|
|
57
|
-
children:
|
|
60
|
+
children: O
|
|
58
61
|
}),
|
|
59
|
-
/* @__PURE__ */
|
|
62
|
+
/* @__PURE__ */ a("div", {
|
|
60
63
|
className: "flex justify-end gap-3 pt-4",
|
|
61
|
-
children: [/* @__PURE__ */
|
|
64
|
+
children: [/* @__PURE__ */ i(o, {
|
|
62
65
|
type: "button",
|
|
63
66
|
variant: "outline",
|
|
64
|
-
onClick: () =>
|
|
65
|
-
disabled:
|
|
66
|
-
children: "Cancel"
|
|
67
|
-
}), /* @__PURE__ */
|
|
67
|
+
onClick: () => w(!1),
|
|
68
|
+
disabled: D,
|
|
69
|
+
children: T("common.cancel", "Cancel")
|
|
70
|
+
}), /* @__PURE__ */ i(o, {
|
|
68
71
|
type: "submit",
|
|
69
|
-
disabled:
|
|
70
|
-
children:
|
|
72
|
+
disabled: D || j,
|
|
73
|
+
children: D ? T("renameProjectDialog.saving", "Saving...") : T("renameProjectDialog.save", "Save")
|
|
71
74
|
})]
|
|
72
75
|
})
|
|
73
76
|
]
|
|
@@ -77,6 +80,6 @@ function x({ project: y, open: x, onOpenChange: S }) {
|
|
|
77
80
|
});
|
|
78
81
|
}
|
|
79
82
|
//#endregion
|
|
80
|
-
export {
|
|
83
|
+
export { C as RenameProjectDialog };
|
|
81
84
|
|
|
82
85
|
//# sourceMappingURL=RenameProjectDialog.js.map
|
|
@@ -1 +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\
|
|
1
|
+
{"version":3,"file":"RenameProjectDialog.js","names":[],"sources":["../../src/components/RenameProjectDialog.tsx"],"sourcesContent":["import { useMemo, 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';\nimport { useTr } from '../hooks/useTr';\n\nfunction renameProjectSchema(tr: ReturnType<typeof useTr>) {\n return 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, tr('renameProjectDialog.validation.nameMin', 'Name must be at least 2 characters'))\n .max(\n 100,\n tr('renameProjectDialog.validation.nameMax', 'Name must be less than 100 characters')\n ),\n });\n}\n\ntype RenameProjectFormData = z.infer<ReturnType<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 tr = useTr();\n const { mutate: updateProject, isPending } = useUpdateProject(project.workspaceId);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n const schema = useMemo(() => renameProjectSchema(tr), [tr]);\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,\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(\n error instanceof Error\n ? error.message\n : tr('renameProjectDialog.submitError', 'Failed to rename project')\n );\n },\n }\n );\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-[500px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('renameProjectDialog.title', 'Rename Project')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'renameProjectDialog.description',\n 'Give this project a new name. The project key stays the same.'\n )}\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>{tr('renameProjectDialog.nameLabel', 'Project Name')}</FormLabel>\n <FormControl>\n <Input\n {...field}\n placeholder={tr('renameProjectDialog.namePlaceholder', 'My Project')}\n />\n </FormControl>\n <FormDescription>\n {tr('renameProjectDialog.nameHint', 'A descriptive name for your project')}\n </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 {tr('common.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={isPending || isUnchanged}>\n {isPending\n ? tr('renameProjectDialog.saving', 'Saving...')\n : tr('renameProjectDialog.save', 'Save')}\n </Button>\n </div>\n </form>\n </FormProvider>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;AAyBA,SAAS,EAAoB,GAA8B;AACzD,QAAO,EAAE,OAAO,EAGd,MAAM,EACH,QAAQ,CACR,MAAM,CACN,IAAI,GAAG,EAAG,0CAA0C,qCAAqC,CAAC,CAC1F,IACC,KACA,EAAG,0CAA0C,wCAAwC,CACtF,EACJ,CAAC;;AAWJ,SAAgB,EAAoB,EAAE,YAAS,SAAM,mBAA0C;CAC7F,IAAM,IAAK,GAAO,EACZ,EAAE,QAAQ,GAAe,iBAAc,EAAiB,EAAQ,YAAY,EAC5E,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAO7D,IAAO,EAAQ;EACnB,QANa,QAAc,EAAoB,EAAG,EAAE,CAAC,EAAG,CAAC;EAOzD,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,MACE,aAAiB,QACb,EAAM,UACN,EAAG,mCAAmC,2BAA2B,CACtE;;GAEJ,CACF;;AAGH,QACE,kBAAC,GAAD;EAAwB;EAAoB;YAC1C,kBAAC,GAAD;GAAyB,WAAU;aAAnC,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACG,EAAG,6BAA6B,iBAAiB,EAC5B,CAAA,EACxB,kBAAC,GAAD,EAAA,UACG,EACC,mCACA,gEACD,EAC2B,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,UAAY,EAAG,iCAAiC,eAAe,EAAa,CAAA;QAC5E,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;SACE,GAAI;SACJ,aAAa,EAAG,uCAAuC,aAAa;SACpE,CAAA,EACU,CAAA;QACd,kBAAC,GAAD,EAAA,UACG,EAAG,gCAAgC,sCAAsC,EAC1D,CAAA;QAClB,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;kBAET,EAAG,iBAAiB,SAAS;QACvB,CAAA,EACT,kBAAC,GAAD;QAAQ,MAAK;QAAS,UAAU,KAAa;kBAC1C,IACG,EAAG,8BAA8B,YAAY,GAC7C,EAAG,4BAA4B,OAAO;QACnC,CAAA,CACL;;MACD;;IACM,CAAA,CACS;;EACT,CAAA"}
|