@burdenoff/microfe-conversations 2026.830.1 → 2026.830.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.
@@ -138,7 +138,7 @@ var w = m`
138
138
  }
139
139
  }
140
140
  `;
141
- function N(e) {
141
+ function N(e, t) {
142
142
  if (typeof e == "string") return e;
143
143
  if (e && typeof e == "object" && "text" in e) {
144
144
  let t = e.text;
@@ -147,7 +147,7 @@ function N(e) {
147
147
  try {
148
148
  return JSON.stringify(e);
149
149
  } catch {
150
- return "Message content unavailable";
150
+ return t;
151
151
  }
152
152
  }
153
153
  var P = new Intl.DateTimeFormat(void 0, {
@@ -198,15 +198,15 @@ function L() {
198
198
  }),
199
199
  a && s && /* @__PURE__ */ _("p", {
200
200
  className: "text-sm text-text-secondary",
201
- children: "Loading reports…"
201
+ children: n("conversations.admin.loadingReports", "Loading reports…")
202
202
  }),
203
203
  a && c && /* @__PURE__ */ _("p", {
204
204
  className: "text-sm text-status-error-text",
205
205
  children: c.message
206
206
  }),
207
207
  a && !s && !c && m.length === 0 && /* @__PURE__ */ _(r, {
208
- title: "No reports waiting for review",
209
- description: "Reported messages will appear here as they are submitted.",
208
+ title: n("conversations.admin.noReportsTitle", "No reports waiting for review"),
209
+ description: n("conversations.admin.noReportsDescription", "Reported messages will appear here as they are submitted."),
210
210
  icon: /* @__PURE__ */ _(u, {
211
211
  className: "size-6",
212
212
  "aria-hidden": "true"
@@ -227,10 +227,10 @@ function L() {
227
227
  className: "flex flex-wrap items-center gap-2",
228
228
  children: [
229
229
  /* @__PURE__ */ _(I, { children: e.status }),
230
- e.message.isHidden && /* @__PURE__ */ _(I, { children: "Hidden" }),
231
- /* @__PURE__ */ v("span", {
230
+ e.message.isHidden && /* @__PURE__ */ _(I, { children: n("conversations.admin.hidden", "Hidden") }),
231
+ /* @__PURE__ */ _("span", {
232
232
  className: "text-xs text-text-secondary",
233
- children: ["Reported ", F(e.createdAt)]
233
+ children: n("conversations.admin.reportedAt", "Reported {{date}}", { date: F(e.createdAt) })
234
234
  })
235
235
  ]
236
236
  }),
@@ -240,27 +240,22 @@ function L() {
240
240
  }),
241
241
  /* @__PURE__ */ _("p", {
242
242
  className: "rounded-md border border-border-seam bg-bg-sunken p-3 text-sm text-text-secondary",
243
- children: N(e.message.content)
243
+ children: N(e.message.content, n("conversations.admin.messageContentUnavailable", "Message content unavailable"))
244
244
  }),
245
- /* @__PURE__ */ v("p", {
245
+ /* @__PURE__ */ _("p", {
246
246
  className: "text-xs text-text-secondary",
247
- children: [
248
- "Message ",
249
- e.messageId,
250
- " in conversation",
251
- " ",
252
- e.message.conversationId,
253
- " by ",
254
- e.message.authorId,
255
- "; reported by ",
256
- e.reportedBy
257
- ]
247
+ children: n("conversations.admin.reportMeta", "Message {{messageId}} in conversation {{conversationId}} by {{authorId}}; reported by {{reportedBy}}", {
248
+ messageId: e.messageId,
249
+ conversationId: e.message.conversationId,
250
+ authorId: e.message.authorId,
251
+ reportedBy: e.reportedBy
252
+ })
258
253
  })
259
254
  ]
260
255
  }), /* @__PURE__ */ _(b, {
261
256
  className: "shrink-0",
262
257
  primary: {
263
- label: "Hide and resolve",
258
+ label: n("conversations.admin.hideAndResolve", "Hide and resolve"),
264
259
  disabled: p,
265
260
  onSelect: () => void d({ variables: { input: {
266
261
  reportId: e.id,
@@ -268,7 +263,7 @@ function L() {
268
263
  } } })
269
264
  },
270
265
  actions: [{
271
- label: "Dismiss",
266
+ label: n("conversations.admin.dismiss", "Dismiss"),
272
267
  disabled: p,
273
268
  onSelect: () => void d({ variables: { input: {
274
269
  reportId: e.id,
@@ -307,23 +302,23 @@ function R() {
307
302
  children: [/* @__PURE__ */ v("select", {
308
303
  value: u,
309
304
  onChange: (e) => d(e.target.value),
310
- "aria-label": "Bulk action",
305
+ "aria-label": n("conversations.admin.bulkAction", "Bulk action"),
311
306
  className: "min-h-11 rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
312
307
  children: [
313
308
  /* @__PURE__ */ _("option", {
314
309
  value: "ARCHIVE",
315
- children: "Archive"
310
+ children: n("conversations.archive", "Archive")
316
311
  }),
317
312
  /* @__PURE__ */ _("option", {
318
313
  value: "UNARCHIVE",
319
- children: "Unarchive"
314
+ children: n("conversations.unarchive", "Unarchive")
320
315
  }),
321
316
  /* @__PURE__ */ _("option", {
322
317
  value: "DELETE",
323
- children: "Delete"
318
+ children: n("conversations.delete", "Delete")
324
319
  })
325
320
  ]
326
- }), /* @__PURE__ */ v(y, {
321
+ }), /* @__PURE__ */ _(y, {
327
322
  type: "button",
328
323
  size: "sm",
329
324
  disabled: o.size === 0 || T,
@@ -331,7 +326,7 @@ function R() {
331
326
  conversationIds: Array.from(o),
332
327
  action: u
333
328
  } } }),
334
- children: ["Apply to ", o.size]
329
+ children: n("conversations.admin.applyToCount", "Apply to {{count}}", { count: o.size })
335
330
  })]
336
331
  }),
337
332
  children: [
@@ -353,7 +348,7 @@ function R() {
353
348
  }),
354
349
  a && p && /* @__PURE__ */ _("p", {
355
350
  className: "text-sm text-text-secondary",
356
- children: "Loading conversations…"
351
+ children: n("conversations.admin.loadingConversations", "Loading conversations…")
357
352
  }),
358
353
  a && m && /* @__PURE__ */ _("p", {
359
354
  className: "text-sm text-status-error-text",
@@ -368,13 +363,13 @@ function R() {
368
363
  children: w.bulkUpdateConversations.successCount
369
364
  }), /* @__PURE__ */ _("p", {
370
365
  className: "text-xs text-text-secondary",
371
- children: "Succeeded"
366
+ children: n("conversations.admin.succeeded", "Succeeded")
372
367
  })] }), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
373
368
  className: "text-2xl font-semibold tabular-nums text-text-primary",
374
369
  children: w.bulkUpdateConversations.failureCount
375
370
  }), /* @__PURE__ */ _("p", {
376
371
  className: "text-xs text-text-secondary",
377
- children: "Failed"
372
+ children: n("conversations.admin.failed", "Failed")
378
373
  })] })]
379
374
  }),
380
375
  a && /* @__PURE__ */ _("div", {
@@ -383,24 +378,21 @@ function R() {
383
378
  className: "flex min-h-11 cursor-pointer items-center gap-3 border-b border-border-seam px-4 py-3 last:border-b-0 hover:bg-accent",
384
379
  children: [/* @__PURE__ */ _("input", {
385
380
  type: "checkbox",
386
- "aria-label": `Select ${e.title || "untitled conversation"}`,
381
+ "aria-label": n("conversations.admin.selectConversationAria", "Select {{title}}", { title: e.title || n("conversations.admin.untitledConversation", "Untitled conversation") }),
387
382
  checked: o.has(e.id),
388
383
  onChange: () => k(e.id)
389
384
  }), /* @__PURE__ */ v("div", {
390
385
  className: "min-w-0 flex-1",
391
386
  children: [/* @__PURE__ */ _("p", {
392
387
  className: "truncate text-sm font-medium text-text-primary",
393
- children: e.title || "Untitled conversation"
394
- }), /* @__PURE__ */ v("p", {
388
+ children: e.title || n("conversations.admin.untitledConversation", "Untitled conversation")
389
+ }), /* @__PURE__ */ _("p", {
395
390
  className: "text-xs text-text-secondary",
396
- children: [
397
- e.type,
398
- " · ",
399
- e.state,
400
- " · Updated",
401
- " ",
402
- F(e.updatedAt)
403
- ]
391
+ children: n("conversations.admin.conversationMeta", "{{type}} · {{state}} · Updated {{date}}", {
392
+ type: e.type,
393
+ state: e.state,
394
+ date: F(e.updatedAt)
395
+ })
404
396
  })]
405
397
  })]
406
398
  }, e.id))
@@ -435,15 +427,15 @@ function z() {
435
427
  }),
436
428
  s && /* @__PURE__ */ _("p", {
437
429
  className: "text-sm text-text-secondary",
438
- children: "Loading activity…"
430
+ children: n("conversations.admin.loadingActivity", "Loading activity…")
439
431
  }),
440
432
  l && /* @__PURE__ */ _("p", {
441
433
  className: "text-sm text-status-error-text",
442
434
  children: l.message
443
435
  }),
444
436
  !s && !l && u.length === 0 && /* @__PURE__ */ _(r, {
445
- title: "No activity found",
446
- description: "Conversation admin actions will appear here.",
437
+ title: n("conversations.admin.noActivityTitle", "No activity found"),
438
+ description: n("conversations.admin.noActivityDescription", "Conversation admin actions will appear here."),
447
439
  illustration: "empty-data"
448
440
  }),
449
441
  /* @__PURE__ */ _("div", {
@@ -464,16 +456,14 @@ function z() {
464
456
  }),
465
457
  /* @__PURE__ */ _("p", {
466
458
  className: "mt-1 text-sm text-text-secondary",
467
- children: e.description || "No description provided"
459
+ children: e.description || n("conversations.admin.noDescription", "No description provided")
468
460
  }),
469
- /* @__PURE__ */ v("p", {
461
+ /* @__PURE__ */ _("p", {
470
462
  className: "mt-2 text-xs text-text-secondary",
471
- children: [
472
- "User ",
473
- e.userId,
474
- " · Resource ",
475
- e.resourceId ?? "workspace"
476
- ]
463
+ children: n("conversations.admin.auditMeta", "User {{userId}} · Resource {{resourceId}}", {
464
+ userId: e.userId,
465
+ resourceId: e.resourceId ?? n("conversations.admin.workspaceResource", "workspace")
466
+ })
477
467
  })
478
468
  ]
479
469
  }, e.id))
@@ -519,8 +509,8 @@ function B() {
519
509
  children: r("conversations.admin.appsPurpose", "Extend conversations with apps and integrations — install bots, automations, or third-party connectors scoped to this workspace, adjust their settings, and remove the ones you no longer use.")
520
510
  }),
521
511
  /* @__PURE__ */ v(n, {
522
- title: "Install app",
523
- description: "Install a workspace-scoped conversation app by app ID.",
512
+ title: r("conversations.admin.installApp", "Install app"),
513
+ description: r("conversations.admin.installAppDescription", "Install a workspace-scoped conversation app by app ID."),
524
514
  children: [/* @__PURE__ */ v(x, {
525
515
  treatment: "glass",
526
516
  glow: !0,
@@ -529,15 +519,15 @@ function B() {
529
519
  /* @__PURE__ */ _("input", {
530
520
  value: l,
531
521
  onChange: (e) => u(e.target.value),
532
- placeholder: "app-id",
533
- "aria-label": "App ID",
522
+ placeholder: r("conversations.admin.appIdPlaceholder", "app-id"),
523
+ "aria-label": r("conversations.admin.appId", "App ID"),
534
524
  className: "min-h-11 rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"
535
525
  }),
536
526
  /* @__PURE__ */ _("textarea", {
537
527
  value: f,
538
528
  onChange: (e) => m(e.target.value),
539
529
  rows: 3,
540
- "aria-label": "App settings JSON",
530
+ "aria-label": r("conversations.admin.appSettingsJson", "App settings JSON"),
541
531
  className: "rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"
542
532
  }),
543
533
  /* @__PURE__ */ _(y, {
@@ -547,7 +537,7 @@ function B() {
547
537
  onClick: () => {
548
538
  let e = G(f);
549
539
  if (!l.trim() || !e) {
550
- D("Enter an app ID and a valid JSON settings object.");
540
+ D(r("conversations.admin.installAppError", "Enter an app ID and a valid JSON settings object."));
551
541
  return;
552
542
  }
553
543
  D(null), R({ variables: { input: {
@@ -556,7 +546,7 @@ function B() {
556
546
  settings: e
557
547
  } } });
558
548
  },
559
- children: "Install"
549
+ children: r("conversations.admin.install", "Install")
560
550
  })
561
551
  ]
562
552
  }), E && /* @__PURE__ */ _("p", {
@@ -566,7 +556,7 @@ function B() {
566
556
  }),
567
557
  N && /* @__PURE__ */ _("p", {
568
558
  className: "text-sm text-text-secondary",
569
- children: "Loading app installations…"
559
+ children: r("conversations.admin.loadingAppInstallations", "Loading app installations…")
570
560
  }),
571
561
  P && /* @__PURE__ */ _("p", {
572
562
  className: "text-sm text-status-error-text",
@@ -587,23 +577,18 @@ function B() {
587
577
  className: "text-sm font-semibold text-text-primary",
588
578
  children: e.appId
589
579
  }), /* @__PURE__ */ _(I, { children: e.state })]
590
- }), /* @__PURE__ */ v("p", {
580
+ }), /* @__PURE__ */ _("p", {
591
581
  className: "text-xs text-text-secondary",
592
- children: [
593
- "Version ",
594
- e.appVersion,
595
- " · Installed by",
596
- " ",
597
- e.installedBy,
598
- " on",
599
- " ",
600
- F(e.installedAt)
601
- ]
582
+ children: r("conversations.admin.appVersionMeta", "Version {{version}} · Installed by {{installedBy}} on {{installedAt}}", {
583
+ version: e.appVersion,
584
+ installedBy: e.installedBy,
585
+ installedAt: F(e.installedAt)
586
+ })
602
587
  })]
603
588
  }), /* @__PURE__ */ _(b, {
604
589
  className: "shrink-0",
605
590
  actions: [{
606
- label: "Uninstall",
591
+ label: r("conversations.admin.uninstall", "Uninstall"),
607
592
  intent: "destructive",
608
593
  disabled: U,
609
594
  icon: /* @__PURE__ */ _(p, {
@@ -611,19 +596,19 @@ function B() {
611
596
  "aria-hidden": "true"
612
597
  }),
613
598
  onSelect: () => {
614
- window.confirm(`Uninstall ${e.appId}? This removes the app and its settings from this workspace.`) && H({ variables: { id: e.id } });
599
+ window.confirm(r("conversations.admin.uninstallConfirm", "Uninstall {{appId}}? This removes the app and its settings from this workspace.", { appId: e.appId })) && H({ variables: { id: e.id } });
615
600
  }
616
601
  }]
617
602
  })]
618
603
  }), /* @__PURE__ */ _(C, {
619
604
  className: "mt-3",
620
605
  collapsedHeight: 0,
621
- label: "Edit settings (JSON)",
606
+ label: r("conversations.admin.editSettingsJson", "Edit settings (JSON)"),
622
607
  children: /* @__PURE__ */ v("div", {
623
608
  className: "grid gap-2 pt-2",
624
609
  children: [/* @__PURE__ */ _("textarea", {
625
610
  rows: 5,
626
- "aria-label": `Settings for ${e.appId}`,
611
+ "aria-label": r("conversations.admin.settingsForApp", "Settings for {{appId}}", { appId: e.appId }),
627
612
  value: w[e.id] ?? "{}",
628
613
  onChange: (t) => T((n) => ({
629
614
  ...n,
@@ -639,7 +624,7 @@ function B() {
639
624
  onClick: () => {
640
625
  let t = G(w[e.id] ?? "{}");
641
626
  if (!t) {
642
- D("Installation settings must be a valid JSON object.");
627
+ D(r("conversations.admin.settingsInvalidJson", "Installation settings must be a valid JSON object."));
643
628
  return;
644
629
  }
645
630
  D(null), B({ variables: {
@@ -647,7 +632,7 @@ function B() {
647
632
  settings: t
648
633
  } });
649
634
  },
650
- children: "Save settings"
635
+ children: r("conversations.admin.saveSettings", "Save settings")
651
636
  })]
652
637
  })
653
638
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationAdminPages.js","names":[],"sources":["../../../src/conversations/pages/ConversationAdminPages.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport { gql } from \"@apollo/client\";\nimport { useMutation, useQuery } from \"@apollo/client/react\";\nimport {\n Activity,\n Archive,\n CheckCircle2,\n Package,\n ShieldCheck,\n Trash2,\n} from \"lucide-react\";\n\nimport {\n Button,\n CTAOverflowMenu,\n GlassCard,\n PagePurpose,\n ShowMore,\n} from \"@burdenoff/fe-libs/ui\";\nimport { EmptyState, PageLayout, PageSection } from \"../components/PageLayout\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { useTr } from \"../utils/i18n\";\n\nconst GET_MESSAGE_REPORTS = gql`\n query ConversationAdminMessageReports(\n $first: Int\n $filter: MessageReportFilter\n ) {\n messageReports(first: $first, filter: $filter) {\n edges {\n node {\n id\n messageId\n reportedBy\n reason\n status\n notes\n createdAt\n reviewedAt\n message {\n id\n conversationId\n authorId\n content\n isHidden\n createdAt\n }\n }\n }\n }\n }\n`;\n\nconst RESOLVE_MESSAGE_REPORT = gql`\n mutation ConversationAdminResolveMessageReport(\n $input: ResolveMessageReportInput!\n ) {\n resolveMessageReport(input: $input) {\n id\n status\n reviewedAt\n notes\n message {\n id\n isHidden\n }\n }\n }\n`;\n\nconst GET_BULK_CONVERSATIONS = gql`\n query ConversationAdminBulkConversations(\n $first: Int\n $filter: ConversationFilter\n ) {\n conversations(first: $first, filter: $filter) {\n edges {\n node {\n id\n title\n type\n state\n updatedAt\n }\n }\n }\n }\n`;\n\nconst BULK_UPDATE_CONVERSATIONS = gql`\n mutation ConversationAdminBulkUpdate(\n $input: BulkConversationOperationInput!\n ) {\n bulkUpdateConversations(input: $input) {\n success\n processedCount\n successCount\n failureCount\n conversationIds\n errors {\n conversationId\n message\n }\n }\n }\n`;\n\nconst GET_CONVERSATION_AUDIT_LOG = gql`\n query ConversationAdminAuditLog($filter: ActivityLogFilter) {\n getActivities(filter: $filter) {\n id\n action\n description\n resourceId\n resourceType\n userId\n timestamp\n }\n }\n`;\n\nconst GET_APP_INSTALLATIONS = gql`\n query ConversationAdminAppInstallations($workspaceId: ID) {\n appInstallations(workspaceId: $workspaceId) {\n id\n appId\n appVersion\n state\n settings\n permissions\n installedBy\n installedAt\n updatedAt\n }\n }\n`;\n\nconst INSTALL_APP = gql`\n mutation ConversationAdminInstallApp($input: InstallAppInput!) {\n installApp(input: $input) {\n id\n appId\n state\n settings\n installedAt\n }\n }\n`;\n\nconst UPDATE_APP_SETTINGS = gql`\n mutation ConversationAdminUpdateAppSettings($id: ID!, $settings: JSON!) {\n updateAppSettings(id: $id, settings: $settings) {\n id\n settings\n updatedAt\n }\n }\n`;\n\nconst UNINSTALL_APP = gql`\n mutation ConversationAdminUninstallApp($id: ID!) {\n uninstallApp(id: $id) {\n appInstallationId\n success\n }\n }\n`;\n\ninterface MessageReportNode {\n id: string;\n messageId: string;\n reportedBy: string;\n reason: string;\n status: string;\n notes?: string | null;\n createdAt: string;\n reviewedAt?: string | null;\n message: {\n id: string;\n conversationId: string;\n authorId: string;\n content: unknown;\n isHidden: boolean;\n createdAt: string;\n };\n}\n\ninterface ConversationNode {\n id: string;\n title?: string | null;\n type: string;\n state: string;\n updatedAt: string;\n}\n\ninterface ActivityLogNode {\n id: string;\n action: string;\n description?: string | null;\n resourceId?: string | null;\n resourceType?: string | null;\n userId: string;\n timestamp: string;\n}\n\ninterface AppInstallationNode {\n id: string;\n appId: string;\n appVersion: string;\n state: string;\n settings: unknown;\n permissions: string[];\n installedBy: string;\n installedAt: string;\n updatedAt: string;\n}\n\ninterface BulkUpdateConversationsResult {\n bulkUpdateConversations: {\n success: boolean;\n processedCount: number;\n successCount: number;\n failureCount: number;\n conversationIds: string[];\n errors: Array<{ conversationId: string; message: string }>;\n };\n}\n\nfunction formatContent(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (content && typeof content === \"object\" && \"text\" in content) {\n const text = (content as { text?: unknown }).text;\n if (typeof text === \"string\") return text;\n }\n try {\n return JSON.stringify(content);\n } catch {\n return \"Message content unavailable\";\n }\n}\n\nconst DATE_FORMATTER = new Intl.DateTimeFormat(undefined, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n});\n\nfunction formatDate(value: string): string {\n return DATE_FORMATTER.format(new Date(value));\n}\n\nfunction StatusPill({ children }: { children: string }) {\n return (\n <span className=\"rounded-full border border-border-subtle bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {children}\n </span>\n );\n}\n\nexport function ModerationQueuePage() {\n const tr = useTr();\n const { workspaceId } = useConversations();\n const { data, loading, error, refetch } = useQuery<{\n messageReports: { edges: Array<{ node: MessageReportNode }> };\n }>(GET_MESSAGE_REPORTS, {\n variables: { first: 50, filter: { statuses: [\"PENDING\", \"REVIEWING\"] } },\n skip: !workspaceId,\n });\n const [resolveReport, { loading: resolving }] = useMutation(\n RESOLVE_MESSAGE_REPORT,\n {\n onCompleted: () => void refetch(),\n },\n );\n\n const reports = data?.messageReports.edges.map((edge) => edge.node) ?? [];\n\n return (\n <PageLayout\n title={tr(\"conversations.admin.moderationPageTitle\", \"Moderation queue\")}\n subtitle={tr(\n \"conversations.admin.moderationPageDescription\",\n \"Review reported messages and apply moderator decisions.\",\n )}\n icon={<ShieldCheck className=\"size-5\" aria-hidden=\"true\" />}\n maxWidth=\"2xl\"\n >\n <PagePurpose icon={<ShieldCheck className=\"size-4\" aria-hidden=\"true\" />}>\n {tr(\n \"conversations.admin.moderationPurpose\",\n \"Keep conversations safe: people can report messages they find abusive, spammy, or off-topic, and they land here for a moderator to hide the message or dismiss the report. Work the queue down to zero to stay on top of issues.\",\n )}\n </PagePurpose>\n {!workspaceId && (\n <EmptyState\n title={tr(\n \"conversations.admin.selectWorkspace\",\n \"Select a workspace\",\n )}\n description={tr(\n \"conversations.admin.selectWorkspaceModerationDescription\",\n \"Choose a workspace to review reported conversation messages.\",\n )}\n icon={<ShieldCheck className=\"size-6\" aria-hidden=\"true\" />}\n illustration=\"empty-generic\"\n />\n )}\n {workspaceId && loading && (\n <p className=\"text-sm text-text-secondary\">Loading reports…</p>\n )}\n {workspaceId && error && (\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n )}\n {workspaceId && !loading && !error && reports.length === 0 && (\n <EmptyState\n title=\"No reports waiting for review\"\n description=\"Reported messages will appear here as they are submitted.\"\n icon={<CheckCircle2 className=\"size-6\" aria-hidden=\"true\" />}\n illustration=\"empty-notifications\"\n />\n )}\n <div className=\"grid gap-3\">\n {reports.map((report) => (\n <GlassCard\n key={report.id}\n treatment=\"glass\"\n className=\"border-border-seam p-4\"\n >\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"space-y-2 min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusPill>{report.status}</StatusPill>\n {report.message.isHidden && <StatusPill>Hidden</StatusPill>}\n <span className=\"text-xs text-text-secondary\">\n Reported {formatDate(report.createdAt)}\n </span>\n </div>\n <p className=\"text-sm font-medium text-text-primary\">\n {report.reason}\n </p>\n <p className=\"rounded-md border border-border-seam bg-bg-sunken p-3 text-sm text-text-secondary\">\n {formatContent(report.message.content)}\n </p>\n <p className=\"text-xs text-text-secondary\">\n Message {report.messageId} in conversation{\" \"}\n {report.message.conversationId} by {report.message.authorId};\n reported by {report.reportedBy}\n </p>\n </div>\n {/* One primary CTA (hide + resolve), the secondary \"Dismiss\"\n collapses into the overflow menu. */}\n <CTAOverflowMenu\n className=\"shrink-0\"\n primary={{\n label: \"Hide and resolve\",\n disabled: resolving,\n onSelect: () =>\n void resolveReport({\n variables: {\n input: { reportId: report.id, action: \"HIDE_MESSAGE\" },\n },\n }),\n }}\n actions={[\n {\n label: \"Dismiss\",\n disabled: resolving,\n onSelect: () =>\n void resolveReport({\n variables: {\n input: { reportId: report.id, action: \"DISMISS\" },\n },\n }),\n },\n ]}\n />\n </div>\n </GlassCard>\n ))}\n </div>\n </PageLayout>\n );\n}\n\nexport function BulkOperationsPage() {\n const tr = useTr();\n const { workspaceId } = useConversations();\n const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set());\n const [action, setAction] = useState<\"ARCHIVE\" | \"UNARCHIVE\" | \"DELETE\">(\n \"ARCHIVE\",\n );\n const { data, loading, error, refetch } = useQuery<{\n conversations: { edges: Array<{ node: ConversationNode }> };\n }>(GET_BULK_CONVERSATIONS, {\n variables: { first: 100 },\n skip: !workspaceId,\n });\n const [bulkUpdate, { data: bulkResult, loading: updating }] = useMutation<\n BulkUpdateConversationsResult,\n { input: { conversationIds: string[]; action: typeof action } }\n >(BULK_UPDATE_CONVERSATIONS, {\n onCompleted: () => {\n setSelectedIds(new Set());\n void refetch();\n },\n });\n const conversations =\n data?.conversations.edges.map((edge) => edge.node) ?? [];\n\n const toggle = (id: string) => {\n setSelectedIds((current) => {\n const next = new Set(current);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n };\n\n return (\n <PageLayout\n title={tr(\n \"conversations.admin.bulkOperationsPageTitle\",\n \"Bulk operations\",\n )}\n subtitle={tr(\n \"conversations.admin.bulkOperationsPageDescription\",\n \"Select workspace conversations and apply archive, unarchive, or delete operations.\",\n )}\n icon={<Archive className=\"size-5\" aria-hidden=\"true\" />}\n maxWidth=\"2xl\"\n headerActions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <select\n value={action}\n onChange={(event) => setAction(event.target.value as typeof action)}\n aria-label=\"Bulk action\"\n className=\"min-h-11 rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"ARCHIVE\">Archive</option>\n <option value=\"UNARCHIVE\">Unarchive</option>\n <option value=\"DELETE\">Delete</option>\n </select>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={selectedIds.size === 0 || updating}\n onClick={() =>\n void bulkUpdate({\n variables: {\n input: { conversationIds: Array.from(selectedIds), action },\n },\n })\n }\n >\n Apply to {selectedIds.size}\n </Button>\n </div>\n }\n >\n <PagePurpose icon={<Archive className=\"size-4\" aria-hidden=\"true\" />}>\n {tr(\n \"conversations.admin.bulkPurpose\",\n \"Clean up at scale instead of one conversation at a time — select many conversations, then archive them to declutter inboxes, unarchive to restore, or delete the ones you no longer need. Handy for closing out finished projects or removing test threads.\",\n )}\n </PagePurpose>\n {!workspaceId && (\n <EmptyState\n title={tr(\n \"conversations.admin.selectWorkspace\",\n \"Select a workspace\",\n )}\n description={tr(\n \"conversations.admin.selectWorkspaceBulkDescription\",\n \"Choose a workspace to manage conversations in bulk.\",\n )}\n icon={<Archive className=\"size-6\" aria-hidden=\"true\" />}\n illustration=\"empty-generic\"\n />\n )}\n {workspaceId && loading && (\n <p className=\"text-sm text-text-secondary\">Loading conversations…</p>\n )}\n {workspaceId && error && (\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n )}\n {bulkResult?.bulkUpdateConversations && (\n <GlassCard\n treatment=\"metric\"\n glow\n className=\"grid grid-cols-2 gap-4 border-border-seam p-4 sm:max-w-md\"\n >\n <div>\n <p className=\"text-2xl font-semibold tabular-nums text-status-success-text\">\n {bulkResult.bulkUpdateConversations.successCount}\n </p>\n <p className=\"text-xs text-text-secondary\">Succeeded</p>\n </div>\n <div>\n <p className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {bulkResult.bulkUpdateConversations.failureCount}\n </p>\n <p className=\"text-xs text-text-secondary\">Failed</p>\n </div>\n </GlassCard>\n )}\n {workspaceId && (\n <div className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface\">\n {conversations.map((conversation) => (\n <label\n key={conversation.id}\n className=\"flex min-h-11 cursor-pointer items-center gap-3 border-b border-border-seam px-4 py-3 last:border-b-0 hover:bg-accent\"\n >\n <input\n type=\"checkbox\"\n aria-label={`Select ${conversation.title || \"untitled conversation\"}`}\n checked={selectedIds.has(conversation.id)}\n onChange={() => toggle(conversation.id)}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {conversation.title || \"Untitled conversation\"}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {conversation.type} · {conversation.state} · Updated{\" \"}\n {formatDate(conversation.updatedAt)}\n </p>\n </div>\n </label>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n\nexport function ConversationAuditLogPage() {\n const tr = useTr();\n const { workspaceId } = useConversations();\n const { data, loading, error } = useQuery<{\n getActivities: ActivityLogNode[];\n }>(GET_CONVERSATION_AUDIT_LOG, {\n variables: {\n filter: { workspaceId, resourceType: \"conversation\", limit: 50 },\n },\n skip: !workspaceId,\n });\n const logs = data?.getActivities ?? [];\n\n return (\n <PageLayout\n title={tr(\"conversations.admin.auditLogPageTitle\", \"Audit log\")}\n subtitle={tr(\n \"conversations.admin.auditLogPageDescription\",\n \"Activity stream for admin and moderator actions on workspace conversations.\",\n )}\n icon={<Activity className=\"size-5\" aria-hidden=\"true\" />}\n maxWidth=\"2xl\"\n >\n <PagePurpose icon={<Activity className=\"size-4\" aria-hidden=\"true\" />}>\n {tr(\n \"conversations.admin.auditPurpose\",\n \"An accountability trail of who changed what — every admin and moderator action on workspace conversations is recorded here. Use it to investigate an incident, confirm a change was made, or satisfy a compliance review.\",\n )}\n </PagePurpose>\n {loading && (\n <p className=\"text-sm text-text-secondary\">Loading activity…</p>\n )}\n {error && (\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n )}\n {!loading && !error && logs.length === 0 && (\n <EmptyState\n title=\"No activity found\"\n description=\"Conversation admin actions will appear here.\"\n illustration=\"empty-data\"\n />\n )}\n <div className=\"grid gap-3\">\n {logs.map((log) => (\n <GlassCard\n key={log.id}\n treatment=\"plain\"\n className=\"border-border-seam p-4\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {log.action}\n </p>\n <span className=\"text-xs tabular-nums text-text-secondary\">\n {formatDate(log.timestamp)}\n </span>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {log.description || \"No description provided\"}\n </p>\n <p className=\"mt-2 text-xs text-text-secondary\">\n User {log.userId} · Resource {log.resourceId ?? \"workspace\"}\n </p>\n </GlassCard>\n ))}\n </div>\n </PageLayout>\n );\n}\n\nexport function AppInstallationsPage() {\n const tr = useTr();\n const { workspaceId } = useConversations();\n const [appId, setAppId] = useState(\"\");\n const [settingsInput, setSettingsInput] = useState(\"{}\");\n const [settingsDrafts, setSettingsDrafts] = useState<Record<string, string>>(\n {},\n );\n const [formError, setFormError] = useState<string | null>(null);\n const { data, loading, error, refetch } = useQuery<{\n appInstallations: AppInstallationNode[];\n }>(GET_APP_INSTALLATIONS, {\n variables: { workspaceId },\n skip: !workspaceId,\n });\n const [installApp, { loading: installing }] = useMutation(INSTALL_APP, {\n onCompleted: () => void refetch(),\n });\n const [updateSettings, { loading: saving }] = useMutation(\n UPDATE_APP_SETTINGS,\n { onCompleted: () => void refetch() },\n );\n const [uninstallApp, { loading: uninstalling }] = useMutation(UNINSTALL_APP, {\n onCompleted: () => void refetch(),\n });\n const installations = useMemo(\n () => data?.appInstallations ?? [],\n [data?.appInstallations],\n );\n\n useEffect(() => {\n setSettingsDrafts((current) => {\n if (installations.every((installation) => current[installation.id])) {\n return current;\n }\n const next = { ...current };\n for (const installation of installations) {\n if (!next[installation.id]) {\n next[installation.id] = JSON.stringify(\n installation.settings ?? {},\n null,\n 2,\n );\n }\n }\n return next;\n });\n }, [installations]);\n\n const parsedSettings = (raw: string): Record<string, unknown> | null => {\n try {\n const parsed = JSON.parse(raw) as unknown;\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n } catch {\n return null;\n }\n };\n\n const handleInstall = () => {\n const settings = parsedSettings(settingsInput);\n if (!appId.trim() || !settings) {\n setFormError(\"Enter an app ID and a valid JSON settings object.\");\n return;\n }\n setFormError(null);\n void installApp({\n variables: { input: { appId: appId.trim(), workspaceId, settings } },\n });\n };\n\n return (\n <PageLayout\n title={tr(\n \"conversations.admin.appInstallationsPageTitle\",\n \"App installations\",\n )}\n subtitle={tr(\n \"conversations.admin.appInstallationsPageDescription\",\n \"Install, configure, and remove conversation apps and integrations.\",\n )}\n icon={<Package className=\"size-5\" aria-hidden=\"true\" />}\n maxWidth=\"2xl\"\n >\n <PagePurpose icon={<Package className=\"size-4\" aria-hidden=\"true\" />}>\n {tr(\n \"conversations.admin.appsPurpose\",\n \"Extend conversations with apps and integrations — install bots, automations, or third-party connectors scoped to this workspace, adjust their settings, and remove the ones you no longer use.\",\n )}\n </PagePurpose>\n <PageSection\n title=\"Install app\"\n description=\"Install a workspace-scoped conversation app by app ID.\"\n >\n {/* The single emphasis zone on this page — installing a new app is the\n primary action an admin takes here. */}\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"grid gap-3 border-border-seam p-4 sm:grid-cols-[minmax(0,1fr)_minmax(0,2fr)_auto]\"\n >\n <input\n value={appId}\n onChange={(event) => setAppId(event.target.value)}\n placeholder=\"app-id\"\n aria-label=\"App ID\"\n className=\"min-h-11 rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n <textarea\n value={settingsInput}\n onChange={(event) => setSettingsInput(event.target.value)}\n rows={3}\n aria-label=\"App settings JSON\"\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={installing}\n onClick={handleInstall}\n >\n Install\n </Button>\n </GlassCard>\n {formError && (\n <p className=\"text-sm text-status-error-text\">{formError}</p>\n )}\n </PageSection>\n {loading && (\n <p className=\"text-sm text-text-secondary\">\n Loading app installations…\n </p>\n )}\n {error && (\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n )}\n <div className=\"grid gap-3\">\n {installations.map((installation) => (\n <GlassCard\n key={installation.id}\n treatment=\"glass\"\n className=\"border-border-seam p-4\"\n >\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {installation.appId}\n </h3>\n <StatusPill>{installation.state}</StatusPill>\n </div>\n <p className=\"text-xs text-text-secondary\">\n Version {installation.appVersion} · Installed by{\" \"}\n {installation.installedBy} on{\" \"}\n {formatDate(installation.installedAt)}\n </p>\n </div>\n {/* Destructive uninstall lives behind the overflow menu and\n asks for confirmation before firing the mutation. */}\n <CTAOverflowMenu\n className=\"shrink-0\"\n actions={[\n {\n label: \"Uninstall\",\n intent: \"destructive\",\n disabled: uninstalling,\n icon: <Trash2 className=\"size-4\" aria-hidden=\"true\" />,\n onSelect: () => {\n if (\n !window.confirm(\n `Uninstall ${installation.appId}? This removes the app and its settings from this workspace.`,\n )\n ) {\n return;\n }\n void uninstallApp({\n variables: { id: installation.id },\n });\n },\n },\n ]}\n />\n </div>\n {/* The raw JSON settings editor is an advanced surface — collapse\n it behind progressive disclosure so the card stays calm. */}\n <ShowMore\n className=\"mt-3\"\n collapsedHeight={0}\n label=\"Edit settings (JSON)\"\n >\n <div className=\"grid gap-2 pt-2\">\n <textarea\n rows={5}\n aria-label={`Settings for ${installation.appId}`}\n value={settingsDrafts[installation.id] ?? \"{}\"}\n onChange={(event) =>\n setSettingsDrafts((current) => ({\n ...current,\n [installation.id]: event.target.value,\n }))\n }\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-2 font-mono text-xs text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={saving}\n className=\"w-fit\"\n onClick={() => {\n const settings = parsedSettings(\n settingsDrafts[installation.id] ?? \"{}\",\n );\n if (!settings) {\n setFormError(\n \"Installation settings must be a valid JSON object.\",\n );\n return;\n }\n setFormError(null);\n void updateSettings({\n variables: { id: installation.id, settings },\n });\n }}\n >\n Save settings\n </Button>\n </div>\n </ShowMore>\n </GlassCard>\n ))}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ConversationAdminPages.js","names":[],"sources":["../../../src/conversations/pages/ConversationAdminPages.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport { gql } from \"@apollo/client\";\nimport { useMutation, useQuery } from \"@apollo/client/react\";\nimport {\n Activity,\n Archive,\n CheckCircle2,\n Package,\n ShieldCheck,\n Trash2,\n} from \"lucide-react\";\n\nimport {\n Button,\n CTAOverflowMenu,\n GlassCard,\n PagePurpose,\n ShowMore,\n} from \"@burdenoff/fe-libs/ui\";\nimport { EmptyState, PageLayout, PageSection } from \"../components/PageLayout\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { useTr } from \"../utils/i18n\";\n\nconst GET_MESSAGE_REPORTS = gql`\n query ConversationAdminMessageReports(\n $first: Int\n $filter: MessageReportFilter\n ) {\n messageReports(first: $first, filter: $filter) {\n edges {\n node {\n id\n messageId\n reportedBy\n reason\n status\n notes\n createdAt\n reviewedAt\n message {\n id\n conversationId\n authorId\n content\n isHidden\n createdAt\n }\n }\n }\n }\n }\n`;\n\nconst RESOLVE_MESSAGE_REPORT = gql`\n mutation ConversationAdminResolveMessageReport(\n $input: ResolveMessageReportInput!\n ) {\n resolveMessageReport(input: $input) {\n id\n status\n reviewedAt\n notes\n message {\n id\n isHidden\n }\n }\n }\n`;\n\nconst GET_BULK_CONVERSATIONS = gql`\n query ConversationAdminBulkConversations(\n $first: Int\n $filter: ConversationFilter\n ) {\n conversations(first: $first, filter: $filter) {\n edges {\n node {\n id\n title\n type\n state\n updatedAt\n }\n }\n }\n }\n`;\n\nconst BULK_UPDATE_CONVERSATIONS = gql`\n mutation ConversationAdminBulkUpdate(\n $input: BulkConversationOperationInput!\n ) {\n bulkUpdateConversations(input: $input) {\n success\n processedCount\n successCount\n failureCount\n conversationIds\n errors {\n conversationId\n message\n }\n }\n }\n`;\n\nconst GET_CONVERSATION_AUDIT_LOG = gql`\n query ConversationAdminAuditLog($filter: ActivityLogFilter) {\n getActivities(filter: $filter) {\n id\n action\n description\n resourceId\n resourceType\n userId\n timestamp\n }\n }\n`;\n\nconst GET_APP_INSTALLATIONS = gql`\n query ConversationAdminAppInstallations($workspaceId: ID) {\n appInstallations(workspaceId: $workspaceId) {\n id\n appId\n appVersion\n state\n settings\n permissions\n installedBy\n installedAt\n updatedAt\n }\n }\n`;\n\nconst INSTALL_APP = gql`\n mutation ConversationAdminInstallApp($input: InstallAppInput!) {\n installApp(input: $input) {\n id\n appId\n state\n settings\n installedAt\n }\n }\n`;\n\nconst UPDATE_APP_SETTINGS = gql`\n mutation ConversationAdminUpdateAppSettings($id: ID!, $settings: JSON!) {\n updateAppSettings(id: $id, settings: $settings) {\n id\n settings\n updatedAt\n }\n }\n`;\n\nconst UNINSTALL_APP = gql`\n mutation ConversationAdminUninstallApp($id: ID!) {\n uninstallApp(id: $id) {\n appInstallationId\n success\n }\n }\n`;\n\ninterface MessageReportNode {\n id: string;\n messageId: string;\n reportedBy: string;\n reason: string;\n status: string;\n notes?: string | null;\n createdAt: string;\n reviewedAt?: string | null;\n message: {\n id: string;\n conversationId: string;\n authorId: string;\n content: unknown;\n isHidden: boolean;\n createdAt: string;\n };\n}\n\ninterface ConversationNode {\n id: string;\n title?: string | null;\n type: string;\n state: string;\n updatedAt: string;\n}\n\ninterface ActivityLogNode {\n id: string;\n action: string;\n description?: string | null;\n resourceId?: string | null;\n resourceType?: string | null;\n userId: string;\n timestamp: string;\n}\n\ninterface AppInstallationNode {\n id: string;\n appId: string;\n appVersion: string;\n state: string;\n settings: unknown;\n permissions: string[];\n installedBy: string;\n installedAt: string;\n updatedAt: string;\n}\n\ninterface BulkUpdateConversationsResult {\n bulkUpdateConversations: {\n success: boolean;\n processedCount: number;\n successCount: number;\n failureCount: number;\n conversationIds: string[];\n errors: Array<{ conversationId: string; message: string }>;\n };\n}\n\nfunction formatContent(content: unknown, fallback: string): string {\n if (typeof content === \"string\") return content;\n if (content && typeof content === \"object\" && \"text\" in content) {\n const text = (content as { text?: unknown }).text;\n if (typeof text === \"string\") return text;\n }\n try {\n return JSON.stringify(content);\n } catch {\n return fallback;\n }\n}\n\nconst DATE_FORMATTER = new Intl.DateTimeFormat(undefined, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n});\n\nfunction formatDate(value: string): string {\n return DATE_FORMATTER.format(new Date(value));\n}\n\nfunction StatusPill({ children }: { children: string }) {\n return (\n <span className=\"rounded-full border border-border-subtle bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {children}\n </span>\n );\n}\n\nexport function ModerationQueuePage() {\n const tr = useTr();\n const { workspaceId } = useConversations();\n const { data, loading, error, refetch } = useQuery<{\n messageReports: { edges: Array<{ node: MessageReportNode }> };\n }>(GET_MESSAGE_REPORTS, {\n variables: { first: 50, filter: { statuses: [\"PENDING\", \"REVIEWING\"] } },\n skip: !workspaceId,\n });\n const [resolveReport, { loading: resolving }] = useMutation(\n RESOLVE_MESSAGE_REPORT,\n {\n onCompleted: () => void refetch(),\n },\n );\n\n const reports = data?.messageReports.edges.map((edge) => edge.node) ?? [];\n\n return (\n <PageLayout\n title={tr(\"conversations.admin.moderationPageTitle\", \"Moderation queue\")}\n subtitle={tr(\n \"conversations.admin.moderationPageDescription\",\n \"Review reported messages and apply moderator decisions.\",\n )}\n icon={<ShieldCheck className=\"size-5\" aria-hidden=\"true\" />}\n maxWidth=\"2xl\"\n >\n <PagePurpose icon={<ShieldCheck className=\"size-4\" aria-hidden=\"true\" />}>\n {tr(\n \"conversations.admin.moderationPurpose\",\n \"Keep conversations safe: people can report messages they find abusive, spammy, or off-topic, and they land here for a moderator to hide the message or dismiss the report. Work the queue down to zero to stay on top of issues.\",\n )}\n </PagePurpose>\n {!workspaceId && (\n <EmptyState\n title={tr(\n \"conversations.admin.selectWorkspace\",\n \"Select a workspace\",\n )}\n description={tr(\n \"conversations.admin.selectWorkspaceModerationDescription\",\n \"Choose a workspace to review reported conversation messages.\",\n )}\n icon={<ShieldCheck className=\"size-6\" aria-hidden=\"true\" />}\n illustration=\"empty-generic\"\n />\n )}\n {workspaceId && loading && (\n <p className=\"text-sm text-text-secondary\">\n {tr(\"conversations.admin.loadingReports\", \"Loading reports…\")}\n </p>\n )}\n {workspaceId && error && (\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n )}\n {workspaceId && !loading && !error && reports.length === 0 && (\n <EmptyState\n title={tr(\n \"conversations.admin.noReportsTitle\",\n \"No reports waiting for review\",\n )}\n description={tr(\n \"conversations.admin.noReportsDescription\",\n \"Reported messages will appear here as they are submitted.\",\n )}\n icon={<CheckCircle2 className=\"size-6\" aria-hidden=\"true\" />}\n illustration=\"empty-notifications\"\n />\n )}\n <div className=\"grid gap-3\">\n {reports.map((report) => (\n <GlassCard\n key={report.id}\n treatment=\"glass\"\n className=\"border-border-seam p-4\"\n >\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"space-y-2 min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusPill>{report.status}</StatusPill>\n {report.message.isHidden && (\n <StatusPill>\n {tr(\"conversations.admin.hidden\", \"Hidden\")}\n </StatusPill>\n )}\n <span className=\"text-xs text-text-secondary\">\n {tr(\"conversations.admin.reportedAt\", \"Reported {{date}}\", {\n date: formatDate(report.createdAt),\n })}\n </span>\n </div>\n <p className=\"text-sm font-medium text-text-primary\">\n {report.reason}\n </p>\n <p className=\"rounded-md border border-border-seam bg-bg-sunken p-3 text-sm text-text-secondary\">\n {formatContent(\n report.message.content,\n tr(\n \"conversations.admin.messageContentUnavailable\",\n \"Message content unavailable\",\n ),\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n \"conversations.admin.reportMeta\",\n \"Message {{messageId}} in conversation {{conversationId}} by {{authorId}}; reported by {{reportedBy}}\",\n {\n messageId: report.messageId,\n conversationId: report.message.conversationId,\n authorId: report.message.authorId,\n reportedBy: report.reportedBy,\n },\n )}\n </p>\n </div>\n {/* One primary CTA (hide + resolve), the secondary \"Dismiss\"\n collapses into the overflow menu. */}\n <CTAOverflowMenu\n className=\"shrink-0\"\n primary={{\n label: tr(\n \"conversations.admin.hideAndResolve\",\n \"Hide and resolve\",\n ),\n disabled: resolving,\n onSelect: () =>\n void resolveReport({\n variables: {\n input: { reportId: report.id, action: \"HIDE_MESSAGE\" },\n },\n }),\n }}\n actions={[\n {\n label: tr(\"conversations.admin.dismiss\", \"Dismiss\"),\n disabled: resolving,\n onSelect: () =>\n void resolveReport({\n variables: {\n input: { reportId: report.id, action: \"DISMISS\" },\n },\n }),\n },\n ]}\n />\n </div>\n </GlassCard>\n ))}\n </div>\n </PageLayout>\n );\n}\n\nexport function BulkOperationsPage() {\n const tr = useTr();\n const { workspaceId } = useConversations();\n const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set());\n const [action, setAction] = useState<\"ARCHIVE\" | \"UNARCHIVE\" | \"DELETE\">(\n \"ARCHIVE\",\n );\n const { data, loading, error, refetch } = useQuery<{\n conversations: { edges: Array<{ node: ConversationNode }> };\n }>(GET_BULK_CONVERSATIONS, {\n variables: { first: 100 },\n skip: !workspaceId,\n });\n const [bulkUpdate, { data: bulkResult, loading: updating }] = useMutation<\n BulkUpdateConversationsResult,\n { input: { conversationIds: string[]; action: typeof action } }\n >(BULK_UPDATE_CONVERSATIONS, {\n onCompleted: () => {\n setSelectedIds(new Set());\n void refetch();\n },\n });\n const conversations =\n data?.conversations.edges.map((edge) => edge.node) ?? [];\n\n const toggle = (id: string) => {\n setSelectedIds((current) => {\n const next = new Set(current);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n };\n\n return (\n <PageLayout\n title={tr(\n \"conversations.admin.bulkOperationsPageTitle\",\n \"Bulk operations\",\n )}\n subtitle={tr(\n \"conversations.admin.bulkOperationsPageDescription\",\n \"Select workspace conversations and apply archive, unarchive, or delete operations.\",\n )}\n icon={<Archive className=\"size-5\" aria-hidden=\"true\" />}\n maxWidth=\"2xl\"\n headerActions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <select\n value={action}\n onChange={(event) => setAction(event.target.value as typeof action)}\n aria-label={tr(\"conversations.admin.bulkAction\", \"Bulk action\")}\n className=\"min-h-11 rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"ARCHIVE\">\n {tr(\"conversations.archive\", \"Archive\")}\n </option>\n <option value=\"UNARCHIVE\">\n {tr(\"conversations.unarchive\", \"Unarchive\")}\n </option>\n <option value=\"DELETE\">\n {tr(\"conversations.delete\", \"Delete\")}\n </option>\n </select>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={selectedIds.size === 0 || updating}\n onClick={() =>\n void bulkUpdate({\n variables: {\n input: { conversationIds: Array.from(selectedIds), action },\n },\n })\n }\n >\n {tr(\"conversations.admin.applyToCount\", \"Apply to {{count}}\", {\n count: selectedIds.size,\n })}\n </Button>\n </div>\n }\n >\n <PagePurpose icon={<Archive className=\"size-4\" aria-hidden=\"true\" />}>\n {tr(\n \"conversations.admin.bulkPurpose\",\n \"Clean up at scale instead of one conversation at a time — select many conversations, then archive them to declutter inboxes, unarchive to restore, or delete the ones you no longer need. Handy for closing out finished projects or removing test threads.\",\n )}\n </PagePurpose>\n {!workspaceId && (\n <EmptyState\n title={tr(\n \"conversations.admin.selectWorkspace\",\n \"Select a workspace\",\n )}\n description={tr(\n \"conversations.admin.selectWorkspaceBulkDescription\",\n \"Choose a workspace to manage conversations in bulk.\",\n )}\n icon={<Archive className=\"size-6\" aria-hidden=\"true\" />}\n illustration=\"empty-generic\"\n />\n )}\n {workspaceId && loading && (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"conversations.admin.loadingConversations\",\n \"Loading conversations…\",\n )}\n </p>\n )}\n {workspaceId && error && (\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n )}\n {bulkResult?.bulkUpdateConversations && (\n <GlassCard\n treatment=\"metric\"\n glow\n className=\"grid grid-cols-2 gap-4 border-border-seam p-4 sm:max-w-md\"\n >\n <div>\n <p className=\"text-2xl font-semibold tabular-nums text-status-success-text\">\n {bulkResult.bulkUpdateConversations.successCount}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\"conversations.admin.succeeded\", \"Succeeded\")}\n </p>\n </div>\n <div>\n <p className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {bulkResult.bulkUpdateConversations.failureCount}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\"conversations.admin.failed\", \"Failed\")}\n </p>\n </div>\n </GlassCard>\n )}\n {workspaceId && (\n <div className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface\">\n {conversations.map((conversation) => (\n <label\n key={conversation.id}\n className=\"flex min-h-11 cursor-pointer items-center gap-3 border-b border-border-seam px-4 py-3 last:border-b-0 hover:bg-accent\"\n >\n <input\n type=\"checkbox\"\n aria-label={tr(\n \"conversations.admin.selectConversationAria\",\n \"Select {{title}}\",\n {\n title:\n conversation.title ||\n tr(\n \"conversations.admin.untitledConversation\",\n \"Untitled conversation\",\n ),\n },\n )}\n checked={selectedIds.has(conversation.id)}\n onChange={() => toggle(conversation.id)}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {conversation.title ||\n tr(\n \"conversations.admin.untitledConversation\",\n \"Untitled conversation\",\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n \"conversations.admin.conversationMeta\",\n \"{{type}} · {{state}} · Updated {{date}}\",\n {\n type: conversation.type,\n state: conversation.state,\n date: formatDate(conversation.updatedAt),\n },\n )}\n </p>\n </div>\n </label>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n\nexport function ConversationAuditLogPage() {\n const tr = useTr();\n const { workspaceId } = useConversations();\n const { data, loading, error } = useQuery<{\n getActivities: ActivityLogNode[];\n }>(GET_CONVERSATION_AUDIT_LOG, {\n variables: {\n filter: { workspaceId, resourceType: \"conversation\", limit: 50 },\n },\n skip: !workspaceId,\n });\n const logs = data?.getActivities ?? [];\n\n return (\n <PageLayout\n title={tr(\"conversations.admin.auditLogPageTitle\", \"Audit log\")}\n subtitle={tr(\n \"conversations.admin.auditLogPageDescription\",\n \"Activity stream for admin and moderator actions on workspace conversations.\",\n )}\n icon={<Activity className=\"size-5\" aria-hidden=\"true\" />}\n maxWidth=\"2xl\"\n >\n <PagePurpose icon={<Activity className=\"size-4\" aria-hidden=\"true\" />}>\n {tr(\n \"conversations.admin.auditPurpose\",\n \"An accountability trail of who changed what — every admin and moderator action on workspace conversations is recorded here. Use it to investigate an incident, confirm a change was made, or satisfy a compliance review.\",\n )}\n </PagePurpose>\n {loading && (\n <p className=\"text-sm text-text-secondary\">\n {tr(\"conversations.admin.loadingActivity\", \"Loading activity…\")}\n </p>\n )}\n {error && (\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n )}\n {!loading && !error && logs.length === 0 && (\n <EmptyState\n title={tr(\"conversations.admin.noActivityTitle\", \"No activity found\")}\n description={tr(\n \"conversations.admin.noActivityDescription\",\n \"Conversation admin actions will appear here.\",\n )}\n illustration=\"empty-data\"\n />\n )}\n <div className=\"grid gap-3\">\n {logs.map((log) => (\n <GlassCard\n key={log.id}\n treatment=\"plain\"\n className=\"border-border-seam p-4\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {log.action}\n </p>\n <span className=\"text-xs tabular-nums text-text-secondary\">\n {formatDate(log.timestamp)}\n </span>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {log.description ||\n tr(\n \"conversations.admin.noDescription\",\n \"No description provided\",\n )}\n </p>\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr(\n \"conversations.admin.auditMeta\",\n \"User {{userId}} · Resource {{resourceId}}\",\n {\n userId: log.userId,\n resourceId:\n log.resourceId ??\n tr(\"conversations.admin.workspaceResource\", \"workspace\"),\n },\n )}\n </p>\n </GlassCard>\n ))}\n </div>\n </PageLayout>\n );\n}\n\nexport function AppInstallationsPage() {\n const tr = useTr();\n const { workspaceId } = useConversations();\n const [appId, setAppId] = useState(\"\");\n const [settingsInput, setSettingsInput] = useState(\"{}\");\n const [settingsDrafts, setSettingsDrafts] = useState<Record<string, string>>(\n {},\n );\n const [formError, setFormError] = useState<string | null>(null);\n const { data, loading, error, refetch } = useQuery<{\n appInstallations: AppInstallationNode[];\n }>(GET_APP_INSTALLATIONS, {\n variables: { workspaceId },\n skip: !workspaceId,\n });\n const [installApp, { loading: installing }] = useMutation(INSTALL_APP, {\n onCompleted: () => void refetch(),\n });\n const [updateSettings, { loading: saving }] = useMutation(\n UPDATE_APP_SETTINGS,\n { onCompleted: () => void refetch() },\n );\n const [uninstallApp, { loading: uninstalling }] = useMutation(UNINSTALL_APP, {\n onCompleted: () => void refetch(),\n });\n const installations = useMemo(\n () => data?.appInstallations ?? [],\n [data?.appInstallations],\n );\n\n useEffect(() => {\n setSettingsDrafts((current) => {\n if (installations.every((installation) => current[installation.id])) {\n return current;\n }\n const next = { ...current };\n for (const installation of installations) {\n if (!next[installation.id]) {\n next[installation.id] = JSON.stringify(\n installation.settings ?? {},\n null,\n 2,\n );\n }\n }\n return next;\n });\n }, [installations]);\n\n const parsedSettings = (raw: string): Record<string, unknown> | null => {\n try {\n const parsed = JSON.parse(raw) as unknown;\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n } catch {\n return null;\n }\n };\n\n const handleInstall = () => {\n const settings = parsedSettings(settingsInput);\n if (!appId.trim() || !settings) {\n setFormError(\n tr(\n \"conversations.admin.installAppError\",\n \"Enter an app ID and a valid JSON settings object.\",\n ),\n );\n return;\n }\n setFormError(null);\n void installApp({\n variables: { input: { appId: appId.trim(), workspaceId, settings } },\n });\n };\n\n return (\n <PageLayout\n title={tr(\n \"conversations.admin.appInstallationsPageTitle\",\n \"App installations\",\n )}\n subtitle={tr(\n \"conversations.admin.appInstallationsPageDescription\",\n \"Install, configure, and remove conversation apps and integrations.\",\n )}\n icon={<Package className=\"size-5\" aria-hidden=\"true\" />}\n maxWidth=\"2xl\"\n >\n <PagePurpose icon={<Package className=\"size-4\" aria-hidden=\"true\" />}>\n {tr(\n \"conversations.admin.appsPurpose\",\n \"Extend conversations with apps and integrations — install bots, automations, or third-party connectors scoped to this workspace, adjust their settings, and remove the ones you no longer use.\",\n )}\n </PagePurpose>\n <PageSection\n title={tr(\"conversations.admin.installApp\", \"Install app\")}\n description={tr(\n \"conversations.admin.installAppDescription\",\n \"Install a workspace-scoped conversation app by app ID.\",\n )}\n >\n {/* The single emphasis zone on this page — installing a new app is the\n primary action an admin takes here. */}\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"grid gap-3 border-border-seam p-4 sm:grid-cols-[minmax(0,1fr)_minmax(0,2fr)_auto]\"\n >\n <input\n value={appId}\n onChange={(event) => setAppId(event.target.value)}\n placeholder={tr(\"conversations.admin.appIdPlaceholder\", \"app-id\")}\n aria-label={tr(\"conversations.admin.appId\", \"App ID\")}\n className=\"min-h-11 rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n <textarea\n value={settingsInput}\n onChange={(event) => setSettingsInput(event.target.value)}\n rows={3}\n aria-label={tr(\n \"conversations.admin.appSettingsJson\",\n \"App settings JSON\",\n )}\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={installing}\n onClick={handleInstall}\n >\n {tr(\"conversations.admin.install\", \"Install\")}\n </Button>\n </GlassCard>\n {formError && (\n <p className=\"text-sm text-status-error-text\">{formError}</p>\n )}\n </PageSection>\n {loading && (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"conversations.admin.loadingAppInstallations\",\n \"Loading app installations…\",\n )}\n </p>\n )}\n {error && (\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n )}\n <div className=\"grid gap-3\">\n {installations.map((installation) => (\n <GlassCard\n key={installation.id}\n treatment=\"glass\"\n className=\"border-border-seam p-4\"\n >\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {installation.appId}\n </h3>\n <StatusPill>{installation.state}</StatusPill>\n </div>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n \"conversations.admin.appVersionMeta\",\n \"Version {{version}} · Installed by {{installedBy}} on {{installedAt}}\",\n {\n version: installation.appVersion,\n installedBy: installation.installedBy,\n installedAt: formatDate(installation.installedAt),\n },\n )}\n </p>\n </div>\n {/* Destructive uninstall lives behind the overflow menu and\n asks for confirmation before firing the mutation. */}\n <CTAOverflowMenu\n className=\"shrink-0\"\n actions={[\n {\n label: tr(\"conversations.admin.uninstall\", \"Uninstall\"),\n intent: \"destructive\",\n disabled: uninstalling,\n icon: <Trash2 className=\"size-4\" aria-hidden=\"true\" />,\n onSelect: () => {\n if (\n !window.confirm(\n tr(\n \"conversations.admin.uninstallConfirm\",\n \"Uninstall {{appId}}? This removes the app and its settings from this workspace.\",\n { appId: installation.appId },\n ),\n )\n ) {\n return;\n }\n void uninstallApp({\n variables: { id: installation.id },\n });\n },\n },\n ]}\n />\n </div>\n {/* The raw JSON settings editor is an advanced surface — collapse\n it behind progressive disclosure so the card stays calm. */}\n <ShowMore\n className=\"mt-3\"\n collapsedHeight={0}\n label={tr(\n \"conversations.admin.editSettingsJson\",\n \"Edit settings (JSON)\",\n )}\n >\n <div className=\"grid gap-2 pt-2\">\n <textarea\n rows={5}\n aria-label={tr(\n \"conversations.admin.settingsForApp\",\n \"Settings for {{appId}}\",\n { appId: installation.appId },\n )}\n value={settingsDrafts[installation.id] ?? \"{}\"}\n onChange={(event) =>\n setSettingsDrafts((current) => ({\n ...current,\n [installation.id]: event.target.value,\n }))\n }\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-2 font-mono text-xs text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={saving}\n className=\"w-fit\"\n onClick={() => {\n const settings = parsedSettings(\n settingsDrafts[installation.id] ?? \"{}\",\n );\n if (!settings) {\n setFormError(\n tr(\n \"conversations.admin.settingsInvalidJson\",\n \"Installation settings must be a valid JSON object.\",\n ),\n );\n return;\n }\n setFormError(null);\n void updateSettings({\n variables: { id: installation.id, settings },\n });\n }}\n >\n {tr(\"conversations.admin.saveSettings\", \"Save settings\")}\n </Button>\n </div>\n </ShowMore>\n </GlassCard>\n ))}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}