@burdenoff/microfe-tours 2026.527.2 → 2026.527.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/tours/ToursUserRoutes.js +2 -4
  2. package/dist/tours/ToursUserRoutes.js.map +1 -1
  3. package/dist/tours/components/RouteHelpers.js +4 -4
  4. package/dist/tours/components/RouteHelpers.js.map +1 -1
  5. package/dist/tours/hooks/useTourPreview.js +39 -39
  6. package/dist/tours/hooks/useTourPreview.js.map +1 -1
  7. package/dist/tours/hooks/useToursList.js.map +1 -1
  8. package/dist/tours/pages/MediaLibraryPage.js +11 -8
  9. package/dist/tours/pages/MediaLibraryPage.js.map +1 -1
  10. package/dist/tours/pages/MyToursPage.js +59 -57
  11. package/dist/tours/pages/MyToursPage.js.map +1 -1
  12. package/dist/tours/pages/MyToursProgressPage.js +65 -61
  13. package/dist/tours/pages/MyToursProgressPage.js.map +1 -1
  14. package/dist/tours/pages/SchedulerPage.js +18 -4
  15. package/dist/tours/pages/SchedulerPage.js.map +1 -1
  16. package/dist/tours/pages/StepEditorPage.js +330 -276
  17. package/dist/tours/pages/StepEditorPage.js.map +1 -1
  18. package/dist/tours/pages/TemplateLibraryPage.js +7 -2
  19. package/dist/tours/pages/TemplateLibraryPage.js.map +1 -1
  20. package/dist/tours/pages/TourBuilderPage.js +25 -11
  21. package/dist/tours/pages/TourBuilderPage.js.map +1 -1
  22. package/dist/tours/pages/TourPreviewPage.js +12 -4
  23. package/dist/tours/pages/TourPreviewPage.js.map +1 -1
  24. package/dist/tours/pages/ToursListPage.js +165 -149
  25. package/dist/tours/pages/ToursListPage.js.map +1 -1
  26. package/dist/tours/providers/ToursProvider.js +1 -1
  27. package/dist/tours/providers/ToursProvider.js.map +1 -1
  28. package/dist/tours/utils/index.js.map +1 -1
  29. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ToursProvider.js","names":[],"sources":["../../../src/tours/providers/ToursProvider.tsx"],"sourcesContent":["import { createContext, useContext, type ReactNode } from 'react';\nimport { MemoryRouter } from 'react-router-dom';\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 = useContext(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,EAAW,EAAa;AACxC,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":[],"sources":["../../../src/tours/providers/ToursProvider.tsx"],"sourcesContent":["import { createContext, use, type ReactNode } from 'react';\nimport { MemoryRouter } from 'react-router-dom';\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 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/tours/utils/index.ts"],"sourcesContent":["import type { TourStatus, TourTriggerType, DeviceType, StepPlacement } from '../types';\n\n/**\n * Format tour status for display\n */\nexport function formatTourStatus(status: TourStatus): string {\n const statusMap: Record<TourStatus, string> = {\n DRAFT: 'Draft',\n PUBLISHED: 'Published',\n ARCHIVED: 'Archived',\n SCHEDULED: 'Scheduled',\n };\n return statusMap[status] || status;\n}\n\n/**\n * Get status color variant\n */\nexport function getStatusColor(\n status: TourStatus\n): 'default' | 'success' | 'warning' | 'destructive' {\n const colorMap: Record<TourStatus, 'default' | 'success' | 'warning' | 'destructive'> = {\n DRAFT: 'default',\n PUBLISHED: 'success',\n ARCHIVED: 'warning',\n SCHEDULED: 'default',\n };\n return colorMap[status] || 'default';\n}\n\n/**\n * Format trigger type for display\n */\nexport function formatTriggerType(triggerType: TourTriggerType): string {\n const triggerMap: Record<TourTriggerType, string> = {\n MANUAL: 'Manual',\n AUTO_FIRST_VISIT: 'Auto (First Visit)',\n AUTO_ALWAYS: 'Auto (Always)',\n ON_EVENT: 'On Event',\n SCHEDULED: 'Scheduled',\n };\n return triggerMap[triggerType] || triggerType;\n}\n\n/**\n * Format device type for display\n */\nexport function formatDeviceType(device: DeviceType): string {\n const deviceMap: Record<DeviceType, string> = {\n mobile: 'Mobile',\n tablet: 'Tablet',\n desktop: 'Desktop',\n };\n return deviceMap[device] || device;\n}\n\n/**\n * Format step placement for display\n */\nexport function formatPlacement(placement: StepPlacement): string {\n const placementMap: Record<StepPlacement, string> = {\n AUTO: 'Auto',\n TOP: 'Top',\n BOTTOM: 'Bottom',\n LEFT: 'Left',\n RIGHT: 'Right',\n CENTER: 'Center',\n };\n return placementMap[placement] || placement;\n}\n\n/**\n * Format file size\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 Bytes';\n\n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB'];\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): string {\n if (seconds < 60) {\n return `${Math.round(seconds)}s`;\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 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;\n }\n\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n\n return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;\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 commas\n */\nexport function formatNumber(num: number): string {\n return num.toLocaleString('en-US');\n}\n\n/**\n * Generate slug from string\n */\nexport function generateSlug(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_-]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n/**\n * Validate CSS selector\n */\nexport function isValidSelector(selector: string): boolean {\n if (!selector || selector.trim() === '') return false;\n\n try {\n document.querySelector(selector);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validate email\n */\nexport function isValidEmail(email: string): boolean {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n return emailRegex.test(email);\n}\n\n/**\n * Validate URL\n */\nexport function isValidUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Truncate text\n */\nexport function truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.substring(0, maxLength - 3) + '...';\n}\n\n/**\n * Get relative time string\n */\nexport function getRelativeTime(date: string | Date): string {\n const now = new Date();\n const then = typeof date === 'string' ? new Date(date) : date;\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000);\n\n if (diffInSeconds < 60) return 'just now';\n if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;\n if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;\n if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;\n if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 604800)}w ago`;\n if (diffInSeconds < 31536000) return `${Math.floor(diffInSeconds / 2592000)}mo ago`;\n return `${Math.floor(diffInSeconds / 31536000)}y ago`;\n}\n\n/**\n * Deep clone object\n */\nexport function deepClone<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj));\n}\n\n/**\n * Debounce function\n */\nexport function debounce<T extends (...args: unknown[]) => unknown>(\n func: T,\n wait: number\n): (...args: Parameters<T>) => void {\n let timeout: ReturnType<typeof setTimeout>;\n return (...args: Parameters<T>) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => func(...args), wait);\n };\n}\n\n/**\n * Throttle function\n */\nexport function throttle<T extends (...args: unknown[]) => unknown>(\n func: T,\n limit: number\n): (...args: Parameters<T>) => void {\n let inThrottle: boolean;\n return (...args: Parameters<T>) => {\n if (!inThrottle) {\n func(...args);\n inThrottle = true;\n setTimeout(() => (inThrottle = false), limit);\n }\n };\n}\n\n/**\n * Generate unique ID\n */\nexport function generateId(prefix: string = 'id'): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Check if object is empty\n */\nexport function isEmpty(obj: object): boolean {\n return Object.keys(obj).length === 0;\n}\n\n/**\n * Get device viewport dimensions\n */\nexport function getDeviceViewport(device: DeviceType): { width: number; height: number } {\n const viewports: Record<DeviceType, { width: number; height: number }> = {\n mobile: { width: 375, height: 667 }, // iPhone SE\n tablet: { width: 768, height: 1024 }, // iPad\n desktop: { width: 1920, height: 1080 }, // Full HD\n };\n return viewports[device];\n}\n\n/**\n * Sanitize HTML content\n */\nexport function sanitizeHtml(html: string): string {\n // This is a basic implementation. In production, use a library like DOMPurify\n const tempDiv = document.createElement('div');\n tempDiv.textContent = html;\n return tempDiv.innerHTML;\n}\n\n/**\n * Calculate completion rate\n */\nexport function calculateCompletionRate(completions: number, views: number): number {\n if (views === 0) return 0;\n return (completions / views) * 100;\n}\n\n/**\n * Calculate drop-off rate\n */\nexport function calculateDropOffRate(currentViews: number, previousViews: number): number {\n if (previousViews === 0) return 0;\n const dropOff = previousViews - currentViews;\n return (dropOff / previousViews) * 100;\n}\n"],"mappings":";AA0EA,SAAgB,EAAe,GAAuB;AACpD,KAAI,MAAU,EAAG,QAAO;CAExB,IAAM,IAAI,MACJ,IAAQ;EAAC;EAAS;EAAM;EAAM;EAAK,EACnC,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,GAAyB;AACtD,KAAI,IAAU,GACZ,QAAO,GAAG,KAAK,MAAM,EAAQ,CAAC;CAGhC,IAAM,IAAU,KAAK,MAAM,IAAU,GAAG,EAClC,IAAmB,KAAK,MAAM,IAAU,GAAG;AAEjD,KAAI,IAAU,GACZ,QAAO,IAAmB,IAAI,GAAG,EAAQ,IAAI,EAAiB,KAAK,GAAG,EAAQ;CAGhF,IAAM,IAAQ,KAAK,MAAM,IAAU,GAAG,EAChC,IAAmB,IAAU;AAEnC,QAAO,IAAmB,IAAI,GAAG,EAAM,IAAI,EAAiB,KAAK,GAAG,EAAM;;AAM5E,SAAgB,EAAiB,GAAe,IAAmB,GAAW;AAC5E,QAAO,GAAG,EAAM,QAAQ,EAAS,CAAC;;AAMpC,SAAgB,EAAa,GAAqB;AAChD,QAAO,EAAI,eAAe,QAAQ"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/tours/utils/index.ts"],"sourcesContent":["/**\n * Format file size\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 Bytes';\n\n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB'];\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): string {\n if (seconds < 60) {\n return `${Math.round(seconds)}s`;\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 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;\n }\n\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n\n return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;\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 commas\n */\nexport function formatNumber(num: number): string {\n return num.toLocaleString('en-US');\n}\n"],"mappings":";AAGA,SAAgB,EAAe,GAAuB;AACpD,KAAI,MAAU,EAAG,QAAO;CAExB,IAAM,IAAI,MACJ,IAAQ;EAAC;EAAS;EAAM;EAAM;EAAK,EACnC,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,GAAyB;AACtD,KAAI,IAAU,GACZ,QAAO,GAAG,KAAK,MAAM,EAAQ,CAAC;CAGhC,IAAM,IAAU,KAAK,MAAM,IAAU,GAAG,EAClC,IAAmB,KAAK,MAAM,IAAU,GAAG;AAEjD,KAAI,IAAU,GACZ,QAAO,IAAmB,IAAI,GAAG,EAAQ,IAAI,EAAiB,KAAK,GAAG,EAAQ;CAGhF,IAAM,IAAQ,KAAK,MAAM,IAAU,GAAG,EAChC,IAAmB,IAAU;AAEnC,QAAO,IAAmB,IAAI,GAAG,EAAM,IAAI,EAAiB,KAAK,GAAG,EAAM;;AAM5E,SAAgB,EAAiB,GAAe,IAAmB,GAAW;AAC5E,QAAO,GAAG,EAAM,QAAQ,EAAS,CAAC;;AAMpC,SAAgB,EAAa,GAAqB;AAChD,QAAO,EAAI,eAAe,QAAQ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-tours",
3
- "version": "2026.527.2",
3
+ "version": "2026.527.3",
4
4
  "description": "Burdenoff Tours Microfrontend - Complete product tours and onboarding interface",
5
5
  "type": "module",
6
6
  "files": [