@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
|
@@ -86,10 +86,10 @@ function N() {
|
|
|
86
86
|
render: ({ field: e }) => /* @__PURE__ */ l(b, { children: [
|
|
87
87
|
/* @__PURE__ */ c(x, { children: "Tenant *" }),
|
|
88
88
|
L ? /* @__PURE__ */ c(g, { className: "h-10 w-full" }) : R ? /* @__PURE__ */ l("div", {
|
|
89
|
-
className: "flex items-center gap-2 text-
|
|
89
|
+
className: "flex items-center gap-2 text-status-error-text text-sm",
|
|
90
90
|
children: [/* @__PURE__ */ c(s, { size: 16 }), "Failed to load tenants"]
|
|
91
91
|
}) : H.length === 0 ? /* @__PURE__ */ c("div", {
|
|
92
|
-
className: "text-
|
|
92
|
+
className: "text-text-secondary text-sm",
|
|
93
93
|
children: "No tenants available. Contact your administrator."
|
|
94
94
|
}) : /* @__PURE__ */ l(T, {
|
|
95
95
|
onValueChange: e.onChange,
|
|
@@ -109,10 +109,10 @@ function N() {
|
|
|
109
109
|
render: ({ field: e }) => /* @__PURE__ */ l(b, { children: [
|
|
110
110
|
/* @__PURE__ */ c(x, { children: "Organization *" }),
|
|
111
111
|
B ? /* @__PURE__ */ c(g, { className: "h-10 w-full" }) : V ? /* @__PURE__ */ l("div", {
|
|
112
|
-
className: "flex items-center gap-2 text-
|
|
112
|
+
className: "flex items-center gap-2 text-status-error-text text-sm",
|
|
113
113
|
children: [/* @__PURE__ */ c(s, { size: 16 }), "Failed to load organizations"]
|
|
114
114
|
}) : K ? J.length === 0 ? /* @__PURE__ */ c("div", {
|
|
115
|
-
className: "text-
|
|
115
|
+
className: "text-text-secondary text-sm",
|
|
116
116
|
children: "No organizations found for this tenant"
|
|
117
117
|
}) : /* @__PURE__ */ l(T, {
|
|
118
118
|
onValueChange: e.onChange,
|
|
@@ -123,7 +123,7 @@ function N() {
|
|
|
123
123
|
children: e.name
|
|
124
124
|
}, e.id)) })]
|
|
125
125
|
}) : /* @__PURE__ */ c("div", {
|
|
126
|
-
className: "text-
|
|
126
|
+
className: "text-text-secondary text-sm",
|
|
127
127
|
children: "Please select a tenant first"
|
|
128
128
|
}),
|
|
129
129
|
/* @__PURE__ */ c(v, { children: "Select which organization owns this workspace" }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateWorkspaceDialog.js","names":[],"sources":["../../src/components/CreateWorkspaceDialog.tsx"],"sourcesContent":["import { useEffect, useMemo } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n Button,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport {\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n FormDescription,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n useForm,\n FormProvider,\n z,\n} from '@burdenoff/fe-libs/form';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useCreateWorkspace } from '../hooks/useWorkspaceMutations';\nimport { useMyTenants, useMyOrganizations } from '../hooks/useTenants';\nimport { generateSlug } from '../utils/slug';\n\nconst createWorkspaceSchema = z.object({\n name: z.string().min(2, 'Name must be at least 2 characters').max(100),\n slug: z\n .string()\n .min(2, 'Slug must be at least 2 characters')\n .max(50)\n .regex(/^[a-z0-9-]+$/, 'Slug can only contain lowercase letters, numbers, and hyphens'),\n tenantId: z.string().min(1, 'Please select a tenant'),\n organizationId: z.string().min(1, 'Please select an organization'),\n});\n\ntype CreateWorkspaceFormData = z.infer<typeof createWorkspaceSchema>;\n\nexport function CreateWorkspaceDialog() {\n const { isCreateWorkspaceDialogOpen, closeCreateWorkspaceDialog } = useWorkspacesStore();\n const { mutate: createWorkspace, isPending } = useCreateWorkspace();\n const { data: tenantsData, isLoading: isLoadingTenants, error: tenantsError } = useMyTenants();\n const {\n data: organizationsData,\n isLoading: isLoadingOrganizations,\n error: organizationsError,\n } = useMyOrganizations();\n\n const tenants = useMemo(() => tenantsData?.items ?? [], [tenantsData]);\n const allOrganizations = useMemo(() => organizationsData?.items ?? [], [organizationsData]);\n\n // Prefer the shared tenant (or the only one) — with a single shared tenant\n // there is nothing to choose, so we auto-select it and hide the picker.\n const defaultTenantId = useMemo(() => {\n const sharedTenant = tenants.find(\n (tenant) => tenant.type === 'SHARED' || tenant.slug === 'burdenoff-shared'\n );\n return sharedTenant?.id ?? tenants[0]?.id ?? '';\n }, [tenants]);\n\n const form = useForm<CreateWorkspaceFormData>({\n schema: createWorkspaceSchema,\n defaultValues: {\n name: '',\n slug: '',\n tenantId: '',\n organizationId: '',\n },\n });\n\n const selectedTenantId = form.watch('tenantId');\n const workspaceName = form.watch('name');\n\n // Auto-select the default tenant when the user has no real choice.\n useEffect(() => {\n if (!form.getValues('tenantId') && defaultTenantId) {\n form.setValue('tenantId', defaultTenantId, { shouldValidate: true });\n }\n }, [defaultTenantId, form]);\n\n // Filter organizations by selected tenant\n const filteredOrganizations = useMemo(() => {\n if (!selectedTenantId) return [];\n return allOrganizations.filter((org) => org.tenantId === selectedTenantId);\n }, [selectedTenantId, allOrganizations]);\n\n // Auto-generate slug from name (clear when name is empty)\n useEffect(() => {\n form.setValue('slug', workspaceName ? generateSlug(workspaceName) : '');\n }, [workspaceName, form]);\n\n // Reset organizationId when tenant changes\n useEffect(() => {\n form.setValue('organizationId', '');\n }, [selectedTenantId, form]);\n\n const onSubmit = (data: CreateWorkspaceFormData) => {\n createWorkspace(\n {\n name: data.name,\n slug: data.slug,\n tenantId: data.tenantId,\n organizationId: data.organizationId,\n },\n {\n onSuccess: () => {\n closeCreateWorkspaceDialog();\n form.reset();\n },\n }\n );\n };\n\n return (\n <ResponsiveDialog open={isCreateWorkspaceDialogOpen} onOpenChange={closeCreateWorkspaceDialog}>\n <ResponsiveDialogContent className=\"sm:max-w-[500px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Create Workspace</ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n {/* Workspace Name */}\n <FormField\n control={form.control}\n name=\"name\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Workspace Name *</FormLabel>\n <FormControl>\n <Input placeholder=\"My Workspace\" {...field} />\n </FormControl>\n <FormDescription>A descriptive name for your workspace</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n {/* Slug */}\n <FormField\n control={form.control}\n name=\"slug\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Slug *</FormLabel>\n <FormControl>\n <Input placeholder=\"my-workspace\" {...field} />\n </FormControl>\n <FormDescription>URL-friendly identifier (auto-generated)</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n {/* Tenant Selection — shown only when the user has more than one\n tenant to choose between. With a single shared tenant it is\n auto-selected and hidden (loading/error/empty still surface). */}\n {tenants.length !== 1 ? (\n <FormField\n control={form.control}\n name=\"tenantId\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Tenant *</FormLabel>\n {isLoadingTenants ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : tenantsError ? (\n <div className=\"flex items-center gap-2 text-destructive text-sm\">\n <AlertCircle size={16} />\n Failed to load tenants\n </div>\n ) : tenants.length === 0 ? (\n <div className=\"text-muted-foreground text-sm\">\n No tenants available. Contact your administrator.\n </div>\n ) : (\n <Select onValueChange={field.onChange} value={field.value}>\n <FormControl>\n <SelectTrigger>\n <SelectValue placeholder=\"Select a tenant\" />\n </SelectTrigger>\n </FormControl>\n <SelectContent>\n {tenants.map((tenant) => (\n <SelectItem key={tenant.id} value={tenant.id}>\n {tenant.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n <FormDescription>Select which tenant this workspace belongs to</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n ) : null}\n\n {/* Organization Selection */}\n <FormField\n control={form.control}\n name=\"organizationId\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Organization *</FormLabel>\n {isLoadingOrganizations ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : organizationsError ? (\n <div className=\"flex items-center gap-2 text-destructive text-sm\">\n <AlertCircle size={16} />\n Failed to load organizations\n </div>\n ) : !selectedTenantId ? (\n <div className=\"text-muted-foreground text-sm\">\n Please select a tenant first\n </div>\n ) : filteredOrganizations.length === 0 ? (\n <div className=\"text-muted-foreground text-sm\">\n No organizations found for this tenant\n </div>\n ) : (\n <Select\n onValueChange={field.onChange}\n value={field.value}\n disabled={!selectedTenantId}\n >\n <FormControl>\n <SelectTrigger>\n <SelectValue placeholder=\"Select an organization\" />\n </SelectTrigger>\n </FormControl>\n <SelectContent>\n {filteredOrganizations.map((org) => (\n <SelectItem key={org.id} value={org.id}>\n {org.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n <FormDescription>Select which organization owns this workspace</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={closeCreateWorkspaceDialog}\n disabled={isPending}\n >\n Cancel\n </Button>\n <Button\n type=\"submit\"\n disabled={isPending || isLoadingTenants || isLoadingOrganizations}\n >\n {isPending ? 'Creating...' : 'Create Workspace'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </FormProvider>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;;;AAiCA,IAAM,IAAwB,EAAE,OAAO;CACrC,MAAM,EAAE,QAAQ,CAAC,IAAI,GAAG,qCAAqC,CAAC,IAAI,IAAI;CACtE,MAAM,EACH,QAAQ,CACR,IAAI,GAAG,qCAAqC,CAC5C,IAAI,GAAG,CACP,MAAM,gBAAgB,gEAAgE;CACzF,UAAU,EAAE,QAAQ,CAAC,IAAI,GAAG,yBAAyB;CACrD,gBAAgB,EAAE,QAAQ,CAAC,IAAI,GAAG,gCAAgC;CACnE,CAAC;AAIF,SAAgB,IAAwB;CACtC,IAAM,EAAE,gCAA6B,kCAA+B,GAAoB,EAClF,EAAE,QAAQ,GAAiB,iBAAc,GAAoB,EAC7D,EAAE,MAAM,GAAa,WAAW,GAAkB,OAAO,MAAiB,GAAc,EACxF,EACJ,MAAM,GACN,WAAW,GACX,OAAO,MACL,GAAoB,EAElB,IAAU,QAAc,GAAa,SAAS,EAAE,EAAE,CAAC,EAAY,CAAC,EAChE,IAAmB,QAAc,GAAmB,SAAS,EAAE,EAAE,CAAC,EAAkB,CAAC,EAIrF,IAAkB,QACD,EAAQ,MAC1B,MAAW,EAAO,SAAS,YAAY,EAAO,SAAS,mBACzD,EACoB,MAAM,EAAQ,IAAI,MAAM,IAC5C,CAAC,EAAQ,CAAC,EAEP,IAAO,EAAiC;EAC5C,QAAQ;EACR,eAAe;GACb,MAAM;GACN,MAAM;GACN,UAAU;GACV,gBAAgB;GACjB;EACF,CAAC,EAEI,IAAmB,EAAK,MAAM,WAAW,EACzC,IAAgB,EAAK,MAAM,OAAO;AAGxC,SAAgB;AACd,EAAI,CAAC,EAAK,UAAU,WAAW,IAAI,KACjC,EAAK,SAAS,YAAY,GAAiB,EAAE,gBAAgB,IAAM,CAAC;IAErE,CAAC,GAAiB,EAAK,CAAC;CAG3B,IAAM,IAAwB,QACvB,IACE,EAAiB,QAAQ,MAAQ,EAAI,aAAa,EAAiB,GAD5C,EAAE,EAE/B,CAAC,GAAkB,EAAiB,CAAC;AAQxC,CALA,QAAgB;AACd,IAAK,SAAS,QAAQ,IAAgB,EAAa,EAAc,GAAG,GAAG;IACtE,CAAC,GAAe,EAAK,CAAC,EAGzB,QAAgB;AACd,IAAK,SAAS,kBAAkB,GAAG;IAClC,CAAC,GAAkB,EAAK,CAAC;CAE5B,IAAM,KAAY,MAAkC;AAClD,IACE;GACE,MAAM,EAAK;GACX,MAAM,EAAK;GACX,UAAU,EAAK;GACf,gBAAgB,EAAK;GACtB,EACD,EACE,iBAAiB;AAEf,GADA,GAA4B,EAC5B,EAAK,OAAO;KAEf,CACF;;AAGH,QACE,kBAAC,GAAD;EAAkB,MAAM;EAA6B,cAAc;YACjE,kBAAC,GAAD;GAAyB,WAAU;aAAnC,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAuB,oBAAwC,CAAA,EACxC,CAAA,EAEzB,kBAAC,GAAD;IAAc,GAAI;cAChB,kBAAC,QAAD;KAAM,UAAU,EAAK,aAAa,EAAS;KAAE,WAAU;eAAvD;MAEE,kBAAC,GAAD;OACE,SAAS,EAAK;OACd,MAAK;OACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;QACE,kBAAC,GAAD,EAAA,UAAW,oBAA4B,CAAA;QACvC,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;SAAO,aAAY;SAAe,GAAI;SAAS,CAAA,EACnC,CAAA;QACd,kBAAC,GAAD,EAAA,UAAiB,yCAAuD,CAAA;QACxE,kBAAC,GAAD,EAAe,CAAA;QACN,EAAA,CAAA;OAEb,CAAA;MAGF,kBAAC,GAAD;OACE,SAAS,EAAK;OACd,MAAK;OACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;QACE,kBAAC,GAAD,EAAA,UAAW,UAAkB,CAAA;QAC7B,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;SAAO,aAAY;SAAe,GAAI;SAAS,CAAA,EACnC,CAAA;QACd,kBAAC,GAAD,EAAA,UAAiB,4CAA0D,CAAA;QAC3E,kBAAC,GAAD,EAAe,CAAA;QACN,EAAA,CAAA;OAEb,CAAA;MAKD,EAAQ,WAAW,IAuChB,OAtCF,kBAAC,GAAD;OACE,SAAS,EAAK;OACd,MAAK;OACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;QACE,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA;QAC9B,IACC,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,GAClC,IACF,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA,EAAA,yBAErB;aACJ,EAAQ,WAAW,IACrB,kBAAC,OAAD;SAAK,WAAU;mBAAgC;SAEzC,CAAA,GAEN,kBAAC,GAAD;SAAQ,eAAe,EAAM;SAAU,OAAO,EAAM;mBAApD,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,aAAY,mBAAoB,CAAA,EAC/B,CAAA,EACJ,CAAA,EACd,kBAAC,GAAD,EAAA,UACG,EAAQ,KAAK,MACZ,kBAAC,GAAD;UAA4B,OAAO,EAAO;oBACvC,EAAO;UACG,EAFI,EAAO,GAEX,CACb,EACY,CAAA,CACT;;QAEX,kBAAC,GAAD,EAAA,UAAiB,iDAA+D,CAAA;QAChF,kBAAC,GAAD,EAAe,CAAA;QACN,EAAA,CAAA;OAEb,CAAA;MAIJ,kBAAC,GAAD;OACE,SAAS,EAAK;OACd,MAAK;OACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;QACE,kBAAC,GAAD,EAAA,UAAW,kBAA0B,CAAA;QACpC,IACC,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,GAClC,IACF,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA,EAAA,+BAErB;aACH,IAID,EAAsB,WAAW,IACnC,kBAAC,OAAD;SAAK,WAAU;mBAAgC;SAEzC,CAAA,GAEN,kBAAC,GAAD;SACE,eAAe,EAAM;SACrB,OAAO,EAAM;SACb,UAAU,CAAC;mBAHb,CAKE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,aAAY,0BAA2B,CAAA,EACtC,CAAA,EACJ,CAAA,EACd,kBAAC,GAAD,EAAA,UACG,EAAsB,KAAK,MAC1B,kBAAC,GAAD;UAAyB,OAAO,EAAI;oBACjC,EAAI;UACM,EAFI,EAAI,GAER,CACb,EACY,CAAA,CACT;aAzBT,kBAAC,OAAD;SAAK,WAAU;mBAAgC;SAEzC,CAAA;QAyBR,kBAAC,GAAD,EAAA,UAAiB,iDAA+D,CAAA;QAChF,kBAAC,GAAD,EAAe,CAAA;QACN,EAAA,CAAA;OAEb,CAAA;MAEF,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,SAAS;OACT,UAAU;iBACX;OAEQ,CAAA,EACT,kBAAC,GAAD;OACE,MAAK;OACL,UAAU,KAAa,KAAoB;iBAE1C,IAAY,gBAAgB;OACtB,CAAA,CACc,EAAA,CAAA;MACpB;;IACM,CAAA,CACS;;EACT,CAAA"}
|
|
1
|
+
{"version":3,"file":"CreateWorkspaceDialog.js","names":[],"sources":["../../src/components/CreateWorkspaceDialog.tsx"],"sourcesContent":["import { useEffect, useMemo } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n Button,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport {\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n FormDescription,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n useForm,\n FormProvider,\n z,\n} from '@burdenoff/fe-libs/form';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useCreateWorkspace } from '../hooks/useWorkspaceMutations';\nimport { useMyTenants, useMyOrganizations } from '../hooks/useTenants';\nimport { generateSlug } from '../utils/slug';\n\nconst createWorkspaceSchema = z.object({\n name: z.string().min(2, 'Name must be at least 2 characters').max(100),\n slug: z\n .string()\n .min(2, 'Slug must be at least 2 characters')\n .max(50)\n .regex(/^[a-z0-9-]+$/, 'Slug can only contain lowercase letters, numbers, and hyphens'),\n tenantId: z.string().min(1, 'Please select a tenant'),\n organizationId: z.string().min(1, 'Please select an organization'),\n});\n\ntype CreateWorkspaceFormData = z.infer<typeof createWorkspaceSchema>;\n\nexport function CreateWorkspaceDialog() {\n const { isCreateWorkspaceDialogOpen, closeCreateWorkspaceDialog } = useWorkspacesStore();\n const { mutate: createWorkspace, isPending } = useCreateWorkspace();\n const { data: tenantsData, isLoading: isLoadingTenants, error: tenantsError } = useMyTenants();\n const {\n data: organizationsData,\n isLoading: isLoadingOrganizations,\n error: organizationsError,\n } = useMyOrganizations();\n\n const tenants = useMemo(() => tenantsData?.items ?? [], [tenantsData]);\n const allOrganizations = useMemo(() => organizationsData?.items ?? [], [organizationsData]);\n\n // Prefer the shared tenant (or the only one) — with a single shared tenant\n // there is nothing to choose, so we auto-select it and hide the picker.\n const defaultTenantId = useMemo(() => {\n const sharedTenant = tenants.find(\n (tenant) => tenant.type === 'SHARED' || tenant.slug === 'burdenoff-shared'\n );\n return sharedTenant?.id ?? tenants[0]?.id ?? '';\n }, [tenants]);\n\n const form = useForm<CreateWorkspaceFormData>({\n schema: createWorkspaceSchema,\n defaultValues: {\n name: '',\n slug: '',\n tenantId: '',\n organizationId: '',\n },\n });\n\n const selectedTenantId = form.watch('tenantId');\n const workspaceName = form.watch('name');\n\n // Auto-select the default tenant when the user has no real choice.\n useEffect(() => {\n if (!form.getValues('tenantId') && defaultTenantId) {\n form.setValue('tenantId', defaultTenantId, { shouldValidate: true });\n }\n }, [defaultTenantId, form]);\n\n // Filter organizations by selected tenant\n const filteredOrganizations = useMemo(() => {\n if (!selectedTenantId) return [];\n return allOrganizations.filter((org) => org.tenantId === selectedTenantId);\n }, [selectedTenantId, allOrganizations]);\n\n // Auto-generate slug from name (clear when name is empty)\n useEffect(() => {\n form.setValue('slug', workspaceName ? generateSlug(workspaceName) : '');\n }, [workspaceName, form]);\n\n // Reset organizationId when tenant changes\n useEffect(() => {\n form.setValue('organizationId', '');\n }, [selectedTenantId, form]);\n\n const onSubmit = (data: CreateWorkspaceFormData) => {\n createWorkspace(\n {\n name: data.name,\n slug: data.slug,\n tenantId: data.tenantId,\n organizationId: data.organizationId,\n },\n {\n onSuccess: () => {\n closeCreateWorkspaceDialog();\n form.reset();\n },\n }\n );\n };\n\n return (\n <ResponsiveDialog open={isCreateWorkspaceDialogOpen} onOpenChange={closeCreateWorkspaceDialog}>\n <ResponsiveDialogContent className=\"sm:max-w-[500px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Create Workspace</ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n {/* Workspace Name */}\n <FormField\n control={form.control}\n name=\"name\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Workspace Name *</FormLabel>\n <FormControl>\n <Input placeholder=\"My Workspace\" {...field} />\n </FormControl>\n <FormDescription>A descriptive name for your workspace</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n {/* Slug */}\n <FormField\n control={form.control}\n name=\"slug\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Slug *</FormLabel>\n <FormControl>\n <Input placeholder=\"my-workspace\" {...field} />\n </FormControl>\n <FormDescription>URL-friendly identifier (auto-generated)</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n {/* Tenant Selection — shown only when the user has more than one\n tenant to choose between. With a single shared tenant it is\n auto-selected and hidden (loading/error/empty still surface). */}\n {tenants.length !== 1 ? (\n <FormField\n control={form.control}\n name=\"tenantId\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Tenant *</FormLabel>\n {isLoadingTenants ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : tenantsError ? (\n <div className=\"flex items-center gap-2 text-status-error-text text-sm\">\n <AlertCircle size={16} />\n Failed to load tenants\n </div>\n ) : tenants.length === 0 ? (\n <div className=\"text-text-secondary text-sm\">\n No tenants available. Contact your administrator.\n </div>\n ) : (\n <Select onValueChange={field.onChange} value={field.value}>\n <FormControl>\n <SelectTrigger>\n <SelectValue placeholder=\"Select a tenant\" />\n </SelectTrigger>\n </FormControl>\n <SelectContent>\n {tenants.map((tenant) => (\n <SelectItem key={tenant.id} value={tenant.id}>\n {tenant.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n <FormDescription>Select which tenant this workspace belongs to</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n ) : null}\n\n {/* Organization Selection */}\n <FormField\n control={form.control}\n name=\"organizationId\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Organization *</FormLabel>\n {isLoadingOrganizations ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : organizationsError ? (\n <div className=\"flex items-center gap-2 text-status-error-text text-sm\">\n <AlertCircle size={16} />\n Failed to load organizations\n </div>\n ) : !selectedTenantId ? (\n <div className=\"text-text-secondary text-sm\">\n Please select a tenant first\n </div>\n ) : filteredOrganizations.length === 0 ? (\n <div className=\"text-text-secondary text-sm\">\n No organizations found for this tenant\n </div>\n ) : (\n <Select\n onValueChange={field.onChange}\n value={field.value}\n disabled={!selectedTenantId}\n >\n <FormControl>\n <SelectTrigger>\n <SelectValue placeholder=\"Select an organization\" />\n </SelectTrigger>\n </FormControl>\n <SelectContent>\n {filteredOrganizations.map((org) => (\n <SelectItem key={org.id} value={org.id}>\n {org.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n <FormDescription>Select which organization owns this workspace</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={closeCreateWorkspaceDialog}\n disabled={isPending}\n >\n Cancel\n </Button>\n <Button\n type=\"submit\"\n disabled={isPending || isLoadingTenants || isLoadingOrganizations}\n >\n {isPending ? 'Creating...' : 'Create Workspace'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </FormProvider>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -62,7 +62,7 @@ function j({ workspaceId: k }) {
|
|
|
62
62
|
className: "space-y-5 pb-4 pr-2",
|
|
63
63
|
children: [
|
|
64
64
|
z && /* @__PURE__ */ s("div", {
|
|
65
|
-
className: "flex items-center gap-2 rounded-md border border-
|
|
65
|
+
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",
|
|
66
66
|
children: [/* @__PURE__ */ o(a, { className: "size-4 flex-shrink-0" }), /* @__PURE__ */ o("span", { children: z })]
|
|
67
67
|
}),
|
|
68
68
|
/* @__PURE__ */ o(S, {
|
|
@@ -92,7 +92,7 @@ function j({ workspaceId: k }) {
|
|
|
92
92
|
children: [/* @__PURE__ */ s("div", {
|
|
93
93
|
className: "space-y-1",
|
|
94
94
|
children: [/* @__PURE__ */ o(w, { children: "Roles (Optional)" }), /* @__PURE__ */ o("p", {
|
|
95
|
-
className: "text-sm text-
|
|
95
|
+
className: "text-sm text-text-secondary",
|
|
96
96
|
children: "Choose roles. Hover any role to preview its full description."
|
|
97
97
|
})]
|
|
98
98
|
}), e.value.length > 0 ? /* @__PURE__ */ s(c, {
|
|
@@ -103,17 +103,17 @@ function j({ workspaceId: k }) {
|
|
|
103
103
|
/* @__PURE__ */ o(b, { children: /* @__PURE__ */ o(v, { children: /* @__PURE__ */ o("div", {
|
|
104
104
|
className: "grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3",
|
|
105
105
|
children: I ? /* @__PURE__ */ o("p", {
|
|
106
|
-
className: "text-sm text-
|
|
106
|
+
className: "text-sm text-text-secondary",
|
|
107
107
|
children: "Loading roles…"
|
|
108
108
|
}) : F.length === 0 ? /* @__PURE__ */ o("p", {
|
|
109
|
-
className: "text-sm text-
|
|
109
|
+
className: "text-sm text-text-secondary",
|
|
110
110
|
children: "No assignable roles were returned for this workspace."
|
|
111
111
|
}) : F.map((t) => {
|
|
112
112
|
let n = e.value.includes(t.id);
|
|
113
113
|
return /* @__PURE__ */ s(g, { children: [/* @__PURE__ */ o(y, {
|
|
114
114
|
asChild: !0,
|
|
115
115
|
children: /* @__PURE__ */ s("label", {
|
|
116
|
-
className: `flex cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-3 text-left text-sm font-medium transition-colors focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 ${n ? "border-primary bg-primary/10 text-
|
|
116
|
+
className: `flex cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-3 text-left text-sm font-medium transition-colors focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)] focus-within:ring-offset-2 ${n ? "border-primary bg-action-primary-bg/10 text-text-primary shadow-sm" : "border-border-subtle bg-bg-surface text-text-primary hover:bg-accent"}`,
|
|
117
117
|
children: [/* @__PURE__ */ o("span", {
|
|
118
118
|
className: "min-w-0 flex-1 truncate",
|
|
119
119
|
children: t.name
|
|
@@ -135,7 +135,7 @@ function j({ workspaceId: k }) {
|
|
|
135
135
|
children: t.name
|
|
136
136
|
}),
|
|
137
137
|
/* @__PURE__ */ o("p", {
|
|
138
|
-
className: "mt-1 text-
|
|
138
|
+
className: "mt-1 text-text-secondary",
|
|
139
139
|
children: t.description || "No description available for this role."
|
|
140
140
|
}),
|
|
141
141
|
t.isSystem ? /* @__PURE__ */ o("p", {
|
|
@@ -158,7 +158,7 @@ function j({ workspaceId: k }) {
|
|
|
158
158
|
children: [/* @__PURE__ */ s("div", {
|
|
159
159
|
className: "space-y-1",
|
|
160
160
|
children: [/* @__PURE__ */ o(w, { children: "Projects to add on acceptance (Optional)" }), /* @__PURE__ */ o("p", {
|
|
161
|
-
className: "text-sm text-
|
|
161
|
+
className: "text-sm text-text-secondary",
|
|
162
162
|
children: "The invited user will be added to these projects automatically after they accept the workspace invitation."
|
|
163
163
|
})]
|
|
164
164
|
}), e.value.length > 0 ? /* @__PURE__ */ s(c, {
|
|
@@ -169,22 +169,22 @@ function j({ workspaceId: k }) {
|
|
|
169
169
|
/* @__PURE__ */ o(b, { children: /* @__PURE__ */ o("div", {
|
|
170
170
|
className: "grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3",
|
|
171
171
|
children: R ? /* @__PURE__ */ o("p", {
|
|
172
|
-
className: "text-sm text-
|
|
172
|
+
className: "text-sm text-text-secondary",
|
|
173
173
|
children: "Loading projects…"
|
|
174
174
|
}) : V.length === 0 ? /* @__PURE__ */ o("p", {
|
|
175
|
-
className: "text-sm text-
|
|
175
|
+
className: "text-sm text-text-secondary",
|
|
176
176
|
children: "No active projects are available in this workspace yet."
|
|
177
177
|
}) : V.map((t) => {
|
|
178
178
|
let n = e.value.includes(t.id);
|
|
179
179
|
return /* @__PURE__ */ s("label", {
|
|
180
|
-
className: `flex cursor-pointer items-start justify-between gap-3 rounded-lg border px-3 py-3 transition-colors focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 ${n ? "border-primary bg-primary/10 text-
|
|
180
|
+
className: `flex cursor-pointer items-start justify-between gap-3 rounded-lg border px-3 py-3 transition-colors focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)] focus-within:ring-offset-2 ${n ? "border-primary bg-action-primary-bg/10 text-text-primary shadow-sm" : "border-border-subtle bg-bg-surface text-text-primary hover:bg-accent"}`,
|
|
181
181
|
children: [/* @__PURE__ */ s("div", {
|
|
182
182
|
className: "min-w-0 flex-1 space-y-1",
|
|
183
183
|
children: [/* @__PURE__ */ o("p", {
|
|
184
184
|
className: "truncate text-sm font-semibold leading-tight",
|
|
185
185
|
children: t.name
|
|
186
186
|
}), /* @__PURE__ */ s("div", {
|
|
187
|
-
className: "flex items-center gap-2 text-xs text-
|
|
187
|
+
className: "flex items-center gap-2 text-xs text-text-secondary",
|
|
188
188
|
children: [/* @__PURE__ */ o("span", {
|
|
189
189
|
className: "truncate font-mono",
|
|
190
190
|
children: t.key
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InviteMemberDialog.js","names":[],"sources":["../../src/components/InviteMemberDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Button,\n Badge,\n Checkbox,\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\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';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useCreateInvitation } from '../hooks/useMemberMutations';\nimport { useProjects } from '../hooks/useProjects';\nimport { useWorkspaceRoles } from '../hooks/useWorkspaceRoles';\n\nconst inviteMemberSchema = z.object({\n email: z.string().email('Invalid email address'),\n // Using a lambda default instead of `.default([])` to keep input type required\n // (zod v4 widens defaulted inputs to `T | undefined`, which breaks useForm typing).\n roleIds: z.array(z.string()),\n projectIds: z.array(z.string()),\n});\n\ntype InviteMemberFormData = z.infer<typeof inviteMemberSchema>;\n\ninterface InviteMemberDialogProps {\n /** Workspace ID to invite the member to (from URL params) */\n workspaceId?: string;\n}\n\nexport function InviteMemberDialog({ workspaceId }: InviteMemberDialogProps) {\n const { isInviteMemberDialogOpen, closeInviteMemberDialog } = useWorkspacesStore();\n const { mutate: createInvitation, isPending } = useCreateInvitation(workspaceId);\n const { data: availableRoles = [], isLoading: isLoadingRoles } = useWorkspaceRoles(workspaceId);\n const { data: projectList, isLoading: isLoadingProjects } = useProjects(\n { archived: false },\n { limit: 100 },\n workspaceId\n );\n const [error, setError] = useState<string | null>(null);\n const availableProjects = projectList?.items ?? [];\n\n const form = useForm<InviteMemberFormData>({\n schema: inviteMemberSchema,\n defaultValues: {\n email: '',\n roleIds: [],\n projectIds: [],\n },\n });\n\n const onSubmit = (data: InviteMemberFormData) => {\n setError(null);\n\n if (!workspaceId) {\n setError('No workspace selected. Please select a workspace first.');\n return;\n }\n\n createInvitation(\n {\n email: data.email,\n roleIds: data.roleIds,\n workspaceId,\n ...(data.projectIds.length > 0\n ? {\n context: {\n type: 'workspace.invitation',\n metadata: { projectIds: data.projectIds },\n },\n }\n : {}),\n },\n {\n onSuccess: () => {\n closeInviteMemberDialog();\n form.reset();\n setError(null);\n },\n onError: (err) => {\n const errorMessage = err instanceof Error ? err.message : 'Failed to send invitation';\n console.error('Failed to create invitation:', err);\n setError(errorMessage);\n },\n }\n );\n };\n\n const handleOpenChange = (open: boolean) => {\n if (!open) {\n closeInviteMemberDialog();\n form.reset();\n setError(null);\n }\n };\n\n return (\n <ResponsiveDialog open={isInviteMemberDialogOpen} onOpenChange={handleOpenChange}>\n <ResponsiveDialogContent className=\"z-[120] flex h-[min(88vh,900px)] flex-col overflow-hidden sm:max-w-[960px] lg:max-w-[1120px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Invite Member</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Invite a new member to this workspace by email. They'll receive an invitation link to\n join.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"min-h-0 flex-1 overflow-y-auto pr-2\">\n <div className=\"space-y-5 pb-4 pr-2\">\n {error && (\n <div className=\"flex items-center gap-2 rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive\">\n <AlertCircle className=\"size-4 flex-shrink-0\" />\n <span>{error}</span>\n </div>\n )}\n\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Email Address *</FormLabel>\n <FormControl>\n <Input\n type=\"email\"\n placeholder=\"member@example.com\"\n {...field}\n disabled={isPending}\n onChange={(e) => {\n field.onChange(e);\n setError(null);\n }}\n />\n </FormControl>\n <FormDescription>\n Enter the email address of the person you want to invite\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"roleIds\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <FormLabel>Roles (Optional)</FormLabel>\n <p className=\"text-sm text-muted-foreground\">\n Choose roles. Hover any role to preview its full description.\n </p>\n </div>\n {field.value.length > 0 ? (\n <Badge variant=\"secondary\">{field.value.length} selected</Badge>\n ) : null}\n </div>\n <FormControl>\n <TooltipProvider>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingRoles ? (\n <p className=\"text-sm text-muted-foreground\">Loading roles…</p>\n ) : availableRoles.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n No assignable roles were returned for this workspace.\n </p>\n ) : (\n availableRoles.map((role) => {\n const isSelected = field.value.includes(role.id);\n return (\n <Tooltip key={role.id}>\n <TooltipTrigger asChild>\n <label\n className={`flex cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-3 text-left text-sm font-medium transition-colors focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-primary/10 text-foreground shadow-sm'\n : 'border-border bg-background text-foreground hover:bg-accent'\n }`}\n >\n <span className=\"min-w-0 flex-1 truncate\">{role.name}</span>\n <Checkbox\n checked={isSelected}\n disabled={isPending}\n onCheckedChange={(checked) => {\n const nextValue = checked\n ? [...field.value, role.id]\n : field.value.filter((id) => id !== role.id);\n field.onChange(nextValue);\n }}\n aria-label={`Select ${role.name}`}\n />\n </label>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-xs text-sm leading-6\">\n <p className=\"font-medium\">{role.name}</p>\n <p className=\"mt-1 text-muted-foreground\">\n {role.description ||\n 'No description available for this role.'}\n </p>\n {role.isSystem ? (\n <p className=\"mt-2 text-xs\">System role</p>\n ) : null}\n </TooltipContent>\n </Tooltip>\n );\n })\n )}\n </div>\n </TooltipProvider>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"projectIds\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <FormLabel>Projects to add on acceptance (Optional)</FormLabel>\n <p className=\"text-sm text-muted-foreground\">\n The invited user will be added to these projects automatically after\n they accept the workspace invitation.\n </p>\n </div>\n {field.value.length > 0 ? (\n <Badge variant=\"secondary\">{field.value.length} selected</Badge>\n ) : null}\n </div>\n <FormControl>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingProjects ? (\n <p className=\"text-sm text-muted-foreground\">Loading projects…</p>\n ) : availableProjects.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n No active projects are available in this workspace yet.\n </p>\n ) : (\n availableProjects.map((project) => {\n const isSelected = field.value.includes(project.id);\n\n return (\n <label\n key={project.id}\n className={`flex cursor-pointer items-start justify-between gap-3 rounded-lg border px-3 py-3 transition-colors focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-primary/10 text-foreground shadow-sm'\n : 'border-border bg-background text-foreground hover:bg-accent'\n }`}\n >\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"truncate text-sm font-semibold leading-tight\">\n {project.name}\n </p>\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span className=\"truncate font-mono\">{project.key}</span>\n <Badge variant=\"outline\">{project.status}</Badge>\n </div>\n </div>\n <Checkbox\n checked={isSelected}\n disabled={isPending}\n onCheckedChange={(checked) => {\n const nextValue = checked\n ? [...field.value, project.id]\n : field.value.filter((id) => id !== project.id);\n field.onChange(nextValue);\n }}\n aria-label={`Select ${project.name}`}\n />\n </label>\n );\n })\n )}\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n </div>\n </div>\n\n <div className=\"flex justify-end gap-3 border-t pt-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={isPending}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={isPending}>\n {isPending ? 'Sending...' : 'Send Invitation'}\n </Button>\n </div>\n </form>\n </FormProvider>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InviteMemberDialog.js","names":[],"sources":["../../src/components/InviteMemberDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Button,\n Badge,\n Checkbox,\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\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';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useCreateInvitation } from '../hooks/useMemberMutations';\nimport { useProjects } from '../hooks/useProjects';\nimport { useWorkspaceRoles } from '../hooks/useWorkspaceRoles';\n\nconst inviteMemberSchema = z.object({\n email: z.string().email('Invalid email address'),\n // Using a lambda default instead of `.default([])` to keep input type required\n // (zod v4 widens defaulted inputs to `T | undefined`, which breaks useForm typing).\n roleIds: z.array(z.string()),\n projectIds: z.array(z.string()),\n});\n\ntype InviteMemberFormData = z.infer<typeof inviteMemberSchema>;\n\ninterface InviteMemberDialogProps {\n /** Workspace ID to invite the member to (from URL params) */\n workspaceId?: string;\n}\n\nexport function InviteMemberDialog({ workspaceId }: InviteMemberDialogProps) {\n const { isInviteMemberDialogOpen, closeInviteMemberDialog } = useWorkspacesStore();\n const { mutate: createInvitation, isPending } = useCreateInvitation(workspaceId);\n const { data: availableRoles = [], isLoading: isLoadingRoles } = useWorkspaceRoles(workspaceId);\n const { data: projectList, isLoading: isLoadingProjects } = useProjects(\n { archived: false },\n { limit: 100 },\n workspaceId\n );\n const [error, setError] = useState<string | null>(null);\n const availableProjects = projectList?.items ?? [];\n\n const form = useForm<InviteMemberFormData>({\n schema: inviteMemberSchema,\n defaultValues: {\n email: '',\n roleIds: [],\n projectIds: [],\n },\n });\n\n const onSubmit = (data: InviteMemberFormData) => {\n setError(null);\n\n if (!workspaceId) {\n setError('No workspace selected. Please select a workspace first.');\n return;\n }\n\n createInvitation(\n {\n email: data.email,\n roleIds: data.roleIds,\n workspaceId,\n ...(data.projectIds.length > 0\n ? {\n context: {\n type: 'workspace.invitation',\n metadata: { projectIds: data.projectIds },\n },\n }\n : {}),\n },\n {\n onSuccess: () => {\n closeInviteMemberDialog();\n form.reset();\n setError(null);\n },\n onError: (err) => {\n const errorMessage = err instanceof Error ? err.message : 'Failed to send invitation';\n console.error('Failed to create invitation:', err);\n setError(errorMessage);\n },\n }\n );\n };\n\n const handleOpenChange = (open: boolean) => {\n if (!open) {\n closeInviteMemberDialog();\n form.reset();\n setError(null);\n }\n };\n\n return (\n <ResponsiveDialog open={isInviteMemberDialogOpen} onOpenChange={handleOpenChange}>\n <ResponsiveDialogContent className=\"z-[120] flex h-[min(88vh,900px)] flex-col overflow-hidden sm:max-w-[960px] lg:max-w-[1120px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Invite Member</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Invite a new member to this workspace by email. They'll receive an invitation link to\n join.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"min-h-0 flex-1 overflow-y-auto pr-2\">\n <div className=\"space-y-5 pb-4 pr-2\">\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 flex-shrink-0\" />\n <span>{error}</span>\n </div>\n )}\n\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Email Address *</FormLabel>\n <FormControl>\n <Input\n type=\"email\"\n placeholder=\"member@example.com\"\n {...field}\n disabled={isPending}\n onChange={(e) => {\n field.onChange(e);\n setError(null);\n }}\n />\n </FormControl>\n <FormDescription>\n Enter the email address of the person you want to invite\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"roleIds\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <FormLabel>Roles (Optional)</FormLabel>\n <p className=\"text-sm text-text-secondary\">\n Choose roles. Hover any role to preview its full description.\n </p>\n </div>\n {field.value.length > 0 ? (\n <Badge variant=\"secondary\">{field.value.length} selected</Badge>\n ) : null}\n </div>\n <FormControl>\n <TooltipProvider>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingRoles ? (\n <p className=\"text-sm text-text-secondary\">Loading roles…</p>\n ) : availableRoles.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No assignable roles were returned for this workspace.\n </p>\n ) : (\n availableRoles.map((role) => {\n const isSelected = field.value.includes(role.id);\n return (\n <Tooltip key={role.id}>\n <TooltipTrigger asChild>\n <label\n className={`flex cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-3 text-left text-sm font-medium transition-colors focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)] focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-action-primary-bg/10 text-text-primary shadow-sm'\n : 'border-border-subtle bg-bg-surface text-text-primary hover:bg-accent'\n }`}\n >\n <span className=\"min-w-0 flex-1 truncate\">{role.name}</span>\n <Checkbox\n checked={isSelected}\n disabled={isPending}\n onCheckedChange={(checked) => {\n const nextValue = checked\n ? [...field.value, role.id]\n : field.value.filter((id) => id !== role.id);\n field.onChange(nextValue);\n }}\n aria-label={`Select ${role.name}`}\n />\n </label>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-xs text-sm leading-6\">\n <p className=\"font-medium\">{role.name}</p>\n <p className=\"mt-1 text-text-secondary\">\n {role.description ||\n 'No description available for this role.'}\n </p>\n {role.isSystem ? (\n <p className=\"mt-2 text-xs\">System role</p>\n ) : null}\n </TooltipContent>\n </Tooltip>\n );\n })\n )}\n </div>\n </TooltipProvider>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"projectIds\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <FormLabel>Projects to add on acceptance (Optional)</FormLabel>\n <p className=\"text-sm text-text-secondary\">\n The invited user will be added to these projects automatically after\n they accept the workspace invitation.\n </p>\n </div>\n {field.value.length > 0 ? (\n <Badge variant=\"secondary\">{field.value.length} selected</Badge>\n ) : null}\n </div>\n <FormControl>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingProjects ? (\n <p className=\"text-sm text-text-secondary\">Loading projects…</p>\n ) : availableProjects.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No active projects are available in this workspace yet.\n </p>\n ) : (\n availableProjects.map((project) => {\n const isSelected = field.value.includes(project.id);\n\n return (\n <label\n key={project.id}\n className={`flex cursor-pointer items-start justify-between gap-3 rounded-lg border px-3 py-3 transition-colors focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)] focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-action-primary-bg/10 text-text-primary shadow-sm'\n : 'border-border-subtle bg-bg-surface text-text-primary hover:bg-accent'\n }`}\n >\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"truncate text-sm font-semibold leading-tight\">\n {project.name}\n </p>\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <span className=\"truncate font-mono\">{project.key}</span>\n <Badge variant=\"outline\">{project.status}</Badge>\n </div>\n </div>\n <Checkbox\n checked={isSelected}\n disabled={isPending}\n onCheckedChange={(checked) => {\n const nextValue = checked\n ? [...field.value, project.id]\n : field.value.filter((id) => id !== project.id);\n field.onChange(nextValue);\n }}\n aria-label={`Select ${project.name}`}\n />\n </label>\n );\n })\n )}\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n </div>\n </div>\n\n <div className=\"flex justify-end gap-3 border-t pt-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={isPending}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={isPending}>\n {isPending ? 'Sending...' : 'Send Invitation'}\n </Button>\n </div>\n </form>\n </FormProvider>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;;;AAiCA,IAAM,IAAqB,EAAE,OAAO;CAClC,OAAO,EAAE,QAAQ,CAAC,MAAM,wBAAwB;CAGhD,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC;CAC5B,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC;CAChC,CAAC;AASF,SAAgB,EAAmB,EAAE,kBAAwC;CAC3E,IAAM,EAAE,6BAA0B,+BAA4B,GAAoB,EAC5E,EAAE,QAAQ,GAAkB,iBAAc,EAAoB,EAAY,EAC1E,EAAE,MAAM,IAAiB,EAAE,EAAE,WAAW,MAAmB,EAAkB,EAAY,EACzF,EAAE,MAAM,GAAa,WAAW,MAAsB,EAC1D,EAAE,UAAU,IAAO,EACnB,EAAE,OAAO,KAAK,EACd,EACD,EACK,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAoB,GAAa,SAAS,EAAE,EAE5C,IAAO,EAA8B;EACzC,QAAQ;EACR,eAAe;GACb,OAAO;GACP,SAAS,EAAE;GACX,YAAY,EAAE;GACf;EACF,CAAC,EAEI,KAAY,MAA+B;AAG/C,MAFA,EAAS,KAAK,EAEV,CAAC,GAAa;AAChB,KAAS,0DAA0D;AACnE;;AAGF,IACE;GACE,OAAO,EAAK;GACZ,SAAS,EAAK;GACd;GACA,GAAI,EAAK,WAAW,SAAS,IACzB,EACE,SAAS;IACP,MAAM;IACN,UAAU,EAAE,YAAY,EAAK,YAAY;IAC1C,EACF,GACD,EAAE;GACP,EACD;GACE,iBAAiB;AAGf,IAFA,GAAyB,EACzB,EAAK,OAAO,EACZ,EAAS,KAAK;;GAEhB,UAAU,MAAQ;IAChB,IAAM,IAAe,aAAe,QAAQ,EAAI,UAAU;AAE1D,IADA,QAAQ,MAAM,gCAAgC,EAAI,EAClD,EAAS,EAAa;;GAEzB,CACF;IAGG,KAAoB,MAAkB;AAC1C,EAAK,MACH,GAAyB,EACzB,EAAK,OAAO,EACZ,EAAS,KAAK;;AAIlB,QACE,kBAAC,GAAD;EAAkB,MAAM;EAA0B,cAAc;YAC9D,kBAAC,GAAD;GAAyB,WAAU;aAAnC,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,iBAAqC,CAAA,EAC5D,kBAAC,GAAD,EAAA,UAA6B,+FAGC,CAAA,CACP,EAAA,CAAA,EAEzB,kBAAC,GAAD;IAAc,GAAI;cAChB,kBAAC,QAAD;KAAM,UAAU,EAAK,aAAa,EAAS;KAAE,WAAU;eAAvD,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACG,KACC,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAa,WAAU,wBAAyB,CAAA,EAChD,kBAAC,QAAD,EAAA,UAAO,GAAa,CAAA,CAChB;;QAGR,kBAAC,GAAD;SACE,SAAS,EAAK;SACd,MAAK;SACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;UACE,kBAAC,GAAD,EAAA,UAAW,mBAA2B,CAAA;UACtC,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;WACE,MAAK;WACL,aAAY;WACZ,GAAI;WACJ,UAAU;WACV,WAAW,MAAM;AAEf,YADA,EAAM,SAAS,EAAE,EACjB,EAAS,KAAK;;WAEhB,CAAA,EACU,CAAA;UACd,kBAAC,GAAD,EAAA,UAAiB,4DAEC,CAAA;UAClB,kBAAC,GAAD,EAAe,CAAA;UACN,EAAA,CAAA;SAEb,CAAA;QAEF,kBAAC,GAAD;SACE,SAAS,EAAK;SACd,MAAK;SACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,GAAD,EAAA,UAAW,oBAA4B,CAAA,EACvC,kBAAC,KAAD;aAAG,WAAU;uBAA8B;aAEvC,CAAA,CACA;eACL,EAAM,MAAM,SAAS,IACpB,kBAAC,GAAD;YAAO,SAAQ;sBAAf,CAA4B,EAAM,MAAM,QAAO,YAAiB;gBAC9D,KACA;;UACN,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;WAAK,WAAU;qBACZ,IACC,kBAAC,KAAD;YAAG,WAAU;sBAA8B;YAAkB,CAAA,GAC3D,EAAe,WAAW,IAC5B,kBAAC,KAAD;YAAG,WAAU;sBAA8B;YAEvC,CAAA,GAEJ,EAAe,KAAK,MAAS;YAC3B,IAAM,IAAa,EAAM,MAAM,SAAS,EAAK,GAAG;AAChD,mBACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;aAAgB,SAAA;uBACd,kBAAC,SAAD;cACE,WAAW,iOACT,IACI,uEACA;wBAJR,CAOE,kBAAC,QAAD;eAAM,WAAU;yBAA2B,EAAK;eAAY,CAAA,EAC5D,kBAAC,GAAD;eACE,SAAS;eACT,UAAU;eACV,kBAAkB,MAAY;gBAC5B,IAAM,IAAY,IACd,CAAC,GAAG,EAAM,OAAO,EAAK,GAAG,GACzB,EAAM,MAAM,QAAQ,MAAO,MAAO,EAAK,GAAG;AAC9C,kBAAM,SAAS,EAAU;;eAE3B,cAAY,UAAU,EAAK;eAC3B,CAAA,CACI;;aACO,CAAA,EACjB,kBAAC,GAAD;aAAgB,WAAU;uBAA1B;cACE,kBAAC,KAAD;eAAG,WAAU;yBAAe,EAAK;eAAS,CAAA;cAC1C,kBAAC,KAAD;eAAG,WAAU;yBACV,EAAK,eACJ;eACA,CAAA;cACH,EAAK,WACJ,kBAAC,KAAD;eAAG,WAAU;yBAAe;eAAe,CAAA,GACzC;cACW;eACT,EAAA,EAjCI,EAAK,GAiCT;aAEZ;WAEA,CAAA,EACU,CAAA,EACN,CAAA;UACd,kBAAC,GAAD,EAAe,CAAA;UACN,EAAA,CAAA;SAEb,CAAA;QAEF,kBAAC,GAAD;SACE,SAAS,EAAK;SACd,MAAK;SACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,GAAD,EAAA,UAAW,4CAAoD,CAAA,EAC/D,kBAAC,KAAD;aAAG,WAAU;uBAA8B;aAGvC,CAAA,CACA;eACL,EAAM,MAAM,SAAS,IACpB,kBAAC,GAAD;YAAO,SAAQ;sBAAf,CAA4B,EAAM,MAAM,QAAO,YAAiB;gBAC9D,KACA;;UACN,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;WAAK,WAAU;qBACZ,IACC,kBAAC,KAAD;YAAG,WAAU;sBAA8B;YAAqB,CAAA,GAC9D,EAAkB,WAAW,IAC/B,kBAAC,KAAD;YAAG,WAAU;sBAA8B;YAEvC,CAAA,GAEJ,EAAkB,KAAK,MAAY;YACjC,IAAM,IAAa,EAAM,MAAM,SAAS,EAAQ,GAAG;AAEnD,mBACE,kBAAC,SAAD;aAEE,WAAW,kMACT,IACI,uEACA;uBALR,CAQE,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,KAAD;eAAG,WAAU;yBACV,EAAQ;eACP,CAAA,EACJ,kBAAC,OAAD;eAAK,WAAU;yBAAf,CACE,kBAAC,QAAD;gBAAM,WAAU;0BAAsB,EAAQ;gBAAW,CAAA,EACzD,kBAAC,GAAD;gBAAO,SAAQ;0BAAW,EAAQ;gBAAe,CAAA,CAC7C;iBACF;iBACN,kBAAC,GAAD;cACE,SAAS;cACT,UAAU;cACV,kBAAkB,MAAY;eAC5B,IAAM,IAAY,IACd,CAAC,GAAG,EAAM,OAAO,EAAQ,GAAG,GAC5B,EAAM,MAAM,QAAQ,MAAO,MAAO,EAAQ,GAAG;AACjD,iBAAM,SAAS,EAAU;;cAE3B,cAAY,UAAU,EAAQ;cAC9B,CAAA,CACI;eA3BD,EAAQ,GA2BP;aAEV;WAEA,CAAA,EACM,CAAA;UACd,kBAAC,GAAD,EAAe,CAAA;UACN,EAAA,CAAA;SAEb,CAAA;QACE;;MACF,CAAA,EAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,eAAe,EAAiB,GAAM;OACtC,UAAU;iBACX;OAEQ,CAAA,EACT,kBAAC,GAAD;OAAQ,MAAK;OAAS,UAAU;iBAC7B,IAAY,eAAe;OACrB,CAAA,CACL;QACD;;IACM,CAAA,CACS;;EACT,CAAA"}
|
|
@@ -35,10 +35,10 @@ function j({ project: j, selected: M = !1, onToggleSelect: N, onTagClick: P, can
|
|
|
35
35
|
onChange: (e) => N(e.target.checked),
|
|
36
36
|
onClick: (e) => e.stopPropagation(),
|
|
37
37
|
"aria-label": `Select project ${j.name}`,
|
|
38
|
-
className: "mt-1 size-4 rounded border-border text-primary"
|
|
38
|
+
className: "mt-1 size-4 rounded border-border-subtle text-primary"
|
|
39
39
|
}),
|
|
40
40
|
/* @__PURE__ */ h("div", {
|
|
41
|
-
className: "p-3 rounded-lg bg-primary/10 transition-colors",
|
|
41
|
+
className: "p-3 rounded-lg bg-action-primary-bg/10 transition-colors",
|
|
42
42
|
children: /* @__PURE__ */ h(f, {
|
|
43
43
|
className: "text-primary",
|
|
44
44
|
size: 24
|
|
@@ -50,7 +50,7 @@ function j({ project: j, selected: M = !1, onToggleSelect: N, onTagClick: P, can
|
|
|
50
50
|
className: "truncate",
|
|
51
51
|
children: j.name
|
|
52
52
|
}), /* @__PURE__ */ h("p", {
|
|
53
|
-
className: "truncate text-sm text-
|
|
53
|
+
className: "truncate text-sm text-text-secondary",
|
|
54
54
|
children: j.key
|
|
55
55
|
})]
|
|
56
56
|
})
|
|
@@ -110,7 +110,7 @@ function j({ project: j, selected: M = !1, onToggleSelect: N, onTagClick: P, can
|
|
|
110
110
|
});
|
|
111
111
|
(t === null ? confirm(`Are you sure you want to delete "${j.name}"?`) : t) && H(j.id);
|
|
112
112
|
},
|
|
113
|
-
className: "text-
|
|
113
|
+
className: "text-status-error-text",
|
|
114
114
|
children: "Delete"
|
|
115
115
|
})
|
|
116
116
|
]
|
|
@@ -133,7 +133,7 @@ function j({ project: j, selected: M = !1, onToggleSelect: N, onTagClick: P, can
|
|
|
133
133
|
})]
|
|
134
134
|
}),
|
|
135
135
|
/* @__PURE__ */ g("div", {
|
|
136
|
-
className: "text-xs text-
|
|
136
|
+
className: "text-xs text-text-secondary",
|
|
137
137
|
children: ["Created ", c(j.createdAt)]
|
|
138
138
|
}),
|
|
139
139
|
W.length > 0 && /* @__PURE__ */ g("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectCard.js","names":[],"sources":["../../src/components/ProjectCard.tsx"],"sourcesContent":["import type React from 'react';\nimport { FolderKanban, Archive, MoreVertical, Fingerprint } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n EntityIdModal,\n useEntityIdModal,\n} from '@burdenoff/fe-libs/ui';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\n\nimport type { Project, ProjectTag } from '../types';\nimport { formatDate } from '../utils/formatters';\nimport { getProjectDisplayTags } from '../utils/projectTags';\nimport { getProjectStatusClasses } from '../utils/tokenHelpers';\nimport {\n useArchiveProject,\n useUnarchiveProject,\n useDeleteProject,\n} from '../hooks/useProjectMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { nativeConfirm, nativeImpact } from '../utils/nativeBridge';\n\ninterface ProjectCardProps {\n project: Project;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n onTagClick?: (tag: ProjectTag) => void;\n canArchive?: boolean;\n canDelete?: boolean;\n}\n\nexport function ProjectCard({\n project,\n selected = false,\n onToggleSelect,\n onTagClick,\n canArchive = false,\n canDelete = false,\n}: ProjectCardProps) {\n const navigate = useNavigate();\n const { basePath = '/workspaces' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { mutate: archiveProject } = useArchiveProject(project.workspaceId);\n const { mutate: unarchiveProject } = useUnarchiveProject(project.workspaceId);\n const { mutate: deleteProject } = useDeleteProject(project.workspaceId);\n const idModal = useEntityIdModal();\n const projectTags = getProjectDisplayTags(project);\n\n const handleCardClick = () => {\n navigate(withCurrentSearch(`${basePath}/${project.workspaceId}/projects/${project.id}`));\n };\n\n const handleArchive = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (project.archived) {\n unarchiveProject(project.id);\n } else {\n archiveProject(project.id);\n }\n };\n\n const handleDelete = async (e: React.MouseEvent) => {\n e.stopPropagation();\n void nativeImpact('medium');\n const native = await nativeConfirm({\n title: 'Delete project',\n message: `Are you sure you want to delete \"${project.name}\"?`,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok =\n native === null ? confirm(`Are you sure you want to delete \"${project.name}\"?`) : native;\n if (ok) {\n deleteProject(project.id);\n }\n };\n\n const handleCardKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleCardClick();\n }\n };\n\n return (\n <>\n <Card\n className={`cursor-pointer relative ${\n selected ? 'ring-2 ring-primary/25 border-primary/50' : ''\n }`}\n onClick={handleCardClick}\n onKeyDown={handleCardKeyDown}\n role=\"button\"\n tabIndex={0}\n aria-label={`Open project ${project.name}`}\n >\n <CardHeader>\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3 flex-1\">\n {onToggleSelect && (\n <input\n type=\"checkbox\"\n checked={selected}\n onChange={(event) => onToggleSelect(event.target.checked)}\n onClick={(event) => event.stopPropagation()}\n aria-label={`Select project ${project.name}`}\n className=\"mt-1 size-4 rounded border-border text-primary\"\n />\n )}\n <div className=\"p-3 rounded-lg bg-primary/10 transition-colors\">\n <FolderKanban className=\"text-primary\" size={24} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <CardTitle className=\"truncate\">{project.name}</CardTitle>\n <p className=\"truncate text-sm text-muted-foreground\">{project.key}</p>\n </div>\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>\n <Button variant=\"ghost\" size=\"sm\" className=\"size-8 p-0\">\n <MoreVertical size={16} />\n <span className=\"sr-only\">Open project actions</span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n onClick={(e) => {\n e.stopPropagation();\n idModal.show({\n id: project.id,\n name: project.name,\n type: 'Project',\n details: [\n { label: 'Key', value: project.key },\n { label: 'Workspace ID', value: project.workspaceId },\n ],\n });\n }}\n >\n <Fingerprint size={16} className=\"mr-2\" />\n Get ID\n </DropdownMenuItem>\n {canArchive && (\n <DropdownMenuItem onClick={handleArchive}>\n <Archive size={16} className=\"mr-2\" />\n {project.archived ? 'Unarchive' : 'Archive'}\n </DropdownMenuItem>\n )}\n {canDelete && (\n <DropdownMenuItem onClick={handleDelete} className=\"text-destructive\">\n Delete\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <Badge className={`border ${getProjectStatusClasses(project.status)}`}>\n {project.status}\n </Badge>\n {project.archived && (\n <Badge\n variant=\"outline\"\n className=\"border-workspace-archived-border text-workspace-archived-text\"\n >\n <Archive size={12} className=\"mr-1\" />\n Archived\n </Badge>\n )}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n Created {formatDate(project.createdAt)}\n </div>\n {projectTags.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Project tags\">\n {projectTags.slice(0, 4).map((tag) => (\n <span\n key={tag.tagId}\n {...(onTagClick\n ? {\n role: 'button',\n tabIndex: 0,\n onClick: (event: React.MouseEvent<HTMLSpanElement>) => {\n event.stopPropagation();\n onTagClick(tag);\n },\n onKeyDown: (event: React.KeyboardEvent<HTMLSpanElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n event.stopPropagation();\n onTagClick(tag);\n }\n },\n }\n : {})}\n >\n <TagBadge\n id={tag.tagId}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n size=\"sm\"\n className={onTagClick ? 'cursor-pointer' : undefined}\n />\n </span>\n ))}\n {projectTags.length > 4 && (\n <Badge variant=\"outline\" className=\"text-xs\">\n +{projectTags.length - 4}\n </Badge>\n )}\n </div>\n )}\n </CardContent>\n </Card>\n <EntityIdModal control={idModal} />\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectCard.js","names":[],"sources":["../../src/components/ProjectCard.tsx"],"sourcesContent":["import type React from 'react';\nimport { FolderKanban, Archive, MoreVertical, Fingerprint } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n EntityIdModal,\n useEntityIdModal,\n} from '@burdenoff/fe-libs/ui';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\n\nimport type { Project, ProjectTag } from '../types';\nimport { formatDate } from '../utils/formatters';\nimport { getProjectDisplayTags } from '../utils/projectTags';\nimport { getProjectStatusClasses } from '../utils/tokenHelpers';\nimport {\n useArchiveProject,\n useUnarchiveProject,\n useDeleteProject,\n} from '../hooks/useProjectMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { nativeConfirm, nativeImpact } from '../utils/nativeBridge';\n\ninterface ProjectCardProps {\n project: Project;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n onTagClick?: (tag: ProjectTag) => void;\n canArchive?: boolean;\n canDelete?: boolean;\n}\n\nexport function ProjectCard({\n project,\n selected = false,\n onToggleSelect,\n onTagClick,\n canArchive = false,\n canDelete = false,\n}: ProjectCardProps) {\n const navigate = useNavigate();\n const { basePath = '/workspaces' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { mutate: archiveProject } = useArchiveProject(project.workspaceId);\n const { mutate: unarchiveProject } = useUnarchiveProject(project.workspaceId);\n const { mutate: deleteProject } = useDeleteProject(project.workspaceId);\n const idModal = useEntityIdModal();\n const projectTags = getProjectDisplayTags(project);\n\n const handleCardClick = () => {\n navigate(withCurrentSearch(`${basePath}/${project.workspaceId}/projects/${project.id}`));\n };\n\n const handleArchive = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (project.archived) {\n unarchiveProject(project.id);\n } else {\n archiveProject(project.id);\n }\n };\n\n const handleDelete = async (e: React.MouseEvent) => {\n e.stopPropagation();\n void nativeImpact('medium');\n const native = await nativeConfirm({\n title: 'Delete project',\n message: `Are you sure you want to delete \"${project.name}\"?`,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok =\n native === null ? confirm(`Are you sure you want to delete \"${project.name}\"?`) : native;\n if (ok) {\n deleteProject(project.id);\n }\n };\n\n const handleCardKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleCardClick();\n }\n };\n\n return (\n <>\n <Card\n className={`cursor-pointer relative ${\n selected ? 'ring-2 ring-primary/25 border-primary/50' : ''\n }`}\n onClick={handleCardClick}\n onKeyDown={handleCardKeyDown}\n role=\"button\"\n tabIndex={0}\n aria-label={`Open project ${project.name}`}\n >\n <CardHeader>\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3 flex-1\">\n {onToggleSelect && (\n <input\n type=\"checkbox\"\n checked={selected}\n onChange={(event) => onToggleSelect(event.target.checked)}\n onClick={(event) => event.stopPropagation()}\n aria-label={`Select project ${project.name}`}\n className=\"mt-1 size-4 rounded border-border-subtle text-primary\"\n />\n )}\n <div className=\"p-3 rounded-lg bg-action-primary-bg/10 transition-colors\">\n <FolderKanban className=\"text-primary\" size={24} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <CardTitle className=\"truncate\">{project.name}</CardTitle>\n <p className=\"truncate text-sm text-text-secondary\">{project.key}</p>\n </div>\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>\n <Button variant=\"ghost\" size=\"sm\" className=\"size-8 p-0\">\n <MoreVertical size={16} />\n <span className=\"sr-only\">Open project actions</span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n onClick={(e) => {\n e.stopPropagation();\n idModal.show({\n id: project.id,\n name: project.name,\n type: 'Project',\n details: [\n { label: 'Key', value: project.key },\n { label: 'Workspace ID', value: project.workspaceId },\n ],\n });\n }}\n >\n <Fingerprint size={16} className=\"mr-2\" />\n Get ID\n </DropdownMenuItem>\n {canArchive && (\n <DropdownMenuItem onClick={handleArchive}>\n <Archive size={16} className=\"mr-2\" />\n {project.archived ? 'Unarchive' : 'Archive'}\n </DropdownMenuItem>\n )}\n {canDelete && (\n <DropdownMenuItem onClick={handleDelete} className=\"text-status-error-text\">\n Delete\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <Badge className={`border ${getProjectStatusClasses(project.status)}`}>\n {project.status}\n </Badge>\n {project.archived && (\n <Badge\n variant=\"outline\"\n className=\"border-workspace-archived-border text-workspace-archived-text\"\n >\n <Archive size={12} className=\"mr-1\" />\n Archived\n </Badge>\n )}\n </div>\n <div className=\"text-xs text-text-secondary\">\n Created {formatDate(project.createdAt)}\n </div>\n {projectTags.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Project tags\">\n {projectTags.slice(0, 4).map((tag) => (\n <span\n key={tag.tagId}\n {...(onTagClick\n ? {\n role: 'button',\n tabIndex: 0,\n onClick: (event: React.MouseEvent<HTMLSpanElement>) => {\n event.stopPropagation();\n onTagClick(tag);\n },\n onKeyDown: (event: React.KeyboardEvent<HTMLSpanElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n event.stopPropagation();\n onTagClick(tag);\n }\n },\n }\n : {})}\n >\n <TagBadge\n id={tag.tagId}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n size=\"sm\"\n className={onTagClick ? 'cursor-pointer' : undefined}\n />\n </span>\n ))}\n {projectTags.length > 4 && (\n <Badge variant=\"outline\" className=\"text-xs\">\n +{projectTags.length - 4}\n </Badge>\n )}\n </div>\n )}\n </CardContent>\n </Card>\n <EntityIdModal control={idModal} />\n </>\n );\n}\n"],"mappings":"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"}
|
|
@@ -78,15 +78,15 @@ function C({ open: C, onOpenChange: w, workspaceId: T, projectId: E, projectName
|
|
|
78
78
|
className: "space-y-4 overflow-hidden",
|
|
79
79
|
children: [
|
|
80
80
|
N ? /* @__PURE__ */ u("div", {
|
|
81
|
-
className: "flex items-center gap-2 rounded-md border border-
|
|
81
|
+
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
82
|
children: [/* @__PURE__ */ l(o, { className: "size-4 shrink-0" }), /* @__PURE__ */ l("span", { children: N })]
|
|
83
83
|
}) : null,
|
|
84
84
|
G instanceof Error ? /* @__PURE__ */ u("div", {
|
|
85
|
-
className: "flex items-center gap-2 rounded-md border border-
|
|
85
|
+
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
86
|
children: [/* @__PURE__ */ l(o, { className: "size-4 shrink-0" }), /* @__PURE__ */ l("span", { children: G.message })]
|
|
87
87
|
}) : null,
|
|
88
88
|
R instanceof Error ? /* @__PURE__ */ u("div", {
|
|
89
|
-
className: "space-y-3 rounded-md border border-
|
|
89
|
+
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
90
|
children: [/* @__PURE__ */ u("div", {
|
|
91
91
|
className: "flex items-center gap-2",
|
|
92
92
|
children: [/* @__PURE__ */ l(o, { className: "size-4 shrink-0" }), /* @__PURE__ */ l("span", { children: R.message })]
|
|
@@ -100,7 +100,7 @@ function C({ open: C, onOpenChange: w, workspaceId: T, projectId: E, projectName
|
|
|
100
100
|
className: "flex items-center gap-2",
|
|
101
101
|
children: [/* @__PURE__ */ u("div", {
|
|
102
102
|
className: "relative flex-1",
|
|
103
|
-
children: [/* @__PURE__ */ l(re, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-
|
|
103
|
+
children: [/* @__PURE__ */ l(re, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ l(f, {
|
|
104
104
|
value: A,
|
|
105
105
|
onChange: (e) => j(e.target.value),
|
|
106
106
|
placeholder: "Search workspace members",
|
|
@@ -119,22 +119,22 @@ function C({ open: C, onOpenChange: w, workspaceId: T, projectId: E, projectName
|
|
|
119
119
|
/* @__PURE__ */ l("div", {
|
|
120
120
|
className: "max-h-[480px] space-y-3 overflow-y-auto pr-1",
|
|
121
121
|
children: L ? null : I || ue || fe || J ? Array.from({ length: 5 }).map((e, t) => /* @__PURE__ */ u("div", {
|
|
122
|
-
className: "rounded-lg border bg-
|
|
122
|
+
className: "rounded-lg border bg-bg-surface p-4",
|
|
123
123
|
children: [/* @__PURE__ */ l(_, { className: "h-5 w-40" }), /* @__PURE__ */ l(_, { className: "mt-2 h-4 w-56" })]
|
|
124
124
|
}, t)) : G instanceof Error ? null : $.length === 0 ? /* @__PURE__ */ u("div", {
|
|
125
|
-
className: "rounded-lg border bg-
|
|
125
|
+
className: "rounded-lg border bg-bg-surface p-8 text-center",
|
|
126
126
|
children: [
|
|
127
|
-
/* @__PURE__ */ l(s, { className: "mx-auto mb-3 size-10 text-
|
|
127
|
+
/* @__PURE__ */ l(s, { className: "mx-auto mb-3 size-10 text-text-secondary" }),
|
|
128
128
|
/* @__PURE__ */ l("h3", {
|
|
129
129
|
className: "text-lg font-semibold",
|
|
130
130
|
children: "No eligible members found"
|
|
131
131
|
}),
|
|
132
132
|
/* @__PURE__ */ l("p", {
|
|
133
|
-
className: "mt-2 text-sm text-
|
|
133
|
+
className: "mt-2 text-sm text-text-secondary",
|
|
134
134
|
children: "All workspace members are already part of this project, or nothing matched your search."
|
|
135
135
|
}),
|
|
136
136
|
Z ? /* @__PURE__ */ u("p", {
|
|
137
|
-
className: "mt-3 text-xs text-
|
|
137
|
+
className: "mt-3 text-xs text-text-secondary",
|
|
138
138
|
children: [
|
|
139
139
|
"Loaded the first ",
|
|
140
140
|
x * b,
|
|
@@ -155,7 +155,7 @@ function C({ open: C, onOpenChange: w, workspaceId: T, projectId: E, projectName
|
|
|
155
155
|
}) : /* @__PURE__ */ u(c, { children: [$.map((e) => {
|
|
156
156
|
let t = Q[e.userId], n = S(e), r = k && le === e.userId;
|
|
157
157
|
return /* @__PURE__ */ u("div", {
|
|
158
|
-
className: "flex items-center justify-between gap-4 rounded-lg border bg-
|
|
158
|
+
className: "flex items-center justify-between gap-4 rounded-lg border bg-bg-surface p-4",
|
|
159
159
|
children: [/* @__PURE__ */ l("div", {
|
|
160
160
|
className: "min-w-0 flex-1",
|
|
161
161
|
children: t ? /* @__PURE__ */ l(v, {
|
|
@@ -167,10 +167,10 @@ function C({ open: C, onOpenChange: w, workspaceId: T, projectId: E, projectName
|
|
|
167
167
|
className: "truncate font-medium",
|
|
168
168
|
children: n.name
|
|
169
169
|
}), n.email ? /* @__PURE__ */ l("p", {
|
|
170
|
-
className: "truncate text-sm text-
|
|
170
|
+
className: "truncate text-sm text-text-secondary",
|
|
171
171
|
children: n.email
|
|
172
172
|
}) : /* @__PURE__ */ l("p", {
|
|
173
|
-
className: "truncate text-xs text-
|
|
173
|
+
className: "truncate text-xs text-text-secondary",
|
|
174
174
|
children: e.userId
|
|
175
175
|
})]
|
|
176
176
|
})
|
|
@@ -182,9 +182,9 @@ function C({ open: C, onOpenChange: w, workspaceId: T, projectId: E, projectName
|
|
|
182
182
|
})]
|
|
183
183
|
}, e.id);
|
|
184
184
|
}), z ? /* @__PURE__ */ u("div", {
|
|
185
|
-
className: "flex flex-col items-center gap-2 rounded-lg border border-dashed bg-
|
|
185
|
+
className: "flex flex-col items-center gap-2 rounded-lg border border-dashed bg-bg-sunken/20 p-4 text-center",
|
|
186
186
|
children: [/* @__PURE__ */ u("p", {
|
|
187
|
-
className: "text-sm text-
|
|
187
|
+
className: "text-sm text-text-secondary",
|
|
188
188
|
children: [
|
|
189
189
|
"Showing ",
|
|
190
190
|
Y.length,
|
|
@@ -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-destructive/20 bg-destructive/10 p-3 text-sm text-destructive\">\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-destructive/20 bg-destructive/10 p-3 text-sm text-destructive\">\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-destructive/20 bg-destructive/10 p-3 text-sm text-destructive\">\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-muted-foreground\" />\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-card 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-card p-8 text-center\">\n <UserPlus className=\"mx-auto mb-3 size-10 text-muted-foreground\" />\n <h3 className=\"text-lg font-semibold\">No eligible members found</h3>\n <p className=\"mt-2 text-sm text-muted-foreground\">\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-muted-foreground\">\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-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\">\n {fallback.email}\n </p>\n ) : (\n <p className=\"truncate text-xs text-muted-foreground\">\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-muted/20 p-4 text-center\">\n <p className=\"text-sm text-muted-foreground\">\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 { 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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"}
|