@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,204 +1,379 @@
1
- import { FILE_FOLDERS as e } from "../hooks/useFiles.js";
2
- import { formatEnumLabel as t } from "../utils/formatters.js";
3
- import { useTcpCollections as n, useTcpRecordOralHistory as r } from "../hooks/useTcpApi.js";
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { FILE_FOLDERS as t, uploadImageToFiles as n } from "../hooks/useFiles.js";
3
+ import { formatEnumLabel as r } from "../utils/formatters.js";
4
+ import { useTcpCollections as i, useTcpRecordOralHistory as a } from "../hooks/useTcpApi.js";
4
5
  import "../hooks/index.js";
5
- import { describeError as i, notify as a } from "../lib/toast.js";
6
- import { FileUploadField as o } from "./FileUploadField.js";
7
- import { useState as s } from "react";
8
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
9
- import { Button as u, Input as d, Label as f, ResponsiveDialog as p, ResponsiveDialogContent as ee, ResponsiveDialogDescription as m, ResponsiveDialogFooter as h, ResponsiveDialogHeader as g, ResponsiveDialogTitle as _, Select as v, SelectContent as y, SelectItem as b, SelectTrigger as x, SelectValue as S, Textarea as C } from "@burdenoff/fe-libs/ui";
6
+ import { describeError as o, notify as s } from "../lib/toast.js";
7
+ import { AudioPlayer as c } from "./AudioPlayer.js";
8
+ import { useRef as l, useState as u } from "react";
9
+ import { FileCheck2 as d, Loader2 as f, UploadCloud as p, X as m } from "lucide-react";
10
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
11
+ import { Button as _, Input as v, Label as y, ResponsiveDialog as b, ResponsiveDialogContent as x, ResponsiveDialogDescription as S, ResponsiveDialogFooter as C, ResponsiveDialogHeader as w, ResponsiveDialogTitle as T, Select as E, SelectContent as D, SelectItem as O, SelectTrigger as k, SelectValue as A, Textarea as j } from "@burdenoff/fe-libs/ui";
10
12
  //#region src/theculturepost/components/RecordOralHistoryDialog.tsx
11
- var w = [
13
+ function M({ folder: t, accept: r, hint: i, promptLabel: a, value: _, onUploaded: v, onClear: y, maxMb: b = 25, disabled: x = !1 }) {
14
+ let S = l(null), [C, w] = u(!1), [T, E] = u(!1), [D, O] = u(null), [k, A] = u(_);
15
+ k !== _ && (A(_), _ || O(null));
16
+ let j = b * 1024 * 1024, M = async (e) => {
17
+ if (!(!e || x)) {
18
+ if (e.size > j) {
19
+ s.error("File too large", `Please choose a file under ${b} MB.`);
20
+ return;
21
+ }
22
+ E(!0);
23
+ try {
24
+ let r = await n(e, t), i = e.type.startsWith("image/"), a = e.type.startsWith("audio/") || e.type.startsWith("video/");
25
+ O(i ? "image" : a ? "audio" : "file"), v({
26
+ id: r.id,
27
+ url: r.url,
28
+ name: r.name
29
+ }, e), s.success("File uploaded", r.name);
30
+ } catch (e) {
31
+ s.error("Upload failed", o(e));
32
+ } finally {
33
+ E(!1);
34
+ }
35
+ }
36
+ }, N = /* @__PURE__ */ h("input", {
37
+ ref: S,
38
+ type: "file",
39
+ accept: r,
40
+ className: "sr-only",
41
+ disabled: x || T,
42
+ onChange: (e) => {
43
+ let t = e.target.files?.[0];
44
+ e.target.value = "", M(t);
45
+ }
46
+ });
47
+ return _ ? /* @__PURE__ */ g("div", {
48
+ className: "flex flex-col gap-2",
49
+ children: [
50
+ D === "image" ? /* @__PURE__ */ h("div", {
51
+ className: "overflow-hidden rounded-xl border border-border-seam",
52
+ children: /* @__PURE__ */ h("img", {
53
+ src: _.url,
54
+ alt: _.name,
55
+ className: "max-h-48 w-full object-cover"
56
+ })
57
+ }) : D === "audio" ? /* @__PURE__ */ h(c, {
58
+ src: _.url,
59
+ seed: _.id
60
+ }) : /* @__PURE__ */ g("div", {
61
+ className: "flex items-center gap-2 rounded-xl border border-border-seam bg-bg-surface px-3 py-2 text-sm",
62
+ children: [/* @__PURE__ */ h(d, { className: "h-4 w-4 shrink-0 text-status-success-text" }), /* @__PURE__ */ h("span", {
63
+ className: "truncate text-text-primary",
64
+ children: _.name
65
+ })]
66
+ }),
67
+ /* @__PURE__ */ g("div", {
68
+ className: "flex items-center gap-4",
69
+ children: [/* @__PURE__ */ h("button", {
70
+ type: "button",
71
+ onClick: () => S.current?.click(),
72
+ disabled: x || T,
73
+ className: "text-sm font-medium text-accent hover:underline disabled:opacity-60",
74
+ children: T ? "Uploading…" : "Replace"
75
+ }), /* @__PURE__ */ g("button", {
76
+ type: "button",
77
+ onClick: y,
78
+ className: "inline-flex items-center gap-1 text-sm text-text-tertiary hover:text-text-primary",
79
+ children: [/* @__PURE__ */ h(m, { className: "h-3.5 w-3.5" }), " Remove"]
80
+ })]
81
+ }),
82
+ N
83
+ ]
84
+ }) : /* @__PURE__ */ g("div", {
85
+ onDragOver: (e) => {
86
+ e.preventDefault(), x || w(!0);
87
+ },
88
+ onDragLeave: () => w(!1),
89
+ onDrop: (e) => {
90
+ e.preventDefault(), w(!1), M(e.dataTransfer.files?.[0]);
91
+ },
92
+ className: e("flex flex-col items-center justify-center gap-2 rounded-xl border border-dashed px-4 py-6 text-center transition-colors", C ? "border-accent bg-[var(--color-accent-soft)]" : "border-border-seam bg-bg-surface", (x || T) && "opacity-60"),
93
+ children: [
94
+ N,
95
+ T ? /* @__PURE__ */ h(f, {
96
+ className: "h-6 w-6 animate-spin text-text-tertiary",
97
+ "aria-hidden": "true"
98
+ }) : /* @__PURE__ */ h(p, {
99
+ className: "h-6 w-6 text-text-tertiary",
100
+ "aria-hidden": "true"
101
+ }),
102
+ /* @__PURE__ */ h("p", {
103
+ className: "text-sm text-text-secondary",
104
+ children: T ? "Uploading…" : a
105
+ }),
106
+ !T && /* @__PURE__ */ g("button", {
107
+ type: "button",
108
+ onClick: () => S.current?.click(),
109
+ disabled: x,
110
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-60",
111
+ children: [/* @__PURE__ */ h(p, { className: "h-4 w-4" }), " Browse files"]
112
+ }),
113
+ /* @__PURE__ */ h("p", {
114
+ className: "text-xs text-text-tertiary",
115
+ children: i
116
+ })
117
+ ]
118
+ });
119
+ }
120
+ var N = [
12
121
  "COMMUNITY",
13
122
  "PUBLIC",
14
123
  "RESTRICTED",
15
124
  "EMBARGOED"
16
- ], T = (e) => {
125
+ ], ee = {
126
+ PUBLIC: "Visible to everyone.",
127
+ COMMUNITY: "Available to community members.",
128
+ RESTRICTED: "Visible to selected groups.",
129
+ EMBARGOED: "Hidden until a future date."
130
+ }, te = 160, ne = 120, re = 60, ie = 300, P = 5e3, ae = (e) => {
17
131
  if (!e) return null;
18
132
  let t = new Date(e);
19
133
  return Number.isNaN(t.getTime()) ? null : t.toISOString();
20
134
  };
21
- function E({ open: E, onOpenChange: D, collectionId: O, onCreated: k }) {
22
- let A = r(), j = n({ limit: 100 }), [M, N] = s(""), [P, F] = s(""), [I, L] = s(""), [R, z] = s(""), [B, V] = s(""), [H, U] = s("COMMUNITY"), [W, G] = s(O ?? ""), [K, q] = s(null), [J, Y] = s(""), [X, Z] = s(""), [Q, $] = s(null), te = () => {
23
- N(""), F(""), L(""), z(""), V(""), U("COMMUNITY"), G(O ?? ""), q(null), Y(""), Z(""), $(null);
135
+ function F({ open: e, onOpenChange: n, collectionId: c, onCreated: l }) {
136
+ let d = a(), f = i({ limit: 100 }), [p, m] = u(""), [F, L] = u(""), [R, z] = u(""), [B, V] = u(""), [H, U] = u(""), [W, G] = u("COMMUNITY"), [K, q] = u(c ?? ""), [J, Y] = u(null), [X, Z] = u(""), [oe, se] = u(""), [Q, $] = u(null), ce = () => {
137
+ m(""), L(""), z(""), V(""), U(""), G("COMMUNITY"), q(c ?? ""), Y(null), Z(""), se(""), $(null);
138
+ }, le = (e) => {
139
+ try {
140
+ let t = new Audio();
141
+ t.preload = "metadata", t.onloadedmetadata = () => {
142
+ Number.isFinite(t.duration) && t.duration > 0 && Z((e) => e || String(Math.round(t.duration)));
143
+ }, t.src = e;
144
+ } catch {}
24
145
  };
25
- return /* @__PURE__ */ c(p, {
26
- open: E,
27
- onOpenChange: D,
28
- children: /* @__PURE__ */ l(ee, {
29
- className: "sm:max-w-lg",
146
+ return /* @__PURE__ */ h(b, {
147
+ open: e,
148
+ onOpenChange: n,
149
+ children: /* @__PURE__ */ g(x, {
150
+ className: "sm:max-w-2xl",
30
151
  children: [
31
- /* @__PURE__ */ l(g, { children: [/* @__PURE__ */ c(_, { children: "Record an oral history" }), /* @__PURE__ */ c(m, { children: "Preserve a first-person account. Attach the recording and a transcript for the archive." })] }),
32
- /* @__PURE__ */ l("div", {
33
- className: "flex max-h-[62vh] flex-col gap-4 overflow-y-auto py-2",
152
+ /* @__PURE__ */ g(w, { children: [/* @__PURE__ */ h(T, { children: "Record an oral history" }), /* @__PURE__ */ h(S, { children: "Preserve a first-person account. Attach the recording and a transcript for the archive." })] }),
153
+ /* @__PURE__ */ g("div", {
154
+ className: "flex max-h-[64vh] flex-col gap-4 overflow-y-auto py-2",
34
155
  children: [
35
- /* @__PURE__ */ l("div", {
156
+ /* @__PURE__ */ g("div", {
36
157
  className: "grid gap-1.5",
37
- children: [/* @__PURE__ */ c(f, { children: "Recording" }), /* @__PURE__ */ c(o, {
38
- folder: e.recordings,
158
+ children: [/* @__PURE__ */ h(y, { children: "Recording" }), /* @__PURE__ */ h(M, {
159
+ folder: t.recordings,
39
160
  accept: "audio/*,video/*",
40
- label: "Upload recording",
41
- hint: "Audio or video. Up to 25 MB.",
42
- fileId: K,
43
- onUploaded: q,
44
- onCleared: () => q(null)
161
+ promptLabel: "Drag and drop an audio or video file here",
162
+ hint: "Audio or video, up to 25 MB.",
163
+ value: J,
164
+ onUploaded: (e) => {
165
+ Y(e), le(e.url);
166
+ },
167
+ onClear: () => Y(null)
45
168
  })]
46
169
  }),
47
- /* @__PURE__ */ l("div", {
48
- className: "grid gap-1.5",
49
- children: [/* @__PURE__ */ c(f, {
50
- htmlFor: "oral-title",
51
- children: "Title"
52
- }), /* @__PURE__ */ c(d, {
53
- id: "oral-title",
54
- value: M,
55
- onChange: (e) => N(e.target.value),
56
- placeholder: "e.g. A weaver remembers the looms"
57
- })]
58
- }),
59
- /* @__PURE__ */ l("div", {
60
- className: "grid grid-cols-2 gap-3",
61
- children: [/* @__PURE__ */ l("div", {
170
+ /* @__PURE__ */ g("div", {
171
+ className: "grid gap-3 sm:grid-cols-2",
172
+ children: [/* @__PURE__ */ g("div", {
173
+ className: "grid gap-1.5",
174
+ children: [
175
+ /* @__PURE__ */ g(y, {
176
+ htmlFor: "oral-title",
177
+ children: ["Title ", /* @__PURE__ */ h("span", {
178
+ className: "text-status-error-text",
179
+ children: "*"
180
+ })]
181
+ }),
182
+ /* @__PURE__ */ h(v, {
183
+ id: "oral-title",
184
+ value: p,
185
+ maxLength: te,
186
+ onChange: (e) => m(e.target.value),
187
+ placeholder: "e.g. A weaver remembers the looms"
188
+ }),
189
+ /* @__PURE__ */ h(I, {
190
+ length: p.length,
191
+ max: te
192
+ })
193
+ ]
194
+ }), /* @__PURE__ */ g("div", {
62
195
  className: "grid gap-1.5",
63
- children: [/* @__PURE__ */ c(f, {
196
+ children: [/* @__PURE__ */ h(y, {
64
197
  htmlFor: "oral-narrator",
65
198
  children: "Narrator"
66
- }), /* @__PURE__ */ c(d, {
199
+ }), /* @__PURE__ */ h(v, {
67
200
  id: "oral-narrator",
68
- value: R,
69
- onChange: (e) => z(e.target.value),
70
- placeholder: "Full name"
71
- })]
72
- }), /* @__PURE__ */ l("div", {
73
- className: "grid gap-1.5",
74
- children: [/* @__PURE__ */ c(f, {
75
- htmlFor: "oral-language",
76
- children: "Language"
77
- }), /* @__PURE__ */ c(d, {
78
- id: "oral-language",
79
201
  value: B,
202
+ maxLength: ne,
80
203
  onChange: (e) => V(e.target.value),
81
- placeholder: "e.g. Tamil"
204
+ placeholder: "Full name"
82
205
  })]
83
206
  })]
84
207
  }),
85
- /* @__PURE__ */ l("div", {
86
- className: "grid grid-cols-2 gap-3",
87
- children: [/* @__PURE__ */ l("div", {
208
+ /* @__PURE__ */ g("div", {
209
+ className: "grid gap-3 sm:grid-cols-2",
210
+ children: [/* @__PURE__ */ g("div", {
88
211
  className: "grid gap-1.5",
89
- children: [/* @__PURE__ */ c(f, { children: "Access level" }), /* @__PURE__ */ l(v, {
212
+ children: [/* @__PURE__ */ h(y, {
213
+ htmlFor: "oral-language",
214
+ children: "Language"
215
+ }), /* @__PURE__ */ h(v, {
216
+ id: "oral-language",
90
217
  value: H,
91
- onValueChange: (e) => U(e),
92
- children: [/* @__PURE__ */ c(x, { children: /* @__PURE__ */ c(S, {}) }), /* @__PURE__ */ c(y, { children: w.map((e) => /* @__PURE__ */ c(b, {
93
- value: e,
94
- children: t(e)
95
- }, e)) })]
218
+ maxLength: re,
219
+ onChange: (e) => U(e.target.value),
220
+ placeholder: "e.g. Tamil"
96
221
  })]
97
- }), /* @__PURE__ */ l("div", {
222
+ }), /* @__PURE__ */ g("div", {
98
223
  className: "grid gap-1.5",
99
- children: [/* @__PURE__ */ c(f, {
224
+ children: [/* @__PURE__ */ h(y, {
100
225
  htmlFor: "oral-duration",
101
226
  children: "Duration (seconds)"
102
- }), /* @__PURE__ */ c(d, {
227
+ }), /* @__PURE__ */ h(v, {
103
228
  id: "oral-duration",
104
229
  type: "number",
105
230
  min: 0,
106
- value: J,
107
- onChange: (e) => Y(e.target.value),
108
- placeholder: "Optional"
231
+ value: X,
232
+ onChange: (e) => Z(e.target.value),
233
+ placeholder: "Auto-filled from the recording"
109
234
  })]
110
235
  })]
111
236
  }),
112
- !O && /* @__PURE__ */ l("div", {
237
+ /* @__PURE__ */ g("div", {
238
+ className: "grid gap-3 sm:grid-cols-2",
239
+ children: [/* @__PURE__ */ g("div", {
240
+ className: "grid gap-1.5",
241
+ children: [/* @__PURE__ */ h(y, {
242
+ htmlFor: "oral-recorded",
243
+ children: "Recorded on"
244
+ }), /* @__PURE__ */ h(v, {
245
+ id: "oral-recorded",
246
+ type: "date",
247
+ value: oe,
248
+ onChange: (e) => se(e.target.value)
249
+ })]
250
+ }), /* @__PURE__ */ g("div", {
251
+ className: "grid gap-1.5",
252
+ children: [
253
+ /* @__PURE__ */ h(y, { children: "Access level" }),
254
+ /* @__PURE__ */ g(E, {
255
+ value: W,
256
+ onValueChange: (e) => G(e),
257
+ children: [/* @__PURE__ */ h(k, {
258
+ "aria-label": "Access level",
259
+ children: /* @__PURE__ */ h(A, {})
260
+ }), /* @__PURE__ */ h(D, { children: N.map((e) => /* @__PURE__ */ h(O, {
261
+ value: e,
262
+ children: r(e)
263
+ }, e)) })]
264
+ }),
265
+ /* @__PURE__ */ h("p", {
266
+ className: "text-xs text-text-tertiary",
267
+ children: ee[W]
268
+ })
269
+ ]
270
+ })]
271
+ }),
272
+ !c && /* @__PURE__ */ g("div", {
113
273
  className: "grid gap-1.5",
114
- children: [/* @__PURE__ */ c(f, { children: "Collection" }), /* @__PURE__ */ l(v, {
115
- value: W,
116
- onValueChange: G,
117
- children: [/* @__PURE__ */ c(x, { children: /* @__PURE__ */ c(S, { placeholder: "Add to a collection (optional)" }) }), /* @__PURE__ */ c(y, { children: (j.data ?? []).map((e) => /* @__PURE__ */ c(b, {
274
+ children: [/* @__PURE__ */ h(y, { children: "Collection" }), /* @__PURE__ */ g(E, {
275
+ value: K,
276
+ onValueChange: q,
277
+ children: [/* @__PURE__ */ h(k, { children: /* @__PURE__ */ h(A, { placeholder: "Add to a collection (optional)" }) }), /* @__PURE__ */ h(D, { children: (f.data ?? []).map((e) => /* @__PURE__ */ h(O, {
118
278
  value: e.id,
119
279
  children: e.name
120
280
  }, e.id)) })]
121
281
  })]
122
282
  }),
123
- /* @__PURE__ */ l("div", {
283
+ /* @__PURE__ */ g("div", {
124
284
  className: "grid gap-1.5",
125
- children: [/* @__PURE__ */ c(f, {
126
- htmlFor: "oral-recorded",
127
- children: "Recorded on"
128
- }), /* @__PURE__ */ c(d, {
129
- id: "oral-recorded",
130
- type: "date",
131
- value: X,
132
- onChange: (e) => Z(e.target.value)
133
- })]
285
+ children: [
286
+ /* @__PURE__ */ h(y, {
287
+ htmlFor: "oral-summary",
288
+ children: "Summary"
289
+ }),
290
+ /* @__PURE__ */ h(j, {
291
+ id: "oral-summary",
292
+ value: F,
293
+ maxLength: ie,
294
+ onChange: (e) => L(e.target.value),
295
+ placeholder: "A short description of what this account covers.",
296
+ rows: 3
297
+ }),
298
+ /* @__PURE__ */ h(I, {
299
+ length: F.length,
300
+ max: ie
301
+ })
302
+ ]
134
303
  }),
135
- /* @__PURE__ */ l("div", {
304
+ /* @__PURE__ */ g("div", {
136
305
  className: "grid gap-1.5",
137
- children: [/* @__PURE__ */ c(f, {
138
- htmlFor: "oral-summary",
139
- children: "Summary"
140
- }), /* @__PURE__ */ c(C, {
141
- id: "oral-summary",
142
- value: P,
143
- onChange: (e) => F(e.target.value),
144
- rows: 2
145
- })]
146
- }),
147
- /* @__PURE__ */ l("div", {
148
- className: "grid gap-1.5",
149
- children: [/* @__PURE__ */ c(f, {
150
- htmlFor: "oral-transcript",
151
- children: "Transcript"
152
- }), /* @__PURE__ */ c(C, {
153
- id: "oral-transcript",
154
- value: I,
155
- onChange: (e) => L(e.target.value),
156
- placeholder: "The transcribed account (line breaks are preserved).",
157
- rows: 4
158
- })]
306
+ children: [
307
+ /* @__PURE__ */ h(y, {
308
+ htmlFor: "oral-transcript",
309
+ children: "Transcript"
310
+ }),
311
+ /* @__PURE__ */ h(j, {
312
+ id: "oral-transcript",
313
+ value: R,
314
+ maxLength: P,
315
+ onChange: (e) => z(e.target.value),
316
+ placeholder: "The transcribed account (line breaks are preserved).",
317
+ rows: 5
318
+ }),
319
+ /* @__PURE__ */ h(I, {
320
+ length: R.length,
321
+ max: P
322
+ })
323
+ ]
159
324
  }),
160
- Q && /* @__PURE__ */ c("p", {
325
+ Q && /* @__PURE__ */ h("p", {
161
326
  className: "text-sm text-status-error-text",
162
327
  children: Q
163
328
  })
164
329
  ]
165
330
  }),
166
- /* @__PURE__ */ l(h, { children: [/* @__PURE__ */ c(u, {
331
+ /* @__PURE__ */ g(C, { children: [/* @__PURE__ */ h(_, {
167
332
  variant: "outline",
168
- onClick: () => D(!1),
333
+ onClick: () => n(!1),
169
334
  children: "Cancel"
170
- }), /* @__PURE__ */ c(u, {
335
+ }), /* @__PURE__ */ h(_, {
171
336
  onClick: async () => {
172
- if (!M.trim()) {
337
+ if (!p.trim()) {
173
338
  $("A title is required.");
174
339
  return;
175
340
  }
176
341
  try {
177
- let e = await A.mutateAsync({
178
- title: M.trim(),
179
- summary: P.trim() || null,
180
- transcript: I.trim() || null,
181
- narratorName: R.trim() || null,
182
- language: B.trim() || null,
183
- accessLevel: H,
184
- collectionId: W || null,
185
- recordingFileId: K,
186
- durationSecs: J ? Number(J) : null,
187
- recordedAt: T(X)
342
+ let e = await d.mutateAsync({
343
+ title: p.trim(),
344
+ summary: F.trim() || null,
345
+ transcript: R.trim() || null,
346
+ narratorName: B.trim() || null,
347
+ language: H.trim() || null,
348
+ accessLevel: W,
349
+ collectionId: K || null,
350
+ recordingFileId: J?.id ?? null,
351
+ durationSecs: X ? Number(X) : null,
352
+ recordedAt: ae(oe)
188
353
  });
189
- a.success("Oral history recorded", e.title), D(!1), te(), k?.(e);
354
+ s.success("Oral history recorded", e.title), n(!1), ce(), l?.(e);
190
355
  } catch (e) {
191
- $(i(e));
356
+ $(o(e));
192
357
  }
193
358
  },
194
- disabled: A.isPending,
359
+ disabled: d.isPending,
195
360
  children: "Record oral history"
196
361
  })] })
197
362
  ]
198
363
  })
199
364
  });
200
365
  }
366
+ function I({ length: e, max: t }) {
367
+ return /* @__PURE__ */ g("p", {
368
+ className: "text-right text-xs tabular-nums text-text-tertiary",
369
+ children: [
370
+ e,
371
+ " / ",
372
+ t
373
+ ]
374
+ });
375
+ }
201
376
  //#endregion
202
- export { E as RecordOralHistoryDialog };
377
+ export { I as Counter, M as MediaDropzone, F as RecordOralHistoryDialog };
203
378
 
204
379
  //# sourceMappingURL=RecordOralHistoryDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RecordOralHistoryDialog.js","names":[],"sources":["../../../src/theculturepost/components/RecordOralHistoryDialog.tsx"],"sourcesContent":["/**\n * RecordOralHistoryDialog — capture an oral history recording.\n * @module theculturepost/components\n *\n * Writes `tcpRecordOralHistory`. The recording (audio/video) is uploaded to\n * wspace-files-svc; `narratorName` is a plain string stored directly.\n */\n\nimport { useState } from 'react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { FileUploadField } from './FileUploadField';\nimport { useTcpRecordOralHistory, useTcpCollections, FILE_FOLDERS } from '../hooks';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpOralHistory, TcpAccessLevel } from '../types';\n\nconst ACCESS_LEVELS: TcpAccessLevel[] = ['COMMUNITY', 'PUBLIC', 'RESTRICTED', 'EMBARGOED'];\n\nconst dateToIso = (value: string): string | null => {\n if (!value) return null;\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d.toISOString();\n};\n\ninterface RecordOralHistoryDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Pre-select a collection. */\n collectionId?: string;\n onCreated?: (oralHistory: TcpOralHistory) => void;\n}\n\nexport function RecordOralHistoryDialog({\n open,\n onOpenChange,\n collectionId,\n onCreated,\n}: RecordOralHistoryDialogProps) {\n const record = useTcpRecordOralHistory();\n const collectionsQuery = useTcpCollections({ limit: 100 });\n\n const [title, setTitle] = useState('');\n const [summary, setSummary] = useState('');\n const [transcript, setTranscript] = useState('');\n const [narratorName, setNarratorName] = useState('');\n const [language, setLanguage] = useState('');\n const [accessLevel, setAccessLevel] = useState<TcpAccessLevel>('COMMUNITY');\n const [pickedCollection, setPickedCollection] = useState(collectionId ?? '');\n const [recordingFileId, setRecordingFileId] = useState<string | null>(null);\n const [durationSecs, setDurationSecs] = useState('');\n const [recordedAt, setRecordedAt] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setSummary('');\n setTranscript('');\n setNarratorName('');\n setLanguage('');\n setAccessLevel('COMMUNITY');\n setPickedCollection(collectionId ?? '');\n setRecordingFileId(null);\n setDurationSecs('');\n setRecordedAt('');\n setError(null);\n };\n\n const submit = async () => {\n if (!title.trim()) {\n setError('A title is required.');\n return;\n }\n try {\n const created = await record.mutateAsync({\n title: title.trim(),\n summary: summary.trim() || null,\n transcript: transcript.trim() || null,\n narratorName: narratorName.trim() || null,\n language: language.trim() || null,\n accessLevel,\n collectionId: pickedCollection || null,\n recordingFileId,\n durationSecs: durationSecs ? Number(durationSecs) : null,\n recordedAt: dateToIso(recordedAt),\n });\n notify.success('Oral history recorded', created.title);\n onOpenChange(false);\n reset();\n onCreated?.(created);\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record an oral history</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Preserve a first-person account. Attach the recording and a transcript for the archive.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"flex max-h-[62vh] flex-col gap-4 overflow-y-auto py-2\">\n <div className=\"grid gap-1.5\">\n <Label>Recording</Label>\n <FileUploadField\n folder={FILE_FOLDERS.recordings}\n accept=\"audio/*,video/*\"\n label=\"Upload recording\"\n hint=\"Audio or video. Up to 25 MB.\"\n fileId={recordingFileId}\n onUploaded={setRecordingFileId}\n onCleared={() => setRecordingFileId(null)}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-title\">Title</Label>\n <Input\n id=\"oral-title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. A weaver remembers the looms\"\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-narrator\">Narrator</Label>\n <Input\n id=\"oral-narrator\"\n value={narratorName}\n onChange={(e) => setNarratorName(e.target.value)}\n placeholder=\"Full name\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-language\">Language</Label>\n <Input\n id=\"oral-language\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n placeholder=\"e.g. Tamil\"\n />\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"grid gap-1.5\">\n <Label>Access level</Label>\n <Select\n value={accessLevel}\n onValueChange={(v) => setAccessLevel(v as TcpAccessLevel)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ACCESS_LEVELS.map((a) => (\n <SelectItem key={a} value={a}>\n {formatEnumLabel(a)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-duration\">Duration (seconds)</Label>\n <Input\n id=\"oral-duration\"\n type=\"number\"\n min={0}\n value={durationSecs}\n onChange={(e) => setDurationSecs(e.target.value)}\n placeholder=\"Optional\"\n />\n </div>\n </div>\n {!collectionId && (\n <div className=\"grid gap-1.5\">\n <Label>Collection</Label>\n <Select value={pickedCollection} onValueChange={setPickedCollection}>\n <SelectTrigger>\n <SelectValue placeholder=\"Add to a collection (optional)\" />\n </SelectTrigger>\n <SelectContent>\n {(collectionsQuery.data ?? []).map((collection) => (\n <SelectItem key={collection.id} value={collection.id}>\n {collection.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-recorded\">Recorded on</Label>\n <Input\n id=\"oral-recorded\"\n type=\"date\"\n value={recordedAt}\n onChange={(e) => setRecordedAt(e.target.value)}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-summary\">Summary</Label>\n <Textarea\n id=\"oral-summary\"\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n rows={2}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-transcript\">Transcript</Label>\n <Textarea\n id=\"oral-transcript\"\n value={transcript}\n onChange={(e) => setTranscript(e.target.value)}\n placeholder=\"The transcribed account (line breaks are preserved).\"\n rows={4}\n />\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={record.isPending}>\n Record oral history\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RecordOralHistoryDialog.js","names":[],"sources":["../../../src/theculturepost/components/RecordOralHistoryDialog.tsx"],"sourcesContent":["/**\n * RecordOralHistoryDialog — capture an oral history recording.\n * @module theculturepost/components\n *\n * Writes `tcpRecordOralHistory`. The recording (audio/video) is uploaded to\n * wspace-files-svc via the shared {@link MediaDropzone} (drag-drop or browse),\n * and previewed inline with the foundation {@link AudioPlayer}. `narratorName`\n * is a plain string stored directly — the schema has NO narrator portrait/age,\n * so none is ever rendered. All `TcpCreateOralHistoryInput` fields are captured.\n */\n\nimport { useRef, useState } from 'react';\nimport { UploadCloud, Loader2, FileCheck2, X } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { AudioPlayer } from './AudioPlayer';\nimport { cn } from '../utils/cn';\nimport {\n uploadImageToFiles,\n useTcpRecordOralHistory,\n useTcpCollections,\n FILE_FOLDERS,\n} from '../hooks';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpOralHistory, TcpAccessLevel } from '../types';\n\n// ---------------------------------------------------------------------------\n// MediaDropzone — a shared drag-drop uploader (reused by the archive dialogs).\n// ---------------------------------------------------------------------------\n\n/** A file uploaded to wspace-files-svc: id (persisted) + presigned url + name. */\nexport interface UploadedRef {\n id: string;\n url: string;\n name: string;\n}\n\ninterface MediaDropzoneProps {\n /** Destination folder in wspace-files-svc (see FILE_FOLDERS). */\n folder: string;\n /** HTML accept attribute (e.g. 'audio/*,video/*'). */\n accept: string;\n /** Muted helper line under the drop prompt. */\n hint: string;\n /** Large prompt shown in the empty dropzone. */\n promptLabel: string;\n /** Current uploaded file (controlled so the parent can reset on submit). */\n value: UploadedRef | null;\n /** Called after a successful upload; `file` is provided for metadata probes. */\n onUploaded: (ref: UploadedRef, file: File) => void;\n onClear: () => void;\n /** Max upload size in MB. */\n maxMb?: number;\n disabled?: boolean;\n}\n\n/**\n * A token-only drag-and-drop file uploader with an inline preview: images show\n * a thumbnail, audio/video show the {@link AudioPlayer}, everything else shows a\n * filename chip. Uploads go straight to wspace-files-svc — only the id is kept.\n */\nexport function MediaDropzone({\n folder,\n accept,\n hint,\n promptLabel,\n value,\n onUploaded,\n onClear,\n maxMb = 25,\n disabled = false,\n}: MediaDropzoneProps) {\n const inputRef = useRef<HTMLInputElement | null>(null);\n const [dragging, setDragging] = useState(false);\n const [uploading, setUploading] = useState(false);\n const [previewType, setPreviewType] = useState<'image' | 'audio' | 'file' | null>(null);\n\n // Keep the preview kind in sync when the parent clears the value (e.g. on reset).\n const [trackedValue, setTrackedValue] = useState(value);\n if (trackedValue !== value) {\n setTrackedValue(value);\n if (!value) setPreviewType(null);\n }\n\n const maxBytes = maxMb * 1024 * 1024;\n\n const ingest = async (file: File | undefined) => {\n if (!file || disabled) return;\n if (file.size > maxBytes) {\n notify.error('File too large', `Please choose a file under ${maxMb} MB.`);\n return;\n }\n setUploading(true);\n try {\n const uploaded = await uploadImageToFiles(file, folder);\n const isImage = file.type.startsWith('image/');\n const isAv = file.type.startsWith('audio/') || file.type.startsWith('video/');\n setPreviewType(isImage ? 'image' : isAv ? 'audio' : 'file');\n onUploaded({ id: uploaded.id, url: uploaded.url, name: uploaded.name }, file);\n notify.success('File uploaded', uploaded.name);\n } catch (err) {\n notify.error('Upload failed', describeError(err));\n } finally {\n setUploading(false);\n }\n };\n\n const hiddenInput = (\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n className=\"sr-only\"\n disabled={disabled || uploading}\n onChange={(e) => {\n const file = e.target.files?.[0];\n e.target.value = '';\n void ingest(file);\n }}\n />\n );\n\n if (value) {\n return (\n <div className=\"flex flex-col gap-2\">\n {previewType === 'image' ? (\n <div className=\"overflow-hidden rounded-xl border border-border-seam\">\n <img src={value.url} alt={value.name} className=\"max-h-48 w-full object-cover\" />\n </div>\n ) : previewType === 'audio' ? (\n <AudioPlayer src={value.url} seed={value.id} />\n ) : (\n <div className=\"flex items-center gap-2 rounded-xl border border-border-seam bg-bg-surface px-3 py-2 text-sm\">\n <FileCheck2 className=\"h-4 w-4 shrink-0 text-status-success-text\" />\n <span className=\"truncate text-text-primary\">{value.name}</span>\n </div>\n )}\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => inputRef.current?.click()}\n disabled={disabled || uploading}\n className=\"text-sm font-medium text-accent hover:underline disabled:opacity-60\"\n >\n {uploading ? 'Uploading…' : 'Replace'}\n </button>\n <button\n type=\"button\"\n onClick={onClear}\n className=\"inline-flex items-center gap-1 text-sm text-text-tertiary hover:text-text-primary\"\n >\n <X className=\"h-3.5 w-3.5\" /> Remove\n </button>\n </div>\n {hiddenInput}\n </div>\n );\n }\n\n return (\n <div\n onDragOver={(e) => {\n e.preventDefault();\n if (!disabled) setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragging(false);\n void ingest(e.dataTransfer.files?.[0]);\n }}\n className={cn(\n 'flex flex-col items-center justify-center gap-2 rounded-xl border border-dashed px-4 py-6 text-center transition-colors',\n dragging\n ? 'border-accent bg-[var(--color-accent-soft)]'\n : 'border-border-seam bg-bg-surface',\n (disabled || uploading) && 'opacity-60'\n )}\n >\n {hiddenInput}\n {uploading ? (\n <Loader2 className=\"h-6 w-6 animate-spin text-text-tertiary\" aria-hidden=\"true\" />\n ) : (\n <UploadCloud className=\"h-6 w-6 text-text-tertiary\" aria-hidden=\"true\" />\n )}\n <p className=\"text-sm text-text-secondary\">{uploading ? 'Uploading…' : promptLabel}</p>\n {!uploading && (\n <button\n type=\"button\"\n onClick={() => inputRef.current?.click()}\n disabled={disabled}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-60\"\n >\n <UploadCloud className=\"h-4 w-4\" /> Browse files\n </button>\n )}\n <p className=\"text-xs text-text-tertiary\">{hint}</p>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Access levels + copy\n// ---------------------------------------------------------------------------\n\nconst ACCESS_LEVELS: TcpAccessLevel[] = ['COMMUNITY', 'PUBLIC', 'RESTRICTED', 'EMBARGOED'];\n\nconst ACCESS_HELP: Record<TcpAccessLevel, string> = {\n PUBLIC: 'Visible to everyone.',\n COMMUNITY: 'Available to community members.',\n RESTRICTED: 'Visible to selected groups.',\n EMBARGOED: 'Hidden until a future date.',\n};\n\nconst TITLE_MAX = 160;\nconst NARRATOR_MAX = 120;\nconst LANGUAGE_MAX = 60;\nconst SUMMARY_MAX = 300;\nconst TRANSCRIPT_MAX = 5000;\n\nconst dateToIso = (value: string): string | null => {\n if (!value) return null;\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d.toISOString();\n};\n\ninterface RecordOralHistoryDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Pre-select a collection. */\n collectionId?: string;\n onCreated?: (oralHistory: TcpOralHistory) => void;\n}\n\nexport function RecordOralHistoryDialog({\n open,\n onOpenChange,\n collectionId,\n onCreated,\n}: RecordOralHistoryDialogProps) {\n const record = useTcpRecordOralHistory();\n const collectionsQuery = useTcpCollections({ limit: 100 });\n\n const [title, setTitle] = useState('');\n const [summary, setSummary] = useState('');\n const [transcript, setTranscript] = useState('');\n const [narratorName, setNarratorName] = useState('');\n const [language, setLanguage] = useState('');\n const [accessLevel, setAccessLevel] = useState<TcpAccessLevel>('COMMUNITY');\n const [pickedCollection, setPickedCollection] = useState(collectionId ?? '');\n const [recording, setRecording] = useState<UploadedRef | null>(null);\n const [durationSecs, setDurationSecs] = useState('');\n const [recordedAt, setRecordedAt] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setSummary('');\n setTranscript('');\n setNarratorName('');\n setLanguage('');\n setAccessLevel('COMMUNITY');\n setPickedCollection(collectionId ?? '');\n setRecording(null);\n setDurationSecs('');\n setRecordedAt('');\n setError(null);\n };\n\n /** Best-effort: read the recording's real duration to pre-fill the field. */\n const probeDuration = (url: string) => {\n try {\n const probe = new Audio();\n probe.preload = 'metadata';\n probe.onloadedmetadata = () => {\n if (Number.isFinite(probe.duration) && probe.duration > 0) {\n setDurationSecs((prev) => (prev ? prev : String(Math.round(probe.duration))));\n }\n };\n probe.src = url;\n } catch {\n // Non-fatal — the field stays manually editable.\n }\n };\n\n const submit = async () => {\n if (!title.trim()) {\n setError('A title is required.');\n return;\n }\n try {\n const created = await record.mutateAsync({\n title: title.trim(),\n summary: summary.trim() || null,\n transcript: transcript.trim() || null,\n narratorName: narratorName.trim() || null,\n language: language.trim() || null,\n accessLevel,\n collectionId: pickedCollection || null,\n recordingFileId: recording?.id ?? null,\n durationSecs: durationSecs ? Number(durationSecs) : null,\n recordedAt: dateToIso(recordedAt),\n });\n notify.success('Oral history recorded', created.title);\n onOpenChange(false);\n reset();\n onCreated?.(created);\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-2xl\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record an oral history</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Preserve a first-person account. Attach the recording and a transcript for the archive.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"flex max-h-[64vh] flex-col gap-4 overflow-y-auto py-2\">\n <div className=\"grid gap-1.5\">\n <Label>Recording</Label>\n <MediaDropzone\n folder={FILE_FOLDERS.recordings}\n accept=\"audio/*,video/*\"\n promptLabel=\"Drag and drop an audio or video file here\"\n hint=\"Audio or video, up to 25 MB.\"\n value={recording}\n onUploaded={(ref) => {\n setRecording(ref);\n probeDuration(ref.url);\n }}\n onClear={() => setRecording(null)}\n />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-title\">\n Title <span className=\"text-status-error-text\">*</span>\n </Label>\n <Input\n id=\"oral-title\"\n value={title}\n maxLength={TITLE_MAX}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. A weaver remembers the looms\"\n />\n <Counter length={title.length} max={TITLE_MAX} />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-narrator\">Narrator</Label>\n <Input\n id=\"oral-narrator\"\n value={narratorName}\n maxLength={NARRATOR_MAX}\n onChange={(e) => setNarratorName(e.target.value)}\n placeholder=\"Full name\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-language\">Language</Label>\n <Input\n id=\"oral-language\"\n value={language}\n maxLength={LANGUAGE_MAX}\n onChange={(e) => setLanguage(e.target.value)}\n placeholder=\"e.g. Tamil\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-duration\">Duration (seconds)</Label>\n <Input\n id=\"oral-duration\"\n type=\"number\"\n min={0}\n value={durationSecs}\n onChange={(e) => setDurationSecs(e.target.value)}\n placeholder=\"Auto-filled from the recording\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-recorded\">Recorded on</Label>\n <Input\n id=\"oral-recorded\"\n type=\"date\"\n value={recordedAt}\n onChange={(e) => setRecordedAt(e.target.value)}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label>Access level</Label>\n <Select\n value={accessLevel}\n onValueChange={(v) => setAccessLevel(v as TcpAccessLevel)}\n >\n <SelectTrigger aria-label=\"Access level\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ACCESS_LEVELS.map((a) => (\n <SelectItem key={a} value={a}>\n {formatEnumLabel(a)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-text-tertiary\">{ACCESS_HELP[accessLevel]}</p>\n </div>\n </div>\n\n {!collectionId && (\n <div className=\"grid gap-1.5\">\n <Label>Collection</Label>\n <Select value={pickedCollection} onValueChange={setPickedCollection}>\n <SelectTrigger>\n <SelectValue placeholder=\"Add to a collection (optional)\" />\n </SelectTrigger>\n <SelectContent>\n {(collectionsQuery.data ?? []).map((collection) => (\n <SelectItem key={collection.id} value={collection.id}>\n {collection.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-summary\">Summary</Label>\n <Textarea\n id=\"oral-summary\"\n value={summary}\n maxLength={SUMMARY_MAX}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"A short description of what this account covers.\"\n rows={3}\n />\n <Counter length={summary.length} max={SUMMARY_MAX} />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-transcript\">Transcript</Label>\n <Textarea\n id=\"oral-transcript\"\n value={transcript}\n maxLength={TRANSCRIPT_MAX}\n onChange={(e) => setTranscript(e.target.value)}\n placeholder=\"The transcribed account (line breaks are preserved).\"\n rows={5}\n />\n <Counter length={transcript.length} max={TRANSCRIPT_MAX} />\n </div>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={record.isPending}>\n Record oral history\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n/** A right-aligned character counter shown under a text field. */\nexport function Counter({ length, max }: { length: number; max: number }) {\n return (\n <p className=\"text-right text-xs tabular-nums text-text-tertiary\">\n {length} / {max}\n </p>\n );\n}\n"],"mappings":"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"}