@burdenoff/microfe-tours 2026.527.1 → 2026.527.3

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 (31) hide show
  1. package/dist/tours/ToursUserRoutes.js +2 -4
  2. package/dist/tours/ToursUserRoutes.js.map +1 -1
  3. package/dist/tours/components/RouteHelpers.js +4 -4
  4. package/dist/tours/components/RouteHelpers.js.map +1 -1
  5. package/dist/tours/hooks/useTourPreview.js +39 -39
  6. package/dist/tours/hooks/useTourPreview.js.map +1 -1
  7. package/dist/tours/hooks/useToursList.js.map +1 -1
  8. package/dist/tours/i18n.js +4 -0
  9. package/dist/tours/i18n.js.map +1 -1
  10. package/dist/tours/pages/MediaLibraryPage.js +11 -8
  11. package/dist/tours/pages/MediaLibraryPage.js.map +1 -1
  12. package/dist/tours/pages/MyToursPage.js +59 -57
  13. package/dist/tours/pages/MyToursPage.js.map +1 -1
  14. package/dist/tours/pages/MyToursProgressPage.js +65 -61
  15. package/dist/tours/pages/MyToursProgressPage.js.map +1 -1
  16. package/dist/tours/pages/SchedulerPage.js +18 -4
  17. package/dist/tours/pages/SchedulerPage.js.map +1 -1
  18. package/dist/tours/pages/StepEditorPage.js +330 -276
  19. package/dist/tours/pages/StepEditorPage.js.map +1 -1
  20. package/dist/tours/pages/TemplateLibraryPage.js +7 -2
  21. package/dist/tours/pages/TemplateLibraryPage.js.map +1 -1
  22. package/dist/tours/pages/TourBuilderPage.js +144 -101
  23. package/dist/tours/pages/TourBuilderPage.js.map +1 -1
  24. package/dist/tours/pages/TourPreviewPage.js +12 -4
  25. package/dist/tours/pages/TourPreviewPage.js.map +1 -1
  26. package/dist/tours/pages/ToursListPage.js +165 -149
  27. package/dist/tours/pages/ToursListPage.js.map +1 -1
  28. package/dist/tours/providers/ToursProvider.js +1 -1
  29. package/dist/tours/providers/ToursProvider.js.map +1 -1
  30. package/dist/tours/utils/index.js.map +1 -1
  31. package/package.json +1 -1
@@ -50,11 +50,9 @@ function _() {
50
50
  });
51
51
  }
52
52
  function v() {
53
- return /* @__PURE__ */ u("div", {
53
+ return /* @__PURE__ */ u("output", {
54
54
  className: "flex min-h-[300px] items-center justify-center p-6 text-center",
55
- role: "status",
56
- "aria-live": "polite",
57
- children: /* @__PURE__ */ u("p", {
55
+ children: /* @__PURE__ */ u("span", {
58
56
  className: "max-w-md text-sm text-muted-foreground",
59
57
  children: "Tours are not available for this workspace right now."
60
58
  })
@@ -1 +1 @@
1
- {"version":3,"file":"ToursUserRoutes.js","names":[],"sources":["../../src/tours/ToursUserRoutes.tsx"],"sourcesContent":["import { lazy, Suspense, useEffect } from 'react';\nimport { Route, Routes, useNavigate } from 'react-router-dom';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { TOURS_FEATURES } from '../constants/featureFlags';\nimport { LazyErrorBoundary, PageLoader } from './components/RouteHelpers';\nimport { useToursContext } from './providers/ToursProvider';\nimport { useToursI18n } from './i18n';\n\n// Eager — primary landing\nimport { MyToursPage } from './pages/MyToursPage';\n\n// Lazy — secondary surface\nconst MyToursProgressPage = lazy(() =>\n import('./pages/MyToursProgressPage').then((m) => ({ default: m.MyToursProgressPage }))\n);\n\n/**\n * 404-style landing shown briefly before redirecting any unknown\n * `/tours/*` path back to the user landing. Gives bookmarkers a signal\n * that the page they tried to load doesn't exist under the user surface,\n * rather than silently snapping them back to `/tours`.\n *\n * Note: react-router's relative `navigate('')` / `navigate('.')` resolves\n * to the current pathname, so for a `*` wildcard matched at\n * `/tours/new` those calls would keep the browser on `/tours/new`. We\n * redirect absolutely via `useToursContext().basePath` so the redirect\n * lands on the landing regardless of how deep the unknown URL was.\n */\nfunction UserRouteNotFound() {\n const navigate = useNavigate();\n const { tr } = useToursI18n();\n const { basePath } = useToursContext();\n\n useEffect(() => {\n const target = basePath && basePath.length > 0 ? basePath : '/';\n const id = window.setTimeout(() => navigate(target, { replace: true }), 1500);\n return () => window.clearTimeout(id);\n }, [navigate, basePath]);\n\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[300px] text-center p-6 gap-2\">\n <h2 className=\"text-lg font-semibold text-foreground\">{tr('myTours.notFoundTitle')}</h2>\n <p className=\"text-sm text-muted-foreground max-w-md\">{tr('myTours.notFoundBody')}</p>\n </div>\n );\n}\n\n/**\n * User-facing routes for the Tours microfrontend.\n *\n * Exposes a tight surface — browse available tours and review own progress.\n * Tour playback itself is handled by the SDK overlay, not by routed pages.\n *\n * Routes (relative to basePath, e.g. `/tours`):\n * - `/` → MyToursPage (browse + status)\n * - `/progress` → MyToursProgressPage (own progress, grouped)\n * - `*` → UserRouteNotFound, then absolute navigate to basePath\n * (collapses any admin-style URL like `/new`, `/media`,\n * `/templates` after briefly signalling unavailability)\n */\nexport function ToursUserRoutes() {\n return (\n <FeatureGate flag={TOURS_FEATURES.USER} defaultValue={true} fallback={<UserToursUnavailable />}>\n <LazyErrorBoundary>\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={<MyToursPage />} />\n <Route path=\"progress\" element={<MyToursProgressPage />} />\n <Route path=\"*\" element={<UserRouteNotFound />} />\n </Routes>\n </Suspense>\n </LazyErrorBoundary>\n </FeatureGate>\n );\n}\n\nfunction UserToursUnavailable() {\n return (\n <div\n className=\"flex min-h-[300px] items-center justify-center p-6 text-center\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <p className=\"max-w-md text-sm text-muted-foreground\">\n Tours are not available for this workspace right now.\n </p>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAYA,IAAM,IAAsB,QAC1B,OAAO,kCAA+B,MAAM,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,CACxF;AAcD,SAAS,IAAoB;CAC3B,IAAM,IAAW,GAAa,EACxB,EAAE,UAAO,GAAc,EACvB,EAAE,gBAAa,GAAiB;AAQtC,QANA,QAAgB;EACd,IAAM,IAAS,KAAY,EAAS,SAAS,IAAI,IAAW,KACtD,IAAK,OAAO,iBAAiB,EAAS,GAAQ,EAAE,SAAS,IAAM,CAAC,EAAE,KAAK;AAC7E,eAAa,OAAO,aAAa,EAAG;IACnC,CAAC,GAAU,EAAS,CAAC,EAGtB,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,MAAD;GAAI,WAAU;aAAyC,EAAG,wBAAwB;GAAM,CAAA,EACxF,kBAAC,KAAD;GAAG,WAAU;aAA0C,EAAG,uBAAuB;GAAK,CAAA,CAClF;;;AAiBV,SAAgB,IAAkB;AAChC,QACE,kBAAC,GAAD;EAAa,MAAM,EAAe;EAAM,cAAc;EAAM,UAAU,kBAAC,GAAD,EAAwB,CAAA;YAC5F,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GAAU,UAAU,kBAAC,GAAD,EAAc,CAAA;aAChC,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,GAAD;KAAO,OAAA;KAAM,SAAS,kBAAC,GAAD,EAAe,CAAA;KAAI,CAAA;IACzC,kBAAC,GAAD;KAAO,MAAK;KAAW,SAAS,kBAAC,GAAD,EAAuB,CAAA;KAAI,CAAA;IAC3D,kBAAC,GAAD;KAAO,MAAK;KAAI,SAAS,kBAAC,GAAD,EAAqB,CAAA;KAAI,CAAA;IAC3C,EAAA,CAAA;GACA,CAAA,EACO,CAAA;EACR,CAAA;;AAIlB,SAAS,IAAuB;AAC9B,QACE,kBAAC,OAAD;EACE,WAAU;EACV,MAAK;EACL,aAAU;YAEV,kBAAC,KAAD;GAAG,WAAU;aAAyC;GAElD,CAAA;EACA,CAAA"}
1
+ {"version":3,"file":"ToursUserRoutes.js","names":[],"sources":["../../src/tours/ToursUserRoutes.tsx"],"sourcesContent":["import { lazy, Suspense, useEffect } from 'react';\nimport { Route, Routes, useNavigate } from 'react-router-dom';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { TOURS_FEATURES } from '../constants/featureFlags';\nimport { LazyErrorBoundary, PageLoader } from './components/RouteHelpers';\nimport { useToursContext } from './providers/ToursProvider';\nimport { useToursI18n } from './i18n';\n\n// Eager — primary landing\nimport { MyToursPage } from './pages/MyToursPage';\n\n// Lazy — secondary surface\nconst MyToursProgressPage = lazy(() =>\n import('./pages/MyToursProgressPage').then((m) => ({ default: m.MyToursProgressPage }))\n);\n\n/**\n * 404-style landing shown briefly before redirecting any unknown\n * `/tours/*` path back to the user landing. Gives bookmarkers a signal\n * that the page they tried to load doesn't exist under the user surface,\n * rather than silently snapping them back to `/tours`.\n *\n * Note: react-router's relative `navigate('')` / `navigate('.')` resolves\n * to the current pathname, so for a `*` wildcard matched at\n * `/tours/new` those calls would keep the browser on `/tours/new`. We\n * redirect absolutely via `useToursContext().basePath` so the redirect\n * lands on the landing regardless of how deep the unknown URL was.\n */\nfunction UserRouteNotFound() {\n const navigate = useNavigate();\n const { tr } = useToursI18n();\n const { basePath } = useToursContext();\n\n useEffect(() => {\n const target = basePath && basePath.length > 0 ? basePath : '/';\n const id = window.setTimeout(() => navigate(target, { replace: true }), 1500);\n return () => window.clearTimeout(id);\n }, [navigate, basePath]);\n\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[300px] text-center p-6 gap-2\">\n <h2 className=\"text-lg font-semibold text-foreground\">{tr('myTours.notFoundTitle')}</h2>\n <p className=\"text-sm text-muted-foreground max-w-md\">{tr('myTours.notFoundBody')}</p>\n </div>\n );\n}\n\n/**\n * User-facing routes for the Tours microfrontend.\n *\n * Exposes a tight surface — browse available tours and review own progress.\n * Tour playback itself is handled by the SDK overlay, not by routed pages.\n *\n * Routes (relative to basePath, e.g. `/tours`):\n * - `/` → MyToursPage (browse + status)\n * - `/progress` → MyToursProgressPage (own progress, grouped)\n * - `*` → UserRouteNotFound, then absolute navigate to basePath\n * (collapses any admin-style URL like `/new`, `/media`,\n * `/templates` after briefly signalling unavailability)\n */\nexport function ToursUserRoutes() {\n return (\n <FeatureGate flag={TOURS_FEATURES.USER} defaultValue={true} fallback={<UserToursUnavailable />}>\n <LazyErrorBoundary>\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={<MyToursPage />} />\n <Route path=\"progress\" element={<MyToursProgressPage />} />\n <Route path=\"*\" element={<UserRouteNotFound />} />\n </Routes>\n </Suspense>\n </LazyErrorBoundary>\n </FeatureGate>\n );\n}\n\nfunction UserToursUnavailable() {\n return (\n <output className=\"flex min-h-[300px] items-center justify-center p-6 text-center\">\n <span className=\"max-w-md text-sm text-muted-foreground\">\n Tours are not available for this workspace right now.\n </span>\n </output>\n );\n}\n"],"mappings":";;;;;;;;;;AAYA,IAAM,IAAsB,QAC1B,OAAO,kCAA+B,MAAM,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,CACxF;AAcD,SAAS,IAAoB;CAC3B,IAAM,IAAW,GAAa,EACxB,EAAE,UAAO,GAAc,EACvB,EAAE,gBAAa,GAAiB;AAQtC,QANA,QAAgB;EACd,IAAM,IAAS,KAAY,EAAS,SAAS,IAAI,IAAW,KACtD,IAAK,OAAO,iBAAiB,EAAS,GAAQ,EAAE,SAAS,IAAM,CAAC,EAAE,KAAK;AAC7E,eAAa,OAAO,aAAa,EAAG;IACnC,CAAC,GAAU,EAAS,CAAC,EAGtB,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,MAAD;GAAI,WAAU;aAAyC,EAAG,wBAAwB;GAAM,CAAA,EACxF,kBAAC,KAAD;GAAG,WAAU;aAA0C,EAAG,uBAAuB;GAAK,CAAA,CAClF;;;AAiBV,SAAgB,IAAkB;AAChC,QACE,kBAAC,GAAD;EAAa,MAAM,EAAe;EAAM,cAAc;EAAM,UAAU,kBAAC,GAAD,EAAwB,CAAA;YAC5F,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GAAU,UAAU,kBAAC,GAAD,EAAc,CAAA;aAChC,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,GAAD;KAAO,OAAA;KAAM,SAAS,kBAAC,GAAD,EAAe,CAAA;KAAI,CAAA;IACzC,kBAAC,GAAD;KAAO,MAAK;KAAW,SAAS,kBAAC,GAAD,EAAuB,CAAA;KAAI,CAAA;IAC3D,kBAAC,GAAD;KAAO,MAAK;KAAI,SAAS,kBAAC,GAAD,EAAqB,CAAA;KAAI,CAAA;IAC3C,EAAA,CAAA;GACA,CAAA,EACO,CAAA;EACR,CAAA;;AAIlB,SAAS,IAAuB;AAC9B,QACE,kBAAC,UAAD;EAAQ,WAAU;YAChB,kBAAC,QAAD;GAAM,WAAU;aAAyC;GAElD,CAAA;EACA,CAAA"}
@@ -5,7 +5,7 @@ import { jsx as a, jsxs as o } from "react/jsx-runtime";
5
5
  function s() {
6
6
  return /* @__PURE__ */ a("div", {
7
7
  className: "flex items-center justify-center min-h-[200px]",
8
- children: /* @__PURE__ */ a(r, { className: "h-8 w-8 animate-spin text-primary" })
8
+ children: /* @__PURE__ */ a(r, { className: "size-8 animate-spin text-primary" })
9
9
  });
10
10
  }
11
11
  var c = 3, l = class extends e {
@@ -40,7 +40,7 @@ var c = 3, l = class extends e {
40
40
  children: [
41
41
  /* @__PURE__ */ a("div", {
42
42
  className: "p-3 rounded-full bg-destructive/10",
43
- children: /* @__PURE__ */ a(n, { className: "h-8 w-8 text-destructive" })
43
+ children: /* @__PURE__ */ a(n, { className: "size-8 text-destructive" })
44
44
  }),
45
45
  /* @__PURE__ */ o("div", {
46
46
  className: "space-y-2",
@@ -56,12 +56,12 @@ var c = 3, l = class extends e {
56
56
  type: "button",
57
57
  onClick: this.handleRetry,
58
58
  className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors",
59
- children: [/* @__PURE__ */ a(i, { className: "h-4 w-4" }), "Try Again"]
59
+ children: [/* @__PURE__ */ a(i, { className: "size-4" }), "Try Again"]
60
60
  }) : /* @__PURE__ */ o("button", {
61
61
  type: "button",
62
62
  onClick: () => window.location.reload(),
63
63
  className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors",
64
- children: [/* @__PURE__ */ a(i, { className: "h-4 w-4" }), "Refresh Page"]
64
+ children: [/* @__PURE__ */ a(i, { className: "size-4" }), "Refresh Page"]
65
65
  })
66
66
  ]
67
67
  });
@@ -1 +1 @@
1
- {"version":3,"file":"RouteHelpers.js","names":[],"sources":["../../../src/tours/components/RouteHelpers.tsx"],"sourcesContent":["import { Component, Fragment, type ReactNode, type ErrorInfo } from 'react';\nimport { Loader2, AlertTriangle, RefreshCw } from 'lucide-react';\n\n/**\n * Loading fallback for lazy-loaded pages.\n * Shared between ToursAdminRoutes and ToursUserRoutes (Section D/E of BOFF-2391).\n */\nexport function PageLoader() {\n return (\n <div className=\"flex items-center justify-center min-h-[200px]\">\n <Loader2 className=\"h-8 w-8 animate-spin text-primary\" />\n </div>\n );\n}\n\n/** Max consecutive retries before the error screen suggests a full reload. */\nconst MAX_RETRY_COUNT = 3;\n\n/**\n * Error boundary for lazy-loaded page chunks.\n *\n * React.lazy() caches rejected promises, so resetting error state alone would\n * re-throw the same cached rejection. The retryCount key forces a full remount\n * of the subtree on retry, creating fresh lazy-component instances.\n */\nexport class LazyErrorBoundary extends Component<\n { children: ReactNode },\n { hasError: boolean; error: Error | null; retryCount: number }\n> {\n constructor(props: { children: ReactNode }) {\n super(props);\n this.state = { hasError: false, error: null, retryCount: 0 };\n }\n\n static getDerivedStateFromError(error: Error) {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n // TODO: forward to error monitoring service (Sentry, etc.)\n // Log the full error server-side / in the console; do NOT surface the raw\n // message to users since chunk-load errors can include CDN paths, build\n // hashes, or deployment details that shouldn't appear in the UI.\n console.error('[Tours] Lazy page load failed:', error, errorInfo);\n }\n\n handleRetry = () => {\n this.setState((prev) => ({\n hasError: false,\n error: null,\n retryCount: prev.retryCount + 1,\n }));\n };\n\n render() {\n if (this.state.hasError) {\n const canRetry = this.state.retryCount < MAX_RETRY_COUNT;\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] gap-4 text-center p-6\">\n <div className=\"p-3 rounded-full bg-destructive/10\">\n <AlertTriangle className=\"h-8 w-8 text-destructive\" />\n </div>\n <div className=\"space-y-2\">\n <h3 className=\"text-lg font-semibold text-foreground\">Failed to load page</h3>\n <p className=\"text-sm text-muted-foreground max-w-md\">\n {canRetry\n ? 'The page chunk could not be loaded. This may be due to a network issue.'\n : 'The page chunk could not be loaded after multiple attempts. Please refresh the page.'}\n </p>\n </div>\n {canRetry ? (\n <button\n type=\"button\"\n onClick={this.handleRetry}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n <RefreshCw className=\"h-4 w-4\" />\n Try Again\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n <RefreshCw className=\"h-4 w-4\" />\n Refresh Page\n </button>\n )}\n </div>\n );\n }\n\n return <Fragment key={this.state.retryCount}>{this.props.children}</Fragment>;\n }\n}\n"],"mappings":";;;;AAOA,SAAgB,IAAa;AAC3B,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,qCAAsC,CAAA;EACrD,CAAA;;AAKV,IAAM,IAAkB,GASX,IAAb,cAAuC,EAGrC;CACA,YAAY,GAAgC;AAE1C,EADA,MAAM,EAAM,EACZ,KAAK,QAAQ;GAAE,UAAU;GAAO,OAAO;GAAM,YAAY;GAAG;;CAG9D,OAAO,yBAAyB,GAAc;AAC5C,SAAO;GAAE,UAAU;GAAM;GAAO;;CAGlC,kBAAkB,GAAc,GAAsB;AAKpD,UAAQ,MAAM,kCAAkC,GAAO,EAAU;;CAGnE,oBAAoB;AAClB,OAAK,UAAU,OAAU;GACvB,UAAU;GACV,OAAO;GACP,YAAY,EAAK,aAAa;GAC/B,EAAE;;CAGL,SAAS;AACP,MAAI,KAAK,MAAM,UAAU;GACvB,IAAM,IAAW,KAAK,MAAM,aAAa;AACzC,UACE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAe,WAAU,4BAA6B,CAAA;MAClD,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAwC;OAAwB,CAAA,EAC9E,kBAAC,KAAD;OAAG,WAAU;iBACV,IACG,4EACA;OACF,CAAA,CACA;;KACL,IACC,kBAAC,UAAD;MACE,MAAK;MACL,SAAS,KAAK;MACd,WAAU;gBAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,YAE1B;UAET,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,OAAO,SAAS,QAAQ;MACvC,WAAU;gBAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,eAE1B;;KAEP;;;AAIV,SAAO,kBAAC,GAAD,EAAA,UAAuC,KAAK,MAAM,UAAoB,EAAvD,KAAK,MAAM,WAA4C"}
1
+ {"version":3,"file":"RouteHelpers.js","names":[],"sources":["../../../src/tours/components/RouteHelpers.tsx"],"sourcesContent":["import { Component, Fragment, type ReactNode, type ErrorInfo } from 'react';\nimport { Loader2, AlertTriangle, RefreshCw } from 'lucide-react';\n\n/**\n * Loading fallback for lazy-loaded pages.\n * Shared between ToursAdminRoutes and ToursUserRoutes (Section D/E of BOFF-2391).\n */\nexport function PageLoader() {\n return (\n <div className=\"flex items-center justify-center min-h-[200px]\">\n <Loader2 className=\"size-8 animate-spin text-primary\" />\n </div>\n );\n}\n\n/** Max consecutive retries before the error screen suggests a full reload. */\nconst MAX_RETRY_COUNT = 3;\n\n/**\n * Error boundary for lazy-loaded page chunks.\n *\n * React.lazy() caches rejected promises, so resetting error state alone would\n * re-throw the same cached rejection. The retryCount key forces a full remount\n * of the subtree on retry, creating fresh lazy-component instances.\n */\nexport class LazyErrorBoundary extends Component<\n { children: ReactNode },\n { hasError: boolean; error: Error | null; retryCount: number }\n> {\n constructor(props: { children: ReactNode }) {\n super(props);\n this.state = { hasError: false, error: null, retryCount: 0 };\n }\n\n static getDerivedStateFromError(error: Error) {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n // TODO: forward to error monitoring service (Sentry, etc.)\n // Log the full error server-side / in the console; do NOT surface the raw\n // message to users since chunk-load errors can include CDN paths, build\n // hashes, or deployment details that shouldn't appear in the UI.\n console.error('[Tours] Lazy page load failed:', error, errorInfo);\n }\n\n handleRetry = () => {\n this.setState((prev) => ({\n hasError: false,\n error: null,\n retryCount: prev.retryCount + 1,\n }));\n };\n\n render() {\n if (this.state.hasError) {\n const canRetry = this.state.retryCount < MAX_RETRY_COUNT;\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] gap-4 text-center p-6\">\n <div className=\"p-3 rounded-full bg-destructive/10\">\n <AlertTriangle className=\"size-8 text-destructive\" />\n </div>\n <div className=\"space-y-2\">\n <h3 className=\"text-lg font-semibold text-foreground\">Failed to load page</h3>\n <p className=\"text-sm text-muted-foreground max-w-md\">\n {canRetry\n ? 'The page chunk could not be loaded. This may be due to a network issue.'\n : 'The page chunk could not be loaded after multiple attempts. Please refresh the page.'}\n </p>\n </div>\n {canRetry ? (\n <button\n type=\"button\"\n onClick={this.handleRetry}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n <RefreshCw className=\"size-4\" />\n Try Again\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh Page\n </button>\n )}\n </div>\n );\n }\n\n return <Fragment key={this.state.retryCount}>{this.props.children}</Fragment>;\n }\n}\n"],"mappings":";;;;AAOA,SAAgB,IAAa;AAC3B,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,oCAAqC,CAAA;EACpD,CAAA;;AAKV,IAAM,IAAkB,GASX,IAAb,cAAuC,EAGrC;CACA,YAAY,GAAgC;AAE1C,EADA,MAAM,EAAM,EACZ,KAAK,QAAQ;GAAE,UAAU;GAAO,OAAO;GAAM,YAAY;GAAG;;CAG9D,OAAO,yBAAyB,GAAc;AAC5C,SAAO;GAAE,UAAU;GAAM;GAAO;;CAGlC,kBAAkB,GAAc,GAAsB;AAKpD,UAAQ,MAAM,kCAAkC,GAAO,EAAU;;CAGnE,oBAAoB;AAClB,OAAK,UAAU,OAAU;GACvB,UAAU;GACV,OAAO;GACP,YAAY,EAAK,aAAa;GAC/B,EAAE;;CAGL,SAAS;AACP,MAAI,KAAK,MAAM,UAAU;GACvB,IAAM,IAAW,KAAK,MAAM,aAAa;AACzC,UACE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAe,WAAU,2BAA4B,CAAA;MACjD,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAwC;OAAwB,CAAA,EAC9E,kBAAC,KAAD;OAAG,WAAU;iBACV,IACG,4EACA;OACF,CAAA,CACA;;KACL,IACC,kBAAC,UAAD;MACE,MAAK;MACL,SAAS,KAAK;MACd,WAAU;gBAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAAA,YAEzB;UAET,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,OAAO,SAAS,QAAQ;MACvC,WAAU;gBAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAAA,eAEzB;;KAEP;;;AAIV,SAAO,kBAAC,GAAD,EAAA,UAAuC,KAAK,MAAM,UAAoB,EAAvD,KAAK,MAAM,WAA4C"}
@@ -1,22 +1,22 @@
1
1
  import { useToursStore as e } from "../store/toursStore.js";
2
- import { useCallback as t, useState as n } from "react";
2
+ import { useCallback as t, useMemo as n, useState as r } from "react";
3
3
  //#region src/tours/hooks/useTourPreview.ts
4
- function r(r) {
5
- let [i, a] = n(!1), [o, s] = n(0), [c, l] = n(null), { devicePreview: u, setDevicePreview: d } = e(), f = r?.steps || [], p = f[o], m = t(() => {
6
- a(!0), s(0);
7
- }, []), h = t(() => {
8
- a(!1), s(0);
4
+ function i(i) {
5
+ let [a, o] = r(!1), [s, c] = r(0), [l, u] = r(null), { devicePreview: d, setDevicePreview: f } = e(), p = n(() => i?.steps ?? [], [i?.steps]), m = p[s], h = t(() => {
6
+ o(!0), c(0);
9
7
  }, []), g = t(() => {
10
- o < f.length - 1 && s((e) => e + 1);
11
- }, [o, f.length]), _ = t(() => {
12
- o > 0 && s((e) => e - 1);
13
- }, [o]), v = t((e) => {
14
- e >= 0 && e < f.length && s(e);
15
- }, [f.length]), y = t((e) => {
16
- d(e);
17
- }, [d]), b = t(() => {
8
+ o(!1), c(0);
9
+ }, []), _ = t(() => {
10
+ s < p.length - 1 && c((e) => e + 1);
11
+ }, [s, p.length]), v = t(() => {
12
+ s > 0 && c((e) => e - 1);
13
+ }, [s]), y = t((e) => {
14
+ e >= 0 && e < p.length && c(e);
15
+ }, [p.length]), b = t((e) => {
16
+ f(e);
17
+ }, [f]), x = t(() => {
18
18
  let e = [], t = [];
19
- if (!r) return e.push("No tour selected"), l({
19
+ if (!i) return e.push("No tour selected"), u({
20
20
  valid: !1,
21
21
  errors: e,
22
22
  warnings: t
@@ -25,13 +25,13 @@ function r(r) {
25
25
  errors: e,
26
26
  warnings: t
27
27
  };
28
- f.length === 0 && e.push("Tour has no steps"), f.forEach((n, r) => {
29
- n.placement !== "CENTER" && !n.targetSelector && e.push(`Step ${r + 1} has no selector`), (!n.content || n.content.trim() === "") && t.push(`Step ${r + 1} has empty content`), u === "mobile" && !n.targetSelectorMobile && !n.targetSelector && t.push(`Step ${r + 1} has no mobile selector`), u === "tablet" && !n.targetSelectorTablet && !n.targetSelector && t.push(`Step ${r + 1} has no tablet selector`), u === "desktop" && !n.targetSelectorDesktop && !n.targetSelector && t.push(`Step ${r + 1} has no desktop selector`), n.media && n.media.length > 0 && n.media.forEach((n, i) => {
28
+ p.length === 0 && e.push("Tour has no steps"), p.forEach((n, r) => {
29
+ n.placement !== "CENTER" && !n.targetSelector && e.push(`Step ${r + 1} has no selector`), (!n.content || n.content.trim() === "") && t.push(`Step ${r + 1} has empty content`), d === "mobile" && !n.targetSelectorMobile && !n.targetSelector && t.push(`Step ${r + 1} has no mobile selector`), d === "tablet" && !n.targetSelectorTablet && !n.targetSelector && t.push(`Step ${r + 1} has no tablet selector`), d === "desktop" && !n.targetSelectorDesktop && !n.targetSelector && t.push(`Step ${r + 1} has no desktop selector`), n.media && n.media.length > 0 && n.media.forEach((n, i) => {
30
30
  n.media.cdnUrl || e.push(`Step ${r + 1}, media ${i + 1} has no URL`), n.media.type === "IMAGE" && !n.media.altText && t.push(`Step ${r + 1}, media ${i + 1} missing alt text`);
31
31
  });
32
32
  });
33
33
  let n = e.length === 0;
34
- return l({
34
+ return u({
35
35
  valid: n,
36
36
  errors: e,
37
37
  warnings: t
@@ -41,30 +41,30 @@ function r(r) {
41
41
  warnings: t
42
42
  };
43
43
  }, [
44
- r,
45
- f,
46
- u
47
- ]), x = t(async () => r ? `${window.location.origin}/tours/${r.id}/preview` : null, [r]);
44
+ i,
45
+ p,
46
+ d
47
+ ]), S = t(async () => i ? `${window.location.origin}/tours/${i.id}/preview` : null, [i]);
48
48
  return {
49
- isPreviewActive: i,
50
- currentStepIndex: o,
51
- currentStep: p,
52
- devicePreview: u,
53
- validationResults: c,
54
- totalSteps: f.length,
55
- canGoNext: o < f.length - 1,
56
- canGoPrevious: o > 0,
57
- startPreview: m,
58
- stopPreview: h,
59
- nextStep: g,
60
- previousStep: _,
61
- goToStep: v,
62
- changeDevice: y,
63
- validateForPreview: b,
64
- generatePreviewLink: x
49
+ isPreviewActive: a,
50
+ currentStepIndex: s,
51
+ currentStep: m,
52
+ devicePreview: d,
53
+ validationResults: l,
54
+ totalSteps: p.length,
55
+ canGoNext: s < p.length - 1,
56
+ canGoPrevious: s > 0,
57
+ startPreview: h,
58
+ stopPreview: g,
59
+ nextStep: _,
60
+ previousStep: v,
61
+ goToStep: y,
62
+ changeDevice: b,
63
+ validateForPreview: x,
64
+ generatePreviewLink: S
65
65
  };
66
66
  }
67
67
  //#endregion
68
- export { r as useTourPreview };
68
+ export { i as useTourPreview };
69
69
 
70
70
  //# sourceMappingURL=useTourPreview.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useTourPreview.js","names":[],"sources":["../../../src/tours/hooks/useTourPreview.ts"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { useToursStore } from '../store/toursStore';\nimport type { Tour, DeviceType } from '../types';\n\n/**\n * Hook for tour preview functionality\n * Manages preview mode, device simulation, and validation\n */\nexport function useTourPreview(tour?: Tour) {\n const [isPreviewActive, setIsPreviewActive] = useState(false);\n const [currentStepIndex, setCurrentStepIndex] = useState(0);\n const [validationResults, setValidationResults] = useState<{\n valid: boolean;\n errors: string[];\n warnings: string[];\n } | null>(null);\n\n const { devicePreview, setDevicePreview } = useToursStore();\n\n const steps = tour?.steps || [];\n const currentStep = steps[currentStepIndex];\n\n /**\n * Start preview mode\n */\n const startPreview = useCallback(() => {\n setIsPreviewActive(true);\n setCurrentStepIndex(0);\n }, []);\n\n /**\n * Stop preview mode\n */\n const stopPreview = useCallback(() => {\n setIsPreviewActive(false);\n setCurrentStepIndex(0);\n }, []);\n\n /**\n * Navigate to next step\n */\n const nextStep = useCallback(() => {\n if (currentStepIndex < steps.length - 1) {\n setCurrentStepIndex((prev) => prev + 1);\n }\n }, [currentStepIndex, steps.length]);\n\n /**\n * Navigate to previous step\n */\n const previousStep = useCallback(() => {\n if (currentStepIndex > 0) {\n setCurrentStepIndex((prev) => prev - 1);\n }\n }, [currentStepIndex]);\n\n /**\n * Jump to specific step\n */\n const goToStep = useCallback(\n (index: number) => {\n if (index >= 0 && index < steps.length) {\n setCurrentStepIndex(index);\n }\n },\n [steps.length]\n );\n\n /**\n * Change preview device\n */\n const changeDevice = useCallback(\n (device: DeviceType) => {\n setDevicePreview(device);\n },\n [setDevicePreview]\n );\n\n /**\n * Validate tour for preview\n * Checks if all selectors exist, media is accessible, etc.\n */\n const validateForPreview = useCallback(() => {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n if (!tour) {\n errors.push('No tour selected');\n setValidationResults({ valid: false, errors, warnings });\n return { valid: false, errors, warnings };\n }\n\n if (steps.length === 0) {\n errors.push('Tour has no steps');\n }\n\n steps.forEach((step, index) => {\n if (step.placement !== 'CENTER' && !step.targetSelector) {\n errors.push(`Step ${index + 1} has no selector`);\n }\n\n if (!step.content || step.content.trim() === '') {\n warnings.push(`Step ${index + 1} has empty content`);\n }\n\n // Check device-specific selectors\n if (devicePreview === 'mobile' && !step.targetSelectorMobile && !step.targetSelector) {\n warnings.push(`Step ${index + 1} has no mobile selector`);\n }\n if (devicePreview === 'tablet' && !step.targetSelectorTablet && !step.targetSelector) {\n warnings.push(`Step ${index + 1} has no tablet selector`);\n }\n if (devicePreview === 'desktop' && !step.targetSelectorDesktop && !step.targetSelector) {\n warnings.push(`Step ${index + 1} has no desktop selector`);\n }\n\n // Check media\n if (step.media && step.media.length > 0) {\n step.media.forEach((media, mediaIndex) => {\n if (!media.media.cdnUrl) {\n errors.push(`Step ${index + 1}, media ${mediaIndex + 1} has no URL`);\n }\n if (media.media.type === 'IMAGE' && !media.media.altText) {\n warnings.push(`Step ${index + 1}, media ${mediaIndex + 1} missing alt text`);\n }\n });\n }\n });\n\n const valid = errors.length === 0;\n setValidationResults({ valid, errors, warnings });\n return { valid, errors, warnings };\n }, [tour, steps, devicePreview]);\n\n /**\n * Generate shareable preview link\n */\n const generatePreviewLink = useCallback(async () => {\n if (!tour) return null;\n return `${window.location.origin}/tours/${tour.id}/preview`;\n }, [tour]);\n\n return {\n isPreviewActive,\n currentStepIndex,\n currentStep,\n devicePreview,\n validationResults,\n totalSteps: steps.length,\n canGoNext: currentStepIndex < steps.length - 1,\n canGoPrevious: currentStepIndex > 0,\n startPreview,\n stopPreview,\n nextStep,\n previousStep,\n goToStep,\n changeDevice,\n validateForPreview,\n generatePreviewLink,\n };\n}\n"],"mappings":";;;AAQA,SAAgB,EAAe,GAAa;CAC1C,IAAM,CAAC,GAAiB,KAAsB,EAAS,GAAM,EACvD,CAAC,GAAkB,KAAuB,EAAS,EAAE,EACrD,CAAC,GAAmB,KAAwB,EAIxC,KAAK,EAET,EAAE,kBAAe,wBAAqB,GAAe,EAErD,IAAQ,GAAM,SAAS,EAAE,EACzB,IAAc,EAAM,IAKpB,IAAe,QAAkB;AAErC,EADA,EAAmB,GAAK,EACxB,EAAoB,EAAE;IACrB,EAAE,CAAC,EAKA,IAAc,QAAkB;AAEpC,EADA,EAAmB,GAAM,EACzB,EAAoB,EAAE;IACrB,EAAE,CAAC,EAKA,IAAW,QAAkB;AACjC,EAAI,IAAmB,EAAM,SAAS,KACpC,GAAqB,MAAS,IAAO,EAAE;IAExC,CAAC,GAAkB,EAAM,OAAO,CAAC,EAK9B,IAAe,QAAkB;AACrC,EAAI,IAAmB,KACrB,GAAqB,MAAS,IAAO,EAAE;IAExC,CAAC,EAAiB,CAAC,EAKhB,IAAW,GACd,MAAkB;AACjB,EAAI,KAAS,KAAK,IAAQ,EAAM,UAC9B,EAAoB,EAAM;IAG9B,CAAC,EAAM,OAAO,CACf,EAKK,IAAe,GAClB,MAAuB;AACtB,IAAiB,EAAO;IAE1B,CAAC,EAAiB,CACnB,EAMK,IAAqB,QAAkB;EAC3C,IAAM,IAAmB,EAAE,EACrB,IAAqB,EAAE;AAE7B,MAAI,CAAC,EAGH,QAFA,EAAO,KAAK,mBAAmB,EAC/B,EAAqB;GAAE,OAAO;GAAO;GAAQ;GAAU,CAAC,EACjD;GAAE,OAAO;GAAO;GAAQ;GAAU;AAO3C,EAJI,EAAM,WAAW,KACnB,EAAO,KAAK,oBAAoB,EAGlC,EAAM,SAAS,GAAM,MAAU;AAqB7B,GApBI,EAAK,cAAc,YAAY,CAAC,EAAK,kBACvC,EAAO,KAAK,QAAQ,IAAQ,EAAE,kBAAkB,GAG9C,CAAC,EAAK,WAAW,EAAK,QAAQ,MAAM,KAAK,OAC3C,EAAS,KAAK,QAAQ,IAAQ,EAAE,oBAAoB,EAIlD,MAAkB,YAAY,CAAC,EAAK,wBAAwB,CAAC,EAAK,kBACpE,EAAS,KAAK,QAAQ,IAAQ,EAAE,yBAAyB,EAEvD,MAAkB,YAAY,CAAC,EAAK,wBAAwB,CAAC,EAAK,kBACpE,EAAS,KAAK,QAAQ,IAAQ,EAAE,yBAAyB,EAEvD,MAAkB,aAAa,CAAC,EAAK,yBAAyB,CAAC,EAAK,kBACtE,EAAS,KAAK,QAAQ,IAAQ,EAAE,0BAA0B,EAIxD,EAAK,SAAS,EAAK,MAAM,SAAS,KACpC,EAAK,MAAM,SAAS,GAAO,MAAe;AAIxC,IAHK,EAAM,MAAM,UACf,EAAO,KAAK,QAAQ,IAAQ,EAAE,UAAU,IAAa,EAAE,aAAa,EAElE,EAAM,MAAM,SAAS,WAAW,CAAC,EAAM,MAAM,WAC/C,EAAS,KAAK,QAAQ,IAAQ,EAAE,UAAU,IAAa,EAAE,mBAAmB;KAE9E;IAEJ;EAEF,IAAM,IAAQ,EAAO,WAAW;AAEhC,SADA,EAAqB;GAAE;GAAO;GAAQ;GAAU,CAAC,EAC1C;GAAE;GAAO;GAAQ;GAAU;IACjC;EAAC;EAAM;EAAO;EAAc,CAAC,EAK1B,IAAsB,EAAY,YACjC,IACE,GAAG,OAAO,SAAS,OAAO,SAAS,EAAK,GAAG,YADhC,MAEjB,CAAC,EAAK,CAAC;AAEV,QAAO;EACL;EACA;EACA;EACA;EACA;EACA,YAAY,EAAM;EAClB,WAAW,IAAmB,EAAM,SAAS;EAC7C,eAAe,IAAmB;EAClC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
1
+ {"version":3,"file":"useTourPreview.js","names":[],"sources":["../../../src/tours/hooks/useTourPreview.ts"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { useToursStore } from '../store/toursStore';\nimport type { Tour, DeviceType } from '../types';\n\n/**\n * Hook for tour preview functionality\n * Manages preview mode, device simulation, and validation\n */\nexport function useTourPreview(tour?: Tour) {\n const [isPreviewActive, setIsPreviewActive] = useState(false);\n const [currentStepIndex, setCurrentStepIndex] = useState(0);\n const [validationResults, setValidationResults] = useState<{\n valid: boolean;\n errors: string[];\n warnings: string[];\n } | null>(null);\n\n const { devicePreview, setDevicePreview } = useToursStore();\n\n const steps = useMemo(() => tour?.steps ?? [], [tour?.steps]);\n const currentStep = steps[currentStepIndex];\n\n /**\n * Start preview mode\n */\n const startPreview = useCallback(() => {\n setIsPreviewActive(true);\n setCurrentStepIndex(0);\n }, []);\n\n /**\n * Stop preview mode\n */\n const stopPreview = useCallback(() => {\n setIsPreviewActive(false);\n setCurrentStepIndex(0);\n }, []);\n\n /**\n * Navigate to next step\n */\n const nextStep = useCallback(() => {\n if (currentStepIndex < steps.length - 1) {\n setCurrentStepIndex((prev) => prev + 1);\n }\n }, [currentStepIndex, steps.length]);\n\n /**\n * Navigate to previous step\n */\n const previousStep = useCallback(() => {\n if (currentStepIndex > 0) {\n setCurrentStepIndex((prev) => prev - 1);\n }\n }, [currentStepIndex]);\n\n /**\n * Jump to specific step\n */\n const goToStep = useCallback(\n (index: number) => {\n if (index >= 0 && index < steps.length) {\n setCurrentStepIndex(index);\n }\n },\n [steps.length]\n );\n\n /**\n * Change preview device\n */\n const changeDevice = useCallback(\n (device: DeviceType) => {\n setDevicePreview(device);\n },\n [setDevicePreview]\n );\n\n /**\n * Validate tour for preview\n * Checks if all selectors exist, media is accessible, etc.\n */\n const validateForPreview = useCallback(() => {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n if (!tour) {\n errors.push('No tour selected');\n setValidationResults({ valid: false, errors, warnings });\n return { valid: false, errors, warnings };\n }\n\n if (steps.length === 0) {\n errors.push('Tour has no steps');\n }\n\n steps.forEach((step, index) => {\n if (step.placement !== 'CENTER' && !step.targetSelector) {\n errors.push(`Step ${index + 1} has no selector`);\n }\n\n if (!step.content || step.content.trim() === '') {\n warnings.push(`Step ${index + 1} has empty content`);\n }\n\n // Check device-specific selectors\n if (devicePreview === 'mobile' && !step.targetSelectorMobile && !step.targetSelector) {\n warnings.push(`Step ${index + 1} has no mobile selector`);\n }\n if (devicePreview === 'tablet' && !step.targetSelectorTablet && !step.targetSelector) {\n warnings.push(`Step ${index + 1} has no tablet selector`);\n }\n if (devicePreview === 'desktop' && !step.targetSelectorDesktop && !step.targetSelector) {\n warnings.push(`Step ${index + 1} has no desktop selector`);\n }\n\n // Check media\n if (step.media && step.media.length > 0) {\n step.media.forEach((media, mediaIndex) => {\n if (!media.media.cdnUrl) {\n errors.push(`Step ${index + 1}, media ${mediaIndex + 1} has no URL`);\n }\n if (media.media.type === 'IMAGE' && !media.media.altText) {\n warnings.push(`Step ${index + 1}, media ${mediaIndex + 1} missing alt text`);\n }\n });\n }\n });\n\n const valid = errors.length === 0;\n setValidationResults({ valid, errors, warnings });\n return { valid, errors, warnings };\n }, [tour, steps, devicePreview]);\n\n /**\n * Generate shareable preview link\n */\n const generatePreviewLink = useCallback(async () => {\n if (!tour) return null;\n return `${window.location.origin}/tours/${tour.id}/preview`;\n }, [tour]);\n\n return {\n isPreviewActive,\n currentStepIndex,\n currentStep,\n devicePreview,\n validationResults,\n totalSteps: steps.length,\n canGoNext: currentStepIndex < steps.length - 1,\n canGoPrevious: currentStepIndex > 0,\n startPreview,\n stopPreview,\n nextStep,\n previousStep,\n goToStep,\n changeDevice,\n validateForPreview,\n generatePreviewLink,\n };\n}\n"],"mappings":";;;AAQA,SAAgB,EAAe,GAAa;CAC1C,IAAM,CAAC,GAAiB,KAAsB,EAAS,GAAM,EACvD,CAAC,GAAkB,KAAuB,EAAS,EAAE,EACrD,CAAC,GAAmB,KAAwB,EAIxC,KAAK,EAET,EAAE,kBAAe,wBAAqB,GAAe,EAErD,IAAQ,QAAc,GAAM,SAAS,EAAE,EAAE,CAAC,GAAM,MAAM,CAAC,EACvD,IAAc,EAAM,IAKpB,IAAe,QAAkB;AAErC,EADA,EAAmB,GAAK,EACxB,EAAoB,EAAE;IACrB,EAAE,CAAC,EAKA,IAAc,QAAkB;AAEpC,EADA,EAAmB,GAAM,EACzB,EAAoB,EAAE;IACrB,EAAE,CAAC,EAKA,IAAW,QAAkB;AACjC,EAAI,IAAmB,EAAM,SAAS,KACpC,GAAqB,MAAS,IAAO,EAAE;IAExC,CAAC,GAAkB,EAAM,OAAO,CAAC,EAK9B,IAAe,QAAkB;AACrC,EAAI,IAAmB,KACrB,GAAqB,MAAS,IAAO,EAAE;IAExC,CAAC,EAAiB,CAAC,EAKhB,IAAW,GACd,MAAkB;AACjB,EAAI,KAAS,KAAK,IAAQ,EAAM,UAC9B,EAAoB,EAAM;IAG9B,CAAC,EAAM,OAAO,CACf,EAKK,IAAe,GAClB,MAAuB;AACtB,IAAiB,EAAO;IAE1B,CAAC,EAAiB,CACnB,EAMK,IAAqB,QAAkB;EAC3C,IAAM,IAAmB,EAAE,EACrB,IAAqB,EAAE;AAE7B,MAAI,CAAC,EAGH,QAFA,EAAO,KAAK,mBAAmB,EAC/B,EAAqB;GAAE,OAAO;GAAO;GAAQ;GAAU,CAAC,EACjD;GAAE,OAAO;GAAO;GAAQ;GAAU;AAO3C,EAJI,EAAM,WAAW,KACnB,EAAO,KAAK,oBAAoB,EAGlC,EAAM,SAAS,GAAM,MAAU;AAqB7B,GApBI,EAAK,cAAc,YAAY,CAAC,EAAK,kBACvC,EAAO,KAAK,QAAQ,IAAQ,EAAE,kBAAkB,GAG9C,CAAC,EAAK,WAAW,EAAK,QAAQ,MAAM,KAAK,OAC3C,EAAS,KAAK,QAAQ,IAAQ,EAAE,oBAAoB,EAIlD,MAAkB,YAAY,CAAC,EAAK,wBAAwB,CAAC,EAAK,kBACpE,EAAS,KAAK,QAAQ,IAAQ,EAAE,yBAAyB,EAEvD,MAAkB,YAAY,CAAC,EAAK,wBAAwB,CAAC,EAAK,kBACpE,EAAS,KAAK,QAAQ,IAAQ,EAAE,yBAAyB,EAEvD,MAAkB,aAAa,CAAC,EAAK,yBAAyB,CAAC,EAAK,kBACtE,EAAS,KAAK,QAAQ,IAAQ,EAAE,0BAA0B,EAIxD,EAAK,SAAS,EAAK,MAAM,SAAS,KACpC,EAAK,MAAM,SAAS,GAAO,MAAe;AAIxC,IAHK,EAAM,MAAM,UACf,EAAO,KAAK,QAAQ,IAAQ,EAAE,UAAU,IAAa,EAAE,aAAa,EAElE,EAAM,MAAM,SAAS,WAAW,CAAC,EAAM,MAAM,WAC/C,EAAS,KAAK,QAAQ,IAAQ,EAAE,UAAU,IAAa,EAAE,mBAAmB;KAE9E;IAEJ;EAEF,IAAM,IAAQ,EAAO,WAAW;AAEhC,SADA,EAAqB;GAAE;GAAO;GAAQ;GAAU,CAAC,EAC1C;GAAE;GAAO;GAAQ;GAAU;IACjC;EAAC;EAAM;EAAO;EAAc,CAAC,EAK1B,IAAsB,EAAY,YACjC,IACE,GAAG,OAAO,SAAS,OAAO,SAAS,EAAK,GAAG,YADhC,MAEjB,CAAC,EAAK,CAAC;AAEV,QAAO;EACL;EACA;EACA;EACA;EACA;EACA,YAAY,EAAM;EAClB,WAAW,IAAmB,EAAM,SAAS;EAC7C,eAAe,IAAmB;EAClC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"useToursList.js","names":[],"sources":["../../../src/tours/hooks/useToursList.ts"],"sourcesContent":["import { useCallback, useState } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport type {\n TourFilters as LocalTourFilters,\n TourPagination as LocalTourPagination,\n} from '../types';\nimport type { TourStatus } from '../../generated/global-types';\nimport { GetToursDocument } from '../../generated/global-operations';\nimport type { GetToursQuery, GetToursQueryVariables } from '../../generated/global-operations';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Tour = any;\n\n/**\n * Hook for fetching and managing tours list\n * Uses the gateway client from fe-libs\n * All tours are now global (no workspace scoping)\n */\nexport function useToursList(\n _initialFilters?: LocalTourFilters,\n _initialPagination?: LocalTourPagination\n) {\n const globalClient = useGatewayClient('global');\n\n const [tours, setTours] = useState<Tour[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [total, setTotal] = useState(0);\n\n const fetchTours = useCallback(\n async (filters?: LocalTourFilters, pagination?: LocalTourPagination) => {\n setLoading(true);\n setError(null);\n\n try {\n const { data, error: queryError } = await globalClient.query<\n GetToursQuery,\n GetToursQueryVariables\n >({\n query: GetToursDocument,\n variables: {\n filters: {\n productId: filters?.productId,\n moduleId: filters?.moduleId,\n pageId: filters?.pageId,\n status:\n filters?.status === 'all' ? undefined : (filters?.status as TourStatus | undefined),\n },\n pagination: {\n limit: pagination?.limit ?? 50,\n offset: pagination?.page ? (pagination.page - 1) * (pagination?.limit ?? 50) : 0,\n },\n },\n fetchPolicy: 'network-only',\n });\n\n const fetchedTours = (data?.tours?.tours ?? []).filter((tour) => {\n if (!filters?.search) return true;\n\n const normalizedSearch = filters.search.toLowerCase();\n return (\n tour.name.toLowerCase().includes(normalizedSearch) ||\n (tour.description ?? '').toLowerCase().includes(normalizedSearch)\n );\n });\n\n setTours(fetchedTours);\n setTotal(data?.tours?.total ?? fetchedTours.length);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch tours'));\n setTours([]);\n setTotal(0);\n } finally {\n setLoading(false);\n }\n },\n [globalClient]\n );\n\n return {\n tours,\n loading,\n error,\n total,\n hasMore: false,\n fetchTours,\n refetch: () => fetchTours(),\n };\n}\n\n/**\n * Lazy version of useToursList for manual fetching\n * @deprecated Use useToursList instead\n */\nexport function useToursListLazy() {\n return useToursList();\n}\n"],"mappings":";;;;AAkBA,SAAgB,EACd,GACA,GACA;CACA,IAAM,IAAe,EAAiB,SAAS,EAEzC,CAAC,GAAO,KAAY,EAAiB,EAAE,CAAC,EACxC,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAuB,KAAK,EAChD,CAAC,GAAO,KAAY,EAAS,EAAE,EAE/B,IAAa,EACjB,OAAO,GAA4B,MAAqC;AAEtE,EADA,EAAW,GAAK,EAChB,EAAS,KAAK;AAEd,MAAI;GACF,IAAM,EAAE,SAAM,OAAO,MAAe,MAAM,EAAa,MAGrD;IACA,OAAO;IACP,WAAW;KACT,SAAS;MACP,WAAW,GAAS;MACpB,UAAU,GAAS;MACnB,QAAQ,GAAS;MACjB,QACE,GAAS,WAAW,QAAQ,KAAA,IAAa,GAAS;MACrD;KACD,YAAY;MACV,OAAO,GAAY,SAAS;MAC5B,QAAQ,GAAY,QAAQ,EAAW,OAAO,MAAM,GAAY,SAAS,MAAM;MAChF;KACF;IACD,aAAa;IACd,CAAC,EAEI,KAAgB,GAAM,OAAO,SAAS,EAAE,EAAE,QAAQ,MAAS;AAC/D,QAAI,CAAC,GAAS,OAAQ,QAAO;IAE7B,IAAM,IAAmB,EAAQ,OAAO,aAAa;AACrD,WACE,EAAK,KAAK,aAAa,CAAC,SAAS,EAAiB,KACjD,EAAK,eAAe,IAAI,aAAa,CAAC,SAAS,EAAiB;KAEnE;AAGF,GADA,EAAS,EAAa,EACtB,EAAS,GAAM,OAAO,SAAS,EAAa,OAAO;WAC5C,GAAK;AAGZ,GAFA,EAAS,aAAe,QAAQ,IAAM,gBAAI,MAAM,wBAAwB,CAAC,EACzE,EAAS,EAAE,CAAC,EACZ,EAAS,EAAE;YACH;AACR,KAAW,GAAM;;IAGrB,CAAC,EAAa,CACf;AAED,QAAO;EACL;EACA;EACA;EACA;EACA,SAAS;EACT;EACA,eAAe,GAAY;EAC5B"}
1
+ {"version":3,"file":"useToursList.js","names":[],"sources":["../../../src/tours/hooks/useToursList.ts"],"sourcesContent":["import { useCallback, useState } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport type {\n TourFilters as LocalTourFilters,\n TourPagination as LocalTourPagination,\n} from '../types';\nimport type { TourStatus } from '../../generated/global-types';\nimport { GetToursDocument } from '../../generated/global-operations';\nimport type { GetToursQuery, GetToursQueryVariables } from '../../generated/global-operations';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Tour = any;\n\n/**\n * Hook for fetching and managing tours list\n * Uses the gateway client from fe-libs\n * All tours are now global (no workspace scoping)\n */\nexport function useToursList(\n _initialFilters?: LocalTourFilters,\n _initialPagination?: LocalTourPagination\n) {\n const globalClient = useGatewayClient('global');\n\n const [tours, setTours] = useState<Tour[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [total, setTotal] = useState(0);\n\n const fetchTours = useCallback(\n async (filters?: LocalTourFilters, pagination?: LocalTourPagination) => {\n setLoading(true);\n setError(null);\n\n try {\n const { data, error: queryError } = await globalClient.query<\n GetToursQuery,\n GetToursQueryVariables\n >({\n query: GetToursDocument,\n variables: {\n filters: {\n productId: filters?.productId,\n moduleId: filters?.moduleId,\n pageId: filters?.pageId,\n status:\n filters?.status === 'all' ? undefined : (filters?.status as TourStatus | undefined),\n },\n pagination: {\n limit: pagination?.limit ?? 50,\n offset: pagination?.page ? (pagination.page - 1) * (pagination?.limit ?? 50) : 0,\n },\n },\n fetchPolicy: 'network-only',\n });\n\n const fetchedTours = (data?.tours?.tours ?? []).filter((tour) => {\n if (!filters?.search) return true;\n\n const normalizedSearch = filters.search.toLowerCase();\n return (\n tour.name.toLowerCase().includes(normalizedSearch) ||\n (tour.description ?? '').toLowerCase().includes(normalizedSearch)\n );\n });\n\n setTours(fetchedTours);\n setTotal(data?.tours?.total ?? fetchedTours.length);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch tours'));\n setTours([]);\n setTotal(0);\n } finally {\n setLoading(false);\n }\n },\n [globalClient]\n );\n\n return {\n tours,\n loading,\n error,\n total,\n hasMore: false,\n fetchTours,\n refetch: () => fetchTours(),\n };\n}\n"],"mappings":";;;;AAkBA,SAAgB,EACd,GACA,GACA;CACA,IAAM,IAAe,EAAiB,SAAS,EAEzC,CAAC,GAAO,KAAY,EAAiB,EAAE,CAAC,EACxC,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAuB,KAAK,EAChD,CAAC,GAAO,KAAY,EAAS,EAAE,EAE/B,IAAa,EACjB,OAAO,GAA4B,MAAqC;AAEtE,EADA,EAAW,GAAK,EAChB,EAAS,KAAK;AAEd,MAAI;GACF,IAAM,EAAE,SAAM,OAAO,MAAe,MAAM,EAAa,MAGrD;IACA,OAAO;IACP,WAAW;KACT,SAAS;MACP,WAAW,GAAS;MACpB,UAAU,GAAS;MACnB,QAAQ,GAAS;MACjB,QACE,GAAS,WAAW,QAAQ,KAAA,IAAa,GAAS;MACrD;KACD,YAAY;MACV,OAAO,GAAY,SAAS;MAC5B,QAAQ,GAAY,QAAQ,EAAW,OAAO,MAAM,GAAY,SAAS,MAAM;MAChF;KACF;IACD,aAAa;IACd,CAAC,EAEI,KAAgB,GAAM,OAAO,SAAS,EAAE,EAAE,QAAQ,MAAS;AAC/D,QAAI,CAAC,GAAS,OAAQ,QAAO;IAE7B,IAAM,IAAmB,EAAQ,OAAO,aAAa;AACrD,WACE,EAAK,KAAK,aAAa,CAAC,SAAS,EAAiB,KACjD,EAAK,eAAe,IAAI,aAAa,CAAC,SAAS,EAAiB;KAEnE;AAGF,GADA,EAAS,EAAa,EACtB,EAAS,GAAM,OAAO,SAAS,EAAa,OAAO;WAC5C,GAAK;AAGZ,GAFA,EAAS,aAAe,QAAQ,IAAM,gBAAI,MAAM,wBAAwB,CAAC,EACzE,EAAS,EAAE,CAAC,EACZ,EAAS,EAAE;YACH;AACR,KAAW,GAAM;;IAGrB,CAAC,EAAa,CACf;AAED,QAAO;EACL;EACA;EACA;EACA;EACA,SAAS;EACT;EACA,eAAe,GAAY;EAC5B"}
@@ -63,8 +63,10 @@ var n = {
63
63
  "templateLibrary.title": "Tour Templates",
64
64
  "templateLibrary.useTemplate": "Use Template",
65
65
  "templateLibrary.usedTimes": "Used",
66
+ "tourBuilder.accessDeniedTitle": "Access Denied",
66
67
  "tourBuilder.analytics": "Analytics",
67
68
  "tourBuilder.createAndAddSteps": "Create & Add Steps",
69
+ "tourBuilder.createAccessDeniedMessage": "You do not have permission to create tours.",
68
70
  "tourBuilder.editSteps": "Edit Steps",
69
71
  "tourBuilder.errorTitle": "Error",
70
72
  "tourBuilder.goBack": "Go back",
@@ -73,6 +75,7 @@ var n = {
73
75
  "tourBuilder.labelPriority": "Priority (1-100)",
74
76
  "tourBuilder.labelProductId": "Product ID *",
75
77
  "tourBuilder.labelTriggerType": "Trigger Type",
78
+ "tourBuilder.loading": "Loading...",
76
79
  "tourBuilder.preview": "Preview",
77
80
  "tourBuilder.saving": "Saving...",
78
81
  "tourBuilder.sectionBasicInfo": "Basic Information",
@@ -94,6 +97,7 @@ var n = {
94
97
  "tourBuilder.triggerOnEvent": "On Event",
95
98
  "tourBuilder.triggerScheduled": "Scheduled",
96
99
  "tourBuilder.updateTour": "Update Tour",
100
+ "tourBuilder.updateAccessDeniedMessage": "You do not have permission to update this tour.",
97
101
  "tourBuilder.validationErrorsTitle": "Validation Errors",
98
102
  "tourPreview.accessDeniedMessage": "You do not have permission to preview this tour.",
99
103
  "tourPreview.accessDeniedTitle": "Access Denied",
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.js","names":[],"sources":["../../src/tours/i18n.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nconst toursTranslationFallbacks = {\n 'stepEditor.addFirstStep': 'Add First Step',\n 'stepEditor.addStep': 'Add Step',\n 'stepEditor.clickAddStep': 'Click \"Add Step\" to create one',\n 'stepEditor.closeSteps': 'Close steps',\n 'stepEditor.content': 'Content',\n 'stepEditor.contentPlaceholder': 'Enter step content or instructions',\n 'stepEditor.createFirstStep': 'Create your first step to get started',\n 'stepEditor.cssSelector': 'CSS Selector',\n 'stepEditor.cssSelectorPlaceholder': \"[data-tour='element-name'] or #id or .class\",\n 'stepEditor.dataTourHint': \"Use data-tour attributes like [data-tour='sidenav'] for best results\",\n 'stepEditor.deleteStep': 'Delete step',\n 'stepEditor.desktopSelector': 'Desktop Selector',\n 'stepEditor.deviceSelectorHint': 'Override the default selector for specific device types',\n 'stepEditor.deviceSpecificSelectors': 'Device-Specific Selectors (Advanced)',\n 'stepEditor.editStep': 'Edit Step',\n 'stepEditor.goBack': 'Go back',\n 'stepEditor.leaveEmptyDefault': 'Leave empty to use default',\n 'stepEditor.mobileSelector': 'Mobile Selector',\n 'stepEditor.noStepSelected': 'No step selected',\n 'stepEditor.noStepsYet': 'No steps yet',\n 'stepEditor.of': 'of',\n 'stepEditor.nextStep': 'Next step',\n 'stepEditor.placementAuto': 'Auto (recommended)',\n 'stepEditor.placementBottom': 'Bottom',\n 'stepEditor.placementCenter': 'Center (modal style)',\n 'stepEditor.placementLeft': 'Left',\n 'stepEditor.placementRight': 'Right',\n 'stepEditor.placementTop': 'Top',\n 'stepEditor.previousStep': 'Previous step',\n 'stepEditor.save': 'Save',\n 'stepEditor.saving': 'Saving...',\n 'stepEditor.selectStepToEdit': 'Select a step from the list to edit it',\n 'stepEditor.stepNumber': 'Step',\n 'stepEditor.steps': 'Steps',\n 'stepEditor.stepsCount': 'steps',\n 'stepEditor.stepsLabel': 'Steps',\n 'stepEditor.stepTitle': 'Step Title',\n 'stepEditor.stepTitlePlaceholder': 'Enter step title',\n 'stepEditor.tabletSelector': 'Tablet Selector',\n 'stepEditor.targetElement': 'target element',\n 'stepEditor.tooltipPlacement': 'Tooltip Placement',\n 'stepEditor.tourFallback': 'Tour',\n 'stepEditor.tourSteps': 'Tour Steps',\n 'stepEditor.untitled': 'Untitled',\n 'templateLibrary.adjustFilters': 'Try adjusting your filters or search query',\n 'templateLibrary.allCategories': 'All Categories',\n 'templateLibrary.categoryCustom': 'Custom',\n 'templateLibrary.categoryFeatureAnnouncement': 'Feature Announcement',\n 'templateLibrary.categoryHelp': 'Help',\n 'templateLibrary.categoryOnboarding': 'Onboarding',\n 'templateLibrary.deleteConfirm': 'Delete this template?',\n 'templateLibrary.deleteTemplate': 'Delete template',\n 'templateLibrary.loading': 'Loading templates...',\n 'templateLibrary.noTemplatesFound': 'No templates found',\n 'templateLibrary.publicOnly': 'Public only',\n 'templateLibrary.searchPlaceholder': 'Search templates...',\n 'templateLibrary.subtitle': 'Browse and use pre-built tour templates',\n 'templateLibrary.times': 'times',\n 'templateLibrary.title': 'Tour Templates',\n 'templateLibrary.useTemplate': 'Use Template',\n 'templateLibrary.usedTimes': 'Used',\n 'tourBuilder.analytics': 'Analytics',\n 'tourBuilder.createAndAddSteps': 'Create & Add Steps',\n 'tourBuilder.editSteps': 'Edit Steps',\n 'tourBuilder.errorTitle': 'Error',\n 'tourBuilder.goBack': 'Go back',\n 'tourBuilder.labelDescription': 'Description',\n 'tourBuilder.labelName': 'Tour Name *',\n 'tourBuilder.labelPriority': 'Priority (1-100)',\n 'tourBuilder.labelProductId': 'Product ID *',\n 'tourBuilder.labelTriggerType': 'Trigger Type',\n 'tourBuilder.preview': 'Preview',\n 'tourBuilder.saving': 'Saving...',\n 'tourBuilder.sectionBasicInfo': 'Basic Information',\n 'tourBuilder.sectionTriggerSettings': 'Trigger Settings',\n 'tourBuilder.seedVibeControls.button': 'Seed VibeControls user tours',\n 'tourBuilder.seedVibeControls.confirm':\n 'This will overwrite the built-in Support, Notifications, and Conversations user tours from the service seed file. Continue?',\n 'tourBuilder.seedVibeControls.description':\n 'Overwrites the built-in Support, Notifications, and Conversations user tours from the service seed file.',\n 'tourBuilder.seedVibeControls.error': 'Failed to seed VibeControls user tours.',\n 'tourBuilder.seedVibeControls.loading': 'Seeding...',\n 'tourBuilder.seedVibeControls.permissionHint':\n 'Requires tour create and update permissions. The service also enforces RBAC when the seed mutation runs.',\n 'tourBuilder.seedVibeControls.result':\n 'Seeded {{totalCount}} tours for {{productId}} in workspace {{workspaceId}}: {{createdCount}} created, {{updatedCount}} updated.',\n 'tourBuilder.seedVibeControls.title': 'Seed VibeControls user tours',\n 'tourBuilder.subtitle': 'Configure tour details and settings',\n 'tourBuilder.titleCreate': 'Create Tour',\n 'tourBuilder.titleEdit': 'Edit Tour',\n 'tourBuilder.triggerAutoAlways': 'Auto (Always)',\n 'tourBuilder.triggerAutoFirstVisit': 'Auto (First Visit)',\n 'tourBuilder.triggerManual': 'Manual',\n 'tourBuilder.triggerOnEvent': 'On Event',\n 'tourBuilder.triggerScheduled': 'Scheduled',\n 'tourBuilder.updateTour': 'Update Tour',\n 'tourBuilder.validationErrorsTitle': 'Validation Errors',\n 'tourPreview.accessDeniedMessage': 'You do not have permission to preview this tour.',\n 'tourPreview.accessDeniedTitle': 'Access Denied',\n 'tourPreview.deviceDesktop': 'Desktop preview',\n 'tourPreview.deviceMobile': 'Mobile preview',\n 'tourPreview.deviceTablet': 'Tablet preview',\n 'tourPreview.finish': 'Finish',\n 'tourPreview.loading': 'Loading tour...',\n 'tourPreview.next': 'Next',\n 'tourPreview.nextFooter': 'Next',\n 'tourPreview.previewSuffix': 'Preview',\n 'tourPreview.previous': 'Previous',\n 'tourPreview.readyHint': 'Click \"Start Preview\" to begin',\n 'tourPreview.readyTitle': 'Preview ready',\n 'tourPreview.startPreview': 'Start Preview',\n 'tourPreview.stepOf': 'Step',\n 'tourPreview.stepOfConnector': 'of',\n 'tourPreview.stopPreview': 'Stop Preview',\n 'tourPreview.validationErrorsTitle': 'Validation Errors',\n 'tourPreview.warningsTitle': 'Warnings',\n 'toursList.accessDeniedMessage': 'You do not have permission to view product tours.',\n 'toursList.accessDeniedTitle': 'Access Denied',\n 'toursList.actionArchive': 'Archive',\n 'toursList.actionDelete': 'Delete',\n 'toursList.actionDuplicate': 'Duplicate',\n 'toursList.actionPublish': 'Publish',\n 'toursList.actionUnpublish': 'Unpublish',\n 'toursList.confirmDelete': 'Are you sure you want to delete this tour?',\n 'toursList.createTour': 'Create Tour',\n 'toursList.editStepsTitle': 'Edit Steps',\n 'toursList.emptySubtitle': 'Create your first tour to get started',\n 'toursList.emptyTitle': 'No tours found',\n 'toursList.errorArchive': 'Failed to archive tour',\n 'toursList.errorDelete': 'Failed to delete tour',\n 'toursList.errorDuplicate': 'Failed to duplicate tour',\n 'toursList.errorPublish': 'Failed to publish tour',\n 'toursList.errorUnpublish': 'Failed to unpublish tour',\n 'toursList.gridView': 'Grid view',\n 'toursList.listView': 'List view',\n 'toursList.loading': 'Loading tours...',\n 'toursList.moreActionsTitle': 'More actions',\n 'toursList.previewButton': 'Preview',\n 'toursList.previewTitle': 'Preview Tour',\n 'toursList.searchPlaceholder': 'Search tours...',\n 'toursList.settingsTitle': 'Settings',\n 'toursList.statusAll': 'All Statuses',\n 'toursList.statusArchived': 'Archived',\n 'toursList.statusDraft': 'Draft',\n 'toursList.statusPublished': 'Published',\n 'toursList.statusScheduled': 'Scheduled',\n 'toursList.steps': 'steps',\n 'toursList.stepsButton': 'Steps',\n 'toursList.subtitle': 'Create and manage interactive product tours',\n 'toursList.title': 'Product Tours',\n 'toursList.views': 'views',\n\n // User-facing surface (BOFF-2391: My Tours + My Progress).\n 'myTours.title': 'My Tours',\n 'myTours.subtitle': 'Walkthroughs available in this product. Take a tour to learn a feature.',\n 'myTours.loading': 'Loading...',\n 'myTours.empty': 'No tours are available in this context yet.',\n 'myTours.status.completed': 'Completed',\n 'myTours.status.inProgress': 'In progress',\n 'myTours.status.skipped': 'Skipped',\n 'myTours.status.notStarted': 'Not started',\n 'myTours.notFoundTitle': 'Page not found',\n 'myTours.notFoundBody': \"That tour page isn't available — returning to My Tours.\",\n\n 'myToursProgress.title': 'My Progress',\n 'myToursProgress.subtitle': 'Tours you have started, completed, or skipped.',\n 'myToursProgress.loading': 'Loading...',\n 'myToursProgress.inProgress': 'In progress',\n 'myToursProgress.completed': 'Completed',\n 'myToursProgress.skipped': 'Skipped or dismissed',\n 'myToursProgress.emptyInProgress': 'No tours in progress.',\n 'myToursProgress.emptyCompleted': 'No completed tours yet.',\n 'myToursProgress.emptySkipped': 'No skipped tours.',\n 'myToursProgress.other': 'Other',\n 'myToursProgress.emptyOther': 'No tours in other states.',\n} as const;\n\nexport type ToursTranslationKey = keyof typeof toursTranslationFallbacks;\n\ntype TranslationParams = Record<string, string | number>;\n\nexport function useToursI18n() {\n const i18n = useI18n();\n // The installed fe-libs typings still expose the older 2-argument shape, but the\n // runtime provider supports fallback strings. Narrow locally so this MFE can rely\n // on fallback-backed translations without changing the shared package here.\n const translateWithFallback = i18n.t as (key: string, fallback?: string) => string;\n\n const tr = useCallback(\n (key: ToursTranslationKey, params?: TranslationParams): string => {\n const resolved = translateWithFallback(key, toursTranslationFallbacks[key]);\n\n if (!params) {\n return resolved;\n }\n\n return Object.entries(params).reduce(\n (value, [paramKey, paramValue]) => value.replace(`{{${paramKey}}}`, String(paramValue)),\n resolved\n );\n },\n [translateWithFallback]\n );\n\n return {\n ...i18n,\n tr,\n };\n}\n\nexport function getToursTranslationFallback(key: ToursTranslationKey) {\n return toursTranslationFallbacks[key];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"i18n.js","names":[],"sources":["../../src/tours/i18n.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nconst toursTranslationFallbacks = {\n 'stepEditor.addFirstStep': 'Add First Step',\n 'stepEditor.addStep': 'Add Step',\n 'stepEditor.clickAddStep': 'Click \"Add Step\" to create one',\n 'stepEditor.closeSteps': 'Close steps',\n 'stepEditor.content': 'Content',\n 'stepEditor.contentPlaceholder': 'Enter step content or instructions',\n 'stepEditor.createFirstStep': 'Create your first step to get started',\n 'stepEditor.cssSelector': 'CSS Selector',\n 'stepEditor.cssSelectorPlaceholder': \"[data-tour='element-name'] or #id or .class\",\n 'stepEditor.dataTourHint': \"Use data-tour attributes like [data-tour='sidenav'] for best results\",\n 'stepEditor.deleteStep': 'Delete step',\n 'stepEditor.desktopSelector': 'Desktop Selector',\n 'stepEditor.deviceSelectorHint': 'Override the default selector for specific device types',\n 'stepEditor.deviceSpecificSelectors': 'Device-Specific Selectors (Advanced)',\n 'stepEditor.editStep': 'Edit Step',\n 'stepEditor.goBack': 'Go back',\n 'stepEditor.leaveEmptyDefault': 'Leave empty to use default',\n 'stepEditor.mobileSelector': 'Mobile Selector',\n 'stepEditor.noStepSelected': 'No step selected',\n 'stepEditor.noStepsYet': 'No steps yet',\n 'stepEditor.of': 'of',\n 'stepEditor.nextStep': 'Next step',\n 'stepEditor.placementAuto': 'Auto (recommended)',\n 'stepEditor.placementBottom': 'Bottom',\n 'stepEditor.placementCenter': 'Center (modal style)',\n 'stepEditor.placementLeft': 'Left',\n 'stepEditor.placementRight': 'Right',\n 'stepEditor.placementTop': 'Top',\n 'stepEditor.previousStep': 'Previous step',\n 'stepEditor.save': 'Save',\n 'stepEditor.saving': 'Saving...',\n 'stepEditor.selectStepToEdit': 'Select a step from the list to edit it',\n 'stepEditor.stepNumber': 'Step',\n 'stepEditor.steps': 'Steps',\n 'stepEditor.stepsCount': 'steps',\n 'stepEditor.stepsLabel': 'Steps',\n 'stepEditor.stepTitle': 'Step Title',\n 'stepEditor.stepTitlePlaceholder': 'Enter step title',\n 'stepEditor.tabletSelector': 'Tablet Selector',\n 'stepEditor.targetElement': 'target element',\n 'stepEditor.tooltipPlacement': 'Tooltip Placement',\n 'stepEditor.tourFallback': 'Tour',\n 'stepEditor.tourSteps': 'Tour Steps',\n 'stepEditor.untitled': 'Untitled',\n 'templateLibrary.adjustFilters': 'Try adjusting your filters or search query',\n 'templateLibrary.allCategories': 'All Categories',\n 'templateLibrary.categoryCustom': 'Custom',\n 'templateLibrary.categoryFeatureAnnouncement': 'Feature Announcement',\n 'templateLibrary.categoryHelp': 'Help',\n 'templateLibrary.categoryOnboarding': 'Onboarding',\n 'templateLibrary.deleteConfirm': 'Delete this template?',\n 'templateLibrary.deleteTemplate': 'Delete template',\n 'templateLibrary.loading': 'Loading templates...',\n 'templateLibrary.noTemplatesFound': 'No templates found',\n 'templateLibrary.publicOnly': 'Public only',\n 'templateLibrary.searchPlaceholder': 'Search templates...',\n 'templateLibrary.subtitle': 'Browse and use pre-built tour templates',\n 'templateLibrary.times': 'times',\n 'templateLibrary.title': 'Tour Templates',\n 'templateLibrary.useTemplate': 'Use Template',\n 'templateLibrary.usedTimes': 'Used',\n 'tourBuilder.accessDeniedTitle': 'Access Denied',\n 'tourBuilder.analytics': 'Analytics',\n 'tourBuilder.createAndAddSteps': 'Create & Add Steps',\n 'tourBuilder.createAccessDeniedMessage': 'You do not have permission to create tours.',\n 'tourBuilder.editSteps': 'Edit Steps',\n 'tourBuilder.errorTitle': 'Error',\n 'tourBuilder.goBack': 'Go back',\n 'tourBuilder.labelDescription': 'Description',\n 'tourBuilder.labelName': 'Tour Name *',\n 'tourBuilder.labelPriority': 'Priority (1-100)',\n 'tourBuilder.labelProductId': 'Product ID *',\n 'tourBuilder.labelTriggerType': 'Trigger Type',\n 'tourBuilder.loading': 'Loading...',\n 'tourBuilder.preview': 'Preview',\n 'tourBuilder.saving': 'Saving...',\n 'tourBuilder.sectionBasicInfo': 'Basic Information',\n 'tourBuilder.sectionTriggerSettings': 'Trigger Settings',\n 'tourBuilder.seedVibeControls.button': 'Seed VibeControls user tours',\n 'tourBuilder.seedVibeControls.confirm':\n 'This will overwrite the built-in Support, Notifications, and Conversations user tours from the service seed file. Continue?',\n 'tourBuilder.seedVibeControls.description':\n 'Overwrites the built-in Support, Notifications, and Conversations user tours from the service seed file.',\n 'tourBuilder.seedVibeControls.error': 'Failed to seed VibeControls user tours.',\n 'tourBuilder.seedVibeControls.loading': 'Seeding...',\n 'tourBuilder.seedVibeControls.permissionHint':\n 'Requires tour create and update permissions. The service also enforces RBAC when the seed mutation runs.',\n 'tourBuilder.seedVibeControls.result':\n 'Seeded {{totalCount}} tours for {{productId}} in workspace {{workspaceId}}: {{createdCount}} created, {{updatedCount}} updated.',\n 'tourBuilder.seedVibeControls.title': 'Seed VibeControls user tours',\n 'tourBuilder.subtitle': 'Configure tour details and settings',\n 'tourBuilder.titleCreate': 'Create Tour',\n 'tourBuilder.titleEdit': 'Edit Tour',\n 'tourBuilder.triggerAutoAlways': 'Auto (Always)',\n 'tourBuilder.triggerAutoFirstVisit': 'Auto (First Visit)',\n 'tourBuilder.triggerManual': 'Manual',\n 'tourBuilder.triggerOnEvent': 'On Event',\n 'tourBuilder.triggerScheduled': 'Scheduled',\n 'tourBuilder.updateTour': 'Update Tour',\n 'tourBuilder.updateAccessDeniedMessage': 'You do not have permission to update this tour.',\n 'tourBuilder.validationErrorsTitle': 'Validation Errors',\n 'tourPreview.accessDeniedMessage': 'You do not have permission to preview this tour.',\n 'tourPreview.accessDeniedTitle': 'Access Denied',\n 'tourPreview.deviceDesktop': 'Desktop preview',\n 'tourPreview.deviceMobile': 'Mobile preview',\n 'tourPreview.deviceTablet': 'Tablet preview',\n 'tourPreview.finish': 'Finish',\n 'tourPreview.loading': 'Loading tour...',\n 'tourPreview.next': 'Next',\n 'tourPreview.nextFooter': 'Next',\n 'tourPreview.previewSuffix': 'Preview',\n 'tourPreview.previous': 'Previous',\n 'tourPreview.readyHint': 'Click \"Start Preview\" to begin',\n 'tourPreview.readyTitle': 'Preview ready',\n 'tourPreview.startPreview': 'Start Preview',\n 'tourPreview.stepOf': 'Step',\n 'tourPreview.stepOfConnector': 'of',\n 'tourPreview.stopPreview': 'Stop Preview',\n 'tourPreview.validationErrorsTitle': 'Validation Errors',\n 'tourPreview.warningsTitle': 'Warnings',\n 'toursList.accessDeniedMessage': 'You do not have permission to view product tours.',\n 'toursList.accessDeniedTitle': 'Access Denied',\n 'toursList.actionArchive': 'Archive',\n 'toursList.actionDelete': 'Delete',\n 'toursList.actionDuplicate': 'Duplicate',\n 'toursList.actionPublish': 'Publish',\n 'toursList.actionUnpublish': 'Unpublish',\n 'toursList.confirmDelete': 'Are you sure you want to delete this tour?',\n 'toursList.createTour': 'Create Tour',\n 'toursList.editStepsTitle': 'Edit Steps',\n 'toursList.emptySubtitle': 'Create your first tour to get started',\n 'toursList.emptyTitle': 'No tours found',\n 'toursList.errorArchive': 'Failed to archive tour',\n 'toursList.errorDelete': 'Failed to delete tour',\n 'toursList.errorDuplicate': 'Failed to duplicate tour',\n 'toursList.errorPublish': 'Failed to publish tour',\n 'toursList.errorUnpublish': 'Failed to unpublish tour',\n 'toursList.gridView': 'Grid view',\n 'toursList.listView': 'List view',\n 'toursList.loading': 'Loading tours...',\n 'toursList.moreActionsTitle': 'More actions',\n 'toursList.previewButton': 'Preview',\n 'toursList.previewTitle': 'Preview Tour',\n 'toursList.searchPlaceholder': 'Search tours...',\n 'toursList.settingsTitle': 'Settings',\n 'toursList.statusAll': 'All Statuses',\n 'toursList.statusArchived': 'Archived',\n 'toursList.statusDraft': 'Draft',\n 'toursList.statusPublished': 'Published',\n 'toursList.statusScheduled': 'Scheduled',\n 'toursList.steps': 'steps',\n 'toursList.stepsButton': 'Steps',\n 'toursList.subtitle': 'Create and manage interactive product tours',\n 'toursList.title': 'Product Tours',\n 'toursList.views': 'views',\n\n // User-facing surface (BOFF-2391: My Tours + My Progress).\n 'myTours.title': 'My Tours',\n 'myTours.subtitle': 'Walkthroughs available in this product. Take a tour to learn a feature.',\n 'myTours.loading': 'Loading...',\n 'myTours.empty': 'No tours are available in this context yet.',\n 'myTours.status.completed': 'Completed',\n 'myTours.status.inProgress': 'In progress',\n 'myTours.status.skipped': 'Skipped',\n 'myTours.status.notStarted': 'Not started',\n 'myTours.notFoundTitle': 'Page not found',\n 'myTours.notFoundBody': \"That tour page isn't available — returning to My Tours.\",\n\n 'myToursProgress.title': 'My Progress',\n 'myToursProgress.subtitle': 'Tours you have started, completed, or skipped.',\n 'myToursProgress.loading': 'Loading...',\n 'myToursProgress.inProgress': 'In progress',\n 'myToursProgress.completed': 'Completed',\n 'myToursProgress.skipped': 'Skipped or dismissed',\n 'myToursProgress.emptyInProgress': 'No tours in progress.',\n 'myToursProgress.emptyCompleted': 'No completed tours yet.',\n 'myToursProgress.emptySkipped': 'No skipped tours.',\n 'myToursProgress.other': 'Other',\n 'myToursProgress.emptyOther': 'No tours in other states.',\n} as const;\n\nexport type ToursTranslationKey = keyof typeof toursTranslationFallbacks;\n\ntype TranslationParams = Record<string, string | number>;\n\nexport function useToursI18n() {\n const i18n = useI18n();\n // The installed fe-libs typings still expose the older 2-argument shape, but the\n // runtime provider supports fallback strings. Narrow locally so this MFE can rely\n // on fallback-backed translations without changing the shared package here.\n const translateWithFallback = i18n.t as (key: string, fallback?: string) => string;\n\n const tr = useCallback(\n (key: ToursTranslationKey, params?: TranslationParams): string => {\n const resolved = translateWithFallback(key, toursTranslationFallbacks[key]);\n\n if (!params) {\n return resolved;\n }\n\n return Object.entries(params).reduce(\n (value, [paramKey, paramValue]) => value.replace(`{{${paramKey}}}`, String(paramValue)),\n resolved\n );\n },\n [translateWithFallback]\n );\n\n return {\n ...i18n,\n tr,\n };\n}\n\nexport function getToursTranslationFallback(key: ToursTranslationKey) {\n return toursTranslationFallbacks[key];\n}\n"],"mappings":"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"}
@@ -26,14 +26,13 @@ function x() {
26
26
  D
27
27
  ]);
28
28
  let R = async (e) => {
29
- if (e) {
30
- for (let t = 0; t < e.length; t++) try {
31
- await j(e[t]);
29
+ e && (await Promise.all(Array.from(e).map(async (e) => {
30
+ try {
31
+ await j(e);
32
32
  } catch {
33
33
  r(w, "media.upload", C);
34
34
  }
35
- A(I === "all" ? void 0 : I, P);
36
- }
35
+ })), A(I === "all" ? void 0 : I, P));
37
36
  }, z = (e) => {
38
37
  switch (e) {
39
38
  case "IMAGE": return /* @__PURE__ */ _(d, { size: 20 });
@@ -65,7 +64,8 @@ function x() {
65
64
  multiple: !0,
66
65
  accept: "image/*,video/*,audio/*,.pdf,.doc,.docx",
67
66
  onChange: (e) => R(e.target.files),
68
- className: "hidden"
67
+ className: "hidden",
68
+ "aria-label": S("mediaLibrary.uploadMedia", "Upload Media")
69
69
  })
70
70
  ]
71
71
  })
@@ -83,12 +83,14 @@ function x() {
83
83
  placeholder: S("mediaLibrary.searchPlaceholder", "Search media..."),
84
84
  value: P,
85
85
  onChange: (e) => F(e.target.value),
86
- className: "w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border rounded-md bg-background"
86
+ className: "w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border rounded-md bg-background",
87
+ "aria-label": S("mediaLibrary.searchPlaceholder", "Search media...")
87
88
  })]
88
89
  }), /* @__PURE__ */ v("select", {
89
90
  value: I,
90
91
  onChange: (e) => L(e.target.value),
91
92
  className: "px-4 py-2 min-h-[44px] border border-border rounded-md bg-background",
93
+ "aria-label": S("mediaLibrary.filterByType", "Filter by media type"),
92
94
  children: [
93
95
  /* @__PURE__ */ _("option", {
94
96
  value: "all",
@@ -164,7 +166,8 @@ function x() {
164
166
  onClick: () => {
165
167
  window.confirm(S("mediaLibrary.deleteConfirm", "Delete this media?")) && M(e.id);
166
168
  },
167
- className: "mt-2 w-full px-2 py-2 min-h-[44px] text-xs text-destructive border border-destructive/30 rounded hover:bg-destructive/10 flex items-center justify-center gap-1",
169
+ type: "button",
170
+ className: "mt-2 w-full p-2 min-h-[44px] text-xs text-destructive border border-destructive/30 rounded hover:bg-destructive/10 flex items-center justify-center gap-1",
168
171
  children: [/* @__PURE__ */ _(m, { size: 12 }), S("mediaLibrary.delete", "Delete")]
169
172
  })
170
173
  ]
@@ -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 { 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 for (let i = 0; i < files.length; i++) {\n try {\n await uploadMedia(files[i]);\n } catch {\n emitToursApiFailed(eventBus, 'media.upload', toursContext);\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 min-h-[44px] 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 min-h-[44px] 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-2 min-h-[44px] 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":"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"}
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 { 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-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 aria-label={tr('mediaLibrary.uploadMedia', 'Upload Media')}\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 min-h-[44px] border border-border rounded-md bg-background\"\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 rounded-md bg-background\"\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 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 type=\"button\"\n className=\"mt-2 w-full p-2 min-h-[44px] 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":"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"}