import { useEffect, useMemo, useRef, useState } from 'react'
import { Plus, Folder, MoreVertical, Trash2, Pencil } from 'lucide-react'
import { useProjects } from '../contexts/ProjectContext'
import { useView } from '../contexts/ViewContext'
import { LtxLogo } from '../components/LtxLogo'
import { Button } from '../components/ui/button'
import { pathToFileUrl } from '../lib/file-url'
import type { Project } from '../types/project-model'
import { useProjectReferencesMigration } from '../hooks/useProjectReferencesMigration'
function formatDate(timestamp: number): string {
const date = new Date(timestamp)
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
}
function ProjectCard({ project, onOpen, onDelete, onRename }: {
project: Project
onOpen: () => void
onDelete: () => void
onRename: () => void
}) {
const [showMenu, setShowMenu] = useState(false)
const [imgError, setImgError] = useState(false)
// Keep existing representative selection logic: prefer first image, else first asset.
const representativeAsset = project.assets.find(a => a.type === 'image') || project.assets[0] || null
const representativeUrl = representativeAsset?.path ? pathToFileUrl(representativeAsset.path) : null
const representativeBigThumbnailUrl = representativeAsset?.bigThumbnailPath
? pathToFileUrl(representativeAsset.bigThumbnailPath)
: null
return (
{/* Thumbnail */}
{representativeAsset && !imgError ? (
representativeAsset.type === 'video' ? (
representativeBigThumbnailUrl ? (

setImgError(true)}
/>
) : representativeUrl ? (
{/* Info */}
{project.name}
{formatDate(project.updatedAt)}
{/* Menu button */}
{/* Dropdown menu */}
{showMenu && (
e.stopPropagation()}
>
)}
)
}
export function Home() {
const { projectIds, getProject, createProject, deleteProject, renameProject } = useProjects()
const { openProject } = useView()
const { migrationStatus, migrateProjects } = useProjectReferencesMigration()
const [isCreating, setIsCreating] = useState(false)
const [newProjectName, setNewProjectName] = useState('')
const [renamingId, setRenamingId] = useState(null)
const [renameValue, setRenameValue] = useState('')
const migrationStartedRef = useRef(false)
useEffect(() => {
if (migrationStatus.status !== 'needed' || migrationStartedRef.current) return
migrationStartedRef.current = true
void migrateProjects()
}, [migrateProjects, migrationStatus.status])
const projects = useMemo(() => (
projectIds
.map(projectId => getProject(projectId))
.filter((project): project is Project => project !== null)
), [getProject, projectIds])
const handleCreateProject = () => {
if (newProjectName.trim()) {
const project = createProject(newProjectName.trim())
setNewProjectName('')
setIsCreating(false)
openProject(project.id)
}
}
const handleRenameProject = (id: string, currentName: string) => {
setRenamingId(id)
setRenameValue(currentName)
}
const submitRename = () => {
if (renamingId && renameValue.trim()) {
renameProject(renamingId, renameValue.trim())
}
setRenamingId(null)
setRenameValue('')
}
if (migrationStatus.status === 'needed' || migrationStatus.status === 'inProgress') {
const progressPct = migrationStatus.status === 'inProgress'
? migrationStatus.ratio * 100
: 0
return (
Migrating project references...
)
}
return (
{/* Sidebar */}
{/* Main Content */}
{/* Header Banner with video background */}
{/* Dark overlay for text readability */}
LTX DeskWEB
Create and manage your video projects
{/* Projects Grid */}
Projects
{projects.length === 0 ? (
No projects yet
Create your first project to get started
) : (
{projects.map(project => (
openProject(project.id)}
onDelete={() => {
if (confirm(`Delete "${project.name}"?`)) {
deleteProject(project.id)
}
}}
onRename={() => handleRenameProject(project.id, project.name)}
/>
))}
)}
{/* Create Project Modal */}
{isCreating && (
)}
{/* Rename Modal */}
{renamingId && (
)}
)
}