@burdenoff/microfe-tours 2026.1014.1 → 2026.1114.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/constants/featureFlags.d.ts +24 -0
  2. package/dist/constants/featureFlags.js +1 -2
  3. package/dist/constants/featureFlags.js.map +1 -1
  4. package/dist/constants/permissions.d.ts +48 -0
  5. package/dist/constants/permissions.js +1 -2
  6. package/dist/constants/permissions.js.map +1 -1
  7. package/dist/dev/main.d.ts +0 -0
  8. package/dist/generated/global-operations.d.ts +1318 -0
  9. package/dist/generated/global-operations.js +2 -3
  10. package/dist/generated/global-operations.js.map +1 -1
  11. package/dist/generated/global-types.d.ts +26469 -0
  12. package/dist/generated/wspace-operations.d.ts +1 -0
  13. package/dist/generated/wspace-types.d.ts +108647 -0
  14. package/dist/index.d.ts +5 -0
  15. package/dist/test/happydom.d.ts +1 -0
  16. package/dist/test/testing-library.d.ts +1 -0
  17. package/dist/tours/ToursAdminRoot.d.ts +14 -0
  18. package/dist/tours/ToursAdminRoot.js +14 -13
  19. package/dist/tours/ToursAdminRoot.js.map +1 -1
  20. package/dist/tours/ToursAdminRoutes.d.ts +11 -0
  21. package/dist/tours/ToursAdminRoutes.js +12 -13
  22. package/dist/tours/ToursAdminRoutes.js.map +1 -1
  23. package/dist/tours/ToursRoot.d.ts +9 -0
  24. package/dist/tours/ToursRoot.js +1 -1
  25. package/dist/tours/ToursUserRoot.d.ts +13 -0
  26. package/dist/tours/ToursUserRoot.js +14 -13
  27. package/dist/tours/ToursUserRoot.js.map +1 -1
  28. package/dist/tours/ToursUserRoutes.d.ts +14 -0
  29. package/dist/tours/ToursUserRoutes.js +13 -13
  30. package/dist/tours/ToursUserRoutes.js.map +1 -1
  31. package/dist/tours/components/MarkdownEditor.d.ts +12 -0
  32. package/dist/tours/components/MarkdownEditor.js +1 -2
  33. package/dist/tours/components/MarkdownEditor.js.map +1 -1
  34. package/dist/tours/components/RouteHelpers.d.ts +31 -0
  35. package/dist/tours/components/RouteHelpers.js +1 -2
  36. package/dist/tours/components/RouteHelpers.js.map +1 -1
  37. package/dist/tours/components/ToursAdminNavRegistrar.d.ts +23 -0
  38. package/dist/tours/components/ToursAdminNavRegistrar.js +4 -3
  39. package/dist/tours/components/ToursAdminNavRegistrar.js.map +1 -1
  40. package/dist/tours/hooks/useMediaLibrary.d.ts +20 -0
  41. package/dist/tours/hooks/useMediaLibrary.js +1 -2
  42. package/dist/tours/hooks/useMediaLibrary.js.map +1 -1
  43. package/dist/tours/hooks/useStepSequencer.d.ts +18 -0
  44. package/dist/tours/hooks/useStepSequencer.js +16 -13
  45. package/dist/tours/hooks/useStepSequencer.js.map +1 -1
  46. package/dist/tours/hooks/useTourAnalytics.d.ts +30 -0
  47. package/dist/tours/hooks/useTourAnalytics.js +1 -2
  48. package/dist/tours/hooks/useTourAnalytics.js.map +1 -1
  49. package/dist/tours/hooks/useTourBuilder.d.ts +59 -0
  50. package/dist/tours/hooks/useTourBuilder.js +10 -6
  51. package/dist/tours/hooks/useTourBuilder.js.map +1 -1
  52. package/dist/tours/hooks/useTourLifecycle.d.ts +21 -0
  53. package/dist/tours/hooks/useTourLifecycle.js +4 -5
  54. package/dist/tours/hooks/useTourLifecycle.js.map +1 -1
  55. package/dist/tours/hooks/useTourPreview.d.ts +31 -0
  56. package/dist/tours/hooks/useTourPreview.js +5 -6
  57. package/dist/tours/hooks/useTourPreview.js.map +1 -1
  58. package/dist/tours/hooks/useToursList.d.ts +15 -0
  59. package/dist/tours/hooks/useToursList.js +1 -2
  60. package/dist/tours/hooks/useToursList.js.map +1 -1
  61. package/dist/tours/hooks/useToursPermissionAccess.d.ts +15 -0
  62. package/dist/tours/hooks/useToursPermissionAccess.js +1 -2
  63. package/dist/tours/hooks/useToursPermissionAccess.js.map +1 -1
  64. package/dist/tours/i18n.d.ts +413 -0
  65. package/dist/tours/i18n.js +1 -2
  66. package/dist/tours/i18n.js.map +1 -1
  67. package/dist/tours/index.d.ts +21 -0
  68. package/dist/tours/index.js +6 -5
  69. package/dist/tours/pages/AnalyticsDashboardPage.d.ts +5 -0
  70. package/dist/tours/pages/AnalyticsDashboardPage.js +6 -7
  71. package/dist/tours/pages/AnalyticsDashboardPage.js.map +1 -1
  72. package/dist/tours/pages/MediaLibraryPage.d.ts +5 -0
  73. package/dist/tours/pages/MediaLibraryPage.js +10 -11
  74. package/dist/tours/pages/MediaLibraryPage.js.map +1 -1
  75. package/dist/tours/pages/MyToursPage.d.ts +10 -0
  76. package/dist/tours/pages/MyToursPage.js +5 -6
  77. package/dist/tours/pages/MyToursPage.js.map +1 -1
  78. package/dist/tours/pages/MyToursProgressPage.d.ts +5 -0
  79. package/dist/tours/pages/MyToursProgressPage.js +5 -6
  80. package/dist/tours/pages/MyToursProgressPage.js.map +1 -1
  81. package/dist/tours/pages/SchedulerPage.d.ts +5 -0
  82. package/dist/tours/pages/SchedulerPage.js +10 -11
  83. package/dist/tours/pages/SchedulerPage.js.map +1 -1
  84. package/dist/tours/pages/StepEditorPage.d.ts +6 -0
  85. package/dist/tours/pages/StepEditorPage.js +302 -309
  86. package/dist/tours/pages/StepEditorPage.js.map +1 -1
  87. package/dist/tours/pages/SystemAnalyticsPage.d.ts +5 -0
  88. package/dist/tours/pages/SystemAnalyticsPage.js +1 -2
  89. package/dist/tours/pages/SystemAnalyticsPage.js.map +1 -1
  90. package/dist/tours/pages/TemplateLibraryPage.d.ts +5 -0
  91. package/dist/tours/pages/TemplateLibraryPage.js +15 -16
  92. package/dist/tours/pages/TemplateLibraryPage.js.map +1 -1
  93. package/dist/tours/pages/TourBuilderPage.d.ts +1 -0
  94. package/dist/tours/pages/TourBuilderPage.js +273 -300
  95. package/dist/tours/pages/TourBuilderPage.js.map +1 -1
  96. package/dist/tours/pages/TourPreviewPage.d.ts +5 -0
  97. package/dist/tours/pages/TourPreviewPage.js +9 -10
  98. package/dist/tours/pages/TourPreviewPage.js.map +1 -1
  99. package/dist/tours/pages/ToursListPage.d.ts +5 -0
  100. package/dist/tours/pages/ToursListPage.js +157 -157
  101. package/dist/tours/pages/ToursListPage.js.map +1 -1
  102. package/dist/tours/providers/ToursProvider.d.ts +25 -0
  103. package/dist/tours/providers/ToursProvider.js +1 -2
  104. package/dist/tours/providers/ToursProvider.js.map +1 -1
  105. package/dist/tours/store/toursStore.d.ts +40 -0
  106. package/dist/tours/store/toursStore.js +5 -6
  107. package/dist/tours/store/toursStore.js.map +1 -1
  108. package/dist/tours/telemetry.d.ts +7 -0
  109. package/dist/tours/telemetry.js +1 -2
  110. package/dist/tours/telemetry.js.map +1 -1
  111. package/dist/tours/types/index.d.ts +348 -0
  112. package/dist/tours/utils/index.d.ts +28 -0
  113. package/dist/tours/utils/index.js +3 -4
  114. package/dist/tours/utils/index.js.map +1 -1
  115. package/dist/tours/utils/joinPath.d.ts +8 -0
  116. package/dist/tours/utils/joinPath.js +1 -2
  117. package/dist/tours/utils/joinPath.js.map +1 -1
  118. package/dist/utils/nativeBridge.d.ts +2 -0
  119. package/dist/utils/nativeBridge.js +1 -2
  120. package/package.json +62 -51
@@ -2,7 +2,7 @@ import { useTr as e } from "../i18n.js";
2
2
  import { Component as t, Fragment as n } from "react";
3
3
  import { AlertTriangle as r, Loader2 as i, RefreshCw as a } from "lucide-react";
4
4
  import { jsx as o, jsxs as s } from "react/jsx-runtime";
5
- //#region src/tours/components/RouteHelpers.tsx
5
+ // src/tours/components/RouteHelpers.tsx
6
6
  function c() {
7
7
  return /* @__PURE__ */ o("div", {
8
8
  className: "flex items-center justify-center min-h-[200px]",
@@ -74,7 +74,6 @@ var d = class extends t {
74
74
  }) : /* @__PURE__ */ o(n, { children: this.props.children }, this.state.retryCount);
75
75
  }
76
76
  };
77
- //#endregion
78
77
  export { d as LazyErrorBoundary, c as PageLoader };
79
78
 
80
79
  //# sourceMappingURL=RouteHelpers.js.map
@@ -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';\nimport { useTr } from '../i18n';\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\ninterface LazyErrorScreenProps {\n retryCount: number;\n onRetry: () => void;\n}\n\nfunction LazyErrorScreen({ retryCount, onRetry }: LazyErrorScreenProps) {\n const tr = useTr();\n const canRetry = retryCount < MAX_RETRY_COUNT;\n\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\">\n {tr('routeErrors.failedToLoadPage', 'Failed to load page')}\n </h3>\n <p className=\"text-sm text-text-secondary max-w-md\">\n {canRetry\n ? tr(\n 'routeErrors.chunkLoadRetry',\n 'The page chunk could not be loaded. This may be due to a network issue.'\n )\n : tr(\n 'routeErrors.chunkLoadRefresh',\n 'The page chunk could not be loaded after multiple attempts. Please refresh the page.'\n )}\n </p>\n </div>\n {canRetry ? (\n <button\n type=\"button\"\n onClick={onRetry}\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 {tr('routeErrors.tryAgain', '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 {tr('routeErrors.refreshPage', 'Refresh Page')}\n </button>\n )}\n </div>\n );\n}\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 return <LazyErrorScreen retryCount={this.state.retryCount} onRetry={this.handleRetry} />;\n }\n\n return <Fragment key={this.state.retryCount}>{this.props.children}</Fragment>;\n }\n}\n"],"mappings":";;;;;AAQA,SAAgB,IAAa;AAC3B,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,oCAAqC,CAAA;EACpD,CAAA;;AAKV,IAAM,IAAkB;AAOxB,SAAS,EAAgB,EAAE,eAAY,cAAiC;CACtE,IAAM,IAAK,GAAO,EACZ,IAAW,IAAa;AAE9B,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAe,WAAU,iCAAkC,CAAA;IACvD,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,gCAAgC,sBAAsB;KACvD,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,IACG,EACE,8BACA,0EACD,GACD,EACE,gCACA,uFACD;KACH,CAAA,CACA;;GACL,IACC,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EAAG,wBAAwB,YAAY,CACjC;QAET,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,OAAO,SAAS,QAAQ;IACvC,WAAU;cAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EAAG,2BAA2B,eAAe,CACvC;;GAEP;;;AAWV,IAAa,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;AAKP,SAJI,KAAK,MAAM,WACN,kBAAC,GAAD;GAAiB,YAAY,KAAK,MAAM;GAAY,SAAS,KAAK;GAAe,CAAA,GAGnF,kBAAC,GAAD,EAAA,UAAuC,KAAK,MAAM,UAAoB,EAAvD,KAAK,MAAM,WAA4C"}
1
+ {"version":3,"file":"RouteHelpers.js","names":["PageLoader","Loader2","MAX_RETRY_COUNT","LazyErrorScreen","tr","useTr","canRetry","retryCount","AlertTriangle","onRetry","RefreshCw","LazyErrorBoundary","Component","props","error","errorInfo","prev","Fragment"],"sources":["../../../src/tours/components/RouteHelpers.tsx"],"sourcesContent":["import { Component, Fragment, type ReactNode, type ErrorInfo } from 'react';\nimport { Loader2, AlertTriangle, RefreshCw } from 'lucide-react';\nimport { useTr } from '../i18n';\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\ninterface LazyErrorScreenProps {\n retryCount: number;\n onRetry: () => void;\n}\n\nfunction LazyErrorScreen({ retryCount, onRetry }: LazyErrorScreenProps) {\n const tr = useTr();\n const canRetry = retryCount < MAX_RETRY_COUNT;\n\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\">\n {tr('routeErrors.failedToLoadPage', 'Failed to load page')}\n </h3>\n <p className=\"text-sm text-text-secondary max-w-md\">\n {canRetry\n ? tr(\n 'routeErrors.chunkLoadRetry',\n 'The page chunk could not be loaded. This may be due to a network issue.'\n )\n : tr(\n 'routeErrors.chunkLoadRefresh',\n 'The page chunk could not be loaded after multiple attempts. Please refresh the page.'\n )}\n </p>\n </div>\n {canRetry ? (\n <button\n type=\"button\"\n onClick={onRetry}\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 {tr('routeErrors.tryAgain', '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 {tr('routeErrors.refreshPage', 'Refresh Page')}\n </button>\n )}\n </div>\n );\n}\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 override 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 override render() {\n if (this.state.hasError) {\n return <LazyErrorScreen retryCount={this.state.retryCount} onRetry={this.handleRetry} />;\n }\n\n return <Fragment key={this.state.retryCount}>{this.props.children}</Fragment>;\n }\n}\n"],"mappings":";;;;;AAQA,SAAgBA,IAAa;CAC3B,OACE,kBAAC,OAAD;EAAK,WAAU;EACb,UAAA,kBAACC,GAAD,EAAS,WAAU,mCAAoC,CAAA;CACpD,CAAA;AAET;AAGA,IAAMC,IAAkB;AAOxB,SAASC,EAAgB,EAAE,eAAY,cAAiC;CACtE,IAAMC,IAAKC,EAAM,GACXC,IAAWC,IAAaL;CAE9B,OACE,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,kBAAC,OAAD;IAAK,WAAU;IACb,UAAA,kBAACM,GAAD,EAAe,WAAU,gCAAiC,CAAA;GACvD,CAAA;GACL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;KACX,UAAAJ,EAAG,gCAAgC,qBAAqB;IACvD,CAAA,GACJ,kBAAC,KAAD;KAAG,WAAU;KACV,UAAAE,IACGF,EACE,8BACA,yEACF,IACAA,EACE,gCACA,sFACF;IACH,CAAA,CACA;;GACJE,IACC,kBAAC,UAAD;IACE,MAAK;IACL,SAASG;IACT,WAAU;IAHZ,UAAA,CAKE,kBAACC,GAAD,EAAW,WAAU,SAAU,CAAA,GAC9BN,EAAG,wBAAwB,WAAW,CACjC;GAER,CAAA,IAAA,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,OAAO,SAAS,OAAO;IACtC,WAAU;IAHZ,UAAA,CAKE,kBAACM,GAAD,EAAW,WAAU,SAAU,CAAA,GAC9BN,EAAG,2BAA2B,cAAc,CACvC;;EAEP;;AAET;AASA,IAAaO,IAAb,cAAuCC,EAGrC;CACA,YAAY,GAAgC;EAE1C,AADA,MAAMC,CAAK,GACX,KAAK,QAAQ;GAAE,UAAU;GAAO,OAAO;GAAM,YAAY;EAAE;CAC7D;CAEA,OAAO,yBAAyB,GAAc;EAC5C,OAAO;GAAE,UAAU;GAAM;EAAM;CACjC;CAEA,kBAA2B,GAAc,GAAsB;EAK7D,QAAQ,MAAM,kCAAkCC,GAAOC,CAAS;CAClE;CAEA,oBAAoB;EAClB,KAAK,UAAU,OAAU;GACvB,UAAU;GACV,OAAO;GACP,YAAYC,EAAK,aAAa;EAChC,EAAE;CACJ;CAEA,SAAkB;EAKhB,OAJI,KAAK,MAAM,WACN,kBAACb,GAAD;GAAiB,YAAY,KAAK,MAAM;GAAY,SAAS,KAAK;EAAc,CAAA,IAGlF,kBAACc,GAAD,EAAA,UAAuC,KAAK,MAAM,SAAmB,GAAtD,KAAK,MAAM,UAA2C;CAC9E;AACF"}
@@ -0,0 +1,23 @@
1
+ import { NavItem } from '../types';
2
+ interface ToursAdminNavRegistrarProps {
3
+ basePath: string;
4
+ registerNavItems: (items: NavItem | NavItem[]) => () => void;
5
+ }
6
+ /**
7
+ * Registers the admin tours nav items with the host shell.
8
+ *
9
+ * Rendered inside `<FeatureFlagProvider>` so it can consult `useFeatureFlag`
10
+ * to conditionally include flag-gated items. Re-registers whenever flag state
11
+ * changes so the host shell's nav reflects the current evaluation in real time.
12
+ *
13
+ * Baseline items (always registered): All Tours, Create Tour, Templates,
14
+ * Analytics. Media Library is gated on `tours.media` because the upload
15
+ * pipeline is still incomplete — the nav item appears only when the flag is
16
+ * enabled for the workspace or the user has opted in via Feature Previews.
17
+ *
18
+ * `registerNavItems` is captured in a ref so a host shell that hands over a
19
+ * fresh closure on every render can't trigger an unregister → re-register
20
+ * storm. Only flag-state / `basePath` changes cause re-registration.
21
+ */
22
+ export declare function ToursAdminNavRegistrar({ basePath, registerNavItems, }: ToursAdminNavRegistrarProps): null;
23
+ export {};
@@ -5,10 +5,12 @@ import { useEffect as r, useMemo as i, useRef as a } from "react";
5
5
  import { useFeatureFlag as o } from "@burdenoff/fe-libs/shared/feature-flags";
6
6
  import { BarChart3 as s, Image as c, LayoutTemplate as l, List as u, Plus as d } from "lucide-react";
7
7
  import { jsx as f } from "react/jsx-runtime";
8
- //#region src/tours/components/ToursAdminNavRegistrar.tsx
8
+ // src/tours/components/ToursAdminNavRegistrar.tsx
9
9
  function p({ basePath: p, registerNavItems: m }) {
10
10
  let h = n(), g = o(e.MEDIA, !1), _ = a(m);
11
- _.current = m;
11
+ r(() => {
12
+ _.current = m;
13
+ });
12
14
  let v = i(() => {
13
15
  let e = [
14
16
  {
@@ -59,7 +61,6 @@ function p({ basePath: p, registerNavItems: m }) {
59
61
  ]);
60
62
  return r(() => _.current(v), [v]), null;
61
63
  }
62
- //#endregion
63
64
  export { p as ToursAdminNavRegistrar };
64
65
 
65
66
  //# sourceMappingURL=ToursAdminNavRegistrar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ToursAdminNavRegistrar.js","names":[],"sources":["../../../src/tours/components/ToursAdminNavRegistrar.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef } from 'react';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { List, Plus, LayoutTemplate, Image, BarChart3 } from 'lucide-react';\n\nimport { TOURS_FEATURES } from '../../constants/featureFlags';\nimport type { NavItem } from '../types';\nimport { joinPath } from '../utils/joinPath';\nimport { useTr } from '../i18n';\n\ninterface ToursAdminNavRegistrarProps {\n basePath: string;\n registerNavItems: (items: NavItem | NavItem[]) => () => void;\n}\n\n/**\n * Registers the admin tours nav items with the host shell.\n *\n * Rendered inside `<FeatureFlagProvider>` so it can consult `useFeatureFlag`\n * to conditionally include flag-gated items. Re-registers whenever flag state\n * changes so the host shell's nav reflects the current evaluation in real time.\n *\n * Baseline items (always registered): All Tours, Create Tour, Templates,\n * Analytics. Media Library is gated on `tours.media` because the upload\n * pipeline is still incomplete — the nav item appears only when the flag is\n * enabled for the workspace or the user has opted in via Feature Previews.\n *\n * `registerNavItems` is captured in a ref so a host shell that hands over a\n * fresh closure on every render can't trigger an unregister → re-register\n * storm. Only flag-state / `basePath` changes cause re-registration.\n */\nexport function ToursAdminNavRegistrar({\n basePath,\n registerNavItems,\n}: ToursAdminNavRegistrarProps) {\n const tr = useTr();\n const mediaEnabled = useFeatureFlag(TOURS_FEATURES.MEDIA, false);\n\n const registerNavItemsRef = useRef(registerNavItems);\n registerNavItemsRef.current = registerNavItems;\n\n const navItems = useMemo<NavItem[]>(() => {\n const items: NavItem[] = [\n {\n id: 'tours-list',\n label: tr('toursNav.allTours', 'All Tours'),\n path: joinPath(basePath, 'list'),\n icon: <List size={20} />,\n order: 10,\n group: 'tours',\n },\n {\n id: 'tours-new',\n label: tr('toursNav.createTour', 'Create Tour'),\n path: joinPath(basePath, 'new'),\n icon: <Plus size={20} />,\n order: 20,\n group: 'tours',\n },\n {\n id: 'tours-templates',\n label: tr('toursNav.templates', 'Templates'),\n path: joinPath(basePath, 'templates'),\n icon: <LayoutTemplate size={20} />,\n order: 30,\n group: 'tours',\n },\n {\n id: 'tours-analytics',\n label: tr('toursNav.analytics', 'Analytics'),\n path: joinPath(basePath, 'analytics'),\n icon: <BarChart3 size={20} />,\n order: 50,\n group: 'tours',\n },\n ];\n\n if (mediaEnabled) {\n items.push({\n id: 'tours-media',\n label: tr('toursNav.mediaLibrary', 'Media Library'),\n path: joinPath(basePath, 'media'),\n icon: <Image size={20} />,\n order: 40,\n group: 'tours',\n });\n }\n\n return items;\n }, [basePath, mediaEnabled, tr]);\n\n useEffect(() => {\n const unregister = registerNavItemsRef.current(navItems);\n return unregister;\n }, [navItems]);\n\n return null;\n}\n"],"mappings":";;;;;;;;AA8BA,SAAgB,EAAuB,EACrC,aACA,uBAC8B;CAC9B,IAAM,IAAK,GAAO,EACZ,IAAe,EAAe,EAAe,OAAO,GAAM,EAE1D,IAAsB,EAAO,EAAiB;AACpD,GAAoB,UAAU;CAE9B,IAAM,IAAW,QAAyB;EACxC,IAAM,IAAmB;GACvB;IACE,IAAI;IACJ,OAAO,EAAG,qBAAqB,YAAY;IAC3C,MAAM,EAAS,GAAU,OAAO;IAChC,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;IACxB,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAG,uBAAuB,cAAc;IAC/C,MAAM,EAAS,GAAU,MAAM;IAC/B,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;IACxB,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAG,sBAAsB,YAAY;IAC5C,MAAM,EAAS,GAAU,YAAY;IACrC,MAAM,kBAAC,GAAD,EAAgB,MAAM,IAAM,CAAA;IAClC,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAG,sBAAsB,YAAY;IAC5C,MAAM,EAAS,GAAU,YAAY;IACrC,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;IAC7B,OAAO;IACP,OAAO;IACR;GACF;AAaD,SAXI,KACF,EAAM,KAAK;GACT,IAAI;GACJ,OAAO,EAAG,yBAAyB,gBAAgB;GACnD,MAAM,EAAS,GAAU,QAAQ;GACjC,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;GACzB,OAAO;GACP,OAAO;GACR,CAAC,EAGG;IACN;EAAC;EAAU;EAAc;EAAG,CAAC;AAOhC,QALA,QACqB,EAAoB,QAAQ,EAAS,EAEvD,CAAC,EAAS,CAAC,EAEP"}
1
+ {"version":3,"file":"ToursAdminNavRegistrar.js","names":["ToursAdminNavRegistrar","tr","useTr","mediaEnabled","useFeatureFlag","TOURS_FEATURES","registerNavItemsRef","useRef","registerNavItems","navItems","useMemo","items","joinPath","basePath","List","Plus","LayoutTemplate","BarChart3","Image","useEffect"],"sources":["../../../src/tours/components/ToursAdminNavRegistrar.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef } from 'react';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { List, Plus, LayoutTemplate, Image, BarChart3 } from 'lucide-react';\n\nimport { TOURS_FEATURES } from '../../constants/featureFlags';\nimport type { NavItem } from '../types';\nimport { joinPath } from '../utils/joinPath';\nimport { useTr } from '../i18n';\n\ninterface ToursAdminNavRegistrarProps {\n basePath: string;\n registerNavItems: (items: NavItem | NavItem[]) => () => void;\n}\n\n/**\n * Registers the admin tours nav items with the host shell.\n *\n * Rendered inside `<FeatureFlagProvider>` so it can consult `useFeatureFlag`\n * to conditionally include flag-gated items. Re-registers whenever flag state\n * changes so the host shell's nav reflects the current evaluation in real time.\n *\n * Baseline items (always registered): All Tours, Create Tour, Templates,\n * Analytics. Media Library is gated on `tours.media` because the upload\n * pipeline is still incomplete — the nav item appears only when the flag is\n * enabled for the workspace or the user has opted in via Feature Previews.\n *\n * `registerNavItems` is captured in a ref so a host shell that hands over a\n * fresh closure on every render can't trigger an unregister → re-register\n * storm. Only flag-state / `basePath` changes cause re-registration.\n */\nexport function ToursAdminNavRegistrar({\n basePath,\n registerNavItems,\n}: ToursAdminNavRegistrarProps) {\n const tr = useTr();\n const mediaEnabled = useFeatureFlag(TOURS_FEATURES.MEDIA, false);\n\n const registerNavItemsRef = useRef(registerNavItems);\n // Keep the latest callback without re-registering when its identity changes. Runs before the\n // registration effect below (effects fire in declaration order).\n useEffect(() => {\n registerNavItemsRef.current = registerNavItems;\n });\n\n const navItems = useMemo<NavItem[]>(() => {\n const items: NavItem[] = [\n {\n id: 'tours-list',\n label: tr('toursNav.allTours', 'All Tours'),\n path: joinPath(basePath, 'list'),\n icon: <List size={20} />,\n order: 10,\n group: 'tours',\n },\n {\n id: 'tours-new',\n label: tr('toursNav.createTour', 'Create Tour'),\n path: joinPath(basePath, 'new'),\n icon: <Plus size={20} />,\n order: 20,\n group: 'tours',\n },\n {\n id: 'tours-templates',\n label: tr('toursNav.templates', 'Templates'),\n path: joinPath(basePath, 'templates'),\n icon: <LayoutTemplate size={20} />,\n order: 30,\n group: 'tours',\n },\n {\n id: 'tours-analytics',\n label: tr('toursNav.analytics', 'Analytics'),\n path: joinPath(basePath, 'analytics'),\n icon: <BarChart3 size={20} />,\n order: 50,\n group: 'tours',\n },\n ];\n\n if (mediaEnabled) {\n items.push({\n id: 'tours-media',\n label: tr('toursNav.mediaLibrary', 'Media Library'),\n path: joinPath(basePath, 'media'),\n icon: <Image size={20} />,\n order: 40,\n group: 'tours',\n });\n }\n\n return items;\n }, [basePath, mediaEnabled, tr]);\n\n useEffect(() => {\n const unregister = registerNavItemsRef.current(navItems);\n return unregister;\n }, [navItems]);\n\n return null;\n}\n"],"mappings":";;;;;;;;AA8BA,SAAgBA,EAAuB,EACrC,aACA,uBAC8B;CAC9B,IAAMC,IAAKC,EAAM,GACXC,IAAeC,EAAeC,EAAe,OAAO,EAAK,GAEzDC,IAAsBC,EAAOC,CAAgB;CAGnD,QAAgB;EACd,EAAoB,UAAUA;CAChC,CAAC;CAED,IAAMC,IAAWC,QAAyB;EACxC,IAAMC,IAAmB;GACvB;IACE,IAAI;IACJ,OAAOV,EAAG,qBAAqB,WAAW;IAC1C,MAAMW,EAASC,GAAU,MAAM;IAC/B,MAAM,kBAACC,GAAD,EAAM,MAAM,GAAK,CAAA;IACvB,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAOb,EAAG,uBAAuB,aAAa;IAC9C,MAAMW,EAASC,GAAU,KAAK;IAC9B,MAAM,kBAACE,GAAD,EAAM,MAAM,GAAK,CAAA;IACvB,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAOd,EAAG,sBAAsB,WAAW;IAC3C,MAAMW,EAASC,GAAU,WAAW;IACpC,MAAM,kBAACG,GAAD,EAAgB,MAAM,GAAK,CAAA;IACjC,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAOf,EAAG,sBAAsB,WAAW;IAC3C,MAAMW,EAASC,GAAU,WAAW;IACpC,MAAM,kBAACI,GAAD,EAAW,MAAM,GAAK,CAAA;IAC5B,OAAO;IACP,OAAO;GACT;EACF;EAaA,OAXId,KACFQ,EAAM,KAAK;GACT,IAAI;GACJ,OAAOV,EAAG,yBAAyB,eAAe;GAClD,MAAMW,EAASC,GAAU,OAAO;GAChC,MAAM,kBAACK,GAAD,EAAO,MAAM,GAAK,CAAA;GACxB,OAAO;GACP,OAAO;EACT,CAAC,GAGIP;CACT,GAAG;EAACE;EAAUV;EAAcF;CAAE,CAAC;CAO/B,OALAkB,QACqBb,EAAoB,QAAQG,CACxC,GACN,CAACA,CAAQ,CAAC,GAEN;AACT"}
@@ -0,0 +1,20 @@
1
+ import { TourMedia, TourMediaType, MediaPosition } from '../types';
2
+ /**
3
+ * Hook for media library functionality
4
+ * Manages media upload, browsing, and attachment
5
+ * All media is now global (no workspace scoping)
6
+ */
7
+ export declare function useMediaLibrary(): {
8
+ media: TourMedia[];
9
+ selectedMedia: TourMedia[];
10
+ uploadProgress: Record<string, number>;
11
+ loading: boolean;
12
+ error: Error | null;
13
+ fetchMedia: (_type?: TourMediaType, _search?: string) => Promise<void>;
14
+ uploadMedia: (file: File, metadata?: Partial<TourMedia>) => Promise<any>;
15
+ deleteMedia: (mediaId: string) => Promise<void>;
16
+ selectMedia: (media: TourMedia) => void;
17
+ unselectMedia: (mediaId: string) => void;
18
+ clearSelectedMedia: () => void;
19
+ attachToStep: (stepId: string, mediaId: string, position: MediaPosition, displayOrder: number) => Promise<any>;
20
+ };
@@ -1,7 +1,7 @@
1
1
  import { useToursStore as e } from "../store/toursStore.js";
2
2
  import { useAttachMediaToStepMutation as t, useCreateTourMediaMetadataMutation as n, useDeleteTourMediaMutation as r, useGetTourMediaQuery as i } from "../../generated/global-operations.js";
3
3
  import { useCallback as a, useMemo as o } from "react";
4
- //#region src/tours/hooks/useMediaLibrary.ts
4
+ // src/tours/hooks/useMediaLibrary.ts
5
5
  function s() {
6
6
  let { selectedMedia: s, uploadProgress: l, selectMedia: u, unselectMedia: d, clearSelectedMedia: f, updateUploadProgress: p, removeUploadProgress: m } = e(), { data: h, loading: g, error: _, refetch: v } = i(), [y] = n(), [b] = r(), [x] = t();
7
7
  return {
@@ -76,7 +76,6 @@ function s() {
76
76
  function c(e) {
77
77
  return e.startsWith("image/") ? "IMAGE" : e.startsWith("video/") ? "VIDEO" : e.startsWith("audio/") ? "AUDIO" : "DOCUMENT";
78
78
  }
79
- //#endregion
80
79
  export { s as useMediaLibrary };
81
80
 
82
81
  //# sourceMappingURL=useMediaLibrary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useMediaLibrary.js","names":[],"sources":["../../../src/tours/hooks/useMediaLibrary.ts"],"sourcesContent":["import { useCallback, useMemo } from 'react';\nimport { useToursStore } from '../store/toursStore';\nimport {\n useGetTourMediaQuery,\n useCreateTourMediaMetadataMutation,\n useDeleteTourMediaMutation,\n useAttachMediaToStepMutation,\n} from '../../generated/global-operations';\nimport type { TourMedia as ApiTourMedia } from '../../generated/global-types';\nimport type { TourMedia, TourMediaType, MediaPosition } from '../types';\n\n/**\n * Hook for media library functionality\n * Manages media upload, browsing, and attachment\n * All media is now global (no workspace scoping)\n */\nexport function useMediaLibrary() {\n const {\n selectedMedia,\n uploadProgress,\n selectMedia,\n unselectMedia,\n clearSelectedMedia,\n updateUploadProgress,\n removeUploadProgress,\n } = useToursStore();\n\n // Fetch media from API\n const { data, loading, error: queryError, refetch } = useGetTourMediaQuery();\n\n // Upload media mutation\n const [createTourMediaMetadataMutation] = useCreateTourMediaMetadataMutation();\n\n // Delete media mutation\n const [deleteTourMediaMutation] = useDeleteTourMediaMutation();\n\n // Attach media to step mutation\n const [attachMediaToStepMutation] = useAttachMediaToStepMutation();\n\n // Transform API response to match our TourMedia type\n const media: TourMedia[] = useMemo(() => {\n if (!data?.tourMedia) return [];\n return data.tourMedia.map((m: ApiTourMedia) => ({\n id: m.id,\n name: m.name,\n type: m.type as TourMediaType,\n cdnUrl: m.cdnUrl || m.storageUrl,\n mimeType: m.mimeType,\n size: m.size,\n duration: m.duration ?? undefined,\n width: m.width ?? undefined,\n height: m.height ?? undefined,\n altText: m.altText ?? undefined,\n caption: m.caption ?? undefined,\n transcript: m.transcript ?? undefined,\n metadata: m.metadata ?? undefined,\n createdBy: m.uploadedBy || '',\n createdAt: m.createdAt,\n updatedAt: m.updatedAt,\n }));\n }, [data]);\n\n const error = queryError ? new Error(queryError.message) : null;\n\n /**\n * Fetch/refetch media library\n */\n const fetchMedia = useCallback(\n async (_type?: TourMediaType, _search?: string) => {\n // Note: type and search filtering will be done client-side\n // since the API doesn't support these filters yet\n await refetch();\n },\n [refetch]\n );\n\n /**\n * Upload media file\n */\n const uploadMedia = useCallback(\n async (file: File, metadata?: Partial<TourMedia>) => {\n const fileId = `upload_${Date.now()}`;\n updateUploadProgress(fileId, 0);\n\n try {\n updateUploadProgress(fileId, 50);\n\n const result = await createTourMediaMetadataMutation({\n variables: {\n input: {\n name: metadata?.name || file.name,\n type: getMediaType(file.type),\n storageUrl: `/uploads/tours/${Date.now()}-${file.name}`,\n storagePath: `tours/${Date.now()}-${file.name}`,\n mimeType: file.type,\n size: file.size,\n altText: metadata?.altText,\n caption: metadata?.caption,\n },\n },\n });\n\n updateUploadProgress(fileId, 100);\n removeUploadProgress(fileId);\n await refetch();\n\n return result.data?.createTourMediaMetadata ?? null;\n } catch (err) {\n removeUploadProgress(fileId);\n throw err;\n }\n },\n [createTourMediaMetadataMutation, updateUploadProgress, removeUploadProgress, refetch]\n );\n\n /**\n * Delete media\n */\n const deleteMedia = useCallback(\n async (mediaId: string) => {\n await deleteTourMediaMutation({\n variables: { id: mediaId },\n });\n unselectMedia(mediaId);\n await refetch();\n },\n [deleteTourMediaMutation, unselectMedia, refetch]\n );\n\n /**\n * Attach media to step\n */\n const attachToStep = useCallback(\n async (stepId: string, mediaId: string, position: MediaPosition, displayOrder: number) => {\n const result = await attachMediaToStepMutation({\n variables: {\n input: {\n stepId,\n mediaId,\n position:\n position === 'body'\n ? 'INLINE'\n : (position.toUpperCase() as 'HEADER' | 'INLINE' | 'FOOTER'),\n metadata: displayOrder > 0 ? { displayOrder } : undefined,\n },\n },\n });\n return result.data?.attachMediaToStep ?? null;\n },\n [attachMediaToStepMutation]\n );\n\n return {\n media,\n selectedMedia,\n uploadProgress,\n loading,\n error,\n fetchMedia,\n uploadMedia,\n deleteMedia,\n selectMedia,\n unselectMedia,\n clearSelectedMedia,\n attachToStep,\n };\n}\n\n/**\n * Helper to determine media type from MIME type\n */\nfunction getMediaType(mimeType: string): 'IMAGE' | 'VIDEO' | 'AUDIO' | 'DOCUMENT' {\n if (mimeType.startsWith('image/')) return 'IMAGE';\n if (mimeType.startsWith('video/')) return 'VIDEO';\n if (mimeType.startsWith('audio/')) return 'AUDIO';\n return 'DOCUMENT';\n}\n"],"mappings":";;;;AAgBA,SAAgB,IAAkB;CAChC,IAAM,EACJ,kBACA,mBACA,gBACA,kBACA,uBACA,yBACA,4BACE,GAAe,EAGb,EAAE,SAAM,YAAS,OAAO,GAAY,eAAY,GAAsB,EAGtE,CAAC,KAAmC,GAAoC,EAGxE,CAAC,KAA2B,GAA4B,EAGxD,CAAC,KAA6B,GAA8B;AAmHlE,QAAO;EACL,OAjHyB,QACpB,GAAM,YACJ,EAAK,UAAU,KAAK,OAAqB;GAC9C,IAAI,EAAE;GACN,MAAM,EAAE;GACR,MAAM,EAAE;GACR,QAAQ,EAAE,UAAU,EAAE;GACtB,UAAU,EAAE;GACZ,MAAM,EAAE;GACR,UAAU,EAAE,YAAY,KAAA;GACxB,OAAO,EAAE,SAAS,KAAA;GAClB,QAAQ,EAAE,UAAU,KAAA;GACpB,SAAS,EAAE,WAAW,KAAA;GACtB,SAAS,EAAE,WAAW,KAAA;GACtB,YAAY,EAAE,cAAc,KAAA;GAC5B,UAAU,EAAE,YAAY,KAAA;GACxB,WAAW,EAAE,cAAc;GAC3B,WAAW,EAAE;GACb,WAAW,EAAE;GACd,EAAE,GAlB0B,EAAE,EAmB9B,CAAC,EAAK,CAAC;EA8FR;EACA;EACA;EACA,OA/FY,IAAiB,MAAM,EAAW,QAAQ,GAAG;EAgGzD,YA3FiB,EACjB,OAAO,GAAuB,MAAqB;AAGjD,SAAM,GAAS;KAEjB,CAAC,EAAQ,CACV;EAqFC,aAhFkB,EAClB,OAAO,GAAY,MAAkC;GACnD,IAAM,IAAS,UAAU,KAAK,KAAK;AACnC,KAAqB,GAAQ,EAAE;AAE/B,OAAI;AACF,MAAqB,GAAQ,GAAG;IAEhC,IAAM,IAAS,MAAM,EAAgC,EACnD,WAAW,EACT,OAAO;KACL,MAAM,GAAU,QAAQ,EAAK;KAC7B,MAAM,EAAa,EAAK,KAAK;KAC7B,YAAY,kBAAkB,KAAK,KAAK,CAAC,GAAG,EAAK;KACjD,aAAa,SAAS,KAAK,KAAK,CAAC,GAAG,EAAK;KACzC,UAAU,EAAK;KACf,MAAM,EAAK;KACX,SAAS,GAAU;KACnB,SAAS,GAAU;KACpB,EACF,EACF,CAAC;AAMF,WAJA,EAAqB,GAAQ,IAAI,EACjC,EAAqB,EAAO,EAC5B,MAAM,GAAS,EAER,EAAO,MAAM,2BAA2B;YACxC,GAAK;AAEZ,UADA,EAAqB,EAAO,EACtB;;KAGV;GAAC;GAAiC;GAAsB;GAAsB;GAAQ,CACvF;EA+CC,aA1CkB,EAClB,OAAO,MAAoB;AAKzB,GAJA,MAAM,EAAwB,EAC5B,WAAW,EAAE,IAAI,GAAS,EAC3B,CAAC,EACF,EAAc,EAAQ,EACtB,MAAM,GAAS;KAEjB;GAAC;GAAyB;GAAe;GAAQ,CAClD;EAkCC;EACA;EACA;EACA,cAhCmB,EACnB,OAAO,GAAgB,GAAiB,GAAyB,OAChD,MAAM,EAA0B,EAC7C,WAAW,EACT,OAAO;GACL;GACA;GACA,UACE,MAAa,SACT,WACC,EAAS,aAAa;GAC7B,UAAU,IAAe,IAAI,EAAE,iBAAc,GAAG,KAAA;GACjD,EACF,EACF,CAAC,EACY,MAAM,qBAAqB,MAE3C,CAAC,EAA0B,CAC5B;EAeA;;AAMH,SAAS,EAAa,GAA4D;AAIhF,QAHI,EAAS,WAAW,SAAS,GAAS,UACtC,EAAS,WAAW,SAAS,GAAS,UACtC,EAAS,WAAW,SAAS,GAAS,UACnC"}
1
+ {"version":3,"file":"useMediaLibrary.js","names":["useMediaLibrary","useToursStore","queryError","useGetTourMediaQuery","createTourMediaMetadataMutation","useCreateTourMediaMetadataMutation","deleteTourMediaMutation","useDeleteTourMediaMutation","attachMediaToStepMutation","useAttachMediaToStepMutation","useMemo","data","m","useCallback","refetch","fileId","result","metadata","file","getMediaType","updateUploadProgress","removeUploadProgress","err","mediaId","unselectMedia","position","displayOrder","mimeType"],"sources":["../../../src/tours/hooks/useMediaLibrary.ts"],"sourcesContent":["import { useCallback, useMemo } from 'react';\nimport { useToursStore } from '../store/toursStore';\nimport {\n useGetTourMediaQuery,\n useCreateTourMediaMetadataMutation,\n useDeleteTourMediaMutation,\n useAttachMediaToStepMutation,\n} from '../../generated/global-operations';\nimport type { TourMedia as ApiTourMedia } from '../../generated/global-types';\nimport type { TourMedia, TourMediaType, MediaPosition } from '../types';\n\n/**\n * Hook for media library functionality\n * Manages media upload, browsing, and attachment\n * All media is now global (no workspace scoping)\n */\nexport function useMediaLibrary() {\n const {\n selectedMedia,\n uploadProgress,\n selectMedia,\n unselectMedia,\n clearSelectedMedia,\n updateUploadProgress,\n removeUploadProgress,\n } = useToursStore();\n\n // Fetch media from API\n const { data, loading, error: queryError, refetch } = useGetTourMediaQuery();\n\n // Upload media mutation\n const [createTourMediaMetadataMutation] = useCreateTourMediaMetadataMutation();\n\n // Delete media mutation\n const [deleteTourMediaMutation] = useDeleteTourMediaMutation();\n\n // Attach media to step mutation\n const [attachMediaToStepMutation] = useAttachMediaToStepMutation();\n\n // Transform API response to match our TourMedia type\n const media: TourMedia[] = useMemo(() => {\n if (!data?.tourMedia) return [];\n return data.tourMedia.map((m: ApiTourMedia) => ({\n id: m.id,\n name: m.name,\n type: m.type as TourMediaType,\n cdnUrl: m.cdnUrl || m.storageUrl,\n mimeType: m.mimeType,\n size: m.size,\n duration: m.duration ?? undefined,\n width: m.width ?? undefined,\n height: m.height ?? undefined,\n altText: m.altText ?? undefined,\n caption: m.caption ?? undefined,\n transcript: m.transcript ?? undefined,\n metadata: m.metadata ?? undefined,\n createdBy: m.uploadedBy || '',\n createdAt: m.createdAt,\n updatedAt: m.updatedAt,\n }));\n }, [data]);\n\n const error = queryError ? new Error(queryError.message) : null;\n\n /**\n * Fetch/refetch media library\n */\n const fetchMedia = useCallback(\n async (_type?: TourMediaType, _search?: string) => {\n // Note: type and search filtering will be done client-side\n // since the API doesn't support these filters yet\n await refetch();\n },\n [refetch]\n );\n\n /**\n * Upload media file\n */\n const uploadMedia = useCallback(\n async (file: File, metadata?: Partial<TourMedia>) => {\n const fileId = `upload_${Date.now()}`;\n updateUploadProgress(fileId, 0);\n\n try {\n updateUploadProgress(fileId, 50);\n\n const result = await createTourMediaMetadataMutation({\n variables: {\n input: {\n name: metadata?.name || file.name,\n type: getMediaType(file.type),\n storageUrl: `/uploads/tours/${Date.now()}-${file.name}`,\n storagePath: `tours/${Date.now()}-${file.name}`,\n mimeType: file.type,\n size: file.size,\n altText: metadata?.altText,\n caption: metadata?.caption,\n },\n },\n });\n\n updateUploadProgress(fileId, 100);\n removeUploadProgress(fileId);\n await refetch();\n\n return result.data?.createTourMediaMetadata ?? null;\n } catch (err) {\n removeUploadProgress(fileId);\n throw err;\n }\n },\n [createTourMediaMetadataMutation, updateUploadProgress, removeUploadProgress, refetch]\n );\n\n /**\n * Delete media\n */\n const deleteMedia = useCallback(\n async (mediaId: string) => {\n await deleteTourMediaMutation({\n variables: { id: mediaId },\n });\n unselectMedia(mediaId);\n await refetch();\n },\n [deleteTourMediaMutation, unselectMedia, refetch]\n );\n\n /**\n * Attach media to step\n */\n const attachToStep = useCallback(\n async (stepId: string, mediaId: string, position: MediaPosition, displayOrder: number) => {\n const result = await attachMediaToStepMutation({\n variables: {\n input: {\n stepId,\n mediaId,\n position:\n position === 'body'\n ? 'INLINE'\n : (position.toUpperCase() as 'HEADER' | 'INLINE' | 'FOOTER'),\n metadata: displayOrder > 0 ? { displayOrder } : undefined,\n },\n },\n });\n return result.data?.attachMediaToStep ?? null;\n },\n [attachMediaToStepMutation]\n );\n\n return {\n media,\n selectedMedia,\n uploadProgress,\n loading,\n error,\n fetchMedia,\n uploadMedia,\n deleteMedia,\n selectMedia,\n unselectMedia,\n clearSelectedMedia,\n attachToStep,\n };\n}\n\n/**\n * Helper to determine media type from MIME type\n */\nfunction getMediaType(mimeType: string): 'IMAGE' | 'VIDEO' | 'AUDIO' | 'DOCUMENT' {\n if (mimeType.startsWith('image/')) return 'IMAGE';\n if (mimeType.startsWith('video/')) return 'VIDEO';\n if (mimeType.startsWith('audio/')) return 'AUDIO';\n return 'DOCUMENT';\n}\n"],"mappings":";;;;AAgBA,SAAgBA,IAAkB;CAChC,IAAM,EACJ,kBACA,mBACA,gBACA,kBACA,uBACA,yBACA,4BACEC,EAAc,GAGZ,EAAE,SAAM,YAAS,OAAOC,GAAY,eAAYC,EAAqB,GAGrE,CAACC,KAAmCC,EAAmC,GAGvE,CAACC,KAA2BC,EAA2B,GAGvD,CAACC,KAA6BC,EAA6B;CAmHjE,OAAO;EACL,OAjHyBC,QACpBC,GAAM,YACJA,EAAK,UAAU,KAAK,OAAqB;GAC9C,IAAIC,EAAE;GACN,MAAMA,EAAE;GACR,MAAMA,EAAE;GACR,QAAQA,EAAE,UAAUA,EAAE;GACtB,UAAUA,EAAE;GACZ,MAAMA,EAAE;GACR,UAAUA,EAAE,YAAY,KAAA;GACxB,OAAOA,EAAE,SAAS,KAAA;GAClB,QAAQA,EAAE,UAAU,KAAA;GACpB,SAASA,EAAE,WAAW,KAAA;GACtB,SAASA,EAAE,WAAW,KAAA;GACtB,YAAYA,EAAE,cAAc,KAAA;GAC5B,UAAUA,EAAE,YAAY,KAAA;GACxB,WAAWA,EAAE,cAAc;GAC3B,WAAWA,EAAE;GACb,WAAWA,EAAE;EACf,EAAE,IAlB2B,CAAC,GAmB7B,CAACD,CAAI,CA6FN;EACA;EACA;EACA;EACA,OA/FYT,IAAiB,MAAMA,EAAW,OAAO,IAAI;EAgGzD,YA3FiBW,EACjB,OAAO,GAAuB,MAAqB;GAGjD,MAAMC,EAAQ;EAChB,GACA,CAACA,CAAO,CAqFR;EACA,aAhFkBD,EAClB,OAAO,GAAY,MAAkC;GACnD,IAAME,IAAS,UAAU,KAAK,IAAI;GAClC,EAAqBA,GAAQ,CAAC;GAE9B,IAAI;IACF,EAAqBA,GAAQ,EAAE;IAE/B,IAAMC,IAAS,MAAMZ,EAAgC,EACnD,WAAW,EACT,OAAO;KACL,MAAMa,GAAU,QAAQC,EAAK;KAC7B,MAAMC,EAAaD,EAAK,IAAI;KAC5B,YAAY,kBAAkB,KAAK,IAAI,EAAE,GAAGA,EAAK;KACjD,aAAa,SAAS,KAAK,IAAI,EAAE,GAAGA,EAAK;KACzC,UAAUA,EAAK;KACf,MAAMA,EAAK;KACX,SAASD,GAAU;KACnB,SAASA,GAAU;IACrB,EACF,EACF,CAAC;IAMD,OAJAG,EAAqBL,GAAQ,GAAG,GAChCM,EAAqBN,CAAM,GAC3B,MAAMD,EAAQ,GAEPE,EAAO,MAAM,2BAA2B;GACjD,SAASM,GAAK;IAEZ,MADAD,EAAqBN,CAAM,GACrBO;GACR;EACF,GACA;GAAClB;GAAiCgB;GAAsBC;GAAsBP;EAAO,CA+CrF;EACA,aA1CkBD,EAClB,OAAO,MAAoB;GAKzB,AAJA,MAAMP,EAAwB,EAC5B,WAAW,EAAE,IAAIiB,EAAQ,EAC3B,CAAC,GACDC,EAAcD,CAAO,GACrB,MAAMT,EAAQ;EAChB,GACA;GAACR;GAAyBkB;GAAeV;EAAO,CAkChD;EACA;EACA;EACA;EACA,cAhCmBD,EACnB,OAAO,GAAgB,GAAiB,GAAyB,OAcxD,MAbcL,EAA0B,EAC7C,WAAW,EACT,OAAO;GACL;GACA;GACA,UACEiB,MAAa,SACT,WACCA,EAAS,YAAY;GAC5B,UAAUC,IAAe,IAAI,EAAE,gBAAa,IAAI,KAAA;EAClD,EACF,EACF,CAAC,EAAA,CACa,MAAM,qBAAqB,MAE3C,CAAClB,CAAyB,CAe1B;CACF;AACF;AAKA,SAASW,EAAa,GAA4D;CAIhF,OAHIQ,EAAS,WAAW,QAAQ,IAAU,UACtCA,EAAS,WAAW,QAAQ,IAAU,UACtCA,EAAS,WAAW,QAAQ,IAAU,UACnC;AACT"}
@@ -0,0 +1,18 @@
1
+ import { TourStep, StepPlacement } from '../types';
2
+ /**
3
+ * Hook for step sequencer functionality
4
+ * Manages step creation, reordering, and editing locally.
5
+ * Steps are persisted to backend via useTourBuilder.syncSteps()
6
+ */
7
+ export declare function useStepSequencer(): {
8
+ steps: TourStep[];
9
+ selectedStep: TourStep | null;
10
+ selectStep: (step: TourStep | null) => void;
11
+ createStep: (stepData?: Partial<TourStep>) => void;
12
+ deleteStep: (stepId: string) => void;
13
+ updateStepField: (stepId: string, field: keyof TourStep, value: unknown) => void;
14
+ setStepSelector: (stepId: string, selector: string, deviceType?: "mobile" | "tablet" | "desktop") => void;
15
+ setStepPlacement: (stepId: string, placement: StepPlacement) => void;
16
+ moveStep: (fromIndex: number, toIndex: number) => void;
17
+ duplicateStep: (stepId: string) => void;
18
+ };
@@ -1,22 +1,23 @@
1
- import { useTr as e } from "../i18n.js";
2
- import { useToursStore as t } from "../store/toursStore.js";
1
+ import { useToursStore as e } from "../store/toursStore.js";
2
+ import { useTr as t } from "../i18n.js";
3
3
  import { useCallback as n } from "react";
4
- //#region src/tours/hooks/useStepSequencer.ts
4
+ // src/tours/hooks/useStepSequencer.ts
5
5
  function r() {
6
- let r = e(), { stepSequence: i, selectedStep: a, setSelectedStep: o, addStep: s, removeStep: c, updateStep: l, reorderSteps: u } = t();
6
+ let r = t(), { stepSequence: i, selectedStep: a, setSelectedStep: o, addStep: s, removeStep: c, updateStep: l, reorderSteps: u } = e();
7
7
  return {
8
8
  steps: i,
9
9
  selectedStep: a,
10
10
  selectStep: o,
11
11
  createStep: n((e) => {
12
- s({
12
+ let t = {
13
13
  title: e?.title || r("stepEditor.newStep", "New Step"),
14
14
  content: e?.content || "",
15
15
  targetSelector: e?.targetSelector || "",
16
16
  placement: e?.placement || "AUTO",
17
17
  stepOrder: i.length + 1,
18
18
  ...e
19
- });
19
+ };
20
+ s(t);
20
21
  }, [
21
22
  s,
22
23
  i.length,
@@ -43,12 +44,15 @@ function r() {
43
44
  }, [u]),
44
45
  duplicateStep: n((e) => {
45
46
  let t = i.find((t) => t.id === e);
46
- t && s({
47
- ...t,
48
- id: void 0,
49
- title: `${t.title} ${r("stepEditor.copySuffix", "(Copy)")}`,
50
- stepOrder: i.length
51
- });
47
+ if (t) {
48
+ let e = {
49
+ ...t,
50
+ id: void 0,
51
+ title: `${t.title} ${r("stepEditor.copySuffix", "(Copy)")}`,
52
+ stepOrder: i.length
53
+ };
54
+ s(e);
55
+ }
52
56
  }, [
53
57
  i,
54
58
  s,
@@ -56,7 +60,6 @@ function r() {
56
60
  ])
57
61
  };
58
62
  }
59
- //#endregion
60
63
  export { r as useStepSequencer };
61
64
 
62
65
  //# sourceMappingURL=useStepSequencer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useStepSequencer.js","names":[],"sources":["../../../src/tours/hooks/useStepSequencer.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useToursStore } from '../store/toursStore';\nimport { useTr } from '../i18n';\nimport type { TourStep, StepPlacement } from '../types';\n\n/**\n * Hook for step sequencer functionality\n * Manages step creation, reordering, and editing locally.\n * Steps are persisted to backend via useTourBuilder.syncSteps()\n */\nexport function useStepSequencer() {\n const tr = useTr();\n const {\n stepSequence,\n selectedStep,\n setSelectedStep,\n addStep,\n removeStep,\n updateStep,\n reorderSteps,\n } = useToursStore();\n\n /**\n * Create new step\n */\n const createStep = useCallback(\n (stepData?: Partial<TourStep>) => {\n const newStep: Partial<TourStep> = {\n title: stepData?.title || tr('stepEditor.newStep', 'New Step'),\n content: stepData?.content || '',\n targetSelector: stepData?.targetSelector || '',\n placement: stepData?.placement || 'AUTO',\n stepOrder: stepSequence.length + 1,\n ...stepData,\n };\n addStep(newStep);\n },\n [addStep, stepSequence.length, tr]\n );\n\n /**\n * Delete step\n */\n const deleteStep = useCallback(\n (stepId: string) => {\n removeStep(stepId);\n if (selectedStep?.id === stepId) {\n setSelectedStep(null);\n }\n },\n [removeStep, selectedStep, setSelectedStep]\n );\n\n /**\n * Update step field\n */\n const updateStepField = useCallback(\n (stepId: string, field: keyof TourStep, value: unknown) => {\n updateStep(stepId, { [field]: value });\n },\n [updateStep]\n );\n\n /**\n * Set step target selector\n */\n const setStepSelector = useCallback(\n (stepId: string, selector: string, deviceType?: 'mobile' | 'tablet' | 'desktop') => {\n if (deviceType === 'mobile') {\n updateStep(stepId, { targetSelectorMobile: selector });\n } else if (deviceType === 'tablet') {\n updateStep(stepId, { targetSelectorTablet: selector });\n } else if (deviceType === 'desktop') {\n updateStep(stepId, { targetSelectorDesktop: selector });\n } else {\n updateStep(stepId, { targetSelector: selector });\n }\n },\n [updateStep]\n );\n\n /**\n * Set step placement\n */\n const setStepPlacement = useCallback(\n (stepId: string, placement: StepPlacement) => {\n updateStep(stepId, { placement });\n },\n [updateStep]\n );\n\n /**\n * Move step\n */\n const moveStep = useCallback(\n (fromIndex: number, toIndex: number) => {\n reorderSteps(fromIndex, toIndex);\n },\n [reorderSteps]\n );\n\n /**\n * Duplicate step\n */\n const duplicateStep = useCallback(\n (stepId: string) => {\n const step = stepSequence.find((s) => s.id === stepId);\n if (step) {\n const duplicatedStep: Partial<TourStep> = {\n ...step,\n id: undefined,\n title: `${step.title} ${tr('stepEditor.copySuffix', '(Copy)')}`,\n stepOrder: stepSequence.length,\n };\n addStep(duplicatedStep);\n }\n },\n [stepSequence, addStep, tr]\n );\n\n return {\n steps: stepSequence,\n selectedStep,\n selectStep: setSelectedStep,\n createStep,\n deleteStep,\n updateStepField,\n setStepSelector,\n setStepPlacement,\n moveStep,\n duplicateStep,\n };\n}\n"],"mappings":";;;;AAUA,SAAgB,IAAmB;CACjC,IAAM,IAAK,GAAO,EACZ,EACJ,iBACA,iBACA,oBACA,YACA,eACA,eACA,oBACE,GAAe;AAoGnB,QAAO;EACL,OAAO;EACP;EACA,YAAY;EACZ,YAnGiB,GAChB,MAAiC;AAShC,KARmC;IACjC,OAAO,GAAU,SAAS,EAAG,sBAAsB,WAAW;IAC9D,SAAS,GAAU,WAAW;IAC9B,gBAAgB,GAAU,kBAAkB;IAC5C,WAAW,GAAU,aAAa;IAClC,WAAW,EAAa,SAAS;IACjC,GAAG;IACJ,CACe;KAElB;GAAC;GAAS,EAAa;GAAQ;GAAG,CACnC;EAuFC,YAlFiB,GAChB,MAAmB;AAElB,GADA,EAAW,EAAO,EACd,GAAc,OAAO,KACvB,EAAgB,KAAK;KAGzB;GAAC;GAAY;GAAc;GAAgB,CAC5C;EA2EC,iBAtEsB,GACrB,GAAgB,GAAuB,MAAmB;AACzD,KAAW,GAAQ,GAAG,IAAQ,GAAO,CAAC;KAExC,CAAC,EAAW,CACb;EAkEC,iBA7DsB,GACrB,GAAgB,GAAkB,MAAiD;AAClF,GAAI,MAAe,WACjB,EAAW,GAAQ,EAAE,sBAAsB,GAAU,CAAC,GAC7C,MAAe,WACxB,EAAW,GAAQ,EAAE,sBAAsB,GAAU,CAAC,GAC7C,MAAe,YACxB,EAAW,GAAQ,EAAE,uBAAuB,GAAU,CAAC,GAEvD,EAAW,GAAQ,EAAE,gBAAgB,GAAU,CAAC;KAGpD,CAAC,EAAW,CACb;EAiDC,kBA5CuB,GACtB,GAAgB,MAA6B;AAC5C,KAAW,GAAQ,EAAE,cAAW,CAAC;KAEnC,CAAC,EAAW,CACb;EAwCC,UAnCe,GACd,GAAmB,MAAoB;AACtC,KAAa,GAAW,EAAQ;KAElC,CAAC,EAAa,CACf;EA+BC,eA1BoB,GACnB,MAAmB;GAClB,IAAM,IAAO,EAAa,MAAM,MAAM,EAAE,OAAO,EAAO;AACtD,GAAI,KAOF,EAN0C;IACxC,GAAG;IACH,IAAI,KAAA;IACJ,OAAO,GAAG,EAAK,MAAM,GAAG,EAAG,yBAAyB,SAAS;IAC7D,WAAW,EAAa;IACzB,CACsB;KAG3B;GAAC;GAAc;GAAS;GAAG,CAC5B;EAaA"}
1
+ {"version":3,"file":"useStepSequencer.js","names":["useStepSequencer","tr","useTr","useToursStore","stepSequence","setSelectedStep","useCallback","newStep","stepData","addStep","removeStep","stepId","selectedStep","field","value","updateStep","deviceType","selector","fromIndex","toIndex","reorderSteps","step","s","duplicatedStep"],"sources":["../../../src/tours/hooks/useStepSequencer.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useToursStore } from '../store/toursStore';\nimport { useTr } from '../i18n';\nimport type { TourStep, StepPlacement } from '../types';\n\n/**\n * Hook for step sequencer functionality\n * Manages step creation, reordering, and editing locally.\n * Steps are persisted to backend via useTourBuilder.syncSteps()\n */\nexport function useStepSequencer() {\n const tr = useTr();\n const {\n stepSequence,\n selectedStep,\n setSelectedStep,\n addStep,\n removeStep,\n updateStep,\n reorderSteps,\n } = useToursStore();\n\n /**\n * Create new step\n */\n const createStep = useCallback(\n (stepData?: Partial<TourStep>) => {\n const newStep: Partial<TourStep> = {\n title: stepData?.title || tr('stepEditor.newStep', 'New Step'),\n content: stepData?.content || '',\n targetSelector: stepData?.targetSelector || '',\n placement: stepData?.placement || 'AUTO',\n stepOrder: stepSequence.length + 1,\n ...stepData,\n };\n addStep(newStep);\n },\n [addStep, stepSequence.length, tr]\n );\n\n /**\n * Delete step\n */\n const deleteStep = useCallback(\n (stepId: string) => {\n removeStep(stepId);\n if (selectedStep?.id === stepId) {\n setSelectedStep(null);\n }\n },\n [removeStep, selectedStep, setSelectedStep]\n );\n\n /**\n * Update step field\n */\n const updateStepField = useCallback(\n (stepId: string, field: keyof TourStep, value: unknown) => {\n updateStep(stepId, { [field]: value });\n },\n [updateStep]\n );\n\n /**\n * Set step target selector\n */\n const setStepSelector = useCallback(\n (stepId: string, selector: string, deviceType?: 'mobile' | 'tablet' | 'desktop') => {\n if (deviceType === 'mobile') {\n updateStep(stepId, { targetSelectorMobile: selector });\n } else if (deviceType === 'tablet') {\n updateStep(stepId, { targetSelectorTablet: selector });\n } else if (deviceType === 'desktop') {\n updateStep(stepId, { targetSelectorDesktop: selector });\n } else {\n updateStep(stepId, { targetSelector: selector });\n }\n },\n [updateStep]\n );\n\n /**\n * Set step placement\n */\n const setStepPlacement = useCallback(\n (stepId: string, placement: StepPlacement) => {\n updateStep(stepId, { placement });\n },\n [updateStep]\n );\n\n /**\n * Move step\n */\n const moveStep = useCallback(\n (fromIndex: number, toIndex: number) => {\n reorderSteps(fromIndex, toIndex);\n },\n [reorderSteps]\n );\n\n /**\n * Duplicate step\n */\n const duplicateStep = useCallback(\n (stepId: string) => {\n const step = stepSequence.find((s) => s.id === stepId);\n if (step) {\n const duplicatedStep: Partial<TourStep> = {\n ...step,\n id: undefined,\n title: `${step.title} ${tr('stepEditor.copySuffix', '(Copy)')}`,\n stepOrder: stepSequence.length,\n };\n addStep(duplicatedStep);\n }\n },\n [stepSequence, addStep, tr]\n );\n\n return {\n steps: stepSequence,\n selectedStep,\n selectStep: setSelectedStep,\n createStep,\n deleteStep,\n updateStepField,\n setStepSelector,\n setStepPlacement,\n moveStep,\n duplicateStep,\n };\n}\n"],"mappings":";;;;AAUA,SAAgBA,IAAmB;CACjC,IAAMC,IAAKC,EAAM,GACX,EACJ,iBACA,iBACA,oBACA,YACA,eACA,eACA,oBACEC,EAAc;CAoGlB,OAAO;EACL,OAAOC;EACP;EACA,YAAYC;EACZ,YAnGiBC,GAChB,MAAiC;GAChC,IAAMC,IAA6B;IACjC,OAAOC,GAAU,SAASP,EAAG,sBAAsB,UAAU;IAC7D,SAASO,GAAU,WAAW;IAC9B,gBAAgBA,GAAU,kBAAkB;IAC5C,WAAWA,GAAU,aAAa;IAClC,WAAWJ,EAAa,SAAS;IACjC,GAAGI;GACL;GACA,EAAQD,CAAO;EACjB,GACA;GAACE;GAASL,EAAa;GAAQH;EAAE,CAuFjC;EACA,YAlFiBK,GAChB,MAAmB;GAElB,AADAI,EAAWC,CAAM,GACbC,GAAc,OAAOD,KACvBN,EAAgB,IAAI;EAExB,GACA;GAACK;GAAYE;GAAcP;EAAe,CA2E1C;EACA,iBAtEsBC,GACrB,GAAgB,GAAuB,MAAmB;GACzD,EAAWK,GAAQ,GAAGE,IAAQC,EAAM,CAAC;EACvC,GACA,CAACC,CAAU,CAkEX;EACA,iBA7DsBT,GACrB,GAAgB,GAAkB,MAAiD;GAClF,AAAIU,MAAe,WACjBD,EAAWJ,GAAQ,EAAE,sBAAsBM,EAAS,CAAC,IAC5CD,MAAe,WACxBD,EAAWJ,GAAQ,EAAE,sBAAsBM,EAAS,CAAC,IAC5CD,MAAe,YACxBD,EAAWJ,GAAQ,EAAE,uBAAuBM,EAAS,CAAC,IAEtDF,EAAWJ,GAAQ,EAAE,gBAAgBM,EAAS,CAAC;EAEnD,GACA,CAACF,CAAU,CAiDX;EACA,kBA5CuBT,GACtB,GAAgB,MAA6B;GAC5C,EAAWK,GAAQ,EAAE,aAAU,CAAC;EAClC,GACA,CAACI,CAAU,CAwCX;EACA,UAnCeT,GACd,GAAmB,MAAoB;GACtC,EAAaY,GAAWC,CAAO;EACjC,GACA,CAACC,CAAY,CA+Bb;EACA,eA1BoBd,GACnB,MAAmB;GAClB,IAAMe,IAAOjB,EAAa,MAAM,MAAMkB,EAAE,OAAOX,CAAM;GACrD,IAAIU,GAAM;IACR,IAAME,IAAoC;KACxC,GAAGF;KACH,IAAI,KAAA;KACJ,OAAO,GAAGA,EAAK,MAAM,GAAGpB,EAAG,yBAAyB,QAAQ;KAC5D,WAAWG,EAAa;IAC1B;IACA,EAAQmB,CAAc;GACxB;EACF,GACA;GAACnB;GAAcK;GAASR;EAAE,CAa1B;CACF;AACF"}
@@ -0,0 +1,30 @@
1
+ import { TourAnalytics } from '../types';
2
+ /**
3
+ * Hook for tour analytics functionality
4
+ * Fetches and processes tour performance data
5
+ */
6
+ export declare function useTourAnalytics(tourId?: string): {
7
+ analytics: TourAnalytics | null;
8
+ loading: boolean;
9
+ error: import('@apollo/client').ApolloError | undefined;
10
+ fetchAnalytics: (_dateRange?: {
11
+ start: string;
12
+ end: string;
13
+ }) => Promise<void>;
14
+ getCompletionRate: () => number;
15
+ getSkipRate: () => number;
16
+ getBestDevice: () => {
17
+ deviceType: import('../types').DeviceType;
18
+ count: number;
19
+ completionRate: number;
20
+ } | null;
21
+ getHighestDropOffStep: () => {
22
+ stepId: string;
23
+ stepOrder: number;
24
+ views: number;
25
+ completions: number;
26
+ dropOffRate: number;
27
+ averageDuration: number;
28
+ } | null;
29
+ exportAnalytics: (format: "csv" | "pdf" | "json") => Promise<void>;
30
+ };
@@ -1,6 +1,6 @@
1
1
  import { useGetTourAnalyticsQuery as e } from "../../generated/global-operations.js";
2
2
  import { useCallback as t, useMemo as n } from "react";
3
- //#region src/tours/hooks/useTourAnalytics.ts
3
+ // src/tours/hooks/useTourAnalytics.ts
4
4
  function r(r) {
5
5
  let { data: i, loading: a, error: o, refetch: s } = e({
6
6
  variables: { tourId: r },
@@ -64,7 +64,6 @@ function r(r) {
64
64
  }, [c])
65
65
  };
66
66
  }
67
- //#endregion
68
67
  export { r as useTourAnalytics };
69
68
 
70
69
  //# sourceMappingURL=useTourAnalytics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useTourAnalytics.js","names":[],"sources":["../../../src/tours/hooks/useTourAnalytics.ts"],"sourcesContent":["import { useCallback, useMemo } from 'react';\nimport { useGetTourAnalyticsQuery } from '../../generated/global-operations';\nimport type {\n DeviceTypeStats,\n TourEventTypeStats,\n TourStepAnalytics,\n} from '../../generated/global-types';\nimport type { TourAnalytics } from '../types';\n\n/**\n * Hook for tour analytics functionality\n * Fetches and processes tour performance data\n */\nexport function useTourAnalytics(tourId?: string) {\n const { data, loading, error, refetch } = useGetTourAnalyticsQuery({\n variables: { tourId: tourId! },\n skip: !tourId,\n });\n\n // Transform API response to match our TourAnalytics type\n const analytics: TourAnalytics | null = useMemo(() => {\n if (!data?.tourAnalytics || !tourId) return null;\n\n const { tourAnalytics, tourStepAnalytics } = data;\n\n return {\n tourId,\n totalViews: tourAnalytics.totalViews,\n totalCompletions: tourAnalytics.totalCompletions,\n totalSkips: tourAnalytics.totalSkips,\n averageCompletionRate: tourAnalytics.averageCompletionRate,\n averageDuration: tourAnalytics.averageDuration,\n byDeviceType: tourAnalytics.byDeviceType.map((d: DeviceTypeStats) => ({\n deviceType: d.deviceType.toLowerCase() as 'mobile' | 'tablet' | 'desktop',\n count: d.count,\n completionRate: d.completionRate,\n })),\n byEventType: tourAnalytics.byEventType.map((e: TourEventTypeStats) => ({\n eventType: e.eventType,\n count: e.count,\n })),\n stepAnalytics: tourStepAnalytics.map((s: TourStepAnalytics, index: number) => ({\n stepId: s.stepId,\n stepOrder: index + 1,\n views: s.viewCount,\n completions: s.completionCount,\n dropOffRate: s.viewCount > 0 ? ((s.viewCount - s.completionCount) / s.viewCount) * 100 : 0,\n averageDuration: s.averageDuration,\n })),\n };\n }, [data, tourId]);\n\n /**\n * Fetch/refetch tour analytics\n */\n const fetchAnalytics = useCallback(\n async (_dateRange?: { start: string; end: string }) => {\n if (!tourId) return;\n await refetch();\n },\n [tourId, refetch]\n );\n\n /**\n * Calculate completion rate percentage\n */\n const getCompletionRate = useCallback(() => {\n if (!analytics || analytics.totalViews === 0) return 0;\n return (analytics.totalCompletions / analytics.totalViews) * 100;\n }, [analytics]);\n\n /**\n * Calculate skip rate percentage\n */\n const getSkipRate = useCallback(() => {\n if (!analytics || analytics.totalViews === 0) return 0;\n return (analytics.totalSkips / analytics.totalViews) * 100;\n }, [analytics]);\n\n /**\n * Get device with highest completion rate\n */\n const getBestDevice = useCallback(() => {\n if (!analytics || analytics.byDeviceType.length === 0) return null;\n return analytics.byDeviceType.reduce((best, current) =>\n current.completionRate > best.completionRate ? current : best\n );\n }, [analytics]);\n\n /**\n * Get step with highest drop-off rate\n */\n const getHighestDropOffStep = useCallback(() => {\n if (!analytics || !analytics.stepAnalytics || analytics.stepAnalytics.length === 0) {\n return null;\n }\n return analytics.stepAnalytics.reduce((worst, current) =>\n current.dropOffRate > worst.dropOffRate ? current : worst\n );\n }, [analytics]);\n\n /**\n * Export analytics data\n */\n const exportAnalytics = useCallback(\n async (format: 'csv' | 'pdf' | 'json') => {\n if (!analytics) return;\n\n let content: string;\n let mimeType: string;\n let extension: string;\n\n if (format === 'json') {\n content = JSON.stringify(analytics, null, 2);\n mimeType = 'application/json';\n extension = 'json';\n } else if (format === 'csv') {\n const headers = ['Metric', 'Value'];\n const rows = [\n ['Total Views', String(analytics.totalViews)],\n ['Total Completions', String(analytics.totalCompletions)],\n ['Total Skips', String(analytics.totalSkips)],\n ['Completion Rate', `${analytics.averageCompletionRate.toFixed(2)}%`],\n ['Average Duration (s)', String(analytics.averageDuration)],\n ];\n content = [headers.join(','), ...rows.map((r) => r.join(','))].join('\\n');\n mimeType = 'text/csv';\n extension = 'csv';\n } else {\n return; // PDF not supported client-side\n }\n\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `tour-analytics-${analytics.tourId}.${extension}`;\n a.click();\n setTimeout(() => URL.revokeObjectURL(url), 100);\n },\n [analytics]\n );\n\n return {\n analytics,\n loading,\n error,\n fetchAnalytics,\n getCompletionRate,\n getSkipRate,\n getBestDevice,\n getHighestDropOffStep,\n exportAnalytics,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useTourAnalytics.js","names":["useTourAnalytics","useGetTourAnalyticsQuery","tourId","analytics","useMemo","data","tourAnalytics","d","tourStepAnalytics","s","index","useCallback","refetch","current","best","worst","content","mimeType","extension","format","headers","rows","r","blob","url","a"],"sources":["../../../src/tours/hooks/useTourAnalytics.ts"],"sourcesContent":["import { useCallback, useMemo } from 'react';\nimport { useGetTourAnalyticsQuery } from '../../generated/global-operations';\nimport type {\n DeviceTypeStats,\n TourEventTypeStats,\n TourStepAnalytics,\n} from '../../generated/global-types';\nimport type { TourAnalytics } from '../types';\n\n/**\n * Hook for tour analytics functionality\n * Fetches and processes tour performance data\n */\nexport function useTourAnalytics(tourId?: string) {\n const { data, loading, error, refetch } = useGetTourAnalyticsQuery({\n variables: { tourId: tourId! },\n skip: !tourId,\n });\n\n // Transform API response to match our TourAnalytics type\n const analytics: TourAnalytics | null = useMemo(() => {\n if (!data?.tourAnalytics || !tourId) return null;\n\n const { tourAnalytics, tourStepAnalytics } = data;\n\n return {\n tourId,\n totalViews: tourAnalytics.totalViews,\n totalCompletions: tourAnalytics.totalCompletions,\n totalSkips: tourAnalytics.totalSkips,\n averageCompletionRate: tourAnalytics.averageCompletionRate,\n averageDuration: tourAnalytics.averageDuration,\n byDeviceType: tourAnalytics.byDeviceType.map((d: DeviceTypeStats) => ({\n deviceType: d.deviceType.toLowerCase() as 'mobile' | 'tablet' | 'desktop',\n count: d.count,\n completionRate: d.completionRate,\n })),\n byEventType: tourAnalytics.byEventType.map((e: TourEventTypeStats) => ({\n eventType: e.eventType,\n count: e.count,\n })),\n stepAnalytics: tourStepAnalytics.map((s: TourStepAnalytics, index: number) => ({\n stepId: s.stepId,\n stepOrder: index + 1,\n views: s.viewCount,\n completions: s.completionCount,\n dropOffRate: s.viewCount > 0 ? ((s.viewCount - s.completionCount) / s.viewCount) * 100 : 0,\n averageDuration: s.averageDuration,\n })),\n };\n }, [data, tourId]);\n\n /**\n * Fetch/refetch tour analytics\n */\n const fetchAnalytics = useCallback(\n async (_dateRange?: { start: string; end: string }) => {\n if (!tourId) return;\n await refetch();\n },\n [tourId, refetch]\n );\n\n /**\n * Calculate completion rate percentage\n */\n const getCompletionRate = useCallback(() => {\n if (!analytics || analytics.totalViews === 0) return 0;\n return (analytics.totalCompletions / analytics.totalViews) * 100;\n }, [analytics]);\n\n /**\n * Calculate skip rate percentage\n */\n const getSkipRate = useCallback(() => {\n if (!analytics || analytics.totalViews === 0) return 0;\n return (analytics.totalSkips / analytics.totalViews) * 100;\n }, [analytics]);\n\n /**\n * Get device with highest completion rate\n */\n const getBestDevice = useCallback(() => {\n if (!analytics || analytics.byDeviceType.length === 0) return null;\n return analytics.byDeviceType.reduce((best, current) =>\n current.completionRate > best.completionRate ? current : best\n );\n }, [analytics]);\n\n /**\n * Get step with highest drop-off rate\n */\n const getHighestDropOffStep = useCallback(() => {\n if (!analytics || !analytics.stepAnalytics || analytics.stepAnalytics.length === 0) {\n return null;\n }\n return analytics.stepAnalytics.reduce((worst, current) =>\n current.dropOffRate > worst.dropOffRate ? current : worst\n );\n }, [analytics]);\n\n /**\n * Export analytics data\n */\n const exportAnalytics = useCallback(\n async (format: 'csv' | 'pdf' | 'json') => {\n if (!analytics) return;\n\n let content: string;\n let mimeType: string;\n let extension: string;\n\n if (format === 'json') {\n content = JSON.stringify(analytics, null, 2);\n mimeType = 'application/json';\n extension = 'json';\n } else if (format === 'csv') {\n const headers = ['Metric', 'Value'];\n const rows = [\n ['Total Views', String(analytics.totalViews)],\n ['Total Completions', String(analytics.totalCompletions)],\n ['Total Skips', String(analytics.totalSkips)],\n ['Completion Rate', `${analytics.averageCompletionRate.toFixed(2)}%`],\n ['Average Duration (s)', String(analytics.averageDuration)],\n ];\n content = [headers.join(','), ...rows.map((r) => r.join(','))].join('\\n');\n mimeType = 'text/csv';\n extension = 'csv';\n } else {\n return; // PDF not supported client-side\n }\n\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `tour-analytics-${analytics.tourId}.${extension}`;\n a.click();\n setTimeout(() => URL.revokeObjectURL(url), 100);\n },\n [analytics]\n );\n\n return {\n analytics,\n loading,\n error,\n fetchAnalytics,\n getCompletionRate,\n getSkipRate,\n getBestDevice,\n getHighestDropOffStep,\n exportAnalytics,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import { Tour, TourStep } from '../types';
2
+ import { TourStatus, TourStepPlacement } from '../../generated/global-types';
3
+ /**
4
+ * Hook for tour builder functionality
5
+ * Manages tour creation, editing, and step synchronization
6
+ * Uses the gateway client from fe-libs
7
+ */
8
+ export declare function useTourBuilder(_tourId?: string): {
9
+ tour: Tour | null;
10
+ steps: TourStep[];
11
+ isDirty: boolean;
12
+ validationErrors: import('../types').ValidationError[];
13
+ saving: boolean;
14
+ loading: boolean;
15
+ deleting: boolean;
16
+ error: Error | null;
17
+ loadTour: (id: string) => Promise<void>;
18
+ createTour: (input: Partial<Tour>) => Promise<{
19
+ __typename?: "Tour";
20
+ id: string;
21
+ name: string;
22
+ slug: string;
23
+ productId: string;
24
+ status: TourStatus;
25
+ createdAt: string;
26
+ } | null>;
27
+ updateTour: (id: string, input: Partial<Tour>) => Promise<{
28
+ __typename?: "Tour";
29
+ id: string;
30
+ name: string;
31
+ slug: string;
32
+ status: TourStatus;
33
+ updatedAt: string;
34
+ } | null>;
35
+ deleteTour: (id: string) => Promise<boolean>;
36
+ syncSteps: (tourIdParam: string, steps: TourStep[]) => Promise<{
37
+ __typename?: "TourStep";
38
+ id: string;
39
+ tourId: string;
40
+ title: string;
41
+ content: string;
42
+ stepOrder: number;
43
+ targetSelector?: string | null;
44
+ placement: TourStepPlacement;
45
+ targetSelectorMobile?: string | null;
46
+ targetSelectorTablet?: string | null;
47
+ targetSelectorDesktop?: string | null;
48
+ customData?: Record<string, unknown> | null;
49
+ actions?: Record<string, unknown> | null;
50
+ requireInteraction: boolean;
51
+ waitForSelector?: string | null;
52
+ waitForEvent?: string | null;
53
+ waitForTimeout?: number | null;
54
+ skipConditions?: Record<string, unknown> | null;
55
+ createdAt: string;
56
+ updatedAt: string;
57
+ }[] | null>;
58
+ saveTour: () => Promise<void>;
59
+ };
@@ -1,13 +1,13 @@
1
- import { useTr as e } from "../i18n.js";
1
+ import { useToursStore as e } from "../store/toursStore.js";
2
2
  import { useToursContext as t } from "../providers/ToursProvider.js";
3
- import { useToursStore as n } from "../store/toursStore.js";
3
+ import { useTr as n } from "../i18n.js";
4
4
  import { CreateTourDocument as r, DeleteTourDocument as i, GetTourDocument as a, SyncTourStepsDocument as o, UpdateTourDocument as s } from "../../generated/global-operations.js";
5
5
  import { useCallback as c, useState as l } from "react";
6
6
  import { useNavigate as u } from "react-router";
7
7
  import { useGatewayClient as d } from "@burdenoff/fe-libs/shared/graphql";
8
- //#region src/tours/hooks/useTourBuilder.ts
8
+ // src/tours/hooks/useTourBuilder.ts
9
9
  function f(f) {
10
- let m = e(), h = d("global"), g = u(), { basePath: _ = "/" } = t(), [v, y] = l(null), [b, x] = l(!1), [S, C] = l(!1), [w, T] = l(!1), { selectedTour: E, stepSequence: D, isDirty: O, validationErrors: k, setSelectedTour: A, updateStepSequence: j, validateTour: M, setDirty: N, notifyToursListChanged: P } = n(), F = c(async (e) => {
10
+ let m = n(), h = d("global"), g = u(), { basePath: _ = "/" } = t(), [v, y] = l(null), [b, x] = l(!1), [S, C] = l(!1), [w, T] = l(!1), { selectedTour: E, stepSequence: D, isDirty: O, validationErrors: k, setSelectedTour: A, updateStepSequence: j, validateTour: M, setDirty: N, notifyToursListChanged: P } = e(), F = c(async (e) => {
11
11
  x(!0), y(null);
12
12
  try {
13
13
  let t = await h.query({
@@ -53,7 +53,12 @@ function f(f) {
53
53
  mutation: r,
54
54
  variables: { input: n }
55
55
  })).data;
56
- return i?.createTour ? (A(i.createTour), P(), g(`${_}/${i.createTour.id}/steps`), i.createTour) : null;
56
+ if (i?.createTour) {
57
+ A(i.createTour), P();
58
+ let e = `${_}/${i.createTour.id}/steps`;
59
+ return g(e), i.createTour;
60
+ }
61
+ return null;
57
62
  } catch (e) {
58
63
  throw y(e instanceof Error ? e : Error(m("tourBuilder.errorCreateTour", "Failed to create tour"))), e;
59
64
  } finally {
@@ -201,7 +206,6 @@ function p(e) {
201
206
  let t = e.trim();
202
207
  return t.length > 0 ? t : void 0;
203
208
  }
204
- //#endregion
205
209
  export { f as useTourBuilder };
206
210
 
207
211
  //# sourceMappingURL=useTourBuilder.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useTourBuilder.js","names":[],"sources":["../../../src/tours/hooks/useTourBuilder.ts"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useNavigate } from 'react-router';\nimport { useToursStore } from '../store/toursStore';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useTr } from '../i18n';\nimport type { Tour, TourStep } from '../types';\nimport type { TourStatus, TourTriggerType, TourStepPlacement } from '../../generated/global-types';\nimport {\n CreateTourDocument,\n UpdateTourDocument,\n DeleteTourDocument,\n GetTourDocument,\n SyncTourStepsDocument,\n} from '../../generated/global-operations';\nimport type {\n GetTourQuery,\n GetTourQueryVariables,\n CreateTourMutation,\n CreateTourMutationVariables,\n UpdateTourMutation,\n UpdateTourMutationVariables,\n DeleteTourMutation,\n DeleteTourMutationVariables,\n SyncTourStepsMutation,\n SyncTourStepsMutationVariables,\n} from '../../generated/global-operations';\n\n/**\n * Hook for tour builder functionality\n * Manages tour creation, editing, and step synchronization\n * Uses the gateway client from fe-libs\n */\nexport function useTourBuilder(_tourId?: string) {\n const tr = useTr();\n const globalClient = useGatewayClient('global');\n const navigate = useNavigate();\n const { basePath = '/' } = useToursContext();\n\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(false);\n const [saving, setSaving] = useState(false);\n const [deleting, setDeleting] = useState(false);\n\n const {\n selectedTour,\n stepSequence,\n isDirty,\n validationErrors,\n setSelectedTour,\n updateStepSequence,\n validateTour,\n setDirty,\n notifyToursListChanged,\n } = useToursStore();\n\n /**\n * Load tour data\n */\n const loadTour = useCallback(\n async (id: string) => {\n setLoading(true);\n setError(null);\n\n try {\n const result = await globalClient.query<GetTourQuery, GetTourQueryVariables>({\n query: GetTourDocument,\n variables: { id },\n fetchPolicy: 'network-only',\n });\n\n if (result.error) {\n throw result.error;\n }\n\n const { data } = result;\n\n if (data?.tour) {\n setSelectedTour(data.tour as Tour);\n updateStepSequence((data.tour.steps || []) as TourStep[]);\n }\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(tr('tourBuilder.errorLoadTour', 'Failed to load tour'))\n );\n } finally {\n setLoading(false);\n }\n },\n [globalClient, setSelectedTour, updateStepSequence, tr]\n );\n\n /**\n * Create new tour\n */\n const createTour = useCallback(\n async (input: Partial<Tour>) => {\n setSaving(true);\n setError(null);\n\n try {\n // Generate slug from name if not provided\n const slug =\n input.slug ||\n (input.name || 'tour')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n\n const createInput = {\n name: input.name || '',\n slug,\n productId: input.productId || 'workspaces',\n description: normalizeOptionalString(input.description),\n moduleId: normalizeOptionalString(input.moduleId),\n pageId: normalizeOptionalString(input.pageId),\n status: (input.status || 'DRAFT') as TourStatus,\n isActive: input.isActive ?? true,\n triggerType: (input.triggerType || 'MANUAL') as TourTriggerType,\n triggerConfig: input.triggerConfig,\n priority: input.priority ?? 50,\n displayOrder: input.displayOrder ?? 1,\n startDate: normalizeOptionalString(input.startDate),\n endDate: normalizeOptionalString(input.endDate),\n mobileConfig: input.mobileConfig,\n tabletConfig: input.tabletConfig,\n desktopConfig: input.desktopConfig,\n };\n\n const result = await globalClient.mutate<CreateTourMutation, CreateTourMutationVariables>({\n mutation: CreateTourDocument,\n variables: { input: createInput },\n });\n\n const data = result.data;\n\n if (data?.createTour) {\n setSelectedTour(data.createTour as Tour);\n notifyToursListChanged();\n\n // Navigate to step editor\n const editPath = `${basePath}/${data.createTour.id}/steps`;\n navigate(editPath);\n\n return data.createTour;\n }\n\n return null;\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(tr('tourBuilder.errorCreateTour', 'Failed to create tour'))\n );\n throw err;\n } finally {\n setSaving(false);\n }\n },\n [globalClient, basePath, navigate, notifyToursListChanged, setSelectedTour, tr]\n );\n\n /**\n * Update existing tour\n */\n const updateTour = useCallback(\n async (id: string, input: Partial<Tour>) => {\n setSaving(true);\n setError(null);\n\n try {\n const updateInput = {\n name: input.name,\n slug: input.slug,\n description: normalizeOptionalString(input.description),\n productId: normalizeOptionalString(input.productId),\n moduleId: normalizeOptionalString(input.moduleId),\n pageId: normalizeOptionalString(input.pageId),\n status: input.status as TourStatus | undefined,\n isActive: input.isActive,\n triggerType: input.triggerType as TourTriggerType | undefined,\n triggerConfig: input.triggerConfig,\n priority: input.priority,\n displayOrder: input.displayOrder,\n startDate: normalizeOptionalString(input.startDate),\n endDate: normalizeOptionalString(input.endDate),\n mobileConfig: input.mobileConfig,\n tabletConfig: input.tabletConfig,\n desktopConfig: input.desktopConfig,\n };\n\n const result = await globalClient.mutate<UpdateTourMutation, UpdateTourMutationVariables>({\n mutation: UpdateTourDocument,\n variables: { id, input: updateInput },\n });\n\n const data = result.data;\n\n if (data?.updateTour) {\n notifyToursListChanged();\n return data.updateTour;\n }\n\n return null;\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(tr('tourBuilder.errorUpdateTour', 'Failed to update tour'))\n );\n throw err;\n } finally {\n setSaving(false);\n }\n },\n [globalClient, notifyToursListChanged, tr]\n );\n\n /**\n * Delete tour\n */\n const deleteTour = useCallback(\n async (id: string) => {\n setDeleting(true);\n setError(null);\n\n try {\n const result = await globalClient.mutate<DeleteTourMutation, DeleteTourMutationVariables>({\n mutation: DeleteTourDocument,\n variables: { id },\n });\n\n const data = result.data;\n\n if (data?.deleteTour) {\n notifyToursListChanged();\n }\n\n return data?.deleteTour ?? false;\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(tr('tourBuilder.errorDeleteTour', 'Failed to delete tour'))\n );\n throw err;\n } finally {\n setDeleting(false);\n }\n },\n [globalClient, notifyToursListChanged, tr]\n );\n\n /**\n * Sync tour steps\n */\n const syncSteps = useCallback(\n async (tourIdParam: string, steps: TourStep[]) => {\n setSaving(true);\n setError(null);\n\n try {\n const stepsInput = steps.map((s, index) => ({\n id: s.id?.startsWith('temp_') ? undefined : s.id,\n title: s.title,\n content: s.content,\n stepOrder: s.stepOrder ?? index + 1,\n targetSelector: s.targetSelector || '',\n placement: (s.placement || 'BOTTOM') as TourStepPlacement,\n targetSelectorMobile: s.targetSelectorMobile,\n targetSelectorTablet: s.targetSelectorTablet,\n targetSelectorDesktop: s.targetSelectorDesktop,\n customData: s.customData,\n actions: s.actions,\n requireInteraction: s.requireInteraction,\n waitForSelector: s.waitForSelector,\n waitForEvent: s.waitForEvent,\n waitForTimeout: s.waitForTimeout,\n skipConditions: s.skipConditions,\n }));\n\n const result = await globalClient.mutate<\n SyncTourStepsMutation,\n SyncTourStepsMutationVariables\n >({\n mutation: SyncTourStepsDocument,\n variables: { tourId: tourIdParam, steps: stepsInput },\n });\n\n const data = result.data;\n\n if (data?.syncTourSteps) {\n updateStepSequence(data.syncTourSteps as TourStep[]);\n setDirty(false);\n return data.syncTourSteps;\n }\n\n return null;\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(tr('tourBuilder.errorSyncSteps', 'Failed to sync steps'))\n );\n throw err;\n } finally {\n setSaving(false);\n }\n },\n [globalClient, updateStepSequence, setDirty, tr]\n );\n\n /**\n * Save tour with validation\n */\n const saveTour = useCallback(async () => {\n const errors = validateTour();\n if (errors.length > 0) {\n throw new Error(tr('tourBuilder.errorValidationFailed', 'Validation failed'));\n }\n\n if (!selectedTour) {\n throw new Error(tr('tourBuilder.errorNoTourSelected', 'No tour selected'));\n }\n\n // Update tour metadata\n if (selectedTour.id && !selectedTour.id.startsWith('temp_')) {\n await updateTour(selectedTour.id, selectedTour);\n } else {\n const created = await createTour(selectedTour);\n if (created && isDirty && stepSequence.length > 0) {\n await syncSteps(created.id, stepSequence);\n }\n return;\n }\n\n // Sync steps\n if (isDirty && selectedTour.id) {\n await syncSteps(selectedTour.id, stepSequence);\n }\n }, [selectedTour, stepSequence, isDirty, validateTour, createTour, updateTour, syncSteps, tr]);\n\n return {\n tour: selectedTour,\n steps: stepSequence,\n isDirty,\n validationErrors,\n saving,\n loading,\n deleting,\n error,\n loadTour,\n createTour,\n updateTour,\n deleteTour,\n syncSteps,\n saveTour,\n };\n}\n\nfunction normalizeOptionalString(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return value as string | undefined;\n }\n\n const normalized = value.trim();\n return normalized.length > 0 ? normalized : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useTourBuilder.js","names":["useTourBuilder","tr","useTr","globalClient","useGatewayClient","navigate","useNavigate","useToursContext","error","setError","useState","loading","setLoading","saving","setSaving","deleting","setDeleting","useToursStore","loadTour","useCallback","result","GetTourDocument","data","setSelectedTour","updateStepSequence","err","createTour","slug","input","createInput","normalizeOptionalString","CreateTourDocument","notifyToursListChanged","editPath","basePath","updateTour","updateInput","UpdateTourDocument","deleteTour","DeleteTourDocument","syncSteps","stepsInput","steps","s","index","SyncTourStepsDocument","tourIdParam","setDirty","selectedTour","stepSequence","validateTour","created","isDirty","value","normalized"],"sources":["../../../src/tours/hooks/useTourBuilder.ts"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useNavigate } from 'react-router';\nimport { useToursStore } from '../store/toursStore';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useTr } from '../i18n';\nimport type { Tour, TourStep } from '../types';\nimport type { TourStatus, TourTriggerType, TourStepPlacement } from '../../generated/global-types';\nimport {\n CreateTourDocument,\n UpdateTourDocument,\n DeleteTourDocument,\n GetTourDocument,\n SyncTourStepsDocument,\n} from '../../generated/global-operations';\nimport type {\n GetTourQuery,\n GetTourQueryVariables,\n CreateTourMutation,\n CreateTourMutationVariables,\n UpdateTourMutation,\n UpdateTourMutationVariables,\n DeleteTourMutation,\n DeleteTourMutationVariables,\n SyncTourStepsMutation,\n SyncTourStepsMutationVariables,\n} from '../../generated/global-operations';\n\n/**\n * Hook for tour builder functionality\n * Manages tour creation, editing, and step synchronization\n * Uses the gateway client from fe-libs\n */\nexport function useTourBuilder(_tourId?: string) {\n const tr = useTr();\n const globalClient = useGatewayClient('global');\n const navigate = useNavigate();\n const { basePath = '/' } = useToursContext();\n\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(false);\n const [saving, setSaving] = useState(false);\n const [deleting, setDeleting] = useState(false);\n\n const {\n selectedTour,\n stepSequence,\n isDirty,\n validationErrors,\n setSelectedTour,\n updateStepSequence,\n validateTour,\n setDirty,\n notifyToursListChanged,\n } = useToursStore();\n\n /**\n * Load tour data\n */\n const loadTour = useCallback(\n async (id: string) => {\n setLoading(true);\n setError(null);\n\n try {\n const result = await globalClient.query<GetTourQuery, GetTourQueryVariables>({\n query: GetTourDocument,\n variables: { id },\n fetchPolicy: 'network-only',\n });\n\n if (result.error) {\n throw result.error;\n }\n\n const { data } = result;\n\n if (data?.tour) {\n setSelectedTour(data.tour as Tour);\n updateStepSequence((data.tour.steps || []) as TourStep[]);\n }\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(tr('tourBuilder.errorLoadTour', 'Failed to load tour'))\n );\n } finally {\n setLoading(false);\n }\n },\n [globalClient, setSelectedTour, updateStepSequence, tr]\n );\n\n /**\n * Create new tour\n */\n const createTour = useCallback(\n async (input: Partial<Tour>) => {\n setSaving(true);\n setError(null);\n\n try {\n // Generate slug from name if not provided\n const slug =\n input.slug ||\n (input.name || 'tour')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n\n const createInput = {\n name: input.name || '',\n slug,\n productId: input.productId || 'workspaces',\n description: normalizeOptionalString(input.description),\n moduleId: normalizeOptionalString(input.moduleId),\n pageId: normalizeOptionalString(input.pageId),\n status: (input.status || 'DRAFT') as TourStatus,\n isActive: input.isActive ?? true,\n triggerType: (input.triggerType || 'MANUAL') as TourTriggerType,\n triggerConfig: input.triggerConfig,\n priority: input.priority ?? 50,\n displayOrder: input.displayOrder ?? 1,\n startDate: normalizeOptionalString(input.startDate),\n endDate: normalizeOptionalString(input.endDate),\n mobileConfig: input.mobileConfig,\n tabletConfig: input.tabletConfig,\n desktopConfig: input.desktopConfig,\n };\n\n const result = await globalClient.mutate<CreateTourMutation, CreateTourMutationVariables>({\n mutation: CreateTourDocument,\n variables: { input: createInput },\n });\n\n const data = result.data;\n\n if (data?.createTour) {\n setSelectedTour(data.createTour as Tour);\n notifyToursListChanged();\n\n // Navigate to step editor\n const editPath = `${basePath}/${data.createTour.id}/steps`;\n navigate(editPath);\n\n return data.createTour;\n }\n\n return null;\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(tr('tourBuilder.errorCreateTour', 'Failed to create tour'))\n );\n throw err;\n } finally {\n setSaving(false);\n }\n },\n [globalClient, basePath, navigate, notifyToursListChanged, setSelectedTour, tr]\n );\n\n /**\n * Update existing tour\n */\n const updateTour = useCallback(\n async (id: string, input: Partial<Tour>) => {\n setSaving(true);\n setError(null);\n\n try {\n const updateInput = {\n name: input.name,\n slug: input.slug,\n description: normalizeOptionalString(input.description),\n productId: normalizeOptionalString(input.productId),\n moduleId: normalizeOptionalString(input.moduleId),\n pageId: normalizeOptionalString(input.pageId),\n status: input.status as TourStatus | undefined,\n isActive: input.isActive,\n triggerType: input.triggerType as TourTriggerType | undefined,\n triggerConfig: input.triggerConfig,\n priority: input.priority,\n displayOrder: input.displayOrder,\n startDate: normalizeOptionalString(input.startDate),\n endDate: normalizeOptionalString(input.endDate),\n mobileConfig: input.mobileConfig,\n tabletConfig: input.tabletConfig,\n desktopConfig: input.desktopConfig,\n };\n\n const result = await globalClient.mutate<UpdateTourMutation, UpdateTourMutationVariables>({\n mutation: UpdateTourDocument,\n variables: { id, input: updateInput },\n });\n\n const data = result.data;\n\n if (data?.updateTour) {\n notifyToursListChanged();\n return data.updateTour;\n }\n\n return null;\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(tr('tourBuilder.errorUpdateTour', 'Failed to update tour'))\n );\n throw err;\n } finally {\n setSaving(false);\n }\n },\n [globalClient, notifyToursListChanged, tr]\n );\n\n /**\n * Delete tour\n */\n const deleteTour = useCallback(\n async (id: string) => {\n setDeleting(true);\n setError(null);\n\n try {\n const result = await globalClient.mutate<DeleteTourMutation, DeleteTourMutationVariables>({\n mutation: DeleteTourDocument,\n variables: { id },\n });\n\n const data = result.data;\n\n if (data?.deleteTour) {\n notifyToursListChanged();\n }\n\n return data?.deleteTour ?? false;\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(tr('tourBuilder.errorDeleteTour', 'Failed to delete tour'))\n );\n throw err;\n } finally {\n setDeleting(false);\n }\n },\n [globalClient, notifyToursListChanged, tr]\n );\n\n /**\n * Sync tour steps\n */\n const syncSteps = useCallback(\n async (tourIdParam: string, steps: TourStep[]) => {\n setSaving(true);\n setError(null);\n\n try {\n const stepsInput = steps.map((s, index) => ({\n id: s.id?.startsWith('temp_') ? undefined : s.id,\n title: s.title,\n content: s.content,\n stepOrder: s.stepOrder ?? index + 1,\n targetSelector: s.targetSelector || '',\n placement: (s.placement || 'BOTTOM') as TourStepPlacement,\n targetSelectorMobile: s.targetSelectorMobile,\n targetSelectorTablet: s.targetSelectorTablet,\n targetSelectorDesktop: s.targetSelectorDesktop,\n customData: s.customData,\n actions: s.actions,\n requireInteraction: s.requireInteraction,\n waitForSelector: s.waitForSelector,\n waitForEvent: s.waitForEvent,\n waitForTimeout: s.waitForTimeout,\n skipConditions: s.skipConditions,\n }));\n\n const result = await globalClient.mutate<\n SyncTourStepsMutation,\n SyncTourStepsMutationVariables\n >({\n mutation: SyncTourStepsDocument,\n variables: { tourId: tourIdParam, steps: stepsInput },\n });\n\n const data = result.data;\n\n if (data?.syncTourSteps) {\n updateStepSequence(data.syncTourSteps as TourStep[]);\n setDirty(false);\n return data.syncTourSteps;\n }\n\n return null;\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(tr('tourBuilder.errorSyncSteps', 'Failed to sync steps'))\n );\n throw err;\n } finally {\n setSaving(false);\n }\n },\n [globalClient, updateStepSequence, setDirty, tr]\n );\n\n /**\n * Save tour with validation\n */\n const saveTour = useCallback(async () => {\n const errors = validateTour();\n if (errors.length > 0) {\n throw new Error(tr('tourBuilder.errorValidationFailed', 'Validation failed'));\n }\n\n if (!selectedTour) {\n throw new Error(tr('tourBuilder.errorNoTourSelected', 'No tour selected'));\n }\n\n // Update tour metadata\n if (selectedTour.id && !selectedTour.id.startsWith('temp_')) {\n await updateTour(selectedTour.id, selectedTour);\n } else {\n const created = await createTour(selectedTour);\n if (created && isDirty && stepSequence.length > 0) {\n await syncSteps(created.id, stepSequence);\n }\n return;\n }\n\n // Sync steps\n if (isDirty && selectedTour.id) {\n await syncSteps(selectedTour.id, stepSequence);\n }\n }, [selectedTour, stepSequence, isDirty, validateTour, createTour, updateTour, syncSteps, tr]);\n\n return {\n tour: selectedTour,\n steps: stepSequence,\n isDirty,\n validationErrors,\n saving,\n loading,\n deleting,\n error,\n loadTour,\n createTour,\n updateTour,\n deleteTour,\n syncSteps,\n saveTour,\n };\n}\n\nfunction normalizeOptionalString(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return value as string | undefined;\n }\n\n const normalized = value.trim();\n return normalized.length > 0 ? normalized : undefined;\n}\n"],"mappings":"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"}