@burdenoff/microfe-planmagnet 2026.1008.1 → 2026.1008.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"TeamInviteMemberPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,wBAAwB,4CAiRvC"}
1
+ {"version":3,"file":"TeamInviteMemberPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"names":[],"mappings":"AA8EA,wBAAgB,wBAAwB,4CAwRvC"}
@@ -1,18 +1,19 @@
1
- import { usePlanMagnet as e, usePlanMagnetNavigate as ee } from "../providers/PlanMagnetProvider.js";
2
- import { useTr as te } from "../../shared/hooks/useTr.js";
3
- import { throwOnError as t } from "../utils/mutationResult.js";
4
- import { refusalMessage as n } from "../product/refusal.js";
5
- import { isValidInviteEmail as r } from "../utils/email.js";
6
- import { invitationEmailReason as i, invitationEmailSent as a } from "../team/invitationEmail.js";
7
- import { PENDING_INVITATIONS_QUERY as o, PendingInvitations as s, RESEND_INVITATION as ne } from "../team/PendingInvitations.js";
8
- import { useMemo as c, useState as l } from "react";
9
- import { RefreshCw as u, Send as d, UserPlus as f, X as re } from "lucide-react";
10
- import { gql as p } from "@apollo/client";
11
- import { useMutation as m, useQuery as h } from "@apollo/client/react";
12
- import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
13
- import { Button as y, Card as b, CardContent as x, CardHeader as S, CardTitle as C, Input as w } from "@burdenoff/fe-libs/ui";
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { useTr as n } from "../../shared/hooks/useTr.js";
3
+ import { throwOnError as r } from "../utils/mutationResult.js";
4
+ import { refusalMessage as i } from "../product/refusal.js";
5
+ import { isValidInviteEmail as a } from "../utils/email.js";
6
+ import { invitationEmailReason as o, invitationEmailSent as s } from "../team/invitationEmail.js";
7
+ import { PENDING_INVITATIONS_QUERY as c, PendingInvitations as l, RESEND_INVITATION as u } from "../team/PendingInvitations.js";
8
+ import { useMemo as d, useState as f } from "react";
9
+ import { RefreshCw as p, Send as ee, UserPlus as te, X as ne } from "lucide-react";
10
+ import { gql as m } from "@apollo/client";
11
+ import { useApolloClient as re, useMutation as h, useQuery as ie } from "@apollo/client/react";
12
+ import { Fragment as ae, jsx as g, jsxs as _ } from "react/jsx-runtime";
13
+ import { Button as v, Card as y, CardContent as b, CardHeader as x, CardTitle as S, Input as C } from "@burdenoff/fe-libs/ui";
14
+ import { clearGraphqlFetchResponseCache as w } from "@burdenoff/fe-libs/shared/graphql";
14
15
  //#region src/planmagnet/pages/TeamInviteMemberPagePage.tsx
15
- var T = p`
16
+ var T = m`
16
17
  query PlanmagnetInviteRoles($scopeId: String!) {
17
18
  roles(
18
19
  scopeId: $scopeId
@@ -24,7 +25,7 @@ var T = p`
24
25
  description
25
26
  }
26
27
  }
27
- `, E = p`
28
+ `, E = m`
28
29
  mutation PlanmagnetInviteMember($input: CreateInvitationInput!) {
29
30
  createInvitation(input: $input) {
30
31
  id
@@ -37,190 +38,189 @@ var T = p`
37
38
  }
38
39
  }
39
40
  `, D = {
40
- refetchQueries: [o],
41
+ refetchQueries: [c],
41
42
  awaitRefetchQueries: !0
42
43
  };
43
44
  function O() {
44
- let o = te(), { workspaceId: p, currentUser: O } = e(), k = ee(), A = h(T, {
45
- variables: { scopeId: p },
46
- skip: !p
47
- }), [ie, j] = m(E, D), [M, N] = m(ne, D), [P, F] = l(""), [I, L] = l(!1), [R, z] = l(""), [B, V] = l(null), [H, U] = l(null), W = H?.workspaceId === (p ?? null) ? H : null, [G, K] = l(null), q = c(() => A.data?.roles ?? [], [A.data]), J = c(() => new Map(q.map((e) => [e.id, e.name])), [q]), Y = q.find((e) => e.id === R) ?? null, X = r(P), Z = X && !!Y && !j.loading && !!p, Q = o("planmagnet.invite.pending.forbidden", "Your access level does not allow managing invitations in this workspace."), ae = async () => {
45
+ let m = n(), { workspaceId: O } = e(), oe = t(), k = re(), A = ie(T, {
46
+ variables: { scopeId: O },
47
+ skip: !O
48
+ }), [j, M] = h(E, D), [N, P] = h(u, D), [F, I] = f(""), [L, R] = f(!1), [z, B] = f(""), [V, H] = f(null), [U, W] = f(null), G = U?.workspaceId === (O ?? null) ? U : null, [K, q] = f(null), J = d(() => A.data?.roles ?? [], [A.data]), se = d(() => new Map(J.map((e) => [e.id, e.name])), [J]), Y = J.find((e) => e.id === z) ?? null, X = a(F), Z = X && !!Y && !M.loading && !!O, Q = m("planmagnet.invite.pending.forbidden", "Your access level does not allow managing invitations in this workspace."), ce = async () => {
48
49
  if (!(!Z || !Y)) {
49
- V(null), U(null), K(null);
50
+ H(null), W(null), q(null);
50
51
  try {
51
- let e = (await t(ie({ variables: { input: {
52
- email: P.trim(),
52
+ let e = (await r(j({ variables: { input: {
53
+ email: F.trim(),
53
54
  roleIds: [Y.id],
54
- workspaceId: p
55
+ workspaceId: O
55
56
  } } }))).data?.createInvitation;
56
- e && U({
57
+ e && W({
57
58
  ...e,
58
- workspaceId: p ?? null
59
- }), F(""), L(!1);
59
+ workspaceId: O ?? null
60
+ }), I(""), R(!1);
60
61
  } catch (e) {
61
- V(n(e, o("planmagnet.invite.errors.create", "The invitation could not be created."), Q));
62
+ H(i(e, m("planmagnet.invite.errors.create", "The invitation could not be created."), Q));
62
63
  }
63
64
  }
64
- }, oe = async () => {
65
- if (W) {
66
- K(null);
65
+ }, le = async () => {
66
+ if (G) {
67
+ q(null);
67
68
  try {
68
- let e = (await t(M({ variables: { id: W.id } }))).data?.resendInvitation;
69
- e && U({
70
- ...W,
69
+ let e = (await r(N({ variables: { id: G.id } }))).data?.resendInvitation;
70
+ e && W({
71
+ ...G,
71
72
  emailStatus: e.emailStatus,
72
73
  emailLastAttemptAt: e.emailLastAttemptAt,
73
74
  emailErrorCode: e.emailErrorCode
74
75
  });
75
76
  } catch (e) {
76
- K(n(e, o("planmagnet.invite.errors.resend", "The invitation email could not be resent."), Q));
77
+ q(i(e, m("planmagnet.invite.errors.resend", "The invitation email could not be resent."), Q)), w(), k.refetchQueries({ include: [c] });
77
78
  }
78
79
  }
79
- }, $ = W ? i(W.emailErrorCode) : null;
80
- return /* @__PURE__ */ v("div", {
80
+ }, $ = G ? o(G.emailErrorCode) : null;
81
+ return /* @__PURE__ */ _("div", {
81
82
  className: "max-w-3xl space-y-6 p-6",
82
83
  children: [
83
- /* @__PURE__ */ v("div", {
84
+ /* @__PURE__ */ _("div", {
84
85
  className: "flex items-center justify-between",
85
- children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("h1", {
86
+ children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("h1", {
86
87
  className: "text-2xl font-semibold",
87
- children: o("planmagnet.invite.title", "Invite Team Member")
88
- }), /* @__PURE__ */ _("p", {
88
+ children: m("planmagnet.invite.title", "Invite Team Member")
89
+ }), /* @__PURE__ */ g("p", {
89
90
  className: "text-sm text-muted-foreground",
90
- children: o("planmagnet.invite.subtitle", "Invite someone to this workspace with a PlanMagnet access level.")
91
- })] }), /* @__PURE__ */ v(y, {
91
+ children: m("planmagnet.invite.subtitle", "Invite someone to this workspace with a PlanMagnet access level.")
92
+ })] }), /* @__PURE__ */ _(v, {
92
93
  variant: "outline",
93
94
  size: "sm",
94
- onClick: () => k("/team/team-page"),
95
- children: [/* @__PURE__ */ _(re, { className: "mr-1 h-4 w-4" }), o("planmagnet.invite.back", "Back to team")]
95
+ onClick: () => oe("/team/team-page"),
96
+ children: [/* @__PURE__ */ g(ne, { className: "mr-1 h-4 w-4" }), m("planmagnet.invite.back", "Back to team")]
96
97
  })]
97
98
  }),
98
- W && a(W) && /* @__PURE__ */ _("div", {
99
+ G && s(G) && /* @__PURE__ */ g("div", {
99
100
  role: "status",
100
101
  className: "rounded-md border border-border bg-muted p-3 text-sm text-status-success-text",
101
102
  "data-testid": "invite-sent",
102
- children: o("planmagnet.invite.sent", "Invitation sent to {{email}}. They get this access when they accept it.", { email: W.email })
103
+ children: m("planmagnet.invite.sent", "Invitation sent to {{email}}. They get this access when they accept it.", { email: G.email })
103
104
  }),
104
- W && !a(W) && $ && /* @__PURE__ */ v("div", {
105
+ G && !s(G) && $ && /* @__PURE__ */ _("div", {
105
106
  role: "status",
106
107
  className: "space-y-2 rounded-md border border-border bg-muted p-3 text-sm",
107
108
  "data-testid": "invite-email-not-sent",
108
109
  children: [
109
- /* @__PURE__ */ _("p", {
110
+ /* @__PURE__ */ g("p", {
110
111
  className: "font-medium text-status-warning-text",
111
- children: o("planmagnet.invite.emailNotSent", "Invitation created for {{email}}, but the email was not sent.", { email: W.email })
112
+ children: m("planmagnet.invite.emailNotSent", "Invitation created for {{email}}, but the email was not sent.", { email: G.email })
112
113
  }),
113
- /* @__PURE__ */ _("p", {
114
+ !K && /* @__PURE__ */ g("p", {
114
115
  className: "text-muted-foreground",
115
116
  "data-testid": "invite-email-not-sent-reason",
116
- children: o($.key, $.fallback)
117
+ children: m($.key, $.fallback)
117
118
  }),
118
- /* @__PURE__ */ v(y, {
119
+ /* @__PURE__ */ _(v, {
119
120
  size: "sm",
120
121
  variant: "outline",
121
- onClick: () => void oe(),
122
- disabled: N.loading,
122
+ onClick: () => void le(),
123
+ disabled: P.loading,
123
124
  "data-testid": "invite-resend",
124
- children: [/* @__PURE__ */ _(u, { className: "mr-1 h-4 w-4" }), N.loading ? o("planmagnet.invite.resending", "Resending…") : o("planmagnet.invite.resend", "Resend email")]
125
+ children: [/* @__PURE__ */ g(p, { className: "mr-1 h-4 w-4" }), P.loading ? m("planmagnet.invite.resending", "Resending…") : m("planmagnet.invite.resend", "Resend email")]
125
126
  }),
126
- G && /* @__PURE__ */ _("p", {
127
+ K && /* @__PURE__ */ g("p", {
127
128
  role: "alert",
128
129
  className: "text-destructive",
129
130
  "data-testid": "invite-resend-error",
130
- children: G
131
+ children: K
131
132
  })
132
133
  ]
133
134
  }),
134
- B && /* @__PURE__ */ _("p", {
135
+ V && /* @__PURE__ */ g("p", {
135
136
  role: "alert",
136
137
  className: "text-sm text-destructive",
137
138
  "data-testid": "invite-error",
138
- children: B
139
+ children: V
139
140
  }),
140
- /* @__PURE__ */ v(b, { children: [/* @__PURE__ */ _(S, { children: /* @__PURE__ */ v(C, {
141
+ /* @__PURE__ */ _(y, { children: [/* @__PURE__ */ g(x, { children: /* @__PURE__ */ _(S, {
141
142
  className: "flex items-center gap-2",
142
- children: [/* @__PURE__ */ _(f, { className: "h-4 w-4" }), o("planmagnet.invite.details", "Invitation details")]
143
- }) }), /* @__PURE__ */ v(x, {
143
+ children: [/* @__PURE__ */ g(te, { className: "h-4 w-4" }), m("planmagnet.invite.details", "Invitation details")]
144
+ }) }), /* @__PURE__ */ _(b, {
144
145
  className: "space-y-4",
145
146
  children: [
146
- /* @__PURE__ */ v("div", { children: [
147
- /* @__PURE__ */ _("label", {
147
+ /* @__PURE__ */ _("div", { children: [
148
+ /* @__PURE__ */ g("label", {
148
149
  htmlFor: "invite-email",
149
150
  className: "text-sm font-medium",
150
- children: o("planmagnet.invite.emailLabel", "Email address")
151
+ children: m("planmagnet.invite.emailLabel", "Email address")
151
152
  }),
152
- /* @__PURE__ */ _(w, {
153
+ /* @__PURE__ */ g(C, {
153
154
  id: "invite-email",
154
155
  type: "email",
155
- value: P,
156
+ value: F,
156
157
  onChange: (e) => {
157
- F(e.target.value), U(null), K(null);
158
+ I(e.target.value), W(null), q(null);
158
159
  },
159
- onBlur: () => L(!0),
160
- "aria-invalid": I && !X,
160
+ onBlur: () => R(!0),
161
+ "aria-invalid": L && !X,
161
162
  "aria-describedby": "invite-email-help",
162
163
  className: "mt-1",
163
164
  placeholder: "colleague@company.com"
164
165
  }),
165
- I && P.trim() !== "" && !X ? /* @__PURE__ */ _("p", {
166
+ L && F.trim() !== "" && !X ? /* @__PURE__ */ g("p", {
166
167
  id: "invite-email-help",
167
168
  role: "alert",
168
169
  className: "mt-1 text-xs text-destructive",
169
- children: o("planmagnet.invite.emailInvalid", "Enter a full email address, like name@company.com.")
170
- }) : /* @__PURE__ */ _("p", {
170
+ children: m("planmagnet.invite.emailInvalid", "Enter a full email address, like name@company.com.")
171
+ }) : /* @__PURE__ */ g("p", {
171
172
  id: "invite-email-help",
172
173
  className: "mt-1 text-xs text-muted-foreground",
173
- children: o("planmagnet.invite.emailHelp", "An invitation email with a link to accept is sent when possible. Pending invitations can be resent below.")
174
+ children: m("planmagnet.invite.emailHelp", "An invitation email with a link to accept is sent when possible. Pending invitations can be resent below.")
174
175
  })
175
176
  ] }),
176
- /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
177
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
177
178
  htmlFor: "invite-role",
178
179
  className: "text-sm font-medium",
179
- children: o("planmagnet.invite.roleLabel", "Access level")
180
- }), A.loading && !A.data ? /* @__PURE__ */ _("p", {
180
+ children: m("planmagnet.invite.roleLabel", "Access level")
181
+ }), A.loading && !A.data ? /* @__PURE__ */ g("p", {
181
182
  className: "mt-1 text-sm text-muted-foreground",
182
- children: o("planmagnet.invite.rolesLoading", "Loading access levels…")
183
- }) : A.error ? /* @__PURE__ */ _("p", {
183
+ children: m("planmagnet.invite.rolesLoading", "Loading access levels…")
184
+ }) : A.error ? /* @__PURE__ */ g("p", {
184
185
  role: "alert",
185
186
  className: "mt-1 text-sm text-destructive",
186
- children: o("planmagnet.invite.rolesFailed", "Access levels could not be loaded: {{reason}}", { reason: A.error.message })
187
- }) : q.length === 0 ? /* @__PURE__ */ _("p", {
187
+ children: m("planmagnet.invite.rolesFailed", "Access levels could not be loaded: {{reason}}", { reason: A.error.message })
188
+ }) : J.length === 0 ? /* @__PURE__ */ g("p", {
188
189
  className: "mt-1 text-sm text-muted-foreground",
189
- children: o("planmagnet.invite.rolesNone", "No PlanMagnet access levels are set up for this workspace yet.")
190
- }) : /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ v("select", {
190
+ children: m("planmagnet.invite.rolesNone", "No PlanMagnet access levels are set up for this workspace yet.")
191
+ }) : /* @__PURE__ */ _(ae, { children: [/* @__PURE__ */ _("select", {
191
192
  id: "invite-role",
192
193
  className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
193
- value: R,
194
- onChange: (e) => z(e.target.value),
194
+ value: z,
195
+ onChange: (e) => B(e.target.value),
195
196
  "data-testid": "invite-role",
196
- children: [/* @__PURE__ */ _("option", {
197
+ children: [/* @__PURE__ */ g("option", {
197
198
  value: "",
198
- children: o("planmagnet.invite.rolePlaceholder", "Choose an access level")
199
- }), q.map((e) => /* @__PURE__ */ _("option", {
199
+ children: m("planmagnet.invite.rolePlaceholder", "Choose an access level")
200
+ }), J.map((e) => /* @__PURE__ */ g("option", {
200
201
  value: e.id,
201
202
  children: e.name
202
203
  }, e.id))]
203
- }), Y?.description && /* @__PURE__ */ _("p", {
204
+ }), Y?.description && /* @__PURE__ */ g("p", {
204
205
  className: "mt-1 text-xs text-muted-foreground",
205
206
  children: Y.description
206
207
  })] })] }),
207
- /* @__PURE__ */ _("div", {
208
+ /* @__PURE__ */ g("div", {
208
209
  className: "flex justify-end",
209
- children: /* @__PURE__ */ v(y, {
210
+ children: /* @__PURE__ */ _(v, {
210
211
  size: "sm",
211
- onClick: ae,
212
+ onClick: ce,
212
213
  disabled: !Z,
213
214
  "data-testid": "invite-send",
214
- children: [/* @__PURE__ */ _(d, { className: "mr-1 h-4 w-4" }), j.loading ? o("planmagnet.invite.sending", "Sending…") : o("planmagnet.invite.send", "Send invite")]
215
+ children: [/* @__PURE__ */ g(ee, { className: "mr-1 h-4 w-4" }), M.loading ? m("planmagnet.invite.sending", "Sending…") : m("planmagnet.invite.send", "Send invite")]
215
216
  })
216
217
  })
217
218
  ]
218
219
  })] }),
219
- /* @__PURE__ */ _(s, {
220
- workspaceId: p,
221
- currentUserId: O?.id,
222
- roleNames: J
223
- }, p ?? "no-workspace")
220
+ /* @__PURE__ */ g(l, {
221
+ workspaceId: O,
222
+ roleNames: se
223
+ }, O ?? "no-workspace")
224
224
  ]
225
225
  });
226
226
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TeamInviteMemberPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { Button, Card, CardContent, CardHeader, CardTitle, Input } from '@burdenoff/fe-libs/ui';\nimport { RefreshCw, Send, UserPlus, X } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { isValidInviteEmail } from '../utils/email';\nimport { refusalMessage } from '../product/refusal';\nimport {\n PENDING_INVITATIONS_QUERY,\n PendingInvitations,\n RESEND_INVITATION,\n type InvitationEmailResult,\n} from '../team/PendingInvitations';\nimport {\n invitationEmailReason,\n invitationEmailSent,\n type InvitationEmailFields,\n} from '../team/invitationEmail';\n\n/**\n * Invite a member (H12, BOFF-7833). The previous page offered job titles (developer, designer, …) in its\n * \"Role\" picker plus invented Department and Team pickers, none of which any backend accepts, and its Send\n * button was disabled on the belief that no invite mutation existed. The platform's createInvitation does\n * exist, validates the email and the role ids, and assigns the roles when the invitee accepts. The access\n * levels offered here are PlanMagnet's own RBAC roles for this workspace, as the platform returns them.\n *\n * BOFF-8866: creating an invitation does not mean its email went out. The service records the email outcome on\n * the invitation (emailStatus / emailErrorCode); the page says \"sent\" only for SENT, otherwise that the invitation\n * exists but the email was not sent, why, and offers Resend. The pending invitations are listed below the form.\n */\n\nconst INVITE_ROLES = gql`\n query PlanmagnetInviteRoles($scopeId: String!) {\n roles(\n scopeId: $scopeId\n filter: { product: \"planmagnet\", includePlatformRoles: false }\n pagination: { limit: 50 }\n ) {\n id\n name\n description\n }\n }\n`;\n\nconst CREATE_INVITATION = gql`\n mutation PlanmagnetInviteMember($input: CreateInvitationInput!) {\n createInvitation(input: $input) {\n id\n email\n status\n expiresAt\n emailStatus\n emailLastAttemptAt\n emailErrorCode\n }\n }\n`;\n\ninterface InviteRole {\n id: string;\n name: string;\n description: string | null;\n}\n\ninterface CreatedInvitation extends InvitationEmailFields {\n id: string;\n email: string;\n /** The workspace it was created in; its Resend is only valid there (the write is scoped by header). */\n workspaceId: string | null;\n}\n\nconst WRITE_OPTIONS = { refetchQueries: [PENDING_INVITATIONS_QUERY], awaitRefetchQueries: true };\n\nexport function TeamInviteMemberPagePage() {\n const tr = useTr();\n const { workspaceId, currentUser } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const roles = useQuery<{ roles: InviteRole[] }>(INVITE_ROLES, {\n variables: { scopeId: workspaceId },\n skip: !workspaceId,\n });\n const [invite, inviteState] = useMutation<{\n createInvitation: InvitationEmailResult & { email: string };\n }>(CREATE_INVITATION, WRITE_OPTIONS);\n const [resend, resendState] = useMutation<{ resendInvitation: InvitationEmailResult }>(\n RESEND_INVITATION,\n WRITE_OPTIONS\n );\n\n const [email, setEmail] = useState('');\n const [emailTouched, setEmailTouched] = useState(false);\n const [roleId, setRoleId] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [createdState, setCreated] = useState<CreatedInvitation | null>(null);\n const created = createdState?.workspaceId === (workspaceId ?? null) ? createdState : null;\n const [resendError, setResendError] = useState<string | null>(null);\n\n const available = useMemo(() => roles.data?.roles ?? [], [roles.data]);\n const roleNames = useMemo(() => new Map(available.map((r) => [r.id, r.name])), [available]);\n const chosen = available.find((r) => r.id === roleId) ?? null;\n const emailOk = isValidInviteEmail(email);\n const canSend = emailOk && !!chosen && !inviteState.loading && !!workspaceId;\n const forbidden = tr(\n 'planmagnet.invite.pending.forbidden',\n 'Your access level does not allow managing invitations in this workspace.'\n );\n\n const send = async () => {\n if (!canSend || !chosen) return;\n setError(null);\n setCreated(null);\n setResendError(null);\n try {\n const result = await throwOnError(\n invite({ variables: { input: { email: email.trim(), roleIds: [chosen.id], workspaceId } } })\n );\n const invitation = result.data?.createInvitation;\n if (invitation) setCreated({ ...invitation, workspaceId: workspaceId ?? null });\n setEmail('');\n setEmailTouched(false);\n } catch (e) {\n setError(\n refusalMessage(\n e,\n tr('planmagnet.invite.errors.create', 'The invitation could not be created.'),\n forbidden\n )\n );\n }\n };\n\n const resendCreated = async () => {\n if (!created) return;\n setResendError(null);\n try {\n const result = await throwOnError(resend({ variables: { id: created.id } }));\n const updated = result.data?.resendInvitation;\n if (updated) {\n setCreated({\n ...created,\n emailStatus: updated.emailStatus,\n emailLastAttemptAt: updated.emailLastAttemptAt,\n emailErrorCode: updated.emailErrorCode,\n });\n }\n } catch (e) {\n setResendError(\n refusalMessage(\n e,\n tr('planmagnet.invite.errors.resend', 'The invitation email could not be resent.'),\n forbidden\n )\n );\n }\n };\n\n const notSentReason = created ? invitationEmailReason(created.emailErrorCode) : null;\n\n return (\n <div className=\"max-w-3xl space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {tr('planmagnet.invite.title', 'Invite Team Member')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.invite.subtitle',\n 'Invite someone to this workspace with a PlanMagnet access level.'\n )}\n </p>\n </div>\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/team/team-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n {tr('planmagnet.invite.back', 'Back to team')}\n </Button>\n </div>\n\n {created && invitationEmailSent(created) && (\n <div\n role=\"status\"\n className=\"rounded-md border border-border bg-muted p-3 text-sm text-status-success-text\"\n data-testid=\"invite-sent\"\n >\n {tr(\n 'planmagnet.invite.sent',\n 'Invitation sent to {{email}}. They get this access when they accept it.',\n { email: created.email }\n )}\n </div>\n )}\n {created && !invitationEmailSent(created) && notSentReason && (\n <div\n role=\"status\"\n className=\"space-y-2 rounded-md border border-border bg-muted p-3 text-sm\"\n data-testid=\"invite-email-not-sent\"\n >\n <p className=\"font-medium text-status-warning-text\">\n {tr(\n 'planmagnet.invite.emailNotSent',\n 'Invitation created for {{email}}, but the email was not sent.',\n { email: created.email }\n )}\n </p>\n <p className=\"text-muted-foreground\" data-testid=\"invite-email-not-sent-reason\">\n {tr(notSentReason.key, notSentReason.fallback)}\n </p>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void resendCreated()}\n disabled={resendState.loading}\n data-testid=\"invite-resend\"\n >\n <RefreshCw className=\"mr-1 h-4 w-4\" />\n {resendState.loading\n ? tr('planmagnet.invite.resending', 'Resending…')\n : tr('planmagnet.invite.resend', 'Resend email')}\n </Button>\n {resendError && (\n <p role=\"alert\" className=\"text-destructive\" data-testid=\"invite-resend-error\">\n {resendError}\n </p>\n )}\n </div>\n )}\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\" data-testid=\"invite-error\">\n {error}\n </p>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <UserPlus className=\"h-4 w-4\" />\n {tr('planmagnet.invite.details', 'Invitation details')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label htmlFor=\"invite-email\" className=\"text-sm font-medium\">\n {tr('planmagnet.invite.emailLabel', 'Email address')}\n </label>\n <Input\n id=\"invite-email\"\n type=\"email\"\n value={email}\n onChange={(e) => {\n setEmail(e.target.value);\n setCreated(null);\n setResendError(null);\n }}\n onBlur={() => setEmailTouched(true)}\n aria-invalid={emailTouched && !emailOk}\n aria-describedby=\"invite-email-help\"\n className=\"mt-1\"\n placeholder=\"colleague@company.com\"\n />\n {emailTouched && email.trim() !== '' && !emailOk ? (\n <p id=\"invite-email-help\" role=\"alert\" className=\"mt-1 text-xs text-destructive\">\n {tr(\n 'planmagnet.invite.emailInvalid',\n 'Enter a full email address, like name@company.com.'\n )}\n </p>\n ) : (\n <p id=\"invite-email-help\" className=\"mt-1 text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.invite.emailHelp',\n 'An invitation email with a link to accept is sent when possible. Pending invitations can be resent below.'\n )}\n </p>\n )}\n </div>\n\n <div>\n <label htmlFor=\"invite-role\" className=\"text-sm font-medium\">\n {tr('planmagnet.invite.roleLabel', 'Access level')}\n </label>\n {roles.loading && !roles.data ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {tr('planmagnet.invite.rolesLoading', 'Loading access levels…')}\n </p>\n ) : roles.error ? (\n <p role=\"alert\" className=\"mt-1 text-sm text-destructive\">\n {tr(\n 'planmagnet.invite.rolesFailed',\n 'Access levels could not be loaded: {{reason}}',\n {\n reason: roles.error.message,\n }\n )}\n </p>\n ) : available.length === 0 ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.invite.rolesNone',\n 'No PlanMagnet access levels are set up for this workspace yet.'\n )}\n </p>\n ) : (\n <>\n <select\n id=\"invite-role\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={roleId}\n onChange={(e) => setRoleId(e.target.value)}\n data-testid=\"invite-role\"\n >\n <option value=\"\">\n {tr('planmagnet.invite.rolePlaceholder', 'Choose an access level')}\n </option>\n {available.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n {chosen?.description && (\n <p className=\"mt-1 text-xs text-muted-foreground\">{chosen.description}</p>\n )}\n </>\n )}\n </div>\n\n <div className=\"flex justify-end\">\n <Button size=\"sm\" onClick={send} disabled={!canSend} data-testid=\"invite-send\">\n <Send className=\"mr-1 h-4 w-4\" />\n {inviteState.loading\n ? tr('planmagnet.invite.sending', 'Sending…')\n : tr('planmagnet.invite.send', 'Send invite')}\n </Button>\n </div>\n </CardContent>\n </Card>\n\n <PendingInvitations\n key={workspaceId ?? 'no-workspace'}\n workspaceId={workspaceId}\n currentUserId={currentUser?.id}\n roleNames={roleNames}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TeamInviteMemberPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useMutation, useQuery } from '@apollo/client/react';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport { Button, Card, CardContent, CardHeader, CardTitle, Input } from '@burdenoff/fe-libs/ui';\nimport { RefreshCw, Send, UserPlus, X } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { isValidInviteEmail } from '../utils/email';\nimport { refusalMessage } from '../product/refusal';\nimport {\n PENDING_INVITATIONS_QUERY,\n PendingInvitations,\n RESEND_INVITATION,\n type InvitationEmailResult,\n} from '../team/PendingInvitations';\nimport {\n invitationEmailReason,\n invitationEmailSent,\n type InvitationEmailFields,\n} from '../team/invitationEmail';\n\n/**\n * Invite a member (H12, BOFF-7833). The previous page offered job titles (developer, designer, …) in its\n * \"Role\" picker plus invented Department and Team pickers, none of which any backend accepts, and its Send\n * button was disabled on the belief that no invite mutation existed. The platform's createInvitation does\n * exist, validates the email and the role ids, and assigns the roles when the invitee accepts. The access\n * levels offered here are PlanMagnet's own RBAC roles for this workspace, as the platform returns them.\n *\n * BOFF-8866: creating an invitation does not mean its email went out. The service records the email outcome on\n * the invitation (emailStatus / emailErrorCode); the page says \"sent\" only for SENT, otherwise that the invitation\n * exists but the email was not sent, why, and offers Resend. The pending invitations are listed below the form.\n */\n\nconst INVITE_ROLES = gql`\n query PlanmagnetInviteRoles($scopeId: String!) {\n roles(\n scopeId: $scopeId\n filter: { product: \"planmagnet\", includePlatformRoles: false }\n pagination: { limit: 50 }\n ) {\n id\n name\n description\n }\n }\n`;\n\nconst CREATE_INVITATION = gql`\n mutation PlanmagnetInviteMember($input: CreateInvitationInput!) {\n createInvitation(input: $input) {\n id\n email\n status\n expiresAt\n emailStatus\n emailLastAttemptAt\n emailErrorCode\n }\n }\n`;\n\ninterface InviteRole {\n id: string;\n name: string;\n description: string | null;\n}\n\ninterface CreatedInvitation extends InvitationEmailFields {\n id: string;\n email: string;\n /** The workspace it was created in; its Resend is only valid there (the write is scoped by header). */\n workspaceId: string | null;\n}\n\nconst WRITE_OPTIONS = { refetchQueries: [PENDING_INVITATIONS_QUERY], awaitRefetchQueries: true };\n\nexport function TeamInviteMemberPagePage() {\n const tr = useTr();\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const client = useApolloClient();\n\n const roles = useQuery<{ roles: InviteRole[] }>(INVITE_ROLES, {\n variables: { scopeId: workspaceId },\n skip: !workspaceId,\n });\n const [invite, inviteState] = useMutation<{\n createInvitation: InvitationEmailResult & { email: string };\n }>(CREATE_INVITATION, WRITE_OPTIONS);\n const [resend, resendState] = useMutation<{ resendInvitation: InvitationEmailResult }>(\n RESEND_INVITATION,\n WRITE_OPTIONS\n );\n\n const [email, setEmail] = useState('');\n const [emailTouched, setEmailTouched] = useState(false);\n const [roleId, setRoleId] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [createdState, setCreated] = useState<CreatedInvitation | null>(null);\n const created = createdState?.workspaceId === (workspaceId ?? null) ? createdState : null;\n const [resendError, setResendError] = useState<string | null>(null);\n\n const available = useMemo(() => roles.data?.roles ?? [], [roles.data]);\n const roleNames = useMemo(() => new Map(available.map((r) => [r.id, r.name])), [available]);\n const chosen = available.find((r) => r.id === roleId) ?? null;\n const emailOk = isValidInviteEmail(email);\n const canSend = emailOk && !!chosen && !inviteState.loading && !!workspaceId;\n const forbidden = tr(\n 'planmagnet.invite.pending.forbidden',\n 'Your access level does not allow managing invitations in this workspace.'\n );\n\n const send = async () => {\n if (!canSend || !chosen) return;\n setError(null);\n setCreated(null);\n setResendError(null);\n try {\n const result = await throwOnError(\n invite({ variables: { input: { email: email.trim(), roleIds: [chosen.id], workspaceId } } })\n );\n const invitation = result.data?.createInvitation;\n if (invitation) setCreated({ ...invitation, workspaceId: workspaceId ?? null });\n setEmail('');\n setEmailTouched(false);\n } catch (e) {\n setError(\n refusalMessage(\n e,\n tr('planmagnet.invite.errors.create', 'The invitation could not be created.'),\n forbidden\n )\n );\n }\n };\n\n const resendCreated = async () => {\n if (!created) return;\n setResendError(null);\n try {\n const result = await throwOnError(resend({ variables: { id: created.id } }));\n const updated = result.data?.resendInvitation;\n if (updated) {\n setCreated({\n ...created,\n emailStatus: updated.emailStatus,\n emailLastAttemptAt: updated.emailLastAttemptAt,\n emailErrorCode: updated.emailErrorCode,\n });\n }\n } catch (e) {\n setResendError(\n refusalMessage(\n e,\n tr('planmagnet.invite.errors.resend', 'The invitation email could not be resent.'),\n forbidden\n )\n );\n // A resend that did not send records its new outcome before it fails, and a failed write runs no\n // refetchQueries: drop fe-libs' 15 s fetch replay and re-read the list, whose row then shows the latest attempt\n // and reason (this block's own reason is hidden while the error stands, as it may be the superseded one).\n clearGraphqlFetchResponseCache();\n void client.refetchQueries({ include: [PENDING_INVITATIONS_QUERY] });\n }\n };\n\n const notSentReason = created ? invitationEmailReason(created.emailErrorCode) : null;\n\n return (\n <div className=\"max-w-3xl space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {tr('planmagnet.invite.title', 'Invite Team Member')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.invite.subtitle',\n 'Invite someone to this workspace with a PlanMagnet access level.'\n )}\n </p>\n </div>\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/team/team-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n {tr('planmagnet.invite.back', 'Back to team')}\n </Button>\n </div>\n\n {created && invitationEmailSent(created) && (\n <div\n role=\"status\"\n className=\"rounded-md border border-border bg-muted p-3 text-sm text-status-success-text\"\n data-testid=\"invite-sent\"\n >\n {tr(\n 'planmagnet.invite.sent',\n 'Invitation sent to {{email}}. They get this access when they accept it.',\n { email: created.email }\n )}\n </div>\n )}\n {created && !invitationEmailSent(created) && notSentReason && (\n <div\n role=\"status\"\n className=\"space-y-2 rounded-md border border-border bg-muted p-3 text-sm\"\n data-testid=\"invite-email-not-sent\"\n >\n <p className=\"font-medium text-status-warning-text\">\n {tr(\n 'planmagnet.invite.emailNotSent',\n 'Invitation created for {{email}}, but the email was not sent.',\n { email: created.email }\n )}\n </p>\n {!resendError && (\n <p className=\"text-muted-foreground\" data-testid=\"invite-email-not-sent-reason\">\n {tr(notSentReason.key, notSentReason.fallback)}\n </p>\n )}\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void resendCreated()}\n disabled={resendState.loading}\n data-testid=\"invite-resend\"\n >\n <RefreshCw className=\"mr-1 h-4 w-4\" />\n {resendState.loading\n ? tr('planmagnet.invite.resending', 'Resending…')\n : tr('planmagnet.invite.resend', 'Resend email')}\n </Button>\n {resendError && (\n <p role=\"alert\" className=\"text-destructive\" data-testid=\"invite-resend-error\">\n {resendError}\n </p>\n )}\n </div>\n )}\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\" data-testid=\"invite-error\">\n {error}\n </p>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <UserPlus className=\"h-4 w-4\" />\n {tr('planmagnet.invite.details', 'Invitation details')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label htmlFor=\"invite-email\" className=\"text-sm font-medium\">\n {tr('planmagnet.invite.emailLabel', 'Email address')}\n </label>\n <Input\n id=\"invite-email\"\n type=\"email\"\n value={email}\n onChange={(e) => {\n setEmail(e.target.value);\n setCreated(null);\n setResendError(null);\n }}\n onBlur={() => setEmailTouched(true)}\n aria-invalid={emailTouched && !emailOk}\n aria-describedby=\"invite-email-help\"\n className=\"mt-1\"\n placeholder=\"colleague@company.com\"\n />\n {emailTouched && email.trim() !== '' && !emailOk ? (\n <p id=\"invite-email-help\" role=\"alert\" className=\"mt-1 text-xs text-destructive\">\n {tr(\n 'planmagnet.invite.emailInvalid',\n 'Enter a full email address, like name@company.com.'\n )}\n </p>\n ) : (\n <p id=\"invite-email-help\" className=\"mt-1 text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.invite.emailHelp',\n 'An invitation email with a link to accept is sent when possible. Pending invitations can be resent below.'\n )}\n </p>\n )}\n </div>\n\n <div>\n <label htmlFor=\"invite-role\" className=\"text-sm font-medium\">\n {tr('planmagnet.invite.roleLabel', 'Access level')}\n </label>\n {roles.loading && !roles.data ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {tr('planmagnet.invite.rolesLoading', 'Loading access levels…')}\n </p>\n ) : roles.error ? (\n <p role=\"alert\" className=\"mt-1 text-sm text-destructive\">\n {tr(\n 'planmagnet.invite.rolesFailed',\n 'Access levels could not be loaded: {{reason}}',\n {\n reason: roles.error.message,\n }\n )}\n </p>\n ) : available.length === 0 ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.invite.rolesNone',\n 'No PlanMagnet access levels are set up for this workspace yet.'\n )}\n </p>\n ) : (\n <>\n <select\n id=\"invite-role\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={roleId}\n onChange={(e) => setRoleId(e.target.value)}\n data-testid=\"invite-role\"\n >\n <option value=\"\">\n {tr('planmagnet.invite.rolePlaceholder', 'Choose an access level')}\n </option>\n {available.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n {chosen?.description && (\n <p className=\"mt-1 text-xs text-muted-foreground\">{chosen.description}</p>\n )}\n </>\n )}\n </div>\n\n <div className=\"flex justify-end\">\n <Button size=\"sm\" onClick={send} disabled={!canSend} data-testid=\"invite-send\">\n <Send className=\"mr-1 h-4 w-4\" />\n {inviteState.loading\n ? tr('planmagnet.invite.sending', 'Sending…')\n : tr('planmagnet.invite.send', 'Send invite')}\n </Button>\n </div>\n </CardContent>\n </Card>\n\n <PendingInvitations\n key={workspaceId ?? 'no-workspace'}\n workspaceId={workspaceId}\n roleNames={roleNames}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -16,11 +16,9 @@ export interface InvitationEmailResult extends InvitationEmailFields {
16
16
  status: string;
17
17
  expiresAt: string | null;
18
18
  }
19
- export declare function PendingInvitations({ workspaceId, currentUserId, roleNames, }: {
19
+ export declare function PendingInvitations({ workspaceId, roleNames, }: {
20
20
  /** The workspace the list is for. The query is scoped by a request header, so callers key this component on it. */
21
21
  workspaceId?: string | null;
22
- /** Cancelling is the inviter's own action; other people's invitations are listed without it. */
23
- currentUserId?: string | null;
24
22
  /** PlanMagnet access-level names by role id, as the page already loaded them. */
25
23
  roleNames: ReadonlyMap<string, string>;
26
24
  }): import("react/jsx-runtime").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"PendingInvitations.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/team/PendingInvitations.tsx"],"names":[],"mappings":"AAWA,OAAO,EAOL,KAAK,qBAAqB,EAC3B,MAAM,mBAAmB,CAAC;AAE3B;;;;;;GAMG;AAEH,uFAAuF;AACvF,eAAO,MAAM,yBAAyB,iCAAiC,CAAC;AAwBxE,iHAAiH;AACjH,eAAO,MAAM,iBAAiB,gCAW7B,CAAC;AAWF,MAAM,WAAW,qBAAsB,SAAQ,qBAAqB;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAyBD,wBAAgB,kBAAkB,CAAC,EACjC,WAAW,EACX,aAAa,EACb,SAAS,GACV,EAAE;IACD,mHAAmH;IACnH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gGAAgG;IAChG,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iFAAiF;IACjF,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC,2CAyRA"}
1
+ {"version":3,"file":"PendingInvitations.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/team/PendingInvitations.tsx"],"names":[],"mappings":"AAWA,OAAO,EAOL,KAAK,qBAAqB,EAC3B,MAAM,mBAAmB,CAAC;AAE3B;;;;;;GAMG;AAEH,uFAAuF;AACvF,eAAO,MAAM,yBAAyB,iCAAiC,CAAC;AAuBxE,iHAAiH;AACjH,eAAO,MAAM,iBAAiB,gCAW7B,CAAC;AAWF,MAAM,WAAW,qBAAsB,SAAQ,qBAAqB;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAwBD,wBAAgB,kBAAkB,CAAC,EACjC,WAAW,EACX,SAAS,GACV,EAAE;IACD,mHAAmH;IACnH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iFAAiF;IACjF,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC,2CAyRA"}
@@ -20,7 +20,6 @@ var D = "PlanmagnetPendingInvitations", O = 50, k = h`
20
20
  email
21
21
  status
22
22
  roleIds
23
- invitedById
24
23
  expiresAt
25
24
  emailStatus
26
25
  emailLastAttemptAt
@@ -56,93 +55,93 @@ var D = "PlanmagnetPendingInvitations", O = 50, k = h`
56
55
  warning: "text-status-warning-text",
57
56
  error: "text-destructive"
58
57
  }, P = (e) => u(e)?.toLocaleString() ?? null;
59
- function F({ workspaceId: h, currentUserId: D, roleNames: F }) {
60
- let I = e(), { data: L, loading: R, error: z, refetch: B } = _(k, {
58
+ function F({ workspaceId: h, roleNames: D }) {
59
+ let F = e(), { data: I, loading: L, error: R, refetch: z } = _(k, {
61
60
  variables: { pagination: { limit: O } },
62
61
  skip: !h,
63
62
  fetchPolicy: "network-only"
64
- }), [V] = g(A, M), [H] = g(j, M), [U, W] = d(null), [G, K] = d(null), [q] = d(() => Date.now()), J = I("planmagnet.invite.pending.forbidden", "Your access level does not allow managing invitations in this workspace."), Y = () => {
65
- E(), B();
66
- }, X = async (e) => {
67
- W(e.id), K(null);
63
+ }), [B] = g(A, M), [V] = g(j, M), [H, U] = d(null), [W, G] = d(null), [K] = d(() => Date.now()), q = F("planmagnet.invite.pending.forbidden", "Your access level does not allow managing invitations in this workspace."), J = () => {
64
+ E(), z();
65
+ }, Y = async (e) => {
66
+ U(e.id), G(null);
68
67
  try {
69
- let t = (await n(V({ variables: { id: e.id } }))).data?.resendInvitation;
70
- if (s(t)) K({
68
+ let t = (await n(B({ variables: { id: e.id } }))).data?.resendInvitation;
69
+ if (s(t)) G({
71
70
  tone: "success",
72
- text: I("planmagnet.invite.pending.resent", "Invitation email sent again to {{email}}.", { email: e.email })
71
+ text: F("planmagnet.invite.pending.resent", "Invitation email sent again to {{email}}.", { email: e.email })
73
72
  });
74
73
  else {
75
74
  let n = o(t?.emailErrorCode);
76
- K({
75
+ G({
77
76
  tone: "warning",
78
- text: I("planmagnet.invite.pending.resendNotSent", "The invitation to {{email}} is still pending, but the email was not sent. {{reason}}", {
77
+ text: F("planmagnet.invite.pending.resendNotSent", "The invitation to {{email}} is still pending, but the email was not sent. {{reason}}", {
79
78
  email: e.email,
80
- reason: I(n.key, n.fallback)
79
+ reason: F(n.key, n.fallback)
81
80
  })
82
81
  });
83
82
  }
84
83
  } catch (t) {
85
- K({
84
+ G({
86
85
  tone: "error",
87
- text: I("planmagnet.invite.pending.resendFailed", "The invitation to {{email}} could not be resent: {{reason}}", {
86
+ text: F("planmagnet.invite.pending.resendFailed", "The invitation to {{email}} could not be resent: {{reason}}", {
88
87
  email: e.email,
89
- reason: i(t, I("planmagnet.invite.pending.resendFailedFallback", "Try again."), J)
88
+ reason: i(t, F("planmagnet.invite.pending.resendFailedFallback", "Try again."), q)
90
89
  })
91
- }), Y();
90
+ }), J();
92
91
  } finally {
93
- W(null);
92
+ U(null);
94
93
  }
95
- }, Z = async (e) => {
96
- W(e.id), K(null);
94
+ }, X = async (e) => {
95
+ U(e.id), G(null);
97
96
  try {
98
- await n(H({ variables: { id: e.id } })), K({
97
+ await n(V({ variables: { id: e.id } })), G({
99
98
  tone: "success",
100
- text: I("planmagnet.invite.pending.cancelled", "The invitation to {{email}} was cancelled.", { email: e.email })
99
+ text: F("planmagnet.invite.pending.cancelled", "The invitation to {{email}} was cancelled.", { email: e.email })
101
100
  });
102
101
  } catch (t) {
103
- K({
102
+ G({
104
103
  tone: "error",
105
- text: I("planmagnet.invite.pending.cancelFailed", "The invitation to {{email}} could not be cancelled: {{reason}}", {
104
+ text: F("planmagnet.invite.pending.cancelFailed", "The invitation to {{email}} could not be cancelled: {{reason}}", {
106
105
  email: e.email,
107
- reason: i(t, I("planmagnet.invite.pending.cancelFailedFallback", "Try again."), J)
106
+ reason: i(t, F("planmagnet.invite.pending.cancelFailedFallback", "Try again."), q)
108
107
  })
109
- }), Y();
108
+ }), J();
110
109
  } finally {
111
- W(null);
110
+ U(null);
112
111
  }
113
- }, Q = L?.workspaceInvitations, $ = Q?.items ?? [], ee = (e) => {
114
- let { names: t, unknown: n } = l(e, F), r = [...t];
115
- return n > 0 && r.push(I("planmagnet.invite.pending.otherRoles", "{{count}} other access level(s)", { count: n })), r.length > 0 ? r.join(", ") : I("planmagnet.invite.pending.noRoles", "No access level");
112
+ }, Z = I?.workspaceInvitations, Q = Z?.items ?? [], $ = (e) => {
113
+ let { names: t, unknown: n } = l(e, D), r = [...t];
114
+ return n > 0 && r.push(F("planmagnet.invite.pending.otherRoles", "{{count}} other access level(s)", { count: n })), r.length > 0 ? r.join(", ") : F("planmagnet.invite.pending.noRoles", "No access level");
116
115
  };
117
116
  return /* @__PURE__ */ y(S, {
118
117
  "data-testid": "pending-invitations",
119
118
  children: [/* @__PURE__ */ v(w, { children: /* @__PURE__ */ y(T, {
120
119
  className: "flex items-center gap-2",
121
- children: [/* @__PURE__ */ v(f, { className: "h-4 w-4" }), I("planmagnet.invite.pending.title", "Pending invitations")]
120
+ children: [/* @__PURE__ */ v(f, { className: "h-4 w-4" }), F("planmagnet.invite.pending.title", "Pending invitations")]
122
121
  }) }), /* @__PURE__ */ y(C, {
123
122
  className: "space-y-3",
124
123
  children: [
125
- G && /* @__PURE__ */ v("p", {
126
- role: G.tone === "error" ? "alert" : "status",
127
- className: `text-sm ${N[G.tone]}`,
124
+ W && /* @__PURE__ */ v("p", {
125
+ role: W.tone === "error" ? "alert" : "status",
126
+ className: `text-sm ${N[W.tone]}`,
128
127
  "data-testid": "pending-invitations-notice",
129
- children: G.text
128
+ children: W.text
130
129
  }),
131
- R && !L ? /* @__PURE__ */ v("p", {
130
+ L && !I ? /* @__PURE__ */ v("p", {
132
131
  className: "text-sm text-muted-foreground",
133
- children: I("planmagnet.invite.pending.loading", "Loading pending invitations…")
134
- }) : z && !L ? /* @__PURE__ */ v("p", {
132
+ children: F("planmagnet.invite.pending.loading", "Loading pending invitations…")
133
+ }) : R && !I ? /* @__PURE__ */ v("p", {
135
134
  role: "alert",
136
135
  className: "text-sm text-destructive",
137
- children: t(z) ? J : I("planmagnet.invite.pending.loadFailed", "Pending invitations could not be loaded: {{reason}}", { reason: z.message })
138
- }) : $.length === 0 ? /* @__PURE__ */ v("p", {
136
+ children: t(R) ? q : F("planmagnet.invite.pending.loadFailed", "Pending invitations could not be loaded: {{reason}}", { reason: R.message })
137
+ }) : Q.length === 0 ? /* @__PURE__ */ v("p", {
139
138
  className: "text-sm text-muted-foreground",
140
- children: I("planmagnet.invite.pending.empty", "No invitations are waiting to be accepted.")
139
+ children: F("planmagnet.invite.pending.empty", "No invitations are waiting to be accepted.")
141
140
  }) : /* @__PURE__ */ v("ul", {
142
141
  className: "divide-y divide-border",
143
142
  "data-testid": "pending-invitations-list",
144
- children: $.map((e) => {
145
- let t = s(e), n = c(e.emailStatus), i = o(e.emailErrorCode), l = P(e.emailLastAttemptAt), d = u(e.expiresAt), f = !!d && d.getTime() <= q, h = U === e.id;
143
+ children: Q.map((e) => {
144
+ let t = s(e), n = c(e.emailStatus), i = o(e.emailErrorCode), l = P(e.emailLastAttemptAt), d = u(e.expiresAt), f = !!d && d.getTime() <= K, h = H === e.id;
146
145
  return /* @__PURE__ */ y("li", {
147
146
  className: "flex flex-col gap-2 py-3 sm:flex-row sm:items-start sm:justify-between",
148
147
  "data-testid": "pending-invitation",
@@ -155,7 +154,7 @@ function F({ workspaceId: h, currentUserId: D, roleNames: F }) {
155
154
  }),
156
155
  /* @__PURE__ */ v("p", {
157
156
  className: "text-xs text-muted-foreground",
158
- children: ee(e.roleIds)
157
+ children: $(e.roleIds)
159
158
  }),
160
159
  /* @__PURE__ */ y("div", {
161
160
  className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground",
@@ -163,18 +162,18 @@ function F({ workspaceId: h, currentUserId: D, roleNames: F }) {
163
162
  /* @__PURE__ */ v(b, {
164
163
  variant: a(e.emailStatus),
165
164
  "data-testid": "pending-invitation-email-status",
166
- children: I(n.key, n.fallback)
165
+ children: F(n.key, n.fallback)
167
166
  }),
168
- l && /* @__PURE__ */ v("span", { children: I("planmagnet.invite.pending.lastAttempt", "Last tried {{when}}", { when: l }) }),
167
+ l && /* @__PURE__ */ v("span", { children: F("planmagnet.invite.pending.lastAttempt", "Last tried {{when}}", { when: l }) }),
169
168
  d && /* @__PURE__ */ v("span", {
170
169
  className: f ? "text-status-warning-text" : void 0,
171
- children: f ? I("planmagnet.invite.pending.expired", "Expired {{when}}", { when: d.toLocaleString() }) : I("planmagnet.invite.pending.expires", "Expires {{when}}", { when: d.toLocaleString() })
170
+ children: f ? F("planmagnet.invite.pending.expired", "Expired {{when}}", { when: d.toLocaleString() }) : F("planmagnet.invite.pending.expires", "Expires {{when}}", { when: d.toLocaleString() })
172
171
  })
173
172
  ]
174
173
  }),
175
174
  !t && e.emailStatus && /* @__PURE__ */ v("p", {
176
175
  className: "text-xs text-status-warning-text",
177
- children: I(i.key, i.fallback)
176
+ children: F(i.key, i.fallback)
178
177
  })
179
178
  ]
180
179
  }), /* @__PURE__ */ y("div", {
@@ -182,32 +181,32 @@ function F({ workspaceId: h, currentUserId: D, roleNames: F }) {
182
181
  children: [/* @__PURE__ */ y(x, {
183
182
  size: "sm",
184
183
  variant: "outline",
185
- disabled: U !== null,
186
- onClick: () => void X(e),
184
+ disabled: H !== null,
185
+ onClick: () => void Y(e),
187
186
  "data-testid": "pending-invitation-resend",
188
- children: [/* @__PURE__ */ v(p, { className: "mr-1 h-4 w-4" }), h ? I("planmagnet.invite.pending.working", "Working…") : I("planmagnet.invite.pending.resend", "Resend")]
189
- }), e.invitedById === D && /* @__PURE__ */ v(r, {
190
- label: I("planmagnet.invite.pending.cancel", "Cancel"),
187
+ children: [/* @__PURE__ */ v(p, { className: "mr-1 h-4 w-4" }), h ? F("planmagnet.invite.pending.working", "Working…") : F("planmagnet.invite.pending.resend", "Resend")]
188
+ }), /* @__PURE__ */ v(r, {
189
+ label: F("planmagnet.invite.pending.cancel", "Cancel"),
191
190
  variant: "outline",
192
191
  icon: /* @__PURE__ */ v(m, { className: "mr-1 h-4 w-4" }),
193
192
  testId: "pending-invitation-cancel",
194
- title: I("planmagnet.invite.pending.confirmCancel.title", "Cancel the invitation to {{email}}?", { email: e.email }),
195
- description: I("planmagnet.invite.pending.confirmCancel.description", "Its link stops working and they will not join this workspace from it. You can invite them again later."),
196
- confirmLabel: I("planmagnet.invite.pending.confirmCancel.confirm", "Cancel invitation"),
197
- cancelLabel: I("planmagnet.invite.pending.confirmCancel.keep", "Keep it"),
193
+ title: F("planmagnet.invite.pending.confirmCancel.title", "Cancel the invitation to {{email}}?", { email: e.email }),
194
+ description: F("planmagnet.invite.pending.confirmCancel.description", "Its link stops working and they will not join this workspace from it. You can invite them again later."),
195
+ confirmLabel: F("planmagnet.invite.pending.confirmCancel.confirm", "Cancel invitation"),
196
+ cancelLabel: F("planmagnet.invite.pending.confirmCancel.keep", "Keep it"),
198
197
  destructive: !0,
199
- disabled: U !== null,
200
- onConfirm: () => void Z(e)
198
+ disabled: H !== null,
199
+ onConfirm: () => void X(e)
201
200
  })]
202
201
  })]
203
202
  }, e.id);
204
203
  })
205
204
  }),
206
- Q?.hasMore && /* @__PURE__ */ v("p", {
205
+ Z?.hasMore && /* @__PURE__ */ v("p", {
207
206
  className: "text-xs text-muted-foreground",
208
- children: I("planmagnet.invite.pending.truncated", "Showing the first {{shown}} of {{total}} pending invitations.", {
209
- shown: $.length,
210
- total: Q.total
207
+ children: F("planmagnet.invite.pending.truncated", "Showing the first {{shown}} of {{total}} pending invitations.", {
208
+ shown: Q.length,
209
+ total: Z.total
211
210
  })
212
211
  })
213
212
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"PendingInvitations.js","names":[],"sources":["../../../src/planmagnet/team/PendingInvitations.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport { Badge, Button, Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { Mail, RefreshCw, X } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { ConfirmButton } from '../controls/ConfirmButton';\nimport { refusalMessage } from '../product/refusal';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport { throwOnError } from '../utils/mutationResult';\nimport {\n invitationEmailBadgeVariant,\n invitationEmailReason,\n invitationEmailSent,\n invitationEmailStatusLabel,\n invitationRoleNames,\n parseInvitationDate,\n type InvitationEmailFields,\n} from './invitationEmail';\n\n/**\n * The workspace's pending invitations on the Invite page (BOFF-8866): who was invited, with which access, whether\n * the invitation email actually went out (and when it was last tried), when the invitation expires, and Resend /\n * Cancel. `workspaceInvitations` and the two writes read the workspace from the gateway's x-workspace-id header.\n * A write's outcome is shown above the list, so a row that leaves the list on the refresh does not take its message\n * with it.\n */\n\n/** Operation name of the list, for `refetchQueries` of every write that changes it. */\nexport const PENDING_INVITATIONS_QUERY = 'PlanmagnetPendingInvitations';\n\nconst PAGE_LIMIT = 50;\n\nconst PENDING_INVITATIONS = gql`\n query PlanmagnetPendingInvitations($pagination: PaginationInput) {\n workspaceInvitations(status: PENDING, pagination: $pagination) {\n items {\n id\n email\n status\n roleIds\n invitedById\n expiresAt\n emailStatus\n emailLastAttemptAt\n emailErrorCode\n }\n total\n hasMore\n }\n }\n`;\n\n/** Sends the invitation email again (and renews the invitation's expiry); answers with the new email outcome. */\nexport const RESEND_INVITATION = gql`\n mutation PlanmagnetResendInvitation($id: ID!) {\n resendInvitation(id: $id) {\n id\n status\n expiresAt\n emailStatus\n emailLastAttemptAt\n emailErrorCode\n }\n }\n`;\n\nconst CANCEL_INVITATION = gql`\n mutation PlanmagnetCancelInvitation($id: ID!) {\n cancelInvitation(id: $id) {\n id\n status\n }\n }\n`;\n\nexport interface InvitationEmailResult extends InvitationEmailFields {\n id: string;\n status: string;\n expiresAt: string | null;\n}\n\ninterface PendingInvitation extends InvitationEmailResult {\n email: string;\n roleIds: string[];\n invitedById: string | null;\n}\n\ninterface PendingInvitationsData {\n workspaceInvitations: { items: PendingInvitation[]; total: number; hasMore: boolean };\n}\n\nconst WRITE_OPTIONS = { refetchQueries: [PENDING_INVITATIONS_QUERY], awaitRefetchQueries: true };\n\ntype Notice = { tone: 'success' | 'warning' | 'error'; text: string };\n\nconst NOTICE_CLASS: Record<Notice['tone'], string> = {\n success: 'text-status-success-text',\n warning: 'text-status-warning-text',\n error: 'text-destructive',\n};\n\nconst formatDate = (value: string | null | undefined): string | null =>\n parseInvitationDate(value)?.toLocaleString() ?? null;\n\nexport function PendingInvitations({\n workspaceId,\n currentUserId,\n roleNames,\n}: {\n /** The workspace the list is for. The query is scoped by a request header, so callers key this component on it. */\n workspaceId?: string | null;\n /** Cancelling is the inviter's own action; other people's invitations are listed without it. */\n currentUserId?: string | null;\n /** PlanMagnet access-level names by role id, as the page already loaded them. */\n roleNames: ReadonlyMap<string, string>;\n}) {\n const tr = useTr();\n const { data, loading, error, refetch } = useQuery<PendingInvitationsData>(PENDING_INVITATIONS, {\n variables: { pagination: { limit: PAGE_LIMIT } },\n skip: !workspaceId,\n // Header-scoped (x-workspace-id), so its cache key is the same in every workspace: never render a cached\n // result from another workspace's visit, always wait for this workspace's answer.\n fetchPolicy: 'network-only',\n });\n const [resend] = useMutation<{ resendInvitation: InvitationEmailResult }>(\n RESEND_INVITATION,\n WRITE_OPTIONS\n );\n const [cancel] = useMutation(CANCEL_INVITATION, WRITE_OPTIONS);\n const [busyId, setBusyId] = useState<string | null>(null);\n const [notice, setNotice] = useState<Notice | null>(null);\n // The clock the expiry badge is judged against, read once per page visit (render must stay pure).\n const [now] = useState(() => Date.now());\n\n const forbidden = tr(\n 'planmagnet.invite.pending.forbidden',\n 'Your access level does not allow managing invitations in this workspace.'\n );\n\n // A refused write (e.g. the invitation was accepted or cancelled elsewhere) leaves the 15 s fetch replay in place;\n // drop it so the re-read shows the invitation as it is now.\n const reloadAfterRefusal = () => {\n clearGraphqlFetchResponseCache();\n void refetch();\n };\n\n const onResend = async (invitation: PendingInvitation) => {\n setBusyId(invitation.id);\n setNotice(null);\n try {\n const result = await throwOnError(resend({ variables: { id: invitation.id } }));\n const updated = result.data?.resendInvitation;\n if (invitationEmailSent(updated)) {\n setNotice({\n tone: 'success',\n text: tr(\n 'planmagnet.invite.pending.resent',\n 'Invitation email sent again to {{email}}.',\n {\n email: invitation.email,\n }\n ),\n });\n } else {\n const reason = invitationEmailReason(updated?.emailErrorCode);\n setNotice({\n tone: 'warning',\n text: tr(\n 'planmagnet.invite.pending.resendNotSent',\n 'The invitation to {{email}} is still pending, but the email was not sent. {{reason}}',\n { email: invitation.email, reason: tr(reason.key, reason.fallback) }\n ),\n });\n }\n } catch (e) {\n setNotice({\n tone: 'error',\n text: tr(\n 'planmagnet.invite.pending.resendFailed',\n 'The invitation to {{email}} could not be resent: {{reason}}',\n {\n email: invitation.email,\n reason: refusalMessage(\n e,\n tr('planmagnet.invite.pending.resendFailedFallback', 'Try again.'),\n forbidden\n ),\n }\n ),\n });\n reloadAfterRefusal();\n } finally {\n setBusyId(null);\n }\n };\n\n const onCancel = async (invitation: PendingInvitation) => {\n setBusyId(invitation.id);\n setNotice(null);\n try {\n await throwOnError(cancel({ variables: { id: invitation.id } }));\n setNotice({\n tone: 'success',\n text: tr(\n 'planmagnet.invite.pending.cancelled',\n 'The invitation to {{email}} was cancelled.',\n {\n email: invitation.email,\n }\n ),\n });\n } catch (e) {\n setNotice({\n tone: 'error',\n text: tr(\n 'planmagnet.invite.pending.cancelFailed',\n 'The invitation to {{email}} could not be cancelled: {{reason}}',\n {\n email: invitation.email,\n reason: refusalMessage(\n e,\n tr('planmagnet.invite.pending.cancelFailedFallback', 'Try again.'),\n forbidden\n ),\n }\n ),\n });\n reloadAfterRefusal();\n } finally {\n setBusyId(null);\n }\n };\n\n const list = data?.workspaceInvitations;\n const items = list?.items ?? [];\n\n const rolesText = (roleIds: string[]): string => {\n const { names, unknown } = invitationRoleNames(roleIds, roleNames);\n const parts = [...names];\n if (unknown > 0) {\n parts.push(\n tr('planmagnet.invite.pending.otherRoles', '{{count}} other access level(s)', {\n count: unknown,\n })\n );\n }\n return parts.length > 0\n ? parts.join(', ')\n : tr('planmagnet.invite.pending.noRoles', 'No access level');\n };\n\n return (\n <Card data-testid=\"pending-invitations\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Mail className=\"h-4 w-4\" />\n {tr('planmagnet.invite.pending.title', 'Pending invitations')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {notice && (\n <p\n role={notice.tone === 'error' ? 'alert' : 'status'}\n className={`text-sm ${NOTICE_CLASS[notice.tone]}`}\n data-testid=\"pending-invitations-notice\"\n >\n {notice.text}\n </p>\n )}\n\n {loading && !data ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr('planmagnet.invite.pending.loading', 'Loading pending invitations…')}\n </p>\n ) : error && !data ? (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {isForbidden(error)\n ? forbidden\n : tr(\n 'planmagnet.invite.pending.loadFailed',\n 'Pending invitations could not be loaded: {{reason}}',\n { reason: error.message }\n )}\n </p>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr('planmagnet.invite.pending.empty', 'No invitations are waiting to be accepted.')}\n </p>\n ) : (\n <ul className=\"divide-y divide-border\" data-testid=\"pending-invitations-list\">\n {items.map((invitation) => {\n const sent = invitationEmailSent(invitation);\n const status = invitationEmailStatusLabel(invitation.emailStatus);\n const reason = invitationEmailReason(invitation.emailErrorCode);\n const attempted = formatDate(invitation.emailLastAttemptAt);\n const expires = parseInvitationDate(invitation.expiresAt);\n const expired = !!expires && expires.getTime() <= now;\n const busy = busyId === invitation.id;\n return (\n <li\n key={invitation.id}\n className=\"flex flex-col gap-2 py-3 sm:flex-row sm:items-start sm:justify-between\"\n data-testid=\"pending-invitation\"\n >\n <div className=\"min-w-0 space-y-1\">\n <p className=\"truncate text-sm font-medium\">{invitation.email}</p>\n <p className=\"text-xs text-muted-foreground\">{rolesText(invitation.roleIds)}</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-muted-foreground\">\n <Badge\n variant={invitationEmailBadgeVariant(invitation.emailStatus)}\n data-testid=\"pending-invitation-email-status\"\n >\n {tr(status.key, status.fallback)}\n </Badge>\n {attempted && (\n <span>\n {tr('planmagnet.invite.pending.lastAttempt', 'Last tried {{when}}', {\n when: attempted,\n })}\n </span>\n )}\n {expires && (\n <span className={expired ? 'text-status-warning-text' : undefined}>\n {expired\n ? tr('planmagnet.invite.pending.expired', 'Expired {{when}}', {\n when: expires.toLocaleString(),\n })\n : tr('planmagnet.invite.pending.expires', 'Expires {{when}}', {\n when: expires.toLocaleString(),\n })}\n </span>\n )}\n </div>\n {!sent && invitation.emailStatus && (\n <p className=\"text-xs text-status-warning-text\">\n {tr(reason.key, reason.fallback)}\n </p>\n )}\n </div>\n <div className=\"flex shrink-0 gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={busyId !== null}\n onClick={() => void onResend(invitation)}\n data-testid=\"pending-invitation-resend\"\n >\n <RefreshCw className=\"mr-1 h-4 w-4\" />\n {busy\n ? tr('planmagnet.invite.pending.working', 'Working…')\n : tr('planmagnet.invite.pending.resend', 'Resend')}\n </Button>\n {invitation.invitedById === currentUserId && (\n <ConfirmButton\n label={tr('planmagnet.invite.pending.cancel', 'Cancel')}\n variant=\"outline\"\n icon={<X className=\"mr-1 h-4 w-4\" />}\n testId=\"pending-invitation-cancel\"\n title={tr(\n 'planmagnet.invite.pending.confirmCancel.title',\n 'Cancel the invitation to {{email}}?',\n { email: invitation.email }\n )}\n description={tr(\n 'planmagnet.invite.pending.confirmCancel.description',\n 'Its link stops working and they will not join this workspace from it. You can invite them again later.'\n )}\n confirmLabel={tr(\n 'planmagnet.invite.pending.confirmCancel.confirm',\n 'Cancel invitation'\n )}\n cancelLabel={tr('planmagnet.invite.pending.confirmCancel.keep', 'Keep it')}\n destructive\n disabled={busyId !== null}\n onConfirm={() => void onCancel(invitation)}\n />\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n\n {list?.hasMore && (\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.invite.pending.truncated',\n 'Showing the first {{shown}} of {{total}} pending invitations.',\n { shown: items.length, total: list.total }\n )}\n </p>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PendingInvitations.js","names":[],"sources":["../../../src/planmagnet/team/PendingInvitations.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport { Badge, Button, Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { Mail, RefreshCw, X } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { ConfirmButton } from '../controls/ConfirmButton';\nimport { refusalMessage } from '../product/refusal';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport { throwOnError } from '../utils/mutationResult';\nimport {\n invitationEmailBadgeVariant,\n invitationEmailReason,\n invitationEmailSent,\n invitationEmailStatusLabel,\n invitationRoleNames,\n parseInvitationDate,\n type InvitationEmailFields,\n} from './invitationEmail';\n\n/**\n * The workspace's pending invitations on the Invite page (BOFF-8866): who was invited, with which access, whether\n * the invitation email actually went out (and when it was last tried), when the invitation expires, and Resend /\n * Cancel. `workspaceInvitations` and the two writes read the workspace from the gateway's x-workspace-id header.\n * A write's outcome is shown above the list, so a row that leaves the list on the refresh does not take its message\n * with it.\n */\n\n/** Operation name of the list, for `refetchQueries` of every write that changes it. */\nexport const PENDING_INVITATIONS_QUERY = 'PlanmagnetPendingInvitations';\n\nconst PAGE_LIMIT = 50;\n\nconst PENDING_INVITATIONS = gql`\n query PlanmagnetPendingInvitations($pagination: PaginationInput) {\n workspaceInvitations(status: PENDING, pagination: $pagination) {\n items {\n id\n email\n status\n roleIds\n expiresAt\n emailStatus\n emailLastAttemptAt\n emailErrorCode\n }\n total\n hasMore\n }\n }\n`;\n\n/** Sends the invitation email again (and renews the invitation's expiry); answers with the new email outcome. */\nexport const RESEND_INVITATION = gql`\n mutation PlanmagnetResendInvitation($id: ID!) {\n resendInvitation(id: $id) {\n id\n status\n expiresAt\n emailStatus\n emailLastAttemptAt\n emailErrorCode\n }\n }\n`;\n\nconst CANCEL_INVITATION = gql`\n mutation PlanmagnetCancelInvitation($id: ID!) {\n cancelInvitation(id: $id) {\n id\n status\n }\n }\n`;\n\nexport interface InvitationEmailResult extends InvitationEmailFields {\n id: string;\n status: string;\n expiresAt: string | null;\n}\n\ninterface PendingInvitation extends InvitationEmailResult {\n email: string;\n roleIds: string[];\n}\n\ninterface PendingInvitationsData {\n workspaceInvitations: { items: PendingInvitation[]; total: number; hasMore: boolean };\n}\n\nconst WRITE_OPTIONS = { refetchQueries: [PENDING_INVITATIONS_QUERY], awaitRefetchQueries: true };\n\ntype Notice = { tone: 'success' | 'warning' | 'error'; text: string };\n\nconst NOTICE_CLASS: Record<Notice['tone'], string> = {\n success: 'text-status-success-text',\n warning: 'text-status-warning-text',\n error: 'text-destructive',\n};\n\nconst formatDate = (value: string | null | undefined): string | null =>\n parseInvitationDate(value)?.toLocaleString() ?? null;\n\nexport function PendingInvitations({\n workspaceId,\n roleNames,\n}: {\n /** The workspace the list is for. The query is scoped by a request header, so callers key this component on it. */\n workspaceId?: string | null;\n /** PlanMagnet access-level names by role id, as the page already loaded them. */\n roleNames: ReadonlyMap<string, string>;\n}) {\n const tr = useTr();\n const { data, loading, error, refetch } = useQuery<PendingInvitationsData>(PENDING_INVITATIONS, {\n variables: { pagination: { limit: PAGE_LIMIT } },\n skip: !workspaceId,\n // Header-scoped (x-workspace-id), so its cache key is the same in every workspace: never render a cached\n // result from another workspace's visit, always wait for this workspace's answer.\n fetchPolicy: 'network-only',\n });\n const [resend] = useMutation<{ resendInvitation: InvitationEmailResult }>(\n RESEND_INVITATION,\n WRITE_OPTIONS\n );\n const [cancel] = useMutation(CANCEL_INVITATION, WRITE_OPTIONS);\n const [busyId, setBusyId] = useState<string | null>(null);\n const [notice, setNotice] = useState<Notice | null>(null);\n // The clock the expiry badge is judged against, read once per page visit (render must stay pure).\n const [now] = useState(() => Date.now());\n\n const forbidden = tr(\n 'planmagnet.invite.pending.forbidden',\n 'Your access level does not allow managing invitations in this workspace.'\n );\n\n // A refused write (e.g. the invitation was accepted or cancelled elsewhere) leaves the 15 s fetch replay in place;\n // drop it so the re-read shows the invitation as it is now.\n const reloadAfterRefusal = () => {\n clearGraphqlFetchResponseCache();\n void refetch();\n };\n\n const onResend = async (invitation: PendingInvitation) => {\n setBusyId(invitation.id);\n setNotice(null);\n try {\n const result = await throwOnError(resend({ variables: { id: invitation.id } }));\n const updated = result.data?.resendInvitation;\n if (invitationEmailSent(updated)) {\n setNotice({\n tone: 'success',\n text: tr(\n 'planmagnet.invite.pending.resent',\n 'Invitation email sent again to {{email}}.',\n {\n email: invitation.email,\n }\n ),\n });\n } else {\n const reason = invitationEmailReason(updated?.emailErrorCode);\n setNotice({\n tone: 'warning',\n text: tr(\n 'planmagnet.invite.pending.resendNotSent',\n 'The invitation to {{email}} is still pending, but the email was not sent. {{reason}}',\n { email: invitation.email, reason: tr(reason.key, reason.fallback) }\n ),\n });\n }\n } catch (e) {\n setNotice({\n tone: 'error',\n text: tr(\n 'planmagnet.invite.pending.resendFailed',\n 'The invitation to {{email}} could not be resent: {{reason}}',\n {\n email: invitation.email,\n reason: refusalMessage(\n e,\n tr('planmagnet.invite.pending.resendFailedFallback', 'Try again.'),\n forbidden\n ),\n }\n ),\n });\n reloadAfterRefusal();\n } finally {\n setBusyId(null);\n }\n };\n\n const onCancel = async (invitation: PendingInvitation) => {\n setBusyId(invitation.id);\n setNotice(null);\n try {\n await throwOnError(cancel({ variables: { id: invitation.id } }));\n setNotice({\n tone: 'success',\n text: tr(\n 'planmagnet.invite.pending.cancelled',\n 'The invitation to {{email}} was cancelled.',\n {\n email: invitation.email,\n }\n ),\n });\n } catch (e) {\n setNotice({\n tone: 'error',\n text: tr(\n 'planmagnet.invite.pending.cancelFailed',\n 'The invitation to {{email}} could not be cancelled: {{reason}}',\n {\n email: invitation.email,\n reason: refusalMessage(\n e,\n tr('planmagnet.invite.pending.cancelFailedFallback', 'Try again.'),\n forbidden\n ),\n }\n ),\n });\n reloadAfterRefusal();\n } finally {\n setBusyId(null);\n }\n };\n\n const list = data?.workspaceInvitations;\n const items = list?.items ?? [];\n\n const rolesText = (roleIds: string[]): string => {\n const { names, unknown } = invitationRoleNames(roleIds, roleNames);\n const parts = [...names];\n if (unknown > 0) {\n parts.push(\n tr('planmagnet.invite.pending.otherRoles', '{{count}} other access level(s)', {\n count: unknown,\n })\n );\n }\n return parts.length > 0\n ? parts.join(', ')\n : tr('planmagnet.invite.pending.noRoles', 'No access level');\n };\n\n return (\n <Card data-testid=\"pending-invitations\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Mail className=\"h-4 w-4\" />\n {tr('planmagnet.invite.pending.title', 'Pending invitations')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {notice && (\n <p\n role={notice.tone === 'error' ? 'alert' : 'status'}\n className={`text-sm ${NOTICE_CLASS[notice.tone]}`}\n data-testid=\"pending-invitations-notice\"\n >\n {notice.text}\n </p>\n )}\n\n {loading && !data ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr('planmagnet.invite.pending.loading', 'Loading pending invitations…')}\n </p>\n ) : error && !data ? (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {isForbidden(error)\n ? forbidden\n : tr(\n 'planmagnet.invite.pending.loadFailed',\n 'Pending invitations could not be loaded: {{reason}}',\n { reason: error.message }\n )}\n </p>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr('planmagnet.invite.pending.empty', 'No invitations are waiting to be accepted.')}\n </p>\n ) : (\n <ul className=\"divide-y divide-border\" data-testid=\"pending-invitations-list\">\n {items.map((invitation) => {\n const sent = invitationEmailSent(invitation);\n const status = invitationEmailStatusLabel(invitation.emailStatus);\n const reason = invitationEmailReason(invitation.emailErrorCode);\n const attempted = formatDate(invitation.emailLastAttemptAt);\n const expires = parseInvitationDate(invitation.expiresAt);\n const expired = !!expires && expires.getTime() <= now;\n const busy = busyId === invitation.id;\n return (\n <li\n key={invitation.id}\n className=\"flex flex-col gap-2 py-3 sm:flex-row sm:items-start sm:justify-between\"\n data-testid=\"pending-invitation\"\n >\n <div className=\"min-w-0 space-y-1\">\n <p className=\"truncate text-sm font-medium\">{invitation.email}</p>\n <p className=\"text-xs text-muted-foreground\">{rolesText(invitation.roleIds)}</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-muted-foreground\">\n <Badge\n variant={invitationEmailBadgeVariant(invitation.emailStatus)}\n data-testid=\"pending-invitation-email-status\"\n >\n {tr(status.key, status.fallback)}\n </Badge>\n {attempted && (\n <span>\n {tr('planmagnet.invite.pending.lastAttempt', 'Last tried {{when}}', {\n when: attempted,\n })}\n </span>\n )}\n {expires && (\n <span className={expired ? 'text-status-warning-text' : undefined}>\n {expired\n ? tr('planmagnet.invite.pending.expired', 'Expired {{when}}', {\n when: expires.toLocaleString(),\n })\n : tr('planmagnet.invite.pending.expires', 'Expires {{when}}', {\n when: expires.toLocaleString(),\n })}\n </span>\n )}\n </div>\n {!sent && invitation.emailStatus && (\n <p className=\"text-xs text-status-warning-text\">\n {tr(reason.key, reason.fallback)}\n </p>\n )}\n </div>\n <div className=\"flex shrink-0 gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={busyId !== null}\n onClick={() => void onResend(invitation)}\n data-testid=\"pending-invitation-resend\"\n >\n <RefreshCw className=\"mr-1 h-4 w-4\" />\n {busy\n ? tr('planmagnet.invite.pending.working', 'Working…')\n : tr('planmagnet.invite.pending.resend', 'Resend')}\n </Button>\n {/* Every row offers Cancel, as it does Resend: cancelInvitation is an RBAC write\n (invitation:cancel), not an inviter-only one, so the gateway decides and a refusal says why. */}\n <ConfirmButton\n label={tr('planmagnet.invite.pending.cancel', 'Cancel')}\n variant=\"outline\"\n icon={<X className=\"mr-1 h-4 w-4\" />}\n testId=\"pending-invitation-cancel\"\n title={tr(\n 'planmagnet.invite.pending.confirmCancel.title',\n 'Cancel the invitation to {{email}}?',\n { email: invitation.email }\n )}\n description={tr(\n 'planmagnet.invite.pending.confirmCancel.description',\n 'Its link stops working and they will not join this workspace from it. You can invite them again later.'\n )}\n confirmLabel={tr(\n 'planmagnet.invite.pending.confirmCancel.confirm',\n 'Cancel invitation'\n )}\n cancelLabel={tr('planmagnet.invite.pending.confirmCancel.keep', 'Keep it')}\n destructive\n disabled={busyId !== null}\n onConfirm={() => void onCancel(invitation)}\n />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n\n {list?.hasMore && (\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.invite.pending.truncated',\n 'Showing the first {{shown}} of {{total}} pending invitations.',\n { shown: items.length, total: list.total }\n )}\n </p>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-planmagnet",
3
- "version": "2026.1008.1",
3
+ "version": "2026.1008.2",
4
4
  "description": "Burdenoff PlanMagnet Microfrontend",
5
5
  "type": "module",
6
6
  "files": [