/** * Sessions Landing Page JavaScript */ // Load sessions on page load document.addEventListener('DOMContentLoaded', () => { checkAuth(); initializeProjectInput(); loadSessionsAndProjects(); }); // Check authentication async function checkAuth() { try { const res = await fetch('/claude/api/auth/status'); if (!res.ok) { throw new Error('Request failed'); } const data = await res.json(); if (!data.authenticated) { // Redirect to login if not authenticated window.location.href = '/claude/login.html'; } } catch (error) { console.error('Auth check failed:', error); } } /** * Initialize project input field in hero section */ function initializeProjectInput() { const input = document.getElementById('project-input'); const status = document.getElementById('input-status'); if (!input) return; // Auto-focus on page load input.focus(); input.addEventListener('input', () => { const projectName = input.value.trim(); const hasInvalidChars = !validateProjectName(projectName); if (hasInvalidChars && projectName.length > 0) { status.textContent = 'Invalid characters'; status.classList.add('error'); } else { status.textContent = ''; status.classList.remove('error'); } }); input.addEventListener('keypress', (e) => { if (e.key === 'Enter') { const projectName = input.value.trim(); if (projectName && validateProjectName(projectName)) { createProject(projectName); } } }); } /** * Create a new project and navigate to IDE */ async function createProject(projectName) { try { showLoadingOverlay('Creating project...'); const res = await fetch('/claude/api/claude/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ metadata: { type: 'chat', source: 'web-ide', project: projectName } }) }); if (!res.ok) throw new Error('Request failed'); const data = await res.json(); if (data.success) { // Minimum display time for smooth UX await new Promise(resolve => setTimeout(resolve, 300)); window.location.href = `/claude/ide?session=${data.session.id}`; } } catch (error) { console.error('Error creating project:', error); hideLoadingOverlay(); showToast('Failed to create project', 'error'); } } /** * Load all sessions and projects, then render grouped by project */ async function loadSessionsAndProjects() { const tbody = document.getElementById('projects-tbody'); const emptyState = document.getElementById('projects-empty'); const table = document.getElementById('projects-table'); if (!tbody) return; tbody.innerHTML = '
${escapeHtml(message)}
`; document.body.appendChild(overlay); } else { // Update message if provided const textElement = overlay.querySelector('.loading-text'); if (textElement) { textElement.textContent = message; } } overlay.classList.remove('hidden'); setTimeout(() => { overlay.classList.add('visible'); }, 10); } /** * Hide loading overlay */ function hideLoadingOverlay() { const overlay = document.getElementById('loading-overlay'); if (overlay) { overlay.classList.remove('visible'); setTimeout(() => { overlay.classList.add('hidden'); }, 300); } } /** * Show toast notification * @param {string} message - The message to display * @param {string} type - The type of toast: 'success', 'error', 'info' * @param {number} duration - Duration in milliseconds (default: 3000) */ function showToast(message, type = 'info', duration = 3000) { // Remove existing toasts const existingToasts = document.querySelectorAll('.toast-notification'); existingToasts.forEach(toast => toast.remove()); // Create toast element const toast = document.createElement('div'); toast.className = `toast-notification toast-${type}`; toast.innerHTML = ` `; document.body.appendChild(toast); // Trigger animation setTimeout(() => { toast.classList.add('visible'); }, 10); // Auto remove after duration setTimeout(() => { toast.classList.remove('visible'); setTimeout(() => { toast.remove(); }, 300); }, duration); } /** * Get toast icon based on type */ function getToastIcon(type) { const icons = { success: '✓', error: '✕', info: 'ℹ', warning: '⚠' }; return icons[type] || icons.info; } /** * Escape HTML to prevent XSS */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Show session context menu for project reassignment */ async function showSessionContextMenu(event, sessionId) { event.preventDefault(); hideSessionContextMenu(); // Create context menu element const menu = document.createElement('div'); menu.id = 'sessionContextMenu'; menu.className = 'session-context-menu'; // Fetch project suggestions let suggestions = []; try { const res = await fetch(`/api/projects/suggestions?sessionId=${sessionId}`); if (res.ok) { const data = await res.json(); suggestions = data.suggestions || []; } } catch (error) { console.error('Error fetching suggestions:', error); } // Build menu HTML let menuHtml = ` `; // Add top 3 suggestions const topSuggestions = suggestions.slice(0, 3); topSuggestions.forEach(suggestion => { const icon = getMatchIcon(suggestion.score); const project = window.projectsMap?.get(suggestion.projectId); if (project) { menuHtml += ` `; } }); // Add "Show All Projects" option menuHtml += ` `; menu.innerHTML = menuHtml; document.body.appendChild(menu); // Position menu at mouse coordinates const x = event.clientX; const y = event.clientY; // Ensure menu doesn't go off screen const menuRect = menu.getBoundingClientRect(); const maxX = window.innerWidth - menuRect.width - 10; const maxY = window.innerHeight - menuRect.height - 10; menu.style.left = `${Math.min(x, maxX)}px`; menu.style.top = `${Math.min(y, maxY)}px`; // Store session ID for event handlers menu.dataset.sessionId = sessionId; // Add event listeners menu.addEventListener('click', handleContextMenuClick); // Close menu when clicking outside setTimeout(() => { document.addEventListener('click', hideSessionContextMenu, { once: true }); }, 10); } /** * Handle context menu item clicks */ async function handleContextMenuClick(event) { const menu = document.getElementById('sessionContextMenu'); if (!menu) return; const target = event.target.closest('.context-menu-item'); if (!target) return; const action = target.dataset.action; const sessionId = menu.dataset.sessionId; hideSessionContextMenu(); switch (action) { case 'open': continueToSession(sessionId); break; case 'move': const projectId = target.dataset.projectId; await moveSessionToProject(sessionId, projectId); break; case 'show-all': showAllProjectsModal(sessionId); break; case 'unassigned': await moveSessionToProject(sessionId, null); break; } } /** * Get match icon based on score */ function getMatchIcon(score) { if (score >= 90) return '🎯'; if (score >= 50) return '📂'; return '💡'; } /** * Move session to project */ async function moveSessionToProject(sessionId, projectId) { try { showLoadingOverlay('Moving session...'); const res = await fetch(`/api/projects/sessions/${sessionId}/move`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectId }) }); if (!res.ok) throw new Error('Request failed'); const data = await res.json(); if (data.success) { hideLoadingOverlay(); showToast('Session moved successfully', 'success'); await loadSessionsAndProjects(); } else { throw new Error(data.error || 'Failed to move session'); } } catch (error) { console.error('Error moving session:', error); hideLoadingOverlay(); showToast(error.message || 'Failed to move session', 'error'); } } /** * Show all projects modal */ function showAllProjectsModal(sessionId) { // Create modal overlay const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.id = 'allProjectsModal'; // Create modal content const modal = document.createElement('div'); modal.className = 'all-projects-modal'; // Build projects list const projects = Array.from(window.projectsMap?.values() || []); let projectsHtml = ''; if (projects.length === 0) { projectsHtml = 'Select a project to move this session to
${projectsHtml}