@burdenoff/microfe-tours 2026.527.1 → 2026.527.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tours/ToursUserRoutes.js +2 -4
- package/dist/tours/ToursUserRoutes.js.map +1 -1
- package/dist/tours/components/RouteHelpers.js +4 -4
- package/dist/tours/components/RouteHelpers.js.map +1 -1
- package/dist/tours/hooks/useTourPreview.js +39 -39
- package/dist/tours/hooks/useTourPreview.js.map +1 -1
- package/dist/tours/hooks/useToursList.js.map +1 -1
- package/dist/tours/i18n.js +4 -0
- package/dist/tours/i18n.js.map +1 -1
- package/dist/tours/pages/MediaLibraryPage.js +11 -8
- package/dist/tours/pages/MediaLibraryPage.js.map +1 -1
- package/dist/tours/pages/MyToursPage.js +59 -57
- package/dist/tours/pages/MyToursPage.js.map +1 -1
- package/dist/tours/pages/MyToursProgressPage.js +65 -61
- package/dist/tours/pages/MyToursProgressPage.js.map +1 -1
- package/dist/tours/pages/SchedulerPage.js +18 -4
- package/dist/tours/pages/SchedulerPage.js.map +1 -1
- package/dist/tours/pages/StepEditorPage.js +330 -276
- package/dist/tours/pages/StepEditorPage.js.map +1 -1
- package/dist/tours/pages/TemplateLibraryPage.js +7 -2
- package/dist/tours/pages/TemplateLibraryPage.js.map +1 -1
- package/dist/tours/pages/TourBuilderPage.js +144 -101
- package/dist/tours/pages/TourBuilderPage.js.map +1 -1
- package/dist/tours/pages/TourPreviewPage.js +12 -4
- package/dist/tours/pages/TourPreviewPage.js.map +1 -1
- package/dist/tours/pages/ToursListPage.js +165 -149
- package/dist/tours/pages/ToursListPage.js.map +1 -1
- package/dist/tours/providers/ToursProvider.js +1 -1
- package/dist/tours/providers/ToursProvider.js.map +1 -1
- package/dist/tours/utils/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StepEditorPage.js","names":[],"sources":["../../../src/tours/pages/StepEditorPage.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport {\n ArrowLeft,\n Plus,\n Save,\n Trash2,\n ChevronLeft,\n ChevronRight,\n ChevronUp,\n ChevronDown,\n Menu,\n Paperclip,\n X,\n} from 'lucide-react';\nimport { PermissionGate } from '@burdenoff/fe-libs/shared/components';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useStepSequencer } from '../hooks/useStepSequencer';\nimport { useTourBuilder } from '../hooks/useTourBuilder';\nimport { useMediaLibrary } from '../hooks/useMediaLibrary';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport { useToursI18n } from '../i18n';\nimport { emitToursApiFailed } from '../telemetry';\n\n/**\n * Step editor page\n * Manage tour steps with sequencer and editor\n * Responsive: sidebar drawer on mobile, side panel on desktop\n */\nexport function StepEditorPage() {\n const { tr } = useToursI18n();\n\n const { tourId, stepId } = useParams();\n const navigate = useNavigate();\n const toursContext = useToursContext();\n const { basePath = '/' } = toursContext;\n const eventBus = useEventBus();\n const { hasPermission } = useToursPermissionAccess();\n const canCreateStep = hasPermission(TOUR_PERMISSIONS.TOUR_STEP_CREATE);\n const canUpdateStep = hasPermission(TOUR_PERMISSIONS.TOUR_STEP_UPDATE);\n const canDeleteStep = hasPermission(TOUR_PERMISSIONS.TOUR_STEP_DELETE);\n\n const { tour, loadTour, syncSteps, saving } = useTourBuilder(tourId);\n const { steps, selectedStep, selectStep, createStep, deleteStep, updateStepField, moveStep } =\n useStepSequencer();\n const { media, fetchMedia, attachToStep, loading: mediaLoading } = useMediaLibrary();\n\n // Mobile sidebar state\n const [isSidebarOpen, setIsSidebarOpen] = useState(false);\n const [isMobile, setIsMobile] = useState(false);\n const [attachmentMediaId, setAttachmentMediaId] = useState('');\n const [attachmentPosition, setAttachmentPosition] = useState<'header' | 'body' | 'footer'>(\n 'body'\n );\n const [attachingMedia, setAttachingMedia] = useState(false);\n\n // Detect mobile viewport\n useEffect(() => {\n const checkMobile = () => {\n const mobile = window.innerWidth < 768;\n setIsMobile(mobile);\n // Auto-close sidebar on desktop\n if (!mobile) {\n setIsSidebarOpen(false);\n }\n };\n checkMobile();\n window.addEventListener('resize', checkMobile);\n return () => window.removeEventListener('resize', checkMobile);\n }, []);\n\n useEffect(() => {\n if (tourId) {\n loadTour(tourId);\n }\n }, [tourId, loadTour]);\n\n useEffect(() => {\n void fetchMedia();\n }, [fetchMedia]);\n\n useEffect(() => {\n const selectedStepId = stepId || selectedStep?.id;\n if (selectedStepId && steps.length > 0) {\n const step = steps.find((s) => s.id === selectedStepId);\n if (step) {\n selectStep(step);\n }\n }\n }, [stepId, steps, selectStep, selectedStep?.id]);\n\n const handleAddStep = () => {\n createStep();\n };\n\n const handleSaveSteps = async () => {\n if (tourId) {\n try {\n await syncSteps(tourId, steps);\n } catch {\n emitToursApiFailed(eventBus, 'step.save', toursContext);\n }\n }\n };\n\n const handleBack = () => {\n navigate(`${basePath}/${tourId}/edit`);\n };\n\n const handleAttachMedia = async () => {\n if (!selectedStep || !tourId || !attachmentMediaId) return;\n if (selectedStep.id.startsWith('temp_')) {\n window.alert('Save your steps before attaching media to them.');\n return;\n }\n\n try {\n setAttachingMedia(true);\n await attachToStep(\n selectedStep.id,\n attachmentMediaId,\n attachmentPosition,\n (selectedStep.media?.length ?? 0) + 1\n );\n await loadTour(tourId);\n setAttachmentMediaId('');\n } catch {\n emitToursApiFailed(eventBus, 'media.attach', toursContext);\n } finally {\n setAttachingMedia(false);\n }\n };\n\n const handleSelectStep = (step: (typeof steps)[0]) => {\n selectStep(step);\n // Close sidebar on mobile after selecting\n if (isMobile) {\n setIsSidebarOpen(false);\n }\n };\n\n return (\n <div className=\"flex flex-col h-full bg-background\">\n {/* Header */}\n <div className=\"border-b border-border p-3 sm:p-4 flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4 bg-card\">\n <div className=\"flex items-center gap-3\">\n <button\n onClick={handleBack}\n className=\"p-2 hover:bg-accent rounded-md shrink-0\"\n aria-label={tr('stepEditor.goBack')}\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex-1 min-w-0\">\n <h1 className=\"text-base sm:text-lg font-semibold truncate\">\n {tour?.name || tr('stepEditor.tourFallback')} - {tr('stepEditor.stepsLabel')}\n </h1>\n <p className=\"text-xs sm:text-sm text-muted-foreground\">\n {steps.length} {tr('stepEditor.stepsCount')}\n </p>\n </div>\n </div>\n\n {/* Action buttons */}\n <div className=\"flex items-center gap-2 sm:gap-3 ml-auto\">\n {/* Mobile: Toggle sidebar button */}\n {isMobile && (\n <button\n onClick={() => setIsSidebarOpen(!isSidebarOpen)}\n className=\"px-3 py-2 border border-border rounded-md hover:bg-accent flex items-center gap-2 sm:hidden\"\n >\n <Menu size={18} />\n <span className=\"text-sm\">{tr('stepEditor.steps')}</span>\n {steps.length > 0 && (\n <span className=\"px-1.5 py-0.5 text-xs bg-primary/20 rounded-full\">\n {steps.length}\n </span>\n )}\n </button>\n )}\n\n {canCreateStep && (\n <button\n onClick={handleAddStep}\n className=\"px-3 sm:px-4 py-2 border border-border rounded-md hover:bg-accent flex items-center gap-2 text-sm sm:text-base\"\n >\n <Plus size={18} />\n <span className=\"hidden xs:inline\">{tr('stepEditor.addStep')}</span>\n </button>\n )}\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_STEP_UPDATE}>\n <button\n onClick={handleSaveSteps}\n disabled={saving || !canUpdateStep}\n className=\"px-3 sm:px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50 flex items-center gap-2 text-sm sm:text-base\"\n >\n <Save size={18} />\n <span className=\"hidden xs:inline\">\n {saving ? tr('stepEditor.saving') : tr('stepEditor.save')}\n </span>\n </button>\n </PermissionGate>\n </div>\n </div>\n\n <div className=\"flex-1 flex overflow-hidden relative\">\n {/* Mobile Sidebar Overlay */}\n {isMobile && isSidebarOpen && (\n <div\n className=\"fixed inset-0 bg-foreground/40 z-40 md:hidden\"\n onClick={() => setIsSidebarOpen(false)}\n />\n )}\n\n {/* Step List Sidebar */}\n <div\n className={`\n ${isMobile ? 'fixed inset-y-0 left-0 z-50 w-[min(18rem,calc(100vw-2rem))] transform transition-transform duration-300' : 'w-64 lg:w-80 shrink-0'}\n ${isMobile && !isSidebarOpen ? '-translate-x-full' : 'translate-x-0'}\n border-r border-border overflow-y-auto bg-card\n `}\n >\n {/* Mobile sidebar header */}\n {isMobile && (\n <div className=\"flex items-center justify-between p-4 border-b border-border sticky top-0 bg-card z-10\">\n <h2 className=\"font-semibold\">{tr('stepEditor.tourSteps')}</h2>\n <button\n onClick={() => setIsSidebarOpen(false)}\n className=\"p-2 min-h-[44px] min-w-[44px] hover:bg-accent rounded-md\"\n aria-label={tr('stepEditor.closeSteps')}\n >\n <X size={20} />\n </button>\n </div>\n )}\n\n <div className=\"p-3 sm:p-4 space-y-2\">\n {steps.length === 0 ? (\n <div className=\"text-center py-8 text-muted-foreground\">\n <p className=\"text-sm\">{tr('stepEditor.noStepsYet')}</p>\n <p className=\"text-xs mt-1\">{tr('stepEditor.clickAddStep')}</p>\n </div>\n ) : (\n steps.map((step, index) => (\n <div\n key={step.id}\n onClick={() => handleSelectStep(step)}\n className={`p-3 border rounded-md cursor-pointer transition-colors ${\n selectedStep?.id === step.id\n ? 'border-primary bg-primary/10'\n : 'border-border hover:bg-accent'\n }`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-xs text-muted-foreground mb-1\">\n {tr('stepEditor.stepNumber')} {index + 1}\n </div>\n <div className=\"font-medium text-sm truncate\">\n {step.title || tr('stepEditor.untitled')}\n </div>\n {step.targetSelector && (\n <div className=\"text-xs text-muted-foreground mt-1 truncate font-mono\">\n {step.targetSelector}\n </div>\n )}\n </div>\n {canDeleteStep && (\n <div className=\"flex items-center gap-1 shrink-0\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n if (index > 0) {\n moveStep(index, index - 1);\n }\n }}\n className=\"p-1.5 sm:p-2 min-h-[32px] min-w-[32px] sm:min-h-[40px] sm:min-w-[40px] hover:bg-accent rounded\"\n title=\"Move step up\"\n aria-label=\"Move step up\"\n disabled={index === 0}\n >\n <ChevronUp size={14} />\n </button>\n <button\n onClick={(e) => {\n e.stopPropagation();\n if (index < steps.length - 1) {\n moveStep(index, index + 1);\n }\n }}\n className=\"p-1.5 sm:p-2 min-h-[32px] min-w-[32px] sm:min-h-[40px] sm:min-w-[40px] hover:bg-accent rounded\"\n title=\"Move step down\"\n aria-label=\"Move step down\"\n disabled={index === steps.length - 1}\n >\n <ChevronDown size={14} />\n </button>\n <button\n onClick={(e) => {\n e.stopPropagation();\n deleteStep(step.id);\n }}\n className=\"p-1.5 sm:p-2 min-h-[32px] min-w-[32px] sm:min-h-[40px] sm:min-w-[40px] hover:bg-destructive/10 rounded text-destructive\"\n title={tr('stepEditor.deleteStep')}\n aria-label={tr('stepEditor.deleteStep')}\n >\n <Trash2 size={14} />\n </button>\n </div>\n )}\n </div>\n </div>\n ))\n )}\n </div>\n </div>\n\n {/* Step Editor */}\n <div className=\"flex-1 overflow-y-auto p-4 sm:p-6\">\n {selectedStep ? (\n <div className=\"max-w-2xl mx-auto space-y-4 sm:space-y-6\">\n {/* Mobile: Step navigation */}\n {isMobile && steps.length > 1 && (\n <div className=\"flex items-center justify-between bg-muted/50 rounded-lg p-2 mb-4\">\n <button\n onClick={() => {\n const currentIndex = steps.findIndex((s) => s.id === selectedStep.id);\n if (currentIndex > 0) {\n selectStep(steps[currentIndex - 1]);\n }\n }}\n disabled={steps.findIndex((s) => s.id === selectedStep.id) === 0}\n className=\"p-2 min-h-[44px] min-w-[44px] hover:bg-accent rounded-md disabled:opacity-30\"\n aria-label={tr('stepEditor.previousStep')}\n >\n <ChevronLeft size={20} />\n </button>\n <span className=\"text-sm text-muted-foreground\">\n {tr('stepEditor.stepNumber')}{' '}\n {steps.findIndex((s) => s.id === selectedStep.id) + 1} {tr('stepEditor.of')}{' '}\n {steps.length}\n </span>\n <button\n onClick={() => {\n const currentIndex = steps.findIndex((s) => s.id === selectedStep.id);\n if (currentIndex < steps.length - 1) {\n selectStep(steps[currentIndex + 1]);\n }\n }}\n disabled={steps.findIndex((s) => s.id === selectedStep.id) === steps.length - 1}\n className=\"p-2 min-h-[44px] min-w-[44px] hover:bg-accent rounded-md disabled:opacity-30\"\n aria-label={tr('stepEditor.nextStep')}\n >\n <ChevronRight size={20} />\n </button>\n </div>\n )}\n\n <h2 className=\"text-lg sm:text-xl font-semibold\">{tr('stepEditor.editStep')}</h2>\n\n {/* Title */}\n <div>\n <label className=\"block text-sm font-medium mb-2\">\n {tr('stepEditor.stepTitle')} *\n </label>\n <input\n type=\"text\"\n value={selectedStep.title}\n onChange={(e) => updateStepField(selectedStep.id, 'title', e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm sm:text-base\"\n placeholder={tr('stepEditor.stepTitlePlaceholder')}\n />\n </div>\n\n {/* Content */}\n <div>\n <label className=\"block text-sm font-medium mb-2\">\n {tr('stepEditor.content')} *\n </label>\n <textarea\n value={selectedStep.content}\n onChange={(e) => updateStepField(selectedStep.id, 'content', e.target.value)}\n rows={4}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm sm:text-base resize-y min-h-[100px]\"\n placeholder={tr('stepEditor.contentPlaceholder')}\n />\n </div>\n\n {/* Selector */}\n <div>\n <label className=\"block text-sm font-medium mb-2\">\n {tr('stepEditor.cssSelector')} *\n <span className=\"text-muted-foreground font-normal ml-2\">\n ({tr('stepEditor.targetElement')})\n </span>\n </label>\n <input\n type=\"text\"\n value={selectedStep.targetSelector || ''}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'targetSelector', e.target.value)\n }\n placeholder={tr('stepEditor.cssSelectorPlaceholder')}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background font-mono text-sm\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n {tr('stepEditor.dataTourHint')}\n </p>\n </div>\n\n {/* Placement */}\n <div>\n <label className=\"block text-sm font-medium mb-2\">\n {tr('stepEditor.tooltipPlacement')}\n </label>\n <select\n value={selectedStep.placement}\n onChange={(e) => updateStepField(selectedStep.id, 'placement', e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm sm:text-base\"\n >\n <option value=\"AUTO\">{tr('stepEditor.placementAuto')}</option>\n <option value=\"TOP\">{tr('stepEditor.placementTop')}</option>\n <option value=\"BOTTOM\">{tr('stepEditor.placementBottom')}</option>\n <option value=\"LEFT\">{tr('stepEditor.placementLeft')}</option>\n <option value=\"RIGHT\">{tr('stepEditor.placementRight')}</option>\n <option value=\"CENTER\">{tr('stepEditor.placementCenter')}</option>\n </select>\n </div>\n\n {/* Device-specific selectors */}\n <details className=\"border border-border rounded-md\">\n <summary className=\"px-4 py-3 cursor-pointer font-medium text-sm hover:bg-accent/50 rounded-t-md\">\n {tr('stepEditor.deviceSpecificSelectors')}\n </summary>\n <div className=\"p-4 pt-0 space-y-3 border-t border-border\">\n <p className=\"text-xs text-muted-foreground mb-3\">\n {tr('stepEditor.deviceSelectorHint')}\n </p>\n <div>\n <label className=\"block text-xs font-medium mb-1\">\n {tr('stepEditor.mobileSelector')}\n </label>\n <input\n type=\"text\"\n value={selectedStep.targetSelectorMobile || ''}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'targetSelectorMobile', e.target.value)\n }\n placeholder={tr('stepEditor.leaveEmptyDefault')}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm font-mono\"\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium mb-1\">\n {tr('stepEditor.tabletSelector')}\n </label>\n <input\n type=\"text\"\n value={selectedStep.targetSelectorTablet || ''}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'targetSelectorTablet', e.target.value)\n }\n placeholder={tr('stepEditor.leaveEmptyDefault')}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm font-mono\"\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium mb-1\">\n {tr('stepEditor.desktopSelector')}\n </label>\n <input\n type=\"text\"\n value={selectedStep.targetSelectorDesktop || ''}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'targetSelectorDesktop', e.target.value)\n }\n placeholder={tr('stepEditor.leaveEmptyDefault')}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm font-mono\"\n />\n </div>\n </div>\n </details>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"flex items-center gap-3 cursor-pointer rounded-md border border-border p-3\">\n <input\n type=\"checkbox\"\n checked={selectedStep.requireInteraction ?? false}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'requireInteraction', e.target.checked)\n }\n className=\"w-4 h-4 rounded\"\n />\n <div>\n <div className=\"text-sm font-medium\">Require interaction</div>\n <div className=\"text-xs text-muted-foreground\">\n User must interact with the highlighted element before advancing.\n </div>\n </div>\n </label>\n\n <div>\n <label className=\"block text-sm font-medium mb-2\">Wait for timeout (ms)</label>\n <input\n type=\"number\"\n min=\"0\"\n value={selectedStep.waitForTimeout ?? ''}\n onChange={(e) =>\n updateStepField(\n selectedStep.id,\n 'waitForTimeout',\n e.target.value ? Number(e.target.value) : undefined\n )\n }\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm\"\n placeholder=\"1500\"\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium mb-2\">Wait for selector</label>\n <input\n type=\"text\"\n value={selectedStep.waitForSelector || ''}\n onChange={(e) =>\n updateStepField(\n selectedStep.id,\n 'waitForSelector',\n e.target.value || undefined\n )\n }\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm font-mono\"\n placeholder=\"[data-tour='agents-tabs']\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium mb-2\">Wait for event</label>\n <input\n type=\"text\"\n value={selectedStep.waitForEvent || ''}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'waitForEvent', e.target.value || undefined)\n }\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm\"\n placeholder=\"tour:ready\"\n />\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium mb-2\">Skip conditions (JSON)</label>\n <textarea\n value={\n selectedStep.skipConditions\n ? JSON.stringify(selectedStep.skipConditions, null, 2)\n : ''\n }\n onChange={(e) => {\n const rawValue = e.target.value.trim();\n if (!rawValue) {\n updateStepField(selectedStep.id, 'skipConditions', undefined);\n return;\n }\n\n try {\n updateStepField(selectedStep.id, 'skipConditions', JSON.parse(rawValue));\n } catch {\n updateStepField(selectedStep.id, 'skipConditions', rawValue);\n }\n }}\n rows={4}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm font-mono\"\n placeholder='{\"selectorMissing\":\"[data-tour=\\\"missing\\\"]\"}'\n />\n </div>\n\n <div className=\"border border-border rounded-md p-4 space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Paperclip size={18} />\n <h3 className=\"text-sm font-semibold\">Step media</h3>\n </div>\n\n {selectedStep.media && selectedStep.media.length > 0 ? (\n <div className=\"space-y-2\">\n {selectedStep.media.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between rounded-md border border-border p-2 text-sm\"\n >\n <div>\n <div className=\"font-medium\">{item.media.name}</div>\n <div className=\"text-xs text-muted-foreground\">{item.position}</div>\n </div>\n <span className=\"text-xs text-muted-foreground\">{item.media.type}</span>\n </div>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n No media attached to this step yet.\n </p>\n )}\n\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-[minmax(0,1fr)_160px_auto]\">\n <select\n value={attachmentMediaId}\n onChange={(e) => setAttachmentMediaId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm\"\n >\n <option value=\"\">\n {mediaLoading ? 'Loading media...' : 'Select uploaded media'}\n </option>\n {media.map((item) => (\n <option key={item.id} value={item.id}>\n {item.name} ({item.type})\n </option>\n ))}\n </select>\n\n <select\n value={attachmentPosition}\n onChange={(e) =>\n setAttachmentPosition(e.target.value as 'header' | 'body' | 'footer')\n }\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm\"\n >\n <option value=\"header\">Header</option>\n <option value=\"body\">Body</option>\n <option value=\"footer\">Footer</option>\n </select>\n\n <button\n type=\"button\"\n onClick={handleAttachMedia}\n disabled={attachingMedia || !attachmentMediaId}\n className=\"px-4 py-2 min-h-[44px] border border-border rounded-md hover:bg-accent disabled:opacity-50\"\n >\n Attach\n </button>\n </div>\n </div>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center h-full text-center px-4\">\n <div className=\"w-16 h-16 bg-muted rounded-full flex items-center justify-center mb-4\">\n <Plus size={24} className=\"text-muted-foreground\" />\n </div>\n <p className=\"text-muted-foreground mb-2\">{tr('stepEditor.noStepSelected')}</p>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {steps.length > 0\n ? tr('stepEditor.selectStepToEdit')\n : tr('stepEditor.createFirstStep')}\n </p>\n {steps.length === 0 && canCreateStep && (\n <button\n onClick={handleAddStep}\n className=\"px-4 py-2 min-h-[44px] bg-primary text-primary-foreground rounded-md hover:bg-primary/90 flex items-center gap-2\"\n >\n <Plus size={18} />\n {tr('stepEditor.addFirstStep')}\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"StepEditorPage.js","names":[],"sources":["../../../src/tours/pages/StepEditorPage.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport {\n ArrowLeft,\n Plus,\n Save,\n Trash2,\n ChevronLeft,\n ChevronRight,\n ChevronUp,\n ChevronDown,\n Menu,\n Paperclip,\n X,\n} from 'lucide-react';\nimport { PermissionGate } from '@burdenoff/fe-libs/shared/components';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useStepSequencer } from '../hooks/useStepSequencer';\nimport { useTourBuilder } from '../hooks/useTourBuilder';\nimport { useMediaLibrary } from '../hooks/useMediaLibrary';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport { useToursI18n } from '../i18n';\nimport { emitToursApiFailed } from '../telemetry';\n\n/**\n * Step editor page\n * Manage tour steps with sequencer and editor\n * Responsive: sidebar drawer on mobile, side panel on desktop\n */\nexport function StepEditorPage() {\n const { tr } = useToursI18n();\n\n const { tourId, stepId } = useParams();\n const navigate = useNavigate();\n const toursContext = useToursContext();\n const { basePath = '/' } = toursContext;\n const eventBus = useEventBus();\n const { hasPermission } = useToursPermissionAccess();\n const canCreateStep = hasPermission(TOUR_PERMISSIONS.TOUR_STEP_CREATE);\n const canUpdateStep = hasPermission(TOUR_PERMISSIONS.TOUR_STEP_UPDATE);\n const canDeleteStep = hasPermission(TOUR_PERMISSIONS.TOUR_STEP_DELETE);\n\n const { tour, loadTour, syncSteps, saving } = useTourBuilder(tourId);\n const { steps, selectedStep, selectStep, createStep, deleteStep, updateStepField, moveStep } =\n useStepSequencer();\n const { media, fetchMedia, attachToStep, loading: mediaLoading } = useMediaLibrary();\n\n // Mobile sidebar state\n const [isSidebarOpen, setIsSidebarOpen] = useState(false);\n const [isMobile, setIsMobile] = useState(\n () => typeof window !== 'undefined' && window.innerWidth < 768\n );\n const [attachmentMediaId, setAttachmentMediaId] = useState('');\n const [attachmentPosition, setAttachmentPosition] = useState<'header' | 'body' | 'footer'>(\n 'body'\n );\n const [attachingMedia, setAttachingMedia] = useState(false);\n\n // Detect mobile viewport\n useEffect(() => {\n const checkMobile = () => {\n const mobile = window.innerWidth < 768;\n setIsMobile(mobile);\n // Auto-close sidebar on desktop\n if (!mobile) {\n setIsSidebarOpen(false);\n }\n };\n window.addEventListener('resize', checkMobile);\n return () => window.removeEventListener('resize', checkMobile);\n }, []);\n\n useEffect(() => {\n if (tourId) {\n loadTour(tourId);\n }\n }, [tourId, loadTour]);\n\n useEffect(() => {\n void fetchMedia();\n }, [fetchMedia]);\n\n useEffect(() => {\n const selectedStepId = stepId || selectedStep?.id;\n if (selectedStepId && steps.length > 0) {\n const step = steps.find((s) => s.id === selectedStepId);\n if (step) {\n selectStep(step);\n }\n }\n }, [stepId, steps, selectStep, selectedStep?.id]);\n\n const handleAddStep = () => {\n createStep();\n };\n\n const handleSaveSteps = async () => {\n if (tourId) {\n try {\n await syncSteps(tourId, steps);\n } catch {\n emitToursApiFailed(eventBus, 'step.save', toursContext);\n }\n }\n };\n\n const handleBack = () => {\n navigate(`${basePath}/${tourId}/edit`);\n };\n\n const handleAttachMedia = async () => {\n if (!selectedStep || !tourId || !attachmentMediaId) return;\n if (selectedStep.id.startsWith('temp_')) {\n window.alert('Save your steps before attaching media to them.');\n return;\n }\n\n try {\n setAttachingMedia(true);\n await attachToStep(\n selectedStep.id,\n attachmentMediaId,\n attachmentPosition,\n (selectedStep.media?.length ?? 0) + 1\n );\n await loadTour(tourId);\n setAttachmentMediaId('');\n } catch {\n emitToursApiFailed(eventBus, 'media.attach', toursContext);\n } finally {\n setAttachingMedia(false);\n }\n };\n\n const handleSelectStep = (step: (typeof steps)[0]) => {\n selectStep(step);\n // Close sidebar on mobile after selecting\n if (isMobile) {\n setIsSidebarOpen(false);\n }\n };\n\n return (\n <div className=\"flex flex-col h-full bg-background\">\n {/* Header */}\n <div className=\"border-b border-border p-3 sm:p-4 flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4 bg-card\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-2 hover:bg-accent rounded-md shrink-0\"\n aria-label={tr('stepEditor.goBack')}\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex-1 min-w-0\">\n <h1 className=\"text-base sm:text-lg font-semibold truncate\">\n {tour?.name || tr('stepEditor.tourFallback')} - {tr('stepEditor.stepsLabel')}\n </h1>\n <p className=\"text-xs sm:text-sm text-muted-foreground\">\n {steps.length} {tr('stepEditor.stepsCount')}\n </p>\n </div>\n </div>\n\n {/* Action buttons */}\n <div className=\"flex items-center gap-2 sm:gap-3 ml-auto\">\n {/* Mobile: Toggle sidebar button */}\n {isMobile && (\n <button\n type=\"button\"\n onClick={() => setIsSidebarOpen(!isSidebarOpen)}\n className=\"px-3 py-2 border border-border rounded-md hover:bg-accent flex items-center gap-2 sm:hidden\"\n >\n <Menu size={18} />\n <span className=\"text-sm\">{tr('stepEditor.steps')}</span>\n {steps.length > 0 && (\n <span className=\"px-1.5 py-0.5 text-xs bg-primary/20 rounded-full\">\n {steps.length}\n </span>\n )}\n </button>\n )}\n\n {canCreateStep && (\n <button\n type=\"button\"\n onClick={handleAddStep}\n className=\"px-3 sm:px-4 py-2 border border-border rounded-md hover:bg-accent flex items-center gap-2 text-sm sm:text-base\"\n >\n <Plus size={18} />\n <span className=\"hidden xs:inline\">{tr('stepEditor.addStep')}</span>\n </button>\n )}\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_STEP_UPDATE}>\n <button\n type=\"button\"\n onClick={handleSaveSteps}\n disabled={saving || !canUpdateStep}\n className=\"px-3 sm:px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50 flex items-center gap-2 text-sm sm:text-base\"\n >\n <Save size={18} />\n <span className=\"hidden xs:inline\">\n {saving ? tr('stepEditor.saving') : tr('stepEditor.save')}\n </span>\n </button>\n </PermissionGate>\n </div>\n </div>\n\n <div className=\"flex-1 flex overflow-hidden relative\">\n {/* Mobile Sidebar Overlay */}\n {isMobile && isSidebarOpen && (\n <button\n type=\"button\"\n aria-label={tr('stepEditor.closeSteps')}\n className=\"fixed inset-0 bg-foreground/40 z-40 md:hidden\"\n onClick={() => setIsSidebarOpen(false)}\n />\n )}\n\n {/* Step List Sidebar */}\n <div\n className={`\n ${isMobile ? 'fixed inset-y-0 left-0 z-50 w-[min(18rem,calc(100vw-2rem))] transform transition-transform duration-300' : 'w-64 lg:w-80 shrink-0'}\n ${isMobile && !isSidebarOpen ? '-translate-x-full' : 'translate-x-0'}\n border-r border-border overflow-y-auto bg-card\n `}\n >\n {/* Mobile sidebar header */}\n {isMobile && (\n <div className=\"flex items-center justify-between p-4 border-b border-border sticky top-0 bg-card z-10\">\n <h2 className=\"font-semibold\">{tr('stepEditor.tourSteps')}</h2>\n <button\n type=\"button\"\n onClick={() => setIsSidebarOpen(false)}\n className=\"p-2 min-h-[44px] min-w-[44px] hover:bg-accent rounded-md\"\n aria-label={tr('stepEditor.closeSteps')}\n >\n <X size={20} />\n </button>\n </div>\n )}\n\n <div className=\"p-3 sm:p-4 space-y-2\">\n {steps.length === 0 ? (\n <div className=\"text-center py-8 text-muted-foreground\">\n <p className=\"text-sm\">{tr('stepEditor.noStepsYet')}</p>\n <p className=\"text-xs mt-1\">{tr('stepEditor.clickAddStep')}</p>\n </div>\n ) : (\n steps.map((step, index) => (\n <div\n key={step.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => handleSelectStep(step)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleSelectStep(step);\n }\n }}\n className={`p-3 border rounded-md cursor-pointer transition-colors ${\n selectedStep?.id === step.id\n ? 'border-primary bg-primary/10'\n : 'border-border hover:bg-accent'\n }`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-xs text-muted-foreground mb-1\">\n {tr('stepEditor.stepNumber')} {index + 1}\n </div>\n <div className=\"font-medium text-sm truncate\">\n {step.title || tr('stepEditor.untitled')}\n </div>\n {step.targetSelector && (\n <div className=\"text-xs text-muted-foreground mt-1 truncate font-mono\">\n {step.targetSelector}\n </div>\n )}\n </div>\n {canDeleteStep && (\n <div className=\"flex items-center gap-1 shrink-0\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n if (index > 0) {\n moveStep(index, index - 1);\n }\n }}\n className=\"p-1.5 sm:p-2 min-h-[32px] min-w-[32px] sm:min-h-[40px] sm:min-w-[40px] hover:bg-accent rounded\"\n title=\"Move step up\"\n aria-label=\"Move step up\"\n disabled={index === 0}\n >\n <ChevronUp size={14} />\n </button>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n if (index < steps.length - 1) {\n moveStep(index, index + 1);\n }\n }}\n className=\"p-1.5 sm:p-2 min-h-[32px] min-w-[32px] sm:min-h-[40px] sm:min-w-[40px] hover:bg-accent rounded\"\n title=\"Move step down\"\n aria-label=\"Move step down\"\n disabled={index === steps.length - 1}\n >\n <ChevronDown size={14} />\n </button>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n deleteStep(step.id);\n }}\n className=\"p-1.5 sm:p-2 min-h-[32px] min-w-[32px] sm:min-h-[40px] sm:min-w-[40px] hover:bg-destructive/10 rounded text-destructive\"\n title={tr('stepEditor.deleteStep')}\n aria-label={tr('stepEditor.deleteStep')}\n >\n <Trash2 size={14} />\n </button>\n </div>\n )}\n </div>\n </div>\n ))\n )}\n </div>\n </div>\n\n {/* Step Editor */}\n <div className=\"flex-1 overflow-y-auto p-4 sm:p-6\">\n {selectedStep ? (\n <div className=\"max-w-2xl mx-auto space-y-4 sm:space-y-6\">\n {/* Mobile: Step navigation */}\n {isMobile && steps.length > 1 && (\n <div className=\"flex items-center justify-between bg-muted/50 rounded-lg p-2 mb-4\">\n <button\n type=\"button\"\n onClick={() => {\n const currentIndex = steps.findIndex((s) => s.id === selectedStep.id);\n if (currentIndex > 0) {\n selectStep(steps[currentIndex - 1]);\n }\n }}\n disabled={steps.findIndex((s) => s.id === selectedStep.id) === 0}\n className=\"p-2 min-h-[44px] min-w-[44px] hover:bg-accent rounded-md disabled:opacity-30\"\n aria-label={tr('stepEditor.previousStep')}\n >\n <ChevronLeft size={20} />\n </button>\n <span className=\"text-sm text-muted-foreground\">\n {tr('stepEditor.stepNumber')}{' '}\n {steps.findIndex((s) => s.id === selectedStep.id) + 1} {tr('stepEditor.of')}{' '}\n {steps.length}\n </span>\n <button\n type=\"button\"\n onClick={() => {\n const currentIndex = steps.findIndex((s) => s.id === selectedStep.id);\n if (currentIndex < steps.length - 1) {\n selectStep(steps[currentIndex + 1]);\n }\n }}\n disabled={steps.findIndex((s) => s.id === selectedStep.id) === steps.length - 1}\n className=\"p-2 min-h-[44px] min-w-[44px] hover:bg-accent rounded-md disabled:opacity-30\"\n aria-label={tr('stepEditor.nextStep')}\n >\n <ChevronRight size={20} />\n </button>\n </div>\n )}\n\n <h2 className=\"text-lg sm:text-xl font-semibold\">{tr('stepEditor.editStep')}</h2>\n\n {/* Title */}\n <div>\n <label className=\"block text-sm font-medium mb-2\" htmlFor=\"step-editor-title\">\n {tr('stepEditor.stepTitle')} *\n </label>\n <input\n id=\"step-editor-title\"\n type=\"text\"\n value={selectedStep.title}\n onChange={(e) => updateStepField(selectedStep.id, 'title', e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm sm:text-base\"\n placeholder={tr('stepEditor.stepTitlePlaceholder')}\n aria-label={tr('stepEditor.stepTitle')}\n />\n </div>\n\n {/* Content */}\n <div>\n <label className=\"block text-sm font-medium mb-2\" htmlFor=\"step-editor-content\">\n {tr('stepEditor.content')} *\n </label>\n <textarea\n id=\"step-editor-content\"\n value={selectedStep.content}\n onChange={(e) => updateStepField(selectedStep.id, 'content', e.target.value)}\n rows={4}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm sm:text-base resize-y min-h-[100px]\"\n placeholder={tr('stepEditor.contentPlaceholder')}\n aria-label={tr('stepEditor.content')}\n />\n </div>\n\n {/* Selector */}\n <div>\n <label\n className=\"block text-sm font-medium mb-2\"\n htmlFor=\"step-editor-css-selector\"\n >\n {tr('stepEditor.cssSelector')} *\n <span className=\"text-muted-foreground font-normal ml-2\">\n ({tr('stepEditor.targetElement')})\n </span>\n </label>\n <input\n id=\"step-editor-css-selector\"\n type=\"text\"\n value={selectedStep.targetSelector || ''}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'targetSelector', e.target.value)\n }\n placeholder={tr('stepEditor.cssSelectorPlaceholder')}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background font-mono text-sm\"\n aria-label=\"{tr('stepEditor.cssSelector')} *\n \n ({tr('stepEditor.targetElement')})\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n {tr('stepEditor.dataTourHint')}\n </p>\n </div>\n\n {/* Placement */}\n <div>\n <label className=\"block text-sm font-medium mb-2\" htmlFor=\"step-editor-placement\">\n {tr('stepEditor.tooltipPlacement')}\n </label>\n <select\n id=\"step-editor-placement\"\n value={selectedStep.placement}\n onChange={(e) => updateStepField(selectedStep.id, 'placement', e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm sm:text-base\"\n aria-label={tr('stepEditor.tooltipPlacement')}\n >\n <option value=\"AUTO\">{tr('stepEditor.placementAuto')}</option>\n <option value=\"TOP\">{tr('stepEditor.placementTop')}</option>\n <option value=\"BOTTOM\">{tr('stepEditor.placementBottom')}</option>\n <option value=\"LEFT\">{tr('stepEditor.placementLeft')}</option>\n <option value=\"RIGHT\">{tr('stepEditor.placementRight')}</option>\n <option value=\"CENTER\">{tr('stepEditor.placementCenter')}</option>\n </select>\n </div>\n\n {/* Device-specific selectors */}\n <details className=\"border border-border rounded-md\">\n <summary className=\"px-4 py-3 cursor-pointer font-medium text-sm hover:bg-accent/50 rounded-t-md\">\n {tr('stepEditor.deviceSpecificSelectors')}\n </summary>\n <div className=\"p-4 pt-0 space-y-3 border-t border-border\">\n <p className=\"text-xs text-muted-foreground mb-3\">\n {tr('stepEditor.deviceSelectorHint')}\n </p>\n <div>\n <label\n className=\"block text-xs font-medium mb-1\"\n htmlFor=\"step-editor-mobile-selector\"\n >\n {tr('stepEditor.mobileSelector')}\n </label>\n <input\n id=\"step-editor-mobile-selector\"\n type=\"text\"\n value={selectedStep.targetSelectorMobile || ''}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'targetSelectorMobile', e.target.value)\n }\n placeholder={tr('stepEditor.leaveEmptyDefault')}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm font-mono\"\n aria-label={tr('stepEditor.mobileSelector')}\n />\n </div>\n <div>\n <label\n className=\"block text-xs font-medium mb-1\"\n htmlFor=\"step-editor-tablet-selector\"\n >\n {tr('stepEditor.tabletSelector')}\n </label>\n <input\n id=\"step-editor-tablet-selector\"\n type=\"text\"\n value={selectedStep.targetSelectorTablet || ''}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'targetSelectorTablet', e.target.value)\n }\n placeholder={tr('stepEditor.leaveEmptyDefault')}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm font-mono\"\n aria-label={tr('stepEditor.tabletSelector')}\n />\n </div>\n <div>\n <label\n className=\"block text-xs font-medium mb-1\"\n htmlFor=\"step-editor-desktop-selector\"\n >\n {tr('stepEditor.desktopSelector')}\n </label>\n <input\n id=\"step-editor-desktop-selector\"\n type=\"text\"\n value={selectedStep.targetSelectorDesktop || ''}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'targetSelectorDesktop', e.target.value)\n }\n placeholder={tr('stepEditor.leaveEmptyDefault')}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm font-mono\"\n aria-label={tr('stepEditor.desktopSelector')}\n />\n </div>\n </div>\n </details>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"flex items-center gap-3 cursor-pointer rounded-md border border-border p-3\">\n <input\n type=\"checkbox\"\n checked={selectedStep.requireInteraction ?? false}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'requireInteraction', e.target.checked)\n }\n className=\"size-4 rounded\"\n aria-label=\"Require interaction\"\n />\n <div>\n <div className=\"text-sm font-medium\">Require interaction</div>\n <div className=\"text-xs text-muted-foreground\">\n User must interact with the highlighted element before advancing.\n </div>\n </div>\n </label>\n\n <div>\n <label\n className=\"block text-sm font-medium mb-2\"\n htmlFor=\"step-editor-wait-timeout\"\n >\n Wait for timeout (ms)\n </label>\n <input\n id=\"step-editor-wait-timeout\"\n type=\"number\"\n min=\"0\"\n value={selectedStep.waitForTimeout ?? ''}\n onChange={(e) =>\n updateStepField(\n selectedStep.id,\n 'waitForTimeout',\n e.target.value ? Number(e.target.value) : undefined\n )\n }\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm\"\n placeholder=\"1500\"\n aria-label=\"Wait for timeout (ms)\"\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label\n className=\"block text-sm font-medium mb-2\"\n htmlFor=\"step-editor-wait-selector\"\n >\n Wait for selector\n </label>\n <input\n id=\"step-editor-wait-selector\"\n type=\"text\"\n value={selectedStep.waitForSelector || ''}\n onChange={(e) =>\n updateStepField(\n selectedStep.id,\n 'waitForSelector',\n e.target.value || undefined\n )\n }\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm font-mono\"\n placeholder=\"[data-tour='agents-tabs']\"\n aria-label=\"Wait for selector\"\n />\n </div>\n\n <div>\n <label\n className=\"block text-sm font-medium mb-2\"\n htmlFor=\"step-editor-wait-event\"\n >\n Wait for event\n </label>\n <input\n id=\"step-editor-wait-event\"\n type=\"text\"\n value={selectedStep.waitForEvent || ''}\n onChange={(e) =>\n updateStepField(selectedStep.id, 'waitForEvent', e.target.value || undefined)\n }\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm\"\n placeholder=\"tour:ready\"\n aria-label=\"Wait for event\"\n />\n </div>\n </div>\n\n <div>\n <label\n className=\"block text-sm font-medium mb-2\"\n htmlFor=\"step-editor-skip-conditions\"\n >\n Skip conditions (JSON)\n </label>\n <textarea\n id=\"step-editor-skip-conditions\"\n value={\n selectedStep.skipConditions\n ? JSON.stringify(selectedStep.skipConditions, null, 2)\n : ''\n }\n onChange={(e) => {\n const rawValue = e.target.value.trim();\n if (!rawValue) {\n updateStepField(selectedStep.id, 'skipConditions', undefined);\n return;\n }\n\n try {\n updateStepField(selectedStep.id, 'skipConditions', JSON.parse(rawValue));\n } catch {\n updateStepField(selectedStep.id, 'skipConditions', rawValue);\n }\n }}\n rows={4}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm font-mono\"\n placeholder='{\"selectorMissing\":\"[data-tour=\\\"missing\\\"]\"}'\n aria-label=\"Skip conditions (JSON)\"\n />\n </div>\n\n <div className=\"border border-border rounded-md p-4 space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Paperclip size={18} />\n <h3 className=\"text-sm font-semibold\">Step media</h3>\n </div>\n\n {selectedStep.media && selectedStep.media.length > 0 ? (\n <div className=\"space-y-2\">\n {selectedStep.media.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between rounded-md border border-border p-2 text-sm\"\n >\n <div>\n <div className=\"font-medium\">{item.media.name}</div>\n <div className=\"text-xs text-muted-foreground\">{item.position}</div>\n </div>\n <span className=\"text-xs text-muted-foreground\">{item.media.type}</span>\n </div>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n No media attached to this step yet.\n </p>\n )}\n\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-[minmax(0,1fr)_160px_auto]\">\n <select\n value={attachmentMediaId}\n onChange={(e) => setAttachmentMediaId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm\"\n aria-label=\"Select media to attach\"\n >\n <option value=\"\">\n {mediaLoading ? 'Loading media...' : 'Select uploaded media'}\n </option>\n {media.map((item) => (\n <option key={item.id} value={item.id}>\n {item.name} ({item.type})\n </option>\n ))}\n </select>\n\n <select\n value={attachmentPosition}\n onChange={(e) =>\n setAttachmentPosition(e.target.value as 'header' | 'body' | 'footer')\n }\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-sm\"\n aria-label=\"Attachment position\"\n >\n <option value=\"header\">Header</option>\n <option value=\"body\">Body</option>\n <option value=\"footer\">Footer</option>\n </select>\n\n <button\n type=\"button\"\n onClick={handleAttachMedia}\n disabled={attachingMedia || !attachmentMediaId}\n className=\"px-4 py-2 min-h-[44px] border border-border rounded-md hover:bg-accent disabled:opacity-50\"\n >\n Attach\n </button>\n </div>\n </div>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center h-full text-center px-4\">\n <div className=\"size-16 bg-muted rounded-full flex items-center justify-center mb-4\">\n <Plus size={24} className=\"text-muted-foreground\" />\n </div>\n <p className=\"text-muted-foreground mb-2\">{tr('stepEditor.noStepSelected')}</p>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {steps.length > 0\n ? tr('stepEditor.selectStepToEdit')\n : tr('stepEditor.createFirstStep')}\n </p>\n {steps.length === 0 && canCreateStep && (\n <button\n type=\"button\"\n onClick={handleAddStep}\n className=\"px-4 py-2 min-h-[44px] bg-primary text-primary-foreground rounded-md hover:bg-primary/90 flex items-center gap-2\"\n >\n <Plus size={18} />\n {tr('stepEditor.addFirstStep')}\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -66,13 +66,15 @@ function S() {
|
|
|
66
66
|
placeholder: S("templateLibrary.searchPlaceholder"),
|
|
67
67
|
value: j,
|
|
68
68
|
onChange: (e) => M(e.target.value),
|
|
69
|
-
className: "w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border rounded-md bg-background"
|
|
69
|
+
className: "w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border rounded-md bg-background",
|
|
70
|
+
"aria-label": S("templateLibrary.searchPlaceholder")
|
|
70
71
|
})]
|
|
71
72
|
}),
|
|
72
73
|
/* @__PURE__ */ y("select", {
|
|
73
74
|
value: N,
|
|
74
75
|
onChange: (e) => P(e.target.value),
|
|
75
76
|
className: "px-4 py-2 min-h-[44px] border border-border rounded-md bg-background",
|
|
77
|
+
"aria-label": S("templateLibrary.allCategories"),
|
|
76
78
|
children: [
|
|
77
79
|
/* @__PURE__ */ v("option", {
|
|
78
80
|
value: "all",
|
|
@@ -102,7 +104,8 @@ function S() {
|
|
|
102
104
|
type: "checkbox",
|
|
103
105
|
checked: F,
|
|
104
106
|
onChange: (e) => I(e.target.checked),
|
|
105
|
-
className: "rounded"
|
|
107
|
+
className: "rounded",
|
|
108
|
+
"aria-label": S("templateLibrary.publicOnly")
|
|
106
109
|
}), /* @__PURE__ */ v("span", {
|
|
107
110
|
className: "text-sm",
|
|
108
111
|
children: S("templateLibrary.publicOnly")
|
|
@@ -170,11 +173,13 @@ function S() {
|
|
|
170
173
|
}), /* @__PURE__ */ y("div", {
|
|
171
174
|
className: "flex items-center gap-2",
|
|
172
175
|
children: [A && /* @__PURE__ */ v("button", {
|
|
176
|
+
type: "button",
|
|
173
177
|
onClick: () => void W(e.id),
|
|
174
178
|
className: "p-2 min-h-[44px] min-w-[44px] text-muted-foreground hover:text-destructive rounded-md",
|
|
175
179
|
"aria-label": S("templateLibrary.deleteTemplate"),
|
|
176
180
|
children: /* @__PURE__ */ v(_, { size: 16 })
|
|
177
181
|
}), k && /* @__PURE__ */ v("button", {
|
|
182
|
+
type: "button",
|
|
178
183
|
onClick: () => V(e.id),
|
|
179
184
|
className: "px-3 py-2 min-h-[44px] bg-primary text-primary-foreground rounded-md hover:bg-primary/90 text-sm",
|
|
180
185
|
children: S("templateLibrary.useTemplate")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TemplateLibraryPage.js","names":[],"sources":["../../../src/tours/pages/TemplateLibraryPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Search, LayoutTemplate, Star, Globe, Lock, Trash2 } from 'lucide-react';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport {\n useGetTourTemplatesQuery,\n useCreateTourFromTemplateMutation,\n useDeleteTourTemplateMutation,\n} from '../../generated/global-operations';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport type { TemplateCategory } from '../types';\nimport { useToursI18n } from '../i18n';\nimport { emitToursApiFailed } from '../telemetry';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\n\n/**\n * Template library page\n * Browse and use tour templates\n */\nexport function TemplateLibraryPage() {\n const { tr } = useToursI18n();\n\n const navigate = useNavigate();\n const toursContext = useToursContext();\n const { basePath = '/' } = toursContext;\n const eventBus = useEventBus();\n const { hasPermission } = useToursPermissionAccess();\n const canListTemplates = hasPermission(TOUR_PERMISSIONS.TOUR_TEMPLATE_LIST);\n const canUseTemplate = hasPermission(TOUR_PERMISSIONS.TOUR_TEMPLATE_USE);\n const canDeleteTemplate = hasPermission(TOUR_PERMISSIONS.TOUR_TEMPLATE_DELETE);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [categoryFilter, setCategoryFilter] = useState<TemplateCategory | 'all'>('all');\n const [publicOnly, setPublicOnly] = useState(false);\n\n const { data, loading } = useGetTourTemplatesQuery({\n skip: !canListTemplates,\n variables: {\n isPublic: publicOnly || undefined,\n category: categoryFilter === 'all' ? undefined : categoryFilter,\n },\n });\n const templates = data?.tourTemplates ?? [];\n\n const filteredTemplates = templates.filter(\n (t: (typeof templates)[number]) =>\n !searchQuery || t.name.toLowerCase().includes(searchQuery.toLowerCase())\n );\n\n const [createFromTemplate] = useCreateTourFromTemplateMutation();\n\n const handleUseTemplate = async (templateId: string) => {\n nativeImpact('light');\n try {\n const { data } = await createFromTemplate({\n variables: { templateId },\n });\n if (data?.createTourFromTemplate) {\n void nativeNotify('success');\n navigate(`${basePath}/${data.createTourFromTemplate.id}/edit`);\n }\n } catch {\n void nativeNotify('error');\n emitToursApiFailed(eventBus, 'template.create_from_template', toursContext);\n }\n };\n\n const [deleteTemplate] = useDeleteTourTemplateMutation();\n\n const handleDeleteTemplate = async (templateId: string) => {\n try {\n await deleteTemplate({\n variables: { id: templateId },\n refetchQueries: ['GetTourTemplates'],\n });\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n emitToursApiFailed(eventBus, 'template.delete', toursContext);\n }\n };\n\n const handleDeleteTemplateClick = async (templateId: string) => {\n nativeImpact('medium');\n const message = tr('templateLibrary.deleteConfirm');\n const nativeResult = await nativeConfirm({\n title: tr('templateLibrary.deleteConfirm'),\n message,\n });\n const confirmed = nativeResult === null ? window.confirm(message) : nativeResult;\n if (confirmed) await handleDeleteTemplate(templateId);\n };\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold\">{tr('templateLibrary.title')}</h1>\n <p className=\"text-sm text-muted-foreground\">{tr('templateLibrary.subtitle')}</p>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <div className=\"flex-1 relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n size={20}\n />\n <input\n type=\"text\"\n placeholder={tr('templateLibrary.searchPlaceholder')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border rounded-md bg-background\"\n />\n </div>\n <select\n value={categoryFilter}\n onChange={(e) => setCategoryFilter(e.target.value as TemplateCategory | 'all')}\n className=\"px-4 py-2 min-h-[44px] border border-border rounded-md bg-background\"\n >\n <option value=\"all\">{tr('templateLibrary.allCategories')}</option>\n <option value=\"ONBOARDING\">{tr('templateLibrary.categoryOnboarding')}</option>\n <option value=\"FEATURE_ANNOUNCEMENT\">\n {tr('templateLibrary.categoryFeatureAnnouncement')}\n </option>\n <option value=\"HELP\">{tr('templateLibrary.categoryHelp')}</option>\n <option value=\"CUSTOM\">{tr('templateLibrary.categoryCustom')}</option>\n </select>\n <label className=\"flex items-center gap-2 px-4 py-2 min-h-[44px] border border-border rounded-md bg-background cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={publicOnly}\n onChange={(e) => setPublicOnly(e.target.checked)}\n className=\"rounded\"\n />\n <span className=\"text-sm\">{tr('templateLibrary.publicOnly')}</span>\n </label>\n </div>\n\n {/* Templates Grid */}\n {loading ? (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground\">{tr('templateLibrary.loading')}</p>\n </div>\n ) : filteredTemplates.length > 0 ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {filteredTemplates.map((template: (typeof filteredTemplates)[number]) => (\n <div\n key={template.id}\n className=\"border border-border rounded-md overflow-hidden hover:shadow-md transition-shadow\"\n >\n {/* Thumbnail */}\n <div className=\"aspect-video bg-muted flex items-center justify-center\">\n {template.thumbnail ? (\n <img\n src={template.thumbnail}\n alt={template.name}\n className=\"w-full h-full object-cover\"\n />\n ) : (\n <LayoutTemplate className=\"text-muted-foreground\" size={48} />\n )}\n </div>\n\n {/* Content */}\n <div className=\"p-4\">\n <div className=\"flex items-start justify-between mb-2\">\n <h3 className=\"font-semibold\">{template.name}</h3>\n <div className=\"flex gap-1\">\n {template.isPremium && <Star className=\"text-warning\" size={16} />}\n {template.isPublic ? (\n <Globe className=\"text-muted-foreground\" size={16} />\n ) : (\n <Lock className=\"text-muted-foreground\" size={16} />\n )}\n </div>\n </div>\n\n {template.description && (\n <p className=\"text-sm text-muted-foreground mb-3 line-clamp-2\">\n {template.description}\n </p>\n )}\n\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs text-muted-foreground\">\n {tr('templateLibrary.usedTimes')} {template.useCount}{' '}\n {tr('templateLibrary.times')}\n </span>\n <div className=\"flex items-center gap-2\">\n {canDeleteTemplate && (\n <button\n onClick={() => void handleDeleteTemplateClick(template.id)}\n className=\"p-2 min-h-[44px] min-w-[44px] text-muted-foreground hover:text-destructive rounded-md\"\n aria-label={tr('templateLibrary.deleteTemplate')}\n >\n <Trash2 size={16} />\n </button>\n )}\n {canUseTemplate && (\n <button\n onClick={() => handleUseTemplate(template.id)}\n className=\"px-3 py-2 min-h-[44px] bg-primary text-primary-foreground rounded-md hover:bg-primary/90 text-sm\"\n >\n {tr('templateLibrary.useTemplate')}\n </button>\n )}\n </div>\n </div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12 border border-dashed border-border rounded-md\">\n <LayoutTemplate className=\"mx-auto mb-4 text-muted-foreground\" size={48} />\n <p className=\"text-muted-foreground mb-2\">{tr('templateLibrary.noTemplatesFound')}</p>\n <p className=\"text-sm text-muted-foreground\">{tr('templateLibrary.adjustFilters')}</p>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TemplateLibraryPage.js","names":[],"sources":["../../../src/tours/pages/TemplateLibraryPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Search, LayoutTemplate, Star, Globe, Lock, Trash2 } from 'lucide-react';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport {\n useGetTourTemplatesQuery,\n useCreateTourFromTemplateMutation,\n useDeleteTourTemplateMutation,\n} from '../../generated/global-operations';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport type { TemplateCategory } from '../types';\nimport { useToursI18n } from '../i18n';\nimport { emitToursApiFailed } from '../telemetry';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\n\n/**\n * Template library page\n * Browse and use tour templates\n */\nexport function TemplateLibraryPage() {\n const { tr } = useToursI18n();\n\n const navigate = useNavigate();\n const toursContext = useToursContext();\n const { basePath = '/' } = toursContext;\n const eventBus = useEventBus();\n const { hasPermission } = useToursPermissionAccess();\n const canListTemplates = hasPermission(TOUR_PERMISSIONS.TOUR_TEMPLATE_LIST);\n const canUseTemplate = hasPermission(TOUR_PERMISSIONS.TOUR_TEMPLATE_USE);\n const canDeleteTemplate = hasPermission(TOUR_PERMISSIONS.TOUR_TEMPLATE_DELETE);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [categoryFilter, setCategoryFilter] = useState<TemplateCategory | 'all'>('all');\n const [publicOnly, setPublicOnly] = useState(false);\n\n const { data, loading } = useGetTourTemplatesQuery({\n skip: !canListTemplates,\n variables: {\n isPublic: publicOnly || undefined,\n category: categoryFilter === 'all' ? undefined : categoryFilter,\n },\n });\n const templates = data?.tourTemplates ?? [];\n\n const filteredTemplates = templates.filter(\n (t: (typeof templates)[number]) =>\n !searchQuery || t.name.toLowerCase().includes(searchQuery.toLowerCase())\n );\n\n const [createFromTemplate] = useCreateTourFromTemplateMutation();\n\n const handleUseTemplate = async (templateId: string) => {\n nativeImpact('light');\n try {\n const { data } = await createFromTemplate({\n variables: { templateId },\n });\n if (data?.createTourFromTemplate) {\n void nativeNotify('success');\n navigate(`${basePath}/${data.createTourFromTemplate.id}/edit`);\n }\n } catch {\n void nativeNotify('error');\n emitToursApiFailed(eventBus, 'template.create_from_template', toursContext);\n }\n };\n\n const [deleteTemplate] = useDeleteTourTemplateMutation();\n\n const handleDeleteTemplate = async (templateId: string) => {\n try {\n await deleteTemplate({\n variables: { id: templateId },\n refetchQueries: ['GetTourTemplates'],\n });\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n emitToursApiFailed(eventBus, 'template.delete', toursContext);\n }\n };\n\n const handleDeleteTemplateClick = async (templateId: string) => {\n nativeImpact('medium');\n const message = tr('templateLibrary.deleteConfirm');\n const nativeResult = await nativeConfirm({\n title: tr('templateLibrary.deleteConfirm'),\n message,\n });\n const confirmed = nativeResult === null ? window.confirm(message) : nativeResult;\n if (confirmed) await handleDeleteTemplate(templateId);\n };\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold\">{tr('templateLibrary.title')}</h1>\n <p className=\"text-sm text-muted-foreground\">{tr('templateLibrary.subtitle')}</p>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <div className=\"flex-1 relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n size={20}\n />\n <input\n type=\"text\"\n placeholder={tr('templateLibrary.searchPlaceholder')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border rounded-md bg-background\"\n aria-label={tr('templateLibrary.searchPlaceholder')}\n />\n </div>\n <select\n value={categoryFilter}\n onChange={(e) => setCategoryFilter(e.target.value as TemplateCategory | 'all')}\n className=\"px-4 py-2 min-h-[44px] border border-border rounded-md bg-background\"\n aria-label={tr('templateLibrary.allCategories')}\n >\n <option value=\"all\">{tr('templateLibrary.allCategories')}</option>\n <option value=\"ONBOARDING\">{tr('templateLibrary.categoryOnboarding')}</option>\n <option value=\"FEATURE_ANNOUNCEMENT\">\n {tr('templateLibrary.categoryFeatureAnnouncement')}\n </option>\n <option value=\"HELP\">{tr('templateLibrary.categoryHelp')}</option>\n <option value=\"CUSTOM\">{tr('templateLibrary.categoryCustom')}</option>\n </select>\n <label className=\"flex items-center gap-2 px-4 py-2 min-h-[44px] border border-border rounded-md bg-background cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={publicOnly}\n onChange={(e) => setPublicOnly(e.target.checked)}\n className=\"rounded\"\n aria-label={tr('templateLibrary.publicOnly')}\n />\n <span className=\"text-sm\">{tr('templateLibrary.publicOnly')}</span>\n </label>\n </div>\n\n {/* Templates Grid */}\n {loading ? (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground\">{tr('templateLibrary.loading')}</p>\n </div>\n ) : filteredTemplates.length > 0 ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {filteredTemplates.map((template: (typeof filteredTemplates)[number]) => (\n <div\n key={template.id}\n className=\"border border-border rounded-md overflow-hidden hover:shadow-md transition-shadow\"\n >\n {/* Thumbnail */}\n <div className=\"aspect-video bg-muted flex items-center justify-center\">\n {template.thumbnail ? (\n <img\n src={template.thumbnail}\n alt={template.name}\n className=\"w-full h-full object-cover\"\n />\n ) : (\n <LayoutTemplate className=\"text-muted-foreground\" size={48} />\n )}\n </div>\n\n {/* Content */}\n <div className=\"p-4\">\n <div className=\"flex items-start justify-between mb-2\">\n <h3 className=\"font-semibold\">{template.name}</h3>\n <div className=\"flex gap-1\">\n {template.isPremium && <Star className=\"text-warning\" size={16} />}\n {template.isPublic ? (\n <Globe className=\"text-muted-foreground\" size={16} />\n ) : (\n <Lock className=\"text-muted-foreground\" size={16} />\n )}\n </div>\n </div>\n\n {template.description && (\n <p className=\"text-sm text-muted-foreground mb-3 line-clamp-2\">\n {template.description}\n </p>\n )}\n\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs text-muted-foreground\">\n {tr('templateLibrary.usedTimes')} {template.useCount}{' '}\n {tr('templateLibrary.times')}\n </span>\n <div className=\"flex items-center gap-2\">\n {canDeleteTemplate && (\n <button\n type=\"button\"\n onClick={() => void handleDeleteTemplateClick(template.id)}\n className=\"p-2 min-h-[44px] min-w-[44px] text-muted-foreground hover:text-destructive rounded-md\"\n aria-label={tr('templateLibrary.deleteTemplate')}\n >\n <Trash2 size={16} />\n </button>\n )}\n {canUseTemplate && (\n <button\n type=\"button\"\n onClick={() => handleUseTemplate(template.id)}\n className=\"px-3 py-2 min-h-[44px] bg-primary text-primary-foreground rounded-md hover:bg-primary/90 text-sm\"\n >\n {tr('templateLibrary.useTemplate')}\n </button>\n )}\n </div>\n </div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12 border border-dashed border-border rounded-md\">\n <LayoutTemplate className=\"mx-auto mb-4 text-muted-foreground\" size={48} />\n <p className=\"text-muted-foreground mb-2\">{tr('templateLibrary.noTemplatesFound')}</p>\n <p className=\"text-sm text-muted-foreground\">{tr('templateLibrary.adjustFilters')}</p>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|