/** * Projects Landing Page JavaScript * CodeNomad-style: Shows projects, clicking opens session picker */ // State let projects = []; let isLoading = false; // Load on page load document.addEventListener('DOMContentLoaded', () => { checkAuth(); initializeHero(); loadProjects(); initializeKeyboardShortcuts(); }); // 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) { // Show login modal instead of redirecting showLoginModal(); return; } // Update nav with username if (data.username) { document.querySelector('.nav-logo').textContent = `Claude Code (${data.username})`; } } catch (error) { console.error('[checkAuth] Error:', error); showLoginModal(); } } // Show login modal function showLoginModal() { const modal = document.getElementById('login-modal'); if (modal) { modal.style.display = 'flex'; } } // Close login modal function closeLoginModal() { const modal = document.getElementById('login-modal'); if (modal) { modal.style.display = 'none'; } } // Handle login form submission async function handleLogin(event) { event.preventDefault(); const username = document.getElementById('login-username').value; const password = document.getElementById('login-password').value; const errorDiv = document.getElementById('login-error'); try { const res = await fetch('/claude/api/login', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await res.json(); if (data.success) { // Login successful closeLoginModal(); loadProjects(); // Update nav with username if (data.username) { document.querySelector('.nav-logo').textContent = `Claude Code (${data.username})`; } } else { // Show error errorDiv.textContent = data.error || 'Login failed'; errorDiv.style.display = 'block'; } } catch (error) { console.error('[handleLogin] Error:', error); errorDiv.textContent = 'Login failed. Please try again.'; errorDiv.style.display = 'block'; } } /** * Initialize hero section */ function initializeHero() { const selectFolderBtn = document.getElementById('select-folder-btn'); if (selectFolderBtn) { selectFolderBtn.addEventListener('click', async () => { await showFolderExplorer(); }); } } /** * Show folder explorer modal */ async function showFolderExplorer() { try { // Load the folder explorer modal if not already loaded if (!window.FolderExplorer) { await loadScript('/claude/claude-ide/components/folder-explorer-modal.js'); } // Show folder explorer if (window.FolderExplorer) { window.FolderExplorer.show(); } } catch (error) { console.error('Error showing folder explorer:', error); showToast('Failed to open folder explorer', 'error'); } } /** * Load all projects from server */ async function loadProjects() { const grid = document.getElementById('projects-grid'); const empty = document.getElementById('projects-empty'); const loading = document.getElementById('projects-loading'); const error = document.getElementById('projects-error'); if (grid) grid.style.display = 'none'; if (empty) empty.style.display = 'none'; if (error) error.style.display = 'none'; if (loading) loading.style.display = 'block'; try { console.log('[Projects] Starting to load projects...'); const res = await fetch('/api/projects', { credentials: 'same-origin' }); console.log('[Projects] Response status:', res.status, res.statusText); if (!res.ok) { throw new Error(`Failed to load projects (HTTP ${res.status})`); } const data = await res.json(); console.log('[Projects] Response data:', data); projects = data.projects || []; if (loading) loading.style.display = 'none'; if (projects.length === 0) { if (empty) empty.style.display = 'block'; } else { if (grid) { grid.style.display = 'grid'; renderProjectsGrid(projects); } } } catch (err) { console.error('[Projects] Error loading projects:', err); console.error('[Projects] Error stack:', err.stack); console.error('[Projects] Error details:', { message: err.message, name: err.name, toString: err.toString() }); // Report to error monitoring if (typeof reportError === 'function') { reportError({ type: 'console', url: window.location.href, message: 'Error loading projects: ' + err.message, stack: err.stack }); } if (loading) loading.style.display = 'none'; if (error) error.style.display = 'block'; } } /** * Render projects grid */ function renderProjectsGrid(projects) { const grid = document.getElementById('projects-grid'); if (!grid) return; grid.innerHTML = projects.map(project => createProjectCard(project)).join(''); // Add click handlers grid.querySelectorAll('.project-card').forEach(card => { const projectId = card.dataset.projectId; const project = projects.find(p => p.id == projectId); if (project) { card.addEventListener('click', () => openProject(project)); } }); } /** * Create a project card HTML */ function createProjectCard(project) { const name = escapeHtml(project.name); const path = escapeHtml(shortenPath(project.path || '')); const sessionCount = project.sessionCount || 0; const relativeTime = getRelativeTime(project.lastActivity); const icon = project.icon || '📁'; // Determine which sources have been used const sources = project.sources || []; const hasCli = sources.includes('cli'); const hasWeb = sources.includes('web'); let sourcesHtml = ''; if (hasCli && hasWeb) { sourcesHtml = `CLI + Web`; } else if (hasCli) { sourcesHtml = `CLI`; } else if (hasWeb) { sourcesHtml = `Web`; } return `
${escapeHtml(message)}
`; document.body.appendChild(overlay); } else { 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); } } /** * Escape HTML to prevent XSS */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Refresh projects function (called from refresh button) function refreshProjects() { loadProjects(); } // Logout function async function logout() { try { await fetch('/claude/api/logout', { method: 'POST' }); window.location.href = '/claude/'; } catch (error) { console.error('Logout failed:', error); } }