@burdenoff/microfe-theculturepost 2026.721.3 → 2026.801.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 (57) hide show
  1. package/dist/theculturepost/TheCulturePostRoutes.js +1 -1
  2. package/dist/theculturepost/TheCulturePostRoutes.js.map +1 -1
  3. package/dist/theculturepost/components/AddCitationDialog.js +1 -1
  4. package/dist/theculturepost/components/AudioRecorderControl.d.ts +28 -0
  5. package/dist/theculturepost/components/AudioRecorderControl.d.ts.map +1 -0
  6. package/dist/theculturepost/components/AudioRecorderControl.js +116 -0
  7. package/dist/theculturepost/components/AudioRecorderControl.js.map +1 -0
  8. package/dist/theculturepost/components/CitationList.js +1 -1
  9. package/dist/theculturepost/components/CreateArchiveItemDialog.js +77 -77
  10. package/dist/theculturepost/components/CreateArchiveItemDialog.js.map +1 -1
  11. package/dist/theculturepost/components/CreateCollectionDialog.js +1 -1
  12. package/dist/theculturepost/components/CreateStoryDialog.js +1 -1
  13. package/dist/theculturepost/components/CreateTopicDialog.js +1 -1
  14. package/dist/theculturepost/components/OpenDiscussionDialog.js +1 -1
  15. package/dist/theculturepost/components/PendingOralHistoryPanel.d.ts +10 -0
  16. package/dist/theculturepost/components/PendingOralHistoryPanel.d.ts.map +1 -0
  17. package/dist/theculturepost/components/PendingOralHistoryPanel.js +67 -0
  18. package/dist/theculturepost/components/PendingOralHistoryPanel.js.map +1 -0
  19. package/dist/theculturepost/components/RecordOralHistoryDialog.d.ts.map +1 -1
  20. package/dist/theculturepost/components/RecordOralHistoryDialog.js +276 -174
  21. package/dist/theculturepost/components/RecordOralHistoryDialog.js.map +1 -1
  22. package/dist/theculturepost/components/ReplyThread.js +1 -1
  23. package/dist/theculturepost/components/StoryCard.js +1 -1
  24. package/dist/theculturepost/components/index.js +1 -1
  25. package/dist/theculturepost/context/TheCulturePostContext.js +1 -1
  26. package/dist/theculturepost/context/TheCulturePostContext.js.map +1 -1
  27. package/dist/theculturepost/hooks/index.d.ts +1 -0
  28. package/dist/theculturepost/hooks/index.d.ts.map +1 -1
  29. package/dist/theculturepost/hooks/index.js +1 -0
  30. package/dist/theculturepost/hooks/usePendingOralHistorySync.d.ts +10 -0
  31. package/dist/theculturepost/hooks/usePendingOralHistorySync.d.ts.map +1 -0
  32. package/dist/theculturepost/hooks/usePendingOralHistorySync.js +59 -0
  33. package/dist/theculturepost/hooks/usePendingOralHistorySync.js.map +1 -0
  34. package/dist/theculturepost/hooks/useTcpApi.js +1 -1
  35. package/dist/theculturepost/hooks/useTcpApi.js.map +1 -1
  36. package/dist/theculturepost/lib/offlineOralHistoryQueue.d.ts +54 -0
  37. package/dist/theculturepost/lib/offlineOralHistoryQueue.d.ts.map +1 -0
  38. package/dist/theculturepost/lib/offlineOralHistoryQueue.js +68 -0
  39. package/dist/theculturepost/lib/offlineOralHistoryQueue.js.map +1 -0
  40. package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js +2 -2
  41. package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js.map +1 -1
  42. package/dist/theculturepost/pages/archive/ArchivePage.d.ts.map +1 -1
  43. package/dist/theculturepost/pages/archive/ArchivePage.js +99 -95
  44. package/dist/theculturepost/pages/archive/ArchivePage.js.map +1 -1
  45. package/dist/theculturepost/pages/archive/CollectionDetailPage.js +1 -1
  46. package/dist/theculturepost/pages/archive/CollectionDetailPage.js.map +1 -1
  47. package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js +2 -2
  48. package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js.map +1 -1
  49. package/dist/theculturepost/pages/stories/StoryDetailPage.js +2 -2
  50. package/dist/theculturepost/pages/stories/StoryDetailPage.js.map +1 -1
  51. package/dist/theculturepost/pages/topics/TopicDetailPage.js +2 -2
  52. package/dist/theculturepost/pages/topics/TopicDetailPage.js.map +1 -1
  53. package/dist/theculturepost/types/index.d.ts +8 -0
  54. package/dist/theculturepost/types/index.d.ts.map +1 -1
  55. package/dist/theculturepost/utils/nativeBridge.js +10 -7
  56. package/dist/theculturepost/utils/nativeBridge.js.map +1 -1
  57. package/package.json +6 -6
@@ -3,7 +3,7 @@ import { usePageAnalytics as t } from "./hooks/usePageAnalytics.js";
3
3
  import { HomePage as n } from "./pages/HomePage.js";
4
4
  import { Suspense as r, lazy as i } from "react";
5
5
  import { FeatureGate as a } from "@burdenoff/fe-libs/shared/feature-flags";
6
- import { Navigate as o, Route as s, Routes as c, useLocation as l } from "react-router-dom";
6
+ import { Navigate as o, Route as s, Routes as c, useLocation as l } from "react-router";
7
7
  import { jsx as u, jsxs as d } from "react/jsx-runtime";
8
8
  import { Spinner as f } from "@burdenoff/fe-libs/ui";
9
9
  //#region src/theculturepost/TheCulturePostRoutes.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"TheCulturePostRoutes.js","names":[],"sources":["../../src/theculturepost/TheCulturePostRoutes.tsx"],"sourcesContent":["/**\n * TheCulturePost Routes\n * @module theculturepost\n *\n * Domain routes. The editorial home page is eager-loaded (primary landing); all\n * other pages are lazy-loaded. Every route maps to a real `tcp*` backend\n * capability.\n */\n\nimport { type FC, lazy, Suspense, type ReactElement } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router-dom';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useTheCulturePost } from './context/TheCulturePostContext';\nimport { usePageAnalytics } from './hooks/usePageAnalytics';\n\nconst FEATURE = {\n home: 'theculturepost.home',\n overview: 'theculturepost.overview',\n stories: 'theculturepost.stories',\n topics: 'theculturepost.topics',\n timeline: 'theculturepost.timeline',\n discussions: 'theculturepost.discussions',\n archive: 'theculturepost.archive',\n saved: 'theculturepost.saved',\n} as const;\n\nfunction FeatureDisabledFallback(): ReactElement {\n return (\n <div className=\"flex min-h-[400px] flex-col items-center justify-center gap-3 text-text-secondary\">\n <span className=\"text-lg font-semibold text-text-primary\">Feature unavailable</span>\n <span className=\"text-sm\">This page is disabled in your workspace.</span>\n </div>\n );\n}\n\nfunction gated(element: ReactElement, flag: string): ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\n// Eager — primary landing\nimport { HomePage } from './pages/HomePage';\n\n// Stories\nconst StoriesPage = lazy(() =>\n import('./pages/stories/StoriesPage').then((m) => ({ default: m.StoriesPage }))\n);\nconst StoryDetailPage = lazy(() =>\n import('./pages/stories/StoryDetailPage').then((m) => ({ default: m.StoryDetailPage }))\n);\nconst SavedStoriesPage = lazy(() =>\n import('./pages/stories/SavedStoriesPage').then((m) => ({ default: m.SavedStoriesPage }))\n);\n\n// Topics\nconst TopicsPage = lazy(() =>\n import('./pages/topics/TopicsPage').then((m) => ({ default: m.TopicsPage }))\n);\nconst TopicDetailPage = lazy(() =>\n import('./pages/topics/TopicDetailPage').then((m) => ({ default: m.TopicDetailPage }))\n);\n\n// Discussions\nconst DiscussionsPage = lazy(() =>\n import('./pages/discussions/DiscussionsPage').then((m) => ({ default: m.DiscussionsPage }))\n);\nconst DiscussionDetailPage = lazy(() =>\n import('./pages/discussions/DiscussionDetailPage').then((m) => ({\n default: m.DiscussionDetailPage,\n }))\n);\n\n// Timeline\nconst TimelinePage = lazy(() =>\n import('./pages/timeline/TimelinePage').then((m) => ({ default: m.TimelinePage }))\n);\n\n// Governance / Overview\nconst GovernancePage = lazy(() =>\n import('./pages/governance/GovernancePage').then((m) => ({ default: m.GovernancePage }))\n);\n\n// Archive\nconst ArchivePage = lazy(() =>\n import('./pages/archive/ArchivePage').then((m) => ({ default: m.ArchivePage }))\n);\nconst CollectionDetailPage = lazy(() =>\n import('./pages/archive/CollectionDetailPage').then((m) => ({ default: m.CollectionDetailPage }))\n);\nconst ArchiveItemDetailPage = lazy(() =>\n import('./pages/archive/ArchiveItemDetailPage').then((m) => ({\n default: m.ArchiveItemDetailPage,\n }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nexport const TheCulturePostRoutes: FC = () => {\n const { basePath, isStandalone } = useTheCulturePost();\n const location = useLocation();\n\n usePageAnalytics();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={gated(<HomePage />, FEATURE.home)} />\n\n <Route path=\"overview\" element={gated(<GovernancePage />, FEATURE.overview)} />\n\n <Route path=\"stories\" element={gated(<StoriesPage />, FEATURE.stories)} />\n <Route path=\"stories/:id\" element={gated(<StoryDetailPage />, FEATURE.stories)} />\n\n <Route path=\"saved\" element={gated(<SavedStoriesPage />, FEATURE.saved)} />\n\n <Route path=\"topics\" element={gated(<TopicsPage />, FEATURE.topics)} />\n <Route path=\"topics/:id\" element={gated(<TopicDetailPage />, FEATURE.topics)} />\n\n <Route path=\"timeline\" element={gated(<TimelinePage />, FEATURE.timeline)} />\n\n <Route path=\"discussions\" element={gated(<DiscussionsPage />, FEATURE.discussions)} />\n <Route\n path=\"discussions/:id\"\n element={gated(<DiscussionDetailPage />, FEATURE.discussions)}\n />\n\n <Route path=\"archive\" element={gated(<ArchivePage />, FEATURE.archive)} />\n <Route\n path=\"archive/collections/:id\"\n element={gated(<CollectionDetailPage />, FEATURE.archive)}\n />\n <Route\n path=\"archive/items/:id\"\n element={gated(<ArchiveItemDetailPage />, FEATURE.archive)}\n />\n\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"TheCulturePostRoutes.js","names":[],"sources":["../../src/theculturepost/TheCulturePostRoutes.tsx"],"sourcesContent":["/**\n * TheCulturePost Routes\n * @module theculturepost\n *\n * Domain routes. The editorial home page is eager-loaded (primary landing); all\n * other pages are lazy-loaded. Every route maps to a real `tcp*` backend\n * capability.\n */\n\nimport { type FC, lazy, Suspense, type ReactElement } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useTheCulturePost } from './context/TheCulturePostContext';\nimport { usePageAnalytics } from './hooks/usePageAnalytics';\n\nconst FEATURE = {\n home: 'theculturepost.home',\n overview: 'theculturepost.overview',\n stories: 'theculturepost.stories',\n topics: 'theculturepost.topics',\n timeline: 'theculturepost.timeline',\n discussions: 'theculturepost.discussions',\n archive: 'theculturepost.archive',\n saved: 'theculturepost.saved',\n} as const;\n\nfunction FeatureDisabledFallback(): ReactElement {\n return (\n <div className=\"flex min-h-[400px] flex-col items-center justify-center gap-3 text-text-secondary\">\n <span className=\"text-lg font-semibold text-text-primary\">Feature unavailable</span>\n <span className=\"text-sm\">This page is disabled in your workspace.</span>\n </div>\n );\n}\n\nfunction gated(element: ReactElement, flag: string): ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\n// Eager — primary landing\nimport { HomePage } from './pages/HomePage';\n\n// Stories\nconst StoriesPage = lazy(() =>\n import('./pages/stories/StoriesPage').then((m) => ({ default: m.StoriesPage }))\n);\nconst StoryDetailPage = lazy(() =>\n import('./pages/stories/StoryDetailPage').then((m) => ({ default: m.StoryDetailPage }))\n);\nconst SavedStoriesPage = lazy(() =>\n import('./pages/stories/SavedStoriesPage').then((m) => ({ default: m.SavedStoriesPage }))\n);\n\n// Topics\nconst TopicsPage = lazy(() =>\n import('./pages/topics/TopicsPage').then((m) => ({ default: m.TopicsPage }))\n);\nconst TopicDetailPage = lazy(() =>\n import('./pages/topics/TopicDetailPage').then((m) => ({ default: m.TopicDetailPage }))\n);\n\n// Discussions\nconst DiscussionsPage = lazy(() =>\n import('./pages/discussions/DiscussionsPage').then((m) => ({ default: m.DiscussionsPage }))\n);\nconst DiscussionDetailPage = lazy(() =>\n import('./pages/discussions/DiscussionDetailPage').then((m) => ({\n default: m.DiscussionDetailPage,\n }))\n);\n\n// Timeline\nconst TimelinePage = lazy(() =>\n import('./pages/timeline/TimelinePage').then((m) => ({ default: m.TimelinePage }))\n);\n\n// Governance / Overview\nconst GovernancePage = lazy(() =>\n import('./pages/governance/GovernancePage').then((m) => ({ default: m.GovernancePage }))\n);\n\n// Archive\nconst ArchivePage = lazy(() =>\n import('./pages/archive/ArchivePage').then((m) => ({ default: m.ArchivePage }))\n);\nconst CollectionDetailPage = lazy(() =>\n import('./pages/archive/CollectionDetailPage').then((m) => ({ default: m.CollectionDetailPage }))\n);\nconst ArchiveItemDetailPage = lazy(() =>\n import('./pages/archive/ArchiveItemDetailPage').then((m) => ({\n default: m.ArchiveItemDetailPage,\n }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nexport const TheCulturePostRoutes: FC = () => {\n const { basePath, isStandalone } = useTheCulturePost();\n const location = useLocation();\n\n usePageAnalytics();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={gated(<HomePage />, FEATURE.home)} />\n\n <Route path=\"overview\" element={gated(<GovernancePage />, FEATURE.overview)} />\n\n <Route path=\"stories\" element={gated(<StoriesPage />, FEATURE.stories)} />\n <Route path=\"stories/:id\" element={gated(<StoryDetailPage />, FEATURE.stories)} />\n\n <Route path=\"saved\" element={gated(<SavedStoriesPage />, FEATURE.saved)} />\n\n <Route path=\"topics\" element={gated(<TopicsPage />, FEATURE.topics)} />\n <Route path=\"topics/:id\" element={gated(<TopicDetailPage />, FEATURE.topics)} />\n\n <Route path=\"timeline\" element={gated(<TimelinePage />, FEATURE.timeline)} />\n\n <Route path=\"discussions\" element={gated(<DiscussionsPage />, FEATURE.discussions)} />\n <Route\n path=\"discussions/:id\"\n element={gated(<DiscussionDetailPage />, FEATURE.discussions)}\n />\n\n <Route path=\"archive\" element={gated(<ArchivePage />, FEATURE.archive)} />\n <Route\n path=\"archive/collections/:id\"\n element={gated(<CollectionDetailPage />, FEATURE.archive)}\n />\n <Route\n path=\"archive/items/:id\"\n element={gated(<ArchiveItemDetailPage />, FEATURE.archive)}\n />\n\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -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