@burdenoff/microfe-theculturepost 2026.704.2 → 2026.705.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.
Files changed (91) hide show
  1. package/dist/theculturepost/TheCulturePostRoot.js +63 -45
  2. package/dist/theculturepost/TheCulturePostRoot.js.map +1 -1
  3. package/dist/theculturepost/TheCulturePostRoutes.js +17 -9
  4. package/dist/theculturepost/TheCulturePostRoutes.js.map +1 -1
  5. package/dist/theculturepost/components/AddCitationDialog.js +131 -102
  6. package/dist/theculturepost/components/AddCitationDialog.js.map +1 -1
  7. package/dist/theculturepost/components/ArchiveItemCard.js +56 -43
  8. package/dist/theculturepost/components/ArchiveItemCard.js.map +1 -1
  9. package/dist/theculturepost/components/AudioPlayer.js +74 -0
  10. package/dist/theculturepost/components/AudioPlayer.js.map +1 -0
  11. package/dist/theculturepost/components/CitationList.js +215 -105
  12. package/dist/theculturepost/components/CitationList.js.map +1 -1
  13. package/dist/theculturepost/components/CollectionCard.js +32 -32
  14. package/dist/theculturepost/components/CollectionCard.js.map +1 -1
  15. package/dist/theculturepost/components/CreateArchiveItemDialog.js +133 -93
  16. package/dist/theculturepost/components/CreateArchiveItemDialog.js.map +1 -1
  17. package/dist/theculturepost/components/CreateCollectionDialog.js +91 -70
  18. package/dist/theculturepost/components/CreateCollectionDialog.js.map +1 -1
  19. package/dist/theculturepost/components/CreateStoryDialog.js +311 -132
  20. package/dist/theculturepost/components/CreateStoryDialog.js.map +1 -1
  21. package/dist/theculturepost/components/DecorativeCover.js +78 -0
  22. package/dist/theculturepost/components/DecorativeCover.js.map +1 -0
  23. package/dist/theculturepost/components/DecorativeDivider.js +45 -0
  24. package/dist/theculturepost/components/DecorativeDivider.js.map +1 -0
  25. package/dist/theculturepost/components/DiscussionCard.js +75 -60
  26. package/dist/theculturepost/components/DiscussionCard.js.map +1 -1
  27. package/dist/theculturepost/components/HistoryTimeline.js +115 -0
  28. package/dist/theculturepost/components/HistoryTimeline.js.map +1 -0
  29. package/dist/theculturepost/components/LifecycleStepper.js +33 -0
  30. package/dist/theculturepost/components/LifecycleStepper.js.map +1 -0
  31. package/dist/theculturepost/components/OpenDiscussionDialog.js +66 -56
  32. package/dist/theculturepost/components/OpenDiscussionDialog.js.map +1 -1
  33. package/dist/theculturepost/components/OralHistoryCard.js +6 -73
  34. package/dist/theculturepost/components/OverlayBadge.js +22 -0
  35. package/dist/theculturepost/components/OverlayBadge.js.map +1 -0
  36. package/dist/theculturepost/components/PageHero.js +62 -0
  37. package/dist/theculturepost/components/PageHero.js.map +1 -0
  38. package/dist/theculturepost/components/RecordOralHistoryDialog.js +306 -131
  39. package/dist/theculturepost/components/RecordOralHistoryDialog.js.map +1 -1
  40. package/dist/theculturepost/components/ReplyThread.js +268 -139
  41. package/dist/theculturepost/components/ReplyThread.js.map +1 -1
  42. package/dist/theculturepost/components/StatusBadge.js +9 -9
  43. package/dist/theculturepost/components/StatusBadge.js.map +1 -1
  44. package/dist/theculturepost/components/StoryCard.js +54 -49
  45. package/dist/theculturepost/components/StoryCard.js.map +1 -1
  46. package/dist/theculturepost/components/TopicCard.js +60 -41
  47. package/dist/theculturepost/components/TopicCard.js.map +1 -1
  48. package/dist/theculturepost/components/Waveform.js +31 -0
  49. package/dist/theculturepost/components/Waveform.js.map +1 -0
  50. package/dist/theculturepost/components/categoryMeta.js +20 -0
  51. package/dist/theculturepost/components/categoryMeta.js.map +1 -0
  52. package/dist/theculturepost/components/index.js +41 -0
  53. package/dist/theculturepost/components/viz/BarChart.js +53 -0
  54. package/dist/theculturepost/components/viz/BarChart.js.map +1 -0
  55. package/dist/theculturepost/components/viz/Donut.js +58 -0
  56. package/dist/theculturepost/components/viz/Donut.js.map +1 -0
  57. package/dist/theculturepost/components/viz/StatTile.js +35 -0
  58. package/dist/theculturepost/components/viz/StatTile.js.map +1 -0
  59. package/dist/theculturepost/components/viz/index.js +3 -0
  60. package/dist/theculturepost/hooks/useFiles.js +1 -1
  61. package/dist/theculturepost/hooks/useFiles.js.map +1 -1
  62. package/dist/theculturepost/pages/HomePage.js +247 -124
  63. package/dist/theculturepost/pages/HomePage.js.map +1 -1
  64. package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js +319 -127
  65. package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js.map +1 -1
  66. package/dist/theculturepost/pages/archive/ArchivePage.js +265 -180
  67. package/dist/theculturepost/pages/archive/ArchivePage.js.map +1 -1
  68. package/dist/theculturepost/pages/archive/CollectionDetailPage.js +158 -143
  69. package/dist/theculturepost/pages/archive/CollectionDetailPage.js.map +1 -1
  70. package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js +155 -93
  71. package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js.map +1 -1
  72. package/dist/theculturepost/pages/discussions/DiscussionsPage.js +16 -16
  73. package/dist/theculturepost/pages/discussions/DiscussionsPage.js.map +1 -1
  74. package/dist/theculturepost/pages/governance/GovernancePage.js +293 -0
  75. package/dist/theculturepost/pages/governance/GovernancePage.js.map +1 -0
  76. package/dist/theculturepost/pages/stories/StoriesPage.js +18 -18
  77. package/dist/theculturepost/pages/stories/StoriesPage.js.map +1 -1
  78. package/dist/theculturepost/pages/stories/StoryDetailPage.js +603 -285
  79. package/dist/theculturepost/pages/stories/StoryDetailPage.js.map +1 -1
  80. package/dist/theculturepost/pages/timeline/TimelinePage.js +162 -0
  81. package/dist/theculturepost/pages/timeline/TimelinePage.js.map +1 -0
  82. package/dist/theculturepost/pages/topics/TopicDetailPage.js +239 -163
  83. package/dist/theculturepost/pages/topics/TopicDetailPage.js.map +1 -1
  84. package/dist/theculturepost/pages/topics/TopicsPage.js +76 -74
  85. package/dist/theculturepost/pages/topics/TopicsPage.js.map +1 -1
  86. package/dist/theculturepost/styles/injectDisplayFont.js +11 -0
  87. package/dist/theculturepost/styles/injectDisplayFont.js.map +1 -0
  88. package/dist/theculturepost/utils/formatters.js +6 -1
  89. package/dist/theculturepost/utils/formatters.js.map +1 -1
  90. package/package.json +1 -1
  91. package/dist/theculturepost/components/OralHistoryCard.js.map +0 -1
@@ -1,156 +1,171 @@
1
1
  import { useTheCulturePost as e } from "../../context/TheCulturePostContext.js";
2
- import { PageLayout as t } from "../../components/PageLayout.js";
3
- import { ErrorView as n, LoadingView as r, Section as i } from "../../components/StateViews.js";
4
2
  import "../../context/index.js";
5
- import { CoverImage as a } from "../../components/CoverImage.js";
6
- import { formatNumber as o } from "../../utils/formatters.js";
7
- import { EmptyState as s } from "../../components/EmptyState.js";
8
- import { useTcpCollection as c } from "../../hooks/useTcpApi.js";
3
+ import { PageHero as t } from "../../components/PageHero.js";
4
+ import { ErrorView as n, LoadingView as r } from "../../components/StateViews.js";
5
+ import { formatNumber as i } from "../../utils/formatters.js";
6
+ import { EmptyState as a } from "../../components/EmptyState.js";
7
+ import { useTcpCollection as o } from "../../hooks/useTcpApi.js";
9
8
  import "../../hooks/index.js";
10
- import { ArchiveItemCard as l } from "../../components/ArchiveItemCard.js";
11
- import { OralHistoryCard as u } from "../../components/OralHistoryCard.js";
12
- import { CreateArchiveItemDialog as d } from "../../components/CreateArchiveItemDialog.js";
13
- import { RecordOralHistoryDialog as f } from "../../components/RecordOralHistoryDialog.js";
14
- import { useState as p } from "react";
15
- import { ArrowLeft as m, AudioLines as h, Boxes as g, Library as _, Plus as v } from "lucide-react";
16
- import { useParams as y } from "react-router-dom";
17
- import { jsx as b, jsxs as x } from "react/jsx-runtime";
18
- import { Button as S, GlassCard as C } from "@burdenoff/fe-libs/ui";
9
+ import { DecorativeDivider as s } from "../../components/DecorativeDivider.js";
10
+ import { ArchiveItemCard as c } from "../../components/ArchiveItemCard.js";
11
+ import { RecordOralHistoryDialog as l } from "../../components/RecordOralHistoryDialog.js";
12
+ import { CreateArchiveItemDialog as u } from "../../components/CreateArchiveItemDialog.js";
13
+ import { OralHistoryPlayerRow as d } from "./ArchivePage.js";
14
+ import { useState as f } from "react";
15
+ import { AudioLines as p, Boxes as m, Mic as h, Plus as g } from "lucide-react";
16
+ import { useParams as _ } from "react-router-dom";
17
+ import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
18
+ import { Button as x, GlassCard as S } from "@burdenoff/fe-libs/ui";
19
19
  //#region src/theculturepost/pages/archive/CollectionDetailPage.tsx
20
- function w() {
21
- let { id: w } = y(), { navigateTo: T } = e(), { data: E, isLoading: D, isError: O, refetch: k } = c(w), [A, j] = p(!1), [M, N] = p(!1), P = /* @__PURE__ */ x(S, {
22
- variant: "ghost",
23
- size: "sm",
24
- onClick: () => T("archive"),
25
- children: [/* @__PURE__ */ b(m, { className: "mr-1.5 h-4 w-4" }), " Back"]
26
- });
27
- if (D) return /* @__PURE__ */ b(t, {
28
- title: "Collection",
29
- actions: P,
30
- children: /* @__PURE__ */ b(r, { label: "Loading collection…" })
31
- });
32
- if (O || !E) return /* @__PURE__ */ b(t, {
33
- title: "Collection",
34
- actions: P,
35
- children: /* @__PURE__ */ b(n, {
36
- title: "Collection not found",
37
- message: "This collection may have been removed, or you may not have access.",
38
- onRetry: () => k()
20
+ function C({ children: e }) {
21
+ return /* @__PURE__ */ y("div", {
22
+ className: "flex h-full flex-col",
23
+ children: /* @__PURE__ */ y("div", {
24
+ className: "flex-1 overflow-auto",
25
+ children: /* @__PURE__ */ y("div", {
26
+ className: "mx-auto flex w-full max-w-6xl flex-col gap-6 p-4 sm:p-6",
27
+ children: e
28
+ })
39
29
  })
40
30
  });
31
+ }
32
+ function w() {
33
+ let { id: w } = _(), { navigateTo: T } = e(), { data: E, isLoading: D, isError: O, refetch: k } = o(w), [A, j] = f(!1), [M, N] = f(!1), P = () => T("archive");
34
+ if (D) return /* @__PURE__ */ y(C, { children: /* @__PURE__ */ y(r, { label: "Loading collection…" }) });
35
+ if (O || !E) return /* @__PURE__ */ y(C, { children: /* @__PURE__ */ y(n, {
36
+ title: "Collection not found",
37
+ message: "This collection may have been removed, or you may not have access.",
38
+ onRetry: () => k()
39
+ }) });
41
40
  let F = E.items ?? [], I = E.oralHistories ?? [];
42
- return /* @__PURE__ */ x(t, {
43
- title: E.name,
44
- actions: P,
45
- children: [
46
- /* @__PURE__ */ x("div", {
47
- className: "mx-auto flex max-w-6xl flex-col gap-6",
48
- children: [
49
- /* @__PURE__ */ x(C, {
50
- treatment: "plain",
51
- className: "flex flex-col gap-4 overflow-hidden p-0 sm:flex-row",
52
- children: [/* @__PURE__ */ b("div", {
53
- className: "sm:w-64 sm:shrink-0",
54
- children: /* @__PURE__ */ b(a, {
55
- fileId: E.cover?.id,
56
- alt: E.name,
57
- rounded: "rounded-none",
58
- className: "aspect-[3/2] w-full sm:h-full",
59
- icon: /* @__PURE__ */ b(_, { className: "h-10 w-10" })
60
- })
61
- }), /* @__PURE__ */ x("div", {
62
- className: "flex flex-1 flex-col gap-2 p-5 sm:p-6",
63
- children: [
64
- E.tradition && /* @__PURE__ */ b("span", {
65
- className: "text-sm font-medium text-text-secondary",
66
- children: E.tradition
67
- }),
68
- E.description && /* @__PURE__ */ b("p", {
69
- className: "whitespace-pre-wrap text-sm leading-relaxed text-text-secondary",
70
- children: E.description
71
- }),
72
- /* @__PURE__ */ x("div", {
73
- className: "mt-auto flex flex-wrap items-center gap-4 pt-2 text-sm text-text-tertiary",
74
- children: [/* @__PURE__ */ x("span", {
75
- className: "inline-flex items-center gap-1.5",
76
- children: [
77
- /* @__PURE__ */ b(g, { className: "h-4 w-4" }),
78
- " ",
79
- o(E.itemCount),
80
- " artifacts"
81
- ]
82
- }), /* @__PURE__ */ x("span", {
83
- className: "inline-flex items-center gap-1.5",
84
- children: [
85
- /* @__PURE__ */ b(h, { className: "h-4 w-4" }),
86
- " ",
87
- o(I.length),
88
- " oral histories"
89
- ]
90
- })]
91
- })
92
- ]
93
- })]
94
- }),
95
- /* @__PURE__ */ b(i, {
96
- title: "Artifacts",
97
- action: /* @__PURE__ */ x(S, {
98
- size: "sm",
99
- variant: "outline",
100
- onClick: () => j(!0),
101
- children: [/* @__PURE__ */ b(v, { className: "mr-1.5 h-4 w-4" }), " Add artifact"]
41
+ return /* @__PURE__ */ b(C, { children: [
42
+ /* @__PURE__ */ y(t, {
43
+ eyebrow: "Collection",
44
+ title: E.name,
45
+ description: E.description ?? void 0,
46
+ coverFileId: E.cover?.id,
47
+ backAction: {
48
+ label: "Archive",
49
+ onClick: P
50
+ },
51
+ meta: /* @__PURE__ */ b(v, { children: [
52
+ E.tradition && /* @__PURE__ */ y("span", {
53
+ className: "inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary",
54
+ children: E.tradition
55
+ }),
56
+ /* @__PURE__ */ b("span", {
57
+ className: "inline-flex items-center gap-1.5 text-sm text-text-tertiary",
58
+ children: [
59
+ /* @__PURE__ */ y(m, {
60
+ className: "h-4 w-4",
61
+ "aria-hidden": "true"
102
62
  }),
103
- children: F.length === 0 ? /* @__PURE__ */ b(s, {
104
- icon: /* @__PURE__ */ b(g, { className: "h-9 w-9" }),
105
- title: "No artifacts in this collection",
106
- description: "Add the first artifact to this collection.",
107
- action: /* @__PURE__ */ x(S, {
108
- onClick: () => j(!0),
109
- children: [/* @__PURE__ */ b(v, { className: "mr-1.5 h-4 w-4" }), " Add artifact"]
110
- })
111
- }) : /* @__PURE__ */ b("div", {
112
- className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
113
- children: F.map((e) => /* @__PURE__ */ b(l, {
114
- item: e,
115
- onOpen: () => T(`archive/items/${e.id}`)
116
- }, e.id))
117
- })
118
- }),
119
- /* @__PURE__ */ b(i, {
120
- title: "Oral histories",
121
- action: /* @__PURE__ */ x(S, {
122
- size: "sm",
123
- variant: "outline",
124
- onClick: () => N(!0),
125
- children: [/* @__PURE__ */ b(v, { className: "mr-1.5 h-4 w-4" }), " Record"]
63
+ " ",
64
+ i(E.itemCount),
65
+ " ",
66
+ E.itemCount === 1 ? "item" : "items"
67
+ ]
68
+ }),
69
+ /* @__PURE__ */ b("span", {
70
+ className: "inline-flex items-center gap-1.5 text-sm text-text-tertiary",
71
+ children: [
72
+ /* @__PURE__ */ y(p, {
73
+ className: "h-4 w-4",
74
+ "aria-hidden": "true"
126
75
  }),
127
- children: I.length === 0 ? /* @__PURE__ */ b(s, {
128
- icon: /* @__PURE__ */ b(h, { className: "h-9 w-9" }),
129
- title: "No oral histories yet",
130
- description: "Record a first-person account for this collection.",
131
- action: /* @__PURE__ */ x(S, {
132
- onClick: () => N(!0),
133
- children: [/* @__PURE__ */ b(v, { className: "mr-1.5 h-4 w-4" }), " Record"]
134
- })
135
- }) : /* @__PURE__ */ b("div", {
136
- className: "grid gap-4 lg:grid-cols-2",
137
- children: I.map((e) => /* @__PURE__ */ b(u, { oralHistory: e }, e.id))
138
- })
76
+ " ",
77
+ i(I.length),
78
+ " ",
79
+ I.length === 1 ? "oral history" : "oral histories"
80
+ ]
81
+ })
82
+ ] }),
83
+ actions: /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ b(x, {
84
+ onClick: () => j(!0),
85
+ children: [/* @__PURE__ */ y(g, { className: "mr-1.5 h-4 w-4" }), " Add artifact"]
86
+ }), /* @__PURE__ */ b(x, {
87
+ variant: "outline",
88
+ onClick: () => N(!0),
89
+ children: [/* @__PURE__ */ y(h, { className: "mr-1.5 h-4 w-4" }), " Record"]
90
+ })] })
91
+ }),
92
+ /* @__PURE__ */ y(s, { className: "py-2" }),
93
+ /* @__PURE__ */ b("div", {
94
+ className: "grid gap-6 lg:grid-cols-2",
95
+ children: [/* @__PURE__ */ b(S, {
96
+ treatment: "plain",
97
+ className: "flex flex-col gap-4 p-5",
98
+ children: [/* @__PURE__ */ b("div", {
99
+ className: "flex items-center justify-between gap-3",
100
+ children: [/* @__PURE__ */ b("h2", {
101
+ className: "flex items-center gap-2 font-display text-lg font-semibold text-text-primary",
102
+ children: [/* @__PURE__ */ y(m, {
103
+ className: "h-5 w-5 text-accent",
104
+ "aria-hidden": "true"
105
+ }), " Items"]
106
+ }), /* @__PURE__ */ b(x, {
107
+ size: "sm",
108
+ variant: "outline",
109
+ onClick: () => j(!0),
110
+ children: [/* @__PURE__ */ y(g, { className: "mr-1.5 h-4 w-4" }), " Add"]
111
+ })]
112
+ }), F.length === 0 ? /* @__PURE__ */ y(a, {
113
+ icon: /* @__PURE__ */ y(m, { className: "h-9 w-9" }),
114
+ title: "No artifacts in this collection",
115
+ description: "Add the first artifact to this collection.",
116
+ action: /* @__PURE__ */ b(x, {
117
+ onClick: () => j(!0),
118
+ children: [/* @__PURE__ */ y(g, { className: "mr-1.5 h-4 w-4" }), " Add artifact"]
139
119
  })
140
- ]
141
- }),
142
- /* @__PURE__ */ b(d, {
143
- open: A,
144
- onOpenChange: j,
145
- collectionId: E.id
146
- }),
147
- /* @__PURE__ */ b(f, {
148
- open: M,
149
- onOpenChange: N,
150
- collectionId: E.id
151
- })
152
- ]
153
- });
120
+ }) : /* @__PURE__ */ y("div", {
121
+ className: "grid gap-4 sm:grid-cols-2",
122
+ children: F.map((e) => /* @__PURE__ */ y(c, {
123
+ item: e,
124
+ onOpen: () => T(`archive/items/${e.id}`)
125
+ }, e.id))
126
+ })]
127
+ }), /* @__PURE__ */ b(S, {
128
+ treatment: "plain",
129
+ className: "flex flex-col gap-4 p-5",
130
+ children: [/* @__PURE__ */ b("div", {
131
+ className: "flex items-center justify-between gap-3",
132
+ children: [/* @__PURE__ */ b("h2", {
133
+ className: "flex items-center gap-2 font-display text-lg font-semibold text-text-primary",
134
+ children: [/* @__PURE__ */ y(p, {
135
+ className: "h-5 w-5 text-accent",
136
+ "aria-hidden": "true"
137
+ }), " Oral histories"]
138
+ }), /* @__PURE__ */ b(x, {
139
+ size: "sm",
140
+ variant: "outline",
141
+ onClick: () => N(!0),
142
+ children: [/* @__PURE__ */ y(h, { className: "mr-1.5 h-4 w-4" }), " Record"]
143
+ })]
144
+ }), I.length === 0 ? /* @__PURE__ */ y(a, {
145
+ icon: /* @__PURE__ */ y(p, { className: "h-9 w-9" }),
146
+ title: "No oral histories yet",
147
+ description: "Record a first-person account for this collection.",
148
+ action: /* @__PURE__ */ b(x, {
149
+ onClick: () => N(!0),
150
+ children: [/* @__PURE__ */ y(h, { className: "mr-1.5 h-4 w-4" }), " Record"]
151
+ })
152
+ }) : /* @__PURE__ */ y("div", {
153
+ className: "flex flex-col gap-4",
154
+ children: I.map((e) => /* @__PURE__ */ y(d, { oralHistory: e }, e.id))
155
+ })]
156
+ })]
157
+ }),
158
+ /* @__PURE__ */ y(u, {
159
+ open: A,
160
+ onOpenChange: j,
161
+ collectionId: E.id
162
+ }),
163
+ /* @__PURE__ */ y(l, {
164
+ open: M,
165
+ onOpenChange: N,
166
+ collectionId: E.id
167
+ })
168
+ ] });
154
169
  }
155
170
  //#endregion
156
171
  export { w as CollectionDetailPage };
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionDetailPage.js","names":[],"sources":["../../../../src/theculturepost/pages/archive/CollectionDetailPage.tsx"],"sourcesContent":["/**\n * CollectionDetailPage — a single archive collection.\n * @module theculturepost/pages/archive\n *\n * Reads `tcpCollection` (with its items + oral histories). Adding an artifact or\n * recording an oral history is pre-scoped to this collection.\n */\n\nimport { useState } from 'react';\nimport { ArrowLeft, Library, Boxes, AudioLines, Plus } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { CoverImage } from '../../components/CoverImage';\nimport { ArchiveItemCard } from '../../components/ArchiveItemCard';\nimport { OralHistoryCard } from '../../components/OralHistoryCard';\nimport { CreateArchiveItemDialog } from '../../components/CreateArchiveItemDialog';\nimport { RecordOralHistoryDialog } from '../../components/RecordOralHistoryDialog';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { useTcpCollection } from '../../hooks';\nimport { useTheCulturePost } from '../../context';\nimport { formatNumber } from '../../utils/formatters';\nimport { useParams } from 'react-router-dom';\n\nexport function CollectionDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useTheCulturePost();\n const { data: collection, isLoading, isError, refetch } = useTcpCollection(id);\n const [addItemOpen, setAddItemOpen] = useState(false);\n const [recordOpen, setRecordOpen] = useState(false);\n\n const backButton = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('archive')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Collection\" actions={backButton}>\n <LoadingView label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (isError || !collection) {\n return (\n <PageLayout title=\"Collection\" actions={backButton}>\n <ErrorView\n title=\"Collection not found\"\n message=\"This collection may have been removed, or you may not have access.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n const items = collection.items ?? [];\n const oralHistories = collection.oralHistories ?? [];\n\n return (\n <PageLayout title={collection.name} actions={backButton}>\n <div className=\"mx-auto flex max-w-6xl flex-col gap-6\">\n <GlassCard\n treatment=\"plain\"\n className=\"flex flex-col gap-4 overflow-hidden p-0 sm:flex-row\"\n >\n <div className=\"sm:w-64 sm:shrink-0\">\n <CoverImage\n fileId={collection.cover?.id}\n alt={collection.name}\n rounded=\"rounded-none\"\n className=\"aspect-[3/2] w-full sm:h-full\"\n icon={<Library className=\"h-10 w-10\" />}\n />\n </div>\n <div className=\"flex flex-1 flex-col gap-2 p-5 sm:p-6\">\n {collection.tradition && (\n <span className=\"text-sm font-medium text-text-secondary\">\n {collection.tradition}\n </span>\n )}\n {collection.description && (\n <p className=\"whitespace-pre-wrap text-sm leading-relaxed text-text-secondary\">\n {collection.description}\n </p>\n )}\n <div className=\"mt-auto flex flex-wrap items-center gap-4 pt-2 text-sm text-text-tertiary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <Boxes className=\"h-4 w-4\" /> {formatNumber(collection.itemCount)} artifacts\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <AudioLines className=\"h-4 w-4\" /> {formatNumber(oralHistories.length)} oral\n histories\n </span>\n </div>\n </div>\n </GlassCard>\n\n <Section\n title=\"Artifacts\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddItemOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add artifact\n </Button>\n }\n >\n {items.length === 0 ? (\n <EmptyState\n icon={<Boxes className=\"h-9 w-9\" />}\n title=\"No artifacts in this collection\"\n description=\"Add the first artifact to this collection.\"\n action={\n <Button onClick={() => setAddItemOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add artifact\n </Button>\n }\n />\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {items.map((item) => (\n <ArchiveItemCard\n key={item.id}\n item={item}\n onOpen={() => navigateTo(`archive/items/${item.id}`)}\n />\n ))}\n </div>\n )}\n </Section>\n\n <Section\n title=\"Oral histories\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setRecordOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record\n </Button>\n }\n >\n {oralHistories.length === 0 ? (\n <EmptyState\n icon={<AudioLines className=\"h-9 w-9\" />}\n title=\"No oral histories yet\"\n description=\"Record a first-person account for this collection.\"\n action={\n <Button onClick={() => setRecordOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record\n </Button>\n }\n />\n ) : (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n {oralHistories.map((oralHistory) => (\n <OralHistoryCard key={oralHistory.id} oralHistory={oralHistory} />\n ))}\n </div>\n )}\n </Section>\n </div>\n\n <CreateArchiveItemDialog\n open={addItemOpen}\n onOpenChange={setAddItemOpen}\n collectionId={collection.id}\n />\n <RecordOralHistoryDialog\n open={recordOpen}\n onOpenChange={setRecordOpen}\n collectionId={collection.id}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CollectionDetailPage.js","names":[],"sources":["../../../../src/theculturepost/pages/archive/CollectionDetailPage.tsx"],"sourcesContent":["/**\n * CollectionDetailPage — a single archive collection.\n * @module theculturepost/pages/archive\n *\n * Reads `tcpCollection` (with its items + oral histories). An editorial\n * {@link PageHero} shows the real cover (collections DO carry a cover file), with\n * real counts (itemCount, oralHistories.length). Two panels below list the\n * artifacts and the listenable oral histories. Adding an artifact or recording an\n * oral history is pre-scoped to this collection.\n */\n\nimport { useState } from 'react';\nimport { Boxes, AudioLines, Plus, Mic } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { useParams } from 'react-router-dom';\nimport { PageHero } from '../../components/PageHero';\nimport { DecorativeDivider } from '../../components/DecorativeDivider';\nimport { ArchiveItemCard } from '../../components/ArchiveItemCard';\nimport { CreateArchiveItemDialog } from '../../components/CreateArchiveItemDialog';\nimport { RecordOralHistoryDialog } from '../../components/RecordOralHistoryDialog';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { OralHistoryPlayerRow } from './ArchivePage';\nimport { useTcpCollection } from '../../hooks';\nimport { useTheCulturePost } from '../../context';\nimport { formatNumber } from '../../utils/formatters';\n\nfunction Shell({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"flex h-full flex-col\">\n <div className=\"flex-1 overflow-auto\">\n <div className=\"mx-auto flex w-full max-w-6xl flex-col gap-6 p-4 sm:p-6\">{children}</div>\n </div>\n </div>\n );\n}\n\nexport function CollectionDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useTheCulturePost();\n const { data: collection, isLoading, isError, refetch } = useTcpCollection(id);\n const [addItemOpen, setAddItemOpen] = useState(false);\n const [recordOpen, setRecordOpen] = useState(false);\n\n const goBack = () => navigateTo('archive');\n\n if (isLoading) {\n return (\n <Shell>\n <LoadingView label=\"Loading collection…\" />\n </Shell>\n );\n }\n\n if (isError || !collection) {\n return (\n <Shell>\n <ErrorView\n title=\"Collection not found\"\n message=\"This collection may have been removed, or you may not have access.\"\n onRetry={() => refetch()}\n />\n </Shell>\n );\n }\n\n const items = collection.items ?? [];\n const oralHistories = collection.oralHistories ?? [];\n\n return (\n <Shell>\n <PageHero\n eyebrow=\"Collection\"\n title={collection.name}\n description={collection.description ?? undefined}\n coverFileId={collection.cover?.id}\n backAction={{ label: 'Archive', onClick: goBack }}\n meta={\n <>\n {collection.tradition && (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {collection.tradition}\n </span>\n )}\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-tertiary\">\n <Boxes className=\"h-4 w-4\" aria-hidden=\"true\" /> {formatNumber(collection.itemCount)}{' '}\n {collection.itemCount === 1 ? 'item' : 'items'}\n </span>\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-tertiary\">\n <AudioLines className=\"h-4 w-4\" aria-hidden=\"true\" />{' '}\n {formatNumber(oralHistories.length)}{' '}\n {oralHistories.length === 1 ? 'oral history' : 'oral histories'}\n </span>\n </>\n }\n actions={\n <>\n <Button onClick={() => setAddItemOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add artifact\n </Button>\n <Button variant=\"outline\" onClick={() => setRecordOpen(true)}>\n <Mic className=\"mr-1.5 h-4 w-4\" /> Record\n </Button>\n </>\n }\n />\n\n <DecorativeDivider className=\"py-2\" />\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Items */}\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"flex items-center gap-2 font-display text-lg font-semibold text-text-primary\">\n <Boxes className=\"h-5 w-5 text-accent\" aria-hidden=\"true\" /> Items\n </h2>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddItemOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add\n </Button>\n </div>\n {items.length === 0 ? (\n <EmptyState\n icon={<Boxes className=\"h-9 w-9\" />}\n title=\"No artifacts in this collection\"\n description=\"Add the first artifact to this collection.\"\n action={\n <Button onClick={() => setAddItemOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add artifact\n </Button>\n }\n />\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {items.map((item) => (\n <ArchiveItemCard\n key={item.id}\n item={item}\n onOpen={() => navigateTo(`archive/items/${item.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Oral histories */}\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"flex items-center gap-2 font-display text-lg font-semibold text-text-primary\">\n <AudioLines className=\"h-5 w-5 text-accent\" aria-hidden=\"true\" /> Oral histories\n </h2>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setRecordOpen(true)}>\n <Mic className=\"mr-1.5 h-4 w-4\" /> Record\n </Button>\n </div>\n {oralHistories.length === 0 ? (\n <EmptyState\n icon={<AudioLines className=\"h-9 w-9\" />}\n title=\"No oral histories yet\"\n description=\"Record a first-person account for this collection.\"\n action={\n <Button onClick={() => setRecordOpen(true)}>\n <Mic className=\"mr-1.5 h-4 w-4\" /> Record\n </Button>\n }\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n {oralHistories.map((oralHistory) => (\n <OralHistoryPlayerRow key={oralHistory.id} oralHistory={oralHistory} />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n\n <CreateArchiveItemDialog\n open={addItemOpen}\n onOpenChange={setAddItemOpen}\n collectionId={collection.id}\n />\n <RecordOralHistoryDialog\n open={recordOpen}\n onOpenChange={setRecordOpen}\n collectionId={collection.id}\n />\n </Shell>\n );\n}\n"],"mappings":"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"}