2026-01-07 09:31:02 +00:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { Card } from '../ui/card';
|
|
|
|
|
import { Button } from '../ui/button';
|
|
|
|
|
import { Badge } from '../ui/badge';
|
|
|
|
|
import { SearchInput } from '../ui/input';
|
2026-01-13 18:47:57 +00:00
|
|
|
import {
|
|
|
|
|
MoreVertical,
|
|
|
|
|
Plus,
|
|
|
|
|
LayoutGrid,
|
|
|
|
|
List,
|
|
|
|
|
Loader2,
|
|
|
|
|
AlertCircle,
|
|
|
|
|
} from 'lucide-react';
|
2026-01-07 09:31:02 +00:00
|
|
|
import { useToast } from '../../context/ToastContext';
|
|
|
|
|
import { CreateProjectModal } from './projects/CreateProjectModal';
|
|
|
|
|
import { ProjectDetailView } from './projects/ProjectDetailView';
|
|
|
|
|
import { projectService, Project } from '../../services/projectService';
|
|
|
|
|
import { logger } from '@/utils/logger';
|
|
|
|
|
|
|
|
|
|
export const ProjectsManager: React.FC = () => {
|
|
|
|
|
const { addToast } = useToast();
|
|
|
|
|
const [viewState, setViewState] = useState<'list' | 'detail'>('list');
|
2026-01-13 18:47:57 +00:00
|
|
|
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(
|
|
|
|
|
null,
|
|
|
|
|
);
|
2026-01-07 09:31:02 +00:00
|
|
|
const [projects, setProjects] = useState<Project[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [filter, setFilter] = useState('All');
|
|
|
|
|
const [search, setSearch] = useState('');
|
|
|
|
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-01-13 18:47:57 +00:00
|
|
|
loadProjects();
|
2026-01-07 09:31:02 +00:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const loadProjects = async () => {
|
2026-01-13 18:47:57 +00:00
|
|
|
try {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
const response = await projectService.list();
|
|
|
|
|
setProjects(response.projects || []);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('Failed to load projects', {
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
|
|
|
});
|
|
|
|
|
// Fallback for demo if API fails
|
|
|
|
|
setProjects([
|
|
|
|
|
{
|
|
|
|
|
id: 'p1',
|
|
|
|
|
name: 'Neon Genesis',
|
|
|
|
|
daw: 'Ableton',
|
|
|
|
|
bpm: 128,
|
|
|
|
|
key: 'C Min',
|
|
|
|
|
status: 'In Progress',
|
|
|
|
|
collaborators: 2,
|
|
|
|
|
modified: '2h ago',
|
|
|
|
|
progress: 65,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'p2',
|
|
|
|
|
name: 'Night City Drift',
|
|
|
|
|
daw: 'FL Studio',
|
|
|
|
|
bpm: 140,
|
|
|
|
|
key: 'F# Min',
|
|
|
|
|
status: 'Mixing',
|
|
|
|
|
collaborators: 0,
|
|
|
|
|
modified: '1d ago',
|
|
|
|
|
progress: 80,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'p3',
|
|
|
|
|
name: 'Mainframe Breach',
|
|
|
|
|
daw: 'Logic Pro',
|
|
|
|
|
bpm: 174,
|
|
|
|
|
key: 'D Maj',
|
|
|
|
|
status: 'Mastering',
|
|
|
|
|
collaborators: 1,
|
|
|
|
|
modified: '3d ago',
|
|
|
|
|
progress: 95,
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --- Actions ---
|
|
|
|
|
|
|
|
|
|
const handleCreate = async (newProjectData: any) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
try {
|
|
|
|
|
const newProject = await projectService.create(newProjectData);
|
|
|
|
|
setProjects([newProject, ...projects]);
|
|
|
|
|
addToast(`Project "${newProject.name}" created`, 'success');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// Fallback
|
|
|
|
|
const mockProject = { id: `p-${Date.now()}`, ...newProjectData };
|
|
|
|
|
setProjects([mockProject, ...projects]);
|
|
|
|
|
addToast('Project created (Offline Mode)', 'success');
|
|
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleUpdate = async (updatedProject: any) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
try {
|
|
|
|
|
await projectService.update(updatedProject.id, updatedProject);
|
|
|
|
|
setProjects((prev) =>
|
|
|
|
|
prev.map((p) => (p.id === updatedProject.id ? updatedProject : p)),
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
setProjects((prev) =>
|
|
|
|
|
prev.map((p) => (p.id === updatedProject.id ? updatedProject : p)),
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDelete = async (id: string) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
try {
|
|
|
|
|
await projectService.delete(id);
|
|
|
|
|
setProjects((prev) => prev.filter((p) => p.id !== id));
|
|
|
|
|
setViewState('list');
|
|
|
|
|
setSelectedProjectId(null);
|
|
|
|
|
addToast('Project deleted', 'info');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
addToast('Failed to delete project', 'error');
|
|
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const openProject = (id: string) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
setSelectedProjectId(id);
|
|
|
|
|
setViewState('detail');
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --- Filtering ---
|
2026-01-13 18:47:57 +00:00
|
|
|
const filteredProjects = projects.filter((p) => {
|
|
|
|
|
const matchesSearch = p.name.toLowerCase().includes(search.toLowerCase());
|
|
|
|
|
const matchesFilter = filter === 'All' || p.daw === filter;
|
|
|
|
|
return matchesSearch && matchesFilter;
|
2026-01-07 09:31:02 +00:00
|
|
|
});
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
const selectedProject = projects.find((p) => p.id === selectedProjectId);
|
2026-01-07 09:31:02 +00:00
|
|
|
|
|
|
|
|
// --- Render Detail View ---
|
|
|
|
|
if (viewState === 'detail' && selectedProject) {
|
2026-01-13 18:47:57 +00:00
|
|
|
return (
|
|
|
|
|
<ProjectDetailView
|
|
|
|
|
project={selectedProject}
|
|
|
|
|
onBack={() => setViewState('list')}
|
|
|
|
|
onUpdate={handleUpdate}
|
|
|
|
|
onDelete={handleDelete}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2026-01-07 09:31:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Render List View ---
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6 animate-fadeIn pb-20">
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="flex flex-col md:flex-row justify-between items-end gap-4">
|
|
|
|
|
<div>
|
2026-01-15 22:54:05 +00:00
|
|
|
<h2 className="text-2xl font-display font-bold text-white mb-2">
|
2026-01-13 18:47:57 +00:00
|
|
|
ACTIVE PROJECTS
|
|
|
|
|
</h2>
|
2026-01-16 00:56:34 +00:00
|
|
|
<p className="text-kodo-content-dim font-mono text-sm">
|
2026-01-13 18:47:57 +00:00
|
|
|
Track progress across all your workstations.
|
|
|
|
|
</p>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
icon={<Plus className="w-4 h-4" />}
|
|
|
|
|
onClick={() => setShowCreateModal(true)}
|
|
|
|
|
>
|
|
|
|
|
NEW PROJECT
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Filter Bar */}
|
|
|
|
|
<div className="flex flex-col md:flex-row gap-4 items-center bg-kodo-ink/50 p-4 rounded-xl border border-kodo-steel/50">
|
|
|
|
|
<div className="w-full md:w-72">
|
|
|
|
|
<SearchInput
|
|
|
|
|
placeholder="Search projects..."
|
|
|
|
|
value={search}
|
|
|
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
|
|
|
/>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="flex gap-2 overflow-x-auto w-full md:w-auto pb-2 md:pb-0">
|
|
|
|
|
{['All', 'Ableton', 'FL Studio', 'Logic Pro'].map((daw) => (
|
|
|
|
|
<button
|
|
|
|
|
key={daw}
|
2026-01-16 00:56:34 +00:00
|
|
|
className={`px-3 py-1.5 rounded-lg text-xs font-bold uppercase tracking-wider transition-colors border ${filter === daw ? 'bg-kodo-cyan text-black border-kodo-cyan' : 'bg-kodo-slate text-kodo-content-dim border-transparent hover:border-kodo-steel'}`}
|
2026-01-13 18:47:57 +00:00
|
|
|
onClick={() => setFilter(daw)}
|
|
|
|
|
>
|
|
|
|
|
{daw}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="ml-auto flex gap-2">
|
|
|
|
|
<div className="bg-kodo-void p-1 rounded-lg border border-kodo-steel flex">
|
consistency: replace custom buttons with Button component (partial)
- Replaced custom button implementations with Button component in 14 files
- Files updated: LiveStreamDetailView, DashboardPage, CommentItem, PostCard, SocialPage, SocialView, AdminUsersView, UserTableRow, ProjectsManager, CloudFileBrowser, FileManagerView, CreatorModal, ImageCropper, BulkUploadModal
- ~31 buttons replaced across high-priority files
- Used appropriate Button variants: ghost, outline, default, secondary, link
- Preserved visual appearance with className overrides where needed
- Action 9.2.1.2 in progress (partial completion)
2026-01-16 01:06:14 +00:00
|
|
|
<Button variant="secondary" size="icon" className="p-1.5">
|
2026-01-13 18:47:57 +00:00
|
|
|
<LayoutGrid className="w-4 h-4" />
|
consistency: replace custom buttons with Button component (partial)
- Replaced custom button implementations with Button component in 14 files
- Files updated: LiveStreamDetailView, DashboardPage, CommentItem, PostCard, SocialPage, SocialView, AdminUsersView, UserTableRow, ProjectsManager, CloudFileBrowser, FileManagerView, CreatorModal, ImageCropper, BulkUploadModal
- ~31 buttons replaced across high-priority files
- Used appropriate Button variants: ghost, outline, default, secondary, link
- Preserved visual appearance with className overrides where needed
- Action 9.2.1.2 in progress (partial completion)
2026-01-16 01:06:14 +00:00
|
|
|
</Button>
|
|
|
|
|
<Button variant="ghost" size="icon" className="p-1.5">
|
2026-01-13 18:47:57 +00:00
|
|
|
<List className="w-4 h-4" />
|
consistency: replace custom buttons with Button component (partial)
- Replaced custom button implementations with Button component in 14 files
- Files updated: LiveStreamDetailView, DashboardPage, CommentItem, PostCard, SocialPage, SocialView, AdminUsersView, UserTableRow, ProjectsManager, CloudFileBrowser, FileManagerView, CreatorModal, ImageCropper, BulkUploadModal
- ~31 buttons replaced across high-priority files
- Used appropriate Button variants: ghost, outline, default, secondary, link
- Preserved visual appearance with className overrides where needed
- Action 9.2.1.2 in progress (partial completion)
2026-01-16 01:06:14 +00:00
|
|
|
</Button>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Projects Grid */}
|
|
|
|
|
{loading ? (
|
|
|
|
|
<div className="flex justify-center py-20">
|
aesthetic-improvements: reduce decorative cyan across multiple component categories (80/20 rule, batch 11)
- Social: FeedView, ConnectionsView, GroupsView, ExploreView, GroupDetailView loading spinners and decorative text, CreatePostModal decorative select text and hashtag links, PostCard decorative tag links and waveform bars and view comments link, CreateGroupModal decorative icon (9 instances)
- Settings: DataExportModal decorative icon, LoginHistory decorative IP text, AppearanceSettingsView decorative icon and selected theme checkmark, BackupsView decorative icon, CloudIntegrationView decorative icon, AccessibilitySettingsView decorative icon, SecuritySettings decorative icon, PasskeyModal decorative icon and loading spinner (8 instances)
- Studio: ProjectsManager loading spinner and progress percentage text, CloudFileBrowser decorative music icons, AIToolsView decorative music icon, ConnectivityView decorative icon, CreateProjectModal decorative icon, CloudSettingsView decorative icon (6 instances)
- Admin: AdminDashboardView loading spinner and decorative chart bars and icon, AdminSettingsView decorative icon, AdminModerationView loading spinner, AdminUsersView loading spinner (5 instances)
- Inventory: InventoryView loading spinner, EquipmentCard decorative price icon, EquipmentDetailView loading spinner and decorative icons and price text, AddEquipmentView decorative icon (5 instances)
- Seller: CreateProductView decorative icon, SellerDashboardView loading spinner and decorative icon and sales text (3 instances)
- Live: LiveStreamDetailView decorative streamer name text (1 instance)
- Developer: DeveloperDashboardView loading spinner, WebhooksView decorative icon (2 instances)
- Upload: BulkUploadModal decorative icon, FilePreviewCard decorative audio file icon, MetadataForm decorative button text, CoverArtUploadModal decorative icon, LyricsEditorModal decorative icon (5 instances)
- Notifications: NotificationItem decorative follow icon and mark as read button, NotificationBell decorative mark all read link (3 instances)
- Total: ~46 files, ~46 instances replaced
- Preserved: Active/selected states (CloudFileBrowser selected files checkmarks, CreatePostModal post type active state, GroupCard/GroupDetailView public/private badges - semantic indicators, DataExportModal checkbox accents - focus/interaction, AppearanceSettingsView selected theme - active state, PasskeyModal checkbox accent - focus/interaction, LyricsEditorModal checkbox accent - focus/interaction, FileUploadZone drag active state - active state, EquipmentDetailView support link - functional link, FlashSaleModal link - functional link, EquipmentDetailView image indicator dots - active state), primary actions, design system variants
- Action 11.3.1.3 in progress (eleventh batch: social, settings, studio, admin, inventory, seller, live, developer, upload, notifications components)
2026-01-16 10:26:33 +00:00
|
|
|
<Loader2 className="w-8 h-8 text-kodo-steel animate-spin" />
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
|
|
|
{filteredProjects.map((project) => (
|
|
|
|
|
<Card
|
|
|
|
|
key={project.id}
|
|
|
|
|
variant="gaming"
|
2026-01-16 09:51:30 +00:00
|
|
|
className="group cursor-pointer hover:border-kodo-steel/50 transition-all hover:-translate-y-1"
|
2026-01-13 18:47:57 +00:00
|
|
|
onClick={() => openProject(project.id)}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex justify-between items-start mb-4">
|
|
|
|
|
<Badge
|
|
|
|
|
label={project.daw}
|
|
|
|
|
variant={
|
|
|
|
|
project.daw === 'Ableton'
|
|
|
|
|
? 'cyan'
|
|
|
|
|
: project.daw === 'FL Studio'
|
|
|
|
|
? 'gold'
|
|
|
|
|
: 'magenta'
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<div
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
addToast('Options menu');
|
|
|
|
|
}}
|
2026-01-07 09:31:02 +00:00
|
|
|
>
|
2026-01-16 00:56:34 +00:00
|
|
|
<MoreVertical className="w-4 h-4 text-kodo-content-dim hover:text-white" />
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-16 09:59:09 +00:00
|
|
|
<h3 className="text-xl font-bold text-white mb-1 group-hover:text-white transition-colors truncate">
|
2026-01-13 18:47:57 +00:00
|
|
|
{project.name}
|
|
|
|
|
</h3>
|
2026-01-16 00:56:34 +00:00
|
|
|
<p className="text-xs text-kodo-content-dim mb-4 font-mono">
|
2026-01-13 18:47:57 +00:00
|
|
|
Last edited {project.modified}
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-2 mb-4">
|
|
|
|
|
<div className="bg-white/5 p-2 rounded text-center border border-white/5">
|
2026-01-16 00:56:34 +00:00
|
|
|
<div className="text-[10px] text-kodo-content-dim uppercase font-bold">
|
2026-01-13 18:47:57 +00:00
|
|
|
BPM
|
|
|
|
|
</div>
|
|
|
|
|
<div className="font-bold text-white">{project.bpm}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="bg-white/5 p-2 rounded text-center border border-white/5">
|
2026-01-16 00:56:34 +00:00
|
|
|
<div className="text-[10px] text-kodo-content-dim uppercase font-bold">
|
2026-01-13 18:47:57 +00:00
|
|
|
Key
|
|
|
|
|
</div>
|
|
|
|
|
<div className="font-bold text-white">{project.key}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mb-4">
|
|
|
|
|
<div className="flex justify-between text-xs mb-1">
|
2026-01-16 00:56:34 +00:00
|
|
|
<span className="text-kodo-content-dim font-bold">
|
2026-01-13 18:47:57 +00:00
|
|
|
{project.status}
|
|
|
|
|
</span>
|
aesthetic-improvements: reduce decorative cyan across multiple component categories (80/20 rule, batch 11)
- Social: FeedView, ConnectionsView, GroupsView, ExploreView, GroupDetailView loading spinners and decorative text, CreatePostModal decorative select text and hashtag links, PostCard decorative tag links and waveform bars and view comments link, CreateGroupModal decorative icon (9 instances)
- Settings: DataExportModal decorative icon, LoginHistory decorative IP text, AppearanceSettingsView decorative icon and selected theme checkmark, BackupsView decorative icon, CloudIntegrationView decorative icon, AccessibilitySettingsView decorative icon, SecuritySettings decorative icon, PasskeyModal decorative icon and loading spinner (8 instances)
- Studio: ProjectsManager loading spinner and progress percentage text, CloudFileBrowser decorative music icons, AIToolsView decorative music icon, ConnectivityView decorative icon, CreateProjectModal decorative icon, CloudSettingsView decorative icon (6 instances)
- Admin: AdminDashboardView loading spinner and decorative chart bars and icon, AdminSettingsView decorative icon, AdminModerationView loading spinner, AdminUsersView loading spinner (5 instances)
- Inventory: InventoryView loading spinner, EquipmentCard decorative price icon, EquipmentDetailView loading spinner and decorative icons and price text, AddEquipmentView decorative icon (5 instances)
- Seller: CreateProductView decorative icon, SellerDashboardView loading spinner and decorative icon and sales text (3 instances)
- Live: LiveStreamDetailView decorative streamer name text (1 instance)
- Developer: DeveloperDashboardView loading spinner, WebhooksView decorative icon (2 instances)
- Upload: BulkUploadModal decorative icon, FilePreviewCard decorative audio file icon, MetadataForm decorative button text, CoverArtUploadModal decorative icon, LyricsEditorModal decorative icon (5 instances)
- Notifications: NotificationItem decorative follow icon and mark as read button, NotificationBell decorative mark all read link (3 instances)
- Total: ~46 files, ~46 instances replaced
- Preserved: Active/selected states (CloudFileBrowser selected files checkmarks, CreatePostModal post type active state, GroupCard/GroupDetailView public/private badges - semantic indicators, DataExportModal checkbox accents - focus/interaction, AppearanceSettingsView selected theme - active state, PasskeyModal checkbox accent - focus/interaction, LyricsEditorModal checkbox accent - focus/interaction, FileUploadZone drag active state - active state, EquipmentDetailView support link - functional link, FlashSaleModal link - functional link, EquipmentDetailView image indicator dots - active state), primary actions, design system variants
- Action 11.3.1.3 in progress (eleventh batch: social, settings, studio, admin, inventory, seller, live, developer, upload, notifications components)
2026-01-16 10:26:33 +00:00
|
|
|
<span className="text-kodo-steel">{project.progress}%</span>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
<div className="h-1.5 bg-kodo-steel rounded-full overflow-hidden">
|
|
|
|
|
<div
|
|
|
|
|
className="h-full bg-kodo-cyan"
|
|
|
|
|
style={{ width: `${project.progress}%` }}
|
|
|
|
|
></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-16 00:56:34 +00:00
|
|
|
<div className="flex justify-between items-center pt-4 border-t border-kodo-steel">
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="flex -space-x-2">
|
2026-01-16 00:56:34 +00:00
|
|
|
<div className="w-6 h-6 rounded-full bg-kodo-steel border border-black"></div>
|
2026-01-13 18:47:57 +00:00
|
|
|
{project.collaborators > 0 && (
|
|
|
|
|
<div className="w-6 h-6 rounded-full bg-kodo-ink border border-black flex items-center justify-center text-[10px] text-white">
|
|
|
|
|
+{project.collaborators}
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<Button variant="ghost" size="sm" className="text-xs h-8">
|
|
|
|
|
OPEN
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
))}
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Add New Placeholder */}
|
|
|
|
|
<div
|
2026-01-16 09:51:30 +00:00
|
|
|
className="border-2 border-dashed border-kodo-steel rounded-xl flex flex-col items-center justify-center p-8 hover:bg-kodo-slate/20 transition-colors cursor-pointer text-kodo-content-dim hover:text-white hover:border-kodo-steel/50 min-h-[250px]"
|
2026-01-13 18:47:57 +00:00
|
|
|
onClick={() => setShowCreateModal(true)}
|
|
|
|
|
>
|
|
|
|
|
<div className="w-16 h-16 bg-kodo-ink rounded-full flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
|
|
|
|
|
<Plus className="w-8 h-8 opacity-50" />
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<span className="font-mono font-bold">START NEW PROJECT</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{filteredProjects.length === 0 && !loading && (
|
2026-01-16 00:56:34 +00:00
|
|
|
<div className="text-center py-20 text-kodo-content-dim">
|
2026-01-13 18:47:57 +00:00
|
|
|
<AlertCircle className="w-12 h-12 mx-auto mb-2 opacity-30" />
|
|
|
|
|
<p>No projects found matching your filters.</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{showCreateModal && (
|
|
|
|
|
<CreateProjectModal
|
|
|
|
|
onClose={() => setShowCreateModal(false)}
|
|
|
|
|
onCreate={handleCreate}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|