@burdenoff/microfe-workspaces 2026.623.1 → 2026.624.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/CreateWorkspaceDialog.js +5 -5
- package/dist/components/CreateWorkspaceDialog.js.map +1 -1
- package/dist/components/InviteMemberDialog.js +11 -11
- package/dist/components/InviteMemberDialog.js.map +1 -1
- package/dist/components/ProjectCard.js +5 -5
- package/dist/components/ProjectCard.js.map +1 -1
- package/dist/components/ProjectMemberPickerDialog.js +14 -14
- package/dist/components/ProjectMemberPickerDialog.js.map +1 -1
- package/dist/components/WorkspaceCard.js +3 -3
- package/dist/components/WorkspaceCard.js.map +1 -1
- package/dist/components/WorkspacesErrorBoundary.js +9 -9
- package/dist/components/WorkspacesErrorBoundary.js.map +1 -1
- package/dist/components/tags/ProjectTagsWidget.js +8 -8
- package/dist/components/tags/ProjectTagsWidget.js.map +1 -1
- package/dist/pages/ActivityListPage.js +12 -12
- package/dist/pages/ActivityListPage.js.map +1 -1
- package/dist/pages/CreateWorkspacePage.js +24 -24
- package/dist/pages/CreateWorkspacePage.js.map +1 -1
- package/dist/pages/InviteAcceptancePage.js +46 -46
- package/dist/pages/InviteAcceptancePage.js.map +1 -1
- package/dist/pages/MembersListPage.js +27 -27
- package/dist/pages/MembersListPage.js.map +1 -1
- package/dist/pages/PendingInvitesPage.js +23 -23
- package/dist/pages/PendingInvitesPage.js.map +1 -1
- package/dist/pages/ProjectDetailPage.js +20 -20
- package/dist/pages/ProjectDetailPage.js.map +1 -1
- package/dist/pages/ProjectMembersPage.js +15 -15
- package/dist/pages/ProjectMembersPage.js.map +1 -1
- package/dist/pages/ProjectsListPage.js +16 -16
- package/dist/pages/ProjectsListPage.js.map +1 -1
- package/dist/pages/WorkspaceDetailPage.js +16 -16
- package/dist/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/pages/WorkspaceInvitePage.js +369 -384
- package/dist/pages/WorkspaceInvitePage.js.map +1 -1
- package/dist/pages/WorkspaceSettingsPage.js +2 -2
- package/dist/pages/WorkspaceSettingsPage.js.map +1 -1
- package/dist/pages/WorkspacesListPage.js +8 -8
- package/dist/pages/WorkspacesListPage.js.map +1 -1
- package/dist/pages/dashboard/DashboardCustomizer.js +3 -3
- package/dist/pages/dashboard/DashboardCustomizer.js.map +1 -1
- package/dist/pages/dashboard/DashboardGrid.js +2 -2
- package/dist/pages/dashboard/DashboardGrid.js.map +1 -1
- package/dist/pages/dashboard/DashboardPage.js +5 -5
- package/dist/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/pages/dashboard/WidgetWrapper.js +6 -6
- package/dist/pages/dashboard/WidgetWrapper.js.map +1 -1
- package/dist/pages/dashboard/widgets/AgentsWidget.js +6 -6
- package/dist/pages/dashboard/widgets/AgentsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/GroupsWidget.js +3 -3
- package/dist/pages/dashboard/widgets/GroupsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/MembersWidget.js +3 -3
- package/dist/pages/dashboard/widgets/MembersWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/ProjectsWidget.js +2 -2
- package/dist/pages/dashboard/widgets/ProjectsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js +3 -3
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/SessionsWidget.js +4 -4
- package/dist/pages/dashboard/widgets/SessionsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/TagCloudWidget.js +2 -2
- package/dist/pages/dashboard/widgets/TagCloudWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/TipsWidget.js +3 -3
- package/dist/pages/dashboard/widgets/TipsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js +2 -2
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/VibesWidget.js +2 -2
- package/dist/pages/dashboard/widgets/VibesWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WelcomeWidget.js +3 -3
- package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js +4 -4
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js +2 -2
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js.map +1 -1
- package/package.json +1 -1
|
@@ -42,7 +42,7 @@ function M() {
|
|
|
42
42
|
className: "text-lg font-semibold",
|
|
43
43
|
children: r(M, "projectDetail.accessDenied", "Access Denied")
|
|
44
44
|
}), /* @__PURE__ */ _("p", {
|
|
45
|
-
className: "text-sm text-
|
|
45
|
+
className: "text-sm text-text-secondary",
|
|
46
46
|
children: r(M, "projectDetail.noPermissionToViewProject", "You don't have permission to view this project.")
|
|
47
47
|
})]
|
|
48
48
|
})
|
|
@@ -104,10 +104,10 @@ function M() {
|
|
|
104
104
|
className: "text-center py-12",
|
|
105
105
|
children: [
|
|
106
106
|
/* @__PURE__ */ _("div", {
|
|
107
|
-
className: "rounded-full bg-
|
|
107
|
+
className: "rounded-full bg-bg-sunken p-6 mb-4 inline-block",
|
|
108
108
|
children: /* @__PURE__ */ _(h, {
|
|
109
109
|
size: 48,
|
|
110
|
-
className: "text-
|
|
110
|
+
className: "text-text-secondary"
|
|
111
111
|
})
|
|
112
112
|
}),
|
|
113
113
|
/* @__PURE__ */ _("h3", {
|
|
@@ -115,7 +115,7 @@ function M() {
|
|
|
115
115
|
children: r(M, "projectDetail.projectNotFound", "Project not found")
|
|
116
116
|
}),
|
|
117
117
|
/* @__PURE__ */ _("p", {
|
|
118
|
-
className: "text-
|
|
118
|
+
className: "text-text-secondary max-w-sm mx-auto mb-4",
|
|
119
119
|
children: r(M, "projectDetail.projectNotFoundDescription", "The project you're looking for doesn't exist or you don't have permission to view it.")
|
|
120
120
|
}),
|
|
121
121
|
/* @__PURE__ */ v(b, {
|
|
@@ -142,7 +142,7 @@ function M() {
|
|
|
142
142
|
/* @__PURE__ */ v("div", {
|
|
143
143
|
className: "flex items-center gap-3",
|
|
144
144
|
children: [/* @__PURE__ */ _("div", {
|
|
145
|
-
className: "p-3 rounded-lg bg-primary/10",
|
|
145
|
+
className: "p-3 rounded-lg bg-action-primary-bg/10",
|
|
146
146
|
children: /* @__PURE__ */ _(h, {
|
|
147
147
|
className: "text-primary",
|
|
148
148
|
size: 28
|
|
@@ -151,7 +151,7 @@ function M() {
|
|
|
151
151
|
className: "text-2xl sm:text-3xl font-bold",
|
|
152
152
|
children: z.name
|
|
153
153
|
}), /* @__PURE__ */ _("p", {
|
|
154
|
-
className: "text-sm text-
|
|
154
|
+
className: "text-sm text-text-secondary font-mono",
|
|
155
155
|
children: z.key
|
|
156
156
|
})] })]
|
|
157
157
|
}),
|
|
@@ -193,7 +193,7 @@ function M() {
|
|
|
193
193
|
q && K && /* @__PURE__ */ _(O, {}),
|
|
194
194
|
K && /* @__PURE__ */ _(D, {
|
|
195
195
|
onClick: () => B(!0),
|
|
196
|
-
className: "text-
|
|
196
|
+
className: "text-status-error-text",
|
|
197
197
|
children: r(M, "projectDetail.deleteProject", "Delete Project")
|
|
198
198
|
})
|
|
199
199
|
]
|
|
@@ -208,7 +208,7 @@ function M() {
|
|
|
208
208
|
className: "flex items-center gap-2 text-base",
|
|
209
209
|
children: [/* @__PURE__ */ _(g, {
|
|
210
210
|
size: 20,
|
|
211
|
-
className: "text-
|
|
211
|
+
className: "text-text-secondary"
|
|
212
212
|
}), r(M, "projectDetail.membersCardTitle", "Members")]
|
|
213
213
|
})
|
|
214
214
|
}), /* @__PURE__ */ v(S, {
|
|
@@ -219,7 +219,7 @@ function M() {
|
|
|
219
219
|
children: Ve ?? 0
|
|
220
220
|
}),
|
|
221
221
|
/* @__PURE__ */ _("p", {
|
|
222
|
-
className: "text-sm text-
|
|
222
|
+
className: "text-sm text-text-secondary",
|
|
223
223
|
children: r(M, "projectDetail.projectMembers", "Project members")
|
|
224
224
|
}),
|
|
225
225
|
Y || J ? /* @__PURE__ */ v("div", {
|
|
@@ -248,21 +248,21 @@ function M() {
|
|
|
248
248
|
className: "flex items-center gap-2 text-base",
|
|
249
249
|
children: [/* @__PURE__ */ _(d, {
|
|
250
250
|
size: 20,
|
|
251
|
-
className: "text-
|
|
251
|
+
className: "text-text-secondary"
|
|
252
252
|
}), r(M, "projectDetail.activityCardTitle", "Activity")]
|
|
253
253
|
})
|
|
254
254
|
}), /* @__PURE__ */ v(S, { children: [/* @__PURE__ */ _("p", {
|
|
255
255
|
className: "text-3xl font-bold",
|
|
256
256
|
children: Ue
|
|
257
257
|
}), /* @__PURE__ */ _("p", {
|
|
258
|
-
className: "text-sm text-
|
|
258
|
+
className: "text-sm text-text-secondary",
|
|
259
259
|
children: r(M, "projectDetail.recentActivityItems", "Recent activity items")
|
|
260
260
|
})] })] })]
|
|
261
261
|
}),
|
|
262
262
|
/* @__PURE__ */ v(x, { children: [/* @__PURE__ */ _(C, { children: /* @__PURE__ */ _(w, { children: r(M, "projectDetail.tagsTitle", "Tags") }) }), /* @__PURE__ */ v(S, {
|
|
263
263
|
className: "space-y-3",
|
|
264
264
|
children: [/* @__PURE__ */ _("p", {
|
|
265
|
-
className: "text-sm text-
|
|
265
|
+
className: "text-sm text-text-secondary",
|
|
266
266
|
children: r(M, "projectDetail.tagsDescription", "Use universal tags to classify and filter this project across the workspace.")
|
|
267
267
|
}), /* @__PURE__ */ _(s, {
|
|
268
268
|
entityType: "Project",
|
|
@@ -303,7 +303,7 @@ function M() {
|
|
|
303
303
|
/* @__PURE__ */ v("div", {
|
|
304
304
|
className: "space-y-1",
|
|
305
305
|
children: [/* @__PURE__ */ v("span", {
|
|
306
|
-
className: "text-
|
|
306
|
+
className: "text-text-secondary flex items-center gap-2",
|
|
307
307
|
children: [/* @__PURE__ */ _(h, { size: 14 }), r(M, "projectDetail.projectId", "Project ID")]
|
|
308
308
|
}), /* @__PURE__ */ _("button", {
|
|
309
309
|
type: "button",
|
|
@@ -323,7 +323,7 @@ function M() {
|
|
|
323
323
|
/* @__PURE__ */ v("div", {
|
|
324
324
|
className: "space-y-1",
|
|
325
325
|
children: [/* @__PURE__ */ v("span", {
|
|
326
|
-
className: "text-
|
|
326
|
+
className: "text-text-secondary flex items-center gap-2",
|
|
327
327
|
children: [/* @__PURE__ */ _(h, { size: 14 }), r(M, "projectDetail.projectKey", "Project Key")]
|
|
328
328
|
}), /* @__PURE__ */ _("span", {
|
|
329
329
|
className: "font-medium",
|
|
@@ -333,21 +333,21 @@ function M() {
|
|
|
333
333
|
/* @__PURE__ */ v("div", {
|
|
334
334
|
className: "space-y-1",
|
|
335
335
|
children: [/* @__PURE__ */ v("span", {
|
|
336
|
-
className: "text-
|
|
336
|
+
className: "text-text-secondary flex items-center gap-2",
|
|
337
337
|
children: [/* @__PURE__ */ _(m, { size: 14 }), r(M, "projectDetail.created", "Created")]
|
|
338
338
|
}), /* @__PURE__ */ _("span", { children: a(z.createdAt) })]
|
|
339
339
|
}),
|
|
340
340
|
/* @__PURE__ */ v("div", {
|
|
341
341
|
className: "space-y-1",
|
|
342
342
|
children: [/* @__PURE__ */ v("span", {
|
|
343
|
-
className: "text-
|
|
343
|
+
className: "text-text-secondary flex items-center gap-2",
|
|
344
344
|
children: [/* @__PURE__ */ _(m, { size: 14 }), r(M, "projectDetail.lastUpdated", "Last Updated")]
|
|
345
345
|
}), /* @__PURE__ */ _("span", { children: a(z.updatedAt) })]
|
|
346
346
|
}),
|
|
347
347
|
/* @__PURE__ */ v("div", {
|
|
348
348
|
className: "space-y-1",
|
|
349
349
|
children: [/* @__PURE__ */ v("span", {
|
|
350
|
-
className: "text-
|
|
350
|
+
className: "text-text-secondary flex items-center gap-2",
|
|
351
351
|
children: [/* @__PURE__ */ _(g, { size: 14 }), r(M, "projectDetail.workspaceId", "Workspace ID")]
|
|
352
352
|
}), /* @__PURE__ */ _("span", {
|
|
353
353
|
className: "font-mono text-xs block",
|
|
@@ -357,7 +357,7 @@ function M() {
|
|
|
357
357
|
/* @__PURE__ */ v("div", {
|
|
358
358
|
className: "space-y-1",
|
|
359
359
|
children: [/* @__PURE__ */ v("span", {
|
|
360
|
-
className: "text-
|
|
360
|
+
className: "text-text-secondary flex items-center gap-2",
|
|
361
361
|
children: [/* @__PURE__ */ _(g, { size: 14 }), r(M, "projectDetail.creatorId", "Creator ID")]
|
|
362
362
|
}), /* @__PURE__ */ _("span", {
|
|
363
363
|
className: "font-mono text-xs block",
|
|
@@ -371,7 +371,7 @@ function M() {
|
|
|
371
371
|
className: "text-sm font-medium mb-2",
|
|
372
372
|
children: r(M, "projectDetail.configuration", "Configuration")
|
|
373
373
|
}), /* @__PURE__ */ _("pre", {
|
|
374
|
-
className: "text-xs bg-
|
|
374
|
+
className: "text-xs bg-bg-sunken p-3 rounded-lg overflow-auto",
|
|
375
375
|
children: JSON.stringify(z.config, null, 2)
|
|
376
376
|
})]
|
|
377
377
|
})]
|
|
@@ -417,7 +417,7 @@ function M() {
|
|
|
417
417
|
}), /* @__PURE__ */ _(ge, {
|
|
418
418
|
onClick: Ke,
|
|
419
419
|
disabled: G,
|
|
420
|
-
className: "bg-
|
|
420
|
+
className: "bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90",
|
|
421
421
|
children: G ? r(M, "projectDetail.deleting", "Deleting...") : r(M, "projectDetail.deleteProject", "Delete Project")
|
|
422
422
|
})] })] })
|
|
423
423
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectDetailPage.js","names":[],"sources":["../../src/pages/ProjectDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport {\n ArrowLeft,\n FolderKanban,\n Users,\n UserPlus,\n Activity,\n Archive,\n MoreVertical,\n Clock,\n} from 'lucide-react';\nimport {\n Button,\n Card,\n CardHeader,\n CardTitle,\n CardContent,\n Badge,\n Skeleton,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n DropdownMenuSeparator,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n EntityIdModal,\n useEntityIdModal,\n} from '@burdenoff/fe-libs/ui';\nimport { type TagItem } from '@burdenoff/fe-libs/tag';\nimport { ProjectTagsWidget } from '../components/tags/ProjectTagsWidget';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { useProject, useProjectMembersCount } from '../hooks/useProjects';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport {\n useArchiveProject,\n useUnarchiveProject,\n useDeleteProject,\n useTagProject,\n useUntagProject,\n useUpdateProject,\n} from '../hooks/useProjectMutations';\nimport { formatDate } from '../utils/formatters';\nimport { nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport { getProjectDisplayTags, toProjectTag } from '../utils/projectTags';\nimport { getProjectStatusClasses } from '../utils/tokenHelpers';\nimport { useWorkspaceActivity } from '../hooks/useWorkspaceActivity';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { ProjectMemberPickerDialog } from '../components/ProjectMemberPickerDialog';\n\nexport function ProjectDetailPage() {\n const { t } = useI18n();\n const idModal = useEntityIdModal();\n\n const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId: string }>();\n useWorkspacesPageView('workspaces.project-detail', {\n workspaceId,\n projectId,\n requireParams: true,\n });\n const navigate = useNavigate();\n const { basePath = '/workspaces' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { data: project, isLoading, error } = useProject(projectId || '', workspaceId);\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [showAddMemberDialog, setShowAddMemberDialog] = useState(false);\n\n const { mutate: archiveProject, isPending: isArchiving } = useArchiveProject(workspaceId);\n const { mutate: unarchiveProject, isPending: isUnarchiving } = useUnarchiveProject(workspaceId);\n const { mutate: deleteProject, isPending: isDeleting } = useDeleteProject(workspaceId);\n const { mutateAsync: tagProject, isPending: isTaggingProject } = useTagProject(workspaceId);\n const { mutateAsync: untagProject, isPending: isUntaggingProject } = useUntagProject(workspaceId);\n const { mutateAsync: updateProject, isPending: isUpdatingProject } =\n useUpdateProject(workspaceId);\n const {\n canViewProject,\n canDeleteProject,\n canArchiveProject,\n canListProjectMembers,\n canAddProjectMember,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n const { data: projectMemberCount } = useProjectMembersCount(projectId || '', workspaceId);\n const displayTags = useMemo(\n () =>\n getProjectDisplayTags({\n tags: project?.tags ?? [],\n tagAssignments: project?.tagAssignments,\n }),\n [project?.tags, project?.tagAssignments]\n );\n const { data: workspaceActivity } = useWorkspaceActivity(workspaceId);\n const relatedActivityCount = (workspaceActivity ?? []).filter(\n (item) => item.resourceId === projectId || item.resourceType?.toLowerCase() === 'project'\n ).length;\n const widgetTags: TagItem[] = displayTags;\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <Skeleton className=\"h-64\" />\n </div>\n );\n }\n\n if (!canViewProject) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'projectDetail.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'projectDetail.noPermissionToViewProject',\n \"You don't have permission to view this project.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n const handleBack = () => {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects`));\n };\n\n const handleArchive = () => {\n if (!project) return;\n if (project.archived) {\n unarchiveProject(project.id, {\n onError: (err) => {\n console.error('Failed to unarchive project:', err);\n },\n });\n } else {\n archiveProject(project.id, {\n onError: (err) => {\n console.error('Failed to archive project:', err);\n },\n });\n }\n };\n\n const handleDelete = () => {\n if (!project) return;\n void nativeImpact('medium');\n deleteProject(project.id, {\n onSuccess: () => {\n void nativeNotify('success');\n setShowDeleteDialog(false);\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects`));\n },\n onError: (err) => {\n void nativeNotify('error');\n console.error('Failed to delete project:', err);\n },\n });\n };\n\n const handleViewProjectMembers = () => {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects/${projectId}/members`));\n };\n\n const syncLegacyProjectTags = async (nextTags: TagItem[]) => {\n if (!project) {\n return;\n }\n\n const normalizedNextTags = nextTags.map((tag) => toProjectTag(tag));\n\n try {\n await updateProject({\n id: project.id,\n input: {\n tags: normalizedNextTags,\n },\n });\n } catch (err) {\n void nativeNotify('warning');\n console.error(\n 'Failed to sync legacy project tag mirror after updating structured project tags:',\n err\n );\n }\n };\n\n const handleAddTag = async (tag: TagItem) => {\n if (!project) {\n return;\n }\n\n try {\n await tagProject({\n entityId: project.id,\n tagId: tag.id,\n });\n\n await syncLegacyProjectTags([...displayTags, toProjectTag(tag)]);\n } catch (err) {\n void nativeNotify('error');\n throw err;\n }\n };\n\n const handleRemoveTag = async (tagId: string) => {\n if (!project) {\n return;\n }\n\n const assignmentId = project.tagAssignments?.find(\n (assignment) => assignment.tagId === tagId\n )?.id;\n try {\n await untagProject({\n entityId: project.id,\n tagId,\n assignmentId,\n });\n\n await syncLegacyProjectTags(\n displayTags.filter((tag) => tag.tagId !== tagId && tag.id !== tagId)\n );\n } catch (err) {\n void nativeNotify('error');\n throw err;\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <Skeleton className=\"h-10 w-32\" />\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n <Skeleton className=\"h-48\" />\n </div>\n );\n }\n\n if (error || !project) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button variant=\"ghost\" onClick={handleBack} className=\"gap-2\">\n <ArrowLeft size={20} />\n {tWithFallback(t, 'projectDetail.backToProjects', 'Back to Projects')}\n </Button>\n <div className=\"text-center py-12\">\n <div className=\"rounded-full bg-muted p-6 mb-4 inline-block\">\n <FolderKanban size={48} className=\"text-muted-foreground\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'projectDetail.projectNotFound', 'Project not found')}\n </h3>\n <p className=\"text-muted-foreground max-w-sm mx-auto mb-4\">\n {tWithFallback(\n t,\n 'projectDetail.projectNotFoundDescription',\n \"The project you're looking for doesn't exist or you don't have permission to view it.\"\n )}\n </p>\n <Button onClick={handleBack}>\n <ArrowLeft size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.backToProjects', 'Back to Projects')}\n </Button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3\">\n <div>\n <Button variant=\"ghost\" onClick={handleBack} className=\"gap-2 mb-2 -ml-2\">\n <ArrowLeft size={20} />\n {tWithFallback(t, 'projectDetail.backToProjects', 'Back to Projects')}\n </Button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-3 rounded-lg bg-primary/10\">\n <FolderKanban className=\"text-primary\" size={28} />\n </div>\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">{project.name}</h1>\n <p className=\"text-sm text-muted-foreground font-mono\">{project.key}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 mt-3\">\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 {tWithFallback(t, 'projectDetail.archived', 'Archived')}\n </Badge>\n )}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"icon\"\n aria-label={tWithFallback(\n t,\n 'projectDetail.openProjectActionsAriaLabel',\n 'Open project actions'\n )}\n >\n <MoreVertical size={20} />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {canArchiveProject && (\n <DropdownMenuItem onClick={handleArchive} disabled={isArchiving || isUnarchiving}>\n <Archive size={16} className=\"mr-2\" />\n {isArchiving || isUnarchiving\n ? tWithFallback(t, 'projectDetail.processing', 'Processing...')\n : project.archived\n ? tWithFallback(t, 'projectDetail.unarchive', 'Unarchive')\n : tWithFallback(t, 'projectDetail.archive', 'Archive')}\n </DropdownMenuItem>\n )}\n {canArchiveProject && canDeleteProject && <DropdownMenuSeparator />}\n {canDeleteProject && (\n <DropdownMenuItem\n onClick={() => setShowDeleteDialog(true)}\n className=\"text-destructive\"\n >\n {tWithFallback(t, 'projectDetail.deleteProject', 'Delete Project')}\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n\n {/* Stats Cards */}\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Users size={20} className=\"text-muted-foreground\" />\n {tWithFallback(t, 'projectDetail.membersCardTitle', 'Members')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-3xl font-bold\">{projectMemberCount ?? 0}</p>\n <p className=\"text-sm text-muted-foreground\">\n {tWithFallback(t, 'projectDetail.projectMembers', 'Project members')}\n </p>\n\n {canAddProjectMember || canListProjectMembers ? (\n <div className=\"flex flex-wrap gap-2\">\n {canAddProjectMember ? (\n <Button size=\"sm\" onClick={() => setShowAddMemberDialog(true)}>\n <UserPlus size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.addProjectMember', 'Add Member')}\n </Button>\n ) : null}\n\n {canListProjectMembers ? (\n <Button variant=\"outline\" size=\"sm\" onClick={handleViewProjectMembers}>\n <Users size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.manageProjectMembers', 'Manage Members')}\n </Button>\n ) : null}\n </div>\n ) : null}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Activity size={20} className=\"text-muted-foreground\" />\n {tWithFallback(t, 'projectDetail.activityCardTitle', 'Activity')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold\">{relatedActivityCount}</p>\n <p className=\"text-sm text-muted-foreground\">\n {tWithFallback(t, 'projectDetail.recentActivityItems', 'Recent activity items')}\n </p>\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'projectDetail.tagsTitle', 'Tags')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'projectDetail.tagsDescription',\n 'Use universal tags to classify and filter this project across the workspace.'\n )}\n </p>\n <ProjectTagsWidget\n entityType=\"Project\"\n entityId={project.id}\n workspaceId={workspaceId}\n tags={widgetTags}\n onAdd={handleAddTag}\n onRemove={handleRemoveTag}\n className={\n isTaggingProject || isUntaggingProject || isUpdatingProject\n ? 'pointer-events-none opacity-75'\n : undefined\n }\n />\n </CardContent>\n </Card>\n\n {/* Project Information */}\n <Card>\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'projectDetail.projectInformation', 'Project Information')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm\">\n <div className=\"space-y-1\">\n <span className=\"text-muted-foreground flex items-center gap-2\">\n <FolderKanban size={14} />\n {tWithFallback(t, 'projectDetail.projectId', 'Project ID')}\n </span>\n <button\n type=\"button\"\n onClick={() =>\n idModal.show({\n id: project.id,\n name: project.name,\n type: 'Project',\n details: project.key ? [{ label: 'Key', value: project.key }] : undefined,\n })\n }\n className=\"inline-flex items-center gap-1.5 text-xs font-medium text-text-link hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded\"\n >\n {tWithFallback(t, 'projectDetail.getId', 'Get ID')}\n </button>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-muted-foreground flex items-center gap-2\">\n <FolderKanban size={14} />\n {tWithFallback(t, 'projectDetail.projectKey', 'Project Key')}\n </span>\n <span className=\"font-medium\">{project.key}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-muted-foreground flex items-center gap-2\">\n <Clock size={14} />\n {tWithFallback(t, 'projectDetail.created', 'Created')}\n </span>\n <span>{formatDate(project.createdAt)}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-muted-foreground flex items-center gap-2\">\n <Clock size={14} />\n {tWithFallback(t, 'projectDetail.lastUpdated', 'Last Updated')}\n </span>\n <span>{formatDate(project.updatedAt)}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-muted-foreground flex items-center gap-2\">\n <Users size={14} />\n {tWithFallback(t, 'projectDetail.workspaceId', 'Workspace ID')}\n </span>\n <span className=\"font-mono text-xs block\">{project.workspaceId}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-muted-foreground flex items-center gap-2\">\n <Users size={14} />\n {tWithFallback(t, 'projectDetail.creatorId', 'Creator ID')}\n </span>\n <span className=\"font-mono text-xs block\">{project.creatorId}</span>\n </div>\n </div>\n\n {project.config && Object.keys(project.config).length > 0 && (\n <div className=\"pt-4 border-t\">\n <h4 className=\"text-sm font-medium mb-2\">\n {tWithFallback(t, 'projectDetail.configuration', 'Configuration')}\n </h4>\n <pre className=\"text-xs bg-muted p-3 rounded-lg overflow-auto\">\n {JSON.stringify(project.config, null, 2)}\n </pre>\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Quick Actions */}\n <Card>\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'projectDetail.quickActions', 'Quick Actions')}</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"flex flex-wrap gap-2\">\n {canListProjectMembers ? (\n <Button variant=\"outline\" size=\"sm\" onClick={handleViewProjectMembers}>\n <Users size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.projectMembersPage', 'Project Members')}\n </Button>\n ) : null}\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/members`))}\n >\n <Users size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.workspaceMembers', 'Workspace Members')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/activity`))}\n >\n <Activity size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.workspaceActivity', 'Workspace Activity')}\n </Button>\n </div>\n </CardContent>\n </Card>\n\n {/* Delete Confirmation Dialog */}\n <AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {tWithFallback(t, 'projectDetail.deleteDialogTitle', 'Delete Project')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {tWithFallback(\n t,\n 'projectDetail.deleteConfirmPrefix',\n 'Are you sure you want to delete \"'\n ) +\n project.name +\n tWithFallback(\n t,\n 'projectDetail.deleteConfirmSuffix',\n '\"? This action cannot be undone and all project data will be permanently removed.'\n )}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={isDeleting}>\n {tWithFallback(t, 'projectDetail.cancel', 'Cancel')}\n </AlertDialogCancel>\n <AlertDialogAction\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n >\n {isDeleting\n ? tWithFallback(t, 'projectDetail.deleting', 'Deleting...')\n : tWithFallback(t, 'projectDetail.deleteProject', 'Delete Project')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n\n {workspaceId && projectId && showAddMemberDialog ? (\n <ProjectMemberPickerDialog\n open={showAddMemberDialog}\n onOpenChange={setShowAddMemberDialog}\n workspaceId={workspaceId}\n projectId={projectId}\n projectName={project.name}\n />\n ) : null}\n <EntityIdModal control={idModal} />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectDetailPage.js","names":[],"sources":["../../src/pages/ProjectDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport {\n ArrowLeft,\n FolderKanban,\n Users,\n UserPlus,\n Activity,\n Archive,\n MoreVertical,\n Clock,\n} from 'lucide-react';\nimport {\n Button,\n Card,\n CardHeader,\n CardTitle,\n CardContent,\n Badge,\n Skeleton,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n DropdownMenuSeparator,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n EntityIdModal,\n useEntityIdModal,\n} from '@burdenoff/fe-libs/ui';\nimport { type TagItem } from '@burdenoff/fe-libs/tag';\nimport { ProjectTagsWidget } from '../components/tags/ProjectTagsWidget';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { useProject, useProjectMembersCount } from '../hooks/useProjects';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport {\n useArchiveProject,\n useUnarchiveProject,\n useDeleteProject,\n useTagProject,\n useUntagProject,\n useUpdateProject,\n} from '../hooks/useProjectMutations';\nimport { formatDate } from '../utils/formatters';\nimport { nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport { getProjectDisplayTags, toProjectTag } from '../utils/projectTags';\nimport { getProjectStatusClasses } from '../utils/tokenHelpers';\nimport { useWorkspaceActivity } from '../hooks/useWorkspaceActivity';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { ProjectMemberPickerDialog } from '../components/ProjectMemberPickerDialog';\n\nexport function ProjectDetailPage() {\n const { t } = useI18n();\n const idModal = useEntityIdModal();\n\n const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId: string }>();\n useWorkspacesPageView('workspaces.project-detail', {\n workspaceId,\n projectId,\n requireParams: true,\n });\n const navigate = useNavigate();\n const { basePath = '/workspaces' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { data: project, isLoading, error } = useProject(projectId || '', workspaceId);\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [showAddMemberDialog, setShowAddMemberDialog] = useState(false);\n\n const { mutate: archiveProject, isPending: isArchiving } = useArchiveProject(workspaceId);\n const { mutate: unarchiveProject, isPending: isUnarchiving } = useUnarchiveProject(workspaceId);\n const { mutate: deleteProject, isPending: isDeleting } = useDeleteProject(workspaceId);\n const { mutateAsync: tagProject, isPending: isTaggingProject } = useTagProject(workspaceId);\n const { mutateAsync: untagProject, isPending: isUntaggingProject } = useUntagProject(workspaceId);\n const { mutateAsync: updateProject, isPending: isUpdatingProject } =\n useUpdateProject(workspaceId);\n const {\n canViewProject,\n canDeleteProject,\n canArchiveProject,\n canListProjectMembers,\n canAddProjectMember,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n const { data: projectMemberCount } = useProjectMembersCount(projectId || '', workspaceId);\n const displayTags = useMemo(\n () =>\n getProjectDisplayTags({\n tags: project?.tags ?? [],\n tagAssignments: project?.tagAssignments,\n }),\n [project?.tags, project?.tagAssignments]\n );\n const { data: workspaceActivity } = useWorkspaceActivity(workspaceId);\n const relatedActivityCount = (workspaceActivity ?? []).filter(\n (item) => item.resourceId === projectId || item.resourceType?.toLowerCase() === 'project'\n ).length;\n const widgetTags: TagItem[] = displayTags;\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <Skeleton className=\"h-64\" />\n </div>\n );\n }\n\n if (!canViewProject) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'projectDetail.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'projectDetail.noPermissionToViewProject',\n \"You don't have permission to view this project.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n const handleBack = () => {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects`));\n };\n\n const handleArchive = () => {\n if (!project) return;\n if (project.archived) {\n unarchiveProject(project.id, {\n onError: (err) => {\n console.error('Failed to unarchive project:', err);\n },\n });\n } else {\n archiveProject(project.id, {\n onError: (err) => {\n console.error('Failed to archive project:', err);\n },\n });\n }\n };\n\n const handleDelete = () => {\n if (!project) return;\n void nativeImpact('medium');\n deleteProject(project.id, {\n onSuccess: () => {\n void nativeNotify('success');\n setShowDeleteDialog(false);\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects`));\n },\n onError: (err) => {\n void nativeNotify('error');\n console.error('Failed to delete project:', err);\n },\n });\n };\n\n const handleViewProjectMembers = () => {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects/${projectId}/members`));\n };\n\n const syncLegacyProjectTags = async (nextTags: TagItem[]) => {\n if (!project) {\n return;\n }\n\n const normalizedNextTags = nextTags.map((tag) => toProjectTag(tag));\n\n try {\n await updateProject({\n id: project.id,\n input: {\n tags: normalizedNextTags,\n },\n });\n } catch (err) {\n void nativeNotify('warning');\n console.error(\n 'Failed to sync legacy project tag mirror after updating structured project tags:',\n err\n );\n }\n };\n\n const handleAddTag = async (tag: TagItem) => {\n if (!project) {\n return;\n }\n\n try {\n await tagProject({\n entityId: project.id,\n tagId: tag.id,\n });\n\n await syncLegacyProjectTags([...displayTags, toProjectTag(tag)]);\n } catch (err) {\n void nativeNotify('error');\n throw err;\n }\n };\n\n const handleRemoveTag = async (tagId: string) => {\n if (!project) {\n return;\n }\n\n const assignmentId = project.tagAssignments?.find(\n (assignment) => assignment.tagId === tagId\n )?.id;\n try {\n await untagProject({\n entityId: project.id,\n tagId,\n assignmentId,\n });\n\n await syncLegacyProjectTags(\n displayTags.filter((tag) => tag.tagId !== tagId && tag.id !== tagId)\n );\n } catch (err) {\n void nativeNotify('error');\n throw err;\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <Skeleton className=\"h-10 w-32\" />\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n <Skeleton className=\"h-48\" />\n </div>\n );\n }\n\n if (error || !project) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button variant=\"ghost\" onClick={handleBack} className=\"gap-2\">\n <ArrowLeft size={20} />\n {tWithFallback(t, 'projectDetail.backToProjects', 'Back to Projects')}\n </Button>\n <div className=\"text-center py-12\">\n <div className=\"rounded-full bg-bg-sunken p-6 mb-4 inline-block\">\n <FolderKanban size={48} className=\"text-text-secondary\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'projectDetail.projectNotFound', 'Project not found')}\n </h3>\n <p className=\"text-text-secondary max-w-sm mx-auto mb-4\">\n {tWithFallback(\n t,\n 'projectDetail.projectNotFoundDescription',\n \"The project you're looking for doesn't exist or you don't have permission to view it.\"\n )}\n </p>\n <Button onClick={handleBack}>\n <ArrowLeft size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.backToProjects', 'Back to Projects')}\n </Button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3\">\n <div>\n <Button variant=\"ghost\" onClick={handleBack} className=\"gap-2 mb-2 -ml-2\">\n <ArrowLeft size={20} />\n {tWithFallback(t, 'projectDetail.backToProjects', 'Back to Projects')}\n </Button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-3 rounded-lg bg-action-primary-bg/10\">\n <FolderKanban className=\"text-primary\" size={28} />\n </div>\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">{project.name}</h1>\n <p className=\"text-sm text-text-secondary font-mono\">{project.key}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 mt-3\">\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 {tWithFallback(t, 'projectDetail.archived', 'Archived')}\n </Badge>\n )}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"icon\"\n aria-label={tWithFallback(\n t,\n 'projectDetail.openProjectActionsAriaLabel',\n 'Open project actions'\n )}\n >\n <MoreVertical size={20} />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {canArchiveProject && (\n <DropdownMenuItem onClick={handleArchive} disabled={isArchiving || isUnarchiving}>\n <Archive size={16} className=\"mr-2\" />\n {isArchiving || isUnarchiving\n ? tWithFallback(t, 'projectDetail.processing', 'Processing...')\n : project.archived\n ? tWithFallback(t, 'projectDetail.unarchive', 'Unarchive')\n : tWithFallback(t, 'projectDetail.archive', 'Archive')}\n </DropdownMenuItem>\n )}\n {canArchiveProject && canDeleteProject && <DropdownMenuSeparator />}\n {canDeleteProject && (\n <DropdownMenuItem\n onClick={() => setShowDeleteDialog(true)}\n className=\"text-status-error-text\"\n >\n {tWithFallback(t, 'projectDetail.deleteProject', 'Delete Project')}\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n\n {/* Stats Cards */}\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Users size={20} className=\"text-text-secondary\" />\n {tWithFallback(t, 'projectDetail.membersCardTitle', 'Members')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-3xl font-bold\">{projectMemberCount ?? 0}</p>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(t, 'projectDetail.projectMembers', 'Project members')}\n </p>\n\n {canAddProjectMember || canListProjectMembers ? (\n <div className=\"flex flex-wrap gap-2\">\n {canAddProjectMember ? (\n <Button size=\"sm\" onClick={() => setShowAddMemberDialog(true)}>\n <UserPlus size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.addProjectMember', 'Add Member')}\n </Button>\n ) : null}\n\n {canListProjectMembers ? (\n <Button variant=\"outline\" size=\"sm\" onClick={handleViewProjectMembers}>\n <Users size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.manageProjectMembers', 'Manage Members')}\n </Button>\n ) : null}\n </div>\n ) : null}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Activity size={20} className=\"text-text-secondary\" />\n {tWithFallback(t, 'projectDetail.activityCardTitle', 'Activity')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold\">{relatedActivityCount}</p>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(t, 'projectDetail.recentActivityItems', 'Recent activity items')}\n </p>\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'projectDetail.tagsTitle', 'Tags')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'projectDetail.tagsDescription',\n 'Use universal tags to classify and filter this project across the workspace.'\n )}\n </p>\n <ProjectTagsWidget\n entityType=\"Project\"\n entityId={project.id}\n workspaceId={workspaceId}\n tags={widgetTags}\n onAdd={handleAddTag}\n onRemove={handleRemoveTag}\n className={\n isTaggingProject || isUntaggingProject || isUpdatingProject\n ? 'pointer-events-none opacity-75'\n : undefined\n }\n />\n </CardContent>\n </Card>\n\n {/* Project Information */}\n <Card>\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'projectDetail.projectInformation', 'Project Information')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm\">\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <FolderKanban size={14} />\n {tWithFallback(t, 'projectDetail.projectId', 'Project ID')}\n </span>\n <button\n type=\"button\"\n onClick={() =>\n idModal.show({\n id: project.id,\n name: project.name,\n type: 'Project',\n details: project.key ? [{ label: 'Key', value: project.key }] : undefined,\n })\n }\n className=\"inline-flex items-center gap-1.5 text-xs font-medium text-text-link hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded\"\n >\n {tWithFallback(t, 'projectDetail.getId', 'Get ID')}\n </button>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <FolderKanban size={14} />\n {tWithFallback(t, 'projectDetail.projectKey', 'Project Key')}\n </span>\n <span className=\"font-medium\">{project.key}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <Clock size={14} />\n {tWithFallback(t, 'projectDetail.created', 'Created')}\n </span>\n <span>{formatDate(project.createdAt)}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <Clock size={14} />\n {tWithFallback(t, 'projectDetail.lastUpdated', 'Last Updated')}\n </span>\n <span>{formatDate(project.updatedAt)}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <Users size={14} />\n {tWithFallback(t, 'projectDetail.workspaceId', 'Workspace ID')}\n </span>\n <span className=\"font-mono text-xs block\">{project.workspaceId}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <Users size={14} />\n {tWithFallback(t, 'projectDetail.creatorId', 'Creator ID')}\n </span>\n <span className=\"font-mono text-xs block\">{project.creatorId}</span>\n </div>\n </div>\n\n {project.config && Object.keys(project.config).length > 0 && (\n <div className=\"pt-4 border-t\">\n <h4 className=\"text-sm font-medium mb-2\">\n {tWithFallback(t, 'projectDetail.configuration', 'Configuration')}\n </h4>\n <pre className=\"text-xs bg-bg-sunken p-3 rounded-lg overflow-auto\">\n {JSON.stringify(project.config, null, 2)}\n </pre>\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Quick Actions */}\n <Card>\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'projectDetail.quickActions', 'Quick Actions')}</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"flex flex-wrap gap-2\">\n {canListProjectMembers ? (\n <Button variant=\"outline\" size=\"sm\" onClick={handleViewProjectMembers}>\n <Users size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.projectMembersPage', 'Project Members')}\n </Button>\n ) : null}\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/members`))}\n >\n <Users size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.workspaceMembers', 'Workspace Members')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/activity`))}\n >\n <Activity size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.workspaceActivity', 'Workspace Activity')}\n </Button>\n </div>\n </CardContent>\n </Card>\n\n {/* Delete Confirmation Dialog */}\n <AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {tWithFallback(t, 'projectDetail.deleteDialogTitle', 'Delete Project')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {tWithFallback(\n t,\n 'projectDetail.deleteConfirmPrefix',\n 'Are you sure you want to delete \"'\n ) +\n project.name +\n tWithFallback(\n t,\n 'projectDetail.deleteConfirmSuffix',\n '\"? This action cannot be undone and all project data will be permanently removed.'\n )}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={isDeleting}>\n {tWithFallback(t, 'projectDetail.cancel', 'Cancel')}\n </AlertDialogCancel>\n <AlertDialogAction\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90\"\n >\n {isDeleting\n ? tWithFallback(t, 'projectDetail.deleting', 'Deleting...')\n : tWithFallback(t, 'projectDetail.deleteProject', 'Delete Project')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n\n {workspaceId && projectId && showAddMemberDialog ? (\n <ProjectMemberPickerDialog\n open={showAddMemberDialog}\n onOpenChange={setShowAddMemberDialog}\n workspaceId={workspaceId}\n projectId={projectId}\n projectName={project.name}\n />\n ) : null}\n <EntityIdModal control={idModal} />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -70,7 +70,7 @@ function T() {
|
|
|
70
70
|
className: "text-lg font-semibold",
|
|
71
71
|
children: n(T, "projectMembers.accessDenied", "Access Denied")
|
|
72
72
|
}), /* @__PURE__ */ f("p", {
|
|
73
|
-
className: "text-sm text-
|
|
73
|
+
className: "text-sm text-text-secondary",
|
|
74
74
|
children: n(T, "projectMembers.noPermission", "You don't have permission to manage project members.")
|
|
75
75
|
})]
|
|
76
76
|
})
|
|
@@ -117,7 +117,7 @@ function T() {
|
|
|
117
117
|
children: n(T, "projectMembers.pageTitle", "Project Members")
|
|
118
118
|
}),
|
|
119
119
|
/* @__PURE__ */ f("p", {
|
|
120
|
-
className: "text-sm text-
|
|
120
|
+
className: "text-sm text-text-secondary sm:text-base",
|
|
121
121
|
children: z?.name ? `${z.name} · ${n(T, "projectMembers.pageSubtitle", "Only existing workspace members can be added to this project.")}` : n(T, "projectMembers.pageSubtitle", "Only existing workspace members can be added to this project.")
|
|
122
122
|
})
|
|
123
123
|
] }), /* @__PURE__ */ p("div", {
|
|
@@ -134,7 +134,7 @@ function T() {
|
|
|
134
134
|
}),
|
|
135
135
|
/* @__PURE__ */ p("div", {
|
|
136
136
|
className: "relative",
|
|
137
|
-
children: [/* @__PURE__ */ f(ae, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-
|
|
137
|
+
children: [/* @__PURE__ */ f(ae, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ f(g, {
|
|
138
138
|
value: j,
|
|
139
139
|
onChange: (e) => M(e.target.value),
|
|
140
140
|
placeholder: n(T, "projectMembers.searchPlaceholder", "Search project members"),
|
|
@@ -142,28 +142,28 @@ function T() {
|
|
|
142
142
|
})]
|
|
143
143
|
}),
|
|
144
144
|
L ? /* @__PURE__ */ f("div", {
|
|
145
|
-
className: "rounded-lg border border-
|
|
145
|
+
className: "rounded-lg border border-status-error-border/20 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
|
|
146
146
|
children: L
|
|
147
147
|
}) : null,
|
|
148
148
|
B || fe ? /* @__PURE__ */ f("div", {
|
|
149
149
|
className: "space-y-4",
|
|
150
150
|
children: Array.from({ length: 4 }).map((e, t) => /* @__PURE__ */ p("div", {
|
|
151
|
-
className: "rounded-lg border bg-
|
|
151
|
+
className: "rounded-lg border bg-bg-surface p-4",
|
|
152
152
|
children: [/* @__PURE__ */ f(v, { className: "h-5 w-40" }), /* @__PURE__ */ f(v, { className: "mt-2 h-4 w-56" })]
|
|
153
153
|
}, t))
|
|
154
154
|
}) : V || U ? /* @__PURE__ */ f("div", {
|
|
155
|
-
className: "rounded-lg border border-
|
|
155
|
+
className: "rounded-lg border border-status-error-border/20 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
|
|
156
156
|
children: U instanceof Error && U.message || V instanceof Error && V.message || n(T, "projectMembers.loadError", "Failed to load project members")
|
|
157
157
|
}) : $.length === 0 ? /* @__PURE__ */ p("div", {
|
|
158
|
-
className: "rounded-lg border bg-
|
|
158
|
+
className: "rounded-lg border bg-bg-surface p-10 text-center",
|
|
159
159
|
children: [
|
|
160
|
-
/* @__PURE__ */ f(se, { className: "mx-auto mb-4 size-12 text-
|
|
160
|
+
/* @__PURE__ */ f(se, { className: "mx-auto mb-4 size-12 text-text-secondary" }),
|
|
161
161
|
/* @__PURE__ */ f("h2", {
|
|
162
162
|
className: "text-xl font-semibold",
|
|
163
163
|
children: j ? n(T, "projectMembers.noResultsTitle", "No matching members found") : n(T, "projectMembers.emptyTitle", "No project members yet")
|
|
164
164
|
}),
|
|
165
165
|
/* @__PURE__ */ f("p", {
|
|
166
|
-
className: "mt-2 text-
|
|
166
|
+
className: "mt-2 text-text-secondary",
|
|
167
167
|
children: j ? n(T, "projectMembers.noResultsSubtitle", "Try a different search to find a project member.") : n(T, "projectMembers.emptySubtitle", "Add workspace members to this project so they can collaborate here.")
|
|
168
168
|
}),
|
|
169
169
|
W ? /* @__PURE__ */ f("div", {
|
|
@@ -188,7 +188,7 @@ function T() {
|
|
|
188
188
|
children: [$.map((e) => {
|
|
189
189
|
let t = Q[e.userId], r = w(Q, e), i = q && F === e.userId;
|
|
190
190
|
return /* @__PURE__ */ p("div", {
|
|
191
|
-
className: "flex items-center justify-between gap-4 rounded-lg border bg-
|
|
191
|
+
className: "flex items-center justify-between gap-4 rounded-lg border bg-bg-surface p-4",
|
|
192
192
|
children: [/* @__PURE__ */ p("div", {
|
|
193
193
|
className: "min-w-0 flex-1",
|
|
194
194
|
children: [t ? /* @__PURE__ */ f(S, {
|
|
@@ -200,10 +200,10 @@ function T() {
|
|
|
200
200
|
className: "truncate font-medium",
|
|
201
201
|
children: r.name
|
|
202
202
|
}), r.email ? /* @__PURE__ */ f("p", {
|
|
203
|
-
className: "truncate text-sm text-
|
|
203
|
+
className: "truncate text-sm text-text-secondary",
|
|
204
204
|
children: r.email
|
|
205
205
|
}) : /* @__PURE__ */ f("p", {
|
|
206
|
-
className: "truncate text-xs text-
|
|
206
|
+
className: "truncate text-xs text-text-secondary",
|
|
207
207
|
children: e.userId
|
|
208
208
|
})]
|
|
209
209
|
}), /* @__PURE__ */ p("div", {
|
|
@@ -213,7 +213,7 @@ function T() {
|
|
|
213
213
|
className: "text-xs",
|
|
214
214
|
children: n(T, "projectMembers.memberBadge", "Project member")
|
|
215
215
|
}), /* @__PURE__ */ p("span", {
|
|
216
|
-
className: "text-xs text-
|
|
216
|
+
className: "text-xs text-text-secondary",
|
|
217
217
|
children: [
|
|
218
218
|
n(T, "projectMembers.addedLabel", "Added"),
|
|
219
219
|
" ",
|
|
@@ -230,9 +230,9 @@ function T() {
|
|
|
230
230
|
}) : null]
|
|
231
231
|
}, e.id);
|
|
232
232
|
}), W ? /* @__PURE__ */ p("div", {
|
|
233
|
-
className: "flex flex-col items-center gap-2 rounded-lg border border-dashed bg-
|
|
233
|
+
className: "flex flex-col items-center gap-2 rounded-lg border border-dashed bg-bg-sunken/20 p-4 text-center",
|
|
234
234
|
children: [/* @__PURE__ */ p("p", {
|
|
235
|
-
className: "text-sm text-
|
|
235
|
+
className: "text-sm text-text-secondary",
|
|
236
236
|
children: [
|
|
237
237
|
"Showing ",
|
|
238
238
|
X.length,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectMembersPage.js","names":[],"sources":["../../src/pages/ProjectMembersPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { ArrowLeft, Search, UserMinus, UserPlus, Users } from 'lucide-react';\nimport {\n ActorIdentity,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport { Badge, Button, Input, PageContainer, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ProjectMemberPickerDialog } from '../components/ProjectMemberPickerDialog';\nimport { useInfiniteProjectMembers } from '../hooks/useProjectMembers';\nimport { useProject } from '../hooks/useProjects';\nimport { useRemoveProjectMember } from '../hooks/useProjectMemberMutations';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { tWithFallback } from '../utils/i18n';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport type { ProjectMember } from '../types';\n\nconst PROJECT_MEMBER_PAGE_SIZE = 100;\n\nfunction getMemberFallback(profileMap: ActorProfileMap, member: ProjectMember) {\n const profile = profileMap[member.userId];\n if (profile) {\n return {\n name: profile.displayName,\n email: profile.email ?? undefined,\n };\n }\n\n return {\n name: member.userId,\n email: undefined,\n };\n}\n\nexport function ProjectMembersPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId: string }>();\n const {\n basePath = '/workspaces',\n apiGatewayUrl,\n authToken,\n organizationId,\n } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const [searchValue, setSearchValue] = useState('');\n const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);\n const [removingUserId, setRemovingUserId] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<string | null>(null);\n\n useWorkspacesPageView('workspaces.project-members', {\n workspaceId,\n projectId,\n requireParams: true,\n });\n\n const {\n data: project,\n isLoading: isLoadingProject,\n error: projectError,\n } = useProject(projectId || '', workspaceId);\n const {\n data: projectMembersPages,\n isLoading: isLoadingMembers,\n error: membersError,\n hasNextPage: hasMoreProjectMembers,\n fetchNextPage: fetchNextProjectMembersPage,\n isFetchingNextPage: isFetchingNextProjectMembersPage,\n } = useInfiniteProjectMembers(projectId || '', workspaceId, PROJECT_MEMBER_PAGE_SIZE);\n const { mutate: removeProjectMember, isPending: isRemovingProjectMember } =\n useRemoveProjectMember(workspaceId);\n\n const {\n canViewProject,\n canListProjectMembers,\n canAddProjectMember,\n canRemoveProjectMember,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n\n const projectMembers = useMemo(\n () => projectMembersPages?.pages.flatMap((page) => page.items) ?? [],\n [projectMembersPages?.pages]\n );\n const totalProjectMembers = projectMembersPages?.pages[0]?.total ?? projectMembers.length;\n const actors = useMemo<ActorInput[]>(\n () => projectMembers.map((member) => ({ actorId: member.userId, actorType: 'user' })),\n [projectMembers]\n );\n const profileMap: ActorProfileMap = useActorProfiles(actors, {\n apiGatewayUrl,\n authToken,\n orgId: organizationId,\n workspaceId,\n });\n\n const filteredMembers = useMemo(() => {\n const needle = searchValue.trim().toLowerCase();\n if (!needle) {\n return projectMembers;\n }\n\n return projectMembers.filter((member) => {\n const fallback = getMemberFallback(profileMap, member);\n return (\n fallback.name.toLowerCase().includes(needle) ||\n fallback.email?.toLowerCase().includes(needle) ||\n member.userId.toLowerCase().includes(needle)\n );\n });\n }, [profileMap, projectMembers, searchValue]);\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-10 w-40\" />\n <Skeleton className=\"h-10 w-64\" />\n <Skeleton className=\"h-40 w-full\" />\n </div>\n );\n }\n\n if (!canViewProject || !canListProjectMembers) {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"space-y-2 text-center\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'projectMembers.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'projectMembers.noPermission',\n \"You don't have permission to manage project members.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n const handleBack = () => {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects/${projectId}`));\n };\n\n const handleRemoveMember = async (member: ProjectMember) => {\n const fallback = getMemberFallback(profileMap, member);\n void nativeImpact('medium');\n setRemoveError(null);\n const message = `${tWithFallback(t, 'projectMembers.removeConfirmPrefix', 'Remove')} ${fallback.name} ${tWithFallback(t, 'projectMembers.removeConfirmSuffix', 'from this project?')}`;\n const native = await nativeConfirm({\n title: tWithFallback(t, 'projectMembers.removeConfirmTitle', 'Remove project member'),\n message,\n okButtonTitle: tWithFallback(t, 'projectMembers.removeAction', 'Remove'),\n cancelButtonTitle: tWithFallback(t, 'projectMembers.cancel', 'Cancel'),\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) {\n return;\n }\n\n setRemovingUserId(member.userId);\n removeProjectMember(\n { projectId: member.projectId, userId: member.userId },\n {\n onSuccess: () => {\n void nativeNotify('success');\n setRemovingUserId(null);\n },\n onError: () => {\n void nativeNotify('error');\n setRemoveError(\n tWithFallback(\n t,\n 'projectMembers.removeError',\n 'Failed to remove project member. Please try again.'\n )\n );\n setRemovingUserId(null);\n },\n }\n );\n };\n\n return (\n <PageContainer padded={false}>\n <div className=\"space-y-4 p-3 sm:space-y-6 sm:p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <Button variant=\"ghost\" onClick={handleBack} className=\"mb-2 -ml-2 gap-2\">\n <ArrowLeft size={20} />\n {tWithFallback(t, 'projectMembers.backToProject', 'Back to Project')}\n </Button>\n <h1 className=\"text-2xl font-bold sm:text-3xl\">\n {tWithFallback(t, 'projectMembers.pageTitle', 'Project Members')}\n </h1>\n <p className=\"text-sm text-muted-foreground sm:text-base\">\n {project?.name\n ? `${project.name} · ${tWithFallback(\n t,\n 'projectMembers.pageSubtitle',\n 'Only existing workspace members can be added to this project.'\n )}`\n : tWithFallback(\n t,\n 'projectMembers.pageSubtitle',\n 'Only existing workspace members can be added to this project.'\n )}\n </p>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\">{totalProjectMembers}</Badge>\n {canAddProjectMember ? (\n <Button\n onClick={() => setIsAddDialogOpen(true)}\n disabled={!workspaceId || !projectId}\n >\n <UserPlus className=\"mr-2 size-4\" />\n {tWithFallback(t, 'projectMembers.addMember', 'Add Member')}\n </Button>\n ) : null}\n </div>\n </div>\n\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={searchValue}\n onChange={(event) => setSearchValue(event.target.value)}\n placeholder={tWithFallback(\n t,\n 'projectMembers.searchPlaceholder',\n 'Search project members'\n )}\n className=\"pl-9\"\n />\n </div>\n\n {removeError ? (\n <div className=\"rounded-lg border border-destructive/20 bg-destructive/10 p-4 text-sm text-destructive\">\n {removeError}\n </div>\n ) : null}\n\n {isLoadingProject || isLoadingMembers ? (\n <div className=\"space-y-4\">\n {Array.from({ length: 4 }).map((_, index) => (\n <div key={index} className=\"rounded-lg border bg-card p-4\">\n <Skeleton className=\"h-5 w-40\" />\n <Skeleton className=\"mt-2 h-4 w-56\" />\n </div>\n ))}\n </div>\n ) : projectError || membersError ? (\n <div className=\"rounded-lg border border-destructive/20 bg-destructive/10 p-4 text-sm text-destructive\">\n {(membersError instanceof Error && membersError.message) ||\n (projectError instanceof Error && projectError.message) ||\n tWithFallback(t, 'projectMembers.loadError', 'Failed to load project members')}\n </div>\n ) : filteredMembers.length === 0 ? (\n <div className=\"rounded-lg border bg-card p-10 text-center\">\n <Users className=\"mx-auto mb-4 size-12 text-muted-foreground\" />\n <h2 className=\"text-xl font-semibold\">\n {searchValue\n ? tWithFallback(t, 'projectMembers.noResultsTitle', 'No matching members found')\n : tWithFallback(t, 'projectMembers.emptyTitle', 'No project members yet')}\n </h2>\n <p className=\"mt-2 text-muted-foreground\">\n {searchValue\n ? tWithFallback(\n t,\n 'projectMembers.noResultsSubtitle',\n 'Try a different search to find a project member.'\n )\n : tWithFallback(\n t,\n 'projectMembers.emptySubtitle',\n 'Add workspace members to this project so they can collaborate here.'\n )}\n </p>\n {hasMoreProjectMembers ? (\n <div className=\"mt-4\">\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextProjectMembersPage()}\n disabled={isFetchingNextProjectMembersPage}\n >\n {isFetchingNextProjectMembersPage ? 'Loading more…' : 'Load More Members'}\n </Button>\n </div>\n ) : null}\n {!searchValue && canAddProjectMember ? (\n <div className=\"mt-4\">\n <Button onClick={() => setIsAddDialogOpen(true)}>\n <UserPlus className=\"mr-2 size-4\" />\n {tWithFallback(t, 'projectMembers.addFirstMember', 'Add First Member')}\n </Button>\n </div>\n ) : null}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredMembers.map((member) => {\n const profile = profileMap[member.userId];\n const fallback = getMemberFallback(profileMap, member);\n const isRemoving = isRemovingProjectMember && removingUserId === member.userId;\n\n return (\n <div\n key={member.id}\n className=\"flex items-center justify-between gap-4 rounded-lg border bg-card 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-muted-foreground\">{fallback.email}</p>\n ) : (\n <p className=\"truncate text-xs text-muted-foreground\">{member.userId}</p>\n )}\n </div>\n )}\n\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\" className=\"text-xs\">\n {tWithFallback(t, 'projectMembers.memberBadge', 'Project member')}\n </Badge>\n <span className=\"text-xs text-muted-foreground\">\n {tWithFallback(t, 'projectMembers.addedLabel', 'Added')}{' '}\n {formatRelativeTime(member.createdAt)}\n </span>\n </div>\n </div>\n\n {canRemoveProjectMember ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void handleRemoveMember(member)}\n disabled={isRemovingProjectMember}\n >\n <UserMinus className=\"mr-2 size-4\" />\n {isRemoving\n ? tWithFallback(t, 'projectMembers.removing', 'Removing...')\n : tWithFallback(t, 'projectMembers.removeAction', 'Remove')}\n </Button>\n ) : null}\n </div>\n );\n })}\n\n {hasMoreProjectMembers ? (\n <div className=\"flex flex-col items-center gap-2 rounded-lg border border-dashed bg-muted/20 p-4 text-center\">\n <p className=\"text-sm text-muted-foreground\">\n Showing {projectMembers.length} of {totalProjectMembers} project members.\n </p>\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextProjectMembersPage()}\n disabled={isFetchingNextProjectMembersPage}\n >\n {isFetchingNextProjectMembersPage ? 'Loading more…' : 'Load More Members'}\n </Button>\n </div>\n ) : null}\n </div>\n )}\n\n {workspaceId && projectId && isAddDialogOpen ? (\n <ProjectMemberPickerDialog\n open={isAddDialogOpen}\n onOpenChange={setIsAddDialogOpen}\n workspaceId={workspaceId}\n projectId={projectId}\n projectName={project?.name}\n />\n ) : null}\n </div>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectMembersPage.js","names":[],"sources":["../../src/pages/ProjectMembersPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { ArrowLeft, Search, UserMinus, UserPlus, Users } from 'lucide-react';\nimport {\n ActorIdentity,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport { Badge, Button, Input, PageContainer, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ProjectMemberPickerDialog } from '../components/ProjectMemberPickerDialog';\nimport { useInfiniteProjectMembers } from '../hooks/useProjectMembers';\nimport { useProject } from '../hooks/useProjects';\nimport { useRemoveProjectMember } from '../hooks/useProjectMemberMutations';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { tWithFallback } from '../utils/i18n';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport type { ProjectMember } from '../types';\n\nconst PROJECT_MEMBER_PAGE_SIZE = 100;\n\nfunction getMemberFallback(profileMap: ActorProfileMap, member: ProjectMember) {\n const profile = profileMap[member.userId];\n if (profile) {\n return {\n name: profile.displayName,\n email: profile.email ?? undefined,\n };\n }\n\n return {\n name: member.userId,\n email: undefined,\n };\n}\n\nexport function ProjectMembersPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId: string }>();\n const {\n basePath = '/workspaces',\n apiGatewayUrl,\n authToken,\n organizationId,\n } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const [searchValue, setSearchValue] = useState('');\n const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);\n const [removingUserId, setRemovingUserId] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<string | null>(null);\n\n useWorkspacesPageView('workspaces.project-members', {\n workspaceId,\n projectId,\n requireParams: true,\n });\n\n const {\n data: project,\n isLoading: isLoadingProject,\n error: projectError,\n } = useProject(projectId || '', workspaceId);\n const {\n data: projectMembersPages,\n isLoading: isLoadingMembers,\n error: membersError,\n hasNextPage: hasMoreProjectMembers,\n fetchNextPage: fetchNextProjectMembersPage,\n isFetchingNextPage: isFetchingNextProjectMembersPage,\n } = useInfiniteProjectMembers(projectId || '', workspaceId, PROJECT_MEMBER_PAGE_SIZE);\n const { mutate: removeProjectMember, isPending: isRemovingProjectMember } =\n useRemoveProjectMember(workspaceId);\n\n const {\n canViewProject,\n canListProjectMembers,\n canAddProjectMember,\n canRemoveProjectMember,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n\n const projectMembers = useMemo(\n () => projectMembersPages?.pages.flatMap((page) => page.items) ?? [],\n [projectMembersPages?.pages]\n );\n const totalProjectMembers = projectMembersPages?.pages[0]?.total ?? projectMembers.length;\n const actors = useMemo<ActorInput[]>(\n () => projectMembers.map((member) => ({ actorId: member.userId, actorType: 'user' })),\n [projectMembers]\n );\n const profileMap: ActorProfileMap = useActorProfiles(actors, {\n apiGatewayUrl,\n authToken,\n orgId: organizationId,\n workspaceId,\n });\n\n const filteredMembers = useMemo(() => {\n const needle = searchValue.trim().toLowerCase();\n if (!needle) {\n return projectMembers;\n }\n\n return projectMembers.filter((member) => {\n const fallback = getMemberFallback(profileMap, member);\n return (\n fallback.name.toLowerCase().includes(needle) ||\n fallback.email?.toLowerCase().includes(needle) ||\n member.userId.toLowerCase().includes(needle)\n );\n });\n }, [profileMap, projectMembers, searchValue]);\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-10 w-40\" />\n <Skeleton className=\"h-10 w-64\" />\n <Skeleton className=\"h-40 w-full\" />\n </div>\n );\n }\n\n if (!canViewProject || !canListProjectMembers) {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"space-y-2 text-center\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'projectMembers.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'projectMembers.noPermission',\n \"You don't have permission to manage project members.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n const handleBack = () => {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects/${projectId}`));\n };\n\n const handleRemoveMember = async (member: ProjectMember) => {\n const fallback = getMemberFallback(profileMap, member);\n void nativeImpact('medium');\n setRemoveError(null);\n const message = `${tWithFallback(t, 'projectMembers.removeConfirmPrefix', 'Remove')} ${fallback.name} ${tWithFallback(t, 'projectMembers.removeConfirmSuffix', 'from this project?')}`;\n const native = await nativeConfirm({\n title: tWithFallback(t, 'projectMembers.removeConfirmTitle', 'Remove project member'),\n message,\n okButtonTitle: tWithFallback(t, 'projectMembers.removeAction', 'Remove'),\n cancelButtonTitle: tWithFallback(t, 'projectMembers.cancel', 'Cancel'),\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) {\n return;\n }\n\n setRemovingUserId(member.userId);\n removeProjectMember(\n { projectId: member.projectId, userId: member.userId },\n {\n onSuccess: () => {\n void nativeNotify('success');\n setRemovingUserId(null);\n },\n onError: () => {\n void nativeNotify('error');\n setRemoveError(\n tWithFallback(\n t,\n 'projectMembers.removeError',\n 'Failed to remove project member. Please try again.'\n )\n );\n setRemovingUserId(null);\n },\n }\n );\n };\n\n return (\n <PageContainer padded={false}>\n <div className=\"space-y-4 p-3 sm:space-y-6 sm:p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <Button variant=\"ghost\" onClick={handleBack} className=\"mb-2 -ml-2 gap-2\">\n <ArrowLeft size={20} />\n {tWithFallback(t, 'projectMembers.backToProject', 'Back to Project')}\n </Button>\n <h1 className=\"text-2xl font-bold sm:text-3xl\">\n {tWithFallback(t, 'projectMembers.pageTitle', 'Project Members')}\n </h1>\n <p className=\"text-sm text-text-secondary sm:text-base\">\n {project?.name\n ? `${project.name} · ${tWithFallback(\n t,\n 'projectMembers.pageSubtitle',\n 'Only existing workspace members can be added to this project.'\n )}`\n : tWithFallback(\n t,\n 'projectMembers.pageSubtitle',\n 'Only existing workspace members can be added to this project.'\n )}\n </p>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\">{totalProjectMembers}</Badge>\n {canAddProjectMember ? (\n <Button\n onClick={() => setIsAddDialogOpen(true)}\n disabled={!workspaceId || !projectId}\n >\n <UserPlus className=\"mr-2 size-4\" />\n {tWithFallback(t, 'projectMembers.addMember', 'Add Member')}\n </Button>\n ) : null}\n </div>\n </div>\n\n <div className=\"relative\">\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={tWithFallback(\n t,\n 'projectMembers.searchPlaceholder',\n 'Search project members'\n )}\n className=\"pl-9\"\n />\n </div>\n\n {removeError ? (\n <div className=\"rounded-lg border border-status-error-border/20 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n {removeError}\n </div>\n ) : null}\n\n {isLoadingProject || isLoadingMembers ? (\n <div className=\"space-y-4\">\n {Array.from({ length: 4 }).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 </div>\n ) : projectError || membersError ? (\n <div className=\"rounded-lg border border-status-error-border/20 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n {(membersError instanceof Error && membersError.message) ||\n (projectError instanceof Error && projectError.message) ||\n tWithFallback(t, 'projectMembers.loadError', 'Failed to load project members')}\n </div>\n ) : filteredMembers.length === 0 ? (\n <div className=\"rounded-lg border bg-bg-surface p-10 text-center\">\n <Users className=\"mx-auto mb-4 size-12 text-text-secondary\" />\n <h2 className=\"text-xl font-semibold\">\n {searchValue\n ? tWithFallback(t, 'projectMembers.noResultsTitle', 'No matching members found')\n : tWithFallback(t, 'projectMembers.emptyTitle', 'No project members yet')}\n </h2>\n <p className=\"mt-2 text-text-secondary\">\n {searchValue\n ? tWithFallback(\n t,\n 'projectMembers.noResultsSubtitle',\n 'Try a different search to find a project member.'\n )\n : tWithFallback(\n t,\n 'projectMembers.emptySubtitle',\n 'Add workspace members to this project so they can collaborate here.'\n )}\n </p>\n {hasMoreProjectMembers ? (\n <div className=\"mt-4\">\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextProjectMembersPage()}\n disabled={isFetchingNextProjectMembersPage}\n >\n {isFetchingNextProjectMembersPage ? 'Loading more…' : 'Load More Members'}\n </Button>\n </div>\n ) : null}\n {!searchValue && canAddProjectMember ? (\n <div className=\"mt-4\">\n <Button onClick={() => setIsAddDialogOpen(true)}>\n <UserPlus className=\"mr-2 size-4\" />\n {tWithFallback(t, 'projectMembers.addFirstMember', 'Add First Member')}\n </Button>\n </div>\n ) : null}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredMembers.map((member) => {\n const profile = profileMap[member.userId];\n const fallback = getMemberFallback(profileMap, member);\n const isRemoving = isRemovingProjectMember && removingUserId === 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\">{fallback.email}</p>\n ) : (\n <p className=\"truncate text-xs text-text-secondary\">{member.userId}</p>\n )}\n </div>\n )}\n\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\" className=\"text-xs\">\n {tWithFallback(t, 'projectMembers.memberBadge', 'Project member')}\n </Badge>\n <span className=\"text-xs text-text-secondary\">\n {tWithFallback(t, 'projectMembers.addedLabel', 'Added')}{' '}\n {formatRelativeTime(member.createdAt)}\n </span>\n </div>\n </div>\n\n {canRemoveProjectMember ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void handleRemoveMember(member)}\n disabled={isRemovingProjectMember}\n >\n <UserMinus className=\"mr-2 size-4\" />\n {isRemoving\n ? tWithFallback(t, 'projectMembers.removing', 'Removing...')\n : tWithFallback(t, 'projectMembers.removeAction', 'Remove')}\n </Button>\n ) : null}\n </div>\n );\n })}\n\n {hasMoreProjectMembers ? (\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 {projectMembers.length} of {totalProjectMembers} project members.\n </p>\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextProjectMembersPage()}\n disabled={isFetchingNextProjectMembersPage}\n >\n {isFetchingNextProjectMembersPage ? 'Loading more…' : 'Load More Members'}\n </Button>\n </div>\n ) : null}\n </div>\n )}\n\n {workspaceId && projectId && isAddDialogOpen ? (\n <ProjectMemberPickerDialog\n open={isAddDialogOpen}\n onOpenChange={setIsAddDialogOpen}\n workspaceId={workspaceId}\n projectId={projectId}\n projectName={project?.name}\n />\n ) : null}\n </div>\n </PageContainer>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAwBA,IAAM,KAA2B;AAEjC,SAAS,EAAkB,GAA6B,GAAuB;CAC7E,IAAM,IAAU,EAAW,EAAO;AAQlC,QAPI,IACK;EACL,MAAM,EAAQ;EACd,OAAO,EAAQ,SAAS,KAAA;EACzB,GAGI;EACL,MAAM,EAAO;EACb,OAAO,KAAA;EACR;;AAGH,SAAgB,IAAqB;CACnC,IAAM,EAAE,SAAM,GAAS,EACjB,KAAW,GAAa,EACxB,EAAE,gBAAa,iBAAc,GAAuD,EACpF,EACJ,eAAW,eACX,mBACA,cACA,sBACE,GAAsB,EACpB,EAAE,yBAAsB,GAAqB,EAC7C,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAiB,KAAsB,EAAS,GAAM,EACvD,CAAC,GAAgB,KAAqB,EAAwB,KAAK,EACnE,CAAC,GAAa,KAAkB,EAAwB,KAAK;AAEnE,GAAsB,8BAA8B;EAClD;EACA;EACA,eAAe;EAChB,CAAC;CAEF,IAAM,EACJ,MAAM,GACN,WAAW,GACX,OAAO,MACL,EAAW,KAAa,IAAI,EAAY,EACtC,EACJ,MAAM,GACN,WAAW,IACX,OAAO,GACP,aAAa,GACb,eAAe,GACf,oBAAoB,MAClB,GAA0B,KAAa,IAAI,GAAa,GAAyB,EAC/E,EAAE,QAAQ,IAAqB,WAAW,MAC9C,GAAuB,EAAY,EAE/B,EACJ,oBACA,2BACA,wBACA,4BACA,WAAW,MACT,GAAyB,EAEvB,IAAiB,QACf,GAAqB,MAAM,SAAS,MAAS,EAAK,MAAM,IAAI,EAAE,EACpE,CAAC,GAAqB,MAAM,CAC7B,EACK,IAAsB,GAAqB,MAAM,IAAI,SAAS,EAAe,QAK7E,IAA8B,EAJrB,QACP,EAAe,KAAK,OAAY;EAAE,SAAS,EAAO;EAAQ,WAAW;EAAQ,EAAE,EACrF,CAAC,EAAe,CACjB,EAC4D;EAC3D;EACA;EACA,OAAO;EACP;EACD,CAAC,EAEI,IAAkB,QAAc;EACpC,IAAM,IAAS,EAAY,MAAM,CAAC,aAAa;AAK/C,SAJK,IAIE,EAAe,QAAQ,MAAW;GACvC,IAAM,IAAW,EAAkB,GAAY,EAAO;AACtD,UACE,EAAS,KAAK,aAAa,CAAC,SAAS,EAAO,IAC5C,EAAS,OAAO,aAAa,CAAC,SAAS,EAAO,IAC9C,EAAO,OAAO,aAAa,CAAC,SAAS,EAAO;IAE9C,GAVO;IAWR;EAAC;EAAY;EAAgB;EAAY,CAAC;AAE7C,KAAI,EACF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;GAClC,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;GAClC,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA;GAChC;;AAIV,KAAI,CAAC,MAAkB,CAAC,GACtB,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAc,GAAG,+BAA+B,gBAAgB;IAC9D,CAAA,EACL,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,GACA,+BACA,uDACD;IACC,CAAA,CACA;;EACF,CAAA;CAIV,IAAM,WAAmB;AACvB,KAAS,EAAkB,GAAG,GAAS,GAAG,EAAY,YAAY,IAAY,CAAC;IAG3E,KAAqB,OAAO,MAA0B;EAC1D,IAAM,IAAW,EAAkB,GAAY,EAAO;AAEtD,EADK,GAAa,SAAS,EAC3B,EAAe,KAAK;EACpB,IAAM,IAAU,GAAG,EAAc,GAAG,sCAAsC,SAAS,CAAC,GAAG,EAAS,KAAK,GAAG,EAAc,GAAG,sCAAsC,qBAAqB,IAC9K,IAAS,MAAM,EAAc;GACjC,OAAO,EAAc,GAAG,qCAAqC,wBAAwB;GACrF;GACA,eAAe,EAAc,GAAG,+BAA+B,SAAS;GACxE,mBAAmB,EAAc,GAAG,yBAAyB,SAAS;GACvE,CAAC;AACgB,SAAW,OAAO,OAAO,QAAQ,EAAQ,GAAG,OAK9D,EAAkB,EAAO,OAAO,EAChC,GACE;GAAE,WAAW,EAAO;GAAW,QAAQ,EAAO;GAAQ,EACtD;GACE,iBAAiB;AAEf,IADK,EAAa,UAAU,EAC5B,EAAkB,KAAK;;GAEzB,eAAe;AASb,IARK,EAAa,QAAQ,EAC1B,EACE,EACE,GACA,8BACA,qDACD,CACF,EACD,EAAkB,KAAK;;GAE1B,CACF;;AAGH,QACE,kBAAC,GAAD;EAAe,QAAQ;YACrB,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAA,UAAA;MACE,kBAAC,GAAD;OAAQ,SAAQ;OAAQ,SAAS;OAAY,WAAU;iBAAvD,CACE,kBAAC,IAAD,EAAW,MAAM,IAAM,CAAA,EACtB,EAAc,GAAG,gCAAgC,kBAAkB,CAC7D;;MACT,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAc,GAAG,4BAA4B,kBAAkB;OAC7D,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,GAAS,OACN,GAAG,EAAQ,KAAK,KAAK,EACnB,GACA,+BACA,gEACD,KACD,EACE,GACA,+BACA,gEACD;OACH,CAAA;MACA,EAAA,CAAA,EAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAO,SAAQ;iBAAa;OAA4B,CAAA,EACvD,IACC,kBAAC,GAAD;OACE,eAAe,EAAmB,GAAK;OACvC,UAAU,CAAC,KAAe,CAAC;iBAF7B,CAIE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EACnC,EAAc,GAAG,4BAA4B,aAAa,CACpD;WACP,KACA;QACF;;IAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,IAAD,EAAQ,WAAU,uEAAwE,CAAA,EAC1F,kBAAC,GAAD;MACE,OAAO;MACP,WAAW,MAAU,EAAe,EAAM,OAAO,MAAM;MACvD,aAAa,EACX,GACA,oCACA,yBACD;MACD,WAAU;MACV,CAAA,CACE;;IAEL,IACC,kBAAC,OAAD;KAAK,WAAU;eACZ;KACG,CAAA,GACJ;IAEH,KAAoB,KACnB,kBAAC,OAAD;KAAK,WAAU;eACZ,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,MACjC,kBAAC,OAAD;MAAiB,WAAU;gBAA3B,CACE,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA,EACjC,kBAAC,GAAD,EAAU,WAAU,iBAAkB,CAAA,CAClC;QAHI,EAGJ,CACN;KACE,CAAA,GACJ,KAAgB,IAClB,kBAAC,OAAD;KAAK,WAAU;eACX,aAAwB,SAAS,EAAa,WAC7C,aAAwB,SAAS,EAAa,WAC/C,EAAc,GAAG,4BAA4B,iCAAiC;KAC5E,CAAA,GACJ,EAAgB,WAAW,IAC7B,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,IAAD,EAAO,WAAU,4CAA6C,CAAA;MAC9D,kBAAC,MAAD;OAAI,WAAU;iBACX,IACG,EAAc,GAAG,iCAAiC,4BAA4B,GAC9E,EAAc,GAAG,6BAA6B,yBAAyB;OACxE,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,IACG,EACE,GACA,oCACA,mDACD,GACD,EACE,GACA,gCACA,sEACD;OACH,CAAA;MACH,IACC,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QACE,SAAQ;QACR,eAAe,KAAK,GAA6B;QACjD,UAAU;kBAET,IAAmC,kBAAkB;QAC/C,CAAA;OACL,CAAA,GACJ;MACH,CAAC,KAAe,IACf,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QAAQ,eAAe,EAAmB,GAAK;kBAA/C,CACE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EACnC,EAAc,GAAG,iCAAiC,mBAAmB,CAC/D;;OACL,CAAA,GACJ;MACA;SAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAgB,KAAK,MAAW;MAC/B,IAAM,IAAU,EAAW,EAAO,SAC5B,IAAW,EAAkB,GAAY,EAAO,EAChD,IAAa,KAA2B,MAAmB,EAAO;AAExE,aACE,kBAAC,OAAD;OAEE,WAAU;iBAFZ,CAIE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,IACC,kBAAC,GAAD;SAAwB;SAAS,SAAQ;SAAS,CAAA,GAElD,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAAwB,EAAS;UAAS,CAAA,EACtD,EAAS,QACR,kBAAC,KAAD;UAAG,WAAU;oBAAwC,EAAS;UAAU,CAAA,GAExE,kBAAC,KAAD;UAAG,WAAU;oBAAwC,EAAO;UAAW,CAAA,CAErE;YAGR,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;UAAY,WAAU;oBAClC,EAAc,GAAG,8BAA8B,iBAAiB;UAC3D,CAAA,EACR,kBAAC,QAAD;UAAM,WAAU;oBAAhB;WACG,EAAc,GAAG,6BAA6B,QAAQ;WAAE;WACxD,EAAmB,EAAO,UAAU;WAChC;YACH;WACF;WAEL,KACC,kBAAC,GAAD;QACE,SAAQ;QACR,MAAK;QACL,eAAe,KAAK,GAAmB,EAAO;QAC9C,UAAU;kBAJZ,CAME,kBAAC,IAAD,EAAW,WAAU,eAAgB,CAAA,EACpC,IACG,EAAc,GAAG,2BAA2B,cAAc,GAC1D,EAAc,GAAG,+BAA+B,SAAS,CACtD;YACP,KACA;SAzCC,EAAO,GAyCR;OAER,EAED,IACC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAb;QAA2C;QAChC,EAAe;QAAO;QAAK;QAAoB;QACtD;UACJ,kBAAC,GAAD;OACE,SAAQ;OACR,eAAe,KAAK,GAA6B;OACjD,UAAU;iBAET,IAAmC,kBAAkB;OAC/C,CAAA,CACL;UACJ,KACA;;IAGP,KAAe,KAAa,IAC3B,kBAAC,IAAD;KACE,MAAM;KACN,cAAc;KACD;KACF;KACX,aAAa,GAAS;KACtB,CAAA,GACA;IACA;;EACQ,CAAA"}
|