@burdenoff/microfe-organization 2026.625.2 → 2026.626.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/organization-management/modules/members/components/InviteStatusBadge.js +6 -2
- package/dist/organization-management/modules/members/components/InviteStatusBadge.js.map +1 -1
- package/dist/organization-management/modules/members/pages/InviteMemberPage.js +58 -53
- package/dist/organization-management/modules/members/pages/InviteMemberPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/InvitePeoplePage.js +56 -52
- package/dist/organization-management/modules/members/pages/InvitePeoplePage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MemberDetailPage.js +72 -68
- package/dist/organization-management/modules/members/pages/MemberDetailPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MembersListPage.js +232 -225
- package/dist/organization-management/modules/members/pages/MembersListPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MyInvitesPage.js +172 -182
- package/dist/organization-management/modules/members/pages/MyInvitesPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js +132 -128
- package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js.map +1 -1
- package/dist/organization-management/modules/organizations/components/OrganizationStatusBadge.js +5 -1
- package/dist/organization-management/modules/organizations/components/OrganizationStatusBadge.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js +42 -37
- package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js +316 -266
- package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js +52 -47
- package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js +108 -96
- package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceStatusBadge.js +5 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceStatusBadge.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js +43 -38
- package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js +341 -286
- package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js +50 -45
- package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js +219 -209
- package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js.map +1 -1
- package/package.json +2 -2
- package/dist/organization-management/shared/components/feedback/EmptyState.js +0 -31
- package/dist/organization-management/shared/components/feedback/EmptyState.js.map +0 -1
|
@@ -13,6 +13,7 @@ function c({ status: c, showIcon: l = !0 }) {
|
|
|
13
13
|
case e.ACTIVE: return {
|
|
14
14
|
label: d("workspaces.status.active", "Active"),
|
|
15
15
|
icon: r,
|
|
16
|
+
pulse: !0,
|
|
16
17
|
className: "bg-[var(--color-status-success-bgSubtle)] text-[var(--color-status-success-text)] border-[var(--color-status-success-border)]"
|
|
17
18
|
};
|
|
18
19
|
case e.INACTIVE: return {
|
|
@@ -40,7 +41,10 @@ function c({ status: c, showIcon: l = !0 }) {
|
|
|
40
41
|
transition-colors duration-200
|
|
41
42
|
${f.className}
|
|
42
43
|
`,
|
|
43
|
-
children: [l && /* @__PURE__ */ a(p, {
|
|
44
|
+
children: [l && /* @__PURE__ */ a(p, {
|
|
45
|
+
size: 14,
|
|
46
|
+
className: "pulse" in f && f.pulse ? "motion-safe:animate-vc-glow-pulse" : ""
|
|
47
|
+
}), f.label]
|
|
44
48
|
});
|
|
45
49
|
}
|
|
46
50
|
//#endregion
|
package/dist/organization-management/modules/workspaces/components/WorkspaceStatusBadge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceStatusBadge.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceStatusBadge.tsx"],"sourcesContent":["import { CircleCheck, CircleX, CircleAlert } from 'lucide-react';\nimport { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WorkspaceStatus } from '../types';\n\ninterface WorkspaceStatusBadgeProps {\n status: WorkspaceStatus;\n showIcon?: boolean;\n}\n\n/**\n * WorkspaceStatusBadge\n * Displays the status of a workspace with color coding and optional icon\n */\nexport function WorkspaceStatusBadge({ status, showIcon = true }: WorkspaceStatusBadgeProps) {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t]\n );\n const getStatusConfig = () => {\n switch (status) {\n case WorkspaceStatus.ACTIVE:\n return {\n label: tr('workspaces.status.active', 'Active'),\n icon: CircleCheck,\n className:\n 'bg-[var(--color-status-success-bgSubtle)] text-[var(--color-status-success-text)] border-[var(--color-status-success-border)]',\n };\n case WorkspaceStatus.INACTIVE:\n return {\n label: tr('workspaces.status.inactive', 'Inactive'),\n icon: CircleX,\n className:\n 'bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)] border-[var(--color-border-default)]',\n };\n case WorkspaceStatus.ARCHIVED:\n return {\n label: tr('workspaces.status.archived', 'Archived'),\n icon: CircleAlert,\n className:\n 'bg-[var(--color-status-warning-bgSubtle)] text-[var(--color-status-warning-text)] border-[var(--color-status-warning-border)]',\n };\n default:\n return {\n label: status,\n icon: CircleAlert,\n className:\n 'bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)] border-[var(--color-border-default)]',\n };\n }\n };\n\n const config = getStatusConfig();\n const Icon = config.icon;\n\n return (\n <span\n className={`\n inline-flex items-center gap-1.5 px-2.5 py-1\n rounded-full text-xs font-medium\n border\n transition-colors duration-200\n ${config.className}\n `}\n >\n {showIcon && <Icon
|
|
1
|
+
{"version":3,"file":"WorkspaceStatusBadge.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceStatusBadge.tsx"],"sourcesContent":["import { CircleCheck, CircleX, CircleAlert } from 'lucide-react';\nimport { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WorkspaceStatus } from '../types';\n\ninterface WorkspaceStatusBadgeProps {\n status: WorkspaceStatus;\n showIcon?: boolean;\n}\n\n/**\n * WorkspaceStatusBadge\n * Displays the status of a workspace with color coding and optional icon\n */\nexport function WorkspaceStatusBadge({ status, showIcon = true }: WorkspaceStatusBadgeProps) {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t]\n );\n const getStatusConfig = () => {\n switch (status) {\n case WorkspaceStatus.ACTIVE:\n return {\n label: tr('workspaces.status.active', 'Active'),\n icon: CircleCheck,\n pulse: true,\n className:\n 'bg-[var(--color-status-success-bgSubtle)] text-[var(--color-status-success-text)] border-[var(--color-status-success-border)]',\n };\n case WorkspaceStatus.INACTIVE:\n return {\n label: tr('workspaces.status.inactive', 'Inactive'),\n icon: CircleX,\n className:\n 'bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)] border-[var(--color-border-default)]',\n };\n case WorkspaceStatus.ARCHIVED:\n return {\n label: tr('workspaces.status.archived', 'Archived'),\n icon: CircleAlert,\n className:\n 'bg-[var(--color-status-warning-bgSubtle)] text-[var(--color-status-warning-text)] border-[var(--color-status-warning-border)]',\n };\n default:\n return {\n label: status,\n icon: CircleAlert,\n className:\n 'bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)] border-[var(--color-border-default)]',\n };\n }\n };\n\n const config = getStatusConfig();\n const Icon = config.icon;\n\n return (\n <span\n className={`\n inline-flex items-center gap-1.5 px-2.5 py-1\n rounded-full text-xs font-medium\n border\n transition-colors duration-200\n ${config.className}\n `}\n >\n {showIcon && (\n <Icon\n size={14}\n className={'pulse' in config && config.pulse ? 'motion-safe:animate-vc-glow-pulse' : ''}\n />\n )}\n {config.label}\n </span>\n );\n}\n"],"mappings":";;;;;;AAcA,SAAgB,EAAqB,EAAE,WAAQ,cAAW,MAAmC;CAC3F,IAAM,EAAE,SAAM,GAAS,EACjB,IAAK,GACR,GAAa,MAAqB;EACjC,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEzC,CAAC,EAAE,CACJ,EAmCK,WAlCwB;AAC5B,UAAQ,GAAR;GACE,KAAK,EAAgB,OACnB,QAAO;IACL,OAAO,EAAG,4BAA4B,SAAS;IAC/C,MAAM;IACN,OAAO;IACP,WACE;IACH;GACH,KAAK,EAAgB,SACnB,QAAO;IACL,OAAO,EAAG,8BAA8B,WAAW;IACnD,MAAM;IACN,WACE;IACH;GACH,KAAK,EAAgB,SACnB,QAAO;IACL,OAAO,EAAG,8BAA8B,WAAW;IACnD,MAAM;IACN,WACE;IACH;GACH,QACE,QAAO;IACL,OAAO;IACP,MAAM;IACN,WACE;IACH;;KAIyB,EAC1B,IAAO,EAAO;AAEpB,QACE,kBAAC,QAAD;EACE,WAAW;;;;;UAKP,EAAO,UAAU;;YANvB,CASG,KACC,kBAAC,GAAD;GACE,MAAM;GACN,WAAW,WAAW,KAAU,EAAO,QAAQ,sCAAsC;GACrF,CAAA,EAEH,EAAO,MACH"}
|
|
@@ -6,73 +6,78 @@ import { LoadingState as i } from "../../../shared/components/feedback/LoadingSt
|
|
|
6
6
|
import { useCreateWorkspace as a } from "../hooks/useWorkspaces.js";
|
|
7
7
|
import { WorkspaceForm as o } from "../components/WorkspaceForm.js";
|
|
8
8
|
import { ArrowLeft as s } from "lucide-react";
|
|
9
|
-
import { GlassCard as c } from "@burdenoff/fe-libs/ui";
|
|
10
|
-
import { usePermissions as
|
|
11
|
-
import { jsx as
|
|
12
|
-
import { useSearchParams as
|
|
9
|
+
import { GlassCard as c, PagePurpose as l } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
import { usePermissions as u } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
11
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
12
|
+
import { useSearchParams as p } from "react-router-dom";
|
|
13
13
|
//#region src/organization-management/modules/workspaces/pages/WorkspaceCreatePage.tsx
|
|
14
|
-
function
|
|
15
|
-
let
|
|
14
|
+
function m() {
|
|
15
|
+
let m = r(), { navigate: h, basePath: g = "/organizations" } = e(), { isLoading: _ } = u(), { createWorkspace: v, loading: y } = a(), [b] = p(), x = b.get("organizationId") || void 0, { organization: S } = n(x || ""), C = async (e) => {
|
|
16
16
|
try {
|
|
17
|
-
let n = await
|
|
18
|
-
if (n.data?.createWorkspace) t.success(
|
|
17
|
+
let n = await v(e);
|
|
18
|
+
if (n.data?.createWorkspace) t.success(m("workspaces.create.success", "Workspace created successfully")), h(x ? `${g}/${x}` : `${g}/workspaces/${n.data.createWorkspace.id}`);
|
|
19
19
|
else if (n.errors && n.errors.length > 0) {
|
|
20
|
-
let e = n.errors[0]?.message ||
|
|
21
|
-
n.errors[0]?.extensions?.code === "UNAUTHENTICATED" ? t.error(
|
|
20
|
+
let e = n.errors[0]?.message || m("workspaces.create.failed", "Failed to create workspace");
|
|
21
|
+
n.errors[0]?.extensions?.code === "UNAUTHENTICATED" ? t.error(m("common.authRequired", "Authentication required. Please log in again.")) : t.error(e);
|
|
22
22
|
}
|
|
23
23
|
} catch (e) {
|
|
24
|
-
let n = e instanceof Error ? e.message :
|
|
24
|
+
let n = e instanceof Error ? e.message : m("common.unexpectedError", "An unexpected error occurred");
|
|
25
25
|
t.error(n);
|
|
26
26
|
}
|
|
27
|
-
},
|
|
28
|
-
|
|
27
|
+
}, w = () => {
|
|
28
|
+
h(x ? `${g}/${x}` : `${g}/workspaces`);
|
|
29
29
|
};
|
|
30
|
-
return
|
|
30
|
+
return _ ? /* @__PURE__ */ d("div", {
|
|
31
31
|
className: "min-h-full bg-[var(--color-bg-canvas)] p-6",
|
|
32
|
-
children: /* @__PURE__ */
|
|
33
|
-
}) : /* @__PURE__ */
|
|
32
|
+
children: /* @__PURE__ */ d(i, { message: m("common.loadingPermissions", "Loading permissions...") })
|
|
33
|
+
}) : /* @__PURE__ */ f("div", {
|
|
34
34
|
className: "min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-200",
|
|
35
|
-
children: [/* @__PURE__ */
|
|
35
|
+
children: [/* @__PURE__ */ f("div", {
|
|
36
36
|
className: "mb-6",
|
|
37
37
|
children: [
|
|
38
|
-
/* @__PURE__ */
|
|
38
|
+
/* @__PURE__ */ f("button", {
|
|
39
39
|
type: "button",
|
|
40
|
-
onClick:
|
|
40
|
+
onClick: w,
|
|
41
41
|
className: "\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n \n transition-all duration-200\n ",
|
|
42
|
-
children: [/* @__PURE__ */
|
|
42
|
+
children: [/* @__PURE__ */ d(s, { size: 20 }), /* @__PURE__ */ d("span", {
|
|
43
43
|
className: "text-sm font-medium",
|
|
44
|
-
children:
|
|
44
|
+
children: x ? `${m("common.backTo", "Back to")} ${S?.name || m("common.organization", "Organization")}` : m("workspaces.create.backToWorkspaces", "Back to Workspaces")
|
|
45
45
|
})]
|
|
46
46
|
}),
|
|
47
|
-
/* @__PURE__ */
|
|
47
|
+
/* @__PURE__ */ d("h1", {
|
|
48
48
|
className: "text-3xl font-bold text-[var(--color-text-primary)] mb-2",
|
|
49
|
-
children:
|
|
49
|
+
children: m("workspaces.create.title", "Create Workspace")
|
|
50
50
|
}),
|
|
51
|
-
/* @__PURE__ */
|
|
51
|
+
/* @__PURE__ */ d("p", {
|
|
52
52
|
className: "text-[var(--color-text-secondary)]",
|
|
53
|
-
children:
|
|
53
|
+
children: x && S ? `${m("workspaces.create.subtitleForOrg", "Add a new workspace to")} ${S.name}` : m("workspaces.create.subtitle", "Add a new workspace to an organization")
|
|
54
|
+
}),
|
|
55
|
+
/* @__PURE__ */ d(l, {
|
|
56
|
+
className: "mt-2 max-w-2xl",
|
|
57
|
+
children: m("workspaces.create.purpose", "A workspace gives a team or project its own dedicated space inside an organization. Create one to group the right people and their work — once it exists you can add members and start collaborating.")
|
|
54
58
|
})
|
|
55
59
|
]
|
|
56
|
-
}), /* @__PURE__ */
|
|
60
|
+
}), /* @__PURE__ */ d("div", {
|
|
57
61
|
className: "max-w-2xl",
|
|
58
|
-
children: /* @__PURE__ */
|
|
59
|
-
treatment: "
|
|
62
|
+
children: /* @__PURE__ */ d(c, {
|
|
63
|
+
treatment: "glass",
|
|
64
|
+
glow: !0,
|
|
60
65
|
className: "border-border-seam p-6",
|
|
61
|
-
children: /* @__PURE__ */
|
|
62
|
-
tenantId:
|
|
63
|
-
tenantName:
|
|
64
|
-
organizationId:
|
|
65
|
-
organizationName:
|
|
66
|
-
showOrganizationSelector: !
|
|
67
|
-
onSubmit:
|
|
68
|
-
onCancel:
|
|
69
|
-
loading:
|
|
66
|
+
children: /* @__PURE__ */ d(o, {
|
|
67
|
+
tenantId: S?.tenantId,
|
|
68
|
+
tenantName: S?.tenantId,
|
|
69
|
+
organizationId: x,
|
|
70
|
+
organizationName: S?.name,
|
|
71
|
+
showOrganizationSelector: !x,
|
|
72
|
+
onSubmit: C,
|
|
73
|
+
onCancel: w,
|
|
74
|
+
loading: y
|
|
70
75
|
})
|
|
71
76
|
})
|
|
72
77
|
})]
|
|
73
78
|
});
|
|
74
79
|
}
|
|
75
80
|
//#endregion
|
|
76
|
-
export {
|
|
81
|
+
export { m as WorkspaceCreatePage };
|
|
77
82
|
|
|
78
83
|
//# sourceMappingURL=WorkspaceCreatePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/pages/WorkspaceCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { useSearchParams } from 'react-router-dom';\nimport { toast } from 'sonner';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateWorkspace } from '../hooks/useWorkspaces';\nimport { useOrganization } from '../../organizations/hooks/useOrganizations';\nimport { WorkspaceForm } from '../components/WorkspaceForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput } from '../types';\n\nexport function WorkspaceCreatePage() {\n const tr = useTr();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n // `usePermissions` is still used for the permissionsLoading gate below\n // even though the static workspace:create page-level guard was removed\n // (see comment further down) — keep the hook call so the loading\n // skeleton still waits for the provider to settle before mounting the\n // form.\n const { isLoading: permissionsLoading } = usePermissions();\n const { createWorkspace, loading } = useCreateWorkspace();\n const [searchParams] = useSearchParams();\n\n // Get organizationId from URL query params (e.g., ?organizationId=xxx)\n const organizationIdFromUrl = searchParams.get('organizationId') || undefined;\n\n // Fetch organization details if ID is provided (to show org name and get tenantId)\n const { organization } = useOrganization(organizationIdFromUrl || '');\n\n const handleSubmit = async (data: CreateWorkspaceInput | UpdateWorkspaceInput) => {\n try {\n const createData = data as CreateWorkspaceInput;\n const result = await createWorkspace(createData);\n if (result.data?.createWorkspace) {\n toast.success(tr('workspaces.create.success', 'Workspace created successfully'));\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces/${result.data.createWorkspace.id}`);\n }\n } else if (result.errors && result.errors.length > 0) {\n // Handle GraphQL errors returned in result\n const errorMessage =\n result.errors[0]?.message || tr('workspaces.create.failed', 'Failed to create workspace');\n const errorCode = result.errors[0]?.extensions?.code;\n\n if (errorCode === 'UNAUTHENTICATED') {\n toast.error(tr('common.authRequired', 'Authentication required. Please log in again.'));\n } else {\n toast.error(errorMessage);\n }\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error\n ? error.message\n : tr('common.unexpectedError', 'An unexpected error occurred');\n toast.error(errorMessage);\n }\n };\n\n const handleCancel = () => {\n // Navigate back to organization if came from there, otherwise to workspaces list\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces`);\n }\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n // NOTE: deliberately NOT gating on hasPermission(WORKSPACE_CREATE) at the\n // page level. `usePermissions()` here resolves at the outer tenant scope\n // (from OrgConsoleProvider), and `workspace:create` is an org-scope\n // permission — a user who is Owner of even one org would be incorrectly\n // denied entry to the form. WorkspaceForm's organization dropdown is\n // permission-filtered (only orgs where the user actually has\n // workspace:create are listed), and the backend @rbac directive on the\n // createWorkspace mutation is the authoritative enforcement point. If a\n // user has no orgs they can create a workspace in, the dropdown will\n // surface that message and submit will stay disabled.\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-200\">\n <div className=\"mb-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n \n transition-all duration-200\n \"\n >\n <ArrowLeft size={20} />\n <span className=\"text-sm font-medium\">\n {organizationIdFromUrl\n ? `${tr('common.backTo', 'Back to')} ${organization?.name || tr('common.organization', 'Organization')}`\n : tr('workspaces.create.backToWorkspaces', 'Back to Workspaces')}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('workspaces.create.title', 'Create Workspace')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {organizationIdFromUrl && organization\n ? `${tr('workspaces.create.subtitleForOrg', 'Add a new workspace to')} ${organization.name}`\n : tr('workspaces.create.subtitle', 'Add a new workspace to an organization')}\n </p>\n </div>\n\n <div className=\"max-w-2xl\">\n <GlassCard treatment=\"
|
|
1
|
+
{"version":3,"file":"WorkspaceCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/pages/WorkspaceCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { useSearchParams } from 'react-router-dom';\nimport { toast } from 'sonner';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateWorkspace } from '../hooks/useWorkspaces';\nimport { useOrganization } from '../../organizations/hooks/useOrganizations';\nimport { WorkspaceForm } from '../components/WorkspaceForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput } from '../types';\n\nexport function WorkspaceCreatePage() {\n const tr = useTr();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n // `usePermissions` is still used for the permissionsLoading gate below\n // even though the static workspace:create page-level guard was removed\n // (see comment further down) — keep the hook call so the loading\n // skeleton still waits for the provider to settle before mounting the\n // form.\n const { isLoading: permissionsLoading } = usePermissions();\n const { createWorkspace, loading } = useCreateWorkspace();\n const [searchParams] = useSearchParams();\n\n // Get organizationId from URL query params (e.g., ?organizationId=xxx)\n const organizationIdFromUrl = searchParams.get('organizationId') || undefined;\n\n // Fetch organization details if ID is provided (to show org name and get tenantId)\n const { organization } = useOrganization(organizationIdFromUrl || '');\n\n const handleSubmit = async (data: CreateWorkspaceInput | UpdateWorkspaceInput) => {\n try {\n const createData = data as CreateWorkspaceInput;\n const result = await createWorkspace(createData);\n if (result.data?.createWorkspace) {\n toast.success(tr('workspaces.create.success', 'Workspace created successfully'));\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces/${result.data.createWorkspace.id}`);\n }\n } else if (result.errors && result.errors.length > 0) {\n // Handle GraphQL errors returned in result\n const errorMessage =\n result.errors[0]?.message || tr('workspaces.create.failed', 'Failed to create workspace');\n const errorCode = result.errors[0]?.extensions?.code;\n\n if (errorCode === 'UNAUTHENTICATED') {\n toast.error(tr('common.authRequired', 'Authentication required. Please log in again.'));\n } else {\n toast.error(errorMessage);\n }\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error\n ? error.message\n : tr('common.unexpectedError', 'An unexpected error occurred');\n toast.error(errorMessage);\n }\n };\n\n const handleCancel = () => {\n // Navigate back to organization if came from there, otherwise to workspaces list\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces`);\n }\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n // NOTE: deliberately NOT gating on hasPermission(WORKSPACE_CREATE) at the\n // page level. `usePermissions()` here resolves at the outer tenant scope\n // (from OrgConsoleProvider), and `workspace:create` is an org-scope\n // permission — a user who is Owner of even one org would be incorrectly\n // denied entry to the form. WorkspaceForm's organization dropdown is\n // permission-filtered (only orgs where the user actually has\n // workspace:create are listed), and the backend @rbac directive on the\n // createWorkspace mutation is the authoritative enforcement point. If a\n // user has no orgs they can create a workspace in, the dropdown will\n // surface that message and submit will stay disabled.\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-200\">\n <div className=\"mb-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n \n transition-all duration-200\n \"\n >\n <ArrowLeft size={20} />\n <span className=\"text-sm font-medium\">\n {organizationIdFromUrl\n ? `${tr('common.backTo', 'Back to')} ${organization?.name || tr('common.organization', 'Organization')}`\n : tr('workspaces.create.backToWorkspaces', 'Back to Workspaces')}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('workspaces.create.title', 'Create Workspace')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {organizationIdFromUrl && organization\n ? `${tr('workspaces.create.subtitleForOrg', 'Add a new workspace to')} ${organization.name}`\n : tr('workspaces.create.subtitle', 'Add a new workspace to an organization')}\n </p>\n <PagePurpose className=\"mt-2 max-w-2xl\">\n {tr(\n 'workspaces.create.purpose',\n 'A workspace gives a team or project its own dedicated space inside an organization. Create one to group the right people and their work — once it exists you can add members and start collaborating.'\n )}\n </PagePurpose>\n </div>\n\n <div className=\"max-w-2xl\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-6\">\n <WorkspaceForm\n tenantId={organization?.tenantId}\n tenantName={organization?.tenantId}\n organizationId={organizationIdFromUrl}\n organizationName={organization?.name}\n showOrganizationSelector={!organizationIdFromUrl}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </GlassCard>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAgB,IAAsB;CACpC,IAAM,IAAK,GAAO,EACZ,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAM3D,EAAE,WAAW,MAAuB,GAAgB,EACpD,EAAE,oBAAiB,eAAY,GAAoB,EACnD,CAAC,KAAgB,GAAiB,EAGlC,IAAwB,EAAa,IAAI,iBAAiB,IAAI,KAAA,GAG9D,EAAE,oBAAiB,EAAgB,KAAyB,GAAG,EAE/D,IAAe,OAAO,MAAsD;AAChF,MAAI;GAEF,IAAM,IAAS,MAAM,EADF,EAC6B;AAChD,OAAI,EAAO,MAAM,gBAEf,CADA,EAAM,QAAQ,EAAG,6BAA6B,iCAAiC,CAAC,EAE9E,EADE,IACO,GAAG,EAAS,GAAG,MAEf,GAAG,EAAS,cAAc,EAAO,KAAK,gBAAgB,KAAK;YAE7D,EAAO,UAAU,EAAO,OAAO,SAAS,GAAG;IAEpD,IAAM,IACJ,EAAO,OAAO,IAAI,WAAW,EAAG,4BAA4B,6BAA6B;AAG3F,IAFkB,EAAO,OAAO,IAAI,YAAY,SAE9B,oBAChB,EAAM,MAAM,EAAG,uBAAuB,gDAAgD,CAAC,GAEvF,EAAM,MAAM,EAAa;;WAGtB,GAAO;GACd,IAAM,IACJ,aAAiB,QACb,EAAM,UACN,EAAG,0BAA0B,+BAA+B;AAClE,KAAM,MAAM,EAAa;;IAIvB,UAAqB;AAEzB,EACE,EADE,IACO,GAAG,EAAS,GAAG,MAEf,GAAG,EAAS,aAAa;;AAuBtC,QAnBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAgBR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAWE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,kBAAC,QAAD;MAAM,WAAU;gBACb,IACG,GAAG,EAAG,iBAAiB,UAAU,CAAC,GAAG,GAAc,QAAQ,EAAG,uBAAuB,eAAe,KACpG,EAAG,sCAAsC,qBAAqB;MAC7D,CAAA,CACA;;IAET,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,2BAA2B,mBAAmB;KAC/C,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,KAAyB,IACtB,GAAG,EAAG,oCAAoC,yBAAyB,CAAC,GAAG,EAAa,SACpF,EAAG,8BAA8B,yCAAyC;KAC5E,CAAA;IACJ,kBAAC,GAAD;KAAa,WAAU;eACpB,EACC,6BACA,wMACD;KACW,CAAA;IACV;MAEN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IAAW,WAAU;IAAQ,MAAA;IAAK,WAAU;cAC1C,kBAAC,GAAD;KACE,UAAU,GAAc;KACxB,YAAY,GAAc;KAC1B,gBAAgB;KAChB,kBAAkB,GAAc;KAChC,0BAA0B,CAAC;KAC3B,UAAU;KACV,UAAU;KACD;KACT,CAAA;IACQ,CAAA;GACR,CAAA,CACF"}
|