@burdenoff/microfe-organization 2026.527.4 → 2026.527.5

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.
@@ -0,0 +1,52 @@
1
+ import { UserRole as e } from "../../../../generated/global-types.js";
2
+ //#region src/organization-management/modules/members/utils/bulkInviteCsv.ts
3
+ var t = new Set([e.Member, e.OrgAdmin]), n = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4
+ function r(e) {
5
+ let t = [], n = "", r = !1;
6
+ for (let i = 0; i < e.length; i += 1) {
7
+ let a = e[i];
8
+ r ? a === "\"" ? e[i + 1] === "\"" ? (n += "\"", i += 1) : r = !1 : n += a : a === "\"" ? r = !0 : a === "," ? (t.push(n), n = "") : n += a;
9
+ }
10
+ return t.push(n), t.map((e) => e.trim());
11
+ }
12
+ function i(e) {
13
+ let i = e.replace(/^\uFEFF/, "").split(/\r?\n/).filter((e) => e.trim().length > 0);
14
+ if (i.length === 0) return [];
15
+ let a = r(i[0]).map((e) => e.toLowerCase()), o = a.indexOf("email");
16
+ if (o === -1) return [{
17
+ rowNumber: 1,
18
+ email: "",
19
+ errors: ["CSV header must include an \"email\" column."]
20
+ }];
21
+ let s = a.indexOf("role"), c = a.indexOf("message"), l = /* @__PURE__ */ new Set();
22
+ return i.slice(1).map((e, i) => {
23
+ let a = r(e), u = i + 2, d = (a[o] ?? "").toLowerCase(), f = s >= 0 ? (a[s] ?? "").toUpperCase() : "", p = c >= 0 ? a[c] ?? "" : "", m = [];
24
+ d ? n.test(d) ? l.has(d) ? m.push("Duplicate email in this CSV.") : l.add(d) : m.push("Invalid email format.") : m.push("Email is required.");
25
+ let h;
26
+ return f && (t.has(f) ? h = f : m.push(`Invalid role "${f}". Use MEMBER or ORG_ADMIN.`)), {
27
+ rowNumber: u,
28
+ email: d,
29
+ role: h,
30
+ message: p || void 0,
31
+ errors: m
32
+ };
33
+ });
34
+ }
35
+ var a = "invite-members-template.csv", o = [
36
+ "email,role,message",
37
+ "alice@example.com,MEMBER,Welcome to the team",
38
+ "bob@example.com,ORG_ADMIN,",
39
+ "carol@example.com,,"
40
+ ].join("\n");
41
+ function s(e, t) {
42
+ let n = e instanceof Error ? e.message : String(e);
43
+ return n.includes("CONFLICT") ? t("members.invite.error.conflict", "An invite already exists for this email address.") : n.includes("already a member") ? t("members.invite.error.alreadyMember", "This user is already a member of the organization.") : n;
44
+ }
45
+ function c() {
46
+ let e = new Blob([o], { type: "text/csv;charset=utf-8" }), t = URL.createObjectURL(e), n = document.createElement("a");
47
+ n.href = t, n.download = a, document.body.appendChild(n), n.click(), document.body.removeChild(n), URL.revokeObjectURL(t);
48
+ }
49
+ //#endregion
50
+ export { c as downloadTemplate, s as formatInviteError, i as parseBulkInviteCsv };
51
+
52
+ //# sourceMappingURL=bulkInviteCsv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulkInviteCsv.js","names":[],"sources":["../../../../../src/organization-management/modules/members/utils/bulkInviteCsv.ts"],"sourcesContent":["/**\n * Minimal CSV parsing + template generation for bulk member invites.\n * Hand-rolled (no csv lib dep) — invite CSVs are small enough to keep\n * the parser inline. Supports double-quoted fields with embedded commas\n * and escaped quotes (RFC 4180 subset).\n *\n * Expected columns (header row required, case-insensitive, any order):\n * email — required\n * role — optional, one of MEMBER | ADMIN | OWNER (case-insensitive)\n * message — optional, free text\n */\n\nimport { UserRole } from '../../../../generated/global-types';\n\nexport interface BulkInviteRow {\n rowNumber: number;\n email: string;\n role?: UserRole;\n message?: string;\n errors: string[];\n}\n\nconst VALID_ROLES = new Set<string>([UserRole.Member, UserRole.OrgAdmin]);\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/**\n * RFC 4180-subset CSV row parser. Handles double-quoted fields, embedded\n * commas and `\"\"` escapes. Trims surrounding whitespace per field but\n * preserves whitespace inside quotes.\n */\nfunction splitCsvLine(line: string): string[] {\n const fields: string[] = [];\n let current = '';\n let inQuotes = false;\n for (let i = 0; i < line.length; i += 1) {\n const ch = line[i];\n if (inQuotes) {\n if (ch === '\"') {\n if (line[i + 1] === '\"') {\n current += '\"';\n i += 1;\n } else {\n inQuotes = false;\n }\n } else {\n current += ch;\n }\n } else if (ch === '\"') {\n inQuotes = true;\n } else if (ch === ',') {\n fields.push(current);\n current = '';\n } else {\n current += ch;\n }\n }\n fields.push(current);\n return fields.map((f) => f.trim());\n}\n\nexport function parseBulkInviteCsv(text: string): BulkInviteRow[] {\n const lines = text\n .replace(/^\\uFEFF/, '')\n .split(/\\r?\\n/)\n .filter((l) => l.trim().length > 0);\n\n if (lines.length === 0) return [];\n\n const header = splitCsvLine(lines[0]).map((h) => h.toLowerCase());\n const emailIdx = header.indexOf('email');\n if (emailIdx === -1) {\n return [\n {\n rowNumber: 1,\n email: '',\n errors: ['CSV header must include an \"email\" column.'],\n },\n ];\n }\n const roleIdx = header.indexOf('role');\n const messageIdx = header.indexOf('message');\n\n const seen = new Set<string>();\n\n return lines.slice(1).map((line, idx) => {\n const fields = splitCsvLine(line);\n const rowNumber = idx + 2;\n const email = (fields[emailIdx] ?? '').toLowerCase();\n const rawRole = roleIdx >= 0 ? (fields[roleIdx] ?? '').toUpperCase() : '';\n const message = messageIdx >= 0 ? (fields[messageIdx] ?? '') : '';\n const errors: string[] = [];\n\n if (!email) {\n errors.push('Email is required.');\n } else if (!EMAIL_RE.test(email)) {\n errors.push('Invalid email format.');\n } else if (seen.has(email)) {\n errors.push('Duplicate email in this CSV.');\n } else {\n seen.add(email);\n }\n\n let role: UserRole | undefined;\n if (rawRole) {\n if (VALID_ROLES.has(rawRole)) {\n role = rawRole as UserRole;\n } else {\n errors.push(`Invalid role \"${rawRole}\". Use MEMBER or ORG_ADMIN.`);\n }\n }\n\n return {\n rowNumber,\n email,\n role,\n message: message || undefined,\n errors,\n };\n });\n}\n\n/**\n * Sample template content + filename. Includes a header row, a couple of\n * placeholder example rows, and a comment-style hint as a trailing row\n * users will likely delete.\n */\nexport const TEMPLATE_FILENAME = 'invite-members-template.csv';\n\nexport const TEMPLATE_CONTENT = [\n 'email,role,message',\n 'alice@example.com,MEMBER,Welcome to the team',\n 'bob@example.com,ORG_ADMIN,',\n 'carol@example.com,,',\n].join('\\n');\n\n/**\n * Map a thrown invite mutation error to a user-facing message, matching the\n * existing single-invite page's mapping (CONFLICT → already invited, etc).\n * Centralised here so single + bulk surfaces stay consistent.\n */\nexport function formatInviteError(\n error: unknown,\n tr: (key: string, fallback: string) => string\n): string {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes('CONFLICT')) {\n return tr('members.invite.error.conflict', 'An invite already exists for this email address.');\n }\n if (message.includes('already a member')) {\n return tr(\n 'members.invite.error.alreadyMember',\n 'This user is already a member of the organization.'\n );\n }\n return message;\n}\n\nexport function downloadTemplate(): void {\n const blob = new Blob([TEMPLATE_CONTENT], { type: 'text/csv;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = TEMPLATE_FILENAME;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n"],"mappings":";;AAsBA,IAAM,IAAc,IAAI,IAAY,CAAC,EAAS,QAAQ,EAAS,SAAS,CAAC,EAEnE,IAAW;AAOjB,SAAS,EAAa,GAAwB;CAC5C,IAAM,IAAmB,EAAE,EACvB,IAAU,IACV,IAAW;AACf,MAAK,IAAI,IAAI,GAAG,IAAI,EAAK,QAAQ,KAAK,GAAG;EACvC,IAAM,IAAK,EAAK;AAChB,EAAI,IACE,MAAO,OACL,EAAK,IAAI,OAAO,QAClB,KAAW,MACX,KAAK,KAEL,IAAW,KAGb,KAAW,IAEJ,MAAO,OAChB,IAAW,KACF,MAAO,OAChB,EAAO,KAAK,EAAQ,EACpB,IAAU,MAEV,KAAW;;AAIf,QADA,EAAO,KAAK,EAAQ,EACb,EAAO,KAAK,MAAM,EAAE,MAAM,CAAC;;AAGpC,SAAgB,EAAmB,GAA+B;CAChE,IAAM,IAAQ,EACX,QAAQ,WAAW,GAAG,CACtB,MAAM,QAAQ,CACd,QAAQ,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE;AAErC,KAAI,EAAM,WAAW,EAAG,QAAO,EAAE;CAEjC,IAAM,IAAS,EAAa,EAAM,GAAG,CAAC,KAAK,MAAM,EAAE,aAAa,CAAC,EAC3D,IAAW,EAAO,QAAQ,QAAQ;AACxC,KAAI,MAAa,GACf,QAAO,CACL;EACE,WAAW;EACX,OAAO;EACP,QAAQ,CAAC,+CAA6C;EACvD,CACF;CAEH,IAAM,IAAU,EAAO,QAAQ,OAAO,EAChC,IAAa,EAAO,QAAQ,UAAU,EAEtC,oBAAO,IAAI,KAAa;AAE9B,QAAO,EAAM,MAAM,EAAE,CAAC,KAAK,GAAM,MAAQ;EACvC,IAAM,IAAS,EAAa,EAAK,EAC3B,IAAY,IAAM,GAClB,KAAS,EAAO,MAAa,IAAI,aAAa,EAC9C,IAAU,KAAW,KAAK,EAAO,MAAY,IAAI,aAAa,GAAG,IACjE,IAAU,KAAc,IAAK,EAAO,MAAe,KAAM,IACzD,IAAmB,EAAE;AAE3B,EAAK,IAEO,EAAS,KAAK,EAAM,GAErB,EAAK,IAAI,EAAM,GACxB,EAAO,KAAK,+BAA+B,GAE3C,EAAK,IAAI,EAAM,GAJf,EAAO,KAAK,wBAAwB,GAFpC,EAAO,KAAK,qBAAqB;EASnC,IAAI;AASJ,SARI,MACE,EAAY,IAAI,EAAQ,GAC1B,IAAO,IAEP,EAAO,KAAK,iBAAiB,EAAQ,6BAA6B,GAI/D;GACL;GACA;GACA;GACA,SAAS,KAAW,KAAA;GACpB;GACD;GACD;;AAQJ,IAAa,IAAoB,+BAEpB,IAAmB;CAC9B;CACA;CACA;CACA;CACD,CAAC,KAAK,KAAK;AAOZ,SAAgB,EACd,GACA,GACQ;CACR,IAAM,IAAU,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM;AAUtE,QATI,EAAQ,SAAS,WAAW,GACvB,EAAG,iCAAiC,mDAAmD,GAE5F,EAAQ,SAAS,mBAAmB,GAC/B,EACL,sCACA,qDACD,GAEI;;AAGT,SAAgB,IAAyB;CACvC,IAAM,IAAO,IAAI,KAAK,CAAC,EAAiB,EAAE,EAAE,MAAM,0BAA0B,CAAC,EACvE,IAAM,IAAI,gBAAgB,EAAK,EAC/B,IAAI,SAAS,cAAc,IAAI;AAMrC,CALA,EAAE,OAAO,GACT,EAAE,WAAW,GACb,SAAS,KAAK,YAAY,EAAE,EAC5B,EAAE,OAAO,EACT,SAAS,KAAK,YAAY,EAAE,EAC5B,IAAI,gBAAgB,EAAI"}
@@ -98,6 +98,22 @@ function v({ open: v, onOpenChange: y, workspaceId: b, organizationId: x, existi
98
98
  },
99
99
  className: "mt-2 inline-flex items-center gap-2 text-sm font-medium text-[var(--color-action-primary-bg)] hover:underline",
100
100
  children: w("workspaces.addMember.inviteInstead", "Invite a new organization member instead")
101
+ }) : null,
102
+ C ? /* @__PURE__ */ _("p", {
103
+ className: "mt-2 text-xs text-[var(--color-text-muted)]",
104
+ children: [
105
+ w("workspaces.addMember.bulkInviteHint", "Need to add many people who are not in this organization yet?"),
106
+ " ",
107
+ /* @__PURE__ */ g("button", {
108
+ type: "button",
109
+ onClick: () => {
110
+ y(!1), C();
111
+ },
112
+ className: "font-medium text-[var(--color-action-primary-bg)] hover:underline",
113
+ children: w("workspaces.addMember.bulkInviteLink", "Bulk-invite them from the organization invite page")
114
+ }),
115
+ "."
116
+ ]
101
117
  }) : null
102
118
  ] }),
103
119
  /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
@@ -1 +1 @@
1
- {"version":3,"file":"AddWorkspaceMemberDialog.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { AlertCircle, Loader2, UserPlus } from 'lucide-react';\nimport { toast } from 'sonner';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useMembers } from '../../members/hooks/useMembers';\nimport { useAddWorkspaceMember } from '../hooks/useWorkspaceMembers';\nimport type { WorkspaceMember } from '../types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface AddWorkspaceMemberDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n workspaceId: string;\n organizationId: string;\n existingMembers: WorkspaceMember[];\n onInviteOrganizationMember?: () => void;\n}\n\nexport function AddWorkspaceMemberDialog({\n open,\n onOpenChange,\n workspaceId,\n organizationId,\n existingMembers,\n onInviteOrganizationMember,\n}: AddWorkspaceMemberDialogProps) {\n const tr = useTr();\n const [selectedUserId, setSelectedUserId] = useState('');\n const [selectedRole, setSelectedRole] = useState<'MEMBER' | 'WORKSPACE_ADMIN'>('MEMBER');\n const [submissionError, setSubmissionError] = useState<string | null>(null);\n\n const { members: organizationMembers, loading: membersLoading } = useMembers(\n // react-doctor-disable-next-line react-doctor/no-event-handler\n organizationId,\n 100,\n 0,\n // react-doctor-disable-next-line react-doctor/no-event-handler\n open\n );\n const { addWorkspaceMember, loading: addingMember } = useAddWorkspaceMember();\n\n const existingUserIds = useMemo(\n // react-doctor-disable-next-line react-doctor/no-event-handler\n () => new Set(existingMembers.map((member) => member.userId)),\n [existingMembers]\n );\n\n const availableMembers = useMemo(\n () => organizationMembers.filter((member) => !existingUserIds.has(member.userId)),\n [existingUserIds, organizationMembers]\n );\n\n useEffect(() => {\n if (!open) {\n setSelectedUserId('');\n setSelectedRole('MEMBER');\n setSubmissionError(null);\n return;\n }\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (!selectedUserId && availableMembers.length > 0) {\n setSelectedUserId(availableMembers[0]?.userId ?? '');\n }\n }, [availableMembers, open, selectedUserId]);\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!selectedUserId) {\n setSubmissionError(\n tr(\n 'workspaces.addMember.selectRequired',\n 'Select an organization member to add to this workspace.'\n )\n );\n return;\n }\n\n try {\n setSubmissionError(null);\n const result = await addWorkspaceMember({\n workspaceId,\n userId: selectedUserId,\n role: selectedRole,\n });\n\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n\n toast.success(tr('workspaces.addMember.success', 'Workspace member added successfully'));\n onOpenChange(false);\n } catch (error) {\n setSubmissionError(\n error instanceof Error\n ? error.message\n : tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-[520px] border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)]\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2 text-[var(--color-text-primary)]\">\n <UserPlus size={18} aria-hidden=\"true\" />\n {tr('workspaces.addMember.title', 'Add workspace member')}\n </DialogTitle>\n <DialogDescription className=\"text-[var(--color-text-secondary)]\">\n {tr(\n 'workspaces.addMember.description',\n 'Choose an existing organization member and assign their workspace role.'\n )}\n </DialogDescription>\n </DialogHeader>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {submissionError ? (\n <div className=\"flex items-start gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3 text-sm text-[var(--color-status-error-text)]\">\n <AlertCircle size={16} className=\"mt-0.5 shrink-0\" aria-hidden=\"true\" />\n <span>{submissionError}</span>\n </div>\n ) : null}\n\n <div>\n <label\n htmlFor=\"workspace-member-user\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.memberLabel', 'Organization member')}\n </label>\n <select\n id=\"workspace-member-user\"\n value={selectedUserId}\n onChange={(event) => setSelectedUserId(event.target.value)}\n disabled={membersLoading || addingMember || availableMembers.length === 0}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n {availableMembers.length === 0 ? (\n <option value=\"\">\n {tr(\n 'workspaces.addMember.noneAvailable',\n 'No eligible organization members available'\n )}\n </option>\n ) : null}\n {availableMembers.map((member) => (\n <option key={member.id} value={member.userId}>\n {member.userId} ({member.role})\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.addMember.filterHelp',\n 'Only members that belong to this organization and are not already in the workspace are listed.'\n )}\n </p>\n {availableMembers.length === 0 && onInviteOrganizationMember ? (\n <button\n type=\"button\"\n onClick={() => {\n onOpenChange(false);\n onInviteOrganizationMember();\n }}\n className=\"mt-2 inline-flex items-center gap-2 text-sm font-medium text-[var(--color-action-primary-bg)] hover:underline\"\n >\n {tr(\n 'workspaces.addMember.inviteInstead',\n 'Invite a new organization member instead'\n )}\n </button>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"workspace-member-role\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.roleLabel', 'Workspace role')}\n </label>\n <select\n id=\"workspace-member-role\"\n value={selectedRole}\n onChange={(event) =>\n setSelectedRole(event.target.value as 'MEMBER' | 'WORKSPACE_ADMIN')\n }\n disabled={addingMember}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n <option value=\"MEMBER\">{tr('members.role.member', 'Member')}</option>\n <option value=\"WORKSPACE_ADMIN\">\n {tr('members.role.workspaceAdmin', 'Workspace Admin')}\n </option>\n </select>\n </div>\n\n <DialogFooter className=\"gap-3 sm:gap-0\">\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n className=\"rounded-lg border border-[var(--color-border-default)] px-4 py-2 text-[var(--color-text-primary)] transition-colors hover:bg-[var(--color-bg-subtle)]\"\n disabled={addingMember}\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\n disabled={addingMember || membersLoading || availableMembers.length === 0}\n >\n {addingMember ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {tr('workspaces.addMember.submit', 'Add member')}\n </button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AddWorkspaceMemberDialog.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { AlertCircle, Loader2, UserPlus } from 'lucide-react';\nimport { toast } from 'sonner';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useMembers } from '../../members/hooks/useMembers';\nimport { useAddWorkspaceMember } from '../hooks/useWorkspaceMembers';\nimport type { WorkspaceMember } from '../types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface AddWorkspaceMemberDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n workspaceId: string;\n organizationId: string;\n existingMembers: WorkspaceMember[];\n onInviteOrganizationMember?: () => void;\n}\n\nexport function AddWorkspaceMemberDialog({\n open,\n onOpenChange,\n workspaceId,\n organizationId,\n existingMembers,\n onInviteOrganizationMember,\n}: AddWorkspaceMemberDialogProps) {\n const tr = useTr();\n const [selectedUserId, setSelectedUserId] = useState('');\n const [selectedRole, setSelectedRole] = useState<'MEMBER' | 'WORKSPACE_ADMIN'>('MEMBER');\n const [submissionError, setSubmissionError] = useState<string | null>(null);\n\n const { members: organizationMembers, loading: membersLoading } = useMembers(\n // react-doctor-disable-next-line react-doctor/no-event-handler\n organizationId,\n 100,\n 0,\n // react-doctor-disable-next-line react-doctor/no-event-handler\n open\n );\n const { addWorkspaceMember, loading: addingMember } = useAddWorkspaceMember();\n\n const existingUserIds = useMemo(\n // react-doctor-disable-next-line react-doctor/no-event-handler\n () => new Set(existingMembers.map((member) => member.userId)),\n [existingMembers]\n );\n\n const availableMembers = useMemo(\n () => organizationMembers.filter((member) => !existingUserIds.has(member.userId)),\n [existingUserIds, organizationMembers]\n );\n\n useEffect(() => {\n if (!open) {\n setSelectedUserId('');\n setSelectedRole('MEMBER');\n setSubmissionError(null);\n return;\n }\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (!selectedUserId && availableMembers.length > 0) {\n setSelectedUserId(availableMembers[0]?.userId ?? '');\n }\n }, [availableMembers, open, selectedUserId]);\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!selectedUserId) {\n setSubmissionError(\n tr(\n 'workspaces.addMember.selectRequired',\n 'Select an organization member to add to this workspace.'\n )\n );\n return;\n }\n\n try {\n setSubmissionError(null);\n const result = await addWorkspaceMember({\n workspaceId,\n userId: selectedUserId,\n role: selectedRole,\n });\n\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n\n toast.success(tr('workspaces.addMember.success', 'Workspace member added successfully'));\n onOpenChange(false);\n } catch (error) {\n setSubmissionError(\n error instanceof Error\n ? error.message\n : tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-[520px] border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)]\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2 text-[var(--color-text-primary)]\">\n <UserPlus size={18} aria-hidden=\"true\" />\n {tr('workspaces.addMember.title', 'Add workspace member')}\n </DialogTitle>\n <DialogDescription className=\"text-[var(--color-text-secondary)]\">\n {tr(\n 'workspaces.addMember.description',\n 'Choose an existing organization member and assign their workspace role.'\n )}\n </DialogDescription>\n </DialogHeader>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {submissionError ? (\n <div className=\"flex items-start gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3 text-sm text-[var(--color-status-error-text)]\">\n <AlertCircle size={16} className=\"mt-0.5 shrink-0\" aria-hidden=\"true\" />\n <span>{submissionError}</span>\n </div>\n ) : null}\n\n <div>\n <label\n htmlFor=\"workspace-member-user\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.memberLabel', 'Organization member')}\n </label>\n <select\n id=\"workspace-member-user\"\n value={selectedUserId}\n onChange={(event) => setSelectedUserId(event.target.value)}\n disabled={membersLoading || addingMember || availableMembers.length === 0}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n {availableMembers.length === 0 ? (\n <option value=\"\">\n {tr(\n 'workspaces.addMember.noneAvailable',\n 'No eligible organization members available'\n )}\n </option>\n ) : null}\n {availableMembers.map((member) => (\n <option key={member.id} value={member.userId}>\n {member.userId} ({member.role})\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.addMember.filterHelp',\n 'Only members that belong to this organization and are not already in the workspace are listed.'\n )}\n </p>\n {availableMembers.length === 0 && onInviteOrganizationMember ? (\n <button\n type=\"button\"\n onClick={() => {\n onOpenChange(false);\n onInviteOrganizationMember();\n }}\n className=\"mt-2 inline-flex items-center gap-2 text-sm font-medium text-[var(--color-action-primary-bg)] hover:underline\"\n >\n {tr(\n 'workspaces.addMember.inviteInstead',\n 'Invite a new organization member instead'\n )}\n </button>\n ) : null}\n {onInviteOrganizationMember ? (\n <p className=\"mt-2 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.addMember.bulkInviteHint',\n 'Need to add many people who are not in this organization yet?'\n )}{' '}\n <button\n type=\"button\"\n onClick={() => {\n onOpenChange(false);\n onInviteOrganizationMember();\n }}\n className=\"font-medium text-[var(--color-action-primary-bg)] hover:underline\"\n >\n {tr(\n 'workspaces.addMember.bulkInviteLink',\n 'Bulk-invite them from the organization invite page'\n )}\n </button>\n .\n </p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"workspace-member-role\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.roleLabel', 'Workspace role')}\n </label>\n <select\n id=\"workspace-member-role\"\n value={selectedRole}\n onChange={(event) =>\n setSelectedRole(event.target.value as 'MEMBER' | 'WORKSPACE_ADMIN')\n }\n disabled={addingMember}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n <option value=\"MEMBER\">{tr('members.role.member', 'Member')}</option>\n <option value=\"WORKSPACE_ADMIN\">\n {tr('members.role.workspaceAdmin', 'Workspace Admin')}\n </option>\n </select>\n </div>\n\n <DialogFooter className=\"gap-3 sm:gap-0\">\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n className=\"rounded-lg border border-[var(--color-border-default)] px-4 py-2 text-[var(--color-text-primary)] transition-colors hover:bg-[var(--color-bg-subtle)]\"\n disabled={addingMember}\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\n disabled={addingMember || membersLoading || availableMembers.length === 0}\n >\n {addingMember ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {tr('workspaces.addMember.submit', 'Add member')}\n </button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-organization",
3
- "version": "2026.527.4",
3
+ "version": "2026.527.5",
4
4
  "description": "Organization management microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [