@burdenoff/microfe-organization 2026.624.1 → 2026.625.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.
Files changed (45) hide show
  1. package/dist/organization-management/AdminOrganizationRoot.js +4 -4
  2. package/dist/organization-management/AdminOrganizationRoot.js.map +1 -1
  3. package/dist/organization-management/UserOrganizationRoot.js +4 -4
  4. package/dist/organization-management/UserOrganizationRoot.js.map +1 -1
  5. package/dist/organization-management/modules/members/components/BulkInviteForm.js +155 -174
  6. package/dist/organization-management/modules/members/components/BulkInviteForm.js.map +1 -1
  7. package/dist/organization-management/modules/members/components/PendingInvitesList.js +78 -101
  8. package/dist/organization-management/modules/members/components/PendingInvitesList.js.map +1 -1
  9. package/dist/organization-management/modules/members/pages/InviteMemberPage.js +58 -52
  10. package/dist/organization-management/modules/members/pages/InviteMemberPage.js.map +1 -1
  11. package/dist/organization-management/modules/members/pages/InvitePeoplePage.js +42 -40
  12. package/dist/organization-management/modules/members/pages/InvitePeoplePage.js.map +1 -1
  13. package/dist/organization-management/modules/members/pages/MemberDetailPage.js +71 -69
  14. package/dist/organization-management/modules/members/pages/MemberDetailPage.js.map +1 -1
  15. package/dist/organization-management/modules/members/pages/MembersListPage.js +233 -204
  16. package/dist/organization-management/modules/members/pages/MembersListPage.js.map +1 -1
  17. package/dist/organization-management/modules/members/pages/MyInvitesPage.js +38 -37
  18. package/dist/organization-management/modules/members/pages/MyInvitesPage.js.map +1 -1
  19. package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js +134 -132
  20. package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js.map +1 -1
  21. package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js +39 -37
  22. package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js.map +1 -1
  23. package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js +164 -167
  24. package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js.map +1 -1
  25. package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js +49 -47
  26. package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js.map +1 -1
  27. package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js +258 -230
  28. package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js.map +1 -1
  29. package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js +43 -50
  30. package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js.map +1 -1
  31. package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js +40 -38
  32. package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js.map +1 -1
  33. package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js +230 -299
  34. package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js.map +1 -1
  35. package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js +47 -45
  36. package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js.map +1 -1
  37. package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js +117 -74
  38. package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js.map +1 -1
  39. package/dist/organization-management/shared/components/feedback/EmptyState.js +1 -1
  40. package/dist/organization-management/shared/components/feedback/EmptyState.js.map +1 -1
  41. package/dist/organization-management/shared/components/feedback/ErrorState.js +1 -1
  42. package/dist/organization-management/shared/components/feedback/ErrorState.js.map +1 -1
  43. package/dist/organization-management/shared/components/feedback/LoadingState.js +1 -1
  44. package/dist/organization-management/shared/components/feedback/LoadingState.js.map +1 -1
  45. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"BulkInviteForm.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/BulkInviteForm.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react';\nimport { CheckCircle2, Download, Loader2, Send, Upload, XCircle } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport {\n type BulkInviteRow,\n downloadTemplate,\n formatInviteError,\n parseBulkInviteCsv,\n} from '../utils/bulkInviteCsv';\n\ninterface BulkInviteFormProps {\n organizationId: string;\n inviteMember: (input: {\n organizationId: string;\n email: string;\n message?: string;\n }) => Promise<unknown>;\n onCancel: () => void;\n loading?: boolean;\n}\n\ntype RowStatus =\n | { kind: 'idle' }\n | { kind: 'sending' }\n | { kind: 'invited' }\n | { kind: 'failed'; message: string };\n\ninterface PreviewRow extends BulkInviteRow {\n status: RowStatus;\n}\n\n/**\n * Bulk-invite via CSV upload. Sequentially fires the per-row invite\n * mutation (concurrency 1) so we don't hammer the gateway and so per-row\n * errors are deterministic. Reuses the same mutation as single-invite,\n * which keeps backend behaviour (refetch, OTel events) identical.\n */\nexport function BulkInviteForm({\n organizationId,\n inviteMember,\n onCancel,\n loading,\n}: BulkInviteFormProps) {\n const tr = useTr();\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [rows, setRows] = useState<PreviewRow[]>([]);\n const [fileName, setFileName] = useState<string | null>(null);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [dragActive, setDragActive] = useState(false);\n\n const handleFile = useCallback(\n (file: File) => {\n const reader = new FileReader();\n reader.onload = () => {\n const text = typeof reader.result === 'string' ? reader.result : '';\n const parsed = parseBulkInviteCsv(text);\n setRows(parsed.map((r) => ({ ...r, status: { kind: 'idle' } })));\n setFileName(file.name);\n };\n reader.onerror = () => {\n toast.error(tr('members.bulkInvite.readError', 'Failed to read CSV file.'));\n };\n reader.readAsText(file);\n },\n [tr]\n );\n\n const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (file) handleFile(file);\n // Reset so re-uploading the same file fires onChange again\n e.target.value = '';\n };\n\n const onDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n setDragActive(false);\n const file = e.dataTransfer.files?.[0];\n if (file) handleFile(file);\n };\n\n const summary = useMemo(() => {\n const total = rows.length;\n const withErrors = rows.filter((r) => r.errors.length > 0).length;\n const sendable = rows.filter((r) => r.errors.length === 0).length;\n const invited = rows.filter((r) => r.status.kind === 'invited').length;\n const failed = rows.filter((r) => r.status.kind === 'failed').length;\n return { total, withErrors, sendable, invited, failed };\n }, [rows]);\n\n const handleSend = async () => {\n if (summary.sendable === 0 || isSubmitting) return;\n setIsSubmitting(true);\n\n const sendableIndices = rows\n .map((r, idx) => ({ r, idx }))\n .filter(({ r }) => r.errors.length === 0 && r.status.kind !== 'invited');\n\n for (const { idx } of sendableIndices) {\n setRows((prev) =>\n prev.map((row, i) => (i === idx ? { ...row, status: { kind: 'sending' } } : row))\n );\n const row = rows[idx];\n try {\n await inviteMember({\n organizationId,\n email: row.email,\n message: row.message,\n });\n setRows((prev) =>\n prev.map((r, i) => (i === idx ? { ...r, status: { kind: 'invited' } } : r))\n );\n } catch (err) {\n const message = formatInviteError(err, tr);\n setRows((prev) =>\n prev.map((r, i) => (i === idx ? { ...r, status: { kind: 'failed', message } } : r))\n );\n }\n }\n\n setIsSubmitting(false);\n setRows((prev) => {\n const invited = prev.filter((r) => r.status.kind === 'invited').length;\n const failed = prev.filter((r) => r.status.kind === 'failed').length;\n toast.success(\n `${tr('members.bulkInvite.summaryInvited', 'Invited')} ${invited} ${tr('members.bulkInvite.summaryOf', 'of')} ${prev.length}. ${failed} ${tr('members.bulkInvite.summaryFailed', 'failed')}.`\n );\n return prev;\n });\n };\n\n const hasRows = rows.length > 0;\n const canSubmit = summary.sendable > 0 && !isSubmitting && !loading;\n\n return (\n <div className=\"space-y-5\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <p className=\"text-sm text-[var(--color-text-muted)]\">\n {tr(\n 'members.bulkInvite.description',\n 'Upload a CSV with one row per person. Required column: email. Optional: role, message.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={downloadTemplate}\n className=\"inline-flex min-h-10 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] transition-colors hover:bg-[var(--color-bg-subtle)]\"\n >\n <Download size={16} aria-hidden=\"true\" />\n {tr('members.bulkInvite.downloadTemplate', 'Download template')}\n </button>\n </div>\n\n <div\n onDragOver={(e) => {\n e.preventDefault();\n setDragActive(true);\n }}\n onDragLeave={() => setDragActive(false)}\n onDrop={onDrop}\n className={`flex flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed p-8 transition-colors ${\n dragActive\n ? 'border-[var(--color-action-primary-bg)] bg-[var(--color-status-info-bgSubtle)]'\n : 'border-[var(--color-border-default)] bg-[var(--color-bg-canvas)]'\n }`}\n >\n <Upload size={28} className=\"text-[var(--color-text-secondary)]\" aria-hidden=\"true\" />\n <div className=\"text-center\">\n <p className=\"text-sm font-medium text-[var(--color-text-primary)]\">\n {fileName\n ? fileName\n : tr('members.bulkInvite.dropHere', 'Drop a CSV file here or click to browse')}\n </p>\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr('members.bulkInvite.csvOnly', 'CSV files only. Max 1000 rows recommended.')}\n </p>\n </div>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".csv,text/csv\"\n onChange={onFileChange}\n className=\"hidden\"\n aria-label={tr('members.bulkInvite.fileInputLabel', 'Choose CSV file')}\n />\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n className=\"inline-flex min-h-10 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2 text-sm text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90\"\n >\n {tr('members.bulkInvite.chooseFile', 'Choose file')}\n </button>\n </div>\n\n {hasRows && (\n <>\n <div className=\"flex flex-wrap gap-4 text-xs text-[var(--color-text-secondary)]\">\n <span>\n {tr('members.bulkInvite.statsTotal', 'Total rows')}: {summary.total}\n </span>\n <span>\n {tr('members.bulkInvite.statsValid', 'Ready')}: {summary.sendable}\n </span>\n {summary.withErrors > 0 && (\n <span className=\"text-[var(--color-status-error-text)]\">\n {tr('members.bulkInvite.statsErrors', 'With errors')}: {summary.withErrors}\n </span>\n )}\n {summary.invited > 0 && (\n <span className=\"text-[var(--color-status-success-text)]\">\n {tr('members.bulkInvite.statsInvited', 'Invited')}: {summary.invited}\n </span>\n )}\n {summary.failed > 0 && (\n <span className=\"text-[var(--color-status-error-text)]\">\n {tr('members.bulkInvite.statsFailed', 'Failed')}: {summary.failed}\n </span>\n )}\n </div>\n\n <div className=\"overflow-hidden rounded-xl border border-[var(--color-border-default)]\">\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full\">\n <thead className=\"bg-[var(--color-bg-subtle)]\">\n <tr>\n <th className=\"px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n #\n </th>\n <th className=\"px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.bulkInvite.colEmail', 'Email')}\n </th>\n <th className=\"px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.bulkInvite.colRole', 'Role')}\n </th>\n <th className=\"px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.bulkInvite.colStatus', 'Status')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-[var(--color-border-default)]\">\n {rows.map((row) => {\n const hasParseErrors = row.errors.length > 0;\n return (\n <tr\n key={`${row.rowNumber}-${row.email}`}\n className=\"bg-[var(--color-bg-surface)]\"\n >\n <td className=\"px-3 py-2 text-xs text-[var(--color-text-secondary)]\">\n {row.rowNumber}\n </td>\n <td className=\"px-3 py-2 text-sm text-[var(--color-text-primary)]\">\n {row.email || (\n <span className=\"italic text-[var(--color-text-muted)]\">\n {tr('members.bulkInvite.empty', '(empty)')}\n </span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-[var(--color-text-secondary)]\">\n {row.role || (\n <span className=\"text-[var(--color-text-muted)]\">MEMBER</span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs\">\n {hasParseErrors ? (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-error-text)]\">\n <XCircle size={14} aria-hidden=\"true\" />\n {row.errors.join(' ')}\n </span>\n ) : row.status.kind === 'sending' ? (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-text-secondary)]\">\n <Loader2 size={14} className=\"animate-spin\" aria-hidden=\"true\" />\n {tr('members.bulkInvite.statusSending', 'Sending...')}\n </span>\n ) : row.status.kind === 'invited' ? (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-success-text)]\">\n <CheckCircle2 size={14} aria-hidden=\"true\" />\n {tr('members.bulkInvite.statusInvited', 'Invited')}\n </span>\n ) : row.status.kind === 'failed' ? (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-error-text)]\">\n <XCircle size={14} aria-hidden=\"true\" />\n {row.status.message}\n </span>\n ) : (\n <span className=\"text-[var(--color-text-secondary)]\">\n {tr('members.bulkInvite.statusReady', 'Ready')}\n </span>\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </div>\n </>\n )}\n\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"button\"\n onClick={handleSend}\n disabled={!canSubmit}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[200px]\"\n >\n {isSubmitting ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Send size={16} aria-hidden=\"true\" />\n )}\n {tr('members.bulkInvite.send', 'Send')} {summary.sendable}{' '}\n {tr('members.bulkInvite.sendSuffix', 'invites')}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={isSubmitting}\n className=\"rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[140px]\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BulkInviteForm.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/BulkInviteForm.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react';\nimport { CheckCircle2, Download, Loader2, Send, Upload, XCircle } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { ResponsiveTable, type ResponsiveTableColumn } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport {\n type BulkInviteRow,\n downloadTemplate,\n formatInviteError,\n parseBulkInviteCsv,\n} from '../utils/bulkInviteCsv';\n\ninterface BulkInviteFormProps {\n organizationId: string;\n inviteMember: (input: {\n organizationId: string;\n email: string;\n message?: string;\n }) => Promise<unknown>;\n onCancel: () => void;\n loading?: boolean;\n}\n\ntype RowStatus =\n | { kind: 'idle' }\n | { kind: 'sending' }\n | { kind: 'invited' }\n | { kind: 'failed'; message: string };\n\ninterface PreviewRow extends BulkInviteRow {\n status: RowStatus;\n}\n\n/**\n * Bulk-invite via CSV upload. Sequentially fires the per-row invite\n * mutation (concurrency 1) so we don't hammer the gateway and so per-row\n * errors are deterministic. Reuses the same mutation as single-invite,\n * which keeps backend behaviour (refetch, OTel events) identical.\n */\nexport function BulkInviteForm({\n organizationId,\n inviteMember,\n onCancel,\n loading,\n}: BulkInviteFormProps) {\n const tr = useTr();\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [rows, setRows] = useState<PreviewRow[]>([]);\n const [fileName, setFileName] = useState<string | null>(null);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [dragActive, setDragActive] = useState(false);\n\n const handleFile = useCallback(\n (file: File) => {\n const reader = new FileReader();\n reader.onload = () => {\n const text = typeof reader.result === 'string' ? reader.result : '';\n const parsed = parseBulkInviteCsv(text);\n setRows(parsed.map((r) => ({ ...r, status: { kind: 'idle' } })));\n setFileName(file.name);\n };\n reader.onerror = () => {\n toast.error(tr('members.bulkInvite.readError', 'Failed to read CSV file.'));\n };\n reader.readAsText(file);\n },\n [tr]\n );\n\n const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (file) handleFile(file);\n // Reset so re-uploading the same file fires onChange again\n e.target.value = '';\n };\n\n const onDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n setDragActive(false);\n const file = e.dataTransfer.files?.[0];\n if (file) handleFile(file);\n };\n\n const summary = useMemo(() => {\n const total = rows.length;\n const withErrors = rows.filter((r) => r.errors.length > 0).length;\n const sendable = rows.filter((r) => r.errors.length === 0).length;\n const invited = rows.filter((r) => r.status.kind === 'invited').length;\n const failed = rows.filter((r) => r.status.kind === 'failed').length;\n return { total, withErrors, sendable, invited, failed };\n }, [rows]);\n\n const handleSend = async () => {\n if (summary.sendable === 0 || isSubmitting) return;\n setIsSubmitting(true);\n\n const sendableIndices = rows\n .map((r, idx) => ({ r, idx }))\n .filter(({ r }) => r.errors.length === 0 && r.status.kind !== 'invited');\n\n for (const { idx } of sendableIndices) {\n setRows((prev) =>\n prev.map((row, i) => (i === idx ? { ...row, status: { kind: 'sending' } } : row))\n );\n const row = rows[idx];\n try {\n await inviteMember({\n organizationId,\n email: row.email,\n message: row.message,\n });\n setRows((prev) =>\n prev.map((r, i) => (i === idx ? { ...r, status: { kind: 'invited' } } : r))\n );\n } catch (err) {\n const message = formatInviteError(err, tr);\n setRows((prev) =>\n prev.map((r, i) => (i === idx ? { ...r, status: { kind: 'failed', message } } : r))\n );\n }\n }\n\n setIsSubmitting(false);\n setRows((prev) => {\n const invited = prev.filter((r) => r.status.kind === 'invited').length;\n const failed = prev.filter((r) => r.status.kind === 'failed').length;\n toast.success(\n `${tr('members.bulkInvite.summaryInvited', 'Invited')} ${invited} ${tr('members.bulkInvite.summaryOf', 'of')} ${prev.length}. ${failed} ${tr('members.bulkInvite.summaryFailed', 'failed')}.`\n );\n return prev;\n });\n };\n\n const hasRows = rows.length > 0;\n const canSubmit = summary.sendable > 0 && !isSubmitting && !loading;\n\n const renderRowStatus = (row: PreviewRow) => {\n const hasParseErrors = row.errors.length > 0;\n if (hasParseErrors) {\n return (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-error-text)]\">\n <XCircle size={14} aria-hidden=\"true\" />\n {row.errors.join(' ')}\n </span>\n );\n }\n if (row.status.kind === 'sending') {\n return (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-text-secondary)]\">\n <Loader2 size={14} className=\"animate-spin\" aria-hidden=\"true\" />\n {tr('members.bulkInvite.statusSending', 'Sending...')}\n </span>\n );\n }\n if (row.status.kind === 'invited') {\n return (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-success-text)]\">\n <CheckCircle2 size={14} aria-hidden=\"true\" />\n {tr('members.bulkInvite.statusInvited', 'Invited')}\n </span>\n );\n }\n if (row.status.kind === 'failed') {\n return (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-error-text)]\">\n <XCircle size={14} aria-hidden=\"true\" />\n {row.status.message}\n </span>\n );\n }\n return (\n <span className=\"text-[var(--color-text-secondary)]\">\n {tr('members.bulkInvite.statusReady', 'Ready')}\n </span>\n );\n };\n\n const previewColumns: ResponsiveTableColumn<PreviewRow>[] = [\n {\n key: 'rowNumber',\n header: '#',\n priority: 'secondary',\n cell: (row) => (\n <span className=\"text-xs tabular-nums text-[var(--color-text-secondary)]\">\n {row.rowNumber}\n </span>\n ),\n },\n {\n key: 'email',\n header: tr('members.bulkInvite.colEmail', 'Email'),\n priority: 'primary',\n cell: (row) =>\n row.email || (\n <span className=\"italic text-[var(--color-text-muted)]\">\n {tr('members.bulkInvite.empty', '(empty)')}\n </span>\n ),\n },\n {\n key: 'role',\n header: tr('members.bulkInvite.colRole', 'Role'),\n priority: 'secondary',\n cell: (row) => row.role || <span className=\"text-[var(--color-text-muted)]\">MEMBER</span>,\n },\n {\n key: 'status',\n header: tr('members.bulkInvite.colStatus', 'Status'),\n priority: 'secondary',\n cell: (row) => renderRowStatus(row),\n },\n ];\n\n return (\n <div className=\"space-y-5\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <p className=\"text-sm text-[var(--color-text-muted)]\">\n {tr(\n 'members.bulkInvite.description',\n 'Upload a CSV with one row per person. Required column: email. Optional: role, message.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={downloadTemplate}\n className=\"inline-flex min-h-10 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] transition-colors hover:bg-[var(--color-bg-subtle)]\"\n >\n <Download size={16} aria-hidden=\"true\" />\n {tr('members.bulkInvite.downloadTemplate', 'Download template')}\n </button>\n </div>\n\n <div\n onDragOver={(e) => {\n e.preventDefault();\n setDragActive(true);\n }}\n onDragLeave={() => setDragActive(false)}\n onDrop={onDrop}\n className={`flex flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed p-8 transition-colors ${\n dragActive\n ? 'border-[var(--color-action-primary-bg)] bg-[var(--color-status-info-bgSubtle)]'\n : 'border-[var(--color-border-default)] bg-[var(--color-bg-canvas)]'\n }`}\n >\n <Upload size={28} className=\"text-[var(--color-text-secondary)]\" aria-hidden=\"true\" />\n <div className=\"text-center\">\n <p className=\"text-sm font-medium text-[var(--color-text-primary)]\">\n {fileName\n ? fileName\n : tr('members.bulkInvite.dropHere', 'Drop a CSV file here or click to browse')}\n </p>\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr('members.bulkInvite.csvOnly', 'CSV files only. Max 1000 rows recommended.')}\n </p>\n </div>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".csv,text/csv\"\n onChange={onFileChange}\n className=\"hidden\"\n aria-label={tr('members.bulkInvite.fileInputLabel', 'Choose CSV file')}\n />\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n className=\"inline-flex min-h-10 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2 text-sm text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90\"\n >\n {tr('members.bulkInvite.chooseFile', 'Choose file')}\n </button>\n </div>\n\n {hasRows && (\n <>\n <div className=\"flex flex-wrap gap-4 text-xs text-[var(--color-text-secondary)]\">\n <span>\n {tr('members.bulkInvite.statsTotal', 'Total rows')}: {summary.total}\n </span>\n <span>\n {tr('members.bulkInvite.statsValid', 'Ready')}: {summary.sendable}\n </span>\n {summary.withErrors > 0 && (\n <span className=\"text-[var(--color-status-error-text)]\">\n {tr('members.bulkInvite.statsErrors', 'With errors')}: {summary.withErrors}\n </span>\n )}\n {summary.invited > 0 && (\n <span className=\"text-[var(--color-status-success-text)]\">\n {tr('members.bulkInvite.statsInvited', 'Invited')}: {summary.invited}\n </span>\n )}\n {summary.failed > 0 && (\n <span className=\"text-[var(--color-status-error-text)]\">\n {tr('members.bulkInvite.statsFailed', 'Failed')}: {summary.failed}\n </span>\n )}\n </div>\n\n <div className=\"rounded-xl border border-[var(--color-border-default)] p-2\">\n <ResponsiveTable\n columns={previewColumns}\n data={rows}\n rowKey={(row) => `${row.rowNumber}-${row.email}`}\n />\n </div>\n </>\n )}\n\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"button\"\n onClick={handleSend}\n disabled={!canSubmit}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[200px]\"\n >\n {isSubmitting ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Send size={16} aria-hidden=\"true\" />\n )}\n {tr('members.bulkInvite.send', 'Send')} {summary.sendable}{' '}\n {tr('members.bulkInvite.sendSuffix', 'invites')}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={isSubmitting}\n className=\"rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[140px]\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -6,141 +6,118 @@ import { InviteStatusBadge as o } from "./InviteStatusBadge.js";
6
6
  import { MemberRoleBadge as s } from "./MemberRoleBadge.js";
7
7
  import { useState as c } from "react";
8
8
  import { AlertCircle as l, Loader2 as u, RotateCw as d, X as f } from "lucide-react";
9
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
9
+ import { ResponsiveTable as p } from "@burdenoff/fe-libs/ui";
10
+ import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
10
11
  //#region src/organization-management/modules/members/components/PendingInvitesList.tsx
11
- function h({ organizationId: h }) {
12
- let g = a(), { invites: _, loading: v, error: y, total: b } = n(h, { status: i.Pending }, 50), { cancelInvite: x, loading: S } = t(), { resendInvite: C, loading: w } = r(), [T, E] = c(null);
13
- if (v && _.length === 0) return /* @__PURE__ */ p("div", {
12
+ function _({ organizationId: _ }) {
13
+ let v = a(), { invites: y, loading: b, error: x, total: S } = n(_, { status: i.Pending }, 50), { cancelInvite: C, loading: w } = t(), { resendInvite: T, loading: E } = r(), [D, O] = c(null);
14
+ if (b && y.length === 0) return /* @__PURE__ */ h("div", {
14
15
  className: "flex items-center justify-center py-8",
15
- children: /* @__PURE__ */ p(u, {
16
+ children: /* @__PURE__ */ h(u, {
16
17
  size: 20,
17
18
  className: "animate-spin text-[var(--color-text-muted)]"
18
19
  })
19
20
  });
20
- if (y) return /* @__PURE__ */ m("p", {
21
+ if (x) return /* @__PURE__ */ g("p", {
21
22
  className: "py-6 text-center text-sm text-[var(--color-status-error-text)]",
22
23
  children: [
23
- g("members.myInvites.loadFailed", "Failed to load invites"),
24
+ v("members.myInvites.loadFailed", "Failed to load invites"),
24
25
  ": ",
25
- y.message
26
+ x.message
26
27
  ]
27
28
  });
28
- if (_.length === 0) return /* @__PURE__ */ p("p", {
29
+ if (y.length === 0) return /* @__PURE__ */ h("p", {
29
30
  className: "text-sm text-[var(--color-text-muted)] text-center py-6",
30
- children: g("members.myInvites.emptyTitle", "No pending invites")
31
+ children: v("members.myInvites.emptyTitle", "No pending invites")
31
32
  });
32
- let D = async (t, n) => {
33
+ let k = async (t, n) => {
33
34
  try {
34
- E(null), await x(t), e.success(`${g("members.invite.cancelSuccess", "Invite cancelled for")} ${n}`);
35
+ O(null), await C(t), e.success(`${v("members.invite.cancelSuccess", "Invite cancelled for")} ${n}`);
35
36
  } catch (t) {
36
- let n = t instanceof Error ? t.message : g("members.invite.cancelFailed", "Failed to cancel invite");
37
- E(n), e.error(n);
37
+ let n = t instanceof Error ? t.message : v("members.invite.cancelFailed", "Failed to cancel invite");
38
+ O(n), e.error(n);
38
39
  }
39
- }, O = async (t, n) => {
40
+ }, A = async (t, n) => {
40
41
  try {
41
- E(null), await C(t), e.success(`${g("members.invite.resendSuccess", "Invite re-sent to")} ${n}`);
42
+ O(null), await T(t), e.success(`${v("members.invite.resendSuccess", "Invite re-sent to")} ${n}`);
42
43
  } catch (t) {
43
- let n = t instanceof Error ? t.message : g("members.invite.resendFailed", "Failed to resend invite");
44
- E(n), e.error(n);
44
+ let n = t instanceof Error ? t.message : v("members.invite.resendFailed", "Failed to resend invite");
45
+ O(n), e.error(n);
45
46
  }
46
- };
47
- return /* @__PURE__ */ m("div", { children: [
48
- /* @__PURE__ */ m("h3", {
47
+ }, j = [
48
+ {
49
+ key: "email",
50
+ header: v("members.invite.emailLabel", "Email Address"),
51
+ priority: "primary",
52
+ cell: (e) => /* @__PURE__ */ h("span", {
53
+ className: "text-[var(--color-text-primary)]",
54
+ children: e.email
55
+ })
56
+ },
57
+ {
58
+ key: "role",
59
+ header: v("members.list.column.role", "Role"),
60
+ cell: (e) => /* @__PURE__ */ h(s, { role: e.role })
61
+ },
62
+ {
63
+ key: "status",
64
+ header: v("common.status", "Status"),
65
+ cell: (e) => /* @__PURE__ */ h(o, { status: e.status })
66
+ },
67
+ {
68
+ key: "expires",
69
+ header: v("members.invite.expires", "Expires"),
70
+ cell: (e) => /* @__PURE__ */ h("span", {
71
+ className: "text-[var(--color-text-muted)]",
72
+ children: new Date(e.expiresAt).toLocaleDateString()
73
+ })
74
+ }
75
+ ];
76
+ return /* @__PURE__ */ g("div", { children: [
77
+ /* @__PURE__ */ g("h3", {
49
78
  className: "text-sm font-semibold text-[var(--color-text-secondary)] uppercase tracking-wider mb-3",
50
79
  children: [
51
- g("members.invite.pendingTitle", "Pending Invites"),
80
+ v("members.invite.pendingTitle", "Pending Invites"),
52
81
  " (",
53
- b,
82
+ S,
54
83
  ")"
55
84
  ]
56
85
  }),
57
- T && /* @__PURE__ */ m("div", {
86
+ D && /* @__PURE__ */ g("div", {
58
87
  className: "mb-3 flex items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-2.5",
59
- children: [/* @__PURE__ */ p(l, {
88
+ children: [/* @__PURE__ */ h(l, {
60
89
  size: 14,
61
90
  className: "shrink-0 text-[var(--color-status-error-text)]"
62
- }), /* @__PURE__ */ p("p", {
91
+ }), /* @__PURE__ */ h("p", {
63
92
  className: "text-sm text-[var(--color-status-error-text)]",
64
- children: T
93
+ children: D
65
94
  })]
66
95
  }),
67
- /* @__PURE__ */ p("div", {
68
- className: "overflow-x-auto rounded-lg border border-[var(--color-border-default)]",
69
- children: /* @__PURE__ */ m("table", {
70
- className: "w-full text-sm",
71
- children: [/* @__PURE__ */ p("thead", { children: /* @__PURE__ */ m("tr", {
72
- className: "bg-[var(--color-bg-subtle)] border-b border-[var(--color-border-default)]",
73
- children: [
74
- /* @__PURE__ */ p("th", {
75
- className: "text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]",
76
- children: g("members.invite.emailLabel", "Email Address")
77
- }),
78
- /* @__PURE__ */ p("th", {
79
- className: "text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]",
80
- children: g("members.list.column.role", "Role")
81
- }),
82
- /* @__PURE__ */ p("th", {
83
- className: "text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]",
84
- children: g("common.status", "Status")
85
- }),
86
- /* @__PURE__ */ p("th", {
87
- className: "text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]",
88
- children: g("members.invite.expires", "Expires")
89
- }),
90
- /* @__PURE__ */ p("th", {
91
- className: "text-right px-4 py-2.5 font-medium text-[var(--color-text-secondary)]",
92
- children: g("common.actions", "Actions")
93
- })
94
- ]
95
- }) }), /* @__PURE__ */ p("tbody", { children: _.map((e) => /* @__PURE__ */ m("tr", {
96
- className: "border-b border-[var(--color-border-default)] last:border-b-0 hover:bg-[var(--color-bg-subtle)] transition-colors",
97
- children: [
98
- /* @__PURE__ */ p("td", {
99
- className: "px-4 py-3 text-[var(--color-text-primary)]",
100
- children: e.email
101
- }),
102
- /* @__PURE__ */ p("td", {
103
- className: "px-4 py-3",
104
- children: /* @__PURE__ */ p(s, { role: e.role })
105
- }),
106
- /* @__PURE__ */ p("td", {
107
- className: "px-4 py-3",
108
- children: /* @__PURE__ */ p(o, { status: e.status })
109
- }),
110
- /* @__PURE__ */ p("td", {
111
- className: "px-4 py-3 text-[var(--color-text-muted)]",
112
- children: new Date(e.expiresAt).toLocaleDateString()
113
- }),
114
- /* @__PURE__ */ p("td", {
115
- className: "px-4 py-3 text-right",
116
- children: /* @__PURE__ */ m("div", {
117
- className: "flex items-center justify-end gap-1",
118
- children: [/* @__PURE__ */ p("button", {
119
- type: "button",
120
- onClick: () => O(e.id, e.email),
121
- disabled: w,
122
- className: "p-1.5 rounded-md hover:bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] transition-colors",
123
- title: g("members.invite.resend", "Resend invite"),
124
- "aria-label": `${g("members.invite.resendFor", "Resend invite for")} ${e.email}`,
125
- children: /* @__PURE__ */ p(d, { size: 14 })
126
- }), /* @__PURE__ */ p("button", {
127
- type: "button",
128
- onClick: () => D(e.id, e.email),
129
- disabled: S,
130
- className: "p-1.5 rounded-md text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)]",
131
- title: g("members.invite.cancel", "Cancel invite"),
132
- "aria-label": `${g("members.invite.cancelFor", "Cancel invite for")} ${e.email}`,
133
- children: /* @__PURE__ */ p(f, { size: 14 })
134
- })]
135
- })
136
- })
137
- ]
138
- }, e.id)) })]
139
- })
96
+ /* @__PURE__ */ h(p, {
97
+ columns: j,
98
+ data: y,
99
+ rowKey: (e) => e.id,
100
+ rowActions: (e) => /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("button", {
101
+ type: "button",
102
+ onClick: () => A(e.id, e.email),
103
+ disabled: E,
104
+ className: "p-1.5 rounded-md hover:bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] transition-colors",
105
+ title: v("members.invite.resend", "Resend invite"),
106
+ "aria-label": `${v("members.invite.resendFor", "Resend invite for")} ${e.email}`,
107
+ children: /* @__PURE__ */ h(d, { size: 14 })
108
+ }), /* @__PURE__ */ h("button", {
109
+ type: "button",
110
+ onClick: () => k(e.id, e.email),
111
+ disabled: w,
112
+ className: "p-1.5 rounded-md text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)]",
113
+ title: v("members.invite.cancel", "Cancel invite"),
114
+ "aria-label": `${v("members.invite.cancelFor", "Cancel invite for")} ${e.email}`,
115
+ children: /* @__PURE__ */ h(f, { size: 14 })
116
+ })] })
140
117
  })
141
118
  ] });
142
119
  }
143
120
  //#endregion
144
- export { h as PendingInvitesList };
121
+ export { _ as PendingInvitesList };
145
122
 
146
123
  //# sourceMappingURL=PendingInvitesList.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PendingInvitesList.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/PendingInvitesList.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { RotateCw, X, Loader2, AlertCircle } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { useOrganizationInvites, useCancelInvite, useResendInvite } from '../hooks/useInvites';\nimport { InviteStatusBadge } from './InviteStatusBadge';\nimport { MemberRoleBadge } from './MemberRoleBadge';\nimport { InviteStatus } from '../../../../generated/global-types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface PendingInvitesListProps {\n organizationId: string;\n}\n\nexport function PendingInvitesList({ organizationId }: PendingInvitesListProps) {\n const tr = useTr();\n const { invites, loading, error, total } = useOrganizationInvites(\n organizationId,\n { status: InviteStatus.Pending },\n 50\n );\n const { cancelInvite, loading: cancelling } = useCancelInvite();\n const { resendInvite, loading: resending } = useResendInvite();\n const [actionError, setActionError] = useState<string | null>(null);\n\n if (loading && invites.length === 0) {\n return (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 size={20} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n );\n }\n\n if (error) {\n return (\n <p className=\"py-6 text-center text-sm text-[var(--color-status-error-text)]\">\n {tr('members.myInvites.loadFailed', 'Failed to load invites')}: {error.message}\n </p>\n );\n }\n\n if (invites.length === 0) {\n return (\n <p className=\"text-sm text-[var(--color-text-muted)] text-center py-6\">\n {tr('members.myInvites.emptyTitle', 'No pending invites')}\n </p>\n );\n }\n\n const handleCancel = async (inviteId: string, email: string) => {\n try {\n setActionError(null);\n await cancelInvite(inviteId);\n toast.success(`${tr('members.invite.cancelSuccess', 'Invite cancelled for')} ${email}`);\n } catch (err) {\n const msg =\n err instanceof Error\n ? err.message\n : tr('members.invite.cancelFailed', 'Failed to cancel invite');\n setActionError(msg);\n toast.error(msg);\n }\n };\n\n const handleResend = async (inviteId: string, email: string) => {\n try {\n setActionError(null);\n await resendInvite(inviteId);\n toast.success(`${tr('members.invite.resendSuccess', 'Invite re-sent to')} ${email}`);\n } catch (err) {\n const msg =\n err instanceof Error\n ? err.message\n : tr('members.invite.resendFailed', 'Failed to resend invite');\n setActionError(msg);\n toast.error(msg);\n }\n };\n\n return (\n <div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-secondary)] uppercase tracking-wider mb-3\">\n {tr('members.invite.pendingTitle', 'Pending Invites')} ({total})\n </h3>\n\n {actionError && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-2.5\">\n <AlertCircle size={14} className=\"shrink-0 text-[var(--color-status-error-text)]\" />\n <p className=\"text-sm text-[var(--color-status-error-text)]\">{actionError}</p>\n </div>\n )}\n\n <div className=\"overflow-x-auto rounded-lg border border-[var(--color-border-default)]\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-[var(--color-bg-subtle)] border-b border-[var(--color-border-default)]\">\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('members.invite.emailLabel', 'Email Address')}\n </th>\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('members.list.column.role', 'Role')}\n </th>\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('common.status', 'Status')}\n </th>\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('members.invite.expires', 'Expires')}\n </th>\n <th className=\"text-right px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('common.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {invites.map((invite) => (\n <tr\n key={invite.id}\n className=\"border-b border-[var(--color-border-default)] last:border-b-0 hover:bg-[var(--color-bg-subtle)] transition-colors\"\n >\n <td className=\"px-4 py-3 text-[var(--color-text-primary)]\">{invite.email}</td>\n <td className=\"px-4 py-3\">\n <MemberRoleBadge role={invite.role} />\n </td>\n <td className=\"px-4 py-3\">\n <InviteStatusBadge status={invite.status} />\n </td>\n <td className=\"px-4 py-3 text-[var(--color-text-muted)]\">\n {new Date(invite.expiresAt).toLocaleDateString()}\n </td>\n <td className=\"px-4 py-3 text-right\">\n <div className=\"flex items-center justify-end gap-1\">\n <button\n type=\"button\"\n onClick={() => handleResend(invite.id, invite.email)}\n disabled={resending}\n className=\"p-1.5 rounded-md hover:bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] transition-colors\"\n title={tr('members.invite.resend', 'Resend invite')}\n aria-label={`${tr('members.invite.resendFor', 'Resend invite for')} ${invite.email}`}\n >\n <RotateCw size={14} />\n </button>\n <button\n type=\"button\"\n onClick={() => handleCancel(invite.id, invite.email)}\n disabled={cancelling}\n className=\"p-1.5 rounded-md text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)]\"\n title={tr('members.invite.cancel', 'Cancel invite')}\n aria-label={`${tr('members.invite.cancelFor', 'Cancel invite for')} ${invite.email}`}\n >\n <X size={14} />\n </button>\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAaA,SAAgB,EAAmB,EAAE,qBAA2C;CAC9E,IAAM,IAAK,GAAO,EACZ,EAAE,YAAS,YAAS,UAAO,aAAU,EACzC,GACA,EAAE,QAAQ,EAAa,SAAS,EAChC,GACD,EACK,EAAE,iBAAc,SAAS,MAAe,GAAiB,EACzD,EAAE,iBAAc,SAAS,MAAc,GAAiB,EACxD,CAAC,GAAa,KAAkB,EAAwB,KAAK;AAEnE,KAAI,KAAW,EAAQ,WAAW,EAChC,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GAAS,MAAM;GAAI,WAAU;GAAgD,CAAA;EACzE,CAAA;AAIV,KAAI,EACF,QACE,kBAAC,KAAD;EAAG,WAAU;YAAb;GACG,EAAG,gCAAgC,yBAAyB;GAAC;GAAG,EAAM;GACrE;;AAIR,KAAI,EAAQ,WAAW,EACrB,QACE,kBAAC,KAAD;EAAG,WAAU;YACV,EAAG,gCAAgC,qBAAqB;EACvD,CAAA;CAIR,IAAM,IAAe,OAAO,GAAkB,MAAkB;AAC9D,MAAI;AAGF,GAFA,EAAe,KAAK,EACpB,MAAM,EAAa,EAAS,EAC5B,EAAM,QAAQ,GAAG,EAAG,gCAAgC,uBAAuB,CAAC,GAAG,IAAQ;WAChF,GAAK;GACZ,IAAM,IACJ,aAAe,QACX,EAAI,UACJ,EAAG,+BAA+B,0BAA0B;AAElE,GADA,EAAe,EAAI,EACnB,EAAM,MAAM,EAAI;;IAId,IAAe,OAAO,GAAkB,MAAkB;AAC9D,MAAI;AAGF,GAFA,EAAe,KAAK,EACpB,MAAM,EAAa,EAAS,EAC5B,EAAM,QAAQ,GAAG,EAAG,gCAAgC,oBAAoB,CAAC,GAAG,IAAQ;WAC7E,GAAK;GACZ,IAAM,IACJ,aAAe,QACX,EAAI,UACJ,EAAG,+BAA+B,0BAA0B;AAElE,GADA,EAAe,EAAI,EACnB,EAAM,MAAM,EAAI;;;AAIpB,QACE,kBAAC,OAAD,EAAA,UAAA;EACE,kBAAC,MAAD;GAAI,WAAU;aAAd;IACG,EAAG,+BAA+B,kBAAkB;IAAC;IAAG;IAAM;IAC5D;;EAEJ,KACC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAa,MAAM;IAAI,WAAU;IAAmD,CAAA,EACpF,kBAAC,KAAD;IAAG,WAAU;cAAiD;IAAgB,CAAA,CAC1E;;EAGR,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;KAAI,WAAU;eAAd;MACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,6BAA6B,gBAAgB;OAC9C,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,4BAA4B,OAAO;OACpC,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,iBAAiB,SAAS;OAC3B,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,0BAA0B,UAAU;OACrC,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,kBAAkB,UAAU;OAC7B,CAAA;MACF;QACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAQ,KAAK,MACZ,kBAAC,MAAD;KAEE,WAAU;eAFZ;MAIE,kBAAC,MAAD;OAAI,WAAU;iBAA8C,EAAO;OAAW,CAAA;MAC9E,kBAAC,MAAD;OAAI,WAAU;iBACZ,kBAAC,GAAD,EAAiB,MAAM,EAAO,MAAQ,CAAA;OACnC,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACZ,kBAAC,GAAD,EAAmB,QAAQ,EAAO,QAAU,CAAA;OACzC,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACX,IAAI,KAAK,EAAO,UAAU,CAAC,oBAAoB;OAC7C,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACZ,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAa,EAAO,IAAI,EAAO,MAAM;SACpD,UAAU;SACV,WAAU;SACV,OAAO,EAAG,yBAAyB,gBAAgB;SACnD,cAAY,GAAG,EAAG,4BAA4B,oBAAoB,CAAC,GAAG,EAAO;mBAE7E,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;SACf,CAAA,EACT,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAa,EAAO,IAAI,EAAO,MAAM;SACpD,UAAU;SACV,WAAU;SACV,OAAO,EAAG,yBAAyB,gBAAgB;SACnD,cAAY,GAAG,EAAG,4BAA4B,oBAAoB,CAAC,GAAG,EAAO;mBAE7E,kBAAC,GAAD,EAAG,MAAM,IAAM,CAAA;SACR,CAAA,CACL;;OACH,CAAA;MACF;OArCE,EAAO,GAqCT,CACL,EACI,CAAA,CACF;;GACJ,CAAA;EACF,EAAA,CAAA"}
1
+ {"version":3,"file":"PendingInvitesList.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/PendingInvitesList.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { RotateCw, X, Loader2, AlertCircle } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { ResponsiveTable, type ResponsiveTableColumn } from '@burdenoff/fe-libs/ui';\nimport { useOrganizationInvites, useCancelInvite, useResendInvite } from '../hooks/useInvites';\nimport { InviteStatusBadge } from './InviteStatusBadge';\nimport { MemberRoleBadge } from './MemberRoleBadge';\nimport { InviteStatus } from '../../../../generated/global-types';\nimport type { OrganizationInvite } from '../../../../generated/global-types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface PendingInvitesListProps {\n organizationId: string;\n}\n\nexport function PendingInvitesList({ organizationId }: PendingInvitesListProps) {\n const tr = useTr();\n const { invites, loading, error, total } = useOrganizationInvites(\n organizationId,\n { status: InviteStatus.Pending },\n 50\n );\n const { cancelInvite, loading: cancelling } = useCancelInvite();\n const { resendInvite, loading: resending } = useResendInvite();\n const [actionError, setActionError] = useState<string | null>(null);\n\n if (loading && invites.length === 0) {\n return (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 size={20} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n );\n }\n\n if (error) {\n return (\n <p className=\"py-6 text-center text-sm text-[var(--color-status-error-text)]\">\n {tr('members.myInvites.loadFailed', 'Failed to load invites')}: {error.message}\n </p>\n );\n }\n\n if (invites.length === 0) {\n return (\n <p className=\"text-sm text-[var(--color-text-muted)] text-center py-6\">\n {tr('members.myInvites.emptyTitle', 'No pending invites')}\n </p>\n );\n }\n\n const handleCancel = async (inviteId: string, email: string) => {\n try {\n setActionError(null);\n await cancelInvite(inviteId);\n toast.success(`${tr('members.invite.cancelSuccess', 'Invite cancelled for')} ${email}`);\n } catch (err) {\n const msg =\n err instanceof Error\n ? err.message\n : tr('members.invite.cancelFailed', 'Failed to cancel invite');\n setActionError(msg);\n toast.error(msg);\n }\n };\n\n const handleResend = async (inviteId: string, email: string) => {\n try {\n setActionError(null);\n await resendInvite(inviteId);\n toast.success(`${tr('members.invite.resendSuccess', 'Invite re-sent to')} ${email}`);\n } catch (err) {\n const msg =\n err instanceof Error\n ? err.message\n : tr('members.invite.resendFailed', 'Failed to resend invite');\n setActionError(msg);\n toast.error(msg);\n }\n };\n\n const columns: ResponsiveTableColumn<OrganizationInvite>[] = [\n {\n key: 'email',\n header: tr('members.invite.emailLabel', 'Email Address'),\n priority: 'primary',\n cell: (invite) => <span className=\"text-[var(--color-text-primary)]\">{invite.email}</span>,\n },\n {\n key: 'role',\n header: tr('members.list.column.role', 'Role'),\n cell: (invite) => <MemberRoleBadge role={invite.role} />,\n },\n {\n key: 'status',\n header: tr('common.status', 'Status'),\n cell: (invite) => <InviteStatusBadge status={invite.status} />,\n },\n {\n key: 'expires',\n header: tr('members.invite.expires', 'Expires'),\n cell: (invite) => (\n <span className=\"text-[var(--color-text-muted)]\">\n {new Date(invite.expiresAt).toLocaleDateString()}\n </span>\n ),\n },\n ];\n\n return (\n <div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-secondary)] uppercase tracking-wider mb-3\">\n {tr('members.invite.pendingTitle', 'Pending Invites')} ({total})\n </h3>\n\n {actionError && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-2.5\">\n <AlertCircle size={14} className=\"shrink-0 text-[var(--color-status-error-text)]\" />\n <p className=\"text-sm text-[var(--color-status-error-text)]\">{actionError}</p>\n </div>\n )}\n\n <ResponsiveTable<OrganizationInvite>\n columns={columns}\n data={invites}\n rowKey={(invite) => invite.id}\n rowActions={(invite) => (\n <>\n <button\n type=\"button\"\n onClick={() => handleResend(invite.id, invite.email)}\n disabled={resending}\n className=\"p-1.5 rounded-md hover:bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] transition-colors\"\n title={tr('members.invite.resend', 'Resend invite')}\n aria-label={`${tr('members.invite.resendFor', 'Resend invite for')} ${invite.email}`}\n >\n <RotateCw size={14} />\n </button>\n <button\n type=\"button\"\n onClick={() => handleCancel(invite.id, invite.email)}\n disabled={cancelling}\n className=\"p-1.5 rounded-md text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)]\"\n title={tr('members.invite.cancel', 'Cancel invite')}\n aria-label={`${tr('members.invite.cancelFor', 'Cancel invite for')} ${invite.email}`}\n >\n <X size={14} />\n </button>\n </>\n )}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -9,108 +9,114 @@ import { BulkInviteForm as s } from "../components/BulkInviteForm.js";
9
9
  import { PendingInvitesList as c } from "../components/PendingInvitesList.js";
10
10
  import { useState as l } from "react";
11
11
  import { ArrowLeft as u, UserPlus as d } from "lucide-react";
12
- import { usePermissions as f } from "@burdenoff/fe-libs/shared/providers/shell";
13
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
14
- import { useParams as h } from "react-router-dom";
12
+ import { GlassCard as f, ShowMore as p } from "@burdenoff/fe-libs/ui";
13
+ import { usePermissions as m } from "@burdenoff/fe-libs/shared/providers/shell";
14
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
15
+ import { useParams as _ } from "react-router-dom";
15
16
  //#region src/organization-management/modules/members/pages/InviteMemberPage.tsx
16
- function g() {
17
- let g = r(), { organizationId: _ } = h(), { navigate: v, basePath: y = "/organizations" } = e(), { hasPermission: b, isLoading: x } = f(), { inviteMember: S, loading: C, error: w } = n(), [T, E] = l("single"), D = async (e) => {
18
- await S(e), t.success(`${g("members.invite.success", "Invitation sent to")} ${e.email}`);
19
- }, O = () => {
20
- v(`${y}/${_}/members`);
17
+ function v() {
18
+ let v = r(), { organizationId: y } = _(), { navigate: b, basePath: x = "/organizations" } = e(), { hasPermission: S, isLoading: C } = m(), { inviteMember: w, loading: T, error: E } = n(), [D, O] = l("single"), k = async (e) => {
19
+ await w(e), t.success(`${v("members.invite.success", "Invitation sent to")} ${e.email}`);
20
+ }, A = () => {
21
+ b(`${x}/${y}/members`);
21
22
  };
22
- return x ? /* @__PURE__ */ p("div", {
23
+ return C ? /* @__PURE__ */ h("div", {
23
24
  className: "min-h-full bg-[var(--color-bg-canvas)] p-6",
24
- children: /* @__PURE__ */ p(i, { message: g("common.loadingPermissions", "Loading permissions...") })
25
- }) : b(a.MEMBER_INVITE) ? /* @__PURE__ */ p("div", {
25
+ children: /* @__PURE__ */ h(i, { message: v("common.loadingPermissions", "Loading permissions...") })
26
+ }) : S(a.MEMBER_INVITE) ? /* @__PURE__ */ h("div", {
26
27
  className: "min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6",
27
- children: /* @__PURE__ */ m("div", {
28
+ children: /* @__PURE__ */ g("div", {
28
29
  className: "mx-auto max-w-3xl space-y-8",
29
30
  children: [
30
- /* @__PURE__ */ m("div", {
31
+ /* @__PURE__ */ g("div", {
31
32
  className: "flex items-center gap-3",
32
- children: [/* @__PURE__ */ p("button", {
33
+ children: [/* @__PURE__ */ h("button", {
33
34
  type: "button",
34
- onClick: O,
35
+ onClick: A,
35
36
  className: "rounded-lg p-2 transition-colors hover:bg-[var(--color-bg-subtle)]",
36
- "aria-label": g("members.invite.backToMembers", "Back to members"),
37
- children: /* @__PURE__ */ p(u, {
37
+ "aria-label": v("members.invite.backToMembers", "Back to members"),
38
+ children: /* @__PURE__ */ h(u, {
38
39
  size: 20,
39
40
  className: "text-[var(--color-text-secondary)]"
40
41
  })
41
- }), /* @__PURE__ */ m("div", {
42
+ }), /* @__PURE__ */ g("div", {
42
43
  className: "flex items-center gap-2",
43
- children: [/* @__PURE__ */ p(d, {
44
+ children: [/* @__PURE__ */ h(d, {
44
45
  size: 24,
45
46
  className: "text-[var(--color-text-primary)]"
46
- }), /* @__PURE__ */ p("h1", {
47
+ }), /* @__PURE__ */ h("h1", {
47
48
  className: "text-2xl font-bold text-[var(--color-text-primary)]",
48
- children: g("members.invite.title", "Invite Member")
49
+ children: v("members.invite.title", "Invite Member")
49
50
  })]
50
51
  })]
51
52
  }),
52
- /* @__PURE__ */ p("div", {
53
+ /* @__PURE__ */ h("div", {
53
54
  className: "inline-flex rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-1",
54
55
  role: "tablist",
55
- "aria-label": g("members.invite.modeToggle", "Invite mode"),
56
+ "aria-label": v("members.invite.modeToggle", "Invite mode"),
56
57
  children: ["single", "bulk"].map((e) => {
57
- let t = T === e, n = e === "single" ? g("members.invite.modeSingle", "Single") : g("members.invite.modeBulk", "Bulk (CSV)");
58
- return /* @__PURE__ */ p("button", {
58
+ let t = D === e, n = e === "single" ? v("members.invite.modeSingle", "Single") : v("members.invite.modeBulk", "Bulk (CSV)");
59
+ return /* @__PURE__ */ h("button", {
59
60
  type: "button",
60
61
  role: "tab",
61
62
  "aria-selected": t,
62
- onClick: () => E(e),
63
+ onClick: () => O(e),
63
64
  className: `min-h-9 rounded-md px-4 py-1.5 text-sm font-medium transition-colors ${t ? "bg-[var(--color-action-primary-bg)] text-[var(--color-action-primary-text)]" : "text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]"}`,
64
65
  children: n
65
66
  }, e);
66
67
  })
67
68
  }),
68
- /* @__PURE__ */ m("div", {
69
- className: "bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)] p-6",
69
+ /* @__PURE__ */ g(f, {
70
+ treatment: "plain",
71
+ className: "border-border-seam p-6",
70
72
  children: [
71
- /* @__PURE__ */ p("h2", {
73
+ /* @__PURE__ */ h("h2", {
72
74
  className: "text-lg font-semibold text-[var(--color-text-primary)] mb-4",
73
- children: T === "single" ? g("members.invite.send", "Send Invite") : g("members.bulkInvite.title", "Bulk Invite from CSV")
75
+ children: D === "single" ? v("members.invite.send", "Send Invite") : v("members.bulkInvite.title", "Bulk Invite from CSV")
74
76
  }),
75
- /* @__PURE__ */ p("p", {
77
+ /* @__PURE__ */ h("p", {
76
78
  className: "text-sm text-[var(--color-text-muted)] mb-6",
77
- children: T === "single" ? g("members.invite.description", "Enter the email address of the person you want to invite. They will receive an email notification and can accept or decline from their account.") : g("members.bulkInvite.pageDescription", "Upload a CSV to invite many people at once. Download the template below to see the expected format.")
79
+ children: D === "single" ? v("members.invite.description", "Enter the email address of the person you want to invite. They will receive an email notification and can accept or decline from their account.") : v("members.bulkInvite.pageDescription", "Upload a CSV to invite many people at once. Download the template below to see the expected format.")
78
80
  }),
79
- T === "single" && w && /* @__PURE__ */ p("div", {
81
+ D === "single" && E && /* @__PURE__ */ h("div", {
80
82
  className: "mb-4 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3",
81
- children: /* @__PURE__ */ p("p", {
83
+ children: /* @__PURE__ */ h("p", {
82
84
  className: "text-sm text-[var(--color-status-error-text)]",
83
- children: w.message.includes("CONFLICT") ? g("members.invite.error.conflict", "An invite already exists for this email address.") : w.message.includes("already a member") ? g("members.invite.error.alreadyMember", "This user is already a member of the organization.") : w.message
85
+ children: E.message.includes("CONFLICT") ? v("members.invite.error.conflict", "An invite already exists for this email address.") : E.message.includes("already a member") ? v("members.invite.error.alreadyMember", "This user is already a member of the organization.") : E.message
84
86
  })
85
87
  }),
86
- T === "single" ? /* @__PURE__ */ p(o, {
87
- organizationId: _,
88
- onSubmit: D,
89
- onCancel: O,
90
- loading: C
91
- }) : /* @__PURE__ */ p(s, {
92
- organizationId: _,
93
- inviteMember: S,
94
- onCancel: O,
95
- loading: C
88
+ D === "single" ? /* @__PURE__ */ h(o, {
89
+ organizationId: y,
90
+ onSubmit: k,
91
+ onCancel: A,
92
+ loading: T
93
+ }) : /* @__PURE__ */ h(s, {
94
+ organizationId: y,
95
+ inviteMember: w,
96
+ onCancel: A,
97
+ loading: T
96
98
  })
97
99
  ]
98
100
  }),
99
- /* @__PURE__ */ p("div", {
100
- className: "bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)] p-6",
101
- children: /* @__PURE__ */ p(c, { organizationId: _ })
101
+ /* @__PURE__ */ h(f, {
102
+ treatment: "plain",
103
+ className: "border-border-seam p-6",
104
+ children: /* @__PURE__ */ h(p, {
105
+ collapsedHeight: 260,
106
+ children: /* @__PURE__ */ h(c, { organizationId: y })
107
+ })
102
108
  })
103
109
  ]
104
110
  })
105
- }) : /* @__PURE__ */ p("div", {
111
+ }) : /* @__PURE__ */ h("div", {
106
112
  className: "min-h-full bg-[var(--color-bg-canvas)] p-6",
107
- children: /* @__PURE__ */ p("div", {
113
+ children: /* @__PURE__ */ h("div", {
108
114
  className: "p-8 text-center text-[var(--color-text-secondary)]",
109
- children: g("members.invite.noPermission", "You don't have permission to invite members.")
115
+ children: v("members.invite.noPermission", "You don't have permission to invite members.")
110
116
  })
111
117
  });
112
118
  }
113
119
  //#endregion
114
- export { g as InviteMemberPage };
120
+ export { v as InviteMemberPage };
115
121
 
116
122
  //# sourceMappingURL=InviteMemberPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"InviteMemberPage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/InviteMemberPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, UserPlus } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useInviteMember } from '../hooks/useInvites';\nimport { InviteMemberForm } from '../components/InviteMemberForm';\nimport { BulkInviteForm } from '../components/BulkInviteForm';\nimport { PendingInvitesList } from '../components/PendingInvitesList';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ntype InviteMode = 'single' | 'bulk';\n\n/**\n * Invite Member Page\n * Email-based invite form with pending invites list below\n */\nexport function InviteMemberPage() {\n const tr = useTr();\n const { organizationId } = useParams<{ organizationId: string }>();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { inviteMember, loading, error } = useInviteMember();\n const [mode, setMode] = useState<InviteMode>('single');\n\n const handleSubmit = async (data: {\n organizationId: string;\n email: string;\n message?: string;\n }) => {\n await inviteMember(data);\n toast.success(`${tr('members.invite.success', 'Invitation sent to')} ${data.email}`);\n };\n\n const handleCancel = () => {\n navigate(`${basePath}/${organizationId}/members`);\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 if (!hasPermission(PERMISSIONS.MEMBER_INVITE)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr('members.invite.noPermission', \"You don't have permission to invite members.\")}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"mx-auto max-w-3xl space-y-8\">\n {/* Header */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"rounded-lg p-2 transition-colors hover:bg-[var(--color-bg-subtle)]\"\n aria-label={tr('members.invite.backToMembers', 'Back to members')}\n >\n <ArrowLeft size={20} className=\"text-[var(--color-text-secondary)]\" />\n </button>\n <div className=\"flex items-center gap-2\">\n <UserPlus size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.invite.title', 'Invite Member')}\n </h1>\n </div>\n </div>\n\n {/* Mode toggle */}\n <div\n className=\"inline-flex rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-1\"\n role=\"tablist\"\n aria-label={tr('members.invite.modeToggle', 'Invite mode')}\n >\n {(['single', 'bulk'] as const).map((m) => {\n const isActive = mode === m;\n const label =\n m === 'single'\n ? tr('members.invite.modeSingle', 'Single')\n : tr('members.invite.modeBulk', 'Bulk (CSV)');\n return (\n <button\n key={m}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setMode(m)}\n className={`min-h-9 rounded-md px-4 py-1.5 text-sm font-medium transition-colors ${\n isActive\n ? 'bg-[var(--color-action-primary-bg)] text-[var(--color-action-primary-text)]'\n : 'text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'\n }`}\n >\n {label}\n </button>\n );\n })}\n </div>\n\n {/* Invite Form Card */}\n <div className=\"bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)] p-6\">\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)] mb-4\">\n {mode === 'single'\n ? tr('members.invite.send', 'Send Invite')\n : tr('members.bulkInvite.title', 'Bulk Invite from CSV')}\n </h2>\n <p className=\"text-sm text-[var(--color-text-muted)] mb-6\">\n {mode === 'single'\n ? tr(\n 'members.invite.description',\n 'Enter the email address of the person you want to invite. They will receive an email notification and can accept or decline from their account.'\n )\n : tr(\n 'members.bulkInvite.pageDescription',\n 'Upload a CSV to invite many people at once. Download the template below to see the expected format.'\n )}\n </p>\n\n {mode === 'single' && error && (\n <div className=\"mb-4 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error.message.includes('CONFLICT')\n ? tr(\n 'members.invite.error.conflict',\n 'An invite already exists for this email address.'\n )\n : error.message.includes('already a member')\n ? tr(\n 'members.invite.error.alreadyMember',\n 'This user is already a member of the organization.'\n )\n : error.message}\n </p>\n </div>\n )}\n\n {mode === 'single' ? (\n <InviteMemberForm\n organizationId={organizationId!}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n ) : (\n <BulkInviteForm\n organizationId={organizationId!}\n inviteMember={inviteMember}\n onCancel={handleCancel}\n loading={loading}\n />\n )}\n </div>\n\n {/* Pending Invites */}\n <div className=\"bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)] p-6\">\n <PendingInvitesList organizationId={organizationId!} />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAoBA,SAAgB,IAAmB;CACjC,IAAM,IAAK,GAAO,EACZ,EAAE,sBAAmB,GAAuC,EAC5D,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAC3D,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,iBAAc,YAAS,aAAU,GAAiB,EACpD,CAAC,GAAM,KAAW,EAAqB,SAAS,EAEhD,IAAe,OAAO,MAItB;AAEJ,EADA,MAAM,EAAa,EAAK,EACxB,EAAM,QAAQ,GAAG,EAAG,0BAA0B,qBAAqB,CAAC,GAAG,EAAK,QAAQ;IAGhF,UAAqB;AACzB,IAAS,GAAG,EAAS,GAAG,EAAe,UAAU;;AAqBnD,QAlBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,cAAc,GAW3C,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAY,EAAG,gCAAgC,kBAAkB;gBAEjE,kBAAC,GAAD;OAAW,MAAM;OAAI,WAAU;OAAuC,CAAA;MAC/D,CAAA,EACT,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAU,MAAM;OAAI,WAAU;OAAqC,CAAA,EACnE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,wBAAwB,gBAAgB;OACzC,CAAA,CACD;QACF;;IAGN,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,cAAY,EAAG,6BAA6B,cAAc;eAExD,CAAC,UAAU,OAAO,CAAW,KAAK,MAAM;MACxC,IAAM,IAAW,MAAS,GACpB,IACJ,MAAM,WACF,EAAG,6BAA6B,SAAS,GACzC,EAAG,2BAA2B,aAAa;AACjD,aACE,kBAAC,UAAD;OAEE,MAAK;OACL,MAAK;OACL,iBAAe;OACf,eAAe,EAAQ,EAAE;OACzB,WAAW,wEACT,IACI,gFACA;iBAGL;OACM,EAZF,EAYE;OAEX;KACE,CAAA;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,MAAD;OAAI,WAAU;iBACX,MAAS,WACN,EAAG,uBAAuB,cAAc,GACxC,EAAG,4BAA4B,uBAAuB;OACvD,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,MAAS,WACN,EACE,8BACA,kJACD,GACD,EACE,sCACA,sGACD;OACH,CAAA;MAEH,MAAS,YAAY,KACpB,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAM,QAAQ,SAAS,WAAW,GAC/B,EACE,iCACA,mDACD,GACD,EAAM,QAAQ,SAAS,mBAAmB,GACxC,EACE,sCACA,qDACD,GACD,EAAM;QACV,CAAA;OACA,CAAA;MAGP,MAAS,WACR,kBAAC,GAAD;OACkB;OAChB,UAAU;OACV,UAAU;OACD;OACT,CAAA,GAEF,kBAAC,GAAD;OACkB;OACF;OACd,UAAU;OACD;OACT,CAAA;MAEA;;IAGN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAoC,mBAAmB,CAAA;KACnD,CAAA;IACF;;EACF,CAAA,GAvHJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAG,+BAA+B,+CAA+C;GAC9E,CAAA;EACF,CAAA"}
1
+ {"version":3,"file":"InviteMemberPage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/InviteMemberPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, UserPlus } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { GlassCard, ShowMore } from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useInviteMember } from '../hooks/useInvites';\nimport { InviteMemberForm } from '../components/InviteMemberForm';\nimport { BulkInviteForm } from '../components/BulkInviteForm';\nimport { PendingInvitesList } from '../components/PendingInvitesList';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ntype InviteMode = 'single' | 'bulk';\n\n/**\n * Invite Member Page\n * Email-based invite form with pending invites list below\n */\nexport function InviteMemberPage() {\n const tr = useTr();\n const { organizationId } = useParams<{ organizationId: string }>();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { inviteMember, loading, error } = useInviteMember();\n const [mode, setMode] = useState<InviteMode>('single');\n\n const handleSubmit = async (data: {\n organizationId: string;\n email: string;\n message?: string;\n }) => {\n await inviteMember(data);\n toast.success(`${tr('members.invite.success', 'Invitation sent to')} ${data.email}`);\n };\n\n const handleCancel = () => {\n navigate(`${basePath}/${organizationId}/members`);\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 if (!hasPermission(PERMISSIONS.MEMBER_INVITE)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr('members.invite.noPermission', \"You don't have permission to invite members.\")}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"mx-auto max-w-3xl space-y-8\">\n {/* Header */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"rounded-lg p-2 transition-colors hover:bg-[var(--color-bg-subtle)]\"\n aria-label={tr('members.invite.backToMembers', 'Back to members')}\n >\n <ArrowLeft size={20} className=\"text-[var(--color-text-secondary)]\" />\n </button>\n <div className=\"flex items-center gap-2\">\n <UserPlus size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.invite.title', 'Invite Member')}\n </h1>\n </div>\n </div>\n\n {/* Mode toggle */}\n <div\n className=\"inline-flex rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-1\"\n role=\"tablist\"\n aria-label={tr('members.invite.modeToggle', 'Invite mode')}\n >\n {(['single', 'bulk'] as const).map((m) => {\n const isActive = mode === m;\n const label =\n m === 'single'\n ? tr('members.invite.modeSingle', 'Single')\n : tr('members.invite.modeBulk', 'Bulk (CSV)');\n return (\n <button\n key={m}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setMode(m)}\n className={`min-h-9 rounded-md px-4 py-1.5 text-sm font-medium transition-colors ${\n isActive\n ? 'bg-[var(--color-action-primary-bg)] text-[var(--color-action-primary-text)]'\n : 'text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'\n }`}\n >\n {label}\n </button>\n );\n })}\n </div>\n\n {/* Invite Form Card */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6\">\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)] mb-4\">\n {mode === 'single'\n ? tr('members.invite.send', 'Send Invite')\n : tr('members.bulkInvite.title', 'Bulk Invite from CSV')}\n </h2>\n <p className=\"text-sm text-[var(--color-text-muted)] mb-6\">\n {mode === 'single'\n ? tr(\n 'members.invite.description',\n 'Enter the email address of the person you want to invite. They will receive an email notification and can accept or decline from their account.'\n )\n : tr(\n 'members.bulkInvite.pageDescription',\n 'Upload a CSV to invite many people at once. Download the template below to see the expected format.'\n )}\n </p>\n\n {mode === 'single' && error && (\n <div className=\"mb-4 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error.message.includes('CONFLICT')\n ? tr(\n 'members.invite.error.conflict',\n 'An invite already exists for this email address.'\n )\n : error.message.includes('already a member')\n ? tr(\n 'members.invite.error.alreadyMember',\n 'This user is already a member of the organization.'\n )\n : error.message}\n </p>\n </div>\n )}\n\n {mode === 'single' ? (\n <InviteMemberForm\n organizationId={organizationId!}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n ) : (\n <BulkInviteForm\n organizationId={organizationId!}\n inviteMember={inviteMember}\n onCancel={handleCancel}\n loading={loading}\n />\n )}\n </GlassCard>\n\n {/* Pending Invites */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6\">\n <ShowMore collapsedHeight={260}>\n <PendingInvitesList organizationId={organizationId!} />\n </ShowMore>\n </GlassCard>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,SAAgB,IAAmB;CACjC,IAAM,IAAK,GAAO,EACZ,EAAE,sBAAmB,GAAuC,EAC5D,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAC3D,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,iBAAc,YAAS,aAAU,GAAiB,EACpD,CAAC,GAAM,KAAW,EAAqB,SAAS,EAEhD,IAAe,OAAO,MAItB;AAEJ,EADA,MAAM,EAAa,EAAK,EACxB,EAAM,QAAQ,GAAG,EAAG,0BAA0B,qBAAqB,CAAC,GAAG,EAAK,QAAQ;IAGhF,UAAqB;AACzB,IAAS,GAAG,EAAS,GAAG,EAAe,UAAU;;AAqBnD,QAlBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,cAAc,GAW3C,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAY,EAAG,gCAAgC,kBAAkB;gBAEjE,kBAAC,GAAD;OAAW,MAAM;OAAI,WAAU;OAAuC,CAAA;MAC/D,CAAA,EACT,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAU,MAAM;OAAI,WAAU;OAAqC,CAAA,EACnE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,wBAAwB,gBAAgB;OACzC,CAAA,CACD;QACF;;IAGN,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,cAAY,EAAG,6BAA6B,cAAc;eAExD,CAAC,UAAU,OAAO,CAAW,KAAK,MAAM;MACxC,IAAM,IAAW,MAAS,GACpB,IACJ,MAAM,WACF,EAAG,6BAA6B,SAAS,GACzC,EAAG,2BAA2B,aAAa;AACjD,aACE,kBAAC,UAAD;OAEE,MAAK;OACL,MAAK;OACL,iBAAe;OACf,eAAe,EAAQ,EAAE;OACzB,WAAW,wEACT,IACI,gFACA;iBAGL;OACM,EAZF,EAYE;OAEX;KACE,CAAA;IAGN,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC;MACE,kBAAC,MAAD;OAAI,WAAU;iBACX,MAAS,WACN,EAAG,uBAAuB,cAAc,GACxC,EAAG,4BAA4B,uBAAuB;OACvD,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,MAAS,WACN,EACE,8BACA,kJACD,GACD,EACE,sCACA,sGACD;OACH,CAAA;MAEH,MAAS,YAAY,KACpB,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAM,QAAQ,SAAS,WAAW,GAC/B,EACE,iCACA,mDACD,GACD,EAAM,QAAQ,SAAS,mBAAmB,GACxC,EACE,sCACA,qDACD,GACD,EAAM;QACV,CAAA;OACA,CAAA;MAGP,MAAS,WACR,kBAAC,GAAD;OACkB;OAChB,UAAU;OACV,UAAU;OACD;OACT,CAAA,GAEF,kBAAC,GAAD;OACkB;OACF;OACd,UAAU;OACD;OACT,CAAA;MAEM;;IAGZ,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eACrC,kBAAC,GAAD;MAAU,iBAAiB;gBACzB,kBAAC,GAAD,EAAoC,mBAAmB,CAAA;MAC9C,CAAA;KACD,CAAA;IACR;;EACF,CAAA,GAzHJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAG,+BAA+B,+CAA+C;GAC9E,CAAA;EACF,CAAA"}