@burdenoff/microfe-tours 2026.601.1 → 2026.624.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,10 +17,10 @@ function g() {
17
17
  }, [e, r]), /* @__PURE__ */ d("div", {
18
18
  className: "flex flex-col items-center justify-center min-h-[300px] text-center p-6 gap-2",
19
19
  children: [/* @__PURE__ */ u("h2", {
20
- className: "text-lg font-semibold text-foreground",
20
+ className: "text-lg font-semibold text-text-primary",
21
21
  children: n("myTours.notFoundTitle")
22
22
  }), /* @__PURE__ */ u("p", {
23
- className: "text-sm text-muted-foreground max-w-md",
23
+ className: "text-sm text-text-secondary max-w-md",
24
24
  children: n("myTours.notFoundBody")
25
25
  })]
26
26
  });
@@ -53,7 +53,7 @@ function v() {
53
53
  return /* @__PURE__ */ u("output", {
54
54
  className: "flex min-h-[300px] items-center justify-center p-6 text-center",
55
55
  children: /* @__PURE__ */ u("span", {
56
- className: "max-w-md text-sm text-muted-foreground",
56
+ className: "max-w-md text-sm text-text-secondary",
57
57
  children: "Tours are not available for this workspace right now."
58
58
  })
59
59
  });
@@ -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 <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"}
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-text-primary\">{tr('myTours.notFoundTitle')}</h2>\n <p className=\"text-sm text-text-secondary 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-text-secondary\">\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;aAA2C,EAAG,wBAAwB;GAAM,CAAA,EAC1F,kBAAC,KAAD;GAAG,WAAU;aAAwC,EAAG,uBAAuB;GAAK,CAAA,CAChF;;;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;aAAuC;GAEhD,CAAA;EACA,CAAA"}
@@ -39,28 +39,28 @@ var c = 3, l = class extends e {
39
39
  className: "flex flex-col items-center justify-center min-h-[400px] gap-4 text-center p-6",
40
40
  children: [
41
41
  /* @__PURE__ */ a("div", {
42
- className: "p-3 rounded-full bg-destructive/10",
43
- children: /* @__PURE__ */ a(n, { className: "size-8 text-destructive" })
42
+ className: "p-3 rounded-full bg-status-error-bg/10",
43
+ children: /* @__PURE__ */ a(n, { className: "size-8 text-status-error-text" })
44
44
  }),
45
45
  /* @__PURE__ */ o("div", {
46
46
  className: "space-y-2",
47
47
  children: [/* @__PURE__ */ a("h3", {
48
- className: "text-lg font-semibold text-foreground",
48
+ className: "text-lg font-semibold text-text-primary",
49
49
  children: "Failed to load page"
50
50
  }), /* @__PURE__ */ a("p", {
51
- className: "text-sm text-muted-foreground max-w-md",
51
+ className: "text-sm text-text-secondary max-w-md",
52
52
  children: e ? "The page chunk could not be loaded. This may be due to a network issue." : "The page chunk could not be loaded after multiple attempts. Please refresh the page."
53
53
  })]
54
54
  }),
55
55
  e ? /* @__PURE__ */ o("button", {
56
56
  type: "button",
57
57
  onClick: this.handleRetry,
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",
58
+ className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors",
59
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
- 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",
63
+ className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors",
64
64
  children: [/* @__PURE__ */ a(i, { className: "size-4" }), "Refresh Page"]
65
65
  })
66
66
  ]
@@ -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=\"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
+ {"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-status-error-bg/10\">\n <AlertTriangle className=\"size-8 text-status-error-text\" />\n </div>\n <div className=\"space-y-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Failed to load page</h3>\n <p className=\"text-sm text-text-secondary 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-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/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-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/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,iCAAkC,CAAA;MACvD,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAA0C;OAAwB,CAAA,EAChF,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"}
@@ -24,17 +24,17 @@ function g() {
24
24
  children: /* @__PURE__ */ p("div", {
25
25
  className: "text-center",
26
26
  children: [/* @__PURE__ */ f("h2", {
27
- className: "text-lg font-semibold text-foreground mb-2",
27
+ className: "text-lg font-semibold text-text-primary mb-2",
28
28
  children: _("analyticsDashboard.accessDenied", "Access Denied")
29
29
  }), /* @__PURE__ */ f("p", {
30
- className: "text-sm text-muted-foreground",
30
+ className: "text-sm text-text-secondary",
31
31
  children: _("analyticsDashboard.noPermission", "You do not have permission to view tour analytics.")
32
32
  })]
33
33
  })
34
34
  }) : C ? /* @__PURE__ */ f("div", {
35
35
  className: "flex items-center justify-center h-full",
36
36
  children: /* @__PURE__ */ f("p", {
37
- className: "text-muted-foreground",
37
+ className: "text-text-secondary",
38
38
  children: _("analyticsDashboard.loading", "Loading analytics...")
39
39
  })
40
40
  }) : S ? /* @__PURE__ */ p("div", {
@@ -44,24 +44,24 @@ function g() {
44
44
  className: "text-xl sm:text-2xl font-bold",
45
45
  children: _("analyticsDashboard.title", "Tour Analytics")
46
46
  }), /* @__PURE__ */ f("p", {
47
- className: "text-sm text-muted-foreground",
47
+ className: "text-sm text-text-secondary",
48
48
  children: _("analyticsDashboard.subtitle", "Performance metrics and insights")
49
49
  })] }),
50
50
  /* @__PURE__ */ p("div", {
51
51
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4",
52
52
  children: [
53
53
  /* @__PURE__ */ p("div", {
54
- className: "border border-border rounded-md p-4 bg-card",
54
+ className: "border border-border-subtle rounded-md p-4 bg-bg-surface",
55
55
  children: [/* @__PURE__ */ p("div", {
56
56
  className: "flex items-center gap-3 mb-2",
57
57
  children: [/* @__PURE__ */ f("div", {
58
- className: "p-2 bg-primary/10 rounded-md",
58
+ className: "p-2 bg-action-primary-bg/10 rounded-md",
59
59
  children: /* @__PURE__ */ f(d, {
60
60
  className: "text-primary",
61
61
  size: 20
62
62
  })
63
63
  }), /* @__PURE__ */ f("span", {
64
- className: "text-sm text-muted-foreground",
64
+ className: "text-sm text-text-secondary",
65
65
  children: _("analyticsDashboard.totalViews", "Total Views")
66
66
  })]
67
67
  }), /* @__PURE__ */ f("div", {
@@ -70,7 +70,7 @@ function g() {
70
70
  })]
71
71
  }),
72
72
  /* @__PURE__ */ p("div", {
73
- className: "border border-border rounded-md p-4 bg-card",
73
+ className: "border border-border-subtle rounded-md p-4 bg-bg-surface",
74
74
  children: [/* @__PURE__ */ p("div", {
75
75
  className: "flex items-center gap-3 mb-2",
76
76
  children: [/* @__PURE__ */ f("div", {
@@ -80,7 +80,7 @@ function g() {
80
80
  size: 20
81
81
  })
82
82
  }), /* @__PURE__ */ f("span", {
83
- className: "text-sm text-muted-foreground",
83
+ className: "text-sm text-text-secondary",
84
84
  children: _("analyticsDashboard.completionRate", "Completion Rate")
85
85
  })]
86
86
  }), /* @__PURE__ */ f("div", {
@@ -89,7 +89,7 @@ function g() {
89
89
  })]
90
90
  }),
91
91
  /* @__PURE__ */ p("div", {
92
- className: "border border-border rounded-md p-4 bg-card",
92
+ className: "border border-border-subtle rounded-md p-4 bg-bg-surface",
93
93
  children: [/* @__PURE__ */ p("div", {
94
94
  className: "flex items-center gap-3 mb-2",
95
95
  children: [/* @__PURE__ */ f("div", {
@@ -99,7 +99,7 @@ function g() {
99
99
  size: 20
100
100
  })
101
101
  }), /* @__PURE__ */ f("span", {
102
- className: "text-sm text-muted-foreground",
102
+ className: "text-sm text-text-secondary",
103
103
  children: _("analyticsDashboard.skipRate", "Skip Rate")
104
104
  })]
105
105
  }), /* @__PURE__ */ f("div", {
@@ -108,7 +108,7 @@ function g() {
108
108
  })]
109
109
  }),
110
110
  /* @__PURE__ */ p("div", {
111
- className: "border border-border rounded-md p-4 bg-card",
111
+ className: "border border-border-subtle rounded-md p-4 bg-bg-surface",
112
112
  children: [/* @__PURE__ */ p("div", {
113
113
  className: "flex items-center gap-3 mb-2",
114
114
  children: [/* @__PURE__ */ f("div", {
@@ -118,7 +118,7 @@ function g() {
118
118
  size: 20
119
119
  })
120
120
  }), /* @__PURE__ */ f("span", {
121
- className: "text-sm text-muted-foreground",
121
+ className: "text-sm text-text-secondary",
122
122
  children: _("analyticsDashboard.avgDuration", "Avg Duration")
123
123
  })]
124
124
  }), /* @__PURE__ */ f("div", {
@@ -129,7 +129,7 @@ function g() {
129
129
  ]
130
130
  }),
131
131
  /* @__PURE__ */ p("div", {
132
- className: "border border-border rounded-md p-4 sm:p-6 bg-card",
132
+ className: "border border-border-subtle rounded-md p-4 sm:p-6 bg-bg-surface",
133
133
  children: [/* @__PURE__ */ f("h2", {
134
134
  className: "text-lg font-semibold mb-4",
135
135
  children: _("analyticsDashboard.deviceBreakdown", "Device Breakdown")
@@ -141,7 +141,7 @@ function g() {
141
141
  className: "text-sm capitalize",
142
142
  children: e.deviceType
143
143
  }), /* @__PURE__ */ p("span", {
144
- className: "text-sm text-muted-foreground",
144
+ className: "text-sm text-text-secondary",
145
145
  children: [
146
146
  i(e.count),
147
147
  " views (",
@@ -150,16 +150,16 @@ function g() {
150
150
  ]
151
151
  })]
152
152
  }), /* @__PURE__ */ f("div", {
153
- className: "w-full bg-muted rounded-full h-2",
153
+ className: "w-full bg-bg-sunken rounded-full h-2",
154
154
  children: /* @__PURE__ */ f("div", {
155
- className: "bg-primary h-2 rounded-full",
155
+ className: "bg-action-primary-bg h-2 rounded-full",
156
156
  style: { width: `${e.completionRate}%` }
157
157
  })
158
158
  })] }, e.deviceType))
159
159
  })]
160
160
  }),
161
161
  S.stepAnalytics && S.stepAnalytics.length > 0 && /* @__PURE__ */ p("div", {
162
- className: "border border-border rounded-md p-4 sm:p-6 bg-card",
162
+ className: "border border-border-subtle rounded-md p-4 sm:p-6 bg-bg-surface",
163
163
  children: [/* @__PURE__ */ f("h2", {
164
164
  className: "text-lg font-semibold mb-4",
165
165
  children: _("analyticsDashboard.stepPerformance", "Step Performance")
@@ -168,7 +168,7 @@ function g() {
168
168
  children: /* @__PURE__ */ p("table", {
169
169
  className: "w-full",
170
170
  children: [/* @__PURE__ */ f("thead", { children: /* @__PURE__ */ p("tr", {
171
- className: "border-b border-border",
171
+ className: "border-b border-border-subtle",
172
172
  children: [
173
173
  /* @__PURE__ */ f("th", {
174
174
  className: "text-left py-2 px-4 text-sm font-medium",
@@ -188,7 +188,7 @@ function g() {
188
188
  })
189
189
  ]
190
190
  }) }), /* @__PURE__ */ f("tbody", { children: S.stepAnalytics.map((e) => /* @__PURE__ */ p("tr", {
191
- className: "border-b border-border",
191
+ className: "border-b border-border-subtle",
192
192
  children: [
193
193
  /* @__PURE__ */ p("td", {
194
194
  className: "py-2 px-4 text-sm",
@@ -219,7 +219,7 @@ function g() {
219
219
  }) : /* @__PURE__ */ f("div", {
220
220
  className: "flex items-center justify-center h-full",
221
221
  children: /* @__PURE__ */ f("p", {
222
- className: "text-muted-foreground",
222
+ className: "text-text-secondary",
223
223
  children: _("analyticsDashboard.noData", "No analytics data available")
224
224
  })
225
225
  });
@@ -1 +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-3 sm: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-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold\">\n {tr('analyticsDashboard.title', 'Tour Analytics')}\n </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-4 sm: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-4 sm: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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"}
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-3 sm:p-6\">\n <div className=\"text-center\">\n <h2 className=\"text-lg font-semibold text-text-primary mb-2\">\n {tr('analyticsDashboard.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\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-text-secondary\">\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-text-secondary\">\n {tr('analyticsDashboard.noData', 'No analytics data available')}\n </p>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold\">\n {tr('analyticsDashboard.title', 'Tour Analytics')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\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-subtle rounded-md p-4 bg-bg-surface\">\n <div className=\"flex items-center gap-3 mb-2\">\n <div className=\"p-2 bg-action-primary-bg/10 rounded-md\">\n <Users className=\"text-primary\" size={20} />\n </div>\n <span className=\"text-sm text-text-secondary\">\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-subtle rounded-md p-4 bg-bg-surface\">\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-text-secondary\">\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-subtle rounded-md p-4 bg-bg-surface\">\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-text-secondary\">\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-subtle rounded-md p-4 bg-bg-surface\">\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-text-secondary\">\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-subtle rounded-md p-4 sm:p-6 bg-bg-surface\">\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-text-secondary\">\n {formatNumber(device.count)} views ({formatPercentage(device.completionRate)})\n </span>\n </div>\n <div className=\"w-full bg-bg-sunken rounded-full h-2\">\n <div\n className=\"bg-action-primary-bg h-2 rounded-full\"\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-subtle rounded-md p-4 sm:p-6 bg-bg-surface\">\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-subtle\">\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-subtle\">\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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"}
@@ -50,12 +50,12 @@ function x() {
50
50
  className: "text-xl sm:text-2xl font-bold",
51
51
  children: S("mediaLibrary.title", "Media Library")
52
52
  }), /* @__PURE__ */ _("p", {
53
- className: "text-sm text-muted-foreground",
53
+ className: "text-sm text-text-secondary",
54
54
  children: S("mediaLibrary.subtitle", "Upload and manage media for tour steps")
55
55
  })] }), /* @__PURE__ */ _(l, {
56
56
  permission: n.TOUR_MEDIA_CREATE,
57
57
  children: /* @__PURE__ */ v("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",
58
+ className: "w-full sm:w-auto px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 cursor-pointer flex items-center justify-center gap-2",
59
59
  children: [
60
60
  /* @__PURE__ */ _(h, { size: 20 }),
61
61
  S("mediaLibrary.uploadMedia", "Upload Media"),
@@ -76,20 +76,20 @@ function x() {
76
76
  children: [/* @__PURE__ */ v("div", {
77
77
  className: "flex-1 relative",
78
78
  children: [/* @__PURE__ */ _(p, {
79
- className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground",
79
+ className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary",
80
80
  size: 20
81
81
  }), /* @__PURE__ */ _("input", {
82
82
  type: "text",
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-subtle rounded-md bg-bg-surface",
87
87
  "aria-label": S("mediaLibrary.searchPlaceholder", "Search media...")
88
88
  })]
89
89
  }), /* @__PURE__ */ v("select", {
90
90
  value: I,
91
91
  onChange: (e) => L(e.target.value),
92
- className: "px-4 py-2 min-h-[44px] border border-border rounded-md bg-background",
92
+ className: "px-4 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface",
93
93
  "aria-label": S("mediaLibrary.filterByType", "Filter by media type"),
94
94
  children: [
95
95
  /* @__PURE__ */ _("option", {
@@ -118,20 +118,20 @@ function x() {
118
118
  Object.keys(N).length > 0 && /* @__PURE__ */ _("div", {
119
119
  className: "space-y-2",
120
120
  children: Object.entries(N).map(([e, t]) => /* @__PURE__ */ v("div", {
121
- className: "border border-border rounded-md p-3",
121
+ className: "border border-border-subtle rounded-md p-3",
122
122
  children: [/* @__PURE__ */ v("div", {
123
123
  className: "flex items-center justify-between mb-2",
124
124
  children: [/* @__PURE__ */ _("span", {
125
125
  className: "text-sm",
126
126
  children: S("mediaLibrary.uploading", "Uploading...")
127
127
  }), /* @__PURE__ */ v("span", {
128
- className: "text-sm text-muted-foreground",
128
+ className: "text-sm text-text-secondary",
129
129
  children: [t, "%"]
130
130
  })]
131
131
  }), /* @__PURE__ */ _("div", {
132
- className: "w-full bg-muted rounded-full h-2",
132
+ className: "w-full bg-bg-sunken rounded-full h-2",
133
133
  children: /* @__PURE__ */ _("div", {
134
- className: "bg-primary h-2 rounded-full transition-all",
134
+ className: "bg-action-primary-bg h-2 rounded-full transition-all",
135
135
  style: { width: `${t}%` }
136
136
  })
137
137
  })]
@@ -140,15 +140,15 @@ function x() {
140
140
  k.length > 0 ? /* @__PURE__ */ _("div", {
141
141
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4",
142
142
  children: k.map((e) => /* @__PURE__ */ v("div", {
143
- className: "border border-border rounded-md overflow-hidden hover:shadow-md transition-shadow",
143
+ className: "border border-border-subtle rounded-md overflow-hidden hover:shadow-md transition-shadow",
144
144
  children: [/* @__PURE__ */ _("div", {
145
- className: "aspect-video bg-muted flex items-center justify-center",
145
+ className: "aspect-video bg-bg-sunken flex items-center justify-center",
146
146
  children: e.type === "IMAGE" ? /* @__PURE__ */ _("img", {
147
147
  src: e.cdnUrl,
148
148
  alt: e.altText || e.name,
149
149
  className: "w-full h-full object-cover"
150
150
  }) : /* @__PURE__ */ _("div", {
151
- className: "text-muted-foreground",
151
+ className: "text-text-secondary",
152
152
  children: z(e.type)
153
153
  })
154
154
  }), /* @__PURE__ */ v("div", {
@@ -159,7 +159,7 @@ function x() {
159
159
  children: e.name
160
160
  }),
161
161
  /* @__PURE__ */ v("div", {
162
- className: "flex items-center justify-between mt-2 text-xs text-muted-foreground",
162
+ className: "flex items-center justify-between mt-2 text-xs text-text-secondary",
163
163
  children: [/* @__PURE__ */ _("span", { children: e.type }), /* @__PURE__ */ _("span", { children: i(e.size) })]
164
164
  }),
165
165
  O && /* @__PURE__ */ v("button", {
@@ -167,25 +167,25 @@ function x() {
167
167
  window.confirm(S("mediaLibrary.deleteConfirm", "Delete this media?")) && M(e.id);
168
168
  },
169
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",
170
+ className: "mt-2 w-full p-2 min-h-[44px] text-xs text-status-error-text border border-status-error-border/30 rounded hover:bg-status-error-bg/10 flex items-center justify-center gap-1",
171
171
  children: [/* @__PURE__ */ _(m, { size: 12 }), S("mediaLibrary.delete", "Delete")]
172
172
  })
173
173
  ]
174
174
  })]
175
175
  }, e.id))
176
176
  }) : /* @__PURE__ */ v("div", {
177
- className: "text-center py-12 border border-dashed border-border rounded-md",
177
+ className: "text-center py-12 border border-dashed border-border-subtle rounded-md",
178
178
  children: [
179
179
  /* @__PURE__ */ _(h, {
180
- className: "mx-auto mb-4 text-muted-foreground",
180
+ className: "mx-auto mb-4 text-text-secondary",
181
181
  size: 48
182
182
  }),
183
183
  /* @__PURE__ */ _("p", {
184
- className: "text-muted-foreground mb-2",
184
+ className: "text-text-secondary mb-2",
185
185
  children: S("mediaLibrary.noMedia", "No media files")
186
186
  }),
187
187
  /* @__PURE__ */ _("p", {
188
- className: "text-sm text-muted-foreground",
188
+ className: "text-sm text-text-secondary",
189
189
  children: S("mediaLibrary.uploadFirstMedia", "Upload your first media file to get started")
190
190
  })
191
191
  ]
@@ -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 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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"}
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-text-secondary\">\n {tr('mediaLibrary.subtitle', 'Upload and manage media for tour steps')}\n </p>\n </div>\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_MEDIA_CREATE}>\n <label className=\"w-full sm:w-auto px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 cursor-pointer flex items-center justify-center gap-2\">\n <Upload size={20} />\n {tr('mediaLibrary.uploadMedia', 'Upload Media')}\n <input\n type=\"file\"\n multiple\n accept=\"image/*,video/*,audio/*,.pdf,.doc,.docx\"\n onChange={(e) => handleFileUpload(e.target.files)}\n className=\"hidden\"\n aria-label={tr('mediaLibrary.uploadMedia', 'Upload Media')}\n />\n </label>\n </PermissionGate>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <div className=\"flex-1 relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n size={20}\n />\n <input\n type=\"text\"\n placeholder={tr('mediaLibrary.searchPlaceholder', 'Search media...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n aria-label={tr('mediaLibrary.searchPlaceholder', 'Search media...')}\n />\n </div>\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value as TourMediaType | 'all')}\n className=\"px-4 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n aria-label={tr('mediaLibrary.filterByType', 'Filter by media type')}\n >\n <option value=\"all\">{tr('mediaLibrary.allTypes', 'All Types')}</option>\n <option value=\"IMAGE\">{tr('mediaLibrary.typeImages', 'Images')}</option>\n <option value=\"VIDEO\">{tr('mediaLibrary.typeVideos', 'Videos')}</option>\n <option value=\"AUDIO\">{tr('mediaLibrary.typeAudio', 'Audio')}</option>\n <option value=\"DOCUMENT\">{tr('mediaLibrary.typeDocuments', 'Documents')}</option>\n </select>\n </div>\n\n {/* Upload Progress */}\n {Object.keys(uploadProgress).length > 0 && (\n <div className=\"space-y-2\">\n {Object.entries(uploadProgress).map(([fileId, progress]) => (\n <div key={fileId} className=\"border border-border-subtle rounded-md p-3\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm\">{tr('mediaLibrary.uploading', 'Uploading...')}</span>\n <span className=\"text-sm text-text-secondary\">{progress}%</span>\n </div>\n <div className=\"w-full bg-bg-sunken rounded-full h-2\">\n <div\n className=\"bg-action-primary-bg h-2 rounded-full transition-all\"\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Media Grid */}\n {media.length > 0 ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4\">\n {media.map((item) => (\n <div\n key={item.id}\n className=\"border border-border-subtle rounded-md overflow-hidden hover:shadow-md transition-shadow\"\n >\n {/* Thumbnail */}\n <div className=\"aspect-video bg-bg-sunken flex items-center justify-center\">\n {item.type === 'IMAGE' ? (\n <img\n src={item.cdnUrl}\n alt={item.altText || item.name}\n className=\"w-full h-full object-cover\"\n />\n ) : (\n <div className=\"text-text-secondary\">{getMediaIcon(item.type)}</div>\n )}\n </div>\n\n {/* Info */}\n <div className=\"p-3\">\n <h3 className=\"font-medium text-sm truncate\">{item.name}</h3>\n <div className=\"flex items-center justify-between mt-2 text-xs text-text-secondary\">\n <span>{item.type}</span>\n <span>{formatFileSize(item.size)}</span>\n </div>\n {canDeleteMedia && (\n <button\n onClick={() => {\n if (window.confirm(tr('mediaLibrary.deleteConfirm', 'Delete this media?')))\n deleteMedia(item.id);\n }}\n type=\"button\"\n className=\"mt-2 w-full p-2 min-h-[44px] text-xs text-status-error-text border border-status-error-border/30 rounded hover:bg-status-error-bg/10 flex items-center justify-center gap-1\"\n >\n <Trash2 size={12} />\n {tr('mediaLibrary.delete', 'Delete')}\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12 border border-dashed border-border-subtle rounded-md\">\n <Upload className=\"mx-auto mb-4 text-text-secondary\" size={48} />\n <p className=\"text-text-secondary mb-2\">\n {tr('mediaLibrary.noMedia', 'No media files')}\n </p>\n <p className=\"text-sm text-text-secondary\">\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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"}
@@ -43,35 +43,35 @@ function _() {
43
43
  return /* @__PURE__ */ u("div", {
44
44
  className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
45
45
  children: [/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("h1", {
46
- className: "text-xl sm:text-2xl font-bold text-foreground",
46
+ className: "text-xl sm:text-2xl font-bold text-text-primary",
47
47
  children: a("myTours.title")
48
48
  }), /* @__PURE__ */ l("p", {
49
- className: "text-sm sm:text-base text-muted-foreground",
49
+ className: "text-sm sm:text-base text-text-secondary",
50
50
  children: a("myTours.subtitle")
51
51
  })] }), E && T.length === 0 ? /* @__PURE__ */ l("output", {
52
- className: "text-sm text-muted-foreground",
52
+ className: "text-sm text-text-secondary",
53
53
  children: a("myTours.loading")
54
54
  }) : T.length === 0 ? /* @__PURE__ */ l("div", {
55
- className: "rounded-lg border border-dashed border-border p-8 text-center",
55
+ className: "rounded-lg border border-dashed border-border-subtle p-8 text-center",
56
56
  children: /* @__PURE__ */ l("output", {
57
- className: "block text-sm text-muted-foreground",
57
+ className: "block text-sm text-text-secondary",
58
58
  children: a("myTours.empty")
59
59
  })
60
60
  }) : /* @__PURE__ */ l("ul", {
61
61
  className: "grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
62
62
  children: T.map((e) => /* @__PURE__ */ u("li", {
63
- className: "rounded-lg border border-border bg-card p-4 flex flex-col gap-2",
63
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4 flex flex-col gap-2",
64
64
  children: [/* @__PURE__ */ u("div", {
65
65
  className: "flex items-start justify-between gap-2",
66
66
  children: [/* @__PURE__ */ l("h2", {
67
- className: "font-semibold text-foreground line-clamp-2",
67
+ className: "font-semibold text-text-primary line-clamp-2",
68
68
  children: e.name
69
69
  }), /* @__PURE__ */ l(v, {
70
70
  status: e.myProgress?.status,
71
71
  tr: a
72
72
  })]
73
73
  }), e.description ? /* @__PURE__ */ l("p", {
74
- className: "text-sm text-muted-foreground line-clamp-3",
74
+ className: "text-sm text-text-secondary line-clamp-3",
75
75
  children: e.description
76
76
  }) : null]
77
77
  }, e.id))
@@ -90,11 +90,11 @@ function v({ status: e, tr: t }) {
90
90
  });
91
91
  case "SKIPPED":
92
92
  case "DISMISSED": return /* @__PURE__ */ u("span", {
93
- className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
93
+ className: "inline-flex items-center gap-1 text-xs text-text-secondary",
94
94
  children: [/* @__PURE__ */ l(s, { size: 14 }), t("myTours.status.skipped")]
95
95
  });
96
96
  default: return /* @__PURE__ */ u("span", {
97
- className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
97
+ className: "inline-flex items-center gap-1 text-xs text-text-secondary",
98
98
  children: [/* @__PURE__ */ l(o, { size: 14 }), t("myTours.status.notStarted")]
99
99
  });
100
100
  }