@tangle-network/agent-app 0.42.7 → 0.42.9

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.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  InvitationsPanel
3
- } from "./chunk-F7D7YCUL.js";
3
+ } from "./chunk-7C64WKAH.js";
4
4
  import "./chunk-63CE7FEZ.js";
5
5
  export {
6
6
  InvitationsPanel
7
7
  };
8
- //# sourceMappingURL=InvitationsPanel-ZKAUYZYU.js.map
8
+ //# sourceMappingURL=InvitationsPanel-XUUWZBPV.js.map
@@ -10,6 +10,18 @@ var ASSIGNABLE = [
10
10
  { value: "editor", label: "Editor" },
11
11
  { value: "admin", label: "Admin" }
12
12
  ];
13
+ var BADGE_BASE = "inline-flex items-center rounded border px-2 py-0.5 text-[10px] font-medium uppercase";
14
+ var STATUS_BADGE = {
15
+ accepted: "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]",
16
+ pending: "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]",
17
+ revoked: "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]",
18
+ expired: "border-[var(--border-default)] text-[var(--text-muted)]"
19
+ };
20
+ var EMAIL_BADGE = {
21
+ sent: "border-[var(--border-default)] text-[var(--text-muted)]",
22
+ not_sent: "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]",
23
+ failed: "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]"
24
+ };
13
25
  function InvitationsPanel({
14
26
  invitations,
15
27
  currentRole,
@@ -152,17 +164,11 @@ function InvitationRow({ invitation, canManage, busy, onCopy, onResend, onRevoke
152
164
  " \xB7 expires ",
153
165
  expiry
154
166
  ] }),
155
- emailFailed && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-[var(--text-danger)]", children: "Email was not sent \u2014 copy the link to share it." })
167
+ emailFailed && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-[var(--surface-danger-text)]", children: "Email was not sent \u2014 copy the link to share it." })
156
168
  ] }),
157
169
  /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
158
- /* @__PURE__ */ jsx("span", { className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-[10px] uppercase text-[var(--text-secondary)]", children: invitation.status }),
159
- /* @__PURE__ */ jsx(
160
- "span",
161
- {
162
- className: `rounded border border-[var(--border-default)] px-2 py-0.5 text-[10px] uppercase ${emailFailed ? "text-[var(--text-danger)]" : "text-[var(--text-muted)]"}`,
163
- children: invitation.emailStatus.replace("_", " ")
164
- }
165
- )
170
+ /* @__PURE__ */ jsx("span", { className: `${BADGE_BASE} ${STATUS_BADGE[invitation.status]}`, children: invitation.status }),
171
+ /* @__PURE__ */ jsx("span", { className: `${BADGE_BASE} ${EMAIL_BADGE[invitation.emailStatus]}`, children: invitation.emailStatus.replace("_", " ") })
166
172
  ] })
167
173
  ] }),
168
174
  canManage && isPending && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-end gap-2", children: [
@@ -203,4 +209,4 @@ function InvitationRow({ invitation, canManage, busy, onCopy, onResend, onRevoke
203
209
  export {
204
210
  InvitationsPanel
205
211
  };
206
- //# sourceMappingURL=chunk-F7D7YCUL.js.map
212
+ //# sourceMappingURL=chunk-7C64WKAH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/teams-react/components/InvitationsPanel.tsx"],"sourcesContent":["/**\n * Email-invitation panel: invite by email + role, and a history of every\n * invitation (pending / accepted / expired / revoked) with its email-delivery\n * status. Pending rows expose copy-link / resend / revoke. Fully callback-driven\n * — the host supplies the data and the async `onInvite` / `onResend` / `onRevoke`\n * callbacks (backed by `./teams/invitations-api`), so this imports no app router,\n * fetch client, or toast. Styled with the shipped Tangle Quiet tokens (`var(--*)`).\n *\n * Mount alongside a list-only `MembersPanel` (`showInviteForm={false}`): the\n * members panel shows accepted members, this owns the invite flow + pending list.\n */\n\nimport { useState } from 'react'\nimport type { WorkspaceRole } from '../../teams/roles'\nimport { hasWorkspaceRole } from '../../teams/roles'\nimport type { InvitationView, InvitationsPanelProps } from '../contracts'\n\nconst ASSIGNABLE: { value: WorkspaceRole; label: string }[] = [\n { value: 'viewer', label: 'Viewer' },\n { value: 'editor', label: 'Editor' },\n { value: 'admin', label: 'Admin' },\n]\n\nconst BADGE_BASE = 'inline-flex items-center rounded border px-2 py-0.5 text-[10px] font-medium uppercase'\n\n// Lifecycle status: green = in, amber = awaiting, red = killed, muted = lapsed.\nconst STATUS_BADGE: Record<InvitationView['status'], string> = {\n accepted: 'border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]',\n pending: 'border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]',\n revoked: 'border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]',\n expired: 'border-[var(--border-default)] text-[var(--text-muted)]',\n}\n\n// Delivery status: red = bounced, amber = not yet attempted, muted = the normal happy path.\nconst EMAIL_BADGE: Record<InvitationView['emailStatus'], string> = {\n sent: 'border-[var(--border-default)] text-[var(--text-muted)]',\n not_sent: 'border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]',\n failed: 'border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]',\n}\n\nexport function InvitationsPanel({\n invitations,\n currentRole,\n onInvite,\n onResend,\n onRevoke,\n onCopy,\n onNotice,\n}: InvitationsPanelProps) {\n const [inviteEmail, setInviteEmail] = useState('')\n const [inviteRole, setInviteRole] = useState<WorkspaceRole>('editor')\n const [inviting, setInviting] = useState(false)\n const [busyId, setBusyId] = useState<string | null>(null)\n\n const canManage = hasWorkspaceRole(currentRole, 'admin')\n\n function notify(kind: 'success' | 'error', message: string) {\n onNotice?.({ kind, message })\n }\n\n async function submitInvite() {\n const email = inviteEmail.trim()\n if (!email || inviting) return\n setInviting(true)\n try {\n await onInvite({ email, role: inviteRole })\n notify('success', `Invitation sent to ${email}`)\n setInviteEmail('')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to invite')\n } finally {\n setInviting(false)\n }\n }\n\n async function copyLink(inviteUrl: string) {\n try {\n if (onCopy) await onCopy({ inviteUrl })\n else await navigator.clipboard.writeText(inviteUrl)\n notify('success', 'Invite link copied')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to copy link')\n }\n }\n\n async function resend(invitationId: string) {\n setBusyId(invitationId)\n try {\n await onResend({ invitationId })\n notify('success', 'Invitation resent')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to resend invitation')\n } finally {\n setBusyId(null)\n }\n }\n\n async function revoke(invitationId: string) {\n setBusyId(invitationId)\n try {\n await onRevoke({ invitationId })\n notify('success', 'Invitation revoked')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to revoke invitation')\n } finally {\n setBusyId(null)\n }\n }\n\n return (\n <section className=\"flex flex-col gap-4\">\n {canManage && (\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-xs font-medium text-[var(--text-muted)]\">Invite by email</label>\n <div className=\"flex gap-2\">\n <input\n type=\"email\"\n placeholder=\"colleague@example.com\"\n value={inviteEmail}\n aria-label=\"Invite email address\"\n onChange={(event) => setInviteEmail(event.target.value)}\n onKeyDown={(event) => { if (event.key === 'Enter') void submitInvite() }}\n className=\"flex-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-muted)]\"\n />\n <select\n value={inviteRole}\n aria-label=\"Invite role\"\n onChange={(event) => setInviteRole(event.target.value as WorkspaceRole)}\n className=\"rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1.5 text-xs text-[var(--text-secondary)]\"\n >\n {ASSIGNABLE.map((option) => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => void submitInvite()}\n disabled={inviting || !inviteEmail.trim()}\n className=\"rounded bg-[var(--brand-primary)] px-3 py-1.5 text-sm text-white disabled:opacity-50\"\n >\n {inviting ? 'Inviting…' : 'Invite'}\n </button>\n </div>\n </div>\n )}\n\n <div className=\"flex flex-col gap-2 border-t border-[var(--border-default)] pt-3\">\n <div className=\"flex items-center justify-between\">\n <h4 className=\"text-xs font-semibold text-[var(--text-primary)]\">Invitation history</h4>\n <span className=\"text-xs text-[var(--text-muted)]\">{invitations.length} total</span>\n </div>\n {invitations.length === 0 ? (\n <p className=\"text-sm text-[var(--text-muted)]\">No invitations yet.</p>\n ) : (\n invitations.map((invitation) => (\n <InvitationRow\n key={invitation.id}\n invitation={invitation}\n canManage={canManage}\n busy={busyId === invitation.id}\n onCopy={() => void copyLink(invitation.inviteUrl)}\n onResend={() => void resend(invitation.id)}\n onRevoke={() => void revoke(invitation.id)}\n />\n ))\n )}\n </div>\n </section>\n )\n}\n\ninterface InvitationRowProps {\n invitation: InvitationView\n canManage: boolean\n busy: boolean\n onCopy(): void\n onResend(): void\n onRevoke(): void\n}\n\nfunction InvitationRow({ invitation, canManage, busy, onCopy, onResend, onRevoke }: InvitationRowProps) {\n const isPending = invitation.status === 'pending'\n const emailFailed = invitation.emailStatus === 'failed'\n const expiry = new Date(invitation.expiresAt).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })\n\n return (\n <div className=\"rounded-lg border border-[var(--border-default)] p-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-[var(--text-primary)]\">{invitation.email}</p>\n <p className=\"text-xs capitalize text-[var(--text-muted)]\">\n {invitation.permissions} · expires {expiry}\n </p>\n {emailFailed && (\n <p className=\"mt-1 text-xs text-[var(--surface-danger-text)]\">Email was not sent — copy the link to share it.</p>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <span className={`${BADGE_BASE} ${STATUS_BADGE[invitation.status]}`}>\n {invitation.status}\n </span>\n <span className={`${BADGE_BASE} ${EMAIL_BADGE[invitation.emailStatus]}`}>\n {invitation.emailStatus.replace('_', ' ')}\n </span>\n </div>\n </div>\n {canManage && isPending && (\n <div className=\"mt-3 flex items-center justify-end gap-2\">\n <button\n type=\"button\"\n onClick={onCopy}\n disabled={busy}\n className=\"rounded border border-[var(--border-default)] px-2 py-1 text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:opacity-50\"\n >\n Copy link\n </button>\n <button\n type=\"button\"\n onClick={onResend}\n disabled={busy}\n className=\"rounded border border-[var(--border-default)] px-2 py-1 text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:opacity-50\"\n >\n {busy ? 'Working…' : 'Resend'}\n </button>\n <button\n type=\"button\"\n onClick={onRevoke}\n disabled={busy}\n className=\"rounded px-2 py-1 text-xs text-[var(--text-danger)] hover:bg-[var(--border-default)] disabled:opacity-50\"\n >\n Revoke\n </button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
@@ -192,9 +192,6 @@ function createInvitationsApi(opts) {
192
192
  error: "Multiple accounts use this email. Contact support to reconcile before accepting this invitation."
193
193
  };
194
194
  }
195
- if (!currentUser.emailVerified) {
196
- return { succeeded: false, status: 403, error: "Verify your email before accepting this invitation" };
197
- }
198
195
  let orgMember = await getOrganizationMember(invitation.organizationId, currentUser.id);
199
196
  if (orgMember?.role === "owner" || orgMember?.role === "admin") {
200
197
  return { succeeded: false, status: 409, error: "This user already has account-level access." };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/teams/invitations-api.ts"],"sourcesContent":["/**\n * Framework-neutral email-invitation lifecycle for the teams module: create /\n * list / resend / revoke / preview / accept over the dedicated\n * `workspace_invitation` table (from `createWorkspaceInvitationTable`). Lifted out\n * of any one app's route file — each app binds this once to its own db/tables/\n * access and mounts the handlers in its own routes with its own auth.\n *\n * Unlike `members-api` (whose \"invite\" is a pending workspaceMember row, no\n * email), this models the rich lifecycle: a dedicated invitation row with status,\n * 7-day expiry, emailStatus, resend, and revoke. On accept it materializes the\n * `organizationMembers` + `workspaceMembers` rows, so `members-api.listMembers`\n * still surfaces accepted members; pending invites live only here.\n *\n * Handlers return a discriminated `InvitationOutcome` (not a `Response`) so the\n * route adapter maps `{ status }` to its own framework's response. Three seams:\n * - `sendInvitationEmail` (REQUIRED) — the app's mail transport. Returns a\n * typed outcome; a failed send never blocks invitation creation (emailStatus\n * is recorded as 'failed' and the invite link is still returned).\n * - `enforceSeat` (OPTIONAL) — billing/seat-limit gate, called at create time\n * only when the invite would consume a NEW seat. Reused from members-api.\n * - `memberSyncSeam.add` (OPTIONAL) — fire-and-forget propagation of the new\n * member to a sandbox/external system on accept. Fail-soft by contract.\n *\n * Imports `drizzle-orm`, so this is a subpath, never re-exported from root.\n */\n\nimport { and, eq, lte, sql } from 'drizzle-orm'\nimport {\n type InvitationEmailStatus,\n type InvitationPermission,\n type InvitationStatus,\n generateInvitationToken,\n getInvitationExpiresAt,\n inviteUrlForToken,\n normalizeInvitationEmail,\n parseInvitationPermission,\n} from './invitations'\nimport { type WorkspaceRole, hasWorkspaceRole } from './roles'\nimport { type EnforceSeatSeam, type MemberSyncSeam, SeatLimitError } from './members-api'\nimport type { TeamDatabase, WorkspaceAccessApi } from './drizzle/access'\nimport type { TeamParentTable, TeamTables } from './drizzle/schema'\nimport type { WorkspaceInvitationRow, WorkspaceInvitationTables } from './drizzle/invitations-schema'\n\n// Re-export the seams an adopter wires, so a consumer can import everything the\n// invitations API needs from this one subpath.\nexport { SeatLimitError } from './members-api'\nexport type { EnforceSeatSeam, MemberSyncSeam } from './members-api'\n\n/** The app's mail transport. Returns a typed outcome; never throws to the API. */\nexport interface SendInvitationEmailInput {\n to: string\n workspaceName: string\n inviterEmail: string\n permission: InvitationPermission\n inviteUrl: string\n expiresAt: Date\n}\nexport type SendInvitationEmailResult = { succeeded: true } | { succeeded: false; error: string }\nexport interface SendInvitationEmailSeam {\n (input: SendInvitationEmailInput): Promise<SendInvitationEmailResult>\n}\n\n/** The product's user table, narrowed to the columns the queries read. */\nexport interface InvitationUserTable {\n id: any\n email: any\n emailVerified: any\n}\n\n/** The product's workspace table, narrowed to the columns the queries read. */\nexport interface InvitationWorkspaceTable {\n id: any\n name: any\n}\n\nexport interface InvitationsApiOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The invitation table from `createWorkspaceInvitationTable`. */\n invitationsTable: WorkspaceInvitationTables\n userTable: TeamParentTable & InvitationUserTable\n workspaceTable: TeamParentTable & InvitationWorkspaceTable\n access: Pick<WorkspaceAccessApi, 'getWorkspaceAccess'>\n /** REQUIRED — the app's mail transport (e.g. Resend + renderInvitationEmail). */\n sendInvitationEmail: SendInvitationEmailSeam\n /** OPTIONAL — seat-limit gate at create time; mirrors members-api. */\n enforceSeat?: EnforceSeatSeam\n /** OPTIONAL — fire-and-forget member propagation on accept. */\n memberSyncSeam?: Pick<MemberSyncSeam, 'add'>\n /** Inviter display fallback when the inviter's email is unknown. */\n productDisplayName?: string\n}\n\nexport interface WorkspaceInvitationView {\n id: string\n workspaceId: string\n organizationId: string\n email: string\n invitedByUserId: string\n inviterEmail: string | null\n permissions: InvitationPermission\n token: string\n status: InvitationStatus\n emailStatus: InvitationEmailStatus\n expiresAt: Date\n createdAt: Date\n acceptedAt: Date | null\n revokedAt: Date | null\n lastSentAt: Date | null\n}\n\nexport interface InvitationPreview {\n workspaceId: string\n workspaceName: string\n email: string\n inviterEmail: string | null\n permissions: InvitationPermission\n status: InvitationStatus\n expiresAt: Date\n}\n\nexport type InvitationOutcome<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; status: number; error: string }\n\n/**\n * Build the invitations API bound to one product's db/tables/access/seams.\n * Returns the six lifecycle handlers an app mounts in its routes.\n */\nexport function createInvitationsApi(opts: InvitationsApiOptions) {\n const { db, tables, access, sendInvitationEmail, enforceSeat, memberSyncSeam } = opts\n const { organizationMembers, workspaceMembers } = tables\n const { workspaceInvitations } = opts.invitationsTable\n const users = opts.userTable\n const workspaces = opts.workspaceTable\n const productDisplayName = opts.productDisplayName ?? 'A workspace admin'\n\n function fireSync(op: () => Promise<void> | void) {\n try {\n Promise.resolve(op()).catch(() => {})\n } catch {\n // seam threw synchronously — swallow; sync is best-effort by contract.\n }\n }\n\n async function createInvitation(input: {\n workspaceId: string\n email: string\n permissions: string | undefined\n invitedByUserId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const now = input.now ?? new Date()\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.invitedByUserId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const permissions = parseInvitationPermission(input.permissions ?? 'editor')\n if (!permissions) return { succeeded: false, status: 400, error: 'Invalid permissions' }\n if (!hasWorkspaceRole(accessRow.role, permissions)) {\n return { succeeded: false, status: 403, error: 'Cannot assign higher permissions than your own' }\n }\n\n const email = normalizeInvitationEmail(input.email)\n if (!email) return { succeeded: false, status: 400, error: 'Missing email' }\n\n const organizationId = accessRow.organization.id\n const workspaceName = (accessRow.workspace as { name: string }).name\n\n await expirePendingInvitations(input.workspaceId, email, now)\n\n const existingPending = await getPendingInvitation(input.workspaceId, email)\n if (existingPending) return { succeeded: false, status: 409, error: 'A pending invitation already exists for this email' }\n\n const accountCheck = await checkExistingAccountAccess(organizationId, input.workspaceId, email, input.invitedByUserId)\n if (!accountCheck.succeeded) return accountCheck\n\n // Seat gate: only a genuinely NEW seat. The checks above reject re-invites,\n // existing workspace members, and org admins; an existing plain org member is\n // already billable (no new seat), so skip the gate for them — mirrors\n // members-api. The pending row is not inserted yet, so the seam's count\n // reflects current usage and denies the invite that would push OVER the limit\n // (at invite time, not accept time).\n if (enforceSeat && !accountCheck.isExistingOrgMember) {\n try {\n await enforceSeat({ actorId: input.invitedByUserId, organizationId })\n } catch (err) {\n if (err instanceof SeatLimitError) return { succeeded: false, status: err.status, error: err.message }\n throw err\n }\n }\n\n const token = generateInvitationToken()\n const expiresAt = getInvitationExpiresAt(now)\n\n const [created] = await db.insert(workspaceInvitations).values({\n workspaceId: input.workspaceId,\n organizationId,\n email,\n invitedByUserId: input.invitedByUserId,\n permissions,\n token,\n status: 'pending',\n emailStatus: 'not_sent',\n expiresAt,\n createdAt: now,\n }).returning()\n\n const inviteUrl = inviteUrlForToken(input.origin, token)\n const emailResult = await sendInvitationEmail({\n to: email,\n workspaceName,\n inviterEmail: accountCheck.inviterEmail ?? productDisplayName,\n permission: permissions,\n inviteUrl,\n expiresAt,\n })\n\n const emailStatus: InvitationEmailStatus = emailResult.succeeded ? 'sent' : 'failed'\n const [updated] = await db.update(workspaceInvitations)\n .set({ emailStatus, lastSentAt: emailResult.succeeded ? now : null })\n .where(eq(workspaceInvitations.id, created!.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, accountCheck.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function listInvitations(input: {\n workspaceId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitations: Array<WorkspaceInvitationView & { inviteUrl: string }> }>> {\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n await expirePendingInvitations(input.workspaceId, undefined, input.now ?? new Date())\n\n const rows = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.workspaceId, input.workspaceId))\n .orderBy(sql`${workspaceInvitations.createdAt} desc`)\n\n return {\n succeeded: true,\n value: {\n invitations: rows.map((row: { invitation: WorkspaceInvitationRow; inviterEmail: string | null }) => ({\n ...toInvitationView(row.invitation, row.inviterEmail),\n inviteUrl: inviteUrlForToken(input.origin, row.invitation.token),\n })),\n },\n }\n }\n\n async function resendInvitation(input: {\n invitationId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const now = input.now ?? new Date()\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be resent' }\n }\n if (row.invitation.expiresAt <= now) {\n await markInvitationExpired(row.invitation.id)\n return { succeeded: false, status: 409, error: 'Invitation has expired' }\n }\n\n const workspaceName = (accessRow.workspace as { name: string }).name\n const inviteUrl = inviteUrlForToken(input.origin, row.invitation.token)\n const emailResult = await sendInvitationEmail({\n to: row.invitation.email,\n workspaceName,\n inviterEmail: row.inviterEmail ?? productDisplayName,\n permission: row.invitation.permissions,\n inviteUrl,\n expiresAt: row.invitation.expiresAt,\n })\n\n const [updated] = await db.update(workspaceInvitations)\n .set({\n emailStatus: emailResult.succeeded ? 'sent' : 'failed',\n lastSentAt: emailResult.succeeded ? now : row.invitation.lastSentAt,\n })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, row.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function revokeInvitation(input: {\n invitationId: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be revoked' }\n }\n\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'revoked', revokedAt: input.now ?? new Date() })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return { succeeded: true, value: { invitation: toInvitationView(updated!, row.inviterEmail) } }\n }\n\n async function getPreview(token: string, now: Date = new Date()): Promise<InvitationOutcome<InvitationPreview>> {\n const row = await getInvitationByToken(token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n return {\n succeeded: true,\n value: {\n workspaceId: invitation.workspaceId,\n workspaceName: row.workspaceName,\n email: invitation.email,\n inviterEmail: row.inviterEmail,\n permissions: invitation.permissions,\n status: invitation.status,\n expiresAt: invitation.expiresAt,\n },\n }\n }\n\n async function acceptInvitation(input: {\n token: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ workspaceId: string }>> {\n const now = input.now ?? new Date()\n const row = await getInvitationByToken(input.token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n const [currentUser] = await db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(eq(users.id, input.userId))\n .limit(1)\n if (!currentUser) return { succeeded: false, status: 401, error: 'Authentication required' }\n\n if (normalizeInvitationEmail(currentUser.email) !== invitation.email) {\n return { succeeded: false, status: 403, error: 'Sign in with the invited email address to accept this invitation' }\n }\n\n const collisionCheck = await findUsersByNormalizedEmail(invitation.email)\n if (collisionCheck.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before accepting this invitation.',\n }\n }\n\n if (!currentUser.emailVerified) {\n return { succeeded: false, status: 403, error: 'Verify your email before accepting this invitation' }\n }\n\n let orgMember = await getOrganizationMember(invitation.organizationId, currentUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n\n if (invitation.status === 'accepted') {\n const existing = orgMember ? await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id) : null\n if (existing) return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n return { succeeded: false, status: 409, error: 'Invitation has already been accepted' }\n }\n if (invitation.status === 'expired') return { succeeded: false, status: 409, error: 'Invitation has expired' }\n if (invitation.status === 'revoked') return { succeeded: false, status: 409, error: 'Invitation has been revoked' }\n\n if (!orgMember) {\n const [inserted] = await db.insert(organizationMembers).values({\n organizationId: invitation.organizationId,\n userId: currentUser.id,\n role: 'member',\n createdAt: now,\n updatedAt: now,\n }).returning()\n if (!inserted) return { succeeded: false, status: 500, error: 'Failed to create organization membership' }\n orgMember = inserted\n }\n\n const existingMember = await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id)\n if (!existingMember) {\n await db.insert(workspaceMembers).values({\n workspaceId: invitation.workspaceId,\n organizationMemberId: orgMember.id,\n userId: currentUser.id,\n role: invitation.permissions,\n inviteEmail: invitation.email,\n // members-api nulls the token on accept to release the unique slot; the\n // invitations flow keeps it (its `inv_` tokens never collide), so the\n // materialized row carries the originating token for audit.\n inviteToken: invitation.token,\n invitedAt: now,\n acceptedAt: now,\n })\n if (memberSyncSeam?.add) {\n const userId = currentUser.id\n const role = invitation.permissions as WorkspaceRole\n fireSync(() => memberSyncSeam.add!({ workspaceId: invitation.workspaceId, userId, role }))\n }\n }\n\n await db.update(workspaceInvitations)\n .set({ status: 'accepted', acceptedAt: now })\n .where(eq(workspaceInvitations.id, invitation.id))\n\n return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n }\n\n // ── internal query helpers ──\n\n async function expirePendingInvitations(workspaceId: string, email: string | undefined, now: Date) {\n const conditions = [\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.status, 'pending' as const),\n lte(workspaceInvitations.expiresAt, now),\n ]\n if (email) conditions.push(eq(workspaceInvitations.email, email))\n await db.update(workspaceInvitations).set({ status: 'expired' }).where(and(...conditions))\n }\n\n async function getPendingInvitation(workspaceId: string, email: string) {\n const [invitation] = await db\n .select({ id: workspaceInvitations.id })\n .from(workspaceInvitations)\n .where(and(\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.email, email),\n eq(workspaceInvitations.status, 'pending' as const),\n ))\n .limit(1)\n return invitation ?? null\n }\n\n async function checkExistingAccountAccess(\n organizationId: string,\n workspaceId: string,\n email: string,\n invitedByUserId: string,\n ): Promise<\n | { succeeded: false; status: number; error: string }\n | { succeeded: true; inviterEmail: string | null; isExistingOrgMember: boolean }\n > {\n const matchingUsers = await findUsersByNormalizedEmail(email)\n const [inviter] = await db\n .select({ email: users.email })\n .from(users)\n .where(eq(users.id, invitedByUserId))\n .limit(1)\n\n if (matchingUsers.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before inviting this user.',\n }\n }\n const existingUser = matchingUsers[0]\n if (!existingUser) return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: false }\n\n const orgMember = await getOrganizationMember(organizationId, existingUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n if (orgMember) {\n const workspaceMember = await getWorkspaceMemberByOrganizationMember(workspaceId, orgMember.id)\n if (workspaceMember) return { succeeded: false, status: 409, error: 'User is already a workspace member' }\n }\n\n return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: Boolean(orgMember) }\n }\n\n async function findUsersByNormalizedEmail(email: string) {\n return db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(sql`lower(${users.email}) = ${email}`)\n }\n\n async function getOrganizationMember(organizationId: string, userId: string) {\n const [member] = await db\n .select()\n .from(organizationMembers)\n .where(and(eq(organizationMembers.organizationId, organizationId), eq(organizationMembers.userId, userId)))\n .limit(1)\n return member ?? null\n }\n\n async function getWorkspaceMemberByOrganizationMember(workspaceId: string, organizationMemberId: string) {\n const [member] = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .where(and(\n eq(workspaceMembers.workspaceId, workspaceId),\n eq(workspaceMembers.organizationMemberId, organizationMemberId),\n ))\n .limit(1)\n return member ?? null\n }\n\n async function getInvitationById(invitationId: string) {\n const [row] = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.id, invitationId))\n .limit(1)\n return row ?? null\n }\n\n async function getInvitationByToken(token: string) {\n const [row] = await db\n .select({\n invitation: workspaceInvitations,\n workspaceName: workspaces.name,\n inviterEmail: users.email,\n })\n .from(workspaceInvitations)\n .innerJoin(workspaces, eq(workspaces.id, workspaceInvitations.workspaceId))\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.token, token))\n .limit(1)\n return row ?? null\n }\n\n async function markInvitationExpired(invitationId: string) {\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'expired' })\n .where(eq(workspaceInvitations.id, invitationId))\n .returning()\n return updated!\n }\n\n function toInvitationView(invitation: WorkspaceInvitationRow, inviterEmail: string | null): WorkspaceInvitationView {\n return {\n id: invitation.id,\n workspaceId: invitation.workspaceId,\n organizationId: invitation.organizationId,\n email: invitation.email,\n invitedByUserId: invitation.invitedByUserId,\n inviterEmail,\n permissions: invitation.permissions,\n token: invitation.token,\n status: invitation.status,\n emailStatus: invitation.emailStatus,\n expiresAt: invitation.expiresAt,\n createdAt: invitation.createdAt,\n acceptedAt: invitation.acceptedAt,\n revokedAt: invitation.revokedAt,\n lastSentAt: invitation.lastSentAt,\n }\n }\n\n return { createInvitation, listInvitations, resendInvitation, revokeInvitation, getPreview, acceptInvitation }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/teams/invitations-api.ts"],"sourcesContent":["/**\n * Framework-neutral email-invitation lifecycle for the teams module: create /\n * list / resend / revoke / preview / accept over the dedicated\n * `workspace_invitation` table (from `createWorkspaceInvitationTable`). Lifted out\n * of any one app's route file — each app binds this once to its own db/tables/\n * access and mounts the handlers in its own routes with its own auth.\n *\n * Unlike `members-api` (whose \"invite\" is a pending workspaceMember row, no\n * email), this models the rich lifecycle: a dedicated invitation row with status,\n * 7-day expiry, emailStatus, resend, and revoke. On accept it materializes the\n * `organizationMembers` + `workspaceMembers` rows, so `members-api.listMembers`\n * still surfaces accepted members; pending invites live only here.\n *\n * Handlers return a discriminated `InvitationOutcome` (not a `Response`) so the\n * route adapter maps `{ status }` to its own framework's response. Three seams:\n * - `sendInvitationEmail` (REQUIRED) — the app's mail transport. Returns a\n * typed outcome; a failed send never blocks invitation creation (emailStatus\n * is recorded as 'failed' and the invite link is still returned).\n * - `enforceSeat` (OPTIONAL) — billing/seat-limit gate, called at create time\n * only when the invite would consume a NEW seat. Reused from members-api.\n * - `memberSyncSeam.add` (OPTIONAL) — fire-and-forget propagation of the new\n * member to a sandbox/external system on accept. Fail-soft by contract.\n *\n * Imports `drizzle-orm`, so this is a subpath, never re-exported from root.\n */\n\nimport { and, eq, lte, sql } from 'drizzle-orm'\nimport {\n type InvitationEmailStatus,\n type InvitationPermission,\n type InvitationStatus,\n generateInvitationToken,\n getInvitationExpiresAt,\n inviteUrlForToken,\n normalizeInvitationEmail,\n parseInvitationPermission,\n} from './invitations'\nimport { type WorkspaceRole, hasWorkspaceRole } from './roles'\nimport { type EnforceSeatSeam, type MemberSyncSeam, SeatLimitError } from './members-api'\nimport type { TeamDatabase, WorkspaceAccessApi } from './drizzle/access'\nimport type { TeamParentTable, TeamTables } from './drizzle/schema'\nimport type { WorkspaceInvitationRow, WorkspaceInvitationTables } from './drizzle/invitations-schema'\n\n// Re-export the seams an adopter wires, so a consumer can import everything the\n// invitations API needs from this one subpath.\nexport { SeatLimitError } from './members-api'\nexport type { EnforceSeatSeam, MemberSyncSeam } from './members-api'\n\n/** The app's mail transport. Returns a typed outcome; never throws to the API. */\nexport interface SendInvitationEmailInput {\n to: string\n workspaceName: string\n inviterEmail: string\n permission: InvitationPermission\n inviteUrl: string\n expiresAt: Date\n}\nexport type SendInvitationEmailResult = { succeeded: true } | { succeeded: false; error: string }\nexport interface SendInvitationEmailSeam {\n (input: SendInvitationEmailInput): Promise<SendInvitationEmailResult>\n}\n\n/** The product's user table, narrowed to the columns the queries read. */\nexport interface InvitationUserTable {\n id: any\n email: any\n emailVerified: any\n}\n\n/** The product's workspace table, narrowed to the columns the queries read. */\nexport interface InvitationWorkspaceTable {\n id: any\n name: any\n}\n\nexport interface InvitationsApiOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The invitation table from `createWorkspaceInvitationTable`. */\n invitationsTable: WorkspaceInvitationTables\n userTable: TeamParentTable & InvitationUserTable\n workspaceTable: TeamParentTable & InvitationWorkspaceTable\n access: Pick<WorkspaceAccessApi, 'getWorkspaceAccess'>\n /** REQUIRED — the app's mail transport (e.g. Resend + renderInvitationEmail). */\n sendInvitationEmail: SendInvitationEmailSeam\n /** OPTIONAL — seat-limit gate at create time; mirrors members-api. */\n enforceSeat?: EnforceSeatSeam\n /** OPTIONAL — fire-and-forget member propagation on accept. */\n memberSyncSeam?: Pick<MemberSyncSeam, 'add'>\n /** Inviter display fallback when the inviter's email is unknown. */\n productDisplayName?: string\n}\n\nexport interface WorkspaceInvitationView {\n id: string\n workspaceId: string\n organizationId: string\n email: string\n invitedByUserId: string\n inviterEmail: string | null\n permissions: InvitationPermission\n token: string\n status: InvitationStatus\n emailStatus: InvitationEmailStatus\n expiresAt: Date\n createdAt: Date\n acceptedAt: Date | null\n revokedAt: Date | null\n lastSentAt: Date | null\n}\n\nexport interface InvitationPreview {\n workspaceId: string\n workspaceName: string\n email: string\n inviterEmail: string | null\n permissions: InvitationPermission\n status: InvitationStatus\n expiresAt: Date\n}\n\nexport type InvitationOutcome<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; status: number; error: string }\n\n/**\n * Build the invitations API bound to one product's db/tables/access/seams.\n * Returns the six lifecycle handlers an app mounts in its routes.\n */\nexport function createInvitationsApi(opts: InvitationsApiOptions) {\n const { db, tables, access, sendInvitationEmail, enforceSeat, memberSyncSeam } = opts\n const { organizationMembers, workspaceMembers } = tables\n const { workspaceInvitations } = opts.invitationsTable\n const users = opts.userTable\n const workspaces = opts.workspaceTable\n const productDisplayName = opts.productDisplayName ?? 'A workspace admin'\n\n function fireSync(op: () => Promise<void> | void) {\n try {\n Promise.resolve(op()).catch(() => {})\n } catch {\n // seam threw synchronously — swallow; sync is best-effort by contract.\n }\n }\n\n async function createInvitation(input: {\n workspaceId: string\n email: string\n permissions: string | undefined\n invitedByUserId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const now = input.now ?? new Date()\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.invitedByUserId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const permissions = parseInvitationPermission(input.permissions ?? 'editor')\n if (!permissions) return { succeeded: false, status: 400, error: 'Invalid permissions' }\n if (!hasWorkspaceRole(accessRow.role, permissions)) {\n return { succeeded: false, status: 403, error: 'Cannot assign higher permissions than your own' }\n }\n\n const email = normalizeInvitationEmail(input.email)\n if (!email) return { succeeded: false, status: 400, error: 'Missing email' }\n\n const organizationId = accessRow.organization.id\n const workspaceName = (accessRow.workspace as { name: string }).name\n\n await expirePendingInvitations(input.workspaceId, email, now)\n\n const existingPending = await getPendingInvitation(input.workspaceId, email)\n if (existingPending) return { succeeded: false, status: 409, error: 'A pending invitation already exists for this email' }\n\n const accountCheck = await checkExistingAccountAccess(organizationId, input.workspaceId, email, input.invitedByUserId)\n if (!accountCheck.succeeded) return accountCheck\n\n // Seat gate: only a genuinely NEW seat. The checks above reject re-invites,\n // existing workspace members, and org admins; an existing plain org member is\n // already billable (no new seat), so skip the gate for them — mirrors\n // members-api. The pending row is not inserted yet, so the seam's count\n // reflects current usage and denies the invite that would push OVER the limit\n // (at invite time, not accept time).\n if (enforceSeat && !accountCheck.isExistingOrgMember) {\n try {\n await enforceSeat({ actorId: input.invitedByUserId, organizationId })\n } catch (err) {\n if (err instanceof SeatLimitError) return { succeeded: false, status: err.status, error: err.message }\n throw err\n }\n }\n\n const token = generateInvitationToken()\n const expiresAt = getInvitationExpiresAt(now)\n\n const [created] = await db.insert(workspaceInvitations).values({\n workspaceId: input.workspaceId,\n organizationId,\n email,\n invitedByUserId: input.invitedByUserId,\n permissions,\n token,\n status: 'pending',\n emailStatus: 'not_sent',\n expiresAt,\n createdAt: now,\n }).returning()\n\n const inviteUrl = inviteUrlForToken(input.origin, token)\n const emailResult = await sendInvitationEmail({\n to: email,\n workspaceName,\n inviterEmail: accountCheck.inviterEmail ?? productDisplayName,\n permission: permissions,\n inviteUrl,\n expiresAt,\n })\n\n const emailStatus: InvitationEmailStatus = emailResult.succeeded ? 'sent' : 'failed'\n const [updated] = await db.update(workspaceInvitations)\n .set({ emailStatus, lastSentAt: emailResult.succeeded ? now : null })\n .where(eq(workspaceInvitations.id, created!.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, accountCheck.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function listInvitations(input: {\n workspaceId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitations: Array<WorkspaceInvitationView & { inviteUrl: string }> }>> {\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n await expirePendingInvitations(input.workspaceId, undefined, input.now ?? new Date())\n\n const rows = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.workspaceId, input.workspaceId))\n .orderBy(sql`${workspaceInvitations.createdAt} desc`)\n\n return {\n succeeded: true,\n value: {\n invitations: rows.map((row: { invitation: WorkspaceInvitationRow; inviterEmail: string | null }) => ({\n ...toInvitationView(row.invitation, row.inviterEmail),\n inviteUrl: inviteUrlForToken(input.origin, row.invitation.token),\n })),\n },\n }\n }\n\n async function resendInvitation(input: {\n invitationId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const now = input.now ?? new Date()\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be resent' }\n }\n if (row.invitation.expiresAt <= now) {\n await markInvitationExpired(row.invitation.id)\n return { succeeded: false, status: 409, error: 'Invitation has expired' }\n }\n\n const workspaceName = (accessRow.workspace as { name: string }).name\n const inviteUrl = inviteUrlForToken(input.origin, row.invitation.token)\n const emailResult = await sendInvitationEmail({\n to: row.invitation.email,\n workspaceName,\n inviterEmail: row.inviterEmail ?? productDisplayName,\n permission: row.invitation.permissions,\n inviteUrl,\n expiresAt: row.invitation.expiresAt,\n })\n\n const [updated] = await db.update(workspaceInvitations)\n .set({\n emailStatus: emailResult.succeeded ? 'sent' : 'failed',\n lastSentAt: emailResult.succeeded ? now : row.invitation.lastSentAt,\n })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, row.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function revokeInvitation(input: {\n invitationId: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be revoked' }\n }\n\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'revoked', revokedAt: input.now ?? new Date() })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return { succeeded: true, value: { invitation: toInvitationView(updated!, row.inviterEmail) } }\n }\n\n async function getPreview(token: string, now: Date = new Date()): Promise<InvitationOutcome<InvitationPreview>> {\n const row = await getInvitationByToken(token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n return {\n succeeded: true,\n value: {\n workspaceId: invitation.workspaceId,\n workspaceName: row.workspaceName,\n email: invitation.email,\n inviterEmail: row.inviterEmail,\n permissions: invitation.permissions,\n status: invitation.status,\n expiresAt: invitation.expiresAt,\n },\n }\n }\n\n async function acceptInvitation(input: {\n token: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ workspaceId: string }>> {\n const now = input.now ?? new Date()\n const row = await getInvitationByToken(input.token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n const [currentUser] = await db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(eq(users.id, input.userId))\n .limit(1)\n if (!currentUser) return { succeeded: false, status: 401, error: 'Authentication required' }\n\n if (normalizeInvitationEmail(currentUser.email) !== invitation.email) {\n return { succeeded: false, status: 403, error: 'Sign in with the invited email address to accept this invitation' }\n }\n\n const collisionCheck = await findUsersByNormalizedEmail(invitation.email)\n if (collisionCheck.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before accepting this invitation.',\n }\n }\n\n let orgMember = await getOrganizationMember(invitation.organizationId, currentUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n\n if (invitation.status === 'accepted') {\n const existing = orgMember ? await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id) : null\n if (existing) return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n return { succeeded: false, status: 409, error: 'Invitation has already been accepted' }\n }\n if (invitation.status === 'expired') return { succeeded: false, status: 409, error: 'Invitation has expired' }\n if (invitation.status === 'revoked') return { succeeded: false, status: 409, error: 'Invitation has been revoked' }\n\n if (!orgMember) {\n const [inserted] = await db.insert(organizationMembers).values({\n organizationId: invitation.organizationId,\n userId: currentUser.id,\n role: 'member',\n createdAt: now,\n updatedAt: now,\n }).returning()\n if (!inserted) return { succeeded: false, status: 500, error: 'Failed to create organization membership' }\n orgMember = inserted\n }\n\n const existingMember = await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id)\n if (!existingMember) {\n await db.insert(workspaceMembers).values({\n workspaceId: invitation.workspaceId,\n organizationMemberId: orgMember.id,\n userId: currentUser.id,\n role: invitation.permissions,\n inviteEmail: invitation.email,\n // members-api nulls the token on accept to release the unique slot; the\n // invitations flow keeps it (its `inv_` tokens never collide), so the\n // materialized row carries the originating token for audit.\n inviteToken: invitation.token,\n invitedAt: now,\n acceptedAt: now,\n })\n if (memberSyncSeam?.add) {\n const userId = currentUser.id\n const role = invitation.permissions as WorkspaceRole\n fireSync(() => memberSyncSeam.add!({ workspaceId: invitation.workspaceId, userId, role }))\n }\n }\n\n await db.update(workspaceInvitations)\n .set({ status: 'accepted', acceptedAt: now })\n .where(eq(workspaceInvitations.id, invitation.id))\n\n return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n }\n\n // ── internal query helpers ──\n\n async function expirePendingInvitations(workspaceId: string, email: string | undefined, now: Date) {\n const conditions = [\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.status, 'pending' as const),\n lte(workspaceInvitations.expiresAt, now),\n ]\n if (email) conditions.push(eq(workspaceInvitations.email, email))\n await db.update(workspaceInvitations).set({ status: 'expired' }).where(and(...conditions))\n }\n\n async function getPendingInvitation(workspaceId: string, email: string) {\n const [invitation] = await db\n .select({ id: workspaceInvitations.id })\n .from(workspaceInvitations)\n .where(and(\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.email, email),\n eq(workspaceInvitations.status, 'pending' as const),\n ))\n .limit(1)\n return invitation ?? null\n }\n\n async function checkExistingAccountAccess(\n organizationId: string,\n workspaceId: string,\n email: string,\n invitedByUserId: string,\n ): Promise<\n | { succeeded: false; status: number; error: string }\n | { succeeded: true; inviterEmail: string | null; isExistingOrgMember: boolean }\n > {\n const matchingUsers = await findUsersByNormalizedEmail(email)\n const [inviter] = await db\n .select({ email: users.email })\n .from(users)\n .where(eq(users.id, invitedByUserId))\n .limit(1)\n\n if (matchingUsers.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before inviting this user.',\n }\n }\n const existingUser = matchingUsers[0]\n if (!existingUser) return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: false }\n\n const orgMember = await getOrganizationMember(organizationId, existingUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n if (orgMember) {\n const workspaceMember = await getWorkspaceMemberByOrganizationMember(workspaceId, orgMember.id)\n if (workspaceMember) return { succeeded: false, status: 409, error: 'User is already a workspace member' }\n }\n\n return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: Boolean(orgMember) }\n }\n\n async function findUsersByNormalizedEmail(email: string) {\n return db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(sql`lower(${users.email}) = ${email}`)\n }\n\n async function getOrganizationMember(organizationId: string, userId: string) {\n const [member] = await db\n .select()\n .from(organizationMembers)\n .where(and(eq(organizationMembers.organizationId, organizationId), eq(organizationMembers.userId, userId)))\n .limit(1)\n return member ?? null\n }\n\n async function getWorkspaceMemberByOrganizationMember(workspaceId: string, organizationMemberId: string) {\n const [member] = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .where(and(\n eq(workspaceMembers.workspaceId, workspaceId),\n eq(workspaceMembers.organizationMemberId, organizationMemberId),\n ))\n .limit(1)\n return member ?? null\n }\n\n async function getInvitationById(invitationId: string) {\n const [row] = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.id, invitationId))\n .limit(1)\n return row ?? null\n }\n\n async function getInvitationByToken(token: string) {\n const [row] = await db\n .select({\n invitation: workspaceInvitations,\n workspaceName: workspaces.name,\n inviterEmail: users.email,\n })\n .from(workspaceInvitations)\n .innerJoin(workspaces, eq(workspaces.id, workspaceInvitations.workspaceId))\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.token, token))\n .limit(1)\n return row ?? null\n }\n\n async function markInvitationExpired(invitationId: string) {\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'expired' })\n .where(eq(workspaceInvitations.id, invitationId))\n .returning()\n return updated!\n }\n\n function toInvitationView(invitation: WorkspaceInvitationRow, inviterEmail: string | null): WorkspaceInvitationView {\n return {\n id: invitation.id,\n workspaceId: invitation.workspaceId,\n organizationId: invitation.organizationId,\n email: invitation.email,\n invitedByUserId: invitation.invitedByUserId,\n inviterEmail,\n permissions: invitation.permissions,\n token: invitation.token,\n status: invitation.status,\n emailStatus: invitation.emailStatus,\n expiresAt: invitation.expiresAt,\n createdAt: invitation.createdAt,\n acceptedAt: invitation.acceptedAt,\n revokedAt: invitation.revokedAt,\n lastSentAt: invitation.lastSentAt,\n }\n }\n\n return { createInvitation, listInvitations, resendInvitation, revokeInvitation, getPreview, acceptInvitation }\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  } from "../chunk-GNL3MG5J.js";
4
4
  import {
5
5
  InvitationsPanel
6
- } from "../chunk-F7D7YCUL.js";
6
+ } from "../chunk-7C64WKAH.js";
7
7
  import {
8
8
  InviteAcceptPage
9
9
  } from "../chunk-VCPZ3HTN.js";
@@ -4,7 +4,7 @@ var MembersPanelLazy = lazy(
4
4
  () => import("../MembersPanel-FOVHWUQ3.js").then((m) => ({ default: m.MembersPanel }))
5
5
  );
6
6
  var InvitationsPanelLazy = lazy(
7
- () => import("../InvitationsPanel-ZKAUYZYU.js").then((m) => ({ default: m.InvitationsPanel }))
7
+ () => import("../InvitationsPanel-XUUWZBPV.js").then((m) => ({ default: m.InvitationsPanel }))
8
8
  );
9
9
  var InviteAcceptPageLazy = lazy(
10
10
  () => import("../InviteAcceptPage-P3QJC3C6.js").then((m) => ({ default: m.InviteAcceptPage }))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.42.7",
3
+ "version": "0.42.9",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/teams-react/components/InvitationsPanel.tsx"],"sourcesContent":["/**\n * Email-invitation panel: invite by email + role, and a history of every\n * invitation (pending / accepted / expired / revoked) with its email-delivery\n * status. Pending rows expose copy-link / resend / revoke. Fully callback-driven\n * — the host supplies the data and the async `onInvite` / `onResend` / `onRevoke`\n * callbacks (backed by `./teams/invitations-api`), so this imports no app router,\n * fetch client, or toast. Styled with the shipped Tangle Quiet tokens (`var(--*)`).\n *\n * Mount alongside a list-only `MembersPanel` (`showInviteForm={false}`): the\n * members panel shows accepted members, this owns the invite flow + pending list.\n */\n\nimport { useState } from 'react'\nimport type { WorkspaceRole } from '../../teams/roles'\nimport { hasWorkspaceRole } from '../../teams/roles'\nimport type { InvitationView, InvitationsPanelProps } from '../contracts'\n\nconst ASSIGNABLE: { value: WorkspaceRole; label: string }[] = [\n { value: 'viewer', label: 'Viewer' },\n { value: 'editor', label: 'Editor' },\n { value: 'admin', label: 'Admin' },\n]\n\nexport function InvitationsPanel({\n invitations,\n currentRole,\n onInvite,\n onResend,\n onRevoke,\n onCopy,\n onNotice,\n}: InvitationsPanelProps) {\n const [inviteEmail, setInviteEmail] = useState('')\n const [inviteRole, setInviteRole] = useState<WorkspaceRole>('editor')\n const [inviting, setInviting] = useState(false)\n const [busyId, setBusyId] = useState<string | null>(null)\n\n const canManage = hasWorkspaceRole(currentRole, 'admin')\n\n function notify(kind: 'success' | 'error', message: string) {\n onNotice?.({ kind, message })\n }\n\n async function submitInvite() {\n const email = inviteEmail.trim()\n if (!email || inviting) return\n setInviting(true)\n try {\n await onInvite({ email, role: inviteRole })\n notify('success', `Invitation sent to ${email}`)\n setInviteEmail('')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to invite')\n } finally {\n setInviting(false)\n }\n }\n\n async function copyLink(inviteUrl: string) {\n try {\n if (onCopy) await onCopy({ inviteUrl })\n else await navigator.clipboard.writeText(inviteUrl)\n notify('success', 'Invite link copied')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to copy link')\n }\n }\n\n async function resend(invitationId: string) {\n setBusyId(invitationId)\n try {\n await onResend({ invitationId })\n notify('success', 'Invitation resent')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to resend invitation')\n } finally {\n setBusyId(null)\n }\n }\n\n async function revoke(invitationId: string) {\n setBusyId(invitationId)\n try {\n await onRevoke({ invitationId })\n notify('success', 'Invitation revoked')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to revoke invitation')\n } finally {\n setBusyId(null)\n }\n }\n\n return (\n <section className=\"flex flex-col gap-4\">\n {canManage && (\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-xs font-medium text-[var(--text-muted)]\">Invite by email</label>\n <div className=\"flex gap-2\">\n <input\n type=\"email\"\n placeholder=\"colleague@example.com\"\n value={inviteEmail}\n aria-label=\"Invite email address\"\n onChange={(event) => setInviteEmail(event.target.value)}\n onKeyDown={(event) => { if (event.key === 'Enter') void submitInvite() }}\n className=\"flex-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-muted)]\"\n />\n <select\n value={inviteRole}\n aria-label=\"Invite role\"\n onChange={(event) => setInviteRole(event.target.value as WorkspaceRole)}\n className=\"rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1.5 text-xs text-[var(--text-secondary)]\"\n >\n {ASSIGNABLE.map((option) => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => void submitInvite()}\n disabled={inviting || !inviteEmail.trim()}\n className=\"rounded bg-[var(--brand-primary)] px-3 py-1.5 text-sm text-white disabled:opacity-50\"\n >\n {inviting ? 'Inviting…' : 'Invite'}\n </button>\n </div>\n </div>\n )}\n\n <div className=\"flex flex-col gap-2 border-t border-[var(--border-default)] pt-3\">\n <div className=\"flex items-center justify-between\">\n <h4 className=\"text-xs font-semibold text-[var(--text-primary)]\">Invitation history</h4>\n <span className=\"text-xs text-[var(--text-muted)]\">{invitations.length} total</span>\n </div>\n {invitations.length === 0 ? (\n <p className=\"text-sm text-[var(--text-muted)]\">No invitations yet.</p>\n ) : (\n invitations.map((invitation) => (\n <InvitationRow\n key={invitation.id}\n invitation={invitation}\n canManage={canManage}\n busy={busyId === invitation.id}\n onCopy={() => void copyLink(invitation.inviteUrl)}\n onResend={() => void resend(invitation.id)}\n onRevoke={() => void revoke(invitation.id)}\n />\n ))\n )}\n </div>\n </section>\n )\n}\n\ninterface InvitationRowProps {\n invitation: InvitationView\n canManage: boolean\n busy: boolean\n onCopy(): void\n onResend(): void\n onRevoke(): void\n}\n\nfunction InvitationRow({ invitation, canManage, busy, onCopy, onResend, onRevoke }: InvitationRowProps) {\n const isPending = invitation.status === 'pending'\n const emailFailed = invitation.emailStatus === 'failed'\n const expiry = new Date(invitation.expiresAt).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })\n\n return (\n <div className=\"rounded-lg border border-[var(--border-default)] p-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-[var(--text-primary)]\">{invitation.email}</p>\n <p className=\"text-xs capitalize text-[var(--text-muted)]\">\n {invitation.permissions} · expires {expiry}\n </p>\n {emailFailed && (\n <p className=\"mt-1 text-xs text-[var(--text-danger)]\">Email was not sent — copy the link to share it.</p>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <span className=\"rounded border border-[var(--border-default)] px-2 py-0.5 text-[10px] uppercase text-[var(--text-secondary)]\">\n {invitation.status}\n </span>\n <span\n className={`rounded border border-[var(--border-default)] px-2 py-0.5 text-[10px] uppercase ${\n emailFailed ? 'text-[var(--text-danger)]' : 'text-[var(--text-muted)]'\n }`}\n >\n {invitation.emailStatus.replace('_', ' ')}\n </span>\n </div>\n </div>\n {canManage && isPending && (\n <div className=\"mt-3 flex items-center justify-end gap-2\">\n <button\n type=\"button\"\n onClick={onCopy}\n disabled={busy}\n className=\"rounded border border-[var(--border-default)] px-2 py-1 text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:opacity-50\"\n >\n Copy link\n </button>\n <button\n type=\"button\"\n onClick={onResend}\n disabled={busy}\n className=\"rounded border border-[var(--border-default)] px-2 py-1 text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:opacity-50\"\n >\n {busy ? 'Working…' : 'Resend'}\n </button>\n <button\n type=\"button\"\n onClick={onRevoke}\n disabled={busy}\n className=\"rounded px-2 py-1 text-xs text-[var(--text-danger)] hover:bg-[var(--border-default)] disabled:opacity-50\"\n >\n Revoke\n </button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}