@burdenoff/microfe-theculturepost 2026.721.2 → 2026.726.1

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 (49) hide show
  1. package/dist/theculturepost/components/AddCitationDialog.js +1 -1
  2. package/dist/theculturepost/components/AudioRecorderControl.d.ts +28 -0
  3. package/dist/theculturepost/components/AudioRecorderControl.d.ts.map +1 -0
  4. package/dist/theculturepost/components/AudioRecorderControl.js +116 -0
  5. package/dist/theculturepost/components/AudioRecorderControl.js.map +1 -0
  6. package/dist/theculturepost/components/CitationList.js +1 -1
  7. package/dist/theculturepost/components/CreateArchiveItemDialog.js +77 -77
  8. package/dist/theculturepost/components/CreateArchiveItemDialog.js.map +1 -1
  9. package/dist/theculturepost/components/CreateCollectionDialog.js +1 -1
  10. package/dist/theculturepost/components/CreateStoryDialog.js +1 -1
  11. package/dist/theculturepost/components/CreateTopicDialog.js +1 -1
  12. package/dist/theculturepost/components/OpenDiscussionDialog.js +1 -1
  13. package/dist/theculturepost/components/PendingOralHistoryPanel.d.ts +10 -0
  14. package/dist/theculturepost/components/PendingOralHistoryPanel.d.ts.map +1 -0
  15. package/dist/theculturepost/components/PendingOralHistoryPanel.js +67 -0
  16. package/dist/theculturepost/components/PendingOralHistoryPanel.js.map +1 -0
  17. package/dist/theculturepost/components/RecordOralHistoryDialog.d.ts.map +1 -1
  18. package/dist/theculturepost/components/RecordOralHistoryDialog.js +276 -174
  19. package/dist/theculturepost/components/RecordOralHistoryDialog.js.map +1 -1
  20. package/dist/theculturepost/components/ReplyThread.js +1 -1
  21. package/dist/theculturepost/components/StoryCard.js +1 -1
  22. package/dist/theculturepost/components/index.js +1 -1
  23. package/dist/theculturepost/hooks/index.d.ts +1 -0
  24. package/dist/theculturepost/hooks/index.d.ts.map +1 -1
  25. package/dist/theculturepost/hooks/index.js +1 -0
  26. package/dist/theculturepost/hooks/usePendingOralHistorySync.d.ts +10 -0
  27. package/dist/theculturepost/hooks/usePendingOralHistorySync.d.ts.map +1 -0
  28. package/dist/theculturepost/hooks/usePendingOralHistorySync.js +59 -0
  29. package/dist/theculturepost/hooks/usePendingOralHistorySync.js.map +1 -0
  30. package/dist/theculturepost/hooks/useTcpApi.d.ts +44 -3
  31. package/dist/theculturepost/hooks/useTcpApi.d.ts.map +1 -1
  32. package/dist/theculturepost/hooks/useTcpApi.js +92 -66
  33. package/dist/theculturepost/hooks/useTcpApi.js.map +1 -1
  34. package/dist/theculturepost/lib/offlineOralHistoryQueue.d.ts +54 -0
  35. package/dist/theculturepost/lib/offlineOralHistoryQueue.d.ts.map +1 -0
  36. package/dist/theculturepost/lib/offlineOralHistoryQueue.js +68 -0
  37. package/dist/theculturepost/lib/offlineOralHistoryQueue.js.map +1 -0
  38. package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js +1 -1
  39. package/dist/theculturepost/pages/archive/ArchivePage.d.ts.map +1 -1
  40. package/dist/theculturepost/pages/archive/ArchivePage.js +99 -95
  41. package/dist/theculturepost/pages/archive/ArchivePage.js.map +1 -1
  42. package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js +1 -1
  43. package/dist/theculturepost/pages/stories/StoryDetailPage.js +1 -1
  44. package/dist/theculturepost/pages/topics/TopicDetailPage.js +1 -1
  45. package/dist/theculturepost/types/index.d.ts +8 -0
  46. package/dist/theculturepost/types/index.d.ts.map +1 -1
  47. package/dist/theculturepost/utils/nativeBridge.js +10 -7
  48. package/dist/theculturepost/utils/nativeBridge.js.map +1 -1
  49. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  import { FILE_FOLDERS as e } from "../hooks/useFiles.js";
2
2
  import { formatEnumLabel as t } from "../utils/formatters.js";
3
3
  import { useTcpAddCitation as n } from "../hooks/useTcpApi.js";
4
- import "../hooks/index.js";
5
4
  import { describeError as r, notify as i } from "../lib/toast.js";
5
+ import "../hooks/index.js";
6
6
  import { FileUploadField as a } from "./FileUploadField.js";
7
7
  import { sourceTypeIcon as o } from "./CitationList.js";
8
8
  import { createElement as s, useState as c } from "react";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * AudioRecorderControl — in-app microphone recording for oral-history capture.
3
+ * @module theculturepost/components
4
+ *
5
+ * Uses the standard `navigator.mediaDevices.getUserMedia` + `MediaRecorder`
6
+ * Web APIs, which work identically inside the Capacitor WebView (once the
7
+ * host app declares `RECORD_AUDIO` / `NSMicrophoneUsageDescription`) and in a
8
+ * desktop browser — no native Capacitor plugin is required for audio-only
9
+ * capture. This is what makes "record now" possible on-device for field
10
+ * oral-history collection (BOFF-4128), as an addition to the existing
11
+ * upload-an-existing-file flow in {@link MediaDropzone}.
12
+ */
13
+ export interface RecordedAudio {
14
+ blob: Blob;
15
+ mimeType: string;
16
+ durationSecs: number;
17
+ /** An object URL for local preview — caller must revoke it when done. */
18
+ previewUrl: string;
19
+ }
20
+ interface AudioRecorderControlProps {
21
+ value: RecordedAudio | null;
22
+ onRecorded: (audio: RecordedAudio) => void;
23
+ onClear: () => void;
24
+ disabled?: boolean;
25
+ }
26
+ export declare function AudioRecorderControl({ value, onRecorded, onClear, disabled }: AudioRecorderControlProps): import("react/jsx-runtime").JSX.Element;
27
+ export {};
28
+ //# sourceMappingURL=AudioRecorderControl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioRecorderControl.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/AudioRecorderControl.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAQH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,yBAAyB;IACjC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAmBD,wBAAgB,oBAAoB,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,yBAAyB,2CAkIvG"}
@@ -0,0 +1,116 @@
1
+ import { isNativePlatform as e } from "../utils/nativeBridge.js";
2
+ import { AudioPlayer as t } from "./AudioPlayer.js";
3
+ import { useEffect as n, useRef as r, useState as i } from "react";
4
+ import { AlertCircle as a, Mic as o, Square as s, Trash2 as c } from "lucide-react";
5
+ import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
6
+ import { Button as f } from "@burdenoff/fe-libs/ui";
7
+ //#region src/theculturepost/components/AudioRecorderControl.tsx
8
+ function p() {
9
+ for (let e of [
10
+ "audio/webm;codecs=opus",
11
+ "audio/webm",
12
+ "audio/mp4",
13
+ "audio/aac"
14
+ ]) if (typeof MediaRecorder < "u" && MediaRecorder.isTypeSupported?.(e)) return e;
15
+ return "";
16
+ }
17
+ function m(e) {
18
+ let t = Math.floor(e / 60), n = Math.floor(e % 60);
19
+ return `${t}:${String(n).padStart(2, "0")}`;
20
+ }
21
+ function h({ value: h, onRecorded: g, onClear: _, disabled: v }) {
22
+ let [y, b] = i(!1), [x, S] = i(0), [C, w] = i(null), T = r(null), E = r([]), D = r(null), O = r(0), k = r(null);
23
+ n(() => () => {
24
+ k.current && clearInterval(k.current), D.current?.getTracks().forEach((e) => e.stop());
25
+ }, []);
26
+ let A = () => {
27
+ k.current &&= (clearInterval(k.current), null);
28
+ };
29
+ return h ? /* @__PURE__ */ d("div", {
30
+ className: "flex flex-col gap-2",
31
+ children: [/* @__PURE__ */ u(t, {
32
+ src: h.previewUrl,
33
+ seed: "local-recording",
34
+ durationSecs: h.durationSecs
35
+ }), /* @__PURE__ */ d("button", {
36
+ type: "button",
37
+ onClick: () => {
38
+ h && URL.revokeObjectURL(h.previewUrl), _(), S(0);
39
+ },
40
+ disabled: v,
41
+ className: "inline-flex w-fit items-center gap-1 text-sm text-text-tertiary hover:text-text-primary disabled:opacity-60",
42
+ children: [/* @__PURE__ */ u(c, { className: "h-3.5 w-3.5" }), " Discard and record again"]
43
+ })]
44
+ }) : /* @__PURE__ */ d("div", {
45
+ className: "flex flex-col items-center gap-3 rounded-xl border border-dashed border-border-seam bg-bg-surface px-4 py-6 text-center",
46
+ children: [y ? /* @__PURE__ */ d(l, { children: [/* @__PURE__ */ d("div", {
47
+ className: "flex items-center gap-2 text-status-error-text",
48
+ children: [/* @__PURE__ */ d("span", {
49
+ className: "relative flex h-3 w-3",
50
+ children: [/* @__PURE__ */ u("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-status-error-text opacity-75" }), /* @__PURE__ */ u("span", { className: "relative inline-flex h-3 w-3 rounded-full bg-status-error-text" })]
51
+ }), /* @__PURE__ */ u("span", {
52
+ className: "font-mono text-lg tabular-nums",
53
+ children: m(x)
54
+ })]
55
+ }), /* @__PURE__ */ d(f, {
56
+ type: "button",
57
+ variant: "outline",
58
+ onClick: () => {
59
+ T.current?.stop(), b(!1);
60
+ },
61
+ children: [/* @__PURE__ */ u(s, { className: "mr-1.5 h-4 w-4" }), " Stop recording"]
62
+ })] }) : /* @__PURE__ */ d(l, { children: [
63
+ /* @__PURE__ */ u(o, {
64
+ className: "h-6 w-6 text-text-tertiary",
65
+ "aria-hidden": "true"
66
+ }),
67
+ /* @__PURE__ */ u("p", {
68
+ className: "text-sm text-text-secondary",
69
+ children: e() ? "Record the testimony directly on this device." : "Record using this device’s microphone."
70
+ }),
71
+ /* @__PURE__ */ d(f, {
72
+ type: "button",
73
+ onClick: async () => {
74
+ if (w(null), typeof navigator > "u" || !navigator.mediaDevices?.getUserMedia) {
75
+ w("Microphone recording is not supported in this browser.");
76
+ return;
77
+ }
78
+ try {
79
+ let e = await navigator.mediaDevices.getUserMedia({ audio: !0 });
80
+ D.current = e;
81
+ let t = p(), n = t ? new MediaRecorder(e, { mimeType: t }) : new MediaRecorder(e);
82
+ E.current = [], n.ondataavailable = (e) => {
83
+ e.data.size > 0 && E.current.push(e.data);
84
+ }, n.onstop = () => {
85
+ A(), e.getTracks().forEach((e) => e.stop());
86
+ let r = n.mimeType || t || "audio/webm", i = new Blob(E.current, { type: r });
87
+ g({
88
+ blob: i,
89
+ mimeType: r,
90
+ durationSecs: Math.max(1, Math.round((Date.now() - O.current) / 1e3)),
91
+ previewUrl: URL.createObjectURL(i)
92
+ });
93
+ }, T.current = n, O.current = Date.now(), S(0), n.start(), b(!0), k.current = setInterval(() => {
94
+ S(Math.round((Date.now() - O.current) / 1e3));
95
+ }, 500);
96
+ } catch {
97
+ w("Microphone access was denied or is unavailable. Check app permissions and try again.");
98
+ }
99
+ },
100
+ disabled: v,
101
+ children: [/* @__PURE__ */ u(o, { className: "mr-1.5 h-4 w-4" }), " Start recording"]
102
+ })
103
+ ] }), C && /* @__PURE__ */ d("p", {
104
+ className: "flex items-center gap-1.5 text-xs text-status-error-text",
105
+ children: [
106
+ /* @__PURE__ */ u(a, { className: "h-3.5 w-3.5 shrink-0" }),
107
+ " ",
108
+ C
109
+ ]
110
+ })]
111
+ });
112
+ }
113
+ //#endregion
114
+ export { h as AudioRecorderControl };
115
+
116
+ //# sourceMappingURL=AudioRecorderControl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioRecorderControl.js","names":[],"sources":["../../../src/theculturepost/components/AudioRecorderControl.tsx"],"sourcesContent":["/**\n * AudioRecorderControl — in-app microphone recording for oral-history capture.\n * @module theculturepost/components\n *\n * Uses the standard `navigator.mediaDevices.getUserMedia` + `MediaRecorder`\n * Web APIs, which work identically inside the Capacitor WebView (once the\n * host app declares `RECORD_AUDIO` / `NSMicrophoneUsageDescription`) and in a\n * desktop browser — no native Capacitor plugin is required for audio-only\n * capture. This is what makes \"record now\" possible on-device for field\n * oral-history collection (BOFF-4128), as an addition to the existing\n * upload-an-existing-file flow in {@link MediaDropzone}.\n */\n\nimport { useEffect, useRef, useState } from 'react';\nimport { Mic, Square, Trash2, AlertCircle } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { AudioPlayer } from './AudioPlayer';\nimport { isNativePlatform } from '../utils/nativeBridge';\n\nexport interface RecordedAudio {\n blob: Blob;\n mimeType: string;\n durationSecs: number;\n /** An object URL for local preview — caller must revoke it when done. */\n previewUrl: string;\n}\n\ninterface AudioRecorderControlProps {\n value: RecordedAudio | null;\n onRecorded: (audio: RecordedAudio) => void;\n onClear: () => void;\n disabled?: boolean;\n}\n\n/** Picks the first MIME type the platform's MediaRecorder actually supports. */\nfunction pickSupportedMimeType(): string {\n const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/aac'];\n for (const candidate of candidates) {\n if (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported?.(candidate)) {\n return candidate;\n }\n }\n return '';\n}\n\nfunction formatElapsed(seconds: number): string {\n const m = Math.floor(seconds / 60);\n const s = Math.floor(seconds % 60);\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n\nexport function AudioRecorderControl({ value, onRecorded, onClear, disabled }: AudioRecorderControlProps) {\n const [recording, setRecording] = useState(false);\n const [elapsed, setElapsed] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const chunksRef = useRef<Blob[]>([]);\n const streamRef = useRef<MediaStream | null>(null);\n const startedAtRef = useRef<number>(0);\n const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n useEffect(\n () => () => {\n // Unmount safety: stop any in-flight recording and release the mic.\n if (timerRef.current) clearInterval(timerRef.current);\n streamRef.current?.getTracks().forEach((track) => track.stop());\n },\n []\n );\n\n const stopTimer = () => {\n if (timerRef.current) {\n clearInterval(timerRef.current);\n timerRef.current = null;\n }\n };\n\n const start = async () => {\n setError(null);\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {\n setError('Microphone recording is not supported in this browser.');\n return;\n }\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n streamRef.current = stream;\n const mimeType = pickSupportedMimeType();\n const recorder = mimeType\n ? new MediaRecorder(stream, { mimeType })\n : new MediaRecorder(stream);\n chunksRef.current = [];\n\n recorder.ondataavailable = (event) => {\n if (event.data.size > 0) chunksRef.current.push(event.data);\n };\n\n recorder.onstop = () => {\n stopTimer();\n stream.getTracks().forEach((track) => track.stop());\n const finalMimeType = recorder.mimeType || mimeType || 'audio/webm';\n const blob = new Blob(chunksRef.current, { type: finalMimeType });\n const durationSecs = Math.max(1, Math.round((Date.now() - startedAtRef.current) / 1000));\n onRecorded({ blob, mimeType: finalMimeType, durationSecs, previewUrl: URL.createObjectURL(blob) });\n };\n\n mediaRecorderRef.current = recorder;\n startedAtRef.current = Date.now();\n setElapsed(0);\n recorder.start();\n setRecording(true);\n timerRef.current = setInterval(() => {\n setElapsed(Math.round((Date.now() - startedAtRef.current) / 1000));\n }, 500);\n } catch {\n setError('Microphone access was denied or is unavailable. Check app permissions and try again.');\n }\n };\n\n const stop = () => {\n mediaRecorderRef.current?.stop();\n setRecording(false);\n };\n\n const discard = () => {\n if (value) URL.revokeObjectURL(value.previewUrl);\n onClear();\n setElapsed(0);\n };\n\n if (value) {\n return (\n <div className=\"flex flex-col gap-2\">\n <AudioPlayer src={value.previewUrl} seed=\"local-recording\" durationSecs={value.durationSecs} />\n <button\n type=\"button\"\n onClick={discard}\n disabled={disabled}\n className=\"inline-flex w-fit items-center gap-1 text-sm text-text-tertiary hover:text-text-primary disabled:opacity-60\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" /> Discard and record again\n </button>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col items-center gap-3 rounded-xl border border-dashed border-border-seam bg-bg-surface px-4 py-6 text-center\">\n {recording ? (\n <>\n <div className=\"flex items-center gap-2 text-status-error-text\">\n <span className=\"relative flex h-3 w-3\">\n <span className=\"absolute inline-flex h-full w-full animate-ping rounded-full bg-status-error-text opacity-75\" />\n <span className=\"relative inline-flex h-3 w-3 rounded-full bg-status-error-text\" />\n </span>\n <span className=\"font-mono text-lg tabular-nums\">{formatElapsed(elapsed)}</span>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={stop}>\n <Square className=\"mr-1.5 h-4 w-4\" /> Stop recording\n </Button>\n </>\n ) : (\n <>\n <Mic className=\"h-6 w-6 text-text-tertiary\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-secondary\">\n {isNativePlatform()\n ? 'Record the testimony directly on this device.'\n : 'Record using this device’s microphone.'}\n </p>\n <Button type=\"button\" onClick={start} disabled={disabled}>\n <Mic className=\"mr-1.5 h-4 w-4\" /> Start recording\n </Button>\n </>\n )}\n {error && (\n <p className=\"flex items-center gap-1.5 text-xs text-status-error-text\">\n <AlertCircle className=\"h-3.5 w-3.5 shrink-0\" /> {error}\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -3,8 +3,8 @@ import { ErrorView as t, LoadingView as n } from "./StateViews.js";
3
3
  import { formatDate as r, formatEnumLabel as i } from "../utils/formatters.js";
4
4
  import { StatusBadge as a } from "./StatusBadge.js";
5
5
  import { useTcpCitations as o, useTcpVerifyCitation as s } from "../hooks/useTcpApi.js";
6
- import "../hooks/index.js";
7
6
  import { describeError as c, notify as l } from "../lib/toast.js";
7
+ import "../hooks/index.js";
8
8
  import { EmptyState as u } from "./EmptyState.js";
9
9
  import { createElement as d } from "react";
10
10
  import { AlertCircle as f, BookMarked as p, BookOpen as m, Box as h, CheckCircle2 as g, ExternalLink as _, FileText as v, Globe as y, GraduationCap as b, HelpCircle as x, Landmark as S, Mic as C, Newspaper as w, Quote as T, ShieldCheck as E, XCircle as D } from "lucide-react";
@@ -1,15 +1,15 @@
1
1
  import { FILE_FOLDERS as e } from "../hooks/useFiles.js";
2
2
  import { formatEnumLabel as t } from "../utils/formatters.js";
3
- import { useTcpCollections as n, useTcpCreateArchiveItem as r } from "../hooks/useTcpApi.js";
3
+ import { useTcpCollections as n, useTcpCreateArchiveItem as ee } from "../hooks/useTcpApi.js";
4
+ import { describeError as te, notify as ne } from "../lib/toast.js";
4
5
  import "../hooks/index.js";
5
- import { describeError as ee, notify as te } from "../lib/toast.js";
6
+ import { TagInput as r } from "./TagInput.js";
6
7
  import { Counter as i, MediaDropzone as a } from "./RecordOralHistoryDialog.js";
7
- import { TagInput as o } from "./TagInput.js";
8
- import { useState as s } from "react";
9
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
10
- import { Button as u, Input as d, Label as f, ResponsiveDialog as ne, ResponsiveDialogContent as re, ResponsiveDialogDescription as ie, ResponsiveDialogFooter as ae, ResponsiveDialogHeader as p, ResponsiveDialogTitle as m, Select as h, SelectContent as g, SelectItem as _, SelectTrigger as v, SelectValue as y, Textarea as b } from "@burdenoff/fe-libs/ui";
8
+ import { useState as o } from "react";
9
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
10
+ import { Button as l, Input as u, Label as d, ResponsiveDialog as re, ResponsiveDialogContent as ie, ResponsiveDialogDescription as ae, ResponsiveDialogFooter as f, ResponsiveDialogHeader as p, ResponsiveDialogTitle as oe, Select as m, SelectContent as h, SelectItem as g, SelectTrigger as _, SelectValue as v, Textarea as y } from "@burdenoff/fe-libs/ui";
11
11
  //#region src/theculturepost/components/CreateArchiveItemDialog.tsx
12
- var x = [
12
+ var se = [
13
13
  "DOCUMENT",
14
14
  "MANUSCRIPT",
15
15
  "PHOTOGRAPH",
@@ -17,38 +17,38 @@ var x = [
17
17
  "VIDEO",
18
18
  "MAP",
19
19
  "OBJECT"
20
- ], oe = [
20
+ ], ce = [
21
21
  "PUBLIC",
22
22
  "COMMUNITY",
23
23
  "RESTRICTED",
24
24
  "EMBARGOED"
25
- ], se = {
25
+ ], b = {
26
26
  PUBLIC: "Visible to everyone.",
27
27
  COMMUNITY: "Available to community members.",
28
28
  RESTRICTED: "Visible to selected groups.",
29
29
  EMBARGOED: "Hidden until a future date."
30
- }, S = 160, C = 600, w = 600, ce = 160, le = (e) => {
30
+ }, x = 160, S = 600, C = 600, le = 160, ue = (e) => {
31
31
  if (!e) return null;
32
32
  let t = new Date(e);
33
33
  return Number.isNaN(t.getTime()) ? null : t.toISOString();
34
34
  };
35
- function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
36
- let k = r(), ue = n({ limit: 100 }), [A, j] = s(""), [M, N] = s(""), [P, F] = s("DOCUMENT"), [I, L] = s("PUBLIC"), [R, z] = s(D ?? ""), [B, V] = s(null), [H, U] = s(""), [W, G] = s(""), [K, q] = s([]), [J, Y] = s([]), [X, Z] = s(""), [Q, $] = s(null), de = () => {
37
- j(""), N(""), F("DOCUMENT"), L("PUBLIC"), z(D ?? ""), V(null), U(""), G(""), q([]), Y([]), Z(""), $(null);
35
+ function w({ open: w, onOpenChange: T, collectionId: E, onCreated: D }) {
36
+ let O = ee(), k = n({ limit: 100 }), [A, j] = o(""), [M, N] = o(""), [P, F] = o("DOCUMENT"), [I, L] = o("PUBLIC"), [R, z] = o(E ?? ""), [B, V] = o(null), [H, U] = o(""), [W, G] = o(""), [K, q] = o([]), [J, Y] = o([]), [X, Z] = o(""), [Q, $] = o(null), de = () => {
37
+ j(""), N(""), F("DOCUMENT"), L("PUBLIC"), z(E ?? ""), V(null), U(""), G(""), q([]), Y([]), Z(""), $(null);
38
38
  };
39
- return /* @__PURE__ */ c(ne, {
40
- open: T,
41
- onOpenChange: E,
42
- children: /* @__PURE__ */ l(re, {
39
+ return /* @__PURE__ */ s(re, {
40
+ open: w,
41
+ onOpenChange: T,
42
+ children: /* @__PURE__ */ c(ie, {
43
43
  className: "sm:max-w-2xl",
44
44
  children: [
45
- /* @__PURE__ */ l(p, { children: [/* @__PURE__ */ c(m, { children: "Add an artifact" }), /* @__PURE__ */ c(ie, { children: "Catalogue a manuscript, photograph, recording, map, object or document with its provenance." })] }),
46
- /* @__PURE__ */ l("div", {
45
+ /* @__PURE__ */ c(p, { children: [/* @__PURE__ */ s(oe, { children: "Add an artifact" }), /* @__PURE__ */ s(ae, { children: "Catalogue a manuscript, photograph, recording, map, object or document with its provenance." })] }),
46
+ /* @__PURE__ */ c("div", {
47
47
  className: "flex max-h-[64vh] flex-col gap-4 overflow-y-auto py-2",
48
48
  children: [
49
- /* @__PURE__ */ l("div", {
49
+ /* @__PURE__ */ c("div", {
50
50
  className: "grid gap-1.5",
51
- children: [/* @__PURE__ */ c(f, { children: "File" }), /* @__PURE__ */ c(a, {
51
+ children: [/* @__PURE__ */ s(d, { children: "File" }), /* @__PURE__ */ s(a, {
52
52
  folder: e.artifacts,
53
53
  accept: "image/*,audio/*,video/*,application/pdf",
54
54
  promptLabel: "Drag and drop the artifact file here",
@@ -58,111 +58,111 @@ function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
58
58
  onClear: () => V(null)
59
59
  })]
60
60
  }),
61
- /* @__PURE__ */ l("div", {
61
+ /* @__PURE__ */ c("div", {
62
62
  className: "grid gap-1.5",
63
63
  children: [
64
- /* @__PURE__ */ l(f, {
64
+ /* @__PURE__ */ c(d, {
65
65
  htmlFor: "item-title",
66
- children: ["Title ", /* @__PURE__ */ c("span", {
66
+ children: ["Title ", /* @__PURE__ */ s("span", {
67
67
  className: "text-status-error-text",
68
68
  children: "*"
69
69
  })]
70
70
  }),
71
- /* @__PURE__ */ c(d, {
71
+ /* @__PURE__ */ s(u, {
72
72
  id: "item-title",
73
73
  value: A,
74
- maxLength: S,
74
+ maxLength: x,
75
75
  onChange: (e) => j(e.target.value),
76
76
  placeholder: "e.g. Palm-leaf manuscript, 18th c."
77
77
  }),
78
- /* @__PURE__ */ c(i, {
78
+ /* @__PURE__ */ s(i, {
79
79
  length: A.length,
80
- max: S
80
+ max: x
81
81
  })
82
82
  ]
83
83
  }),
84
- /* @__PURE__ */ l("div", {
84
+ /* @__PURE__ */ c("div", {
85
85
  className: "grid gap-3 sm:grid-cols-2",
86
- children: [/* @__PURE__ */ l("div", {
86
+ children: [/* @__PURE__ */ c("div", {
87
87
  className: "grid gap-1.5",
88
- children: [/* @__PURE__ */ c(f, { children: "Kind" }), /* @__PURE__ */ l(h, {
88
+ children: [/* @__PURE__ */ s(d, { children: "Kind" }), /* @__PURE__ */ c(m, {
89
89
  value: P,
90
90
  onValueChange: (e) => F(e),
91
- children: [/* @__PURE__ */ c(v, { children: /* @__PURE__ */ c(y, {}) }), /* @__PURE__ */ c(g, { children: x.map((e) => /* @__PURE__ */ c(_, {
91
+ children: [/* @__PURE__ */ s(_, { children: /* @__PURE__ */ s(v, {}) }), /* @__PURE__ */ s(h, { children: se.map((e) => /* @__PURE__ */ s(g, {
92
92
  value: e,
93
93
  children: t(e)
94
94
  }, e)) })]
95
95
  })]
96
- }), /* @__PURE__ */ l("div", {
96
+ }), /* @__PURE__ */ c("div", {
97
97
  className: "grid gap-1.5",
98
98
  children: [
99
- /* @__PURE__ */ c(f, { children: "Access level" }),
100
- /* @__PURE__ */ l(h, {
99
+ /* @__PURE__ */ s(d, { children: "Access level" }),
100
+ /* @__PURE__ */ c(m, {
101
101
  value: I,
102
102
  onValueChange: (e) => L(e),
103
- children: [/* @__PURE__ */ c(v, { children: /* @__PURE__ */ c(y, {}) }), /* @__PURE__ */ c(g, { children: oe.map((e) => /* @__PURE__ */ c(_, {
103
+ children: [/* @__PURE__ */ s(_, { children: /* @__PURE__ */ s(v, {}) }), /* @__PURE__ */ s(h, { children: ce.map((e) => /* @__PURE__ */ s(g, {
104
104
  value: e,
105
105
  children: t(e)
106
106
  }, e)) })]
107
107
  }),
108
- /* @__PURE__ */ c("p", {
108
+ /* @__PURE__ */ s("p", {
109
109
  className: "text-xs text-text-tertiary",
110
- children: se[I]
110
+ children: b[I]
111
111
  })
112
112
  ]
113
113
  })]
114
114
  }),
115
- !D && /* @__PURE__ */ l("div", {
115
+ !E && /* @__PURE__ */ c("div", {
116
116
  className: "grid gap-1.5",
117
- children: [/* @__PURE__ */ c(f, { children: "Collection" }), /* @__PURE__ */ l(h, {
117
+ children: [/* @__PURE__ */ s(d, { children: "Collection" }), /* @__PURE__ */ c(m, {
118
118
  value: R,
119
119
  onValueChange: z,
120
- children: [/* @__PURE__ */ c(v, { children: /* @__PURE__ */ c(y, { placeholder: "Add to a collection (optional)" }) }), /* @__PURE__ */ c(g, { children: (ue.data ?? []).map((e) => /* @__PURE__ */ c(_, {
120
+ children: [/* @__PURE__ */ s(_, { children: /* @__PURE__ */ s(v, { placeholder: "Add to a collection (optional)" }) }), /* @__PURE__ */ s(h, { children: (k.data ?? []).map((e) => /* @__PURE__ */ s(g, {
121
121
  value: e.id,
122
122
  children: e.name
123
123
  }, e.id)) })]
124
124
  })]
125
125
  }),
126
- /* @__PURE__ */ l("div", {
126
+ /* @__PURE__ */ c("div", {
127
127
  className: "grid gap-1.5",
128
128
  children: [
129
- /* @__PURE__ */ c(f, {
129
+ /* @__PURE__ */ s(d, {
130
130
  htmlFor: "item-desc",
131
131
  children: "Description"
132
132
  }),
133
- /* @__PURE__ */ c(b, {
133
+ /* @__PURE__ */ s(y, {
134
134
  id: "item-desc",
135
135
  value: M,
136
- maxLength: C,
136
+ maxLength: S,
137
137
  onChange: (e) => N(e.target.value),
138
138
  rows: 2
139
139
  }),
140
- /* @__PURE__ */ c(i, {
140
+ /* @__PURE__ */ s(i, {
141
141
  length: M.length,
142
- max: C
142
+ max: S
143
143
  })
144
144
  ]
145
145
  }),
146
- /* @__PURE__ */ l("div", {
146
+ /* @__PURE__ */ c("div", {
147
147
  className: "grid gap-3 sm:grid-cols-2",
148
- children: [/* @__PURE__ */ l("div", {
148
+ children: [/* @__PURE__ */ c("div", {
149
149
  className: "grid gap-1.5",
150
- children: [/* @__PURE__ */ c(f, {
150
+ children: [/* @__PURE__ */ s(d, {
151
151
  htmlFor: "item-origin",
152
152
  children: "Origin"
153
- }), /* @__PURE__ */ c(d, {
153
+ }), /* @__PURE__ */ s(u, {
154
154
  id: "item-origin",
155
155
  value: W,
156
- maxLength: ce,
156
+ maxLength: le,
157
157
  onChange: (e) => G(e.target.value),
158
158
  placeholder: "Place of origin"
159
159
  })]
160
- }), /* @__PURE__ */ l("div", {
160
+ }), /* @__PURE__ */ c("div", {
161
161
  className: "grid gap-1.5",
162
- children: [/* @__PURE__ */ c(f, {
162
+ children: [/* @__PURE__ */ s(d, {
163
163
  htmlFor: "item-dated",
164
164
  children: "Dated"
165
- }), /* @__PURE__ */ c(d, {
165
+ }), /* @__PURE__ */ s(u, {
166
166
  id: "item-dated",
167
167
  type: "date",
168
168
  value: X,
@@ -170,33 +170,33 @@ function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
170
170
  })]
171
171
  })]
172
172
  }),
173
- /* @__PURE__ */ l("div", {
173
+ /* @__PURE__ */ c("div", {
174
174
  className: "grid gap-1.5",
175
175
  children: [
176
- /* @__PURE__ */ c(f, {
176
+ /* @__PURE__ */ s(d, {
177
177
  htmlFor: "item-provenance",
178
178
  children: "Provenance"
179
179
  }),
180
- /* @__PURE__ */ c(b, {
180
+ /* @__PURE__ */ s(y, {
181
181
  id: "item-provenance",
182
182
  value: H,
183
- maxLength: w,
183
+ maxLength: C,
184
184
  onChange: (e) => U(e.target.value),
185
185
  placeholder: "Chain of custody / source history",
186
186
  rows: 2
187
187
  }),
188
- /* @__PURE__ */ c(i, {
188
+ /* @__PURE__ */ s(i, {
189
189
  length: H.length,
190
- max: w
190
+ max: C
191
191
  })
192
192
  ]
193
193
  }),
194
- /* @__PURE__ */ l("div", {
194
+ /* @__PURE__ */ c("div", {
195
195
  className: "grid gap-1.5",
196
- children: [/* @__PURE__ */ c(f, {
196
+ children: [/* @__PURE__ */ s(d, {
197
197
  htmlFor: "item-langs",
198
198
  children: "Languages"
199
- }), /* @__PURE__ */ c(o, {
199
+ }), /* @__PURE__ */ s(r, {
200
200
  id: "item-langs",
201
201
  value: K,
202
202
  onChange: q,
@@ -204,36 +204,36 @@ function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
204
204
  preserveCase: !0
205
205
  })]
206
206
  }),
207
- /* @__PURE__ */ l("div", {
207
+ /* @__PURE__ */ c("div", {
208
208
  className: "grid gap-1.5",
209
- children: [/* @__PURE__ */ c(f, {
209
+ children: [/* @__PURE__ */ s(d, {
210
210
  htmlFor: "item-tags",
211
211
  children: "Tags"
212
- }), /* @__PURE__ */ c(o, {
212
+ }), /* @__PURE__ */ s(r, {
213
213
  id: "item-tags",
214
214
  value: J,
215
215
  onChange: Y,
216
216
  placeholder: "Add tags…"
217
217
  })]
218
218
  }),
219
- Q && /* @__PURE__ */ c("p", {
219
+ Q && /* @__PURE__ */ s("p", {
220
220
  className: "text-sm text-status-error-text",
221
221
  children: Q
222
222
  })
223
223
  ]
224
224
  }),
225
- /* @__PURE__ */ l(ae, { children: [/* @__PURE__ */ c(u, {
225
+ /* @__PURE__ */ c(f, { children: [/* @__PURE__ */ s(l, {
226
226
  variant: "outline",
227
- onClick: () => E(!1),
227
+ onClick: () => T(!1),
228
228
  children: "Cancel"
229
- }), /* @__PURE__ */ c(u, {
229
+ }), /* @__PURE__ */ s(l, {
230
230
  onClick: async () => {
231
231
  if (!A.trim()) {
232
232
  $("A title is required.");
233
233
  return;
234
234
  }
235
235
  try {
236
- let e = await k.mutateAsync({
236
+ let e = await O.mutateAsync({
237
237
  title: A.trim(),
238
238
  description: M.trim() || null,
239
239
  kind: P,
@@ -244,14 +244,14 @@ function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
244
244
  origin: W.trim() || null,
245
245
  languages: K.length ? K : null,
246
246
  tags: J.length ? J : null,
247
- datedAt: le(X)
247
+ datedAt: ue(X)
248
248
  });
249
- te.success("Artifact added", e.title), E(!1), de(), O?.(e);
249
+ ne.success("Artifact added", e.title), T(!1), de(), D?.(e);
250
250
  } catch (e) {
251
- $(ee(e));
251
+ $(te(e));
252
252
  }
253
253
  },
254
- disabled: k.isPending,
254
+ disabled: O.isPending,
255
255
  children: "Add artifact"
256
256
  })] })
257
257
  ]
@@ -259,6 +259,6 @@ function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
259
259
  });
260
260
  }
261
261
  //#endregion
262
- export { T as CreateArchiveItemDialog };
262
+ export { w as CreateArchiveItemDialog };
263
263
 
264
264
  //# sourceMappingURL=CreateArchiveItemDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CreateArchiveItemDialog.js","names":[],"sources":["../../../src/theculturepost/components/CreateArchiveItemDialog.tsx"],"sourcesContent":["/**\n * CreateArchiveItemDialog — add an artifact to the archive.\n * @module theculturepost/components\n *\n * Writes `tcpCreateArchiveItem`. The binary is uploaded to wspace-files-svc via\n * the shared {@link MediaDropzone} (drag-drop) and only its id is stored here.\n * The schema exposes a SINGLE `file` per item — never a gallery. Kind + access\n * level are chosen up front.\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 { TagInput } from './TagInput';\nimport { MediaDropzone, Counter, type UploadedRef } from './RecordOralHistoryDialog';\nimport { useTcpCreateArchiveItem, useTcpCollections, FILE_FOLDERS } from '../hooks';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpArchiveItem, TcpArchiveItemKind, TcpAccessLevel } from '../types';\n\nconst KINDS: TcpArchiveItemKind[] = [\n 'DOCUMENT',\n 'MANUSCRIPT',\n 'PHOTOGRAPH',\n 'AUDIO',\n 'VIDEO',\n 'MAP',\n 'OBJECT',\n];\n\nconst ACCESS_LEVELS: TcpAccessLevel[] = ['PUBLIC', 'COMMUNITY', '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 DESCRIPTION_MAX = 600;\nconst PROVENANCE_MAX = 600;\nconst ORIGIN_MAX = 160;\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 CreateArchiveItemDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Pre-select a collection (e.g. from a collection detail page). */\n collectionId?: string;\n onCreated?: (item: TcpArchiveItem) => void;\n}\n\nexport function CreateArchiveItemDialog({\n open,\n onOpenChange,\n collectionId,\n onCreated,\n}: CreateArchiveItemDialogProps) {\n const createItem = useTcpCreateArchiveItem();\n const collectionsQuery = useTcpCollections({ limit: 100 });\n\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n const [kind, setKind] = useState<TcpArchiveItemKind>('DOCUMENT');\n const [accessLevel, setAccessLevel] = useState<TcpAccessLevel>('PUBLIC');\n const [pickedCollection, setPickedCollection] = useState(collectionId ?? '');\n const [file, setFile] = useState<UploadedRef | null>(null);\n const [provenance, setProvenance] = useState('');\n const [origin, setOrigin] = useState('');\n const [languages, setLanguages] = useState<string[]>([]);\n const [tags, setTags] = useState<string[]>([]);\n const [datedAt, setDatedAt] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setDescription('');\n setKind('DOCUMENT');\n setAccessLevel('PUBLIC');\n setPickedCollection(collectionId ?? '');\n setFile(null);\n setProvenance('');\n setOrigin('');\n setLanguages([]);\n setTags([]);\n setDatedAt('');\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 createItem.mutateAsync({\n title: title.trim(),\n description: description.trim() || null,\n kind,\n accessLevel,\n collectionId: pickedCollection || null,\n fileId: file?.id ?? null,\n provenance: provenance.trim() || null,\n origin: origin.trim() || null,\n languages: languages.length ? languages : null,\n tags: tags.length ? tags : null,\n datedAt: dateToIso(datedAt),\n });\n notify.success('Artifact added', 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>Add an artifact</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Catalogue a manuscript, photograph, recording, map, object or document with its\n provenance.\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>File</Label>\n <MediaDropzone\n folder={FILE_FOLDERS.artifacts}\n accept=\"image/*,audio/*,video/*,application/pdf\"\n promptLabel=\"Drag and drop the artifact file here\"\n hint=\"Image, audio, video or PDF, up to 25 MB. One file per artifact.\"\n value={file}\n onUploaded={(ref) => setFile(ref)}\n onClear={() => setFile(null)}\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-title\">\n Title <span className=\"text-status-error-text\">*</span>\n </Label>\n <Input\n id=\"item-title\"\n value={title}\n maxLength={TITLE_MAX}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Palm-leaf manuscript, 18th c.\"\n />\n <Counter length={title.length} max={TITLE_MAX} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label>Kind</Label>\n <Select value={kind} onValueChange={(v) => setKind(v as TcpArchiveItemKind)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {KINDS.map((k) => (\n <SelectItem key={k} value={k}>\n {formatEnumLabel(k)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\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>\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=\"item-desc\">Description</Label>\n <Textarea\n id=\"item-desc\"\n value={description}\n maxLength={DESCRIPTION_MAX}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n />\n <Counter length={description.length} max={DESCRIPTION_MAX} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-origin\">Origin</Label>\n <Input\n id=\"item-origin\"\n value={origin}\n maxLength={ORIGIN_MAX}\n onChange={(e) => setOrigin(e.target.value)}\n placeholder=\"Place of origin\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-dated\">Dated</Label>\n <Input\n id=\"item-dated\"\n type=\"date\"\n value={datedAt}\n onChange={(e) => setDatedAt(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-provenance\">Provenance</Label>\n <Textarea\n id=\"item-provenance\"\n value={provenance}\n maxLength={PROVENANCE_MAX}\n onChange={(e) => setProvenance(e.target.value)}\n placeholder=\"Chain of custody / source history\"\n rows={2}\n />\n <Counter length={provenance.length} max={PROVENANCE_MAX} />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-langs\">Languages</Label>\n <TagInput\n id=\"item-langs\"\n value={languages}\n onChange={setLanguages}\n placeholder=\"e.g. Tamil, Sanskrit…\"\n preserveCase\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-tags\">Tags</Label>\n <TagInput id=\"item-tags\" value={tags} onChange={setTags} placeholder=\"Add tags…\" />\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={createItem.isPending}>\n Add artifact\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreateArchiveItemDialog.js","names":[],"sources":["../../../src/theculturepost/components/CreateArchiveItemDialog.tsx"],"sourcesContent":["/**\n * CreateArchiveItemDialog — add an artifact to the archive.\n * @module theculturepost/components\n *\n * Writes `tcpCreateArchiveItem`. The binary is uploaded to wspace-files-svc via\n * the shared {@link MediaDropzone} (drag-drop) and only its id is stored here.\n * The schema exposes a SINGLE `file` per item — never a gallery. Kind + access\n * level are chosen up front.\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 { TagInput } from './TagInput';\nimport { MediaDropzone, Counter, type UploadedRef } from './RecordOralHistoryDialog';\nimport { useTcpCreateArchiveItem, useTcpCollections, FILE_FOLDERS } from '../hooks';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpArchiveItem, TcpArchiveItemKind, TcpAccessLevel } from '../types';\n\nconst KINDS: TcpArchiveItemKind[] = [\n 'DOCUMENT',\n 'MANUSCRIPT',\n 'PHOTOGRAPH',\n 'AUDIO',\n 'VIDEO',\n 'MAP',\n 'OBJECT',\n];\n\nconst ACCESS_LEVELS: TcpAccessLevel[] = ['PUBLIC', 'COMMUNITY', '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 DESCRIPTION_MAX = 600;\nconst PROVENANCE_MAX = 600;\nconst ORIGIN_MAX = 160;\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 CreateArchiveItemDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Pre-select a collection (e.g. from a collection detail page). */\n collectionId?: string;\n onCreated?: (item: TcpArchiveItem) => void;\n}\n\nexport function CreateArchiveItemDialog({\n open,\n onOpenChange,\n collectionId,\n onCreated,\n}: CreateArchiveItemDialogProps) {\n const createItem = useTcpCreateArchiveItem();\n const collectionsQuery = useTcpCollections({ limit: 100 });\n\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n const [kind, setKind] = useState<TcpArchiveItemKind>('DOCUMENT');\n const [accessLevel, setAccessLevel] = useState<TcpAccessLevel>('PUBLIC');\n const [pickedCollection, setPickedCollection] = useState(collectionId ?? '');\n const [file, setFile] = useState<UploadedRef | null>(null);\n const [provenance, setProvenance] = useState('');\n const [origin, setOrigin] = useState('');\n const [languages, setLanguages] = useState<string[]>([]);\n const [tags, setTags] = useState<string[]>([]);\n const [datedAt, setDatedAt] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setDescription('');\n setKind('DOCUMENT');\n setAccessLevel('PUBLIC');\n setPickedCollection(collectionId ?? '');\n setFile(null);\n setProvenance('');\n setOrigin('');\n setLanguages([]);\n setTags([]);\n setDatedAt('');\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 createItem.mutateAsync({\n title: title.trim(),\n description: description.trim() || null,\n kind,\n accessLevel,\n collectionId: pickedCollection || null,\n fileId: file?.id ?? null,\n provenance: provenance.trim() || null,\n origin: origin.trim() || null,\n languages: languages.length ? languages : null,\n tags: tags.length ? tags : null,\n datedAt: dateToIso(datedAt),\n });\n notify.success('Artifact added', 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>Add an artifact</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Catalogue a manuscript, photograph, recording, map, object or document with its\n provenance.\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>File</Label>\n <MediaDropzone\n folder={FILE_FOLDERS.artifacts}\n accept=\"image/*,audio/*,video/*,application/pdf\"\n promptLabel=\"Drag and drop the artifact file here\"\n hint=\"Image, audio, video or PDF, up to 25 MB. One file per artifact.\"\n value={file}\n onUploaded={(ref) => setFile(ref)}\n onClear={() => setFile(null)}\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-title\">\n Title <span className=\"text-status-error-text\">*</span>\n </Label>\n <Input\n id=\"item-title\"\n value={title}\n maxLength={TITLE_MAX}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Palm-leaf manuscript, 18th c.\"\n />\n <Counter length={title.length} max={TITLE_MAX} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label>Kind</Label>\n <Select value={kind} onValueChange={(v) => setKind(v as TcpArchiveItemKind)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {KINDS.map((k) => (\n <SelectItem key={k} value={k}>\n {formatEnumLabel(k)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\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>\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=\"item-desc\">Description</Label>\n <Textarea\n id=\"item-desc\"\n value={description}\n maxLength={DESCRIPTION_MAX}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n />\n <Counter length={description.length} max={DESCRIPTION_MAX} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-origin\">Origin</Label>\n <Input\n id=\"item-origin\"\n value={origin}\n maxLength={ORIGIN_MAX}\n onChange={(e) => setOrigin(e.target.value)}\n placeholder=\"Place of origin\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-dated\">Dated</Label>\n <Input\n id=\"item-dated\"\n type=\"date\"\n value={datedAt}\n onChange={(e) => setDatedAt(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-provenance\">Provenance</Label>\n <Textarea\n id=\"item-provenance\"\n value={provenance}\n maxLength={PROVENANCE_MAX}\n onChange={(e) => setProvenance(e.target.value)}\n placeholder=\"Chain of custody / source history\"\n rows={2}\n />\n <Counter length={provenance.length} max={PROVENANCE_MAX} />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-langs\">Languages</Label>\n <TagInput\n id=\"item-langs\"\n value={languages}\n onChange={setLanguages}\n placeholder=\"e.g. Tamil, Sanskrit…\"\n preserveCase\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-tags\">Tags</Label>\n <TagInput id=\"item-tags\" value={tags} onChange={setTags} placeholder=\"Add tags…\" />\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={createItem.isPending}>\n Add artifact\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;;;;AAmCA,IAAM,KAA8B;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,KAAkC;CAAC;CAAU;CAAa;CAAc;CAAY,EAEpF,IAA8C;CAClD,QAAQ;CACR,WAAW;CACX,YAAY;CACZ,WAAW;CACZ,EAEK,IAAY,KACZ,IAAkB,KAClB,IAAiB,KACjB,KAAa,KAEb,MAAa,MAAiC;AAClD,KAAI,CAAC,EAAO,QAAO;CACnB,IAAM,IAAI,IAAI,KAAK,EAAM;AACzB,QAAO,OAAO,MAAM,EAAE,SAAS,CAAC,GAAG,OAAO,EAAE,aAAa;;AAW3D,SAAgB,EAAwB,EACtC,SACA,iBACA,iBACA,gBAC+B;CAC/B,IAAM,IAAa,IAAyB,EACtC,IAAmB,EAAkB,EAAE,OAAO,KAAK,CAAC,EAEpD,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAM,KAAW,EAA6B,WAAW,EAC1D,CAAC,GAAa,KAAkB,EAAyB,SAAS,EAClE,CAAC,GAAkB,KAAuB,EAAS,KAAgB,GAAG,EACtE,CAAC,GAAM,KAAW,EAA6B,KAAK,EACpD,CAAC,GAAY,KAAiB,EAAS,GAAG,EAC1C,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAW,KAAgB,EAAmB,EAAE,CAAC,EAClD,CAAC,GAAM,KAAW,EAAmB,EAAE,CAAC,EACxC,CAAC,GAAS,KAAc,EAAS,GAAG,EACpC,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,WAAc;AAYlB,EAXA,EAAS,GAAG,EACZ,EAAe,GAAG,EAClB,EAAQ,WAAW,EACnB,EAAe,SAAS,EACxB,EAAoB,KAAgB,GAAG,EACvC,EAAQ,KAAK,EACb,EAAc,GAAG,EACjB,EAAU,GAAG,EACb,EAAa,EAAE,CAAC,EAChB,EAAQ,EAAE,CAAC,EACX,EAAW,GAAG,EACd,EAAS,KAAK;;AA+BhB,QACE,kBAAC,IAAD;EAAwB;EAAoB;YAC1C,kBAAC,IAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAuB,mBAAuC,CAAA,EAC9D,kBAAC,IAAD,EAAA,UAA6B,+FAGC,CAAA,CACP,EAAA,CAAA;IAEzB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,QAAY,CAAA,EACnB,kBAAC,GAAD;QACE,QAAQ,EAAa;QACrB,QAAO;QACP,aAAY;QACZ,MAAK;QACL,OAAO;QACP,aAAa,MAAQ,EAAQ,EAAI;QACjC,eAAe,EAAQ,KAAK;QAC5B,CAAA,CACE;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAO,SAAQ;mBAAf,CAA4B,UACpB,kBAAC,QAAD;UAAM,WAAU;oBAAyB;UAAQ,CAAA,CACjD;;QACR,kBAAC,GAAD;SACE,IAAG;SACH,OAAO;SACP,WAAW;SACX,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;SACzC,aAAY;SACZ,CAAA;QACF,kBAAC,GAAD;SAAS,QAAQ,EAAM;SAAQ,KAAK;SAAa,CAAA;QAC7C;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,QAAY,CAAA,EACnB,kBAAC,GAAD;SAAQ,OAAO;SAAM,gBAAgB,MAAM,EAAQ,EAAwB;mBAA3E,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAe,CAAA,EACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,GAAM,KAAK,MACV,kBAAC,GAAD;UAAoB,OAAO;oBACxB,EAAgB,EAAE;UACR,EAFI,EAEJ,CACb,EACY,CAAA,CACT;WACL;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD,EAAA,UAAO,gBAAoB,CAAA;SAC3B,kBAAC,GAAD;UACE,OAAO;UACP,gBAAgB,MAAM,EAAe,EAAoB;oBAF3D,CAIE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAe,CAAA,EACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,GAAc,KAAK,MAClB,kBAAC,GAAD;WAAoB,OAAO;qBACxB,EAAgB,EAAE;WACR,EAFI,EAEJ,CACb,EACY,CAAA,CACT;;SACT,kBAAC,KAAD;UAAG,WAAU;oBAA8B,EAAY;UAAiB,CAAA;SACpE;UACF;;MAEL,CAAC,KACA,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,cAAkB,CAAA,EACzB,kBAAC,GAAD;QAAQ,OAAO;QAAkB,eAAe;kBAAhD,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,aAAY,kCAAmC,CAAA,EAC9C,CAAA,EAChB,kBAAC,GAAD,EAAA,WACI,EAAiB,QAAQ,EAAE,EAAE,KAAK,MAClC,kBAAC,GAAD;SAAgC,OAAO,EAAW;mBAC/C,EAAW;SACD,EAFI,EAAW,GAEf,CACb,EACY,CAAA,CACT;UACL;;MAGR,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAO,SAAQ;mBAAY;SAAmB,CAAA;QAC9C,kBAAC,GAAD;SACE,IAAG;SACH,OAAO;SACP,WAAW;SACX,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;SAC/C,MAAM;SACN,CAAA;QACF,kBAAC,GAAD;SAAS,QAAQ,EAAY;SAAQ,KAAK;SAAmB,CAAA;QACzD;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAc;SAAc,CAAA,EAC3C,kBAAC,GAAD;SACE,IAAG;SACH,OAAO;SACP,WAAW;SACX,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;SAC1C,aAAY;SACZ,CAAA,CACE;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAa;SAAa,CAAA,EACzC,kBAAC,GAAD;SACE,IAAG;SACH,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAW,EAAE,OAAO,MAAM;SAC3C,CAAA,CACE;UACF;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAO,SAAQ;mBAAkB;SAAkB,CAAA;QACnD,kBAAC,GAAD;SACE,IAAG;SACH,OAAO;SACP,WAAW;SACX,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;SAC9C,aAAY;SACZ,MAAM;SACN,CAAA;QACF,kBAAC,GAAD;SAAS,QAAQ,EAAW;SAAQ,KAAK;SAAkB,CAAA;QACvD;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAa;QAAiB,CAAA,EAC7C,kBAAC,GAAD;QACE,IAAG;QACH,OAAO;QACP,UAAU;QACV,aAAY;QACZ,cAAA;QACA,CAAA,CACE;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAY;QAAY,CAAA,EACvC,kBAAC,GAAD;QAAU,IAAG;QAAY,OAAO;QAAM,UAAU;QAAS,aAAY;QAAc,CAAA,CAC/E;;MAEL,KAAS,kBAAC,KAAD;OAAG,WAAU;iBAAkC;OAAU,CAAA;MAC/D;;IAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAa,GAAM;eAAE;KAErD,CAAA,EACT,kBAAC,GAAD;KAAQ,SAhMD,YAAY;AACzB,UAAI,CAAC,EAAM,MAAM,EAAE;AACjB,SAAS,uBAAuB;AAChC;;AAEF,UAAI;OACF,IAAM,IAAU,MAAM,EAAW,YAAY;QAC3C,OAAO,EAAM,MAAM;QACnB,aAAa,EAAY,MAAM,IAAI;QACnC;QACA;QACA,cAAc,KAAoB;QAClC,QAAQ,GAAM,MAAM;QACpB,YAAY,EAAW,MAAM,IAAI;QACjC,QAAQ,EAAO,MAAM,IAAI;QACzB,WAAW,EAAU,SAAS,IAAY;QAC1C,MAAM,EAAK,SAAS,IAAO;QAC3B,SAAS,GAAU,EAAQ;QAC5B,CAAC;AAIF,OAHA,GAAO,QAAQ,kBAAkB,EAAQ,MAAM,EAC/C,EAAa,GAAM,EACnB,IAAO,EACP,IAAY,EAAQ;eACb,GAAK;AACZ,SAAS,GAAc,EAAI,CAAC;;;KAwKC,UAAU,EAAW;eAAW;KAEhD,CAAA,CACc,EAAA,CAAA;IACD;;EACT,CAAA"}
@@ -1,7 +1,7 @@
1
1
  import { FILE_FOLDERS as e } from "../hooks/useFiles.js";
2
2
  import { useTcpCreateCollection as t } from "../hooks/useTcpApi.js";
3
- import "../hooks/index.js";
4
3
  import { describeError as n, notify as r } from "../lib/toast.js";
4
+ import "../hooks/index.js";
5
5
  import { slugify as i } from "../utils/slug.js";
6
6
  import { Counter as a, MediaDropzone as o } from "./RecordOralHistoryDialog.js";
7
7
  import { useState as s } from "react";