@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.2
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/components/actions/ActionCard.js +2 -2
- package/dist/components/actions/ActionCard.js.map +1 -1
- package/dist/components/actions/ExecutionHistory.js +1 -1
- package/dist/components/actions/ExecutionHistory.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js +2 -2
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
- package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
- package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
- package/dist/components/agent-manager/BridleConfigPanel.js +23 -23
- package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js +5 -5
- package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionConfigPanel.js +6 -6
- package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
- package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
- package/dist/components/agents/AgentCard.js +14 -11
- package/dist/components/agents/AgentCard.js.map +1 -1
- package/dist/components/ai/AIContextsTab.js +1 -1
- package/dist/components/ai/AIContextsTab.js.map +1 -1
- package/dist/components/ai/AIOverviewTab.js +4 -4
- package/dist/components/ai/AIOverviewTab.js.map +1 -1
- package/dist/components/ai/AIPlanPanel.js +4 -4
- package/dist/components/ai/AIPlanPanel.js.map +1 -1
- package/dist/components/ai/AIPlaygroundTab.js +1 -1
- package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
- package/dist/components/ai/AIPromptsTab.js +2 -2
- package/dist/components/ai/AIPromptsTab.js.map +1 -1
- package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
- package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
- package/dist/components/ai/AIQueueTab.js +4 -4
- package/dist/components/ai/AIQueueTab.js.map +1 -1
- package/dist/components/ai/AISessionsTab.js +4 -4
- package/dist/components/ai/AISessionsTab.js.map +1 -1
- package/dist/components/ai/AITasksTab.js +1 -1
- package/dist/components/ai/AITasksTab.js.map +1 -1
- package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
- package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
- package/dist/components/ai/PluginHarnessPicker.js +3 -3
- package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
- package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
- package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
- package/dist/components/audit/AuditDeleteDialog.js +2 -2
- package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
- package/dist/components/audit/AuditExportButton.js +5 -5
- package/dist/components/audit/AuditExportButton.js.map +1 -1
- package/dist/components/audit/AuditLogDetail.js +9 -9
- package/dist/components/audit/AuditLogDetail.js.map +1 -1
- package/dist/components/audit/AuditLogFilters.js +1 -1
- package/dist/components/audit/AuditLogFilters.js.map +1 -1
- package/dist/components/audit/AuditLogStats.js +1 -1
- package/dist/components/audit/AuditLogStats.js.map +1 -1
- package/dist/components/audit/AuditLogTable.js +28 -28
- package/dist/components/audit/AuditLogTable.js.map +1 -1
- package/dist/components/catalog/CatalogCard.js +3 -3
- package/dist/components/catalog/CatalogCard.js.map +1 -1
- package/dist/components/catalog/CatalogForm.js +1 -1
- package/dist/components/catalog/CatalogForm.js.map +1 -1
- package/dist/components/common/ActionAgentTargetNotice.js +6 -6
- package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
- package/dist/components/common/CommandEditor.js +1 -1
- package/dist/components/common/CommandEditor.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/docs/DocsPreviewPanel.js +1 -1
- package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
- package/dist/components/docs/DocsRevisionHistory.js +4 -4
- package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
- package/dist/components/docs/DocsSettingsPanel.js +7 -7
- package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
- package/dist/components/docs/DocsTabPanel.js +1 -1
- package/dist/components/docs/DocsTabPanel.js.map +1 -1
- package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
- package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
- package/dist/components/graph/MeshDeployWizard.js +3 -3
- package/dist/components/graph/MeshDeployWizard.js.map +1 -1
- package/dist/components/graph/MeshRequestPanel.js +2 -2
- package/dist/components/graph/MeshRequestPanel.js.map +1 -1
- package/dist/components/idp-components/ComponentCard.js +5 -5
- package/dist/components/idp-components/ComponentCard.js.map +1 -1
- package/dist/components/idp-components/ComponentForm.js +1 -1
- package/dist/components/idp-components/ComponentForm.js.map +1 -1
- package/dist/components/idp-components/DependencyGraphTab.js +7 -7
- package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
- package/dist/components/logs/LogViewer.js +14 -14
- package/dist/components/logs/LogViewer.js.map +1 -1
- package/dist/components/notes/NoteCard.js +1 -1
- package/dist/components/notes/NoteCard.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +2 -2
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/notes/NoteList.js +1 -1
- package/dist/components/notes/NoteList.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +1 -1
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
- package/dist/components/scorecard/ScorecardPanel.js +3 -3
- package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
- package/dist/components/security/SecurityScanList.js +11 -11
- package/dist/components/security/SecurityScanList.js.map +1 -1
- package/dist/components/sessions/SessionCard.js +26 -19
- package/dist/components/sessions/SessionCard.js.map +1 -1
- package/dist/components/sessions/SessionStatusBadge.js +7 -7
- package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
- package/dist/components/shared/AgentSelector.js +4 -4
- package/dist/components/shared/AgentSelector.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +3 -3
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/Card.js +4 -4
- package/dist/components/shared/Card.js.map +1 -1
- package/dist/components/shared/ErrorState.js +15 -9
- package/dist/components/shared/ErrorState.js.map +1 -1
- package/dist/components/shared/FilterDropdown.js +2 -2
- package/dist/components/shared/FilterDropdown.js.map +1 -1
- package/dist/components/shared/PageSkeleton.js +6 -6
- package/dist/components/shared/PageSkeleton.js.map +1 -1
- package/dist/components/shared/SearchInput.js +1 -1
- package/dist/components/shared/SearchInput.js.map +1 -1
- package/dist/components/shared/StatCard.js +7 -7
- package/dist/components/shared/StatCard.js.map +1 -1
- package/dist/components/templates/PublishTemplateForm.js +2 -2
- package/dist/components/templates/PublishTemplateForm.js.map +1 -1
- package/dist/components/templates/TemplateCard.js +5 -5
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/templates/TemplateWizard.js +2 -2
- package/dist/components/templates/TemplateWizard.js.map +1 -1
- package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
- package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
- package/dist/components/tunnels/ProviderBadge.js +1 -1
- package/dist/components/tunnels/ProviderBadge.js.map +1 -1
- package/dist/components/tunnels/TunnelCard.js +16 -16
- package/dist/components/tunnels/TunnelCard.js.map +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
- package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
- package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
- package/dist/components/vibecalendar/LogsDrawer.js +2 -2
- package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
- package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
- package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
- package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
- package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
- package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
- package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
- package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
- package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
- package/dist/components/vibecalendar/types.js +6 -6
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
- package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
- package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
- package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +8 -8
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
- package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
- package/dist/components/vibes/VibeCard.js +22 -18
- package/dist/components/vibes/VibeCard.js.map +1 -1
- package/dist/components/vibes/VibeList.js +16 -12
- package/dist/components/vibes/VibeList.js.map +1 -1
- package/dist/pages/ActionsPage.js +4 -3
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +9 -9
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentsLandingPage.js +4 -4
- package/dist/pages/AgentsLandingPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +14 -14
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/AnalyticsPage.js +8 -8
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +11 -11
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +12 -9
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +7 -7
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +12 -9
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/EnvTopologyPage.js +2 -2
- package/dist/pages/EnvTopologyPage.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +12 -12
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/pages/HealthPage.js +12 -12
- package/dist/pages/HealthPage.js.map +1 -1
- package/dist/pages/LogsPage.js +4 -4
- package/dist/pages/LogsPage.js.map +1 -1
- package/dist/pages/NoteDetailsPage.js +5 -5
- package/dist/pages/NoteDetailsPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +74 -41
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +6 -3
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/ScheduledJobDetailPage.js +7 -7
- package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +21 -21
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +20 -20
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SettingsPage.js +64 -52
- package/dist/pages/SettingsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +21 -21
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/SharedVibeDeckPage.js +5 -5
- package/dist/pages/SharedVibeDeckPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +56 -65
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +4 -4
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +10 -10
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/TemplatesPage.js +6 -5
- package/dist/pages/TemplatesPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +3 -3
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/dist/pages/VibeCalendarPage.js +3 -3
- package/dist/pages/VibeCalendarPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +3 -3
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +15 -15
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibeTaskDetailPage.js +5 -5
- package/dist/pages/VibeTaskDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +18 -18
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +11 -11
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AIBookmarksPage.js +1 -1
- package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +6 -6
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/pages/tunnels/AuditTab.js +1 -1
- package/dist/pages/tunnels/AuditTab.js.map +1 -1
- package/dist/pages/tunnels/DomainsTab.js +1 -1
- package/dist/pages/tunnels/DomainsTab.js.map +1 -1
- package/dist/pages/tunnels/ProvidersTab.js +1 -1
- package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
- package/dist/pages/tunnels/SessionsTab.js +1 -1
- package/dist/pages/tunnels/SessionsTab.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/UsageTab.js +1 -1
- package/dist/pages/tunnels/UsageTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TemplateDetailPage.js","names":[],"sources":["../../src/pages/TemplateDetailPage.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n ArrowLeft,\n FileCode,\n Copy,\n Layers,\n Tag,\n FolderTree,\n Clock,\n RefreshCw,\n Trash2,\n Variable,\n Github,\n CheckCircle2,\n XCircle,\n Loader2,\n} from 'lucide-react';\nimport {\n PageHeader,\n Card,\n EmptyState,\n ErrorState,\n ConfirmDialog,\n QuotaExhaustedDialog,\n} from '@/components/shared';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { TemplateWizard } from '@/components/templates';\nimport type { TemplateVariable, GitHubConfig, GitHubConnection } from '@/components/templates';\nimport { EntityShareButton } from '@/components/sharing';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useGetTemplateQuery,\n useInstantiateTemplateMutation,\n useDeleteTemplateMutation,\n useUpdateComponentMutation,\n useForkGitHubRepoMutation,\n useListActiveConnectionsQuery,\n} from '@/generated/wspace-operations';\n\nfunction substituteVars(text: string, vars: Record<string, unknown>): string {\n return text.replace(/\\{\\{(\\w+)\\}\\}|\\{(\\w+)\\}/g, (_match, a, b) => {\n const key = a ?? b;\n return key in vars ? String(vars[key]) : _match;\n });\n}\n\nfunction parseGitHubRepo(url: string): { owner: string; repo: string } | null {\n try {\n const match = url.match(/^https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+?)(?:\\.git)?(?:\\/.*)?$/);\n if (!match) return null;\n return { owner: match[1], repo: match[2] };\n } catch {\n return null;\n }\n}\n\nasync function scaffoldGitHubRepo(\n fullName: string,\n headers: Record<string, string>,\n data: {\n name: string;\n description: string;\n variableValues: Record<string, unknown>;\n },\n template: {\n name: string;\n description?: string | null;\n setupInstructions?: string | null;\n fileTree?: unknown;\n variables?: unknown;\n } | null\n) {\n if (!template) return;\n\n const vars = data.variableValues;\n const templateVars = Array.isArray(template.variables)\n ? (template.variables as Array<{ name: string; title?: string }>)\n : [];\n\n // Build README content\n const lines: string[] = [];\n lines.push(`# ${data.name}`);\n lines.push('');\n if (data.description) {\n lines.push(data.description);\n lines.push('');\n }\n lines.push(`> Scaffolded from VibeControls template: **${template.name}**`);\n lines.push('');\n\n // Variable values table\n if (templateVars.length > 0 && Object.keys(vars).length > 0) {\n lines.push('## Configuration');\n lines.push('');\n lines.push('| Variable | Value |');\n lines.push('|----------|-------|');\n for (const v of templateVars) {\n const val = vars[v.name];\n if (val !== undefined) {\n lines.push(`| ${v.title ?? v.name} | \\`${String(val)}\\` |`);\n }\n }\n lines.push('');\n }\n\n // Setup instructions (with variable substitution)\n if (template.setupInstructions) {\n lines.push('## Getting Started');\n lines.push('');\n lines.push(substituteVars(template.setupInstructions, vars));\n lines.push('');\n }\n\n // File tree\n if (template.fileTree) {\n lines.push('## Project Structure');\n lines.push('');\n lines.push('```');\n lines.push(\n typeof template.fileTree === 'string'\n ? template.fileTree\n : JSON.stringify(template.fileTree, null, 2)\n );\n lines.push('```');\n lines.push('');\n }\n\n const readmeContent = lines.join('\\n');\n\n // Get existing README.md SHA so we can update it\n const existing = await fetch(`https://api.github.com/repos/${fullName}/contents/README.md`, {\n headers,\n });\n let sha: string | undefined;\n if (existing.ok) {\n const existingData = (await existing.json()) as { sha?: string };\n sha = existingData.sha;\n }\n\n // Push scaffolded README.md\n await fetch(`https://api.github.com/repos/${fullName}/contents/README.md`, {\n method: 'PUT',\n headers,\n body: JSON.stringify({\n message: `chore: scaffold from VibeControls template \"${template.name}\"`,\n content: btoa(unescape(encodeURIComponent(readmeContent))),\n ...(sha ? { sha } : {}),\n }),\n });\n\n // Push GETTING_STARTED.md with setup instructions + resolved variable values\n if (template.setupInstructions || (templateVars.length > 0 && Object.keys(vars).length > 0)) {\n const gsLines: string[] = ['# Getting Started', ''];\n\n if (template.setupInstructions) {\n gsLines.push(substituteVars(template.setupInstructions, vars));\n gsLines.push('');\n }\n\n // Always append a resolved variables section so values are never lost\n if (templateVars.length > 0 && Object.keys(vars).length > 0) {\n gsLines.push('---');\n gsLines.push('');\n gsLines.push('## Template Variable Values');\n gsLines.push('');\n gsLines.push('These are the values you chose when creating this project:');\n gsLines.push('');\n for (const v of templateVars) {\n const val = vars[v.name];\n if (val !== undefined && val !== '') {\n gsLines.push(`**${v.title ?? v.name}**`);\n gsLines.push('');\n gsLines.push('```');\n gsLines.push(String(val));\n gsLines.push('```');\n gsLines.push('');\n }\n }\n }\n\n await fetch(`https://api.github.com/repos/${fullName}/contents/GETTING_STARTED.md`, {\n method: 'PUT',\n headers,\n body: JSON.stringify({\n message: 'docs: add getting started guide from template',\n content: btoa(unescape(encodeURIComponent(gsLines.join('\\n')))),\n }),\n });\n }\n}\n\nconst categoryStyles: Record<string, string> = {\n STARTER: 'bg-bg-sunken text-text-secondary',\n MICROSERVICE: 'bg-action-primary-bg/10 text-action-primary-bg',\n FRONTEND: 'bg-status-info-bg text-status-info-text',\n BACKEND: 'bg-action-primary-bg/10 text-action-primary-bg',\n FULLSTACK: 'bg-status-success-bg text-status-success-text',\n INFRASTRUCTURE: 'bg-status-warning-bg text-status-warning-text',\n LIBRARY: 'bg-status-warning-bg text-status-warning-text',\n CUSTOM: 'bg-bg-sunken text-text-secondary',\n};\n\nexport function TemplateDetailPage() {\n const { t } = useI18n();\n const { id } = useParams<{ id: string }>();\n const navigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_READ);\n // `instantiateVibecontrolsTemplate` creates a component, but the gateway\n // directive for that mutation is `@rbac(action: \"create\", resource: \"template\")`.\n const canCreateTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_CREATE);\n const canDeleteTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_DELETE);\n const canReadIntegrationConnection = hasPermission(VIBE_PERMISSIONS.INTEGRATION_CONNECTION_READ);\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const [showWizard, setShowWizard] = useState(false);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [cloneError, setCloneError] = useState<string | null>(null);\n const [forkNotice, setForkNotice] = useState<string | null>(null);\n const [repoProgress, setRepoProgress] = useState<{\n stage: string;\n done: boolean;\n error: boolean;\n } | null>(null);\n\n const { data, loading, error, refetch } = useGetTemplateQuery({\n variables: { id: id || '' },\n skip: !id || permissionsLoading || !canReadTemplate,\n fetchPolicy: 'cache-and-network',\n });\n\n const [instantiateTemplate] = useInstantiateTemplateMutation({\n refetchQueries: ['ListComponents'],\n });\n const [deleteTemplate] = useDeleteTemplateMutation({ refetchQueries: ['ListTemplates'] });\n const [updateComponent] = useUpdateComponentMutation();\n const [forkGitHubRepo] = useForkGitHubRepoMutation();\n\n // Fetch active GitHub connections for the workspace\n const { data: connectionsData } = useListActiveConnectionsQuery({\n variables: { filter: { status: 'ACTIVE' as const }, pagination: { limit: 50, offset: 0 } },\n fetchPolicy: 'cache-first',\n skip: !canCreateTemplate || !canReadIntegrationConnection,\n });\n const githubConnections: GitHubConnection[] = (connectionsData?.getConnections?.items ?? [])\n .filter((c: { provider?: { key?: string } | null }) => c.provider?.key === 'github')\n .map(\n (c: {\n id: string;\n name: string;\n externalAccountEmail?: string | null;\n externalAccountName?: string | null;\n provider: { key: string; name: string };\n }) => ({\n id: c.id,\n name: c.name,\n externalAccountEmail: c.externalAccountEmail,\n externalAccountName: c.externalAccountName,\n provider: { key: c.provider.key, name: c.provider.name },\n })\n );\n\n const template = data?.vibecontrolsTemplate ?? null;\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">Loading permissions…</p>\n </div>\n );\n if (!canReadTemplate)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-foreground\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary\">\n You don't have permission to access this page.\n </p>\n </div>\n </div>\n );\n\n const handleBack = () => {\n navigate('/templates');\n };\n\n const handleClone = async (data: {\n name: string;\n description: string;\n variableValues: Record<string, unknown>;\n githubConfig: GitHubConfig | null;\n }) => {\n if (!canCreateTemplate) return;\n\n setShowWizard(false);\n setRepoProgress({ stage: 'Creating component…', done: false, error: false });\n\n const result = await instantiateTemplate({\n variables: {\n templateId: id,\n input: {\n name: data.name,\n description: data.description || undefined,\n ...(Object.keys(data.variableValues).length > 0 && {\n variableValues: data.variableValues,\n }),\n },\n },\n });\n if (result.error && isQuotaExhaustedError(result.error)) {\n setRepoProgress(null);\n setShowQuotaExhausted(true);\n return;\n }\n const newId = result.data?.instantiateVibecontrolsTemplate?.id;\n\n // GitHub repo creation + scaffolding\n if (newId && data.githubConfig?.enabled) {\n // Ensure repo URL starts empty before GitHub runs\n await updateComponent({ variables: { id: newId, input: { repositoryUrl: null } } });\n const gh = data.githubConfig;\n\n // Priority: explicit sourceRepositoryUrl > snapshot URL > sourceComponent.repositoryUrl\n const snapshotRepoUrl = (template?.componentSnapshot as Record<string, unknown> | null)\n ?.repositoryUrl as string | undefined;\n const sourceRepoUrl =\n template?.sourceRepositoryUrl ||\n snapshotRepoUrl ||\n template?.sourceComponent?.repositoryUrl ||\n undefined;\n\n try {\n if (gh.connectionId) {\n // ── OAuth path: backend resolves the token server-side ──────────────\n setRepoProgress({\n stage: sourceRepoUrl\n ? 'Forking repository via connected account…'\n : 'Creating GitHub repository via connected account…',\n done: false,\n error: false,\n });\n\n const forkResult = await forkGitHubRepo({\n variables: {\n input: {\n connectionId: gh.connectionId,\n sourceRepoUrl: sourceRepoUrl || undefined,\n repoName: gh.repoName,\n visibility: gh.visibility === 'private' ? 'PRIVATE' : 'PUBLIC',\n scope: gh.scope === 'org' ? 'ORG' : 'USER',\n orgName: gh.orgName || undefined,\n description:\n data.description || `Created from VibeControls template: ${template?.name}`,\n },\n },\n });\n\n const forkData = forkResult.data?.forkVibecontrolsGitHubRepo;\n if (forkData) {\n setRepoProgress({\n stage: forkData.isForked ? 'Repository forked!' : 'Repository created!',\n done: true,\n error: false,\n });\n await updateComponent({\n variables: { id: newId, input: { repositoryUrl: forkData.repoUrl } },\n });\n await new Promise((r) => setTimeout(r, 1200));\n } else {\n setRepoProgress({\n stage: 'GitHub repo creation failed — check your connected account',\n done: false,\n error: true,\n });\n setTimeout(() => setRepoProgress(null), 6000);\n }\n } else {\n // ── PAT path: existing client-side GitHub API flow ──────────────────\n const ghHeaders = {\n Authorization: `Bearer ${gh.token}`,\n 'Content-Type': 'application/json',\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n };\n const apiBase = 'https://api.github.com';\n\n const sourceRepo = sourceRepoUrl ? parseGitHubRepo(sourceRepoUrl) : null;\n let repoData: { html_url: string; full_name: string } | null = null;\n let isForked = false;\n\n if (sourceRepo) {\n setRepoProgress({\n stage: `Forking ${sourceRepo.owner}/${sourceRepo.repo}…`,\n done: false,\n error: false,\n });\n const forkRes = await fetch(\n `${apiBase}/repos/${encodeURIComponent(sourceRepo.owner)}/${encodeURIComponent(sourceRepo.repo)}/forks`,\n {\n method: 'POST',\n headers: ghHeaders,\n body: JSON.stringify({\n name: gh.repoName,\n ...(gh.scope === 'org' ? { organization: gh.orgName } : {}),\n default_branch_only: false,\n }),\n }\n );\n\n if (forkRes.ok) {\n repoData = (await forkRes.json()) as { html_url: string; full_name: string };\n isForked = true;\n } else {\n const forkErr = (await forkRes.json()) as { message?: string };\n setRepoProgress(null);\n setForkNotice(\n `Could not fork ${sourceRepoUrl} (${forkRes.status}: ${forkErr.message ?? forkRes.statusText}) — creating a new empty repository instead.`\n );\n setTimeout(() => setForkNotice(null), 10000);\n }\n }\n\n if (!repoData) {\n setRepoProgress({ stage: 'Creating GitHub repository…', done: false, error: false });\n const createEndpoint =\n gh.scope === 'org'\n ? `${apiBase}/orgs/${encodeURIComponent(gh.orgName)}/repos`\n : `${apiBase}/user/repos`;\n\n const repoRes = await fetch(createEndpoint, {\n method: 'POST',\n headers: ghHeaders,\n body: JSON.stringify({\n name: gh.repoName,\n private: gh.visibility === 'private',\n auto_init: true,\n description:\n data.description || `Created from VibeControls template: ${template?.name}`,\n }),\n });\n\n if (repoRes.ok) {\n repoData = (await repoRes.json()) as { html_url: string; full_name: string };\n } else {\n const errBody = (await repoRes.json()) as { message?: string };\n setRepoProgress({\n stage: `GitHub repo failed: ${errBody.message ?? repoRes.statusText}`,\n done: false,\n error: true,\n });\n setTimeout(() => setRepoProgress(null), 6000);\n }\n }\n\n if (repoData) {\n if (!isForked) {\n setRepoProgress({ stage: 'Scaffolding README…', done: false, error: false });\n await scaffoldGitHubRepo(repoData.full_name, ghHeaders, data, template);\n }\n\n setRepoProgress({\n stage: isForked ? 'Repository forked!' : 'Repository created!',\n done: true,\n error: false,\n });\n await updateComponent({\n variables: { id: newId, input: { repositoryUrl: repoData.html_url } },\n });\n await new Promise((r) => setTimeout(r, 1200));\n setRepoProgress(null);\n } else {\n await new Promise((r) => setTimeout(r, 3000));\n setRepoProgress(null);\n }\n }\n } catch {\n setRepoProgress({\n stage: 'GitHub repo creation failed — check your credentials',\n done: false,\n error: true,\n });\n setTimeout(() => setRepoProgress(null), 6000);\n }\n }\n\n setRepoProgress(null);\n if (newId) {\n navigate(`/components/${newId}`);\n } else {\n setSuccessMessage(tr('template.clonedSuccess', 'Component created — check Components list'));\n setTimeout(() => setSuccessMessage(null), 4000);\n }\n };\n\n const handleDeleteTemplate = async () => {\n if (!id) return;\n setIsDeleting(true);\n try {\n await deleteTemplate({ variables: { id } });\n setShowDeleteConfirm(false);\n navigate('/templates');\n } finally {\n setIsDeleting(false);\n }\n };\n\n // Guard states\n if (loading && !template) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('template.backToTemplates', 'Back to Templates')}\n </button>\n <PageSkeleton variant=\"detail\" />\n </MotionPage>\n );\n }\n\n if (error && !template) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('template.backToTemplates', 'Back to Templates')}\n </button>\n <ErrorState\n title={tr('template.failedLoad', 'Failed to load template')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n if (!template) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('template.backToTemplates', 'Back to Templates')}\n </button>\n <EmptyState\n icon={<FileCode className=\"size-8 text-text-secondary\" />}\n title={tr('template.notFound', 'Template not found')}\n description={`No template with ID \"${id}\" exists`}\n action={\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('template.backToTemplates', 'Back to Templates')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n {/* Success Toast */}\n {successMessage && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-success-bg text-status-success-text px-4 py-3 rounded-md shadow-lg\">\n {successMessage}\n </div>\n )}\n {forkNotice && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-warning-bg text-status-warning-text px-4 py-3 rounded-md shadow-lg text-sm max-w-sm\">\n {forkNotice}\n </div>\n )}\n {/* Error Toast */}\n {cloneError && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-error-bg text-status-error-text px-4 py-3 rounded-md shadow-lg\">\n {cloneError}\n </div>\n )}\n {/* Repo creation progress overlay */}\n {repoProgress && (\n <div\n className=\"fixed inset-0 z-[9999] flex items-center justify-center p-4\"\n style={{ backgroundColor: 'rgba(0,0,0,0.55)' }}\n >\n <div className=\"bg-bg-surface border border-border-default rounded-xl shadow-2xl p-8 flex flex-col items-center gap-4 max-w-xs w-full\">\n {repoProgress.done ? (\n <CheckCircle2 className=\"size-10 text-status-success-text\" />\n ) : repoProgress.error ? (\n <XCircle className=\"size-10 text-status-error-text\" />\n ) : (\n <Loader2 className=\"size-10 text-action-primary-bg animate-spin\" />\n )}\n <p className=\"text-sm font-medium text-text-primary text-center\">\n {repoProgress.stage}\n </p>\n </div>\n </div>\n )}\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('template.backToTemplates', 'Back to Templates')}\n </button>\n\n {/* Header */}\n <PageHeader\n title={template.name}\n description={template.description || tr('template.noDescription', 'No description')}\n actions={\n <div className=\"flex items-center gap-2 flex-wrap\">\n <EntityShareButton\n entity=\"template\"\n resource={{ id: template.id, name: template.name }}\n label={tr('template.share', 'Share')}\n variant=\"button\"\n />\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center justify-center p-2 bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n {canCreateTemplate && (\n <button\n type=\"button\"\n onClick={() => setShowWizard(true)}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Copy className=\"size-4\" />\n {tr('template.useTemplate', 'Use Template')}\n </button>\n )}\n {canDeleteTemplate && (\n <button\n type=\"button\"\n data-testid=\"template-delete-button\"\n onClick={() => setShowDeleteConfirm(true)}\n className=\"flex items-center gap-2 px-4 py-2 bg-status-error-bg text-status-error-text rounded-md hover:opacity-90 transition-opacity\"\n aria-label={tr('template.deleteTemplate', 'Delete Template')}\n >\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n {tr('common.delete', 'Delete')}\n </button>\n )}\n </div>\n }\n />\n\n {/* Context hint for new users */}\n {canCreateTemplate && (\n <p className=\"text-sm text-text-muted\">\n Click <span className=\"font-medium text-text-secondary\">Use Template</span> to open a\n short wizard — give your new component a name, fill in any variables, and optionally\n create a GitHub repository. The whole thing takes under a minute.\n </p>\n )}\n\n {/* Badges */}\n <div className=\"flex items-center gap-3 flex-wrap\">\n {template.category && (\n <span\n className={`inline-flex items-center gap-1.5 px-2.5 py-1 text-sm font-medium rounded ${categoryStyles[template.category] || 'bg-bg-sunken text-text-secondary'}`}\n >\n <Layers className=\"size-3.5\" />\n {template.category}\n </span>\n )}\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Copy className=\"size-3.5\" />\n {template.cloneCount} {tr('template.clones', 'clones')}\n </span>\n {template.sourceRepositoryUrl && (\n <a\n href={template.sourceRepositoryUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className=\"inline-flex items-center gap-1.5 text-sm text-action-primary-bg hover:underline\"\n >\n <Github className=\"size-3.5\" />\n {template.sourceRepositoryUrl.replace('https://github.com/', '')}\n </a>\n )}\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock className=\"size-3.5\" />\n {new Date(template.createdAt).toLocaleDateString()}\n </span>\n </div>\n\n {/* Tags */}\n {template.tags?.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {template.tags.map((tag: string) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 px-2.5 py-1 text-sm bg-bg-sunken text-text-secondary rounded-md\"\n >\n <Tag className=\"size-3\" />\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Universal tags */}\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Tag className=\"size-5\" />\n <h3 className=\"font-semibold text-text-primary\">{tr('template.tags', 'Tags')}</h3>\n </div>\n <div className=\"p-4\">\n <EntityTagPicker entityType=\"VIBECONTROLS_TEMPLATE\" entityId={template.id} />\n </div>\n </Card>\n\n {/* Source Component */}\n {template.sourceComponent && (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('template.sourceComponent', 'Source Component')}\n </h3>\n </div>\n <div className=\"p-4\">\n <button\n type=\"button\"\n onClick={() => navigate(`/components/${template.sourceComponent.id}`)}\n className=\"flex items-center gap-3 text-left hover:bg-bg-sunken p-2 -m-2 rounded-md transition-colors\"\n >\n <div className=\"size-10 rounded-lg bg-action-primary-bg/10 flex items-center justify-center flex-shrink-0\">\n <FileCode className=\"size-5 text-action-primary-bg\" />\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{template.sourceComponent.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {template.sourceComponent.type} · {template.sourceComponent.lifecycle}\n </p>\n </div>\n </button>\n </div>\n </Card>\n )}\n\n {/* Cloned Components */}\n {template.clonedComponents && template.clonedComponents.length > 0 && (\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Copy className=\"size-5\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('template.createdComponents', 'Created Components')}\n </h3>\n <span className=\"ml-auto text-xs text-text-muted\">\n {template.clonedComponents.length} component\n {template.clonedComponents.length !== 1 ? 's' : ''}\n </span>\n </div>\n <div className=\"divide-y divide-border-default\">\n {template.clonedComponents.map(\n (comp: {\n id: string;\n name: string;\n description?: string | null;\n type: string;\n lifecycle: string;\n repositoryUrl?: string | null;\n createdAt: string;\n }) => (\n <button\n type=\"button\"\n key={comp.id}\n onClick={() => navigate(`/components/${comp.id}`)}\n className=\"flex items-center gap-3 w-full text-left px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"size-8 rounded-md bg-action-primary-bg/10 flex items-center justify-center flex-shrink-0\">\n <FileCode className=\"size-4 text-action-primary-bg\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{comp.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {comp.type} · {comp.lifecycle}\n {comp.repositoryUrl && (\n <>\n {' '}\n ·{' '}\n <span className=\"font-mono\">\n {comp.repositoryUrl.replace('https://github.com/', '')}\n </span>\n </>\n )}\n </p>\n </div>\n <span className=\"text-xs text-text-muted flex-shrink-0\">\n {new Date(comp.createdAt).toLocaleDateString()}\n </span>\n </button>\n )\n )}\n </div>\n </Card>\n )}\n\n {/* Variables preview */}\n {Array.isArray(template.variables) &&\n (template.variables as TemplateVariable[]).length > 0 && (\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Variable className=\"size-5\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('template.variables', 'Template Variables')}\n </h3>\n <span className=\"ml-auto text-xs text-text-muted\">\n {(template.variables as TemplateVariable[]).length} variable\n {(template.variables as TemplateVariable[]).length !== 1 ? 's' : ''}\n </span>\n </div>\n <div className=\"divide-y divide-border-default\">\n {(template.variables as TemplateVariable[]).map((v) => (\n <div key={v.name} className=\"px-4 py-3 flex items-start gap-4\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary font-mono\">\n {v.name}\n </span>\n {v.required && (\n <span className=\"text-xs bg-status-error-bg text-status-error-text px-1.5 py-0.5 rounded\">\n required\n </span>\n )}\n <span className=\"text-xs bg-bg-sunken text-text-muted px-1.5 py-0.5 rounded\">\n {v.type}\n </span>\n </div>\n {v.title && v.title !== v.name && (\n <p className=\"text-xs text-text-secondary mt-0.5\">{v.title}</p>\n )}\n {v.default && (\n <p className=\"text-xs text-text-muted mt-0.5\">\n Default: <span className=\"font-mono\">{v.default}</span>\n </p>\n )}\n </div>\n {v.options.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {v.options.map((opt) => (\n <span\n key={opt}\n className=\"text-xs bg-bg-sunken text-text-secondary px-1.5 py-0.5 rounded\"\n >\n {opt}\n </span>\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n </Card>\n )}\n\n {/* Setup Instructions */}\n {template.setupInstructions && (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('template.setupInstructions', 'Setup Instructions')}\n </h3>\n </div>\n <div className=\"p-4\">\n <div className=\"prose prose-sm max-w-none text-text-primary\">\n <pre className=\"bg-bg-sunken rounded-md p-4 text-xs sm:text-sm overflow-x-auto whitespace-pre-wrap font-mono text-text-primary\">\n {template.setupInstructions}\n </pre>\n </div>\n </div>\n </Card>\n )}\n\n {/* File Tree */}\n {template.fileTree && (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <FolderTree className=\"size-5\" />\n {tr('template.fileTree', 'File Tree')}\n </h3>\n </div>\n <div className=\"p-4\">\n <pre className=\"bg-bg-sunken rounded-md p-4 text-xs sm:text-sm overflow-x-auto font-mono text-text-primary\">\n {typeof template.fileTree === 'string'\n ? template.fileTree\n : JSON.stringify(template.fileTree, null, 2)}\n </pre>\n </div>\n </Card>\n )}\n\n {showWizard && canCreateTemplate && (\n <TemplateWizard\n templateName={template.name}\n variables={\n Array.isArray(template.variables) ? (template.variables as TemplateVariable[]) : []\n }\n sourceRepoUrl={\n template.sourceRepositoryUrl ||\n ((template.componentSnapshot as Record<string, unknown> | null)\n ?.repositoryUrl as string) ||\n template.sourceComponent?.repositoryUrl ||\n null\n }\n githubConnections={githubConnections}\n onClose={() => setShowWizard(false)}\n onSubmit={handleClone}\n />\n )}\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Component Quota Reached\"\n message=\"You've reached the maximum number of components on your current plan. Upgrade your plan or purchase an add-on to create more components.\"\n />\n\n <ConfirmDialog\n isOpen={showDeleteConfirm}\n onClose={() => setShowDeleteConfirm(false)}\n onConfirm={handleDeleteTemplate}\n title={tr('template.deleteTemplate', 'Delete Template')}\n message={tr(\n 'template.deleteConfirmMessage',\n `Are you sure you want to delete template \"${template.name}\"? Existing instantiated components are unaffected, but the template will no longer be available for cloning. This cannot be undone.`\n )}\n confirmLabel={tr('common.delete', 'Delete')}\n variant=\"danger\"\n isLoading={isDeleting}\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TemplateDetailPage.js","names":[],"sources":["../../src/pages/TemplateDetailPage.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n ArrowLeft,\n FileCode,\n Copy,\n Layers,\n Tag,\n FolderTree,\n Clock,\n RefreshCw,\n Trash2,\n Variable,\n Github,\n CheckCircle2,\n XCircle,\n Loader2,\n} from 'lucide-react';\nimport {\n PageHeader,\n Card,\n EmptyState,\n ErrorState,\n ConfirmDialog,\n QuotaExhaustedDialog,\n} from '@/components/shared';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { TemplateWizard } from '@/components/templates';\nimport type { TemplateVariable, GitHubConfig, GitHubConnection } from '@/components/templates';\nimport { EntityShareButton } from '@/components/sharing';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useGetTemplateQuery,\n useInstantiateTemplateMutation,\n useDeleteTemplateMutation,\n useUpdateComponentMutation,\n useForkGitHubRepoMutation,\n useListActiveConnectionsQuery,\n} from '@/generated/wspace-operations';\n\nfunction substituteVars(text: string, vars: Record<string, unknown>): string {\n return text.replace(/\\{\\{(\\w+)\\}\\}|\\{(\\w+)\\}/g, (_match, a, b) => {\n const key = a ?? b;\n return key in vars ? String(vars[key]) : _match;\n });\n}\n\nfunction parseGitHubRepo(url: string): { owner: string; repo: string } | null {\n try {\n const match = url.match(/^https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+?)(?:\\.git)?(?:\\/.*)?$/);\n if (!match) return null;\n return { owner: match[1], repo: match[2] };\n } catch {\n return null;\n }\n}\n\nasync function scaffoldGitHubRepo(\n fullName: string,\n headers: Record<string, string>,\n data: {\n name: string;\n description: string;\n variableValues: Record<string, unknown>;\n },\n template: {\n name: string;\n description?: string | null;\n setupInstructions?: string | null;\n fileTree?: unknown;\n variables?: unknown;\n } | null\n) {\n if (!template) return;\n\n const vars = data.variableValues;\n const templateVars = Array.isArray(template.variables)\n ? (template.variables as Array<{ name: string; title?: string }>)\n : [];\n\n // Build README content\n const lines: string[] = [];\n lines.push(`# ${data.name}`);\n lines.push('');\n if (data.description) {\n lines.push(data.description);\n lines.push('');\n }\n lines.push(`> Scaffolded from VibeControls template: **${template.name}**`);\n lines.push('');\n\n // Variable values table\n if (templateVars.length > 0 && Object.keys(vars).length > 0) {\n lines.push('## Configuration');\n lines.push('');\n lines.push('| Variable | Value |');\n lines.push('|----------|-------|');\n for (const v of templateVars) {\n const val = vars[v.name];\n if (val !== undefined) {\n lines.push(`| ${v.title ?? v.name} | \\`${String(val)}\\` |`);\n }\n }\n lines.push('');\n }\n\n // Setup instructions (with variable substitution)\n if (template.setupInstructions) {\n lines.push('## Getting Started');\n lines.push('');\n lines.push(substituteVars(template.setupInstructions, vars));\n lines.push('');\n }\n\n // File tree\n if (template.fileTree) {\n lines.push('## Project Structure');\n lines.push('');\n lines.push('```');\n lines.push(\n typeof template.fileTree === 'string'\n ? template.fileTree\n : JSON.stringify(template.fileTree, null, 2)\n );\n lines.push('```');\n lines.push('');\n }\n\n const readmeContent = lines.join('\\n');\n\n // Get existing README.md SHA so we can update it\n const existing = await fetch(`https://api.github.com/repos/${fullName}/contents/README.md`, {\n headers,\n });\n let sha: string | undefined;\n if (existing.ok) {\n const existingData = (await existing.json()) as { sha?: string };\n sha = existingData.sha;\n }\n\n // Push scaffolded README.md\n await fetch(`https://api.github.com/repos/${fullName}/contents/README.md`, {\n method: 'PUT',\n headers,\n body: JSON.stringify({\n message: `chore: scaffold from VibeControls template \"${template.name}\"`,\n content: btoa(unescape(encodeURIComponent(readmeContent))),\n ...(sha ? { sha } : {}),\n }),\n });\n\n // Push GETTING_STARTED.md with setup instructions + resolved variable values\n if (template.setupInstructions || (templateVars.length > 0 && Object.keys(vars).length > 0)) {\n const gsLines: string[] = ['# Getting Started', ''];\n\n if (template.setupInstructions) {\n gsLines.push(substituteVars(template.setupInstructions, vars));\n gsLines.push('');\n }\n\n // Always append a resolved variables section so values are never lost\n if (templateVars.length > 0 && Object.keys(vars).length > 0) {\n gsLines.push('---');\n gsLines.push('');\n gsLines.push('## Template Variable Values');\n gsLines.push('');\n gsLines.push('These are the values you chose when creating this project:');\n gsLines.push('');\n for (const v of templateVars) {\n const val = vars[v.name];\n if (val !== undefined && val !== '') {\n gsLines.push(`**${v.title ?? v.name}**`);\n gsLines.push('');\n gsLines.push('```');\n gsLines.push(String(val));\n gsLines.push('```');\n gsLines.push('');\n }\n }\n }\n\n await fetch(`https://api.github.com/repos/${fullName}/contents/GETTING_STARTED.md`, {\n method: 'PUT',\n headers,\n body: JSON.stringify({\n message: 'docs: add getting started guide from template',\n content: btoa(unescape(encodeURIComponent(gsLines.join('\\n')))),\n }),\n });\n }\n}\n\nconst categoryStyles: Record<string, string> = {\n STARTER: 'bg-bg-sunken text-text-secondary',\n MICROSERVICE: 'bg-[var(--color-accent-soft)] text-action-primary-bg',\n FRONTEND: 'bg-status-info-bg text-status-info-text',\n BACKEND: 'bg-[var(--color-accent-soft)] text-action-primary-bg',\n FULLSTACK: 'bg-status-success-bg text-status-success-text',\n INFRASTRUCTURE: 'bg-status-warning-bg text-status-warning-text',\n LIBRARY: 'bg-status-warning-bg text-status-warning-text',\n CUSTOM: 'bg-bg-sunken text-text-secondary',\n};\n\nexport function TemplateDetailPage() {\n const { t } = useI18n();\n const { id } = useParams<{ id: string }>();\n const navigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_READ);\n // `instantiateVibecontrolsTemplate` creates a component, but the gateway\n // directive for that mutation is `@rbac(action: \"create\", resource: \"template\")`.\n const canCreateTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_CREATE);\n const canDeleteTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_DELETE);\n const canReadIntegrationConnection = hasPermission(VIBE_PERMISSIONS.INTEGRATION_CONNECTION_READ);\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const [showWizard, setShowWizard] = useState(false);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [cloneError, setCloneError] = useState<string | null>(null);\n const [forkNotice, setForkNotice] = useState<string | null>(null);\n const [repoProgress, setRepoProgress] = useState<{\n stage: string;\n done: boolean;\n error: boolean;\n } | null>(null);\n\n const { data, loading, error, refetch } = useGetTemplateQuery({\n variables: { id: id || '' },\n skip: !id || permissionsLoading || !canReadTemplate,\n fetchPolicy: 'cache-and-network',\n });\n\n const [instantiateTemplate] = useInstantiateTemplateMutation({\n refetchQueries: ['ListComponents'],\n });\n const [deleteTemplate] = useDeleteTemplateMutation({ refetchQueries: ['ListTemplates'] });\n const [updateComponent] = useUpdateComponentMutation();\n const [forkGitHubRepo] = useForkGitHubRepoMutation();\n\n // Fetch active GitHub connections for the workspace\n const { data: connectionsData } = useListActiveConnectionsQuery({\n variables: { filter: { status: 'ACTIVE' as const }, pagination: { limit: 50, offset: 0 } },\n fetchPolicy: 'cache-first',\n skip: !canCreateTemplate || !canReadIntegrationConnection,\n });\n const githubConnections: GitHubConnection[] = (connectionsData?.getConnections?.items ?? [])\n .filter((c: { provider?: { key?: string } | null }) => c.provider?.key === 'github')\n .map(\n (c: {\n id: string;\n name: string;\n externalAccountEmail?: string | null;\n externalAccountName?: string | null;\n provider: { key: string; name: string };\n }) => ({\n id: c.id,\n name: c.name,\n externalAccountEmail: c.externalAccountEmail,\n externalAccountName: c.externalAccountName,\n provider: { key: c.provider.key, name: c.provider.name },\n })\n );\n\n const template = data?.vibecontrolsTemplate ?? null;\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">Loading permissions…</p>\n </div>\n );\n if (!canReadTemplate)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary\">\n You don't have permission to access this page.\n </p>\n </div>\n </div>\n );\n\n const handleBack = () => {\n navigate('/templates');\n };\n\n const handleClone = async (data: {\n name: string;\n description: string;\n variableValues: Record<string, unknown>;\n githubConfig: GitHubConfig | null;\n }) => {\n if (!canCreateTemplate) return;\n\n setShowWizard(false);\n setRepoProgress({ stage: 'Creating component…', done: false, error: false });\n\n const result = await instantiateTemplate({\n variables: {\n templateId: id,\n input: {\n name: data.name,\n description: data.description || undefined,\n ...(Object.keys(data.variableValues).length > 0 && {\n variableValues: data.variableValues,\n }),\n },\n },\n });\n if (result.error && isQuotaExhaustedError(result.error)) {\n setRepoProgress(null);\n setShowQuotaExhausted(true);\n return;\n }\n const newId = result.data?.instantiateVibecontrolsTemplate?.id;\n\n // GitHub repo creation + scaffolding\n if (newId && data.githubConfig?.enabled) {\n // Ensure repo URL starts empty before GitHub runs\n await updateComponent({ variables: { id: newId, input: { repositoryUrl: null } } });\n const gh = data.githubConfig;\n\n // Priority: explicit sourceRepositoryUrl > snapshot URL > sourceComponent.repositoryUrl\n const snapshotRepoUrl = (template?.componentSnapshot as Record<string, unknown> | null)\n ?.repositoryUrl as string | undefined;\n const sourceRepoUrl =\n template?.sourceRepositoryUrl ||\n snapshotRepoUrl ||\n template?.sourceComponent?.repositoryUrl ||\n undefined;\n\n try {\n if (gh.connectionId) {\n // ── OAuth path: backend resolves the token server-side ──────────────\n setRepoProgress({\n stage: sourceRepoUrl\n ? 'Forking repository via connected account…'\n : 'Creating GitHub repository via connected account…',\n done: false,\n error: false,\n });\n\n const forkResult = await forkGitHubRepo({\n variables: {\n input: {\n connectionId: gh.connectionId,\n sourceRepoUrl: sourceRepoUrl || undefined,\n repoName: gh.repoName,\n visibility: gh.visibility === 'private' ? 'PRIVATE' : 'PUBLIC',\n scope: gh.scope === 'org' ? 'ORG' : 'USER',\n orgName: gh.orgName || undefined,\n description:\n data.description || `Created from VibeControls template: ${template?.name}`,\n },\n },\n });\n\n const forkData = forkResult.data?.forkVibecontrolsGitHubRepo;\n if (forkData) {\n setRepoProgress({\n stage: forkData.isForked ? 'Repository forked!' : 'Repository created!',\n done: true,\n error: false,\n });\n await updateComponent({\n variables: { id: newId, input: { repositoryUrl: forkData.repoUrl } },\n });\n await new Promise((r) => setTimeout(r, 1200));\n } else {\n setRepoProgress({\n stage: 'GitHub repo creation failed — check your connected account',\n done: false,\n error: true,\n });\n setTimeout(() => setRepoProgress(null), 6000);\n }\n } else {\n // ── PAT path: existing client-side GitHub API flow ──────────────────\n const ghHeaders = {\n Authorization: `Bearer ${gh.token}`,\n 'Content-Type': 'application/json',\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n };\n const apiBase = 'https://api.github.com';\n\n const sourceRepo = sourceRepoUrl ? parseGitHubRepo(sourceRepoUrl) : null;\n let repoData: { html_url: string; full_name: string } | null = null;\n let isForked = false;\n\n if (sourceRepo) {\n setRepoProgress({\n stage: `Forking ${sourceRepo.owner}/${sourceRepo.repo}…`,\n done: false,\n error: false,\n });\n const forkRes = await fetch(\n `${apiBase}/repos/${encodeURIComponent(sourceRepo.owner)}/${encodeURIComponent(sourceRepo.repo)}/forks`,\n {\n method: 'POST',\n headers: ghHeaders,\n body: JSON.stringify({\n name: gh.repoName,\n ...(gh.scope === 'org' ? { organization: gh.orgName } : {}),\n default_branch_only: false,\n }),\n }\n );\n\n if (forkRes.ok) {\n repoData = (await forkRes.json()) as { html_url: string; full_name: string };\n isForked = true;\n } else {\n const forkErr = (await forkRes.json()) as { message?: string };\n setRepoProgress(null);\n setForkNotice(\n `Could not fork ${sourceRepoUrl} (${forkRes.status}: ${forkErr.message ?? forkRes.statusText}) — creating a new empty repository instead.`\n );\n setTimeout(() => setForkNotice(null), 10000);\n }\n }\n\n if (!repoData) {\n setRepoProgress({ stage: 'Creating GitHub repository…', done: false, error: false });\n const createEndpoint =\n gh.scope === 'org'\n ? `${apiBase}/orgs/${encodeURIComponent(gh.orgName)}/repos`\n : `${apiBase}/user/repos`;\n\n const repoRes = await fetch(createEndpoint, {\n method: 'POST',\n headers: ghHeaders,\n body: JSON.stringify({\n name: gh.repoName,\n private: gh.visibility === 'private',\n auto_init: true,\n description:\n data.description || `Created from VibeControls template: ${template?.name}`,\n }),\n });\n\n if (repoRes.ok) {\n repoData = (await repoRes.json()) as { html_url: string; full_name: string };\n } else {\n const errBody = (await repoRes.json()) as { message?: string };\n setRepoProgress({\n stage: `GitHub repo failed: ${errBody.message ?? repoRes.statusText}`,\n done: false,\n error: true,\n });\n setTimeout(() => setRepoProgress(null), 6000);\n }\n }\n\n if (repoData) {\n if (!isForked) {\n setRepoProgress({ stage: 'Scaffolding README…', done: false, error: false });\n await scaffoldGitHubRepo(repoData.full_name, ghHeaders, data, template);\n }\n\n setRepoProgress({\n stage: isForked ? 'Repository forked!' : 'Repository created!',\n done: true,\n error: false,\n });\n await updateComponent({\n variables: { id: newId, input: { repositoryUrl: repoData.html_url } },\n });\n await new Promise((r) => setTimeout(r, 1200));\n setRepoProgress(null);\n } else {\n await new Promise((r) => setTimeout(r, 3000));\n setRepoProgress(null);\n }\n }\n } catch {\n setRepoProgress({\n stage: 'GitHub repo creation failed — check your credentials',\n done: false,\n error: true,\n });\n setTimeout(() => setRepoProgress(null), 6000);\n }\n }\n\n setRepoProgress(null);\n if (newId) {\n navigate(`/components/${newId}`);\n } else {\n setSuccessMessage(tr('template.clonedSuccess', 'Component created — check Components list'));\n setTimeout(() => setSuccessMessage(null), 4000);\n }\n };\n\n const handleDeleteTemplate = async () => {\n if (!id) return;\n setIsDeleting(true);\n try {\n await deleteTemplate({ variables: { id } });\n setShowDeleteConfirm(false);\n navigate('/templates');\n } finally {\n setIsDeleting(false);\n }\n };\n\n // Guard states\n if (loading && !template) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('template.backToTemplates', 'Back to Templates')}\n </button>\n <PageSkeleton variant=\"detail\" />\n </MotionPage>\n );\n }\n\n if (error && !template) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('template.backToTemplates', 'Back to Templates')}\n </button>\n <ErrorState\n title={tr('template.failedLoad', 'Failed to load template')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n if (!template) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('template.backToTemplates', 'Back to Templates')}\n </button>\n <EmptyState\n icon={<FileCode className=\"size-8 text-text-secondary\" />}\n title={tr('template.notFound', 'Template not found')}\n description={`No template with ID \"${id}\" exists`}\n action={\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('template.backToTemplates', 'Back to Templates')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n {/* Success Toast */}\n {successMessage && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-success-bg text-status-success-text px-4 py-3 rounded-md shadow-lg\">\n {successMessage}\n </div>\n )}\n {forkNotice && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-warning-bg text-status-warning-text px-4 py-3 rounded-md shadow-lg text-sm max-w-sm\">\n {forkNotice}\n </div>\n )}\n {/* Error Toast */}\n {cloneError && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-error-bg text-status-error-text px-4 py-3 rounded-md shadow-lg\">\n {cloneError}\n </div>\n )}\n {/* Repo creation progress overlay */}\n {repoProgress && (\n <div\n className=\"fixed inset-0 z-[9999] flex items-center justify-center p-4\"\n style={{ backgroundColor: 'rgba(0,0,0,0.55)' }}\n >\n <div className=\"bg-bg-surface border border-border-default rounded-xl shadow-2xl p-8 flex flex-col items-center gap-4 max-w-xs w-full\">\n {repoProgress.done ? (\n <CheckCircle2 className=\"size-10 text-status-success-text\" />\n ) : repoProgress.error ? (\n <XCircle className=\"size-10 text-status-error-text\" />\n ) : (\n <Loader2 className=\"size-10 text-action-primary-bg animate-spin\" />\n )}\n <p className=\"text-sm font-medium text-text-primary text-center\">\n {repoProgress.stage}\n </p>\n </div>\n </div>\n )}\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('template.backToTemplates', 'Back to Templates')}\n </button>\n\n {/* Header */}\n <PageHeader\n title={template.name}\n description={template.description || tr('template.noDescription', 'No description')}\n actions={\n <div className=\"flex items-center gap-2 flex-wrap\">\n <EntityShareButton\n entity=\"template\"\n resource={{ id: template.id, name: template.name }}\n label={tr('template.share', 'Share')}\n variant=\"button\"\n />\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center justify-center p-2 bg-bg-sunken text-text-primary rounded-md hover:bg-action-secondary-bg-hover transition-colors disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n {canCreateTemplate && (\n <button\n type=\"button\"\n onClick={() => setShowWizard(true)}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Copy className=\"size-4\" />\n {tr('template.useTemplate', 'Use Template')}\n </button>\n )}\n {canDeleteTemplate && (\n <button\n type=\"button\"\n data-testid=\"template-delete-button\"\n onClick={() => setShowDeleteConfirm(true)}\n className=\"flex items-center gap-2 px-4 py-2 bg-status-error-bg text-status-error-text rounded-md hover:opacity-90 transition-opacity\"\n aria-label={tr('template.deleteTemplate', 'Delete Template')}\n >\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n {tr('common.delete', 'Delete')}\n </button>\n )}\n </div>\n }\n />\n\n {/* Context hint for new users */}\n {canCreateTemplate && (\n <p className=\"text-sm text-text-muted\">\n Click <span className=\"font-medium text-text-secondary\">Use Template</span> to open a\n short wizard — give your new component a name, fill in any variables, and optionally\n create a GitHub repository. The whole thing takes under a minute.\n </p>\n )}\n\n {/* Badges */}\n <div className=\"flex items-center gap-3 flex-wrap\">\n {template.category && (\n <span\n className={`inline-flex items-center gap-1.5 px-2.5 py-1 text-sm font-medium rounded ${categoryStyles[template.category] || 'bg-bg-sunken text-text-secondary'}`}\n >\n <Layers className=\"size-3.5\" />\n {template.category}\n </span>\n )}\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Copy className=\"size-3.5\" />\n {template.cloneCount} {tr('template.clones', 'clones')}\n </span>\n {template.sourceRepositoryUrl && (\n <a\n href={template.sourceRepositoryUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className=\"inline-flex items-center gap-1.5 text-sm text-action-primary-bg hover:underline\"\n >\n <Github className=\"size-3.5\" />\n {template.sourceRepositoryUrl.replace('https://github.com/', '')}\n </a>\n )}\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock className=\"size-3.5\" />\n {new Date(template.createdAt).toLocaleDateString()}\n </span>\n </div>\n\n {/* Tags */}\n {template.tags?.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {template.tags.map((tag: string) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 px-2.5 py-1 text-sm bg-bg-sunken text-text-secondary rounded-md\"\n >\n <Tag className=\"size-3\" />\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Universal tags */}\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Tag className=\"size-5\" />\n <h3 className=\"font-semibold text-text-primary\">{tr('template.tags', 'Tags')}</h3>\n </div>\n <div className=\"p-4\">\n <EntityTagPicker entityType=\"VIBECONTROLS_TEMPLATE\" entityId={template.id} />\n </div>\n </Card>\n\n {/* Source Component */}\n {template.sourceComponent && (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('template.sourceComponent', 'Source Component')}\n </h3>\n </div>\n <div className=\"p-4\">\n <button\n type=\"button\"\n onClick={() => navigate(`/components/${template.sourceComponent.id}`)}\n className=\"flex items-center gap-3 text-left hover:bg-bg-sunken p-2 -m-2 rounded-md transition-colors\"\n >\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center flex-shrink-0\">\n <FileCode className=\"size-5 text-action-primary-bg\" />\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{template.sourceComponent.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {template.sourceComponent.type} · {template.sourceComponent.lifecycle}\n </p>\n </div>\n </button>\n </div>\n </Card>\n )}\n\n {/* Cloned Components */}\n {template.clonedComponents && template.clonedComponents.length > 0 && (\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Copy className=\"size-5\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('template.createdComponents', 'Created Components')}\n </h3>\n <span className=\"ml-auto text-xs text-text-muted\">\n {template.clonedComponents.length} component\n {template.clonedComponents.length !== 1 ? 's' : ''}\n </span>\n </div>\n <div className=\"divide-y divide-border-default\">\n {template.clonedComponents.map(\n (comp: {\n id: string;\n name: string;\n description?: string | null;\n type: string;\n lifecycle: string;\n repositoryUrl?: string | null;\n createdAt: string;\n }) => (\n <button\n type=\"button\"\n key={comp.id}\n onClick={() => navigate(`/components/${comp.id}`)}\n className=\"flex items-center gap-3 w-full text-left px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"size-8 rounded-md bg-[var(--color-accent-soft)] flex items-center justify-center flex-shrink-0\">\n <FileCode className=\"size-4 text-action-primary-bg\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{comp.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {comp.type} · {comp.lifecycle}\n {comp.repositoryUrl && (\n <>\n {' '}\n ·{' '}\n <span className=\"font-mono\">\n {comp.repositoryUrl.replace('https://github.com/', '')}\n </span>\n </>\n )}\n </p>\n </div>\n <span className=\"text-xs text-text-muted flex-shrink-0\">\n {new Date(comp.createdAt).toLocaleDateString()}\n </span>\n </button>\n )\n )}\n </div>\n </Card>\n )}\n\n {/* Variables preview */}\n {Array.isArray(template.variables) &&\n (template.variables as TemplateVariable[]).length > 0 && (\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Variable className=\"size-5\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('template.variables', 'Template Variables')}\n </h3>\n <span className=\"ml-auto text-xs text-text-muted\">\n {(template.variables as TemplateVariable[]).length} variable\n {(template.variables as TemplateVariable[]).length !== 1 ? 's' : ''}\n </span>\n </div>\n <div className=\"divide-y divide-border-default\">\n {(template.variables as TemplateVariable[]).map((v) => (\n <div key={v.name} className=\"px-4 py-3 flex items-start gap-4\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary font-mono\">\n {v.name}\n </span>\n {v.required && (\n <span className=\"text-xs bg-status-error-bg text-status-error-text px-1.5 py-0.5 rounded\">\n required\n </span>\n )}\n <span className=\"text-xs bg-bg-sunken text-text-muted px-1.5 py-0.5 rounded\">\n {v.type}\n </span>\n </div>\n {v.title && v.title !== v.name && (\n <p className=\"text-xs text-text-secondary mt-0.5\">{v.title}</p>\n )}\n {v.default && (\n <p className=\"text-xs text-text-muted mt-0.5\">\n Default: <span className=\"font-mono\">{v.default}</span>\n </p>\n )}\n </div>\n {v.options.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {v.options.map((opt) => (\n <span\n key={opt}\n className=\"text-xs bg-bg-sunken text-text-secondary px-1.5 py-0.5 rounded\"\n >\n {opt}\n </span>\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n </Card>\n )}\n\n {/* Setup Instructions */}\n {template.setupInstructions && (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('template.setupInstructions', 'Setup Instructions')}\n </h3>\n </div>\n <div className=\"p-4\">\n <div className=\"prose prose-sm max-w-none text-text-primary\">\n <pre className=\"bg-bg-sunken rounded-md p-4 text-xs sm:text-sm overflow-x-auto whitespace-pre-wrap font-mono text-text-primary\">\n {template.setupInstructions}\n </pre>\n </div>\n </div>\n </Card>\n )}\n\n {/* File Tree */}\n {template.fileTree && (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <FolderTree className=\"size-5\" />\n {tr('template.fileTree', 'File Tree')}\n </h3>\n </div>\n <div className=\"p-4\">\n <pre className=\"bg-bg-sunken rounded-md p-4 text-xs sm:text-sm overflow-x-auto font-mono text-text-primary\">\n {typeof template.fileTree === 'string'\n ? template.fileTree\n : JSON.stringify(template.fileTree, null, 2)}\n </pre>\n </div>\n </Card>\n )}\n\n {showWizard && canCreateTemplate && (\n <TemplateWizard\n templateName={template.name}\n variables={\n Array.isArray(template.variables) ? (template.variables as TemplateVariable[]) : []\n }\n sourceRepoUrl={\n template.sourceRepositoryUrl ||\n ((template.componentSnapshot as Record<string, unknown> | null)\n ?.repositoryUrl as string) ||\n template.sourceComponent?.repositoryUrl ||\n null\n }\n githubConnections={githubConnections}\n onClose={() => setShowWizard(false)}\n onSubmit={handleClone}\n />\n )}\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Component Quota Reached\"\n message=\"You've reached the maximum number of components on your current plan. Upgrade your plan or purchase an add-on to create more components.\"\n />\n\n <ConfirmDialog\n isOpen={showDeleteConfirm}\n onClose={() => setShowDeleteConfirm(false)}\n onConfirm={handleDeleteTemplate}\n title={tr('template.deleteTemplate', 'Delete Template')}\n message={tr(\n 'template.deleteConfirmMessage',\n `Are you sure you want to delete template \"${template.name}\"? Existing instantiated components are unaffected, but the template will no longer be available for cloning. This cannot be undone.`\n )}\n confirmLabel={tr('common.delete', 'Delete')}\n variant=\"danger\"\n isLoading={isDeleting}\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
@@ -130,14 +130,14 @@ function w() {
|
|
|
130
130
|
children: "Loading permissions…"
|
|
131
131
|
})
|
|
132
132
|
}) : O ? V && q.length === 0 ? /* @__PURE__ */ y(C, {
|
|
133
|
-
className: "space-y-6
|
|
133
|
+
className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen",
|
|
134
134
|
children: /* @__PURE__ */ y(r, {
|
|
135
135
|
title: k("template.failedLoad", "Failed to load templates"),
|
|
136
136
|
message: V.message,
|
|
137
137
|
onRetry: () => H()
|
|
138
138
|
})
|
|
139
139
|
}) : /* @__PURE__ */ b(C, {
|
|
140
|
-
className: "space-y-6
|
|
140
|
+
className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen",
|
|
141
141
|
children: [
|
|
142
142
|
/* @__PURE__ */ y(n, {
|
|
143
143
|
title: k("template.title", "Templates"),
|
|
@@ -146,8 +146,9 @@ function w() {
|
|
|
146
146
|
type: "button",
|
|
147
147
|
onClick: () => H(),
|
|
148
148
|
disabled: B,
|
|
149
|
-
className: "flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-
|
|
149
|
+
className: "flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50",
|
|
150
150
|
title: k("common.refresh", "Refresh"),
|
|
151
|
+
"aria-label": k("common.refresh", "Refresh"),
|
|
151
152
|
children: /* @__PURE__ */ y(g, { className: `size-4 ${B ? "animate-spin" : ""}` })
|
|
152
153
|
})
|
|
153
154
|
}),
|
|
@@ -208,7 +209,7 @@ function w() {
|
|
|
208
209
|
type: "button",
|
|
209
210
|
onClick: Y,
|
|
210
211
|
disabled: L,
|
|
211
|
-
className: "flex items-center gap-2 px-4 py-2 bg-bg-sunken text-text-primary rounded-
|
|
212
|
+
className: "flex items-center gap-2 px-4 py-2 bg-bg-sunken text-text-primary rounded-button hover:bg-action-secondary-bg-hover transition-colors disabled:opacity-50",
|
|
212
213
|
children: L ? k("common.loading", "Loading...") : k("common.loadMore", "Load more")
|
|
213
214
|
})
|
|
214
215
|
})
|
|
@@ -218,7 +219,7 @@ function w() {
|
|
|
218
219
|
children: /* @__PURE__ */ b("div", {
|
|
219
220
|
className: "text-center space-y-2",
|
|
220
221
|
children: [/* @__PURE__ */ y("h2", {
|
|
221
|
-
className: "text-lg font-semibold text-
|
|
222
|
+
className: "text-lg font-semibold text-text-primary",
|
|
222
223
|
children: "Access Denied"
|
|
223
224
|
}), /* @__PURE__ */ y("p", {
|
|
224
225
|
className: "text-sm text-text-secondary",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TemplatesPage.js","names":[],"sources":["../../src/pages/TemplatesPage.tsx"],"sourcesContent":["import { useState, useCallback, useEffect } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport { FileCode, RefreshCw } from 'lucide-react';\nimport { TemplateCard } from '@/components/templates';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport type { TemplateCardData } from '@/components/templates';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n EmptyState,\n ErrorState,\n GetHelpButton,\n} from '@/components/shared';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence } from 'framer-motion';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useListTemplatesQuery } from '@/generated/wspace-operations';\n\nexport function TemplatesPage() {\n const navigate = useVibeNavigate();\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_READ);\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n if (!params) return template;\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const categoryOptions = [\n { value: 'all', label: tr('template.allCategories', 'All Categories') },\n { value: 'STARTER', label: tr('template.categoryStarter', 'Starter') },\n { value: 'MICROSERVICE', label: tr('template.categoryMicroservice', 'Microservice') },\n { value: 'FRONTEND', label: tr('template.categoryFrontend', 'Frontend') },\n { value: 'BACKEND', label: tr('template.categoryBackend', 'Backend') },\n { value: 'FULLSTACK', label: tr('template.categoryFullstack', 'Fullstack') },\n { value: 'INFRASTRUCTURE', label: tr('template.categoryInfrastructure', 'Infrastructure') },\n { value: 'LIBRARY', label: tr('template.categoryLibrary', 'Library') },\n { value: 'CUSTOM', label: tr('template.categoryCustom', 'Custom') },\n ];\n\n const PAGE_SIZE = 50;\n const [searchQuery, setSearchQuery] = useState('');\n const [categoryFilter, setCategoryFilter] = useState('all');\n const [accumulated, setAccumulated] = useState<TemplateCardData[]>([]);\n const [loadingMore, setLoadingMore] = useState(false);\n\n const { data, loading, error, refetch, fetchMore } = useListTemplatesQuery({\n variables: {\n filter: {\n category: categoryFilter !== 'all' ? categoryFilter : undefined,\n search: searchQuery || undefined,\n },\n pagination: { limit: PAGE_SIZE, offset: 0 },\n },\n skip: permissionsLoading || !canReadTemplate,\n fetchPolicy: 'cache-and-network',\n notifyOnNetworkStatusChange: true,\n });\n\n const mapItems = useCallback(\n (items: ReadonlyArray<Record<string, unknown>>): TemplateCardData[] =>\n items.map((item) => ({\n id: item.id as string,\n name: item.name as string,\n description: (item.description as string) || null,\n category: (item.category as string) || null,\n cloneCount: (item.cloneCount as number) || 0,\n variableCount: Array.isArray(item.variables) ? (item.variables as unknown[]).length : 0,\n tags: (item.tags as string[]) || [],\n createdAt: item.createdAt as string,\n updatedAt: item.updatedAt as string,\n })),\n []\n );\n\n useEffect(() => {\n const items = data?.vibecontrolsTemplates?.items ?? [];\n setAccumulated(mapItems(items as ReadonlyArray<Record<string, unknown>>));\n }, [data?.vibecontrolsTemplates?.items, mapItems]);\n\n const total = data?.vibecontrolsTemplates?.total ?? 0;\n const hasMore = accumulated.length < total;\n const templates = accumulated;\n const filteredTemplates = accumulated;\n\n const handleLoadMore = useCallback(async () => {\n if (loadingMore || !hasMore) return;\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { limit: PAGE_SIZE, offset: accumulated.length } },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult?.vibecontrolsTemplates) return prev;\n const prevList = prev.vibecontrolsTemplates;\n const nextList = fetchMoreResult.vibecontrolsTemplates;\n return {\n ...prev,\n vibecontrolsTemplates: {\n ...nextList,\n items: [...(prevList?.items ?? []), ...nextList.items],\n total: nextList.total,\n hasMore: nextList.hasMore,\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n }, [fetchMore, accumulated.length, loadingMore, hasMore]);\n\n const handleTemplateSelect = (tmpl: TemplateCardData) => {\n navigate(`/templates/${tmpl.id}`);\n };\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">Loading permissions…</p>\n </div>\n );\n if (!canReadTemplate)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-foreground\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary\">\n You don't have permission to access this page.\n </p>\n </div>\n </div>\n );\n\n if (error && templates.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <ErrorState\n title={tr('template.failedLoad', 'Failed to load templates')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <PageHeader\n title={tr('template.title', 'Templates')}\n description={\n total > 0\n ? tr(\n 'template.description',\n '{{total}} ready-made blueprints — pick one to scaffold a new component instantly',\n {\n total: templates.length,\n }\n )\n : tr(\n 'template.descriptionEmpty',\n 'Reusable blueprints your team publishes from components — pick one to scaffold a new component in seconds'\n )\n }\n actions={\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n }\n />\n\n {/* Filters — SearchInput / FilterDropdown are custom components that don't\n forward rest props onto a DOM node, so the tour anchors live on\n wrapper <div>s. */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <div data-tour=\"vibecontrols-template-search\" className=\"flex-1 sm:w-64 sm:flex-none\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('template.searchPlaceholder', 'Search templates...')}\n className=\"w-full\"\n />\n </div>\n <div data-tour=\"vibecontrols-template-category-filter\">\n <FilterDropdown\n label={tr('template.filterCategory', 'Category')}\n value={categoryFilter}\n options={categoryOptions}\n onChange={setCategoryFilter}\n />\n </div>\n </div>\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {tr('template.showingResults', 'Showing {{filtered}} of {{total}} templates', {\n filtered: filteredTemplates.length,\n total,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">{tr('common.refreshing', '(refreshing...)')}</span>\n )}\n </p>\n\n {/* Grid */}\n {loading && templates.length === 0 ? (\n <PageSkeleton variant=\"cards\" count={6} />\n ) : filteredTemplates.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n <MotionList\n key=\"template-grid\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n data-tour=\"vibecontrols-template-grid\"\n >\n {filteredTemplates.map((tmpl) => (\n <MotionItem key={tmpl.id}>\n <TemplateCard template={tmpl} onOpen={handleTemplateSelect} />\n </MotionItem>\n ))}\n </MotionList>\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<FileCode className=\"size-8 text-text-secondary\" />}\n title={\n searchQuery || categoryFilter !== 'all'\n ? tr('template.emptyNoResults', 'No templates found')\n : tr('template.emptyNoTemplates', 'No templates yet')\n }\n description={\n searchQuery || categoryFilter !== 'all'\n ? tr('template.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'template.emptyCreate',\n 'No templates yet. To create one: open any component → click the ⋯ menu → \"Publish as Template\". It will appear here and anyone in your workspace can use it as a starting point.'\n )\n }\n action={\n !searchQuery && categoryFilter === 'all' ? (\n <GetHelpButton prompt=\"What is a Template in VibeControls and how do I create one?\" />\n ) : undefined\n }\n />\n )}\n\n {/* Load more */}\n {hasMore && filteredTemplates.length > 0 && (\n <div className=\"flex justify-center pt-2\">\n <button\n type=\"button\"\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"flex items-center gap-2 px-4 py-2 bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken-hover transition-colors disabled:opacity-50\"\n >\n {loadingMore ? tr('common.loading', 'Loading...') : tr('common.loadMore', 'Load more')}\n </button>\n </div>\n )}\n </MotionPage>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,IAAgB;CAC9B,IAAM,IAAW,GAAiB,EAC5B,EAAE,SAAM,GAAS,EACjB,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,IAAkB,EAAc,EAAiB,cAAc,EAE/D,IAAK,GACR,GAAa,GAAkB,MAAqD;EACnF,IAAM,IAAa,EAAE,GAAK,EAAO,EAC3B,IAAW,MAAe,IAAM,IAAW;AAEjD,SADK,IACE,OAAO,QAAQ,EAAO,CAAC,QAC3B,GAAK,CAAC,GAAU,OAAgB,EAAI,QAAQ,KAAK,EAAS,KAAK,OAAO,EAAW,CAAC,EACnF,EACD,GAJmB;IAMtB,CAAC,EAAE,CACJ,EAEK,IAAkB;EACtB;GAAE,OAAO;GAAO,OAAO,EAAG,0BAA0B,iBAAiB;GAAE;EACvE;GAAE,OAAO;GAAW,OAAO,EAAG,4BAA4B,UAAU;GAAE;EACtE;GAAE,OAAO;GAAgB,OAAO,EAAG,iCAAiC,eAAe;GAAE;EACrF;GAAE,OAAO;GAAY,OAAO,EAAG,6BAA6B,WAAW;GAAE;EACzE;GAAE,OAAO;GAAW,OAAO,EAAG,4BAA4B,UAAU;GAAE;EACtE;GAAE,OAAO;GAAa,OAAO,EAAG,8BAA8B,YAAY;GAAE;EAC5E;GAAE,OAAO;GAAkB,OAAO,EAAG,mCAAmC,iBAAiB;GAAE;EAC3F;GAAE,OAAO;GAAW,OAAO,EAAG,4BAA4B,UAAU;GAAE;EACtE;GAAE,OAAO;GAAU,OAAO,EAAG,2BAA2B,SAAS;GAAE;EACpE,EAGK,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAgB,KAAqB,EAAS,MAAM,EACrD,CAAC,GAAa,KAAkB,EAA6B,EAAE,CAAC,EAChE,CAAC,GAAa,KAAkB,EAAS,GAAM,EAE/C,EAAE,SAAM,YAAS,UAAO,YAAS,iBAAc,EAAsB;EACzE,WAAW;GACT,QAAQ;IACN,UAAU,MAAmB,QAAyB,KAAA,IAAjB;IACrC,QAAQ,KAAe,KAAA;IACxB;GACD,YAAY;IAAE,OAAO;IAAW,QAAQ;IAAG;GAC5C;EACD,MAAM,KAAsB,CAAC;EAC7B,aAAa;EACb,6BAA6B;EAC9B,CAAC,EAEI,IAAW,GACd,MACC,EAAM,KAAK,OAAU;EACnB,IAAI,EAAK;EACT,MAAM,EAAK;EACX,aAAc,EAAK,eAA0B;EAC7C,UAAW,EAAK,YAAuB;EACvC,YAAa,EAAK,cAAyB;EAC3C,eAAe,MAAM,QAAQ,EAAK,UAAU,GAAI,EAAK,UAAwB,SAAS;EACtF,MAAO,EAAK,QAAqB,EAAE;EACnC,WAAW,EAAK;EAChB,WAAW,EAAK;EACjB,EAAE,EACL,EAAE,CACH;AAED,SAAgB;AAEd,IAAe,EADD,GAAM,uBAAuB,SAAS,EAAE,CACkB,CAAC;IACxE,CAAC,GAAM,uBAAuB,OAAO,EAAS,CAAC;CAElD,IAAM,IAAQ,GAAM,uBAAuB,SAAS,GAC9C,IAAU,EAAY,SAAS,GAC/B,IAAY,GACZ,IAAoB,GAEpB,IAAiB,EAAY,YAAY;AACzC,aAAe,CAAC,IACpB;KAAe,GAAK;AACpB,OAAI;AACF,UAAM,EAAU;KACd,WAAW,EAAE,YAAY;MAAE,OAAO;MAAW,QAAQ,EAAY;MAAQ,EAAE;KAC3E,cAAc,GAAM,EAAE,yBAAsB;AAC1C,UAAI,CAAC,GAAiB,sBAAuB,QAAO;MACpD,IAAM,IAAW,EAAK,uBAChB,IAAW,EAAgB;AACjC,aAAO;OACL,GAAG;OACH,uBAAuB;QACrB,GAAG;QACH,OAAO,CAAC,GAAI,GAAU,SAAS,EAAE,EAAG,GAAG,EAAS,MAAM;QACtD,OAAO,EAAS;QAChB,SAAS,EAAS;QACnB;OACF;;KAEJ,CAAC;aACM;AACR,MAAe,GAAM;;;IAEtB;EAAC;EAAW,EAAY;EAAQ;EAAa;EAAQ,CAAC,EAEnD,KAAwB,MAA2B;AACvD,IAAS,cAAc,EAAK,KAAK;;AAiCnC,QA9BI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,KAAD;GAAG,WAAU;aAAsB;GAAwB,CAAA;EACvD,CAAA,GAEL,IAYD,KAAS,EAAU,WAAW,IAE9B,kBAAC,GAAD;EAAY,WAAU;YACpB,kBAAC,GAAD;GACE,OAAO,EAAG,uBAAuB,2BAA2B;GAC5D,SAAS,EAAM;GACf,eAAe,GAAS;GACxB,CAAA;EACS,CAAA,GAKf,kBAAC,GAAD;EAAY,WAAU;YAAtB;GACE,kBAAC,GAAD;IACE,OAAO,EAAG,kBAAkB,YAAY;IACxC,aACE,IAAQ,IACJ,EACE,wBACA,oFACA,EACE,OAAO,EAAU,QAClB,CACF,GACD,EACE,6BACA,4GACD;IAEP,SACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,GAAS;KACxB,UAAU;KACV,WAAU;KACV,OAAO,EAAG,kBAAkB,UAAU;eAEtC,kBAAC,GAAD,EAAW,WAAW,UAAU,IAAU,iBAAiB,MAAQ,CAAA;KAC5D,CAAA;IAEX,CAAA;GAKF,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,aAAU;KAA+B,WAAU;eACtD,kBAAC,GAAD;MACE,OAAO;MACP,UAAU;MACV,aAAa,EAAG,8BAA8B,sBAAsB;MACpE,WAAU;MACV,CAAA;KACE,CAAA,EACN,kBAAC,OAAD;KAAK,aAAU;eACb,kBAAC,GAAD;MACE,OAAO,EAAG,2BAA2B,WAAW;MAChD,OAAO;MACP,SAAS;MACT,UAAU;MACV,CAAA;KACE,CAAA,CACF;;GAGN,kBAAC,KAAD;IAAG,WAAU;cAAb,CACG,EAAG,2BAA2B,+CAA+C;KAC5E,UAAU,EAAkB;KAC5B;KACD,CAAC,EACD,KACC,kBAAC,QAAD;KAAM,WAAU;eAAwB,EAAG,qBAAqB,kBAAkB;KAAQ,CAAA,CAE1F;;GAGH,KAAW,EAAU,WAAW,IAC/B,kBAAC,GAAD;IAAc,SAAQ;IAAQ,OAAO;IAAK,CAAA,GACxC,EAAkB,SAAS,IAC7B,kBAAC,GAAD;IAAiB,MAAK;cACpB,kBAAC,GAAD;KAEE,SAAS;KACT,WAAU;KACV,aAAU;eAET,EAAkB,KAAK,MACtB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAc,UAAU;MAAM,QAAQ;MAAwB,CAAA,EACnD,EAFI,EAAK,GAET,CACb;KACS,EAVP,gBAUO;IACG,CAAA,GAElB,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAU,WAAU,8BAA+B,CAAA;IACzD,OACE,KAAe,MAAmB,QAC9B,EAAG,2BAA2B,qBAAqB,GACnD,EAAG,6BAA6B,mBAAmB;IAEzD,aACE,KAAe,MAAmB,QAC9B,EAAG,8BAA8B,uCAAuC,GACxE,EACE,wBACA,qLACD;IAEP,QACE,CAAC,KAAe,MAAmB,QACjC,kBAAC,GAAD,EAAe,QAAO,+DAAgE,CAAA,GACpF,KAAA;IAEN,CAAA;GAIH,KAAW,EAAkB,SAAS,KACrC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU;KACV,WAAU;eAET,IAAc,EAAG,kBAAkB,aAAa,GAAG,EAAG,mBAAmB,YAAY;KAC/E,CAAA;IACL,CAAA;GAEG;MA7IX,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cAAwC;IAAkB,CAAA,EACxE,kBAAC,KAAD;IAAG,WAAU;cAA8B;IAEvC,CAAA,CACA;;EACF,CAAA"}
|
|
1
|
+
{"version":3,"file":"TemplatesPage.js","names":[],"sources":["../../src/pages/TemplatesPage.tsx"],"sourcesContent":["import { useState, useCallback, useEffect } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport { FileCode, RefreshCw } from 'lucide-react';\nimport { TemplateCard } from '@/components/templates';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport type { TemplateCardData } from '@/components/templates';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n EmptyState,\n ErrorState,\n GetHelpButton,\n} from '@/components/shared';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence } from 'framer-motion';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useListTemplatesQuery } from '@/generated/wspace-operations';\n\nexport function TemplatesPage() {\n const navigate = useVibeNavigate();\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_READ);\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n if (!params) return template;\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const categoryOptions = [\n { value: 'all', label: tr('template.allCategories', 'All Categories') },\n { value: 'STARTER', label: tr('template.categoryStarter', 'Starter') },\n { value: 'MICROSERVICE', label: tr('template.categoryMicroservice', 'Microservice') },\n { value: 'FRONTEND', label: tr('template.categoryFrontend', 'Frontend') },\n { value: 'BACKEND', label: tr('template.categoryBackend', 'Backend') },\n { value: 'FULLSTACK', label: tr('template.categoryFullstack', 'Fullstack') },\n { value: 'INFRASTRUCTURE', label: tr('template.categoryInfrastructure', 'Infrastructure') },\n { value: 'LIBRARY', label: tr('template.categoryLibrary', 'Library') },\n { value: 'CUSTOM', label: tr('template.categoryCustom', 'Custom') },\n ];\n\n const PAGE_SIZE = 50;\n const [searchQuery, setSearchQuery] = useState('');\n const [categoryFilter, setCategoryFilter] = useState('all');\n const [accumulated, setAccumulated] = useState<TemplateCardData[]>([]);\n const [loadingMore, setLoadingMore] = useState(false);\n\n const { data, loading, error, refetch, fetchMore } = useListTemplatesQuery({\n variables: {\n filter: {\n category: categoryFilter !== 'all' ? categoryFilter : undefined,\n search: searchQuery || undefined,\n },\n pagination: { limit: PAGE_SIZE, offset: 0 },\n },\n skip: permissionsLoading || !canReadTemplate,\n fetchPolicy: 'cache-and-network',\n notifyOnNetworkStatusChange: true,\n });\n\n const mapItems = useCallback(\n (items: ReadonlyArray<Record<string, unknown>>): TemplateCardData[] =>\n items.map((item) => ({\n id: item.id as string,\n name: item.name as string,\n description: (item.description as string) || null,\n category: (item.category as string) || null,\n cloneCount: (item.cloneCount as number) || 0,\n variableCount: Array.isArray(item.variables) ? (item.variables as unknown[]).length : 0,\n tags: (item.tags as string[]) || [],\n createdAt: item.createdAt as string,\n updatedAt: item.updatedAt as string,\n })),\n []\n );\n\n useEffect(() => {\n const items = data?.vibecontrolsTemplates?.items ?? [];\n setAccumulated(mapItems(items as ReadonlyArray<Record<string, unknown>>));\n }, [data?.vibecontrolsTemplates?.items, mapItems]);\n\n const total = data?.vibecontrolsTemplates?.total ?? 0;\n const hasMore = accumulated.length < total;\n const templates = accumulated;\n const filteredTemplates = accumulated;\n\n const handleLoadMore = useCallback(async () => {\n if (loadingMore || !hasMore) return;\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { limit: PAGE_SIZE, offset: accumulated.length } },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult?.vibecontrolsTemplates) return prev;\n const prevList = prev.vibecontrolsTemplates;\n const nextList = fetchMoreResult.vibecontrolsTemplates;\n return {\n ...prev,\n vibecontrolsTemplates: {\n ...nextList,\n items: [...(prevList?.items ?? []), ...nextList.items],\n total: nextList.total,\n hasMore: nextList.hasMore,\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n }, [fetchMore, accumulated.length, loadingMore, hasMore]);\n\n const handleTemplateSelect = (tmpl: TemplateCardData) => {\n navigate(`/templates/${tmpl.id}`);\n };\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">Loading permissions…</p>\n </div>\n );\n if (!canReadTemplate)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary\">\n You don't have permission to access this page.\n </p>\n </div>\n </div>\n );\n\n if (error && templates.length === 0) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <ErrorState\n title={tr('template.failedLoad', 'Failed to load templates')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <PageHeader\n title={tr('template.title', 'Templates')}\n description={\n total > 0\n ? tr(\n 'template.description',\n '{{total}} ready-made blueprints — pick one to scaffold a new component instantly',\n {\n total: templates.length,\n }\n )\n : tr(\n 'template.descriptionEmpty',\n 'Reusable blueprints your team publishes from components — pick one to scaffold a new component in seconds'\n )\n }\n actions={\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n aria-label={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n }\n />\n\n {/* Filters — SearchInput / FilterDropdown are custom components that don't\n forward rest props onto a DOM node, so the tour anchors live on\n wrapper <div>s. */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <div data-tour=\"vibecontrols-template-search\" className=\"flex-1 sm:w-64 sm:flex-none\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('template.searchPlaceholder', 'Search templates...')}\n className=\"w-full\"\n />\n </div>\n <div data-tour=\"vibecontrols-template-category-filter\">\n <FilterDropdown\n label={tr('template.filterCategory', 'Category')}\n value={categoryFilter}\n options={categoryOptions}\n onChange={setCategoryFilter}\n />\n </div>\n </div>\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {tr('template.showingResults', 'Showing {{filtered}} of {{total}} templates', {\n filtered: filteredTemplates.length,\n total,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">{tr('common.refreshing', '(refreshing...)')}</span>\n )}\n </p>\n\n {/* Grid */}\n {loading && templates.length === 0 ? (\n <PageSkeleton variant=\"cards\" count={6} />\n ) : filteredTemplates.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n <MotionList\n key=\"template-grid\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n data-tour=\"vibecontrols-template-grid\"\n >\n {filteredTemplates.map((tmpl) => (\n <MotionItem key={tmpl.id}>\n <TemplateCard template={tmpl} onOpen={handleTemplateSelect} />\n </MotionItem>\n ))}\n </MotionList>\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<FileCode className=\"size-8 text-text-secondary\" />}\n title={\n searchQuery || categoryFilter !== 'all'\n ? tr('template.emptyNoResults', 'No templates found')\n : tr('template.emptyNoTemplates', 'No templates yet')\n }\n description={\n searchQuery || categoryFilter !== 'all'\n ? tr('template.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'template.emptyCreate',\n 'No templates yet. To create one: open any component → click the ⋯ menu → \"Publish as Template\". It will appear here and anyone in your workspace can use it as a starting point.'\n )\n }\n action={\n !searchQuery && categoryFilter === 'all' ? (\n <GetHelpButton prompt=\"What is a Template in VibeControls and how do I create one?\" />\n ) : undefined\n }\n />\n )}\n\n {/* Load more */}\n {hasMore && filteredTemplates.length > 0 && (\n <div className=\"flex justify-center pt-2\">\n <button\n type=\"button\"\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"flex items-center gap-2 px-4 py-2 bg-bg-sunken text-text-primary rounded-button hover:bg-action-secondary-bg-hover transition-colors disabled:opacity-50\"\n >\n {loadingMore ? tr('common.loading', 'Loading...') : tr('common.loadMore', 'Load more')}\n </button>\n </div>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
@@ -134,14 +134,14 @@ function m() {
|
|
|
134
134
|
actions: Te ? /* @__PURE__ */ d(fe, { filter: K }) : void 0
|
|
135
135
|
}),
|
|
136
136
|
/* @__PURE__ */ f("div", {
|
|
137
|
-
className: "mb-4 flex items-start gap-2 rounded-md border border-status-info-border bg-status-info-bg
|
|
137
|
+
className: "mb-4 flex items-start gap-2 rounded-md border border-status-info-border bg-status-info-bg-subtle px-3 py-2 text-xs text-status-info-text",
|
|
138
138
|
children: [/* @__PURE__ */ d(u, {
|
|
139
139
|
size: 14,
|
|
140
140
|
className: "mt-0.5 flex-shrink-0"
|
|
141
141
|
}), /* @__PURE__ */ f("span", { children: [H("audit.retentionBanner", `Audit logs are kept for ${Ne} days. Older entries are automatically purged.`), Pe ? ` ${H("audit.retentionNextCleanup", `Next cleanup: ${new Date(Pe).toLocaleString()}.`)}` : ""] })]
|
|
142
142
|
}),
|
|
143
143
|
A ? /* @__PURE__ */ f("div", {
|
|
144
|
-
className: "mb-4 flex items-center justify-between gap-2 rounded-md border border-action-primary-border bg-
|
|
144
|
+
className: "mb-4 flex items-center justify-between gap-2 rounded-md border border-action-primary-border bg-[var(--color-accent-soft)] px-3 py-2 text-xs text-text-primary",
|
|
145
145
|
children: [/* @__PURE__ */ f("span", {
|
|
146
146
|
className: "truncate",
|
|
147
147
|
children: [
|
|
@@ -199,7 +199,7 @@ function m() {
|
|
|
199
199
|
vibes: Me
|
|
200
200
|
}),
|
|
201
201
|
Z.length > 0 ? /* @__PURE__ */ d("div", {
|
|
202
|
-
className: "rounded-lg border border-border-
|
|
202
|
+
className: "rounded-lg border border-border-default bg-bg-surface p-4",
|
|
203
203
|
children: /* @__PURE__ */ f("div", {
|
|
204
204
|
className: "flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between",
|
|
205
205
|
children: [/* @__PURE__ */ f("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeAuditPage.js","names":[],"sources":["../../src/pages/VibeAuditPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { Shield } from 'lucide-react';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { isNative, nativeNotify } from '../utils/nativeBridge';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n AuditDeleteDialog,\n AuditExportButton,\n AuditLogDetail,\n AuditLogFilters,\n AuditLogStats,\n AuditLogTable,\n} from '@/components/audit';\nimport {\n EmptyState,\n ErrorState,\n GetHelpButton,\n LoadingState,\n PageHeader,\n} from '@/components/shared';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport {\n useBulkDeleteVibeAuditLogsMutation,\n useDeleteVibeAuditLogMutation,\n useGetVibeAuditLogQuery,\n useListAgentsQuery,\n useListVibesQuery,\n useVibeAuditDashboardQuery,\n} from '@/generated/wspace-operations';\nimport type {\n VibeAuditAction,\n VibeAuditLogFilterInput,\n VibeAuditResourceType,\n VibeAuditStatus,\n} from '@/generated/wspace-types';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\n\nconst PAGE_SIZE = 50;\n\nexport function VibeAuditPage() {\n useVibeControls();\n\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');\n const [actionFilter, setActionFilter] = useState<string | null>('all');\n const [resourceTypeFilter, setResourceTypeFilter] = useState<string | null>('all');\n const [statusFilter, setStatusFilter] = useState<string | null>('all');\n const [agentFilter, setAgentFilter] = useState<string | null>('all');\n const [vibeFilter, setVibeFilter] = useState<string | null>('all');\n const [hasCommandFilter, setHasCommandFilter] = useState(false);\n const [page, setPage] = useState(0);\n // F2 — session-history drill-down. Initialised from a ?sessionId= URL param\n // (reciprocal link from the in-session History tab) and also settable by\n // clicking \"Show only this session\" on a SESSION-typed audit entry.\n const [sessionDrillId, setSessionDrillId] = useState<string | null>(() => {\n if (typeof window === 'undefined') return null;\n return new URLSearchParams(window.location.search).get('sessionId');\n });\n const [selectedLogId, setSelectedLogId] = useState<string | null>(null);\n const [selectedLogIds, setSelectedLogIds] = useState<Set<string>>(new Set());\n const [deleteTargetIds, setDeleteTargetIds] = useState<string[]>([]);\n const [deleteError, setDeleteError] = useState<string | null>(null);\n const [deleteSuccess, setDeleteSuccess] = useState<string | null>(null);\n\n const deleteErrorTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const canListAudit = hasPermission(VIBE_PERMISSIONS.AUDIT_LIST);\n const canDeleteAudit = hasPermission(VIBE_PERMISSIONS.AUDIT_DELETE);\n const canExportAudit = hasPermission(VIBE_PERMISSIONS.AUDIT_EXPORT);\n const shouldSkipQueries = permissionsLoading || !canListAudit;\n\n const filter: VibeAuditLogFilterInput = {};\n if (debouncedSearchQuery) filter.search = debouncedSearchQuery;\n if (actionFilter && actionFilter !== 'all') filter.action = actionFilter as VibeAuditAction;\n if (resourceTypeFilter && resourceTypeFilter !== 'all')\n filter.resourceType = resourceTypeFilter as VibeAuditResourceType;\n if (statusFilter && statusFilter !== 'all') filter.status = statusFilter as VibeAuditStatus;\n if (hasCommandFilter) filter.hasCommand = true;\n // Agent / Vibe filters: backend exposes `resourceId` + `resourceType`.\n // We narrow `resourceType` accordingly so the server returns only audit\n // rows whose target is the chosen agent / vibe. If the user already\n // chose a resourceType that conflicts (e.g. SESSION), the resourceId\n // filter would over-narrow — but that combination is documented as\n // intentional power-user behaviour.\n if (agentFilter && agentFilter !== 'all') {\n filter.resourceId = agentFilter;\n if (!filter.resourceType) filter.resourceType = 'AGENT' as VibeAuditResourceType;\n } else if (vibeFilter && vibeFilter !== 'all') {\n filter.resourceId = vibeFilter;\n if (!filter.resourceType) filter.resourceType = 'VIBE' as VibeAuditResourceType;\n }\n // Session drill-down — the server matches resourceType=SESSION AND\n // resourceId=sessionId. Works for killed (soft-deleted) sessions too.\n if (sessionDrillId) filter.sessionId = sessionDrillId;\n\n const { data, loading, error, refetch } = useVibeAuditDashboardQuery({\n variables: {\n filter,\n pagination: { limit: Math.min(Math.max((page + 1) * PAGE_SIZE, PAGE_SIZE), 100) },\n },\n skip: shouldSkipQueries,\n fetchPolicy: 'cache-and-network',\n notifyOnNetworkStatusChange: true,\n });\n\n const { data: agentsData } = useListAgentsQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n skip: shouldSkipQueries,\n });\n const agentsList = (agentsData?.vibecontrolsAgents ?? []).map(\n (a: { id: string; name: string }) => ({ id: a.id, name: a.name })\n );\n\n const { data: vibesData } = useListVibesQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n skip: shouldSkipQueries,\n });\n const vibesList = (vibesData?.vibes?.items ?? []).map((v: { id: string; name: string }) => ({\n id: v.id,\n name: v.name,\n }));\n\n const retentionDays = data?.vibeAuditLogStats?.retentionPolicy?.retentionDays ?? 90;\n const nextCleanupAt = data?.vibeAuditLogStats?.retentionPolicy?.nextCleanup ?? null;\n\n const { data: detailData } = useGetVibeAuditLogQuery({\n variables: { id: selectedLogId! },\n skip: !selectedLogId,\n });\n\n const [deleteAuditLog, { loading: deleteLoading }] = useDeleteVibeAuditLogMutation();\n const [bulkDeleteAuditLogs] = useBulkDeleteVibeAuditLogsMutation();\n\n useEffect(() => {\n const timeoutId = window.setTimeout(() => {\n setDebouncedSearchQuery(searchQuery);\n setPage(0);\n }, 300);\n\n return () => window.clearTimeout(timeoutId);\n }, [searchQuery]);\n\n useEffect(() => {\n return () => {\n if (deleteErrorTimer.current) {\n clearTimeout(deleteErrorTimer.current);\n }\n };\n }, []);\n\n const clearDeleteError = useCallback(() => {\n setDeleteError(null);\n if (deleteErrorTimer.current) {\n clearTimeout(deleteErrorTimer.current);\n deleteErrorTimer.current = null;\n }\n }, []);\n\n const clearDeleteSuccess = useCallback(() => {\n setDeleteSuccess(null);\n }, []);\n\n const allItems = data?.vibeAuditLogs.items ?? [];\n const items = allItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);\n const totalAuditItems = data?.vibeAuditLogs.total ?? 0;\n const visibleLogIds = items.map((item: (typeof items)[number]) => item.id);\n const allVisibleSelected =\n visibleLogIds.length > 0 && visibleLogIds.every((id: string) => selectedLogIds.has(id));\n const partiallySelected =\n visibleLogIds.some((id: string) => selectedLogIds.has(id)) && !allVisibleSelected;\n\n const handleDelete = async (reason: string) => {\n if (deleteTargetIds.length === 0) return;\n\n clearDeleteError();\n clearDeleteSuccess();\n\n try {\n if (deleteTargetIds.length === 1) {\n await deleteAuditLog({ variables: { id: deleteTargetIds[0], input: { reason } } });\n setDeleteSuccess('Deleted 1 audit log');\n } else {\n const result = await bulkDeleteAuditLogs({\n variables: { ids: deleteTargetIds, input: { reason } },\n });\n const deletedCount = result.data?.bulkDeleteVibeAuditLogs.deletedCount ?? 0;\n const failedCount = result.data?.bulkDeleteVibeAuditLogs.failedCount ?? 0;\n setDeleteSuccess(\n `Deleted ${deletedCount} audit logs${failedCount > 0 ? ` • ${failedCount} failed` : ''}`\n );\n }\n\n setDeleteTargetIds([]);\n setSelectedLogIds((previous) => {\n const next = new Set(previous);\n deleteTargetIds.forEach((id) => next.delete(id));\n return next;\n });\n if (selectedLogId && deleteTargetIds.includes(selectedLogId)) {\n setSelectedLogId(null);\n }\n await refetch();\n if (isNative()) void nativeNotify('success');\n } catch (deleteAuditError) {\n console.error('Failed to delete audit log:', deleteAuditError);\n setDeleteError(tr('audit.deleteFailed', 'Failed to delete audit log. Please try again.'));\n if (isNative()) void nativeNotify('error');\n deleteErrorTimer.current = setTimeout(() => {\n setDeleteError(null);\n deleteErrorTimer.current = null;\n }, 30000);\n }\n };\n\n const handleOpenDelete = useCallback(\n (id: string) => {\n if (!canDeleteAudit) return;\n clearDeleteError();\n clearDeleteSuccess();\n setDeleteTargetIds([id]);\n },\n [canDeleteAudit, clearDeleteError, clearDeleteSuccess]\n );\n\n const handleToggleLogSelection = useCallback((id: string, checked: boolean) => {\n setSelectedLogIds((previous) => {\n const next = new Set(previous);\n if (checked) {\n next.add(id);\n } else {\n next.delete(id);\n }\n return next;\n });\n }, []);\n\n const handleToggleAllVisible = useCallback(\n (checked: boolean) => {\n setSelectedLogIds((previous) => {\n const next = new Set(previous);\n visibleLogIds.forEach((id: string) => {\n if (checked) {\n next.add(id);\n } else {\n next.delete(id);\n }\n });\n return next;\n });\n },\n [visibleLogIds]\n );\n\n const handleOpenBulkDelete = useCallback(() => {\n if (!canDeleteAudit || selectedLogIds.size === 0) return;\n clearDeleteError();\n clearDeleteSuccess();\n setDeleteTargetIds(Array.from(selectedLogIds));\n }, [canDeleteAudit, clearDeleteError, clearDeleteSuccess, selectedLogIds]);\n\n if (permissionsLoading) {\n return (\n <div className=\"mx-auto max-w-[1400px] p-4 sm:p-6\">\n <LoadingState message={tr('audit.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListAudit) {\n return (\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'common.accessDeniedDescription',\n \"You don't have permission to access this resource.\"\n )}\n />\n );\n }\n\n return (\n <div className=\"mx-auto max-w-[1400px] p-4 sm:p-6\">\n <PageHeader\n title={tr('nav.vibeAudit', 'Audit')}\n description={tr(\n 'audit.description',\n 'Comprehensive audit trail of all VibeControls operations'\n )}\n actions={canExportAudit ? <AuditExportButton filter={filter} /> : undefined}\n />\n\n {/* Retention banner — sourced from the backend `retentionPolicy`\n so the displayed window updates if the tier changes. */}\n <div className=\"mb-4 flex items-start gap-2 rounded-md border border-status-info-border bg-status-info-bg/10 px-3 py-2 text-xs text-status-info-text\">\n <Shield size={14} className=\"mt-0.5 flex-shrink-0\" />\n <span>\n {tr(\n 'audit.retentionBanner',\n `Audit logs are kept for ${retentionDays} days. Older entries are automatically purged.`\n )}\n {nextCleanupAt\n ? ` ${tr('audit.retentionNextCleanup', `Next cleanup: ${new Date(nextCleanupAt).toLocaleString()}.`)}`\n : ''}\n </span>\n </div>\n\n {/* Session drill-down banner — shown when scoped to one session's activity. */}\n {sessionDrillId ? (\n <div className=\"mb-4 flex items-center justify-between gap-2 rounded-md border border-action-primary-border bg-action-primary-bg/10 px-3 py-2 text-xs text-text-primary\">\n <span className=\"truncate\">\n {tr('audit.sessionScopeBanner', 'Showing activity for session')}{' '}\n <span className=\"font-mono\">{sessionDrillId}</span>\n </span>\n <button\n type=\"button\"\n onClick={() => {\n setSessionDrillId(null);\n setPage(0);\n }}\n className=\"flex-shrink-0 rounded bg-bg-sunken px-2 py-1 font-medium text-text-secondary hover:text-text-primary\"\n >\n {tr('audit.clearSessionScope', 'Clear')}\n </button>\n </div>\n ) : null}\n\n {/* AuditLogStats is a custom component that doesn't forward rest props\n onto a DOM node, so the tour anchor lives on a wrapper <div>. */}\n <div data-tour=\"audit-log-stats\">\n <AuditLogStats stats={data?.vibeAuditLogStats} loading={loading && !data} />\n </div>\n\n <AuditLogFilters\n searchQuery={searchQuery}\n onSearchChange={setSearchQuery}\n actionFilter={actionFilter}\n onActionChange={(value) => {\n setActionFilter(value);\n setPage(0);\n }}\n resourceTypeFilter={resourceTypeFilter}\n onResourceTypeChange={(value) => {\n setResourceTypeFilter(value);\n setPage(0);\n }}\n statusFilter={statusFilter}\n onStatusChange={(value) => {\n setStatusFilter(value);\n setPage(0);\n }}\n hasCommandFilter={hasCommandFilter}\n onHasCommandChange={(value) => {\n setHasCommandFilter(value);\n setPage(0);\n }}\n agentFilter={agentFilter}\n onAgentChange={(value) => {\n setAgentFilter(value);\n // Agent + Vibe are mutually exclusive via resourceId — clearing one when\n // the other is set keeps the resolved filter input coherent.\n if (value !== 'all') setVibeFilter('all');\n setPage(0);\n }}\n agents={agentsList}\n vibeFilter={vibeFilter}\n onVibeChange={(value) => {\n setVibeFilter(value);\n if (value !== 'all') setAgentFilter('all');\n setPage(0);\n }}\n vibes={vibesList}\n />\n\n {items.length > 0 ? (\n <div className=\"rounded-lg border border-border-primary bg-bg-surface p-4\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"text-sm font-medium text-text-primary\">\n {selectedLogIds.size} selected\n </span>\n {selectedLogIds.size > 0 ? (\n <button\n type=\"button\"\n onClick={() => setSelectedLogIds(new Set())}\n className=\"text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n Clear selection\n </button>\n ) : null}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleOpenBulkDelete}\n disabled={!canDeleteAudit || selectedLogIds.size === 0}\n className=\"rounded-md border border-status-error-border px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Delete Selected{selectedLogIds.size > 0 ? ` (${selectedLogIds.size})` : ''}\n </button>\n </div>\n </div>\n </div>\n ) : null}\n\n {loading && !data ? (\n <LoadingState message={tr('audit.loadingLogs', 'Loading audit logs...')} />\n ) : error ? (\n <ErrorState message={error.message} onRetry={() => refetch()} />\n ) : totalAuditItems === 0 ? (\n <EmptyState\n icon={<Shield size={48} className=\"text-text-secondary\" />}\n title={tr('audit.noLogs', 'No audit logs')}\n description={tr(\n 'audit.emptyDescription',\n 'Audit logs will appear here as operations are performed in VibeControls.'\n )}\n action={\n <GetHelpButton prompt=\"What is the Audit Log in VibeControls and how do I use it?\" />\n }\n />\n ) : (\n <div data-tour=\"audit-log-table\">\n <AuditLogTable\n items={items}\n total={totalAuditItems}\n hasMore={data?.vibeAuditLogs.hasMore ?? false}\n page={page}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n onSelectLog={setSelectedLogId}\n onDeleteLog={handleOpenDelete}\n selectedLogId={selectedLogId}\n selectedLogIds={selectedLogIds}\n allVisibleSelected={allVisibleSelected}\n partiallySelected={partiallySelected}\n onToggleLogSelection={handleToggleLogSelection}\n onToggleAllVisible={handleToggleAllVisible}\n />\n </div>\n )}\n\n {selectedLogId && detailData?.vibeAuditLog ? (\n <AuditLogDetail\n log={detailData.vibeAuditLog}\n onClose={() => setSelectedLogId(null)}\n footer={\n detailData.vibeAuditLog.resourceType === 'SESSION' &&\n detailData.vibeAuditLog.resourceId &&\n detailData.vibeAuditLog.resourceId !== sessionDrillId ? (\n <button\n type=\"button\"\n onClick={() => {\n setSessionDrillId(detailData.vibeAuditLog?.resourceId ?? null);\n setSelectedLogId(null);\n setPage(0);\n }}\n className=\"w-full rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover\"\n >\n {tr('audit.showOnlyThisSession', \"Show only this session's activity\")}\n </button>\n ) : null\n }\n />\n ) : null}\n\n {deleteError ? (\n <div\n role=\"alert\"\n className=\"fixed bottom-4 right-4 z-50 rounded-md bg-status-error-bg px-4 py-3 text-sm text-status-error-text shadow-lg\"\n >\n {deleteError}\n <button\n type=\"button\"\n onClick={clearDeleteError}\n className=\"ml-3 text-xs underline opacity-80 hover:opacity-100\"\n >\n {tr('common.dismiss', 'Dismiss')}\n </button>\n </div>\n ) : null}\n\n {deleteSuccess ? (\n <div\n role=\"status\"\n className=\"fixed bottom-4 right-4 z-50 rounded-md bg-status-success-bg px-4 py-3 text-sm text-status-success-text shadow-lg\"\n >\n {deleteSuccess}\n <button\n type=\"button\"\n onClick={clearDeleteSuccess}\n className=\"ml-3 text-xs underline opacity-80 hover:opacity-100\"\n >\n {tr('common.dismiss', 'Dismiss')}\n </button>\n </div>\n ) : null}\n\n <AuditDeleteDialog\n isOpen={deleteTargetIds.length > 0}\n onClose={() => setDeleteTargetIds([])}\n onConfirm={handleDelete}\n loading={deleteLoading}\n title={deleteTargetIds.length > 1 ? 'Delete Audit Logs' : 'Delete Audit Log'}\n description={\n deleteTargetIds.length > 1\n ? `This will permanently delete ${deleteTargetIds.length} audit logs and record the reason in the audit trail.`\n : 'This action will be recorded in the audit trail.'\n }\n confirmLabel={deleteTargetIds.length > 1 ? 'Delete Logs' : 'Delete'}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAsCA,IAAM,IAAY;AAElB,SAAgB,IAAgB;AAC9B,KAAiB;CAEjB,IAAM,EAAE,SAAM,IAAS,EACjB,EAAE,kBAAe,WAAW,GAAoB,mBAAgB,IAAgB,EAEhF,CAAC,GAAa,MAAkB,EAAS,GAAG,EAC5C,CAAC,GAAsB,MAA2B,EAAS,GAAG,EAC9D,CAAC,GAAc,MAAmB,EAAwB,MAAM,EAChE,CAAC,GAAoB,MAAyB,EAAwB,MAAM,EAC5E,CAAC,GAAc,MAAmB,EAAwB,MAAM,EAChE,CAAC,GAAa,KAAkB,EAAwB,MAAM,EAC9D,CAAC,GAAY,KAAiB,EAAwB,MAAM,EAC5D,CAAC,GAAkB,MAAuB,EAAS,GAAM,EACzD,CAAC,GAAM,KAAW,EAAS,EAAE,EAI7B,CAAC,GAAgB,MAAqB,QACtC,OAAO,SAAW,MAAoB,OACnC,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAAC,IAAI,YAAY,CACnE,EACI,CAAC,GAAe,KAAoB,EAAwB,KAAK,EACjE,CAAC,GAAgB,KAAqB,kBAAsB,IAAI,KAAK,CAAC,EACtE,CAAC,GAAiB,KAAsB,EAAmB,EAAE,CAAC,EAC9D,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAe,KAAoB,EAAwB,KAAK,EAEjE,IAAmB,GAA6C,KAAK,EAErE,IAAK,GACR,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAE,CACJ,EAEK,IAAe,EAAc,EAAiB,WAAW,EACzD,IAAiB,EAAc,EAAiB,aAAa,EAC7D,KAAiB,EAAc,EAAiB,aAAa,EAC7D,IAAoB,KAAsB,CAAC,GAE3C,IAAkC,EAAE;AAsB1C,CArBI,MAAsB,EAAO,SAAS,IACtC,KAAgB,MAAiB,UAAO,EAAO,SAAS,IACxD,KAAsB,MAAuB,UAC/C,EAAO,eAAe,IACpB,KAAgB,MAAiB,UAAO,EAAO,SAAS,IACxD,MAAkB,EAAO,aAAa,KAOtC,KAAe,MAAgB,SACjC,EAAO,aAAa,GACpB,AAA0B,EAAO,iBAAe,WACvC,KAAc,MAAe,UACtC,EAAO,aAAa,GACpB,AAA0B,EAAO,iBAAe,SAI9C,MAAgB,EAAO,YAAY;CAEvC,IAAM,EAAE,SAAM,aAAS,WAAO,gBAAY,GAA2B;EACnE,WAAW;GACT;GACA,YAAY,EAAE,OAAO,KAAK,IAAI,KAAK,KAAK,IAAO,KAAK,GAAW,EAAU,EAAE,IAAI,EAAE;GAClF;EACD,MAAM;EACN,aAAa;EACb,6BAA6B;EAC9B,CAAC,EAEI,EAAE,MAAM,OAAe,EAAmB;EAC9C,WAAW,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE;EACzC,aAAa;EACb,MAAM;EACP,CAAC,EACI,MAAc,IAAY,sBAAsB,EAAE,EAAE,KACvD,OAAqC;EAAE,IAAI,EAAE;EAAI,MAAM,EAAE;EAAM,EACjE,EAEK,EAAE,MAAM,OAAc,GAAkB;EAC5C,WAAW,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE;EACzC,aAAa;EACb,MAAM;EACP,CAAC,EACI,MAAa,IAAW,OAAO,SAAS,EAAE,EAAE,KAAK,OAAqC;EAC1F,IAAI,EAAE;EACN,MAAM,EAAE;EACT,EAAE,EAEG,KAAgB,GAAM,mBAAmB,iBAAiB,iBAAiB,IAC3E,KAAgB,GAAM,mBAAmB,iBAAiB,eAAe,MAEzE,EAAE,MAAM,MAAe,EAAwB;EACnD,WAAW,EAAE,IAAI,GAAgB;EACjC,MAAM,CAAC;EACR,CAAC,EAEI,CAAC,IAAgB,EAAE,SAAS,QAAmB,GAA+B,EAC9E,CAAC,MAAuB,GAAoC;AAWlE,CATA,SAAgB;EACd,IAAM,IAAY,OAAO,iBAAiB;AAExC,GADA,GAAwB,EAAY,EACpC,EAAQ,EAAE;KACT,IAAI;AAEP,eAAa,OAAO,aAAa,EAAU;IAC1C,CAAC,EAAY,CAAC,EAEjB,eACe;AACX,EAAI,EAAiB,WACnB,aAAa,EAAiB,QAAQ;IAGzC,EAAE,CAAC;CAEN,IAAM,IAAmB,QAAkB;AAEzC,EADA,EAAe,KAAK,EACpB,AAEE,EAAiB,aADjB,aAAa,EAAiB,QAAQ,EACX;IAE5B,EAAE,CAAC,EAEA,IAAqB,QAAkB;AAC3C,IAAiB,KAAK;IACrB,EAAE,CAAC,EAGA,KADW,GAAM,cAAc,SAAS,EAAE,EACzB,MAAM,IAAO,IAAY,IAAO,KAAK,EAAU,EAChE,KAAkB,GAAM,cAAc,SAAS,GAC/C,IAAgB,EAAM,KAAK,MAAiC,EAAK,GAAG,EACpE,IACJ,EAAc,SAAS,KAAK,EAAc,OAAO,MAAe,EAAe,IAAI,EAAG,CAAC,EACnF,KACJ,EAAc,MAAM,MAAe,EAAe,IAAI,EAAG,CAAC,IAAI,CAAC,GAE3D,KAAe,OAAO,MAAmB;AACzC,QAAgB,WAAW,GAG/B;GADA,GAAkB,EAClB,GAAoB;AAEpB,OAAI;AACF,QAAI,EAAgB,WAAW,EAE7B,CADA,MAAM,GAAe,EAAE,WAAW;KAAE,IAAI,EAAgB;KAAI,OAAO,EAAE,WAAQ;KAAE,EAAE,CAAC,EAClF,EAAiB,sBAAsB;SAClC;KACL,IAAM,IAAS,MAAM,GAAoB,EACvC,WAAW;MAAE,KAAK;MAAiB,OAAO,EAAE,WAAQ;MAAE,EACvD,CAAC,EACI,IAAe,EAAO,MAAM,wBAAwB,gBAAgB,GACpE,IAAc,EAAO,MAAM,wBAAwB,eAAe;AACxE,OACE,WAAW,EAAa,aAAa,IAAc,IAAI,MAAM,EAAY,WAAW,KACrF;;AAaH,IAVA,EAAmB,EAAE,CAAC,EACtB,GAAmB,MAAa;KAC9B,IAAM,IAAO,IAAI,IAAI,EAAS;AAE9B,YADA,EAAgB,SAAS,MAAO,EAAK,OAAO,EAAG,CAAC,EACzC;MACP,EACE,KAAiB,EAAgB,SAAS,EAAc,IAC1D,EAAiB,KAAK,EAExB,MAAM,IAAS,EACX,GAAU,IAAO,EAAa,UAAU;YACrC,GAAkB;AAIzB,IAHA,QAAQ,MAAM,+BAA+B,EAAiB,EAC9D,EAAe,EAAG,sBAAsB,gDAAgD,CAAC,EACrF,GAAU,IAAO,EAAa,QAAQ,EAC1C,EAAiB,UAAU,iBAAiB;AAE1C,KADA,EAAe,KAAK,EACpB,EAAiB,UAAU;OAC1B,IAAM;;;IAIP,KAAmB,GACtB,MAAe;AACT,QACL,GAAkB,EAClB,GAAoB,EACpB,EAAmB,CAAC,EAAG,CAAC;IAE1B;EAAC;EAAgB;EAAkB;EAAmB,CACvD,EAEK,KAA2B,GAAa,GAAY,MAAqB;AAC7E,KAAmB,MAAa;GAC9B,IAAM,IAAO,IAAI,IAAI,EAAS;AAM9B,UALI,IACF,EAAK,IAAI,EAAG,GAEZ,EAAK,OAAO,EAAG,EAEV;IACP;IACD,EAAE,CAAC,EAEA,KAAyB,GAC5B,MAAqB;AACpB,KAAmB,MAAa;GAC9B,IAAM,IAAO,IAAI,IAAI,EAAS;AAQ9B,UAPA,EAAc,SAAS,MAAe;AACpC,IAAI,IACF,EAAK,IAAI,EAAG,GAEZ,EAAK,OAAO,EAAG;KAEjB,EACK;IACP;IAEJ,CAAC,EAAc,CAChB,EAEK,KAAuB,QAAkB;AACzC,GAAC,KAAkB,EAAe,SAAS,MAC/C,GAAkB,EAClB,GAAoB,EACpB,EAAmB,MAAM,KAAK,EAAe,CAAC;IAC7C;EAAC;EAAgB;EAAkB;EAAoB;EAAe,CAAC;AAsB1E,QApBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,4BAA4B,yBAAyB,EAAI,CAAA;EAC/E,CAAA,GAIN,KAAe,EAAY,mBAAmB,SAAS,KAAK,CAAC,IAE7D,kBAAC,IAAD;EACE,OAAO,EAAG,4BAA4B,gBAAgB;EACtD,aAAa,EACX,kCACA,qDACD;EACD,CAAA,GAKJ,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,IAAD;IACE,OAAO,EAAG,iBAAiB,QAAQ;IACnC,aAAa,EACX,qBACA,2DACD;IACD,SAAS,KAAiB,kBAAC,IAAD,EAA2B,WAAU,CAAA,GAAG,KAAA;IAClE,CAAA;GAIF,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAQ,MAAM;KAAI,WAAU;KAAyB,CAAA,EACrD,kBAAC,QAAD,EAAA,UAAA,CACG,EACC,yBACA,2BAA2B,GAAc,gDAC1C,EACA,KACG,IAAI,EAAG,8BAA8B,iBAAiB,IAAI,KAAK,GAAc,CAAC,gBAAgB,CAAC,GAAG,KAClG,GACC,EAAA,CAAA,CACH;;GAGL,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,EAAG,4BAA4B,+BAA+B;MAAE;MACjE,kBAAC,QAAD;OAAM,WAAU;iBAAa;OAAsB,CAAA;MAC9C;QACP,kBAAC,UAAD;KACE,MAAK;KACL,eAAe;AAEb,MADA,GAAkB,KAAK,EACvB,EAAQ,EAAE;;KAEZ,WAAU;eAET,EAAG,2BAA2B,QAAQ;KAChC,CAAA,CACL;QACJ;GAIJ,kBAAC,OAAD;IAAK,aAAU;cACb,kBAAC,IAAD;KAAe,OAAO,GAAM;KAAmB,SAAS,MAAW,CAAC;KAAQ,CAAA;IACxE,CAAA;GAEN,kBAAC,IAAD;IACe;IACb,gBAAgB;IACF;IACd,iBAAiB,MAAU;AAEzB,KADA,GAAgB,EAAM,EACtB,EAAQ,EAAE;;IAEQ;IACpB,uBAAuB,MAAU;AAE/B,KADA,GAAsB,EAAM,EAC5B,EAAQ,EAAE;;IAEE;IACd,iBAAiB,MAAU;AAEzB,KADA,GAAgB,EAAM,EACtB,EAAQ,EAAE;;IAEM;IAClB,qBAAqB,MAAU;AAE7B,KADA,GAAoB,EAAM,EAC1B,EAAQ,EAAE;;IAEC;IACb,gBAAgB,MAAU;AAKxB,KAJA,EAAe,EAAM,EAGjB,MAAU,SAAO,EAAc,MAAM,EACzC,EAAQ,EAAE;;IAEZ,QAAQ;IACI;IACZ,eAAe,MAAU;AAGvB,KAFA,EAAc,EAAM,EAChB,MAAU,SAAO,EAAe,MAAM,EAC1C,EAAQ,EAAE;;IAEZ,OAAO;IACP,CAAA;GAED,EAAM,SAAS,IACd,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACG,EAAe,MAAK,YAChB;UACN,EAAe,OAAO,IACrB,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,kBAAkB,IAAI,KAAK,CAAC;OAC3C,WAAU;iBACX;OAEQ,CAAA,GACP,KACA;SACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,UAAU,CAAC,KAAkB,EAAe,SAAS;OACrD,WAAU;iBAJZ,CAKC,mBACiB,EAAe,OAAO,IAAI,KAAK,EAAe,KAAK,KAAK,GACjE;;MACL,CAAA,CACF;;IACF,CAAA,GACJ;GAEH,MAAW,CAAC,IACX,kBAAC,GAAD,EAAc,SAAS,EAAG,qBAAqB,wBAAwB,EAAI,CAAA,GACzE,KACF,kBAAC,IAAD;IAAY,SAAS,GAAM;IAAS,eAAe,IAAS;IAAI,CAAA,GAC9D,OAAoB,IACtB,kBAAC,IAAD;IACE,MAAM,kBAAC,GAAD;KAAQ,MAAM;KAAI,WAAU;KAAwB,CAAA;IAC1D,OAAO,EAAG,gBAAgB,gBAAgB;IAC1C,aAAa,EACX,0BACA,2EACD;IACD,QACE,kBAAC,IAAD,EAAe,QAAO,8DAA+D,CAAA;IAEvF,CAAA,GAEF,kBAAC,OAAD;IAAK,aAAU;cACb,kBAAC,IAAD;KACS;KACP,OAAO;KACP,SAAS,GAAM,cAAc,WAAW;KAClC;KACN,UAAU;KACV,cAAc;KACd,aAAa;KACb,aAAa;KACE;KACC;KACI;KACD;KACnB,sBAAsB;KACtB,oBAAoB;KACpB,CAAA;IACE,CAAA;GAGP,KAAiB,GAAY,eAC5B,kBAAC,IAAD;IACE,KAAK,EAAW;IAChB,eAAe,EAAiB,KAAK;IACrC,QACE,EAAW,aAAa,iBAAiB,aACzC,EAAW,aAAa,cACxB,EAAW,aAAa,eAAe,IACrC,kBAAC,UAAD;KACE,MAAK;KACL,eAAe;AAGb,MAFA,GAAkB,EAAW,cAAc,cAAc,KAAK,EAC9D,EAAiB,KAAK,EACtB,EAAQ,EAAE;;KAEZ,WAAU;eAET,EAAG,6BAA6B,oCAAoC;KAC9D,CAAA,GACP;IAEN,CAAA,GACA;GAEH,IACC,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAFZ,CAIG,GACD,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAET,EAAG,kBAAkB,UAAU;KACzB,CAAA,CACL;QACJ;GAEH,IACC,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAFZ,CAIG,GACD,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAET,EAAG,kBAAkB,UAAU;KACzB,CAAA,CACL;QACJ;GAEJ,kBAAC,IAAD;IACE,QAAQ,EAAgB,SAAS;IACjC,eAAe,EAAmB,EAAE,CAAC;IACrC,WAAW;IACX,SAAS;IACT,OAAO,EAAgB,SAAS,IAAI,sBAAsB;IAC1D,aACE,EAAgB,SAAS,IACrB,gCAAgC,EAAgB,OAAO,yDACvD;IAEN,cAAc,EAAgB,SAAS,IAAI,gBAAgB;IAC3D,CAAA;GACE"}
|
|
1
|
+
{"version":3,"file":"VibeAuditPage.js","names":[],"sources":["../../src/pages/VibeAuditPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { Shield } from 'lucide-react';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { isNative, nativeNotify } from '../utils/nativeBridge';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n AuditDeleteDialog,\n AuditExportButton,\n AuditLogDetail,\n AuditLogFilters,\n AuditLogStats,\n AuditLogTable,\n} from '@/components/audit';\nimport {\n EmptyState,\n ErrorState,\n GetHelpButton,\n LoadingState,\n PageHeader,\n} from '@/components/shared';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport {\n useBulkDeleteVibeAuditLogsMutation,\n useDeleteVibeAuditLogMutation,\n useGetVibeAuditLogQuery,\n useListAgentsQuery,\n useListVibesQuery,\n useVibeAuditDashboardQuery,\n} from '@/generated/wspace-operations';\nimport type {\n VibeAuditAction,\n VibeAuditLogFilterInput,\n VibeAuditResourceType,\n VibeAuditStatus,\n} from '@/generated/wspace-types';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\n\nconst PAGE_SIZE = 50;\n\nexport function VibeAuditPage() {\n useVibeControls();\n\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');\n const [actionFilter, setActionFilter] = useState<string | null>('all');\n const [resourceTypeFilter, setResourceTypeFilter] = useState<string | null>('all');\n const [statusFilter, setStatusFilter] = useState<string | null>('all');\n const [agentFilter, setAgentFilter] = useState<string | null>('all');\n const [vibeFilter, setVibeFilter] = useState<string | null>('all');\n const [hasCommandFilter, setHasCommandFilter] = useState(false);\n const [page, setPage] = useState(0);\n // F2 — session-history drill-down. Initialised from a ?sessionId= URL param\n // (reciprocal link from the in-session History tab) and also settable by\n // clicking \"Show only this session\" on a SESSION-typed audit entry.\n const [sessionDrillId, setSessionDrillId] = useState<string | null>(() => {\n if (typeof window === 'undefined') return null;\n return new URLSearchParams(window.location.search).get('sessionId');\n });\n const [selectedLogId, setSelectedLogId] = useState<string | null>(null);\n const [selectedLogIds, setSelectedLogIds] = useState<Set<string>>(new Set());\n const [deleteTargetIds, setDeleteTargetIds] = useState<string[]>([]);\n const [deleteError, setDeleteError] = useState<string | null>(null);\n const [deleteSuccess, setDeleteSuccess] = useState<string | null>(null);\n\n const deleteErrorTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const canListAudit = hasPermission(VIBE_PERMISSIONS.AUDIT_LIST);\n const canDeleteAudit = hasPermission(VIBE_PERMISSIONS.AUDIT_DELETE);\n const canExportAudit = hasPermission(VIBE_PERMISSIONS.AUDIT_EXPORT);\n const shouldSkipQueries = permissionsLoading || !canListAudit;\n\n const filter: VibeAuditLogFilterInput = {};\n if (debouncedSearchQuery) filter.search = debouncedSearchQuery;\n if (actionFilter && actionFilter !== 'all') filter.action = actionFilter as VibeAuditAction;\n if (resourceTypeFilter && resourceTypeFilter !== 'all')\n filter.resourceType = resourceTypeFilter as VibeAuditResourceType;\n if (statusFilter && statusFilter !== 'all') filter.status = statusFilter as VibeAuditStatus;\n if (hasCommandFilter) filter.hasCommand = true;\n // Agent / Vibe filters: backend exposes `resourceId` + `resourceType`.\n // We narrow `resourceType` accordingly so the server returns only audit\n // rows whose target is the chosen agent / vibe. If the user already\n // chose a resourceType that conflicts (e.g. SESSION), the resourceId\n // filter would over-narrow — but that combination is documented as\n // intentional power-user behaviour.\n if (agentFilter && agentFilter !== 'all') {\n filter.resourceId = agentFilter;\n if (!filter.resourceType) filter.resourceType = 'AGENT' as VibeAuditResourceType;\n } else if (vibeFilter && vibeFilter !== 'all') {\n filter.resourceId = vibeFilter;\n if (!filter.resourceType) filter.resourceType = 'VIBE' as VibeAuditResourceType;\n }\n // Session drill-down — the server matches resourceType=SESSION AND\n // resourceId=sessionId. Works for killed (soft-deleted) sessions too.\n if (sessionDrillId) filter.sessionId = sessionDrillId;\n\n const { data, loading, error, refetch } = useVibeAuditDashboardQuery({\n variables: {\n filter,\n pagination: { limit: Math.min(Math.max((page + 1) * PAGE_SIZE, PAGE_SIZE), 100) },\n },\n skip: shouldSkipQueries,\n fetchPolicy: 'cache-and-network',\n notifyOnNetworkStatusChange: true,\n });\n\n const { data: agentsData } = useListAgentsQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n skip: shouldSkipQueries,\n });\n const agentsList = (agentsData?.vibecontrolsAgents ?? []).map(\n (a: { id: string; name: string }) => ({ id: a.id, name: a.name })\n );\n\n const { data: vibesData } = useListVibesQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n skip: shouldSkipQueries,\n });\n const vibesList = (vibesData?.vibes?.items ?? []).map((v: { id: string; name: string }) => ({\n id: v.id,\n name: v.name,\n }));\n\n const retentionDays = data?.vibeAuditLogStats?.retentionPolicy?.retentionDays ?? 90;\n const nextCleanupAt = data?.vibeAuditLogStats?.retentionPolicy?.nextCleanup ?? null;\n\n const { data: detailData } = useGetVibeAuditLogQuery({\n variables: { id: selectedLogId! },\n skip: !selectedLogId,\n });\n\n const [deleteAuditLog, { loading: deleteLoading }] = useDeleteVibeAuditLogMutation();\n const [bulkDeleteAuditLogs] = useBulkDeleteVibeAuditLogsMutation();\n\n useEffect(() => {\n const timeoutId = window.setTimeout(() => {\n setDebouncedSearchQuery(searchQuery);\n setPage(0);\n }, 300);\n\n return () => window.clearTimeout(timeoutId);\n }, [searchQuery]);\n\n useEffect(() => {\n return () => {\n if (deleteErrorTimer.current) {\n clearTimeout(deleteErrorTimer.current);\n }\n };\n }, []);\n\n const clearDeleteError = useCallback(() => {\n setDeleteError(null);\n if (deleteErrorTimer.current) {\n clearTimeout(deleteErrorTimer.current);\n deleteErrorTimer.current = null;\n }\n }, []);\n\n const clearDeleteSuccess = useCallback(() => {\n setDeleteSuccess(null);\n }, []);\n\n const allItems = data?.vibeAuditLogs.items ?? [];\n const items = allItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);\n const totalAuditItems = data?.vibeAuditLogs.total ?? 0;\n const visibleLogIds = items.map((item: (typeof items)[number]) => item.id);\n const allVisibleSelected =\n visibleLogIds.length > 0 && visibleLogIds.every((id: string) => selectedLogIds.has(id));\n const partiallySelected =\n visibleLogIds.some((id: string) => selectedLogIds.has(id)) && !allVisibleSelected;\n\n const handleDelete = async (reason: string) => {\n if (deleteTargetIds.length === 0) return;\n\n clearDeleteError();\n clearDeleteSuccess();\n\n try {\n if (deleteTargetIds.length === 1) {\n await deleteAuditLog({ variables: { id: deleteTargetIds[0], input: { reason } } });\n setDeleteSuccess('Deleted 1 audit log');\n } else {\n const result = await bulkDeleteAuditLogs({\n variables: { ids: deleteTargetIds, input: { reason } },\n });\n const deletedCount = result.data?.bulkDeleteVibeAuditLogs.deletedCount ?? 0;\n const failedCount = result.data?.bulkDeleteVibeAuditLogs.failedCount ?? 0;\n setDeleteSuccess(\n `Deleted ${deletedCount} audit logs${failedCount > 0 ? ` • ${failedCount} failed` : ''}`\n );\n }\n\n setDeleteTargetIds([]);\n setSelectedLogIds((previous) => {\n const next = new Set(previous);\n deleteTargetIds.forEach((id) => next.delete(id));\n return next;\n });\n if (selectedLogId && deleteTargetIds.includes(selectedLogId)) {\n setSelectedLogId(null);\n }\n await refetch();\n if (isNative()) void nativeNotify('success');\n } catch (deleteAuditError) {\n console.error('Failed to delete audit log:', deleteAuditError);\n setDeleteError(tr('audit.deleteFailed', 'Failed to delete audit log. Please try again.'));\n if (isNative()) void nativeNotify('error');\n deleteErrorTimer.current = setTimeout(() => {\n setDeleteError(null);\n deleteErrorTimer.current = null;\n }, 30000);\n }\n };\n\n const handleOpenDelete = useCallback(\n (id: string) => {\n if (!canDeleteAudit) return;\n clearDeleteError();\n clearDeleteSuccess();\n setDeleteTargetIds([id]);\n },\n [canDeleteAudit, clearDeleteError, clearDeleteSuccess]\n );\n\n const handleToggleLogSelection = useCallback((id: string, checked: boolean) => {\n setSelectedLogIds((previous) => {\n const next = new Set(previous);\n if (checked) {\n next.add(id);\n } else {\n next.delete(id);\n }\n return next;\n });\n }, []);\n\n const handleToggleAllVisible = useCallback(\n (checked: boolean) => {\n setSelectedLogIds((previous) => {\n const next = new Set(previous);\n visibleLogIds.forEach((id: string) => {\n if (checked) {\n next.add(id);\n } else {\n next.delete(id);\n }\n });\n return next;\n });\n },\n [visibleLogIds]\n );\n\n const handleOpenBulkDelete = useCallback(() => {\n if (!canDeleteAudit || selectedLogIds.size === 0) return;\n clearDeleteError();\n clearDeleteSuccess();\n setDeleteTargetIds(Array.from(selectedLogIds));\n }, [canDeleteAudit, clearDeleteError, clearDeleteSuccess, selectedLogIds]);\n\n if (permissionsLoading) {\n return (\n <div className=\"mx-auto max-w-[1400px] p-4 sm:p-6\">\n <LoadingState message={tr('audit.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListAudit) {\n return (\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'common.accessDeniedDescription',\n \"You don't have permission to access this resource.\"\n )}\n />\n );\n }\n\n return (\n <div className=\"mx-auto max-w-[1400px] p-4 sm:p-6\">\n <PageHeader\n title={tr('nav.vibeAudit', 'Audit')}\n description={tr(\n 'audit.description',\n 'Comprehensive audit trail of all VibeControls operations'\n )}\n actions={canExportAudit ? <AuditExportButton filter={filter} /> : undefined}\n />\n\n {/* Retention banner — sourced from the backend `retentionPolicy`\n so the displayed window updates if the tier changes. */}\n <div className=\"mb-4 flex items-start gap-2 rounded-md border border-status-info-border bg-status-info-bg-subtle px-3 py-2 text-xs text-status-info-text\">\n <Shield size={14} className=\"mt-0.5 flex-shrink-0\" />\n <span>\n {tr(\n 'audit.retentionBanner',\n `Audit logs are kept for ${retentionDays} days. Older entries are automatically purged.`\n )}\n {nextCleanupAt\n ? ` ${tr('audit.retentionNextCleanup', `Next cleanup: ${new Date(nextCleanupAt).toLocaleString()}.`)}`\n : ''}\n </span>\n </div>\n\n {/* Session drill-down banner — shown when scoped to one session's activity. */}\n {sessionDrillId ? (\n <div className=\"mb-4 flex items-center justify-between gap-2 rounded-md border border-action-primary-border bg-[var(--color-accent-soft)] px-3 py-2 text-xs text-text-primary\">\n <span className=\"truncate\">\n {tr('audit.sessionScopeBanner', 'Showing activity for session')}{' '}\n <span className=\"font-mono\">{sessionDrillId}</span>\n </span>\n <button\n type=\"button\"\n onClick={() => {\n setSessionDrillId(null);\n setPage(0);\n }}\n className=\"flex-shrink-0 rounded bg-bg-sunken px-2 py-1 font-medium text-text-secondary hover:text-text-primary\"\n >\n {tr('audit.clearSessionScope', 'Clear')}\n </button>\n </div>\n ) : null}\n\n {/* AuditLogStats is a custom component that doesn't forward rest props\n onto a DOM node, so the tour anchor lives on a wrapper <div>. */}\n <div data-tour=\"audit-log-stats\">\n <AuditLogStats stats={data?.vibeAuditLogStats} loading={loading && !data} />\n </div>\n\n <AuditLogFilters\n searchQuery={searchQuery}\n onSearchChange={setSearchQuery}\n actionFilter={actionFilter}\n onActionChange={(value) => {\n setActionFilter(value);\n setPage(0);\n }}\n resourceTypeFilter={resourceTypeFilter}\n onResourceTypeChange={(value) => {\n setResourceTypeFilter(value);\n setPage(0);\n }}\n statusFilter={statusFilter}\n onStatusChange={(value) => {\n setStatusFilter(value);\n setPage(0);\n }}\n hasCommandFilter={hasCommandFilter}\n onHasCommandChange={(value) => {\n setHasCommandFilter(value);\n setPage(0);\n }}\n agentFilter={agentFilter}\n onAgentChange={(value) => {\n setAgentFilter(value);\n // Agent + Vibe are mutually exclusive via resourceId — clearing one when\n // the other is set keeps the resolved filter input coherent.\n if (value !== 'all') setVibeFilter('all');\n setPage(0);\n }}\n agents={agentsList}\n vibeFilter={vibeFilter}\n onVibeChange={(value) => {\n setVibeFilter(value);\n if (value !== 'all') setAgentFilter('all');\n setPage(0);\n }}\n vibes={vibesList}\n />\n\n {items.length > 0 ? (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"text-sm font-medium text-text-primary\">\n {selectedLogIds.size} selected\n </span>\n {selectedLogIds.size > 0 ? (\n <button\n type=\"button\"\n onClick={() => setSelectedLogIds(new Set())}\n className=\"text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n Clear selection\n </button>\n ) : null}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleOpenBulkDelete}\n disabled={!canDeleteAudit || selectedLogIds.size === 0}\n className=\"rounded-md border border-status-error-border px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Delete Selected{selectedLogIds.size > 0 ? ` (${selectedLogIds.size})` : ''}\n </button>\n </div>\n </div>\n </div>\n ) : null}\n\n {loading && !data ? (\n <LoadingState message={tr('audit.loadingLogs', 'Loading audit logs...')} />\n ) : error ? (\n <ErrorState message={error.message} onRetry={() => refetch()} />\n ) : totalAuditItems === 0 ? (\n <EmptyState\n icon={<Shield size={48} className=\"text-text-secondary\" />}\n title={tr('audit.noLogs', 'No audit logs')}\n description={tr(\n 'audit.emptyDescription',\n 'Audit logs will appear here as operations are performed in VibeControls.'\n )}\n action={\n <GetHelpButton prompt=\"What is the Audit Log in VibeControls and how do I use it?\" />\n }\n />\n ) : (\n <div data-tour=\"audit-log-table\">\n <AuditLogTable\n items={items}\n total={totalAuditItems}\n hasMore={data?.vibeAuditLogs.hasMore ?? false}\n page={page}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n onSelectLog={setSelectedLogId}\n onDeleteLog={handleOpenDelete}\n selectedLogId={selectedLogId}\n selectedLogIds={selectedLogIds}\n allVisibleSelected={allVisibleSelected}\n partiallySelected={partiallySelected}\n onToggleLogSelection={handleToggleLogSelection}\n onToggleAllVisible={handleToggleAllVisible}\n />\n </div>\n )}\n\n {selectedLogId && detailData?.vibeAuditLog ? (\n <AuditLogDetail\n log={detailData.vibeAuditLog}\n onClose={() => setSelectedLogId(null)}\n footer={\n detailData.vibeAuditLog.resourceType === 'SESSION' &&\n detailData.vibeAuditLog.resourceId &&\n detailData.vibeAuditLog.resourceId !== sessionDrillId ? (\n <button\n type=\"button\"\n onClick={() => {\n setSessionDrillId(detailData.vibeAuditLog?.resourceId ?? null);\n setSelectedLogId(null);\n setPage(0);\n }}\n className=\"w-full rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover\"\n >\n {tr('audit.showOnlyThisSession', \"Show only this session's activity\")}\n </button>\n ) : null\n }\n />\n ) : null}\n\n {deleteError ? (\n <div\n role=\"alert\"\n className=\"fixed bottom-4 right-4 z-50 rounded-md bg-status-error-bg px-4 py-3 text-sm text-status-error-text shadow-lg\"\n >\n {deleteError}\n <button\n type=\"button\"\n onClick={clearDeleteError}\n className=\"ml-3 text-xs underline opacity-80 hover:opacity-100\"\n >\n {tr('common.dismiss', 'Dismiss')}\n </button>\n </div>\n ) : null}\n\n {deleteSuccess ? (\n <div\n role=\"status\"\n className=\"fixed bottom-4 right-4 z-50 rounded-md bg-status-success-bg px-4 py-3 text-sm text-status-success-text shadow-lg\"\n >\n {deleteSuccess}\n <button\n type=\"button\"\n onClick={clearDeleteSuccess}\n className=\"ml-3 text-xs underline opacity-80 hover:opacity-100\"\n >\n {tr('common.dismiss', 'Dismiss')}\n </button>\n </div>\n ) : null}\n\n <AuditDeleteDialog\n isOpen={deleteTargetIds.length > 0}\n onClose={() => setDeleteTargetIds([])}\n onConfirm={handleDelete}\n loading={deleteLoading}\n title={deleteTargetIds.length > 1 ? 'Delete Audit Logs' : 'Delete Audit Log'}\n description={\n deleteTargetIds.length > 1\n ? `This will permanently delete ${deleteTargetIds.length} audit logs and record the reason in the audit trail.`\n : 'This action will be recorded in the audit trail.'\n }\n confirmLabel={deleteTargetIds.length > 1 ? 'Delete Logs' : 'Delete'}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -198,14 +198,14 @@ function m() {
|
|
|
198
198
|
type: "button",
|
|
199
199
|
onClick: () => W(),
|
|
200
200
|
disabled: ke,
|
|
201
|
-
className: "rounded px-3 py-1 text-xs text-status-error-text transition-colors hover:bg-status-error-bg
|
|
201
|
+
className: "rounded px-3 py-1 text-xs text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50",
|
|
202
202
|
children: I("calendar.retry", "Retry")
|
|
203
203
|
})
|
|
204
204
|
]
|
|
205
205
|
}),
|
|
206
206
|
P && /* @__PURE__ */ f("div", {
|
|
207
207
|
role: "alert",
|
|
208
|
-
className: "flex items-start gap-2 rounded-lg border border-status-error-border
|
|
208
|
+
className: "flex items-start gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
209
209
|
children: [
|
|
210
210
|
/* @__PURE__ */ d(u, {
|
|
211
211
|
size: 16,
|
|
@@ -218,7 +218,7 @@ function m() {
|
|
|
218
218
|
/* @__PURE__ */ d("button", {
|
|
219
219
|
type: "button",
|
|
220
220
|
onClick: () => F(null),
|
|
221
|
-
className: "p-0.5 rounded hover:bg-
|
|
221
|
+
className: "p-0.5 rounded hover:bg-bg-sunken transition-colors",
|
|
222
222
|
"aria-label": I("common.dismiss", "Dismiss"),
|
|
223
223
|
children: /* @__PURE__ */ d(fe, { size: 14 })
|
|
224
224
|
})
|