diff --git a/screenshot/image.png b/screenshot/image.png new file mode 100644 index 0000000..a61f8c2 Binary files /dev/null and b/screenshot/image.png differ diff --git a/src/app/folder-viewer/page.tsx b/src/app/folder-viewer/page.tsx index 78df133..0187bad 100644 --- a/src/app/folder-viewer/page.tsx +++ b/src/app/folder-viewer/page.tsx @@ -20,6 +20,7 @@ const FolderViewerPage = () => { const searchParams = useSearchParams(); const path = searchParams.get("path"); const [items, setItems] = useState([]); + const [loading, setLoading] = useState(false); useEffect(() => { if (path) { @@ -28,9 +29,16 @@ const FolderViewerPage = () => { }, [path]); const fetchItems = async (currentPath: string) => { - const res = await fetch(`/api/files?path=${currentPath}`); - const data = await res.json(); - setItems(data); + setLoading(true); + try { + const res = await fetch(`/api/files?path=${currentPath}`); + const data = await res.json(); + setItems(data); + } catch (error) { + console.error('Error fetching items:', error); + } finally { + setLoading(false); + } }; const formatFileSize = (bytes: number) => { @@ -64,57 +72,113 @@ const FolderViewerPage = () => { } return ( -
-

{path}

-
- {items.map((item) => ( - - - {item.isDirectory ? ( - -
- -
- - ) : isMediaFile(item) ? ( -
- {item.name} { - (e.target as HTMLImageElement).src = '/placeholder.svg'; - }} - /> -
- {item.type === 'video' ? ( - - ) : ( - - )} -
-
- ) : ( -
- {getFileIcon(item)} -
- )} -
- - {item.name} - - Size: {formatFileSize(item.size)} - {!item.isDirectory && Type: {item.type || 'File'}} - - -
- ))} -
- {items.length === 0 && ( -
-

No items found in this directory.

+
+ {loading && ( +
+
+
+
+ Loading directory... +
+
)} + +
+ {!path ? ( +
+
+
+ +
+

Select a Library

+

+ Choose a media library from the sidebar to browse your files +

+
+
+ ) : ( + <> +
+ +

+ {path.split('/').pop()} +

+
+ +
+ {items.map((item) => ( +
+ +
+ {item.isDirectory ? ( +
+
+ +
+
+ ) : isMediaFile(item) ? ( +
+ {item.name} +
+
+ {item.type === 'video' ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+ ) : ( +
+
+ {getFileIcon(item)} +
+
+ )} +
+
+

{item.name}

+

{formatFileSize(item.size)}

+
+ +
+ ))} +
+ + {items.length === 0 && !loading && ( +
+
+
+ +
+

Empty Directory

+

No files or folders found in this location.

+
+
+ )} + + )} +
); }; diff --git a/src/app/photos/page.tsx b/src/app/photos/page.tsx index c3800d1..f86688b 100644 --- a/src/app/photos/page.tsx +++ b/src/app/photos/page.tsx @@ -1,8 +1,10 @@ 'use client'; import { useEffect, useState } from 'react'; +import Link from 'next/link'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; +import { Image as ImageIcon } from 'lucide-react'; interface Photo { id: number; @@ -43,43 +45,82 @@ export default function PhotosPage() { if (loading) { return ( -
-
Loading photos...
+
+
+
+ +
+

Loading photos...

+
); } return ( -
-

Photos

-
- {photos.map((photo) => ( - - - {photo.title} { - (e.target as HTMLImageElement).src = '/placeholder-image.jpg'; - }} - /> - - - {photo.title} - - Size: {formatFileSize(photo.size)} - Path: {photo.path} - - - - ))} -
- {photos.length === 0 && ( -
-

No photos found. Add media libraries to scan for photos.

+
+
+
+

+ Photos +

+

+ {photos.length} {photos.length === 1 ? 'photo' : 'photos'} found +

- )} + + {photos.length > 0 ? ( +
+ {photos.map((photo) => ( +
+
+ {photo.title} { + (e.target as HTMLImageElement).src = '/placeholder-image.jpg'; + }} + /> +
+
+
+ +
+
+
+
+

+ {photo.title} +

+

+ {formatFileSize(photo.size)} +

+
+
+ ))} +
+ ) : ( +
+
+
+ +
+

+ No Photos Found +

+

+ Add media libraries to scan for photos +

+ + + +
+
+ )} +
); } \ No newline at end of file diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 3a980cb..fa22ecb 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -2,6 +2,7 @@ "use client"; import { useState, useEffect } from "react"; +import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; @@ -111,120 +112,181 @@ const SettingsPage = () => { }; return ( -
-
- - {error && ( - - {error} - - )} +
+
+
+

+ Settings +

+

+ Configure your media libraries and system preferences +

+
- {scanStatus && ( - - {scanStatus} - - )} +
+
+
+
+
+ +
+
+

Media Libraries

+

Manage your media source directories

+
+
-
- - - - - Manage Media Libraries - - - Add or remove media library paths to scan for videos and photos - - - -
- { - setNewLibraryPath(e.target.value); - setError(null); - }} - onKeyPress={(e) => e.key === 'Enter' && addLibrary()} - className="flex-1" - /> - -
+
+
+ { + setNewLibraryPath(e.target.value); + setError(null); + }} + onKeyPress={(e) => e.key === 'Enter' && addLibrary()} + className="flex-1 px-4 py-3 bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" + /> + +
- {libraries.length > 0 ? ( -
-

Existing Libraries ({libraries.length})

- {libraries.map((lib) => ( -
-
- - {lib.path} -
- + {error && ( +
+

{error}

- ))} -
- ) : ( -
- -

No media libraries configured yet.

-

Add your first library above to get started.

-
- )} - - + )} - - - Media Scanner - - Scan your libraries for new videos and photos - - - - -

- {libraries.length === 0 - ? "Add at least one library to enable scanning" - : "This will scan all configured libraries for new media files"} -

-
-
+ {libraries.length > 0 && ( +
+

+ {libraries.length} {libraries.length === 1 ? 'Library' : 'Libraries'} +

+
+ {libraries.map((lib) => ( +
+
+
+ +
+
+

{lib.path}

+

Ready to scan

+
+
+ +
+ ))} +
+
+ )} - - - System Information - - -
- Libraries: - {libraries.length} + {libraries.length === 0 && ( +
+
+ +
+

No libraries configured

+

Add your first library to get started

+
+ )} +
-
- Database: - SQLite + +
+
+
+ + + +
+
+

Media Scanner

+

Discover new media files

+
+
+ +
+ + + {scanStatus && ( +
+

+ {scanStatus} +

+
+ )} + +

+ {libraries.length === 0 + ? "Add at least one library to enable scanning" + : "Scan will discover new videos and photos"} +

+
- - +
+ +
+
+

System Status

+
+
+ Libraries + {libraries.length} +
+
+ Database + SQLite +
+
+ Status + Active +
+
+
+ +
+

Quick Actions

+
+ + View Videos + + + View Photos + + + Browse Files + +
+
+
+
); diff --git a/src/app/videos/page.tsx b/src/app/videos/page.tsx index b5f294f..ee0e925 100644 --- a/src/app/videos/page.tsx +++ b/src/app/videos/page.tsx @@ -2,14 +2,8 @@ "use client"; import { useState, useEffect } from "react"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Header } from "@/components/ui/header"; +import Link from "next/link"; +import { Film } from "lucide-react"; interface Video { id: number; @@ -49,43 +43,82 @@ const VideosPage = () => { if (loading) { return ( -
-
Loading videos...
+
+
+
+ +
+

Loading videos...

+
); } return ( -
-
-
- {videos.map((video) => ( - - - {video.title} { - (e.target as HTMLImageElement).src = '/placeholder.svg'; - }} - /> - - - {video.title} - - Size: {formatFileSize(video.size)} - Path: {video.path} - - - - ))} -
- {videos.length === 0 && ( -
-

No videos found. Add media libraries to scan for videos.

+
+
+
+

+ Videos +

+

+ {videos.length} {videos.length === 1 ? 'video' : 'videos'} found +

- )} + + {videos.length > 0 ? ( +
+ {videos.map((video) => ( +
+
+ {video.title} { + (e.target as HTMLImageElement).src = '/placeholder.svg'; + }} + /> +
+
+
+ +
+
+
+
+

+ {video.title} +

+

+ {formatFileSize(video.size)} +

+
+
+ ))} +
+ ) : ( +
+
+
+ +
+

+ No Videos Found +

+

+ Add media libraries to scan for videos +

+ + + +
+
+ )} +
); }; diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index 5fef9b1..86ec5b2 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -11,24 +11,32 @@ import { Video, Image, Folder, + Film, + Image as ImageIcon, } from "lucide-react"; import Link from "next/link"; -import { usePathname } from "next/navigation"; +import { usePathname, useSearchParams } from "next/navigation"; import { cn } from "@/lib/utils"; +import { Suspense } from "react"; -const Sidebar = () => { +const SidebarContent = () => { const [isCollapsed, setIsCollapsed] = useState(false); const [libraries, setLibraries] = useState<{ id: number; path: string }[]>([]); const pathname = usePathname(); + const searchParams = useSearchParams(); useEffect(() => { fetchLibraries(); }, []); const fetchLibraries = async () => { - const res = await fetch("/api/libraries"); - const data = await res.json(); - setLibraries(data); + try { + const res = await fetch("/api/libraries"); + const data = await res.json(); + setLibraries(data); + } catch (error) { + console.error('Error fetching libraries:', error); + } }; const toggleSidebar = () => { @@ -37,76 +45,117 @@ const Sidebar = () => { const navItems = [ { href: "/", label: "Home", icon: Home }, + { href: "/videos", label: "Videos", icon: Film }, + { href: "/photos", label: "Photos", icon: ImageIcon }, { href: "/settings", label: "Settings", icon: Settings }, - { href: "/videos", label: "Videos", icon: Video }, - { href: "/photos", label: "Photos", icon: Image }, ]; return (
-
+ {/* Header */} +
{!isCollapsed && ( -

NextAV

+
+
+
+
+

NextAV

+
)} -
-
+ )} + + {/* Footer */} +
+
+ {!isCollapsed ? "NextAV v1.0" : "v1.0"} +
+
); }; +const Sidebar = () => { + return ( + }> + + + ); +}; + export default Sidebar;