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>
This commit is contained in:
uroma
2026-01-21 14:40:14 +00:00
Unverified
parent 7ffb8a8492
commit b830e1187e
9 changed files with 3212 additions and 141 deletions

View File

@@ -0,0 +1,443 @@
/**
* 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);
}
}