import { useState, useRef, useEffect } from 'react'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Palette, Upload, X, ImageIcon } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useAppStore } from '@/store/app-store'; import { IconPicker } from '@/components/layout/project-switcher/components/icon-picker'; import { getAuthenticatedImageUrl } from '@/lib/api-fetch'; import { getHttpApiClient } from '@/lib/http-api-client'; import { toast } from 'sonner'; import type { Project } from '@/lib/electron'; interface ProjectIdentitySectionProps { project: Project; } export function ProjectIdentitySection({ project }: ProjectIdentitySectionProps) { const { setProjectIcon, setProjectName, setProjectCustomIcon } = useAppStore(); const [projectName, setProjectNameLocal] = useState(project.name || ''); const [projectIcon, setProjectIconLocal] = useState(project.icon || null); const [customIconPath, setCustomIconPathLocal] = useState( project.customIconPath || null ); const [isUploadingIcon, setIsUploadingIcon] = useState(false); const fileInputRef = useRef(null); // Sync local state when project changes useEffect(() => { setProjectNameLocal(project.name || ''); setProjectIconLocal(project.icon || null); setCustomIconPathLocal(project.customIconPath || null); }, [project]); // Auto-save when values change const handleNameChange = (name: string) => { setProjectNameLocal(name); if (name.trim() && name.trim() !== project.name) { setProjectName(project.id, name.trim()); } }; const handleIconChange = (icon: string | null) => { setProjectIconLocal(icon); setProjectIcon(project.id, icon); }; const handleCustomIconChange = (path: string | null) => { setCustomIconPathLocal(path); setProjectCustomIcon(project.id, path); // Clear Lucide icon when custom icon is set if (path) { setProjectIconLocal(null); setProjectIcon(project.id, null); } }; const handleCustomIconUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // Validate file type const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; if (!validTypes.includes(file.type)) { toast.error('Invalid file type', { description: 'Please upload a PNG, JPG, GIF, or WebP image.', }); return; } // Validate file size (max 5MB for icons - allows animated GIFs) if (file.size > 5 * 1024 * 1024) { toast.error('File too large', { description: 'Please upload an image smaller than 5MB.', }); return; } setIsUploadingIcon(true); try { // Convert to base64 const reader = new FileReader(); reader.onload = async () => { try { const base64Data = reader.result as string; const result = await getHttpApiClient().saveImageToTemp( base64Data, `project-icon-${file.name}`, file.type, project.path ); if (result.success && result.path) { handleCustomIconChange(result.path); toast.success('Icon uploaded successfully'); } else { toast.error('Failed to upload icon', { description: result.error || 'Please try again.', }); } } catch { toast.error('Failed to upload icon', { description: 'Network error. Please try again.', }); } finally { setIsUploadingIcon(false); } }; reader.onerror = () => { toast.error('Failed to read file', { description: 'Please try again with a different file.', }); setIsUploadingIcon(false); }; reader.readAsDataURL(file); } catch { toast.error('Failed to upload icon'); setIsUploadingIcon(false); } }; const handleRemoveCustomIcon = () => { handleCustomIconChange(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; return (

Project Identity

Customize how your project appears in the sidebar and project switcher.

{/* Project Name */}
handleNameChange(e.target.value)} placeholder="Enter project name" />
{/* Project Icon */}

Choose a preset icon or upload a custom image

{/* Custom Icon Upload */}
{customIconPath ? (
Custom project icon
) : (
)}

PNG, JPG, GIF or WebP. Max 5MB.

{/* Preset Icon Picker - only show if no custom icon */} {!customIconPath && ( )}
); }