@burdenoff/microfe-tours 2026.828.1 → 2026.830.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tours/ToursAdminRoot.js +28 -27
- package/dist/tours/ToursAdminRoot.js.map +1 -1
- package/dist/tours/ToursAdminRoutes.js +56 -55
- package/dist/tours/ToursAdminRoutes.js.map +1 -1
- package/dist/tours/ToursUserRoot.js +34 -29
- package/dist/tours/ToursUserRoot.js.map +1 -1
- package/dist/tours/ToursUserRoutes.js +33 -33
- package/dist/tours/ToursUserRoutes.js.map +1 -1
- package/dist/tours/components/MarkdownEditor.js +85 -80
- package/dist/tours/components/MarkdownEditor.js.map +1 -1
- package/dist/tours/components/RouteHelpers.js +47 -42
- package/dist/tours/components/RouteHelpers.js.map +1 -1
- package/dist/tours/components/ToursAdminNavRegistrar.js +32 -27
- package/dist/tours/components/ToursAdminNavRegistrar.js.map +1 -1
- package/dist/tours/hooks/useStepSequencer.js +45 -36
- package/dist/tours/hooks/useStepSequencer.js.map +1 -1
- package/dist/tours/hooks/useTourBuilder.js +106 -93
- package/dist/tours/hooks/useTourBuilder.js.map +1 -1
- package/dist/tours/hooks/useTourPreview.js +51 -41
- package/dist/tours/hooks/useTourPreview.js.map +1 -1
- package/dist/tours/hooks/useToursList.js +28 -27
- package/dist/tours/hooks/useToursList.js.map +1 -1
- package/dist/tours/i18n.js +187 -5
- package/dist/tours/i18n.js.map +1 -1
- package/dist/tours/pages/AnalyticsDashboardPage.js +108 -109
- package/dist/tours/pages/AnalyticsDashboardPage.js.map +1 -1
- package/dist/tours/pages/MediaLibraryPage.js +58 -61
- package/dist/tours/pages/MediaLibraryPage.js.map +1 -1
- package/dist/tours/pages/MyToursPage.js +5 -5
- package/dist/tours/pages/MyToursProgressPage.js +5 -5
- package/dist/tours/pages/SchedulerPage.js +78 -81
- package/dist/tours/pages/SchedulerPage.js.map +1 -1
- package/dist/tours/pages/StepEditorPage.js +32 -32
- package/dist/tours/pages/StepEditorPage.js.map +1 -1
- package/dist/tours/pages/SystemAnalyticsPage.js +75 -79
- package/dist/tours/pages/SystemAnalyticsPage.js.map +1 -1
- package/dist/tours/pages/TemplateLibraryPage.js +8 -8
- package/dist/tours/pages/TourBuilderPage.js +279 -279
- package/dist/tours/pages/TourBuilderPage.js.map +1 -1
- package/dist/tours/pages/TourPreviewPage.js +6 -6
- package/dist/tours/pages/ToursListPage.js +155 -155
- package/dist/tours/pages/ToursListPage.js.map +1 -1
- package/dist/tours/store/toursStore.js +11 -7
- package/dist/tours/store/toursStore.js.map +1 -1
- package/dist/tours/utils/index.js +32 -19
- package/dist/tours/utils/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,40 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { useToursI18n as e } from "../i18n.js";
|
|
2
|
+
import { useToursContext as t } from "../providers/ToursProvider.js";
|
|
3
|
+
import { useToursPermissionAccess as n } from "../hooks/useToursPermissionAccess.js";
|
|
4
|
+
import { TOUR_PERMISSIONS as r } from "../../constants/permissions.js";
|
|
5
|
+
import { emitToursApiFailed as i } from "../telemetry.js";
|
|
6
|
+
import { useToursFormatters as a } from "../utils/index.js";
|
|
7
|
+
import { useMediaLibrary as o } from "../hooks/useMediaLibrary.js";
|
|
8
|
+
import { useEffect as s, useState as c } from "react";
|
|
8
9
|
import { PermissionGate as l } from "@burdenoff/fe-libs/shared/components";
|
|
9
10
|
import { FileText as u, Image as d, Music as f, Search as p, Trash2 as m, Upload as h, Video as g } from "lucide-react";
|
|
10
11
|
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
11
12
|
import { GlassCard as y, IllustratedEmptyState as b, PagePurpose as x } from "@burdenoff/fe-libs/ui";
|
|
12
|
-
import {
|
|
13
|
-
import { useEventBus as C } from "@burdenoff/fe-libs/shared/events";
|
|
13
|
+
import { useEventBus as S } from "@burdenoff/fe-libs/shared/events";
|
|
14
14
|
//#region src/tours/pages/MediaLibraryPage.tsx
|
|
15
|
-
function
|
|
16
|
-
let {
|
|
17
|
-
let n = w(e);
|
|
18
|
-
return n === e ? t : n;
|
|
19
|
-
}, [w]), E = e(), D = C(), { hasPermission: O, isLoading: k } = t(), A = O(n.TOUR_MEDIA_LIST), j = O(n.TOUR_MEDIA_DELETE), { media: M, fetchMedia: N, uploadMedia: P, deleteMedia: F, uploadProgress: I } = a(), [L, R] = c(""), [z, B] = c("all");
|
|
15
|
+
function C() {
|
|
16
|
+
let { tr: C } = e(), w = t(), T = S(), { hasPermission: E, isLoading: D } = n(), O = E(r.TOUR_MEDIA_LIST), k = E(r.TOUR_MEDIA_DELETE), { media: A, fetchMedia: j, uploadMedia: M, deleteMedia: N, uploadProgress: P } = o(), { formatFileSize: F } = a(), [I, L] = c(""), [R, z] = c("all");
|
|
20
17
|
s(() => {
|
|
21
|
-
|
|
18
|
+
D || !O || j(R === "all" ? void 0 : R, I);
|
|
22
19
|
}, [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
j,
|
|
21
|
+
R,
|
|
22
|
+
I,
|
|
23
|
+
D,
|
|
24
|
+
O
|
|
28
25
|
]);
|
|
29
|
-
let
|
|
26
|
+
let B = async (e) => {
|
|
30
27
|
e && (await Promise.all(Array.from(e).map(async (e) => {
|
|
31
28
|
try {
|
|
32
|
-
await
|
|
29
|
+
await M(e);
|
|
33
30
|
} catch {
|
|
34
|
-
|
|
31
|
+
i(T, "media.upload", w);
|
|
35
32
|
}
|
|
36
|
-
})),
|
|
37
|
-
},
|
|
33
|
+
})), j(R === "all" ? void 0 : R, I));
|
|
34
|
+
}, V = (e) => {
|
|
38
35
|
switch (e) {
|
|
39
36
|
case "IMAGE": return /* @__PURE__ */ _(d, { size: 20 });
|
|
40
37
|
case "VIDEO": return /* @__PURE__ */ _(g, { size: 20 });
|
|
@@ -49,30 +46,30 @@ function w() {
|
|
|
49
46
|
className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3",
|
|
50
47
|
children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("h1", {
|
|
51
48
|
className: "text-xl sm:text-2xl font-bold",
|
|
52
|
-
children:
|
|
49
|
+
children: C("mediaLibrary.title", "Media Library")
|
|
53
50
|
}), /* @__PURE__ */ _("p", {
|
|
54
51
|
className: "text-sm text-text-secondary",
|
|
55
|
-
children:
|
|
52
|
+
children: C("mediaLibrary.subtitle", "Upload and manage media for tour steps")
|
|
56
53
|
})] }), /* @__PURE__ */ _(l, {
|
|
57
|
-
permission:
|
|
54
|
+
permission: r.TOUR_MEDIA_CREATE,
|
|
58
55
|
children: /* @__PURE__ */ v("label", {
|
|
59
56
|
className: "w-full sm:w-auto px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 cursor-pointer flex items-center justify-center gap-2",
|
|
60
57
|
children: [
|
|
61
58
|
/* @__PURE__ */ _(h, { size: 20 }),
|
|
62
|
-
|
|
59
|
+
C("mediaLibrary.uploadMedia", "Upload Media"),
|
|
63
60
|
/* @__PURE__ */ _("input", {
|
|
64
61
|
type: "file",
|
|
65
62
|
multiple: !0,
|
|
66
63
|
accept: "image/*,video/*,audio/*,.pdf,.doc,.docx",
|
|
67
|
-
onChange: (e) =>
|
|
64
|
+
onChange: (e) => B(e.target.files),
|
|
68
65
|
className: "hidden",
|
|
69
|
-
"aria-label":
|
|
66
|
+
"aria-label": C("mediaLibrary.uploadMedia", "Upload Media")
|
|
70
67
|
})
|
|
71
68
|
]
|
|
72
69
|
})
|
|
73
70
|
})]
|
|
74
71
|
}),
|
|
75
|
-
/* @__PURE__ */ _(x, { children:
|
|
72
|
+
/* @__PURE__ */ _(x, { children: C("mediaLibrary.purpose", "Store and organise the images, videos, audio, and documents you attach to tour steps. Upload an asset here once, then reference it from any step — handy for product screenshots, short demo clips, or callout graphics that make a walkthrough clearer.") }),
|
|
76
73
|
/* @__PURE__ */ v("div", {
|
|
77
74
|
className: "flex flex-col sm:flex-row gap-3",
|
|
78
75
|
children: [/* @__PURE__ */ v("div", {
|
|
@@ -82,50 +79,50 @@ function w() {
|
|
|
82
79
|
size: 20
|
|
83
80
|
}), /* @__PURE__ */ _("input", {
|
|
84
81
|
type: "text",
|
|
85
|
-
placeholder:
|
|
86
|
-
value:
|
|
87
|
-
onChange: (e) =>
|
|
82
|
+
placeholder: C("mediaLibrary.searchPlaceholder", "Search media..."),
|
|
83
|
+
value: I,
|
|
84
|
+
onChange: (e) => L(e.target.value),
|
|
88
85
|
className: "w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface",
|
|
89
|
-
"aria-label":
|
|
86
|
+
"aria-label": C("mediaLibrary.searchPlaceholder", "Search media...")
|
|
90
87
|
})]
|
|
91
88
|
}), /* @__PURE__ */ v("select", {
|
|
92
|
-
value:
|
|
93
|
-
onChange: (e) =>
|
|
89
|
+
value: R,
|
|
90
|
+
onChange: (e) => z(e.target.value),
|
|
94
91
|
className: "px-4 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface",
|
|
95
|
-
"aria-label":
|
|
92
|
+
"aria-label": C("mediaLibrary.filterByType", "Filter by media type"),
|
|
96
93
|
children: [
|
|
97
94
|
/* @__PURE__ */ _("option", {
|
|
98
95
|
value: "all",
|
|
99
|
-
children:
|
|
96
|
+
children: C("mediaLibrary.allTypes", "All Types")
|
|
100
97
|
}),
|
|
101
98
|
/* @__PURE__ */ _("option", {
|
|
102
99
|
value: "IMAGE",
|
|
103
|
-
children:
|
|
100
|
+
children: C("mediaLibrary.typeImages", "Images")
|
|
104
101
|
}),
|
|
105
102
|
/* @__PURE__ */ _("option", {
|
|
106
103
|
value: "VIDEO",
|
|
107
|
-
children:
|
|
104
|
+
children: C("mediaLibrary.typeVideos", "Videos")
|
|
108
105
|
}),
|
|
109
106
|
/* @__PURE__ */ _("option", {
|
|
110
107
|
value: "AUDIO",
|
|
111
|
-
children:
|
|
108
|
+
children: C("mediaLibrary.typeAudio", "Audio")
|
|
112
109
|
}),
|
|
113
110
|
/* @__PURE__ */ _("option", {
|
|
114
111
|
value: "DOCUMENT",
|
|
115
|
-
children:
|
|
112
|
+
children: C("mediaLibrary.typeDocuments", "Documents")
|
|
116
113
|
})
|
|
117
114
|
]
|
|
118
115
|
})]
|
|
119
116
|
}),
|
|
120
|
-
Object.keys(
|
|
117
|
+
Object.keys(P).length > 0 && /* @__PURE__ */ _("div", {
|
|
121
118
|
className: "space-y-2",
|
|
122
|
-
children: Object.entries(
|
|
119
|
+
children: Object.entries(P).map(([e, t]) => /* @__PURE__ */ v("div", {
|
|
123
120
|
className: "border border-border-subtle rounded-md p-3",
|
|
124
121
|
children: [/* @__PURE__ */ v("div", {
|
|
125
122
|
className: "flex items-center justify-between mb-2",
|
|
126
123
|
children: [/* @__PURE__ */ _("span", {
|
|
127
124
|
className: "text-sm",
|
|
128
|
-
children:
|
|
125
|
+
children: C("mediaLibrary.uploading", "Uploading...")
|
|
129
126
|
}), /* @__PURE__ */ v("span", {
|
|
130
127
|
className: "text-sm text-text-secondary",
|
|
131
128
|
children: [t, "%"]
|
|
@@ -139,9 +136,9 @@ function w() {
|
|
|
139
136
|
})]
|
|
140
137
|
}, e))
|
|
141
138
|
}),
|
|
142
|
-
|
|
139
|
+
A.length > 0 ? /* @__PURE__ */ _("div", {
|
|
143
140
|
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4",
|
|
144
|
-
children:
|
|
141
|
+
children: A.map((e) => /* @__PURE__ */ v(y, {
|
|
145
142
|
treatment: "plain",
|
|
146
143
|
className: "overflow-hidden p-0 border-border-seam shadow-[var(--shadow-pop)]",
|
|
147
144
|
children: [/* @__PURE__ */ _("div", {
|
|
@@ -152,7 +149,7 @@ function w() {
|
|
|
152
149
|
className: "w-full h-full object-cover"
|
|
153
150
|
}) : /* @__PURE__ */ _("div", {
|
|
154
151
|
className: "text-text-secondary",
|
|
155
|
-
children:
|
|
152
|
+
children: V(e.type)
|
|
156
153
|
})
|
|
157
154
|
}), /* @__PURE__ */ v("div", {
|
|
158
155
|
className: "p-3",
|
|
@@ -163,37 +160,37 @@ function w() {
|
|
|
163
160
|
}),
|
|
164
161
|
/* @__PURE__ */ v("div", {
|
|
165
162
|
className: "flex items-center justify-between mt-2 text-xs text-text-secondary",
|
|
166
|
-
children: [/* @__PURE__ */ _("span", { children: e.type }), /* @__PURE__ */ _("span", { children:
|
|
163
|
+
children: [/* @__PURE__ */ _("span", { children: e.type }), /* @__PURE__ */ _("span", { children: F(e.size) })]
|
|
167
164
|
}),
|
|
168
|
-
|
|
165
|
+
k && /* @__PURE__ */ v("button", {
|
|
169
166
|
onClick: () => {
|
|
170
|
-
window.confirm(
|
|
167
|
+
window.confirm(C("mediaLibrary.deleteConfirm", "Delete this media?")) && N(e.id);
|
|
171
168
|
},
|
|
172
169
|
type: "button",
|
|
173
170
|
className: "mt-2 w-full p-2 min-h-[44px] text-xs text-status-error-text border border-status-error-border/30 rounded hover:bg-status-error-bg/10 flex items-center justify-center gap-1",
|
|
174
|
-
children: [/* @__PURE__ */ _(m, { size: 12 }),
|
|
171
|
+
children: [/* @__PURE__ */ _(m, { size: 12 }), C("mediaLibrary.delete", "Delete")]
|
|
175
172
|
})
|
|
176
173
|
]
|
|
177
174
|
})]
|
|
178
175
|
}, e.id))
|
|
179
176
|
}) : /* @__PURE__ */ _(b, {
|
|
180
177
|
illustration: "empty-files",
|
|
181
|
-
title:
|
|
182
|
-
description:
|
|
178
|
+
title: C("mediaLibrary.noMedia", "No media files"),
|
|
179
|
+
description: C("mediaLibrary.uploadFirstMedia", "Upload your first media file to get started"),
|
|
183
180
|
action: /* @__PURE__ */ _(l, {
|
|
184
|
-
permission:
|
|
181
|
+
permission: r.TOUR_MEDIA_CREATE,
|
|
185
182
|
children: /* @__PURE__ */ v("label", {
|
|
186
183
|
className: "px-4 py-2 min-h-[44px] bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 cursor-pointer inline-flex items-center justify-center gap-2",
|
|
187
184
|
children: [
|
|
188
185
|
/* @__PURE__ */ _(h, { size: 20 }),
|
|
189
|
-
|
|
186
|
+
C("mediaLibrary.uploadMedia", "Upload Media"),
|
|
190
187
|
/* @__PURE__ */ _("input", {
|
|
191
188
|
type: "file",
|
|
192
189
|
multiple: !0,
|
|
193
190
|
accept: "image/*,video/*,audio/*,.pdf,.doc,.docx",
|
|
194
|
-
onChange: (e) =>
|
|
191
|
+
onChange: (e) => B(e.target.files),
|
|
195
192
|
className: "hidden",
|
|
196
|
-
"aria-label":
|
|
193
|
+
"aria-label": C("mediaLibrary.uploadMedia", "Upload Media")
|
|
197
194
|
})
|
|
198
195
|
]
|
|
199
196
|
})
|
|
@@ -203,6 +200,6 @@ function w() {
|
|
|
203
200
|
});
|
|
204
201
|
}
|
|
205
202
|
//#endregion
|
|
206
|
-
export {
|
|
203
|
+
export { C as MediaLibraryPage };
|
|
207
204
|
|
|
208
205
|
//# sourceMappingURL=MediaLibraryPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaLibraryPage.js","names":[],"sources":["../../../src/tours/pages/MediaLibraryPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { Upload, Search, Image as ImageIcon, Video, Music, FileText, Trash2 } from 'lucide-react';\nimport { PermissionGate } from '@burdenoff/fe-libs/shared/components';\nimport { GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useMediaLibrary } from '../hooks/useMediaLibrary';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport { formatFileSize } from '../utils';\nimport type { TourMediaType } from '../types';\nimport { emitToursApiFailed } from '../telemetry';\n\n/**\n * Media library page\n * Browse, upload, and manage tour media\n */\nexport function MediaLibraryPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const toursContext = useToursContext();\n const eventBus = useEventBus();\n\n const { hasPermission, isLoading: permissionsLoading } = useToursPermissionAccess();\n const canListMedia = hasPermission(TOUR_PERMISSIONS.TOUR_MEDIA_LIST);\n const canDeleteMedia = hasPermission(TOUR_PERMISSIONS.TOUR_MEDIA_DELETE);\n\n const { media, fetchMedia, uploadMedia, deleteMedia, uploadProgress } = useMediaLibrary();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [typeFilter, setTypeFilter] = useState<TourMediaType | 'all'>('all');\n\n useEffect(() => {\n if (permissionsLoading || !canListMedia) return;\n fetchMedia(typeFilter === 'all' ? undefined : typeFilter, searchQuery);\n }, [fetchMedia, typeFilter, searchQuery, permissionsLoading, canListMedia]);\n\n const handleFileUpload = async (files: FileList | null) => {\n if (!files) return;\n\n await Promise.all(\n Array.from(files).map(async (file) => {\n try {\n await uploadMedia(file);\n } catch {\n emitToursApiFailed(eventBus, 'media.upload', toursContext);\n }\n })\n );\n\n // Refresh media list\n fetchMedia(typeFilter === 'all' ? undefined : typeFilter, searchQuery);\n };\n\n const getMediaIcon = (type: TourMediaType) => {\n switch (type) {\n case 'IMAGE':\n return <ImageIcon size={20} />;\n case 'VIDEO':\n return <Video size={20} />;\n case 'AUDIO':\n return <Music size={20} />;\n case 'DOCUMENT':\n return <FileText size={20} />;\n }\n };\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold\">\n {tr('mediaLibrary.title', 'Media Library')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('mediaLibrary.subtitle', 'Upload and manage media for tour steps')}\n </p>\n </div>\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_MEDIA_CREATE}>\n <label className=\"w-full sm:w-auto px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 cursor-pointer flex items-center justify-center gap-2\">\n <Upload size={20} />\n {tr('mediaLibrary.uploadMedia', 'Upload Media')}\n <input\n type=\"file\"\n multiple\n accept=\"image/*,video/*,audio/*,.pdf,.doc,.docx\"\n onChange={(e) => handleFileUpload(e.target.files)}\n className=\"hidden\"\n aria-label={tr('mediaLibrary.uploadMedia', 'Upload Media')}\n />\n </label>\n </PermissionGate>\n </div>\n\n <PagePurpose>\n {tr(\n 'mediaLibrary.purpose',\n 'Store and organise the images, videos, audio, and documents you attach to tour steps. Upload an asset here once, then reference it from any step — handy for product screenshots, short demo clips, or callout graphics that make a walkthrough clearer.'\n )}\n </PagePurpose>\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <div className=\"flex-1 relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n size={20}\n />\n <input\n type=\"text\"\n placeholder={tr('mediaLibrary.searchPlaceholder', 'Search media...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n aria-label={tr('mediaLibrary.searchPlaceholder', 'Search media...')}\n />\n </div>\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value as TourMediaType | 'all')}\n className=\"px-4 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n aria-label={tr('mediaLibrary.filterByType', 'Filter by media type')}\n >\n <option value=\"all\">{tr('mediaLibrary.allTypes', 'All Types')}</option>\n <option value=\"IMAGE\">{tr('mediaLibrary.typeImages', 'Images')}</option>\n <option value=\"VIDEO\">{tr('mediaLibrary.typeVideos', 'Videos')}</option>\n <option value=\"AUDIO\">{tr('mediaLibrary.typeAudio', 'Audio')}</option>\n <option value=\"DOCUMENT\">{tr('mediaLibrary.typeDocuments', 'Documents')}</option>\n </select>\n </div>\n\n {/* Upload Progress */}\n {Object.keys(uploadProgress).length > 0 && (\n <div className=\"space-y-2\">\n {Object.entries(uploadProgress).map(([fileId, progress]) => (\n <div key={fileId} className=\"border border-border-subtle rounded-md p-3\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm\">{tr('mediaLibrary.uploading', 'Uploading...')}</span>\n <span className=\"text-sm text-text-secondary\">{progress}%</span>\n </div>\n <div className=\"w-full bg-bg-sunken rounded-full h-2\">\n <div\n className=\"bg-action-primary-bg h-2 rounded-full transition-all\"\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Media Grid */}\n {media.length > 0 ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4\">\n {media.map((item) => (\n <GlassCard\n key={item.id}\n treatment=\"plain\"\n className=\"overflow-hidden p-0 border-border-seam shadow-[var(--shadow-pop)]\"\n >\n {/* Thumbnail */}\n <div className=\"aspect-video bg-bg-sunken flex items-center justify-center\">\n {item.type === 'IMAGE' ? (\n <img\n src={item.cdnUrl}\n alt={item.altText || item.name}\n className=\"w-full h-full object-cover\"\n />\n ) : (\n <div className=\"text-text-secondary\">{getMediaIcon(item.type)}</div>\n )}\n </div>\n\n {/* Info */}\n <div className=\"p-3\">\n <h3 className=\"font-medium text-sm truncate\">{item.name}</h3>\n <div className=\"flex items-center justify-between mt-2 text-xs text-text-secondary\">\n <span>{item.type}</span>\n <span>{formatFileSize(item.size)}</span>\n </div>\n {canDeleteMedia && (\n <button\n onClick={() => {\n if (window.confirm(tr('mediaLibrary.deleteConfirm', 'Delete this media?')))\n deleteMedia(item.id);\n }}\n type=\"button\"\n className=\"mt-2 w-full p-2 min-h-[44px] text-xs text-status-error-text border border-status-error-border/30 rounded hover:bg-status-error-bg/10 flex items-center justify-center gap-1\"\n >\n <Trash2 size={12} />\n {tr('mediaLibrary.delete', 'Delete')}\n </button>\n )}\n </div>\n </GlassCard>\n ))}\n </div>\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-files\"\n title={tr('mediaLibrary.noMedia', 'No media files')}\n description={tr(\n 'mediaLibrary.uploadFirstMedia',\n 'Upload your first media file to get started'\n )}\n action={\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_MEDIA_CREATE}>\n <label className=\"px-4 py-2 min-h-[44px] bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 cursor-pointer inline-flex items-center justify-center gap-2\">\n <Upload size={20} />\n {tr('mediaLibrary.uploadMedia', 'Upload Media')}\n <input\n type=\"file\"\n multiple\n accept=\"image/*,video/*,audio/*,.pdf,.doc,.docx\"\n onChange={(e) => handleFileUpload(e.target.files)}\n className=\"hidden\"\n aria-label={tr('mediaLibrary.uploadMedia', 'Upload Media')}\n />\n </label>\n </PermissionGate>\n }\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MediaLibraryPage.js","names":[],"sources":["../../../src/tours/pages/MediaLibraryPage.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { Upload, Search, Image as ImageIcon, Video, Music, FileText, Trash2 } from 'lucide-react';\nimport { PermissionGate } from '@burdenoff/fe-libs/shared/components';\nimport { GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useToursI18n } from '../i18n';\nimport { useMediaLibrary } from '../hooks/useMediaLibrary';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport { useToursFormatters } from '../utils';\nimport type { TourMediaType } from '../types';\nimport { emitToursApiFailed } from '../telemetry';\n\n/**\n * Media library page\n * Browse, upload, and manage tour media\n */\nexport function MediaLibraryPage() {\n const { tr } = useToursI18n();\n\n const toursContext = useToursContext();\n const eventBus = useEventBus();\n\n const { hasPermission, isLoading: permissionsLoading } = useToursPermissionAccess();\n const canListMedia = hasPermission(TOUR_PERMISSIONS.TOUR_MEDIA_LIST);\n const canDeleteMedia = hasPermission(TOUR_PERMISSIONS.TOUR_MEDIA_DELETE);\n\n const { media, fetchMedia, uploadMedia, deleteMedia, uploadProgress } = useMediaLibrary();\n const { formatFileSize } = useToursFormatters();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [typeFilter, setTypeFilter] = useState<TourMediaType | 'all'>('all');\n\n useEffect(() => {\n if (permissionsLoading || !canListMedia) return;\n fetchMedia(typeFilter === 'all' ? undefined : typeFilter, searchQuery);\n }, [fetchMedia, typeFilter, searchQuery, permissionsLoading, canListMedia]);\n\n const handleFileUpload = async (files: FileList | null) => {\n if (!files) return;\n\n await Promise.all(\n Array.from(files).map(async (file) => {\n try {\n await uploadMedia(file);\n } catch {\n emitToursApiFailed(eventBus, 'media.upload', toursContext);\n }\n })\n );\n\n // Refresh media list\n fetchMedia(typeFilter === 'all' ? undefined : typeFilter, searchQuery);\n };\n\n const getMediaIcon = (type: TourMediaType) => {\n switch (type) {\n case 'IMAGE':\n return <ImageIcon size={20} />;\n case 'VIDEO':\n return <Video size={20} />;\n case 'AUDIO':\n return <Music size={20} />;\n case 'DOCUMENT':\n return <FileText size={20} />;\n }\n };\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold\">\n {tr('mediaLibrary.title', 'Media Library')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('mediaLibrary.subtitle', 'Upload and manage media for tour steps')}\n </p>\n </div>\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_MEDIA_CREATE}>\n <label className=\"w-full sm:w-auto px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 cursor-pointer flex items-center justify-center gap-2\">\n <Upload size={20} />\n {tr('mediaLibrary.uploadMedia', 'Upload Media')}\n <input\n type=\"file\"\n multiple\n accept=\"image/*,video/*,audio/*,.pdf,.doc,.docx\"\n onChange={(e) => handleFileUpload(e.target.files)}\n className=\"hidden\"\n aria-label={tr('mediaLibrary.uploadMedia', 'Upload Media')}\n />\n </label>\n </PermissionGate>\n </div>\n\n <PagePurpose>\n {tr(\n 'mediaLibrary.purpose',\n 'Store and organise the images, videos, audio, and documents you attach to tour steps. Upload an asset here once, then reference it from any step — handy for product screenshots, short demo clips, or callout graphics that make a walkthrough clearer.'\n )}\n </PagePurpose>\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <div className=\"flex-1 relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n size={20}\n />\n <input\n type=\"text\"\n placeholder={tr('mediaLibrary.searchPlaceholder', 'Search media...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n aria-label={tr('mediaLibrary.searchPlaceholder', 'Search media...')}\n />\n </div>\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value as TourMediaType | 'all')}\n className=\"px-4 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n aria-label={tr('mediaLibrary.filterByType', 'Filter by media type')}\n >\n <option value=\"all\">{tr('mediaLibrary.allTypes', 'All Types')}</option>\n <option value=\"IMAGE\">{tr('mediaLibrary.typeImages', 'Images')}</option>\n <option value=\"VIDEO\">{tr('mediaLibrary.typeVideos', 'Videos')}</option>\n <option value=\"AUDIO\">{tr('mediaLibrary.typeAudio', 'Audio')}</option>\n <option value=\"DOCUMENT\">{tr('mediaLibrary.typeDocuments', 'Documents')}</option>\n </select>\n </div>\n\n {/* Upload Progress */}\n {Object.keys(uploadProgress).length > 0 && (\n <div className=\"space-y-2\">\n {Object.entries(uploadProgress).map(([fileId, progress]) => (\n <div key={fileId} className=\"border border-border-subtle rounded-md p-3\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm\">{tr('mediaLibrary.uploading', 'Uploading...')}</span>\n <span className=\"text-sm text-text-secondary\">{progress}%</span>\n </div>\n <div className=\"w-full bg-bg-sunken rounded-full h-2\">\n <div\n className=\"bg-action-primary-bg h-2 rounded-full transition-all\"\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Media Grid */}\n {media.length > 0 ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4\">\n {media.map((item) => (\n <GlassCard\n key={item.id}\n treatment=\"plain\"\n className=\"overflow-hidden p-0 border-border-seam shadow-[var(--shadow-pop)]\"\n >\n {/* Thumbnail */}\n <div className=\"aspect-video bg-bg-sunken flex items-center justify-center\">\n {item.type === 'IMAGE' ? (\n <img\n src={item.cdnUrl}\n alt={item.altText || item.name}\n className=\"w-full h-full object-cover\"\n />\n ) : (\n <div className=\"text-text-secondary\">{getMediaIcon(item.type)}</div>\n )}\n </div>\n\n {/* Info */}\n <div className=\"p-3\">\n <h3 className=\"font-medium text-sm truncate\">{item.name}</h3>\n <div className=\"flex items-center justify-between mt-2 text-xs text-text-secondary\">\n <span>{item.type}</span>\n <span>{formatFileSize(item.size)}</span>\n </div>\n {canDeleteMedia && (\n <button\n onClick={() => {\n if (window.confirm(tr('mediaLibrary.deleteConfirm', 'Delete this media?')))\n deleteMedia(item.id);\n }}\n type=\"button\"\n className=\"mt-2 w-full p-2 min-h-[44px] text-xs text-status-error-text border border-status-error-border/30 rounded hover:bg-status-error-bg/10 flex items-center justify-center gap-1\"\n >\n <Trash2 size={12} />\n {tr('mediaLibrary.delete', 'Delete')}\n </button>\n )}\n </div>\n </GlassCard>\n ))}\n </div>\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-files\"\n title={tr('mediaLibrary.noMedia', 'No media files')}\n description={tr(\n 'mediaLibrary.uploadFirstMedia',\n 'Upload your first media file to get started'\n )}\n action={\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_MEDIA_CREATE}>\n <label className=\"px-4 py-2 min-h-[44px] bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 cursor-pointer inline-flex items-center justify-center gap-2\">\n <Upload size={20} />\n {tr('mediaLibrary.uploadMedia', 'Upload Media')}\n <input\n type=\"file\"\n multiple\n accept=\"image/*,video/*,audio/*,.pdf,.doc,.docx\"\n onChange={(e) => handleFileUpload(e.target.files)}\n className=\"hidden\"\n aria-label={tr('mediaLibrary.uploadMedia', 'Upload Media')}\n />\n </label>\n </PermissionGate>\n }\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { useToursI18n as e } from "../i18n.js";
|
|
2
|
+
import { useToursContext as t } from "../providers/ToursProvider.js";
|
|
3
|
+
import { useGetToursForContextQuery as n } from "../../generated/global-operations.js";
|
|
4
4
|
import { useEffect as r, useRef as i } from "react";
|
|
5
5
|
import { Markdown as a } from "@burdenoff/fe-libs/shared/components";
|
|
6
6
|
import { CheckCircle2 as o, Circle as s, MinusCircle as c, Play as l } from "lucide-react";
|
|
@@ -17,7 +17,7 @@ function S(e, t) {
|
|
|
17
17
|
return e && b.test(e) ? e : t;
|
|
18
18
|
}
|
|
19
19
|
function C() {
|
|
20
|
-
let { tr: o } =
|
|
20
|
+
let { tr: o } = e(), { productId: s, moduleId: c, pageId: l, workspaceId: y } = t(), [b] = f(), C = v(), T = i(!1);
|
|
21
21
|
r(() => {
|
|
22
22
|
T.current || (T.current = !0, C.emit("tours.page.viewed", {
|
|
23
23
|
page: "/tours",
|
|
@@ -33,7 +33,7 @@ function C() {
|
|
|
33
33
|
l,
|
|
34
34
|
y
|
|
35
35
|
]);
|
|
36
|
-
let E = x(s, void 0) ?? x(b.get("productId"), void 0), D = x(c, void 0) ?? x(b.get("moduleId"), void 0), O = S(l, void 0) ?? S(b.get("pageId"), void 0), { data: k, loading: A } =
|
|
36
|
+
let E = x(s, void 0) ?? x(b.get("productId"), void 0), D = x(c, void 0) ?? x(b.get("moduleId"), void 0), O = S(l, void 0) ?? S(b.get("pageId"), void 0), { data: k, loading: A } = n({
|
|
37
37
|
variables: E ? { context: {
|
|
38
38
|
productId: E,
|
|
39
39
|
moduleId: D,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { useToursI18n as e } from "../i18n.js";
|
|
2
|
+
import { useToursContext as t } from "../providers/ToursProvider.js";
|
|
3
|
+
import { useGetUserTourProgressesQuery as n } from "../../generated/global-operations.js";
|
|
4
4
|
import { useEffect as r, useMemo as i, useRef as a } from "react";
|
|
5
5
|
import { CheckCircle2 as o, HelpCircle as s, MinusCircle as c, Play as l } from "lucide-react";
|
|
6
6
|
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
@@ -8,7 +8,7 @@ import { EmphasisPanel as f, IllustratedEmptyState as p, PagePurpose as m } from
|
|
|
8
8
|
import { useEventBus as h } from "@burdenoff/fe-libs/shared/events";
|
|
9
9
|
//#region src/tours/pages/MyToursProgressPage.tsx
|
|
10
10
|
function g() {
|
|
11
|
-
let { tr: g } =
|
|
11
|
+
let { tr: g } = e(), { currentUser: v, productId: y, moduleId: b, pageId: x, workspaceId: S } = t(), C = h(), w = a(!1);
|
|
12
12
|
r(() => {
|
|
13
13
|
w.current || (w.current = !0, C.emit("tours.page.viewed", {
|
|
14
14
|
page: "/tours/progress",
|
|
@@ -24,7 +24,7 @@ function g() {
|
|
|
24
24
|
x,
|
|
25
25
|
S
|
|
26
26
|
]);
|
|
27
|
-
let { data: T, loading: E } =
|
|
27
|
+
let { data: T, loading: E } = n({
|
|
28
28
|
variables: {
|
|
29
29
|
userId: v?.id,
|
|
30
30
|
pagination: {
|