@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 { createContext as e, use as t } from "react";
2
2
  import { jsx as n } from "react/jsx-runtime";
3
3
  import { MemoryRouter as r } from "react-router";
4
4
  import { PermissionProvider as i } from "@burdenoff/fe-libs/shared/providers/shell";
5
- //#region src/tours/providers/ToursProvider.tsx
5
+ // src/tours/providers/ToursProvider.tsx
6
6
  var a = e(null), o = () => {
7
7
  let e = t(a);
8
8
  if (!e) throw Error("useToursContext must be used within ToursProvider");
@@ -22,7 +22,6 @@ function s({ children: e, ...t }) {
22
22
  });
23
23
  return t.basePath ? c : /* @__PURE__ */ n(r, { children: c });
24
24
  }
25
- //#endregion
26
25
  export { s as ToursProvider, o as useToursContext };
27
26
 
28
27
  //# sourceMappingURL=ToursProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ToursProvider.js","names":[],"sources":["../../../src/tours/providers/ToursProvider.tsx"],"sourcesContent":["import { createContext, use, type ReactNode } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { ToursRootProps, ToursVariant } from '../types';\n\n/**\n * Context value for Tours microfrontend.\n *\n * Carries the ToursRoot props plus a resolved `variant` (always set; defaults\n * to \"admin\" when the caller did not specify one). Pages can read `variant`\n * to branch UI affordances (e.g., the user surface should never attempt\n * admin-only queries).\n */\ntype ToursContextValue = ToursRootProps & { variant: ToursVariant };\n\nconst ToursContext = createContext<ToursContextValue | null>(null);\n\n/**\n * Hook to access Tours context.\n */\nexport const useToursContext = () => {\n const context = use(ToursContext);\n if (!context) {\n throw new Error('useToursContext must be used within ToursProvider');\n }\n return context;\n};\n\n/**\n * Provider component for Tours microfrontend.\n * Sets up routing, context, and global-scoped permission checking.\n */\nexport function ToursProvider({ children, ...props }: ToursRootProps & { children: ReactNode }) {\n // No useMemo here: `props` is a fresh object on every parent render (spread\n // from the shell's JSX), so any memo keyed on `props` would re-create the\n // value every render and defeat itself. Consumer re-rendering is governed\n // by the shell anyway; keeping this lean avoids a false sense of stability.\n const contextValue: ToursContextValue = { ...props, variant: props.variant ?? 'admin' };\n const permissionScopeId = props.tenantId ?? props.organizationId ?? props.workspaceId ?? null;\n\n const coreProviders = (\n <ToursContext.Provider value={contextValue}>\n <PermissionProvider scopeId={permissionScopeId} gateway=\"global\">\n {children}\n </PermissionProvider>\n </ToursContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (props.basePath) {\n // Integrated mode: shell already has BrowserRouter, don't create nested router\n return coreProviders;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{coreProviders}</MemoryRouter>;\n}\n"],"mappings":";;;;;AAeA,IAAM,IAAe,EAAwC,KAAK,EAKrD,UAAwB;CACnC,IAAM,IAAU,EAAI,EAAa;AACjC,KAAI,CAAC,EACH,OAAU,MAAM,oDAAoD;AAEtE,QAAO;;AAOT,SAAgB,EAAc,EAAE,aAAU,GAAG,KAAmD;CAK9F,IAAM,IAAkC;EAAE,GAAG;EAAO,SAAS,EAAM,WAAW;EAAS,EACjF,IAAoB,EAAM,YAAY,EAAM,kBAAkB,EAAM,eAAe,MAEnF,IACJ,kBAAC,EAAa,UAAd;EAAuB,OAAO;YAC5B,kBAAC,GAAD;GAAoB,SAAS;GAAmB,SAAQ;GACrD;GACkB,CAAA;EACC,CAAA;AAW1B,QANI,EAAM,WAED,IAIF,kBAAC,GAAD,EAAA,UAAe,GAA6B,CAAA"}
1
+ {"version":3,"file":"ToursProvider.js","names":["ToursContext","createContext","useToursContext","context","use","ToursProvider","props","contextValue","permissionScopeId","coreProviders","PermissionProvider","MemoryRouter"],"sources":["../../../src/tours/providers/ToursProvider.tsx"],"sourcesContent":["import { createContext, use, type ReactNode } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { ToursRootProps, ToursVariant } from '../types';\n\n/**\n * Context value for Tours microfrontend.\n *\n * Carries the ToursRoot props plus a resolved `variant` (always set; defaults\n * to \"admin\" when the caller did not specify one). Pages can read `variant`\n * to branch UI affordances (e.g., the user surface should never attempt\n * admin-only queries).\n */\ntype ToursContextValue = ToursRootProps & { variant: ToursVariant };\n\nconst ToursContext = createContext<ToursContextValue | null>(null);\n\n/**\n * Hook to access Tours context.\n */\nexport const useToursContext = () => {\n const context = use(ToursContext);\n if (!context) {\n throw new Error('useToursContext must be used within ToursProvider');\n }\n return context;\n};\n\n/**\n * Provider component for Tours microfrontend.\n * Sets up routing, context, and global-scoped permission checking.\n */\nexport function ToursProvider({ children, ...props }: ToursRootProps & { children: ReactNode }) {\n // No useMemo here: `props` is a fresh object on every parent render (spread\n // from the shell's JSX), so any memo keyed on `props` would re-create the\n // value every render and defeat itself. Consumer re-rendering is governed\n // by the shell anyway; keeping this lean avoids a false sense of stability.\n const contextValue: ToursContextValue = { ...props, variant: props.variant ?? 'admin' };\n const permissionScopeId = props.tenantId ?? props.organizationId ?? props.workspaceId ?? null;\n\n const coreProviders = (\n <ToursContext.Provider value={contextValue}>\n <PermissionProvider scopeId={permissionScopeId} gateway=\"global\">\n {children}\n </PermissionProvider>\n </ToursContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (props.basePath) {\n // Integrated mode: shell already has BrowserRouter, don't create nested router\n return coreProviders;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{coreProviders}</MemoryRouter>;\n}\n"],"mappings":";;;;;AAeA,IAAMA,IAAeC,EAAwC,IAAI,GAKpDC,UAAwB;CACnC,IAAMC,IAAUC,EAAIJ,CAAY;CAChC,IAAI,CAACG,GACH,MAAU,MAAM,mDAAmD;CAErE,OAAOA;AACT;AAMA,SAAgBE,EAAc,EAAE,aAAU,GAAGC,KAAmD;CAK9F,IAAMC,IAAkC;EAAE,GAAGD;EAAO,SAASA,EAAM,WAAW;CAAQ,GAChFE,IAAoBF,EAAM,YAAYA,EAAM,kBAAkBA,EAAM,eAAe,MAEnFG,IACJ,kBAACT,EAAa,UAAd;EAAuB,OAAOO;EAC5B,UAAA,kBAACG,GAAD;GAAoB,SAASF;GAAmB,SAAQ;GACrD;EACiB,CAAA;CACC,CAAA;CAWzB,OANIF,EAAM,WAEDG,IAIF,kBAACE,GAAD,EAAA,UAAeF,EAA4B,CAAA;AACpD"}
@@ -0,0 +1,40 @@
1
+ import { Tour, TourStep, TourMedia, TourFilters, ValidationError, ToursViewMode, DeviceType } from '../types';
2
+ interface ToursState {
3
+ selectedTour: Tour | null;
4
+ selectedStep: TourStep | null;
5
+ viewMode: ToursViewMode;
6
+ devicePreview: DeviceType;
7
+ tourFilter: TourFilters;
8
+ toursListRevision: number;
9
+ stepSequence: TourStep[];
10
+ isDirty: boolean;
11
+ validationErrors: ValidationError[];
12
+ selectedMedia: TourMedia[];
13
+ uploadProgress: Record<string, number>;
14
+ sidebarOpen: boolean;
15
+ resetTransientState: () => void;
16
+ setSelectedTour: (tour: Tour | null) => void;
17
+ setSelectedStep: (step: TourStep | null) => void;
18
+ setViewMode: (mode: ToursViewMode) => void;
19
+ setDevicePreview: (device: DeviceType) => void;
20
+ toggleSidebar: () => void;
21
+ setSidebarOpen: (open: boolean) => void;
22
+ updateTourFilter: (filter: Partial<TourFilters>) => void;
23
+ resetTourFilter: () => void;
24
+ notifyToursListChanged: () => void;
25
+ updateStepSequence: (steps: TourStep[]) => void;
26
+ reorderSteps: (startIndex: number, endIndex: number) => void;
27
+ addStep: (step: Partial<TourStep>) => void;
28
+ removeStep: (stepId: string) => void;
29
+ updateStep: (stepId: string, updates: Partial<TourStep>) => void;
30
+ setDirty: (dirty: boolean) => void;
31
+ validateTour: () => ValidationError[];
32
+ clearValidationErrors: () => void;
33
+ selectMedia: (media: TourMedia) => void;
34
+ unselectMedia: (mediaId: string) => void;
35
+ clearSelectedMedia: () => void;
36
+ updateUploadProgress: (fileId: string, progress: number) => void;
37
+ removeUploadProgress: (fileId: string) => void;
38
+ }
39
+ export declare const useToursStore: import('zustand').UseBoundStore<import('zustand').StoreApi<ToursState>>;
40
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { create as e } from "zustand";
2
- //#region src/tours/store/toursStore.ts
2
+ // src/tours/store/toursStore.ts
3
3
  var t = {
4
4
  search: "",
5
5
  status: "all",
@@ -108,7 +108,7 @@ var t = {
108
108
  setDirty: (t) => e({ isDirty: t }),
109
109
  validateTour: () => {
110
110
  let t = [], { selectedTour: r, stepSequence: i } = n();
111
- return r ? ((!r.name || r.name.trim() === "") && t.push({
111
+ return r ? (r.name && r.name.trim() !== "" || t.push({
112
112
  field: "name",
113
113
  message: "validation.tourNameRequired"
114
114
  }), r.productId || t.push({
@@ -119,15 +119,15 @@ var t = {
119
119
  message: "validation.stepsRequired"
120
120
  }), i.forEach((e, n) => {
121
121
  let r = n + 1;
122
- (!e.title || e.title.trim() === "") && t.push({
122
+ e.title && e.title.trim() !== "" || t.push({
123
123
  field: `step[${n}].title`,
124
124
  message: "validation.stepTitleRequired",
125
125
  params: { number: r }
126
- }), (!e.content || e.content.trim() === "") && t.push({
126
+ }), e.content && e.content.trim() !== "" || t.push({
127
127
  field: `step[${n}].content`,
128
128
  message: "validation.stepContentRequired",
129
129
  params: { number: r }
130
- }), e.placement !== "CENTER" && (!e.targetSelector || e.targetSelector.trim() === "") && t.push({
130
+ }), e.placement === "CENTER" || e.targetSelector && e.targetSelector.trim() !== "" || t.push({
131
131
  field: `step[${n}].targetSelector`,
132
132
  message: "validation.stepSelectorRequired",
133
133
  params: { number: r }
@@ -157,7 +157,6 @@ var t = {
157
157
  delete r[t], e({ uploadProgress: r });
158
158
  }
159
159
  }));
160
- //#endregion
161
160
  export { n as useToursStore };
162
161
 
163
162
  //# sourceMappingURL=toursStore.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"toursStore.js","names":[],"sources":["../../../src/tours/store/toursStore.ts"],"sourcesContent":["import { create } from 'zustand';\nimport type {\n Tour,\n TourStep,\n TourMedia,\n TourFilters,\n ValidationError,\n ToursViewMode,\n DeviceType,\n} from '../types';\n\ninterface ToursState {\n // View state\n selectedTour: Tour | null;\n selectedStep: TourStep | null;\n viewMode: ToursViewMode;\n devicePreview: DeviceType;\n\n // Filters\n tourFilter: TourFilters;\n toursListRevision: number;\n\n // Builder state\n stepSequence: TourStep[];\n isDirty: boolean;\n validationErrors: ValidationError[];\n\n // Media state\n selectedMedia: TourMedia[];\n uploadProgress: Record<string, number>;\n\n // UI state\n sidebarOpen: boolean;\n\n // Lifecycle\n resetTransientState: () => void;\n\n // Actions - View\n setSelectedTour: (tour: Tour | null) => void;\n setSelectedStep: (step: TourStep | null) => void;\n setViewMode: (mode: ToursViewMode) => void;\n setDevicePreview: (device: DeviceType) => void;\n toggleSidebar: () => void;\n setSidebarOpen: (open: boolean) => void;\n\n // Actions - Filters\n updateTourFilter: (filter: Partial<TourFilters>) => void;\n resetTourFilter: () => void;\n notifyToursListChanged: () => void;\n\n // Actions - Steps\n updateStepSequence: (steps: TourStep[]) => void;\n reorderSteps: (startIndex: number, endIndex: number) => void;\n addStep: (step: Partial<TourStep>) => void;\n removeStep: (stepId: string) => void;\n updateStep: (stepId: string, updates: Partial<TourStep>) => void;\n\n // Actions - Validation\n setDirty: (dirty: boolean) => void;\n validateTour: () => ValidationError[];\n clearValidationErrors: () => void;\n\n // Actions - Media\n selectMedia: (media: TourMedia) => void;\n unselectMedia: (mediaId: string) => void;\n clearSelectedMedia: () => void;\n updateUploadProgress: (fileId: string, progress: number) => void;\n removeUploadProgress: (fileId: string) => void;\n}\n\nconst defaultFilter: TourFilters = {\n search: '',\n status: 'all',\n productId: undefined,\n moduleId: undefined,\n pageId: undefined,\n dateRange: { start: null, end: null },\n createdBy: undefined,\n};\n\nexport const useToursStore = create<ToursState>((set, get) => ({\n // Initial state\n selectedTour: null,\n selectedStep: null,\n viewMode: 'grid',\n devicePreview: 'desktop',\n tourFilter: defaultFilter,\n toursListRevision: 0,\n stepSequence: [],\n isDirty: false,\n validationErrors: [],\n selectedMedia: [],\n uploadProgress: {},\n sidebarOpen: true,\n\n resetTransientState: () =>\n set({\n selectedTour: null,\n selectedStep: null,\n viewMode: 'grid',\n devicePreview: 'desktop',\n tourFilter: defaultFilter,\n toursListRevision: 0,\n stepSequence: [],\n isDirty: false,\n validationErrors: [],\n selectedMedia: [],\n uploadProgress: {},\n sidebarOpen: true,\n }),\n\n // View actions\n setSelectedTour: (tour) =>\n set({\n selectedTour: tour,\n stepSequence: tour?.steps || [],\n isDirty: false,\n validationErrors: [],\n }),\n\n setSelectedStep: (step) => set({ selectedStep: step }),\n\n setViewMode: (mode) => set({ viewMode: mode }),\n\n setDevicePreview: (device) => set({ devicePreview: device }),\n\n toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),\n\n setSidebarOpen: (open) => set({ sidebarOpen: open }),\n\n // Filter actions\n updateTourFilter: (filter) =>\n set((state) => ({\n tourFilter: { ...state.tourFilter, ...filter },\n })),\n\n resetTourFilter: () => set({ tourFilter: defaultFilter }),\n\n notifyToursListChanged: () =>\n set((state) => ({ toursListRevision: state.toursListRevision + 1 })),\n\n // Step actions\n updateStepSequence: (steps) => set({ stepSequence: steps, isDirty: true }),\n\n reorderSteps: (startIndex, endIndex) => {\n const steps = [...get().stepSequence];\n const [removed] = steps.splice(startIndex, 1);\n steps.splice(endIndex, 0, removed);\n const reorderedSteps = steps.map((step, index) => ({\n ...step,\n stepOrder: index + 1,\n }));\n set({ stepSequence: reorderedSteps, isDirty: true });\n },\n\n addStep: (step) => {\n const steps = [...get().stepSequence];\n const newStep: TourStep = {\n id: `temp_${Date.now()}`,\n tourId: get().selectedTour?.id || '',\n title: step.title || 'New Step',\n content: step.content || '',\n stepOrder: steps.length + 1,\n targetSelector: step.targetSelector || '',\n placement: step.placement || 'AUTO',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n ...step,\n } as TourStep;\n steps.push(newStep);\n set({ stepSequence: steps, isDirty: true });\n },\n\n removeStep: (stepId) => {\n const steps = get().stepSequence.filter((s) => s.id !== stepId);\n const reorderedSteps = steps.map((step, index) => ({\n ...step,\n stepOrder: index + 1,\n }));\n set({ stepSequence: reorderedSteps, isDirty: true });\n },\n\n updateStep: (stepId, updates) => {\n const steps = get().stepSequence.map((step) =>\n step.id === stepId ? { ...step, ...updates } : step\n );\n set({ stepSequence: steps, isDirty: true });\n },\n\n // Validation actions\n setDirty: (dirty) => set({ isDirty: dirty }),\n\n validateTour: () => {\n const errors: ValidationError[] = [];\n const { selectedTour, stepSequence } = get();\n\n if (!selectedTour) {\n errors.push({ field: 'tour', message: 'validation.noTourSelected' });\n set({ validationErrors: errors });\n return errors;\n }\n\n if (!selectedTour.name || selectedTour.name.trim() === '') {\n errors.push({ field: 'name', message: 'validation.tourNameRequired' });\n }\n\n if (!selectedTour.productId) {\n errors.push({ field: 'productId', message: 'validation.productIdRequired' });\n }\n\n if (stepSequence.length === 0) {\n errors.push({ field: 'steps', message: 'validation.stepsRequired' });\n }\n\n stepSequence.forEach((step, index) => {\n const number = index + 1;\n if (!step.title || step.title.trim() === '') {\n errors.push({\n field: `step[${index}].title`,\n message: 'validation.stepTitleRequired',\n params: { number },\n });\n }\n if (!step.content || step.content.trim() === '') {\n errors.push({\n field: `step[${index}].content`,\n message: 'validation.stepContentRequired',\n params: { number },\n });\n }\n if (\n step.placement !== 'CENTER' &&\n (!step.targetSelector || step.targetSelector.trim() === '')\n ) {\n errors.push({\n field: `step[${index}].targetSelector`,\n message: 'validation.stepSelectorRequired',\n params: { number },\n });\n }\n });\n\n set({ validationErrors: errors });\n return errors;\n },\n\n clearValidationErrors: () => set({ validationErrors: [] }),\n\n // Media actions\n selectMedia: (media) => {\n const selected = [...get().selectedMedia];\n if (!selected.find((m) => m.id === media.id)) {\n selected.push(media);\n set({ selectedMedia: selected });\n }\n },\n\n unselectMedia: (mediaId) => {\n set({\n selectedMedia: get().selectedMedia.filter((m) => m.id !== mediaId),\n });\n },\n\n clearSelectedMedia: () => set({ selectedMedia: [] }),\n\n updateUploadProgress: (fileId, progress) => {\n set({\n uploadProgress: { ...get().uploadProgress, [fileId]: progress },\n });\n },\n\n removeUploadProgress: (fileId) => {\n const progress = { ...get().uploadProgress };\n delete progress[fileId];\n set({ uploadProgress: progress });\n },\n}));\n"],"mappings":"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"}
1
+ {"version":3,"file":"toursStore.js","names":["defaultFilter","useToursStore","create","set","tour","step","mode","device","state","open","filter","steps","get","removed","startIndex","endIndex","index","newStep","s","stepId","updates","dirty","errors","selectedTour","stepSequence","number","selected","m","media","mediaId","fileId","progress"],"sources":["../../../src/tours/store/toursStore.ts"],"sourcesContent":["import { create } from 'zustand';\nimport type {\n Tour,\n TourStep,\n TourMedia,\n TourFilters,\n ValidationError,\n ToursViewMode,\n DeviceType,\n} from '../types';\n\ninterface ToursState {\n // View state\n selectedTour: Tour | null;\n selectedStep: TourStep | null;\n viewMode: ToursViewMode;\n devicePreview: DeviceType;\n\n // Filters\n tourFilter: TourFilters;\n toursListRevision: number;\n\n // Builder state\n stepSequence: TourStep[];\n isDirty: boolean;\n validationErrors: ValidationError[];\n\n // Media state\n selectedMedia: TourMedia[];\n uploadProgress: Record<string, number>;\n\n // UI state\n sidebarOpen: boolean;\n\n // Lifecycle\n resetTransientState: () => void;\n\n // Actions - View\n setSelectedTour: (tour: Tour | null) => void;\n setSelectedStep: (step: TourStep | null) => void;\n setViewMode: (mode: ToursViewMode) => void;\n setDevicePreview: (device: DeviceType) => void;\n toggleSidebar: () => void;\n setSidebarOpen: (open: boolean) => void;\n\n // Actions - Filters\n updateTourFilter: (filter: Partial<TourFilters>) => void;\n resetTourFilter: () => void;\n notifyToursListChanged: () => void;\n\n // Actions - Steps\n updateStepSequence: (steps: TourStep[]) => void;\n reorderSteps: (startIndex: number, endIndex: number) => void;\n addStep: (step: Partial<TourStep>) => void;\n removeStep: (stepId: string) => void;\n updateStep: (stepId: string, updates: Partial<TourStep>) => void;\n\n // Actions - Validation\n setDirty: (dirty: boolean) => void;\n validateTour: () => ValidationError[];\n clearValidationErrors: () => void;\n\n // Actions - Media\n selectMedia: (media: TourMedia) => void;\n unselectMedia: (mediaId: string) => void;\n clearSelectedMedia: () => void;\n updateUploadProgress: (fileId: string, progress: number) => void;\n removeUploadProgress: (fileId: string) => void;\n}\n\nconst defaultFilter: TourFilters = {\n search: '',\n status: 'all',\n productId: undefined,\n moduleId: undefined,\n pageId: undefined,\n dateRange: { start: null, end: null },\n createdBy: undefined,\n};\n\nexport const useToursStore = create<ToursState>((set, get) => ({\n // Initial state\n selectedTour: null,\n selectedStep: null,\n viewMode: 'grid',\n devicePreview: 'desktop',\n tourFilter: defaultFilter,\n toursListRevision: 0,\n stepSequence: [],\n isDirty: false,\n validationErrors: [],\n selectedMedia: [],\n uploadProgress: {},\n sidebarOpen: true,\n\n resetTransientState: () =>\n set({\n selectedTour: null,\n selectedStep: null,\n viewMode: 'grid',\n devicePreview: 'desktop',\n tourFilter: defaultFilter,\n toursListRevision: 0,\n stepSequence: [],\n isDirty: false,\n validationErrors: [],\n selectedMedia: [],\n uploadProgress: {},\n sidebarOpen: true,\n }),\n\n // View actions\n setSelectedTour: (tour) =>\n set({\n selectedTour: tour,\n stepSequence: tour?.steps || [],\n isDirty: false,\n validationErrors: [],\n }),\n\n setSelectedStep: (step) => set({ selectedStep: step }),\n\n setViewMode: (mode) => set({ viewMode: mode }),\n\n setDevicePreview: (device) => set({ devicePreview: device }),\n\n toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),\n\n setSidebarOpen: (open) => set({ sidebarOpen: open }),\n\n // Filter actions\n updateTourFilter: (filter) =>\n set((state) => ({\n tourFilter: { ...state.tourFilter, ...filter },\n })),\n\n resetTourFilter: () => set({ tourFilter: defaultFilter }),\n\n notifyToursListChanged: () =>\n set((state) => ({ toursListRevision: state.toursListRevision + 1 })),\n\n // Step actions\n updateStepSequence: (steps) => set({ stepSequence: steps, isDirty: true }),\n\n reorderSteps: (startIndex, endIndex) => {\n const steps = [...get().stepSequence];\n const [removed] = steps.splice(startIndex, 1);\n steps.splice(endIndex, 0, removed);\n const reorderedSteps = steps.map((step, index) => ({\n ...step,\n stepOrder: index + 1,\n }));\n set({ stepSequence: reorderedSteps, isDirty: true });\n },\n\n addStep: (step) => {\n const steps = [...get().stepSequence];\n const newStep: TourStep = {\n id: `temp_${Date.now()}`,\n tourId: get().selectedTour?.id || '',\n title: step.title || 'New Step',\n content: step.content || '',\n stepOrder: steps.length + 1,\n targetSelector: step.targetSelector || '',\n placement: step.placement || 'AUTO',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n ...step,\n } as TourStep;\n steps.push(newStep);\n set({ stepSequence: steps, isDirty: true });\n },\n\n removeStep: (stepId) => {\n const steps = get().stepSequence.filter((s) => s.id !== stepId);\n const reorderedSteps = steps.map((step, index) => ({\n ...step,\n stepOrder: index + 1,\n }));\n set({ stepSequence: reorderedSteps, isDirty: true });\n },\n\n updateStep: (stepId, updates) => {\n const steps = get().stepSequence.map((step) =>\n step.id === stepId ? { ...step, ...updates } : step\n );\n set({ stepSequence: steps, isDirty: true });\n },\n\n // Validation actions\n setDirty: (dirty) => set({ isDirty: dirty }),\n\n validateTour: () => {\n const errors: ValidationError[] = [];\n const { selectedTour, stepSequence } = get();\n\n if (!selectedTour) {\n errors.push({ field: 'tour', message: 'validation.noTourSelected' });\n set({ validationErrors: errors });\n return errors;\n }\n\n if (!selectedTour.name || selectedTour.name.trim() === '') {\n errors.push({ field: 'name', message: 'validation.tourNameRequired' });\n }\n\n if (!selectedTour.productId) {\n errors.push({ field: 'productId', message: 'validation.productIdRequired' });\n }\n\n if (stepSequence.length === 0) {\n errors.push({ field: 'steps', message: 'validation.stepsRequired' });\n }\n\n stepSequence.forEach((step, index) => {\n const number = index + 1;\n if (!step.title || step.title.trim() === '') {\n errors.push({\n field: `step[${index}].title`,\n message: 'validation.stepTitleRequired',\n params: { number },\n });\n }\n if (!step.content || step.content.trim() === '') {\n errors.push({\n field: `step[${index}].content`,\n message: 'validation.stepContentRequired',\n params: { number },\n });\n }\n if (\n step.placement !== 'CENTER' &&\n (!step.targetSelector || step.targetSelector.trim() === '')\n ) {\n errors.push({\n field: `step[${index}].targetSelector`,\n message: 'validation.stepSelectorRequired',\n params: { number },\n });\n }\n });\n\n set({ validationErrors: errors });\n return errors;\n },\n\n clearValidationErrors: () => set({ validationErrors: [] }),\n\n // Media actions\n selectMedia: (media) => {\n const selected = [...get().selectedMedia];\n if (!selected.find((m) => m.id === media.id)) {\n selected.push(media);\n set({ selectedMedia: selected });\n }\n },\n\n unselectMedia: (mediaId) => {\n set({\n selectedMedia: get().selectedMedia.filter((m) => m.id !== mediaId),\n });\n },\n\n clearSelectedMedia: () => set({ selectedMedia: [] }),\n\n updateUploadProgress: (fileId, progress) => {\n set({\n uploadProgress: { ...get().uploadProgress, [fileId]: progress },\n });\n },\n\n removeUploadProgress: (fileId) => {\n const progress = { ...get().uploadProgress };\n delete progress[fileId];\n set({ uploadProgress: progress });\n },\n}));\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { BurdenoffEventName, EventBus } from '@burdenoff/fe-libs/shared/events';
2
+ import { ToursRootProps } from './types';
3
+ export declare const TOURS_RYBBIT_ALLOWLIST: BurdenoffEventName[];
4
+ export type ToursApiOperation = 'tour.save' | 'step.save' | 'media.attach' | 'media.upload' | 'schedule.create' | 'template.create_from_template' | 'template.delete' | 'template.save';
5
+ type ToursTelemetryContext = Pick<ToursRootProps, 'productId' | 'workspaceId'>;
6
+ export declare function emitToursApiFailed(eventBus: EventBus, operation: ToursApiOperation, context: ToursTelemetryContext): void;
7
+ export {};
@@ -1,4 +1,4 @@
1
- //#region src/tours/telemetry.ts
1
+ // src/tours/telemetry.ts
2
2
  var e = [
3
3
  "tours.page.viewed",
4
4
  "tours.tour.started",
@@ -13,7 +13,6 @@ function t(e, t, n) {
13
13
  workspaceId: n.workspaceId
14
14
  }, { component: "microfe-tours" });
15
15
  }
16
- //#endregion
17
16
  export { e as TOURS_RYBBIT_ALLOWLIST, t as emitToursApiFailed };
18
17
 
19
18
  //# sourceMappingURL=telemetry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.js","names":[],"sources":["../../src/tours/telemetry.ts"],"sourcesContent":["import type { BurdenoffEventName, EventBus } from '@burdenoff/fe-libs/shared/events';\nimport type { ToursRootProps } from './types';\n\nexport const TOURS_RYBBIT_ALLOWLIST: BurdenoffEventName[] = [\n 'tours.page.viewed',\n 'tours.tour.started',\n 'tours.tour.completed',\n 'tours.tour.skipped',\n];\n\nexport type ToursApiOperation =\n | 'tour.save'\n | 'step.save'\n | 'media.attach'\n | 'media.upload'\n | 'schedule.create'\n | 'template.create_from_template'\n | 'template.delete'\n | 'template.save';\n\ntype ToursTelemetryContext = Pick<ToursRootProps, 'productId' | 'workspaceId'>;\n\nexport function emitToursApiFailed(\n eventBus: EventBus,\n operation: ToursApiOperation,\n context: ToursTelemetryContext\n) {\n eventBus.emit(\n 'tours.api.failed',\n {\n // The event registry currently allows only user-facing Tours routes.\n // App-shell event metadata still carries the exact browser pathname.\n route: '/tours',\n operation,\n productId: context.productId,\n workspaceId: context.workspaceId,\n },\n { component: 'microfe-tours' }\n );\n}\n"],"mappings":";AAGA,IAAa,IAA+C;CAC1D;CACA;CACA;CACA;CACD;AAcD,SAAgB,EACd,GACA,GACA,GACA;AACA,GAAS,KACP,oBACA;EAGE,OAAO;EACP;EACA,WAAW,EAAQ;EACnB,aAAa,EAAQ;EACtB,EACD,EAAE,WAAW,iBAAiB,CAC/B"}
1
+ {"version":3,"file":"telemetry.js","names":["TOURS_RYBBIT_ALLOWLIST","emitToursApiFailed","context"],"sources":["../../src/tours/telemetry.ts"],"sourcesContent":["import type { BurdenoffEventName, EventBus } from '@burdenoff/fe-libs/shared/events';\nimport type { ToursRootProps } from './types';\n\nexport const TOURS_RYBBIT_ALLOWLIST: BurdenoffEventName[] = [\n 'tours.page.viewed',\n 'tours.tour.started',\n 'tours.tour.completed',\n 'tours.tour.skipped',\n];\n\nexport type ToursApiOperation =\n | 'tour.save'\n | 'step.save'\n | 'media.attach'\n | 'media.upload'\n | 'schedule.create'\n | 'template.create_from_template'\n | 'template.delete'\n | 'template.save';\n\ntype ToursTelemetryContext = Pick<ToursRootProps, 'productId' | 'workspaceId'>;\n\nexport function emitToursApiFailed(\n eventBus: EventBus,\n operation: ToursApiOperation,\n context: ToursTelemetryContext\n) {\n eventBus.emit(\n 'tours.api.failed',\n {\n // The event registry currently allows only user-facing Tours routes.\n // App-shell event metadata still carries the exact browser pathname.\n route: '/tours',\n operation,\n productId: context.productId,\n workspaceId: context.workspaceId,\n },\n { component: 'microfe-tours' }\n );\n}\n"],"mappings":";AAGA,IAAaA,IAA+C;CAC1D;CACA;CACA;CACA;AACF;AAcA,SAAgBC,EACd,GACA,GACA,GACA;CACA,EAAS,KACP,oBACA;EAGE,OAAO;EACP;EACA,WAAWC,EAAQ;EACnB,aAAaA,EAAQ;CACvB,GACA,EAAE,WAAW,gBAAgB,CAC/B;AACF"}
@@ -0,0 +1,348 @@
1
+ import { ToursTranslationKey } from '../i18n';
2
+ export interface NavItem {
3
+ id: string;
4
+ label: string;
5
+ path: string;
6
+ icon?: React.ReactNode;
7
+ order?: number;
8
+ group?: string;
9
+ }
10
+ /**
11
+ * Footer item for shell integration
12
+ */
13
+ export interface FooterItem {
14
+ id: string;
15
+ label: string;
16
+ type: 'status' | 'action';
17
+ order?: number;
18
+ section: 'left' | 'center' | 'right';
19
+ }
20
+ /**
21
+ * Base microfrontend root props
22
+ */
23
+ export interface MicrofrontendRootProps {
24
+ }
25
+ /**
26
+ * Tours UI variant — selects which surface area is rendered.
27
+ *
28
+ * - "admin" — full authoring suite (CRUD, media library, scheduler, analytics).
29
+ * Consumed by `ToursAdminRoot` from `admin-app`.
30
+ * - "user" — read-only "My Tours" + "My Progress" surface. Consumed by
31
+ * `ToursUserRoot` from product user shells (e.g., vibecontrols-app).
32
+ *
33
+ * Defaults to "admin" for back-compatibility with the original `ToursRoot`.
34
+ */
35
+ export type ToursVariant = 'admin' | 'user';
36
+ /**
37
+ * Props for the ToursRoot component
38
+ */
39
+ export interface ToursRootProps extends MicrofrontendRootProps {
40
+ /**
41
+ * Base path for the tours microfrontend
42
+ */
43
+ basePath?: string;
44
+ /**
45
+ * Which Tours surface to render. Defaults to "admin".
46
+ * @see ToursVariant
47
+ */
48
+ variant?: ToursVariant;
49
+ /**
50
+ * Product identifier the shell is mounting this MFE under (e.g., "workspaces",
51
+ * "vibecontrols", "bigconsole"). Used by `MyToursPage.toursForContext` to
52
+ * scope the listed tours to the right product — NOT a user-controlled query
53
+ * param. Shells should pass the product slug they are themselves known as.
54
+ */
55
+ productId?: string;
56
+ /**
57
+ * Optional module identifier within the product (e.g., "calendar"). Narrows
58
+ * the tour list further when the shell can supply it.
59
+ */
60
+ moduleId?: string;
61
+ /**
62
+ * Optional page identifier within the module (e.g., "settings/team"). Narrows
63
+ * the tour list to tours scoped at the current page.
64
+ */
65
+ pageId?: string;
66
+ /**
67
+ * Current workspace ID (fallback scope for embedded contexts)
68
+ */
69
+ workspaceId?: string;
70
+ /**
71
+ * Current tenant ID (preferred scope for global permission checks)
72
+ */
73
+ tenantId?: string;
74
+ /**
75
+ * Current organization ID (fallback scope for global permission checks)
76
+ */
77
+ organizationId?: string;
78
+ /**
79
+ * API Gateway URL for GraphQL requests
80
+ */
81
+ apiGatewayUrl?: string;
82
+ /**
83
+ * Authentication token for API requests
84
+ */
85
+ authToken?: string;
86
+ /**
87
+ * Navigation function provided by the shell
88
+ */
89
+ navigate?: (path: string) => void;
90
+ /**
91
+ * Current user information
92
+ */
93
+ currentUser?: {
94
+ id: string;
95
+ email: string;
96
+ firstName?: string;
97
+ lastName?: string;
98
+ };
99
+ /**
100
+ * Optional function to register navigation items with the shell
101
+ * Injected by the shell app
102
+ */
103
+ registerNavItems?: (items: NavItem | NavItem[]) => () => void;
104
+ /**
105
+ * Optional function to register footer items with the shell
106
+ * Injected by the shell app
107
+ */
108
+ registerFooterItems?: (items: FooterItem | FooterItem[]) => () => void;
109
+ }
110
+ /**
111
+ * Tour status enumeration
112
+ */
113
+ export type TourStatus = 'DRAFT' | 'PUBLISHED' | 'ARCHIVED' | 'SCHEDULED';
114
+ /**
115
+ * Tour trigger type enumeration
116
+ */
117
+ export type TourTriggerType = 'MANUAL' | 'AUTO_FIRST_VISIT' | 'AUTO_ALWAYS' | 'ON_EVENT' | 'SCHEDULED';
118
+ /**
119
+ * Step placement enumeration
120
+ */
121
+ export type StepPlacement = 'AUTO' | 'TOP' | 'BOTTOM' | 'LEFT' | 'RIGHT' | 'CENTER';
122
+ /**
123
+ * Media type enumeration
124
+ */
125
+ export type TourMediaType = 'IMAGE' | 'VIDEO' | 'AUDIO' | 'DOCUMENT';
126
+ /**
127
+ * Device type enumeration
128
+ */
129
+ export type DeviceType = 'mobile' | 'tablet' | 'desktop';
130
+ /**
131
+ * Media position within step
132
+ */
133
+ export type MediaPosition = 'header' | 'body' | 'inline' | 'footer';
134
+ /**
135
+ * Tour template category
136
+ */
137
+ export type TemplateCategory = 'ONBOARDING' | 'FEATURE_ANNOUNCEMENT' | 'HELP' | 'CUSTOM';
138
+ /**
139
+ * Recurrence pattern for scheduled tours
140
+ */
141
+ export type RecurrencePattern = 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'CUSTOM';
142
+ /**
143
+ * Tour entity
144
+ */
145
+ export interface Tour {
146
+ id: string;
147
+ name: string;
148
+ slug: string;
149
+ description?: string;
150
+ productId: string;
151
+ moduleId?: string;
152
+ pageId?: string;
153
+ status: TourStatus;
154
+ isActive: boolean;
155
+ triggerType: TourTriggerType;
156
+ triggerConfig?: Record<string, unknown>;
157
+ conditions?: Record<string, unknown>;
158
+ config?: Record<string, unknown>;
159
+ mobileConfig?: Record<string, unknown>;
160
+ tabletConfig?: Record<string, unknown>;
161
+ desktopConfig?: Record<string, unknown>;
162
+ priority: number;
163
+ displayOrder: number;
164
+ startDate?: string;
165
+ endDate?: string;
166
+ viewCount: number;
167
+ completionCount: number;
168
+ skipCount: number;
169
+ steps?: TourStep[];
170
+ schedules?: TourSchedule[];
171
+ metadata?: Record<string, unknown>;
172
+ version: number;
173
+ createdBy: string;
174
+ updatedBy?: string;
175
+ createdAt: string;
176
+ updatedAt: string;
177
+ }
178
+ /**
179
+ * Tour step entity
180
+ */
181
+ export interface TourStep {
182
+ id: string;
183
+ tourId: string;
184
+ title: string;
185
+ content: string;
186
+ stepOrder: number;
187
+ targetSelector?: string;
188
+ placement: StepPlacement;
189
+ targetSelectorMobile?: string;
190
+ targetSelectorTablet?: string;
191
+ targetSelectorDesktop?: string;
192
+ customData?: Record<string, unknown>;
193
+ actions?: Record<string, unknown>;
194
+ requireInteraction?: boolean;
195
+ waitForSelector?: string;
196
+ waitForEvent?: string;
197
+ waitForTimeout?: number;
198
+ skipConditions?: Record<string, unknown>;
199
+ media?: TourStepMedia[];
200
+ metadata?: Record<string, unknown>;
201
+ createdAt: string;
202
+ updatedAt: string;
203
+ }
204
+ /**
205
+ * Tour media entity
206
+ */
207
+ export interface TourMedia {
208
+ id: string;
209
+ name: string;
210
+ type: TourMediaType;
211
+ cdnUrl: string;
212
+ mimeType: string;
213
+ size: number;
214
+ duration?: number;
215
+ width?: number;
216
+ height?: number;
217
+ altText?: string;
218
+ caption?: string;
219
+ transcript?: string;
220
+ metadata?: Record<string, unknown>;
221
+ createdBy: string;
222
+ createdAt: string;
223
+ updatedAt: string;
224
+ }
225
+ /**
226
+ * Tour step media (junction entity)
227
+ */
228
+ export interface TourStepMedia {
229
+ id: string;
230
+ stepId: string;
231
+ mediaId: string;
232
+ order?: number;
233
+ position: MediaPosition;
234
+ media: TourMedia;
235
+ }
236
+ /**
237
+ * Tour schedule entity
238
+ */
239
+ export interface TourSchedule {
240
+ id: string;
241
+ tourId: string;
242
+ startDate: string;
243
+ endDate?: string;
244
+ recurrenceRule?: string;
245
+ timezone: string;
246
+ isActive: boolean;
247
+ nextExecutionAt?: string;
248
+ executionCount?: number;
249
+ createdAt: string;
250
+ updatedAt: string;
251
+ }
252
+ /**
253
+ * Tour template entity
254
+ */
255
+ export interface TourTemplate {
256
+ id: string;
257
+ name: string;
258
+ slug: string;
259
+ description?: string;
260
+ category: TemplateCategory;
261
+ tags: string[];
262
+ thumbnail?: string;
263
+ config: Record<string, unknown>;
264
+ isPublic: boolean;
265
+ isPremium: boolean;
266
+ useCount: number;
267
+ createdBy: string;
268
+ createdAt: string;
269
+ updatedAt: string;
270
+ }
271
+ /**
272
+ * Tour analytics data
273
+ */
274
+ export interface TourAnalytics {
275
+ tourId: string;
276
+ totalViews: number;
277
+ totalCompletions: number;
278
+ totalSkips: number;
279
+ averageCompletionRate: number;
280
+ averageDuration: number;
281
+ byDeviceType: Array<{
282
+ deviceType: DeviceType;
283
+ count: number;
284
+ completionRate: number;
285
+ }>;
286
+ byEventType: Array<{
287
+ eventType: string;
288
+ count: number;
289
+ }>;
290
+ stepAnalytics?: Array<{
291
+ stepId: string;
292
+ stepOrder: number;
293
+ views: number;
294
+ completions: number;
295
+ dropOffRate: number;
296
+ averageDuration: number;
297
+ }>;
298
+ }
299
+ /**
300
+ * Tour filters for queries
301
+ */
302
+ export interface TourFilters {
303
+ search?: string;
304
+ status?: TourStatus | 'all';
305
+ productId?: string;
306
+ moduleId?: string;
307
+ pageId?: string;
308
+ dateRange?: {
309
+ start: string | null;
310
+ end: string | null;
311
+ };
312
+ createdBy?: string;
313
+ }
314
+ /**
315
+ * Pagination for tour queries
316
+ */
317
+ export interface TourPagination {
318
+ page: number;
319
+ limit: number;
320
+ }
321
+ /**
322
+ * Tours query result
323
+ */
324
+ export interface ToursQueryResult {
325
+ tours: Tour[];
326
+ total: number;
327
+ hasMore: boolean;
328
+ }
329
+ /**
330
+ * Validation error
331
+ */
332
+ export interface ValidationError {
333
+ field: string;
334
+ message: ToursTranslationKey;
335
+ params?: Record<string, string | number>;
336
+ }
337
+ /**
338
+ * View mode for tours list
339
+ */
340
+ export type ToursViewMode = 'grid' | 'list';
341
+ /**
342
+ * Tab in step editor
343
+ */
344
+ export type StepEditorTab = 'content' | 'targeting' | 'actions' | 'advanced';
345
+ /**
346
+ * Tour builder step
347
+ */
348
+ export type TourBuilderStep = 'info' | 'steps' | 'targeting' | 'schedule' | 'preview';
@@ -0,0 +1,28 @@
1
+ import { ToursTranslationKey } from '../i18n';
2
+ type TranslateFn = (key: ToursTranslationKey) => string;
3
+ /**
4
+ * Format file size
5
+ */
6
+ export declare function formatFileSize(bytes: number, t?: TranslateFn): string;
7
+ /**
8
+ * Format duration (seconds to human readable)
9
+ */
10
+ export declare function formatDuration(seconds: number, t?: TranslateFn): string;
11
+ /**
12
+ * Format percentage
13
+ */
14
+ export declare function formatPercentage(value: number, decimals?: number): string;
15
+ /**
16
+ * Format number with locale-aware separators
17
+ */
18
+ export declare function formatNumber(num: number): string;
19
+ /**
20
+ * Hook that returns formatter helpers bound to the current translation context.
21
+ */
22
+ export declare function useToursFormatters(): {
23
+ formatFileSize: (bytes: number) => string;
24
+ formatDuration: (seconds: number) => string;
25
+ formatNumber: typeof formatNumber;
26
+ formatPercentage: typeof formatPercentage;
27
+ };
28
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { useToursI18n as e } from "../i18n.js";
2
2
  import { useMemo as t } from "react";
3
- //#region src/tours/utils/index.ts
3
+ // src/tours/utils/index.ts
4
4
  function n(e, t) {
5
5
  let n = t ? t("common.fileSize.bytes") : "Bytes";
6
6
  if (e === 0) return `0 ${n}`;
@@ -10,7 +10,7 @@ function n(e, t) {
10
10
  t ? t("common.fileSize.mb") : "MB",
11
11
  t ? t("common.fileSize.gb") : "GB"
12
12
  ], a = Math.floor(Math.log(e) / Math.log(r));
13
- return Math.round(e / r ** +a * 100) / 100 + " " + i[a];
13
+ return Math.round(e / r ** a * 100) / 100 + " " + i[a];
14
14
  }
15
15
  function r(e, t) {
16
16
  let n = t ? t("common.duration.seconds") : "s", r = t ? t("common.duration.minutes") : "m", i = t ? t("common.duration.hours") : "h";
@@ -35,7 +35,6 @@ function o() {
35
35
  formatPercentage: i
36
36
  }), [o]);
37
37
  }
38
- //#endregion
39
- export { o as useToursFormatters };
38
+ export { r as formatDuration, n as formatFileSize, a as formatNumber, i as formatPercentage, o as useToursFormatters };
40
39
 
41
40
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/tours/utils/index.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport { useToursI18n, type ToursTranslationKey } from '../i18n';\n\ntype TranslateFn = (key: ToursTranslationKey) => string;\n\n/**\n * Format file size\n */\nexport function formatFileSize(bytes: number, t?: TranslateFn): string {\n const zeroLabel = t ? t('common.fileSize.bytes') : 'Bytes';\n if (bytes === 0) return `0 ${zeroLabel}`;\n\n const k = 1024;\n const sizes = [\n t ? t('common.fileSize.bytes') : 'Bytes',\n t ? t('common.fileSize.kb') : 'KB',\n t ? t('common.fileSize.mb') : 'MB',\n t ? t('common.fileSize.gb') : 'GB',\n ];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];\n}\n\n/**\n * Format duration (seconds to human readable)\n */\nexport function formatDuration(seconds: number, t?: TranslateFn): string {\n const secLabel = t ? t('common.duration.seconds') : 's';\n const minLabel = t ? t('common.duration.minutes') : 'm';\n const hrLabel = t ? t('common.duration.hours') : 'h';\n\n if (seconds < 60) {\n return `${Math.round(seconds)}${secLabel}`;\n }\n\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = Math.round(seconds % 60);\n\n if (minutes < 60) {\n return remainingSeconds > 0\n ? `${minutes}${minLabel} ${remainingSeconds}${secLabel}`\n : `${minutes}${minLabel}`;\n }\n\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n\n return remainingMinutes > 0\n ? `${hours}${hrLabel} ${remainingMinutes}${minLabel}`\n : `${hours}${hrLabel}`;\n}\n\n/**\n * Format percentage\n */\nexport function formatPercentage(value: number, decimals: number = 1): string {\n return `${value.toFixed(decimals)}%`;\n}\n\n/**\n * Format number with locale-aware separators\n */\nexport function formatNumber(num: number): string {\n return num.toLocaleString();\n}\n\n/**\n * Hook that returns formatter helpers bound to the current translation context.\n */\nexport function useToursFormatters() {\n const { tr } = useToursI18n();\n\n return useMemo(\n () => ({\n formatFileSize: (bytes: number) => formatFileSize(bytes, tr),\n formatDuration: (seconds: number) => formatDuration(seconds, tr),\n formatNumber,\n formatPercentage,\n }),\n [tr]\n );\n}\n"],"mappings":";;;AAQA,SAAgB,EAAe,GAAe,GAAyB;CACrE,IAAM,IAAY,IAAI,EAAE,wBAAwB,GAAG;AACnD,KAAI,MAAU,EAAG,QAAO,KAAK;CAE7B,IAAM,IAAI,MACJ,IAAQ;EACZ,IAAI,EAAE,wBAAwB,GAAG;EACjC,IAAI,EAAE,qBAAqB,GAAG;EAC9B,IAAI,EAAE,qBAAqB,GAAG;EAC9B,IAAI,EAAE,qBAAqB,GAAG;EAC/B,EACK,IAAI,KAAK,MAAM,KAAK,IAAI,EAAM,GAAG,KAAK,IAAI,EAAE,CAAC;AAEnD,QAAO,KAAK,MAAO,IAAiB,MAAG,IAAM,IAAI,GAAG,MAAM,MAAM,EAAM;;AAMxE,SAAgB,EAAe,GAAiB,GAAyB;CACvE,IAAM,IAAW,IAAI,EAAE,0BAA0B,GAAG,KAC9C,IAAW,IAAI,EAAE,0BAA0B,GAAG,KAC9C,IAAU,IAAI,EAAE,wBAAwB,GAAG;AAEjD,KAAI,IAAU,GACZ,QAAO,GAAG,KAAK,MAAM,EAAQ,GAAG;CAGlC,IAAM,IAAU,KAAK,MAAM,IAAU,GAAG,EAClC,IAAmB,KAAK,MAAM,IAAU,GAAG;AAEjD,KAAI,IAAU,GACZ,QAAO,IAAmB,IACtB,GAAG,IAAU,EAAS,GAAG,IAAmB,MAC5C,GAAG,IAAU;CAGnB,IAAM,IAAQ,KAAK,MAAM,IAAU,GAAG,EAChC,IAAmB,IAAU;AAEnC,QAAO,IAAmB,IACtB,GAAG,IAAQ,EAAQ,GAAG,IAAmB,MACzC,GAAG,IAAQ;;AAMjB,SAAgB,EAAiB,GAAe,IAAmB,GAAW;AAC5E,QAAO,GAAG,EAAM,QAAQ,EAAS,CAAC;;AAMpC,SAAgB,EAAa,GAAqB;AAChD,QAAO,EAAI,gBAAgB;;AAM7B,SAAgB,IAAqB;CACnC,IAAM,EAAE,UAAO,GAAc;AAE7B,QAAO,SACE;EACL,iBAAiB,MAAkB,EAAe,GAAO,EAAG;EAC5D,iBAAiB,MAAoB,EAAe,GAAS,EAAG;EAChE;EACA;EACD,GACD,CAAC,EAAG,CACL"}
1
+ {"version":3,"file":"index.js","names":["formatFileSize","zeroLabel","bytes","k","sizes","i","formatDuration","secLabel","minLabel","hrLabel","seconds","minutes","remainingSeconds","hours","remainingMinutes","formatPercentage","value","decimals","formatNumber","num","useToursFormatters","useToursI18n","useMemo","tr"],"sources":["../../../src/tours/utils/index.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport { useToursI18n, type ToursTranslationKey } from '../i18n';\n\ntype TranslateFn = (key: ToursTranslationKey) => string;\n\n/**\n * Format file size\n */\nexport function formatFileSize(bytes: number, t?: TranslateFn): string {\n const zeroLabel = t ? t('common.fileSize.bytes') : 'Bytes';\n if (bytes === 0) return `0 ${zeroLabel}`;\n\n const k = 1024;\n const sizes = [\n t ? t('common.fileSize.bytes') : 'Bytes',\n t ? t('common.fileSize.kb') : 'KB',\n t ? t('common.fileSize.mb') : 'MB',\n t ? t('common.fileSize.gb') : 'GB',\n ];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];\n}\n\n/**\n * Format duration (seconds to human readable)\n */\nexport function formatDuration(seconds: number, t?: TranslateFn): string {\n const secLabel = t ? t('common.duration.seconds') : 's';\n const minLabel = t ? t('common.duration.minutes') : 'm';\n const hrLabel = t ? t('common.duration.hours') : 'h';\n\n if (seconds < 60) {\n return `${Math.round(seconds)}${secLabel}`;\n }\n\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = Math.round(seconds % 60);\n\n if (minutes < 60) {\n return remainingSeconds > 0\n ? `${minutes}${minLabel} ${remainingSeconds}${secLabel}`\n : `${minutes}${minLabel}`;\n }\n\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n\n return remainingMinutes > 0\n ? `${hours}${hrLabel} ${remainingMinutes}${minLabel}`\n : `${hours}${hrLabel}`;\n}\n\n/**\n * Format percentage\n */\nexport function formatPercentage(value: number, decimals: number = 1): string {\n return `${value.toFixed(decimals)}%`;\n}\n\n/**\n * Format number with locale-aware separators\n */\nexport function formatNumber(num: number): string {\n return num.toLocaleString();\n}\n\n/**\n * Hook that returns formatter helpers bound to the current translation context.\n */\nexport function useToursFormatters() {\n const { tr } = useToursI18n();\n\n return useMemo(\n () => ({\n formatFileSize: (bytes: number) => formatFileSize(bytes, tr),\n formatDuration: (seconds: number) => formatDuration(seconds, tr),\n formatNumber,\n formatPercentage,\n }),\n [tr]\n );\n}\n"],"mappings":";;;AAQA,SAAgBA,EAAe,GAAe,GAAyB;CACrE,IAAMC,IAAY,IAAI,EAAE,uBAAuB,IAAI;CACnD,IAAIC,MAAU,GAAG,OAAO,KAAKD;CAE7B,IAAME,IAAI,MACJC,IAAQ;EACZ,IAAI,EAAE,uBAAuB,IAAI;EACjC,IAAI,EAAE,oBAAoB,IAAI;EAC9B,IAAI,EAAE,oBAAoB,IAAI;EAC9B,IAAI,EAAE,oBAAoB,IAAI;CAChC,GACMC,IAAI,KAAK,MAAM,KAAK,IAAIH,CAAK,IAAI,KAAK,IAAIC,CAAC,CAAC;CAElD,OAAO,KAAK,MAAOD,IAAiBC,KAAGE,IAAM,GAAG,IAAI,MAAM,MAAMD,EAAMC;AACxE;AAKA,SAAgBC,EAAe,GAAiB,GAAyB;CACvE,IAAMC,IAAW,IAAI,EAAE,yBAAyB,IAAI,KAC9CC,IAAW,IAAI,EAAE,yBAAyB,IAAI,KAC9CC,IAAU,IAAI,EAAE,uBAAuB,IAAI;CAEjD,IAAIC,IAAU,IACZ,OAAO,GAAG,KAAK,MAAMA,CAAO,IAAIH;CAGlC,IAAMI,IAAU,KAAK,MAAMD,IAAU,EAAE,GACjCE,IAAmB,KAAK,MAAMF,IAAU,EAAE;CAEhD,IAAIC,IAAU,IACZ,OAAOC,IAAmB,IACtB,GAAGD,IAAUH,EAAS,GAAGI,IAAmBL,MAC5C,GAAGI,IAAUH;CAGnB,IAAMK,IAAQ,KAAK,MAAMF,IAAU,EAAE,GAC/BG,IAAmBH,IAAU;CAEnC,OAAOG,IAAmB,IACtB,GAAGD,IAAQJ,EAAQ,GAAGK,IAAmBN,MACzC,GAAGK,IAAQJ;AACjB;AAKA,SAAgBM,EAAiB,GAAe,IAAmB,GAAW;CAC5E,OAAO,GAAGC,EAAM,QAAQC,CAAQ,EAAE;AACpC;AAKA,SAAgBC,EAAa,GAAqB;CAChD,OAAOC,EAAI,eAAe;AAC5B;AAKA,SAAgBC,IAAqB;CACnC,IAAM,EAAE,UAAOC,EAAa;CAE5B,OAAOC,SACE;EACL,iBAAiB,MAAkBtB,EAAeE,GAAOqB,CAAE;EAC3D,iBAAiB,MAAoBjB,EAAeI,GAASa,CAAE;EAC/D;EACA;CACF,IACA,CAACA,CAAE,CACL;AACF"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Join a shell `basePath` (e.g. `/tours`) with a route fragment, collapsing
3
+ * duplicate slashes and handling leading/trailing-slash variants safely.
4
+ *
5
+ * Extracted so ToursAdminRoot and ToursUserRoot share one implementation
6
+ * instead of maintaining copy-pasted helpers.
7
+ */
8
+ export declare function joinPath(base: string, path: string): string;
@@ -1,8 +1,7 @@
1
- //#region src/tours/utils/joinPath.ts
1
+ // src/tours/utils/joinPath.ts
2
2
  function e(e, t) {
3
3
  return (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
4
4
  }
5
- //#endregion
6
5
  export { e as joinPath };
7
6
 
8
7
  //# sourceMappingURL=joinPath.js.map