@burdenoff/microfe-tours 2026.510.112
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/README.md +342 -0
- package/dist/__tests__/setup.d.ts +2 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/constants/featureFlags.d.ts +25 -0
- package/dist/constants/featureFlags.d.ts.map +1 -0
- package/dist/constants/featureFlags.js +9 -0
- package/dist/constants/featureFlags.js.map +1 -0
- package/dist/constants/permissions.d.ts +49 -0
- package/dist/constants/permissions.d.ts.map +1 -0
- package/dist/constants/permissions.js +38 -0
- package/dist/constants/permissions.js.map +1 -0
- package/dist/dev/main.d.ts +2 -0
- package/dist/dev/main.d.ts.map +1 -0
- package/dist/generated/global-operations.d.ts +1270 -0
- package/dist/generated/global-operations.d.ts.map +1 -0
- package/dist/generated/global-operations.js +650 -0
- package/dist/generated/global-operations.js.map +1 -0
- package/dist/generated/global-types.d.ts +20448 -0
- package/dist/generated/global-types.d.ts.map +1 -0
- package/dist/generated/wspace-operations.d.ts +2 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -0
- package/dist/generated/wspace-types.d.ts +53398 -0
- package/dist/generated/wspace-types.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/tours/ToursAdminRoot.d.ts +16 -0
- package/dist/tours/ToursAdminRoot.d.ts.map +1 -0
- package/dist/tours/ToursAdminRoot.js +42 -0
- package/dist/tours/ToursAdminRoot.js.map +1 -0
- package/dist/tours/ToursAdminRoutes.d.ts +12 -0
- package/dist/tours/ToursAdminRoutes.d.ts.map +1 -0
- package/dist/tours/ToursAdminRoutes.js +109 -0
- package/dist/tours/ToursAdminRoutes.js.map +1 -0
- package/dist/tours/ToursRoot.d.ts +10 -0
- package/dist/tours/ToursRoot.d.ts.map +1 -0
- package/dist/tours/ToursRoot.js +1 -0
- package/dist/tours/ToursRoutes.d.ts +8 -0
- package/dist/tours/ToursRoutes.d.ts.map +1 -0
- package/dist/tours/ToursUserRoot.d.ts +15 -0
- package/dist/tours/ToursUserRoot.d.ts.map +1 -0
- package/dist/tours/ToursUserRoot.js +48 -0
- package/dist/tours/ToursUserRoot.js.map +1 -0
- package/dist/tours/ToursUserRoutes.d.ts +15 -0
- package/dist/tours/ToursUserRoutes.d.ts.map +1 -0
- package/dist/tours/ToursUserRoutes.js +66 -0
- package/dist/tours/ToursUserRoutes.js.map +1 -0
- package/dist/tours/components/RouteHelpers.d.ts +33 -0
- package/dist/tours/components/RouteHelpers.d.ts.map +1 -0
- package/dist/tours/components/RouteHelpers.js +75 -0
- package/dist/tours/components/RouteHelpers.js.map +1 -0
- package/dist/tours/components/ToursAdminNavRegistrar.d.ts +25 -0
- package/dist/tours/components/ToursAdminNavRegistrar.d.ts.map +1 -0
- package/dist/tours/components/ToursAdminNavRegistrar.js +60 -0
- package/dist/tours/components/ToursAdminNavRegistrar.js.map +1 -0
- package/dist/tours/components/ui.d.ts +2 -0
- package/dist/tours/components/ui.d.ts.map +1 -0
- package/dist/tours/hooks/useMediaLibrary.d.ts +22 -0
- package/dist/tours/hooks/useMediaLibrary.d.ts.map +1 -0
- package/dist/tours/hooks/useMediaLibrary.js +82 -0
- package/dist/tours/hooks/useMediaLibrary.js.map +1 -0
- package/dist/tours/hooks/useStepSequencer.d.ts +20 -0
- package/dist/tours/hooks/useStepSequencer.d.ts.map +1 -0
- package/dist/tours/hooks/useStepSequencer.js +53 -0
- package/dist/tours/hooks/useStepSequencer.js.map +1 -0
- package/dist/tours/hooks/useTourAnalytics.d.ts +32 -0
- package/dist/tours/hooks/useTourAnalytics.d.ts.map +1 -0
- package/dist/tours/hooks/useTourAnalytics.js +70 -0
- package/dist/tours/hooks/useTourAnalytics.js.map +1 -0
- package/dist/tours/hooks/useTourBuilder.d.ts +61 -0
- package/dist/tours/hooks/useTourBuilder.d.ts.map +1 -0
- package/dist/tours/hooks/useTourBuilder.js +192 -0
- package/dist/tours/hooks/useTourBuilder.js.map +1 -0
- package/dist/tours/hooks/useTourLifecycle.d.ts +22 -0
- package/dist/tours/hooks/useTourLifecycle.d.ts.map +1 -0
- package/dist/tours/hooks/useTourLifecycle.js +28 -0
- package/dist/tours/hooks/useTourLifecycle.js.map +1 -0
- package/dist/tours/hooks/useTourPreview.d.ts +33 -0
- package/dist/tours/hooks/useTourPreview.d.ts.map +1 -0
- package/dist/tours/hooks/useTourPreview.js +70 -0
- package/dist/tours/hooks/useTourPreview.js.map +1 -0
- package/dist/tours/hooks/useToursList.d.ts +30 -0
- package/dist/tours/hooks/useToursList.d.ts.map +1 -0
- package/dist/tours/hooks/useToursList.js +49 -0
- package/dist/tours/hooks/useToursList.js.map +1 -0
- package/dist/tours/hooks/useToursPermissionAccess.d.ts +16 -0
- package/dist/tours/hooks/useToursPermissionAccess.d.ts.map +1 -0
- package/dist/tours/hooks/useToursPermissionAccess.js +9 -0
- package/dist/tours/hooks/useToursPermissionAccess.js.map +1 -0
- package/dist/tours/i18n.d.ts +172 -0
- package/dist/tours/i18n.d.ts.map +1 -0
- package/dist/tours/i18n.js +175 -0
- package/dist/tours/i18n.js.map +1 -0
- package/dist/tours/index.d.ts +21 -0
- package/dist/tours/index.d.ts.map +1 -0
- package/dist/tours/index.js +5 -0
- package/dist/tours/pages/AnalyticsDashboardPage.d.ts +6 -0
- package/dist/tours/pages/AnalyticsDashboardPage.d.ts.map +1 -0
- package/dist/tours/pages/AnalyticsDashboardPage.js +230 -0
- package/dist/tours/pages/AnalyticsDashboardPage.js.map +1 -0
- package/dist/tours/pages/MediaLibraryPage.d.ts +6 -0
- package/dist/tours/pages/MediaLibraryPage.d.ts.map +1 -0
- package/dist/tours/pages/MediaLibraryPage.js +195 -0
- package/dist/tours/pages/MediaLibraryPage.js.map +1 -0
- package/dist/tours/pages/MyToursPage.d.ts +11 -0
- package/dist/tours/pages/MyToursPage.d.ts.map +1 -0
- package/dist/tours/pages/MyToursPage.js +103 -0
- package/dist/tours/pages/MyToursPage.js.map +1 -0
- package/dist/tours/pages/MyToursProgressPage.d.ts +6 -0
- package/dist/tours/pages/MyToursProgressPage.d.ts.map +1 -0
- package/dist/tours/pages/MyToursProgressPage.js +142 -0
- package/dist/tours/pages/MyToursProgressPage.js.map +1 -0
- package/dist/tours/pages/SchedulerPage.d.ts +6 -0
- package/dist/tours/pages/SchedulerPage.d.ts.map +1 -0
- package/dist/tours/pages/SchedulerPage.js +231 -0
- package/dist/tours/pages/SchedulerPage.js.map +1 -0
- package/dist/tours/pages/StepEditorPage.d.ts +7 -0
- package/dist/tours/pages/StepEditorPage.d.ts.map +1 -0
- package/dist/tours/pages/StepEditorPage.js +586 -0
- package/dist/tours/pages/StepEditorPage.js.map +1 -0
- package/dist/tours/pages/SystemAnalyticsPage.d.ts +6 -0
- package/dist/tours/pages/SystemAnalyticsPage.d.ts.map +1 -0
- package/dist/tours/pages/SystemAnalyticsPage.js +155 -0
- package/dist/tours/pages/SystemAnalyticsPage.js.map +1 -0
- package/dist/tours/pages/TemplateLibraryPage.d.ts +6 -0
- package/dist/tours/pages/TemplateLibraryPage.d.ts.map +1 -0
- package/dist/tours/pages/TemplateLibraryPage.js +207 -0
- package/dist/tours/pages/TemplateLibraryPage.js.map +1 -0
- package/dist/tours/pages/TourBuilderPage.d.ts +6 -0
- package/dist/tours/pages/TourBuilderPage.d.ts.map +1 -0
- package/dist/tours/pages/TourBuilderPage.js +363 -0
- package/dist/tours/pages/TourBuilderPage.js.map +1 -0
- package/dist/tours/pages/TourPreviewPage.d.ts +6 -0
- package/dist/tours/pages/TourPreviewPage.d.ts.map +1 -0
- package/dist/tours/pages/TourPreviewPage.js +239 -0
- package/dist/tours/pages/TourPreviewPage.js.map +1 -0
- package/dist/tours/pages/ToursListPage.d.ts +6 -0
- package/dist/tours/pages/ToursListPage.d.ts.map +1 -0
- package/dist/tours/pages/ToursListPage.js +310 -0
- package/dist/tours/pages/ToursListPage.js.map +1 -0
- package/dist/tours/providers/ToursProvider.d.ts +27 -0
- package/dist/tours/providers/ToursProvider.d.ts.map +1 -0
- package/dist/tours/providers/ToursProvider.js +28 -0
- package/dist/tours/providers/ToursProvider.js.map +1 -0
- package/dist/tours/store/toursStore.d.ts +39 -0
- package/dist/tours/store/toursStore.d.ts.map +1 -0
- package/dist/tours/store/toursStore.js +143 -0
- package/dist/tours/store/toursStore.js.map +1 -0
- package/dist/tours/types/index.d.ts +350 -0
- package/dist/tours/types/index.d.ts.map +1 -0
- package/dist/tours/utils/cn.d.ts +8 -0
- package/dist/tours/utils/cn.d.ts.map +1 -0
- package/dist/tours/utils/index.d.ts +102 -0
- package/dist/tours/utils/index.d.ts.map +1 -0
- package/dist/tours/utils/index.js +28 -0
- package/dist/tours/utils/index.js.map +1 -0
- package/dist/tours/utils/joinPath.d.ts +9 -0
- package/dist/tours/utils/joinPath.d.ts.map +1 -0
- package/dist/tours/utils/joinPath.js +8 -0
- package/dist/tours/utils/joinPath.js.map +1 -0
- package/dist/utils/nativeBridge.d.ts +3 -0
- package/dist/utils/nativeBridge.d.ts.map +1 -0
- package/dist/utils/nativeBridge.js +2 -0
- package/package.json +114 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { useToursPermissionAccess as e } from "../hooks/useToursPermissionAccess.js";
|
|
2
|
+
import { TOUR_PERMISSIONS as t } from "../../constants/permissions.js";
|
|
3
|
+
import { useTourAnalytics as n } from "../hooks/useTourAnalytics.js";
|
|
4
|
+
import { formatDuration as r, formatNumber as i, formatPercentage as a } from "../utils/index.js";
|
|
5
|
+
import { useCallback as o, useEffect as s } from "react";
|
|
6
|
+
import { BarChart3 as c, Timer as l, TrendingUp as u, Users as d } from "lucide-react";
|
|
7
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
8
|
+
import { useParams as m } from "react-router-dom";
|
|
9
|
+
import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
|
+
//#region src/tours/pages/AnalyticsDashboardPage.tsx
|
|
11
|
+
function g() {
|
|
12
|
+
let { t: g } = h(), _ = o((e, t) => {
|
|
13
|
+
let n = g(e);
|
|
14
|
+
return n === e ? t : n;
|
|
15
|
+
}, [g]), { tourId: v } = m(), { hasPermission: y, isLoading: b } = e(), x = y(t.TOUR_ANALYTICS_READ), { analytics: S, loading: C, fetchAnalytics: w, getCompletionRate: T, getSkipRate: E } = n(v);
|
|
16
|
+
return s(() => {
|
|
17
|
+
b || !x || w();
|
|
18
|
+
}, [
|
|
19
|
+
w,
|
|
20
|
+
b,
|
|
21
|
+
x
|
|
22
|
+
]), !b && !x ? /* @__PURE__ */ f("div", {
|
|
23
|
+
className: "flex items-center justify-center h-full p-6",
|
|
24
|
+
children: /* @__PURE__ */ p("div", {
|
|
25
|
+
className: "text-center",
|
|
26
|
+
children: [/* @__PURE__ */ f("h2", {
|
|
27
|
+
className: "text-lg font-semibold text-foreground mb-2",
|
|
28
|
+
children: _("analyticsDashboard.accessDenied", "Access Denied")
|
|
29
|
+
}), /* @__PURE__ */ f("p", {
|
|
30
|
+
className: "text-sm text-muted-foreground",
|
|
31
|
+
children: _("analyticsDashboard.noPermission", "You do not have permission to view tour analytics.")
|
|
32
|
+
})]
|
|
33
|
+
})
|
|
34
|
+
}) : C ? /* @__PURE__ */ f("div", {
|
|
35
|
+
className: "flex items-center justify-center h-full",
|
|
36
|
+
children: /* @__PURE__ */ f("p", {
|
|
37
|
+
className: "text-muted-foreground",
|
|
38
|
+
children: _("analyticsDashboard.loading", "Loading analytics...")
|
|
39
|
+
})
|
|
40
|
+
}) : S ? /* @__PURE__ */ p("div", {
|
|
41
|
+
className: "space-y-6 p-6",
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h1", {
|
|
44
|
+
className: "text-2xl font-bold",
|
|
45
|
+
children: _("analyticsDashboard.title", "Tour Analytics")
|
|
46
|
+
}), /* @__PURE__ */ f("p", {
|
|
47
|
+
className: "text-sm text-muted-foreground",
|
|
48
|
+
children: _("analyticsDashboard.subtitle", "Performance metrics and insights")
|
|
49
|
+
})] }),
|
|
50
|
+
/* @__PURE__ */ p("div", {
|
|
51
|
+
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4",
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ p("div", {
|
|
54
|
+
className: "border border-border rounded-md p-4 bg-card",
|
|
55
|
+
children: [/* @__PURE__ */ p("div", {
|
|
56
|
+
className: "flex items-center gap-3 mb-2",
|
|
57
|
+
children: [/* @__PURE__ */ f("div", {
|
|
58
|
+
className: "p-2 bg-primary/10 rounded-md",
|
|
59
|
+
children: /* @__PURE__ */ f(d, {
|
|
60
|
+
className: "text-primary",
|
|
61
|
+
size: 20
|
|
62
|
+
})
|
|
63
|
+
}), /* @__PURE__ */ f("span", {
|
|
64
|
+
className: "text-sm text-muted-foreground",
|
|
65
|
+
children: _("analyticsDashboard.totalViews", "Total Views")
|
|
66
|
+
})]
|
|
67
|
+
}), /* @__PURE__ */ f("div", {
|
|
68
|
+
className: "text-2xl font-bold",
|
|
69
|
+
children: i(S.totalViews)
|
|
70
|
+
})]
|
|
71
|
+
}),
|
|
72
|
+
/* @__PURE__ */ p("div", {
|
|
73
|
+
className: "border border-border rounded-md p-4 bg-card",
|
|
74
|
+
children: [/* @__PURE__ */ p("div", {
|
|
75
|
+
className: "flex items-center gap-3 mb-2",
|
|
76
|
+
children: [/* @__PURE__ */ f("div", {
|
|
77
|
+
className: "p-2 bg-success/10 rounded-md",
|
|
78
|
+
children: /* @__PURE__ */ f(u, {
|
|
79
|
+
className: "text-success",
|
|
80
|
+
size: 20
|
|
81
|
+
})
|
|
82
|
+
}), /* @__PURE__ */ f("span", {
|
|
83
|
+
className: "text-sm text-muted-foreground",
|
|
84
|
+
children: _("analyticsDashboard.completionRate", "Completion Rate")
|
|
85
|
+
})]
|
|
86
|
+
}), /* @__PURE__ */ f("div", {
|
|
87
|
+
className: "text-2xl font-bold",
|
|
88
|
+
children: a(T())
|
|
89
|
+
})]
|
|
90
|
+
}),
|
|
91
|
+
/* @__PURE__ */ p("div", {
|
|
92
|
+
className: "border border-border rounded-md p-4 bg-card",
|
|
93
|
+
children: [/* @__PURE__ */ p("div", {
|
|
94
|
+
className: "flex items-center gap-3 mb-2",
|
|
95
|
+
children: [/* @__PURE__ */ f("div", {
|
|
96
|
+
className: "p-2 bg-warning/10 rounded-md",
|
|
97
|
+
children: /* @__PURE__ */ f(c, {
|
|
98
|
+
className: "text-warning",
|
|
99
|
+
size: 20
|
|
100
|
+
})
|
|
101
|
+
}), /* @__PURE__ */ f("span", {
|
|
102
|
+
className: "text-sm text-muted-foreground",
|
|
103
|
+
children: _("analyticsDashboard.skipRate", "Skip Rate")
|
|
104
|
+
})]
|
|
105
|
+
}), /* @__PURE__ */ f("div", {
|
|
106
|
+
className: "text-2xl font-bold",
|
|
107
|
+
children: a(E())
|
|
108
|
+
})]
|
|
109
|
+
}),
|
|
110
|
+
/* @__PURE__ */ p("div", {
|
|
111
|
+
className: "border border-border rounded-md p-4 bg-card",
|
|
112
|
+
children: [/* @__PURE__ */ p("div", {
|
|
113
|
+
className: "flex items-center gap-3 mb-2",
|
|
114
|
+
children: [/* @__PURE__ */ f("div", {
|
|
115
|
+
className: "p-2 bg-info/10 rounded-md",
|
|
116
|
+
children: /* @__PURE__ */ f(l, {
|
|
117
|
+
className: "text-info",
|
|
118
|
+
size: 20
|
|
119
|
+
})
|
|
120
|
+
}), /* @__PURE__ */ f("span", {
|
|
121
|
+
className: "text-sm text-muted-foreground",
|
|
122
|
+
children: _("analyticsDashboard.avgDuration", "Avg Duration")
|
|
123
|
+
})]
|
|
124
|
+
}), /* @__PURE__ */ f("div", {
|
|
125
|
+
className: "text-2xl font-bold",
|
|
126
|
+
children: r(S.averageDuration)
|
|
127
|
+
})]
|
|
128
|
+
})
|
|
129
|
+
]
|
|
130
|
+
}),
|
|
131
|
+
/* @__PURE__ */ p("div", {
|
|
132
|
+
className: "border border-border rounded-md p-6 bg-card",
|
|
133
|
+
children: [/* @__PURE__ */ f("h2", {
|
|
134
|
+
className: "text-lg font-semibold mb-4",
|
|
135
|
+
children: _("analyticsDashboard.deviceBreakdown", "Device Breakdown")
|
|
136
|
+
}), /* @__PURE__ */ f("div", {
|
|
137
|
+
className: "space-y-3",
|
|
138
|
+
children: S.byDeviceType.map((e) => /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ p("div", {
|
|
139
|
+
className: "flex items-center justify-between mb-2",
|
|
140
|
+
children: [/* @__PURE__ */ f("span", {
|
|
141
|
+
className: "text-sm capitalize",
|
|
142
|
+
children: e.deviceType
|
|
143
|
+
}), /* @__PURE__ */ p("span", {
|
|
144
|
+
className: "text-sm text-muted-foreground",
|
|
145
|
+
children: [
|
|
146
|
+
i(e.count),
|
|
147
|
+
" views (",
|
|
148
|
+
a(e.completionRate),
|
|
149
|
+
")"
|
|
150
|
+
]
|
|
151
|
+
})]
|
|
152
|
+
}), /* @__PURE__ */ f("div", {
|
|
153
|
+
className: "w-full bg-muted rounded-full h-2",
|
|
154
|
+
children: /* @__PURE__ */ f("div", {
|
|
155
|
+
className: "bg-primary h-2 rounded-full",
|
|
156
|
+
style: { width: `${e.completionRate}%` }
|
|
157
|
+
})
|
|
158
|
+
})] }, e.deviceType))
|
|
159
|
+
})]
|
|
160
|
+
}),
|
|
161
|
+
S.stepAnalytics && S.stepAnalytics.length > 0 && /* @__PURE__ */ p("div", {
|
|
162
|
+
className: "border border-border rounded-md p-6 bg-card",
|
|
163
|
+
children: [/* @__PURE__ */ f("h2", {
|
|
164
|
+
className: "text-lg font-semibold mb-4",
|
|
165
|
+
children: _("analyticsDashboard.stepPerformance", "Step Performance")
|
|
166
|
+
}), /* @__PURE__ */ f("div", {
|
|
167
|
+
className: "overflow-x-auto",
|
|
168
|
+
children: /* @__PURE__ */ p("table", {
|
|
169
|
+
className: "w-full",
|
|
170
|
+
children: [/* @__PURE__ */ f("thead", { children: /* @__PURE__ */ p("tr", {
|
|
171
|
+
className: "border-b border-border",
|
|
172
|
+
children: [
|
|
173
|
+
/* @__PURE__ */ f("th", {
|
|
174
|
+
className: "text-left py-2 px-4 text-sm font-medium",
|
|
175
|
+
children: _("analyticsDashboard.tableStep", "Step")
|
|
176
|
+
}),
|
|
177
|
+
/* @__PURE__ */ f("th", {
|
|
178
|
+
className: "text-right py-2 px-4 text-sm font-medium",
|
|
179
|
+
children: _("analyticsDashboard.tableViews", "Views")
|
|
180
|
+
}),
|
|
181
|
+
/* @__PURE__ */ f("th", {
|
|
182
|
+
className: "text-right py-2 px-4 text-sm font-medium",
|
|
183
|
+
children: _("analyticsDashboard.tableCompletions", "Completions")
|
|
184
|
+
}),
|
|
185
|
+
/* @__PURE__ */ f("th", {
|
|
186
|
+
className: "text-right py-2 px-4 text-sm font-medium",
|
|
187
|
+
children: _("analyticsDashboard.tableDropOffRate", "Drop-off Rate")
|
|
188
|
+
})
|
|
189
|
+
]
|
|
190
|
+
}) }), /* @__PURE__ */ f("tbody", { children: S.stepAnalytics.map((e) => /* @__PURE__ */ p("tr", {
|
|
191
|
+
className: "border-b border-border",
|
|
192
|
+
children: [
|
|
193
|
+
/* @__PURE__ */ p("td", {
|
|
194
|
+
className: "py-2 px-4 text-sm",
|
|
195
|
+
children: [
|
|
196
|
+
_("analyticsDashboard.stepLabel", "Step"),
|
|
197
|
+
" ",
|
|
198
|
+
e.stepOrder
|
|
199
|
+
]
|
|
200
|
+
}),
|
|
201
|
+
/* @__PURE__ */ f("td", {
|
|
202
|
+
className: "text-right py-2 px-4 text-sm",
|
|
203
|
+
children: i(e.views)
|
|
204
|
+
}),
|
|
205
|
+
/* @__PURE__ */ f("td", {
|
|
206
|
+
className: "text-right py-2 px-4 text-sm",
|
|
207
|
+
children: i(e.completions)
|
|
208
|
+
}),
|
|
209
|
+
/* @__PURE__ */ f("td", {
|
|
210
|
+
className: "text-right py-2 px-4 text-sm",
|
|
211
|
+
children: a(e.dropOffRate)
|
|
212
|
+
})
|
|
213
|
+
]
|
|
214
|
+
}, e.stepId)) })]
|
|
215
|
+
})
|
|
216
|
+
})]
|
|
217
|
+
})
|
|
218
|
+
]
|
|
219
|
+
}) : /* @__PURE__ */ f("div", {
|
|
220
|
+
className: "flex items-center justify-center h-full",
|
|
221
|
+
children: /* @__PURE__ */ f("p", {
|
|
222
|
+
className: "text-muted-foreground",
|
|
223
|
+
children: _("analyticsDashboard.noData", "No analytics data available")
|
|
224
|
+
})
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
export { g as AnalyticsDashboardPage };
|
|
229
|
+
|
|
230
|
+
//# sourceMappingURL=AnalyticsDashboardPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnalyticsDashboardPage.js","names":[],"sources":["../../../src/tours/pages/AnalyticsDashboardPage.tsx"],"sourcesContent":["import { useCallback, useEffect } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { BarChart3, TrendingUp, Users, Timer } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useTourAnalytics } from '../hooks/useTourAnalytics';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport { formatNumber, formatPercentage, formatDuration } from '../utils';\n\n/**\n * Analytics dashboard page\n * Display tour performance metrics and charts\n */\nexport function AnalyticsDashboardPage() {\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 { tourId } = useParams();\n const { hasPermission, isLoading: permissionsLoading } = useToursPermissionAccess();\n const canViewAnalytics = hasPermission(TOUR_PERMISSIONS.TOUR_ANALYTICS_READ);\n\n const { analytics, loading, fetchAnalytics, getCompletionRate, getSkipRate } =\n useTourAnalytics(tourId);\n\n useEffect(() => {\n if (permissionsLoading || !canViewAnalytics) return;\n fetchAnalytics();\n }, [fetchAnalytics, permissionsLoading, canViewAnalytics]);\n\n if (!permissionsLoading && !canViewAnalytics) {\n return (\n <div className=\"flex items-center justify-center h-full p-6\">\n <div className=\"text-center\">\n <h2 className=\"text-lg font-semibold text-foreground mb-2\">\n {tr('analyticsDashboard.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'analyticsDashboard.noPermission',\n 'You do not have permission to view tour analytics.'\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <p className=\"text-muted-foreground\">\n {tr('analyticsDashboard.loading', 'Loading analytics...')}\n </p>\n </div>\n );\n }\n\n if (!analytics) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <p className=\"text-muted-foreground\">\n {tr('analyticsDashboard.noData', 'No analytics data available')}\n </p>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl font-bold\">{tr('analyticsDashboard.title', 'Tour Analytics')}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr('analyticsDashboard.subtitle', 'Performance metrics and insights')}\n </p>\n </div>\n\n {/* Summary Cards */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <div className=\"border border-border rounded-md p-4 bg-card\">\n <div className=\"flex items-center gap-3 mb-2\">\n <div className=\"p-2 bg-primary/10 rounded-md\">\n <Users className=\"text-primary\" size={20} />\n </div>\n <span className=\"text-sm text-muted-foreground\">\n {tr('analyticsDashboard.totalViews', 'Total Views')}\n </span>\n </div>\n <div className=\"text-2xl font-bold\">{formatNumber(analytics.totalViews)}</div>\n </div>\n\n <div className=\"border border-border rounded-md p-4 bg-card\">\n <div className=\"flex items-center gap-3 mb-2\">\n <div className=\"p-2 bg-success/10 rounded-md\">\n <TrendingUp className=\"text-success\" size={20} />\n </div>\n <span className=\"text-sm text-muted-foreground\">\n {tr('analyticsDashboard.completionRate', 'Completion Rate')}\n </span>\n </div>\n <div className=\"text-2xl font-bold\">{formatPercentage(getCompletionRate())}</div>\n </div>\n\n <div className=\"border border-border rounded-md p-4 bg-card\">\n <div className=\"flex items-center gap-3 mb-2\">\n <div className=\"p-2 bg-warning/10 rounded-md\">\n <BarChart3 className=\"text-warning\" size={20} />\n </div>\n <span className=\"text-sm text-muted-foreground\">\n {tr('analyticsDashboard.skipRate', 'Skip Rate')}\n </span>\n </div>\n <div className=\"text-2xl font-bold\">{formatPercentage(getSkipRate())}</div>\n </div>\n\n <div className=\"border border-border rounded-md p-4 bg-card\">\n <div className=\"flex items-center gap-3 mb-2\">\n <div className=\"p-2 bg-info/10 rounded-md\">\n <Timer className=\"text-info\" size={20} />\n </div>\n <span className=\"text-sm text-muted-foreground\">\n {tr('analyticsDashboard.avgDuration', 'Avg Duration')}\n </span>\n </div>\n <div className=\"text-2xl font-bold\">{formatDuration(analytics.averageDuration)}</div>\n </div>\n </div>\n\n {/* Device Breakdown */}\n <div className=\"border border-border rounded-md p-6 bg-card\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {tr('analyticsDashboard.deviceBreakdown', 'Device Breakdown')}\n </h2>\n <div className=\"space-y-3\">\n {analytics.byDeviceType.map((device) => (\n <div key={device.deviceType}>\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm capitalize\">{device.deviceType}</span>\n <span className=\"text-sm text-muted-foreground\">\n {formatNumber(device.count)} views ({formatPercentage(device.completionRate)})\n </span>\n </div>\n <div className=\"w-full bg-muted rounded-full h-2\">\n <div\n className=\"bg-primary h-2 rounded-full\"\n style={{ width: `${device.completionRate}%` }}\n />\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Step Analytics */}\n {analytics.stepAnalytics && analytics.stepAnalytics.length > 0 && (\n <div className=\"border border-border rounded-md p-6 bg-card\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {tr('analyticsDashboard.stepPerformance', 'Step Performance')}\n </h2>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"border-b border-border\">\n <th className=\"text-left py-2 px-4 text-sm font-medium\">\n {tr('analyticsDashboard.tableStep', 'Step')}\n </th>\n <th className=\"text-right py-2 px-4 text-sm font-medium\">\n {tr('analyticsDashboard.tableViews', 'Views')}\n </th>\n <th className=\"text-right py-2 px-4 text-sm font-medium\">\n {tr('analyticsDashboard.tableCompletions', 'Completions')}\n </th>\n <th className=\"text-right py-2 px-4 text-sm font-medium\">\n {tr('analyticsDashboard.tableDropOffRate', 'Drop-off Rate')}\n </th>\n </tr>\n </thead>\n <tbody>\n {analytics.stepAnalytics.map((step) => (\n <tr key={step.stepId} className=\"border-b border-border\">\n <td className=\"py-2 px-4 text-sm\">\n {tr('analyticsDashboard.stepLabel', 'Step')} {step.stepOrder}\n </td>\n <td className=\"text-right py-2 px-4 text-sm\">{formatNumber(step.views)}</td>\n <td className=\"text-right py-2 px-4 text-sm\">\n {formatNumber(step.completions)}\n </td>\n <td className=\"text-right py-2 px-4 text-sm\">\n {formatPercentage(step.dropOffRate)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediaLibraryPage.d.ts","sourceRoot":"","sources":["../../../src/tours/pages/MediaLibraryPage.tsx"],"names":[],"mappings":"AAUA;;;GAGG;AACH,wBAAgB,gBAAgB,4CAyL/B"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { useToursPermissionAccess as e } from "../hooks/useToursPermissionAccess.js";
|
|
2
|
+
import { TOUR_PERMISSIONS as t } from "../../constants/permissions.js";
|
|
3
|
+
import { formatFileSize as n } from "../utils/index.js";
|
|
4
|
+
import { useMediaLibrary as r } from "../hooks/useMediaLibrary.js";
|
|
5
|
+
import { useCallback as i, useEffect as a, useState as o } from "react";
|
|
6
|
+
import { PermissionGate as s } from "@burdenoff/fe-libs/shared/components";
|
|
7
|
+
import { FileText as c, Image as l, Music as u, Search as d, Trash2 as f, Upload as p, Video as m } from "lucide-react";
|
|
8
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
9
|
+
import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
|
+
//#region src/tours/pages/MediaLibraryPage.tsx
|
|
11
|
+
function v() {
|
|
12
|
+
let { t: v } = _(), y = i((e, t) => {
|
|
13
|
+
let n = v(e);
|
|
14
|
+
return n === e ? t : n;
|
|
15
|
+
}, [v]), { hasPermission: b, isLoading: x } = e(), S = b(t.TOUR_MEDIA_LIST);
|
|
16
|
+
b(t.TOUR_MEDIA_CREATE);
|
|
17
|
+
let C = b(t.TOUR_MEDIA_DELETE), { media: w, fetchMedia: T, uploadMedia: E, deleteMedia: D, uploadProgress: O } = r(), [k, A] = o(""), [j, M] = o("all");
|
|
18
|
+
a(() => {
|
|
19
|
+
x || !S || T(j === "all" ? void 0 : j, k);
|
|
20
|
+
}, [
|
|
21
|
+
T,
|
|
22
|
+
j,
|
|
23
|
+
k,
|
|
24
|
+
x,
|
|
25
|
+
S
|
|
26
|
+
]);
|
|
27
|
+
let N = async (e) => {
|
|
28
|
+
if (e) {
|
|
29
|
+
for (let t = 0; t < e.length; t++) try {
|
|
30
|
+
await E(e[t]);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error("Failed to upload file:", e);
|
|
33
|
+
}
|
|
34
|
+
T(j === "all" ? void 0 : j, k);
|
|
35
|
+
}
|
|
36
|
+
}, P = (e) => {
|
|
37
|
+
switch (e) {
|
|
38
|
+
case "IMAGE": return /* @__PURE__ */ h(l, { size: 20 });
|
|
39
|
+
case "VIDEO": return /* @__PURE__ */ h(m, { size: 20 });
|
|
40
|
+
case "AUDIO": return /* @__PURE__ */ h(u, { size: 20 });
|
|
41
|
+
case "DOCUMENT": return /* @__PURE__ */ h(c, { size: 20 });
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return /* @__PURE__ */ g("div", {
|
|
45
|
+
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ g("div", {
|
|
48
|
+
className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3",
|
|
49
|
+
children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("h1", {
|
|
50
|
+
className: "text-xl sm:text-2xl font-bold",
|
|
51
|
+
children: y("mediaLibrary.title", "Media Library")
|
|
52
|
+
}), /* @__PURE__ */ h("p", {
|
|
53
|
+
className: "text-sm text-muted-foreground",
|
|
54
|
+
children: y("mediaLibrary.subtitle", "Upload and manage media for tour steps")
|
|
55
|
+
})] }), /* @__PURE__ */ h(s, {
|
|
56
|
+
permission: t.TOUR_MEDIA_CREATE,
|
|
57
|
+
children: /* @__PURE__ */ g("label", {
|
|
58
|
+
className: "w-full sm:w-auto px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 cursor-pointer flex items-center justify-center gap-2",
|
|
59
|
+
children: [
|
|
60
|
+
/* @__PURE__ */ h(p, { size: 20 }),
|
|
61
|
+
y("mediaLibrary.uploadMedia", "Upload Media"),
|
|
62
|
+
/* @__PURE__ */ h("input", {
|
|
63
|
+
type: "file",
|
|
64
|
+
multiple: !0,
|
|
65
|
+
accept: "image/*,video/*,audio/*,.pdf,.doc,.docx",
|
|
66
|
+
onChange: (e) => N(e.target.files),
|
|
67
|
+
className: "hidden"
|
|
68
|
+
})
|
|
69
|
+
]
|
|
70
|
+
})
|
|
71
|
+
})]
|
|
72
|
+
}),
|
|
73
|
+
/* @__PURE__ */ g("div", {
|
|
74
|
+
className: "flex flex-col sm:flex-row gap-3",
|
|
75
|
+
children: [/* @__PURE__ */ g("div", {
|
|
76
|
+
className: "flex-1 relative",
|
|
77
|
+
children: [/* @__PURE__ */ h(d, {
|
|
78
|
+
className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground",
|
|
79
|
+
size: 20
|
|
80
|
+
}), /* @__PURE__ */ h("input", {
|
|
81
|
+
type: "text",
|
|
82
|
+
placeholder: y("mediaLibrary.searchPlaceholder", "Search media..."),
|
|
83
|
+
value: k,
|
|
84
|
+
onChange: (e) => A(e.target.value),
|
|
85
|
+
className: "w-full pl-[3.25rem] pr-4 py-2 border border-border rounded-md bg-background"
|
|
86
|
+
})]
|
|
87
|
+
}), /* @__PURE__ */ g("select", {
|
|
88
|
+
value: j,
|
|
89
|
+
onChange: (e) => M(e.target.value),
|
|
90
|
+
className: "px-4 py-2 border border-border rounded-md bg-background",
|
|
91
|
+
children: [
|
|
92
|
+
/* @__PURE__ */ h("option", {
|
|
93
|
+
value: "all",
|
|
94
|
+
children: y("mediaLibrary.allTypes", "All Types")
|
|
95
|
+
}),
|
|
96
|
+
/* @__PURE__ */ h("option", {
|
|
97
|
+
value: "IMAGE",
|
|
98
|
+
children: y("mediaLibrary.typeImages", "Images")
|
|
99
|
+
}),
|
|
100
|
+
/* @__PURE__ */ h("option", {
|
|
101
|
+
value: "VIDEO",
|
|
102
|
+
children: y("mediaLibrary.typeVideos", "Videos")
|
|
103
|
+
}),
|
|
104
|
+
/* @__PURE__ */ h("option", {
|
|
105
|
+
value: "AUDIO",
|
|
106
|
+
children: y("mediaLibrary.typeAudio", "Audio")
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ h("option", {
|
|
109
|
+
value: "DOCUMENT",
|
|
110
|
+
children: y("mediaLibrary.typeDocuments", "Documents")
|
|
111
|
+
})
|
|
112
|
+
]
|
|
113
|
+
})]
|
|
114
|
+
}),
|
|
115
|
+
Object.keys(O).length > 0 && /* @__PURE__ */ h("div", {
|
|
116
|
+
className: "space-y-2",
|
|
117
|
+
children: Object.entries(O).map(([e, t]) => /* @__PURE__ */ g("div", {
|
|
118
|
+
className: "border border-border rounded-md p-3",
|
|
119
|
+
children: [/* @__PURE__ */ g("div", {
|
|
120
|
+
className: "flex items-center justify-between mb-2",
|
|
121
|
+
children: [/* @__PURE__ */ h("span", {
|
|
122
|
+
className: "text-sm",
|
|
123
|
+
children: y("mediaLibrary.uploading", "Uploading...")
|
|
124
|
+
}), /* @__PURE__ */ g("span", {
|
|
125
|
+
className: "text-sm text-muted-foreground",
|
|
126
|
+
children: [t, "%"]
|
|
127
|
+
})]
|
|
128
|
+
}), /* @__PURE__ */ h("div", {
|
|
129
|
+
className: "w-full bg-muted rounded-full h-2",
|
|
130
|
+
children: /* @__PURE__ */ h("div", {
|
|
131
|
+
className: "bg-primary h-2 rounded-full transition-all",
|
|
132
|
+
style: { width: `${t}%` }
|
|
133
|
+
})
|
|
134
|
+
})]
|
|
135
|
+
}, e))
|
|
136
|
+
}),
|
|
137
|
+
w.length > 0 ? /* @__PURE__ */ h("div", {
|
|
138
|
+
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4",
|
|
139
|
+
children: w.map((e) => /* @__PURE__ */ g("div", {
|
|
140
|
+
className: "border border-border rounded-md overflow-hidden hover:shadow-md transition-shadow",
|
|
141
|
+
children: [/* @__PURE__ */ h("div", {
|
|
142
|
+
className: "aspect-video bg-muted flex items-center justify-center",
|
|
143
|
+
children: e.type === "IMAGE" ? /* @__PURE__ */ h("img", {
|
|
144
|
+
src: e.cdnUrl,
|
|
145
|
+
alt: e.altText || e.name,
|
|
146
|
+
className: "w-full h-full object-cover"
|
|
147
|
+
}) : /* @__PURE__ */ h("div", {
|
|
148
|
+
className: "text-muted-foreground",
|
|
149
|
+
children: P(e.type)
|
|
150
|
+
})
|
|
151
|
+
}), /* @__PURE__ */ g("div", {
|
|
152
|
+
className: "p-3",
|
|
153
|
+
children: [
|
|
154
|
+
/* @__PURE__ */ h("h3", {
|
|
155
|
+
className: "font-medium text-sm truncate",
|
|
156
|
+
children: e.name
|
|
157
|
+
}),
|
|
158
|
+
/* @__PURE__ */ g("div", {
|
|
159
|
+
className: "flex items-center justify-between mt-2 text-xs text-muted-foreground",
|
|
160
|
+
children: [/* @__PURE__ */ h("span", { children: e.type }), /* @__PURE__ */ h("span", { children: n(e.size) })]
|
|
161
|
+
}),
|
|
162
|
+
C && /* @__PURE__ */ g("button", {
|
|
163
|
+
onClick: () => {
|
|
164
|
+
window.confirm(y("mediaLibrary.deleteConfirm", "Delete this media?")) && D(e.id);
|
|
165
|
+
},
|
|
166
|
+
className: "mt-2 w-full px-2 py-1 text-xs text-destructive border border-destructive/30 rounded hover:bg-destructive/10 flex items-center justify-center gap-1",
|
|
167
|
+
children: [/* @__PURE__ */ h(f, { size: 12 }), y("mediaLibrary.delete", "Delete")]
|
|
168
|
+
})
|
|
169
|
+
]
|
|
170
|
+
})]
|
|
171
|
+
}, e.id))
|
|
172
|
+
}) : /* @__PURE__ */ g("div", {
|
|
173
|
+
className: "text-center py-12 border border-dashed border-border rounded-md",
|
|
174
|
+
children: [
|
|
175
|
+
/* @__PURE__ */ h(p, {
|
|
176
|
+
className: "mx-auto mb-4 text-muted-foreground",
|
|
177
|
+
size: 48
|
|
178
|
+
}),
|
|
179
|
+
/* @__PURE__ */ h("p", {
|
|
180
|
+
className: "text-muted-foreground mb-2",
|
|
181
|
+
children: y("mediaLibrary.noMedia", "No media files")
|
|
182
|
+
}),
|
|
183
|
+
/* @__PURE__ */ h("p", {
|
|
184
|
+
className: "text-sm text-muted-foreground",
|
|
185
|
+
children: y("mediaLibrary.uploadFirstMedia", "Upload your first media file to get started")
|
|
186
|
+
})
|
|
187
|
+
]
|
|
188
|
+
})
|
|
189
|
+
]
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
export { v as MediaLibraryPage };
|
|
194
|
+
|
|
195
|
+
//# sourceMappingURL=MediaLibraryPage.js.map
|
|
@@ -0,0 +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 { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useMediaLibrary } from '../hooks/useMediaLibrary';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport { formatFileSize } from '../utils';\nimport type { TourMediaType } from '../types';\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 { hasPermission, isLoading: permissionsLoading } = useToursPermissionAccess();\n const canListMedia = hasPermission(TOUR_PERMISSIONS.TOUR_MEDIA_LIST);\n const canUploadMedia = hasPermission(TOUR_PERMISSIONS.TOUR_MEDIA_CREATE);\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 for (let i = 0; i < files.length; i++) {\n try {\n await uploadMedia(files[i]);\n } catch (error) {\n console.error('Failed to upload file:', error);\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-muted-foreground\">\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-primary text-primary-foreground rounded-md hover:bg-primary/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 />\n </label>\n </PermissionGate>\n </div>\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-muted-foreground\"\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 border border-border rounded-md bg-background\"\n />\n </div>\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value as TourMediaType | 'all')}\n className=\"px-4 py-2 border border-border rounded-md bg-background\"\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 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-muted-foreground\">{progress}%</span>\n </div>\n <div className=\"w-full bg-muted rounded-full h-2\">\n <div\n className=\"bg-primary 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 <div\n key={item.id}\n className=\"border border-border rounded-md overflow-hidden hover:shadow-md transition-shadow\"\n >\n {/* Thumbnail */}\n <div className=\"aspect-video bg-muted 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-muted-foreground\">{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-muted-foreground\">\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 className=\"mt-2 w-full px-2 py-1 text-xs text-destructive border border-destructive/30 rounded hover:bg-destructive/10 flex items-center justify-center gap-1\"\n >\n <Trash2 size={12} />\n {tr('mediaLibrary.delete', 'Delete')}\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12 border border-dashed border-border rounded-md\">\n <Upload className=\"mx-auto mb-4 text-muted-foreground\" size={48} />\n <p className=\"text-muted-foreground mb-2\">\n {tr('mediaLibrary.noMedia', 'No media files')}\n </p>\n <p className=\"text-sm text-muted-foreground\">\n {tr('mediaLibrary.uploadFirstMedia', 'Upload your first media file to get started')}\n </p>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAcA,SAAgB,IAAmB;CACjC,IAAM,EAAE,SAAM,GAAS,EACjB,IAAK,GACR,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAE,CACJ,EAEK,EAAE,kBAAe,WAAW,MAAuB,GAA0B,EAC7E,IAAe,EAAc,EAAiB,gBAAgB;AAC7C,GAAc,EAAiB,kBAAkB;CACxE,IAAM,IAAiB,EAAc,EAAiB,kBAAkB,EAElE,EAAE,UAAO,eAAY,gBAAa,gBAAa,sBAAmB,GAAiB,EAEnF,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAY,KAAiB,EAAgC,MAAM;AAE1E,SAAgB;AACV,OAAsB,CAAC,KAC3B,EAAW,MAAe,QAAQ,KAAA,IAAY,GAAY,EAAY;IACrE;EAAC;EAAY;EAAY;EAAa;EAAoB;EAAa,CAAC;CAE3E,IAAM,IAAmB,OAAO,MAA2B;AACpD,SAEL;QAAK,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,IAChC,KAAI;AACF,UAAM,EAAY,EAAM,GAAG;YACpB,GAAO;AACd,YAAQ,MAAM,0BAA0B,EAAM;;AAKlD,KAAW,MAAe,QAAQ,KAAA,IAAY,GAAY,EAAY;;IAGlE,KAAgB,MAAwB;AAC5C,UAAQ,GAAR;GACE,KAAK,QACH,QAAO,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;GAChC,KAAK,QACH,QAAO,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;GAC5B,KAAK,QACH,QAAO,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;GAC5B,KAAK,WACH,QAAO,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;;;AAInC,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,sBAAsB,gBAAgB;KACvC,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,yBAAyB,yCAAyC;KACpE,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,GAAD;KAAgB,YAAY,EAAiB;eAC3C,kBAAC,SAAD;MAAO,WAAU;gBAAjB;OACE,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;OACnB,EAAG,4BAA4B,eAAe;OAC/C,kBAAC,SAAD;QACE,MAAK;QACL,UAAA;QACA,QAAO;QACP,WAAW,MAAM,EAAiB,EAAE,OAAO,MAAM;QACjD,WAAU;QACV,CAAA;OACI;;KACO,CAAA,CACb;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,WAAU;MACV,MAAM;MACN,CAAA,EACF,kBAAC,SAAD;MACE,MAAK;MACL,aAAa,EAAG,kCAAkC,kBAAkB;MACpE,OAAO;MACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;MAC/C,WAAU;MACV,CAAA,CACE;QACN,kBAAC,UAAD;KACE,OAAO;KACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAA+B;KACvE,WAAU;eAHZ;MAKE,kBAAC,UAAD;OAAQ,OAAM;iBAAO,EAAG,yBAAyB,YAAY;OAAU,CAAA;MACvE,kBAAC,UAAD;OAAQ,OAAM;iBAAS,EAAG,2BAA2B,SAAS;OAAU,CAAA;MACxE,kBAAC,UAAD;OAAQ,OAAM;iBAAS,EAAG,2BAA2B,SAAS;OAAU,CAAA;MACxE,kBAAC,UAAD;OAAQ,OAAM;iBAAS,EAAG,0BAA0B,QAAQ;OAAU,CAAA;MACtE,kBAAC,UAAD;OAAQ,OAAM;iBAAY,EAAG,8BAA8B,YAAY;OAAU,CAAA;MAC1E;OACL;;GAGL,OAAO,KAAK,EAAe,CAAC,SAAS,KACpC,kBAAC,OAAD;IAAK,WAAU;cACZ,OAAO,QAAQ,EAAe,CAAC,KAAK,CAAC,GAAQ,OAC5C,kBAAC,OAAD;KAAkB,WAAU;eAA5B,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAW,EAAG,0BAA0B,eAAe;OAAQ,CAAA,EAC/E,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CAAiD,GAAS,IAAQ;SAC9D;SACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,OAAO,GAAG,EAAS,IAAI;OAChC,CAAA;MACE,CAAA,CACF;OAXI,EAWJ,CACN;IACE,CAAA;GAIP,EAAM,SAAS,IACd,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAM,KAAK,MACV,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAKE,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAK,SAAS,UACb,kBAAC,OAAD;OACE,KAAK,EAAK;OACV,KAAK,EAAK,WAAW,EAAK;OAC1B,WAAU;OACV,CAAA,GAEF,kBAAC,OAAD;OAAK,WAAU;iBAAyB,EAAa,EAAK,KAAK;OAAO,CAAA;MAEpE,CAAA,EAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,MAAD;QAAI,WAAU;kBAAgC,EAAK;QAAU,CAAA;OAC7D,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD,EAAA,UAAO,EAAK,MAAY,CAAA,EACxB,kBAAC,QAAD,EAAA,UAAO,EAAe,EAAK,KAAK,EAAQ,CAAA,CACpC;;OACL,KACC,kBAAC,UAAD;QACE,eAAe;AACb,SAAI,OAAO,QAAQ,EAAG,8BAA8B,qBAAqB,CAAC,IACxE,EAAY,EAAK,GAAG;;QAExB,WAAU;kBALZ,CAOE,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACnB,EAAG,uBAAuB,SAAS,CAC7B;;OAEP;QACF;OApCC,EAAK,GAoCN,CACN;IACE,CAAA,GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAQ,WAAU;MAAqC,MAAM;MAAM,CAAA;KACnE,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,wBAAwB,iBAAiB;MAC3C,CAAA;KACJ,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,iCAAiC,8CAA8C;MACjF,CAAA;KACA;;GAEJ"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MyToursPage — user-facing browse for available tours.
|
|
3
|
+
*
|
|
4
|
+
* Lists tours returned by `toursForContext` (filtered to the current
|
|
5
|
+
* product/module/page surface) and decorates each with the requesting
|
|
6
|
+
* user's `TourUserProgress` status (Completed / In progress / Skipped /
|
|
7
|
+
* Not started). Read-only — tours are actually played via the SDK overlay,
|
|
8
|
+
* not via mutations on this page.
|
|
9
|
+
*/
|
|
10
|
+
export declare function MyToursPage(): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=MyToursPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MyToursPage.d.ts","sourceRoot":"","sources":["../../../src/tours/pages/MyToursPage.tsx"],"names":[],"mappings":"AAoCA;;;;;;;;GAQG;AACH,wBAAgB,WAAW,4CAkF1B"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useToursContext as e } from "../providers/ToursProvider.js";
|
|
2
|
+
import { useGetToursForContextQuery as t } from "../../generated/global-operations.js";
|
|
3
|
+
import { useToursI18n as n } from "../i18n.js";
|
|
4
|
+
import { useEffect as r } from "react";
|
|
5
|
+
import { CheckCircle2 as i, Circle as a, MinusCircle as o, Play as s } from "lucide-react";
|
|
6
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
7
|
+
import { useSearchParams as u } from "react-router-dom";
|
|
8
|
+
import { useEventBus as d } from "@burdenoff/fe-libs/shared/events";
|
|
9
|
+
//#region src/tours/pages/MyToursPage.tsx
|
|
10
|
+
var f = /^[a-zA-Z0-9_-]+$/, p = /^[a-zA-Z0-9_\-/]+$/;
|
|
11
|
+
function m(e, t) {
|
|
12
|
+
return e && f.test(e) ? e : t;
|
|
13
|
+
}
|
|
14
|
+
function h(e, t) {
|
|
15
|
+
return e && p.test(e) ? e : t;
|
|
16
|
+
}
|
|
17
|
+
function g() {
|
|
18
|
+
let { tr: i } = n(), { productId: a, moduleId: o, pageId: s, workspaceId: f } = e(), [p] = u(), g = d();
|
|
19
|
+
r(() => {
|
|
20
|
+
g.emit("tours.page.viewed", {
|
|
21
|
+
page: "/tours",
|
|
22
|
+
productId: a,
|
|
23
|
+
moduleId: o,
|
|
24
|
+
pageId: s,
|
|
25
|
+
workspaceId: f
|
|
26
|
+
});
|
|
27
|
+
}, []);
|
|
28
|
+
let v = m(a, void 0) ?? m(p.get("productId"), void 0), y = m(o, void 0) ?? m(p.get("moduleId"), void 0), b = h(s, void 0) ?? h(p.get("pageId"), void 0), { data: x, loading: S } = t({
|
|
29
|
+
variables: v ? { context: {
|
|
30
|
+
productId: v,
|
|
31
|
+
moduleId: y,
|
|
32
|
+
pageId: b
|
|
33
|
+
} } : void 0,
|
|
34
|
+
skip: !v,
|
|
35
|
+
fetchPolicy: "cache-and-network"
|
|
36
|
+
}), C = x?.toursForContext ?? [], w = S;
|
|
37
|
+
return /* @__PURE__ */ l("div", {
|
|
38
|
+
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
39
|
+
children: [/* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("h1", {
|
|
40
|
+
className: "text-xl sm:text-2xl font-bold text-foreground",
|
|
41
|
+
children: i("myTours.title")
|
|
42
|
+
}), /* @__PURE__ */ c("p", {
|
|
43
|
+
className: "text-sm sm:text-base text-muted-foreground",
|
|
44
|
+
children: i("myTours.subtitle")
|
|
45
|
+
})] }), w && C.length === 0 ? /* @__PURE__ */ c("div", {
|
|
46
|
+
className: "text-sm text-muted-foreground",
|
|
47
|
+
role: "status",
|
|
48
|
+
"aria-live": "polite",
|
|
49
|
+
children: i("myTours.loading")
|
|
50
|
+
}) : C.length === 0 ? /* @__PURE__ */ c("div", {
|
|
51
|
+
className: "rounded-lg border border-dashed border-border p-8 text-center",
|
|
52
|
+
children: /* @__PURE__ */ c("p", {
|
|
53
|
+
className: "text-sm text-muted-foreground",
|
|
54
|
+
role: "status",
|
|
55
|
+
"aria-live": "polite",
|
|
56
|
+
children: i("myTours.empty")
|
|
57
|
+
})
|
|
58
|
+
}) : /* @__PURE__ */ c("ul", {
|
|
59
|
+
className: "grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
|
|
60
|
+
children: C.map((e) => /* @__PURE__ */ l("li", {
|
|
61
|
+
className: "rounded-lg border border-border bg-card p-4 flex flex-col gap-2",
|
|
62
|
+
children: [/* @__PURE__ */ l("div", {
|
|
63
|
+
className: "flex items-start justify-between gap-2",
|
|
64
|
+
children: [/* @__PURE__ */ c("h2", {
|
|
65
|
+
className: "font-semibold text-foreground line-clamp-2",
|
|
66
|
+
children: e.name
|
|
67
|
+
}), /* @__PURE__ */ c(_, {
|
|
68
|
+
status: e.myProgress?.status,
|
|
69
|
+
tr: i
|
|
70
|
+
})]
|
|
71
|
+
}), e.description ? /* @__PURE__ */ c("p", {
|
|
72
|
+
className: "text-sm text-muted-foreground line-clamp-3",
|
|
73
|
+
children: e.description
|
|
74
|
+
}) : null]
|
|
75
|
+
}, e.id))
|
|
76
|
+
})]
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function _({ status: e, tr: t }) {
|
|
80
|
+
switch (e) {
|
|
81
|
+
case "COMPLETED": return /* @__PURE__ */ l("span", {
|
|
82
|
+
className: "inline-flex items-center gap-1 text-xs text-success",
|
|
83
|
+
children: [/* @__PURE__ */ c(i, { size: 14 }), t("myTours.status.completed")]
|
|
84
|
+
});
|
|
85
|
+
case "IN_PROGRESS": return /* @__PURE__ */ l("span", {
|
|
86
|
+
className: "inline-flex items-center gap-1 text-xs text-warning",
|
|
87
|
+
children: [/* @__PURE__ */ c(s, { size: 14 }), t("myTours.status.inProgress")]
|
|
88
|
+
});
|
|
89
|
+
case "SKIPPED":
|
|
90
|
+
case "DISMISSED": return /* @__PURE__ */ l("span", {
|
|
91
|
+
className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
|
|
92
|
+
children: [/* @__PURE__ */ c(o, { size: 14 }), t("myTours.status.skipped")]
|
|
93
|
+
});
|
|
94
|
+
default: return /* @__PURE__ */ l("span", {
|
|
95
|
+
className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
|
|
96
|
+
children: [/* @__PURE__ */ c(a, { size: 14 }), t("myTours.status.notStarted")]
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { g as MyToursPage };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=MyToursPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MyToursPage.js","names":[],"sources":["../../../src/tours/pages/MyToursPage.tsx"],"sourcesContent":["import { useEffect } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { CheckCircle2, Circle, MinusCircle, Play } from 'lucide-react';\nimport {\n useGetToursForContextQuery,\n type GetToursForContextQuery,\n} from '../../generated/global-operations';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useToursI18n, type ToursTranslationKey } from '../i18n';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\n\ntype TourSummary = GetToursForContextQuery['toursForContext'][number];\n\n// Plain slug validator for productId and moduleId.\nconst SLUG_PATTERN = /^[a-zA-Z0-9_-]+$/;\n\n// pageId is documented with path-style values like \"settings/team\" per\n// ToursRootProps — tighten to URL-safe characters only, but permit `/`.\nconst PAGE_PATH_PATTERN = /^[a-zA-Z0-9_\\-/]+$/;\n\nfunction safeSlug<T extends string | undefined>(\n raw: string | null | undefined,\n fallback: T\n): string | T {\n if (raw && SLUG_PATTERN.test(raw)) return raw;\n return fallback;\n}\n\nfunction safePageId<T extends string | undefined>(\n raw: string | null | undefined,\n fallback: T\n): string | T {\n if (raw && PAGE_PATH_PATTERN.test(raw)) return raw;\n return fallback;\n}\n\n/**\n * MyToursPage — user-facing browse for available tours.\n *\n * Lists tours returned by `toursForContext` (filtered to the current\n * product/module/page surface) and decorates each with the requesting\n * user's `TourUserProgress` status (Completed / In progress / Skipped /\n * Not started). Read-only — tours are actually played via the SDK overlay,\n * not via mutations on this page.\n */\nexport function MyToursPage() {\n const { tr } = useToursI18n();\n const {\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n } = useToursContext();\n const [searchParams] = useSearchParams();\n const eventBus = useEventBus();\n\n // Emit page view event on mount\n useEffect(() => {\n eventBus.emit('tours.page.viewed', {\n page: '/tours',\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Preferred source is the shell's productId prop (the host app knows\n // which product it is — \"workspaces\", \"vibecontrols\", \"bigconsole\", etc.).\n // Fall back to a query-param override for deep-link scenarios, but only\n // after passing a plain-slug validator so we never forward arbitrary\n // strings into the GraphQL variables. No hardcoded product default —\n // if neither source provides one, skip the query entirely so non-\n // workspaces integrations don't get workspaces tours.\n const productId =\n safeSlug(shellProductId, undefined) ?? safeSlug(searchParams.get('productId'), undefined);\n const moduleId =\n safeSlug(shellModuleId, undefined) ?? safeSlug(searchParams.get('moduleId'), undefined);\n const pageId =\n safePageId(shellPageId, undefined) ?? safePageId(searchParams.get('pageId'), undefined);\n\n const { data: toursData, loading: toursLoading } = useGetToursForContextQuery({\n variables: productId ? { context: { productId, moduleId, pageId } } : undefined,\n skip: !productId,\n fetchPolicy: 'cache-and-network',\n });\n\n const tours = toursData?.toursForContext ?? [];\n const isLoading = toursLoading;\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-foreground\">{tr('myTours.title')}</h1>\n <p className=\"text-sm sm:text-base text-muted-foreground\">{tr('myTours.subtitle')}</p>\n </div>\n\n {isLoading && tours.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\" role=\"status\" aria-live=\"polite\">\n {tr('myTours.loading')}\n </div>\n ) : tours.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border p-8 text-center\">\n <p className=\"text-sm text-muted-foreground\" role=\"status\" aria-live=\"polite\">\n {tr('myTours.empty')}\n </p>\n </div>\n ) : (\n <ul className=\"grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {tours.map((tour: TourSummary) => (\n <li\n key={tour.id}\n className=\"rounded-lg border border-border bg-card p-4 flex flex-col gap-2\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h2 className=\"font-semibold text-foreground line-clamp-2\">{tour.name}</h2>\n <ProgressBadge status={tour.myProgress?.status} tr={tr} />\n </div>\n {tour.description ? (\n <p className=\"text-sm text-muted-foreground line-clamp-3\">{tour.description}</p>\n ) : null}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction ProgressBadge({\n status,\n tr,\n}: {\n status: string | undefined;\n tr: (key: ToursTranslationKey) => string;\n}) {\n switch (status) {\n case 'COMPLETED':\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-success\">\n <CheckCircle2 size={14} />\n {tr('myTours.status.completed')}\n </span>\n );\n case 'IN_PROGRESS':\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-warning\">\n <Play size={14} />\n {tr('myTours.status.inProgress')}\n </span>\n );\n case 'SKIPPED':\n case 'DISMISSED':\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <MinusCircle size={14} />\n {tr('myTours.status.skipped')}\n </span>\n );\n default:\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <Circle size={14} />\n {tr('myTours.status.notStarted')}\n </span>\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MyToursProgressPage — user-facing read-only view of the current user's
|
|
3
|
+
* tour interactions, grouped by status (In progress / Completed / Skipped).
|
|
4
|
+
*/
|
|
5
|
+
export declare function MyToursProgressPage(): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=MyToursProgressPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MyToursProgressPage.d.ts","sourceRoot":"","sources":["../../../src/tours/pages/MyToursProgressPage.tsx"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,wBAAgB,mBAAmB,4CAyGlC"}
|