Files
SuperCharged-Claude-Code-Up…/public/claude-ide/projects-landing.js
uroma b830e1187e Fix folder explorer error reporting and add logging
- Show actual server error message when project creation fails
- Add console logging to debug project creation

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-21 14:40:14 +00:00

444 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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) {
window.location.href = '/claude/login.html';
}
} catch (error) {
console.error('Auth check failed:', error);
}
}
/**
* 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 = `<span class="source-badge both">CLI + Web</span>`;
} else if (hasCli) {
sourcesHtml = `<span class="source-badge cli">CLI</span>`;
} else if (hasWeb) {
sourcesHtml = `<span class="source-badge web">Web</span>`;
}
return `
<div class="project-card" data-project-id="${project.id}">
<div class="project-card-header">
<div class="project-icon">${icon}</div>
<div class="project-info">
<h3 class="project-name">${name}</h3>
<div class="project-path">${path}</div>
</div>
</div>
<div class="project-meta">
<div class="meta-item session-count">
<span class="icon">💬</span>
<span>${sessionCount} session${sessionCount !== 1 ? 's' : ''}</span>
</div>
<div class="meta-item last-activity">
<span class="icon">🕐</span>
<span>${relativeTime}</span>
</div>
<div class="project-sources">
${sourcesHtml}
</div>
</div>
</div>
`;
}
/**
* Open project - show session picker modal
*/
async function openProject(project) {
try {
// Load the session picker modal if not already loaded
if (!window.SessionPicker) {
await loadScript('/claude/claude-ide/components/session-picker-modal.js');
}
// Show session picker for this project
if (window.SessionPicker) {
window.SessionPicker.show(project);
}
} catch (error) {
console.error('Error opening project:', error);
showToast('Failed to open project', 'error');
}
}
/**
* Show create project modal
*/
function showCreateProjectModal() {
// For now, use a simple prompt
// TODO: Replace with proper modal dialog
const name = prompt('Enter project name:');
if (!name) return;
const path = prompt('Enter folder path (e.g., ~/projects/my-app):');
if (!path) return;
createProject(name, path);
}
/**
* Create a new project
*/
async function createProject(name, path) {
try {
showLoadingOverlay('Creating project...');
const res = await fetch('/api/projects', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, path })
});
if (!res.ok) {
throw new Error('Failed to create project');
}
const data = await res.json();
if (data.success) {
hideLoadingOverlay();
showToast('Project created successfully', 'success');
await loadProjects(); // Reload projects list
} else {
throw new Error(data.error || 'Failed to create project');
}
} catch (error) {
console.error('Error creating project:', error);
hideLoadingOverlay();
showToast(error.message || 'Failed to create project', 'error');
}
}
/**
* Initialize keyboard shortcuts
*/
function initializeKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Cmd/Ctrl + N - New project / Select folder
if ((e.metaKey || e.ctrlKey) && e.key === 'n') {
e.preventDefault();
showFolderExplorer();
}
// Cmd/Ctrl + R - Refresh projects
if ((e.metaKey || e.ctrlKey) && e.key === 'r') {
e.preventDefault();
loadProjects();
}
});
}
/**
* Get relative time string
*/
function getRelativeTime(timestamp) {
if (!timestamp) return 'Never';
const date = new Date(timestamp);
const now = new Date();
const diffMins = Math.floor((now - date) / 60000);
const diffHours = Math.floor((now - date) / 3600000);
const diffDays = Math.floor((now - date) / 86400000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays < 7) return `${diffDays}d ago`;
return date.toLocaleDateString();
}
/**
* Shorten file path for display
*/
function shortenPath(fullPath) {
if (!fullPath) return '';
// Show last 3 parts of path
const parts = fullPath.split('/');
if (parts.length > 3) {
return '...' + fullPath.slice(fullPath.indexOf('/', fullPath.length - 40));
}
return fullPath;
}
/**
* Load a script dynamically
*/
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
/**
* Show toast notification
*/
function showToast(message, type = 'info', duration = 3000) {
const existingToasts = document.querySelectorAll('.toast-notification');
existingToasts.forEach(toast => toast.remove());
const toast = document.createElement('div');
toast.className = `toast-notification toast-${type}`;
toast.innerHTML = `
<span class="toast-icon">${getToastIcon(type)}</span>
<span class="toast-message">${escapeHtml(message)}</span>
`;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.add('visible');
}, 10);
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;
}
/**
* Show loading overlay
*/
function showLoadingOverlay(message = 'Loading...') {
let overlay = document.getElementById('loading-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'loading-overlay';
overlay.className = 'loading-overlay';
overlay.innerHTML = `
<div class="loading-spinner"></div>
<p class="loading-text">${escapeHtml(message)}</p>
`;
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);
}
}